summaryrefslogtreecommitdiffstats
path: root/drivers/usb/serial
diff options
context:
space:
mode:
authorJoel Fernandes <joelagnelf@nvidia.com>2026-07-18 13:15:36 -0400
committerPaul E. McKenney <paulmck@kernel.org>2026-07-23 10:39:57 -0700
commit34ff636f322c6d5e4f0a52338fa63a7dff09a495 (patch)
tree7629759c1ec7148b95bc8ab3080df20a5ecd2273 /drivers/usb/serial
downloadlinux-stable-34ff636f322c6d5e4f0a52338fa63a7dff09a495.tar.gz
linux-stable-34ff636f322c6d5e4f0a52338fa63a7dff09a495.zip
scftorture: Count single_rpc offline failures in statistics outputgrafted
scf_torture_stats_print() aggregates each invoker thread's counters into a local scf_statistics structure before printing, but the n_single_rpc_ofl field is missing from the aggregation loop. As a result, the "single_rpc_ofl" value printed in the statistics line is always zero, even when smp_call_function_single() invocations for the RPC test have failed due to offline CPUs and been counted by the invoker threads. Add the missing accumulation so that the printed value reflects the actual counts. Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com> Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Diffstat (limited to 'drivers/usb/serial')
-rw-r--r--drivers/usb/serial/Kconfig656
-rw-r--r--drivers/usb/serial/Makefile65
-rw-r--r--drivers/usb/serial/Makefile-keyspan_pda_fw17
-rw-r--r--drivers/usb/serial/aircable.c160
-rw-r--r--drivers/usb/serial/ark3116.c733
-rw-r--r--drivers/usb/serial/belkin_sa.c481
-rw-r--r--drivers/usb/serial/belkin_sa.h120
-rw-r--r--drivers/usb/serial/bus.c171
-rw-r--r--drivers/usb/serial/ch341.c893
-rw-r--r--drivers/usb/serial/console.c304
-rw-r--r--drivers/usb/serial/cp210x.c2201
-rw-r--r--drivers/usb/serial/cyberjack.c420
-rw-r--r--drivers/usb/serial/cypress_m8.c1222
-rw-r--r--drivers/usb/serial/cypress_m8.h82
-rw-r--r--drivers/usb/serial/digi_acceleport.c1573
-rw-r--r--drivers/usb/serial/empeg.c122
-rw-r--r--drivers/usb/serial/ezusb_convert.pl51
-rw-r--r--drivers/usb/serial/f81232.c1077
-rw-r--r--drivers/usb/serial/f81534.c1574
-rw-r--r--drivers/usb/serial/ftdi_sio.c2876
-rw-r--r--drivers/usb/serial/ftdi_sio.h579
-rw-r--r--drivers/usb/serial/ftdi_sio_ids.h1638
-rw-r--r--drivers/usb/serial/garmin_gps.c1442
-rw-r--r--drivers/usb/serial/generic.c656
-rw-r--r--drivers/usb/serial/io_16654.h192
-rw-r--r--drivers/usb/serial/io_edgeport.c3131
-rw-r--r--drivers/usb/serial/io_edgeport.h61
-rw-r--r--drivers/usb/serial/io_ionsp.h451
-rw-r--r--drivers/usb/serial/io_ti.c2769
-rw-r--r--drivers/usb/serial/io_ti.h181
-rw-r--r--drivers/usb/serial/io_usbvend.h682
-rw-r--r--drivers/usb/serial/ipaq.c604
-rw-r--r--drivers/usb/serial/ipw.c310
-rw-r--r--drivers/usb/serial/ir-usb.c485
-rw-r--r--drivers/usb/serial/iuu_phoenix.c1205
-rw-r--r--drivers/usb/serial/iuu_phoenix.h117
-rw-r--r--drivers/usb/serial/keyspan.c3105
-rw-r--r--drivers/usb/serial/keyspan_pda.c720
-rw-r--r--drivers/usb/serial/keyspan_usa26msg.h261
-rw-r--r--drivers/usb/serial/keyspan_usa28msg.h202
-rw-r--r--drivers/usb/serial/keyspan_usa49msg.h283
-rw-r--r--drivers/usb/serial/keyspan_usa67msg.h255
-rw-r--r--drivers/usb/serial/keyspan_usa90msg.h199
-rw-r--r--drivers/usb/serial/kl5kusb105.c512
-rw-r--r--drivers/usb/serial/kl5kusb105.h66
-rw-r--r--drivers/usb/serial/kobil_sct.c511
-rw-r--r--drivers/usb/serial/kobil_sct.h78
-rw-r--r--drivers/usb/serial/mct_u232.c780
-rw-r--r--drivers/usb/serial/mct_u232.h463
-rw-r--r--drivers/usb/serial/metro-usb.c368
-rw-r--r--drivers/usb/serial/mos7720.c1764
-rw-r--r--drivers/usb/serial/mos7840.c1805
-rw-r--r--drivers/usb/serial/mxuport.c1318
-rw-r--r--drivers/usb/serial/navman.c115
-rw-r--r--drivers/usb/serial/omninet.c177
-rw-r--r--drivers/usb/serial/opticon.c403
-rw-r--r--drivers/usb/serial/option.c2729
-rw-r--r--drivers/usb/serial/oti6858.c839
-rw-r--r--drivers/usb/serial/oti6858.h11
-rw-r--r--drivers/usb/serial/pl2303.c1307
-rw-r--r--drivers/usb/serial/pl2303.h177
-rw-r--r--drivers/usb/serial/qcaux.c87
-rw-r--r--drivers/usb/serial/qcserial.c490
-rw-r--r--drivers/usb/serial/quatech2.c955
-rw-r--r--drivers/usb/serial/safe_serial.c309
-rw-r--r--drivers/usb/serial/sierra.c1058
-rw-r--r--drivers/usb/serial/spcp8x5.c478
-rw-r--r--drivers/usb/serial/ssu100.c526
-rw-r--r--drivers/usb/serial/symbolserial.c191
-rw-r--r--drivers/usb/serial/ti_usb_3410_5052.c1659
-rw-r--r--drivers/usb/serial/upd78f0730.c434
-rw-r--r--drivers/usb/serial/usb-serial-simple.c173
-rw-r--r--drivers/usb/serial/usb-serial.c1550
-rw-r--r--drivers/usb/serial/usb-wwan.h64
-rw-r--r--drivers/usb/serial/usb_debug.c113
-rw-r--r--drivers/usb/serial/usb_wwan.c657
-rw-r--r--drivers/usb/serial/visor.c573
-rw-r--r--drivers/usb/serial/visor.h157
-rw-r--r--drivers/usb/serial/whiteheat.c802
-rw-r--r--drivers/usb/serial/whiteheat.h298
-rw-r--r--drivers/usb/serial/wishbone-serial.c88
-rw-r--r--drivers/usb/serial/xr_serial.c1111
-rw-r--r--drivers/usb/serial/xsens_mt.c67
83 files changed, 59589 insertions, 0 deletions
diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig
new file mode 100644
index 000000000..ef8d1c73c
--- /dev/null
+++ b/drivers/usb/serial/Kconfig
@@ -0,0 +1,656 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# USB Serial device configuration
+#
+
+menuconfig USB_SERIAL
+ tristate "USB Serial Converter support"
+ depends on TTY
+ help
+ Say Y here if you have a USB device that provides normal serial
+ ports, or acts like a serial device, and you want to connect it to
+ your USB bus.
+
+ Please read <file:Documentation/usb/usb-serial.rst> for more
+ information on the specifics of the different devices that are
+ supported, and on how to use them.
+
+ To compile this driver as a module, choose M here: the
+ module will be called usbserial.
+
+if USB_SERIAL
+
+config USB_SERIAL_CONSOLE
+ bool "USB Serial Console device support"
+ depends on USB_SERIAL=y
+ help
+ If you say Y here, it will be possible to use a USB to serial
+ converter port as the system console (the system console is the
+ device which receives all kernel messages and warnings and which
+ allows logins in single user mode). This could be useful if some
+ terminal or printer is connected to that serial port.
+
+ Even if you say Y here, the currently visible virtual console
+ (/dev/tty0) will still be used as the system console by default, but
+ you can alter that using a kernel command line option such as
+ "console=ttyUSB0". (Try "man bootparam" or see the documentation of
+ your boot loader (lilo or loadlin) about how to pass options to the
+ kernel at boot time.)
+
+ If you don't have a VGA card installed and you say Y here, the
+ kernel will automatically use the first USB to serial converter
+ port, /dev/ttyUSB0, as system console.
+
+ If unsure, say N.
+
+config USB_SERIAL_GENERIC
+ bool "USB Generic Serial Driver"
+ help
+ Say Y here if you want to use the generic USB serial driver. Please
+ read <file:Documentation/usb/usb-serial.rst> for more information on
+ using this driver. It is recommended that the "USB Serial converter
+ support" be compiled as a module for this driver to be used
+ properly.
+
+config USB_SERIAL_SIMPLE
+ tristate "USB Serial Simple Driver"
+ help
+ Say Y here to use the USB serial "simple" driver. This driver
+ handles a wide range of very simple devices, all in one
+ driver. Specifically, it supports:
+ - Suunto ANT+ USB device.
+ - Medtronic CareLink USB device
+ - Fundamental Software dongle.
+ - Google USB serial devices
+ - HP4x calculators
+ - Libtransistor USB console
+ - a number of Motorola phones
+ - Motorola Tetra devices
+ - Nokia mobile phones
+ - Novatel Wireless GPS receivers
+ - Siemens USB/MPI adapter.
+ - ViVOtech ViVOpay USB device.
+ - Infineon Modem Flashloader USB interface
+ - ZIO Motherboard USB serial interface
+
+ To compile this driver as a module, choose M here: the module
+ will be called usb-serial-simple.
+
+config USB_SERIAL_AIRCABLE
+ tristate "USB AIRcable Bluetooth Dongle Driver"
+ help
+ Say Y here if you want to use USB AIRcable Bluetooth Dongle.
+
+ To compile this driver as a module, choose M here: the module
+ will be called aircable.
+
+config USB_SERIAL_ARK3116
+ tristate "USB ARK Micro 3116 USB Serial Driver"
+ help
+ Say Y here if you want to use a ARK Micro 3116 USB to Serial
+ device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ark3116
+
+config USB_SERIAL_BELKIN
+ tristate "USB Belkin and Peracom Single Port Serial Driver"
+ help
+ Say Y here if you want to use a Belkin USB Serial single port
+ adaptor (F5U103 is one of the model numbers) or the Peracom single
+ port USB to serial adapter.
+
+ To compile this driver as a module, choose M here: the
+ module will be called belkin_sa.
+
+config USB_SERIAL_CH341
+ tristate "USB Winchiphead CH341 Single Port Serial Driver"
+ help
+ Say Y here if you want to use a Winchiphead CH341 single port
+ USB to serial adapter.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ch341.
+
+config USB_SERIAL_WHITEHEAT
+ tristate "USB ConnectTech WhiteHEAT Serial Driver"
+ select USB_EZUSB_FX2
+ help
+ Say Y here if you want to use a ConnectTech WhiteHEAT 4 port
+ USB to serial converter device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called whiteheat.
+
+config USB_SERIAL_DIGI_ACCELEPORT
+ tristate "USB Digi International AccelePort USB Serial Driver"
+ help
+ Say Y here if you want to use Digi AccelePort USB 2 or 4 devices,
+ 2 port (plus parallel port) and 4 port USB serial converters. The
+ parallel port on the USB 2 appears as a third serial port on Linux.
+ The Digi Acceleport USB 8 is not yet supported by this driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called digi_acceleport.
+
+config USB_SERIAL_CP210X
+ tristate "USB CP210x family of UART Bridge Controllers"
+ help
+ Say Y here if you want to use a CP2101/CP2102/CP2103 based USB
+ to RS232 converters.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cp210x.
+
+config USB_SERIAL_CYPRESS_M8
+ tristate "USB Cypress M8 USB Serial Driver"
+ help
+ Say Y here if you want to use a device that contains the Cypress
+ USB to Serial microcontroller, such as the DeLorme Earthmate GPS.
+
+ Attempted SMP support... send bug reports!
+
+ Supported microcontrollers in the CY4601 family are:
+ CY7C63741 CY7C63742 CY7C63743 CY7C64013
+
+ To compile this driver as a module, choose M here: the
+ module will be called cypress_m8.
+
+config USB_SERIAL_EMPEG
+ tristate "USB Empeg empeg-car Mark I/II Driver"
+ help
+ Say Y here if you want to connect to your Empeg empeg-car Mark I/II
+ mp3 player via USB. The driver uses a single ttyUSB{0,1,2,...}
+ device node. See <file:Documentation/usb/usb-serial.rst> for more
+ tidbits of information.
+
+ To compile this driver as a module, choose M here: the
+ module will be called empeg.
+
+config USB_SERIAL_FTDI_SIO
+ tristate "USB FTDI Single Port Serial Driver"
+ help
+ Say Y here if you want to use a FTDI SIO single port USB to serial
+ converter device. The implementation I have is called the USC-1000.
+ This driver has also been tested with the 245 and 232 devices.
+
+ See <http://ftdi-usb-sio.sourceforge.net/> for more
+ information on this driver and the device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ftdi_sio.
+
+config USB_SERIAL_VISOR
+ tristate "USB Handspring Visor / Palm m50x / Sony Clie Driver"
+ help
+ Say Y here if you want to connect to your HandSpring Visor, Palm
+ m500 or m505 through its USB docking station. See
+ <http://usbvisor.sourceforge.net/index.php3> for more information on using this
+ driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called visor.
+
+config USB_SERIAL_IPAQ
+ tristate "USB PocketPC PDA Driver"
+ help
+ Say Y here if you want to connect to your Compaq iPAQ, HP Jornada
+ or any other PDA running Windows CE 3.0 or PocketPC 2002
+ using a USB cradle/cable. For information on using the driver,
+ read <file:Documentation/usb/usb-serial.rst>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ipaq.
+
+config USB_SERIAL_IR
+ tristate "USB IR Dongle Serial Driver"
+ help
+ Say Y here if you want to enable simple serial support for USB IrDA
+ devices. This is useful if you do not want to use the full IrDA
+ stack.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ir-usb.
+
+config USB_SERIAL_EDGEPORT
+ tristate "USB Inside Out Edgeport Serial Driver"
+ help
+ Say Y here if you want to use any of the following devices from
+ Inside Out Networks (Digi):
+ Edgeport/4
+ Rapidport/4
+ Edgeport/4t
+ Edgeport/2
+ Edgeport/4i
+ Edgeport/2i
+ Edgeport/421
+ Edgeport/21
+ Edgeport/8
+ Edgeport/8 Dual
+ Edgeport/2D8
+ Edgeport/4D8
+ Edgeport/8i
+ Edgeport/2 DIN
+ Edgeport/4 DIN
+ Edgeport/16 Dual
+
+ To compile this driver as a module, choose M here: the
+ module will be called io_edgeport.
+
+config USB_SERIAL_EDGEPORT_TI
+ tristate "USB Inside Out Edgeport Serial Driver (TI devices)"
+ help
+ Say Y here if you want to use any of the devices from Inside Out
+ Networks (Digi) that are not supported by the io_edgeport driver.
+ This includes the Edgeport/1 device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called io_ti.
+
+config USB_SERIAL_F81232
+ tristate "USB Fintek F81232 Single Port Serial Driver"
+ help
+ Say Y here if you want to use the Fintek F81232 single
+ port usb to serial adapter.
+
+ To compile this driver as a module, choose M here: the
+ module will be called f81232.
+
+config USB_SERIAL_F8153X
+ tristate "USB Fintek F81532/534 Multi-Ports Serial Driver"
+ help
+ Say Y here if you want to use the Fintek F81532/534 Multi-Ports
+ USB to serial adapter.
+
+ To compile this driver as a module, choose M here: the
+ module will be called f81534.
+
+
+config USB_SERIAL_GARMIN
+ tristate "USB Garmin GPS driver"
+ help
+ Say Y here if you want to connect to your Garmin GPS.
+ Should work with most Garmin GPS devices which have a native USB port.
+
+ See <http://sourceforge.net/projects/garmin-gps> for the latest
+ version of the driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called garmin_gps.
+
+config USB_SERIAL_IPW
+ tristate "USB IPWireless (3G UMTS TDD) Driver"
+ select USB_SERIAL_WWAN
+ help
+ Say Y here if you want to use a IPWireless USB modem such as
+ the ones supplied by Axity3G/Sentech South Africa.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ipw.
+
+config USB_SERIAL_IUU
+ tristate "USB Infinity USB Unlimited Phoenix Driver"
+ help
+ Say Y here if you want to use a IUU in phoenix mode and get
+ an extra ttyUSBx device. More information available on
+ http://eczema.ecze.com/iuu_phoenix.html
+
+ To compile this driver as a module, choose M here: the
+ module will be called iuu_phoenix.o
+
+config USB_SERIAL_KEYSPAN_PDA
+ tristate "USB Keyspan PDA / Xircom Single Port Serial Driver"
+ select USB_EZUSB_FX2
+ help
+ Say Y here if you want to use a Keyspan PDA, Xircom or Entrega single
+ port USB to serial converter device. This driver makes use of
+ firmware developed from scratch by Brian Warner.
+
+ To compile this driver as a module, choose M here: the
+ module will be called keyspan_pda.
+
+config USB_SERIAL_KEYSPAN
+ tristate "USB Keyspan USA-xxx Serial Driver"
+ select USB_EZUSB_FX2
+ help
+ Say Y here if you want to use Keyspan USB to serial converter
+ devices. This driver makes use of Keyspan's official firmware
+ and was developed with their support. You must also include
+ firmware to support your particular device(s).
+
+ See <http://blemings.org/hugh/keyspan.html> for more information.
+
+ To compile this driver as a module, choose M here: the
+ module will be called keyspan.
+
+config USB_SERIAL_KLSI
+ tristate "USB KL5KUSB105 (Palmconnect) Driver"
+ help
+ Say Y here if you want to use a KL5KUSB105 - based single port
+ serial adapter. The most widely known -- and currently the only
+ tested -- device in this category is the PalmConnect USB Serial
+ adapter sold by Palm Inc. for use with their Palm III and Palm V
+ series PDAs.
+
+ Please read <file:Documentation/usb/usb-serial.rst> for more
+ information.
+
+ To compile this driver as a module, choose M here: the
+ module will be called kl5kusb105.
+
+config USB_SERIAL_KOBIL_SCT
+ tristate "USB KOBIL chipcard reader"
+ help
+ Say Y here if you want to use one of the following KOBIL USB chipcard
+ readers:
+
+ - USB TWIN
+ - KAAN Standard Plus
+ - KAAN SIM
+ - SecOVID Reader Plus
+ - B1 Professional
+ - KAAN Professional
+
+ Note that you need a current CT-API.
+ To compile this driver as a module, choose M here: the
+ module will be called kobil_sct.
+
+config USB_SERIAL_MCT_U232
+ tristate "USB MCT Single Port Serial Driver"
+ help
+ Say Y here if you want to use a USB Serial single port adapter from
+ Magic Control Technology Corp. (U232 is one of the model numbers).
+
+ This driver also works with Sitecom U232-P25 and D-Link DU-H3SP USB
+ BAY, Belkin F5U109, and Belkin F5U409 devices.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mct_u232.
+
+config USB_SERIAL_METRO
+ tristate "USB Metrologic Instruments USB-POS Barcode Scanner Driver"
+ help
+ Say Y here if you want to use a USB POS Metrologic barcode scanner.
+
+ To compile this driver as a module, choose M here: the
+ module will be called metro-usb.
+
+config USB_SERIAL_MOS7720
+ tristate "USB Moschip 7720 Serial Driver"
+ help
+ Say Y here if you want to use USB Serial single and double
+ port adapters from Moschip Semiconductor Tech.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mos7720.
+
+config USB_SERIAL_MOS7715_PARPORT
+ bool "Support for parallel port on the Moschip 7715"
+ depends on USB_SERIAL_MOS7720
+ depends on PARPORT=y || PARPORT=USB_SERIAL_MOS7720
+ select PARPORT_NOT_PC
+ help
+ Say Y if you have a Moschip 7715 device and would like to use
+ the parallel port it provides. The port will register with
+ the parport subsystem as a low-level driver.
+
+config USB_SERIAL_MOS7840
+ tristate "USB Moschip 7840/7820 USB Serial Driver"
+ help
+ Say Y here if you want to use a MCS7840 Quad-Serial or MCS7820
+ Dual-Serial port device from MosChip Semiconductor.
+
+ The MCS7840 and MCS7820 have been developed to connect a wide range
+ of standard serial devices to a USB host. The MCS7840 has a USB
+ device controller connected to four (4) individual UARTs while the
+ MCS7820 controller connects to two (2) individual UARTs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mos7840. If unsure, choose N.
+
+config USB_SERIAL_MXUPORT
+ tristate "USB Moxa UPORT Serial Driver"
+ help
+ Say Y here if you want to use a MOXA UPort Serial hub.
+
+ This driver supports:
+
+ [2 Port]
+ - UPort 1250 : 2 Port RS-232/422/485 USB to Serial Hub
+ - UPort 1250I : 2 Port RS-232/422/485 USB to Serial Hub with
+ Isolation
+
+ [4 Port]
+ - UPort 1410 : 4 Port RS-232 USB to Serial Hub
+ - UPort 1450 : 4 Port RS-232/422/485 USB to Serial Hub
+ - UPort 1450I : 4 Port RS-232/422/485 USB to Serial Hub with
+ Isolation
+
+ [8 Port]
+ - UPort 1610-8 : 8 Port RS-232 USB to Serial Hub
+ - UPort 1650-8 : 8 Port RS-232/422/485 USB to Serial Hub
+
+ [16 Port]
+ - UPort 1610-16 : 16 Port RS-232 USB to Serial Hub
+ - UPort 1650-16 : 16 Port RS-232/422/485 USB to Serial Hub
+
+ To compile this driver as a module, choose M here: the
+ module will be called mxuport.
+
+config USB_SERIAL_NAVMAN
+ tristate "USB Navman GPS device"
+ help
+ To compile this driver as a module, choose M here: the
+ module will be called navman.
+
+config USB_SERIAL_PL2303
+ tristate "USB Prolific 2303 Single Port Serial Driver"
+ help
+ Say Y here if you want to use the PL2303 USB Serial single port
+ adapter from Prolific.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pl2303.
+
+config USB_SERIAL_OTI6858
+ tristate "USB Ours Technology Inc. OTi-6858 USB To RS232 Bridge Controller"
+ help
+ Say Y here if you want to use the OTi-6858 single port USB to serial
+ converter device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called oti6858.
+
+config USB_SERIAL_QCAUX
+ tristate "USB Qualcomm Auxiliary Serial Port Driver"
+ help
+ Say Y here if you want to use the auxiliary serial ports provided
+ by many modems based on Qualcomm chipsets. These ports often use
+ a proprietary protocol called DM and cannot be used for AT- or
+ PPP-based communication.
+
+ To compile this driver as a module, choose M here: the
+ module will be called qcaux. If unsure, choose N.
+
+config USB_SERIAL_QUALCOMM
+ tristate "USB Qualcomm Serial modem"
+ select USB_SERIAL_WWAN
+ help
+ Say Y here if you have a Qualcomm USB modem device. These are
+ usually wireless cellular modems.
+
+ To compile this driver as a module, choose M here: the
+ module will be called qcserial.
+
+config USB_SERIAL_SPCP8X5
+ tristate "USB SPCP8x5 USB To Serial Driver"
+ help
+ Say Y here if you want to use the spcp8x5 converter chip. This is
+ commonly found in some Z-Wave USB devices.
+
+ To compile this driver as a module, choose M here: the
+ module will be called spcp8x5.
+
+config USB_SERIAL_SAFE
+ tristate "USB Safe Serial (Encapsulated) Driver"
+
+config USB_SERIAL_SAFE_PADDED
+ bool "USB Secure Encapsulated Driver - Padded"
+ depends on USB_SERIAL_SAFE
+
+config USB_SERIAL_SIERRAWIRELESS
+ tristate "USB Sierra Wireless Driver"
+ help
+ Say M here if you want to use Sierra Wireless devices.
+
+ Many devices have a feature known as TRU-Install. For those devices
+ to work properly, the USB Storage Sierra feature must be enabled.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sierra.
+
+config USB_SERIAL_SYMBOL
+ tristate "USB Symbol Barcode driver (serial mode)"
+ help
+ Say Y here if you want to use a Symbol USB Barcode device
+ in serial emulation mode.
+
+ To compile this driver as a module, choose M here: the
+ module will be called symbolserial.
+
+config USB_SERIAL_TI
+ tristate "USB TI 3410/5052 Serial Driver"
+ help
+ Say Y here if you want to use the TI USB 3410 or 5052
+ serial devices.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ti_usb_3410_5052.
+
+config USB_SERIAL_CYBERJACK
+ tristate "USB REINER SCT cyberJack pinpad/e-com chipcard reader"
+ help
+ Say Y here if you want to use a cyberJack pinpad/e-com USB chipcard
+ reader. This is an interface to ISO 7816 compatible contact-based
+ chipcards, e.g. GSM SIMs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cyberjack.
+
+ If unsure, say N.
+
+config USB_SERIAL_WWAN
+ tristate
+
+config USB_SERIAL_OPTION
+ tristate "USB driver for GSM and CDMA modems"
+ select USB_SERIAL_WWAN
+ help
+ Say Y here if you have a GSM or CDMA modem that's connected to USB.
+
+ This driver also supports several PCMCIA cards which have a
+ built-in OHCI-USB adapter and an internally-connected GSM modem.
+ The USB bus on these cards is not accessible externally.
+
+ Supported devices include (some of?) those made by:
+ Option, Huawei, Audiovox, Novatel Wireless, or Anydata.
+
+ To compile this driver as a module, choose M here: the
+ module will be called option.
+
+ If this driver doesn't recognize your device,
+ it might be accessible via the FTDI_SIO driver.
+
+config USB_SERIAL_OMNINET
+ tristate "USB ZyXEL omni.net LCD Plus Driver"
+ help
+ Say Y here if you want to use a ZyXEL omni.net LCD ISDN TA.
+
+ To compile this driver as a module, choose M here: the
+ module will be called omninet.
+
+config USB_SERIAL_OPTICON
+ tristate "USB Opticon Barcode driver (serial mode)"
+ help
+ Say Y here if you want to use a Opticon USB Barcode device
+ in serial emulation mode.
+
+ To compile this driver as a module, choose M here: the
+ module will be called opticon.
+
+config USB_SERIAL_XSENS_MT
+ tristate "Xsens motion tracker serial interface driver"
+ help
+ Say Y here if you want to use Xsens motion trackers.
+
+ This driver supports the new generation of motion trackers
+ by Xsens. Older devices can be accessed using the FTDI_SIO
+ driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called xsens_mt.
+
+config USB_SERIAL_WISHBONE
+ tristate "USB-Wishbone adapter interface driver"
+ help
+ Say Y here if you want to use a USB attached Wishbone bus.
+
+ Wishbone is an open hardware SoC bus commonly used in FPGA
+ designs. Bus access can be serialized using the Etherbone
+ protocol <http://www.ohwr.org/projects/etherbone-core>.
+
+ This driver is intended to be used with devices which attach
+ their internal Wishbone bus to a USB serial interface using
+ the Etherbone protocol. A userspace library is required to
+ speak the protocol made available by this driver as ttyUSBx.
+
+ To compile this driver as a module, choose M here: the
+ module will be called wishbone-serial.
+
+config USB_SERIAL_SSU100
+ tristate "USB Quatech SSU-100 Single Port Serial Driver"
+ help
+ Say Y here if you want to use the Quatech SSU-100 single
+ port usb to serial adapter.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ssu100.
+
+config USB_SERIAL_QT2
+ tristate "USB Quatech Serial Driver for USB 2 devices"
+ help
+ Say Y here if you want to use the Quatech USB 2
+ serial adapters.
+
+ To compile this driver as a module, choose M here: the
+ module will be called quatech-serial.
+
+config USB_SERIAL_UPD78F0730
+ tristate "USB Renesas uPD78F0730 Single Port Serial Driver"
+ help
+ Say Y here if you want to use the Renesas uPD78F0730
+ serial driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called upd78f0730.
+
+config USB_SERIAL_XR
+ tristate "USB MaxLinear/Exar USB to Serial driver"
+ help
+ Say Y here if you want to use MaxLinear/Exar USB to Serial converter
+ devices.
+
+ To compile this driver as a module, choose M here: the
+ module will be called xr_serial.
+
+config USB_SERIAL_DEBUG
+ tristate "USB Debugging Device"
+ help
+ Say Y here if you have a USB debugging device used to receive
+ debugging data from another machine. The most common of these
+ devices is the NetChip TurboCONNECT device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called usb-debug.
+
+endif # USB_SERIAL
diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile
new file mode 100644
index 000000000..c7bb1a881
--- /dev/null
+++ b/drivers/usb/serial/Makefile
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the USB serial device drivers.
+#
+
+# Object file lists.
+
+obj-$(CONFIG_USB_SERIAL) += usbserial.o
+
+usbserial-y := usb-serial.o generic.o bus.o
+
+usbserial-$(CONFIG_USB_SERIAL_CONSOLE) += console.o
+
+obj-$(CONFIG_USB_SERIAL_AIRCABLE) += aircable.o
+obj-$(CONFIG_USB_SERIAL_ARK3116) += ark3116.o
+obj-$(CONFIG_USB_SERIAL_BELKIN) += belkin_sa.o
+obj-$(CONFIG_USB_SERIAL_CH341) += ch341.o
+obj-$(CONFIG_USB_SERIAL_CP210X) += cp210x.o
+obj-$(CONFIG_USB_SERIAL_CYBERJACK) += cyberjack.o
+obj-$(CONFIG_USB_SERIAL_CYPRESS_M8) += cypress_m8.o
+obj-$(CONFIG_USB_SERIAL_DEBUG) += usb_debug.o
+obj-$(CONFIG_USB_SERIAL_DIGI_ACCELEPORT) += digi_acceleport.o
+obj-$(CONFIG_USB_SERIAL_EDGEPORT) += io_edgeport.o
+obj-$(CONFIG_USB_SERIAL_EDGEPORT_TI) += io_ti.o
+obj-$(CONFIG_USB_SERIAL_EMPEG) += empeg.o
+obj-$(CONFIG_USB_SERIAL_F81232) += f81232.o
+obj-$(CONFIG_USB_SERIAL_F8153X) += f81534.o
+obj-$(CONFIG_USB_SERIAL_FTDI_SIO) += ftdi_sio.o
+obj-$(CONFIG_USB_SERIAL_GARMIN) += garmin_gps.o
+obj-$(CONFIG_USB_SERIAL_IPAQ) += ipaq.o
+obj-$(CONFIG_USB_SERIAL_IPW) += ipw.o
+obj-$(CONFIG_USB_SERIAL_IR) += ir-usb.o
+obj-$(CONFIG_USB_SERIAL_IUU) += iuu_phoenix.o
+obj-$(CONFIG_USB_SERIAL_KEYSPAN) += keyspan.o
+obj-$(CONFIG_USB_SERIAL_KEYSPAN_PDA) += keyspan_pda.o
+obj-$(CONFIG_USB_SERIAL_KLSI) += kl5kusb105.o
+obj-$(CONFIG_USB_SERIAL_KOBIL_SCT) += kobil_sct.o
+obj-$(CONFIG_USB_SERIAL_MCT_U232) += mct_u232.o
+obj-$(CONFIG_USB_SERIAL_METRO) += metro-usb.o
+obj-$(CONFIG_USB_SERIAL_MOS7720) += mos7720.o
+obj-$(CONFIG_USB_SERIAL_MOS7840) += mos7840.o
+obj-$(CONFIG_USB_SERIAL_MXUPORT) += mxuport.o
+obj-$(CONFIG_USB_SERIAL_NAVMAN) += navman.o
+obj-$(CONFIG_USB_SERIAL_OMNINET) += omninet.o
+obj-$(CONFIG_USB_SERIAL_OPTICON) += opticon.o
+obj-$(CONFIG_USB_SERIAL_OPTION) += option.o
+obj-$(CONFIG_USB_SERIAL_OTI6858) += oti6858.o
+obj-$(CONFIG_USB_SERIAL_PL2303) += pl2303.o
+obj-$(CONFIG_USB_SERIAL_QCAUX) += qcaux.o
+obj-$(CONFIG_USB_SERIAL_QUALCOMM) += qcserial.o
+obj-$(CONFIG_USB_SERIAL_QT2) += quatech2.o
+obj-$(CONFIG_USB_SERIAL_SAFE) += safe_serial.o
+obj-$(CONFIG_USB_SERIAL_SIERRAWIRELESS) += sierra.o
+obj-$(CONFIG_USB_SERIAL_SIMPLE) += usb-serial-simple.o
+obj-$(CONFIG_USB_SERIAL_SPCP8X5) += spcp8x5.o
+obj-$(CONFIG_USB_SERIAL_SSU100) += ssu100.o
+obj-$(CONFIG_USB_SERIAL_SYMBOL) += symbolserial.o
+obj-$(CONFIG_USB_SERIAL_WWAN) += usb_wwan.o
+obj-$(CONFIG_USB_SERIAL_TI) += ti_usb_3410_5052.o
+obj-$(CONFIG_USB_SERIAL_UPD78F0730) += upd78f0730.o
+obj-$(CONFIG_USB_SERIAL_VISOR) += visor.o
+obj-$(CONFIG_USB_SERIAL_WISHBONE) += wishbone-serial.o
+obj-$(CONFIG_USB_SERIAL_WHITEHEAT) += whiteheat.o
+obj-$(CONFIG_USB_SERIAL_XR) += xr_serial.o
+obj-$(CONFIG_USB_SERIAL_XSENS_MT) += xsens_mt.o
diff --git a/drivers/usb/serial/Makefile-keyspan_pda_fw b/drivers/usb/serial/Makefile-keyspan_pda_fw
new file mode 100644
index 000000000..503b472d8
--- /dev/null
+++ b/drivers/usb/serial/Makefile-keyspan_pda_fw
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# some rules to handle the quirks of the 'as31' assembler, like
+# insisting upon fixed suffixes for the input and output files,
+# and its lack of preprocessor support
+
+all: keyspan_pda_fw.h
+
+%.asm: %.S
+ gcc -x assembler-with-cpp -P -E -o $@ $<
+
+%.hex: %.asm
+ as31 -l $<
+ mv $*.obj $@
+
+%_fw.h: %.hex ezusb_convert.pl
+ perl ezusb_convert.pl $* < $< > $@
diff --git a/drivers/usb/serial/aircable.c b/drivers/usb/serial/aircable.c
new file mode 100644
index 000000000..2a76f1f0e
--- /dev/null
+++ b/drivers/usb/serial/aircable.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AIRcable USB Bluetooth Dongle Driver.
+ *
+ * Copyright (C) 2010 Johan Hovold <jhovold@gmail.com>
+ * Copyright (C) 2006 Manuel Francisco Naranjo (naranjo.manuel@gmail.com)
+ *
+ * The device works as an standard CDC device, it has 2 interfaces, the first
+ * one is for firmware access and the second is the serial one.
+ * The protocol is very simply, there are two possibilities reading or writing.
+ * When writing the first urb must have a Header that starts with 0x20 0x29 the
+ * next two bytes must say how much data will be sent.
+ * When reading the process is almost equal except that the header starts with
+ * 0x00 0x20.
+ *
+ * The device simply need some stuff to understand data coming from the usb
+ * buffer: The First and Second byte is used for a Header, the Third and Fourth
+ * tells the device the amount of information the package holds.
+ * Packages are 60 bytes long Header Stuff.
+ * When writing to the device the first two bytes of the header are 0x20 0x29
+ * When reading the bytes are 0x00 0x20, or 0x00 0x10, there is an strange
+ * situation, when too much data arrives to the device because it sends the data
+ * but with out the header. I will use a simply hack to override this situation,
+ * if there is data coming that does not contain any header, then that is data
+ * that must go directly to the tty, as there is no documentation about if there
+ * is any other control code, I will simply check for the first
+ * one.
+ *
+ * I have taken some info from a Greg Kroah-Hartman article:
+ * http://www.linuxjournal.com/article/6573
+ * And from Linux Device Driver Kit CD, which is a great work, the authors taken
+ * the work to recompile lots of information an knowledge in drivers development
+ * and made it all available inside a cd.
+ * URL: http://kernel.org/pub/linux/kernel/people/gregkh/ddk/
+ *
+ */
+
+#include <linux/unaligned.h>
+#include <linux/tty.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/tty_flip.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+/* Vendor and Product ID */
+#define AIRCABLE_VID 0x16CA
+#define AIRCABLE_USB_PID 0x1502
+
+/* Protocol Stuff */
+#define HCI_HEADER_LENGTH 0x4
+#define TX_HEADER_0 0x20
+#define TX_HEADER_1 0x29
+#define RX_HEADER_0 0x00
+#define RX_HEADER_1 0x20
+#define HCI_COMPLETE_FRAME 64
+
+/* rx_flags */
+#define THROTTLED 0x01
+#define ACTUALLY_THROTTLED 0x02
+
+#define DRIVER_AUTHOR "Naranjo, Manuel Francisco <naranjo.manuel@gmail.com>, Johan Hovold <jhovold@gmail.com>"
+#define DRIVER_DESC "AIRcable USB Driver"
+
+/* ID table that will be registered with USB core */
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(AIRCABLE_VID, AIRCABLE_USB_PID) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static int aircable_prepare_write_buffer(struct usb_serial_port *port,
+ void *dest, size_t size)
+{
+ int count;
+ unsigned char *buf = dest;
+
+ count = kfifo_out_locked(&port->write_fifo, buf + HCI_HEADER_LENGTH,
+ size - HCI_HEADER_LENGTH, &port->lock);
+ buf[0] = TX_HEADER_0;
+ buf[1] = TX_HEADER_1;
+ put_unaligned_le16(count, &buf[2]);
+
+ return count + HCI_HEADER_LENGTH;
+}
+
+static int aircable_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ /* Ignore the first interface, which has no bulk endpoints. */
+ if (epds->num_bulk_out == 0) {
+ dev_dbg(&serial->interface->dev,
+ "ignoring interface with no bulk-out endpoints\n");
+ return -ENODEV;
+ }
+
+ return 1;
+}
+
+static int aircable_process_packet(struct usb_serial_port *port,
+ int has_headers, char *packet, int len)
+{
+ if (has_headers) {
+ len -= HCI_HEADER_LENGTH;
+ packet += HCI_HEADER_LENGTH;
+ }
+ if (len <= 0) {
+ dev_dbg(&port->dev, "%s - malformed packet\n", __func__);
+ return 0;
+ }
+
+ tty_insert_flip_string(&port->port, packet, len);
+
+ return len;
+}
+
+static void aircable_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ char *data = urb->transfer_buffer;
+ int has_headers;
+ int count;
+ int len;
+ int i;
+
+ has_headers = (urb->actual_length > 2 && data[0] == RX_HEADER_0);
+
+ count = 0;
+ for (i = 0; i < urb->actual_length; i += HCI_COMPLETE_FRAME) {
+ len = min_t(int, urb->actual_length - i, HCI_COMPLETE_FRAME);
+ count += aircable_process_packet(port, has_headers,
+ &data[i], len);
+ }
+
+ if (count)
+ tty_flip_buffer_push(&port->port);
+}
+
+static struct usb_serial_driver aircable_device = {
+ .driver = {
+ .name = "aircable",
+ },
+ .id_table = id_table,
+ .bulk_out_size = HCI_COMPLETE_FRAME,
+ .calc_num_ports = aircable_calc_num_ports,
+ .process_read_urb = aircable_process_read_urb,
+ .prepare_write_buffer = aircable_prepare_write_buffer,
+ .throttle = usb_serial_generic_throttle,
+ .unthrottle = usb_serial_generic_unthrottle,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &aircable_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/ark3116.c b/drivers/usb/serial/ark3116.c
new file mode 100644
index 000000000..3a574b6ee
--- /dev/null
+++ b/drivers/usb/serial/ark3116.c
@@ -0,0 +1,733 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2009 by Bart Hartgers (bart.hartgers+ark3116@gmail.com)
+ * Original version:
+ * Copyright (C) 2006
+ * Simon Schulz (ark3116_driver <at> auctionant.de)
+ *
+ * ark3116
+ * - implements a driver for the arkmicro ark3116 chipset (vendor=0x6547,
+ * productid=0x0232) (used in a datacable called KQ-U8A)
+ *
+ * Supports full modem status lines, break, hardware flow control. Does not
+ * support software flow control, since I do not know how to enable it in hw.
+ *
+ * This driver is a essentially new implementation. I initially dug
+ * into the old ark3116.c driver and suddenly realized the ark3116 is
+ * a 16450 with a USB interface glued to it. See comments at the
+ * bottom of this file.
+ */
+
+#include <linux/kernel.h>
+#include <linux/ioctl.h>
+#include <linux/tty.h>
+#include <linux/slab.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+
+#define DRIVER_AUTHOR "Bart Hartgers <bart.hartgers+ark3116@gmail.com>"
+#define DRIVER_DESC "USB ARK3116 serial/IrDA driver"
+#define DRIVER_DEV_DESC "ARK3116 RS232/IrDA"
+#define DRIVER_NAME "ark3116"
+
+/* usb timeout of 1 second */
+#define ARK_TIMEOUT 1000
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x6547, 0x0232) },
+ { USB_DEVICE(0x18ec, 0x3118) }, /* USB to IrDA adapter */
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static int is_irda(struct usb_serial *serial)
+{
+ struct usb_device *dev = serial->dev;
+ if (le16_to_cpu(dev->descriptor.idVendor) == 0x18ec &&
+ le16_to_cpu(dev->descriptor.idProduct) == 0x3118)
+ return 1;
+ return 0;
+}
+
+struct ark3116_private {
+ int irda; /* 1 for irda device */
+
+ /* protects hw register updates */
+ struct mutex hw_lock;
+
+ int quot; /* baudrate divisor */
+ __u32 lcr; /* line control register value */
+ __u32 hcr; /* handshake control register (0x8)
+ * value */
+ __u32 mcr; /* modem control register value */
+
+ /* protects the status values below */
+ spinlock_t status_lock;
+ __u32 msr; /* modem status register value */
+ __u32 lsr; /* line status register value */
+};
+
+static int ark3116_write_reg(struct usb_serial *serial,
+ unsigned reg, __u8 val)
+{
+ int result;
+ /* 0xfe 0x40 are magic values taken from original driver */
+ result = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ 0xfe, 0x40, val, reg,
+ NULL, 0, ARK_TIMEOUT);
+ if (result)
+ return result;
+
+ return 0;
+}
+
+static int ark3116_read_reg(struct usb_serial *serial,
+ unsigned reg, unsigned char *buf)
+{
+ int result;
+ /* 0xfe 0xc0 are magic values taken from original driver */
+ result = usb_control_msg(serial->dev,
+ usb_rcvctrlpipe(serial->dev, 0),
+ 0xfe, 0xc0, 0, reg,
+ buf, 1, ARK_TIMEOUT);
+ if (result < 1) {
+ dev_err(&serial->interface->dev,
+ "failed to read register %u: %d\n",
+ reg, result);
+ if (result >= 0)
+ result = -EIO;
+
+ return result;
+ }
+
+ return 0;
+}
+
+static inline int calc_divisor(int bps)
+{
+ /* Original ark3116 made some exceptions in rounding here
+ * because windows did the same. Assume that is not really
+ * necessary.
+ * Crystal is 12MHz, probably because of USB, but we divide by 4?
+ */
+ return (12000000 + 2*bps) / (4*bps);
+}
+
+static int ark3116_port_probe(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct ark3116_private *priv;
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ mutex_init(&priv->hw_lock);
+ spin_lock_init(&priv->status_lock);
+
+ priv->irda = is_irda(serial);
+
+ usb_set_serial_port_data(port, priv);
+
+ /* setup the hardware */
+ ark3116_write_reg(serial, UART_IER, 0);
+ /* disable DMA */
+ ark3116_write_reg(serial, UART_FCR, 0);
+ /* handshake control */
+ priv->hcr = 0;
+ ark3116_write_reg(serial, 0x8 , 0);
+ /* modem control */
+ priv->mcr = 0;
+ ark3116_write_reg(serial, UART_MCR, 0);
+
+ if (!(priv->irda)) {
+ ark3116_write_reg(serial, 0xb , 0);
+ } else {
+ ark3116_write_reg(serial, 0xb , 1);
+ ark3116_write_reg(serial, 0xc , 0);
+ ark3116_write_reg(serial, 0xd , 0x41);
+ ark3116_write_reg(serial, 0xa , 1);
+ }
+
+ /* setup baudrate */
+ ark3116_write_reg(serial, UART_LCR, UART_LCR_DLAB);
+
+ /* setup for 9600 8N1 */
+ priv->quot = calc_divisor(9600);
+ ark3116_write_reg(serial, UART_DLL, priv->quot & 0xff);
+ ark3116_write_reg(serial, UART_DLM, (priv->quot>>8) & 0xff);
+
+ priv->lcr = UART_LCR_WLEN8;
+ ark3116_write_reg(serial, UART_LCR, UART_LCR_WLEN8);
+
+ ark3116_write_reg(serial, 0xe, 0);
+
+ if (priv->irda)
+ ark3116_write_reg(serial, 0x9, 0);
+
+ dev_info(&port->dev, "using %s mode\n", priv->irda ? "IrDA" : "RS232");
+
+ return 0;
+}
+
+static void ark3116_port_remove(struct usb_serial_port *port)
+{
+ struct ark3116_private *priv = usb_get_serial_port_data(port);
+
+ /* device is closed, so URBs and DMA should be down */
+ mutex_destroy(&priv->hw_lock);
+ kfree(priv);
+}
+
+static void ark3116_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct usb_serial *serial = port->serial;
+ struct ark3116_private *priv = usb_get_serial_port_data(port);
+ struct ktermios *termios = &tty->termios;
+ unsigned int cflag = termios->c_cflag;
+ int bps = tty_get_baud_rate(tty);
+ int quot;
+ __u8 lcr, hcr, eval;
+
+ /* set data bit count */
+ lcr = UART_LCR_WLEN(tty_get_char_size(cflag));
+
+ if (cflag & CSTOPB)
+ lcr |= UART_LCR_STOP;
+ if (cflag & PARENB)
+ lcr |= UART_LCR_PARITY;
+ if (!(cflag & PARODD))
+ lcr |= UART_LCR_EPAR;
+ if (cflag & CMSPAR)
+ lcr |= UART_LCR_SPAR;
+
+ /* handshake control */
+ hcr = (cflag & CRTSCTS) ? 0x03 : 0x00;
+
+ /* calc baudrate */
+ dev_dbg(&port->dev, "%s - setting bps to %d\n", __func__, bps);
+ eval = 0;
+ switch (bps) {
+ case 0:
+ quot = calc_divisor(9600);
+ break;
+ default:
+ if ((bps < 75) || (bps > 3000000))
+ bps = 9600;
+ quot = calc_divisor(bps);
+ break;
+ case 460800:
+ eval = 1;
+ quot = calc_divisor(bps);
+ break;
+ case 921600:
+ eval = 2;
+ quot = calc_divisor(bps);
+ break;
+ }
+
+ /* Update state: synchronize */
+ mutex_lock(&priv->hw_lock);
+
+ /* keep old LCR_SBC bit */
+ lcr |= (priv->lcr & UART_LCR_SBC);
+
+ dev_dbg(&port->dev, "%s - setting hcr:0x%02x,lcr:0x%02x,quot:%d\n",
+ __func__, hcr, lcr, quot);
+
+ /* handshake control */
+ if (priv->hcr != hcr) {
+ priv->hcr = hcr;
+ ark3116_write_reg(serial, 0x8, hcr);
+ }
+
+ /* baudrate */
+ if (priv->quot != quot) {
+ priv->quot = quot;
+ priv->lcr = lcr; /* need to write lcr anyway */
+
+ /* disable DMA since transmit/receive is
+ * shadowed by UART_DLL
+ */
+ ark3116_write_reg(serial, UART_FCR, 0);
+
+ ark3116_write_reg(serial, UART_LCR,
+ lcr|UART_LCR_DLAB);
+ ark3116_write_reg(serial, UART_DLL, quot & 0xff);
+ ark3116_write_reg(serial, UART_DLM, (quot>>8) & 0xff);
+
+ /* restore lcr */
+ ark3116_write_reg(serial, UART_LCR, lcr);
+ /* magic baudrate thingy: not sure what it does,
+ * but windows does this as well.
+ */
+ ark3116_write_reg(serial, 0xe, eval);
+
+ /* enable DMA */
+ ark3116_write_reg(serial, UART_FCR, UART_FCR_DMA_SELECT);
+ } else if (priv->lcr != lcr) {
+ priv->lcr = lcr;
+ ark3116_write_reg(serial, UART_LCR, lcr);
+ }
+
+ mutex_unlock(&priv->hw_lock);
+
+ /* check for software flow control */
+ if (I_IXOFF(tty) || I_IXON(tty)) {
+ dev_warn(&port->dev,
+ "software flow control not implemented\n");
+ }
+
+ /* Don't rewrite B0 */
+ if (tty_termios_baud_rate(termios))
+ tty_termios_encode_baud_rate(termios, bps, bps);
+}
+
+static void ark3116_close(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+
+ /* disable DMA */
+ ark3116_write_reg(serial, UART_FCR, 0);
+
+ /* deactivate interrupts */
+ ark3116_write_reg(serial, UART_IER, 0);
+
+ usb_serial_generic_close(port);
+
+ usb_kill_urb(port->interrupt_in_urb);
+}
+
+static int ark3116_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct ark3116_private *priv = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ unsigned char *buf;
+ int result;
+
+ buf = kmalloc(1, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ result = usb_serial_generic_open(tty, port);
+ if (result) {
+ dev_dbg(&port->dev,
+ "%s - usb_serial_generic_open failed: %d\n",
+ __func__, result);
+ goto err_free;
+ }
+
+ /* remove any data still left: also clears error state */
+ ark3116_read_reg(serial, UART_RX, buf);
+
+ /* read modem status */
+ result = ark3116_read_reg(serial, UART_MSR, buf);
+ if (result)
+ goto err_close;
+ priv->msr = *buf;
+
+ /* read line status */
+ result = ark3116_read_reg(serial, UART_LSR, buf);
+ if (result)
+ goto err_close;
+ priv->lsr = *buf;
+
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (result) {
+ dev_err(&port->dev, "submit irq_in urb failed %d\n",
+ result);
+ goto err_close;
+ }
+
+ /* activate interrupts */
+ ark3116_write_reg(port->serial, UART_IER, UART_IER_MSI|UART_IER_RLSI);
+
+ /* enable DMA */
+ ark3116_write_reg(port->serial, UART_FCR, UART_FCR_DMA_SELECT);
+
+ /* setup termios */
+ if (tty)
+ ark3116_set_termios(tty, port, NULL);
+
+ kfree(buf);
+
+ return 0;
+
+err_close:
+ usb_serial_generic_close(port);
+err_free:
+ kfree(buf);
+
+ return result;
+}
+
+static int ark3116_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ark3116_private *priv = usb_get_serial_port_data(port);
+ __u32 status;
+ __u32 ctrl;
+ unsigned long flags;
+
+ mutex_lock(&priv->hw_lock);
+ ctrl = priv->mcr;
+ mutex_unlock(&priv->hw_lock);
+
+ spin_lock_irqsave(&priv->status_lock, flags);
+ status = priv->msr;
+ spin_unlock_irqrestore(&priv->status_lock, flags);
+
+ return (status & UART_MSR_DSR ? TIOCM_DSR : 0) |
+ (status & UART_MSR_CTS ? TIOCM_CTS : 0) |
+ (status & UART_MSR_RI ? TIOCM_RI : 0) |
+ (status & UART_MSR_DCD ? TIOCM_CD : 0) |
+ (ctrl & UART_MCR_DTR ? TIOCM_DTR : 0) |
+ (ctrl & UART_MCR_RTS ? TIOCM_RTS : 0) |
+ (ctrl & UART_MCR_OUT1 ? TIOCM_OUT1 : 0) |
+ (ctrl & UART_MCR_OUT2 ? TIOCM_OUT2 : 0);
+}
+
+static int ark3116_tiocmset(struct tty_struct *tty,
+ unsigned set, unsigned clr)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ark3116_private *priv = usb_get_serial_port_data(port);
+
+ /* we need to take the mutex here, to make sure that the value
+ * in priv->mcr is actually the one that is in the hardware
+ */
+
+ mutex_lock(&priv->hw_lock);
+
+ if (set & TIOCM_RTS)
+ priv->mcr |= UART_MCR_RTS;
+ if (set & TIOCM_DTR)
+ priv->mcr |= UART_MCR_DTR;
+ if (set & TIOCM_OUT1)
+ priv->mcr |= UART_MCR_OUT1;
+ if (set & TIOCM_OUT2)
+ priv->mcr |= UART_MCR_OUT2;
+ if (clr & TIOCM_RTS)
+ priv->mcr &= ~UART_MCR_RTS;
+ if (clr & TIOCM_DTR)
+ priv->mcr &= ~UART_MCR_DTR;
+ if (clr & TIOCM_OUT1)
+ priv->mcr &= ~UART_MCR_OUT1;
+ if (clr & TIOCM_OUT2)
+ priv->mcr &= ~UART_MCR_OUT2;
+
+ ark3116_write_reg(port->serial, UART_MCR, priv->mcr);
+
+ mutex_unlock(&priv->hw_lock);
+
+ return 0;
+}
+
+static int ark3116_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ark3116_private *priv = usb_get_serial_port_data(port);
+ int ret;
+
+ /* LCR is also used for other things: protect access */
+ mutex_lock(&priv->hw_lock);
+
+ if (break_state)
+ priv->lcr |= UART_LCR_SBC;
+ else
+ priv->lcr &= ~UART_LCR_SBC;
+
+ ret = ark3116_write_reg(port->serial, UART_LCR, priv->lcr);
+
+ mutex_unlock(&priv->hw_lock);
+
+ return ret;
+}
+
+static void ark3116_update_msr(struct usb_serial_port *port, __u8 msr)
+{
+ struct ark3116_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->status_lock, flags);
+ priv->msr = msr;
+ spin_unlock_irqrestore(&priv->status_lock, flags);
+
+ if (msr & UART_MSR_ANY_DELTA) {
+ /* update input line counters */
+ if (msr & UART_MSR_DCTS)
+ port->icount.cts++;
+ if (msr & UART_MSR_DDSR)
+ port->icount.dsr++;
+ if (msr & UART_MSR_DDCD)
+ port->icount.dcd++;
+ if (msr & UART_MSR_TERI)
+ port->icount.rng++;
+ wake_up_interruptible(&port->port.delta_msr_wait);
+ }
+}
+
+static void ark3116_update_lsr(struct usb_serial_port *port, __u8 lsr)
+{
+ struct ark3116_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->status_lock, flags);
+ /* combine bits */
+ priv->lsr |= lsr;
+ spin_unlock_irqrestore(&priv->status_lock, flags);
+
+ if (lsr&UART_LSR_BRK_ERROR_BITS) {
+ if (lsr & UART_LSR_BI)
+ port->icount.brk++;
+ if (lsr & UART_LSR_FE)
+ port->icount.frame++;
+ if (lsr & UART_LSR_PE)
+ port->icount.parity++;
+ if (lsr & UART_LSR_OE)
+ port->icount.overrun++;
+ }
+}
+
+static void ark3116_read_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ int status = urb->status;
+ const __u8 *data = urb->transfer_buffer;
+ int result;
+
+ switch (status) {
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ break;
+ case 0: /* success */
+ /* discovered this by trail and error... */
+ if ((urb->actual_length == 4) && (data[0] == 0xe8)) {
+ const __u8 id = data[1]&UART_IIR_ID;
+ dev_dbg(&port->dev, "%s: iir=%02x\n", __func__, data[1]);
+ if (id == UART_IIR_MSI) {
+ dev_dbg(&port->dev, "%s: msr=%02x\n",
+ __func__, data[3]);
+ ark3116_update_msr(port, data[3]);
+ break;
+ } else if (id == UART_IIR_RLSI) {
+ dev_dbg(&port->dev, "%s: lsr=%02x\n",
+ __func__, data[2]);
+ ark3116_update_lsr(port, data[2]);
+ break;
+ }
+ }
+ /*
+ * Not sure what this data meant...
+ */
+ usb_serial_debug_data(&port->dev, __func__,
+ urb->actual_length,
+ urb->transfer_buffer);
+ break;
+ }
+
+ result = usb_submit_urb(urb, GFP_ATOMIC);
+ if (result)
+ dev_err(&port->dev, "failed to resubmit interrupt urb: %d\n",
+ result);
+}
+
+
+/* Data comes in via the bulk (data) URB, errors/interrupts via the int URB.
+ * This means that we cannot be sure which data byte has an associated error
+ * condition, so we report an error for all data in the next bulk read.
+ *
+ * Actually, there might even be a window between the bulk data leaving the
+ * ark and reading/resetting the lsr in the read_bulk_callback where an
+ * interrupt for the next data block could come in.
+ * Without somekind of ordering on the ark, we would have to report the
+ * error for the next block of data as well...
+ * For now, let's pretend this can't happen.
+ */
+static void ark3116_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct ark3116_private *priv = usb_get_serial_port_data(port);
+ unsigned char *data = urb->transfer_buffer;
+ char tty_flag = TTY_NORMAL;
+ unsigned long flags;
+ __u32 lsr;
+
+ /* update line status */
+ spin_lock_irqsave(&priv->status_lock, flags);
+ lsr = priv->lsr;
+ priv->lsr &= ~UART_LSR_BRK_ERROR_BITS;
+ spin_unlock_irqrestore(&priv->status_lock, flags);
+
+ if (!urb->actual_length)
+ return;
+
+ if (lsr & UART_LSR_BRK_ERROR_BITS) {
+ if (lsr & UART_LSR_BI)
+ tty_flag = TTY_BREAK;
+ else if (lsr & UART_LSR_PE)
+ tty_flag = TTY_PARITY;
+ else if (lsr & UART_LSR_FE)
+ tty_flag = TTY_FRAME;
+
+ /* overrun is special, not associated with a char */
+ if (lsr & UART_LSR_OE)
+ tty_insert_flip_char(&port->port, 0, TTY_OVERRUN);
+ }
+ tty_insert_flip_string_fixed_flag(&port->port, data, tty_flag,
+ urb->actual_length);
+ tty_flip_buffer_push(&port->port);
+}
+
+static struct usb_serial_driver ark3116_device = {
+ .driver = {
+ .name = "ark3116",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ .num_interrupt_in = 1,
+ .port_probe = ark3116_port_probe,
+ .port_remove = ark3116_port_remove,
+ .set_termios = ark3116_set_termios,
+ .tiocmget = ark3116_tiocmget,
+ .tiocmset = ark3116_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .open = ark3116_open,
+ .close = ark3116_close,
+ .break_ctl = ark3116_break_ctl,
+ .read_int_callback = ark3116_read_int_callback,
+ .process_read_urb = ark3116_process_read_urb,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &ark3116_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_LICENSE("GPL");
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+
+/*
+ * The following describes what I learned from studying the old
+ * ark3116.c driver, disassembling the windows driver, and some lucky
+ * guesses. Since I do not have any datasheet or other
+ * documentation, inaccuracies are almost guaranteed.
+ *
+ * Some specs for the ARK3116 can be found here:
+ * http://web.archive.org/web/20060318000438/
+ * www.arkmicro.com/en/products/view.php?id=10
+ * On that page, 2 GPIO pins are mentioned: I assume these are the
+ * OUT1 and OUT2 pins of the UART, so I added support for those
+ * through the MCR. Since the pins are not available on my hardware,
+ * I could not verify this.
+ * Also, it states there is "on-chip hardware flow control". I have
+ * discovered how to enable that. Unfortunately, I do not know how to
+ * enable XON/XOFF (software) flow control, which would need support
+ * from the chip as well to work. Because of the wording on the web
+ * page there is a real possibility the chip simply does not support
+ * software flow control.
+ *
+ * I got my ark3116 as part of a mobile phone adapter cable. On the
+ * PCB, the following numbered contacts are present:
+ *
+ * 1:- +5V
+ * 2:o DTR
+ * 3:i RX
+ * 4:i DCD
+ * 5:o RTS
+ * 6:o TX
+ * 7:i RI
+ * 8:i DSR
+ * 10:- 0V
+ * 11:i CTS
+ *
+ * On my chip, all signals seem to be 3.3V, but 5V tolerant. But that
+ * may be different for the one you have ;-).
+ *
+ * The windows driver limits the registers to 0-F, so I assume there
+ * are actually 16 present on the device.
+ *
+ * On an UART interrupt, 4 bytes of data come in on the interrupt
+ * endpoint. The bytes are 0xe8 IIR LSR MSR.
+ *
+ * The baudrate seems to be generated from the 12MHz crystal, using
+ * 4-times subsampling. So quot=12e6/(4*baud). Also see description
+ * of register E.
+ *
+ * Registers 0-7:
+ * These seem to be the same as for a regular 16450. The FCR is set
+ * to UART_FCR_DMA_SELECT (0x8), I guess to enable transfers between
+ * the UART and the USB bridge/DMA engine.
+ *
+ * Register 8:
+ * By trial and error, I found out that bit 0 enables hardware CTS,
+ * stopping TX when CTS is +5V. Bit 1 does the same for RTS, making
+ * RTS +5V when the 3116 cannot transfer the data to the USB bus
+ * (verified by disabling the reading URB). Note that as far as I can
+ * tell, the windows driver does NOT use this, so there might be some
+ * hardware bug or something.
+ *
+ * According to a patch provided here
+ * https://lore.kernel.org/lkml/200907261419.50702.linux@rainbow-software.org
+ * the ARK3116 can also be used as an IrDA dongle. Since I do not have
+ * such a thing, I could not investigate that aspect. However, I can
+ * speculate ;-).
+ *
+ * - IrDA encodes data differently than RS232. Most likely, one of
+ * the bits in registers 9..E enables the IR ENDEC (encoder/decoder).
+ * - Depending on the IR transceiver, the input and output need to be
+ * inverted, so there are probably bits for that as well.
+ * - IrDA is half-duplex, so there should be a bit for selecting that.
+ *
+ * This still leaves at least two registers unaccounted for. Perhaps
+ * The chip can do XON/XOFF or CRC in HW?
+ *
+ * Register 9:
+ * Set to 0x00 for IrDA, when the baudrate is initialised.
+ *
+ * Register A:
+ * Set to 0x01 for IrDA, at init.
+ *
+ * Register B:
+ * Set to 0x01 for IrDA, 0x00 for RS232, at init.
+ *
+ * Register C:
+ * Set to 00 for IrDA, at init.
+ *
+ * Register D:
+ * Set to 0x41 for IrDA, at init.
+ *
+ * Register E:
+ * Somekind of baudrate override. The windows driver seems to set
+ * this to 0x00 for normal baudrates, 0x01 for 460800, 0x02 for 921600.
+ * Since 460800 and 921600 cannot be obtained by dividing 3MHz by an integer,
+ * it could be somekind of subdivisor thingy.
+ * However,it does not seem to do anything: selecting 921600 (divisor 3,
+ * reg E=2), still gets 1 MHz. I also checked if registers 9, C or F would
+ * work, but they don't.
+ *
+ * Register F: unknown
+ */
diff --git a/drivers/usb/serial/belkin_sa.c b/drivers/usb/serial/belkin_sa.c
new file mode 100644
index 000000000..cfff29263
--- /dev/null
+++ b/drivers/usb/serial/belkin_sa.c
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Belkin USB Serial Adapter Driver
+ *
+ * Copyright (C) 2000 William Greathouse (wgreathouse@smva.com)
+ * Copyright (C) 2000-2001 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2010 Johan Hovold (jhovold@gmail.com)
+ *
+ * This program is largely derived from work by the linux-usb group
+ * and associated source files. Please see the usb/serial files for
+ * individual credits and copyrights.
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ *
+ * TODO:
+ * -- Add true modem control line query capability. Currently we track the
+ * states reported by the interrupt and the states we request.
+ * -- Add support for flush commands
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include "belkin_sa.h"
+
+#define DRIVER_AUTHOR "William Greathouse <wgreathouse@smva.com>"
+#define DRIVER_DESC "USB Belkin Serial converter driver"
+
+/* function prototypes for a Belkin USB Serial Adapter F5U103 */
+static int belkin_sa_port_probe(struct usb_serial_port *port);
+static void belkin_sa_port_remove(struct usb_serial_port *port);
+static int belkin_sa_open(struct tty_struct *tty,
+ struct usb_serial_port *port);
+static void belkin_sa_close(struct usb_serial_port *port);
+static void belkin_sa_read_int_callback(struct urb *urb);
+static void belkin_sa_process_read_urb(struct urb *urb);
+static void belkin_sa_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+static int belkin_sa_break_ctl(struct tty_struct *tty, int break_state);
+static int belkin_sa_tiocmget(struct tty_struct *tty);
+static int belkin_sa_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear);
+
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(BELKIN_SA_VID, BELKIN_SA_PID) },
+ { USB_DEVICE(BELKIN_OLD_VID, BELKIN_OLD_PID) },
+ { USB_DEVICE(PERACOM_VID, PERACOM_PID) },
+ { USB_DEVICE(GOHUBS_VID, GOHUBS_PID) },
+ { USB_DEVICE(GOHUBS_VID, HANDYLINK_PID) },
+ { USB_DEVICE(BELKIN_DOCKSTATION_VID, BELKIN_DOCKSTATION_PID) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+/* All of the device info needed for the serial converters */
+static struct usb_serial_driver belkin_device = {
+ .driver = {
+ .name = "belkin",
+ },
+ .description = "Belkin / Peracom / GoHubs USB Serial Adapter",
+ .id_table = id_table,
+ .num_ports = 1,
+ .open = belkin_sa_open,
+ .close = belkin_sa_close,
+ .read_int_callback = belkin_sa_read_int_callback,
+ .process_read_urb = belkin_sa_process_read_urb,
+ .set_termios = belkin_sa_set_termios,
+ .break_ctl = belkin_sa_break_ctl,
+ .tiocmget = belkin_sa_tiocmget,
+ .tiocmset = belkin_sa_tiocmset,
+ .port_probe = belkin_sa_port_probe,
+ .port_remove = belkin_sa_port_remove,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &belkin_device, NULL
+};
+
+struct belkin_sa_private {
+ spinlock_t lock;
+ unsigned long control_state;
+ unsigned char last_lsr;
+ unsigned char last_msr;
+ int bad_flow_control;
+};
+
+
+/*
+ * ***************************************************************************
+ * Belkin USB Serial Adapter F5U103 specific driver functions
+ * ***************************************************************************
+ */
+
+#define WDR_TIMEOUT 5000 /* default urb timeout */
+
+/* assumes that struct usb_serial *serial is available */
+#define BSA_USB_CMD(c, v) usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), \
+ (c), BELKIN_SA_SET_REQUEST_TYPE, \
+ (v), 0, NULL, 0, WDR_TIMEOUT)
+
+static int belkin_sa_port_probe(struct usb_serial_port *port)
+{
+ struct usb_device *dev = port->serial->dev;
+ struct belkin_sa_private *priv;
+
+ priv = kmalloc_obj(struct belkin_sa_private);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->lock);
+ priv->control_state = 0;
+ priv->last_lsr = 0;
+ priv->last_msr = 0;
+ /* see comments at top of file */
+ priv->bad_flow_control =
+ (le16_to_cpu(dev->descriptor.bcdDevice) <= 0x0206) ? 1 : 0;
+ dev_info(&dev->dev, "bcdDevice: %04x, bfc: %d\n",
+ le16_to_cpu(dev->descriptor.bcdDevice),
+ priv->bad_flow_control);
+
+ usb_set_serial_port_data(port, priv);
+
+ return 0;
+}
+
+static void belkin_sa_port_remove(struct usb_serial_port *port)
+{
+ struct belkin_sa_private *priv;
+
+ priv = usb_get_serial_port_data(port);
+ kfree(priv);
+}
+
+static int belkin_sa_open(struct tty_struct *tty,
+ struct usb_serial_port *port)
+{
+ int retval;
+
+ retval = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (retval) {
+ dev_err(&port->dev, "usb_submit_urb(read int) failed\n");
+ return retval;
+ }
+
+ retval = usb_serial_generic_open(tty, port);
+ if (retval)
+ usb_kill_urb(port->interrupt_in_urb);
+
+ return retval;
+}
+
+static void belkin_sa_close(struct usb_serial_port *port)
+{
+ usb_serial_generic_close(port);
+ usb_kill_urb(port->interrupt_in_urb);
+}
+
+static void belkin_sa_read_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct belkin_sa_private *priv;
+ unsigned char *data = urb->transfer_buffer;
+ int retval;
+ int status = urb->status;
+ unsigned long flags;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ goto exit;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
+
+ if (urb->actual_length < BELKIN_SA_MSR_INDEX + 1)
+ goto exit;
+
+ /* Handle known interrupt data */
+ /* ignore data[0] and data[1] */
+
+ priv = usb_get_serial_port_data(port);
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->last_msr = data[BELKIN_SA_MSR_INDEX];
+
+ /* Record Control Line states */
+ if (priv->last_msr & BELKIN_SA_MSR_DSR)
+ priv->control_state |= TIOCM_DSR;
+ else
+ priv->control_state &= ~TIOCM_DSR;
+
+ if (priv->last_msr & BELKIN_SA_MSR_CTS)
+ priv->control_state |= TIOCM_CTS;
+ else
+ priv->control_state &= ~TIOCM_CTS;
+
+ if (priv->last_msr & BELKIN_SA_MSR_RI)
+ priv->control_state |= TIOCM_RI;
+ else
+ priv->control_state &= ~TIOCM_RI;
+
+ if (priv->last_msr & BELKIN_SA_MSR_CD)
+ priv->control_state |= TIOCM_CD;
+ else
+ priv->control_state &= ~TIOCM_CD;
+
+ priv->last_lsr = data[BELKIN_SA_LSR_INDEX];
+ spin_unlock_irqrestore(&priv->lock, flags);
+exit:
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(&port->dev, "%s - usb_submit_urb failed with "
+ "result %d\n", __func__, retval);
+}
+
+static void belkin_sa_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct belkin_sa_private *priv = usb_get_serial_port_data(port);
+ unsigned char *data = urb->transfer_buffer;
+ unsigned long flags;
+ unsigned char status;
+ char tty_flag;
+
+ /* Update line status */
+ tty_flag = TTY_NORMAL;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ status = priv->last_lsr;
+ priv->last_lsr &= ~BELKIN_SA_LSR_ERR;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (!urb->actual_length)
+ return;
+
+ if (status & BELKIN_SA_LSR_ERR) {
+ /* Break takes precedence over parity, which takes precedence
+ * over framing errors. */
+ if (status & BELKIN_SA_LSR_BI)
+ tty_flag = TTY_BREAK;
+ else if (status & BELKIN_SA_LSR_PE)
+ tty_flag = TTY_PARITY;
+ else if (status & BELKIN_SA_LSR_FE)
+ tty_flag = TTY_FRAME;
+ dev_dbg(&port->dev, "tty_flag = %d\n", tty_flag);
+
+ /* Overrun is special, not associated with a char. */
+ if (status & BELKIN_SA_LSR_OE)
+ tty_insert_flip_char(&port->port, 0, TTY_OVERRUN);
+ }
+
+ tty_insert_flip_string_fixed_flag(&port->port, data, tty_flag,
+ urb->actual_length);
+ tty_flip_buffer_push(&port->port);
+}
+
+static void belkin_sa_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct usb_serial *serial = port->serial;
+ struct belkin_sa_private *priv = usb_get_serial_port_data(port);
+ unsigned int iflag;
+ unsigned int cflag;
+ unsigned int old_iflag = 0;
+ unsigned int old_cflag = 0;
+ __u16 urb_value = 0; /* Will hold the new flags */
+ unsigned long flags;
+ unsigned long control_state;
+ int bad_flow_control;
+ speed_t baud;
+ struct ktermios *termios = &tty->termios;
+
+ iflag = termios->c_iflag;
+ cflag = termios->c_cflag;
+
+ termios->c_cflag &= ~CMSPAR;
+
+ /* get a local copy of the current port settings */
+ spin_lock_irqsave(&priv->lock, flags);
+ control_state = priv->control_state;
+ bad_flow_control = priv->bad_flow_control;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ old_iflag = old_termios->c_iflag;
+ old_cflag = old_termios->c_cflag;
+
+ /* Set the baud rate */
+ if ((cflag & CBAUD) != (old_cflag & CBAUD)) {
+ /* reassert DTR and (maybe) RTS on transition from B0 */
+ if ((old_cflag & CBAUD) == B0) {
+ control_state |= (TIOCM_DTR|TIOCM_RTS);
+ if (BSA_USB_CMD(BELKIN_SA_SET_DTR_REQUEST, 1) < 0)
+ dev_err(&port->dev, "Set DTR error\n");
+ /* don't set RTS if using hardware flow control */
+ if (!(old_cflag & CRTSCTS))
+ if (BSA_USB_CMD(BELKIN_SA_SET_RTS_REQUEST
+ , 1) < 0)
+ dev_err(&port->dev, "Set RTS error\n");
+ }
+ }
+
+ baud = tty_get_baud_rate(tty);
+ if (baud) {
+ urb_value = BELKIN_SA_BAUD(baud);
+ /* Clip to maximum speed */
+ if (urb_value == 0)
+ urb_value = 1;
+ /* Turn it back into a resulting real baud rate */
+ baud = BELKIN_SA_BAUD(urb_value);
+
+ /* Report the actual baud rate back to the caller */
+ tty_encode_baud_rate(tty, baud, baud);
+ if (BSA_USB_CMD(BELKIN_SA_SET_BAUDRATE_REQUEST, urb_value) < 0)
+ dev_err(&port->dev, "Set baudrate error\n");
+ } else {
+ /* Disable flow control */
+ if (BSA_USB_CMD(BELKIN_SA_SET_FLOW_CTRL_REQUEST,
+ BELKIN_SA_FLOW_NONE) < 0)
+ dev_err(&port->dev, "Disable flowcontrol error\n");
+ /* Drop RTS and DTR */
+ control_state &= ~(TIOCM_DTR | TIOCM_RTS);
+ if (BSA_USB_CMD(BELKIN_SA_SET_DTR_REQUEST, 0) < 0)
+ dev_err(&port->dev, "DTR LOW error\n");
+ if (BSA_USB_CMD(BELKIN_SA_SET_RTS_REQUEST, 0) < 0)
+ dev_err(&port->dev, "RTS LOW error\n");
+ }
+
+ /* set the parity */
+ if ((cflag ^ old_cflag) & (PARENB | PARODD)) {
+ if (cflag & PARENB)
+ urb_value = (cflag & PARODD) ? BELKIN_SA_PARITY_ODD
+ : BELKIN_SA_PARITY_EVEN;
+ else
+ urb_value = BELKIN_SA_PARITY_NONE;
+ if (BSA_USB_CMD(BELKIN_SA_SET_PARITY_REQUEST, urb_value) < 0)
+ dev_err(&port->dev, "Set parity error\n");
+ }
+
+ /* set the number of data bits */
+ if ((cflag & CSIZE) != (old_cflag & CSIZE)) {
+ urb_value = BELKIN_SA_DATA_BITS(tty_get_char_size(cflag));
+ if (BSA_USB_CMD(BELKIN_SA_SET_DATA_BITS_REQUEST, urb_value) < 0)
+ dev_err(&port->dev, "Set data bits error\n");
+ }
+
+ /* set the number of stop bits */
+ if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) {
+ urb_value = (cflag & CSTOPB) ? BELKIN_SA_STOP_BITS(2)
+ : BELKIN_SA_STOP_BITS(1);
+ if (BSA_USB_CMD(BELKIN_SA_SET_STOP_BITS_REQUEST,
+ urb_value) < 0)
+ dev_err(&port->dev, "Set stop bits error\n");
+ }
+
+ /* Set flow control */
+ if (((iflag ^ old_iflag) & (IXOFF | IXON)) ||
+ ((cflag ^ old_cflag) & CRTSCTS)) {
+ urb_value = 0;
+ if ((iflag & IXOFF) || (iflag & IXON))
+ urb_value |= (BELKIN_SA_FLOW_OXON | BELKIN_SA_FLOW_IXON);
+ else
+ urb_value &= ~(BELKIN_SA_FLOW_OXON | BELKIN_SA_FLOW_IXON);
+
+ if (cflag & CRTSCTS)
+ urb_value |= (BELKIN_SA_FLOW_OCTS | BELKIN_SA_FLOW_IRTS);
+ else
+ urb_value &= ~(BELKIN_SA_FLOW_OCTS | BELKIN_SA_FLOW_IRTS);
+
+ if (bad_flow_control)
+ urb_value &= ~(BELKIN_SA_FLOW_IRTS);
+
+ if (BSA_USB_CMD(BELKIN_SA_SET_FLOW_CTRL_REQUEST, urb_value) < 0)
+ dev_err(&port->dev, "Set flow control error\n");
+ }
+
+ /* save off the modified port settings */
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->control_state = control_state;
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static int belkin_sa_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_serial *serial = port->serial;
+ int ret;
+
+ ret = BSA_USB_CMD(BELKIN_SA_SET_BREAK_REQUEST, break_state ? 1 : 0);
+ if (ret < 0) {
+ dev_err(&port->dev, "Set break_ctl %d\n", break_state);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int belkin_sa_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct belkin_sa_private *priv = usb_get_serial_port_data(port);
+ unsigned long control_state;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ control_state = priv->control_state;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return control_state;
+}
+
+static int belkin_sa_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_serial *serial = port->serial;
+ struct belkin_sa_private *priv = usb_get_serial_port_data(port);
+ unsigned long control_state;
+ unsigned long flags;
+ int retval = 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ control_state = priv->control_state;
+
+ if (set & TIOCM_RTS)
+ control_state |= TIOCM_RTS;
+ if (set & TIOCM_DTR)
+ control_state |= TIOCM_DTR;
+ if (clear & TIOCM_RTS)
+ control_state &= ~TIOCM_RTS;
+ if (clear & TIOCM_DTR)
+ control_state &= ~TIOCM_DTR;
+
+ priv->control_state = control_state;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if ((set | clear) & TIOCM_RTS) {
+ retval = BSA_USB_CMD(BELKIN_SA_SET_RTS_REQUEST,
+ !!(control_state & TIOCM_RTS));
+ if (retval < 0) {
+ dev_err(&port->dev, "Set RTS error %d\n", retval);
+ goto exit;
+ }
+ }
+
+ if ((set | clear) & TIOCM_DTR) {
+ retval = BSA_USB_CMD(BELKIN_SA_SET_DTR_REQUEST,
+ !!(control_state & TIOCM_DTR));
+ if (retval < 0) {
+ dev_err(&port->dev, "Set DTR error %d\n", retval);
+ goto exit;
+ }
+ }
+exit:
+ return retval;
+}
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/belkin_sa.h b/drivers/usb/serial/belkin_sa.h
new file mode 100644
index 000000000..89ec30c63
--- /dev/null
+++ b/drivers/usb/serial/belkin_sa.h
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Definitions for Belkin USB Serial Adapter Driver
+ *
+ * Copyright (C) 2000
+ * William Greathouse (wgreathouse@smva.com)
+ *
+ * This program is largely derived from work by the linux-usb group
+ * and associated source files. Please see the usb/serial files for
+ * individual credits and copyrights.
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ *
+ * 12-Mar-2001 gkh
+ * Added GoHubs GO-COM232 device id.
+ *
+ * 06-Nov-2000 gkh
+ * Added old Belkin and Peracom device ids, which this driver supports
+ *
+ * 12-Oct-2000 William Greathouse
+ * First cut at supporting Belkin USB Serial Adapter F5U103
+ * I did not have a copy of the original work to support this
+ * adapter, so pardon any stupid mistakes. All of the information
+ * I am using to write this driver was acquired by using a modified
+ * UsbSnoop on Windows2000.
+ *
+ */
+
+#ifndef __LINUX_USB_SERIAL_BSA_H
+#define __LINUX_USB_SERIAL_BSA_H
+
+#define BELKIN_DOCKSTATION_VID 0x050d /* Vendor Id */
+#define BELKIN_DOCKSTATION_PID 0x1203 /* Product Id */
+
+#define BELKIN_SA_VID 0x050d /* Vendor Id */
+#define BELKIN_SA_PID 0x0103 /* Product Id */
+
+#define BELKIN_OLD_VID 0x056c /* Belkin's "old" vendor id */
+#define BELKIN_OLD_PID 0x8007 /* Belkin's "old" single port serial converter's id */
+
+#define PERACOM_VID 0x0565 /* Peracom's vendor id */
+#define PERACOM_PID 0x0001 /* Peracom's single port serial converter's id */
+
+#define GOHUBS_VID 0x0921 /* GoHubs vendor id */
+#define GOHUBS_PID 0x1000 /* GoHubs single port serial converter's id (identical to the Peracom device) */
+#define HANDYLINK_PID 0x1200 /* HandyLink USB's id (identical to the Peracom device) */
+
+/* Vendor Request Interface */
+#define BELKIN_SA_SET_BAUDRATE_REQUEST 0 /* Set baud rate */
+#define BELKIN_SA_SET_STOP_BITS_REQUEST 1 /* Set stop bits (1,2) */
+#define BELKIN_SA_SET_DATA_BITS_REQUEST 2 /* Set data bits (5,6,7,8) */
+#define BELKIN_SA_SET_PARITY_REQUEST 3 /* Set parity (None, Even, Odd) */
+
+#define BELKIN_SA_SET_DTR_REQUEST 10 /* Set DTR state */
+#define BELKIN_SA_SET_RTS_REQUEST 11 /* Set RTS state */
+#define BELKIN_SA_SET_BREAK_REQUEST 12 /* Set BREAK state */
+
+#define BELKIN_SA_SET_FLOW_CTRL_REQUEST 16 /* Set flow control mode */
+
+
+#ifdef WHEN_I_LEARN_THIS
+#define BELKIN_SA_SET_MAGIC_REQUEST 17 /* I don't know, possibly flush */
+ /* (always in Wininit sequence before flow control) */
+#define BELKIN_SA_RESET xx /* Reset the port */
+#define BELKIN_SA_GET_MODEM_STATUS xx /* Force return of modem status register */
+#endif
+
+#define BELKIN_SA_SET_REQUEST_TYPE 0x40
+
+#define BELKIN_SA_BAUD(b) (230400/b)
+
+#define BELKIN_SA_STOP_BITS(b) (b-1)
+
+#define BELKIN_SA_DATA_BITS(b) (b-5)
+
+#define BELKIN_SA_PARITY_NONE 0
+#define BELKIN_SA_PARITY_EVEN 1
+#define BELKIN_SA_PARITY_ODD 2
+#define BELKIN_SA_PARITY_MARK 3
+#define BELKIN_SA_PARITY_SPACE 4
+
+#define BELKIN_SA_FLOW_NONE 0x0000 /* No flow control */
+#define BELKIN_SA_FLOW_OCTS 0x0001 /* use CTS input to throttle output */
+#define BELKIN_SA_FLOW_ODSR 0x0002 /* use DSR input to throttle output */
+#define BELKIN_SA_FLOW_IDSR 0x0004 /* use DSR input to enable receive */
+#define BELKIN_SA_FLOW_IDTR 0x0008 /* use DTR output for input flow control */
+#define BELKIN_SA_FLOW_IRTS 0x0010 /* use RTS output for input flow control */
+#define BELKIN_SA_FLOW_ORTS 0x0020 /* use RTS to indicate data available to send */
+#define BELKIN_SA_FLOW_ERRSUB 0x0040 /* ???? guess ???? substitute inline errors */
+#define BELKIN_SA_FLOW_OXON 0x0080 /* use XON/XOFF for output flow control */
+#define BELKIN_SA_FLOW_IXON 0x0100 /* use XON/XOFF for input flow control */
+
+/*
+ * It seems that the interrupt pipe is closely modelled after the
+ * 16550 register layout. This is probably because the adapter can
+ * be used in a "DOS" environment to simulate a standard hardware port.
+ */
+#define BELKIN_SA_LSR_INDEX 2 /* Line Status Register */
+#define BELKIN_SA_LSR_RDR 0x01 /* receive data ready */
+#define BELKIN_SA_LSR_OE 0x02 /* overrun error */
+#define BELKIN_SA_LSR_PE 0x04 /* parity error */
+#define BELKIN_SA_LSR_FE 0x08 /* framing error */
+#define BELKIN_SA_LSR_BI 0x10 /* break indicator */
+#define BELKIN_SA_LSR_THE 0x20 /* tx holding register empty */
+#define BELKIN_SA_LSR_TE 0x40 /* transmit register empty */
+#define BELKIN_SA_LSR_ERR 0x80 /* OE | PE | FE | BI */
+
+#define BELKIN_SA_MSR_INDEX 3 /* Modem Status Register */
+#define BELKIN_SA_MSR_DCTS 0x01 /* Delta CTS */
+#define BELKIN_SA_MSR_DDSR 0x02 /* Delta DSR */
+#define BELKIN_SA_MSR_DRI 0x04 /* Delta RI */
+#define BELKIN_SA_MSR_DCD 0x08 /* Delta CD */
+#define BELKIN_SA_MSR_CTS 0x10 /* Current CTS */
+#define BELKIN_SA_MSR_DSR 0x20 /* Current DSR */
+#define BELKIN_SA_MSR_RI 0x40 /* Current RI */
+#define BELKIN_SA_MSR_CD 0x80 /* Current CD */
+
+#endif /* __LINUX_USB_SERIAL_BSA_H */
+
diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c
new file mode 100644
index 000000000..9e2a18c0b
--- /dev/null
+++ b/drivers/usb/serial/bus.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Serial Converter Bus specific functions
+ *
+ * Copyright (C) 2002 Greg Kroah-Hartman (greg@kroah.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+static int usb_serial_device_match(struct device *dev,
+ const struct device_driver *drv)
+{
+ const struct usb_serial_port *port = to_usb_serial_port(dev);
+ const struct usb_serial_driver *driver = to_usb_serial_driver(drv);
+
+ /*
+ * drivers are already assigned to ports in serial_probe so it's
+ * a simple check here.
+ */
+ if (driver == port->serial->type)
+ return 1;
+
+ return 0;
+}
+
+static int usb_serial_device_probe(struct device *dev)
+{
+ struct usb_serial_port *port = to_usb_serial_port(dev);
+ struct usb_serial_driver *driver;
+ struct device *tty_dev;
+ int retval = 0;
+ int minor;
+
+ /* make sure suspend/resume doesn't race against port_probe */
+ retval = usb_autopm_get_interface(port->serial->interface);
+ if (retval)
+ return retval;
+
+ driver = port->serial->type;
+ if (driver->port_probe) {
+ retval = driver->port_probe(port);
+ if (retval)
+ goto err_autopm_put;
+ }
+
+ minor = port->minor;
+ tty_dev = tty_port_register_device(&port->port, usb_serial_tty_driver,
+ minor, dev);
+ if (IS_ERR(tty_dev)) {
+ retval = PTR_ERR(tty_dev);
+ goto err_port_remove;
+ }
+
+ usb_autopm_put_interface(port->serial->interface);
+
+ dev_info(&port->serial->dev->dev,
+ "%s converter now attached to ttyUSB%d\n",
+ driver->description, minor);
+
+ return 0;
+
+err_port_remove:
+ if (driver->port_remove)
+ driver->port_remove(port);
+err_autopm_put:
+ usb_autopm_put_interface(port->serial->interface);
+
+ return retval;
+}
+
+static void usb_serial_device_remove(struct device *dev)
+{
+ struct usb_serial_port *port = to_usb_serial_port(dev);
+ struct usb_serial_driver *driver;
+ int minor;
+ int autopm_err;
+
+ /*
+ * Make sure suspend/resume doesn't race against port_remove.
+ *
+ * Note that no further runtime PM callbacks will be made if
+ * autopm_get fails.
+ */
+ autopm_err = usb_autopm_get_interface(port->serial->interface);
+
+ minor = port->minor;
+ tty_unregister_device(usb_serial_tty_driver, minor);
+
+ driver = port->serial->type;
+ if (driver->port_remove)
+ driver->port_remove(port);
+
+ dev_info(dev, "%s converter now disconnected from ttyUSB%d\n",
+ driver->description, minor);
+
+ if (!autopm_err)
+ usb_autopm_put_interface(port->serial->interface);
+}
+
+static ssize_t new_id_store(struct device_driver *driver,
+ const char *buf, size_t count)
+{
+ struct usb_serial_driver *usb_drv = to_usb_serial_driver(driver);
+ ssize_t retval = usb_store_new_id(&usb_drv->dynids, usb_drv->id_table,
+ driver, buf, count);
+
+ if (retval >= 0 && usb_drv->usb_driver != NULL)
+ retval = usb_store_new_id(&usb_drv->usb_driver->dynids,
+ usb_drv->usb_driver->id_table,
+ &usb_drv->usb_driver->driver,
+ buf, count);
+ return retval;
+}
+
+static ssize_t new_id_show(struct device_driver *driver, char *buf)
+{
+ struct usb_serial_driver *usb_drv = to_usb_serial_driver(driver);
+
+ return usb_show_dynids(&usb_drv->dynids, buf);
+}
+static DRIVER_ATTR_RW(new_id);
+
+static struct attribute *usb_serial_drv_attrs[] = {
+ &driver_attr_new_id.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(usb_serial_drv);
+
+static void free_dynids(struct usb_serial_driver *drv)
+{
+ struct usb_dynid *dynid, *n;
+
+ guard(mutex)(&usb_dynids_lock);
+ list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) {
+ list_del(&dynid->node);
+ kfree(dynid);
+ }
+}
+
+const struct bus_type usb_serial_bus_type = {
+ .name = "usb-serial",
+ .match = usb_serial_device_match,
+ .probe = usb_serial_device_probe,
+ .remove = usb_serial_device_remove,
+ .drv_groups = usb_serial_drv_groups,
+};
+
+int usb_serial_bus_register(struct usb_serial_driver *driver)
+{
+ int retval;
+
+ driver->driver.bus = &usb_serial_bus_type;
+ INIT_LIST_HEAD(&driver->dynids.list);
+
+ retval = driver_register(&driver->driver);
+
+ return retval;
+}
+
+void usb_serial_bus_deregister(struct usb_serial_driver *driver)
+{
+ free_dynids(driver);
+ driver_unregister(&driver->driver);
+}
+
diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c
new file mode 100644
index 000000000..569f4aede
--- /dev/null
+++ b/drivers/usb/serial/ch341.c
@@ -0,0 +1,893 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2007, Frank A Kingswood <frank@kingswood-consulting.co.uk>
+ * Copyright 2007, Werner Cornelius <werner@cornelius-consult.de>
+ * Copyright 2009, Boris Hajduk <boris@hajduk.org>
+ *
+ * ch341.c implements a serial port driver for the Winchiphead CH341.
+ *
+ * The CH341 device can be used to implement an RS232 asynchronous
+ * serial port, an IEEE-1284 parallel printer port or a memory-like
+ * interface. In all cases the CH341 supports an I2C interface as well.
+ * This driver only supports the asynchronous serial interface.
+ */
+
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/serial.h>
+#include <linux/unaligned.h>
+
+#define DEFAULT_BAUD_RATE 9600
+#define DEFAULT_TIMEOUT 1000
+
+/* flags for IO-Bits */
+#define CH341_BIT_RTS (1 << 6)
+#define CH341_BIT_DTR (1 << 5)
+
+/******************************/
+/* interrupt pipe definitions */
+/******************************/
+/* always 4 interrupt bytes */
+/* first irq byte normally 0x08 */
+/* second irq byte base 0x7d + below */
+/* third irq byte base 0x94 + below */
+/* fourth irq byte normally 0xee */
+
+/* second interrupt byte */
+#define CH341_MULT_STAT 0x04 /* multiple status since last interrupt event */
+
+/* status returned in third interrupt answer byte, inverted in data
+ from irq */
+#define CH341_BIT_CTS 0x01
+#define CH341_BIT_DSR 0x02
+#define CH341_BIT_RI 0x04
+#define CH341_BIT_DCD 0x08
+#define CH341_BITS_MODEM_STAT 0x0f /* all bits */
+
+/* Break support - the information used to implement this was gleaned from
+ * the Net/FreeBSD uchcom.c driver by Takanori Watanabe. Domo arigato.
+ */
+
+#define CH341_REQ_READ_VERSION 0x5F
+#define CH341_REQ_WRITE_REG 0x9A
+#define CH341_REQ_READ_REG 0x95
+#define CH341_REQ_SERIAL_INIT 0xA1
+#define CH341_REQ_MODEM_CTRL 0xA4
+
+#define CH341_REG_BREAK 0x05
+#define CH341_REG_PRESCALER 0x12
+#define CH341_REG_DIVISOR 0x13
+#define CH341_REG_LCR 0x18
+#define CH341_REG_LCR2 0x25
+#define CH341_REG_FLOW_CTL 0x27
+
+#define CH341_NBREAK_BITS 0x01
+
+#define CH341_LCR_ENABLE_RX 0x80
+#define CH341_LCR_ENABLE_TX 0x40
+#define CH341_LCR_MARK_SPACE 0x20
+#define CH341_LCR_PAR_EVEN 0x10
+#define CH341_LCR_ENABLE_PAR 0x08
+#define CH341_LCR_STOP_BITS_2 0x04
+#define CH341_LCR_CS8 0x03
+#define CH341_LCR_CS7 0x02
+#define CH341_LCR_CS6 0x01
+#define CH341_LCR_CS5 0x00
+
+#define CH341_FLOW_CTL_NONE 0x00
+#define CH341_FLOW_CTL_RTSCTS 0x01
+
+#define CH341_QUIRK_LIMITED_PRESCALER BIT(0)
+#define CH341_QUIRK_SIMULATE_BREAK BIT(1)
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x1a86, 0x5523) },
+ { USB_DEVICE(0x1a86, 0x7522) },
+ { USB_DEVICE(0x1a86, 0x7523) },
+ { USB_DEVICE(0x2184, 0x0057) },
+ { USB_DEVICE(0x4348, 0x5523) },
+ { USB_DEVICE(0x9986, 0x7523) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+struct ch341_private {
+ spinlock_t lock; /* access lock */
+ unsigned baud_rate; /* set baud rate */
+ u8 mcr;
+ u8 msr;
+ u8 lcr;
+
+ unsigned long quirks;
+ u8 version;
+
+ unsigned long break_end;
+};
+
+static void ch341_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+
+static int ch341_control_out(struct usb_device *dev, u8 request,
+ u16 value, u16 index)
+{
+ int r;
+
+ dev_dbg(&dev->dev, "%s - (%02x,%04x,%04x)\n", __func__,
+ request, value, index);
+
+ r = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ value, index, NULL, 0, DEFAULT_TIMEOUT);
+ if (r < 0)
+ dev_err(&dev->dev, "failed to send control message: %d\n", r);
+
+ return r;
+}
+
+static int ch341_control_in(struct usb_device *dev,
+ u8 request, u16 value, u16 index,
+ char *buf, unsigned bufsize)
+{
+ int r;
+
+ dev_dbg(&dev->dev, "%s - (%02x,%04x,%04x,%u)\n", __func__,
+ request, value, index, bufsize);
+
+ r = usb_control_msg_recv(dev, 0, request,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ value, index, buf, bufsize, DEFAULT_TIMEOUT,
+ GFP_KERNEL);
+ if (r) {
+ dev_err(&dev->dev, "failed to receive control message: %d\n",
+ r);
+ return r;
+ }
+
+ return 0;
+}
+
+#define CH341_CLKRATE 48000000
+#define CH341_CLK_DIV(ps, fact) (1 << (12 - 3 * (ps) - (fact)))
+#define CH341_MIN_RATE(ps) (CH341_CLKRATE / (CH341_CLK_DIV((ps), 1) * 512))
+
+static const speed_t ch341_min_rates[] = {
+ CH341_MIN_RATE(0),
+ CH341_MIN_RATE(1),
+ CH341_MIN_RATE(2),
+ CH341_MIN_RATE(3),
+};
+
+/* Supported range is 46 to 3000000 bps. */
+#define CH341_MIN_BPS DIV_ROUND_UP(CH341_CLKRATE, CH341_CLK_DIV(0, 0) * 256)
+#define CH341_MAX_BPS (CH341_CLKRATE / (CH341_CLK_DIV(3, 0) * 2))
+
+/*
+ * The device line speed is given by the following equation:
+ *
+ * baudrate = 48000000 / (2^(12 - 3 * ps - fact) * div), where
+ *
+ * 0 <= ps <= 3,
+ * 0 <= fact <= 1,
+ * 2 <= div <= 256 if fact = 0, or
+ * 9 <= div <= 256 if fact = 1
+ */
+static int ch341_get_divisor(struct ch341_private *priv, speed_t speed)
+{
+ unsigned int fact, div, clk_div;
+ bool force_fact0 = false;
+ int ps;
+
+ /*
+ * Clamp to supported range, this makes the (ps < 0) and (div < 2)
+ * sanity checks below redundant.
+ */
+ speed = clamp_val(speed, CH341_MIN_BPS, CH341_MAX_BPS);
+
+ /*
+ * Start with highest possible base clock (fact = 1) that will give a
+ * divisor strictly less than 512.
+ */
+ fact = 1;
+ for (ps = 3; ps >= 0; ps--) {
+ if (speed > ch341_min_rates[ps])
+ break;
+ }
+
+ if (ps < 0)
+ return -EINVAL;
+
+ /* Determine corresponding divisor, rounding down. */
+ clk_div = CH341_CLK_DIV(ps, fact);
+ div = CH341_CLKRATE / (clk_div * speed);
+
+ /* Some devices require a lower base clock if ps < 3. */
+ if (ps < 3 && (priv->quirks & CH341_QUIRK_LIMITED_PRESCALER))
+ force_fact0 = true;
+
+ /* Halve base clock (fact = 0) if required. */
+ if (div < 9 || div > 255 || force_fact0) {
+ div /= 2;
+ clk_div *= 2;
+ fact = 0;
+ }
+
+ if (div < 2)
+ return -EINVAL;
+
+ /*
+ * Pick next divisor if resulting rate is closer to the requested one,
+ * scale up to avoid rounding errors on low rates.
+ */
+ if (16 * CH341_CLKRATE / (clk_div * div) - 16 * speed >=
+ 16 * speed - 16 * CH341_CLKRATE / (clk_div * (div + 1)))
+ div++;
+
+ /*
+ * Prefer lower base clock (fact = 0) if even divisor.
+ *
+ * Note that this makes the receiver more tolerant to errors.
+ */
+ if (fact == 1 && div % 2 == 0) {
+ div /= 2;
+ fact = 0;
+ }
+
+ return (0x100 - div) << 8 | fact << 2 | ps;
+}
+
+static int ch341_set_baudrate_lcr(struct usb_device *dev,
+ struct ch341_private *priv,
+ speed_t baud_rate, u8 lcr)
+{
+ int val;
+ int r;
+
+ if (!baud_rate)
+ return -EINVAL;
+
+ val = ch341_get_divisor(priv, baud_rate);
+ if (val < 0)
+ return -EINVAL;
+
+ /*
+ * CH341A buffers data until a full endpoint-size packet (32 bytes)
+ * has been received unless bit 7 is set.
+ *
+ * At least one device with version 0x27 appears to have this bit
+ * inverted.
+ */
+ if (priv->version > 0x27)
+ val |= BIT(7);
+
+ r = ch341_control_out(dev, CH341_REQ_WRITE_REG,
+ CH341_REG_DIVISOR << 8 | CH341_REG_PRESCALER,
+ val);
+ if (r)
+ return r;
+
+ /*
+ * Chip versions before version 0x30 as read using
+ * CH341_REQ_READ_VERSION used separate registers for line control
+ * (stop bits, parity and word length). Version 0x30 and above use
+ * CH341_REG_LCR only and CH341_REG_LCR2 is always set to zero.
+ */
+ if (priv->version < 0x30)
+ return 0;
+
+ r = ch341_control_out(dev, CH341_REQ_WRITE_REG,
+ CH341_REG_LCR2 << 8 | CH341_REG_LCR, lcr);
+ if (r)
+ return r;
+
+ return r;
+}
+
+static int ch341_set_handshake(struct usb_device *dev, u8 control)
+{
+ return ch341_control_out(dev, CH341_REQ_MODEM_CTRL, ~control, 0);
+}
+
+static int ch341_get_status(struct usb_device *dev, struct ch341_private *priv)
+{
+ const unsigned int size = 2;
+ u8 buffer[2];
+ int r;
+ unsigned long flags;
+
+ r = ch341_control_in(dev, CH341_REQ_READ_REG, 0x0706, 0, buffer, size);
+ if (r)
+ return r;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->msr = (~(*buffer)) & CH341_BITS_MODEM_STAT;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+static int ch341_configure(struct usb_device *dev, struct ch341_private *priv)
+{
+ const unsigned int size = 2;
+ u8 buffer[2];
+ int r;
+
+ /* expect two bytes 0x27 0x00 */
+ r = ch341_control_in(dev, CH341_REQ_READ_VERSION, 0, 0, buffer, size);
+ if (r)
+ return r;
+
+ priv->version = buffer[0];
+ dev_dbg(&dev->dev, "Chip version: 0x%02x\n", priv->version);
+
+ r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT, 0, 0);
+ if (r < 0)
+ return r;
+
+ r = ch341_set_baudrate_lcr(dev, priv, priv->baud_rate, priv->lcr);
+ if (r < 0)
+ return r;
+
+ r = ch341_set_handshake(dev, priv->mcr);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int ch341_detect_quirks(struct usb_serial_port *port)
+{
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ struct usb_device *udev = port->serial->dev;
+ const unsigned int size = 2;
+ unsigned long quirks = 0;
+ u8 buffer[2];
+ int r;
+
+ /*
+ * A subset of CH34x devices does not support all features. The
+ * prescaler is limited and there is no support for sending a RS232
+ * break condition. A read failure when trying to set up the latter is
+ * used to detect these devices.
+ */
+ r = usb_control_msg_recv(udev, 0, CH341_REQ_READ_REG,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ CH341_REG_BREAK, 0, &buffer, size,
+ DEFAULT_TIMEOUT, GFP_KERNEL);
+ if (r == -EPIPE) {
+ dev_info(&port->dev, "break control not supported, using simulated break\n");
+ quirks = CH341_QUIRK_LIMITED_PRESCALER | CH341_QUIRK_SIMULATE_BREAK;
+ r = 0;
+ } else if (r) {
+ dev_err(&port->dev, "failed to read break control: %d\n", r);
+ }
+
+ if (quirks) {
+ dev_dbg(&port->dev, "enabling quirk flags: 0x%02lx\n", quirks);
+ priv->quirks |= quirks;
+ }
+
+ return r;
+}
+
+static int ch341_port_probe(struct usb_serial_port *port)
+{
+ struct ch341_private *priv;
+ int r;
+
+ priv = kzalloc_obj(struct ch341_private);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->lock);
+ priv->baud_rate = DEFAULT_BAUD_RATE;
+ /*
+ * Some CH340 devices appear unable to change the initial LCR
+ * settings, so set a sane 8N1 default.
+ */
+ priv->lcr = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX | CH341_LCR_CS8;
+
+ r = ch341_configure(port->serial->dev, priv);
+ if (r < 0)
+ goto error;
+
+ usb_set_serial_port_data(port, priv);
+
+ r = ch341_detect_quirks(port);
+ if (r < 0)
+ goto error;
+
+ return 0;
+
+error: kfree(priv);
+ return r;
+}
+
+static void ch341_port_remove(struct usb_serial_port *port)
+{
+ struct ch341_private *priv;
+
+ priv = usb_get_serial_port_data(port);
+ kfree(priv);
+}
+
+static int ch341_carrier_raised(struct usb_serial_port *port)
+{
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ if (priv->msr & CH341_BIT_DCD)
+ return 1;
+ return 0;
+}
+
+static void ch341_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ /* drop DTR and RTS */
+ spin_lock_irqsave(&priv->lock, flags);
+ if (on)
+ priv->mcr |= CH341_BIT_RTS | CH341_BIT_DTR;
+ else
+ priv->mcr &= ~(CH341_BIT_RTS | CH341_BIT_DTR);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ ch341_set_handshake(port->serial->dev, priv->mcr);
+}
+
+static void ch341_close(struct usb_serial_port *port)
+{
+ usb_serial_generic_close(port);
+ usb_kill_urb(port->interrupt_in_urb);
+}
+
+
+/* open this device, set default parameters */
+static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ int r;
+
+ if (tty)
+ ch341_set_termios(tty, port, NULL);
+
+ dev_dbg(&port->dev, "%s - submitting interrupt urb\n", __func__);
+ r = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (r) {
+ dev_err(&port->dev, "%s - failed to submit interrupt urb: %d\n",
+ __func__, r);
+ return r;
+ }
+
+ r = ch341_get_status(port->serial->dev, priv);
+ if (r < 0) {
+ dev_err(&port->dev, "failed to read modem status: %d\n", r);
+ goto err_kill_interrupt_urb;
+ }
+
+ r = usb_serial_generic_open(tty, port);
+ if (r)
+ goto err_kill_interrupt_urb;
+
+ return 0;
+
+err_kill_interrupt_urb:
+ usb_kill_urb(port->interrupt_in_urb);
+
+ return r;
+}
+
+static void ch341_set_flow_control(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ u16 flow_ctl;
+ int r;
+
+ if (C_CRTSCTS(tty))
+ flow_ctl = CH341_FLOW_CTL_RTSCTS;
+ else
+ flow_ctl = CH341_FLOW_CTL_NONE;
+
+ r = ch341_control_out(port->serial->dev,
+ CH341_REQ_WRITE_REG,
+ (CH341_REG_FLOW_CTL << 8) | CH341_REG_FLOW_CTL,
+ (flow_ctl << 8) | flow_ctl);
+ if (r < 0 && old_termios) {
+ tty->termios.c_cflag &= ~CRTSCTS;
+ tty->termios.c_cflag |= (old_termios->c_cflag & CRTSCTS);
+ }
+}
+
+/* Old_termios contains the original termios settings and
+ * tty->termios contains the new setting to be used.
+ */
+static void ch341_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ unsigned baud_rate;
+ unsigned long flags;
+ u8 lcr;
+ int r;
+
+ /* redundant changes may cause the chip to lose bytes */
+ if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios))
+ return;
+
+ baud_rate = tty_get_baud_rate(tty);
+
+ lcr = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX;
+
+ switch (C_CSIZE(tty)) {
+ case CS5:
+ lcr |= CH341_LCR_CS5;
+ break;
+ case CS6:
+ lcr |= CH341_LCR_CS6;
+ break;
+ case CS7:
+ lcr |= CH341_LCR_CS7;
+ break;
+ case CS8:
+ lcr |= CH341_LCR_CS8;
+ break;
+ }
+
+ if (C_PARENB(tty)) {
+ lcr |= CH341_LCR_ENABLE_PAR;
+ if (C_PARODD(tty) == 0)
+ lcr |= CH341_LCR_PAR_EVEN;
+ if (C_CMSPAR(tty))
+ lcr |= CH341_LCR_MARK_SPACE;
+ }
+
+ if (C_CSTOPB(tty))
+ lcr |= CH341_LCR_STOP_BITS_2;
+
+ if (baud_rate) {
+ priv->baud_rate = baud_rate;
+
+ r = ch341_set_baudrate_lcr(port->serial->dev, priv,
+ priv->baud_rate, lcr);
+ if (r < 0 && old_termios) {
+ priv->baud_rate = tty_termios_baud_rate(old_termios);
+ tty_termios_copy_hw(&tty->termios, old_termios);
+ } else if (r == 0) {
+ priv->lcr = lcr;
+ }
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (C_BAUD(tty) == B0)
+ priv->mcr &= ~(CH341_BIT_DTR | CH341_BIT_RTS);
+ else if (old_termios && (old_termios->c_cflag & CBAUD) == B0)
+ priv->mcr |= (CH341_BIT_DTR | CH341_BIT_RTS);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ ch341_set_handshake(port->serial->dev, priv->mcr);
+
+ ch341_set_flow_control(tty, port, old_termios);
+}
+
+/*
+ * A subset of all CH34x devices don't support a real break condition and
+ * reading CH341_REG_BREAK fails (see also ch341_detect_quirks). This function
+ * simulates a break condition by lowering the baud rate to the minimum
+ * supported by the hardware upon enabling the break condition and sending
+ * a NUL byte.
+ *
+ * Incoming data is corrupted while the break condition is being simulated.
+ *
+ * Normally the duration of the break condition can be controlled individually
+ * by userspace using TIOCSBRK and TIOCCBRK or by passing an argument to
+ * TCSBRKP. Due to how the simulation is implemented the duration can't be
+ * controlled. The duration is always about (1s / 46bd * 9bit) = 196ms.
+ */
+static int ch341_simulate_break(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ unsigned long now, delay;
+ int r, r2;
+
+ if (break_state != 0) {
+ dev_dbg(&port->dev, "enter break state requested\n");
+
+ r = ch341_set_baudrate_lcr(port->serial->dev, priv,
+ CH341_MIN_BPS,
+ CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX | CH341_LCR_CS8);
+ if (r < 0) {
+ dev_err(&port->dev,
+ "failed to change baud rate to %u: %d\n",
+ CH341_MIN_BPS, r);
+ goto restore;
+ }
+
+ r = tty_put_char(tty, '\0');
+ if (r < 0) {
+ dev_err(&port->dev,
+ "failed to write NUL byte for simulated break condition: %d\n",
+ r);
+ goto restore;
+ }
+
+ /*
+ * Compute expected transmission duration including safety
+ * margin. The original baud rate is only restored after the
+ * computed point in time.
+ *
+ * 11 bits = 1 start, 8 data, 1 stop, 1 margin
+ */
+ priv->break_end = jiffies + (11 * HZ / CH341_MIN_BPS);
+
+ return 0;
+ }
+
+ dev_dbg(&port->dev, "leave break state requested\n");
+
+ now = jiffies;
+
+ if (time_before(now, priv->break_end)) {
+ /* Wait until NUL byte is written */
+ delay = priv->break_end - now;
+ dev_dbg(&port->dev,
+ "wait %d ms while transmitting NUL byte at %u baud\n",
+ jiffies_to_msecs(delay), CH341_MIN_BPS);
+ schedule_timeout_interruptible(delay);
+ }
+
+ r = 0;
+restore:
+ /* Restore original baud rate */
+ r2 = ch341_set_baudrate_lcr(port->serial->dev, priv, priv->baud_rate,
+ priv->lcr);
+ if (r2 < 0) {
+ dev_err(&port->dev,
+ "restoring original baud rate of %u failed: %d\n",
+ priv->baud_rate, r2);
+ return r2;
+ }
+
+ return r;
+}
+
+static int ch341_break_ctl(struct tty_struct *tty, int break_state)
+{
+ const u16 ch341_break_reg = (CH341_REG_LCR << 8) | CH341_REG_BREAK;
+ struct usb_serial_port *port = tty->driver_data;
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ u16 reg_contents;
+ u8 break_reg[2];
+ int r;
+
+ if (priv->quirks & CH341_QUIRK_SIMULATE_BREAK)
+ return ch341_simulate_break(tty, break_state);
+
+ r = ch341_control_in(port->serial->dev, CH341_REQ_READ_REG,
+ ch341_break_reg, 0, break_reg, 2);
+ if (r) {
+ dev_err(&port->dev, "%s - USB control read error (%d)\n",
+ __func__, r);
+ if (r > 0)
+ r = -EIO;
+ return r;
+ }
+ dev_dbg(&port->dev, "%s - initial ch341 break register contents - reg1: %x, reg2: %x\n",
+ __func__, break_reg[0], break_reg[1]);
+ if (break_state != 0) {
+ dev_dbg(&port->dev, "%s - Enter break state requested\n", __func__);
+ break_reg[0] &= ~CH341_NBREAK_BITS;
+ break_reg[1] &= ~CH341_LCR_ENABLE_TX;
+ } else {
+ dev_dbg(&port->dev, "%s - Leave break state requested\n", __func__);
+ break_reg[0] |= CH341_NBREAK_BITS;
+ break_reg[1] |= CH341_LCR_ENABLE_TX;
+ }
+ dev_dbg(&port->dev, "%s - New ch341 break register contents - reg1: %x, reg2: %x\n",
+ __func__, break_reg[0], break_reg[1]);
+ reg_contents = get_unaligned_le16(break_reg);
+ r = ch341_control_out(port->serial->dev, CH341_REQ_WRITE_REG,
+ ch341_break_reg, reg_contents);
+ if (r < 0) {
+ dev_err(&port->dev, "%s - USB control write error (%d)\n",
+ __func__, r);
+ return r;
+ }
+
+ return 0;
+}
+
+static int ch341_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ u8 control;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (set & TIOCM_RTS)
+ priv->mcr |= CH341_BIT_RTS;
+ if (set & TIOCM_DTR)
+ priv->mcr |= CH341_BIT_DTR;
+ if (clear & TIOCM_RTS)
+ priv->mcr &= ~CH341_BIT_RTS;
+ if (clear & TIOCM_DTR)
+ priv->mcr &= ~CH341_BIT_DTR;
+ control = priv->mcr;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return ch341_set_handshake(port->serial->dev, control);
+}
+
+static void ch341_update_status(struct usb_serial_port *port,
+ unsigned char *data, size_t len)
+{
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ struct tty_struct *tty;
+ unsigned long flags;
+ u8 status;
+ u8 delta;
+
+ if (len < 4)
+ return;
+
+ status = ~data[2] & CH341_BITS_MODEM_STAT;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ delta = status ^ priv->msr;
+ priv->msr = status;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (data[1] & CH341_MULT_STAT)
+ dev_dbg(&port->dev, "%s - multiple status change\n", __func__);
+
+ if (!delta)
+ return;
+
+ if (delta & CH341_BIT_CTS)
+ port->icount.cts++;
+ if (delta & CH341_BIT_DSR)
+ port->icount.dsr++;
+ if (delta & CH341_BIT_RI)
+ port->icount.rng++;
+ if (delta & CH341_BIT_DCD) {
+ port->icount.dcd++;
+ tty = tty_port_tty_get(&port->port);
+ if (tty) {
+ usb_serial_handle_dcd_change(port, tty,
+ status & CH341_BIT_DCD);
+ tty_kref_put(tty);
+ }
+ }
+
+ wake_up_interruptible(&port->port.delta_msr_wait);
+}
+
+static void ch341_read_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ unsigned int len = urb->actual_length;
+ int status;
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&urb->dev->dev, "%s - urb shutting down: %d\n",
+ __func__, urb->status);
+ return;
+ default:
+ dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n",
+ __func__, urb->status);
+ goto exit;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__, len, data);
+ ch341_update_status(port, data, len);
+exit:
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status) {
+ dev_err(&urb->dev->dev, "%s - usb_submit_urb failed: %d\n",
+ __func__, status);
+ }
+}
+
+static int ch341_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ u8 mcr;
+ u8 status;
+ unsigned int result;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ mcr = priv->mcr;
+ status = priv->msr;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ result = ((mcr & CH341_BIT_DTR) ? TIOCM_DTR : 0)
+ | ((mcr & CH341_BIT_RTS) ? TIOCM_RTS : 0)
+ | ((status & CH341_BIT_CTS) ? TIOCM_CTS : 0)
+ | ((status & CH341_BIT_DSR) ? TIOCM_DSR : 0)
+ | ((status & CH341_BIT_RI) ? TIOCM_RI : 0)
+ | ((status & CH341_BIT_DCD) ? TIOCM_CD : 0);
+
+ dev_dbg(&port->dev, "%s - result = %x\n", __func__, result);
+
+ return result;
+}
+
+static int ch341_reset_resume(struct usb_serial *serial)
+{
+ struct usb_serial_port *port = serial->port[0];
+ struct ch341_private *priv;
+ int ret;
+
+ priv = usb_get_serial_port_data(port);
+ if (!priv)
+ return 0;
+
+ /* reconfigure ch341 serial port after bus-reset */
+ ch341_configure(serial->dev, priv);
+
+ if (tty_port_initialized(&port->port)) {
+ ret = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
+ if (ret) {
+ dev_err(&port->dev, "failed to submit interrupt urb: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = ch341_get_status(port->serial->dev, priv);
+ if (ret < 0) {
+ dev_err(&port->dev, "failed to read modem status: %d\n",
+ ret);
+ }
+ }
+
+ return usb_serial_generic_resume(serial);
+}
+
+static struct usb_serial_driver ch341_device = {
+ .driver = {
+ .name = "ch341-uart",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .open = ch341_open,
+ .dtr_rts = ch341_dtr_rts,
+ .carrier_raised = ch341_carrier_raised,
+ .close = ch341_close,
+ .set_termios = ch341_set_termios,
+ .break_ctl = ch341_break_ctl,
+ .tiocmget = ch341_tiocmget,
+ .tiocmset = ch341_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .read_int_callback = ch341_read_int_callback,
+ .port_probe = ch341_port_probe,
+ .port_remove = ch341_port_remove,
+ .reset_resume = ch341_reset_resume,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &ch341_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION("Winchiphead CH341 USB Serial driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/console.c b/drivers/usb/serial/console.c
new file mode 100644
index 000000000..29f091908
--- /dev/null
+++ b/drivers/usb/serial/console.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Serial Console driver
+ *
+ * Copyright (C) 2001 - 2002 Greg Kroah-Hartman (greg@kroah.com)
+ *
+ * Thanks to Randy Dunlap for the original version of this code.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/console.h>
+#include <linux/serial.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+struct usbcons_info {
+ int magic;
+ int break_flag;
+ struct usb_serial_port *port;
+};
+
+static struct usbcons_info usbcons_info;
+static struct console usbcons;
+
+/*
+ * ------------------------------------------------------------
+ * USB Serial console driver
+ *
+ * Much of the code here is copied from drivers/char/serial.c
+ * and implements a phony serial console in the same way that
+ * serial.c does so that in case some software queries it,
+ * it will get the same results.
+ *
+ * Things that are different from the way the serial port code
+ * does things, is that we call the lower level usb-serial
+ * driver code to initialize the device, and we set the initial
+ * console speeds based on the command line arguments.
+ * ------------------------------------------------------------
+ */
+
+static const struct tty_operations usb_console_fake_tty_ops = {
+};
+
+/*
+ * The parsing of the command line works exactly like the
+ * serial.c code, except that the specifier is "ttyUSB" instead
+ * of "ttyS".
+ */
+static int usb_console_setup(struct console *co, char *options)
+{
+ struct usbcons_info *info = &usbcons_info;
+ int baud = 9600;
+ int bits = 8;
+ int parity = 'n';
+ int doflow = 0;
+ int cflag = CREAD | HUPCL | CLOCAL;
+ char *s;
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+ int retval;
+ struct tty_struct *tty = NULL;
+ struct ktermios dummy;
+
+ if (options) {
+ baud = simple_strtoul(options, NULL, 10);
+ s = options;
+ while (*s >= '0' && *s <= '9')
+ s++;
+ if (*s)
+ parity = *s++;
+ if (*s)
+ bits = *s++ - '0';
+ if (*s)
+ doflow = (*s++ == 'r');
+ }
+
+ /* Sane default */
+ if (baud == 0)
+ baud = 9600;
+
+ switch (bits) {
+ case 7:
+ cflag |= CS7;
+ break;
+ default:
+ case 8:
+ cflag |= CS8;
+ break;
+ }
+ switch (parity) {
+ case 'o': case 'O':
+ cflag |= PARODD;
+ break;
+ case 'e': case 'E':
+ cflag |= PARENB;
+ break;
+ }
+
+ if (doflow)
+ cflag |= CRTSCTS;
+
+ /*
+ * no need to check the index here: if the index is wrong, console
+ * code won't call us
+ */
+ port = usb_serial_port_get_by_minor(co->index);
+ if (port == NULL) {
+ /* no device is connected yet, sorry :( */
+ pr_err("No USB device connected to ttyUSB%i\n", co->index);
+ return -ENODEV;
+ }
+ serial = port->serial;
+
+ retval = usb_autopm_get_interface(serial->interface);
+ if (retval)
+ goto error_get_interface;
+
+ tty_port_tty_set(&port->port, NULL);
+
+ info->port = port;
+
+ ++port->port.count;
+ if (!tty_port_initialized(&port->port)) {
+ if (serial->type->set_termios) {
+ /*
+ * allocate a fake tty so the driver can initialize
+ * the termios structure, then later call set_termios to
+ * configure according to command line arguments
+ */
+ tty = kzalloc_obj(*tty);
+ if (!tty) {
+ retval = -ENOMEM;
+ goto reset_open_count;
+ }
+ kref_init(&tty->kref);
+ tty->driver = usb_serial_tty_driver;
+ tty->index = co->index;
+ init_ldsem(&tty->ldisc_sem);
+ spin_lock_init(&tty->files_lock);
+ INIT_LIST_HEAD(&tty->tty_files);
+ kref_get(&tty->driver->kref);
+ __module_get(tty->driver->owner);
+ tty->ops = &usb_console_fake_tty_ops;
+ tty_init_termios(tty);
+ tty_port_tty_set(&port->port, tty);
+ }
+
+ /* only call the device specific open if this
+ * is the first time the port is opened */
+ retval = serial->type->open(NULL, port);
+ if (retval) {
+ dev_err(&port->dev, "could not open USB console port\n");
+ goto fail;
+ }
+
+ if (serial->type->set_termios) {
+ tty->termios.c_cflag = cflag;
+ tty_termios_encode_baud_rate(&tty->termios, baud, baud);
+ memset(&dummy, 0, sizeof(struct ktermios));
+ serial->type->set_termios(tty, port, &dummy);
+
+ tty_port_tty_set(&port->port, NULL);
+ tty_save_termios(tty);
+ tty_kref_put(tty);
+ }
+ tty_port_set_initialized(&port->port, true);
+ }
+ /* Now that any required fake tty operations are completed restore
+ * the tty port count */
+ --port->port.count;
+ /* The console is special in terms of closing the device so
+ * indicate this port is now acting as a system console. */
+ port->port.console = 1;
+
+ mutex_unlock(&serial->disc_mutex);
+ return retval;
+
+ fail:
+ tty_port_tty_set(&port->port, NULL);
+ tty_kref_put(tty);
+ reset_open_count:
+ port->port.count = 0;
+ info->port = NULL;
+ usb_autopm_put_interface(serial->interface);
+ error_get_interface:
+ mutex_unlock(&serial->disc_mutex);
+ usb_serial_put(serial);
+ return retval;
+}
+
+static void usb_console_write(struct console *co,
+ const char *buf, unsigned count)
+{
+ static struct usbcons_info *info = &usbcons_info;
+ struct usb_serial_port *port = info->port;
+ struct usb_serial *serial;
+ int retval = -ENODEV;
+
+ if (!port || port->serial->dev->state == USB_STATE_NOTATTACHED)
+ return;
+ serial = port->serial;
+
+ if (count == 0)
+ return;
+
+ dev_dbg(&port->dev, "%s - %d byte(s)\n", __func__, count);
+
+ if (!port->port.console) {
+ dev_dbg(&port->dev, "%s - port not opened\n", __func__);
+ return;
+ }
+
+ while (count) {
+ unsigned int i;
+ unsigned int lf;
+ /* search for LF so we can insert CR if necessary */
+ for (i = 0, lf = 0 ; i < count ; i++) {
+ if (*(buf + i) == 10) {
+ lf = 1;
+ i++;
+ break;
+ }
+ }
+ /* pass on to the driver specific version of this function if
+ it is available */
+ retval = serial->type->write(NULL, port, buf, i);
+ dev_dbg(&port->dev, "%s - write: %d\n", __func__, retval);
+ if (lf) {
+ /* append CR after LF */
+ unsigned char cr = 13;
+ retval = serial->type->write(NULL, port, &cr, 1);
+ dev_dbg(&port->dev, "%s - write cr: %d\n",
+ __func__, retval);
+ }
+ buf += i;
+ count -= i;
+ }
+}
+
+static struct tty_driver *usb_console_device(struct console *co, int *index)
+{
+ struct tty_driver **p = (struct tty_driver **)co->data;
+
+ if (!*p)
+ return NULL;
+
+ *index = co->index;
+ return *p;
+}
+
+static struct console usbcons = {
+ .name = "ttyUSB",
+ .write = usb_console_write,
+ .device = usb_console_device,
+ .setup = usb_console_setup,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+ .data = &usb_serial_tty_driver,
+};
+
+void usb_serial_console_disconnect(struct usb_serial *serial)
+{
+ if (serial->port[0] && serial->port[0] == usbcons_info.port) {
+ usb_serial_console_exit();
+ usb_serial_put(serial);
+ }
+}
+
+void usb_serial_console_init(int minor)
+{
+ if (minor == 0) {
+ /*
+ * Call register_console() if this is the first device plugged
+ * in. If we call it earlier, then the callback to
+ * console_setup() will fail, as there is not a device seen by
+ * the USB subsystem yet.
+ */
+ /*
+ * Register console.
+ * NOTES:
+ * console_setup() is called (back) immediately (from
+ * register_console). console_write() is called immediately
+ * from register_console iff CON_PRINTBUFFER is set in flags.
+ */
+ pr_debug("registering the USB serial console.\n");
+ register_console(&usbcons);
+ }
+}
+
+void usb_serial_console_exit(void)
+{
+ if (usbcons_info.port) {
+ unregister_console(&usbcons);
+ usbcons_info.port->port.console = 0;
+ usbcons_info.port = NULL;
+ }
+}
+
diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c
new file mode 100644
index 000000000..333b8b463
--- /dev/null
+++ b/drivers/usb/serial/cp210x.c
@@ -0,0 +1,2201 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Silicon Laboratories CP210x USB to RS232 serial adaptor driver
+ *
+ * Copyright (C) 2005 Craig Shelley (craig@microtron.org.uk)
+ * Copyright (C) 2010-2021 Johan Hovold (johan@kernel.org)
+ *
+ * Support to set flow control line levels using TIOCMGET and TIOCMSET
+ * thanks to Karl Hiramoto karl@hiramoto.org. RTSCTS hardware flow
+ * control thanks to Munir Nassar nassarmu@real-time.com
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/gpio/driver.h>
+#include <linux/bitops.h>
+#include <linux/mutex.h>
+
+#define DRIVER_DESC "Silicon Labs CP210x RS232 serial adaptor driver"
+
+/*
+ * Function Prototypes
+ */
+static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *);
+static void cp210x_close(struct usb_serial_port *);
+static void cp210x_change_speed(struct tty_struct *, struct usb_serial_port *,
+ const struct ktermios *);
+static void cp210x_set_termios(struct tty_struct *, struct usb_serial_port *,
+ const struct ktermios *);
+static bool cp210x_tx_empty(struct usb_serial_port *port);
+static int cp210x_tiocmget(struct tty_struct *);
+static int cp210x_tiocmset(struct tty_struct *, unsigned int, unsigned int);
+static int cp210x_tiocmset_port(struct usb_serial_port *port,
+ unsigned int, unsigned int);
+static int cp210x_break_ctl(struct tty_struct *, int);
+static int cp210x_attach(struct usb_serial *);
+static void cp210x_disconnect(struct usb_serial *);
+static void cp210x_release(struct usb_serial *);
+static int cp210x_port_probe(struct usb_serial_port *);
+static void cp210x_port_remove(struct usb_serial_port *);
+static void cp210x_dtr_rts(struct usb_serial_port *port, int on);
+static void cp210x_process_read_urb(struct urb *urb);
+static void cp210x_enable_event_mode(struct usb_serial_port *port);
+static void cp210x_disable_event_mode(struct usb_serial_port *port);
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x0404, 0x034C) }, /* NCR Retail IO Box */
+ { USB_DEVICE(0x045B, 0x0053) }, /* Renesas RX610 RX-Stick */
+ { USB_DEVICE(0x0471, 0x066A) }, /* AKTAKOM ACE-1001 cable */
+ { USB_DEVICE(0x0489, 0xE000) }, /* Pirelli Broadband S.p.A, DP-L10 SIP/GSM Mobile */
+ { USB_DEVICE(0x0489, 0xE003) }, /* Pirelli Broadband S.p.A, DP-L10 SIP/GSM Mobile */
+ { USB_DEVICE(0x04BF, 0x1301) }, /* TDK Corporation NC0110013M - Network Controller */
+ { USB_DEVICE(0x04BF, 0x1303) }, /* TDK Corporation MM0110113M - i3 Micro Module */
+ { USB_DEVICE(0x0745, 0x1000) }, /* CipherLab USB CCD Barcode Scanner 1000 */
+ { USB_DEVICE(0x0846, 0x1100) }, /* NetGear Managed Switch M4100 series, M5300 series, M7100 series */
+ { USB_DEVICE(0x08e6, 0x5501) }, /* Gemalto Prox-PU/CU contactless smartcard reader */
+ { USB_DEVICE(0x08FD, 0x000A) }, /* Digianswer A/S , ZigBee/802.15.4 MAC Device */
+ { USB_DEVICE(0x0908, 0x0070) }, /* Siemens SCALANCE LPE-9000 USB Serial Console */
+ { USB_DEVICE(0x0908, 0x01FF) }, /* Siemens RUGGEDCOM USB Serial Console */
+ { USB_DEVICE(0x0988, 0x0578) }, /* Teraoka AD2000 */
+ { USB_DEVICE(0x0B00, 0x3070) }, /* Ingenico 3070 */
+ { USB_DEVICE(0x0BED, 0x1100) }, /* MEI (TM) Cashflow-SC Bill/Voucher Acceptor */
+ { USB_DEVICE(0x0BED, 0x1101) }, /* MEI series 2000 Combo Acceptor */
+ { USB_DEVICE(0x0FCF, 0x1003) }, /* Dynastream ANT development board */
+ { USB_DEVICE(0x0FCF, 0x1004) }, /* Dynastream ANT2USB */
+ { USB_DEVICE(0x0FCF, 0x1006) }, /* Dynastream ANT development board */
+ { USB_DEVICE(0x0FDE, 0xCA05) }, /* OWL Wireless Electricity Monitor CM-160 */
+ { USB_DEVICE(0x106F, 0x0003) }, /* CPI / Money Controls Bulk Coin Recycler */
+ { USB_DEVICE(0x10A6, 0xAA26) }, /* Knock-off DCU-11 cable */
+ { USB_DEVICE(0x10AB, 0x10C5) }, /* Siemens MC60 Cable */
+ { USB_DEVICE(0x10B5, 0xAC70) }, /* Nokia CA-42 USB */
+ { USB_DEVICE(0x10C4, 0x0F91) }, /* Vstabi */
+ { USB_DEVICE(0x10C4, 0x1101) }, /* Arkham Technology DS101 Bus Monitor */
+ { USB_DEVICE(0x10C4, 0x1601) }, /* Arkham Technology DS101 Adapter */
+ { USB_DEVICE(0x10C4, 0x800A) }, /* SPORTident BSM7-D-USB main station */
+ { USB_DEVICE(0x10C4, 0x803B) }, /* Pololu USB-serial converter */
+ { USB_DEVICE(0x10C4, 0x8044) }, /* Cygnal Debug Adapter */
+ { USB_DEVICE(0x10C4, 0x804E) }, /* Software Bisque Paramount ME build-in converter */
+ { USB_DEVICE(0x10C4, 0x8053) }, /* Enfora EDG1228 */
+ { USB_DEVICE(0x10C4, 0x8054) }, /* Enfora GSM2228 */
+ { USB_DEVICE(0x10C4, 0x8056) }, /* Lorenz Messtechnik devices */
+ { USB_DEVICE(0x10C4, 0x8066) }, /* Argussoft In-System Programmer */
+ { USB_DEVICE(0x10C4, 0x806F) }, /* IMS USB to RS422 Converter Cable */
+ { USB_DEVICE(0x10C4, 0x807A) }, /* Crumb128 board */
+ { USB_DEVICE(0x10C4, 0x80C4) }, /* Cygnal Integrated Products, Inc., Optris infrared thermometer */
+ { USB_DEVICE(0x10C4, 0x80CA) }, /* Degree Controls Inc */
+ { USB_DEVICE(0x10C4, 0x80DD) }, /* Tracient RFID */
+ { USB_DEVICE(0x10C4, 0x80F6) }, /* Suunto sports instrument */
+ { USB_DEVICE(0x10C4, 0x8115) }, /* Arygon NFC/Mifare Reader */
+ { USB_DEVICE(0x10C4, 0x813D) }, /* Burnside Telecom Deskmobile */
+ { USB_DEVICE(0x10C4, 0x813F) }, /* Tams Master Easy Control */
+ { USB_DEVICE(0x10C4, 0x814A) }, /* West Mountain Radio RIGblaster P&P */
+ { USB_DEVICE(0x10C4, 0x814B) }, /* West Mountain Radio RIGtalk */
+ { USB_DEVICE(0x2405, 0x0003) }, /* West Mountain Radio RIGblaster Advantage */
+ { USB_DEVICE(0x10C4, 0x8156) }, /* B&G H3000 link cable */
+ { USB_DEVICE(0x10C4, 0x815E) }, /* Helicomm IP-Link 1220-DVM */
+ { USB_DEVICE(0x10C4, 0x815F) }, /* Timewave HamLinkUSB */
+ { USB_DEVICE(0x10C4, 0x817C) }, /* CESINEL MEDCAL N Power Quality Monitor */
+ { USB_DEVICE(0x10C4, 0x817D) }, /* CESINEL MEDCAL NT Power Quality Monitor */
+ { USB_DEVICE(0x10C4, 0x817E) }, /* CESINEL MEDCAL S Power Quality Monitor */
+ { USB_DEVICE(0x10C4, 0x818B) }, /* AVIT Research USB to TTL */
+ { USB_DEVICE(0x10C4, 0x819F) }, /* MJS USB Toslink Switcher */
+ { USB_DEVICE(0x10C4, 0x81A6) }, /* ThinkOptics WavIt */
+ { USB_DEVICE(0x10C4, 0x81A9) }, /* Multiplex RC Interface */
+ { USB_DEVICE(0x10C4, 0x81AC) }, /* MSD Dash Hawk */
+ { USB_DEVICE(0x10C4, 0x81AD) }, /* INSYS USB Modem */
+ { USB_DEVICE(0x10C4, 0x81C8) }, /* Lipowsky Industrie Elektronik GmbH, Baby-JTAG */
+ { USB_DEVICE(0x10C4, 0x81D7) }, /* IAI Corp. RCB-CV-USB USB to RS485 Adaptor */
+ { USB_DEVICE(0x10C4, 0x81E2) }, /* Lipowsky Industrie Elektronik GmbH, Baby-LIN */
+ { USB_DEVICE(0x10C4, 0x81E7) }, /* Aerocomm Radio */
+ { USB_DEVICE(0x10C4, 0x81E8) }, /* Zephyr Bioharness */
+ { USB_DEVICE(0x10C4, 0x81F2) }, /* C1007 HF band RFID controller */
+ { USB_DEVICE(0x10C4, 0x8218) }, /* Lipowsky Industrie Elektronik GmbH, HARP-1 */
+ { USB_DEVICE(0x10C4, 0x822B) }, /* Modem EDGE(GSM) Comander 2 */
+ { USB_DEVICE(0x10C4, 0x826B) }, /* Cygnal Integrated Products, Inc., Fasttrax GPS demonstration module */
+ { USB_DEVICE(0x10C4, 0x8281) }, /* Nanotec Plug & Drive */
+ { USB_DEVICE(0x10C4, 0x8293) }, /* Telegesis ETRX2USB */
+ { USB_DEVICE(0x10C4, 0x82AA) }, /* Silicon Labs IFS-USB-DATACABLE used with Quint UPS */
+ { USB_DEVICE(0x10C4, 0x82EF) }, /* CESINEL FALCO 6105 AC Power Supply */
+ { USB_DEVICE(0x10C4, 0x82F1) }, /* CESINEL MEDCAL EFD Earth Fault Detector */
+ { USB_DEVICE(0x10C4, 0x82F2) }, /* CESINEL MEDCAL ST Network Analyzer */
+ { USB_DEVICE(0x10C4, 0x82F4) }, /* Starizona MicroTouch */
+ { USB_DEVICE(0x10C4, 0x82F9) }, /* Procyon AVS */
+ { USB_DEVICE(0x10C4, 0x8341) }, /* Siemens MC35PU GPRS Modem */
+ { USB_DEVICE(0x10C4, 0x8382) }, /* Cygnal Integrated Products, Inc. */
+ { USB_DEVICE(0x10C4, 0x83A8) }, /* Amber Wireless AMB2560 */
+ { USB_DEVICE(0x10C4, 0x83AA) }, /* Mark-10 Digital Force Gauge */
+ { USB_DEVICE(0x10C4, 0x83D8) }, /* DekTec DTA Plus VHF/UHF Booster/Attenuator */
+ { USB_DEVICE(0x10C4, 0x8411) }, /* Kyocera GPS Module */
+ { USB_DEVICE(0x10C4, 0x8414) }, /* Decagon USB Cable Adapter */
+ { USB_DEVICE(0x10C4, 0x8418) }, /* IRZ Automation Teleport SG-10 GSM/GPRS Modem */
+ { USB_DEVICE(0x10C4, 0x846E) }, /* BEI USB Sensor Interface (VCP) */
+ { USB_DEVICE(0x10C4, 0x8470) }, /* Juniper Networks BX Series System Console */
+ { USB_DEVICE(0x10C4, 0x8477) }, /* Balluff RFID */
+ { USB_DEVICE(0x10C4, 0x84B6) }, /* Starizona Hyperion */
+ { USB_DEVICE(0x10C4, 0x851E) }, /* CESINEL MEDCAL PT Network Analyzer */
+ { USB_DEVICE(0x10C4, 0x85A7) }, /* LifeScan OneTouch Verio IQ */
+ { USB_DEVICE(0x10C4, 0x85B8) }, /* CESINEL ReCon T Energy Logger */
+ { USB_DEVICE(0x10C4, 0x85EA) }, /* AC-Services IBUS-IF */
+ { USB_DEVICE(0x10C4, 0x85EB) }, /* AC-Services CIS-IBUS */
+ { USB_DEVICE(0x10C4, 0x85F8) }, /* Virtenio Preon32 */
+ { USB_DEVICE(0x10C4, 0x863C) }, /* MGP Instruments PDS100 */
+ { USB_DEVICE(0x10C4, 0x8664) }, /* AC-Services CAN-IF */
+ { USB_DEVICE(0x10C4, 0x8665) }, /* AC-Services OBD-IF */
+ { USB_DEVICE(0x10C4, 0x87ED) }, /* IMST USB-Stick for Smart Meter */
+ { USB_DEVICE(0x10C4, 0x8856) }, /* CEL EM357 ZigBee USB Stick - LR */
+ { USB_DEVICE(0x10C4, 0x8857) }, /* CEL EM357 ZigBee USB Stick */
+ { USB_DEVICE(0x10C4, 0x88A4) }, /* MMB Networks ZigBee USB Device */
+ { USB_DEVICE(0x10C4, 0x88A5) }, /* Planet Innovation Ingeni ZigBee USB Device */
+ { USB_DEVICE(0x10C4, 0x88D8) }, /* Acuity Brands nLight Air Adapter */
+ { USB_DEVICE(0x10C4, 0x88FB) }, /* CESINEL MEDCAL STII Network Analyzer */
+ { USB_DEVICE(0x10C4, 0x8938) }, /* CESINEL MEDCAL S II Network Analyzer */
+ { USB_DEVICE(0x10C4, 0x8946) }, /* Ketra N1 Wireless Interface */
+ { USB_DEVICE(0x10C4, 0x8962) }, /* Brim Brothers charging dock */
+ { USB_DEVICE(0x10C4, 0x8977) }, /* CEL MeshWorks DevKit Device */
+ { USB_DEVICE(0x10C4, 0x8998) }, /* KCF Technologies PRN */
+ { USB_DEVICE(0x10C4, 0x89A4) }, /* CESINEL FTBC Flexible Thyristor Bridge Controller */
+ { USB_DEVICE(0x10C4, 0x89FB) }, /* Qivicon ZigBee USB Radio Stick */
+ { USB_DEVICE(0x10C4, 0x8A2A) }, /* HubZ dual ZigBee and Z-Wave dongle */
+ { USB_DEVICE(0x10C4, 0x8A5B) }, /* CEL EM3588 ZigBee USB Stick */
+ { USB_DEVICE(0x10C4, 0x8A5E) }, /* CEL EM3588 ZigBee USB Stick Long Range */
+ { USB_DEVICE(0x10C4, 0x8B34) }, /* Qivicon ZigBee USB Radio Stick */
+ { USB_DEVICE(0x10C4, 0xEA60) }, /* Silicon Labs factory default */
+ { USB_DEVICE(0x10C4, 0xEA61) }, /* Silicon Labs factory default */
+ { USB_DEVICE(0x10C4, 0xEA63) }, /* Silicon Labs Windows Update (CP2101-4/CP2102N) */
+ { USB_DEVICE(0x10C4, 0xEA70) }, /* Silicon Labs factory default */
+ { USB_DEVICE(0x10C4, 0xEA71) }, /* Infinity GPS-MIC-1 Radio Monophone */
+ { USB_DEVICE(0x10C4, 0xEA7A) }, /* Silicon Labs Windows Update (CP2105) */
+ { USB_DEVICE(0x10C4, 0xEA7B) }, /* Silicon Labs Windows Update (CP2108) */
+ { USB_DEVICE(0x10C4, 0xF001) }, /* Elan Digital Systems USBscope50 */
+ { USB_DEVICE(0x10C4, 0xF002) }, /* Elan Digital Systems USBwave12 */
+ { USB_DEVICE(0x10C4, 0xF003) }, /* Elan Digital Systems USBpulse100 */
+ { USB_DEVICE(0x10C4, 0xF004) }, /* Elan Digital Systems USBcount50 */
+ { USB_DEVICE(0x10C5, 0xEA61) }, /* Silicon Labs MobiData GPRS USB Modem */
+ { USB_DEVICE(0x10CE, 0xEA6A) }, /* Silicon Labs MobiData GPRS USB Modem 100EU */
+ { USB_DEVICE(0x11CA, 0x0212) }, /* Verifone USB to Printer (UART, CP2102) */
+ { USB_DEVICE(0x12B8, 0xEC60) }, /* Link G4 ECU */
+ { USB_DEVICE(0x12B8, 0xEC62) }, /* Link G4+ ECU */
+ { USB_DEVICE(0x13AD, 0x9999) }, /* Baltech card reader */
+ { USB_DEVICE(0x1555, 0x0004) }, /* Owen AC4 USB-RS485 Converter */
+ { USB_DEVICE(0x155A, 0x1006) }, /* ELDAT Easywave RX09 */
+ { USB_DEVICE(0x166A, 0x0201) }, /* Clipsal 5500PACA C-Bus Pascal Automation Controller */
+ { USB_DEVICE(0x166A, 0x0301) }, /* Clipsal 5800PC C-Bus Wireless PC Interface */
+ { USB_DEVICE(0x166A, 0x0303) }, /* Clipsal 5500PCU C-Bus USB interface */
+ { USB_DEVICE(0x166A, 0x0304) }, /* Clipsal 5000CT2 C-Bus Black and White Touchscreen */
+ { USB_DEVICE(0x166A, 0x0305) }, /* Clipsal C-5000CT2 C-Bus Spectrum Colour Touchscreen */
+ { USB_DEVICE(0x166A, 0x0401) }, /* Clipsal L51xx C-Bus Architectural Dimmer */
+ { USB_DEVICE(0x166A, 0x0101) }, /* Clipsal 5560884 C-Bus Multi-room Audio Matrix Switcher */
+ { USB_DEVICE(0x16C0, 0x09B0) }, /* Lunatico Seletek */
+ { USB_DEVICE(0x16C0, 0x09B1) }, /* Lunatico Seletek */
+ { USB_DEVICE(0x16D6, 0x0001) }, /* Jablotron serial interface */
+ { USB_DEVICE(0x16DC, 0x0010) }, /* W-IE-NE-R Plein & Baus GmbH PL512 Power Supply */
+ { USB_DEVICE(0x16DC, 0x0011) }, /* W-IE-NE-R Plein & Baus GmbH RCM Remote Control for MARATON Power Supply */
+ { USB_DEVICE(0x16DC, 0x0012) }, /* W-IE-NE-R Plein & Baus GmbH MPOD Multi Channel Power Supply */
+ { USB_DEVICE(0x16DC, 0x0015) }, /* W-IE-NE-R Plein & Baus GmbH CML Control, Monitoring and Data Logger */
+ { USB_DEVICE(0x17A8, 0x0001) }, /* Kamstrup Optical Eye/3-wire */
+ { USB_DEVICE(0x17A8, 0x0005) }, /* Kamstrup M-Bus Master MultiPort 250D */
+ { USB_DEVICE(0x17A8, 0x0011) }, /* Kamstrup 444 MHz RF sniffer */
+ { USB_DEVICE(0x17A8, 0x0013) }, /* Kamstrup 870 MHz RF sniffer */
+ { USB_DEVICE(0x17A8, 0x0101) }, /* Kamstrup 868 MHz wM-Bus C-Mode Meter Reader (Int Ant) */
+ { USB_DEVICE(0x17A8, 0x0102) }, /* Kamstrup 868 MHz wM-Bus C-Mode Meter Reader (Ext Ant) */
+ { USB_DEVICE(0x17F4, 0xAAAA) }, /* Wavesense Jazz blood glucose meter */
+ { USB_DEVICE(0x1843, 0x0200) }, /* Vaisala USB Instrument Cable */
+ { USB_DEVICE(0x18EF, 0xE00F) }, /* ELV USB-I2C-Interface */
+ { USB_DEVICE(0x18EF, 0xE025) }, /* ELV Marble Sound Board 1 */
+ { USB_DEVICE(0x18EF, 0xE030) }, /* ELV ALC 8xxx Battery Charger */
+ { USB_DEVICE(0x18EF, 0xE032) }, /* ELV TFD500 Data Logger */
+ { USB_DEVICE(0x1901, 0x0190) }, /* GE B850 CP2105 Recorder interface */
+ { USB_DEVICE(0x1901, 0x0193) }, /* GE B650 CP2104 PMC interface */
+ { USB_DEVICE(0x1901, 0x0194) }, /* GE Healthcare Remote Alarm Box */
+ { USB_DEVICE(0x1901, 0x0195) }, /* GE B850/B650/B450 CP2104 DP UART interface */
+ { USB_DEVICE(0x1901, 0x0196) }, /* GE B850 CP2105 DP UART interface */
+ { USB_DEVICE(0x1901, 0x0197) }, /* GE CS1000 M.2 Key E serial interface */
+ { USB_DEVICE(0x1901, 0x0198) }, /* GE CS1000 Display serial interface */
+ { USB_DEVICE(0x199B, 0xBA30) }, /* LORD WSDA-200-USB */
+ { USB_DEVICE(0x19CF, 0x3000) }, /* Parrot NMEA GPS Flight Recorder */
+ { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */
+ { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */
+ { USB_DEVICE(0x1B93, 0x1013) }, /* Phoenix Contact UPS Device */
+ { USB_DEVICE(0x1BA4, 0x0002) }, /* Silicon Labs 358x factory default */
+ { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */
+ { USB_DEVICE(0x1D6F, 0x0010) }, /* Seluxit ApS RF Dongle */
+ { USB_DEVICE(0x1E29, 0x0102) }, /* Festo CPX-USB */
+ { USB_DEVICE(0x1E29, 0x0501) }, /* Festo CMSP */
+ { USB_DEVICE(0x1FB9, 0x0100) }, /* Lake Shore Model 121 Current Source */
+ { USB_DEVICE(0x1FB9, 0x0200) }, /* Lake Shore Model 218A Temperature Monitor */
+ { USB_DEVICE(0x1FB9, 0x0201) }, /* Lake Shore Model 219 Temperature Monitor */
+ { USB_DEVICE(0x1FB9, 0x0202) }, /* Lake Shore Model 233 Temperature Transmitter */
+ { USB_DEVICE(0x1FB9, 0x0203) }, /* Lake Shore Model 235 Temperature Transmitter */
+ { USB_DEVICE(0x1FB9, 0x0300) }, /* Lake Shore Model 335 Temperature Controller */
+ { USB_DEVICE(0x1FB9, 0x0301) }, /* Lake Shore Model 336 Temperature Controller */
+ { USB_DEVICE(0x1FB9, 0x0302) }, /* Lake Shore Model 350 Temperature Controller */
+ { USB_DEVICE(0x1FB9, 0x0303) }, /* Lake Shore Model 371 AC Bridge */
+ { USB_DEVICE(0x1FB9, 0x0400) }, /* Lake Shore Model 411 Handheld Gaussmeter */
+ { USB_DEVICE(0x1FB9, 0x0401) }, /* Lake Shore Model 425 Gaussmeter */
+ { USB_DEVICE(0x1FB9, 0x0402) }, /* Lake Shore Model 455A Gaussmeter */
+ { USB_DEVICE(0x1FB9, 0x0403) }, /* Lake Shore Model 475A Gaussmeter */
+ { USB_DEVICE(0x1FB9, 0x0404) }, /* Lake Shore Model 465 Three Axis Gaussmeter */
+ { USB_DEVICE(0x1FB9, 0x0600) }, /* Lake Shore Model 625A Superconducting MPS */
+ { USB_DEVICE(0x1FB9, 0x0601) }, /* Lake Shore Model 642A Magnet Power Supply */
+ { USB_DEVICE(0x1FB9, 0x0602) }, /* Lake Shore Model 648 Magnet Power Supply */
+ { USB_DEVICE(0x1FB9, 0x0700) }, /* Lake Shore Model 737 VSM Controller */
+ { USB_DEVICE(0x1FB9, 0x0701) }, /* Lake Shore Model 776 Hall Matrix */
+ { USB_DEVICE(0x2184, 0x0030) }, /* GW Instek GDM-834x Digital Multimeter */
+ { USB_DEVICE(0x2626, 0xEA60) }, /* Aruba Networks 7xxx USB Serial Console */
+ { USB_DEVICE(0x3195, 0xF190) }, /* Link Instruments MSO-19 */
+ { USB_DEVICE(0x3195, 0xF280) }, /* Link Instruments MSO-28 */
+ { USB_DEVICE(0x3195, 0xF281) }, /* Link Instruments MSO-28 */
+ { USB_DEVICE(0x3923, 0x7A0B) }, /* National Instruments USB Serial Console */
+ { USB_DEVICE(0x413C, 0x9500) }, /* DW700 GPS USB interface */
+ { } /* Terminating Entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+struct cp210x_serial_private {
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gc;
+ bool gpio_registered;
+ u16 gpio_pushpull;
+ u16 gpio_altfunc;
+ u16 gpio_input;
+#endif
+ u8 partnum;
+ u32 fw_version;
+ speed_t min_speed;
+ speed_t max_speed;
+ bool use_actual_rate;
+ bool no_flow_control;
+ bool no_event_mode;
+};
+
+enum cp210x_event_state {
+ ES_DATA,
+ ES_ESCAPE,
+ ES_LSR,
+ ES_LSR_DATA_0,
+ ES_LSR_DATA_1,
+ ES_MSR
+};
+
+struct cp210x_port_private {
+ u8 bInterfaceNumber;
+ bool event_mode;
+ enum cp210x_event_state event_state;
+ u8 lsr;
+
+ struct mutex mutex;
+ bool crtscts;
+ bool dtr;
+ bool rts;
+};
+
+static struct usb_serial_driver cp210x_device = {
+ .driver = {
+ .name = "cp210x",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .bulk_in_size = 256,
+ .bulk_out_size = 256,
+ .open = cp210x_open,
+ .close = cp210x_close,
+ .break_ctl = cp210x_break_ctl,
+ .set_termios = cp210x_set_termios,
+ .tx_empty = cp210x_tx_empty,
+ .throttle = usb_serial_generic_throttle,
+ .unthrottle = usb_serial_generic_unthrottle,
+ .tiocmget = cp210x_tiocmget,
+ .tiocmset = cp210x_tiocmset,
+ .get_icount = usb_serial_generic_get_icount,
+ .attach = cp210x_attach,
+ .disconnect = cp210x_disconnect,
+ .release = cp210x_release,
+ .port_probe = cp210x_port_probe,
+ .port_remove = cp210x_port_remove,
+ .dtr_rts = cp210x_dtr_rts,
+ .process_read_urb = cp210x_process_read_urb,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &cp210x_device, NULL
+};
+
+/* Config request types */
+#define REQTYPE_HOST_TO_INTERFACE 0x41
+#define REQTYPE_INTERFACE_TO_HOST 0xc1
+#define REQTYPE_HOST_TO_DEVICE 0x40
+#define REQTYPE_DEVICE_TO_HOST 0xc0
+
+/* Config request codes */
+#define CP210X_IFC_ENABLE 0x00
+#define CP210X_SET_BAUDDIV 0x01
+#define CP210X_GET_BAUDDIV 0x02
+#define CP210X_SET_LINE_CTL 0x03
+#define CP210X_GET_LINE_CTL 0x04
+#define CP210X_SET_BREAK 0x05
+#define CP210X_IMM_CHAR 0x06
+#define CP210X_SET_MHS 0x07
+#define CP210X_GET_MDMSTS 0x08
+#define CP210X_SET_XON 0x09
+#define CP210X_SET_XOFF 0x0A
+#define CP210X_SET_EVENTMASK 0x0B
+#define CP210X_GET_EVENTMASK 0x0C
+#define CP210X_SET_CHAR 0x0D
+#define CP210X_GET_CHARS 0x0E
+#define CP210X_GET_PROPS 0x0F
+#define CP210X_GET_COMM_STATUS 0x10
+#define CP210X_RESET 0x11
+#define CP210X_PURGE 0x12
+#define CP210X_SET_FLOW 0x13
+#define CP210X_GET_FLOW 0x14
+#define CP210X_EMBED_EVENTS 0x15
+#define CP210X_GET_EVENTSTATE 0x16
+#define CP210X_SET_CHARS 0x19
+#define CP210X_GET_BAUDRATE 0x1D
+#define CP210X_SET_BAUDRATE 0x1E
+#define CP210X_VENDOR_SPECIFIC 0xFF
+
+/* CP210X_IFC_ENABLE */
+#define UART_ENABLE 0x0001
+#define UART_DISABLE 0x0000
+
+/* CP210X_(SET|GET)_BAUDDIV */
+#define BAUD_RATE_GEN_FREQ 0x384000
+
+/* CP210X_(SET|GET)_LINE_CTL */
+#define BITS_DATA_MASK 0X0f00
+#define BITS_DATA_5 0X0500
+#define BITS_DATA_6 0X0600
+#define BITS_DATA_7 0X0700
+#define BITS_DATA_8 0X0800
+#define BITS_DATA_9 0X0900
+
+#define BITS_PARITY_MASK 0x00f0
+#define BITS_PARITY_NONE 0x0000
+#define BITS_PARITY_ODD 0x0010
+#define BITS_PARITY_EVEN 0x0020
+#define BITS_PARITY_MARK 0x0030
+#define BITS_PARITY_SPACE 0x0040
+
+#define BITS_STOP_MASK 0x000f
+#define BITS_STOP_1 0x0000
+#define BITS_STOP_1_5 0x0001
+#define BITS_STOP_2 0x0002
+
+/* CP210X_SET_BREAK */
+#define BREAK_ON 0x0001
+#define BREAK_OFF 0x0000
+
+/* CP210X_(SET_MHS|GET_MDMSTS) */
+#define CONTROL_DTR 0x0001
+#define CONTROL_RTS 0x0002
+#define CONTROL_CTS 0x0010
+#define CONTROL_DSR 0x0020
+#define CONTROL_RING 0x0040
+#define CONTROL_DCD 0x0080
+#define CONTROL_WRITE_DTR 0x0100
+#define CONTROL_WRITE_RTS 0x0200
+
+/* CP210X_(GET|SET)_CHARS */
+struct cp210x_special_chars {
+ u8 bEofChar;
+ u8 bErrorChar;
+ u8 bBreakChar;
+ u8 bEventChar;
+ u8 bXonChar;
+ u8 bXoffChar;
+};
+
+/* CP210X_VENDOR_SPECIFIC values */
+#define CP210X_GET_FW_VER 0x000E
+#define CP210X_READ_2NCONFIG 0x000E
+#define CP210X_GET_FW_VER_2N 0x0010
+#define CP210X_READ_LATCH 0x00C2
+#define CP210X_GET_PARTNUM 0x370B
+#define CP210X_GET_PORTCONFIG 0x370C
+#define CP210X_GET_DEVICEMODE 0x3711
+#define CP210X_WRITE_LATCH 0x37E1
+
+/* Part number definitions */
+#define CP210X_PARTNUM_CP2101 0x01
+#define CP210X_PARTNUM_CP2102 0x02
+#define CP210X_PARTNUM_CP2103 0x03
+#define CP210X_PARTNUM_CP2104 0x04
+#define CP210X_PARTNUM_CP2105 0x05
+#define CP210X_PARTNUM_CP2108 0x08
+#define CP210X_PARTNUM_CP2102N_QFN28 0x20
+#define CP210X_PARTNUM_CP2102N_QFN24 0x21
+#define CP210X_PARTNUM_CP2102N_QFN20 0x22
+#define CP210X_PARTNUM_UNKNOWN 0xFF
+
+/* CP210X_GET_COMM_STATUS returns these 0x13 bytes */
+struct cp210x_comm_status {
+ __le32 ulErrors;
+ __le32 ulHoldReasons;
+ __le32 ulAmountInInQueue;
+ __le32 ulAmountInOutQueue;
+ u8 bEofReceived;
+ u8 bWaitForImmediate;
+ u8 bReserved;
+} __packed;
+
+/*
+ * CP210X_PURGE - 16 bits passed in wValue of USB request.
+ * SiLabs app note AN571 gives a strange description of the 4 bits:
+ * bit 0 or bit 2 clears the transmit queue and 1 or 3 receive.
+ * writing 1 to all, however, purges cp2108 well enough to avoid the hang.
+ */
+#define PURGE_ALL 0x000f
+
+/* CP210X_EMBED_EVENTS */
+#define CP210X_ESCCHAR 0xec
+
+#define CP210X_LSR_OVERRUN BIT(1)
+#define CP210X_LSR_PARITY BIT(2)
+#define CP210X_LSR_FRAME BIT(3)
+#define CP210X_LSR_BREAK BIT(4)
+
+
+/* CP210X_GET_FLOW/CP210X_SET_FLOW read/write these 0x10 bytes */
+struct cp210x_flow_ctl {
+ __le32 ulControlHandshake;
+ __le32 ulFlowReplace;
+ __le32 ulXonLimit;
+ __le32 ulXoffLimit;
+};
+
+/* cp210x_flow_ctl::ulControlHandshake */
+#define CP210X_SERIAL_DTR_MASK GENMASK(1, 0)
+#define CP210X_SERIAL_DTR_INACTIVE (0 << 0)
+#define CP210X_SERIAL_DTR_ACTIVE (1 << 0)
+#define CP210X_SERIAL_DTR_FLOW_CTL (2 << 0)
+#define CP210X_SERIAL_CTS_HANDSHAKE BIT(3)
+#define CP210X_SERIAL_DSR_HANDSHAKE BIT(4)
+#define CP210X_SERIAL_DCD_HANDSHAKE BIT(5)
+#define CP210X_SERIAL_DSR_SENSITIVITY BIT(6)
+
+/* cp210x_flow_ctl::ulFlowReplace */
+#define CP210X_SERIAL_AUTO_TRANSMIT BIT(0)
+#define CP210X_SERIAL_AUTO_RECEIVE BIT(1)
+#define CP210X_SERIAL_ERROR_CHAR BIT(2)
+#define CP210X_SERIAL_NULL_STRIPPING BIT(3)
+#define CP210X_SERIAL_BREAK_CHAR BIT(4)
+#define CP210X_SERIAL_RTS_MASK GENMASK(7, 6)
+#define CP210X_SERIAL_RTS_INACTIVE (0 << 6)
+#define CP210X_SERIAL_RTS_ACTIVE (1 << 6)
+#define CP210X_SERIAL_RTS_FLOW_CTL (2 << 6)
+#define CP210X_SERIAL_XOFF_CONTINUE BIT(31)
+
+/* CP210X_VENDOR_SPECIFIC, CP210X_GET_DEVICEMODE call reads these 0x2 bytes. */
+struct cp210x_pin_mode {
+ u8 eci;
+ u8 sci;
+};
+
+#define CP210X_PIN_MODE_MODEM 0
+#define CP210X_PIN_MODE_GPIO BIT(0)
+
+/*
+ * CP210X_VENDOR_SPECIFIC, CP210X_GET_PORTCONFIG call reads these 0xf bytes
+ * on a CP2105 chip. Structure needs padding due to unused/unspecified bytes.
+ */
+struct cp210x_dual_port_config {
+ __le16 gpio_mode;
+ u8 __pad0[2];
+ __le16 reset_state;
+ u8 __pad1[4];
+ __le16 suspend_state;
+ u8 sci_cfg;
+ u8 eci_cfg;
+ u8 device_cfg;
+} __packed;
+
+/*
+ * CP210X_VENDOR_SPECIFIC, CP210X_GET_PORTCONFIG call reads these 0xd bytes
+ * on a CP2104 chip. Structure needs padding due to unused/unspecified bytes.
+ */
+struct cp210x_single_port_config {
+ __le16 gpio_mode;
+ u8 __pad0[2];
+ __le16 reset_state;
+ u8 __pad1[4];
+ __le16 suspend_state;
+ u8 device_cfg;
+} __packed;
+
+/* GPIO modes */
+#define CP210X_SCI_GPIO_MODE_OFFSET 9
+#define CP210X_SCI_GPIO_MODE_MASK GENMASK(11, 9)
+
+#define CP210X_ECI_GPIO_MODE_OFFSET 2
+#define CP210X_ECI_GPIO_MODE_MASK GENMASK(3, 2)
+
+#define CP210X_GPIO_MODE_OFFSET 8
+#define CP210X_GPIO_MODE_MASK GENMASK(11, 8)
+
+/* CP2105 port configuration values */
+#define CP2105_GPIO0_TXLED_MODE BIT(0)
+#define CP2105_GPIO1_RXLED_MODE BIT(1)
+#define CP2105_GPIO1_RS485_MODE BIT(2)
+
+/* CP2104 port configuration values */
+#define CP2104_GPIO0_TXLED_MODE BIT(0)
+#define CP2104_GPIO1_RXLED_MODE BIT(1)
+#define CP2104_GPIO2_RS485_MODE BIT(2)
+
+struct cp210x_quad_port_state {
+ __le16 gpio_mode_pb0;
+ __le16 gpio_mode_pb1;
+ __le16 gpio_mode_pb2;
+ __le16 gpio_mode_pb3;
+ __le16 gpio_mode_pb4;
+
+ __le16 gpio_lowpower_pb0;
+ __le16 gpio_lowpower_pb1;
+ __le16 gpio_lowpower_pb2;
+ __le16 gpio_lowpower_pb3;
+ __le16 gpio_lowpower_pb4;
+
+ __le16 gpio_latch_pb0;
+ __le16 gpio_latch_pb1;
+ __le16 gpio_latch_pb2;
+ __le16 gpio_latch_pb3;
+ __le16 gpio_latch_pb4;
+};
+
+/*
+ * CP210X_VENDOR_SPECIFIC, CP210X_GET_PORTCONFIG call reads these 0x49 bytes
+ * on a CP2108 chip.
+ *
+ * See https://www.silabs.com/documents/public/application-notes/an978-cp210x-usb-to-uart-api-specification.pdf
+ */
+struct cp210x_quad_port_config {
+ struct cp210x_quad_port_state reset_state;
+ struct cp210x_quad_port_state suspend_state;
+ u8 ipdelay_ifc[4];
+ u8 enhancedfxn_ifc[4];
+ u8 enhancedfxn_device;
+ u8 extclkfreq[4];
+} __packed;
+
+#define CP2108_EF_IFC_GPIO_TXLED 0x01
+#define CP2108_EF_IFC_GPIO_RXLED 0x02
+#define CP2108_EF_IFC_GPIO_RS485 0x04
+#define CP2108_EF_IFC_GPIO_RS485_LOGIC 0x08
+#define CP2108_EF_IFC_GPIO_CLOCK 0x10
+#define CP2108_EF_IFC_DYNAMIC_SUSPEND 0x40
+
+/* CP2102N configuration array indices */
+#define CP210X_2NCONFIG_CONFIG_VERSION_IDX 2
+#define CP210X_2NCONFIG_GPIO_MODE_IDX 581
+#define CP210X_2NCONFIG_GPIO_RSTLATCH_IDX 587
+#define CP210X_2NCONFIG_GPIO_CONTROL_IDX 600
+
+/* CP2102N QFN20 port configuration values */
+#define CP2102N_QFN20_GPIO2_TXLED_MODE BIT(2)
+#define CP2102N_QFN20_GPIO3_RXLED_MODE BIT(3)
+#define CP2102N_QFN20_GPIO1_RS485_MODE BIT(4)
+#define CP2102N_QFN20_GPIO0_CLK_MODE BIT(6)
+
+/*
+ * CP210X_VENDOR_SPECIFIC, CP210X_WRITE_LATCH call writes these 0x02 bytes
+ * for CP2102N, CP2103, CP2104 and CP2105.
+ */
+struct cp210x_gpio_write {
+ u8 mask;
+ u8 state;
+};
+
+/*
+ * CP210X_VENDOR_SPECIFIC, CP210X_WRITE_LATCH call writes these 0x04 bytes
+ * for CP2108.
+ */
+struct cp210x_gpio_write16 {
+ __le16 mask;
+ __le16 state;
+};
+
+/*
+ * Helper to get interface number when we only have struct usb_serial.
+ */
+static u8 cp210x_interface_num(struct usb_serial *serial)
+{
+ struct usb_host_interface *cur_altsetting;
+
+ cur_altsetting = serial->interface->cur_altsetting;
+
+ return cur_altsetting->desc.bInterfaceNumber;
+}
+
+/*
+ * Reads a variable-sized block of CP210X_ registers, identified by req.
+ * Returns data into buf in native USB byte order.
+ */
+static int cp210x_read_reg_block(struct usb_serial_port *port, u8 req,
+ void *buf, int bufsize)
+{
+ struct usb_serial *serial = port->serial;
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ int result;
+
+
+ result = usb_control_msg_recv(serial->dev, 0, req,
+ REQTYPE_INTERFACE_TO_HOST, 0,
+ port_priv->bInterfaceNumber, buf, bufsize,
+ USB_CTRL_SET_TIMEOUT, GFP_KERNEL);
+ if (result) {
+ dev_err(&port->dev, "failed get req 0x%x size %d status: %d\n",
+ req, bufsize, result);
+ return result;
+ }
+
+ return 0;
+}
+
+/*
+ * Reads any 8-bit CP210X_ register identified by req.
+ */
+static int cp210x_read_u8_reg(struct usb_serial_port *port, u8 req, u8 *val)
+{
+ return cp210x_read_reg_block(port, req, val, sizeof(*val));
+}
+
+/*
+ * Reads a variable-sized vendor block of CP210X_ registers, identified by val.
+ * Returns data into buf in native USB byte order.
+ */
+static int cp210x_read_vendor_block(struct usb_serial *serial, u8 type, u16 val,
+ void *buf, int bufsize)
+{
+ int result;
+
+ result = usb_control_msg_recv(serial->dev, 0, CP210X_VENDOR_SPECIFIC,
+ type, val, cp210x_interface_num(serial), buf, bufsize,
+ USB_CTRL_GET_TIMEOUT, GFP_KERNEL);
+ if (result) {
+ dev_err(&serial->interface->dev,
+ "failed to get vendor val 0x%04x size %d: %d\n", val,
+ bufsize, result);
+ return result;
+ }
+
+ return 0;
+}
+
+/*
+ * Writes any 16-bit CP210X_ register (req) whose value is passed
+ * entirely in the wValue field of the USB request.
+ */
+static int cp210x_write_u16_reg(struct usb_serial_port *port, u8 req, u16 val)
+{
+ struct usb_serial *serial = port->serial;
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ int result;
+
+ result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ req, REQTYPE_HOST_TO_INTERFACE, val,
+ port_priv->bInterfaceNumber, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (result < 0) {
+ dev_err(&port->dev, "failed set request 0x%x status: %d\n",
+ req, result);
+ }
+
+ return result;
+}
+
+/*
+ * Writes a variable-sized block of CP210X_ registers, identified by req.
+ * Data in buf must be in native USB byte order.
+ */
+static int cp210x_write_reg_block(struct usb_serial_port *port, u8 req,
+ void *buf, int bufsize)
+{
+ struct usb_serial *serial = port->serial;
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ int result;
+
+ result = usb_control_msg_send(serial->dev, 0, req,
+ REQTYPE_HOST_TO_INTERFACE, 0,
+ port_priv->bInterfaceNumber, buf, bufsize,
+ USB_CTRL_SET_TIMEOUT, GFP_KERNEL);
+ if (result) {
+ dev_err(&port->dev, "failed set req 0x%x size %d status: %d\n",
+ req, bufsize, result);
+ return result;
+ }
+
+ return 0;
+}
+
+/*
+ * Writes any 32-bit CP210X_ register identified by req.
+ */
+static int cp210x_write_u32_reg(struct usb_serial_port *port, u8 req, u32 val)
+{
+ __le32 le32_val;
+
+ le32_val = cpu_to_le32(val);
+
+ return cp210x_write_reg_block(port, req, &le32_val, sizeof(le32_val));
+}
+
+#ifdef CONFIG_GPIOLIB
+/*
+ * Writes a variable-sized vendor block of CP210X_ registers, identified by val.
+ * Data in buf must be in native USB byte order.
+ */
+static int cp210x_write_vendor_block(struct usb_serial *serial, u8 type,
+ u16 val, void *buf, int bufsize)
+{
+ int result;
+
+ result = usb_control_msg_send(serial->dev, 0, CP210X_VENDOR_SPECIFIC,
+ type, val, cp210x_interface_num(serial), buf, bufsize,
+ USB_CTRL_SET_TIMEOUT, GFP_KERNEL);
+ if (result) {
+ dev_err(&serial->interface->dev,
+ "failed to set vendor val 0x%04x size %d: %d\n", val,
+ bufsize, result);
+ return result;
+ }
+
+ return 0;
+}
+#endif
+
+static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ int result;
+
+ result = cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_ENABLE);
+ if (result) {
+ dev_err(&port->dev, "%s - Unable to enable UART\n", __func__);
+ return result;
+ }
+
+ if (tty)
+ cp210x_set_termios(tty, port, NULL);
+
+ result = usb_serial_generic_open(tty, port);
+ if (result)
+ goto err_disable;
+
+ return 0;
+
+err_disable:
+ cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_DISABLE);
+ port_priv->event_mode = false;
+
+ return result;
+}
+
+static void cp210x_close(struct usb_serial_port *port)
+{
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+
+ usb_serial_generic_close(port);
+
+ /* Clear both queues; cp2108 needs this to avoid an occasional hang */
+ cp210x_write_u16_reg(port, CP210X_PURGE, PURGE_ALL);
+
+ cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_DISABLE);
+
+ /* Disabling the interface disables event-insertion mode. */
+ port_priv->event_mode = false;
+}
+
+static void cp210x_process_lsr(struct usb_serial_port *port, unsigned char lsr, char *flag)
+{
+ if (lsr & CP210X_LSR_BREAK) {
+ port->icount.brk++;
+ *flag = TTY_BREAK;
+ } else if (lsr & CP210X_LSR_PARITY) {
+ port->icount.parity++;
+ *flag = TTY_PARITY;
+ } else if (lsr & CP210X_LSR_FRAME) {
+ port->icount.frame++;
+ *flag = TTY_FRAME;
+ }
+
+ if (lsr & CP210X_LSR_OVERRUN) {
+ port->icount.overrun++;
+ tty_insert_flip_char(&port->port, 0, TTY_OVERRUN);
+ }
+}
+
+static bool cp210x_process_char(struct usb_serial_port *port, unsigned char *ch, char *flag)
+{
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+
+ switch (port_priv->event_state) {
+ case ES_DATA:
+ if (*ch == CP210X_ESCCHAR) {
+ port_priv->event_state = ES_ESCAPE;
+ break;
+ }
+ return false;
+ case ES_ESCAPE:
+ switch (*ch) {
+ case 0:
+ dev_dbg(&port->dev, "%s - escape char\n", __func__);
+ *ch = CP210X_ESCCHAR;
+ port_priv->event_state = ES_DATA;
+ return false;
+ case 1:
+ port_priv->event_state = ES_LSR_DATA_0;
+ break;
+ case 2:
+ port_priv->event_state = ES_LSR;
+ break;
+ case 3:
+ port_priv->event_state = ES_MSR;
+ break;
+ default:
+ dev_err(&port->dev, "malformed event 0x%02x\n", *ch);
+ port_priv->event_state = ES_DATA;
+ break;
+ }
+ break;
+ case ES_LSR_DATA_0:
+ port_priv->lsr = *ch;
+ port_priv->event_state = ES_LSR_DATA_1;
+ break;
+ case ES_LSR_DATA_1:
+ dev_dbg(&port->dev, "%s - lsr = 0x%02x, data = 0x%02x\n",
+ __func__, port_priv->lsr, *ch);
+ cp210x_process_lsr(port, port_priv->lsr, flag);
+ port_priv->event_state = ES_DATA;
+ return false;
+ case ES_LSR:
+ dev_dbg(&port->dev, "%s - lsr = 0x%02x\n", __func__, *ch);
+ port_priv->lsr = *ch;
+ cp210x_process_lsr(port, port_priv->lsr, flag);
+ port_priv->event_state = ES_DATA;
+ break;
+ case ES_MSR:
+ dev_dbg(&port->dev, "%s - msr = 0x%02x\n", __func__, *ch);
+ /* unimplemented */
+ port_priv->event_state = ES_DATA;
+ break;
+ }
+
+ return true;
+}
+
+static void cp210x_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ unsigned char *ch = urb->transfer_buffer;
+ char flag;
+ int i;
+
+ if (!urb->actual_length)
+ return;
+
+ if (port_priv->event_mode) {
+ for (i = 0; i < urb->actual_length; i++, ch++) {
+ flag = TTY_NORMAL;
+
+ if (cp210x_process_char(port, ch, &flag))
+ continue;
+
+ tty_insert_flip_char(&port->port, *ch, flag);
+ }
+ } else {
+ tty_insert_flip_string(&port->port, ch, urb->actual_length);
+ }
+ tty_flip_buffer_push(&port->port);
+}
+
+/*
+ * Read how many bytes are waiting in the TX queue.
+ */
+static int cp210x_get_tx_queue_byte_count(struct usb_serial_port *port,
+ u32 *count)
+{
+ struct usb_serial *serial = port->serial;
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ struct cp210x_comm_status sts;
+ int result;
+
+ result = usb_control_msg_recv(serial->dev, 0, CP210X_GET_COMM_STATUS,
+ REQTYPE_INTERFACE_TO_HOST, 0,
+ port_priv->bInterfaceNumber, &sts, sizeof(sts),
+ USB_CTRL_GET_TIMEOUT, GFP_KERNEL);
+ if (result) {
+ dev_err(&port->dev, "failed to get comm status: %d\n", result);
+ return result;
+ }
+
+ *count = le32_to_cpu(sts.ulAmountInOutQueue);
+
+ return 0;
+}
+
+static bool cp210x_tx_empty(struct usb_serial_port *port)
+{
+ int err;
+ u32 count;
+
+ err = cp210x_get_tx_queue_byte_count(port, &count);
+ if (err)
+ return true;
+
+ return !count;
+}
+
+struct cp210x_rate {
+ speed_t rate;
+ speed_t high;
+};
+
+static const struct cp210x_rate cp210x_an205_table1[] = {
+ { 300, 300 },
+ { 600, 600 },
+ { 1200, 1200 },
+ { 1800, 1800 },
+ { 2400, 2400 },
+ { 4000, 4000 },
+ { 4800, 4803 },
+ { 7200, 7207 },
+ { 9600, 9612 },
+ { 14400, 14428 },
+ { 16000, 16062 },
+ { 19200, 19250 },
+ { 28800, 28912 },
+ { 38400, 38601 },
+ { 51200, 51558 },
+ { 56000, 56280 },
+ { 57600, 58053 },
+ { 64000, 64111 },
+ { 76800, 77608 },
+ { 115200, 117028 },
+ { 128000, 129347 },
+ { 153600, 156868 },
+ { 230400, 237832 },
+ { 250000, 254234 },
+ { 256000, 273066 },
+ { 460800, 491520 },
+ { 500000, 567138 },
+ { 576000, 670254 },
+ { 921600, UINT_MAX }
+};
+
+/*
+ * Quantises the baud rate as per AN205 Table 1
+ */
+static speed_t cp210x_get_an205_rate(speed_t baud)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cp210x_an205_table1); ++i) {
+ if (baud <= cp210x_an205_table1[i].high)
+ break;
+ }
+
+ return cp210x_an205_table1[i].rate;
+}
+
+static speed_t cp210x_get_actual_rate(speed_t baud)
+{
+ unsigned int prescale = 1;
+ unsigned int div;
+
+ if (baud <= 365)
+ prescale = 4;
+
+ div = DIV_ROUND_CLOSEST(48000000, 2 * prescale * baud);
+ baud = 48000000 / (2 * prescale * div);
+
+ return baud;
+}
+
+/*
+ * CP2101 supports the following baud rates:
+ *
+ * 300, 600, 1200, 1800, 2400, 4800, 7200, 9600, 14400, 19200, 28800,
+ * 38400, 56000, 57600, 115200, 128000, 230400, 460800, 921600
+ *
+ * CP2102 and CP2103 support the following additional rates:
+ *
+ * 4000, 16000, 51200, 64000, 76800, 153600, 250000, 256000, 500000,
+ * 576000
+ *
+ * The device will map a requested rate to a supported one, but the result
+ * of requests for rates greater than 1053257 is undefined (see AN205).
+ *
+ * CP2104, CP2105 and CP2110 support most rates up to 2M, 921k and 1M baud,
+ * respectively, with an error less than 1%. The actual rates are determined
+ * by
+ *
+ * div = round(freq / (2 x prescale x request))
+ * actual = freq / (2 x prescale x div)
+ *
+ * For CP2104 and CP2105 freq is 48Mhz and prescale is 4 for request <= 365bps
+ * or 1 otherwise.
+ * For CP2110 freq is 24Mhz and prescale is 4 for request <= 300bps or 1
+ * otherwise.
+ */
+static void cp210x_change_speed(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct usb_serial *serial = port->serial;
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ u32 baud;
+
+ if (tty->termios.c_ospeed == 0)
+ return;
+
+ /*
+ * This maps the requested rate to the actual rate, a valid rate on
+ * cp2102 or cp2103, or to an arbitrary rate in [1M, max_speed].
+ */
+ baud = clamp(tty->termios.c_ospeed, priv->min_speed, priv->max_speed);
+
+ if (priv->use_actual_rate)
+ baud = cp210x_get_actual_rate(baud);
+ else if (baud < 1000000)
+ baud = cp210x_get_an205_rate(baud);
+
+ dev_dbg(&port->dev, "%s - setting baud rate to %u\n", __func__, baud);
+ if (cp210x_write_u32_reg(port, CP210X_SET_BAUDRATE, baud)) {
+ dev_warn(&port->dev, "failed to set baud rate to %u\n", baud);
+ if (old_termios)
+ baud = old_termios->c_ospeed;
+ else
+ baud = 9600;
+ }
+
+ tty_encode_baud_rate(tty, baud, baud);
+}
+
+static void cp210x_enable_event_mode(struct usb_serial_port *port)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(port->serial);
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ int ret;
+
+ if (port_priv->event_mode)
+ return;
+
+ if (priv->no_event_mode)
+ return;
+
+ port_priv->event_state = ES_DATA;
+ port_priv->event_mode = true;
+
+ ret = cp210x_write_u16_reg(port, CP210X_EMBED_EVENTS, CP210X_ESCCHAR);
+ if (ret) {
+ dev_err(&port->dev, "failed to enable events: %d\n", ret);
+ port_priv->event_mode = false;
+ }
+}
+
+static void cp210x_disable_event_mode(struct usb_serial_port *port)
+{
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ int ret;
+
+ if (!port_priv->event_mode)
+ return;
+
+ ret = cp210x_write_u16_reg(port, CP210X_EMBED_EVENTS, 0);
+ if (ret) {
+ dev_err(&port->dev, "failed to disable events: %d\n", ret);
+ return;
+ }
+
+ port_priv->event_mode = false;
+}
+
+static bool cp210x_termios_change(const struct ktermios *a, const struct ktermios *b)
+{
+ bool iflag_change, cc_change;
+
+ iflag_change = ((a->c_iflag ^ b->c_iflag) & (INPCK | IXON | IXOFF));
+ cc_change = a->c_cc[VSTART] != b->c_cc[VSTART] ||
+ a->c_cc[VSTOP] != b->c_cc[VSTOP];
+
+ return tty_termios_hw_change(a, b) || iflag_change || cc_change;
+}
+
+static void cp210x_set_flow_control(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(port->serial);
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ struct cp210x_special_chars chars;
+ struct cp210x_flow_ctl flow_ctl;
+ u32 flow_repl;
+ u32 ctl_hs;
+ bool crtscts;
+ int ret;
+
+ /*
+ * Some CP2102N interpret ulXonLimit as ulFlowReplace (erratum
+ * CP2102N_E104). Report back that flow control is not supported.
+ */
+ if (priv->no_flow_control) {
+ tty->termios.c_cflag &= ~CRTSCTS;
+ tty->termios.c_iflag &= ~(IXON | IXOFF);
+ }
+
+ if (tty->termios.c_ospeed != 0 &&
+ old_termios && old_termios->c_ospeed != 0 &&
+ C_CRTSCTS(tty) == (old_termios->c_cflag & CRTSCTS) &&
+ I_IXON(tty) == (old_termios->c_iflag & IXON) &&
+ I_IXOFF(tty) == (old_termios->c_iflag & IXOFF) &&
+ START_CHAR(tty) == old_termios->c_cc[VSTART] &&
+ STOP_CHAR(tty) == old_termios->c_cc[VSTOP]) {
+ return;
+ }
+
+ if (I_IXON(tty) || I_IXOFF(tty)) {
+ memset(&chars, 0, sizeof(chars));
+
+ chars.bXonChar = START_CHAR(tty);
+ chars.bXoffChar = STOP_CHAR(tty);
+
+ ret = cp210x_write_reg_block(port, CP210X_SET_CHARS, &chars,
+ sizeof(chars));
+ if (ret) {
+ dev_err(&port->dev, "failed to set special chars: %d\n",
+ ret);
+ }
+ }
+
+ mutex_lock(&port_priv->mutex);
+
+ if (tty->termios.c_ospeed == 0) {
+ port_priv->dtr = false;
+ port_priv->rts = false;
+ } else if (old_termios && old_termios->c_ospeed == 0) {
+ port_priv->dtr = true;
+ port_priv->rts = true;
+ }
+
+ ret = cp210x_read_reg_block(port, CP210X_GET_FLOW, &flow_ctl,
+ sizeof(flow_ctl));
+ if (ret)
+ goto out_unlock;
+
+ ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake);
+ flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace);
+
+ ctl_hs &= ~CP210X_SERIAL_DSR_HANDSHAKE;
+ ctl_hs &= ~CP210X_SERIAL_DCD_HANDSHAKE;
+ ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY;
+ ctl_hs &= ~CP210X_SERIAL_DTR_MASK;
+ if (port_priv->dtr)
+ ctl_hs |= CP210X_SERIAL_DTR_ACTIVE;
+ else
+ ctl_hs |= CP210X_SERIAL_DTR_INACTIVE;
+
+ flow_repl &= ~CP210X_SERIAL_RTS_MASK;
+ if (C_CRTSCTS(tty)) {
+ ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE;
+ if (port_priv->rts)
+ flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL;
+ else
+ flow_repl |= CP210X_SERIAL_RTS_INACTIVE;
+ crtscts = true;
+ } else {
+ ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE;
+ if (port_priv->rts)
+ flow_repl |= CP210X_SERIAL_RTS_ACTIVE;
+ else
+ flow_repl |= CP210X_SERIAL_RTS_INACTIVE;
+ crtscts = false;
+ }
+
+ if (I_IXOFF(tty)) {
+ flow_repl |= CP210X_SERIAL_AUTO_RECEIVE;
+
+ flow_ctl.ulXonLimit = cpu_to_le32(128);
+ flow_ctl.ulXoffLimit = cpu_to_le32(128);
+ } else {
+ flow_repl &= ~CP210X_SERIAL_AUTO_RECEIVE;
+ }
+
+ if (I_IXON(tty))
+ flow_repl |= CP210X_SERIAL_AUTO_TRANSMIT;
+ else
+ flow_repl &= ~CP210X_SERIAL_AUTO_TRANSMIT;
+
+ dev_dbg(&port->dev, "%s - ctrl = 0x%02x, flow = 0x%02x\n", __func__,
+ ctl_hs, flow_repl);
+
+ flow_ctl.ulControlHandshake = cpu_to_le32(ctl_hs);
+ flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl);
+
+ ret = cp210x_write_reg_block(port, CP210X_SET_FLOW, &flow_ctl,
+ sizeof(flow_ctl));
+ if (ret)
+ goto out_unlock;
+
+ port_priv->crtscts = crtscts;
+out_unlock:
+ mutex_unlock(&port_priv->mutex);
+}
+
+static void cp210x_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(port->serial);
+ u16 bits;
+ int ret;
+
+ if (old_termios && !cp210x_termios_change(&tty->termios, old_termios) &&
+ tty->termios.c_ospeed != 0)
+ return;
+
+ if (!old_termios || tty->termios.c_ospeed != old_termios->c_ospeed)
+ cp210x_change_speed(tty, port, old_termios);
+
+ /* CP2101 only supports CS8, 1 stop bit and non-stick parity. */
+ if (priv->partnum == CP210X_PARTNUM_CP2101) {
+ tty->termios.c_cflag &= ~(CSIZE | CSTOPB | CMSPAR);
+ tty->termios.c_cflag |= CS8;
+ }
+
+ bits = 0;
+
+ switch (C_CSIZE(tty)) {
+ case CS5:
+ bits |= BITS_DATA_5;
+ break;
+ case CS6:
+ bits |= BITS_DATA_6;
+ break;
+ case CS7:
+ bits |= BITS_DATA_7;
+ break;
+ case CS8:
+ default:
+ bits |= BITS_DATA_8;
+ break;
+ }
+
+ if (C_PARENB(tty)) {
+ if (C_CMSPAR(tty)) {
+ if (C_PARODD(tty))
+ bits |= BITS_PARITY_MARK;
+ else
+ bits |= BITS_PARITY_SPACE;
+ } else {
+ if (C_PARODD(tty))
+ bits |= BITS_PARITY_ODD;
+ else
+ bits |= BITS_PARITY_EVEN;
+ }
+ }
+
+ if (C_CSTOPB(tty))
+ bits |= BITS_STOP_2;
+ else
+ bits |= BITS_STOP_1;
+
+ ret = cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, bits);
+ if (ret)
+ dev_err(&port->dev, "failed to set line control: %d\n", ret);
+
+ cp210x_set_flow_control(tty, port, old_termios);
+
+ /*
+ * Enable event-insertion mode only if input parity checking is
+ * enabled for now.
+ */
+ if (I_INPCK(tty))
+ cp210x_enable_event_mode(port);
+ else
+ cp210x_disable_event_mode(port);
+}
+
+static int cp210x_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ return cp210x_tiocmset_port(port, set, clear);
+}
+
+static int cp210x_tiocmset_port(struct usb_serial_port *port,
+ unsigned int set, unsigned int clear)
+{
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ struct cp210x_flow_ctl flow_ctl;
+ u32 ctl_hs, flow_repl;
+ u16 control = 0;
+ int ret;
+
+ mutex_lock(&port_priv->mutex);
+
+ if (set & TIOCM_RTS) {
+ port_priv->rts = true;
+ control |= CONTROL_RTS;
+ control |= CONTROL_WRITE_RTS;
+ }
+ if (set & TIOCM_DTR) {
+ port_priv->dtr = true;
+ control |= CONTROL_DTR;
+ control |= CONTROL_WRITE_DTR;
+ }
+ if (clear & TIOCM_RTS) {
+ port_priv->rts = false;
+ control &= ~CONTROL_RTS;
+ control |= CONTROL_WRITE_RTS;
+ }
+ if (clear & TIOCM_DTR) {
+ port_priv->dtr = false;
+ control &= ~CONTROL_DTR;
+ control |= CONTROL_WRITE_DTR;
+ }
+
+ /*
+ * Use SET_FLOW to set DTR and enable/disable auto-RTS when hardware
+ * flow control is enabled.
+ */
+ if (port_priv->crtscts && control & CONTROL_WRITE_RTS) {
+ ret = cp210x_read_reg_block(port, CP210X_GET_FLOW, &flow_ctl,
+ sizeof(flow_ctl));
+ if (ret)
+ goto out_unlock;
+
+ ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake);
+ flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace);
+
+ ctl_hs &= ~CP210X_SERIAL_DTR_MASK;
+ if (port_priv->dtr)
+ ctl_hs |= CP210X_SERIAL_DTR_ACTIVE;
+ else
+ ctl_hs |= CP210X_SERIAL_DTR_INACTIVE;
+
+ flow_repl &= ~CP210X_SERIAL_RTS_MASK;
+ if (port_priv->rts)
+ flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL;
+ else
+ flow_repl |= CP210X_SERIAL_RTS_INACTIVE;
+
+ flow_ctl.ulControlHandshake = cpu_to_le32(ctl_hs);
+ flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl);
+
+ dev_dbg(&port->dev, "%s - ctrl = 0x%02x, flow = 0x%02x\n",
+ __func__, ctl_hs, flow_repl);
+
+ ret = cp210x_write_reg_block(port, CP210X_SET_FLOW, &flow_ctl,
+ sizeof(flow_ctl));
+ } else {
+ dev_dbg(&port->dev, "%s - control = 0x%04x\n", __func__, control);
+
+ ret = cp210x_write_u16_reg(port, CP210X_SET_MHS, control);
+ }
+out_unlock:
+ mutex_unlock(&port_priv->mutex);
+
+ return ret;
+}
+
+static void cp210x_dtr_rts(struct usb_serial_port *port, int on)
+{
+ if (on)
+ cp210x_tiocmset_port(port, TIOCM_DTR | TIOCM_RTS, 0);
+ else
+ cp210x_tiocmset_port(port, 0, TIOCM_DTR | TIOCM_RTS);
+}
+
+static int cp210x_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ u8 control;
+ int result;
+
+ result = cp210x_read_u8_reg(port, CP210X_GET_MDMSTS, &control);
+ if (result)
+ return result;
+
+ result = ((control & CONTROL_DTR) ? TIOCM_DTR : 0)
+ |((control & CONTROL_RTS) ? TIOCM_RTS : 0)
+ |((control & CONTROL_CTS) ? TIOCM_CTS : 0)
+ |((control & CONTROL_DSR) ? TIOCM_DSR : 0)
+ |((control & CONTROL_RING)? TIOCM_RI : 0)
+ |((control & CONTROL_DCD) ? TIOCM_CD : 0);
+
+ dev_dbg(&port->dev, "%s - control = 0x%02x\n", __func__, control);
+
+ return result;
+}
+
+static int cp210x_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct cp210x_serial_private *priv = usb_get_serial_data(port->serial);
+ u16 state;
+
+ if (priv->partnum == CP210X_PARTNUM_CP2105) {
+ if (cp210x_interface_num(port->serial) == 1)
+ return -ENOTTY;
+ }
+
+ if (break_state == 0)
+ state = BREAK_OFF;
+ else
+ state = BREAK_ON;
+
+ dev_dbg(&port->dev, "%s - turning break %s\n", __func__,
+ state == BREAK_OFF ? "off" : "on");
+
+ return cp210x_write_u16_reg(port, CP210X_SET_BREAK, state);
+}
+
+#ifdef CONFIG_GPIOLIB
+static int cp210x_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct usb_serial *serial = gpiochip_get_data(gc);
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ u8 req_type;
+ u16 mask;
+ int result;
+ int len;
+
+ result = usb_autopm_get_interface(serial->interface);
+ if (result)
+ return result;
+
+ switch (priv->partnum) {
+ case CP210X_PARTNUM_CP2105:
+ req_type = REQTYPE_INTERFACE_TO_HOST;
+ len = 1;
+ break;
+ case CP210X_PARTNUM_CP2108:
+ req_type = REQTYPE_INTERFACE_TO_HOST;
+ len = 2;
+ break;
+ default:
+ req_type = REQTYPE_DEVICE_TO_HOST;
+ len = 1;
+ break;
+ }
+
+ mask = 0;
+ result = cp210x_read_vendor_block(serial, req_type, CP210X_READ_LATCH,
+ &mask, len);
+
+ usb_autopm_put_interface(serial->interface);
+
+ if (result < 0)
+ return result;
+
+ le16_to_cpus(&mask);
+
+ return !!(mask & BIT(gpio));
+}
+
+static int cp210x_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value)
+{
+ struct usb_serial *serial = gpiochip_get_data(gc);
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ struct cp210x_gpio_write16 buf16;
+ struct cp210x_gpio_write buf;
+ u16 mask, state;
+ u16 wIndex;
+ int result;
+
+ if (value == 1)
+ state = BIT(gpio);
+ else
+ state = 0;
+
+ mask = BIT(gpio);
+
+ result = usb_autopm_get_interface(serial->interface);
+ if (result)
+ goto out;
+
+ switch (priv->partnum) {
+ case CP210X_PARTNUM_CP2105:
+ buf.mask = (u8)mask;
+ buf.state = (u8)state;
+ result = cp210x_write_vendor_block(serial,
+ REQTYPE_HOST_TO_INTERFACE,
+ CP210X_WRITE_LATCH, &buf,
+ sizeof(buf));
+ break;
+ case CP210X_PARTNUM_CP2108:
+ buf16.mask = cpu_to_le16(mask);
+ buf16.state = cpu_to_le16(state);
+ result = cp210x_write_vendor_block(serial,
+ REQTYPE_HOST_TO_INTERFACE,
+ CP210X_WRITE_LATCH, &buf16,
+ sizeof(buf16));
+ break;
+ default:
+ wIndex = state << 8 | mask;
+ result = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ CP210X_VENDOR_SPECIFIC,
+ REQTYPE_HOST_TO_DEVICE,
+ CP210X_WRITE_LATCH,
+ wIndex,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ break;
+ }
+
+ usb_autopm_put_interface(serial->interface);
+out:
+ if (result < 0) {
+ dev_err(&serial->interface->dev, "failed to set GPIO value: %d\n",
+ result);
+ return result;
+ }
+
+ return 0;
+}
+
+static int cp210x_gpio_direction_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct usb_serial *serial = gpiochip_get_data(gc);
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+
+ return priv->gpio_input & BIT(gpio);
+}
+
+static int cp210x_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct usb_serial *serial = gpiochip_get_data(gc);
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+
+ if (priv->partnum == CP210X_PARTNUM_CP2105) {
+ /* hardware does not support an input mode */
+ return -ENOTSUPP;
+ }
+
+ /* push-pull pins cannot be changed to be inputs */
+ if (priv->gpio_pushpull & BIT(gpio))
+ return -EINVAL;
+
+ /* make sure to release pin if it is being driven low */
+ cp210x_gpio_set(gc, gpio, 1);
+
+ priv->gpio_input |= BIT(gpio);
+
+ return 0;
+}
+
+static int cp210x_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio,
+ int value)
+{
+ struct usb_serial *serial = gpiochip_get_data(gc);
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+
+ priv->gpio_input &= ~BIT(gpio);
+
+ return cp210x_gpio_set(gc, gpio, value);
+}
+
+static int cp210x_gpio_set_config(struct gpio_chip *gc, unsigned int gpio,
+ unsigned long config)
+{
+ struct usb_serial *serial = gpiochip_get_data(gc);
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ enum pin_config_param param = pinconf_to_config_param(config);
+
+ /* Succeed only if in correct mode (this can't be set at runtime) */
+ if ((param == PIN_CONFIG_DRIVE_PUSH_PULL) &&
+ (priv->gpio_pushpull & BIT(gpio)))
+ return 0;
+
+ if ((param == PIN_CONFIG_DRIVE_OPEN_DRAIN) &&
+ !(priv->gpio_pushpull & BIT(gpio)))
+ return 0;
+
+ return -ENOTSUPP;
+}
+
+static int cp210x_gpio_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask, unsigned int ngpios)
+{
+ struct usb_serial *serial = gpiochip_get_data(gc);
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ struct device *dev = &serial->interface->dev;
+ unsigned long altfunc_mask = priv->gpio_altfunc;
+
+ bitmap_complement(valid_mask, &altfunc_mask, ngpios);
+
+ if (bitmap_empty(valid_mask, ngpios))
+ dev_dbg(dev, "no pin configured for GPIO\n");
+ else
+ dev_dbg(dev, "GPIO.%*pbl configured for GPIO\n", ngpios,
+ valid_mask);
+ return 0;
+}
+
+/*
+ * This function is for configuring GPIO using shared pins, where other signals
+ * are made unavailable by configuring the use of GPIO. This is believed to be
+ * only applicable to the cp2105 at this point, the other devices supported by
+ * this driver that provide GPIO do so in a way that does not impact other
+ * signals and are thus expected to have very different initialisation.
+ */
+static int cp2105_gpioconf_init(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ struct cp210x_pin_mode mode;
+ struct cp210x_dual_port_config config;
+ u8 intf_num = cp210x_interface_num(serial);
+ u8 iface_config;
+ int result;
+
+ result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST,
+ CP210X_GET_DEVICEMODE, &mode,
+ sizeof(mode));
+ if (result < 0)
+ return result;
+
+ result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST,
+ CP210X_GET_PORTCONFIG, &config,
+ sizeof(config));
+ if (result < 0)
+ return result;
+
+ /* 2 banks of GPIO - One for the pins taken from each serial port */
+ if (intf_num == 0) {
+ priv->gc.ngpio = 2;
+
+ if (mode.eci == CP210X_PIN_MODE_MODEM) {
+ /* mark all GPIOs of this interface as reserved */
+ priv->gpio_altfunc = 0xff;
+ return 0;
+ }
+
+ iface_config = config.eci_cfg;
+ priv->gpio_pushpull = (u8)((le16_to_cpu(config.gpio_mode) &
+ CP210X_ECI_GPIO_MODE_MASK) >>
+ CP210X_ECI_GPIO_MODE_OFFSET);
+ } else if (intf_num == 1) {
+ priv->gc.ngpio = 3;
+
+ if (mode.sci == CP210X_PIN_MODE_MODEM) {
+ /* mark all GPIOs of this interface as reserved */
+ priv->gpio_altfunc = 0xff;
+ return 0;
+ }
+
+ iface_config = config.sci_cfg;
+ priv->gpio_pushpull = (u8)((le16_to_cpu(config.gpio_mode) &
+ CP210X_SCI_GPIO_MODE_MASK) >>
+ CP210X_SCI_GPIO_MODE_OFFSET);
+ } else {
+ return -ENODEV;
+ }
+
+ /* mark all pins which are not in GPIO mode */
+ if (iface_config & CP2105_GPIO0_TXLED_MODE) /* GPIO 0 */
+ priv->gpio_altfunc |= BIT(0);
+ if (iface_config & (CP2105_GPIO1_RXLED_MODE | /* GPIO 1 */
+ CP2105_GPIO1_RS485_MODE))
+ priv->gpio_altfunc |= BIT(1);
+
+ /* driver implementation for CP2105 only supports outputs */
+ priv->gpio_input = 0;
+
+ return 0;
+}
+
+static int cp2104_gpioconf_init(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ struct cp210x_single_port_config config;
+ u8 iface_config;
+ u8 gpio_latch;
+ int result;
+ u8 i;
+
+ result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST,
+ CP210X_GET_PORTCONFIG, &config,
+ sizeof(config));
+ if (result < 0)
+ return result;
+
+ priv->gc.ngpio = 4;
+
+ iface_config = config.device_cfg;
+ priv->gpio_pushpull = (u8)((le16_to_cpu(config.gpio_mode) &
+ CP210X_GPIO_MODE_MASK) >>
+ CP210X_GPIO_MODE_OFFSET);
+ gpio_latch = (u8)((le16_to_cpu(config.reset_state) &
+ CP210X_GPIO_MODE_MASK) >>
+ CP210X_GPIO_MODE_OFFSET);
+
+ /* mark all pins which are not in GPIO mode */
+ if (iface_config & CP2104_GPIO0_TXLED_MODE) /* GPIO 0 */
+ priv->gpio_altfunc |= BIT(0);
+ if (iface_config & CP2104_GPIO1_RXLED_MODE) /* GPIO 1 */
+ priv->gpio_altfunc |= BIT(1);
+ if (iface_config & CP2104_GPIO2_RS485_MODE) /* GPIO 2 */
+ priv->gpio_altfunc |= BIT(2);
+
+ /*
+ * Like CP2102N, CP2104 has also no strict input and output pin
+ * modes.
+ * Do the same input mode emulation as CP2102N.
+ */
+ for (i = 0; i < priv->gc.ngpio; ++i) {
+ /*
+ * Set direction to "input" iff pin is open-drain and reset
+ * value is 1.
+ */
+ if (!(priv->gpio_pushpull & BIT(i)) && (gpio_latch & BIT(i)))
+ priv->gpio_input |= BIT(i);
+ }
+
+ return 0;
+}
+
+static int cp2108_gpio_init(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ struct cp210x_quad_port_config config;
+ u16 gpio_latch;
+ int result;
+ u8 i;
+
+ result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST,
+ CP210X_GET_PORTCONFIG, &config,
+ sizeof(config));
+ if (result < 0)
+ return result;
+
+ priv->gc.ngpio = 16;
+ priv->gpio_pushpull = le16_to_cpu(config.reset_state.gpio_mode_pb1);
+ gpio_latch = le16_to_cpu(config.reset_state.gpio_latch_pb1);
+
+ /*
+ * Mark all pins which are not in GPIO mode.
+ *
+ * Refer to table 9.1 "GPIO Mode alternate Functions" in the datasheet:
+ * https://www.silabs.com/documents/public/data-sheets/cp2108-datasheet.pdf
+ *
+ * Alternate functions of GPIO0 to GPIO3 are determine by enhancedfxn_ifc[0]
+ * and the similarly for the other pins; enhancedfxn_ifc[1]: GPIO4 to GPIO7,
+ * enhancedfxn_ifc[2]: GPIO8 to GPIO11, enhancedfxn_ifc[3]: GPIO12 to GPIO15.
+ */
+ for (i = 0; i < 4; i++) {
+ if (config.enhancedfxn_ifc[i] & CP2108_EF_IFC_GPIO_TXLED)
+ priv->gpio_altfunc |= BIT(i * 4);
+ if (config.enhancedfxn_ifc[i] & CP2108_EF_IFC_GPIO_RXLED)
+ priv->gpio_altfunc |= BIT((i * 4) + 1);
+ if (config.enhancedfxn_ifc[i] & CP2108_EF_IFC_GPIO_RS485)
+ priv->gpio_altfunc |= BIT((i * 4) + 2);
+ if (config.enhancedfxn_ifc[i] & CP2108_EF_IFC_GPIO_CLOCK)
+ priv->gpio_altfunc |= BIT((i * 4) + 3);
+ }
+
+ /*
+ * Like CP2102N, CP2108 has also no strict input and output pin
+ * modes. Do the same input mode emulation as CP2102N.
+ */
+ for (i = 0; i < priv->gc.ngpio; ++i) {
+ /*
+ * Set direction to "input" iff pin is open-drain and reset
+ * value is 1.
+ */
+ if (!(priv->gpio_pushpull & BIT(i)) && (gpio_latch & BIT(i)))
+ priv->gpio_input |= BIT(i);
+ }
+
+ return 0;
+}
+
+static int cp2102n_gpioconf_init(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ const u16 config_size = 0x02a6;
+ u8 gpio_rst_latch;
+ u8 config_version;
+ u8 gpio_pushpull;
+ u8 *config_buf;
+ u8 gpio_latch;
+ u8 gpio_ctrl;
+ int result;
+ u8 i;
+
+ /*
+ * Retrieve device configuration from the device.
+ * The array received contains all customization settings done at the
+ * factory/manufacturer. Format of the array is documented at the
+ * time of writing at:
+ * https://www.silabs.com/community/interface/knowledge-base.entry.html/2017/03/31/cp2102n_setconfig-xsfa
+ */
+ config_buf = kmalloc(config_size, GFP_KERNEL);
+ if (!config_buf)
+ return -ENOMEM;
+
+ result = cp210x_read_vendor_block(serial,
+ REQTYPE_DEVICE_TO_HOST,
+ CP210X_READ_2NCONFIG,
+ config_buf,
+ config_size);
+ if (result < 0) {
+ kfree(config_buf);
+ return result;
+ }
+
+ config_version = config_buf[CP210X_2NCONFIG_CONFIG_VERSION_IDX];
+ gpio_pushpull = config_buf[CP210X_2NCONFIG_GPIO_MODE_IDX];
+ gpio_ctrl = config_buf[CP210X_2NCONFIG_GPIO_CONTROL_IDX];
+ gpio_rst_latch = config_buf[CP210X_2NCONFIG_GPIO_RSTLATCH_IDX];
+
+ kfree(config_buf);
+
+ /* Make sure this is a config format we understand. */
+ if (config_version != 0x01)
+ return -ENOTSUPP;
+
+ priv->gc.ngpio = 4;
+
+ /*
+ * Get default pin states after reset. Needed so we can determine
+ * the direction of an open-drain pin.
+ */
+ gpio_latch = (gpio_rst_latch >> 3) & 0x0f;
+
+ /* 0 indicates open-drain mode, 1 is push-pull */
+ priv->gpio_pushpull = (gpio_pushpull >> 3) & 0x0f;
+
+ /* 0 indicates GPIO mode, 1 is alternate function */
+ if (priv->partnum == CP210X_PARTNUM_CP2102N_QFN20) {
+ /* QFN20 is special... */
+ if (gpio_ctrl & CP2102N_QFN20_GPIO0_CLK_MODE) /* GPIO 0 */
+ priv->gpio_altfunc |= BIT(0);
+ if (gpio_ctrl & CP2102N_QFN20_GPIO1_RS485_MODE) /* GPIO 1 */
+ priv->gpio_altfunc |= BIT(1);
+ if (gpio_ctrl & CP2102N_QFN20_GPIO2_TXLED_MODE) /* GPIO 2 */
+ priv->gpio_altfunc |= BIT(2);
+ if (gpio_ctrl & CP2102N_QFN20_GPIO3_RXLED_MODE) /* GPIO 3 */
+ priv->gpio_altfunc |= BIT(3);
+ } else {
+ priv->gpio_altfunc = (gpio_ctrl >> 2) & 0x0f;
+ }
+
+ if (priv->partnum == CP210X_PARTNUM_CP2102N_QFN28) {
+ /*
+ * For the QFN28 package, GPIO4-6 are controlled by
+ * the low three bits of the mode/latch fields.
+ * Contrary to the document linked above, the bits for
+ * the SUSPEND pins are elsewhere. No alternate
+ * function is available for these pins.
+ */
+ priv->gc.ngpio = 7;
+ gpio_latch |= (gpio_rst_latch & 7) << 4;
+ priv->gpio_pushpull |= (gpio_pushpull & 7) << 4;
+ }
+
+ /*
+ * The CP2102N does not strictly has input and output pin modes,
+ * it only knows open-drain and push-pull modes which is set at
+ * factory. An open-drain pin can function both as an
+ * input or an output. We emulate input mode for open-drain pins
+ * by making sure they are not driven low, and we do not allow
+ * push-pull pins to be set as an input.
+ */
+ for (i = 0; i < priv->gc.ngpio; ++i) {
+ /*
+ * Set direction to "input" iff pin is open-drain and reset
+ * value is 1.
+ */
+ if (!(priv->gpio_pushpull & BIT(i)) && (gpio_latch & BIT(i)))
+ priv->gpio_input |= BIT(i);
+ }
+
+ return 0;
+}
+
+static int cp210x_gpio_init(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ int result;
+
+ switch (priv->partnum) {
+ case CP210X_PARTNUM_CP2104:
+ result = cp2104_gpioconf_init(serial);
+ break;
+ case CP210X_PARTNUM_CP2105:
+ result = cp2105_gpioconf_init(serial);
+ break;
+ case CP210X_PARTNUM_CP2108:
+ /*
+ * The GPIOs are not tied to any specific port so only register
+ * once for interface 0.
+ */
+ if (cp210x_interface_num(serial) != 0)
+ return 0;
+ result = cp2108_gpio_init(serial);
+ break;
+ case CP210X_PARTNUM_CP2102N_QFN28:
+ case CP210X_PARTNUM_CP2102N_QFN24:
+ case CP210X_PARTNUM_CP2102N_QFN20:
+ result = cp2102n_gpioconf_init(serial);
+ break;
+ default:
+ return 0;
+ }
+
+ if (result < 0)
+ return result;
+
+ priv->gc.label = "cp210x";
+ priv->gc.get_direction = cp210x_gpio_direction_get;
+ priv->gc.direction_input = cp210x_gpio_direction_input;
+ priv->gc.direction_output = cp210x_gpio_direction_output;
+ priv->gc.get = cp210x_gpio_get;
+ priv->gc.set = cp210x_gpio_set;
+ priv->gc.set_config = cp210x_gpio_set_config;
+ priv->gc.init_valid_mask = cp210x_gpio_init_valid_mask;
+ priv->gc.owner = THIS_MODULE;
+ priv->gc.parent = &serial->interface->dev;
+ priv->gc.base = -1;
+ priv->gc.can_sleep = true;
+
+ result = gpiochip_add_data(&priv->gc, serial);
+ if (!result)
+ priv->gpio_registered = true;
+
+ return result;
+}
+
+static void cp210x_gpio_remove(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+
+ if (priv->gpio_registered) {
+ gpiochip_remove(&priv->gc);
+ priv->gpio_registered = false;
+ }
+}
+
+#else
+
+static int cp210x_gpio_init(struct usb_serial *serial)
+{
+ return 0;
+}
+
+static void cp210x_gpio_remove(struct usb_serial *serial)
+{
+ /* Nothing to do */
+}
+
+#endif
+
+static int cp210x_port_probe(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct cp210x_port_private *port_priv;
+
+ port_priv = kzalloc_obj(*port_priv);
+ if (!port_priv)
+ return -ENOMEM;
+
+ port_priv->bInterfaceNumber = cp210x_interface_num(serial);
+ mutex_init(&port_priv->mutex);
+
+ usb_set_serial_port_data(port, port_priv);
+
+ return 0;
+}
+
+static void cp210x_port_remove(struct usb_serial_port *port)
+{
+ struct cp210x_port_private *port_priv;
+
+ port_priv = usb_get_serial_port_data(port);
+ kfree(port_priv);
+}
+
+static void cp210x_init_max_speed(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ bool use_actual_rate = false;
+ speed_t min = 300;
+ speed_t max;
+
+ switch (priv->partnum) {
+ case CP210X_PARTNUM_CP2101:
+ max = 921600;
+ break;
+ case CP210X_PARTNUM_CP2102:
+ case CP210X_PARTNUM_CP2103:
+ max = 1000000;
+ break;
+ case CP210X_PARTNUM_CP2104:
+ use_actual_rate = true;
+ max = 2000000;
+ break;
+ case CP210X_PARTNUM_CP2108:
+ max = 2000000;
+ break;
+ case CP210X_PARTNUM_CP2105:
+ if (cp210x_interface_num(serial) == 0) {
+ use_actual_rate = true;
+ max = 2000000; /* ECI */
+ } else {
+ min = 2400;
+ max = 921600; /* SCI */
+ }
+ break;
+ case CP210X_PARTNUM_CP2102N_QFN28:
+ case CP210X_PARTNUM_CP2102N_QFN24:
+ case CP210X_PARTNUM_CP2102N_QFN20:
+ use_actual_rate = true;
+ max = 3000000;
+ break;
+ default:
+ max = 2000000;
+ break;
+ }
+
+ priv->min_speed = min;
+ priv->max_speed = max;
+ priv->use_actual_rate = use_actual_rate;
+}
+
+static void cp2102_determine_quirks(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ u8 *buf;
+ int ret;
+
+ buf = kmalloc(2, GFP_KERNEL);
+ if (!buf)
+ return;
+ /*
+ * Some (possibly counterfeit) CP2102 do not support event-insertion
+ * mode and respond differently to malformed vendor requests.
+ * Specifically, they return one instead of two bytes when sent a
+ * two-byte part-number request.
+ */
+ ret = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+ CP210X_VENDOR_SPECIFIC, REQTYPE_DEVICE_TO_HOST,
+ CP210X_GET_PARTNUM, 0, buf, 2, USB_CTRL_GET_TIMEOUT);
+ if (ret == 1) {
+ dev_dbg(&serial->interface->dev,
+ "device does not support event-insertion mode\n");
+ priv->no_event_mode = true;
+ }
+
+ kfree(buf);
+}
+
+static int cp210x_get_fw_version(struct usb_serial *serial, u16 value)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ u8 ver[3];
+ int ret;
+
+ ret = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST, value,
+ ver, sizeof(ver));
+ if (ret)
+ return ret;
+
+ dev_dbg(&serial->interface->dev, "%s - %d.%d.%d\n", __func__,
+ ver[0], ver[1], ver[2]);
+
+ priv->fw_version = ver[0] << 16 | ver[1] << 8 | ver[2];
+
+ return 0;
+}
+
+static void cp210x_determine_type(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ int ret;
+
+ ret = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST,
+ CP210X_GET_PARTNUM, &priv->partnum,
+ sizeof(priv->partnum));
+ if (ret < 0) {
+ dev_warn(&serial->interface->dev,
+ "querying part number failed\n");
+ priv->partnum = CP210X_PARTNUM_UNKNOWN;
+ return;
+ }
+
+ dev_dbg(&serial->interface->dev, "partnum = 0x%02x\n", priv->partnum);
+
+ switch (priv->partnum) {
+ case CP210X_PARTNUM_CP2102:
+ cp2102_determine_quirks(serial);
+ break;
+ case CP210X_PARTNUM_CP2105:
+ case CP210X_PARTNUM_CP2108:
+ cp210x_get_fw_version(serial, CP210X_GET_FW_VER);
+ break;
+ case CP210X_PARTNUM_CP2102N_QFN28:
+ case CP210X_PARTNUM_CP2102N_QFN24:
+ case CP210X_PARTNUM_CP2102N_QFN20:
+ ret = cp210x_get_fw_version(serial, CP210X_GET_FW_VER_2N);
+ if (ret)
+ break;
+ if (priv->fw_version <= 0x10004)
+ priv->no_flow_control = true;
+ break;
+ default:
+ break;
+ }
+}
+
+static int cp210x_attach(struct usb_serial *serial)
+{
+ int result;
+ struct cp210x_serial_private *priv;
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ usb_set_serial_data(serial, priv);
+
+ cp210x_determine_type(serial);
+ cp210x_init_max_speed(serial);
+
+ result = cp210x_gpio_init(serial);
+ if (result < 0) {
+ dev_err(&serial->interface->dev, "GPIO initialisation failed: %d\n",
+ result);
+ }
+
+ return 0;
+}
+
+static void cp210x_disconnect(struct usb_serial *serial)
+{
+ cp210x_gpio_remove(serial);
+}
+
+static void cp210x_release(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+
+ cp210x_gpio_remove(serial);
+
+ kfree(priv);
+}
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/cyberjack.c b/drivers/usb/serial/cyberjack.c
new file mode 100644
index 000000000..57e20eb02
--- /dev/null
+++ b/drivers/usb/serial/cyberjack.c
@@ -0,0 +1,420 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * REINER SCT cyberJack pinpad/e-com USB Chipcard Reader Driver
+ *
+ * Copyright (C) 2001 REINER SCT
+ * Author: Matthias Bruestle
+ *
+ * Contact: support@reiner-sct.com (see MAINTAINERS)
+ *
+ * This program is largely derived from work by the linux-usb group
+ * and associated source files. Please see the usb/serial files for
+ * individual credits and copyrights.
+ *
+ * Thanks to Greg Kroah-Hartman (greg@kroah.com) for his help and
+ * patience.
+ *
+ * In case of problems, please write to the contact e-mail address
+ * mentioned above.
+ *
+ * Please note that later models of the cyberjack reader family are
+ * supported by a libusb-based userspace device driver.
+ *
+ * Homepage: http://www.reiner-sct.de/support/treiber_cyberjack.php#linux
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#define CYBERJACK_LOCAL_BUF_SIZE 32
+
+#define DRIVER_AUTHOR "Matthias Bruestle"
+#define DRIVER_DESC "REINER SCT cyberJack pinpad/e-com USB Chipcard Reader Driver"
+
+
+#define CYBERJACK_VENDOR_ID 0x0C4B
+#define CYBERJACK_PRODUCT_ID 0x0100
+
+/* Function prototypes */
+static int cyberjack_port_probe(struct usb_serial_port *port);
+static void cyberjack_port_remove(struct usb_serial_port *port);
+static int cyberjack_open(struct tty_struct *tty,
+ struct usb_serial_port *port);
+static void cyberjack_close(struct usb_serial_port *port);
+static int cyberjack_write(struct tty_struct *tty,
+ struct usb_serial_port *port, const unsigned char *buf, int count);
+static unsigned int cyberjack_write_room(struct tty_struct *tty);
+static void cyberjack_read_int_callback(struct urb *urb);
+static void cyberjack_read_bulk_callback(struct urb *urb);
+static void cyberjack_write_bulk_callback(struct urb *urb);
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(CYBERJACK_VENDOR_ID, CYBERJACK_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_serial_driver cyberjack_device = {
+ .driver = {
+ .name = "cyberjack",
+ },
+ .description = "Reiner SCT Cyberjack USB card reader",
+ .id_table = id_table,
+ .num_ports = 1,
+ .num_bulk_out = 1,
+ .port_probe = cyberjack_port_probe,
+ .port_remove = cyberjack_port_remove,
+ .open = cyberjack_open,
+ .close = cyberjack_close,
+ .write = cyberjack_write,
+ .write_room = cyberjack_write_room,
+ .read_int_callback = cyberjack_read_int_callback,
+ .read_bulk_callback = cyberjack_read_bulk_callback,
+ .write_bulk_callback = cyberjack_write_bulk_callback,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &cyberjack_device, NULL
+};
+
+struct cyberjack_private {
+ spinlock_t lock; /* Lock for SMP */
+ short rdtodo; /* Bytes still to read */
+ unsigned char wrbuf[5*64]; /* Buffer for collecting data to write */
+ short wrfilled; /* Overall data size we already got */
+ short wrsent; /* Data already sent */
+};
+
+static int cyberjack_port_probe(struct usb_serial_port *port)
+{
+ struct cyberjack_private *priv;
+ int result;
+
+ priv = kmalloc_obj(struct cyberjack_private);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->lock);
+ priv->rdtodo = 0;
+ priv->wrfilled = 0;
+ priv->wrsent = 0;
+
+ usb_set_serial_port_data(port, priv);
+
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (result)
+ dev_err(&port->dev, "usb_submit_urb(read int) failed\n");
+
+ return 0;
+}
+
+static void cyberjack_port_remove(struct usb_serial_port *port)
+{
+ struct cyberjack_private *priv;
+
+ usb_kill_urb(port->interrupt_in_urb);
+
+ priv = usb_get_serial_port_data(port);
+ kfree(priv);
+}
+
+static int cyberjack_open(struct tty_struct *tty,
+ struct usb_serial_port *port)
+{
+ struct cyberjack_private *priv;
+ unsigned long flags;
+
+ dev_dbg(&port->dev, "%s - usb_clear_halt\n", __func__);
+ usb_clear_halt(port->serial->dev, port->write_urb->pipe);
+
+ priv = usb_get_serial_port_data(port);
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->rdtodo = 0;
+ priv->wrfilled = 0;
+ priv->wrsent = 0;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static void cyberjack_close(struct usb_serial_port *port)
+{
+ usb_kill_urb(port->write_urb);
+ usb_kill_urb(port->read_urb);
+}
+
+static int cyberjack_write(struct tty_struct *tty,
+ struct usb_serial_port *port, const unsigned char *buf, int count)
+{
+ struct device *dev = &port->dev;
+ struct cyberjack_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ int result;
+ int wrexpected;
+
+ if (count == 0) {
+ dev_dbg(dev, "%s - write request of 0 bytes\n", __func__);
+ return 0;
+ }
+
+ if (!test_and_clear_bit(0, &port->write_urbs_free)) {
+ dev_dbg(dev, "%s - already writing\n", __func__);
+ return 0;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ if (count+priv->wrfilled > sizeof(priv->wrbuf)) {
+ /* To much data for buffer. Reset buffer. */
+ priv->wrfilled = 0;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ set_bit(0, &port->write_urbs_free);
+ return 0;
+ }
+
+ /* Copy data */
+ memcpy(priv->wrbuf + priv->wrfilled, buf, count);
+
+ usb_serial_debug_data(dev, __func__, count, priv->wrbuf + priv->wrfilled);
+ priv->wrfilled += count;
+
+ if (priv->wrfilled >= 3) {
+ wrexpected = ((int)priv->wrbuf[2]<<8)+priv->wrbuf[1]+3;
+ dev_dbg(dev, "%s - expected data: %d\n", __func__, wrexpected);
+ } else
+ wrexpected = sizeof(priv->wrbuf);
+
+ if (priv->wrfilled >= wrexpected) {
+ /* We have enough data to begin transmission */
+ int length;
+
+ dev_dbg(dev, "%s - transmitting data (frame 1)\n", __func__);
+ length = (wrexpected > port->bulk_out_size) ?
+ port->bulk_out_size : wrexpected;
+
+ memcpy(port->write_urb->transfer_buffer, priv->wrbuf, length);
+ priv->wrsent = length;
+
+ /* set up our urb */
+ port->write_urb->transfer_buffer_length = length;
+
+ /* send the data out the bulk port */
+ result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ if (result) {
+ dev_err(&port->dev,
+ "%s - failed submitting write urb, error %d\n",
+ __func__, result);
+ /* Throw away data. No better idea what to do with it. */
+ priv->wrfilled = 0;
+ priv->wrsent = 0;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ set_bit(0, &port->write_urbs_free);
+ return 0;
+ }
+
+ dev_dbg(dev, "%s - priv->wrsent=%d\n", __func__, priv->wrsent);
+ dev_dbg(dev, "%s - priv->wrfilled=%d\n", __func__, priv->wrfilled);
+
+ if (priv->wrsent >= priv->wrfilled) {
+ dev_dbg(dev, "%s - buffer cleaned\n", __func__);
+ memset(priv->wrbuf, 0, sizeof(priv->wrbuf));
+ priv->wrfilled = 0;
+ priv->wrsent = 0;
+ }
+ }
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return count;
+}
+
+static unsigned int cyberjack_write_room(struct tty_struct *tty)
+{
+ /* FIXME: .... */
+ return CYBERJACK_LOCAL_BUF_SIZE;
+}
+
+static void cyberjack_read_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct cyberjack_private *priv = usb_get_serial_port_data(port);
+ struct device *dev = &port->dev;
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+ unsigned long flags;
+ int result;
+
+ /* the urb might have been killed. */
+ if (status)
+ return;
+
+ usb_serial_debug_data(dev, __func__, urb->actual_length, data);
+
+ /* React only to interrupts signaling a bulk_in transfer */
+ if (urb->actual_length == 4 && data[0] == 0x01) {
+ short old_rdtodo;
+
+ /* This is a announcement of coming bulk_ins. */
+ unsigned short size = ((unsigned short)data[3]<<8)+data[2]+3;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ old_rdtodo = priv->rdtodo;
+
+ if (old_rdtodo > SHRT_MAX - size) {
+ dev_dbg(dev, "Too many bulk_in urbs to do.\n");
+ spin_unlock_irqrestore(&priv->lock, flags);
+ goto resubmit;
+ }
+
+ /* "+=" is probably more fault tolerant than "=" */
+ priv->rdtodo += size;
+
+ dev_dbg(dev, "%s - rdtodo: %d\n", __func__, priv->rdtodo);
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (!old_rdtodo) {
+ result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
+ if (result)
+ dev_err(dev, "%s - failed resubmitting read urb, error %d\n",
+ __func__, result);
+ dev_dbg(dev, "%s - usb_submit_urb(read urb)\n", __func__);
+ }
+ }
+
+resubmit:
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
+ if (result)
+ dev_err(&port->dev, "usb_submit_urb(read int) failed\n");
+ dev_dbg(dev, "%s - usb_submit_urb(int urb)\n", __func__);
+}
+
+static void cyberjack_read_bulk_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct cyberjack_private *priv = usb_get_serial_port_data(port);
+ struct device *dev = &port->dev;
+ unsigned char *data = urb->transfer_buffer;
+ unsigned long flags;
+ short todo;
+ int result;
+ int status = urb->status;
+
+ usb_serial_debug_data(dev, __func__, urb->actual_length, data);
+ if (status) {
+ dev_dbg(dev, "%s - nonzero read bulk status received: %d\n",
+ __func__, status);
+ return;
+ }
+
+ if (urb->actual_length) {
+ tty_insert_flip_string(&port->port, data, urb->actual_length);
+ tty_flip_buffer_push(&port->port);
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ /* Reduce urbs to do by one. */
+ priv->rdtodo -= urb->actual_length;
+ /* Just to be sure */
+ if (priv->rdtodo < 0)
+ priv->rdtodo = 0;
+ todo = priv->rdtodo;
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ dev_dbg(dev, "%s - rdtodo: %d\n", __func__, todo);
+
+ /* Continue to read if we have still urbs to do. */
+ if (todo /* || (urb->actual_length==port->bulk_in_endpointAddress)*/) {
+ result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
+ if (result)
+ dev_err(dev, "%s - failed resubmitting read urb, error %d\n",
+ __func__, result);
+ dev_dbg(dev, "%s - usb_submit_urb(read urb)\n", __func__);
+ }
+}
+
+static void cyberjack_write_bulk_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct cyberjack_private *priv = usb_get_serial_port_data(port);
+ struct device *dev = &port->dev;
+ int status = urb->status;
+ unsigned long flags;
+ bool resubmitted = false;
+
+ if (status) {
+ dev_dbg(dev, "%s - nonzero write bulk status received: %d\n",
+ __func__, status);
+ set_bit(0, &port->write_urbs_free);
+ return;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ /* only do something if we have more data to send */
+ if (priv->wrfilled) {
+ int length, blksize, result;
+
+ dev_dbg(dev, "%s - transmitting data (frame n)\n", __func__);
+
+ length = ((priv->wrfilled - priv->wrsent) > port->bulk_out_size) ?
+ port->bulk_out_size : (priv->wrfilled - priv->wrsent);
+
+ memcpy(port->write_urb->transfer_buffer,
+ priv->wrbuf + priv->wrsent, length);
+ priv->wrsent += length;
+
+ /* set up our urb */
+ port->write_urb->transfer_buffer_length = length;
+
+ /* send the data out the bulk port */
+ result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ if (result) {
+ dev_err(dev, "%s - failed submitting write urb, error %d\n",
+ __func__, result);
+ /* Throw away data. No better idea what to do with it. */
+ priv->wrfilled = 0;
+ priv->wrsent = 0;
+ goto exit;
+ }
+
+ resubmitted = true;
+
+ dev_dbg(dev, "%s - priv->wrsent=%d\n", __func__, priv->wrsent);
+ dev_dbg(dev, "%s - priv->wrfilled=%d\n", __func__, priv->wrfilled);
+
+ blksize = ((int)priv->wrbuf[2]<<8)+priv->wrbuf[1]+3;
+
+ if (priv->wrsent >= priv->wrfilled ||
+ priv->wrsent >= blksize) {
+ dev_dbg(dev, "%s - buffer cleaned\n", __func__);
+ memset(priv->wrbuf, 0, sizeof(priv->wrbuf));
+ priv->wrfilled = 0;
+ priv->wrsent = 0;
+ }
+ }
+
+exit:
+ spin_unlock_irqrestore(&priv->lock, flags);
+ if (!resubmitted)
+ set_bit(0, &port->write_urbs_free);
+ usb_serial_port_softint(port);
+}
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c
new file mode 100644
index 000000000..78fb1389b
--- /dev/null
+++ b/drivers/usb/serial/cypress_m8.c
@@ -0,0 +1,1222 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * USB Cypress M8 driver
+ *
+ * Copyright (C) 2004
+ * Lonnie Mendez (dignome@gmail.com)
+ * Copyright (C) 2003,2004
+ * Neil Whelchel (koyama@firstlight.net)
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ *
+ * See http://geocities.com/i0xox0i for information on this driver and the
+ * earthmate usb device.
+ */
+
+/* Thanks to Neil Whelchel for writing the first cypress m8 implementation
+ for linux. */
+/* Thanks to cypress for providing references for the hid reports. */
+/* Thanks to Jiang Zhang for providing links and for general help. */
+/* Code originates and was built up from ftdi_sio, belkin, pl2303 and others.*/
+
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/serial.h>
+#include <linux/kfifo.h>
+#include <linux/delay.h>
+#include <linux/unaligned.h>
+
+#include "cypress_m8.h"
+
+
+static bool stats;
+static int interval;
+static bool unstable_bauds;
+
+#define DRIVER_AUTHOR "Lonnie Mendez <dignome@gmail.com>, Neil Whelchel <koyama@firstlight.net>"
+#define DRIVER_DESC "Cypress USB to Serial Driver"
+
+/* write buffer size defines */
+#define CYPRESS_BUF_SIZE 1024
+
+static const struct usb_device_id id_table_earthmate[] = {
+ { USB_DEVICE(VENDOR_ID_DELORME, PRODUCT_ID_EARTHMATEUSB) },
+ { USB_DEVICE(VENDOR_ID_DELORME, PRODUCT_ID_EARTHMATEUSB_LT20) },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id id_table_cyphidcomrs232[] = {
+ { USB_DEVICE(VENDOR_ID_CYPRESS, PRODUCT_ID_CYPHIDCOM) },
+ { USB_DEVICE(VENDOR_ID_SAI, PRODUCT_ID_CYPHIDCOM) },
+ { USB_DEVICE(VENDOR_ID_POWERCOM, PRODUCT_ID_UPS) },
+ { USB_DEVICE(VENDOR_ID_FRWD, PRODUCT_ID_CYPHIDCOM_FRWD) },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id id_table_nokiaca42v2[] = {
+ { USB_DEVICE(VENDOR_ID_DAZZLE, PRODUCT_ID_CA42) },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id id_table_combined[] = {
+ { USB_DEVICE(VENDOR_ID_DELORME, PRODUCT_ID_EARTHMATEUSB) },
+ { USB_DEVICE(VENDOR_ID_DELORME, PRODUCT_ID_EARTHMATEUSB_LT20) },
+ { USB_DEVICE(VENDOR_ID_CYPRESS, PRODUCT_ID_CYPHIDCOM) },
+ { USB_DEVICE(VENDOR_ID_SAI, PRODUCT_ID_CYPHIDCOM) },
+ { USB_DEVICE(VENDOR_ID_POWERCOM, PRODUCT_ID_UPS) },
+ { USB_DEVICE(VENDOR_ID_FRWD, PRODUCT_ID_CYPHIDCOM_FRWD) },
+ { USB_DEVICE(VENDOR_ID_DAZZLE, PRODUCT_ID_CA42) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table_combined);
+
+enum packet_format {
+ packet_format_1, /* b0:status, b1:payload count */
+ packet_format_2 /* b0[7:3]:status, b0[2:0]:payload count */
+};
+
+struct cypress_private {
+ spinlock_t lock; /* private lock */
+ int chiptype; /* identifier of device, for quirks/etc */
+ int bytes_in; /* used for statistics */
+ int bytes_out; /* used for statistics */
+ int cmd_count; /* used for statistics */
+ int cmd_ctrl; /* always set this to 1 before issuing a command */
+ struct kfifo write_fifo; /* write fifo */
+ int write_urb_in_use; /* write urb in use indicator */
+ int write_urb_interval; /* interval to use for write urb */
+ int read_urb_interval; /* interval to use for read urb */
+ int comm_is_ok; /* true if communication is (still) ok */
+ __u8 line_control; /* holds dtr / rts value */
+ __u8 current_status; /* received from last read - info on dsr,cts,cd,ri,etc */
+ __u8 current_config; /* stores the current configuration byte */
+ __u8 rx_flags; /* throttling - used from whiteheat/ftdi_sio */
+ enum packet_format pkt_fmt; /* format to use for packet send / receive */
+ int get_cfg_unsafe; /* If true, the CYPRESS_GET_CONFIG is unsafe */
+ int baud_rate; /* stores current baud rate in
+ integer form */
+ char prev_status; /* used for TIOCMIWAIT */
+};
+
+/* function prototypes for the Cypress USB to serial device */
+static int cypress_earthmate_port_probe(struct usb_serial_port *port);
+static int cypress_hidcom_port_probe(struct usb_serial_port *port);
+static int cypress_ca42v2_port_probe(struct usb_serial_port *port);
+static void cypress_port_remove(struct usb_serial_port *port);
+static int cypress_open(struct tty_struct *tty, struct usb_serial_port *port);
+static void cypress_close(struct usb_serial_port *port);
+static void cypress_dtr_rts(struct usb_serial_port *port, int on);
+static int cypress_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count);
+static void cypress_send(struct usb_serial_port *port);
+static unsigned int cypress_write_room(struct tty_struct *tty);
+static void cypress_earthmate_init_termios(struct tty_struct *tty);
+static void cypress_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+static int cypress_tiocmget(struct tty_struct *tty);
+static int cypress_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear);
+static unsigned int cypress_chars_in_buffer(struct tty_struct *tty);
+static void cypress_throttle(struct tty_struct *tty);
+static void cypress_unthrottle(struct tty_struct *tty);
+static void cypress_set_dead(struct usb_serial_port *port);
+static void cypress_read_int_callback(struct urb *urb);
+static void cypress_write_int_callback(struct urb *urb);
+
+static struct usb_serial_driver cypress_earthmate_device = {
+ .driver = {
+ .name = "earthmate",
+ },
+ .description = "DeLorme Earthmate USB",
+ .id_table = id_table_earthmate,
+ .num_ports = 1,
+ .port_probe = cypress_earthmate_port_probe,
+ .port_remove = cypress_port_remove,
+ .open = cypress_open,
+ .close = cypress_close,
+ .dtr_rts = cypress_dtr_rts,
+ .write = cypress_write,
+ .write_room = cypress_write_room,
+ .init_termios = cypress_earthmate_init_termios,
+ .set_termios = cypress_set_termios,
+ .tiocmget = cypress_tiocmget,
+ .tiocmset = cypress_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .chars_in_buffer = cypress_chars_in_buffer,
+ .throttle = cypress_throttle,
+ .unthrottle = cypress_unthrottle,
+ .read_int_callback = cypress_read_int_callback,
+ .write_int_callback = cypress_write_int_callback,
+};
+
+static struct usb_serial_driver cypress_hidcom_device = {
+ .driver = {
+ .name = "cyphidcom",
+ },
+ .description = "HID->COM RS232 Adapter",
+ .id_table = id_table_cyphidcomrs232,
+ .num_ports = 1,
+ .port_probe = cypress_hidcom_port_probe,
+ .port_remove = cypress_port_remove,
+ .open = cypress_open,
+ .close = cypress_close,
+ .dtr_rts = cypress_dtr_rts,
+ .write = cypress_write,
+ .write_room = cypress_write_room,
+ .set_termios = cypress_set_termios,
+ .tiocmget = cypress_tiocmget,
+ .tiocmset = cypress_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .chars_in_buffer = cypress_chars_in_buffer,
+ .throttle = cypress_throttle,
+ .unthrottle = cypress_unthrottle,
+ .read_int_callback = cypress_read_int_callback,
+ .write_int_callback = cypress_write_int_callback,
+};
+
+static struct usb_serial_driver cypress_ca42v2_device = {
+ .driver = {
+ .name = "nokiaca42v2",
+ },
+ .description = "Nokia CA-42 V2 Adapter",
+ .id_table = id_table_nokiaca42v2,
+ .num_ports = 1,
+ .port_probe = cypress_ca42v2_port_probe,
+ .port_remove = cypress_port_remove,
+ .open = cypress_open,
+ .close = cypress_close,
+ .dtr_rts = cypress_dtr_rts,
+ .write = cypress_write,
+ .write_room = cypress_write_room,
+ .set_termios = cypress_set_termios,
+ .tiocmget = cypress_tiocmget,
+ .tiocmset = cypress_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .chars_in_buffer = cypress_chars_in_buffer,
+ .throttle = cypress_throttle,
+ .unthrottle = cypress_unthrottle,
+ .read_int_callback = cypress_read_int_callback,
+ .write_int_callback = cypress_write_int_callback,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &cypress_earthmate_device, &cypress_hidcom_device,
+ &cypress_ca42v2_device, NULL
+};
+
+/*****************************************************************************
+ * Cypress serial helper functions
+ *****************************************************************************/
+
+/* FRWD Dongle hidcom needs to skip reset and speed checks */
+static inline bool is_frwd(struct usb_device *dev)
+{
+ return ((le16_to_cpu(dev->descriptor.idVendor) == VENDOR_ID_FRWD) &&
+ (le16_to_cpu(dev->descriptor.idProduct) == PRODUCT_ID_CYPHIDCOM_FRWD));
+}
+
+static int analyze_baud_rate(struct usb_serial_port *port, speed_t new_rate)
+{
+ struct cypress_private *priv;
+ priv = usb_get_serial_port_data(port);
+
+ if (unstable_bauds)
+ return new_rate;
+
+ /* FRWD Dongle uses 115200 bps */
+ if (is_frwd(port->serial->dev))
+ return new_rate;
+
+ /*
+ * The general purpose firmware for the Cypress M8 allows for
+ * a maximum speed of 57600bps (I have no idea whether DeLorme
+ * chose to use the general purpose firmware or not), if you
+ * need to modify this speed setting for your own project
+ * please add your own chiptype and modify the code likewise.
+ * The Cypress HID->COM device will work successfully up to
+ * 115200bps (but the actual throughput is around 3kBps).
+ */
+ if (port->serial->dev->speed == USB_SPEED_LOW) {
+ /*
+ * Mike Isely <isely@pobox.com> 2-Feb-2008: The
+ * Cypress app note that describes this mechanism
+ * states that the low-speed part can't handle more
+ * than 800 bytes/sec, in which case 4800 baud is the
+ * safest speed for a part like that.
+ */
+ if (new_rate > 4800) {
+ dev_dbg(&port->dev,
+ "%s - failed setting baud rate, device incapable speed %d\n",
+ __func__, new_rate);
+ return -1;
+ }
+ }
+ switch (priv->chiptype) {
+ case CT_EARTHMATE:
+ if (new_rate <= 600) {
+ /* 300 and 600 baud rates are supported under
+ * the generic firmware, but are not used with
+ * NMEA and SiRF protocols */
+ dev_dbg(&port->dev,
+ "%s - failed setting baud rate, unsupported speed of %d on Earthmate GPS\n",
+ __func__, new_rate);
+ return -1;
+ }
+ break;
+ default:
+ break;
+ }
+ return new_rate;
+}
+
+
+/* This function can either set or retrieve the current serial line settings */
+static int cypress_serial_control(struct tty_struct *tty,
+ struct usb_serial_port *port, speed_t baud_rate, int data_bits,
+ int stop_bits, int parity_enable, int parity_type, int reset,
+ int cypress_request_type)
+{
+ int new_baudrate = 0, retval = 0, tries = 0;
+ struct cypress_private *priv;
+ struct device *dev = &port->dev;
+ u8 *feature_buffer;
+ const unsigned int feature_len = 5;
+ unsigned long flags;
+
+ priv = usb_get_serial_port_data(port);
+
+ if (!priv->comm_is_ok)
+ return -ENODEV;
+
+ feature_buffer = kcalloc(feature_len, sizeof(u8), GFP_KERNEL);
+ if (!feature_buffer)
+ return -ENOMEM;
+
+ switch (cypress_request_type) {
+ case CYPRESS_SET_CONFIG:
+ /* 0 means 'Hang up' so doesn't change the true bit rate */
+ new_baudrate = priv->baud_rate;
+ if (baud_rate && baud_rate != priv->baud_rate) {
+ dev_dbg(dev, "%s - baud rate is changing\n", __func__);
+ retval = analyze_baud_rate(port, baud_rate);
+ if (retval >= 0) {
+ new_baudrate = retval;
+ dev_dbg(dev, "%s - New baud rate set to %d\n",
+ __func__, new_baudrate);
+ }
+ }
+ dev_dbg(dev, "%s - baud rate is being sent as %d\n", __func__,
+ new_baudrate);
+
+ /* fill the feature_buffer with new configuration */
+ put_unaligned_le32(new_baudrate, feature_buffer);
+ feature_buffer[4] |= data_bits - 5; /* assign data bits in 2 bit space ( max 3 ) */
+ /* 1 bit gap */
+ feature_buffer[4] |= (stop_bits << 3); /* assign stop bits in 1 bit space */
+ feature_buffer[4] |= (parity_enable << 4); /* assign parity flag in 1 bit space */
+ feature_buffer[4] |= (parity_type << 5); /* assign parity type in 1 bit space */
+ /* 1 bit gap */
+ feature_buffer[4] |= (reset << 7); /* assign reset at end of byte, 1 bit space */
+
+ dev_dbg(dev, "%s - device is being sent this feature report:\n", __func__);
+ dev_dbg(dev, "%s - %02X - %02X - %02X - %02X - %02X\n", __func__,
+ feature_buffer[0], feature_buffer[1],
+ feature_buffer[2], feature_buffer[3],
+ feature_buffer[4]);
+
+ do {
+ retval = usb_control_msg(port->serial->dev,
+ usb_sndctrlpipe(port->serial->dev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_RECIP_INTERFACE | USB_TYPE_CLASS,
+ 0x0300, 0, feature_buffer,
+ feature_len, 500);
+
+ if (tries++ >= 3)
+ break;
+
+ } while (retval != feature_len &&
+ retval != -ENODEV);
+
+ if (retval != feature_len) {
+ dev_err(dev, "%s - failed sending serial line settings - %d\n",
+ __func__, retval);
+ cypress_set_dead(port);
+ } else {
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->baud_rate = new_baudrate;
+ priv->current_config = feature_buffer[4];
+ spin_unlock_irqrestore(&priv->lock, flags);
+ /* If we asked for a speed change encode it */
+ if (baud_rate)
+ tty_encode_baud_rate(tty,
+ new_baudrate, new_baudrate);
+ }
+ break;
+ case CYPRESS_GET_CONFIG:
+ if (priv->get_cfg_unsafe) {
+ /* Not implemented for this device,
+ and if we try to do it we're likely
+ to crash the hardware. */
+ retval = -ENOTTY;
+ goto out;
+ }
+ dev_dbg(dev, "%s - retrieving serial line settings\n", __func__);
+ do {
+ retval = usb_control_msg(port->serial->dev,
+ usb_rcvctrlpipe(port->serial->dev, 0),
+ HID_REQ_GET_REPORT,
+ USB_DIR_IN | USB_RECIP_INTERFACE | USB_TYPE_CLASS,
+ 0x0300, 0, feature_buffer,
+ feature_len, 500);
+
+ if (tries++ >= 3)
+ break;
+ } while (retval != feature_len
+ && retval != -ENODEV);
+
+ if (retval != feature_len) {
+ dev_err(dev, "%s - failed to retrieve serial line settings - %d\n",
+ __func__, retval);
+ cypress_set_dead(port);
+ goto out;
+ } else {
+ spin_lock_irqsave(&priv->lock, flags);
+ /* store the config in one byte, and later
+ use bit masks to check values */
+ priv->current_config = feature_buffer[4];
+ priv->baud_rate = get_unaligned_le32(feature_buffer);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ }
+ }
+ spin_lock_irqsave(&priv->lock, flags);
+ ++priv->cmd_count;
+ spin_unlock_irqrestore(&priv->lock, flags);
+out:
+ kfree(feature_buffer);
+ return retval;
+} /* cypress_serial_control */
+
+
+static void cypress_set_dead(struct usb_serial_port *port)
+{
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (!priv->comm_is_ok) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return;
+ }
+ priv->comm_is_ok = 0;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ dev_err(&port->dev, "cypress_m8 suspending failing port %d - "
+ "interval might be too short\n", port->port_number);
+}
+
+
+/*****************************************************************************
+ * Cypress serial driver functions
+ *****************************************************************************/
+
+
+static int cypress_generic_port_probe(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct cypress_private *priv;
+
+ if (!port->interrupt_out_urb || !port->interrupt_in_urb) {
+ dev_err(&port->dev, "required endpoint is missing\n");
+ return -ENODEV;
+ }
+
+ /*
+ * The buffer must be large enough for the one or two-byte header (and
+ * following data), but assume anything smaller than eight bytes is
+ * broken.
+ */
+ if (port->interrupt_out_size < 8)
+ return -EINVAL;
+
+ priv = kzalloc_obj(struct cypress_private);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->comm_is_ok = !0;
+ spin_lock_init(&priv->lock);
+ if (kfifo_alloc(&priv->write_fifo, CYPRESS_BUF_SIZE, GFP_KERNEL)) {
+ kfree(priv);
+ return -ENOMEM;
+ }
+
+ /* Skip reset for FRWD device. It is a workaound:
+ device hangs if it receives SET_CONFIGURE in Configured
+ state. */
+ if (!is_frwd(serial->dev))
+ usb_reset_configuration(serial->dev);
+
+ priv->cmd_ctrl = 0;
+ priv->line_control = 0;
+ priv->rx_flags = 0;
+ /* Default packet format setting is determined by packet size.
+ Anything with a size larger then 9 must have a separate
+ count field since the 3 bit count field is otherwise too
+ small. Otherwise we can use the slightly more compact
+ format. This is in accordance with the cypress_m8 serial
+ converter app note. */
+ if (port->interrupt_out_size > 9)
+ priv->pkt_fmt = packet_format_1;
+ else
+ priv->pkt_fmt = packet_format_2;
+
+ if (interval > 0) {
+ priv->write_urb_interval = interval;
+ priv->read_urb_interval = interval;
+ dev_dbg(&port->dev, "%s - read & write intervals forced to %d\n",
+ __func__, interval);
+ } else {
+ priv->write_urb_interval = port->interrupt_out_urb->interval;
+ priv->read_urb_interval = port->interrupt_in_urb->interval;
+ dev_dbg(&port->dev, "%s - intervals: read=%d write=%d\n",
+ __func__, priv->read_urb_interval,
+ priv->write_urb_interval);
+ }
+ usb_set_serial_port_data(port, priv);
+
+ port->port.drain_delay = 256;
+
+ return 0;
+}
+
+
+static int cypress_earthmate_port_probe(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct cypress_private *priv;
+ int ret;
+
+ ret = cypress_generic_port_probe(port);
+ if (ret) {
+ dev_dbg(&port->dev, "%s - Failed setting up port\n", __func__);
+ return ret;
+ }
+
+ priv = usb_get_serial_port_data(port);
+ priv->chiptype = CT_EARTHMATE;
+ /* All Earthmate devices use the separated-count packet
+ format! Idiotic. */
+ priv->pkt_fmt = packet_format_1;
+ if (serial->dev->descriptor.idProduct !=
+ cpu_to_le16(PRODUCT_ID_EARTHMATEUSB)) {
+ /* The old original USB Earthmate seemed able to
+ handle GET_CONFIG requests; everything they've
+ produced since that time crashes if this command is
+ attempted :-( */
+ dev_dbg(&port->dev,
+ "%s - Marking this device as unsafe for GET_CONFIG commands\n",
+ __func__);
+ priv->get_cfg_unsafe = !0;
+ }
+
+ return 0;
+}
+
+static int cypress_hidcom_port_probe(struct usb_serial_port *port)
+{
+ struct cypress_private *priv;
+ int ret;
+
+ ret = cypress_generic_port_probe(port);
+ if (ret) {
+ dev_dbg(&port->dev, "%s - Failed setting up port\n", __func__);
+ return ret;
+ }
+
+ priv = usb_get_serial_port_data(port);
+ priv->chiptype = CT_CYPHIDCOM;
+
+ return 0;
+}
+
+static int cypress_ca42v2_port_probe(struct usb_serial_port *port)
+{
+ struct cypress_private *priv;
+ int ret;
+
+ ret = cypress_generic_port_probe(port);
+ if (ret) {
+ dev_dbg(&port->dev, "%s - Failed setting up port\n", __func__);
+ return ret;
+ }
+
+ priv = usb_get_serial_port_data(port);
+ priv->chiptype = CT_CA42V2;
+
+ return 0;
+}
+
+static void cypress_port_remove(struct usb_serial_port *port)
+{
+ struct cypress_private *priv;
+
+ priv = usb_get_serial_port_data(port);
+
+ kfifo_free(&priv->write_fifo);
+ kfree(priv);
+}
+
+static int cypress_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ unsigned long flags;
+ int result = 0;
+
+ if (!priv->comm_is_ok)
+ return -EIO;
+
+ /* clear halts before open */
+ usb_clear_halt(serial->dev, 0x81);
+ usb_clear_halt(serial->dev, 0x02);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ /* reset read/write statistics */
+ priv->bytes_in = 0;
+ priv->bytes_out = 0;
+ priv->cmd_count = 0;
+ priv->rx_flags = 0;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /* Set termios */
+ cypress_send(port);
+
+ if (tty)
+ cypress_set_termios(tty, port, NULL);
+
+ /* setup the port and start reading from the device */
+ usb_fill_int_urb(port->interrupt_in_urb, serial->dev,
+ usb_rcvintpipe(serial->dev, port->interrupt_in_endpointAddress),
+ port->interrupt_in_urb->transfer_buffer,
+ port->interrupt_in_urb->transfer_buffer_length,
+ cypress_read_int_callback, port, priv->read_urb_interval);
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+
+ if (result) {
+ dev_err(&port->dev,
+ "%s - failed submitting read urb, error %d\n",
+ __func__, result);
+ cypress_set_dead(port);
+ }
+
+ return result;
+} /* cypress_open */
+
+static void cypress_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ /* drop dtr and rts */
+ spin_lock_irq(&priv->lock);
+ if (on == 0)
+ priv->line_control = 0;
+ else
+ priv->line_control = CONTROL_DTR | CONTROL_RTS;
+ priv->cmd_ctrl = 1;
+ spin_unlock_irq(&priv->lock);
+ cypress_write(NULL, port, NULL, 0);
+}
+
+static void cypress_close(struct usb_serial_port *port)
+{
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ kfifo_reset_out(&priv->write_fifo);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ dev_dbg(&port->dev, "%s - stopping urbs\n", __func__);
+ usb_kill_urb(port->interrupt_in_urb);
+ usb_kill_urb(port->interrupt_out_urb);
+
+ if (stats)
+ dev_info(&port->dev, "Statistics: %d Bytes In | %d Bytes Out | %d Commands Issued\n",
+ priv->bytes_in, priv->bytes_out, priv->cmd_count);
+} /* cypress_close */
+
+
+static int cypress_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count)
+{
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+
+ dev_dbg(&port->dev, "%s - %d bytes\n", __func__, count);
+
+ /* line control commands, which need to be executed immediately,
+ are not put into the buffer for obvious reasons.
+ */
+ if (priv->cmd_ctrl) {
+ count = 0;
+ goto finish;
+ }
+
+ if (!count)
+ return count;
+
+ count = kfifo_in_locked(&priv->write_fifo, buf, count, &priv->lock);
+
+finish:
+ cypress_send(port);
+
+ return count;
+} /* cypress_write */
+
+
+static void cypress_send(struct usb_serial_port *port)
+{
+ int count = 0, result, offset, actual_size;
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ struct device *dev = &port->dev;
+ unsigned long flags;
+
+ if (!priv->comm_is_ok)
+ return;
+
+ dev_dbg(dev, "%s - interrupt out size is %d\n", __func__,
+ port->interrupt_out_size);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->write_urb_in_use) {
+ dev_dbg(dev, "%s - can't write, urb in use\n", __func__);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /* clear buffer */
+ memset(port->interrupt_out_urb->transfer_buffer, 0,
+ port->interrupt_out_size);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ switch (priv->pkt_fmt) {
+ default:
+ case packet_format_1:
+ /* this is for the CY7C64013... */
+ offset = 2;
+ port->interrupt_out_buffer[0] = priv->line_control;
+ break;
+ case packet_format_2:
+ /* this is for the CY7C63743... */
+ offset = 1;
+ port->interrupt_out_buffer[0] = priv->line_control;
+ break;
+ }
+
+ if (priv->line_control & CONTROL_RESET)
+ priv->line_control &= ~CONTROL_RESET;
+
+ if (priv->cmd_ctrl) {
+ priv->cmd_count++;
+ dev_dbg(dev, "%s - line control command being issued\n", __func__);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ goto send;
+ } else
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ count = kfifo_out_locked(&priv->write_fifo,
+ &port->interrupt_out_buffer[offset],
+ port->interrupt_out_size - offset,
+ &priv->lock);
+ if (count == 0)
+ return;
+
+ switch (priv->pkt_fmt) {
+ default:
+ case packet_format_1:
+ port->interrupt_out_buffer[1] = count;
+ break;
+ case packet_format_2:
+ port->interrupt_out_buffer[0] |= count;
+ }
+
+ dev_dbg(dev, "%s - count is %d\n", __func__, count);
+
+send:
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->write_urb_in_use = 1;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (priv->cmd_ctrl)
+ actual_size = 1;
+ else
+ actual_size = count +
+ (priv->pkt_fmt == packet_format_1 ? 2 : 1);
+
+ usb_serial_debug_data(dev, __func__, port->interrupt_out_size,
+ port->interrupt_out_urb->transfer_buffer);
+
+ usb_fill_int_urb(port->interrupt_out_urb, port->serial->dev,
+ usb_sndintpipe(port->serial->dev, port->interrupt_out_endpointAddress),
+ port->interrupt_out_buffer, actual_size,
+ cypress_write_int_callback, port, priv->write_urb_interval);
+ result = usb_submit_urb(port->interrupt_out_urb, GFP_ATOMIC);
+ if (result) {
+ dev_err_console(port,
+ "%s - failed submitting write urb, error %d\n",
+ __func__, result);
+ priv->write_urb_in_use = 0;
+ cypress_set_dead(port);
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->cmd_ctrl)
+ priv->cmd_ctrl = 0;
+
+ /* do not count the line control and size bytes */
+ priv->bytes_out += count;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ usb_serial_port_softint(port);
+} /* cypress_send */
+
+
+/* returns how much space is available in the soft buffer */
+static unsigned int cypress_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ unsigned int room;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ room = kfifo_avail(&priv->write_fifo);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, room);
+ return room;
+}
+
+
+static int cypress_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ __u8 status, control;
+ unsigned int result = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ control = priv->line_control;
+ status = priv->current_status;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ result = ((control & CONTROL_DTR) ? TIOCM_DTR : 0)
+ | ((control & CONTROL_RTS) ? TIOCM_RTS : 0)
+ | ((status & UART_CTS) ? TIOCM_CTS : 0)
+ | ((status & UART_DSR) ? TIOCM_DSR : 0)
+ | ((status & UART_RI) ? TIOCM_RI : 0)
+ | ((status & UART_CD) ? TIOCM_CD : 0);
+
+ dev_dbg(&port->dev, "%s - result = %x\n", __func__, result);
+
+ return result;
+}
+
+
+static int cypress_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (set & TIOCM_RTS)
+ priv->line_control |= CONTROL_RTS;
+ if (set & TIOCM_DTR)
+ priv->line_control |= CONTROL_DTR;
+ if (clear & TIOCM_RTS)
+ priv->line_control &= ~CONTROL_RTS;
+ if (clear & TIOCM_DTR)
+ priv->line_control &= ~CONTROL_DTR;
+ priv->cmd_ctrl = 1;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return cypress_write(tty, port, NULL, 0);
+}
+
+static void cypress_earthmate_init_termios(struct tty_struct *tty)
+{
+ tty_encode_baud_rate(tty, 4800, 4800);
+}
+
+static void cypress_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ struct device *dev = &port->dev;
+ int data_bits, stop_bits, parity_type, parity_enable;
+ unsigned int cflag;
+ unsigned long flags;
+ __u8 oldlines;
+ int linechange = 0;
+
+ /* Unsupported features need clearing */
+ tty->termios.c_cflag &= ~(CMSPAR|CRTSCTS);
+
+ cflag = tty->termios.c_cflag;
+
+ /* set number of data bits, parity, stop bits */
+ /* when parity is disabled the parity type bit is ignored */
+
+ /* 1 means 2 stop bits, 0 means 1 stop bit */
+ stop_bits = cflag & CSTOPB ? 1 : 0;
+
+ if (cflag & PARENB) {
+ parity_enable = 1;
+ /* 1 means odd parity, 0 means even parity */
+ parity_type = cflag & PARODD ? 1 : 0;
+ } else
+ parity_enable = parity_type = 0;
+
+ data_bits = tty_get_char_size(cflag);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ oldlines = priv->line_control;
+ if ((cflag & CBAUD) == B0) {
+ /* drop dtr and rts */
+ dev_dbg(dev, "%s - dropping the lines, baud rate 0bps\n", __func__);
+ priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS);
+ } else
+ priv->line_control = (CONTROL_DTR | CONTROL_RTS);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ dev_dbg(dev, "%s - sending %d stop_bits, %d parity_enable, %d parity_type, %d data_bits (+5)\n",
+ __func__, stop_bits, parity_enable, parity_type, data_bits);
+
+ cypress_serial_control(tty, port, tty_get_baud_rate(tty),
+ data_bits, stop_bits,
+ parity_enable, parity_type,
+ 0, CYPRESS_SET_CONFIG);
+
+ /* we perform a CYPRESS_GET_CONFIG so that the current settings are
+ * filled into the private structure this should confirm that all is
+ * working if it returns what we just set */
+ cypress_serial_control(tty, port, 0, 0, 0, 0, 0, 0, CYPRESS_GET_CONFIG);
+
+ /* Here we can define custom tty settings for devices; the main tty
+ * termios flag base comes from empeg.c */
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->chiptype == CT_EARTHMATE && priv->baud_rate == 4800) {
+ dev_dbg(dev, "Using custom termios settings for a baud rate of 4800bps.\n");
+ /* define custom termios settings for NMEA protocol */
+
+ tty->termios.c_iflag /* input modes - */
+ &= ~(IGNBRK /* disable ignore break */
+ | BRKINT /* disable break causes interrupt */
+ | PARMRK /* disable mark parity errors */
+ | ISTRIP /* disable clear high bit of input char */
+ | INLCR /* disable translate NL to CR */
+ | IGNCR /* disable ignore CR */
+ | ICRNL /* disable translate CR to NL */
+ | IXON); /* disable enable XON/XOFF flow control */
+
+ tty->termios.c_oflag /* output modes */
+ &= ~OPOST; /* disable postprocess output char */
+
+ tty->termios.c_lflag /* line discipline modes */
+ &= ~(ECHO /* disable echo input characters */
+ | ECHONL /* disable echo new line */
+ | ICANON /* disable erase, kill, werase, and rprnt
+ special characters */
+ | ISIG /* disable interrupt, quit, and suspend
+ special characters */
+ | IEXTEN); /* disable non-POSIX special characters */
+ } /* CT_CYPHIDCOM: Application should handle this for device */
+
+ linechange = (priv->line_control != oldlines);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /* if necessary, set lines */
+ if (linechange) {
+ priv->cmd_ctrl = 1;
+ cypress_write(tty, port, NULL, 0);
+ }
+} /* cypress_set_termios */
+
+
+/* returns amount of data still left in soft buffer */
+static unsigned int cypress_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ unsigned int chars;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ chars = kfifo_len(&priv->write_fifo);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, chars);
+ return chars;
+}
+
+
+static void cypress_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+
+ spin_lock_irq(&priv->lock);
+ priv->rx_flags = THROTTLED;
+ spin_unlock_irq(&priv->lock);
+}
+
+
+static void cypress_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ int actually_throttled, result;
+
+ spin_lock_irq(&priv->lock);
+ actually_throttled = priv->rx_flags & ACTUALLY_THROTTLED;
+ priv->rx_flags = 0;
+ spin_unlock_irq(&priv->lock);
+
+ if (!priv->comm_is_ok)
+ return;
+
+ if (actually_throttled) {
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (result) {
+ dev_err(&port->dev, "%s - failed submitting read urb, "
+ "error %d\n", __func__, result);
+ cypress_set_dead(port);
+ }
+ }
+}
+
+
+static void cypress_read_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ struct device *dev = &urb->dev->dev;
+ struct tty_struct *tty;
+ unsigned char *data = urb->transfer_buffer;
+ unsigned long flags;
+ char tty_flag = TTY_NORMAL;
+ int bytes = 0;
+ int result;
+ int status = urb->status;
+ int i;
+
+ switch (status) {
+ case 0: /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* precursor to disconnect so just go away */
+ return;
+ case -EPIPE:
+ /* Can't call usb_clear_halt while in_interrupt */
+ fallthrough;
+ default:
+ /* something ugly is going on... */
+ dev_err(dev, "%s - unexpected nonzero read status received: %d\n",
+ __func__, status);
+ cypress_set_dead(port);
+ return;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->rx_flags & THROTTLED) {
+ dev_dbg(dev, "%s - now throttling\n", __func__);
+ priv->rx_flags |= ACTUALLY_THROTTLED;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ tty = tty_port_tty_get(&port->port);
+ if (!tty) {
+ dev_dbg(dev, "%s - bad tty pointer - exiting\n", __func__);
+ return;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ result = urb->actual_length;
+ i = 0;
+ switch (priv->pkt_fmt) {
+ default:
+ case packet_format_1:
+ /* This is for the CY7C64013... */
+ if (result < 2)
+ break;
+ priv->current_status = data[0] & 0xF8;
+ bytes = data[1] + 2;
+ i = 2;
+ break;
+ case packet_format_2:
+ /* This is for the CY7C63743... */
+ if (result < 1)
+ break;
+ priv->current_status = data[0] & 0xF8;
+ bytes = (data[0] & 0x07) + 1;
+ i = 1;
+ break;
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+ if (i == 0) {
+ dev_dbg(dev, "%s - short packet received: %d bytes\n",
+ __func__, result);
+ goto continue_read;
+ }
+ if (result < bytes) {
+ dev_dbg(dev,
+ "%s - wrong packet size - received %d bytes but packet said %d bytes\n",
+ __func__, result, bytes);
+ goto continue_read;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ /* check to see if status has changed */
+ if (priv->current_status != priv->prev_status) {
+ u8 delta = priv->current_status ^ priv->prev_status;
+
+ if (delta & UART_MSR_MASK) {
+ if (delta & UART_CTS)
+ port->icount.cts++;
+ if (delta & UART_DSR)
+ port->icount.dsr++;
+ if (delta & UART_RI)
+ port->icount.rng++;
+ if (delta & UART_CD)
+ port->icount.dcd++;
+
+ wake_up_interruptible(&port->port.delta_msr_wait);
+ }
+
+ priv->prev_status = priv->current_status;
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /* hangup, as defined in acm.c... this might be a bad place for it
+ * though */
+ if (tty && !C_CLOCAL(tty) && !(priv->current_status & UART_CD)) {
+ dev_dbg(dev, "%s - calling hangup\n", __func__);
+ tty_hangup(tty);
+ goto continue_read;
+ }
+
+ /* There is one error bit... I'm assuming it is a parity error
+ * indicator as the generic firmware will set this bit to 1 if a
+ * parity error occurs.
+ * I can not find reference to any other error events. */
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->current_status & CYP_ERROR) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ tty_flag = TTY_PARITY;
+ dev_dbg(dev, "%s - Parity Error detected\n", __func__);
+ } else
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /* process read if there is data other than line status */
+ if (bytes > i) {
+ tty_insert_flip_string_fixed_flag(&port->port, data + i,
+ tty_flag, bytes - i);
+ tty_flip_buffer_push(&port->port);
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ /* control and status byte(s) are also counted */
+ priv->bytes_in += bytes;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+continue_read:
+ tty_kref_put(tty);
+
+ /* Continue trying to always read */
+
+ if (priv->comm_is_ok) {
+ usb_fill_int_urb(port->interrupt_in_urb, port->serial->dev,
+ usb_rcvintpipe(port->serial->dev,
+ port->interrupt_in_endpointAddress),
+ port->interrupt_in_urb->transfer_buffer,
+ port->interrupt_in_urb->transfer_buffer_length,
+ cypress_read_int_callback, port,
+ priv->read_urb_interval);
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
+ if (result && result != -EPERM) {
+ dev_err(dev, "%s - failed resubmitting read urb, error %d\n",
+ __func__, result);
+ cypress_set_dead(port);
+ }
+ }
+} /* cypress_read_int_callback */
+
+
+static void cypress_write_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct cypress_private *priv = usb_get_serial_port_data(port);
+ struct device *dev = &urb->dev->dev;
+ int status = urb->status;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ priv->write_urb_in_use = 0;
+ return;
+ case -EPIPE:
+ /* Cannot call usb_clear_halt while in_interrupt */
+ fallthrough;
+ default:
+ dev_err(dev, "%s - unexpected nonzero write status received: %d\n",
+ __func__, status);
+ cypress_set_dead(port);
+ break;
+ }
+ priv->write_urb_in_use = 0;
+
+ /* send any buffered data */
+ cypress_send(port);
+}
+
+module_usb_serial_driver(serial_drivers, id_table_combined);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+module_param(stats, bool, 0644);
+MODULE_PARM_DESC(stats, "Enable statistics or not");
+module_param(interval, int, 0644);
+MODULE_PARM_DESC(interval, "Overrides interrupt interval");
+module_param(unstable_bauds, bool, 0644);
+MODULE_PARM_DESC(unstable_bauds, "Allow unstable baud rates");
diff --git a/drivers/usb/serial/cypress_m8.h b/drivers/usb/serial/cypress_m8.h
new file mode 100644
index 000000000..16b7410ad
--- /dev/null
+++ b/drivers/usb/serial/cypress_m8.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef CYPRESS_M8_H
+#define CYPRESS_M8_H
+
+/*
+ * definitions and function prototypes used for the cypress USB to Serial
+ * controller
+ */
+
+/*
+ * For sending our feature buffer - controlling serial communication states.
+ * Linux HID has no support for serial devices so we do this through the driver
+ */
+#define HID_REQ_GET_REPORT 0x01
+#define HID_REQ_SET_REPORT 0x09
+
+/* List other cypress USB to Serial devices here, and add them to the id_table */
+
+/* DeLorme Earthmate USB - a GPS device */
+#define VENDOR_ID_DELORME 0x1163
+#define PRODUCT_ID_EARTHMATEUSB 0x0100
+#define PRODUCT_ID_EARTHMATEUSB_LT20 0x0200
+
+/* Cypress HID->COM RS232 Adapter */
+#define VENDOR_ID_CYPRESS 0x04b4
+#define PRODUCT_ID_CYPHIDCOM 0x5500
+
+/* Simply Automated HID->COM UPB PIM (using Cypress PID 0x5500) */
+#define VENDOR_ID_SAI 0x17dd
+
+/* FRWD Dongle - a GPS sports watch */
+#define VENDOR_ID_FRWD 0x6737
+#define PRODUCT_ID_CYPHIDCOM_FRWD 0x0001
+
+/* Powercom UPS, chip CY7C63723 */
+#define VENDOR_ID_POWERCOM 0x0d9f
+#define PRODUCT_ID_UPS 0x0002
+
+/* Nokia CA-42 USB to serial cable */
+#define VENDOR_ID_DAZZLE 0x07d0
+#define PRODUCT_ID_CA42 0x4101
+/* End of device listing */
+
+/* Used for setting / requesting serial line settings */
+#define CYPRESS_SET_CONFIG 0x01
+#define CYPRESS_GET_CONFIG 0x02
+
+/* Used for throttle control */
+#define THROTTLED 0x1
+#define ACTUALLY_THROTTLED 0x2
+
+/*
+ * chiptypes - used in case firmware differs from the generic form ... offering
+ * different baud speeds/etc.
+ */
+#define CT_EARTHMATE 0x01
+#define CT_CYPHIDCOM 0x02
+#define CT_CA42V2 0x03
+#define CT_GENERIC 0x0F
+/* End of chiptype definitions */
+
+/*
+ * RS-232 serial data communication protocol definitions.
+ *
+ * These are sent / read at byte 0 of the input/output hid reports.
+ * You can find these values defined in the CY4601 USB to Serial design notes.
+ */
+
+#define CONTROL_DTR 0x20 /* data terminal ready */
+#define CONTROL_RTS 0x10 /* request to send */
+#define CONTROL_RESET 0x08 /* sent with output report */
+
+#define UART_MSR_MASK 0xf0
+#define UART_RI 0x80 /* ring indicator */
+#define UART_CD 0x40 /* carrier detect */
+#define UART_DSR 0x20 /* data set ready */
+#define UART_CTS 0x10 /* clear to send */
+#define CYP_ERROR 0x08 /* received from input report */
+
+/* End of RS-232 protocol definitions */
+
+#endif /* CYPRESS_M8_H */
diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c
new file mode 100644
index 000000000..dea039163
--- /dev/null
+++ b/drivers/usb/serial/digi_acceleport.c
@@ -0,0 +1,1573 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+* Digi AccelePort USB-4 and USB-2 Serial Converters
+*
+* Copyright 2000 by Digi International
+*
+* Shamelessly based on Brian Warner's keyspan_pda.c and Greg Kroah-Hartman's
+* usb-serial driver.
+*
+* Peter Berger (pberger@brimson.com)
+* Al Borchers (borchers@steinerpoint.com)
+*/
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+#include <linux/sched/signal.h>
+#include <linux/usb/serial.h>
+
+/* Defines */
+
+#define DRIVER_AUTHOR "Peter Berger <pberger@brimson.com>, Al Borchers <borchers@steinerpoint.com>"
+#define DRIVER_DESC "Digi AccelePort USB-2/USB-4 Serial Converter driver"
+
+/* port output buffer length -- must be <= transfer buffer length - 2 */
+/* so we can be sure to send the full buffer in one urb */
+#define DIGI_OUT_BUF_SIZE 8
+
+/* port input buffer length -- must be >= transfer buffer length - 3 */
+/* so we can be sure to hold at least one full buffer from one urb */
+#define DIGI_IN_BUF_SIZE 64
+
+/* retry timeout while sleeping */
+#define DIGI_RETRY_TIMEOUT (HZ/10)
+
+/* timeout while waiting for tty output to drain in close */
+/* this delay is used twice in close, so the total delay could */
+/* be twice this value */
+#define DIGI_CLOSE_TIMEOUT (5*HZ)
+
+
+/* AccelePort USB Defines */
+
+/* ids */
+#define DIGI_VENDOR_ID 0x05c5
+#define DIGI_2_ID 0x0002 /* USB-2 */
+#define DIGI_4_ID 0x0004 /* USB-4 */
+
+/* commands
+ * "INB": can be used on the in-band endpoint
+ * "OOB": can be used on the out-of-band endpoint
+ */
+#define DIGI_CMD_SET_BAUD_RATE 0 /* INB, OOB */
+#define DIGI_CMD_SET_WORD_SIZE 1 /* INB, OOB */
+#define DIGI_CMD_SET_PARITY 2 /* INB, OOB */
+#define DIGI_CMD_SET_STOP_BITS 3 /* INB, OOB */
+#define DIGI_CMD_SET_INPUT_FLOW_CONTROL 4 /* INB, OOB */
+#define DIGI_CMD_SET_OUTPUT_FLOW_CONTROL 5 /* INB, OOB */
+#define DIGI_CMD_SET_DTR_SIGNAL 6 /* INB, OOB */
+#define DIGI_CMD_SET_RTS_SIGNAL 7 /* INB, OOB */
+#define DIGI_CMD_READ_INPUT_SIGNALS 8 /* OOB */
+#define DIGI_CMD_IFLUSH_FIFO 9 /* OOB */
+#define DIGI_CMD_RECEIVE_ENABLE 10 /* INB, OOB */
+#define DIGI_CMD_BREAK_CONTROL 11 /* INB, OOB */
+#define DIGI_CMD_LOCAL_LOOPBACK 12 /* INB, OOB */
+#define DIGI_CMD_TRANSMIT_IDLE 13 /* INB, OOB */
+#define DIGI_CMD_READ_UART_REGISTER 14 /* OOB */
+#define DIGI_CMD_WRITE_UART_REGISTER 15 /* INB, OOB */
+#define DIGI_CMD_AND_UART_REGISTER 16 /* INB, OOB */
+#define DIGI_CMD_OR_UART_REGISTER 17 /* INB, OOB */
+#define DIGI_CMD_SEND_DATA 18 /* INB */
+#define DIGI_CMD_RECEIVE_DATA 19 /* INB */
+#define DIGI_CMD_RECEIVE_DISABLE 20 /* INB */
+#define DIGI_CMD_GET_PORT_TYPE 21 /* OOB */
+
+/* baud rates */
+#define DIGI_BAUD_50 0
+#define DIGI_BAUD_75 1
+#define DIGI_BAUD_110 2
+#define DIGI_BAUD_150 3
+#define DIGI_BAUD_200 4
+#define DIGI_BAUD_300 5
+#define DIGI_BAUD_600 6
+#define DIGI_BAUD_1200 7
+#define DIGI_BAUD_1800 8
+#define DIGI_BAUD_2400 9
+#define DIGI_BAUD_4800 10
+#define DIGI_BAUD_7200 11
+#define DIGI_BAUD_9600 12
+#define DIGI_BAUD_14400 13
+#define DIGI_BAUD_19200 14
+#define DIGI_BAUD_28800 15
+#define DIGI_BAUD_38400 16
+#define DIGI_BAUD_57600 17
+#define DIGI_BAUD_76800 18
+#define DIGI_BAUD_115200 19
+#define DIGI_BAUD_153600 20
+#define DIGI_BAUD_230400 21
+#define DIGI_BAUD_460800 22
+
+/* arguments */
+#define DIGI_WORD_SIZE_5 0
+#define DIGI_WORD_SIZE_6 1
+#define DIGI_WORD_SIZE_7 2
+#define DIGI_WORD_SIZE_8 3
+
+#define DIGI_PARITY_NONE 0
+#define DIGI_PARITY_ODD 1
+#define DIGI_PARITY_EVEN 2
+#define DIGI_PARITY_MARK 3
+#define DIGI_PARITY_SPACE 4
+
+#define DIGI_STOP_BITS_1 0
+#define DIGI_STOP_BITS_2 1
+
+#define DIGI_INPUT_FLOW_CONTROL_XON_XOFF 1
+#define DIGI_INPUT_FLOW_CONTROL_RTS 2
+#define DIGI_INPUT_FLOW_CONTROL_DTR 4
+
+#define DIGI_OUTPUT_FLOW_CONTROL_XON_XOFF 1
+#define DIGI_OUTPUT_FLOW_CONTROL_CTS 2
+#define DIGI_OUTPUT_FLOW_CONTROL_DSR 4
+
+#define DIGI_DTR_INACTIVE 0
+#define DIGI_DTR_ACTIVE 1
+#define DIGI_DTR_INPUT_FLOW_CONTROL 2
+
+#define DIGI_RTS_INACTIVE 0
+#define DIGI_RTS_ACTIVE 1
+#define DIGI_RTS_INPUT_FLOW_CONTROL 2
+#define DIGI_RTS_TOGGLE 3
+
+#define DIGI_FLUSH_TX 1
+#define DIGI_FLUSH_RX 2
+#define DIGI_RESUME_TX 4 /* clears xoff condition */
+
+#define DIGI_TRANSMIT_NOT_IDLE 0
+#define DIGI_TRANSMIT_IDLE 1
+
+#define DIGI_DISABLE 0
+#define DIGI_ENABLE 1
+
+#define DIGI_DEASSERT 0
+#define DIGI_ASSERT 1
+
+/* in band status codes */
+#define DIGI_OVERRUN_ERROR 4
+#define DIGI_PARITY_ERROR 8
+#define DIGI_FRAMING_ERROR 16
+#define DIGI_BREAK_ERROR 32
+
+/* out of band status */
+#define DIGI_NO_ERROR 0
+#define DIGI_BAD_FIRST_PARAMETER 1
+#define DIGI_BAD_SECOND_PARAMETER 2
+#define DIGI_INVALID_LINE 3
+#define DIGI_INVALID_OPCODE 4
+
+/* input signals */
+#define DIGI_READ_INPUT_SIGNALS_SLOT 1
+#define DIGI_READ_INPUT_SIGNALS_ERR 2
+#define DIGI_READ_INPUT_SIGNALS_BUSY 4
+#define DIGI_READ_INPUT_SIGNALS_PE 8
+#define DIGI_READ_INPUT_SIGNALS_CTS 16
+#define DIGI_READ_INPUT_SIGNALS_DSR 32
+#define DIGI_READ_INPUT_SIGNALS_RI 64
+#define DIGI_READ_INPUT_SIGNALS_DCD 128
+
+
+/* Structures */
+
+struct digi_serial {
+ spinlock_t ds_serial_lock;
+ struct usb_serial_port *ds_oob_port; /* out-of-band port */
+ int ds_oob_port_num; /* index of out-of-band port */
+ int ds_device_started;
+};
+
+struct digi_port {
+ spinlock_t dp_port_lock;
+ int dp_port_num;
+ int dp_out_buf_len;
+ unsigned char dp_out_buf[DIGI_OUT_BUF_SIZE];
+ int dp_write_urb_in_use;
+ unsigned int dp_modem_signals;
+ int dp_transmit_idle;
+ wait_queue_head_t dp_transmit_idle_wait;
+ int dp_throttled;
+ int dp_throttle_restart;
+ wait_queue_head_t dp_flush_wait;
+ wait_queue_head_t dp_close_wait; /* wait queue for close */
+ wait_queue_head_t write_wait;
+ struct usb_serial_port *dp_port;
+};
+
+
+/* Local Function Declarations */
+
+static int digi_write_oob_command(struct usb_serial_port *port,
+ unsigned char *buf, int count, int interruptible);
+static int digi_write_inb_command(struct usb_serial_port *port,
+ unsigned char *buf, int count, unsigned long timeout);
+static int digi_set_modem_signals(struct usb_serial_port *port,
+ unsigned int modem_signals, int interruptible);
+static int digi_transmit_idle(struct usb_serial_port *port,
+ unsigned long timeout);
+static void digi_rx_throttle(struct tty_struct *tty);
+static void digi_rx_unthrottle(struct tty_struct *tty);
+static void digi_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+static int digi_break_ctl(struct tty_struct *tty, int break_state);
+static int digi_tiocmget(struct tty_struct *tty);
+static int digi_tiocmset(struct tty_struct *tty, unsigned int set,
+ unsigned int clear);
+static int digi_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count);
+static void digi_write_bulk_callback(struct urb *urb);
+static unsigned int digi_write_room(struct tty_struct *tty);
+static unsigned int digi_chars_in_buffer(struct tty_struct *tty);
+static int digi_open(struct tty_struct *tty, struct usb_serial_port *port);
+static void digi_close(struct usb_serial_port *port);
+static void digi_dtr_rts(struct usb_serial_port *port, int on);
+static int digi_startup_device(struct usb_serial *serial);
+static int digi_startup(struct usb_serial *serial);
+static void digi_disconnect(struct usb_serial *serial);
+static void digi_release(struct usb_serial *serial);
+static int digi_port_probe(struct usb_serial_port *port);
+static void digi_port_remove(struct usb_serial_port *port);
+static void digi_read_bulk_callback(struct urb *urb);
+static int digi_read_inb_callback(struct urb *urb);
+static int digi_read_oob_callback(struct urb *urb);
+
+
+static const struct usb_device_id id_table_combined[] = {
+ { USB_DEVICE(DIGI_VENDOR_ID, DIGI_2_ID) },
+ { USB_DEVICE(DIGI_VENDOR_ID, DIGI_4_ID) },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id id_table_2[] = {
+ { USB_DEVICE(DIGI_VENDOR_ID, DIGI_2_ID) },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id id_table_4[] = {
+ { USB_DEVICE(DIGI_VENDOR_ID, DIGI_4_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table_combined);
+
+/* device info needed for the Digi serial converter */
+
+static struct usb_serial_driver digi_acceleport_2_device = {
+ .driver = {
+ .name = "digi_2",
+ },
+ .description = "Digi 2 port USB adapter",
+ .id_table = id_table_2,
+ .num_ports = 3,
+ .num_bulk_in = 4,
+ .num_bulk_out = 4,
+ .open = digi_open,
+ .close = digi_close,
+ .dtr_rts = digi_dtr_rts,
+ .write = digi_write,
+ .write_room = digi_write_room,
+ .write_bulk_callback = digi_write_bulk_callback,
+ .read_bulk_callback = digi_read_bulk_callback,
+ .chars_in_buffer = digi_chars_in_buffer,
+ .throttle = digi_rx_throttle,
+ .unthrottle = digi_rx_unthrottle,
+ .set_termios = digi_set_termios,
+ .break_ctl = digi_break_ctl,
+ .tiocmget = digi_tiocmget,
+ .tiocmset = digi_tiocmset,
+ .attach = digi_startup,
+ .disconnect = digi_disconnect,
+ .release = digi_release,
+ .port_probe = digi_port_probe,
+ .port_remove = digi_port_remove,
+};
+
+static struct usb_serial_driver digi_acceleport_4_device = {
+ .driver = {
+ .name = "digi_4",
+ },
+ .description = "Digi 4 port USB adapter",
+ .id_table = id_table_4,
+ .num_ports = 4,
+ .num_bulk_in = 5,
+ .num_bulk_out = 5,
+ .open = digi_open,
+ .close = digi_close,
+ .write = digi_write,
+ .write_room = digi_write_room,
+ .write_bulk_callback = digi_write_bulk_callback,
+ .read_bulk_callback = digi_read_bulk_callback,
+ .chars_in_buffer = digi_chars_in_buffer,
+ .throttle = digi_rx_throttle,
+ .unthrottle = digi_rx_unthrottle,
+ .set_termios = digi_set_termios,
+ .break_ctl = digi_break_ctl,
+ .tiocmget = digi_tiocmget,
+ .tiocmset = digi_tiocmset,
+ .attach = digi_startup,
+ .disconnect = digi_disconnect,
+ .release = digi_release,
+ .port_probe = digi_port_probe,
+ .port_remove = digi_port_remove,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &digi_acceleport_2_device, &digi_acceleport_4_device, NULL
+};
+
+/* Functions */
+
+/*
+ * Cond Wait Interruptible Timeout Irqrestore
+ *
+ * Do spin_unlock_irqrestore and interruptible_sleep_on_timeout
+ * so that wake ups are not lost if they occur between the unlock
+ * and the sleep. In other words, spin_unlock_irqrestore and
+ * interruptible_sleep_on_timeout are "atomic" with respect to
+ * wake ups. This is used to implement condition variables.
+ *
+ * interruptible_sleep_on_timeout is deprecated and has been replaced
+ * with the equivalent code.
+ */
+
+static long cond_wait_interruptible_timeout_irqrestore(
+ wait_queue_head_t *q, long timeout,
+ spinlock_t *lock, unsigned long flags)
+__releases(lock)
+{
+ DEFINE_WAIT(wait);
+
+ prepare_to_wait(q, &wait, TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(lock, flags);
+ timeout = schedule_timeout(timeout);
+ finish_wait(q, &wait);
+
+ return timeout;
+}
+
+/*
+ * Digi Write OOB Command
+ *
+ * Write commands on the out of band port. Commands are 4
+ * bytes each, multiple commands can be sent at once, and
+ * no command will be split across USB packets. Returns 0
+ * if successful, -EINTR if interrupted while sleeping and
+ * the interruptible flag is true, or a negative error
+ * returned by usb_submit_urb.
+ */
+
+static int digi_write_oob_command(struct usb_serial_port *port,
+ unsigned char *buf, int count, int interruptible)
+{
+ int ret = 0;
+ int len;
+ struct usb_serial_port *oob_port = (struct usb_serial_port *)((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port;
+ struct digi_port *oob_priv = usb_get_serial_port_data(oob_port);
+ unsigned long flags;
+
+ dev_dbg(&port->dev,
+ "digi_write_oob_command: TOP: port=%d, count=%d\n",
+ oob_priv->dp_port_num, count);
+
+ spin_lock_irqsave(&oob_priv->dp_port_lock, flags);
+ while (count > 0) {
+ while (oob_priv->dp_write_urb_in_use) {
+ cond_wait_interruptible_timeout_irqrestore(
+ &oob_priv->write_wait, DIGI_RETRY_TIMEOUT,
+ &oob_priv->dp_port_lock, flags);
+ if (interruptible && signal_pending(current))
+ return -EINTR;
+ spin_lock_irqsave(&oob_priv->dp_port_lock, flags);
+ }
+
+ /* len must be a multiple of 4, so commands are not split */
+ len = min(count, oob_port->bulk_out_size);
+ if (len > 4)
+ len &= ~3;
+ memcpy(oob_port->write_urb->transfer_buffer, buf, len);
+ oob_port->write_urb->transfer_buffer_length = len;
+
+ ret = usb_submit_urb(oob_port->write_urb, GFP_ATOMIC);
+ if (ret)
+ break;
+
+ oob_priv->dp_write_urb_in_use = 1;
+ count -= len;
+ buf += len;
+ }
+ spin_unlock_irqrestore(&oob_priv->dp_port_lock, flags);
+ if (ret)
+ dev_err(&port->dev, "%s: usb_submit_urb failed, ret=%d\n",
+ __func__, ret);
+ return ret;
+
+}
+
+
+/*
+ * Digi Write In Band Command
+ *
+ * Write commands on the given port. Commands are 4
+ * bytes each, multiple commands can be sent at once, and
+ * no command will be split across USB packets. If timeout
+ * is non-zero, write in band command will return after
+ * waiting unsuccessfully for the URB status to clear for
+ * timeout ticks. Returns 0 if successful, or a negative
+ * error returned by digi_write.
+ */
+
+static int digi_write_inb_command(struct usb_serial_port *port,
+ unsigned char *buf, int count, unsigned long timeout)
+{
+ int ret = 0;
+ int len;
+ struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned char *data = port->write_urb->transfer_buffer;
+ unsigned long expire;
+ unsigned long flags;
+
+ dev_dbg(&port->dev, "digi_write_inb_command: TOP: port=%d, count=%d\n",
+ priv->dp_port_num, count);
+
+ if (timeout)
+ expire = jiffies + timeout;
+
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+ while (count > 0 && ret == 0) {
+ while (priv->dp_write_urb_in_use) {
+ if (timeout && time_after(jiffies, expire)) {
+ ret = -ETIMEDOUT;
+ break;
+ }
+ cond_wait_interruptible_timeout_irqrestore(
+ &priv->write_wait, DIGI_RETRY_TIMEOUT,
+ &priv->dp_port_lock, flags);
+ if (signal_pending(current))
+ return -EINTR;
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+ }
+
+ if (ret)
+ break;
+
+ /* len must be a multiple of 4 and small enough to */
+ /* guarantee the write will send buffered data first, */
+ /* so commands are in order with data and not split */
+ len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len);
+ if (len > 4)
+ len &= ~3;
+
+ /* write any buffered data first */
+ if (priv->dp_out_buf_len > 0) {
+ data[0] = DIGI_CMD_SEND_DATA;
+ data[1] = priv->dp_out_buf_len;
+ memcpy(data + 2, priv->dp_out_buf,
+ priv->dp_out_buf_len);
+ memcpy(data + 2 + priv->dp_out_buf_len, buf, len);
+ port->write_urb->transfer_buffer_length
+ = priv->dp_out_buf_len + 2 + len;
+ } else {
+ memcpy(data, buf, len);
+ port->write_urb->transfer_buffer_length = len;
+ }
+
+ ret = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ if (ret == 0) {
+ priv->dp_write_urb_in_use = 1;
+ priv->dp_out_buf_len = 0;
+ count -= len;
+ buf += len;
+ }
+
+ }
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+
+ if (ret)
+ dev_err(&port->dev,
+ "%s: usb_submit_urb failed, ret=%d, port=%d\n",
+ __func__, ret, priv->dp_port_num);
+ return ret;
+}
+
+
+/*
+ * Digi Set Modem Signals
+ *
+ * Sets or clears DTR and RTS on the port, according to the
+ * modem_signals argument. Use TIOCM_DTR and TIOCM_RTS flags
+ * for the modem_signals argument. Returns 0 if successful,
+ * -EINTR if interrupted while sleeping, or a non-zero error
+ * returned by usb_submit_urb.
+ */
+
+static int digi_set_modem_signals(struct usb_serial_port *port,
+ unsigned int modem_signals, int interruptible)
+{
+
+ int ret;
+ struct digi_port *port_priv = usb_get_serial_port_data(port);
+ struct usb_serial_port *oob_port = (struct usb_serial_port *) ((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port;
+ struct digi_port *oob_priv = usb_get_serial_port_data(oob_port);
+ unsigned char *data = oob_port->write_urb->transfer_buffer;
+ unsigned long flags;
+
+ dev_dbg(&port->dev,
+ "digi_set_modem_signals: TOP: port=%d, modem_signals=0x%x\n",
+ port_priv->dp_port_num, modem_signals);
+
+ spin_lock_irqsave(&oob_priv->dp_port_lock, flags);
+ spin_lock(&port_priv->dp_port_lock);
+
+ while (oob_priv->dp_write_urb_in_use) {
+ spin_unlock(&port_priv->dp_port_lock);
+ cond_wait_interruptible_timeout_irqrestore(
+ &oob_priv->write_wait, DIGI_RETRY_TIMEOUT,
+ &oob_priv->dp_port_lock, flags);
+ if (interruptible && signal_pending(current))
+ return -EINTR;
+ spin_lock_irqsave(&oob_priv->dp_port_lock, flags);
+ spin_lock(&port_priv->dp_port_lock);
+ }
+ data[0] = DIGI_CMD_SET_DTR_SIGNAL;
+ data[1] = port_priv->dp_port_num;
+ data[2] = (modem_signals & TIOCM_DTR) ?
+ DIGI_DTR_ACTIVE : DIGI_DTR_INACTIVE;
+ data[3] = 0;
+ data[4] = DIGI_CMD_SET_RTS_SIGNAL;
+ data[5] = port_priv->dp_port_num;
+ data[6] = (modem_signals & TIOCM_RTS) ?
+ DIGI_RTS_ACTIVE : DIGI_RTS_INACTIVE;
+ data[7] = 0;
+
+ oob_port->write_urb->transfer_buffer_length = 8;
+
+ ret = usb_submit_urb(oob_port->write_urb, GFP_ATOMIC);
+ if (ret == 0) {
+ oob_priv->dp_write_urb_in_use = 1;
+ port_priv->dp_modem_signals &= ~(TIOCM_DTR | TIOCM_RTS);
+ port_priv->dp_modem_signals |=
+ modem_signals & (TIOCM_DTR | TIOCM_RTS);
+ }
+ spin_unlock(&port_priv->dp_port_lock);
+ spin_unlock_irqrestore(&oob_priv->dp_port_lock, flags);
+ if (ret)
+ dev_err(&port->dev, "%s: usb_submit_urb failed, ret=%d\n",
+ __func__, ret);
+ return ret;
+}
+
+/*
+ * Digi Transmit Idle
+ *
+ * Digi transmit idle waits, up to timeout ticks, for the transmitter
+ * to go idle. It returns 0 if successful or a negative error.
+ *
+ * There are race conditions here if more than one process is calling
+ * digi_transmit_idle on the same port at the same time. However, this
+ * is only called from close, and only one process can be in close on a
+ * port at a time, so its ok.
+ */
+
+static int digi_transmit_idle(struct usb_serial_port *port,
+ unsigned long timeout)
+{
+ int ret;
+ unsigned char buf[2];
+ struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+ priv->dp_transmit_idle = 0;
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+
+ buf[0] = DIGI_CMD_TRANSMIT_IDLE;
+ buf[1] = 0;
+
+ timeout += jiffies;
+
+ ret = digi_write_inb_command(port, buf, 2, timeout - jiffies);
+ if (ret != 0)
+ return ret;
+
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+
+ while (time_before(jiffies, timeout) && !priv->dp_transmit_idle) {
+ cond_wait_interruptible_timeout_irqrestore(
+ &priv->dp_transmit_idle_wait, DIGI_RETRY_TIMEOUT,
+ &priv->dp_port_lock, flags);
+ if (signal_pending(current))
+ return -EINTR;
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+ }
+ priv->dp_transmit_idle = 0;
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+ return 0;
+
+}
+
+
+static void digi_rx_throttle(struct tty_struct *tty)
+{
+ unsigned long flags;
+ struct usb_serial_port *port = tty->driver_data;
+ struct digi_port *priv = usb_get_serial_port_data(port);
+
+ /* stop receiving characters by not resubmitting the read urb */
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+ priv->dp_throttled = 1;
+ priv->dp_throttle_restart = 0;
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+}
+
+
+static void digi_rx_unthrottle(struct tty_struct *tty)
+{
+ int ret = 0;
+ unsigned long flags;
+ struct usb_serial_port *port = tty->driver_data;
+ struct digi_port *priv = usb_get_serial_port_data(port);
+
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+
+ /* restart read chain */
+ if (priv->dp_throttle_restart)
+ ret = usb_submit_urb(port->read_urb, GFP_ATOMIC);
+
+ /* turn throttle off */
+ priv->dp_throttled = 0;
+ priv->dp_throttle_restart = 0;
+
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+
+ if (ret)
+ dev_err(&port->dev,
+ "%s: usb_submit_urb failed, ret=%d, port=%d\n",
+ __func__, ret, priv->dp_port_num);
+}
+
+
+static void digi_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct digi_port *priv = usb_get_serial_port_data(port);
+ struct device *dev = &port->dev;
+ unsigned int iflag = tty->termios.c_iflag;
+ unsigned int cflag = tty->termios.c_cflag;
+ unsigned int old_iflag = old_termios->c_iflag;
+ unsigned int old_cflag = old_termios->c_cflag;
+ unsigned char buf[32];
+ unsigned int modem_signals;
+ int arg, ret;
+ int i = 0;
+ speed_t baud;
+
+ dev_dbg(dev,
+ "digi_set_termios: TOP: port=%d, iflag=0x%x, old_iflag=0x%x, cflag=0x%x, old_cflag=0x%x\n",
+ priv->dp_port_num, iflag, old_iflag, cflag, old_cflag);
+
+ /* set baud rate */
+ baud = tty_get_baud_rate(tty);
+ if (baud != tty_termios_baud_rate(old_termios)) {
+ arg = -1;
+
+ /* reassert DTR and (maybe) RTS on transition from B0 */
+ if ((old_cflag & CBAUD) == B0) {
+ /* don't set RTS if using hardware flow control */
+ /* and throttling input */
+ modem_signals = TIOCM_DTR;
+ if (!C_CRTSCTS(tty) || !tty_throttled(tty))
+ modem_signals |= TIOCM_RTS;
+ digi_set_modem_signals(port, modem_signals, 1);
+ }
+ switch (baud) {
+ /* drop DTR and RTS on transition to B0 */
+ case 0: digi_set_modem_signals(port, 0, 1); break;
+ case 50: arg = DIGI_BAUD_50; break;
+ case 75: arg = DIGI_BAUD_75; break;
+ case 110: arg = DIGI_BAUD_110; break;
+ case 150: arg = DIGI_BAUD_150; break;
+ case 200: arg = DIGI_BAUD_200; break;
+ case 300: arg = DIGI_BAUD_300; break;
+ case 600: arg = DIGI_BAUD_600; break;
+ case 1200: arg = DIGI_BAUD_1200; break;
+ case 1800: arg = DIGI_BAUD_1800; break;
+ case 2400: arg = DIGI_BAUD_2400; break;
+ case 4800: arg = DIGI_BAUD_4800; break;
+ case 9600: arg = DIGI_BAUD_9600; break;
+ case 19200: arg = DIGI_BAUD_19200; break;
+ case 38400: arg = DIGI_BAUD_38400; break;
+ case 57600: arg = DIGI_BAUD_57600; break;
+ case 115200: arg = DIGI_BAUD_115200; break;
+ case 230400: arg = DIGI_BAUD_230400; break;
+ case 460800: arg = DIGI_BAUD_460800; break;
+ default:
+ arg = DIGI_BAUD_9600;
+ baud = 9600;
+ break;
+ }
+ if (arg != -1) {
+ buf[i++] = DIGI_CMD_SET_BAUD_RATE;
+ buf[i++] = priv->dp_port_num;
+ buf[i++] = arg;
+ buf[i++] = 0;
+ }
+ }
+ /* set parity */
+ tty->termios.c_cflag &= ~CMSPAR;
+
+ if ((cflag & (PARENB | PARODD)) != (old_cflag & (PARENB | PARODD))) {
+ if (cflag & PARENB) {
+ if (cflag & PARODD)
+ arg = DIGI_PARITY_ODD;
+ else
+ arg = DIGI_PARITY_EVEN;
+ } else {
+ arg = DIGI_PARITY_NONE;
+ }
+ buf[i++] = DIGI_CMD_SET_PARITY;
+ buf[i++] = priv->dp_port_num;
+ buf[i++] = arg;
+ buf[i++] = 0;
+ }
+ /* set word size */
+ if ((cflag & CSIZE) != (old_cflag & CSIZE)) {
+ arg = -1;
+ switch (cflag & CSIZE) {
+ case CS5: arg = DIGI_WORD_SIZE_5; break;
+ case CS6: arg = DIGI_WORD_SIZE_6; break;
+ case CS7: arg = DIGI_WORD_SIZE_7; break;
+ case CS8: arg = DIGI_WORD_SIZE_8; break;
+ default:
+ dev_dbg(dev,
+ "digi_set_termios: can't handle word size %d\n",
+ cflag & CSIZE);
+ break;
+ }
+
+ if (arg != -1) {
+ buf[i++] = DIGI_CMD_SET_WORD_SIZE;
+ buf[i++] = priv->dp_port_num;
+ buf[i++] = arg;
+ buf[i++] = 0;
+ }
+
+ }
+
+ /* set stop bits */
+ if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) {
+
+ if ((cflag & CSTOPB))
+ arg = DIGI_STOP_BITS_2;
+ else
+ arg = DIGI_STOP_BITS_1;
+
+ buf[i++] = DIGI_CMD_SET_STOP_BITS;
+ buf[i++] = priv->dp_port_num;
+ buf[i++] = arg;
+ buf[i++] = 0;
+
+ }
+
+ /* set input flow control */
+ if ((iflag & IXOFF) != (old_iflag & IXOFF) ||
+ (cflag & CRTSCTS) != (old_cflag & CRTSCTS)) {
+ arg = 0;
+ if (iflag & IXOFF)
+ arg |= DIGI_INPUT_FLOW_CONTROL_XON_XOFF;
+ else
+ arg &= ~DIGI_INPUT_FLOW_CONTROL_XON_XOFF;
+
+ if (cflag & CRTSCTS) {
+ arg |= DIGI_INPUT_FLOW_CONTROL_RTS;
+
+ /* On USB-4 it is necessary to assert RTS prior */
+ /* to selecting RTS input flow control. */
+ buf[i++] = DIGI_CMD_SET_RTS_SIGNAL;
+ buf[i++] = priv->dp_port_num;
+ buf[i++] = DIGI_RTS_ACTIVE;
+ buf[i++] = 0;
+
+ } else {
+ arg &= ~DIGI_INPUT_FLOW_CONTROL_RTS;
+ }
+ buf[i++] = DIGI_CMD_SET_INPUT_FLOW_CONTROL;
+ buf[i++] = priv->dp_port_num;
+ buf[i++] = arg;
+ buf[i++] = 0;
+ }
+
+ /* set output flow control */
+ if ((iflag & IXON) != (old_iflag & IXON) ||
+ (cflag & CRTSCTS) != (old_cflag & CRTSCTS)) {
+ arg = 0;
+ if (iflag & IXON)
+ arg |= DIGI_OUTPUT_FLOW_CONTROL_XON_XOFF;
+ else
+ arg &= ~DIGI_OUTPUT_FLOW_CONTROL_XON_XOFF;
+
+ if (cflag & CRTSCTS)
+ arg |= DIGI_OUTPUT_FLOW_CONTROL_CTS;
+ else
+ arg &= ~DIGI_OUTPUT_FLOW_CONTROL_CTS;
+
+ buf[i++] = DIGI_CMD_SET_OUTPUT_FLOW_CONTROL;
+ buf[i++] = priv->dp_port_num;
+ buf[i++] = arg;
+ buf[i++] = 0;
+ }
+
+ /* set receive enable/disable */
+ if ((cflag & CREAD) != (old_cflag & CREAD)) {
+ if (cflag & CREAD)
+ arg = DIGI_ENABLE;
+ else
+ arg = DIGI_DISABLE;
+
+ buf[i++] = DIGI_CMD_RECEIVE_ENABLE;
+ buf[i++] = priv->dp_port_num;
+ buf[i++] = arg;
+ buf[i++] = 0;
+ }
+ ret = digi_write_oob_command(port, buf, i, 1);
+ if (ret != 0)
+ dev_dbg(dev, "digi_set_termios: write oob failed, ret=%d\n", ret);
+ tty_encode_baud_rate(tty, baud, baud);
+}
+
+
+static int digi_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned char buf[4];
+
+ buf[0] = DIGI_CMD_BREAK_CONTROL;
+ buf[1] = 2; /* length */
+ buf[2] = break_state ? 1 : 0;
+ buf[3] = 0; /* pad */
+
+ return digi_write_inb_command(port, buf, 4, 0);
+}
+
+
+static int digi_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned int val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+ val = priv->dp_modem_signals;
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+ return val;
+}
+
+
+static int digi_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned int val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+ val = (priv->dp_modem_signals & ~clear) | set;
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+ return digi_set_modem_signals(port, val, 1);
+}
+
+
+static int digi_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count)
+{
+
+ int ret, data_len, new_len;
+ struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned char *data = port->write_urb->transfer_buffer;
+ unsigned long flags;
+
+ dev_dbg(&port->dev, "digi_write: TOP: port=%d, count=%d\n",
+ priv->dp_port_num, count);
+
+ /* copy user data (which can sleep) before getting spin lock */
+ count = min(count, port->bulk_out_size-2);
+ count = min(64, count);
+
+ /* be sure only one write proceeds at a time */
+ /* there are races on the port private buffer */
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+
+ /* wait for urb status clear to submit another urb */
+ if (priv->dp_write_urb_in_use) {
+ /* buffer data if count is 1 (probably put_char) if possible */
+ if (count == 1 && priv->dp_out_buf_len < DIGI_OUT_BUF_SIZE) {
+ priv->dp_out_buf[priv->dp_out_buf_len++] = *buf;
+ new_len = 1;
+ } else {
+ new_len = 0;
+ }
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+ return new_len;
+ }
+
+ /* allow space for any buffered data and for new data, up to */
+ /* transfer buffer size - 2 (for command and length bytes) */
+ new_len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len);
+ data_len = new_len + priv->dp_out_buf_len;
+
+ if (data_len == 0) {
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+ return 0;
+ }
+
+ port->write_urb->transfer_buffer_length = data_len+2;
+
+ *data++ = DIGI_CMD_SEND_DATA;
+ *data++ = data_len;
+
+ /* copy in buffered data first */
+ memcpy(data, priv->dp_out_buf, priv->dp_out_buf_len);
+ data += priv->dp_out_buf_len;
+
+ /* copy in new data */
+ memcpy(data, buf, new_len);
+
+ ret = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ if (ret == 0) {
+ priv->dp_write_urb_in_use = 1;
+ ret = new_len;
+ priv->dp_out_buf_len = 0;
+ }
+
+ /* return length of new data written, or error */
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+ if (ret < 0)
+ dev_err_console(port,
+ "%s: usb_submit_urb failed, ret=%d, port=%d\n",
+ __func__, ret, priv->dp_port_num);
+ dev_dbg(&port->dev, "digi_write: returning %d\n", ret);
+ return ret;
+
+}
+
+static void digi_write_bulk_callback(struct urb *urb)
+{
+
+ struct usb_serial_port *port = urb->context;
+ struct usb_serial *serial;
+ struct digi_port *priv;
+ struct digi_serial *serial_priv;
+ unsigned long flags;
+ int ret = 0;
+ int status = urb->status;
+ bool wakeup;
+
+ /* port and serial sanity check */
+ if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) {
+ pr_err("%s: port or port->private is NULL, status=%d\n",
+ __func__, status);
+ return;
+ }
+ serial = port->serial;
+ if (serial == NULL || (serial_priv = usb_get_serial_data(serial)) == NULL) {
+ dev_err(&port->dev,
+ "%s: serial or serial->private is NULL, status=%d\n",
+ __func__, status);
+ return;
+ }
+
+ /* handle oob callback */
+ if (priv->dp_port_num == serial_priv->ds_oob_port_num) {
+ dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n");
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+ priv->dp_write_urb_in_use = 0;
+ wake_up_interruptible(&priv->write_wait);
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+ return;
+ }
+
+ /* try to send any buffered data on this port */
+ wakeup = true;
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+ priv->dp_write_urb_in_use = 0;
+ if (priv->dp_out_buf_len > 0) {
+ *((unsigned char *)(port->write_urb->transfer_buffer))
+ = (unsigned char)DIGI_CMD_SEND_DATA;
+ *((unsigned char *)(port->write_urb->transfer_buffer) + 1)
+ = (unsigned char)priv->dp_out_buf_len;
+ port->write_urb->transfer_buffer_length =
+ priv->dp_out_buf_len + 2;
+ memcpy(port->write_urb->transfer_buffer + 2, priv->dp_out_buf,
+ priv->dp_out_buf_len);
+ ret = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ if (ret == 0) {
+ priv->dp_write_urb_in_use = 1;
+ priv->dp_out_buf_len = 0;
+ wakeup = false;
+ }
+ }
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+
+ if (ret && ret != -EPERM)
+ dev_err_console(port,
+ "%s: usb_submit_urb failed, ret=%d, port=%d\n",
+ __func__, ret, priv->dp_port_num);
+
+ if (wakeup)
+ tty_port_tty_wakeup(&port->port);
+}
+
+static unsigned int digi_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ unsigned int room;
+
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+
+ if (priv->dp_write_urb_in_use)
+ room = 0;
+ else
+ room = port->bulk_out_size - 2 - priv->dp_out_buf_len;
+
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+ dev_dbg(&port->dev, "digi_write_room: port=%d, room=%u\n", priv->dp_port_num, room);
+ return room;
+
+}
+
+static unsigned int digi_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ unsigned int chars;
+
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+ if (priv->dp_write_urb_in_use)
+ chars = port->bulk_out_size - 2;
+ else
+ chars = priv->dp_out_buf_len;
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+
+ dev_dbg(&port->dev, "%s: port=%d, chars=%d\n", __func__,
+ priv->dp_port_num, chars);
+ return chars;
+}
+
+static void digi_dtr_rts(struct usb_serial_port *port, int on)
+{
+ /* Adjust DTR and RTS */
+ digi_set_modem_signals(port, on * (TIOCM_DTR | TIOCM_RTS), 1);
+}
+
+static int digi_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ int ret;
+ unsigned char buf[32];
+ struct digi_port *priv = usb_get_serial_port_data(port);
+ struct ktermios not_termios;
+ int throttled;
+
+ /* be sure the device is started up */
+ if (digi_startup_device(port->serial) != 0)
+ return -ENXIO;
+
+ /* read modem signals automatically whenever they change */
+ buf[0] = DIGI_CMD_READ_INPUT_SIGNALS;
+ buf[1] = priv->dp_port_num;
+ buf[2] = DIGI_ENABLE;
+ buf[3] = 0;
+
+ /* flush fifos */
+ buf[4] = DIGI_CMD_IFLUSH_FIFO;
+ buf[5] = priv->dp_port_num;
+ buf[6] = DIGI_FLUSH_TX | DIGI_FLUSH_RX;
+ buf[7] = 0;
+
+ ret = digi_write_oob_command(port, buf, 8, 1);
+ if (ret != 0)
+ dev_dbg(&port->dev, "digi_open: write oob failed, ret=%d\n", ret);
+
+ /* set termios settings */
+ if (tty) {
+ not_termios.c_cflag = ~tty->termios.c_cflag;
+ not_termios.c_iflag = ~tty->termios.c_iflag;
+ digi_set_termios(tty, port, &not_termios);
+ }
+
+ spin_lock_irq(&priv->dp_port_lock);
+ throttled = priv->dp_throttle_restart;
+ priv->dp_throttled = 0;
+ priv->dp_throttle_restart = 0;
+ spin_unlock_irq(&priv->dp_port_lock);
+
+ if (throttled) {
+ ret = usb_submit_urb(port->read_urb, GFP_KERNEL);
+ if (ret) {
+ dev_err(&port->dev, "failed to submit read urb: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+
+static void digi_close(struct usb_serial_port *port)
+{
+ DEFINE_WAIT(wait);
+ int ret;
+ unsigned char buf[32];
+ struct digi_port *priv = usb_get_serial_port_data(port);
+
+ mutex_lock(&port->serial->disc_mutex);
+ /* if disconnected, just clear flags */
+ if (port->serial->disconnected)
+ goto exit;
+
+ /* FIXME: Transmit idle belongs in the wait_unti_sent path */
+ digi_transmit_idle(port, DIGI_CLOSE_TIMEOUT);
+
+ /* disable input flow control */
+ buf[0] = DIGI_CMD_SET_INPUT_FLOW_CONTROL;
+ buf[1] = priv->dp_port_num;
+ buf[2] = DIGI_DISABLE;
+ buf[3] = 0;
+
+ /* disable output flow control */
+ buf[4] = DIGI_CMD_SET_OUTPUT_FLOW_CONTROL;
+ buf[5] = priv->dp_port_num;
+ buf[6] = DIGI_DISABLE;
+ buf[7] = 0;
+
+ /* disable reading modem signals automatically */
+ buf[8] = DIGI_CMD_READ_INPUT_SIGNALS;
+ buf[9] = priv->dp_port_num;
+ buf[10] = DIGI_DISABLE;
+ buf[11] = 0;
+
+ /* disable receive */
+ buf[12] = DIGI_CMD_RECEIVE_ENABLE;
+ buf[13] = priv->dp_port_num;
+ buf[14] = DIGI_DISABLE;
+ buf[15] = 0;
+
+ /* flush fifos */
+ buf[16] = DIGI_CMD_IFLUSH_FIFO;
+ buf[17] = priv->dp_port_num;
+ buf[18] = DIGI_FLUSH_TX | DIGI_FLUSH_RX;
+ buf[19] = 0;
+
+ ret = digi_write_oob_command(port, buf, 20, 0);
+ if (ret != 0)
+ dev_dbg(&port->dev, "digi_close: write oob failed, ret=%d\n",
+ ret);
+ /* wait for final commands on oob port to complete */
+ prepare_to_wait(&priv->dp_flush_wait, &wait,
+ TASK_INTERRUPTIBLE);
+ schedule_timeout(DIGI_CLOSE_TIMEOUT);
+ finish_wait(&priv->dp_flush_wait, &wait);
+
+ /* shutdown any outstanding bulk writes */
+ usb_kill_urb(port->write_urb);
+exit:
+ spin_lock_irq(&priv->dp_port_lock);
+ priv->dp_write_urb_in_use = 0;
+ wake_up_interruptible(&priv->dp_close_wait);
+ spin_unlock_irq(&priv->dp_port_lock);
+ mutex_unlock(&port->serial->disc_mutex);
+}
+
+
+/*
+ * Digi Startup Device
+ *
+ * Starts reads on all ports. Must be called AFTER startup, with
+ * urbs initialized. Returns 0 if successful, non-zero error otherwise.
+ */
+
+static int digi_startup_device(struct usb_serial *serial)
+{
+ int i, ret = 0;
+ struct digi_serial *serial_priv = usb_get_serial_data(serial);
+ struct usb_serial_port *port;
+
+ /* be sure this happens exactly once */
+ spin_lock(&serial_priv->ds_serial_lock);
+ if (serial_priv->ds_device_started) {
+ spin_unlock(&serial_priv->ds_serial_lock);
+ return 0;
+ }
+ serial_priv->ds_device_started = 1;
+ spin_unlock(&serial_priv->ds_serial_lock);
+
+ /* start reading from each bulk in endpoint for the device */
+ /* set USB_DISABLE_SPD flag for write bulk urbs */
+ for (i = 0; i < serial->type->num_ports + 1; i++) {
+ port = serial->port[i];
+ ret = usb_submit_urb(port->read_urb, GFP_KERNEL);
+ if (ret != 0) {
+ dev_err(&port->dev,
+ "%s: usb_submit_urb failed, ret=%d, port=%d\n",
+ __func__, ret, i);
+ break;
+ }
+ }
+ return ret;
+}
+
+static int digi_port_init(struct usb_serial_port *port, unsigned port_num)
+{
+ struct digi_port *priv;
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->dp_port_lock);
+ priv->dp_port_num = port_num;
+ init_waitqueue_head(&priv->dp_transmit_idle_wait);
+ init_waitqueue_head(&priv->dp_flush_wait);
+ init_waitqueue_head(&priv->dp_close_wait);
+ init_waitqueue_head(&priv->write_wait);
+ priv->dp_port = port;
+
+ usb_set_serial_port_data(port, priv);
+
+ return 0;
+}
+
+static int digi_startup(struct usb_serial *serial)
+{
+ struct digi_serial *serial_priv;
+ int oob_port_num;
+ int ret;
+ int i;
+
+ /*
+ * The port bulk-out buffers must be large enough for header and
+ * buffered data.
+ */
+ for (i = 0; i < serial->type->num_ports; i++) {
+ if (serial->port[i]->bulk_out_size < DIGI_OUT_BUF_SIZE + 2)
+ return -EINVAL;
+ }
+
+ /*
+ * The OOB port bulk-out buffer must be large enough for the two
+ * commands in digi_set_modem_signals().
+ */
+ oob_port_num = serial->type->num_ports;
+ if (serial->port[oob_port_num]->bulk_out_size < 8)
+ return -EINVAL;
+
+ serial_priv = kzalloc_obj(*serial_priv);
+ if (!serial_priv)
+ return -ENOMEM;
+
+ spin_lock_init(&serial_priv->ds_serial_lock);
+ serial_priv->ds_oob_port_num = oob_port_num;
+ serial_priv->ds_oob_port = serial->port[oob_port_num];
+
+ ret = digi_port_init(serial_priv->ds_oob_port,
+ serial_priv->ds_oob_port_num);
+ if (ret) {
+ kfree(serial_priv);
+ return ret;
+ }
+
+ usb_set_serial_data(serial, serial_priv);
+
+ return 0;
+}
+
+
+static void digi_disconnect(struct usb_serial *serial)
+{
+ int i;
+
+ /* stop reads and writes on all ports */
+ for (i = 0; i < serial->type->num_ports + 1; i++) {
+ usb_kill_urb(serial->port[i]->read_urb);
+ usb_kill_urb(serial->port[i]->write_urb);
+ }
+}
+
+
+static void digi_release(struct usb_serial *serial)
+{
+ struct digi_serial *serial_priv;
+ struct digi_port *priv;
+
+ serial_priv = usb_get_serial_data(serial);
+
+ priv = usb_get_serial_port_data(serial_priv->ds_oob_port);
+ kfree(priv);
+
+ kfree(serial_priv);
+}
+
+static int digi_port_probe(struct usb_serial_port *port)
+{
+ return digi_port_init(port, port->port_number);
+}
+
+static void digi_port_remove(struct usb_serial_port *port)
+{
+ struct digi_port *priv;
+
+ priv = usb_get_serial_port_data(port);
+ kfree(priv);
+}
+
+static void digi_read_bulk_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct digi_port *priv;
+ struct digi_serial *serial_priv;
+ int ret;
+ int status = urb->status;
+
+ /* port sanity check, do not resubmit if port is not valid */
+ if (port == NULL)
+ return;
+ priv = usb_get_serial_port_data(port);
+ if (priv == NULL) {
+ dev_err(&port->dev, "%s: port->private is NULL, status=%d\n",
+ __func__, status);
+ return;
+ }
+ if (port->serial == NULL ||
+ (serial_priv = usb_get_serial_data(port->serial)) == NULL) {
+ dev_err(&port->dev, "%s: serial is bad or serial->private "
+ "is NULL, status=%d\n", __func__, status);
+ return;
+ }
+
+ /* do not resubmit urb if it has any status error */
+ if (status) {
+ dev_err(&port->dev,
+ "%s: nonzero read bulk status: status=%d, port=%d\n",
+ __func__, status, priv->dp_port_num);
+ return;
+ }
+
+ /* handle oob or inb callback, do not resubmit if error */
+ if (priv->dp_port_num == serial_priv->ds_oob_port_num) {
+ if (digi_read_oob_callback(urb) != 0)
+ return;
+ } else {
+ if (digi_read_inb_callback(urb) != 0)
+ return;
+ }
+
+ /* continue read */
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret != 0 && ret != -EPERM) {
+ dev_err(&port->dev,
+ "%s: failed resubmitting urb, ret=%d, port=%d\n",
+ __func__, ret, priv->dp_port_num);
+ }
+
+}
+
+/*
+ * Digi Read INB Callback
+ *
+ * Digi Read INB Callback handles reads on the in band ports, sending
+ * the data on to the tty subsystem. When called we know port and
+ * port->private are not NULL and port->serial has been validated.
+ * It returns 0 if successful, 1 if successful but the port is
+ * throttled, and -1 if the sanity checks failed.
+ */
+
+static int digi_read_inb_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned char *buf = urb->transfer_buffer;
+ unsigned long flags;
+ int opcode;
+ int len;
+ int port_status;
+ unsigned char *data;
+ int tty_flag, throttled;
+
+ /* short/multiple packet check */
+ if (urb->actual_length < 2) {
+ dev_warn(&port->dev, "short packet received\n");
+ return -1;
+ }
+
+ opcode = buf[0];
+ len = buf[1];
+
+ if (urb->actual_length != len + 2) {
+ dev_err(&port->dev, "malformed packet received: port=%d, opcode=%d, len=%d, actual_length=%u\n",
+ priv->dp_port_num, opcode, len, urb->actual_length);
+ return -1;
+ }
+
+ if (opcode == DIGI_CMD_RECEIVE_DATA && len < 1) {
+ dev_err(&port->dev, "malformed data packet received\n");
+ return -1;
+ }
+
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+
+ /* check for throttle; if set, do not resubmit read urb */
+ /* indicate the read chain needs to be restarted on unthrottle */
+ throttled = priv->dp_throttled;
+ if (throttled)
+ priv->dp_throttle_restart = 1;
+
+ /* receive data */
+ if (opcode == DIGI_CMD_RECEIVE_DATA) {
+ port_status = buf[2];
+ data = &buf[3];
+
+ /* get flag from port_status */
+ tty_flag = 0;
+
+ /* overrun is special, not associated with a char */
+ if (port_status & DIGI_OVERRUN_ERROR)
+ tty_insert_flip_char(&port->port, 0, TTY_OVERRUN);
+
+ /* break takes precedence over parity, */
+ /* which takes precedence over framing errors */
+ if (port_status & DIGI_BREAK_ERROR)
+ tty_flag = TTY_BREAK;
+ else if (port_status & DIGI_PARITY_ERROR)
+ tty_flag = TTY_PARITY;
+ else if (port_status & DIGI_FRAMING_ERROR)
+ tty_flag = TTY_FRAME;
+
+ /* data length is len-1 (one byte of len is port_status) */
+ --len;
+ if (len > 0) {
+ tty_insert_flip_string_fixed_flag(&port->port, data,
+ tty_flag, len);
+ tty_flip_buffer_push(&port->port);
+ }
+ }
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+
+ if (opcode == DIGI_CMD_RECEIVE_DISABLE)
+ dev_dbg(&port->dev, "%s: got RECEIVE_DISABLE\n", __func__);
+ else if (opcode != DIGI_CMD_RECEIVE_DATA)
+ dev_dbg(&port->dev, "%s: unknown opcode: %d\n", __func__, opcode);
+
+ return throttled ? 1 : 0;
+
+}
+
+
+/*
+ * Digi Read OOB Callback
+ *
+ * Digi Read OOB Callback handles reads on the out of band port.
+ * When called we know port and port->private are not NULL and
+ * the port->serial is valid. It returns 0 if successful, and
+ * -1 if the sanity checks failed.
+ */
+
+static int digi_read_oob_callback(struct urb *urb)
+{
+
+ struct usb_serial_port *port = urb->context;
+ struct usb_serial *serial = port->serial;
+ struct tty_struct *tty;
+ struct digi_port *priv;
+ unsigned char *buf = urb->transfer_buffer;
+ int opcode, line, status, val;
+ unsigned long flags;
+ int i;
+ unsigned int rts;
+
+ if (urb->actual_length < 4)
+ return -1;
+
+ /* handle each oob command */
+ for (i = 0; i < urb->actual_length - 3; i += 4) {
+ opcode = buf[i];
+ line = buf[i + 1];
+ status = buf[i + 2];
+ val = buf[i + 3];
+
+ dev_dbg(&port->dev, "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n",
+ opcode, line, status, val);
+
+ if (status != 0 || line >= serial->type->num_ports)
+ continue;
+
+ port = serial->port[line];
+
+ priv = usb_get_serial_port_data(port);
+ if (priv == NULL)
+ return -1;
+
+ tty = tty_port_tty_get(&port->port);
+
+ rts = 0;
+ if (tty)
+ rts = C_CRTSCTS(tty);
+
+ if (tty && opcode == DIGI_CMD_READ_INPUT_SIGNALS) {
+ bool wakeup = false;
+
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+ /* convert from digi flags to termiox flags */
+ if (val & DIGI_READ_INPUT_SIGNALS_CTS) {
+ priv->dp_modem_signals |= TIOCM_CTS;
+ if (rts)
+ wakeup = true;
+ } else {
+ priv->dp_modem_signals &= ~TIOCM_CTS;
+ /* port must be open to use tty struct */
+ }
+ if (val & DIGI_READ_INPUT_SIGNALS_DSR)
+ priv->dp_modem_signals |= TIOCM_DSR;
+ else
+ priv->dp_modem_signals &= ~TIOCM_DSR;
+ if (val & DIGI_READ_INPUT_SIGNALS_RI)
+ priv->dp_modem_signals |= TIOCM_RI;
+ else
+ priv->dp_modem_signals &= ~TIOCM_RI;
+ if (val & DIGI_READ_INPUT_SIGNALS_DCD)
+ priv->dp_modem_signals |= TIOCM_CD;
+ else
+ priv->dp_modem_signals &= ~TIOCM_CD;
+
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+
+ if (wakeup)
+ tty_port_tty_wakeup(&port->port);
+ } else if (opcode == DIGI_CMD_TRANSMIT_IDLE) {
+ spin_lock_irqsave(&priv->dp_port_lock, flags);
+ priv->dp_transmit_idle = 1;
+ wake_up_interruptible(&priv->dp_transmit_idle_wait);
+ spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+ } else if (opcode == DIGI_CMD_IFLUSH_FIFO) {
+ wake_up_interruptible(&priv->dp_flush_wait);
+ }
+ tty_kref_put(tty);
+ }
+ return 0;
+
+}
+
+module_usb_serial_driver(serial_drivers, id_table_combined);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/empeg.c b/drivers/usb/serial/empeg.c
new file mode 100644
index 000000000..4937575ca
--- /dev/null
+++ b/drivers/usb/serial/empeg.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Empeg empeg-car player driver
+ *
+ * Copyright (C) 2000, 2001
+ * Gary Brubaker (xavyer@ix.netcom.com)
+ *
+ * Copyright (C) 1999 - 2001
+ * Greg Kroah-Hartman (greg@kroah.com)
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Gary Brubaker <xavyer@ix.netcom.com>"
+#define DRIVER_DESC "USB Empeg Mark I/II Driver"
+
+#define EMPEG_VENDOR_ID 0x084f
+#define EMPEG_PRODUCT_ID 0x0001
+
+/* function prototypes for an empeg-car player */
+static int empeg_startup(struct usb_serial *serial);
+static void empeg_init_termios(struct tty_struct *tty);
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(EMPEG_VENDOR_ID, EMPEG_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_serial_driver empeg_device = {
+ .driver = {
+ .name = "empeg",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .bulk_out_size = 256,
+ .throttle = usb_serial_generic_throttle,
+ .unthrottle = usb_serial_generic_unthrottle,
+ .attach = empeg_startup,
+ .init_termios = empeg_init_termios,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &empeg_device, NULL
+};
+
+static int empeg_startup(struct usb_serial *serial)
+{
+ int r;
+
+ if (serial->dev->actconfig->desc.bConfigurationValue != 1) {
+ dev_err(&serial->dev->dev, "active config #%d != 1 ??\n",
+ serial->dev->actconfig->desc.bConfigurationValue);
+ return -ENODEV;
+ }
+
+ r = usb_reset_configuration(serial->dev);
+
+ /* continue on with initialization */
+ return r;
+}
+
+static void empeg_init_termios(struct tty_struct *tty)
+{
+ struct ktermios *termios = &tty->termios;
+
+ /*
+ * The empeg-car player wants these particular tty settings.
+ * You could, for example, change the baud rate, however the
+ * player only supports 115200 (currently), so there is really
+ * no point in support for changes to the tty settings.
+ * (at least for now)
+ *
+ * The default requirements for this device are:
+ */
+ termios->c_iflag
+ &= ~(IGNBRK /* disable ignore break */
+ | BRKINT /* disable break causes interrupt */
+ | PARMRK /* disable mark parity errors */
+ | ISTRIP /* disable clear high bit of input characters */
+ | INLCR /* disable translate NL to CR */
+ | IGNCR /* disable ignore CR */
+ | ICRNL /* disable translate CR to NL */
+ | IXON); /* disable enable XON/XOFF flow control */
+
+ termios->c_oflag
+ &= ~OPOST; /* disable postprocess output characters */
+
+ termios->c_lflag
+ &= ~(ECHO /* disable echo input characters */
+ | ECHONL /* disable echo new line */
+ | ICANON /* disable erase, kill, werase, and rprnt special characters */
+ | ISIG /* disable interrupt, quit, and suspend special characters */
+ | IEXTEN); /* disable non-POSIX special characters */
+
+ termios->c_cflag
+ &= ~(CSIZE /* no size */
+ | PARENB /* disable parity bit */
+ | CBAUD); /* clear current baud rate */
+
+ termios->c_cflag
+ |= CS8; /* character size 8 bits */
+
+ tty_encode_baud_rate(tty, 115200, 115200);
+}
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/ezusb_convert.pl b/drivers/usb/serial/ezusb_convert.pl
new file mode 100644
index 000000000..40d23f21e
--- /dev/null
+++ b/drivers/usb/serial/ezusb_convert.pl
@@ -0,0 +1,51 @@
+#! /usr/bin/perl -w
+# SPDX-License-Identifier: GPL-2.0
+
+
+# convert an Intel HEX file into a set of C records usable by the firmware
+# loading code in usb-serial.c (or others)
+
+# accepts the .hex file(s) on stdin, a basename (to name the initialized
+# array) as an argument, and prints the .h file to stdout. Typical usage:
+# perl ezusb_convert.pl foo <foo.hex >fw_foo.h
+
+
+my $basename = $ARGV[0];
+die "no base name specified" unless $basename;
+
+while (<STDIN>) {
+ # ':' <len> <addr> <type> <len-data> <crc> '\r'
+ # len, type, crc are 2-char hex, addr is 4-char hex. type is 00 for
+ # normal records, 01 for EOF
+ my($lenstring, $addrstring, $typestring, $reststring, $doscrap) =
+ /^:(\w\w)(\w\w\w\w)(\w\w)(\w+)(\r?)$/;
+ die "malformed line: $_" unless $reststring;
+ last if $typestring eq '01';
+ my($len) = hex($lenstring);
+ my($addr) = hex($addrstring);
+ my(@bytes) = unpack("C*", pack("H".(2*$len), $reststring));
+ #pop(@bytes); # last byte is a CRC
+ push(@records, [$addr, \@bytes]);
+}
+
+@sorted_records = sort { $a->[0] <=> $b->[0] } @records;
+
+print <<"EOF";
+/*
+ * ${basename}_fw.h
+ *
+ * Generated from ${basename}.s by ezusb_convert.pl
+ * This file is presumed to be under the same copyright as the source file
+ * from which it was derived.
+ */
+
+EOF
+
+print "static const struct ezusb_hex_record ${basename}_firmware[] = {\n";
+foreach $r (@sorted_records) {
+ printf("{ 0x%04x,\t%d,\t{", $r->[0], scalar(@{$r->[1]}));
+ print join(", ", map {sprintf('0x%02x', $_);} @{$r->[1]});
+ print "} },\n";
+}
+print "{ 0xffff,\t0,\t{0x00} }\n";
+print "};\n";
diff --git a/drivers/usb/serial/f81232.c b/drivers/usb/serial/f81232.c
new file mode 100644
index 000000000..dcceb95d7
--- /dev/null
+++ b/drivers/usb/serial/f81232.c
@@ -0,0 +1,1077 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Fintek F81232 USB to serial adaptor driver
+ * Fintek F81532A/534A/535/536 USB to 2/4/8/12 serial adaptor driver
+ *
+ * Copyright (C) 2012 Greg Kroah-Hartman (gregkh@linuxfoundation.org)
+ * Copyright (C) 2012 Linux Foundation
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/serial_reg.h>
+
+#define F81232_ID \
+ { USB_DEVICE(0x1934, 0x0706) } /* 1 port UART device */
+
+#define F81534A_SERIES_ID \
+ { USB_DEVICE(0x2c42, 0x1602) }, /* In-Box 2 port UART device */ \
+ { USB_DEVICE(0x2c42, 0x1604) }, /* In-Box 4 port UART device */ \
+ { USB_DEVICE(0x2c42, 0x1605) }, /* In-Box 8 port UART device */ \
+ { USB_DEVICE(0x2c42, 0x1606) }, /* In-Box 12 port UART device */ \
+ { USB_DEVICE(0x2c42, 0x1608) }, /* Non-Flash type */ \
+ { USB_DEVICE(0x2c42, 0x1632) }, /* 2 port UART device */ \
+ { USB_DEVICE(0x2c42, 0x1634) }, /* 4 port UART device */ \
+ { USB_DEVICE(0x2c42, 0x1635) }, /* 8 port UART device */ \
+ { USB_DEVICE(0x2c42, 0x1636) } /* 12 port UART device */
+
+#define F81534A_CTRL_ID \
+ { USB_DEVICE(0x2c42, 0x16f8) } /* Global control device */
+
+static const struct usb_device_id f81232_id_table[] = {
+ F81232_ID,
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id f81534a_id_table[] = {
+ F81534A_SERIES_ID,
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id f81534a_ctrl_id_table[] = {
+ F81534A_CTRL_ID,
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id combined_id_table[] = {
+ F81232_ID,
+ F81534A_SERIES_ID,
+ F81534A_CTRL_ID,
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, combined_id_table);
+
+/* Maximum baudrate for F81232 */
+#define F81232_MAX_BAUDRATE 1500000
+#define F81232_DEF_BAUDRATE 9600
+
+/* USB Control EP parameter */
+#define F81232_REGISTER_REQUEST 0xa0
+#define F81232_GET_REGISTER 0xc0
+#define F81232_SET_REGISTER 0x40
+
+#define SERIAL_BASE_ADDRESS 0x0120
+#define RECEIVE_BUFFER_REGISTER (0x00 + SERIAL_BASE_ADDRESS)
+#define INTERRUPT_ENABLE_REGISTER (0x01 + SERIAL_BASE_ADDRESS)
+#define FIFO_CONTROL_REGISTER (0x02 + SERIAL_BASE_ADDRESS)
+#define LINE_CONTROL_REGISTER (0x03 + SERIAL_BASE_ADDRESS)
+#define MODEM_CONTROL_REGISTER (0x04 + SERIAL_BASE_ADDRESS)
+#define LINE_STATUS_REGISTER (0x05 + SERIAL_BASE_ADDRESS)
+#define MODEM_STATUS_REGISTER (0x06 + SERIAL_BASE_ADDRESS)
+
+/*
+ * F81232 Clock registers (106h)
+ *
+ * Bit1-0: Clock source selector
+ * 00: 1.846MHz.
+ * 01: 18.46MHz.
+ * 10: 24MHz.
+ * 11: 14.77MHz.
+ */
+#define F81232_CLK_REGISTER 0x106
+#define F81232_CLK_1_846_MHZ 0
+#define F81232_CLK_18_46_MHZ BIT(0)
+#define F81232_CLK_24_MHZ BIT(1)
+#define F81232_CLK_14_77_MHZ (BIT(1) | BIT(0))
+#define F81232_CLK_MASK GENMASK(1, 0)
+
+#define F81534A_MODE_REG 0x107
+#define F81534A_TRIGGER_MASK GENMASK(3, 2)
+#define F81534A_TRIGGER_MULTIPLE_4X BIT(3)
+#define F81534A_FIFO_128BYTE (BIT(1) | BIT(0))
+
+/* Serial port self GPIO control, 2bytes [control&output data][input data] */
+#define F81534A_GPIO_REG 0x10e
+#define F81534A_GPIO_MODE2_DIR BIT(6) /* 1: input, 0: output */
+#define F81534A_GPIO_MODE1_DIR BIT(5)
+#define F81534A_GPIO_MODE0_DIR BIT(4)
+#define F81534A_GPIO_MODE2_OUTPUT BIT(2)
+#define F81534A_GPIO_MODE1_OUTPUT BIT(1)
+#define F81534A_GPIO_MODE0_OUTPUT BIT(0)
+
+#define F81534A_CTRL_CMD_ENABLE_PORT 0x116
+
+struct f81232_private {
+ struct mutex lock;
+ u8 modem_control;
+ u8 modem_status;
+ u8 shadow_lcr;
+ speed_t baud_base;
+ struct work_struct lsr_work;
+ struct work_struct interrupt_work;
+ struct usb_serial_port *port;
+};
+
+static u32 const baudrate_table[] = { 115200, 921600, 1152000, 1500000 };
+static u8 const clock_table[] = { F81232_CLK_1_846_MHZ, F81232_CLK_14_77_MHZ,
+ F81232_CLK_18_46_MHZ, F81232_CLK_24_MHZ };
+
+static int calc_baud_divisor(speed_t baudrate, speed_t clockrate)
+{
+ return DIV_ROUND_CLOSEST(clockrate, baudrate);
+}
+
+static int f81232_get_register(struct usb_serial_port *port, u16 reg, u8 *val)
+{
+ int status;
+ struct usb_device *dev = port->serial->dev;
+
+ status = usb_control_msg_recv(dev,
+ 0,
+ F81232_REGISTER_REQUEST,
+ F81232_GET_REGISTER,
+ reg,
+ 0,
+ val,
+ sizeof(*val),
+ USB_CTRL_GET_TIMEOUT,
+ GFP_KERNEL);
+ if (status) {
+ dev_err(&port->dev, "%s failed status: %d\n", __func__, status);
+ status = usb_translate_errors(status);
+ }
+
+ return status;
+}
+
+static int f81232_set_register(struct usb_serial_port *port, u16 reg, u8 val)
+{
+ int status;
+ struct usb_device *dev = port->serial->dev;
+
+ status = usb_control_msg_send(dev,
+ 0,
+ F81232_REGISTER_REQUEST,
+ F81232_SET_REGISTER,
+ reg,
+ 0,
+ &val,
+ sizeof(val),
+ USB_CTRL_SET_TIMEOUT,
+ GFP_KERNEL);
+ if (status) {
+ dev_err(&port->dev, "%s failed status: %d\n", __func__, status);
+ status = usb_translate_errors(status);
+ }
+
+ return status;
+}
+
+static int f81232_set_mask_register(struct usb_serial_port *port, u16 reg,
+ u8 mask, u8 val)
+{
+ int status;
+ u8 tmp;
+
+ status = f81232_get_register(port, reg, &tmp);
+ if (status)
+ return status;
+
+ tmp = (tmp & ~mask) | (val & mask);
+
+ return f81232_set_register(port, reg, tmp);
+}
+
+static void f81232_read_msr(struct usb_serial_port *port)
+{
+ int status;
+ u8 current_msr;
+ struct tty_struct *tty;
+ struct f81232_private *priv = usb_get_serial_port_data(port);
+
+ mutex_lock(&priv->lock);
+ status = f81232_get_register(port, MODEM_STATUS_REGISTER,
+ &current_msr);
+ if (status) {
+ dev_err(&port->dev, "%s fail, status: %d\n", __func__, status);
+ mutex_unlock(&priv->lock);
+ return;
+ }
+
+ if (!(current_msr & UART_MSR_ANY_DELTA)) {
+ mutex_unlock(&priv->lock);
+ return;
+ }
+
+ priv->modem_status = current_msr;
+
+ if (current_msr & UART_MSR_DCTS)
+ port->icount.cts++;
+ if (current_msr & UART_MSR_DDSR)
+ port->icount.dsr++;
+ if (current_msr & UART_MSR_TERI)
+ port->icount.rng++;
+ if (current_msr & UART_MSR_DDCD) {
+ port->icount.dcd++;
+ tty = tty_port_tty_get(&port->port);
+ if (tty) {
+ usb_serial_handle_dcd_change(port, tty,
+ current_msr & UART_MSR_DCD);
+
+ tty_kref_put(tty);
+ }
+ }
+
+ wake_up_interruptible(&port->port.delta_msr_wait);
+ mutex_unlock(&priv->lock);
+}
+
+static int f81232_set_mctrl(struct usb_serial_port *port,
+ unsigned int set, unsigned int clear)
+{
+ u8 val;
+ int status;
+ struct f81232_private *priv = usb_get_serial_port_data(port);
+
+ if (((set | clear) & (TIOCM_DTR | TIOCM_RTS)) == 0)
+ return 0; /* no change */
+
+ /* 'set' takes precedence over 'clear' */
+ clear &= ~set;
+
+ /* force enable interrupt with OUT2 */
+ mutex_lock(&priv->lock);
+ val = UART_MCR_OUT2 | priv->modem_control;
+
+ if (clear & TIOCM_DTR)
+ val &= ~UART_MCR_DTR;
+
+ if (clear & TIOCM_RTS)
+ val &= ~UART_MCR_RTS;
+
+ if (set & TIOCM_DTR)
+ val |= UART_MCR_DTR;
+
+ if (set & TIOCM_RTS)
+ val |= UART_MCR_RTS;
+
+ dev_dbg(&port->dev, "%s new:%02x old:%02x\n", __func__,
+ val, priv->modem_control);
+
+ status = f81232_set_register(port, MODEM_CONTROL_REGISTER, val);
+ if (status) {
+ dev_err(&port->dev, "%s set MCR status < 0\n", __func__);
+ mutex_unlock(&priv->lock);
+ return status;
+ }
+
+ priv->modem_control = val;
+ mutex_unlock(&priv->lock);
+
+ return 0;
+}
+
+static void f81232_update_line_status(struct usb_serial_port *port,
+ unsigned char *data,
+ size_t actual_length)
+{
+ struct f81232_private *priv = usb_get_serial_port_data(port);
+
+ if (!actual_length)
+ return;
+
+ switch (data[0] & 0x07) {
+ case 0x00: /* msr change */
+ dev_dbg(&port->dev, "IIR: MSR Change: %02x\n", data[0]);
+ schedule_work(&priv->interrupt_work);
+ break;
+ case 0x02: /* tx-empty */
+ break;
+ case 0x04: /* rx data available */
+ break;
+ case 0x06: /* lsr change */
+ /* we can forget it. the LSR will read from bulk-in */
+ dev_dbg(&port->dev, "IIR: LSR Change: %02x\n", data[0]);
+ break;
+ }
+}
+
+static void f81232_read_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ unsigned int actual_length = urb->actual_length;
+ int status = urb->status;
+ int retval;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ goto exit;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__,
+ urb->actual_length, urb->transfer_buffer);
+
+ f81232_update_line_status(port, data, actual_length);
+
+exit:
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(&urb->dev->dev,
+ "%s - usb_submit_urb failed with result %d\n",
+ __func__, retval);
+}
+
+static char f81232_handle_lsr(struct usb_serial_port *port, u8 lsr)
+{
+ struct f81232_private *priv = usb_get_serial_port_data(port);
+ char tty_flag = TTY_NORMAL;
+
+ if (!(lsr & UART_LSR_BRK_ERROR_BITS))
+ return tty_flag;
+
+ if (lsr & UART_LSR_BI) {
+ tty_flag = TTY_BREAK;
+ port->icount.brk++;
+ usb_serial_handle_break(port);
+ } else if (lsr & UART_LSR_PE) {
+ tty_flag = TTY_PARITY;
+ port->icount.parity++;
+ } else if (lsr & UART_LSR_FE) {
+ tty_flag = TTY_FRAME;
+ port->icount.frame++;
+ }
+
+ if (lsr & UART_LSR_OE) {
+ port->icount.overrun++;
+ schedule_work(&priv->lsr_work);
+ tty_insert_flip_char(&port->port, 0, TTY_OVERRUN);
+ }
+
+ return tty_flag;
+}
+
+static void f81232_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ char tty_flag;
+ unsigned int i;
+ u8 lsr;
+
+ /*
+ * When opening the port we get a 1-byte packet with the current LSR,
+ * which we discard.
+ */
+ if ((urb->actual_length < 2) || (urb->actual_length % 2))
+ return;
+
+ /* bulk-in data: [LSR(1Byte)+DATA(1Byte)][LSR(1Byte)+DATA(1Byte)]... */
+
+ for (i = 0; i < urb->actual_length; i += 2) {
+ lsr = data[i];
+ tty_flag = f81232_handle_lsr(port, lsr);
+
+ if (port->sysrq) {
+ if (usb_serial_handle_sysrq_char(port, data[i + 1]))
+ continue;
+ }
+
+ tty_insert_flip_char(&port->port, data[i + 1], tty_flag);
+ }
+
+ tty_flip_buffer_push(&port->port);
+}
+
+static void f81534a_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ char tty_flag;
+ unsigned int i;
+ u8 lsr;
+ u8 len;
+
+ if (urb->actual_length < 3) {
+ dev_err(&port->dev, "short message received: %d\n",
+ urb->actual_length);
+ return;
+ }
+
+ len = data[0];
+ if (len != urb->actual_length) {
+ dev_err(&port->dev, "malformed message received: %d (%d)\n",
+ urb->actual_length, len);
+ return;
+ }
+
+ /* bulk-in data: [LEN][Data.....][LSR] */
+ lsr = data[len - 1];
+ tty_flag = f81232_handle_lsr(port, lsr);
+
+ if (port->sysrq) {
+ for (i = 1; i < len - 1; ++i) {
+ if (!usb_serial_handle_sysrq_char(port, data[i])) {
+ tty_insert_flip_char(&port->port, data[i],
+ tty_flag);
+ }
+ }
+ } else {
+ tty_insert_flip_string_fixed_flag(&port->port, &data[1],
+ tty_flag, len - 2);
+ }
+
+ tty_flip_buffer_push(&port->port);
+}
+
+static int f81232_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct f81232_private *priv = usb_get_serial_port_data(port);
+ int status;
+
+ mutex_lock(&priv->lock);
+
+ if (break_state)
+ priv->shadow_lcr |= UART_LCR_SBC;
+ else
+ priv->shadow_lcr &= ~UART_LCR_SBC;
+
+ status = f81232_set_register(port, LINE_CONTROL_REGISTER,
+ priv->shadow_lcr);
+ if (status)
+ dev_err(&port->dev, "set break failed: %d\n", status);
+
+ mutex_unlock(&priv->lock);
+
+ return status;
+}
+
+static int f81232_find_clk(speed_t baudrate)
+{
+ int idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(baudrate_table); ++idx) {
+ if (baudrate <= baudrate_table[idx] &&
+ baudrate_table[idx] % baudrate == 0)
+ return idx;
+ }
+
+ return -EINVAL;
+}
+
+static void f81232_set_baudrate(struct tty_struct *tty,
+ struct usb_serial_port *port, speed_t baudrate,
+ speed_t old_baudrate)
+{
+ struct f81232_private *priv = usb_get_serial_port_data(port);
+ u8 lcr;
+ int divisor;
+ int status = 0;
+ int i;
+ int idx;
+ speed_t baud_list[] = { baudrate, old_baudrate, F81232_DEF_BAUDRATE };
+
+ for (i = 0; i < ARRAY_SIZE(baud_list); ++i) {
+ baudrate = baud_list[i];
+ if (baudrate == 0) {
+ tty_encode_baud_rate(tty, 0, 0);
+ return;
+ }
+
+ idx = f81232_find_clk(baudrate);
+ if (idx >= 0) {
+ tty_encode_baud_rate(tty, baudrate, baudrate);
+ break;
+ }
+ }
+
+ if (idx < 0)
+ return;
+
+ priv->baud_base = baudrate_table[idx];
+ divisor = calc_baud_divisor(baudrate, priv->baud_base);
+
+ status = f81232_set_mask_register(port, F81232_CLK_REGISTER,
+ F81232_CLK_MASK, clock_table[idx]);
+ if (status) {
+ dev_err(&port->dev, "%s failed to set CLK_REG: %d\n",
+ __func__, status);
+ return;
+ }
+
+ status = f81232_get_register(port, LINE_CONTROL_REGISTER,
+ &lcr); /* get LCR */
+ if (status) {
+ dev_err(&port->dev, "%s failed to get LCR: %d\n",
+ __func__, status);
+ return;
+ }
+
+ status = f81232_set_register(port, LINE_CONTROL_REGISTER,
+ lcr | UART_LCR_DLAB); /* Enable DLAB */
+ if (status) {
+ dev_err(&port->dev, "%s failed to set DLAB: %d\n",
+ __func__, status);
+ return;
+ }
+
+ status = f81232_set_register(port, RECEIVE_BUFFER_REGISTER,
+ divisor & 0x00ff); /* low */
+ if (status) {
+ dev_err(&port->dev, "%s failed to set baudrate MSB: %d\n",
+ __func__, status);
+ goto reapply_lcr;
+ }
+
+ status = f81232_set_register(port, INTERRUPT_ENABLE_REGISTER,
+ (divisor & 0xff00) >> 8); /* high */
+ if (status) {
+ dev_err(&port->dev, "%s failed to set baudrate LSB: %d\n",
+ __func__, status);
+ }
+
+reapply_lcr:
+ status = f81232_set_register(port, LINE_CONTROL_REGISTER,
+ lcr & ~UART_LCR_DLAB);
+ if (status) {
+ dev_err(&port->dev, "%s failed to set DLAB: %d\n",
+ __func__, status);
+ }
+}
+
+static int f81232_port_enable(struct usb_serial_port *port)
+{
+ u8 val;
+ int status;
+
+ /* fifo on, trigger8, clear TX/RX*/
+ val = UART_FCR_TRIGGER_8 | UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR |
+ UART_FCR_CLEAR_XMIT;
+
+ status = f81232_set_register(port, FIFO_CONTROL_REGISTER, val);
+ if (status) {
+ dev_err(&port->dev, "%s failed to set FCR: %d\n",
+ __func__, status);
+ return status;
+ }
+
+ /* MSR Interrupt only, LSR will read from Bulk-in odd byte */
+ status = f81232_set_register(port, INTERRUPT_ENABLE_REGISTER,
+ UART_IER_MSI);
+ if (status) {
+ dev_err(&port->dev, "%s failed to set IER: %d\n",
+ __func__, status);
+ return status;
+ }
+
+ return 0;
+}
+
+static int f81232_port_disable(struct usb_serial_port *port)
+{
+ int status;
+
+ status = f81232_set_register(port, INTERRUPT_ENABLE_REGISTER, 0);
+ if (status) {
+ dev_err(&port->dev, "%s failed to set IER: %d\n",
+ __func__, status);
+ return status;
+ }
+
+ return 0;
+}
+
+static void f81232_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct f81232_private *priv = usb_get_serial_port_data(port);
+ u8 new_lcr = 0;
+ int status = 0;
+ speed_t baudrate;
+ speed_t old_baud;
+
+ /* Don't change anything if nothing has changed */
+ if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios))
+ return;
+
+ if (C_BAUD(tty) == B0)
+ f81232_set_mctrl(port, 0, TIOCM_DTR | TIOCM_RTS);
+ else if (old_termios && (old_termios->c_cflag & CBAUD) == B0)
+ f81232_set_mctrl(port, TIOCM_DTR | TIOCM_RTS, 0);
+
+ baudrate = tty_get_baud_rate(tty);
+ if (baudrate > 0) {
+ if (old_termios)
+ old_baud = tty_termios_baud_rate(old_termios);
+ else
+ old_baud = F81232_DEF_BAUDRATE;
+
+ f81232_set_baudrate(tty, port, baudrate, old_baud);
+ }
+
+ if (C_PARENB(tty)) {
+ new_lcr |= UART_LCR_PARITY;
+
+ if (!C_PARODD(tty))
+ new_lcr |= UART_LCR_EPAR;
+
+ if (C_CMSPAR(tty))
+ new_lcr |= UART_LCR_SPAR;
+ }
+
+ if (C_CSTOPB(tty))
+ new_lcr |= UART_LCR_STOP;
+
+ new_lcr |= UART_LCR_WLEN(tty_get_char_size(tty->termios.c_cflag));
+
+ mutex_lock(&priv->lock);
+
+ new_lcr |= (priv->shadow_lcr & UART_LCR_SBC);
+ status = f81232_set_register(port, LINE_CONTROL_REGISTER, new_lcr);
+ if (status) {
+ dev_err(&port->dev, "%s failed to set LCR: %d\n",
+ __func__, status);
+ }
+
+ priv->shadow_lcr = new_lcr;
+
+ mutex_unlock(&priv->lock);
+}
+
+static int f81232_tiocmget(struct tty_struct *tty)
+{
+ int r;
+ struct usb_serial_port *port = tty->driver_data;
+ struct f81232_private *port_priv = usb_get_serial_port_data(port);
+ u8 mcr, msr;
+
+ /* force get current MSR changed state */
+ f81232_read_msr(port);
+
+ mutex_lock(&port_priv->lock);
+ mcr = port_priv->modem_control;
+ msr = port_priv->modem_status;
+ mutex_unlock(&port_priv->lock);
+
+ r = (mcr & UART_MCR_DTR ? TIOCM_DTR : 0) |
+ (mcr & UART_MCR_RTS ? TIOCM_RTS : 0) |
+ (msr & UART_MSR_CTS ? TIOCM_CTS : 0) |
+ (msr & UART_MSR_DCD ? TIOCM_CAR : 0) |
+ (msr & UART_MSR_RI ? TIOCM_RI : 0) |
+ (msr & UART_MSR_DSR ? TIOCM_DSR : 0);
+
+ return r;
+}
+
+static int f81232_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ return f81232_set_mctrl(port, set, clear);
+}
+
+static int f81232_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ int result;
+
+ result = f81232_port_enable(port);
+ if (result)
+ return result;
+
+ /* Setup termios */
+ if (tty)
+ f81232_set_termios(tty, port, NULL);
+
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (result) {
+ dev_err(&port->dev, "%s - failed submitting interrupt urb,"
+ " error %d\n", __func__, result);
+ return result;
+ }
+
+ result = usb_serial_generic_open(tty, port);
+ if (result) {
+ usb_kill_urb(port->interrupt_in_urb);
+ return result;
+ }
+
+ return 0;
+}
+
+static int f81534a_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ int status;
+ u8 mask;
+ u8 val;
+
+ val = F81534A_TRIGGER_MULTIPLE_4X | F81534A_FIFO_128BYTE;
+ mask = F81534A_TRIGGER_MASK | F81534A_FIFO_128BYTE;
+
+ status = f81232_set_mask_register(port, F81534A_MODE_REG, mask, val);
+ if (status) {
+ dev_err(&port->dev, "failed to set MODE_REG: %d\n", status);
+ return status;
+ }
+
+ return f81232_open(tty, port);
+}
+
+static void f81232_close(struct usb_serial_port *port)
+{
+ struct f81232_private *port_priv = usb_get_serial_port_data(port);
+
+ f81232_port_disable(port);
+ usb_serial_generic_close(port);
+ usb_kill_urb(port->interrupt_in_urb);
+ flush_work(&port_priv->interrupt_work);
+ flush_work(&port_priv->lsr_work);
+}
+
+static void f81232_dtr_rts(struct usb_serial_port *port, int on)
+{
+ if (on)
+ f81232_set_mctrl(port, TIOCM_DTR | TIOCM_RTS, 0);
+ else
+ f81232_set_mctrl(port, 0, TIOCM_DTR | TIOCM_RTS);
+}
+
+static bool f81232_tx_empty(struct usb_serial_port *port)
+{
+ int status;
+ u8 tmp;
+
+ status = f81232_get_register(port, LINE_STATUS_REGISTER, &tmp);
+ if (!status) {
+ if ((tmp & UART_LSR_TEMT) != UART_LSR_TEMT)
+ return false;
+ }
+
+ return true;
+}
+
+static int f81232_carrier_raised(struct usb_serial_port *port)
+{
+ u8 msr;
+ struct f81232_private *priv = usb_get_serial_port_data(port);
+
+ mutex_lock(&priv->lock);
+ msr = priv->modem_status;
+ mutex_unlock(&priv->lock);
+
+ if (msr & UART_MSR_DCD)
+ return 1;
+ return 0;
+}
+
+static void f81232_get_serial(struct tty_struct *tty, struct serial_struct *ss)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct f81232_private *priv = usb_get_serial_port_data(port);
+
+ ss->baud_base = priv->baud_base;
+}
+
+static void f81232_interrupt_work(struct work_struct *work)
+{
+ struct f81232_private *priv =
+ container_of(work, struct f81232_private, interrupt_work);
+
+ f81232_read_msr(priv->port);
+}
+
+static void f81232_lsr_worker(struct work_struct *work)
+{
+ struct f81232_private *priv;
+ struct usb_serial_port *port;
+ int status;
+ u8 tmp;
+
+ priv = container_of(work, struct f81232_private, lsr_work);
+ port = priv->port;
+
+ status = f81232_get_register(port, LINE_STATUS_REGISTER, &tmp);
+ if (status)
+ dev_warn(&port->dev, "read LSR failed: %d\n", status);
+}
+
+static int f81534a_ctrl_set_register(struct usb_interface *intf, u16 reg,
+ u16 size, void *val)
+{
+ return usb_control_msg_send(interface_to_usbdev(intf),
+ 0,
+ F81232_REGISTER_REQUEST,
+ F81232_SET_REGISTER,
+ reg,
+ 0,
+ val,
+ size,
+ USB_CTRL_SET_TIMEOUT,
+ GFP_KERNEL);
+}
+
+static int f81534a_ctrl_get_register(struct usb_interface *intf, u16 reg,
+ u16 size, void *val)
+{
+ return usb_control_msg_recv(interface_to_usbdev(intf),
+ 0,
+ F81232_REGISTER_REQUEST,
+ F81232_GET_REGISTER,
+ reg,
+ 0,
+ val,
+ size,
+ USB_CTRL_GET_TIMEOUT,
+ GFP_KERNEL);
+}
+
+static int f81534a_ctrl_enable_all_ports(struct usb_interface *intf, bool en)
+{
+ unsigned char enable[2] = {0};
+ int status;
+
+ /*
+ * Enable all available serial ports, define as following:
+ * bit 15 : Reset behavior (when HUB got soft reset)
+ * 0: maintain all serial port enabled state.
+ * 1: disable all serial port.
+ * bit 0~11 : Serial port enable bit.
+ */
+ if (en) {
+ /*
+ * The Fintek F81532A/534A/535/536 family relies on the
+ * F81534A_CTRL_CMD_ENABLE_PORT (116h) register during
+ * initialization to both determine serial port status and
+ * control port creation.
+ *
+ * If the driver experiences fast load/unload cycles, the
+ * device state may becomes unstable, resulting in the
+ * incomplete generation of serial ports.
+ *
+ * Performing a dummy read operation on the register prior
+ * to the initial write command resolves the issue.
+ *
+ * This clears the device's stale internal state. Subsequent
+ * write operations will correctly generate all serial ports.
+ */
+ status = f81534a_ctrl_get_register(intf,
+ F81534A_CTRL_CMD_ENABLE_PORT,
+ sizeof(enable),
+ enable);
+ if (status)
+ return status;
+
+ enable[0] = 0xff;
+ enable[1] = 0x8f;
+ }
+
+ status = f81534a_ctrl_set_register(intf, F81534A_CTRL_CMD_ENABLE_PORT,
+ sizeof(enable), enable);
+ if (status)
+ dev_err(&intf->dev, "failed to enable ports: %d\n", status);
+
+ return status;
+}
+
+static int f81534a_ctrl_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return f81534a_ctrl_enable_all_ports(intf, true);
+}
+
+static void f81534a_ctrl_disconnect(struct usb_interface *intf)
+{
+ f81534a_ctrl_enable_all_ports(intf, false);
+}
+
+static int f81534a_ctrl_resume(struct usb_interface *intf)
+{
+ return f81534a_ctrl_enable_all_ports(intf, true);
+}
+
+static int f81232_port_probe(struct usb_serial_port *port)
+{
+ struct f81232_private *priv;
+
+ priv = devm_kzalloc(&port->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ mutex_init(&priv->lock);
+ INIT_WORK(&priv->interrupt_work, f81232_interrupt_work);
+ INIT_WORK(&priv->lsr_work, f81232_lsr_worker);
+
+ usb_set_serial_port_data(port, priv);
+
+ priv->port = port;
+
+ return 0;
+}
+
+static int f81534a_port_probe(struct usb_serial_port *port)
+{
+ int status;
+
+ /* tri-state with pull-high, default RS232 Mode */
+ status = f81232_set_register(port, F81534A_GPIO_REG,
+ F81534A_GPIO_MODE2_DIR);
+ if (status)
+ return status;
+
+ return f81232_port_probe(port);
+}
+
+static int f81232_suspend(struct usb_serial *serial, pm_message_t message)
+{
+ struct usb_serial_port *port = serial->port[0];
+ struct f81232_private *port_priv = usb_get_serial_port_data(port);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i)
+ usb_kill_urb(port->read_urbs[i]);
+
+ usb_kill_urb(port->interrupt_in_urb);
+
+ if (port_priv) {
+ flush_work(&port_priv->interrupt_work);
+ flush_work(&port_priv->lsr_work);
+ }
+
+ return 0;
+}
+
+static int f81232_resume(struct usb_serial *serial)
+{
+ struct usb_serial_port *port = serial->port[0];
+ int result;
+
+ if (tty_port_initialized(&port->port)) {
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
+ if (result) {
+ dev_err(&port->dev, "submit interrupt urb failed: %d\n",
+ result);
+ return result;
+ }
+ }
+
+ return usb_serial_generic_resume(serial);
+}
+
+static struct usb_serial_driver f81232_device = {
+ .driver = {
+ .name = "f81232",
+ },
+ .id_table = f81232_id_table,
+ .num_ports = 1,
+ .bulk_in_size = 256,
+ .bulk_out_size = 256,
+ .open = f81232_open,
+ .close = f81232_close,
+ .dtr_rts = f81232_dtr_rts,
+ .carrier_raised = f81232_carrier_raised,
+ .get_serial = f81232_get_serial,
+ .break_ctl = f81232_break_ctl,
+ .set_termios = f81232_set_termios,
+ .tiocmget = f81232_tiocmget,
+ .tiocmset = f81232_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .tx_empty = f81232_tx_empty,
+ .process_read_urb = f81232_process_read_urb,
+ .read_int_callback = f81232_read_int_callback,
+ .port_probe = f81232_port_probe,
+ .suspend = f81232_suspend,
+ .resume = f81232_resume,
+};
+
+static struct usb_serial_driver f81534a_device = {
+ .driver = {
+ .name = "f81534a",
+ },
+ .id_table = f81534a_id_table,
+ .num_ports = 1,
+ .open = f81534a_open,
+ .close = f81232_close,
+ .dtr_rts = f81232_dtr_rts,
+ .carrier_raised = f81232_carrier_raised,
+ .get_serial = f81232_get_serial,
+ .break_ctl = f81232_break_ctl,
+ .set_termios = f81232_set_termios,
+ .tiocmget = f81232_tiocmget,
+ .tiocmset = f81232_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .tx_empty = f81232_tx_empty,
+ .process_read_urb = f81534a_process_read_urb,
+ .read_int_callback = f81232_read_int_callback,
+ .port_probe = f81534a_port_probe,
+ .suspend = f81232_suspend,
+ .resume = f81232_resume,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &f81232_device,
+ &f81534a_device,
+ NULL,
+};
+
+static struct usb_driver f81534a_ctrl_driver = {
+ .name = "f81534a_ctrl",
+ .id_table = f81534a_ctrl_id_table,
+ .probe = f81534a_ctrl_probe,
+ .disconnect = f81534a_ctrl_disconnect,
+ .resume = f81534a_ctrl_resume,
+};
+
+static int __init f81232_init(void)
+{
+ int status;
+
+ status = usb_register_driver(&f81534a_ctrl_driver, THIS_MODULE,
+ KBUILD_MODNAME);
+ if (status)
+ return status;
+
+ status = usb_serial_register_drivers(serial_drivers, KBUILD_MODNAME,
+ combined_id_table);
+ if (status) {
+ usb_deregister(&f81534a_ctrl_driver);
+ return status;
+ }
+
+ return 0;
+}
+
+static void __exit f81232_exit(void)
+{
+ usb_serial_deregister_drivers(serial_drivers);
+ usb_deregister(&f81534a_ctrl_driver);
+}
+
+module_init(f81232_init);
+module_exit(f81232_exit);
+
+MODULE_DESCRIPTION("Fintek F81232/532A/534A/535/536 USB to serial driver");
+MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>");
+MODULE_AUTHOR("Peter Hong <peter_hong@fintek.com.tw>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/f81534.c b/drivers/usb/serial/f81534.c
new file mode 100644
index 000000000..0037767e4
--- /dev/null
+++ b/drivers/usb/serial/f81534.c
@@ -0,0 +1,1574 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * F81532/F81534 USB to Serial Ports Bridge
+ *
+ * F81532 => 2 Serial Ports
+ * F81534 => 4 Serial Ports
+ *
+ * Copyright (C) 2016 Feature Integration Technology Inc., (Fintek)
+ * Copyright (C) 2016 Tom Tsai (Tom_Tsai@fintek.com.tw)
+ * Copyright (C) 2016 Peter Hong (Peter_Hong@fintek.com.tw)
+ *
+ * The F81532/F81534 had 1 control endpoint for setting, 1 endpoint bulk-out
+ * for all serial port TX and 1 endpoint bulk-in for all serial port read in
+ * (Read Data/MSR/LSR).
+ *
+ * Write URB is fixed with 512bytes, per serial port used 128Bytes.
+ * It can be described by f81534_prepare_write_buffer()
+ *
+ * Read URB is 512Bytes max, per serial port used 128Bytes.
+ * It can be described by f81534_process_read_urb() and maybe received with
+ * 128x1,2,3,4 bytes.
+ *
+ */
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/module.h>
+
+/* Serial Port register Address */
+#define F81534_UART_BASE_ADDRESS 0x1200
+#define F81534_UART_OFFSET 0x10
+#define F81534_DIVISOR_LSB_REG (0x00 + F81534_UART_BASE_ADDRESS)
+#define F81534_DIVISOR_MSB_REG (0x01 + F81534_UART_BASE_ADDRESS)
+#define F81534_INTERRUPT_ENABLE_REG (0x01 + F81534_UART_BASE_ADDRESS)
+#define F81534_FIFO_CONTROL_REG (0x02 + F81534_UART_BASE_ADDRESS)
+#define F81534_LINE_CONTROL_REG (0x03 + F81534_UART_BASE_ADDRESS)
+#define F81534_MODEM_CONTROL_REG (0x04 + F81534_UART_BASE_ADDRESS)
+#define F81534_LINE_STATUS_REG (0x05 + F81534_UART_BASE_ADDRESS)
+#define F81534_MODEM_STATUS_REG (0x06 + F81534_UART_BASE_ADDRESS)
+#define F81534_CLOCK_REG (0x08 + F81534_UART_BASE_ADDRESS)
+#define F81534_CONFIG1_REG (0x09 + F81534_UART_BASE_ADDRESS)
+
+#define F81534_DEF_CONF_ADDRESS_START 0x3000
+#define F81534_DEF_CONF_SIZE 12
+
+#define F81534_CUSTOM_ADDRESS_START 0x2f00
+#define F81534_CUSTOM_DATA_SIZE 0x10
+#define F81534_CUSTOM_NO_CUSTOM_DATA 0xff
+#define F81534_CUSTOM_VALID_TOKEN 0xf0
+#define F81534_CONF_OFFSET 1
+#define F81534_CONF_INIT_GPIO_OFFSET 4
+#define F81534_CONF_WORK_GPIO_OFFSET 8
+#define F81534_CONF_GPIO_SHUTDOWN 7
+#define F81534_CONF_GPIO_RS232 1
+
+#define F81534_MAX_DATA_BLOCK 64
+#define F81534_MAX_BUS_RETRY 20
+
+/* Default URB timeout for USB operations */
+#define F81534_USB_MAX_RETRY 10
+#define F81534_USB_TIMEOUT 2000
+#define F81534_SET_GET_REGISTER 0xA0
+
+#define F81534_NUM_PORT 4
+#define F81534_UNUSED_PORT 0xff
+#define F81534_WRITE_BUFFER_SIZE 512
+
+#define DRIVER_DESC "Fintek F81532/F81534"
+#define FINTEK_VENDOR_ID_1 0x1934
+#define FINTEK_VENDOR_ID_2 0x2C42
+#define FINTEK_DEVICE_ID 0x1202
+#define F81534_MAX_TX_SIZE 124
+#define F81534_MAX_RX_SIZE 124
+#define F81534_RECEIVE_BLOCK_SIZE 128
+#define F81534_MAX_RECEIVE_BLOCK_SIZE 512
+
+#define F81534_TOKEN_RECEIVE 0x01
+#define F81534_TOKEN_WRITE 0x02
+#define F81534_TOKEN_TX_EMPTY 0x03
+#define F81534_TOKEN_MSR_CHANGE 0x04
+
+/*
+ * We used interal SPI bus to access FLASH section. We must wait the SPI bus to
+ * idle if we performed any command.
+ *
+ * SPI Bus status register: F81534_BUS_REG_STATUS
+ * Bit 0/1 : BUSY
+ * Bit 2 : IDLE
+ */
+#define F81534_BUS_BUSY (BIT(0) | BIT(1))
+#define F81534_BUS_IDLE BIT(2)
+#define F81534_BUS_READ_DATA 0x1004
+#define F81534_BUS_REG_STATUS 0x1003
+#define F81534_BUS_REG_START 0x1002
+#define F81534_BUS_REG_END 0x1001
+
+#define F81534_CMD_READ 0x03
+
+#define F81534_DEFAULT_BAUD_RATE 9600
+
+#define F81534_PORT_CONF_RS232 0
+#define F81534_PORT_CONF_RS485 BIT(0)
+#define F81534_PORT_CONF_RS485_INVERT (BIT(0) | BIT(1))
+#define F81534_PORT_CONF_MODE_MASK GENMASK(1, 0)
+#define F81534_PORT_CONF_DISABLE_PORT BIT(3)
+#define F81534_PORT_CONF_NOT_EXIST_PORT BIT(7)
+#define F81534_PORT_UNAVAILABLE \
+ (F81534_PORT_CONF_DISABLE_PORT | F81534_PORT_CONF_NOT_EXIST_PORT)
+
+
+#define F81534_1X_RXTRIGGER 0xc3
+#define F81534_8X_RXTRIGGER 0xcf
+
+/*
+ * F81532/534 Clock registers (offset +08h)
+ *
+ * Bit0: UART Enable (always on)
+ * Bit2-1: Clock source selector
+ * 00: 1.846MHz.
+ * 01: 18.46MHz.
+ * 10: 24MHz.
+ * 11: 14.77MHz.
+ * Bit4: Auto direction(RTS) control (RTS pin Low when TX)
+ * Bit5: Invert direction(RTS) when Bit4 enabled (RTS pin high when TX)
+ */
+
+#define F81534_UART_EN BIT(0)
+#define F81534_CLK_1_846_MHZ 0
+#define F81534_CLK_18_46_MHZ BIT(1)
+#define F81534_CLK_24_MHZ BIT(2)
+#define F81534_CLK_14_77_MHZ (BIT(1) | BIT(2))
+#define F81534_CLK_MASK GENMASK(2, 1)
+#define F81534_CLK_TX_DELAY_1BIT BIT(3)
+#define F81534_CLK_RS485_MODE BIT(4)
+#define F81534_CLK_RS485_INVERT BIT(5)
+
+static const struct usb_device_id f81534_id_table[] = {
+ { USB_DEVICE(FINTEK_VENDOR_ID_1, FINTEK_DEVICE_ID) },
+ { USB_DEVICE(FINTEK_VENDOR_ID_2, FINTEK_DEVICE_ID) },
+ {} /* Terminating entry */
+};
+
+#define F81534_TX_EMPTY_BIT 0
+
+struct f81534_serial_private {
+ u8 conf_data[F81534_DEF_CONF_SIZE];
+ int tty_idx[F81534_NUM_PORT];
+ u8 setting_idx;
+ int opened_port;
+ struct mutex urb_mutex;
+};
+
+struct f81534_port_private {
+ struct mutex mcr_mutex;
+ struct mutex lcr_mutex;
+ struct work_struct lsr_work;
+ struct usb_serial_port *port;
+ unsigned long tx_empty;
+ spinlock_t msr_lock;
+ u32 baud_base;
+ u8 shadow_mcr;
+ u8 shadow_lcr;
+ u8 shadow_msr;
+ u8 shadow_clk;
+ u8 phy_num;
+};
+
+struct f81534_pin_data {
+ const u16 reg_addr;
+ const u8 reg_mask;
+};
+
+struct f81534_port_out_pin {
+ struct f81534_pin_data pin[3];
+};
+
+/* Pin output value for M2/M1/M0(SD) */
+static const struct f81534_port_out_pin f81534_port_out_pins[] = {
+ { { { 0x2ae8, BIT(7) }, { 0x2a90, BIT(5) }, { 0x2a90, BIT(4) } } },
+ { { { 0x2ae8, BIT(6) }, { 0x2ae8, BIT(0) }, { 0x2ae8, BIT(3) } } },
+ { { { 0x2a90, BIT(0) }, { 0x2ae8, BIT(2) }, { 0x2a80, BIT(6) } } },
+ { { { 0x2a90, BIT(3) }, { 0x2a90, BIT(2) }, { 0x2a90, BIT(1) } } },
+};
+
+static u32 const baudrate_table[] = { 115200, 921600, 1152000, 1500000 };
+static u8 const clock_table[] = { F81534_CLK_1_846_MHZ, F81534_CLK_14_77_MHZ,
+ F81534_CLK_18_46_MHZ, F81534_CLK_24_MHZ };
+
+static int f81534_logic_to_phy_port(struct usb_serial *serial,
+ struct usb_serial_port *port)
+{
+ struct f81534_serial_private *serial_priv =
+ usb_get_serial_data(port->serial);
+ int count = 0;
+ int i;
+
+ for (i = 0; i < F81534_NUM_PORT; ++i) {
+ if (serial_priv->conf_data[i] & F81534_PORT_UNAVAILABLE)
+ continue;
+
+ if (port->port_number == count)
+ return i;
+
+ ++count;
+ }
+
+ return -ENODEV;
+}
+
+static int f81534_set_register(struct usb_serial *serial, u16 reg, u8 data)
+{
+ struct usb_interface *interface = serial->interface;
+ struct usb_device *dev = serial->dev;
+ size_t count = F81534_USB_MAX_RETRY;
+ int status;
+ u8 *tmp;
+
+ tmp = kmalloc(sizeof(u8), GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ *tmp = data;
+
+ /*
+ * Our device maybe not reply when heavily loading, We'll retry for
+ * F81534_USB_MAX_RETRY times.
+ */
+ while (count--) {
+ status = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ F81534_SET_GET_REGISTER,
+ USB_TYPE_VENDOR | USB_DIR_OUT,
+ reg, 0, tmp, sizeof(u8),
+ F81534_USB_TIMEOUT);
+ if (status == sizeof(u8)) {
+ status = 0;
+ break;
+ }
+ }
+
+ if (status < 0) {
+ dev_err(&interface->dev, "%s: reg: %x data: %x failed: %d\n",
+ __func__, reg, data, status);
+ }
+
+ kfree(tmp);
+ return status;
+}
+
+static int f81534_get_register(struct usb_serial *serial, u16 reg, u8 *data)
+{
+ struct usb_interface *interface = serial->interface;
+ struct usb_device *dev = serial->dev;
+ size_t count = F81534_USB_MAX_RETRY;
+ int status;
+ u8 *tmp;
+
+ tmp = kmalloc(sizeof(u8), GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ /*
+ * Our device maybe not reply when heavily loading, We'll retry for
+ * F81534_USB_MAX_RETRY times.
+ */
+ while (count--) {
+ status = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+ F81534_SET_GET_REGISTER,
+ USB_TYPE_VENDOR | USB_DIR_IN,
+ reg, 0, tmp, sizeof(u8),
+ F81534_USB_TIMEOUT);
+ if (status > 0) {
+ status = 0;
+ break;
+ } else if (status == 0) {
+ status = -EIO;
+ }
+ }
+
+ if (status < 0) {
+ dev_err(&interface->dev, "%s: reg: %x failed: %d\n", __func__,
+ reg, status);
+ goto end;
+ }
+
+ *data = *tmp;
+
+end:
+ kfree(tmp);
+ return status;
+}
+
+static int f81534_set_mask_register(struct usb_serial *serial, u16 reg,
+ u8 mask, u8 data)
+{
+ int status;
+ u8 tmp;
+
+ status = f81534_get_register(serial, reg, &tmp);
+ if (status)
+ return status;
+
+ tmp &= ~mask;
+ tmp |= (mask & data);
+
+ return f81534_set_register(serial, reg, tmp);
+}
+
+static int f81534_set_phy_port_register(struct usb_serial *serial, int phy,
+ u16 reg, u8 data)
+{
+ return f81534_set_register(serial, reg + F81534_UART_OFFSET * phy,
+ data);
+}
+
+static int f81534_get_phy_port_register(struct usb_serial *serial, int phy,
+ u16 reg, u8 *data)
+{
+ return f81534_get_register(serial, reg + F81534_UART_OFFSET * phy,
+ data);
+}
+
+static int f81534_set_port_register(struct usb_serial_port *port, u16 reg,
+ u8 data)
+{
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+
+ return f81534_set_register(port->serial,
+ reg + port_priv->phy_num * F81534_UART_OFFSET, data);
+}
+
+static int f81534_get_port_register(struct usb_serial_port *port, u16 reg,
+ u8 *data)
+{
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+
+ return f81534_get_register(port->serial,
+ reg + port_priv->phy_num * F81534_UART_OFFSET, data);
+}
+
+/*
+ * If we try to access the internal flash via SPI bus, we should check the bus
+ * status for every command. e.g., F81534_BUS_REG_START/F81534_BUS_REG_END
+ */
+static int f81534_wait_for_spi_idle(struct usb_serial *serial)
+{
+ size_t count = F81534_MAX_BUS_RETRY;
+ u8 tmp;
+ int status;
+
+ do {
+ status = f81534_get_register(serial, F81534_BUS_REG_STATUS,
+ &tmp);
+ if (status)
+ return status;
+
+ if (tmp & F81534_BUS_BUSY)
+ continue;
+
+ if (tmp & F81534_BUS_IDLE)
+ break;
+
+ } while (--count);
+
+ if (!count) {
+ dev_err(&serial->interface->dev,
+ "%s: timed out waiting for idle SPI bus\n",
+ __func__);
+ return -EIO;
+ }
+
+ return f81534_set_register(serial, F81534_BUS_REG_STATUS,
+ tmp & ~F81534_BUS_IDLE);
+}
+
+static int f81534_get_spi_register(struct usb_serial *serial, u16 reg,
+ u8 *data)
+{
+ int status;
+
+ status = f81534_get_register(serial, reg, data);
+ if (status)
+ return status;
+
+ return f81534_wait_for_spi_idle(serial);
+}
+
+static int f81534_set_spi_register(struct usb_serial *serial, u16 reg, u8 data)
+{
+ int status;
+
+ status = f81534_set_register(serial, reg, data);
+ if (status)
+ return status;
+
+ return f81534_wait_for_spi_idle(serial);
+}
+
+static int f81534_read_flash(struct usb_serial *serial, u32 address,
+ size_t size, u8 *buf)
+{
+ u8 tmp_buf[F81534_MAX_DATA_BLOCK];
+ size_t block = 0;
+ size_t read_size;
+ size_t count;
+ int status;
+ int offset;
+ u16 reg_tmp;
+
+ status = f81534_set_spi_register(serial, F81534_BUS_REG_START,
+ F81534_CMD_READ);
+ if (status)
+ return status;
+
+ status = f81534_set_spi_register(serial, F81534_BUS_REG_START,
+ (address >> 16) & 0xff);
+ if (status)
+ return status;
+
+ status = f81534_set_spi_register(serial, F81534_BUS_REG_START,
+ (address >> 8) & 0xff);
+ if (status)
+ return status;
+
+ status = f81534_set_spi_register(serial, F81534_BUS_REG_START,
+ (address >> 0) & 0xff);
+ if (status)
+ return status;
+
+ /* Continuous read mode */
+ do {
+ read_size = min_t(size_t, F81534_MAX_DATA_BLOCK, size);
+
+ for (count = 0; count < read_size; ++count) {
+ /* To write F81534_BUS_REG_END when final byte */
+ if (size <= F81534_MAX_DATA_BLOCK &&
+ read_size == count + 1)
+ reg_tmp = F81534_BUS_REG_END;
+ else
+ reg_tmp = F81534_BUS_REG_START;
+
+ /*
+ * Dummy code, force IC to generate a read pulse, the
+ * set of value 0xf1 is dont care (any value is ok)
+ */
+ status = f81534_set_spi_register(serial, reg_tmp,
+ 0xf1);
+ if (status)
+ return status;
+
+ status = f81534_get_spi_register(serial,
+ F81534_BUS_READ_DATA,
+ &tmp_buf[count]);
+ if (status)
+ return status;
+
+ offset = count + block * F81534_MAX_DATA_BLOCK;
+ buf[offset] = tmp_buf[count];
+ }
+
+ size -= read_size;
+ ++block;
+ } while (size);
+
+ return 0;
+}
+
+static void f81534_prepare_write_buffer(struct usb_serial_port *port, u8 *buf)
+{
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+ int phy_num = port_priv->phy_num;
+ u8 tx_len;
+ int i;
+
+ /*
+ * The block layout is fixed with 4x128 Bytes, per 128 Bytes a port.
+ * index 0: port phy idx (e.g., 0,1,2,3)
+ * index 1: only F81534_TOKEN_WRITE
+ * index 2: serial TX out length
+ * index 3: fix to 0
+ * index 4~127: serial out data block
+ */
+ for (i = 0; i < F81534_NUM_PORT; ++i) {
+ buf[i * F81534_RECEIVE_BLOCK_SIZE] = i;
+ buf[i * F81534_RECEIVE_BLOCK_SIZE + 1] = F81534_TOKEN_WRITE;
+ buf[i * F81534_RECEIVE_BLOCK_SIZE + 2] = 0;
+ buf[i * F81534_RECEIVE_BLOCK_SIZE + 3] = 0;
+ }
+
+ tx_len = kfifo_out_locked(&port->write_fifo,
+ &buf[phy_num * F81534_RECEIVE_BLOCK_SIZE + 4],
+ F81534_MAX_TX_SIZE, &port->lock);
+
+ buf[phy_num * F81534_RECEIVE_BLOCK_SIZE + 2] = tx_len;
+}
+
+static int f81534_submit_writer(struct usb_serial_port *port, gfp_t mem_flags)
+{
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+ struct urb *urb;
+ unsigned long flags;
+ int result;
+
+ /* Check is any data in write_fifo */
+ spin_lock_irqsave(&port->lock, flags);
+
+ if (kfifo_is_empty(&port->write_fifo)) {
+ spin_unlock_irqrestore(&port->lock, flags);
+ return 0;
+ }
+
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ /* Check H/W is TXEMPTY */
+ if (!test_and_clear_bit(F81534_TX_EMPTY_BIT, &port_priv->tx_empty))
+ return 0;
+
+ urb = port->write_urbs[0];
+ f81534_prepare_write_buffer(port, port->bulk_out_buffers[0]);
+ urb->transfer_buffer_length = F81534_WRITE_BUFFER_SIZE;
+
+ result = usb_submit_urb(urb, mem_flags);
+ if (result) {
+ set_bit(F81534_TX_EMPTY_BIT, &port_priv->tx_empty);
+ dev_err(&port->dev, "%s: submit failed: %d\n", __func__,
+ result);
+ return result;
+ }
+
+ usb_serial_port_softint(port);
+ return 0;
+}
+
+static u32 f81534_calc_baud_divisor(u32 baudrate, u32 clockrate)
+{
+ /* Round to nearest divisor */
+ return DIV_ROUND_CLOSEST(clockrate, baudrate);
+}
+
+static int f81534_find_clk(u32 baudrate)
+{
+ int idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(baudrate_table); ++idx) {
+ if (baudrate <= baudrate_table[idx] &&
+ baudrate_table[idx] % baudrate == 0)
+ return idx;
+ }
+
+ return -EINVAL;
+}
+
+static int f81534_set_port_config(struct usb_serial_port *port,
+ struct tty_struct *tty, u32 baudrate, u32 old_baudrate, u8 lcr)
+{
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+ u32 divisor;
+ int status;
+ int i;
+ int idx;
+ u8 value;
+ u32 baud_list[] = {baudrate, old_baudrate, F81534_DEFAULT_BAUD_RATE};
+
+ for (i = 0; i < ARRAY_SIZE(baud_list); ++i) {
+ baudrate = baud_list[i];
+ if (baudrate == 0) {
+ tty_encode_baud_rate(tty, 0, 0);
+ return 0;
+ }
+
+ idx = f81534_find_clk(baudrate);
+ if (idx >= 0) {
+ tty_encode_baud_rate(tty, baudrate, baudrate);
+ break;
+ }
+ }
+
+ if (idx < 0)
+ return -EINVAL;
+
+ port_priv->baud_base = baudrate_table[idx];
+ port_priv->shadow_clk &= ~F81534_CLK_MASK;
+ port_priv->shadow_clk |= clock_table[idx];
+
+ status = f81534_set_port_register(port, F81534_CLOCK_REG,
+ port_priv->shadow_clk);
+ if (status) {
+ dev_err(&port->dev, "CLOCK_REG setting failed\n");
+ return status;
+ }
+
+ if (baudrate <= 1200)
+ value = F81534_1X_RXTRIGGER; /* 128 FIFO & TL: 1x */
+ else
+ value = F81534_8X_RXTRIGGER; /* 128 FIFO & TL: 8x */
+
+ status = f81534_set_port_register(port, F81534_CONFIG1_REG, value);
+ if (status) {
+ dev_err(&port->dev, "%s: CONFIG1 setting failed\n", __func__);
+ return status;
+ }
+
+ if (baudrate <= 1200)
+ value = UART_FCR_TRIGGER_1 | UART_FCR_ENABLE_FIFO; /* TL: 1 */
+ else
+ value = UART_FCR_TRIGGER_8 | UART_FCR_ENABLE_FIFO; /* TL: 8 */
+
+ status = f81534_set_port_register(port, F81534_FIFO_CONTROL_REG,
+ value);
+ if (status) {
+ dev_err(&port->dev, "%s: FCR setting failed\n", __func__);
+ return status;
+ }
+
+ divisor = f81534_calc_baud_divisor(baudrate, port_priv->baud_base);
+
+ mutex_lock(&port_priv->lcr_mutex);
+
+ value = UART_LCR_DLAB;
+ status = f81534_set_port_register(port, F81534_LINE_CONTROL_REG,
+ value);
+ if (status) {
+ dev_err(&port->dev, "%s: set LCR failed\n", __func__);
+ goto out_unlock;
+ }
+
+ value = divisor & 0xff;
+ status = f81534_set_port_register(port, F81534_DIVISOR_LSB_REG, value);
+ if (status) {
+ dev_err(&port->dev, "%s: set DLAB LSB failed\n", __func__);
+ goto out_unlock;
+ }
+
+ value = (divisor >> 8) & 0xff;
+ status = f81534_set_port_register(port, F81534_DIVISOR_MSB_REG, value);
+ if (status) {
+ dev_err(&port->dev, "%s: set DLAB MSB failed\n", __func__);
+ goto out_unlock;
+ }
+
+ value = lcr | (port_priv->shadow_lcr & UART_LCR_SBC);
+ status = f81534_set_port_register(port, F81534_LINE_CONTROL_REG,
+ value);
+ if (status) {
+ dev_err(&port->dev, "%s: set LCR failed\n", __func__);
+ goto out_unlock;
+ }
+
+ port_priv->shadow_lcr = value;
+out_unlock:
+ mutex_unlock(&port_priv->lcr_mutex);
+
+ return status;
+}
+
+static int f81534_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+ int status;
+
+ mutex_lock(&port_priv->lcr_mutex);
+
+ if (break_state)
+ port_priv->shadow_lcr |= UART_LCR_SBC;
+ else
+ port_priv->shadow_lcr &= ~UART_LCR_SBC;
+
+ status = f81534_set_port_register(port, F81534_LINE_CONTROL_REG,
+ port_priv->shadow_lcr);
+ if (status)
+ dev_err(&port->dev, "set break failed: %d\n", status);
+
+ mutex_unlock(&port_priv->lcr_mutex);
+
+ return status;
+}
+
+static int f81534_update_mctrl(struct usb_serial_port *port, unsigned int set,
+ unsigned int clear)
+{
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+ int status;
+ u8 tmp;
+
+ if (((set | clear) & (TIOCM_DTR | TIOCM_RTS)) == 0)
+ return 0; /* no change */
+
+ mutex_lock(&port_priv->mcr_mutex);
+
+ /* 'Set' takes precedence over 'Clear' */
+ clear &= ~set;
+
+ /* Always enable UART_MCR_OUT2 */
+ tmp = UART_MCR_OUT2 | port_priv->shadow_mcr;
+
+ if (clear & TIOCM_DTR)
+ tmp &= ~UART_MCR_DTR;
+
+ if (clear & TIOCM_RTS)
+ tmp &= ~UART_MCR_RTS;
+
+ if (set & TIOCM_DTR)
+ tmp |= UART_MCR_DTR;
+
+ if (set & TIOCM_RTS)
+ tmp |= UART_MCR_RTS;
+
+ status = f81534_set_port_register(port, F81534_MODEM_CONTROL_REG, tmp);
+ if (status < 0) {
+ dev_err(&port->dev, "%s: MCR write failed\n", __func__);
+ mutex_unlock(&port_priv->mcr_mutex);
+ return status;
+ }
+
+ port_priv->shadow_mcr = tmp;
+ mutex_unlock(&port_priv->mcr_mutex);
+ return 0;
+}
+
+/*
+ * This function will search the data area with token F81534_CUSTOM_VALID_TOKEN
+ * for latest configuration index. If nothing found
+ * (*index = F81534_CUSTOM_NO_CUSTOM_DATA), We'll load default configure in
+ * F81534_DEF_CONF_ADDRESS_START section.
+ *
+ * Due to we only use block0 to save data, so *index should be 0 or
+ * F81534_CUSTOM_NO_CUSTOM_DATA.
+ */
+static int f81534_find_config_idx(struct usb_serial *serial, u8 *index)
+{
+ u8 tmp;
+ int status;
+
+ status = f81534_read_flash(serial, F81534_CUSTOM_ADDRESS_START, 1,
+ &tmp);
+ if (status) {
+ dev_err(&serial->interface->dev, "%s: read failed: %d\n",
+ __func__, status);
+ return status;
+ }
+
+ /* We'll use the custom data when the data is valid. */
+ if (tmp == F81534_CUSTOM_VALID_TOKEN)
+ *index = 0;
+ else
+ *index = F81534_CUSTOM_NO_CUSTOM_DATA;
+
+ return 0;
+}
+
+/*
+ * The F81532/534 will not report serial port to USB serial subsystem when
+ * H/W DCD/DSR/CTS/RI/RX pin connected to ground.
+ *
+ * To detect RX pin status, we'll enable MCR interal loopback, disable it and
+ * delayed for 60ms. It connected to ground If LSR register report UART_LSR_BI.
+ */
+static bool f81534_check_port_hw_disabled(struct usb_serial *serial, int phy)
+{
+ int status;
+ u8 old_mcr;
+ u8 msr;
+ u8 lsr;
+ u8 msr_mask;
+
+ msr_mask = UART_MSR_DCD | UART_MSR_RI | UART_MSR_DSR | UART_MSR_CTS;
+
+ status = f81534_get_phy_port_register(serial, phy,
+ F81534_MODEM_STATUS_REG, &msr);
+ if (status)
+ return false;
+
+ if ((msr & msr_mask) != msr_mask)
+ return false;
+
+ status = f81534_set_phy_port_register(serial, phy,
+ F81534_FIFO_CONTROL_REG, UART_FCR_ENABLE_FIFO |
+ UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+ if (status)
+ return false;
+
+ status = f81534_get_phy_port_register(serial, phy,
+ F81534_MODEM_CONTROL_REG, &old_mcr);
+ if (status)
+ return false;
+
+ status = f81534_set_phy_port_register(serial, phy,
+ F81534_MODEM_CONTROL_REG, UART_MCR_LOOP);
+ if (status)
+ return false;
+
+ status = f81534_set_phy_port_register(serial, phy,
+ F81534_MODEM_CONTROL_REG, 0x0);
+ if (status)
+ return false;
+
+ msleep(60);
+
+ status = f81534_get_phy_port_register(serial, phy,
+ F81534_LINE_STATUS_REG, &lsr);
+ if (status)
+ return false;
+
+ status = f81534_set_phy_port_register(serial, phy,
+ F81534_MODEM_CONTROL_REG, old_mcr);
+ if (status)
+ return false;
+
+ if ((lsr & UART_LSR_BI) == UART_LSR_BI)
+ return true;
+
+ return false;
+}
+
+/*
+ * We had 2 generation of F81532/534 IC. All has an internal storage.
+ *
+ * 1st is pure USB-to-TTL RS232 IC and designed for 4 ports only, no any
+ * internal data will used. All mode and gpio control should manually set
+ * by AP or Driver and all storage space value are 0xff. The
+ * f81534_calc_num_ports() will run to final we marked as "oldest version"
+ * for this IC.
+ *
+ * 2rd is designed to more generic to use any transceiver and this is our
+ * mass production type. We'll save data in F81534_CUSTOM_ADDRESS_START
+ * (0x2f00) with 9bytes. The 1st byte is a indicater. If the token is
+ * F81534_CUSTOM_VALID_TOKEN(0xf0), the IC is 2nd gen type, the following
+ * 4bytes save port mode (0:RS232/1:RS485 Invert/2:RS485), and the last
+ * 4bytes save GPIO state(value from 0~7 to represent 3 GPIO output pin).
+ * The f81534_calc_num_ports() will run to "new style" with checking
+ * F81534_PORT_UNAVAILABLE section.
+ */
+static int f81534_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ struct f81534_serial_private *serial_priv;
+ struct device *dev = &serial->interface->dev;
+ int size_bulk_in = usb_endpoint_maxp(epds->bulk_in[0]);
+ int size_bulk_out = usb_endpoint_maxp(epds->bulk_out[0]);
+ u8 num_port = 0;
+ int index = 0;
+ int status;
+ int i;
+
+ if (size_bulk_out != F81534_WRITE_BUFFER_SIZE ||
+ size_bulk_in != F81534_MAX_RECEIVE_BLOCK_SIZE) {
+ dev_err(dev, "unsupported endpoint max packet size\n");
+ return -ENODEV;
+ }
+
+ serial_priv = devm_kzalloc(&serial->interface->dev,
+ sizeof(*serial_priv), GFP_KERNEL);
+ if (!serial_priv)
+ return -ENOMEM;
+
+ usb_set_serial_data(serial, serial_priv);
+ mutex_init(&serial_priv->urb_mutex);
+
+ /* Check had custom setting */
+ status = f81534_find_config_idx(serial, &serial_priv->setting_idx);
+ if (status) {
+ dev_err(&serial->interface->dev, "%s: find idx failed: %d\n",
+ __func__, status);
+ return status;
+ }
+
+ /*
+ * We'll read custom data only when data available, otherwise we'll
+ * read default value instead.
+ */
+ if (serial_priv->setting_idx != F81534_CUSTOM_NO_CUSTOM_DATA) {
+ status = f81534_read_flash(serial,
+ F81534_CUSTOM_ADDRESS_START +
+ F81534_CONF_OFFSET,
+ sizeof(serial_priv->conf_data),
+ serial_priv->conf_data);
+ if (status) {
+ dev_err(&serial->interface->dev,
+ "%s: get custom data failed: %d\n",
+ __func__, status);
+ return status;
+ }
+
+ dev_dbg(&serial->interface->dev,
+ "%s: read config from block: %d\n", __func__,
+ serial_priv->setting_idx);
+ } else {
+ /* Read default board setting */
+ status = f81534_read_flash(serial,
+ F81534_DEF_CONF_ADDRESS_START,
+ sizeof(serial_priv->conf_data),
+ serial_priv->conf_data);
+ if (status) {
+ dev_err(&serial->interface->dev,
+ "%s: read failed: %d\n", __func__,
+ status);
+ return status;
+ }
+
+ dev_dbg(&serial->interface->dev, "%s: read default config\n",
+ __func__);
+ }
+
+ /* New style, find all possible ports */
+ for (i = 0; i < F81534_NUM_PORT; ++i) {
+ if (f81534_check_port_hw_disabled(serial, i))
+ serial_priv->conf_data[i] |= F81534_PORT_UNAVAILABLE;
+
+ if (serial_priv->conf_data[i] & F81534_PORT_UNAVAILABLE)
+ continue;
+
+ ++num_port;
+ }
+
+ if (!num_port) {
+ dev_warn(&serial->interface->dev,
+ "no config found, assuming 4 ports\n");
+ num_port = 4; /* Nothing found, oldest version IC */
+ }
+
+ /* Assign phy-to-logic mapping */
+ for (i = 0; i < F81534_NUM_PORT; ++i) {
+ if (serial_priv->conf_data[i] & F81534_PORT_UNAVAILABLE)
+ continue;
+
+ serial_priv->tty_idx[i] = index++;
+ dev_dbg(&serial->interface->dev,
+ "%s: phy_num: %d, tty_idx: %d\n", __func__, i,
+ serial_priv->tty_idx[i]);
+ }
+
+ /*
+ * Setup bulk-out endpoint multiplexing. All ports share the same
+ * bulk-out endpoint.
+ */
+ BUILD_BUG_ON(ARRAY_SIZE(epds->bulk_out) < F81534_NUM_PORT);
+
+ for (i = 1; i < num_port; ++i)
+ epds->bulk_out[i] = epds->bulk_out[0];
+
+ epds->num_bulk_out = num_port;
+
+ return num_port;
+}
+
+static void f81534_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ u8 new_lcr = 0;
+ int status;
+ u32 baud;
+ u32 old_baud;
+
+ if (C_BAUD(tty) == B0)
+ f81534_update_mctrl(port, 0, TIOCM_DTR | TIOCM_RTS);
+ else if (old_termios && (old_termios->c_cflag & CBAUD) == B0)
+ f81534_update_mctrl(port, TIOCM_DTR | TIOCM_RTS, 0);
+
+ if (C_PARENB(tty)) {
+ new_lcr |= UART_LCR_PARITY;
+
+ if (!C_PARODD(tty))
+ new_lcr |= UART_LCR_EPAR;
+
+ if (C_CMSPAR(tty))
+ new_lcr |= UART_LCR_SPAR;
+ }
+
+ if (C_CSTOPB(tty))
+ new_lcr |= UART_LCR_STOP;
+
+ new_lcr |= UART_LCR_WLEN(tty_get_char_size(tty->termios.c_cflag));
+
+ baud = tty_get_baud_rate(tty);
+ if (!baud)
+ return;
+
+ if (old_termios)
+ old_baud = tty_termios_baud_rate(old_termios);
+ else
+ old_baud = F81534_DEFAULT_BAUD_RATE;
+
+ dev_dbg(&port->dev, "%s: baud: %d\n", __func__, baud);
+
+ status = f81534_set_port_config(port, tty, baud, old_baud, new_lcr);
+ if (status < 0) {
+ dev_err(&port->dev, "%s: set port config failed: %d\n",
+ __func__, status);
+ }
+}
+
+static int f81534_submit_read_urb(struct usb_serial *serial, gfp_t flags)
+{
+ return usb_serial_generic_submit_read_urbs(serial->port[0], flags);
+}
+
+static void f81534_msr_changed(struct usb_serial_port *port, u8 msr)
+{
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+ struct tty_struct *tty;
+ unsigned long flags;
+ u8 old_msr;
+
+ if (!(msr & UART_MSR_ANY_DELTA))
+ return;
+
+ spin_lock_irqsave(&port_priv->msr_lock, flags);
+ old_msr = port_priv->shadow_msr;
+ port_priv->shadow_msr = msr;
+ spin_unlock_irqrestore(&port_priv->msr_lock, flags);
+
+ dev_dbg(&port->dev, "%s: MSR from %02x to %02x\n", __func__, old_msr,
+ msr);
+
+ /* Update input line counters */
+ if (msr & UART_MSR_DCTS)
+ port->icount.cts++;
+ if (msr & UART_MSR_DDSR)
+ port->icount.dsr++;
+ if (msr & UART_MSR_DDCD)
+ port->icount.dcd++;
+ if (msr & UART_MSR_TERI)
+ port->icount.rng++;
+
+ wake_up_interruptible(&port->port.delta_msr_wait);
+
+ if (!(msr & UART_MSR_DDCD))
+ return;
+
+ dev_dbg(&port->dev, "%s: DCD Changed: phy_num: %d from %x to %x\n",
+ __func__, port_priv->phy_num, old_msr, msr);
+
+ tty = tty_port_tty_get(&port->port);
+ if (!tty)
+ return;
+
+ usb_serial_handle_dcd_change(port, tty, msr & UART_MSR_DCD);
+ tty_kref_put(tty);
+}
+
+static int f81534_read_msr(struct usb_serial_port *port)
+{
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ int status;
+ u8 msr;
+
+ /* Get MSR initial value */
+ status = f81534_get_port_register(port, F81534_MODEM_STATUS_REG, &msr);
+ if (status)
+ return status;
+
+ /* Force update current state */
+ spin_lock_irqsave(&port_priv->msr_lock, flags);
+ port_priv->shadow_msr = msr;
+ spin_unlock_irqrestore(&port_priv->msr_lock, flags);
+
+ return 0;
+}
+
+static int f81534_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct f81534_serial_private *serial_priv =
+ usb_get_serial_data(port->serial);
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+ int status;
+
+ status = f81534_set_port_register(port,
+ F81534_FIFO_CONTROL_REG, UART_FCR_ENABLE_FIFO |
+ UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+ if (status) {
+ dev_err(&port->dev, "%s: Clear FIFO failed: %d\n", __func__,
+ status);
+ return status;
+ }
+
+ if (tty)
+ f81534_set_termios(tty, port, NULL);
+
+ status = f81534_read_msr(port);
+ if (status)
+ return status;
+
+ mutex_lock(&serial_priv->urb_mutex);
+
+ /* Submit Read URBs for first port opened */
+ if (!serial_priv->opened_port) {
+ status = f81534_submit_read_urb(port->serial, GFP_KERNEL);
+ if (status)
+ goto exit;
+ }
+
+ serial_priv->opened_port++;
+
+exit:
+ mutex_unlock(&serial_priv->urb_mutex);
+
+ set_bit(F81534_TX_EMPTY_BIT, &port_priv->tx_empty);
+ return status;
+}
+
+static void f81534_close(struct usb_serial_port *port)
+{
+ struct f81534_serial_private *serial_priv =
+ usb_get_serial_data(port->serial);
+ struct usb_serial_port *port0 = port->serial->port[0];
+ unsigned long flags;
+ size_t i;
+
+ usb_kill_urb(port->write_urbs[0]);
+
+ spin_lock_irqsave(&port->lock, flags);
+ kfifo_reset_out(&port->write_fifo);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ /* Kill Read URBs when final port closed */
+ mutex_lock(&serial_priv->urb_mutex);
+ serial_priv->opened_port--;
+
+ if (!serial_priv->opened_port) {
+ for (i = 0; i < ARRAY_SIZE(port0->read_urbs); ++i)
+ usb_kill_urb(port0->read_urbs[i]);
+ }
+
+ mutex_unlock(&serial_priv->urb_mutex);
+}
+
+static void f81534_get_serial_info(struct tty_struct *tty, struct serial_struct *ss)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct f81534_port_private *port_priv;
+
+ port_priv = usb_get_serial_port_data(port);
+
+ ss->baud_base = port_priv->baud_base;
+}
+
+static void f81534_process_per_serial_block(struct usb_serial_port *port,
+ u8 *data)
+{
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+ int phy_num = data[0];
+ size_t read_size = 0;
+ size_t i;
+ char tty_flag;
+ int status;
+ u8 lsr;
+
+ /*
+ * The block layout is 128 Bytes
+ * index 0: port phy idx (e.g., 0,1,2,3),
+ * index 1: It's could be
+ * F81534_TOKEN_RECEIVE
+ * F81534_TOKEN_TX_EMPTY
+ * F81534_TOKEN_MSR_CHANGE
+ * index 2: serial in size (data+lsr, must be even)
+ * meaningful for F81534_TOKEN_RECEIVE only
+ * index 3: current MSR with this device
+ * index 4~127: serial in data block (data+lsr, must be even)
+ */
+ switch (data[1]) {
+ case F81534_TOKEN_TX_EMPTY:
+ set_bit(F81534_TX_EMPTY_BIT, &port_priv->tx_empty);
+
+ /* Try to submit writer */
+ status = f81534_submit_writer(port, GFP_ATOMIC);
+ if (status)
+ dev_err(&port->dev, "%s: submit failed\n", __func__);
+ return;
+
+ case F81534_TOKEN_MSR_CHANGE:
+ f81534_msr_changed(port, data[3]);
+ return;
+
+ case F81534_TOKEN_RECEIVE:
+ read_size = data[2];
+ if (read_size > F81534_MAX_RX_SIZE) {
+ dev_err(&port->dev,
+ "%s: phy: %d read_size: %zu larger than: %d\n",
+ __func__, phy_num, read_size,
+ F81534_MAX_RX_SIZE);
+ return;
+ }
+
+ break;
+
+ default:
+ dev_warn(&port->dev, "%s: unknown token: %02x\n", __func__,
+ data[1]);
+ return;
+ }
+
+ for (i = 4; i < 4 + read_size; i += 2) {
+ tty_flag = TTY_NORMAL;
+ lsr = data[i + 1];
+
+ if (lsr & UART_LSR_BRK_ERROR_BITS) {
+ if (lsr & UART_LSR_BI) {
+ tty_flag = TTY_BREAK;
+ port->icount.brk++;
+ usb_serial_handle_break(port);
+ } else if (lsr & UART_LSR_PE) {
+ tty_flag = TTY_PARITY;
+ port->icount.parity++;
+ } else if (lsr & UART_LSR_FE) {
+ tty_flag = TTY_FRAME;
+ port->icount.frame++;
+ }
+
+ if (lsr & UART_LSR_OE) {
+ port->icount.overrun++;
+ tty_insert_flip_char(&port->port, 0,
+ TTY_OVERRUN);
+ }
+
+ schedule_work(&port_priv->lsr_work);
+ }
+
+ if (port->sysrq) {
+ if (usb_serial_handle_sysrq_char(port, data[i]))
+ continue;
+ }
+
+ tty_insert_flip_char(&port->port, data[i], tty_flag);
+ }
+
+ tty_flip_buffer_push(&port->port);
+}
+
+static void f81534_process_read_urb(struct urb *urb)
+{
+ struct f81534_serial_private *serial_priv;
+ struct usb_serial_port *port;
+ struct usb_serial *serial;
+ u8 *buf;
+ int phy_port_num;
+ int tty_port_num;
+ size_t i;
+
+ if (!urb->actual_length ||
+ urb->actual_length % F81534_RECEIVE_BLOCK_SIZE) {
+ return;
+ }
+
+ port = urb->context;
+ serial = port->serial;
+ buf = urb->transfer_buffer;
+ serial_priv = usb_get_serial_data(serial);
+
+ for (i = 0; i < urb->actual_length; i += F81534_RECEIVE_BLOCK_SIZE) {
+ phy_port_num = buf[i];
+ if (phy_port_num >= F81534_NUM_PORT) {
+ dev_err(&port->dev,
+ "%s: phy_port_num: %d larger than: %d\n",
+ __func__, phy_port_num, F81534_NUM_PORT);
+ continue;
+ }
+
+ tty_port_num = serial_priv->tty_idx[phy_port_num];
+ port = serial->port[tty_port_num];
+
+ if (tty_port_initialized(&port->port))
+ f81534_process_per_serial_block(port, &buf[i]);
+ }
+}
+
+static void f81534_write_usb_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ENOENT:
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ dev_dbg(&port->dev, "%s - urb stopped: %d\n",
+ __func__, urb->status);
+ return;
+ case -EPIPE:
+ dev_err(&port->dev, "%s - urb stopped: %d\n",
+ __func__, urb->status);
+ return;
+ default:
+ dev_dbg(&port->dev, "%s - nonzero urb status: %d\n",
+ __func__, urb->status);
+ break;
+ }
+}
+
+static void f81534_lsr_worker(struct work_struct *work)
+{
+ struct f81534_port_private *port_priv;
+ struct usb_serial_port *port;
+ int status;
+ u8 tmp;
+
+ port_priv = container_of(work, struct f81534_port_private, lsr_work);
+ port = port_priv->port;
+
+ status = f81534_get_port_register(port, F81534_LINE_STATUS_REG, &tmp);
+ if (status)
+ dev_warn(&port->dev, "read LSR failed: %d\n", status);
+}
+
+static int f81534_set_port_output_pin(struct usb_serial_port *port)
+{
+ struct f81534_serial_private *serial_priv;
+ struct f81534_port_private *port_priv;
+ struct usb_serial *serial;
+ const struct f81534_port_out_pin *pins;
+ int status;
+ int i;
+ u8 value;
+ u8 idx;
+
+ serial = port->serial;
+ serial_priv = usb_get_serial_data(serial);
+ port_priv = usb_get_serial_port_data(port);
+
+ idx = F81534_CONF_INIT_GPIO_OFFSET + port_priv->phy_num;
+ value = serial_priv->conf_data[idx];
+ if (value >= F81534_CONF_GPIO_SHUTDOWN) {
+ /*
+ * Newer IC configure will make transceiver in shutdown mode on
+ * initial power on. We need enable it before using UARTs.
+ */
+ idx = F81534_CONF_WORK_GPIO_OFFSET + port_priv->phy_num;
+ value = serial_priv->conf_data[idx];
+ if (value >= F81534_CONF_GPIO_SHUTDOWN)
+ value = F81534_CONF_GPIO_RS232;
+ }
+
+ pins = &f81534_port_out_pins[port_priv->phy_num];
+
+ for (i = 0; i < ARRAY_SIZE(pins->pin); ++i) {
+ status = f81534_set_mask_register(serial,
+ pins->pin[i].reg_addr, pins->pin[i].reg_mask,
+ value & BIT(i) ? pins->pin[i].reg_mask : 0);
+ if (status)
+ return status;
+ }
+
+ dev_dbg(&port->dev, "Output pin (M0/M1/M2): %d\n", value);
+ return 0;
+}
+
+static int f81534_port_probe(struct usb_serial_port *port)
+{
+ struct f81534_serial_private *serial_priv;
+ struct f81534_port_private *port_priv;
+ int ret;
+ u8 value;
+
+ serial_priv = usb_get_serial_data(port->serial);
+ port_priv = devm_kzalloc(&port->dev, sizeof(*port_priv), GFP_KERNEL);
+ if (!port_priv)
+ return -ENOMEM;
+
+ /*
+ * We'll make tx frame error when baud rate from 384~500kps. So we'll
+ * delay all tx data frame with 1bit.
+ */
+ port_priv->shadow_clk = F81534_UART_EN | F81534_CLK_TX_DELAY_1BIT;
+ spin_lock_init(&port_priv->msr_lock);
+ mutex_init(&port_priv->mcr_mutex);
+ mutex_init(&port_priv->lcr_mutex);
+ INIT_WORK(&port_priv->lsr_work, f81534_lsr_worker);
+
+ /* Assign logic-to-phy mapping */
+ ret = f81534_logic_to_phy_port(port->serial, port);
+ if (ret < 0)
+ return ret;
+
+ port_priv->phy_num = ret;
+ port_priv->port = port;
+ usb_set_serial_port_data(port, port_priv);
+ dev_dbg(&port->dev, "%s: port_number: %d, phy_num: %d\n", __func__,
+ port->port_number, port_priv->phy_num);
+
+ /*
+ * The F81532/534 will hang-up when enable LSR interrupt in IER and
+ * occur data overrun. So we'll disable the LSR interrupt in probe()
+ * and submit the LSR worker to clear LSR state when reported LSR error
+ * bit with bulk-in data in f81534_process_per_serial_block().
+ */
+ ret = f81534_set_port_register(port, F81534_INTERRUPT_ENABLE_REG,
+ UART_IER_RDI | UART_IER_THRI | UART_IER_MSI);
+ if (ret)
+ return ret;
+
+ value = serial_priv->conf_data[port_priv->phy_num];
+ switch (value & F81534_PORT_CONF_MODE_MASK) {
+ case F81534_PORT_CONF_RS485_INVERT:
+ port_priv->shadow_clk |= F81534_CLK_RS485_MODE |
+ F81534_CLK_RS485_INVERT;
+ dev_dbg(&port->dev, "RS485 invert mode\n");
+ break;
+ case F81534_PORT_CONF_RS485:
+ port_priv->shadow_clk |= F81534_CLK_RS485_MODE;
+ dev_dbg(&port->dev, "RS485 mode\n");
+ break;
+
+ default:
+ case F81534_PORT_CONF_RS232:
+ dev_dbg(&port->dev, "RS232 mode\n");
+ break;
+ }
+
+ return f81534_set_port_output_pin(port);
+}
+
+static void f81534_port_remove(struct usb_serial_port *port)
+{
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+
+ flush_work(&port_priv->lsr_work);
+}
+
+static int f81534_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+ int status;
+ int r;
+ u8 msr;
+ u8 mcr;
+
+ /* Read current MSR from device */
+ status = f81534_get_port_register(port, F81534_MODEM_STATUS_REG, &msr);
+ if (status)
+ return status;
+
+ mutex_lock(&port_priv->mcr_mutex);
+ mcr = port_priv->shadow_mcr;
+ mutex_unlock(&port_priv->mcr_mutex);
+
+ r = (mcr & UART_MCR_DTR ? TIOCM_DTR : 0) |
+ (mcr & UART_MCR_RTS ? TIOCM_RTS : 0) |
+ (msr & UART_MSR_CTS ? TIOCM_CTS : 0) |
+ (msr & UART_MSR_DCD ? TIOCM_CAR : 0) |
+ (msr & UART_MSR_RI ? TIOCM_RI : 0) |
+ (msr & UART_MSR_DSR ? TIOCM_DSR : 0);
+
+ return r;
+}
+
+static int f81534_tiocmset(struct tty_struct *tty, unsigned int set,
+ unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ return f81534_update_mctrl(port, set, clear);
+}
+
+static void f81534_dtr_rts(struct usb_serial_port *port, int on)
+{
+ if (on)
+ f81534_update_mctrl(port, TIOCM_DTR | TIOCM_RTS, 0);
+ else
+ f81534_update_mctrl(port, 0, TIOCM_DTR | TIOCM_RTS);
+}
+
+static int f81534_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const u8 *buf, int count)
+{
+ int bytes_out, status;
+
+ if (!count)
+ return 0;
+
+ bytes_out = kfifo_in_locked(&port->write_fifo, buf, count,
+ &port->lock);
+
+ status = f81534_submit_writer(port, GFP_ATOMIC);
+ if (status) {
+ dev_err(&port->dev, "%s: submit failed\n", __func__);
+ return status;
+ }
+
+ return bytes_out;
+}
+
+static bool f81534_tx_empty(struct usb_serial_port *port)
+{
+ struct f81534_port_private *port_priv = usb_get_serial_port_data(port);
+
+ return test_bit(F81534_TX_EMPTY_BIT, &port_priv->tx_empty);
+}
+
+static int f81534_resume(struct usb_serial *serial)
+{
+ struct f81534_serial_private *serial_priv =
+ usb_get_serial_data(serial);
+ struct usb_serial_port *port;
+ int error = 0;
+ int status;
+ size_t i;
+
+ /*
+ * We'll register port 0 bulkin when port had opened, It'll take all
+ * port received data, MSR register change and TX_EMPTY information.
+ */
+ mutex_lock(&serial_priv->urb_mutex);
+
+ if (serial_priv->opened_port) {
+ status = f81534_submit_read_urb(serial, GFP_NOIO);
+ if (status) {
+ mutex_unlock(&serial_priv->urb_mutex);
+ return status;
+ }
+ }
+
+ mutex_unlock(&serial_priv->urb_mutex);
+
+ for (i = 0; i < serial->num_ports; i++) {
+ port = serial->port[i];
+ if (!tty_port_initialized(&port->port))
+ continue;
+
+ status = f81534_submit_writer(port, GFP_NOIO);
+ if (status) {
+ dev_err(&port->dev, "%s: submit failed\n", __func__);
+ ++error;
+ }
+ }
+
+ if (error)
+ return -EIO;
+
+ return 0;
+}
+
+static struct usb_serial_driver f81534_device = {
+ .driver = {
+ .name = "f81534",
+ },
+ .description = DRIVER_DESC,
+ .id_table = f81534_id_table,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ .open = f81534_open,
+ .close = f81534_close,
+ .write = f81534_write,
+ .tx_empty = f81534_tx_empty,
+ .calc_num_ports = f81534_calc_num_ports,
+ .port_probe = f81534_port_probe,
+ .port_remove = f81534_port_remove,
+ .break_ctl = f81534_break_ctl,
+ .dtr_rts = f81534_dtr_rts,
+ .process_read_urb = f81534_process_read_urb,
+ .get_serial = f81534_get_serial_info,
+ .tiocmget = f81534_tiocmget,
+ .tiocmset = f81534_tiocmset,
+ .write_bulk_callback = f81534_write_usb_callback,
+ .set_termios = f81534_set_termios,
+ .resume = f81534_resume,
+};
+
+static struct usb_serial_driver *const serial_drivers[] = {
+ &f81534_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, f81534_id_table);
+
+MODULE_DEVICE_TABLE(usb, f81534_id_table);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Peter Hong <Peter_Hong@fintek.com.tw>");
+MODULE_AUTHOR("Tom Tsai <Tom_Tsai@fintek.com.tw>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c
new file mode 100644
index 000000000..88dd32da8
--- /dev/null
+++ b/drivers/usb/serial/ftdi_sio.c
@@ -0,0 +1,2876 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * USB FTDI SIO driver
+ *
+ * Copyright (C) 2009 - 2013
+ * Johan Hovold (jhovold@gmail.com)
+ * Copyright (C) 1999 - 2001
+ * Greg Kroah-Hartman (greg@kroah.com)
+ * Bill Ryder (bryder@sgi.com)
+ * Copyright (C) 2002
+ * Kuba Ober (kuba@mareimbrium.org)
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ *
+ * See http://ftdi-usb-sio.sourceforge.net for up to date testing info
+ * and extra documentation
+ *
+ * Change entries from 2004 and earlier can be found in versions of this
+ * file in kernel versions prior to the 2.6.24 release.
+ *
+ */
+
+/* Bill Ryder - bryder@sgi.com - wrote the FTDI_SIO implementation */
+/* Thanx to FTDI for so kindly providing details of the protocol required */
+/* to talk to the device */
+/* Thanx to gkh and the rest of the usb dev group for all code I have
+ assimilated :-) */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/serial.h>
+#include <linux/gpio/driver.h>
+#include <linux/usb/serial.h>
+#include "ftdi_sio.h"
+#include "ftdi_sio_ids.h"
+
+#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Bill Ryder <bryder@sgi.com>, Kuba Ober <kuba@mareimbrium.org>, Andreas Mohr, Johan Hovold <jhovold@gmail.com>"
+#define DRIVER_DESC "USB FTDI Serial Converters Driver"
+
+enum ftdi_chip_type {
+ SIO,
+ FT232A,
+ FT232B,
+ FT2232C,
+ FT232R,
+ FT232H,
+ FT2232H,
+ FT4232H,
+ FT4232HA,
+ FT232HP,
+ FT233HP,
+ FT2232HP,
+ FT2233HP,
+ FT4232HP,
+ FT4233HP,
+ FTX,
+};
+
+struct ftdi_private {
+ enum ftdi_chip_type chip_type;
+ int baud_base; /* baud base clock for divisor setting */
+ int custom_divisor; /* custom_divisor kludge, this is for
+ baud_base (different from what goes to the
+ chip!) */
+ u16 last_set_data_value; /* the last data state set - needed for doing
+ * a break
+ */
+ int flags; /* some ASYNC_xxxx flags are supported */
+ unsigned long last_dtr_rts; /* saved modem control outputs */
+ char prev_status; /* Used for TIOCMIWAIT */
+ char transmit_empty; /* If transmitter is empty or not */
+ u16 channel; /* channel index, or 0 for legacy types */
+
+ speed_t force_baud; /* if non-zero, force the baud rate to
+ this value */
+ int force_rtscts; /* if non-zero, force RTS-CTS to always
+ be enabled */
+
+ unsigned int latency; /* latency setting in use */
+ unsigned short max_packet_size;
+ struct mutex cfg_lock; /* Avoid mess by parallel calls of config ioctl() and change_speed() */
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gc;
+ struct mutex gpio_lock; /* protects GPIO state */
+ bool gpio_registered; /* is the gpiochip in kernel registered */
+ bool gpio_used; /* true if the user requested a gpio */
+ u8 gpio_altfunc; /* which pins are in gpio mode */
+ u8 gpio_output; /* pin directions cache */
+ u8 gpio_value; /* pin value for outputs */
+#endif
+};
+
+struct ftdi_quirk {
+ int (*probe)(struct usb_serial *);
+ /* Special settings for probed ports. */
+ void (*port_probe)(struct ftdi_private *);
+};
+
+static int ftdi_jtag_probe(struct usb_serial *serial);
+static int ftdi_stmclite_probe(struct usb_serial *serial);
+static int ftdi_8u2232c_probe(struct usb_serial *serial);
+static void ftdi_usb_uirt_setup(struct ftdi_private *priv);
+static void ftdi_he_tira1_setup(struct ftdi_private *priv);
+
+static const struct ftdi_quirk ftdi_jtag_quirk = {
+ .probe = ftdi_jtag_probe,
+};
+
+static const struct ftdi_quirk ftdi_ndi_quirk = {
+};
+
+static const struct ftdi_quirk ftdi_usb_uirt_quirk = {
+ .port_probe = ftdi_usb_uirt_setup,
+};
+
+static const struct ftdi_quirk ftdi_he_tira1_quirk = {
+ .port_probe = ftdi_he_tira1_setup,
+};
+
+static const struct ftdi_quirk ftdi_stmclite_quirk = {
+ .probe = ftdi_stmclite_probe,
+};
+
+static const struct ftdi_quirk ftdi_8u2232c_quirk = {
+ .probe = ftdi_8u2232c_probe,
+};
+
+/*
+ * The 8U232AM has the same API as the sio except for:
+ * - it can support MUCH higher baudrates; up to:
+ * o 921600 for RS232 and 2000000 for RS422/485 at 48MHz
+ * o 230400 at 12MHz
+ * so .. 8U232AM's baudrate setting codes are different
+ * - it has a two byte status code.
+ * - it returns characters every 16ms (the FTDI does it every 40ms)
+ *
+ * the bcdDevice value is used to differentiate FT232BM and FT245BM from
+ * the earlier FT8U232AM and FT8U232BM. For now, include all known VID/PID
+ * combinations in both tables.
+ * FIXME: perhaps bcdDevice can also identify 12MHz FT8U232AM devices,
+ * but I don't know if those ever went into mass production. [Ian Abbott]
+ */
+
+
+
+/*
+ * Device ID not listed? Test it using
+ * /sys/bus/usb-serial/drivers/ftdi_sio/new_id and send a patch or report.
+ */
+static const struct usb_device_id id_table_combined[] = {
+ { USB_DEVICE(FTDI_VID, FTDI_BRICK_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ZEITCONTROL_TAGTRACE_MIFARE_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CTI_MINI_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CTI_NANO_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_AMC232_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CANUSB_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CANDAPTER_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_BM_ATOM_NANO_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_NXTCAM_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_EV3CON_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_0_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_3_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_4_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_5_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_6_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_7_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_USINT_CAT_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_USINT_WKEY_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_USINT_RS232_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ACTZWAVE_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IRTRANS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IPLUS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IPLUS2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_DMX4ALL) },
+ { USB_DEVICE(FTDI_VID, FTDI_SIO_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_8U232AM_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_8U232AM_ALT_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_232RL_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_8U2232C_PID) ,
+ .driver_info = (kernel_ulong_t)&ftdi_8u2232c_quirk },
+ { USB_DEVICE(FTDI_VID, FTDI_4232H_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_232H_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_FTX_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_FT2233HP_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_FT4233HP_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_FT2232HP_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_FT4232HP_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_FT233HP_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_FT232HP_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_FT4232HA_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MICRO_CHAMELEON_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_RELAIS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_SNIFFER_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_THROTTLE_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_GATEWAY_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_GBM_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_GBM_BOOST_PID) },
+ { USB_DEVICE(NEWPORT_VID, NEWPORT_AGILIS_PID) },
+ { USB_DEVICE(NEWPORT_VID, NEWPORT_CONEX_CC_PID) },
+ { USB_DEVICE(NEWPORT_VID, NEWPORT_CONEX_AGP_PID) },
+ { USB_DEVICE(INTERBIOMETRICS_VID, INTERBIOMETRICS_IOBOARD_PID) },
+ { USB_DEVICE(INTERBIOMETRICS_VID, INTERBIOMETRICS_MINI_IOBOARD_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SPROG_II) },
+ { USB_DEVICE(FTDI_VID, FTDI_TAGSYS_LP101_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TAGSYS_P200X_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_LENZ_LIUSB_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_632_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_634_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_547_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_633_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_631_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_635_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_640_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_642_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_DSS20_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_URBAN_0_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_URBAN_1_PID) },
+ { USB_DEVICE(FTDI_NF_RIC_VID, FTDI_NF_RIC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_VNHCPCUSB_D_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_0_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_3_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_4_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_5_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_6_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_R2000KU_TRUE_RNG) },
+ { USB_DEVICE(FTDI_VID, FTDI_VARDAAN_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_AUTO_M3_OP_COM_V2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0100_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0101_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0102_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0103_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0104_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0105_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0106_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0107_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0108_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0109_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0110_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0111_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0112_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0113_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0114_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0115_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0116_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0117_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0118_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0119_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0120_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0121_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0122_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0123_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0124_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0125_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0126_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0127_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0128_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0129_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0130_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0131_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0132_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0133_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0134_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0135_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0136_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0137_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0138_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0139_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0140_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0141_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0142_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0143_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0144_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0145_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0146_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0147_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0148_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0149_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0150_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0151_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0152_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0153_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0154_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0155_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0156_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0157_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0158_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0159_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0160_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0161_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0162_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0163_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0164_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0165_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0166_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0167_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0168_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0169_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0170_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0171_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0172_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0173_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0174_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0175_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0176_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0177_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0178_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0179_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0180_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0181_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0182_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0183_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0184_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0185_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0186_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0187_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0188_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0189_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0190_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0191_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0192_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0193_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0194_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0195_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0196_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0197_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0198_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0199_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A0_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A1_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A3_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A4_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A5_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A6_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A7_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A8_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A9_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AA_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AB_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AC_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AD_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AE_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AF_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B0_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B1_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B3_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B4_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B5_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B6_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B7_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B8_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B9_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BA_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BB_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BC_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BD_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BE_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BF_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C0_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C1_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C3_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C4_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C5_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C6_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C7_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C8_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C9_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CA_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CB_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CC_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CD_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CE_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CF_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D0_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D1_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D3_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D4_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D5_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D6_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D7_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D8_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D9_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DA_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DB_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DC_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DD_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DE_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DF_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E0_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E1_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E3_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E4_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E5_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E6_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E7_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E8_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E9_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EA_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EB_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EC_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01ED_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EE_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EF_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F0_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F1_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F3_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F4_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F5_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F6_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F7_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F8_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F9_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FA_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FB_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FC_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FD_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FE_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FF_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_4701_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9300_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9301_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9302_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9303_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9304_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9305_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9306_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9307_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9308_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9309_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_930A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_930B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_930C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_930D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_930E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_930F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9310_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9311_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9312_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9313_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9314_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9315_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9316_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9317_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9318_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9319_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_931A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_931B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_931C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_931D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_931E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_931F_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PERLE_ULTRAPORT_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PIEGROUP_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TNC_X_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_USBX_707_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2101_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2102_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2103_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2104_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2106_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2201_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2201_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2202_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2202_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2203_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2203_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_4_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_4_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_4_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_4_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_5_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_6_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_7_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_8_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_4_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_5_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_6_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_7_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_8_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_4_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_5_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_6_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_7_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_8_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_4_PID) },
+ { USB_DEVICE(IDTECH_VID, IDTECH_IDT1221U_PID) },
+ { USB_DEVICE(OCT_VID, OCT_US101_PID) },
+ { USB_DEVICE(OCT_VID, OCT_DK201_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_HE_TIRA1_PID),
+ .driver_info = (kernel_ulong_t)&ftdi_he_tira1_quirk },
+ { USB_DEVICE(FTDI_VID, FTDI_USB_UIRT_PID),
+ .driver_info = (kernel_ulong_t)&ftdi_usb_uirt_quirk },
+ { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_1) },
+ { USB_DEVICE(FTDI_VID, PROTEGO_R2X0) },
+ { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_3) },
+ { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_4) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E808_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E809_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80A_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80B_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80C_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80D_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80E_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80F_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E888_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E889_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88A_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88B_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88C_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88D_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88E_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88F_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UO100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UM100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UR100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_ALC8500_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PYRAMID_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_FHZ1000PC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_US485_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_PICPRO_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_PCMCIA_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_PK1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_RS232MON_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_APP70_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_PEDO_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_PROD_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TAVIR_STK500_PID) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_TIAO_UMPA_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_NT_ORIONLXM_PID, 1) },
+ { USB_DEVICE(FTDI_VID, FTDI_NT_ORIONLX_PLUS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_NT_ORION_IO_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_NT_ORIONMX_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SYNAPSE_SS200_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CUSTOMWARE_MINIPLEX_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CUSTOMWARE_MINIPLEX2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CUSTOMWARE_MINIPLEX2WI_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CUSTOMWARE_MINIPLEX3_PID) },
+ /*
+ * ELV devices:
+ */
+ { USB_DEVICE(FTDI_ELV_VID, FTDI_ELV_WS300_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_USR_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_MSM1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_KL100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_WS550_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_EC3000_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_WS888_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_TWS550_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_FEM_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_CLI7000_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_PPS7330_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_TFM100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UDF77_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UIO88_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UAD8_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UDA7_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_USI2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_T1100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_PCD200_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_ULA200_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_CSI8_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_EM1000DL_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_PCK100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_RFP500_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_FS20SIG_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UTP8_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_WS300PC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_WS444PC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_FHZ1300PC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_EM1010PC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_WS500_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_HS485_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UMS100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_TFD128_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_FM3RX_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_WS777_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PALMSENS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IVIUM_XSTAT_PID) },
+ { USB_DEVICE(FTDI_VID, LINX_SDMUSBQSS_PID) },
+ { USB_DEVICE(FTDI_VID, LINX_MASTERDEVEL2_PID) },
+ { USB_DEVICE(FTDI_VID, LINX_FUTURE_0_PID) },
+ { USB_DEVICE(FTDI_VID, LINX_FUTURE_1_PID) },
+ { USB_DEVICE(FTDI_VID, LINX_FUTURE_2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CCSICDU20_0_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CCSICDU40_1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CCSMACHX_2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CCSLOAD_N_GO_3_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CCSICDU64_4_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CCSPRIME8_5_PID) },
+ { USB_DEVICE(FTDI_VID, INSIDE_ACCESSO) },
+ { USB_DEVICE(INTREPID_VID, INTREPID_VALUECAN_PID) },
+ { USB_DEVICE(INTREPID_VID, INTREPID_NEOVI_PID) },
+ { USB_DEVICE(FALCOM_VID, FALCOM_TWIST_PID) },
+ { USB_DEVICE(FALCOM_VID, FALCOM_SAMBA_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SUUNTO_SPORTS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OCEANIC_PID) },
+ { USB_DEVICE(TTI_VID, TTI_QL355P_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_RM_CANVIEW_PID) },
+ { USB_DEVICE(ACTON_VID, ACTON_SPECTRAPRO_PID) },
+ { USB_DEVICE(CONTEC_VID, CONTEC_COM1USBH_PID) },
+ { USB_DEVICE(MITSUBISHI_VID, MITSUBISHI_FXUSB_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USOTL4_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USTL4_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USO9ML2_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USOPTL4_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USPTL4_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USO9ML2DR_2_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USO9ML2DR_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USOPTL4DR2_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USOPTL4DR_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_485USB9F_2W_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_485USB9F_4W_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_232USB9M_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_485USBTB_2W_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_485USBTB_4W_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_TTL5USB9M_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_TTL3USB9M_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_ZZ_PROG1_USB_PID) },
+ { USB_DEVICE(FTDI_VID, EVER_ECO_PRO_CDS) },
+ { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_3_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_0_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_1_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_2_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_3_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_4_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_5_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_6_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_7_PID) },
+ { USB_DEVICE(XSENS_VID, XSENS_AWINDA_DONGLE_PID) },
+ { USB_DEVICE(XSENS_VID, XSENS_AWINDA_STATION_PID) },
+ { USB_DEVICE(XSENS_VID, XSENS_CONVERTER_PID) },
+ { USB_DEVICE(XSENS_VID, XSENS_MTDEVBOARD_PID) },
+ { USB_DEVICE(XSENS_VID, XSENS_MTIUSBCONVERTER_PID) },
+ { USB_DEVICE(XSENS_VID, XSENS_MTW_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OMNI1509) },
+ { USB_DEVICE(MOBILITY_VID, MOBILITY_USB_SERIAL_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ACTIVE_ROBOTS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_KW_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_YS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_Y6_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_Y8_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_IC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_DB9_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_RS232_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_Y9_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TERATRONIK_VCP_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TERATRONIK_D2XX_PID) },
+ { USB_DEVICE(EVOLUTION_VID, EVOLUTION_ER1_PID) },
+ { USB_DEVICE(EVOLUTION_VID, EVO_HYBRID_PID) },
+ { USB_DEVICE(EVOLUTION_VID, EVO_RCM4_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ARTEMIS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16C_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16HR_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16HRC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16IC_PID) },
+ { USB_DEVICE(KOBIL_VID, KOBIL_CONV_B1_PID) },
+ { USB_DEVICE(KOBIL_VID, KOBIL_CONV_KAAN_PID) },
+ { USB_DEVICE(POSIFLEX_VID, POSIFLEX_PP7000_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TTUSB_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ECLO_COM_1WIRE_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_WESTREX_MODEL_777_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_WESTREX_MODEL_8900F_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PCDJ_DAC2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_RRCIRKITS_LOCOBUFFER_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ASK_RDR400_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_NZR_SEM_USB_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_1_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_OPC_U_UC_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2C1_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2C2_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2D_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2VT_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2VR_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP4KVT_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP4KVR_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2KVT_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2KVR_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ACG_HFDUAL_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_YEI_SERVOCENTER31_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_THORLABS_PID) },
+ { USB_DEVICE(TESTO_VID, TESTO_1_PID) },
+ { USB_DEVICE(TESTO_VID, TESTO_3_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GAMMA_SCOUT_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TACTRIX_OPENPORT_13M_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TACTRIX_OPENPORT_13S_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TACTRIX_OPENPORT_13U_PID) },
+ { USB_DEVICE(ELEKTOR_VID, ELEKTOR_FT323R_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_NDI_HUC_PID),
+ .driver_info = (kernel_ulong_t)&ftdi_ndi_quirk },
+ { USB_DEVICE(FTDI_VID, FTDI_NDI_SPECTRA_SCU_PID),
+ .driver_info = (kernel_ulong_t)&ftdi_ndi_quirk },
+ { USB_DEVICE(FTDI_VID, FTDI_NDI_FUTURE_2_PID),
+ .driver_info = (kernel_ulong_t)&ftdi_ndi_quirk },
+ { USB_DEVICE(FTDI_VID, FTDI_NDI_FUTURE_3_PID),
+ .driver_info = (kernel_ulong_t)&ftdi_ndi_quirk },
+ { USB_DEVICE(FTDI_VID, FTDI_NDI_AURORA_SCU_PID),
+ .driver_info = (kernel_ulong_t)&ftdi_ndi_quirk },
+ { USB_DEVICE(FTDI_NDI_VID, FTDI_NDI_EMGUIDE_GEMINI_PID),
+ .driver_info = (kernel_ulong_t)&ftdi_ndi_quirk },
+ { USB_DEVICE(TELLDUS_VID, TELLDUS_TELLSTICK_PID) },
+ { USB_DEVICE(NOVITUS_VID, NOVITUS_BONO_E_PID) },
+ { USB_DEVICE(FTDI_VID, RTSYSTEMS_USB_VX8_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_S03_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_59_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_57A_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_57B_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_29A_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_29B_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_29F_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_62B_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_S01_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_63_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_29C_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_81B_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_82B_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_K5D_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_K4Y_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_K5G_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_S05_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_60_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_61_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_62_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_63B_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_64_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_65_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_92_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_92D_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_W5R_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_A5R_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_PW1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MAXSTREAM_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PHI_FISCO_PID) },
+ { USB_DEVICE(TML_VID, TML_USB_SERIAL_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELSTER_UNICOM_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PROPOX_JTAGCABLEII_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PROPOX_ISPCABLEIII_PID) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, CYBER_CORTEX_AV_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(OLIMEX_VID, OLIMEX_ARM_USB_OCD_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(OLIMEX_VID, OLIMEX_ARM_USB_OCD_H_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(OLIMEX_VID, OLIMEX_ARM_USB_TINY_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(OLIMEX_VID, OLIMEX_ARM_USB_TINY_H_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(FIC_VID, FIC_NEO1973_DEBUG_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_OOCDLINK_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, LMI_LM3S_DEVEL_BOARD_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, LMI_LM3S_EVAL_BOARD_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, LMI_LM3S_ICDI_BOARD_PID, 1) },
+ { USB_DEVICE(FTDI_VID, FTDI_AXE027_PID) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_TURTELIZER_PID, 1) },
+ { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID_USB60F) },
+ { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID_SCU18) },
+ { USB_DEVICE(FTDI_VID, FTDI_REU_TINY_PID) },
+
+ /* Papouch devices based on FTDI chip */
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_AP485_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB422_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485_2_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_AP485_2_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB422_2_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485S_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485C_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_LEC_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB232_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_TMU_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_IRAMP_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_DRAK5_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO8x8_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO4x4_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO2x2_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO10x1_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO30x3_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO60x3_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO2x16_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO3x32_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_DRAK6_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_UPSUSB_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_MU_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SIMUKEY_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_AD4USB_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_GMUX_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_GMSR_PID) },
+
+ { USB_DEVICE(FTDI_VID, FTDI_DOMINTELL_DGQG_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_DOMINTELL_DUSB_PID) },
+ { USB_DEVICE(ALTI2_VID, ALTI2_N3_PID) },
+ { USB_DEVICE(FTDI_VID, DIEBOLD_BCS_SE923_PID) },
+ { USB_DEVICE(ATMEL_VID, STK541_PID) },
+ { USB_DEVICE(DE_VID, STB_PID) },
+ { USB_DEVICE(DE_VID, WHT_PID) },
+ { USB_DEVICE_INTERFACE_NUMBER(ADI_VID, ADI_GNICE_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(ADI_VID, ADI_GNICEPLUS_PID, 1) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MICROCHIP_VID, MICROCHIP_USB_BOARD_PID,
+ USB_CLASS_VENDOR_SPEC,
+ USB_SUBCLASS_VENDOR_SPEC, 0x00) },
+ { USB_DEVICE_INTERFACE_NUMBER(ACTEL_VID, MICROSEMI_ARROW_SF2PLUS_BOARD_PID, 2) },
+ { USB_DEVICE(JETI_VID, JETI_SPC1201_PID) },
+ { USB_DEVICE_INTERFACE_NUMBER(MARVELL_VID, MARVELL_SHEEVAPLUG_PID, 1) },
+ { USB_DEVICE(LARSENBRUSGAARD_VID, LB_ALTITRACK_PID) },
+ { USB_DEVICE(GN_OTOMETRICS_VID, AURICAL_USB_PID) },
+ { USB_DEVICE(FTDI_VID, PI_C865_PID) },
+ { USB_DEVICE(FTDI_VID, PI_C857_PID) },
+ { USB_DEVICE(PI_VID, PI_C866_PID) },
+ { USB_DEVICE(PI_VID, PI_C663_PID) },
+ { USB_DEVICE(PI_VID, PI_C725_PID) },
+ { USB_DEVICE(PI_VID, PI_E517_PID) },
+ { USB_DEVICE(PI_VID, PI_C863_PID) },
+ { USB_DEVICE(PI_VID, PI_E861_PID) },
+ { USB_DEVICE(PI_VID, PI_C867_PID) },
+ { USB_DEVICE(PI_VID, PI_E609_PID) },
+ { USB_DEVICE(PI_VID, PI_E709_PID) },
+ { USB_DEVICE(PI_VID, PI_100F_PID) },
+ { USB_DEVICE(PI_VID, PI_1011_PID) },
+ { USB_DEVICE(PI_VID, PI_1012_PID) },
+ { USB_DEVICE(PI_VID, PI_1013_PID) },
+ { USB_DEVICE(PI_VID, PI_1014_PID) },
+ { USB_DEVICE(PI_VID, PI_1015_PID) },
+ { USB_DEVICE(PI_VID, PI_1016_PID) },
+ { USB_DEVICE(KONDO_VID, KONDO_USB_SERIAL_PID) },
+ { USB_DEVICE(BAYER_VID, BAYER_CONTOUR_CABLE_PID) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, MARVELL_OPENRD_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, TI_XDS100V2_PID, 1) },
+ { USB_DEVICE(FTDI_VID, HAMEG_HO820_PID) },
+ { USB_DEVICE(FTDI_VID, HAMEG_HO720_PID) },
+ { USB_DEVICE(FTDI_VID, HAMEG_HO730_PID) },
+ { USB_DEVICE(FTDI_VID, HAMEG_HO870_PID) },
+ { USB_DEVICE(FTDI_VID, MJSG_GENERIC_PID) },
+ { USB_DEVICE(FTDI_VID, MJSG_SR_RADIO_PID) },
+ { USB_DEVICE(FTDI_VID, MJSG_HD_RADIO_PID) },
+ { USB_DEVICE(FTDI_VID, MJSG_XM_RADIO_PID) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, XVERVE_SIGNALYZER_ST_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, XVERVE_SIGNALYZER_SLITE_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, XVERVE_SIGNALYZER_SH2_PID, 1) },
+ { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SH4_PID),
+ .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk },
+ { USB_DEVICE(FTDI_VID, SEGWAY_RMP200_PID) },
+ { USB_DEVICE(FTDI_VID, ACCESIO_COM4SM_PID) },
+ { USB_DEVICE_INTERFACE_NUMBER(IONICS_VID, IONICS_PLUGCOMPUTER_PID, 1) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_24_MASTER_WING_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_PC_WING_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_USB_DMX_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MIDI_TIMECODE_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MINI_WING_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MAXI_WING_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MEDIA_WING_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_WING_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_LOGBOOKML_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_LS_LOGBOOK_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_HS_LOGBOOK_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CINTERION_MC55I_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_FHE_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_DOTEC_PID) },
+ { USB_DEVICE_INTERFACE_NUMBER(QIHARDWARE_VID, MILKYMISTONE_JTAGSERIAL_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(ST_VID, ST_STMCLT_2232_PID, 1) },
+ { USB_DEVICE(ST_VID, ST_STMCLT_4232_PID),
+ .driver_info = (kernel_ulong_t)&ftdi_stmclite_quirk },
+ { USB_DEVICE(FTDI_VID, FTDI_RF_R106) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_DISTORTEC_JTAG_LOCK_PICK_PID, 1) },
+ { USB_DEVICE(FTDI_VID, FTDI_LUMEL_PD12_PID) },
+ /* Crucible Devices */
+ { USB_DEVICE(FTDI_VID, FTDI_CT_COMET_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_Z3X_PID) },
+ /* Cressi Devices */
+ { USB_DEVICE(FTDI_VID, FTDI_CRESSI_PID) },
+ /* Brainboxes Devices */
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_VX_001_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_VX_012_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_VX_023_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_VX_034_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_101_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_159_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_1_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_2_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_3_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_4_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_5_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_6_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_7_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_8_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_235_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_257_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_279_1_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_279_2_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_279_3_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_279_4_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_313_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_320_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_324_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_346_1_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_346_2_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_357_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_606_1_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_606_2_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_606_3_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_701_1_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_701_2_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_842_1_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_842_2_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_842_3_PID) },
+ { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_842_4_PID) },
+ /* ekey Devices */
+ { USB_DEVICE(FTDI_VID, FTDI_EKEY_CONV_USB_PID) },
+ /* Infineon Devices */
+ { USB_DEVICE_INTERFACE_NUMBER(INFINEON_VID, INFINEON_TRIBOARD_TC1798_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(INFINEON_VID, INFINEON_TRIBOARD_TC2X7_PID, 1) },
+ /* GE Healthcare devices */
+ { USB_DEVICE(GE_HEALTHCARE_VID, GE_HEALTHCARE_NEMO_TRACKER_PID) },
+ /* Active Research (Actisense) devices */
+ { USB_DEVICE(FTDI_VID, ACTISENSE_NDC_PID) },
+ { USB_DEVICE(FTDI_VID, ACTISENSE_USG_PID) },
+ { USB_DEVICE(FTDI_VID, ACTISENSE_NGT_PID) },
+ { USB_DEVICE(FTDI_VID, ACTISENSE_NGW_PID) },
+ { USB_DEVICE(FTDI_VID, ACTISENSE_UID_PID) },
+ { USB_DEVICE(FTDI_VID, ACTISENSE_USA_PID) },
+ { USB_DEVICE(FTDI_VID, ACTISENSE_NGX_PID) },
+ { USB_DEVICE(FTDI_VID, ACTISENSE_D9AF_PID) },
+ { USB_DEVICE(FTDI_VID, CHETCO_SEAGAUGE_PID) },
+ { USB_DEVICE(FTDI_VID, CHETCO_SEASWITCH_PID) },
+ { USB_DEVICE(FTDI_VID, CHETCO_SEASMART_NMEA2000_PID) },
+ { USB_DEVICE(FTDI_VID, CHETCO_SEASMART_ETHERNET_PID) },
+ { USB_DEVICE(FTDI_VID, CHETCO_SEASMART_WIFI_PID) },
+ { USB_DEVICE(FTDI_VID, CHETCO_SEASMART_DISPLAY_PID) },
+ { USB_DEVICE(FTDI_VID, CHETCO_SEASMART_LITE_PID) },
+ { USB_DEVICE(FTDI_VID, CHETCO_SEASMART_ANALOG_PID) },
+ /* Belimo Automation devices */
+ { USB_DEVICE(FTDI_VID, BELIMO_ZTH_PID) },
+ { USB_DEVICE(FTDI_VID, BELIMO_ZIP_PID) },
+ /* ICP DAS I-756xU devices */
+ { USB_DEVICE(ICPDAS_VID, ICPDAS_I7560U_PID) },
+ { USB_DEVICE(ICPDAS_VID, ICPDAS_I7561U_PID) },
+ { USB_DEVICE(ICPDAS_VID, ICPDAS_I7563U_PID) },
+ { USB_DEVICE(WICED_VID, WICED_USB20706V2_PID) },
+ { USB_DEVICE_INTERFACE_NUMBER(TI_VID, TI_CC3200_LAUNCHPAD_PID, 1) },
+ { USB_DEVICE(CYPRESS_VID, CYPRESS_WICED_BT_USB_PID) },
+ { USB_DEVICE(CYPRESS_VID, CYPRESS_WICED_WL_USB_PID) },
+ { USB_DEVICE(AIRBUS_DS_VID, AIRBUS_DS_P8GR) },
+ /* EZPrototypes devices */
+ { USB_DEVICE(EZPROTOTYPES_VID, HJELMSLUND_USB485_ISO_PID) },
+ { USB_DEVICE_INTERFACE_NUMBER(UNJO_VID, UNJO_ISODEBUG_V1_PID, 1) },
+ /* Sienna devices */
+ { USB_DEVICE(FTDI_VID, FTDI_SIENNA_PID) },
+ { USB_DEVICE(ECHELON_VID, ECHELON_U20_PID) },
+ /* IDS GmbH devices */
+ { USB_DEVICE(IDS_VID, IDS_SI31A_PID) },
+ { USB_DEVICE(IDS_VID, IDS_CM31A_PID) },
+ /* Omron devices */
+ { USB_DEVICE(OMRON_VID, OMRON_CS1W_CIF31_PID) },
+ /* U-Blox devices */
+ { USB_DEVICE(UBLOX_VID, UBLOX_C099F9P_ZED_PID) },
+ { USB_DEVICE(UBLOX_VID, UBLOX_C099F9P_ODIN_PID) },
+ { USB_DEVICE_INTERFACE_NUMBER(UBLOX_VID, UBLOX_EVK_M101_PID, 2) },
+ /* FreeCalypso USB adapters */
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_FALCONIA_JTAG_BUF_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_FALCONIA_JTAG_UNBUF_PID, 1) },
+ /* GMC devices */
+ { USB_DEVICE(GMC_VID, GMC_Z216C_PID) },
+ /* Altera USB Blaster 3 */
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_6022_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_6025_PID, 2) },
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_6026_PID, 2) },
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_6026_PID, 3) },
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_6029_PID, 2) },
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602A_PID, 2) },
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602A_PID, 3) },
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602C_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602D_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602D_PID, 2) },
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602E_PID, 1) },
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602E_PID, 2) },
+ { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602E_PID, 3) },
+ /* Abacus Electrics */
+ { USB_DEVICE(FTDI_VID, ABACUS_OPTICAL_PROBE_PID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table_combined);
+
+static const char *ftdi_chip_name[] = {
+ [SIO] = "SIO", /* the serial part of FT8U100AX */
+ [FT232A] = "FT232A",
+ [FT232B] = "FT232B",
+ [FT2232C] = "FT2232C/D",
+ [FT232R] = "FT232R",
+ [FT232H] = "FT232H",
+ [FT2232H] = "FT2232H",
+ [FT4232H] = "FT4232H",
+ [FT4232HA] = "FT4232HA",
+ [FT232HP] = "FT232HP",
+ [FT233HP] = "FT233HP",
+ [FT2232HP] = "FT2232HP",
+ [FT2233HP] = "FT2233HP",
+ [FT4232HP] = "FT4232HP",
+ [FT4233HP] = "FT4233HP",
+ [FTX] = "FT-X",
+};
+
+
+/* Used for TIOCMIWAIT */
+#define FTDI_STATUS_B0_MASK (FTDI_RS0_CTS | FTDI_RS0_DSR | FTDI_RS0_RI | FTDI_RS0_RLSD)
+#define FTDI_STATUS_B1_MASK (FTDI_RS_BI)
+/* End TIOCMIWAIT */
+
+static void ftdi_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+static int ftdi_get_modem_status(struct usb_serial_port *port,
+ unsigned char status[2]);
+
+#define WDR_TIMEOUT 5000 /* default urb timeout */
+#define WDR_SHORT_TIMEOUT 1000 /* shorter urb timeout */
+
+/*
+ * ***************************************************************************
+ * Utility functions
+ * ***************************************************************************
+ */
+
+static unsigned short int ftdi_232am_baud_base_to_divisor(int baud, int base)
+{
+ unsigned short int divisor;
+ /* divisor shifted 3 bits to the left */
+ int divisor3 = DIV_ROUND_CLOSEST(base, 2 * baud);
+ if ((divisor3 & 0x7) == 7)
+ divisor3++; /* round x.7/8 up to x+1 */
+ divisor = divisor3 >> 3;
+ divisor3 &= 0x7;
+ if (divisor3 == 1)
+ divisor |= 0xc000; /* +0.125 */
+ else if (divisor3 >= 4)
+ divisor |= 0x4000; /* +0.5 */
+ else if (divisor3 != 0)
+ divisor |= 0x8000; /* +0.25 */
+ else if (divisor == 1)
+ divisor = 0; /* special case for maximum baud rate */
+ return divisor;
+}
+
+static unsigned short int ftdi_232am_baud_to_divisor(int baud)
+{
+ return ftdi_232am_baud_base_to_divisor(baud, 48000000);
+}
+
+static u32 ftdi_232bm_baud_base_to_divisor(int baud, int base)
+{
+ static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 };
+ u32 divisor;
+ /* divisor shifted 3 bits to the left */
+ int divisor3 = DIV_ROUND_CLOSEST(base, 2 * baud);
+ divisor = divisor3 >> 3;
+ divisor |= (u32)divfrac[divisor3 & 0x7] << 14;
+ /* Deal with special cases for highest baud rates. */
+ if (divisor == 1) /* 1.0 */
+ divisor = 0;
+ else if (divisor == 0x4001) /* 1.5 */
+ divisor = 1;
+ return divisor;
+}
+
+static u32 ftdi_232bm_baud_to_divisor(int baud)
+{
+ return ftdi_232bm_baud_base_to_divisor(baud, 48000000);
+}
+
+static u32 ftdi_2232h_baud_base_to_divisor(int baud, int base)
+{
+ static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 };
+ u32 divisor;
+ int divisor3;
+
+ /* hi-speed baud rate is 10-bit sampling instead of 16-bit */
+ divisor3 = DIV_ROUND_CLOSEST(8 * base, 10 * baud);
+
+ divisor = divisor3 >> 3;
+ divisor |= (u32)divfrac[divisor3 & 0x7] << 14;
+ /* Deal with special cases for highest baud rates. */
+ if (divisor == 1) /* 1.0 */
+ divisor = 0;
+ else if (divisor == 0x4001) /* 1.5 */
+ divisor = 1;
+ /*
+ * Set this bit to turn off a divide by 2.5 on baud rate generator
+ * This enables baud rates up to 12Mbaud but cannot reach below 1200
+ * baud with this bit set
+ */
+ divisor |= 0x00020000;
+ return divisor;
+}
+
+static u32 ftdi_2232h_baud_to_divisor(int baud)
+{
+ return ftdi_2232h_baud_base_to_divisor(baud, 120000000);
+}
+
+#define set_mctrl(port, set) update_mctrl((port), (set), 0)
+#define clear_mctrl(port, clear) update_mctrl((port), 0, (clear))
+
+static int update_mctrl(struct usb_serial_port *port, unsigned int set,
+ unsigned int clear)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct device *dev = &port->dev;
+ unsigned value;
+ int rv;
+
+ if (((set | clear) & (TIOCM_DTR | TIOCM_RTS)) == 0) {
+ dev_dbg(dev, "%s - DTR|RTS not being set|cleared\n", __func__);
+ return 0; /* no change */
+ }
+
+ clear &= ~set; /* 'set' takes precedence over 'clear' */
+ value = 0;
+ if (clear & TIOCM_DTR)
+ value |= FTDI_SIO_SET_DTR_LOW;
+ if (clear & TIOCM_RTS)
+ value |= FTDI_SIO_SET_RTS_LOW;
+ if (set & TIOCM_DTR)
+ value |= FTDI_SIO_SET_DTR_HIGH;
+ if (set & TIOCM_RTS)
+ value |= FTDI_SIO_SET_RTS_HIGH;
+ rv = usb_control_msg(port->serial->dev,
+ usb_sndctrlpipe(port->serial->dev, 0),
+ FTDI_SIO_SET_MODEM_CTRL_REQUEST,
+ FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE,
+ value, priv->channel,
+ NULL, 0, WDR_TIMEOUT);
+ if (rv < 0) {
+ dev_dbg(dev, "%s Error from MODEM_CTRL urb: DTR %s, RTS %s\n",
+ __func__,
+ (set & TIOCM_DTR) ? "HIGH" : (clear & TIOCM_DTR) ? "LOW" : "unchanged",
+ (set & TIOCM_RTS) ? "HIGH" : (clear & TIOCM_RTS) ? "LOW" : "unchanged");
+ rv = usb_translate_errors(rv);
+ } else {
+ dev_dbg(dev, "%s - DTR %s, RTS %s\n", __func__,
+ (set & TIOCM_DTR) ? "HIGH" : (clear & TIOCM_DTR) ? "LOW" : "unchanged",
+ (set & TIOCM_RTS) ? "HIGH" : (clear & TIOCM_RTS) ? "LOW" : "unchanged");
+ /* FIXME: locking on last_dtr_rts */
+ priv->last_dtr_rts = (priv->last_dtr_rts & ~clear) | set;
+ }
+ return rv;
+}
+
+
+static u32 get_ftdi_divisor(struct tty_struct *tty,
+ struct usb_serial_port *port)
+{
+ const struct ftdi_quirk *quirk = usb_get_serial_data(port->serial);
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct device *dev = &port->dev;
+ u32 div_value = 0;
+ int div_okay = 1;
+ int baud;
+
+ baud = tty_get_baud_rate(tty);
+ dev_dbg(dev, "%s - tty_get_baud_rate reports speed %d\n", __func__, baud);
+
+ /*
+ * Observe deprecated async-compatible custom_divisor hack, update
+ * baudrate if needed.
+ */
+ if (baud == 38400 &&
+ ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) &&
+ (priv->custom_divisor)) {
+ baud = priv->baud_base / priv->custom_divisor;
+ dev_dbg(dev, "%s - custom divisor %d sets baud rate to %d\n",
+ __func__, priv->custom_divisor, baud);
+ }
+
+ if (!baud)
+ baud = 9600;
+ switch (priv->chip_type) {
+ case SIO:
+ switch (baud) {
+ case 300: div_value = ftdi_sio_b300; break;
+ case 600: div_value = ftdi_sio_b600; break;
+ case 1200: div_value = ftdi_sio_b1200; break;
+ case 2400: div_value = ftdi_sio_b2400; break;
+ case 4800: div_value = ftdi_sio_b4800; break;
+ case 9600: div_value = ftdi_sio_b9600; break;
+ case 19200: div_value = ftdi_sio_b19200; break;
+ case 38400: div_value = ftdi_sio_b38400; break;
+ case 57600: div_value = ftdi_sio_b57600; break;
+ case 115200: div_value = ftdi_sio_b115200; break;
+ default:
+ dev_dbg(dev, "%s - Baudrate (%d) requested is not supported\n",
+ __func__, baud);
+ div_value = ftdi_sio_b9600;
+ baud = 9600;
+ div_okay = 0;
+ }
+ break;
+ case FT232A:
+ if (baud <= 3000000) {
+ div_value = ftdi_232am_baud_to_divisor(baud);
+ } else {
+ dev_dbg(dev, "%s - Baud rate too high!\n", __func__);
+ baud = 9600;
+ div_value = ftdi_232am_baud_to_divisor(9600);
+ div_okay = 0;
+ }
+ break;
+ case FT232B:
+ case FT2232C:
+ case FT232R:
+ case FTX:
+ if (baud <= 3000000) {
+ if (quirk == &ftdi_ndi_quirk && baud == 19200)
+ baud = 1200000;
+ div_value = ftdi_232bm_baud_to_divisor(baud);
+ } else {
+ dev_dbg(dev, "%s - Baud rate too high!\n", __func__);
+ div_value = ftdi_232bm_baud_to_divisor(9600);
+ div_okay = 0;
+ baud = 9600;
+ }
+ break;
+ default:
+ if ((baud <= 12000000) && (baud >= 1200)) {
+ div_value = ftdi_2232h_baud_to_divisor(baud);
+ } else if (baud < 1200) {
+ div_value = ftdi_232bm_baud_to_divisor(baud);
+ } else {
+ dev_dbg(dev, "%s - Baud rate too high!\n", __func__);
+ div_value = ftdi_232bm_baud_to_divisor(9600);
+ div_okay = 0;
+ baud = 9600;
+ }
+ break;
+ }
+
+ if (div_okay) {
+ dev_dbg(dev, "%s - Baud rate set to %d (divisor 0x%lX) on chip %s\n",
+ __func__, baud, (unsigned long)div_value,
+ ftdi_chip_name[priv->chip_type]);
+ }
+
+ tty_encode_baud_rate(tty, baud, baud);
+ return div_value;
+}
+
+static int change_speed(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ u16 value;
+ u16 index;
+ u32 index_value;
+ int rv;
+
+ index_value = get_ftdi_divisor(tty, port);
+ value = (u16)index_value;
+ index = (u16)(index_value >> 16);
+ if (priv->channel)
+ index = (u16)((index << 8) | priv->channel);
+
+ rv = usb_control_msg(port->serial->dev,
+ usb_sndctrlpipe(port->serial->dev, 0),
+ FTDI_SIO_SET_BAUDRATE_REQUEST,
+ FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE,
+ value, index,
+ NULL, 0, WDR_SHORT_TIMEOUT);
+ return rv;
+}
+
+static int write_latency_timer(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct usb_device *udev = port->serial->dev;
+ int rv;
+ int l = priv->latency;
+
+ if (priv->chip_type == SIO || priv->chip_type == FT232A)
+ return -EINVAL;
+
+ if (priv->flags & ASYNC_LOW_LATENCY)
+ l = 1;
+
+ dev_dbg(&port->dev, "%s: setting latency timer = %i\n", __func__, l);
+
+ rv = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ FTDI_SIO_SET_LATENCY_TIMER_REQUEST,
+ FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE,
+ l, priv->channel,
+ NULL, 0, WDR_TIMEOUT);
+ if (rv < 0)
+ dev_err(&port->dev, "Unable to write latency timer: %i\n", rv);
+ return rv;
+}
+
+static int _read_latency_timer(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct usb_device *udev = port->serial->dev;
+ u8 buf;
+ int rv;
+
+ rv = usb_control_msg_recv(udev, 0, FTDI_SIO_GET_LATENCY_TIMER_REQUEST,
+ FTDI_SIO_GET_LATENCY_TIMER_REQUEST_TYPE, 0,
+ priv->channel, &buf, 1, WDR_TIMEOUT,
+ GFP_KERNEL);
+ if (rv == 0)
+ rv = buf;
+
+ return rv;
+}
+
+static int read_latency_timer(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ int rv;
+
+ if (priv->chip_type == SIO || priv->chip_type == FT232A)
+ return -EINVAL;
+
+ rv = _read_latency_timer(port);
+ if (rv < 0) {
+ dev_err(&port->dev, "Unable to read latency timer: %i\n", rv);
+ return rv;
+ }
+
+ priv->latency = rv;
+
+ return 0;
+}
+
+static void get_serial_info(struct tty_struct *tty, struct serial_struct *ss)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+
+ mutex_lock(&priv->cfg_lock);
+ ss->flags = priv->flags;
+ ss->baud_base = priv->baud_base;
+ ss->custom_divisor = priv->custom_divisor;
+ mutex_unlock(&priv->cfg_lock);
+}
+
+static int set_serial_info(struct tty_struct *tty, struct serial_struct *ss)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ int old_flags, old_divisor;
+
+ mutex_lock(&priv->cfg_lock);
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((ss->flags ^ priv->flags) & ~ASYNC_USR_MASK) {
+ mutex_unlock(&priv->cfg_lock);
+ return -EPERM;
+ }
+ }
+
+ old_flags = priv->flags;
+ old_divisor = priv->custom_divisor;
+
+ priv->flags = ss->flags & ASYNC_FLAGS;
+ priv->custom_divisor = ss->custom_divisor;
+
+ write_latency_timer(port);
+
+ if ((priv->flags ^ old_flags) & ASYNC_SPD_MASK ||
+ ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST &&
+ priv->custom_divisor != old_divisor)) {
+
+ /* warn about deprecation unless clearing */
+ if (priv->flags & ASYNC_SPD_MASK)
+ dev_warn_ratelimited(&port->dev, "use of SPD flags is deprecated\n");
+
+ change_speed(tty, port);
+ }
+ mutex_unlock(&priv->cfg_lock);
+ return 0;
+}
+
+static int get_lsr_info(struct usb_serial_port *port,
+ unsigned int __user *retinfo)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ unsigned int result = 0;
+
+ if (priv->transmit_empty)
+ result = TIOCSER_TEMT;
+
+ if (copy_to_user(retinfo, &result, sizeof(unsigned int)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ftdi_determine_type(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ struct usb_device *udev = serial->dev;
+ unsigned int version, ifnum;
+
+ version = le16_to_cpu(udev->descriptor.bcdDevice);
+ ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber;
+
+ /* Assume Hi-Speed type */
+ priv->baud_base = 120000000 / 2;
+ priv->channel = CHANNEL_A + ifnum;
+
+ switch (version) {
+ case 0x200:
+ priv->chip_type = FT232A;
+ priv->baud_base = 48000000 / 2;
+ priv->channel = 0;
+ /*
+ * FT232B devices have a bug where bcdDevice gets set to 0x200
+ * when iSerialNumber is 0. Assume it is an FT232B in case the
+ * latency timer is readable.
+ */
+ if (udev->descriptor.iSerialNumber == 0 &&
+ _read_latency_timer(port) >= 0) {
+ priv->chip_type = FT232B;
+ }
+ break;
+ case 0x400:
+ priv->chip_type = FT232B;
+ priv->baud_base = 48000000 / 2;
+ priv->channel = 0;
+ break;
+ case 0x500:
+ priv->chip_type = FT2232C;
+ priv->baud_base = 48000000 / 2;
+ break;
+ case 0x600:
+ priv->chip_type = FT232R;
+ priv->baud_base = 48000000 / 2;
+ priv->channel = 0;
+ break;
+ case 0x700:
+ priv->chip_type = FT2232H;
+ break;
+ case 0x800:
+ priv->chip_type = FT4232H;
+ break;
+ case 0x900:
+ priv->chip_type = FT232H;
+ break;
+ case 0x1000:
+ priv->chip_type = FTX;
+ priv->baud_base = 48000000 / 2;
+ break;
+ case 0x2800:
+ priv->chip_type = FT2233HP;
+ break;
+ case 0x2900:
+ priv->chip_type = FT4233HP;
+ break;
+ case 0x3000:
+ priv->chip_type = FT2232HP;
+ break;
+ case 0x3100:
+ priv->chip_type = FT4232HP;
+ break;
+ case 0x3200:
+ priv->chip_type = FT233HP;
+ break;
+ case 0x3300:
+ priv->chip_type = FT232HP;
+ break;
+ case 0x3600:
+ priv->chip_type = FT4232HA;
+ break;
+ default:
+ if (version < 0x200) {
+ priv->chip_type = SIO;
+ priv->baud_base = 12000000 / 16;
+ priv->channel = 0;
+ } else {
+ dev_err(&port->dev, "unknown device type: 0x%02x\n", version);
+ return -ENODEV;
+ }
+ }
+
+ dev_info(&udev->dev, "Detected %s\n", ftdi_chip_name[priv->chip_type]);
+
+ return 0;
+}
+
+
+/*
+ * Determine the maximum packet size for the device. This depends on the chip
+ * type and the USB host capabilities. The value should be obtained from the
+ * device descriptor as the chip will use the appropriate values for the host.
+ */
+static void ftdi_set_max_packet_size(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct usb_interface *interface = port->serial->interface;
+ struct usb_endpoint_descriptor *ep_desc;
+ unsigned num_endpoints;
+ unsigned i;
+
+ num_endpoints = interface->cur_altsetting->desc.bNumEndpoints;
+ if (!num_endpoints)
+ return;
+
+ /*
+ * NOTE: Some customers have programmed FT232R/FT245R devices
+ * with an endpoint size of 0 - not good. In this case, we
+ * want to override the endpoint descriptor setting and use a
+ * value of 64 for wMaxPacketSize.
+ */
+ for (i = 0; i < num_endpoints; i++) {
+ ep_desc = &interface->cur_altsetting->endpoint[i].desc;
+ if (!ep_desc->wMaxPacketSize) {
+ ep_desc->wMaxPacketSize = cpu_to_le16(0x40);
+ dev_warn(&port->dev, "Overriding wMaxPacketSize on endpoint %d\n",
+ usb_endpoint_num(ep_desc));
+ }
+ }
+
+ /* Set max packet size based on last descriptor. */
+ priv->max_packet_size = usb_endpoint_maxp(ep_desc);
+}
+
+
+/*
+ * ***************************************************************************
+ * Sysfs Attribute
+ * ***************************************************************************
+ */
+
+static ssize_t latency_timer_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_serial_port *port = to_usb_serial_port(dev);
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ if (priv->flags & ASYNC_LOW_LATENCY)
+ return sprintf(buf, "1\n");
+ else
+ return sprintf(buf, "%u\n", priv->latency);
+}
+
+/* Write a new value of the latency timer, in units of milliseconds. */
+static ssize_t latency_timer_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *valbuf, size_t count)
+{
+ struct usb_serial_port *port = to_usb_serial_port(dev);
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ u8 v;
+ int rv;
+
+ if (kstrtou8(valbuf, 10, &v))
+ return -EINVAL;
+
+ priv->latency = v;
+ rv = write_latency_timer(port);
+ if (rv < 0)
+ return -EIO;
+ return count;
+}
+static DEVICE_ATTR_RW(latency_timer);
+
+/* Write an event character directly to the FTDI register. The ASCII
+ value is in the low 8 bits, with the enable bit in the 9th bit. */
+static ssize_t event_char_store(struct device *dev,
+ struct device_attribute *attr, const char *valbuf, size_t count)
+{
+ struct usb_serial_port *port = to_usb_serial_port(dev);
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct usb_device *udev = port->serial->dev;
+ unsigned int v;
+ int rv;
+
+ if (kstrtouint(valbuf, 0, &v) || v >= 0x200)
+ return -EINVAL;
+
+ dev_dbg(&port->dev, "%s: setting event char = 0x%03x\n", __func__, v);
+
+ rv = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ FTDI_SIO_SET_EVENT_CHAR_REQUEST,
+ FTDI_SIO_SET_EVENT_CHAR_REQUEST_TYPE,
+ v, priv->channel,
+ NULL, 0, WDR_TIMEOUT);
+ if (rv < 0) {
+ dev_dbg(&port->dev, "Unable to write event character: %i\n", rv);
+ return -EIO;
+ }
+
+ return count;
+}
+static DEVICE_ATTR_WO(event_char);
+
+static struct attribute *ftdi_attrs[] = {
+ &dev_attr_event_char.attr,
+ &dev_attr_latency_timer.attr,
+ NULL
+};
+
+static umode_t ftdi_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct usb_serial_port *port = to_usb_serial_port(dev);
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ enum ftdi_chip_type type = priv->chip_type;
+
+ if (attr == &dev_attr_event_char.attr) {
+ if (type == SIO)
+ return 0;
+ }
+
+ if (attr == &dev_attr_latency_timer.attr) {
+ if (type == SIO || type == FT232A)
+ return 0;
+ }
+
+ return attr->mode;
+}
+
+static const struct attribute_group ftdi_group = {
+ .attrs = ftdi_attrs,
+ .is_visible = ftdi_is_visible,
+};
+
+static const struct attribute_group *ftdi_groups[] = {
+ &ftdi_group,
+ NULL
+};
+
+#ifdef CONFIG_GPIOLIB
+
+static int ftdi_set_bitmode(struct usb_serial_port *port, u8 mode)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ int result;
+ u16 val;
+
+ result = usb_autopm_get_interface(serial->interface);
+ if (result)
+ return result;
+
+ val = (mode << 8) | (priv->gpio_output << 4) | priv->gpio_value;
+ result = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ FTDI_SIO_SET_BITMODE_REQUEST,
+ FTDI_SIO_SET_BITMODE_REQUEST_TYPE, val,
+ priv->channel, NULL, 0, WDR_TIMEOUT);
+ if (result < 0) {
+ dev_err(&serial->interface->dev,
+ "bitmode request failed for value 0x%04x: %d\n",
+ val, result);
+ }
+
+ usb_autopm_put_interface(serial->interface);
+
+ return result;
+}
+
+static int ftdi_set_cbus_pins(struct usb_serial_port *port)
+{
+ return ftdi_set_bitmode(port, FTDI_SIO_BITMODE_CBUS);
+}
+
+static int ftdi_exit_cbus_mode(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+
+ priv->gpio_output = 0;
+ priv->gpio_value = 0;
+ return ftdi_set_bitmode(port, FTDI_SIO_BITMODE_RESET);
+}
+
+static int ftdi_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+ struct usb_serial_port *port = gpiochip_get_data(gc);
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ int result;
+
+ mutex_lock(&priv->gpio_lock);
+ if (!priv->gpio_used) {
+ /* Set default pin states, as we cannot get them from device */
+ priv->gpio_output = 0x00;
+ priv->gpio_value = 0x00;
+ result = ftdi_set_cbus_pins(port);
+ if (result) {
+ mutex_unlock(&priv->gpio_lock);
+ return result;
+ }
+
+ priv->gpio_used = true;
+ }
+ mutex_unlock(&priv->gpio_lock);
+
+ return 0;
+}
+
+static int ftdi_read_cbus_pins(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ u8 buf;
+ int result;
+
+ result = usb_autopm_get_interface(serial->interface);
+ if (result)
+ return result;
+
+ result = usb_control_msg_recv(serial->dev, 0,
+ FTDI_SIO_READ_PINS_REQUEST,
+ FTDI_SIO_READ_PINS_REQUEST_TYPE, 0,
+ priv->channel, &buf, 1, WDR_TIMEOUT,
+ GFP_KERNEL);
+ if (result == 0)
+ result = buf;
+
+ usb_autopm_put_interface(serial->interface);
+
+ return result;
+}
+
+static int ftdi_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct usb_serial_port *port = gpiochip_get_data(gc);
+ int result;
+
+ result = ftdi_read_cbus_pins(port);
+ if (result < 0)
+ return result;
+
+ return !!(result & BIT(gpio));
+}
+
+static int ftdi_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value)
+{
+ struct usb_serial_port *port = gpiochip_get_data(gc);
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ int result;
+
+ mutex_lock(&priv->gpio_lock);
+
+ if (value)
+ priv->gpio_value |= BIT(gpio);
+ else
+ priv->gpio_value &= ~BIT(gpio);
+
+ result = ftdi_set_cbus_pins(port);
+
+ mutex_unlock(&priv->gpio_lock);
+
+ return result;
+}
+
+static int ftdi_gpio_get_multiple(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct usb_serial_port *port = gpiochip_get_data(gc);
+ int result;
+
+ result = ftdi_read_cbus_pins(port);
+ if (result < 0)
+ return result;
+
+ *bits = result & *mask;
+
+ return 0;
+}
+
+static int ftdi_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct usb_serial_port *port = gpiochip_get_data(gc);
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ int result;
+
+ mutex_lock(&priv->gpio_lock);
+
+ priv->gpio_value &= ~(*mask);
+ priv->gpio_value |= *bits & *mask;
+ result = ftdi_set_cbus_pins(port);
+
+ mutex_unlock(&priv->gpio_lock);
+
+ return result;
+}
+
+static int ftdi_gpio_direction_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct usb_serial_port *port = gpiochip_get_data(gc);
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+
+ return !(priv->gpio_output & BIT(gpio));
+}
+
+static int ftdi_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct usb_serial_port *port = gpiochip_get_data(gc);
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ int result;
+
+ mutex_lock(&priv->gpio_lock);
+
+ priv->gpio_output &= ~BIT(gpio);
+ result = ftdi_set_cbus_pins(port);
+
+ mutex_unlock(&priv->gpio_lock);
+
+ return result;
+}
+
+static int ftdi_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio,
+ int value)
+{
+ struct usb_serial_port *port = gpiochip_get_data(gc);
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ int result;
+
+ mutex_lock(&priv->gpio_lock);
+
+ priv->gpio_output |= BIT(gpio);
+ if (value)
+ priv->gpio_value |= BIT(gpio);
+ else
+ priv->gpio_value &= ~BIT(gpio);
+
+ result = ftdi_set_cbus_pins(port);
+
+ mutex_unlock(&priv->gpio_lock);
+
+ return result;
+}
+
+static int ftdi_gpio_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ struct usb_serial_port *port = gpiochip_get_data(gc);
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ unsigned long map = priv->gpio_altfunc;
+
+ bitmap_complement(valid_mask, &map, ngpios);
+
+ if (bitmap_empty(valid_mask, ngpios))
+ dev_dbg(&port->dev, "no CBUS pin configured for GPIO\n");
+ else
+ dev_dbg(&port->dev, "CBUS%*pbl configured for GPIO\n", ngpios,
+ valid_mask);
+
+ return 0;
+}
+
+static int ftdi_read_eeprom(struct usb_serial *serial, void *dst, u16 addr,
+ u16 nbytes)
+{
+ int read = 0;
+
+ if (addr % 2 != 0)
+ return -EINVAL;
+ if (nbytes % 2 != 0)
+ return -EINVAL;
+
+ /* Read EEPROM two bytes at a time */
+ while (read < nbytes) {
+ int rv;
+
+ rv = usb_control_msg(serial->dev,
+ usb_rcvctrlpipe(serial->dev, 0),
+ FTDI_SIO_READ_EEPROM_REQUEST,
+ FTDI_SIO_READ_EEPROM_REQUEST_TYPE,
+ 0, (addr + read) / 2, dst + read, 2,
+ WDR_TIMEOUT);
+ if (rv < 2) {
+ if (rv >= 0)
+ return -EIO;
+ else
+ return rv;
+ }
+
+ read += rv;
+ }
+
+ return 0;
+}
+
+static int ftdi_gpio_init_ft232h(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ u16 cbus_config;
+ u8 *buf;
+ int ret;
+ int i;
+
+ buf = kmalloc(4, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = ftdi_read_eeprom(port->serial, buf, 0x1a, 4);
+ if (ret < 0)
+ goto out_free;
+
+ /*
+ * FT232H CBUS Memory Map
+ *
+ * 0x1a: X- (upper nibble -> AC5)
+ * 0x1b: -X (lower nibble -> AC6)
+ * 0x1c: XX (upper nibble -> AC9 | lower nibble -> AC8)
+ */
+ cbus_config = buf[2] << 8 | (buf[1] & 0xf) << 4 | (buf[0] & 0xf0) >> 4;
+
+ priv->gc.ngpio = 4;
+ priv->gpio_altfunc = 0xff;
+
+ for (i = 0; i < priv->gc.ngpio; ++i) {
+ if ((cbus_config & 0xf) == FTDI_FTX_CBUS_MUX_GPIO)
+ priv->gpio_altfunc &= ~BIT(i);
+ cbus_config >>= 4;
+ }
+
+out_free:
+ kfree(buf);
+
+ return ret;
+}
+
+static int ftdi_gpio_init_ft232r(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ u16 cbus_config;
+ u8 *buf;
+ int ret;
+ int i;
+
+ buf = kmalloc(2, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = ftdi_read_eeprom(port->serial, buf, 0x14, 2);
+ if (ret < 0)
+ goto out_free;
+
+ cbus_config = le16_to_cpup((__le16 *)buf);
+ dev_dbg(&port->dev, "cbus_config = 0x%04x\n", cbus_config);
+
+ priv->gc.ngpio = 4;
+
+ priv->gpio_altfunc = 0xff;
+ for (i = 0; i < priv->gc.ngpio; ++i) {
+ if ((cbus_config & 0xf) == FTDI_FT232R_CBUS_MUX_GPIO)
+ priv->gpio_altfunc &= ~BIT(i);
+ cbus_config >>= 4;
+ }
+out_free:
+ kfree(buf);
+
+ return ret;
+}
+
+static int ftdi_gpio_init_ftx(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ const u16 cbus_cfg_addr = 0x1a;
+ const u16 cbus_cfg_size = 4;
+ u8 *cbus_cfg_buf;
+ int result;
+ u8 i;
+
+ cbus_cfg_buf = kmalloc(cbus_cfg_size, GFP_KERNEL);
+ if (!cbus_cfg_buf)
+ return -ENOMEM;
+
+ result = ftdi_read_eeprom(serial, cbus_cfg_buf,
+ cbus_cfg_addr, cbus_cfg_size);
+ if (result < 0)
+ goto out_free;
+
+ /* FIXME: FT234XD alone has 1 GPIO, but how to recognize this IC? */
+ priv->gc.ngpio = 4;
+
+ /* Determine which pins are configured for CBUS bitbanging */
+ priv->gpio_altfunc = 0xff;
+ for (i = 0; i < priv->gc.ngpio; ++i) {
+ if (cbus_cfg_buf[i] == FTDI_FTX_CBUS_MUX_GPIO)
+ priv->gpio_altfunc &= ~BIT(i);
+ }
+
+out_free:
+ kfree(cbus_cfg_buf);
+
+ return result;
+}
+
+static int ftdi_gpio_init(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ int result;
+
+ switch (priv->chip_type) {
+ case FT232H:
+ result = ftdi_gpio_init_ft232h(port);
+ break;
+ case FT232R:
+ result = ftdi_gpio_init_ft232r(port);
+ break;
+ case FTX:
+ result = ftdi_gpio_init_ftx(port);
+ break;
+ default:
+ return 0;
+ }
+
+ if (result < 0)
+ return result;
+
+ mutex_init(&priv->gpio_lock);
+
+ priv->gc.label = "ftdi-cbus";
+ priv->gc.request = ftdi_gpio_request;
+ priv->gc.get_direction = ftdi_gpio_direction_get;
+ priv->gc.direction_input = ftdi_gpio_direction_input;
+ priv->gc.direction_output = ftdi_gpio_direction_output;
+ priv->gc.init_valid_mask = ftdi_gpio_init_valid_mask;
+ priv->gc.get = ftdi_gpio_get;
+ priv->gc.set = ftdi_gpio_set;
+ priv->gc.get_multiple = ftdi_gpio_get_multiple;
+ priv->gc.set_multiple = ftdi_gpio_set_multiple;
+ priv->gc.owner = THIS_MODULE;
+ priv->gc.parent = &serial->interface->dev;
+ priv->gc.base = -1;
+ priv->gc.can_sleep = true;
+
+ result = gpiochip_add_data(&priv->gc, port);
+ if (!result)
+ priv->gpio_registered = true;
+
+ return result;
+}
+
+static void ftdi_gpio_remove(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+
+ if (priv->gpio_registered) {
+ gpiochip_remove(&priv->gc);
+ priv->gpio_registered = false;
+ }
+
+ if (priv->gpio_used) {
+ /* Exiting CBUS-mode does not reset pin states. */
+ ftdi_exit_cbus_mode(port);
+ priv->gpio_used = false;
+ }
+}
+
+#else
+
+static int ftdi_gpio_init(struct usb_serial_port *port)
+{
+ return 0;
+}
+
+static void ftdi_gpio_remove(struct usb_serial_port *port) { }
+
+#endif /* CONFIG_GPIOLIB */
+
+/*
+ * ***************************************************************************
+ * FTDI driver specific functions
+ * ***************************************************************************
+ */
+
+static int ftdi_probe(struct usb_serial *serial, const struct usb_device_id *id)
+{
+ const struct ftdi_quirk *quirk = (struct ftdi_quirk *)id->driver_info;
+
+ if (quirk && quirk->probe) {
+ int ret = quirk->probe(serial);
+ if (ret != 0)
+ return ret;
+ }
+
+ usb_set_serial_data(serial, (void *)id->driver_info);
+
+ return 0;
+}
+
+static int ftdi_port_probe(struct usb_serial_port *port)
+{
+ const struct ftdi_quirk *quirk = usb_get_serial_data(port->serial);
+ struct ftdi_private *priv;
+ int result;
+
+ priv = kzalloc_obj(struct ftdi_private);
+ if (!priv)
+ return -ENOMEM;
+
+ mutex_init(&priv->cfg_lock);
+
+ if (quirk && quirk->port_probe)
+ quirk->port_probe(priv);
+
+ usb_set_serial_port_data(port, priv);
+
+ result = ftdi_determine_type(port);
+ if (result)
+ goto err_free;
+
+ ftdi_set_max_packet_size(port);
+ if (quirk == &ftdi_ndi_quirk)
+ priv->latency = 1;
+ else if (read_latency_timer(port) < 0)
+ priv->latency = 16;
+ write_latency_timer(port);
+
+ result = ftdi_gpio_init(port);
+ if (result < 0) {
+ dev_err(&port->serial->interface->dev,
+ "GPIO initialisation failed: %d\n",
+ result);
+ }
+
+ return 0;
+
+err_free:
+ kfree(priv);
+
+ return result;
+}
+
+/*
+ * Setup for the USB-UIRT device, which requires hardwired baudrate
+ * (38400 gets mapped to 312500).
+ */
+static void ftdi_usb_uirt_setup(struct ftdi_private *priv)
+{
+ priv->flags |= ASYNC_SPD_CUST;
+ priv->custom_divisor = 77;
+ priv->force_baud = 38400;
+}
+
+/*
+ * Setup for the HE-TIRA1 device, which requires hardwired baudrate
+ * (38400 gets mapped to 100000) and RTS-CTS enabled.
+ */
+static void ftdi_he_tira1_setup(struct ftdi_private *priv)
+{
+ priv->flags |= ASYNC_SPD_CUST;
+ priv->custom_divisor = 240;
+ priv->force_baud = 38400;
+ priv->force_rtscts = 1;
+}
+
+/*
+ * First port on JTAG adaptors such as Olimex arm-usb-ocd or the FIC/OpenMoko
+ * Neo1973 Debug Board is reserved for JTAG interface and can be accessed from
+ * userspace using openocd.
+ */
+static int ftdi_jtag_probe(struct usb_serial *serial)
+{
+ struct usb_interface *intf = serial->interface;
+ int ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
+
+ if (ifnum == 0)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int ftdi_8u2232c_probe(struct usb_serial *serial)
+{
+ struct usb_interface *intf = serial->interface;
+ struct usb_device *udev = serial->dev;
+ int ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
+
+ if (ifnum == 0) {
+ if (udev->manufacturer &&
+ !strcmp(udev->manufacturer, "CALAO Systems"))
+ return -ENODEV;
+
+ if (udev->product &&
+ (!strcmp(udev->product, "Arrow USB Blaster") ||
+ !strcmp(udev->product, "BeagleBone/XDS100V2") ||
+ !strcmp(udev->product, "SNAP Connect E10")))
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/*
+ * First two ports on JTAG adaptors using an FT4232 such as STMicroelectronics's
+ * ST Micro Connect Lite are reserved for JTAG or other non-UART interfaces and
+ * can be accessed from userspace.
+ * The next two ports are enabled as UARTs by default, where port 2 is
+ * a conventional RS-232 UART.
+ */
+static int ftdi_stmclite_probe(struct usb_serial *serial)
+{
+ struct usb_interface *intf = serial->interface;
+ int ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
+
+ if (ifnum < 2)
+ return -ENODEV;
+
+ return 0;
+}
+
+static void ftdi_port_remove(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+
+ ftdi_gpio_remove(port);
+
+ kfree(priv);
+}
+
+static int ftdi_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct usb_device *dev = port->serial->dev;
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+
+ /* No error checking for this (will get errors later anyway) */
+ /* See ftdi_sio.h for description of what is reset */
+ usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ FTDI_SIO_RESET_REQUEST, FTDI_SIO_RESET_REQUEST_TYPE,
+ FTDI_SIO_RESET_SIO,
+ priv->channel, NULL, 0, WDR_TIMEOUT);
+
+ /* Termios defaults are set by usb_serial_init. We don't change
+ port->tty->termios - this would lose speed settings, etc.
+ This is same behaviour as serial.c/rs_open() - Kuba */
+
+ /* ftdi_set_termios will send usb control messages */
+ if (tty)
+ ftdi_set_termios(tty, port, NULL);
+
+ return usb_serial_generic_open(tty, port);
+}
+
+static void ftdi_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+
+ /* Disable flow control */
+ if (!on) {
+ if (usb_control_msg(port->serial->dev,
+ usb_sndctrlpipe(port->serial->dev, 0),
+ FTDI_SIO_SET_FLOW_CTRL_REQUEST,
+ FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
+ 0, priv->channel, NULL, 0,
+ WDR_TIMEOUT) < 0) {
+ dev_err(&port->dev, "error from flowcontrol urb\n");
+ }
+ }
+ /* drop RTS and DTR */
+ if (on)
+ set_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+ else
+ clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+}
+
+/* The SIO requires the first byte to have:
+ * B0 1
+ * B1 0
+ * B2..7 length of message excluding byte 0
+ *
+ * The new devices do not require this byte
+ */
+static int ftdi_prepare_write_buffer(struct usb_serial_port *port,
+ void *dest, size_t size)
+{
+ struct ftdi_private *priv;
+ int count;
+ unsigned long flags;
+
+ priv = usb_get_serial_port_data(port);
+
+ if (priv->chip_type == SIO) {
+ unsigned char *buffer = dest;
+ int i, len, c;
+
+ count = 0;
+ spin_lock_irqsave(&port->lock, flags);
+ for (i = 0; i < size - 1; i += priv->max_packet_size) {
+ len = min_t(int, size - i, priv->max_packet_size) - 1;
+ c = kfifo_out(&port->write_fifo, &buffer[i + 1], len);
+ if (!c)
+ break;
+ port->icount.tx += c;
+ buffer[i] = (c << 2) + 1;
+ count += c + 1;
+ }
+ spin_unlock_irqrestore(&port->lock, flags);
+ } else {
+ count = kfifo_out_locked(&port->write_fifo, dest, size,
+ &port->lock);
+ port->icount.tx += count;
+ }
+
+ return count;
+}
+
+#define FTDI_RS_ERR_MASK (FTDI_RS_BI | FTDI_RS_PE | FTDI_RS_FE | FTDI_RS_OE)
+
+static int ftdi_process_packet(struct usb_serial_port *port,
+ struct ftdi_private *priv, unsigned char *buf, int len)
+{
+ unsigned char status;
+ bool brkint = false;
+ int i;
+ char flag;
+
+ if (len < 2) {
+ dev_dbg(&port->dev, "malformed packet\n");
+ return 0;
+ }
+
+ /* Compare new line status to the old one, signal if different/
+ N.B. packet may be processed more than once, but differences
+ are only processed once. */
+ status = buf[0] & FTDI_STATUS_B0_MASK;
+ if (status != priv->prev_status) {
+ char diff_status = status ^ priv->prev_status;
+
+ if (diff_status & FTDI_RS0_CTS)
+ port->icount.cts++;
+ if (diff_status & FTDI_RS0_DSR)
+ port->icount.dsr++;
+ if (diff_status & FTDI_RS0_RI)
+ port->icount.rng++;
+ if (diff_status & FTDI_RS0_RLSD) {
+ struct tty_struct *tty;
+
+ port->icount.dcd++;
+ tty = tty_port_tty_get(&port->port);
+ if (tty)
+ usb_serial_handle_dcd_change(port, tty,
+ status & FTDI_RS0_RLSD);
+ tty_kref_put(tty);
+ }
+
+ wake_up_interruptible(&port->port.delta_msr_wait);
+ priv->prev_status = status;
+ }
+
+ /* save if the transmitter is empty or not */
+ if (buf[1] & FTDI_RS_TEMT)
+ priv->transmit_empty = 1;
+ else
+ priv->transmit_empty = 0;
+
+ if (len == 2)
+ return 0; /* status only */
+
+ /*
+ * Break and error status must only be processed for packets with
+ * data payload to avoid over-reporting.
+ */
+ flag = TTY_NORMAL;
+ if (buf[1] & FTDI_RS_ERR_MASK) {
+ /*
+ * Break takes precedence over parity, which takes precedence
+ * over framing errors. Note that break is only associated
+ * with the last character in the buffer and only when it's a
+ * NUL.
+ */
+ if (buf[1] & FTDI_RS_BI && buf[len - 1] == '\0') {
+ port->icount.brk++;
+ brkint = true;
+ }
+ if (buf[1] & FTDI_RS_PE) {
+ flag = TTY_PARITY;
+ port->icount.parity++;
+ } else if (buf[1] & FTDI_RS_FE) {
+ flag = TTY_FRAME;
+ port->icount.frame++;
+ }
+ /* Overrun is special, not associated with a char */
+ if (buf[1] & FTDI_RS_OE) {
+ port->icount.overrun++;
+ tty_insert_flip_char(&port->port, 0, TTY_OVERRUN);
+ }
+ }
+
+ port->icount.rx += len - 2;
+
+ if (brkint || port->sysrq) {
+ for (i = 2; i < len; i++) {
+ if (brkint && i == len - 1) {
+ if (usb_serial_handle_break(port))
+ return len - 3;
+ flag = TTY_BREAK;
+ }
+ if (usb_serial_handle_sysrq_char(port, buf[i]))
+ continue;
+ tty_insert_flip_char(&port->port, buf[i], flag);
+ }
+ } else {
+ tty_insert_flip_string_fixed_flag(&port->port, buf + 2, flag,
+ len - 2);
+ }
+
+ return len - 2;
+}
+
+static void ftdi_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ char *data = urb->transfer_buffer;
+ int i;
+ int len;
+ int count = 0;
+
+ for (i = 0; i < urb->actual_length; i += priv->max_packet_size) {
+ len = min_t(int, urb->actual_length - i, priv->max_packet_size);
+ count += ftdi_process_packet(port, priv, &data[i], len);
+ }
+
+ if (count)
+ tty_flip_buffer_push(&port->port);
+}
+
+static int ftdi_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ u16 value;
+ int ret;
+
+ /* break_state = -1 to turn on break, and 0 to turn off break */
+ /* see drivers/char/tty_io.c to see it used */
+ /* last_set_data_value NEVER has the break bit set in it */
+
+ if (break_state)
+ value = priv->last_set_data_value | FTDI_SIO_SET_BREAK;
+ else
+ value = priv->last_set_data_value;
+
+ ret = usb_control_msg(port->serial->dev,
+ usb_sndctrlpipe(port->serial->dev, 0),
+ FTDI_SIO_SET_DATA_REQUEST,
+ FTDI_SIO_SET_DATA_REQUEST_TYPE,
+ value, priv->channel,
+ NULL, 0, WDR_TIMEOUT);
+ if (ret < 0) {
+ dev_err(&port->dev, "%s FAILED to enable/disable break state (state was %d)\n",
+ __func__, break_state);
+ return ret;
+ }
+
+ dev_dbg(&port->dev, "%s break state is %d - urb is %d\n", __func__,
+ break_state, value);
+
+ return 0;
+}
+
+static bool ftdi_tx_empty(struct usb_serial_port *port)
+{
+ unsigned char buf[2];
+ int ret;
+
+ ret = ftdi_get_modem_status(port, buf);
+ if (ret == 2) {
+ if (!(buf[1] & FTDI_RS_TEMT))
+ return false;
+ }
+
+ return true;
+}
+
+/* old_termios contains the original termios settings and tty->termios contains
+ * the new setting to be used
+ * WARNING: set_termios calls this with old_termios in kernel space
+ */
+static void ftdi_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct usb_device *dev = port->serial->dev;
+ struct device *ddev = &port->dev;
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct ktermios *termios = &tty->termios;
+ unsigned int cflag;
+ u16 value, index;
+ int ret;
+
+ /* Force baud rate if this device requires it, unless it is set to
+ B0. */
+ if (priv->force_baud && ((termios->c_cflag & CBAUD) != B0)) {
+ dev_dbg(ddev, "%s: forcing baud rate for this device\n", __func__);
+ tty_encode_baud_rate(tty, priv->force_baud,
+ priv->force_baud);
+ }
+
+ /* Force RTS-CTS if this device requires it. */
+ if (priv->force_rtscts) {
+ dev_dbg(ddev, "%s: forcing rtscts for this device\n", __func__);
+ termios->c_cflag |= CRTSCTS;
+ }
+
+ /*
+ * All FTDI UART chips are limited to CS7/8. We shouldn't pretend to
+ * support CS5/6 and revert the CSIZE setting instead.
+ *
+ * CS5 however is used to control some smartcard readers which abuse
+ * this limitation to switch modes. Original FTDI chips fall back to
+ * eight data bits.
+ *
+ * TODO: Implement a quirk to only allow this with mentioned
+ * readers. One I know of (Argolis Smartreader V1)
+ * returns "USB smartcard server" as iInterface string.
+ * The vendor didn't bother with a custom VID/PID of
+ * course.
+ */
+ if (C_CSIZE(tty) == CS6) {
+ dev_warn(ddev, "requested CSIZE setting not supported\n");
+
+ termios->c_cflag &= ~CSIZE;
+ if (old_termios)
+ termios->c_cflag |= old_termios->c_cflag & CSIZE;
+ else
+ termios->c_cflag |= CS8;
+ }
+
+ cflag = termios->c_cflag;
+
+ if (!old_termios)
+ goto no_skip;
+
+ if (old_termios->c_cflag == termios->c_cflag
+ && old_termios->c_ispeed == termios->c_ispeed
+ && old_termios->c_ospeed == termios->c_ospeed)
+ goto no_c_cflag_changes;
+
+ /* NOTE These routines can get interrupted by
+ ftdi_sio_read_bulk_callback - need to examine what this means -
+ don't see any problems yet */
+
+ if ((old_termios->c_cflag & (CSIZE|PARODD|PARENB|CMSPAR|CSTOPB)) ==
+ (termios->c_cflag & (CSIZE|PARODD|PARENB|CMSPAR|CSTOPB)))
+ goto no_data_parity_stop_changes;
+
+no_skip:
+ /* Set number of data bits, parity, stop bits */
+
+ value = 0;
+ value |= (cflag & CSTOPB ? FTDI_SIO_SET_DATA_STOP_BITS_2 :
+ FTDI_SIO_SET_DATA_STOP_BITS_1);
+ if (cflag & PARENB) {
+ if (cflag & CMSPAR)
+ value |= cflag & PARODD ?
+ FTDI_SIO_SET_DATA_PARITY_MARK :
+ FTDI_SIO_SET_DATA_PARITY_SPACE;
+ else
+ value |= cflag & PARODD ?
+ FTDI_SIO_SET_DATA_PARITY_ODD :
+ FTDI_SIO_SET_DATA_PARITY_EVEN;
+ } else {
+ value |= FTDI_SIO_SET_DATA_PARITY_NONE;
+ }
+ switch (cflag & CSIZE) {
+ case CS5:
+ dev_dbg(ddev, "Setting CS5 quirk\n");
+ break;
+ case CS7:
+ value |= 7;
+ dev_dbg(ddev, "Setting CS7\n");
+ break;
+ default:
+ case CS8:
+ value |= 8;
+ dev_dbg(ddev, "Setting CS8\n");
+ break;
+ }
+
+ /* This is needed by the break command since it uses the same command
+ - but is or'ed with this value */
+ priv->last_set_data_value = value;
+
+ if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ FTDI_SIO_SET_DATA_REQUEST,
+ FTDI_SIO_SET_DATA_REQUEST_TYPE,
+ value, priv->channel,
+ NULL, 0, WDR_SHORT_TIMEOUT) < 0) {
+ dev_err(ddev, "%s FAILED to set databits/stopbits/parity\n",
+ __func__);
+ }
+
+ /* Now do the baudrate */
+no_data_parity_stop_changes:
+ if ((cflag & CBAUD) == B0) {
+ /* Disable flow control */
+ if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ FTDI_SIO_SET_FLOW_CTRL_REQUEST,
+ FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
+ 0, priv->channel,
+ NULL, 0, WDR_TIMEOUT) < 0) {
+ dev_err(ddev, "%s error from disable flowcontrol urb\n",
+ __func__);
+ }
+ /* Drop RTS and DTR */
+ clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+ } else {
+ /* set the baudrate determined before */
+ mutex_lock(&priv->cfg_lock);
+ if (change_speed(tty, port))
+ dev_err(ddev, "%s urb failed to set baudrate\n", __func__);
+ mutex_unlock(&priv->cfg_lock);
+ /* Ensure RTS and DTR are raised when baudrate changed from 0 */
+ if (old_termios && (old_termios->c_cflag & CBAUD) == B0)
+ set_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+ }
+
+no_c_cflag_changes:
+ /* Set hardware-assisted flow control */
+ value = 0;
+
+ if (C_CRTSCTS(tty)) {
+ dev_dbg(&port->dev, "enabling rts/cts flow control\n");
+ index = FTDI_SIO_RTS_CTS_HS;
+ } else if (I_IXON(tty)) {
+ dev_dbg(&port->dev, "enabling xon/xoff flow control\n");
+ index = FTDI_SIO_XON_XOFF_HS;
+ value = STOP_CHAR(tty) << 8 | START_CHAR(tty);
+ } else {
+ dev_dbg(&port->dev, "disabling flow control\n");
+ index = FTDI_SIO_DISABLE_FLOW_CTRL;
+ }
+
+ index |= priv->channel;
+
+ ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ FTDI_SIO_SET_FLOW_CTRL_REQUEST,
+ FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
+ value, index, NULL, 0, WDR_TIMEOUT);
+ if (ret < 0)
+ dev_err(&port->dev, "failed to set flow control: %d\n", ret);
+}
+
+/*
+ * Get modem-control status.
+ *
+ * Returns the number of status bytes retrieved (device dependant), or
+ * negative error code.
+ */
+static int ftdi_get_modem_status(struct usb_serial_port *port,
+ unsigned char status[2])
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ unsigned char *buf;
+ int len;
+ int ret;
+
+ buf = kmalloc(2, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ /*
+ * The device returns a two byte value (the SIO a 1 byte value) in the
+ * same format as the data returned from the IN endpoint.
+ */
+ if (priv->chip_type == SIO)
+ len = 1;
+ else
+ len = 2;
+
+ ret = usb_control_msg(port->serial->dev,
+ usb_rcvctrlpipe(port->serial->dev, 0),
+ FTDI_SIO_GET_MODEM_STATUS_REQUEST,
+ FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE,
+ 0, priv->channel,
+ buf, len, WDR_TIMEOUT);
+
+ /* NOTE: We allow short responses and handle that below. */
+ if (ret < 1) {
+ dev_err(&port->dev, "failed to get modem status: %d\n", ret);
+ if (ret >= 0)
+ ret = -EIO;
+ ret = usb_translate_errors(ret);
+ goto out;
+ }
+
+ status[0] = buf[0];
+ if (ret > 1)
+ status[1] = buf[1];
+ else
+ status[1] = 0;
+
+ dev_dbg(&port->dev, "%s - 0x%02x%02x\n", __func__, status[0],
+ status[1]);
+out:
+ kfree(buf);
+
+ return ret;
+}
+
+static int ftdi_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ unsigned char buf[2];
+ int ret;
+
+ ret = ftdi_get_modem_status(port, buf);
+ if (ret < 0)
+ return ret;
+
+ ret = (buf[0] & FTDI_SIO_DSR_MASK ? TIOCM_DSR : 0) |
+ (buf[0] & FTDI_SIO_CTS_MASK ? TIOCM_CTS : 0) |
+ (buf[0] & FTDI_SIO_RI_MASK ? TIOCM_RI : 0) |
+ (buf[0] & FTDI_SIO_RLSD_MASK ? TIOCM_CD : 0) |
+ priv->last_dtr_rts;
+
+ return ret;
+}
+
+static int ftdi_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ return update_mctrl(port, set, clear);
+}
+
+static int ftdi_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ void __user *argp = (void __user *)arg;
+
+ switch (cmd) {
+ case TIOCSERGETLSR:
+ return get_lsr_info(port, argp);
+ default:
+ break;
+ }
+
+ return -ENOIOCTLCMD;
+}
+
+static struct usb_serial_driver ftdi_device = {
+ .driver = {
+ .name = "ftdi_sio",
+ .dev_groups = ftdi_groups,
+ },
+ .description = "FTDI USB Serial Device",
+ .id_table = id_table_combined,
+ .num_ports = 1,
+ .bulk_in_size = 512,
+ .bulk_out_size = 256,
+ .probe = ftdi_probe,
+ .port_probe = ftdi_port_probe,
+ .port_remove = ftdi_port_remove,
+ .open = ftdi_open,
+ .dtr_rts = ftdi_dtr_rts,
+ .throttle = usb_serial_generic_throttle,
+ .unthrottle = usb_serial_generic_unthrottle,
+ .process_read_urb = ftdi_process_read_urb,
+ .prepare_write_buffer = ftdi_prepare_write_buffer,
+ .tiocmget = ftdi_tiocmget,
+ .tiocmset = ftdi_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .ioctl = ftdi_ioctl,
+ .get_serial = get_serial_info,
+ .set_serial = set_serial_info,
+ .set_termios = ftdi_set_termios,
+ .break_ctl = ftdi_break_ctl,
+ .tx_empty = ftdi_tx_empty,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &ftdi_device, NULL
+};
+module_usb_serial_driver(serial_drivers, id_table_combined);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h
new file mode 100644
index 000000000..55ea61264
--- /dev/null
+++ b/drivers/usb/serial/ftdi_sio.h
@@ -0,0 +1,579 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Driver definitions for the FTDI USB Single Port Serial Converter -
+ * known as FTDI_SIO (Serial Input/Output application of the chipset)
+ *
+ * For USB vendor/product IDs (VID/PID), please see ftdi_sio_ids.h
+ *
+ *
+ * The example I have is known as the USC-1000 which is available from
+ * http://www.dse.co.nz - cat no XH4214 It looks similar to this:
+ * http://www.dansdata.com/usbser.htm but I can't be sure There are other
+ * USC-1000s which don't look like my device though so beware!
+ *
+ * The device is based on the FTDI FT8U100AX chip. It has a DB25 on one side,
+ * USB on the other.
+ *
+ * Thanx to FTDI (http://www.ftdichip.com) for so kindly providing details
+ * of the protocol required to talk to the device and ongoing assistence
+ * during development.
+ *
+ * Bill Ryder - bryder@sgi.com formerly of Silicon Graphics, Inc.- wrote the
+ * FTDI_SIO implementation.
+ *
+ */
+
+/* Commands */
+#define FTDI_SIO_RESET 0 /* Reset the port */
+#define FTDI_SIO_MODEM_CTRL 1 /* Set the modem control register */
+#define FTDI_SIO_SET_FLOW_CTRL 2 /* Set flow control register */
+#define FTDI_SIO_SET_BAUD_RATE 3 /* Set baud rate */
+#define FTDI_SIO_SET_DATA 4 /* Set the data characteristics of
+ the port */
+#define FTDI_SIO_GET_MODEM_STATUS 5 /* Retrieve current value of modem
+ status register */
+#define FTDI_SIO_SET_EVENT_CHAR 6 /* Set the event character */
+#define FTDI_SIO_SET_ERROR_CHAR 7 /* Set the error character */
+#define FTDI_SIO_SET_LATENCY_TIMER 9 /* Set the latency timer */
+#define FTDI_SIO_GET_LATENCY_TIMER 0x0a /* Get the latency timer */
+#define FTDI_SIO_SET_BITMODE 0x0b /* Set bitbang mode */
+#define FTDI_SIO_READ_PINS 0x0c /* Read immediate value of pins */
+#define FTDI_SIO_READ_EEPROM 0x90 /* Read EEPROM */
+
+/* Channel indices for FT2232, FT2232H and FT4232H devices */
+#define CHANNEL_A 1
+#define CHANNEL_B 2
+#define CHANNEL_C 3
+#define CHANNEL_D 4
+
+
+/*
+ * BmRequestType: 1100 0000b
+ * bRequest: FTDI_E2_READ
+ * wValue: 0
+ * wIndex: Address of word to read
+ * wLength: 2
+ * Data: Will return a word of data from E2Address
+ *
+ */
+
+/* Port Identifier Table */
+#define PIT_DEFAULT 0 /* SIOA */
+#define PIT_SIOA 1 /* SIOA */
+/* The device this driver is tested with one has only one port */
+#define PIT_SIOB 2 /* SIOB */
+#define PIT_PARALLEL 3 /* Parallel */
+
+/* FTDI_SIO_RESET */
+#define FTDI_SIO_RESET_REQUEST FTDI_SIO_RESET
+#define FTDI_SIO_RESET_REQUEST_TYPE 0x40
+#define FTDI_SIO_RESET_SIO 0
+#define FTDI_SIO_RESET_PURGE_RX 1
+#define FTDI_SIO_RESET_PURGE_TX 2
+
+/*
+ * BmRequestType: 0100 0000B
+ * bRequest: FTDI_SIO_RESET
+ * wValue: Control Value
+ * 0 = Reset SIO
+ * 1 = Purge RX buffer
+ * 2 = Purge TX buffer
+ * wIndex: Port
+ * wLength: 0
+ * Data: None
+ *
+ * The Reset SIO command has this effect:
+ *
+ * Sets flow control set to 'none'
+ * Event char = $0D
+ * Event trigger = disabled
+ * Purge RX buffer
+ * Purge TX buffer
+ * Clear DTR
+ * Clear RTS
+ * baud and data format not reset
+ *
+ * The Purge RX and TX buffer commands affect nothing except the buffers
+ *
+ */
+
+/* FTDI_SIO_SET_BAUDRATE */
+#define FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE 0x40
+#define FTDI_SIO_SET_BAUDRATE_REQUEST 3
+
+/*
+ * BmRequestType: 0100 0000B
+ * bRequest: FTDI_SIO_SET_BAUDRATE
+ * wValue: BaudDivisor value - see below
+ * wIndex: Port
+ * wLength: 0
+ * Data: None
+ * The BaudDivisor values are calculated as follows:
+ * - BaseClock is either 12000000 or 48000000 depending on the device.
+ * FIXME: I wish I knew how to detect old chips to select proper base clock!
+ * - BaudDivisor is a fixed point number encoded in a funny way.
+ * (--WRONG WAY OF THINKING--)
+ * BaudDivisor is a fixed point number encoded with following bit weighs:
+ * (-2)(-1)(13..0). It is a radical with a denominator of 4, so values
+ * end with 0.0 (00...), 0.25 (10...), 0.5 (01...), and 0.75 (11...).
+ * (--THE REALITY--)
+ * The both-bits-set has quite different meaning from 0.75 - the chip
+ * designers have decided it to mean 0.125 instead of 0.75.
+ * This info looked up in FTDI application note "FT8U232 DEVICES \ Data Rates
+ * and Flow Control Consideration for USB to RS232".
+ * - BaudDivisor = (BaseClock / 16) / BaudRate, where the (=) operation should
+ * automagically re-encode the resulting value to take fractions into
+ * consideration.
+ * As all values are integers, some bit twiddling is in order:
+ * BaudDivisor = (BaseClock / 16 / BaudRate) |
+ * (((BaseClock / 2 / BaudRate) & 4) ? 0x4000 // 0.5
+ * : ((BaseClock / 2 / BaudRate) & 2) ? 0x8000 // 0.25
+ * : ((BaseClock / 2 / BaudRate) & 1) ? 0xc000 // 0.125
+ * : 0)
+ *
+ * For the FT232BM, a 17th divisor bit was introduced to encode the multiples
+ * of 0.125 missing from the FT8U232AM. Bits 16 to 14 are coded as follows
+ * (the first four codes are the same as for the FT8U232AM, where bit 16 is
+ * always 0):
+ * 000 - add .000 to divisor
+ * 001 - add .500 to divisor
+ * 010 - add .250 to divisor
+ * 011 - add .125 to divisor
+ * 100 - add .375 to divisor
+ * 101 - add .625 to divisor
+ * 110 - add .750 to divisor
+ * 111 - add .875 to divisor
+ * Bits 15 to 0 of the 17-bit divisor are placed in the urb value. Bit 16 is
+ * placed in bit 0 of the urb index.
+ *
+ * Note that there are a couple of special cases to support the highest baud
+ * rates. If the calculated divisor value is 1, this needs to be replaced with
+ * 0. Additionally for the FT232BM, if the calculated divisor value is 0x4001
+ * (1.5), this needs to be replaced with 0x0001 (1) (but this divisor value is
+ * not supported by the FT8U232AM).
+ */
+
+enum ftdi_sio_baudrate {
+ ftdi_sio_b300 = 0,
+ ftdi_sio_b600 = 1,
+ ftdi_sio_b1200 = 2,
+ ftdi_sio_b2400 = 3,
+ ftdi_sio_b4800 = 4,
+ ftdi_sio_b9600 = 5,
+ ftdi_sio_b19200 = 6,
+ ftdi_sio_b38400 = 7,
+ ftdi_sio_b57600 = 8,
+ ftdi_sio_b115200 = 9
+};
+
+/*
+ * The ftdi_8U232AM_xxMHz_byyy constants have been removed. The encoded divisor
+ * values are calculated internally.
+ */
+#define FTDI_SIO_SET_DATA_REQUEST FTDI_SIO_SET_DATA
+#define FTDI_SIO_SET_DATA_REQUEST_TYPE 0x40
+#define FTDI_SIO_SET_DATA_PARITY_NONE (0x0 << 8)
+#define FTDI_SIO_SET_DATA_PARITY_ODD (0x1 << 8)
+#define FTDI_SIO_SET_DATA_PARITY_EVEN (0x2 << 8)
+#define FTDI_SIO_SET_DATA_PARITY_MARK (0x3 << 8)
+#define FTDI_SIO_SET_DATA_PARITY_SPACE (0x4 << 8)
+#define FTDI_SIO_SET_DATA_STOP_BITS_1 (0x0 << 11)
+#define FTDI_SIO_SET_DATA_STOP_BITS_15 (0x1 << 11)
+#define FTDI_SIO_SET_DATA_STOP_BITS_2 (0x2 << 11)
+#define FTDI_SIO_SET_BREAK (0x1 << 14)
+/* FTDI_SIO_SET_DATA */
+
+/*
+ * BmRequestType: 0100 0000B
+ * bRequest: FTDI_SIO_SET_DATA
+ * wValue: Data characteristics (see below)
+ * wIndex: Port
+ * wLength: 0
+ * Data: No
+ *
+ * Data characteristics
+ *
+ * B0..7 Number of data bits
+ * B8..10 Parity
+ * 0 = None
+ * 1 = Odd
+ * 2 = Even
+ * 3 = Mark
+ * 4 = Space
+ * B11..13 Stop Bits
+ * 0 = 1
+ * 1 = 1.5
+ * 2 = 2
+ * B14
+ * 1 = TX ON (break)
+ * 0 = TX OFF (normal state)
+ * B15 Reserved
+ *
+ */
+
+
+
+/* FTDI_SIO_MODEM_CTRL */
+#define FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE 0x40
+#define FTDI_SIO_SET_MODEM_CTRL_REQUEST FTDI_SIO_MODEM_CTRL
+
+/*
+ * BmRequestType: 0100 0000B
+ * bRequest: FTDI_SIO_MODEM_CTRL
+ * wValue: ControlValue (see below)
+ * wIndex: Port
+ * wLength: 0
+ * Data: None
+ *
+ * NOTE: If the device is in RTS/CTS flow control, the RTS set by this
+ * command will be IGNORED without an error being returned
+ * Also - you can not set DTR and RTS with one control message
+ */
+
+#define FTDI_SIO_SET_DTR_MASK 0x1
+#define FTDI_SIO_SET_DTR_HIGH ((FTDI_SIO_SET_DTR_MASK << 8) | 1)
+#define FTDI_SIO_SET_DTR_LOW ((FTDI_SIO_SET_DTR_MASK << 8) | 0)
+#define FTDI_SIO_SET_RTS_MASK 0x2
+#define FTDI_SIO_SET_RTS_HIGH ((FTDI_SIO_SET_RTS_MASK << 8) | 2)
+#define FTDI_SIO_SET_RTS_LOW ((FTDI_SIO_SET_RTS_MASK << 8) | 0)
+
+/*
+ * ControlValue
+ * B0 DTR state
+ * 0 = reset
+ * 1 = set
+ * B1 RTS state
+ * 0 = reset
+ * 1 = set
+ * B2..7 Reserved
+ * B8 DTR state enable
+ * 0 = ignore
+ * 1 = use DTR state
+ * B9 RTS state enable
+ * 0 = ignore
+ * 1 = use RTS state
+ * B10..15 Reserved
+ */
+
+/* FTDI_SIO_SET_FLOW_CTRL */
+#define FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE 0x40
+#define FTDI_SIO_SET_FLOW_CTRL_REQUEST FTDI_SIO_SET_FLOW_CTRL
+#define FTDI_SIO_DISABLE_FLOW_CTRL 0x0
+#define FTDI_SIO_RTS_CTS_HS (0x1 << 8)
+#define FTDI_SIO_DTR_DSR_HS (0x2 << 8)
+#define FTDI_SIO_XON_XOFF_HS (0x4 << 8)
+/*
+ * BmRequestType: 0100 0000b
+ * bRequest: FTDI_SIO_SET_FLOW_CTRL
+ * wValue: Xoff/Xon
+ * wIndex: Protocol/Port - hIndex is protocol / lIndex is port
+ * wLength: 0
+ * Data: None
+ *
+ * hIndex protocol is:
+ * B0 Output handshaking using RTS/CTS
+ * 0 = disabled
+ * 1 = enabled
+ * B1 Output handshaking using DTR/DSR
+ * 0 = disabled
+ * 1 = enabled
+ * B2 Xon/Xoff handshaking
+ * 0 = disabled
+ * 1 = enabled
+ *
+ * A value of zero in the hIndex field disables handshaking
+ *
+ * If Xon/Xoff handshaking is specified, the hValue field should contain the
+ * XOFF character and the lValue field contains the XON character.
+ */
+
+/*
+ * FTDI_SIO_GET_LATENCY_TIMER
+ *
+ * Set the timeout interval. The FTDI collects data from the
+ * device, transmitting it to the host when either A) 62 bytes are
+ * received, or B) the timeout interval has elapsed and the buffer
+ * contains at least 1 byte. Setting this value to a small number
+ * can dramatically improve performance for applications which send
+ * small packets, since the default value is 16ms.
+ */
+#define FTDI_SIO_GET_LATENCY_TIMER_REQUEST FTDI_SIO_GET_LATENCY_TIMER
+#define FTDI_SIO_GET_LATENCY_TIMER_REQUEST_TYPE 0xC0
+
+/*
+ * BmRequestType: 1100 0000b
+ * bRequest: FTDI_SIO_GET_LATENCY_TIMER
+ * wValue: 0
+ * wIndex: Port
+ * wLength: 0
+ * Data: latency (on return)
+ */
+
+/*
+ * FTDI_SIO_SET_LATENCY_TIMER
+ *
+ * Set the timeout interval. The FTDI collects data from the
+ * device, transmitting it to the host when either A) 62 bytes are
+ * received, or B) the timeout interval has elapsed and the buffer
+ * contains at least 1 byte. Setting this value to a small number
+ * can dramatically improve performance for applications which send
+ * small packets, since the default value is 16ms.
+ */
+#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST FTDI_SIO_SET_LATENCY_TIMER
+#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE 0x40
+
+/*
+ * BmRequestType: 0100 0000b
+ * bRequest: FTDI_SIO_SET_LATENCY_TIMER
+ * wValue: Latency (milliseconds)
+ * wIndex: Port
+ * wLength: 0
+ * Data: None
+ *
+ * wValue:
+ * B0..7 Latency timer
+ * B8..15 0
+ *
+ */
+
+/*
+ * FTDI_SIO_SET_EVENT_CHAR
+ *
+ * Set the special event character for the specified communications port.
+ * If the device sees this character it will immediately return the
+ * data read so far - rather than wait 40ms or until 62 bytes are read
+ * which is what normally happens.
+ */
+
+
+#define FTDI_SIO_SET_EVENT_CHAR_REQUEST FTDI_SIO_SET_EVENT_CHAR
+#define FTDI_SIO_SET_EVENT_CHAR_REQUEST_TYPE 0x40
+
+
+/*
+ * BmRequestType: 0100 0000b
+ * bRequest: FTDI_SIO_SET_EVENT_CHAR
+ * wValue: EventChar
+ * wIndex: Port
+ * wLength: 0
+ * Data: None
+ *
+ * wValue:
+ * B0..7 Event Character
+ * B8 Event Character Processing
+ * 0 = disabled
+ * 1 = enabled
+ * B9..15 Reserved
+ *
+ */
+
+/* FTDI_SIO_SET_ERROR_CHAR */
+
+/*
+ * Set the parity error replacement character for the specified communications
+ * port
+ */
+
+/*
+ * BmRequestType: 0100 0000b
+ * bRequest: FTDI_SIO_SET_EVENT_CHAR
+ * wValue: Error Char
+ * wIndex: Port
+ * wLength: 0
+ * Data: None
+ *
+ *Error Char
+ * B0..7 Error Character
+ * B8 Error Character Processing
+ * 0 = disabled
+ * 1 = enabled
+ * B9..15 Reserved
+ *
+ */
+
+/* FTDI_SIO_GET_MODEM_STATUS */
+/* Retrieve the current value of the modem status register */
+
+#define FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE 0xc0
+#define FTDI_SIO_GET_MODEM_STATUS_REQUEST FTDI_SIO_GET_MODEM_STATUS
+#define FTDI_SIO_CTS_MASK 0x10
+#define FTDI_SIO_DSR_MASK 0x20
+#define FTDI_SIO_RI_MASK 0x40
+#define FTDI_SIO_RLSD_MASK 0x80
+/*
+ * BmRequestType: 1100 0000b
+ * bRequest: FTDI_SIO_GET_MODEM_STATUS
+ * wValue: zero
+ * wIndex: Port
+ * wLength: 1
+ * Data: Status
+ *
+ * One byte of data is returned
+ * B0..3 0
+ * B4 CTS
+ * 0 = inactive
+ * 1 = active
+ * B5 DSR
+ * 0 = inactive
+ * 1 = active
+ * B6 Ring Indicator (RI)
+ * 0 = inactive
+ * 1 = active
+ * B7 Receive Line Signal Detect (RLSD)
+ * 0 = inactive
+ * 1 = active
+ */
+
+/* FTDI_SIO_SET_BITMODE */
+#define FTDI_SIO_SET_BITMODE_REQUEST_TYPE 0x40
+#define FTDI_SIO_SET_BITMODE_REQUEST FTDI_SIO_SET_BITMODE
+
+/* Possible bitmodes for FTDI_SIO_SET_BITMODE_REQUEST */
+#define FTDI_SIO_BITMODE_RESET 0x00
+#define FTDI_SIO_BITMODE_CBUS 0x20
+
+/* FTDI_SIO_READ_PINS */
+#define FTDI_SIO_READ_PINS_REQUEST_TYPE 0xc0
+#define FTDI_SIO_READ_PINS_REQUEST FTDI_SIO_READ_PINS
+
+/*
+ * FTDI_SIO_READ_EEPROM
+ *
+ * EEPROM format found in FTDI AN_201, "FT-X MTP memory Configuration",
+ * http://www.ftdichip.com/Support/Documents/AppNotes/AN_201_FT-X%20MTP%20Memory%20Configuration.pdf
+ */
+#define FTDI_SIO_READ_EEPROM_REQUEST_TYPE 0xc0
+#define FTDI_SIO_READ_EEPROM_REQUEST FTDI_SIO_READ_EEPROM
+
+#define FTDI_FTX_CBUS_MUX_GPIO 0x8
+#define FTDI_FT232R_CBUS_MUX_GPIO 0xa
+
+
+/* Descriptors returned by the device
+ *
+ * Device Descriptor
+ *
+ * Offset Field Size Value Description
+ * 0 bLength 1 0x12 Size of descriptor in bytes
+ * 1 bDescriptorType 1 0x01 DEVICE Descriptor Type
+ * 2 bcdUSB 2 0x0110 USB Spec Release Number
+ * 4 bDeviceClass 1 0x00 Class Code
+ * 5 bDeviceSubClass 1 0x00 SubClass Code
+ * 6 bDeviceProtocol 1 0x00 Protocol Code
+ * 7 bMaxPacketSize0 1 0x08 Maximum packet size for endpoint 0
+ * 8 idVendor 2 0x0403 Vendor ID
+ * 10 idProduct 2 0x8372 Product ID (FTDI_SIO_PID)
+ * 12 bcdDevice 2 0x0001 Device release number
+ * 14 iManufacturer 1 0x01 Index of man. string desc
+ * 15 iProduct 1 0x02 Index of prod string desc
+ * 16 iSerialNumber 1 0x02 Index of serial nmr string desc
+ * 17 bNumConfigurations 1 0x01 Number of possible configurations
+ *
+ * Configuration Descriptor
+ *
+ * Offset Field Size Value
+ * 0 bLength 1 0x09 Size of descriptor in bytes
+ * 1 bDescriptorType 1 0x02 CONFIGURATION Descriptor Type
+ * 2 wTotalLength 2 0x0020 Total length of data
+ * 4 bNumInterfaces 1 0x01 Number of interfaces supported
+ * 5 bConfigurationValue 1 0x01 Argument for SetCOnfiguration() req
+ * 6 iConfiguration 1 0x02 Index of config string descriptor
+ * 7 bmAttributes 1 0x20 Config characteristics Remote Wakeup
+ * 8 MaxPower 1 0x1E Max power consumption
+ *
+ * Interface Descriptor
+ *
+ * Offset Field Size Value
+ * 0 bLength 1 0x09 Size of descriptor in bytes
+ * 1 bDescriptorType 1 0x04 INTERFACE Descriptor Type
+ * 2 bInterfaceNumber 1 0x00 Number of interface
+ * 3 bAlternateSetting 1 0x00 Value used to select alternate
+ * 4 bNumEndpoints 1 0x02 Number of endpoints
+ * 5 bInterfaceClass 1 0xFF Class Code
+ * 6 bInterfaceSubClass 1 0xFF Subclass Code
+ * 7 bInterfaceProtocol 1 0xFF Protocol Code
+ * 8 iInterface 1 0x02 Index of interface string description
+ *
+ * IN Endpoint Descriptor
+ *
+ * Offset Field Size Value
+ * 0 bLength 1 0x07 Size of descriptor in bytes
+ * 1 bDescriptorType 1 0x05 ENDPOINT descriptor type
+ * 2 bEndpointAddress 1 0x82 Address of endpoint
+ * 3 bmAttributes 1 0x02 Endpoint attributes - Bulk
+ * 4 bNumEndpoints 2 0x0040 maximum packet size
+ * 5 bInterval 1 0x00 Interval for polling endpoint
+ *
+ * OUT Endpoint Descriptor
+ *
+ * Offset Field Size Value
+ * 0 bLength 1 0x07 Size of descriptor in bytes
+ * 1 bDescriptorType 1 0x05 ENDPOINT descriptor type
+ * 2 bEndpointAddress 1 0x02 Address of endpoint
+ * 3 bmAttributes 1 0x02 Endpoint attributes - Bulk
+ * 4 bNumEndpoints 2 0x0040 maximum packet size
+ * 5 bInterval 1 0x00 Interval for polling endpoint
+ *
+ * DATA FORMAT
+ *
+ * IN Endpoint
+ *
+ * The device reserves the first two bytes of data on this endpoint to contain
+ * the current values of the modem and line status registers. In the absence of
+ * data, the device generates a message consisting of these two status bytes
+ * every 40 ms
+ *
+ * Byte 0: Modem Status
+ *
+ * Offset Description
+ * B0 Reserved - must be 1
+ * B1 Reserved - must be 0
+ * B2 Reserved - must be 0
+ * B3 Reserved - must be 0
+ * B4 Clear to Send (CTS)
+ * B5 Data Set Ready (DSR)
+ * B6 Ring Indicator (RI)
+ * B7 Receive Line Signal Detect (RLSD)
+ *
+ * Byte 1: Line Status
+ *
+ * Offset Description
+ * B0 Data Ready (DR)
+ * B1 Overrun Error (OE)
+ * B2 Parity Error (PE)
+ * B3 Framing Error (FE)
+ * B4 Break Interrupt (BI)
+ * B5 Transmitter Holding Register (THRE)
+ * B6 Transmitter Empty (TEMT)
+ * B7 Error in RCVR FIFO
+ *
+ */
+#define FTDI_RS0_CTS (1 << 4)
+#define FTDI_RS0_DSR (1 << 5)
+#define FTDI_RS0_RI (1 << 6)
+#define FTDI_RS0_RLSD (1 << 7)
+
+#define FTDI_RS_DR 1
+#define FTDI_RS_OE (1<<1)
+#define FTDI_RS_PE (1<<2)
+#define FTDI_RS_FE (1<<3)
+#define FTDI_RS_BI (1<<4)
+#define FTDI_RS_THRE (1<<5)
+#define FTDI_RS_TEMT (1<<6)
+#define FTDI_RS_FIFO (1<<7)
+
+/*
+ * OUT Endpoint
+ *
+ * This device reserves the first bytes of data on this endpoint contain the
+ * length and port identifier of the message. For the FTDI USB Serial converter
+ * the port identifier is always 1.
+ *
+ * Byte 0: Line Status
+ *
+ * Offset Description
+ * B0 Reserved - must be 1
+ * B1 Reserved - must be 0
+ * B2..7 Length of message - (not including Byte 0)
+ *
+ */
diff --git a/drivers/usb/serial/ftdi_sio_ids.h b/drivers/usb/serial/ftdi_sio_ids.h
new file mode 100644
index 000000000..6c76cfebf
--- /dev/null
+++ b/drivers/usb/serial/ftdi_sio_ids.h
@@ -0,0 +1,1638 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * vendor/product IDs (VID/PID) of devices using FTDI USB serial converters.
+ * Please keep numerically sorted within individual areas, thanks!
+ *
+ * Philipp Gühring - pg@futureware.at - added the Device ID of the USB relais
+ * from Rudolf Gugler
+ *
+ */
+
+
+/**********************************/
+/***** devices using FTDI VID *****/
+/**********************************/
+
+
+#define FTDI_VID 0x0403 /* Vendor Id */
+
+
+/*** "original" FTDI device PIDs ***/
+
+#define FTDI_8U232AM_PID 0x6001 /* Similar device to SIO above */
+#define FTDI_8U232AM_ALT_PID 0x6006 /* FTDI's alternate PID for above */
+#define FTDI_8U2232C_PID 0x6010 /* Dual channel device */
+#define FTDI_4232H_PID 0x6011 /* Quad channel hi-speed device */
+#define FTDI_232H_PID 0x6014 /* Single channel hi-speed device */
+#define FTDI_FTX_PID 0x6015 /* FT-X series (FT201X, FT230X, FT231X, etc) */
+#define FTDI_FT2233HP_PID 0x6040 /* Dual channel hi-speed device with PD */
+#define FTDI_FT4233HP_PID 0x6041 /* Quad channel hi-speed device with PD */
+#define FTDI_FT2232HP_PID 0x6042 /* Dual channel hi-speed device with PD */
+#define FTDI_FT4232HP_PID 0x6043 /* Quad channel hi-speed device with PD */
+#define FTDI_FT233HP_PID 0x6044 /* Dual channel hi-speed device with PD */
+#define FTDI_FT232HP_PID 0x6045 /* Dual channel hi-speed device with PD */
+#define FTDI_FT4232HA_PID 0x6048 /* Quad channel automotive grade hi-speed device */
+#define FTDI_SIO_PID 0x8372 /* Product Id SIO application of 8U100AX */
+#define FTDI_232RL_PID 0xFBFA /* Product ID for FT232RL */
+
+
+/*** third-party PIDs (using FTDI_VID) ***/
+
+/*
+ * Certain versions of the official Windows FTDI driver reprogrammed
+ * counterfeit FTDI devices to PID 0. Support these devices anyway.
+ */
+#define FTDI_BRICK_PID 0x0000
+
+#define FTDI_LUMEL_PD12_PID 0x6002
+
+/*
+ * Custom USB adapters made by Falconia Partners LLC
+ * for FreeCalypso project, ID codes allocated to Falconia by FTDI.
+ */
+#define FTDI_FALCONIA_JTAG_BUF_PID 0x7150
+#define FTDI_FALCONIA_JTAG_UNBUF_PID 0x7151
+
+/* Sienna Serial Interface by Secyourit GmbH */
+#define FTDI_SIENNA_PID 0x8348
+
+/* Cyber Cortex AV by Fabulous Silicon (http://fabuloussilicon.com) */
+#define CYBER_CORTEX_AV_PID 0x8698
+
+/*
+ * Marvell OpenRD Base, Client
+ * http://www.open-rd.org
+ * OpenRD Base, Client use VID 0x0403
+ */
+#define MARVELL_OPENRD_PID 0x9e90
+
+/* www.candapter.com Ewert Energy Systems CANdapter device */
+#define FTDI_CANDAPTER_PID 0x9F80 /* Product Id */
+
+#define FTDI_BM_ATOM_NANO_PID 0xa559 /* Basic Micro ATOM Nano USB2Serial */
+
+/*
+ * Texas Instruments XDS100v2 JTAG / BeagleBone A3
+ * http://processors.wiki.ti.com/index.php/XDS100
+ * http://beagleboard.org/bone
+ */
+#define TI_XDS100V2_PID 0xa6d0
+
+#define FTDI_NXTCAM_PID 0xABB8 /* NXTCam for Mindstorms NXT */
+#define FTDI_EV3CON_PID 0xABB9 /* Mindstorms EV3 Console Adapter */
+
+/* US Interface Navigator (http://www.usinterface.com/) */
+#define FTDI_USINT_CAT_PID 0xb810 /* Navigator CAT and 2nd PTT lines */
+#define FTDI_USINT_WKEY_PID 0xb811 /* Navigator WKEY and FSK lines */
+#define FTDI_USINT_RS232_PID 0xb812 /* Navigator RS232 and CONFIG lines */
+
+/* OOCDlink by Joern Kaipf <joernk@web.de>
+ * (http://www.joernonline.de/) */
+#define FTDI_OOCDLINK_PID 0xbaf8 /* Amontec JTAGkey */
+
+/* Luminary Micro Stellaris Boards, VID = FTDI_VID */
+/* FTDI 2332C Dual channel device, side A=245 FIFO (JTAG), Side B=RS232 UART */
+#define LMI_LM3S_DEVEL_BOARD_PID 0xbcd8
+#define LMI_LM3S_EVAL_BOARD_PID 0xbcd9
+#define LMI_LM3S_ICDI_BOARD_PID 0xbcda
+
+#define FTDI_AXE027_PID 0xBD90 /* PICAXE AXE027 USB download cable */
+
+#define FTDI_TURTELIZER_PID 0xBDC8 /* JTAG/RS-232 adapter by egnite GmbH */
+
+/* OpenDCC (www.opendcc.de) product id */
+#define FTDI_OPENDCC_PID 0xBFD8
+#define FTDI_OPENDCC_SNIFFER_PID 0xBFD9
+#define FTDI_OPENDCC_THROTTLE_PID 0xBFDA
+#define FTDI_OPENDCC_GATEWAY_PID 0xBFDB
+#define FTDI_OPENDCC_GBM_PID 0xBFDC
+#define FTDI_OPENDCC_GBM_BOOST_PID 0xBFDD
+
+/* NZR SEM 16+ USB (http://www.nzr.de) */
+#define FTDI_NZR_SEM_USB_PID 0xC1E0 /* NZR SEM-LOG16+ */
+
+/*
+ * RR-CirKits LocoBuffer USB (http://www.rr-cirkits.com)
+ */
+#define FTDI_RRCIRKITS_LOCOBUFFER_PID 0xc7d0 /* LocoBuffer USB */
+
+/* DMX4ALL DMX Interfaces */
+#define FTDI_DMX4ALL 0xC850
+
+/*
+ * ASK.fr devices
+ */
+#define FTDI_ASK_RDR400_PID 0xC991 /* ASK RDR 400 series card reader */
+
+/* www.starting-point-systems.com µChameleon device */
+#define FTDI_MICRO_CHAMELEON_PID 0xCAA0 /* Product Id */
+
+/*
+ * Tactrix OpenPort (ECU) devices.
+ * OpenPort 1.3M submitted by Donour Sizemore.
+ * OpenPort 1.3S and 1.3U submitted by Ian Abbott.
+ */
+#define FTDI_TACTRIX_OPENPORT_13M_PID 0xCC48 /* OpenPort 1.3 Mitsubishi */
+#define FTDI_TACTRIX_OPENPORT_13S_PID 0xCC49 /* OpenPort 1.3 Subaru */
+#define FTDI_TACTRIX_OPENPORT_13U_PID 0xCC4A /* OpenPort 1.3 Universal */
+
+#define FTDI_DISTORTEC_JTAG_LOCK_PICK_PID 0xCFF8
+
+/* SCS HF Radio Modems PID's (http://www.scs-ptc.com) */
+/* the VID is the standard ftdi vid (FTDI_VID) */
+#define FTDI_SCS_DEVICE_0_PID 0xD010 /* SCS PTC-IIusb */
+#define FTDI_SCS_DEVICE_1_PID 0xD011 /* SCS Tracker / DSP TNC */
+#define FTDI_SCS_DEVICE_2_PID 0xD012
+#define FTDI_SCS_DEVICE_3_PID 0xD013
+#define FTDI_SCS_DEVICE_4_PID 0xD014
+#define FTDI_SCS_DEVICE_5_PID 0xD015
+#define FTDI_SCS_DEVICE_6_PID 0xD016
+#define FTDI_SCS_DEVICE_7_PID 0xD017
+
+/* iPlus device */
+#define FTDI_IPLUS_PID 0xD070 /* Product Id */
+#define FTDI_IPLUS2_PID 0xD071 /* Product Id */
+
+/*
+ * Gamma Scout (http://gamma-scout.com/). Submitted by rsc@runtux.com.
+ */
+#define FTDI_GAMMA_SCOUT_PID 0xD678 /* Gamma Scout online */
+
+/* Propox devices */
+#define FTDI_PROPOX_JTAGCABLEII_PID 0xD738
+#define FTDI_PROPOX_ISPCABLEIII_PID 0xD739
+
+/* Lenz LI-USB Computer Interface. */
+#define FTDI_LENZ_LIUSB_PID 0xD780
+
+/* Vardaan Enterprises Serial Interface VEUSB422R3 */
+#define FTDI_VARDAAN_PID 0xF070
+
+/* Auto-M3 Ltd. - OP-COM USB V2 - OBD interface Adapter */
+#define FTDI_AUTO_M3_OP_COM_V2_PID 0x4f50
+
+/*
+ * Xsens Technologies BV products (http://www.xsens.com).
+ */
+#define XSENS_VID 0x2639
+#define XSENS_AWINDA_STATION_PID 0x0101
+#define XSENS_AWINDA_DONGLE_PID 0x0102
+#define XSENS_MTW_PID 0x0200 /* Xsens MTw */
+#define XSENS_MTDEVBOARD_PID 0x0300 /* Motion Tracker Development Board */
+#define XSENS_MTIUSBCONVERTER_PID 0x0301 /* MTi USB converter */
+#define XSENS_CONVERTER_PID 0xD00D /* Xsens USB-serial converter */
+
+/* Xsens devices using FTDI VID */
+#define XSENS_CONVERTER_0_PID 0xD388 /* Xsens USB converter */
+#define XSENS_CONVERTER_1_PID 0xD389 /* Xsens Wireless Receiver */
+#define XSENS_CONVERTER_2_PID 0xD38A
+#define XSENS_CONVERTER_3_PID 0xD38B /* Xsens USB-serial converter */
+#define XSENS_CONVERTER_4_PID 0xD38C /* Xsens Wireless Receiver */
+#define XSENS_CONVERTER_5_PID 0xD38D /* Xsens Awinda Station */
+#define XSENS_CONVERTER_6_PID 0xD38E
+#define XSENS_CONVERTER_7_PID 0xD38F
+
+/**
+ * Zolix (www.zolix.com.cb) product ids
+ */
+#define FTDI_OMNI1509 0xD491 /* Omni1509 embedded USB-serial */
+
+/*
+ * NDI (www.ndigital.com) product ids
+ */
+#define FTDI_NDI_HUC_PID 0xDA70 /* NDI Host USB Converter */
+#define FTDI_NDI_SPECTRA_SCU_PID 0xDA71 /* NDI Spectra SCU */
+#define FTDI_NDI_FUTURE_2_PID 0xDA72 /* NDI future device #2 */
+#define FTDI_NDI_FUTURE_3_PID 0xDA73 /* NDI future device #3 */
+#define FTDI_NDI_AURORA_SCU_PID 0xDA74 /* NDI Aurora SCU */
+
+#define FTDI_NDI_VID 0x23F2
+#define FTDI_NDI_EMGUIDE_GEMINI_PID 0x0003 /* NDI Emguide Gemini */
+
+/*
+ * ChamSys Limited (www.chamsys.co.uk) USB wing/interface product IDs
+ */
+#define FTDI_CHAMSYS_24_MASTER_WING_PID 0xDAF8
+#define FTDI_CHAMSYS_PC_WING_PID 0xDAF9
+#define FTDI_CHAMSYS_USB_DMX_PID 0xDAFA
+#define FTDI_CHAMSYS_MIDI_TIMECODE_PID 0xDAFB
+#define FTDI_CHAMSYS_MINI_WING_PID 0xDAFC
+#define FTDI_CHAMSYS_MAXI_WING_PID 0xDAFD
+#define FTDI_CHAMSYS_MEDIA_WING_PID 0xDAFE
+#define FTDI_CHAMSYS_WING_PID 0xDAFF
+
+/*
+ * Westrex International devices submitted by Cory Lee
+ */
+#define FTDI_WESTREX_MODEL_777_PID 0xDC00 /* Model 777 */
+#define FTDI_WESTREX_MODEL_8900F_PID 0xDC01 /* Model 8900F */
+
+/*
+ * ACG Identification Technologies GmbH products (http://www.acg.de/).
+ * Submitted by anton -at- goto10 -dot- org.
+ */
+#define FTDI_ACG_HFDUAL_PID 0xDD20 /* HF Dual ISO Reader (RFID) */
+
+/*
+ * Definitions for Artemis astronomical USB based cameras
+ * Check it at http://www.artemisccd.co.uk/
+ */
+#define FTDI_ARTEMIS_PID 0xDF28 /* All Artemis Cameras */
+
+/*
+ * Definitions for ATIK Instruments astronomical USB based cameras
+ * Check it at http://www.atik-instruments.com/
+ */
+#define FTDI_ATIK_ATK16_PID 0xDF30 /* ATIK ATK-16 Grayscale Camera */
+#define FTDI_ATIK_ATK16C_PID 0xDF32 /* ATIK ATK-16C Colour Camera */
+#define FTDI_ATIK_ATK16HR_PID 0xDF31 /* ATIK ATK-16HR Grayscale Camera */
+#define FTDI_ATIK_ATK16HRC_PID 0xDF33 /* ATIK ATK-16HRC Colour Camera */
+#define FTDI_ATIK_ATK16IC_PID 0xDF35 /* ATIK ATK-16IC Grayscale Camera */
+
+/*
+ * Yost Engineering, Inc. products (www.yostengineering.com).
+ * PID 0xE050 submitted by Aaron Prose.
+ */
+#define FTDI_YEI_SERVOCENTER31_PID 0xE050 /* YEI ServoCenter3.1 USB */
+
+/*
+ * ELV USB devices submitted by Christian Abt of ELV (www.elv.de).
+ * Almost all of these devices use FTDI's vendor ID (0x0403).
+ * Further IDs taken from ELV Windows .inf file.
+ *
+ * The previously included PID for the UO 100 module was incorrect.
+ * In fact, that PID was for ELV's UR 100 USB-RS232 converter (0xFB58).
+ *
+ * Armin Laeuger originally sent the PID for the UM 100 module.
+ */
+#define FTDI_ELV_VID 0x1B1F /* ELV AG */
+#define FTDI_ELV_WS300_PID 0xC006 /* eQ3 WS 300 PC II */
+#define FTDI_ELV_USR_PID 0xE000 /* ELV Universal-Sound-Recorder */
+#define FTDI_ELV_MSM1_PID 0xE001 /* ELV Mini-Sound-Modul */
+#define FTDI_ELV_KL100_PID 0xE002 /* ELV Kfz-Leistungsmesser KL 100 */
+#define FTDI_ELV_WS550_PID 0xE004 /* WS 550 */
+#define FTDI_ELV_EC3000_PID 0xE006 /* ENERGY CONTROL 3000 USB */
+#define FTDI_ELV_WS888_PID 0xE008 /* WS 888 */
+#define FTDI_ELV_TWS550_PID 0xE009 /* Technoline WS 550 */
+#define FTDI_ELV_FEM_PID 0xE00A /* Funk Energie Monitor */
+#define FTDI_ELV_FHZ1300PC_PID 0xE0E8 /* FHZ 1300 PC */
+#define FTDI_ELV_WS500_PID 0xE0E9 /* PC-Wetterstation (WS 500) */
+#define FTDI_ELV_HS485_PID 0xE0EA /* USB to RS-485 adapter */
+#define FTDI_ELV_UMS100_PID 0xE0EB /* ELV USB Master-Slave Schaltsteckdose UMS 100 */
+#define FTDI_ELV_TFD128_PID 0xE0EC /* ELV Temperatur-Feuchte-Datenlogger TFD 128 */
+#define FTDI_ELV_FM3RX_PID 0xE0ED /* ELV Messwertuebertragung FM3 RX */
+#define FTDI_ELV_WS777_PID 0xE0EE /* Conrad WS 777 */
+#define FTDI_ELV_EM1010PC_PID 0xE0EF /* Energy monitor EM 1010 PC */
+#define FTDI_ELV_CSI8_PID 0xE0F0 /* Computer-Schalt-Interface (CSI 8) */
+#define FTDI_ELV_EM1000DL_PID 0xE0F1 /* PC-Datenlogger fuer Energiemonitor (EM 1000 DL) */
+#define FTDI_ELV_PCK100_PID 0xE0F2 /* PC-Kabeltester (PCK 100) */
+#define FTDI_ELV_RFP500_PID 0xE0F3 /* HF-Leistungsmesser (RFP 500) */
+#define FTDI_ELV_FS20SIG_PID 0xE0F4 /* Signalgeber (FS 20 SIG) */
+#define FTDI_ELV_UTP8_PID 0xE0F5 /* ELV UTP 8 */
+#define FTDI_ELV_WS300PC_PID 0xE0F6 /* PC-Wetterstation (WS 300 PC) */
+#define FTDI_ELV_WS444PC_PID 0xE0F7 /* Conrad WS 444 PC */
+#define FTDI_PHI_FISCO_PID 0xE40B /* PHI Fisco USB to Serial cable */
+#define FTDI_ELV_UAD8_PID 0xF068 /* USB-AD-Wandler (UAD 8) */
+#define FTDI_ELV_UDA7_PID 0xF069 /* USB-DA-Wandler (UDA 7) */
+#define FTDI_ELV_USI2_PID 0xF06A /* USB-Schrittmotoren-Interface (USI 2) */
+#define FTDI_ELV_T1100_PID 0xF06B /* Thermometer (T 1100) */
+#define FTDI_ELV_PCD200_PID 0xF06C /* PC-Datenlogger (PCD 200) */
+#define FTDI_ELV_ULA200_PID 0xF06D /* USB-LCD-Ansteuerung (ULA 200) */
+#define FTDI_ELV_ALC8500_PID 0xF06E /* ALC 8500 Expert */
+#define FTDI_ELV_FHZ1000PC_PID 0xF06F /* FHZ 1000 PC */
+#define FTDI_ELV_UR100_PID 0xFB58 /* USB-RS232-Umsetzer (UR 100) */
+#define FTDI_ELV_UM100_PID 0xFB5A /* USB-Modul UM 100 */
+#define FTDI_ELV_UO100_PID 0xFB5B /* USB-Modul UO 100 */
+/* Additional ELV PIDs that default to using the FTDI D2XX drivers on
+ * MS Windows, rather than the FTDI Virtual Com Port drivers.
+ * Maybe these will be easier to use with the libftdi/libusb user-space
+ * drivers, or possibly the Comedi drivers in some cases. */
+#define FTDI_ELV_CLI7000_PID 0xFB59 /* Computer-Light-Interface (CLI 7000) */
+#define FTDI_ELV_PPS7330_PID 0xFB5C /* Processor-Power-Supply (PPS 7330) */
+#define FTDI_ELV_TFM100_PID 0xFB5D /* Temperatur-Feuchte-Messgeraet (TFM 100) */
+#define FTDI_ELV_UDF77_PID 0xFB5E /* USB DCF Funkuhr (UDF 77) */
+#define FTDI_ELV_UIO88_PID 0xFB5F /* USB-I/O Interface (UIO 88) */
+
+/*
+ * EVER Eco Pro UPS (http://www.ever.com.pl/)
+ */
+
+#define EVER_ECO_PRO_CDS 0xe520 /* RS-232 converter */
+
+/*
+ * Active Robots product ids.
+ */
+#define FTDI_ACTIVE_ROBOTS_PID 0xE548 /* USB comms board */
+
+/* Pyramid Computer GmbH */
+#define FTDI_PYRAMID_PID 0xE6C8 /* Pyramid Appliance Display */
+
+/* www.elsterelectricity.com Elster Unicom III Optical Probe */
+#define FTDI_ELSTER_UNICOM_PID 0xE700 /* Product Id */
+
+/*
+ * Gude Analog- und Digitalsysteme GmbH
+ */
+#define FTDI_GUDEADS_E808_PID 0xE808
+#define FTDI_GUDEADS_E809_PID 0xE809
+#define FTDI_GUDEADS_E80A_PID 0xE80A
+#define FTDI_GUDEADS_E80B_PID 0xE80B
+#define FTDI_GUDEADS_E80C_PID 0xE80C
+#define FTDI_GUDEADS_E80D_PID 0xE80D
+#define FTDI_GUDEADS_E80E_PID 0xE80E
+#define FTDI_GUDEADS_E80F_PID 0xE80F
+#define FTDI_GUDEADS_E888_PID 0xE888 /* Expert ISDN Control USB */
+#define FTDI_GUDEADS_E889_PID 0xE889 /* USB RS-232 OptoBridge */
+#define FTDI_GUDEADS_E88A_PID 0xE88A
+#define FTDI_GUDEADS_E88B_PID 0xE88B
+#define FTDI_GUDEADS_E88C_PID 0xE88C
+#define FTDI_GUDEADS_E88D_PID 0xE88D
+#define FTDI_GUDEADS_E88E_PID 0xE88E
+#define FTDI_GUDEADS_E88F_PID 0xE88F
+
+/*
+ * Eclo (http://www.eclo.pt/) product IDs.
+ * PID 0xEA90 submitted by Martin Grill.
+ */
+#define FTDI_ECLO_COM_1WIRE_PID 0xEA90 /* COM to 1-Wire USB adaptor */
+
+/* TNC-X USB-to-packet-radio adapter, versions prior to 3.0 (DLP module) */
+#define FTDI_TNC_X_PID 0xEBE0
+
+/*
+ * Teratronik product ids.
+ * Submitted by O. Wölfelschneider.
+ */
+#define FTDI_TERATRONIK_VCP_PID 0xEC88 /* Teratronik device (preferring VCP driver on windows) */
+#define FTDI_TERATRONIK_D2XX_PID 0xEC89 /* Teratronik device (preferring D2XX driver on windows) */
+
+/* Rig Expert Ukraine devices */
+#define FTDI_REU_TINY_PID 0xED22 /* RigExpert Tiny */
+
+/*
+ * Hameg HO820 and HO870 interface (using VID 0x0403)
+ */
+#define HAMEG_HO820_PID 0xed74
+#define HAMEG_HO730_PID 0xed73
+#define HAMEG_HO720_PID 0xed72
+#define HAMEG_HO870_PID 0xed71
+
+/*
+ * MaxStream devices www.maxstream.net
+ */
+#define FTDI_MAXSTREAM_PID 0xEE18 /* Xbee PKG-U Module */
+
+/*
+ * microHAM product IDs (http://www.microham.com).
+ * Submitted by Justin Burket (KL1RL) <zorton@jtan.com>
+ * and Mike Studer (K6EEP) <k6eep@hamsoftware.org>.
+ * Ian Abbott <abbotti@mev.co.uk> added a few more from the driver INF file.
+ */
+#define FTDI_MHAM_KW_PID 0xEEE8 /* USB-KW interface */
+#define FTDI_MHAM_YS_PID 0xEEE9 /* USB-YS interface */
+#define FTDI_MHAM_Y6_PID 0xEEEA /* USB-Y6 interface */
+#define FTDI_MHAM_Y8_PID 0xEEEB /* USB-Y8 interface */
+#define FTDI_MHAM_IC_PID 0xEEEC /* USB-IC interface */
+#define FTDI_MHAM_DB9_PID 0xEEED /* USB-DB9 interface */
+#define FTDI_MHAM_RS232_PID 0xEEEE /* USB-RS232 interface */
+#define FTDI_MHAM_Y9_PID 0xEEEF /* USB-Y9 interface */
+
+/* Domintell products http://www.domintell.com */
+#define FTDI_DOMINTELL_DGQG_PID 0xEF50 /* Master */
+#define FTDI_DOMINTELL_DUSB_PID 0xEF51 /* DUSB01 module */
+
+/*
+ * The following are the values for the Perle Systems
+ * UltraPort USB serial converters
+ */
+#define FTDI_PERLE_ULTRAPORT_PID 0xF0C0 /* Perle UltraPort Product Id */
+
+/* Sprog II (Andrew Crosland's SprogII DCC interface) */
+#define FTDI_SPROG_II 0xF0C8
+
+/*
+ * Two of the Tagsys RFID Readers
+ */
+#define FTDI_TAGSYS_LP101_PID 0xF0E9 /* Tagsys L-P101 RFID*/
+#define FTDI_TAGSYS_P200X_PID 0xF0EE /* Tagsys Medio P200x RFID*/
+
+/* an infrared receiver for user access control with IR tags */
+#define FTDI_PIEGROUP_PID 0xF208 /* Product Id */
+
+/* ACT Solutions HomePro ZWave interface
+ (http://www.act-solutions.com/HomePro-Product-Matrix.html) */
+#define FTDI_ACTZWAVE_PID 0xF2D0
+
+/*
+ * 4N-GALAXY.DE PIDs for CAN-USB, USB-RS232, USB-RS422, USB-RS485,
+ * USB-TTY aktiv, USB-TTY passiv. Some PIDs are used by several devices
+ * and I'm not entirely sure which are used by which.
+ */
+#define FTDI_4N_GALAXY_DE_1_PID 0xF3C0
+#define FTDI_4N_GALAXY_DE_2_PID 0xF3C1
+#define FTDI_4N_GALAXY_DE_3_PID 0xF3C2
+
+/*
+ * Ivium Technologies product IDs
+ */
+#define FTDI_PALMSENS_PID 0xf440
+#define FTDI_IVIUM_XSTAT_PID 0xf441
+
+/*
+ * Linx Technologies product ids
+ */
+#define LINX_SDMUSBQSS_PID 0xF448 /* Linx SDM-USB-QS-S */
+#define LINX_MASTERDEVEL2_PID 0xF449 /* Linx Master Development 2.0 */
+#define LINX_FUTURE_0_PID 0xF44A /* Linx future device */
+#define LINX_FUTURE_1_PID 0xF44B /* Linx future device */
+#define LINX_FUTURE_2_PID 0xF44C /* Linx future device */
+
+/*
+ * Abacus Electrics
+ */
+#define ABACUS_OPTICAL_PROBE_PID 0xf458 /* ABACUS ELECTRICS Optical Probe */
+
+/*
+ * Oceanic product ids
+ */
+#define FTDI_OCEANIC_PID 0xF460 /* Oceanic dive instrument */
+
+/*
+ * SUUNTO product ids
+ */
+#define FTDI_SUUNTO_SPORTS_PID 0xF680 /* Suunto Sports instrument */
+
+/* USB-UIRT - An infrared receiver and transmitter using the 8U232AM chip */
+/* http://www.usbuirt.com/ */
+#define FTDI_USB_UIRT_PID 0xF850 /* Product Id */
+
+/* CCS Inc. ICDU/ICDU40 product ID -
+ * the FT232BM is used in an in-circuit-debugger unit for PIC16's/PIC18's */
+#define FTDI_CCSICDU20_0_PID 0xF9D0
+#define FTDI_CCSICDU40_1_PID 0xF9D1
+#define FTDI_CCSMACHX_2_PID 0xF9D2
+#define FTDI_CCSLOAD_N_GO_3_PID 0xF9D3
+#define FTDI_CCSICDU64_4_PID 0xF9D4
+#define FTDI_CCSPRIME8_5_PID 0xF9D5
+
+/*
+ * The following are the values for the Matrix Orbital LCD displays,
+ * which are the FT232BM ( similar to the 8U232AM )
+ */
+#define FTDI_MTXORB_0_PID 0xFA00 /* Matrix Orbital Product Id */
+#define FTDI_MTXORB_1_PID 0xFA01 /* Matrix Orbital Product Id */
+#define FTDI_MTXORB_2_PID 0xFA02 /* Matrix Orbital Product Id */
+#define FTDI_MTXORB_3_PID 0xFA03 /* Matrix Orbital Product Id */
+#define FTDI_MTXORB_4_PID 0xFA04 /* Matrix Orbital Product Id */
+#define FTDI_MTXORB_5_PID 0xFA05 /* Matrix Orbital Product Id */
+#define FTDI_MTXORB_6_PID 0xFA06 /* Matrix Orbital Product Id */
+
+/*
+ * Home Electronics (www.home-electro.com) USB gadgets
+ */
+#define FTDI_HE_TIRA1_PID 0xFA78 /* Tira-1 IR transceiver */
+
+/* Inside Accesso contactless reader (http://www.insidecontactless.com/) */
+#define INSIDE_ACCESSO 0xFAD0
+
+/*
+ * ThorLabs USB motor drivers
+ */
+#define FTDI_THORLABS_PID 0xfaf0 /* ThorLabs USB motor drivers */
+
+/*
+ * Protego product ids
+ */
+#define PROTEGO_SPECIAL_1 0xFC70 /* special/unknown device */
+#define PROTEGO_R2X0 0xFC71 /* R200-USB TRNG unit (R210, R220, and R230) */
+#define PROTEGO_SPECIAL_3 0xFC72 /* special/unknown device */
+#define PROTEGO_SPECIAL_4 0xFC73 /* special/unknown device */
+
+/*
+ * Sony Ericsson product ids
+ */
+#define FTDI_DSS20_PID 0xFC82 /* DSS-20 Sync Station for Sony Ericsson P800 */
+#define FTDI_URBAN_0_PID 0xFC8A /* Sony Ericsson Urban, uart #0 */
+#define FTDI_URBAN_1_PID 0xFC8B /* Sony Ericsson Urban, uart #1 */
+
+/* www.irtrans.de device */
+#define FTDI_IRTRANS_PID 0xFC60 /* Product Id */
+
+/*
+ * RM Michaelides CANview USB (http://www.rmcan.com) (FTDI_VID)
+ * CAN fieldbus interface adapter, added by port GmbH www.port.de)
+ * Ian Abbott changed the macro names for consistency.
+ */
+#define FTDI_RM_CANVIEW_PID 0xfd60 /* Product Id */
+/* www.thoughttechnology.com/ TT-USB provide with procomp use ftdi_sio */
+#define FTDI_TTUSB_PID 0xFF20 /* Product Id */
+
+#define FTDI_USBX_707_PID 0xF857 /* ADSTech IR Blaster USBX-707 (FTDI_VID) */
+
+#define FTDI_RELAIS_PID 0xFA10 /* Relais device from Rudolf Gugler */
+
+/*
+ * PCDJ use ftdi based dj-controllers. The following PID is
+ * for their DAC-2 device http://www.pcdjhardware.com/DAC2.asp
+ * (the VID is the standard ftdi vid (FTDI_VID), PID sent by Wouter Paesen)
+ */
+#define FTDI_PCDJ_DAC2_PID 0xFA88
+
+#define FTDI_R2000KU_TRUE_RNG 0xFB80 /* R2000KU TRUE RNG (FTDI_VID) */
+
+/*
+ * DIEBOLD BCS SE923 (FTDI_VID)
+ */
+#define DIEBOLD_BCS_SE923_PID 0xfb99
+
+/* www.crystalfontz.com devices
+ * - thanx for providing free devices for evaluation !
+ * they use the ftdi chipset for the USB interface
+ * and the vendor id is the same
+ */
+#define FTDI_XF_632_PID 0xFC08 /* 632: 16x2 Character Display */
+#define FTDI_XF_634_PID 0xFC09 /* 634: 20x4 Character Display */
+#define FTDI_XF_547_PID 0xFC0A /* 547: Two line Display */
+#define FTDI_XF_633_PID 0xFC0B /* 633: 16x2 Character Display with Keys */
+#define FTDI_XF_631_PID 0xFC0C /* 631: 20x2 Character Display */
+#define FTDI_XF_635_PID 0xFC0D /* 635: 20x4 Character Display */
+#define FTDI_XF_640_PID 0xFC0E /* 640: Two line Display */
+#define FTDI_XF_642_PID 0xFC0F /* 642: Two line Display */
+
+/*
+ * Video Networks Limited / Homechoice in the UK use an ftdi-based device
+ * for their 1Mb broadband internet service. The following PID is exhibited
+ * by the usb device supplied (the VID is the standard ftdi vid (FTDI_VID)
+ */
+#define FTDI_VNHCPCUSB_D_PID 0xfe38 /* Product Id */
+
+/* AlphaMicro Components AMC-232USB01 device (FTDI_VID) */
+#define FTDI_AMC232_PID 0xFF00 /* Product Id */
+
+/*
+ * IBS elektronik product ids (FTDI_VID)
+ * Submitted by Thomas Schleusener
+ */
+#define FTDI_IBS_US485_PID 0xff38 /* IBS US485 (USB<-->RS422/485 interface) */
+#define FTDI_IBS_PICPRO_PID 0xff39 /* IBS PIC-Programmer */
+#define FTDI_IBS_PCMCIA_PID 0xff3a /* IBS Card reader for PCMCIA SRAM-cards */
+#define FTDI_IBS_PK1_PID 0xff3b /* IBS PK1 - Particel counter */
+#define FTDI_IBS_RS232MON_PID 0xff3c /* IBS RS232 - Monitor */
+#define FTDI_IBS_APP70_PID 0xff3d /* APP 70 (dust monitoring system) */
+#define FTDI_IBS_PEDO_PID 0xff3e /* IBS PEDO-Modem (RF modem 868.35 MHz) */
+#define FTDI_IBS_PROD_PID 0xff3f /* future device */
+/* www.canusb.com Lawicel CANUSB device (FTDI_VID) */
+#define FTDI_CANUSB_PID 0xFFA8 /* Product Id */
+
+/*
+ * TavIR AVR product ids (FTDI_VID)
+ */
+#define FTDI_TAVIR_STK500_PID 0xFA33 /* STK500 AVR programmer */
+
+/*
+ * TIAO product ids (FTDI_VID)
+ * http://www.tiaowiki.com/w/Main_Page
+ */
+#define FTDI_TIAO_UMPA_PID 0x8a98 /* TIAO/DIYGADGET USB Multi-Protocol Adapter */
+
+/*
+ * NovaTech product ids (FTDI_VID)
+ */
+#define FTDI_NT_ORIONLXM_PID 0x7c90 /* OrionLXm Substation Automation Platform */
+#define FTDI_NT_ORIONLX_PLUS_PID 0x7c91 /* OrionLX+ Substation Automation Platform */
+#define FTDI_NT_ORION_IO_PID 0x7c92 /* Orion I/O */
+#define FTDI_NT_ORIONMX_PID 0x7c93 /* OrionMX */
+
+/*
+ * Synapse Wireless product ids (FTDI_VID)
+ * http://www.synapse-wireless.com
+ */
+#define FTDI_SYNAPSE_SS200_PID 0x9090 /* SS200 - SNAP Stick 200 */
+
+/*
+ * CustomWare / ShipModul NMEA multiplexers product ids (FTDI_VID)
+ */
+#define FTDI_CUSTOMWARE_MINIPLEX_PID 0xfd48 /* MiniPlex first generation NMEA Multiplexer */
+#define FTDI_CUSTOMWARE_MINIPLEX2_PID 0xfd49 /* MiniPlex-USB and MiniPlex-2 series */
+#define FTDI_CUSTOMWARE_MINIPLEX2WI_PID 0xfd4a /* MiniPlex-2Wi */
+#define FTDI_CUSTOMWARE_MINIPLEX3_PID 0xfd4b /* MiniPlex-3 series */
+
+
+/********************************/
+/** third-party VID/PID combos **/
+/********************************/
+
+
+
+/*
+ * Atmel STK541
+ */
+#define ATMEL_VID 0x03eb /* Vendor ID */
+#define STK541_PID 0x2109 /* Zigbee Controller */
+
+/*
+ * Texas Instruments
+ */
+#define TI_VID 0x0451
+#define TI_CC3200_LAUNCHPAD_PID 0xC32A /* SimpleLink Wi-Fi CC3200 LaunchPad */
+
+/*
+ * Blackfin gnICE JTAG
+ * http://docs.blackfin.uclinux.org/doku.php?id=hw:jtag:gnice
+ */
+#define ADI_VID 0x0456
+#define ADI_GNICE_PID 0xF000
+#define ADI_GNICEPLUS_PID 0xF001
+
+/*
+ * Cypress WICED USB UART
+ */
+#define CYPRESS_VID 0x04B4
+#define CYPRESS_WICED_BT_USB_PID 0x009B
+#define CYPRESS_WICED_WL_USB_PID 0xF900
+
+/*
+ * Microchip Technology, Inc.
+ *
+ * MICROCHIP_VID (0x04D8) and MICROCHIP_USB_BOARD_PID (0x000A) are
+ * used by single function CDC ACM class based firmware demo
+ * applications. The VID/PID has also been used in firmware
+ * emulating FTDI serial chips by:
+ * Hornby Elite - Digital Command Control Console
+ * http://www.hornby.com/hornby-dcc/controllers/
+ */
+#define MICROCHIP_VID 0x04D8
+#define MICROCHIP_USB_BOARD_PID 0x000A /* CDC RS-232 Emulation Demo */
+
+/*
+ * RATOC REX-USB60F
+ */
+#define RATOC_VENDOR_ID 0x0584
+#define RATOC_PRODUCT_ID_USB60F 0xb020
+#define RATOC_PRODUCT_ID_SCU18 0xb03a
+
+/*
+ * Infineon Technologies
+ */
+#define INFINEON_VID 0x058b
+#define INFINEON_TRIBOARD_TC1798_PID 0x0028 /* DAS JTAG TriBoard TC1798 V1.0 */
+#define INFINEON_TRIBOARD_TC2X7_PID 0x0043 /* DAS JTAG TriBoard TC2X7 V1.0 */
+
+/*
+ * Omron corporation (https://www.omron.com)
+ */
+ #define OMRON_VID 0x0590
+ #define OMRON_CS1W_CIF31_PID 0x00b2
+
+/*
+ * Acton Research Corp.
+ */
+#define ACTON_VID 0x0647 /* Vendor ID */
+#define ACTON_SPECTRAPRO_PID 0x0100
+
+/*
+ * Contec products (http://www.contec.com)
+ * Submitted by Daniel Sangorrin
+ */
+#define CONTEC_VID 0x06CE /* Vendor ID */
+#define CONTEC_COM1USBH_PID 0x8311 /* COM-1(USB)H */
+
+/*
+ * Mitsubishi Electric Corp. (http://www.meau.com)
+ * Submitted by Konstantin Holoborodko
+ */
+#define MITSUBISHI_VID 0x06D3
+#define MITSUBISHI_FXUSB_PID 0x0284 /* USB/RS422 converters: FX-USB-AW/-BD */
+
+/*
+ * Definitions for B&B Electronics products.
+ */
+#define BANDB_VID 0x0856 /* B&B Electronics Vendor ID */
+#define BANDB_USOTL4_PID 0xAC01 /* USOTL4 Isolated RS-485 Converter */
+#define BANDB_USTL4_PID 0xAC02 /* USTL4 RS-485 Converter */
+#define BANDB_USO9ML2_PID 0xAC03 /* USO9ML2 Isolated RS-232 Converter */
+#define BANDB_USOPTL4_PID 0xAC11
+#define BANDB_USPTL4_PID 0xAC12
+#define BANDB_USO9ML2DR_2_PID 0xAC16
+#define BANDB_USO9ML2DR_PID 0xAC17
+#define BANDB_USOPTL4DR2_PID 0xAC18 /* USOPTL4R-2 2-port Isolated RS-232 Converter */
+#define BANDB_USOPTL4DR_PID 0xAC19
+#define BANDB_485USB9F_2W_PID 0xAC25
+#define BANDB_485USB9F_4W_PID 0xAC26
+#define BANDB_232USB9M_PID 0xAC27
+#define BANDB_485USBTB_2W_PID 0xAC33
+#define BANDB_485USBTB_4W_PID 0xAC34
+#define BANDB_TTL5USB9M_PID 0xAC49
+#define BANDB_TTL3USB9M_PID 0xAC50
+#define BANDB_ZZ_PROG1_USB_PID 0xBA02
+
+/*
+ * Echelon USB Serial Interface
+ */
+#define ECHELON_VID 0x0920
+#define ECHELON_U20_PID 0x7500
+
+/*
+ * Intrepid Control Systems (http://www.intrepidcs.com/) ValueCAN and NeoVI
+ */
+#define INTREPID_VID 0x093C
+#define INTREPID_VALUECAN_PID 0x0601
+#define INTREPID_NEOVI_PID 0x0701
+
+/*
+ * WICED USB UART
+ */
+#define WICED_VID 0x0A5C
+#define WICED_USB20706V2_PID 0x6422
+
+/*
+ * Definitions for ID TECH (www.idt-net.com) devices
+ */
+#define IDTECH_VID 0x0ACD /* ID TECH Vendor ID */
+#define IDTECH_IDT1221U_PID 0x0300 /* IDT1221U USB to RS-232 adapter */
+
+/*
+ * Definitions for Omnidirectional Control Technology, Inc. devices
+ */
+#define OCT_VID 0x0B39 /* OCT vendor ID */
+/* Note: OCT US101 is also rebadged as Dick Smith Electronics (NZ) XH6381 */
+/* Also rebadged as Dick Smith Electronics (Aus) XH6451 */
+/* Also rebadged as SIIG Inc. model US2308 hardware version 1 */
+#define OCT_DK201_PID 0x0103 /* OCT DK201 USB docking station */
+#define OCT_US101_PID 0x0421 /* OCT US101 USB to RS-232 */
+
+/*
+ * Definitions for Icom Inc. devices
+ */
+#define ICOM_VID 0x0C26 /* Icom vendor ID */
+/* Note: ID-1 is a communications tranceiver for HAM-radio operators */
+#define ICOM_ID_1_PID 0x0004 /* ID-1 USB to RS-232 */
+/* Note: OPC is an Optional cable to connect an Icom Tranceiver */
+#define ICOM_OPC_U_UC_PID 0x0018 /* OPC-478UC, OPC-1122U cloning cable */
+/* Note: ID-RP* devices are Icom Repeater Devices for HAM-radio */
+#define ICOM_ID_RP2C1_PID 0x0009 /* ID-RP2C Asset 1 to RS-232 */
+#define ICOM_ID_RP2C2_PID 0x000A /* ID-RP2C Asset 2 to RS-232 */
+#define ICOM_ID_RP2D_PID 0x000B /* ID-RP2D configuration port*/
+#define ICOM_ID_RP2VT_PID 0x000C /* ID-RP2V Transmit config port */
+#define ICOM_ID_RP2VR_PID 0x000D /* ID-RP2V Receive config port */
+#define ICOM_ID_RP4KVT_PID 0x0010 /* ID-RP4000V Transmit config port */
+#define ICOM_ID_RP4KVR_PID 0x0011 /* ID-RP4000V Receive config port */
+#define ICOM_ID_RP2KVT_PID 0x0012 /* ID-RP2000V Transmit config port */
+#define ICOM_ID_RP2KVR_PID 0x0013 /* ID-RP2000V Receive config port */
+
+/*
+ * GN Otometrics (http://www.otometrics.com)
+ * Submitted by Ville Sundberg.
+ */
+#define GN_OTOMETRICS_VID 0x0c33 /* Vendor ID */
+#define AURICAL_USB_PID 0x0010 /* Aurical USB Audiometer */
+
+/*
+ * The following are the values for the Sealevel SeaLINK+ adapters.
+ * (Original list sent by Tuan Hoang. Ian Abbott renamed the macros and
+ * removed some PIDs that don't seem to match any existing products.)
+ */
+#define SEALEVEL_VID 0x0c52 /* Sealevel Vendor ID */
+#define SEALEVEL_2101_PID 0x2101 /* SeaLINK+232 (2101/2105) */
+#define SEALEVEL_2102_PID 0x2102 /* SeaLINK+485 (2102) */
+#define SEALEVEL_2103_PID 0x2103 /* SeaLINK+232I (2103) */
+#define SEALEVEL_2104_PID 0x2104 /* SeaLINK+485I (2104) */
+#define SEALEVEL_2106_PID 0x9020 /* SeaLINK+422 (2106) */
+#define SEALEVEL_2201_1_PID 0x2211 /* SeaPORT+2/232 (2201) Port 1 */
+#define SEALEVEL_2201_2_PID 0x2221 /* SeaPORT+2/232 (2201) Port 2 */
+#define SEALEVEL_2202_1_PID 0x2212 /* SeaPORT+2/485 (2202) Port 1 */
+#define SEALEVEL_2202_2_PID 0x2222 /* SeaPORT+2/485 (2202) Port 2 */
+#define SEALEVEL_2203_1_PID 0x2213 /* SeaPORT+2 (2203) Port 1 */
+#define SEALEVEL_2203_2_PID 0x2223 /* SeaPORT+2 (2203) Port 2 */
+#define SEALEVEL_2401_1_PID 0x2411 /* SeaPORT+4/232 (2401) Port 1 */
+#define SEALEVEL_2401_2_PID 0x2421 /* SeaPORT+4/232 (2401) Port 2 */
+#define SEALEVEL_2401_3_PID 0x2431 /* SeaPORT+4/232 (2401) Port 3 */
+#define SEALEVEL_2401_4_PID 0x2441 /* SeaPORT+4/232 (2401) Port 4 */
+#define SEALEVEL_2402_1_PID 0x2412 /* SeaPORT+4/485 (2402) Port 1 */
+#define SEALEVEL_2402_2_PID 0x2422 /* SeaPORT+4/485 (2402) Port 2 */
+#define SEALEVEL_2402_3_PID 0x2432 /* SeaPORT+4/485 (2402) Port 3 */
+#define SEALEVEL_2402_4_PID 0x2442 /* SeaPORT+4/485 (2402) Port 4 */
+#define SEALEVEL_2403_1_PID 0x2413 /* SeaPORT+4 (2403) Port 1 */
+#define SEALEVEL_2403_2_PID 0x2423 /* SeaPORT+4 (2403) Port 2 */
+#define SEALEVEL_2403_3_PID 0x2433 /* SeaPORT+4 (2403) Port 3 */
+#define SEALEVEL_2403_4_PID 0x2443 /* SeaPORT+4 (2403) Port 4 */
+#define SEALEVEL_2801_1_PID 0X2811 /* SeaLINK+8/232 (2801) Port 1 */
+#define SEALEVEL_2801_2_PID 0X2821 /* SeaLINK+8/232 (2801) Port 2 */
+#define SEALEVEL_2801_3_PID 0X2831 /* SeaLINK+8/232 (2801) Port 3 */
+#define SEALEVEL_2801_4_PID 0X2841 /* SeaLINK+8/232 (2801) Port 4 */
+#define SEALEVEL_2801_5_PID 0X2851 /* SeaLINK+8/232 (2801) Port 5 */
+#define SEALEVEL_2801_6_PID 0X2861 /* SeaLINK+8/232 (2801) Port 6 */
+#define SEALEVEL_2801_7_PID 0X2871 /* SeaLINK+8/232 (2801) Port 7 */
+#define SEALEVEL_2801_8_PID 0X2881 /* SeaLINK+8/232 (2801) Port 8 */
+#define SEALEVEL_2802_1_PID 0X2812 /* SeaLINK+8/485 (2802) Port 1 */
+#define SEALEVEL_2802_2_PID 0X2822 /* SeaLINK+8/485 (2802) Port 2 */
+#define SEALEVEL_2802_3_PID 0X2832 /* SeaLINK+8/485 (2802) Port 3 */
+#define SEALEVEL_2802_4_PID 0X2842 /* SeaLINK+8/485 (2802) Port 4 */
+#define SEALEVEL_2802_5_PID 0X2852 /* SeaLINK+8/485 (2802) Port 5 */
+#define SEALEVEL_2802_6_PID 0X2862 /* SeaLINK+8/485 (2802) Port 6 */
+#define SEALEVEL_2802_7_PID 0X2872 /* SeaLINK+8/485 (2802) Port 7 */
+#define SEALEVEL_2802_8_PID 0X2882 /* SeaLINK+8/485 (2802) Port 8 */
+#define SEALEVEL_2803_1_PID 0X2813 /* SeaLINK+8 (2803) Port 1 */
+#define SEALEVEL_2803_2_PID 0X2823 /* SeaLINK+8 (2803) Port 2 */
+#define SEALEVEL_2803_3_PID 0X2833 /* SeaLINK+8 (2803) Port 3 */
+#define SEALEVEL_2803_4_PID 0X2843 /* SeaLINK+8 (2803) Port 4 */
+#define SEALEVEL_2803_5_PID 0X2853 /* SeaLINK+8 (2803) Port 5 */
+#define SEALEVEL_2803_6_PID 0X2863 /* SeaLINK+8 (2803) Port 6 */
+#define SEALEVEL_2803_7_PID 0X2873 /* SeaLINK+8 (2803) Port 7 */
+#define SEALEVEL_2803_8_PID 0X2883 /* SeaLINK+8 (2803) Port 8 */
+#define SEALEVEL_2803R_1_PID 0Xa02a /* SeaLINK+8 (2803-ROHS) Port 1+2 */
+#define SEALEVEL_2803R_2_PID 0Xa02b /* SeaLINK+8 (2803-ROHS) Port 3+4 */
+#define SEALEVEL_2803R_3_PID 0Xa02c /* SeaLINK+8 (2803-ROHS) Port 5+6 */
+#define SEALEVEL_2803R_4_PID 0Xa02d /* SeaLINK+8 (2803-ROHS) Port 7+8 */
+
+/*
+ * JETI SPECTROMETER SPECBOS 1201
+ * http://www.jeti.com/cms/index.php/instruments/other-instruments/specbos-2101
+ */
+#define JETI_VID 0x0c6c
+#define JETI_SPC1201_PID 0x04b2
+
+/*
+ * FTDI USB UART chips used in construction projects from the
+ * Elektor Electronics magazine (http://www.elektor.com/)
+ */
+#define ELEKTOR_VID 0x0C7D
+#define ELEKTOR_FT323R_PID 0x0005 /* RFID-Reader, issue 09-2006 */
+
+/*
+ * Posiflex inc retail equipment (http://www.posiflex.com.tw)
+ */
+#define POSIFLEX_VID 0x0d3a /* Vendor ID */
+#define POSIFLEX_PP7000_PID 0x0300 /* PP-7000II thermal printer */
+
+/*
+ * The following are the values for two KOBIL chipcard terminals.
+ */
+#define KOBIL_VID 0x0d46 /* KOBIL Vendor ID */
+#define KOBIL_CONV_B1_PID 0x2020 /* KOBIL Konverter for B1 */
+#define KOBIL_CONV_KAAN_PID 0x2021 /* KOBIL_Konverter for KAAN */
+
+#define FTDI_NF_RIC_VID 0x0DCD /* Vendor Id */
+#define FTDI_NF_RIC_PID 0x0001 /* Product Id */
+
+/*
+ * Falcom Wireless Communications GmbH
+ */
+#define FALCOM_VID 0x0F94 /* Vendor Id */
+#define FALCOM_TWIST_PID 0x0001 /* Falcom Twist USB GPRS modem */
+#define FALCOM_SAMBA_PID 0x0005 /* Falcom Samba USB GPRS modem */
+
+/* Larsen and Brusgaard AltiTrack/USBtrack */
+#define LARSENBRUSGAARD_VID 0x0FD8
+#define LB_ALTITRACK_PID 0x0001
+
+/*
+ * TTi (Thurlby Thandar Instruments)
+ */
+#define TTI_VID 0x103E /* Vendor Id */
+#define TTI_QL355P_PID 0x03E8 /* TTi QL355P power supply */
+
+/*
+ * Newport Cooperation (www.newport.com)
+ */
+#define NEWPORT_VID 0x104D
+#define NEWPORT_AGILIS_PID 0x3000
+#define NEWPORT_CONEX_CC_PID 0x3002
+#define NEWPORT_CONEX_AGP_PID 0x3006
+
+/* Interbiometrics USB I/O Board */
+/* Developed for Interbiometrics by Rudolf Gugler */
+#define INTERBIOMETRICS_VID 0x1209
+#define INTERBIOMETRICS_IOBOARD_PID 0x1002
+#define INTERBIOMETRICS_MINI_IOBOARD_PID 0x1006
+
+/*
+ * Testo products (http://www.testo.com/)
+ * Submitted by Colin Leroy
+ */
+#define TESTO_VID 0x128D
+#define TESTO_1_PID 0x0001
+#define TESTO_3_PID 0x0003
+
+/*
+ * Mobility Electronics products.
+ */
+#define MOBILITY_VID 0x1342
+#define MOBILITY_USB_SERIAL_PID 0x0202 /* EasiDock USB 200 serial */
+
+/*
+ * FIC / OpenMoko, Inc. http://wiki.openmoko.org/wiki/Neo1973_Debug_Board_v3
+ * Submitted by Harald Welte <laforge@openmoko.org>
+ */
+#define FIC_VID 0x1457
+#define FIC_NEO1973_DEBUG_PID 0x5118
+
+/*
+ * Actel / Microsemi
+ */
+#define ACTEL_VID 0x1514
+#define MICROSEMI_ARROW_SF2PLUS_BOARD_PID 0x2008
+
+/* Olimex */
+#define OLIMEX_VID 0x15BA
+#define OLIMEX_ARM_USB_OCD_PID 0x0003
+#define OLIMEX_ARM_USB_TINY_PID 0x0004
+#define OLIMEX_ARM_USB_TINY_H_PID 0x002a
+#define OLIMEX_ARM_USB_OCD_H_PID 0x002b
+
+/*
+ * Telldus Technologies
+ */
+#define TELLDUS_VID 0x1781 /* Vendor ID */
+#define TELLDUS_TELLSTICK_PID 0x0C30 /* RF control dongle 433 MHz using FT232RL */
+
+/*
+ * NOVITUS printers
+ */
+#define NOVITUS_VID 0x1a28
+#define NOVITUS_BONO_E_PID 0x6010
+
+/*
+ * ICPDAS I-756*U devices
+ */
+#define ICPDAS_VID 0x1b5c
+#define ICPDAS_I7560U_PID 0x0103
+#define ICPDAS_I7561U_PID 0x0104
+#define ICPDAS_I7563U_PID 0x0105
+
+/*
+ * Airbus Defence and Space
+ */
+#define AIRBUS_DS_VID 0x1e8e /* Vendor ID */
+#define AIRBUS_DS_P8GR 0x6001 /* Tetra P8GR */
+
+/*
+ * RT Systems programming cables for various ham radios
+ */
+/* This device uses the VID of FTDI */
+#define RTSYSTEMS_USB_VX8_PID 0x9e50 /* USB-VX8 USB to 7 pin modular plug for Yaesu VX-8 radio */
+
+#define RTSYSTEMS_VID 0x2100 /* Vendor ID */
+#define RTSYSTEMS_USB_S03_PID 0x9001 /* RTS-03 USB to Serial Adapter */
+#define RTSYSTEMS_USB_59_PID 0x9e50 /* USB-59 USB to 8 pin plug */
+#define RTSYSTEMS_USB_57A_PID 0x9e51 /* USB-57A USB to 4pin 3.5mm plug */
+#define RTSYSTEMS_USB_57B_PID 0x9e52 /* USB-57B USB to extended 4pin 3.5mm plug */
+#define RTSYSTEMS_USB_29A_PID 0x9e53 /* USB-29A USB to 3.5mm stereo plug */
+#define RTSYSTEMS_USB_29B_PID 0x9e54 /* USB-29B USB to 6 pin mini din */
+#define RTSYSTEMS_USB_29F_PID 0x9e55 /* USB-29F USB to 6 pin modular plug */
+#define RTSYSTEMS_USB_62B_PID 0x9e56 /* USB-62B USB to 8 pin mini din plug*/
+#define RTSYSTEMS_USB_S01_PID 0x9e57 /* USB-RTS01 USB to 3.5 mm stereo plug*/
+#define RTSYSTEMS_USB_63_PID 0x9e58 /* USB-63 USB to 9 pin female*/
+#define RTSYSTEMS_USB_29C_PID 0x9e59 /* USB-29C USB to 4 pin modular plug*/
+#define RTSYSTEMS_USB_81B_PID 0x9e5A /* USB-81 USB to 8 pin mini din plug*/
+#define RTSYSTEMS_USB_82B_PID 0x9e5B /* USB-82 USB to 2.5 mm stereo plug*/
+#define RTSYSTEMS_USB_K5D_PID 0x9e5C /* USB-K5D USB to 8 pin modular plug*/
+#define RTSYSTEMS_USB_K4Y_PID 0x9e5D /* USB-K4Y USB to 2.5/3.5 mm plugs*/
+#define RTSYSTEMS_USB_K5G_PID 0x9e5E /* USB-K5G USB to 8 pin modular plug*/
+#define RTSYSTEMS_USB_S05_PID 0x9e5F /* USB-RTS05 USB to 2.5 mm stereo plug*/
+#define RTSYSTEMS_USB_60_PID 0x9e60 /* USB-60 USB to 6 pin din*/
+#define RTSYSTEMS_USB_61_PID 0x9e61 /* USB-61 USB to 6 pin mini din*/
+#define RTSYSTEMS_USB_62_PID 0x9e62 /* USB-62 USB to 8 pin mini din*/
+#define RTSYSTEMS_USB_63B_PID 0x9e63 /* USB-63 USB to 9 pin female*/
+#define RTSYSTEMS_USB_64_PID 0x9e64 /* USB-64 USB to 9 pin male*/
+#define RTSYSTEMS_USB_65_PID 0x9e65 /* USB-65 USB to 9 pin female null modem*/
+#define RTSYSTEMS_USB_92_PID 0x9e66 /* USB-92 USB to 12 pin plug*/
+#define RTSYSTEMS_USB_92D_PID 0x9e67 /* USB-92D USB to 12 pin plug data*/
+#define RTSYSTEMS_USB_W5R_PID 0x9e68 /* USB-W5R USB to 8 pin modular plug*/
+#define RTSYSTEMS_USB_A5R_PID 0x9e69 /* USB-A5R USB to 8 pin modular plug*/
+#define RTSYSTEMS_USB_PW1_PID 0x9e6A /* USB-PW1 USB to 8 pin modular plug*/
+
+/*
+ * Physik Instrumente
+ * http://www.physikinstrumente.com/en/products/
+ */
+/* These two devices use the VID of FTDI */
+#define PI_C865_PID 0xe0a0 /* PI C-865 Piezomotor Controller */
+#define PI_C857_PID 0xe0a1 /* PI Encoder Trigger Box */
+
+#define PI_VID 0x1a72 /* Vendor ID */
+#define PI_C866_PID 0x1000 /* PI C-866 Piezomotor Controller */
+#define PI_C663_PID 0x1001 /* PI C-663 Mercury-Step */
+#define PI_C725_PID 0x1002 /* PI C-725 Piezomotor Controller */
+#define PI_E517_PID 0x1005 /* PI E-517 Digital Piezo Controller Operation Module */
+#define PI_C863_PID 0x1007 /* PI C-863 */
+#define PI_E861_PID 0x1008 /* PI E-861 Piezomotor Controller */
+#define PI_C867_PID 0x1009 /* PI C-867 Piezomotor Controller */
+#define PI_E609_PID 0x100D /* PI E-609 Digital Piezo Controller */
+#define PI_E709_PID 0x100E /* PI E-709 Digital Piezo Controller */
+#define PI_100F_PID 0x100F /* PI Digital Piezo Controller */
+#define PI_1011_PID 0x1011 /* PI Digital Piezo Controller */
+#define PI_1012_PID 0x1012 /* PI Motion Controller */
+#define PI_1013_PID 0x1013 /* PI Motion Controller */
+#define PI_1014_PID 0x1014 /* PI Device */
+#define PI_1015_PID 0x1015 /* PI Device */
+#define PI_1016_PID 0x1016 /* PI Digital Servo Module */
+
+/*
+ * Kondo Kagaku Co.Ltd.
+ * http://www.kondo-robot.com/EN
+ */
+#define KONDO_VID 0x165c
+#define KONDO_USB_SERIAL_PID 0x0002
+
+/*
+ * Bayer Ascensia Contour blood glucose meter USB-converter cable.
+ * http://winglucofacts.com/cables/
+ */
+#define BAYER_VID 0x1A79
+#define BAYER_CONTOUR_CABLE_PID 0x6001
+
+/*
+ * Matrix Orbital Intelligent USB displays.
+ * http://www.matrixorbital.com
+ */
+#define MTXORB_VID 0x1B3D
+#define MTXORB_FTDI_RANGE_0100_PID 0x0100
+#define MTXORB_FTDI_RANGE_0101_PID 0x0101
+#define MTXORB_FTDI_RANGE_0102_PID 0x0102
+#define MTXORB_FTDI_RANGE_0103_PID 0x0103
+#define MTXORB_FTDI_RANGE_0104_PID 0x0104
+#define MTXORB_FTDI_RANGE_0105_PID 0x0105
+#define MTXORB_FTDI_RANGE_0106_PID 0x0106
+#define MTXORB_FTDI_RANGE_0107_PID 0x0107
+#define MTXORB_FTDI_RANGE_0108_PID 0x0108
+#define MTXORB_FTDI_RANGE_0109_PID 0x0109
+#define MTXORB_FTDI_RANGE_010A_PID 0x010A
+#define MTXORB_FTDI_RANGE_010B_PID 0x010B
+#define MTXORB_FTDI_RANGE_010C_PID 0x010C
+#define MTXORB_FTDI_RANGE_010D_PID 0x010D
+#define MTXORB_FTDI_RANGE_010E_PID 0x010E
+#define MTXORB_FTDI_RANGE_010F_PID 0x010F
+#define MTXORB_FTDI_RANGE_0110_PID 0x0110
+#define MTXORB_FTDI_RANGE_0111_PID 0x0111
+#define MTXORB_FTDI_RANGE_0112_PID 0x0112
+#define MTXORB_FTDI_RANGE_0113_PID 0x0113
+#define MTXORB_FTDI_RANGE_0114_PID 0x0114
+#define MTXORB_FTDI_RANGE_0115_PID 0x0115
+#define MTXORB_FTDI_RANGE_0116_PID 0x0116
+#define MTXORB_FTDI_RANGE_0117_PID 0x0117
+#define MTXORB_FTDI_RANGE_0118_PID 0x0118
+#define MTXORB_FTDI_RANGE_0119_PID 0x0119
+#define MTXORB_FTDI_RANGE_011A_PID 0x011A
+#define MTXORB_FTDI_RANGE_011B_PID 0x011B
+#define MTXORB_FTDI_RANGE_011C_PID 0x011C
+#define MTXORB_FTDI_RANGE_011D_PID 0x011D
+#define MTXORB_FTDI_RANGE_011E_PID 0x011E
+#define MTXORB_FTDI_RANGE_011F_PID 0x011F
+#define MTXORB_FTDI_RANGE_0120_PID 0x0120
+#define MTXORB_FTDI_RANGE_0121_PID 0x0121
+#define MTXORB_FTDI_RANGE_0122_PID 0x0122
+#define MTXORB_FTDI_RANGE_0123_PID 0x0123
+#define MTXORB_FTDI_RANGE_0124_PID 0x0124
+#define MTXORB_FTDI_RANGE_0125_PID 0x0125
+#define MTXORB_FTDI_RANGE_0126_PID 0x0126
+#define MTXORB_FTDI_RANGE_0127_PID 0x0127
+#define MTXORB_FTDI_RANGE_0128_PID 0x0128
+#define MTXORB_FTDI_RANGE_0129_PID 0x0129
+#define MTXORB_FTDI_RANGE_012A_PID 0x012A
+#define MTXORB_FTDI_RANGE_012B_PID 0x012B
+#define MTXORB_FTDI_RANGE_012C_PID 0x012C
+#define MTXORB_FTDI_RANGE_012D_PID 0x012D
+#define MTXORB_FTDI_RANGE_012E_PID 0x012E
+#define MTXORB_FTDI_RANGE_012F_PID 0x012F
+#define MTXORB_FTDI_RANGE_0130_PID 0x0130
+#define MTXORB_FTDI_RANGE_0131_PID 0x0131
+#define MTXORB_FTDI_RANGE_0132_PID 0x0132
+#define MTXORB_FTDI_RANGE_0133_PID 0x0133
+#define MTXORB_FTDI_RANGE_0134_PID 0x0134
+#define MTXORB_FTDI_RANGE_0135_PID 0x0135
+#define MTXORB_FTDI_RANGE_0136_PID 0x0136
+#define MTXORB_FTDI_RANGE_0137_PID 0x0137
+#define MTXORB_FTDI_RANGE_0138_PID 0x0138
+#define MTXORB_FTDI_RANGE_0139_PID 0x0139
+#define MTXORB_FTDI_RANGE_013A_PID 0x013A
+#define MTXORB_FTDI_RANGE_013B_PID 0x013B
+#define MTXORB_FTDI_RANGE_013C_PID 0x013C
+#define MTXORB_FTDI_RANGE_013D_PID 0x013D
+#define MTXORB_FTDI_RANGE_013E_PID 0x013E
+#define MTXORB_FTDI_RANGE_013F_PID 0x013F
+#define MTXORB_FTDI_RANGE_0140_PID 0x0140
+#define MTXORB_FTDI_RANGE_0141_PID 0x0141
+#define MTXORB_FTDI_RANGE_0142_PID 0x0142
+#define MTXORB_FTDI_RANGE_0143_PID 0x0143
+#define MTXORB_FTDI_RANGE_0144_PID 0x0144
+#define MTXORB_FTDI_RANGE_0145_PID 0x0145
+#define MTXORB_FTDI_RANGE_0146_PID 0x0146
+#define MTXORB_FTDI_RANGE_0147_PID 0x0147
+#define MTXORB_FTDI_RANGE_0148_PID 0x0148
+#define MTXORB_FTDI_RANGE_0149_PID 0x0149
+#define MTXORB_FTDI_RANGE_014A_PID 0x014A
+#define MTXORB_FTDI_RANGE_014B_PID 0x014B
+#define MTXORB_FTDI_RANGE_014C_PID 0x014C
+#define MTXORB_FTDI_RANGE_014D_PID 0x014D
+#define MTXORB_FTDI_RANGE_014E_PID 0x014E
+#define MTXORB_FTDI_RANGE_014F_PID 0x014F
+#define MTXORB_FTDI_RANGE_0150_PID 0x0150
+#define MTXORB_FTDI_RANGE_0151_PID 0x0151
+#define MTXORB_FTDI_RANGE_0152_PID 0x0152
+#define MTXORB_FTDI_RANGE_0153_PID 0x0153
+#define MTXORB_FTDI_RANGE_0154_PID 0x0154
+#define MTXORB_FTDI_RANGE_0155_PID 0x0155
+#define MTXORB_FTDI_RANGE_0156_PID 0x0156
+#define MTXORB_FTDI_RANGE_0157_PID 0x0157
+#define MTXORB_FTDI_RANGE_0158_PID 0x0158
+#define MTXORB_FTDI_RANGE_0159_PID 0x0159
+#define MTXORB_FTDI_RANGE_015A_PID 0x015A
+#define MTXORB_FTDI_RANGE_015B_PID 0x015B
+#define MTXORB_FTDI_RANGE_015C_PID 0x015C
+#define MTXORB_FTDI_RANGE_015D_PID 0x015D
+#define MTXORB_FTDI_RANGE_015E_PID 0x015E
+#define MTXORB_FTDI_RANGE_015F_PID 0x015F
+#define MTXORB_FTDI_RANGE_0160_PID 0x0160
+#define MTXORB_FTDI_RANGE_0161_PID 0x0161
+#define MTXORB_FTDI_RANGE_0162_PID 0x0162
+#define MTXORB_FTDI_RANGE_0163_PID 0x0163
+#define MTXORB_FTDI_RANGE_0164_PID 0x0164
+#define MTXORB_FTDI_RANGE_0165_PID 0x0165
+#define MTXORB_FTDI_RANGE_0166_PID 0x0166
+#define MTXORB_FTDI_RANGE_0167_PID 0x0167
+#define MTXORB_FTDI_RANGE_0168_PID 0x0168
+#define MTXORB_FTDI_RANGE_0169_PID 0x0169
+#define MTXORB_FTDI_RANGE_016A_PID 0x016A
+#define MTXORB_FTDI_RANGE_016B_PID 0x016B
+#define MTXORB_FTDI_RANGE_016C_PID 0x016C
+#define MTXORB_FTDI_RANGE_016D_PID 0x016D
+#define MTXORB_FTDI_RANGE_016E_PID 0x016E
+#define MTXORB_FTDI_RANGE_016F_PID 0x016F
+#define MTXORB_FTDI_RANGE_0170_PID 0x0170
+#define MTXORB_FTDI_RANGE_0171_PID 0x0171
+#define MTXORB_FTDI_RANGE_0172_PID 0x0172
+#define MTXORB_FTDI_RANGE_0173_PID 0x0173
+#define MTXORB_FTDI_RANGE_0174_PID 0x0174
+#define MTXORB_FTDI_RANGE_0175_PID 0x0175
+#define MTXORB_FTDI_RANGE_0176_PID 0x0176
+#define MTXORB_FTDI_RANGE_0177_PID 0x0177
+#define MTXORB_FTDI_RANGE_0178_PID 0x0178
+#define MTXORB_FTDI_RANGE_0179_PID 0x0179
+#define MTXORB_FTDI_RANGE_017A_PID 0x017A
+#define MTXORB_FTDI_RANGE_017B_PID 0x017B
+#define MTXORB_FTDI_RANGE_017C_PID 0x017C
+#define MTXORB_FTDI_RANGE_017D_PID 0x017D
+#define MTXORB_FTDI_RANGE_017E_PID 0x017E
+#define MTXORB_FTDI_RANGE_017F_PID 0x017F
+#define MTXORB_FTDI_RANGE_0180_PID 0x0180
+#define MTXORB_FTDI_RANGE_0181_PID 0x0181
+#define MTXORB_FTDI_RANGE_0182_PID 0x0182
+#define MTXORB_FTDI_RANGE_0183_PID 0x0183
+#define MTXORB_FTDI_RANGE_0184_PID 0x0184
+#define MTXORB_FTDI_RANGE_0185_PID 0x0185
+#define MTXORB_FTDI_RANGE_0186_PID 0x0186
+#define MTXORB_FTDI_RANGE_0187_PID 0x0187
+#define MTXORB_FTDI_RANGE_0188_PID 0x0188
+#define MTXORB_FTDI_RANGE_0189_PID 0x0189
+#define MTXORB_FTDI_RANGE_018A_PID 0x018A
+#define MTXORB_FTDI_RANGE_018B_PID 0x018B
+#define MTXORB_FTDI_RANGE_018C_PID 0x018C
+#define MTXORB_FTDI_RANGE_018D_PID 0x018D
+#define MTXORB_FTDI_RANGE_018E_PID 0x018E
+#define MTXORB_FTDI_RANGE_018F_PID 0x018F
+#define MTXORB_FTDI_RANGE_0190_PID 0x0190
+#define MTXORB_FTDI_RANGE_0191_PID 0x0191
+#define MTXORB_FTDI_RANGE_0192_PID 0x0192
+#define MTXORB_FTDI_RANGE_0193_PID 0x0193
+#define MTXORB_FTDI_RANGE_0194_PID 0x0194
+#define MTXORB_FTDI_RANGE_0195_PID 0x0195
+#define MTXORB_FTDI_RANGE_0196_PID 0x0196
+#define MTXORB_FTDI_RANGE_0197_PID 0x0197
+#define MTXORB_FTDI_RANGE_0198_PID 0x0198
+#define MTXORB_FTDI_RANGE_0199_PID 0x0199
+#define MTXORB_FTDI_RANGE_019A_PID 0x019A
+#define MTXORB_FTDI_RANGE_019B_PID 0x019B
+#define MTXORB_FTDI_RANGE_019C_PID 0x019C
+#define MTXORB_FTDI_RANGE_019D_PID 0x019D
+#define MTXORB_FTDI_RANGE_019E_PID 0x019E
+#define MTXORB_FTDI_RANGE_019F_PID 0x019F
+#define MTXORB_FTDI_RANGE_01A0_PID 0x01A0
+#define MTXORB_FTDI_RANGE_01A1_PID 0x01A1
+#define MTXORB_FTDI_RANGE_01A2_PID 0x01A2
+#define MTXORB_FTDI_RANGE_01A3_PID 0x01A3
+#define MTXORB_FTDI_RANGE_01A4_PID 0x01A4
+#define MTXORB_FTDI_RANGE_01A5_PID 0x01A5
+#define MTXORB_FTDI_RANGE_01A6_PID 0x01A6
+#define MTXORB_FTDI_RANGE_01A7_PID 0x01A7
+#define MTXORB_FTDI_RANGE_01A8_PID 0x01A8
+#define MTXORB_FTDI_RANGE_01A9_PID 0x01A9
+#define MTXORB_FTDI_RANGE_01AA_PID 0x01AA
+#define MTXORB_FTDI_RANGE_01AB_PID 0x01AB
+#define MTXORB_FTDI_RANGE_01AC_PID 0x01AC
+#define MTXORB_FTDI_RANGE_01AD_PID 0x01AD
+#define MTXORB_FTDI_RANGE_01AE_PID 0x01AE
+#define MTXORB_FTDI_RANGE_01AF_PID 0x01AF
+#define MTXORB_FTDI_RANGE_01B0_PID 0x01B0
+#define MTXORB_FTDI_RANGE_01B1_PID 0x01B1
+#define MTXORB_FTDI_RANGE_01B2_PID 0x01B2
+#define MTXORB_FTDI_RANGE_01B3_PID 0x01B3
+#define MTXORB_FTDI_RANGE_01B4_PID 0x01B4
+#define MTXORB_FTDI_RANGE_01B5_PID 0x01B5
+#define MTXORB_FTDI_RANGE_01B6_PID 0x01B6
+#define MTXORB_FTDI_RANGE_01B7_PID 0x01B7
+#define MTXORB_FTDI_RANGE_01B8_PID 0x01B8
+#define MTXORB_FTDI_RANGE_01B9_PID 0x01B9
+#define MTXORB_FTDI_RANGE_01BA_PID 0x01BA
+#define MTXORB_FTDI_RANGE_01BB_PID 0x01BB
+#define MTXORB_FTDI_RANGE_01BC_PID 0x01BC
+#define MTXORB_FTDI_RANGE_01BD_PID 0x01BD
+#define MTXORB_FTDI_RANGE_01BE_PID 0x01BE
+#define MTXORB_FTDI_RANGE_01BF_PID 0x01BF
+#define MTXORB_FTDI_RANGE_01C0_PID 0x01C0
+#define MTXORB_FTDI_RANGE_01C1_PID 0x01C1
+#define MTXORB_FTDI_RANGE_01C2_PID 0x01C2
+#define MTXORB_FTDI_RANGE_01C3_PID 0x01C3
+#define MTXORB_FTDI_RANGE_01C4_PID 0x01C4
+#define MTXORB_FTDI_RANGE_01C5_PID 0x01C5
+#define MTXORB_FTDI_RANGE_01C6_PID 0x01C6
+#define MTXORB_FTDI_RANGE_01C7_PID 0x01C7
+#define MTXORB_FTDI_RANGE_01C8_PID 0x01C8
+#define MTXORB_FTDI_RANGE_01C9_PID 0x01C9
+#define MTXORB_FTDI_RANGE_01CA_PID 0x01CA
+#define MTXORB_FTDI_RANGE_01CB_PID 0x01CB
+#define MTXORB_FTDI_RANGE_01CC_PID 0x01CC
+#define MTXORB_FTDI_RANGE_01CD_PID 0x01CD
+#define MTXORB_FTDI_RANGE_01CE_PID 0x01CE
+#define MTXORB_FTDI_RANGE_01CF_PID 0x01CF
+#define MTXORB_FTDI_RANGE_01D0_PID 0x01D0
+#define MTXORB_FTDI_RANGE_01D1_PID 0x01D1
+#define MTXORB_FTDI_RANGE_01D2_PID 0x01D2
+#define MTXORB_FTDI_RANGE_01D3_PID 0x01D3
+#define MTXORB_FTDI_RANGE_01D4_PID 0x01D4
+#define MTXORB_FTDI_RANGE_01D5_PID 0x01D5
+#define MTXORB_FTDI_RANGE_01D6_PID 0x01D6
+#define MTXORB_FTDI_RANGE_01D7_PID 0x01D7
+#define MTXORB_FTDI_RANGE_01D8_PID 0x01D8
+#define MTXORB_FTDI_RANGE_01D9_PID 0x01D9
+#define MTXORB_FTDI_RANGE_01DA_PID 0x01DA
+#define MTXORB_FTDI_RANGE_01DB_PID 0x01DB
+#define MTXORB_FTDI_RANGE_01DC_PID 0x01DC
+#define MTXORB_FTDI_RANGE_01DD_PID 0x01DD
+#define MTXORB_FTDI_RANGE_01DE_PID 0x01DE
+#define MTXORB_FTDI_RANGE_01DF_PID 0x01DF
+#define MTXORB_FTDI_RANGE_01E0_PID 0x01E0
+#define MTXORB_FTDI_RANGE_01E1_PID 0x01E1
+#define MTXORB_FTDI_RANGE_01E2_PID 0x01E2
+#define MTXORB_FTDI_RANGE_01E3_PID 0x01E3
+#define MTXORB_FTDI_RANGE_01E4_PID 0x01E4
+#define MTXORB_FTDI_RANGE_01E5_PID 0x01E5
+#define MTXORB_FTDI_RANGE_01E6_PID 0x01E6
+#define MTXORB_FTDI_RANGE_01E7_PID 0x01E7
+#define MTXORB_FTDI_RANGE_01E8_PID 0x01E8
+#define MTXORB_FTDI_RANGE_01E9_PID 0x01E9
+#define MTXORB_FTDI_RANGE_01EA_PID 0x01EA
+#define MTXORB_FTDI_RANGE_01EB_PID 0x01EB
+#define MTXORB_FTDI_RANGE_01EC_PID 0x01EC
+#define MTXORB_FTDI_RANGE_01ED_PID 0x01ED
+#define MTXORB_FTDI_RANGE_01EE_PID 0x01EE
+#define MTXORB_FTDI_RANGE_01EF_PID 0x01EF
+#define MTXORB_FTDI_RANGE_01F0_PID 0x01F0
+#define MTXORB_FTDI_RANGE_01F1_PID 0x01F1
+#define MTXORB_FTDI_RANGE_01F2_PID 0x01F2
+#define MTXORB_FTDI_RANGE_01F3_PID 0x01F3
+#define MTXORB_FTDI_RANGE_01F4_PID 0x01F4
+#define MTXORB_FTDI_RANGE_01F5_PID 0x01F5
+#define MTXORB_FTDI_RANGE_01F6_PID 0x01F6
+#define MTXORB_FTDI_RANGE_01F7_PID 0x01F7
+#define MTXORB_FTDI_RANGE_01F8_PID 0x01F8
+#define MTXORB_FTDI_RANGE_01F9_PID 0x01F9
+#define MTXORB_FTDI_RANGE_01FA_PID 0x01FA
+#define MTXORB_FTDI_RANGE_01FB_PID 0x01FB
+#define MTXORB_FTDI_RANGE_01FC_PID 0x01FC
+#define MTXORB_FTDI_RANGE_01FD_PID 0x01FD
+#define MTXORB_FTDI_RANGE_01FE_PID 0x01FE
+#define MTXORB_FTDI_RANGE_01FF_PID 0x01FF
+#define MTXORB_FTDI_RANGE_4701_PID 0x4701
+#define MTXORB_FTDI_RANGE_9300_PID 0x9300
+#define MTXORB_FTDI_RANGE_9301_PID 0x9301
+#define MTXORB_FTDI_RANGE_9302_PID 0x9302
+#define MTXORB_FTDI_RANGE_9303_PID 0x9303
+#define MTXORB_FTDI_RANGE_9304_PID 0x9304
+#define MTXORB_FTDI_RANGE_9305_PID 0x9305
+#define MTXORB_FTDI_RANGE_9306_PID 0x9306
+#define MTXORB_FTDI_RANGE_9307_PID 0x9307
+#define MTXORB_FTDI_RANGE_9308_PID 0x9308
+#define MTXORB_FTDI_RANGE_9309_PID 0x9309
+#define MTXORB_FTDI_RANGE_930A_PID 0x930A
+#define MTXORB_FTDI_RANGE_930B_PID 0x930B
+#define MTXORB_FTDI_RANGE_930C_PID 0x930C
+#define MTXORB_FTDI_RANGE_930D_PID 0x930D
+#define MTXORB_FTDI_RANGE_930E_PID 0x930E
+#define MTXORB_FTDI_RANGE_930F_PID 0x930F
+#define MTXORB_FTDI_RANGE_9310_PID 0x9310
+#define MTXORB_FTDI_RANGE_9311_PID 0x9311
+#define MTXORB_FTDI_RANGE_9312_PID 0x9312
+#define MTXORB_FTDI_RANGE_9313_PID 0x9313
+#define MTXORB_FTDI_RANGE_9314_PID 0x9314
+#define MTXORB_FTDI_RANGE_9315_PID 0x9315
+#define MTXORB_FTDI_RANGE_9316_PID 0x9316
+#define MTXORB_FTDI_RANGE_9317_PID 0x9317
+#define MTXORB_FTDI_RANGE_9318_PID 0x9318
+#define MTXORB_FTDI_RANGE_9319_PID 0x9319
+#define MTXORB_FTDI_RANGE_931A_PID 0x931A
+#define MTXORB_FTDI_RANGE_931B_PID 0x931B
+#define MTXORB_FTDI_RANGE_931C_PID 0x931C
+#define MTXORB_FTDI_RANGE_931D_PID 0x931D
+#define MTXORB_FTDI_RANGE_931E_PID 0x931E
+#define MTXORB_FTDI_RANGE_931F_PID 0x931F
+
+/*
+ * The Mobility Lab (TML)
+ * Submitted by Pierre Castella
+ */
+#define TML_VID 0x1B91 /* Vendor ID */
+#define TML_USB_SERIAL_PID 0x0064 /* USB - Serial Converter */
+
+/* Alti-2 products http://www.alti-2.com */
+#define ALTI2_VID 0x1BC9
+#define ALTI2_N3_PID 0x6001 /* Neptune 3 */
+
+/*
+ * Ionics PlugComputer
+ */
+#define IONICS_VID 0x1c0c
+#define IONICS_PLUGCOMPUTER_PID 0x0102
+
+/*
+ * EZPrototypes (PID reseller)
+ */
+#define EZPROTOTYPES_VID 0x1c40
+#define HJELMSLUND_USB485_ISO_PID 0x0477
+
+/*
+ * Dresden Elektronik Sensor Terminal Board
+ */
+#define DE_VID 0x1cf1 /* Vendor ID */
+#define STB_PID 0x0001 /* Sensor Terminal Board */
+#define WHT_PID 0x0004 /* Wireless Handheld Terminal */
+
+/*
+ * STMicroelectonics
+ */
+#define ST_VID 0x0483
+#define ST_STMCLT_2232_PID 0x3746
+#define ST_STMCLT_4232_PID 0x3747
+
+/*
+ * Papouch products (http://www.papouch.com/)
+ * Submitted by Folkert van Heusden
+ */
+
+#define PAPOUCH_VID 0x5050 /* Vendor ID */
+#define PAPOUCH_SB485_PID 0x0100 /* Papouch SB485 USB-485/422 Converter */
+#define PAPOUCH_AP485_PID 0x0101 /* AP485 USB-RS485 Converter */
+#define PAPOUCH_SB422_PID 0x0102 /* Papouch SB422 USB-RS422 Converter */
+#define PAPOUCH_SB485_2_PID 0x0103 /* Papouch SB485 USB-485/422 Converter */
+#define PAPOUCH_AP485_2_PID 0x0104 /* AP485 USB-RS485 Converter */
+#define PAPOUCH_SB422_2_PID 0x0105 /* Papouch SB422 USB-RS422 Converter */
+#define PAPOUCH_SB485S_PID 0x0106 /* Papouch SB485S USB-485/422 Converter */
+#define PAPOUCH_SB485C_PID 0x0107 /* Papouch SB485C USB-485/422 Converter */
+#define PAPOUCH_LEC_PID 0x0300 /* LEC USB Converter */
+#define PAPOUCH_SB232_PID 0x0301 /* Papouch SB232 USB-RS232 Converter */
+#define PAPOUCH_TMU_PID 0x0400 /* TMU USB Thermometer */
+#define PAPOUCH_IRAMP_PID 0x0500 /* Papouch IRAmp Duplex */
+#define PAPOUCH_DRAK5_PID 0x0700 /* Papouch DRAK5 */
+#define PAPOUCH_QUIDO8x8_PID 0x0800 /* Papouch Quido 8/8 Module */
+#define PAPOUCH_QUIDO4x4_PID 0x0900 /* Papouch Quido 4/4 Module */
+#define PAPOUCH_QUIDO2x2_PID 0x0a00 /* Papouch Quido 2/2 Module */
+#define PAPOUCH_QUIDO10x1_PID 0x0b00 /* Papouch Quido 10/1 Module */
+#define PAPOUCH_QUIDO30x3_PID 0x0c00 /* Papouch Quido 30/3 Module */
+#define PAPOUCH_QUIDO60x3_PID 0x0d00 /* Papouch Quido 60(100)/3 Module */
+#define PAPOUCH_QUIDO2x16_PID 0x0e00 /* Papouch Quido 2/16 Module */
+#define PAPOUCH_QUIDO3x32_PID 0x0f00 /* Papouch Quido 3/32 Module */
+#define PAPOUCH_DRAK6_PID 0x1000 /* Papouch DRAK6 */
+#define PAPOUCH_UPSUSB_PID 0x8000 /* Papouch UPS-USB adapter */
+#define PAPOUCH_MU_PID 0x8001 /* MU controller */
+#define PAPOUCH_SIMUKEY_PID 0x8002 /* Papouch SimuKey */
+#define PAPOUCH_AD4USB_PID 0x8003 /* AD4USB Measurement Module */
+#define PAPOUCH_GMUX_PID 0x8004 /* Papouch GOLIATH MUX */
+#define PAPOUCH_GMSR_PID 0x8005 /* Papouch GOLIATH MSR */
+
+/*
+ * Marvell SheevaPlug
+ */
+#define MARVELL_VID 0x9e88
+#define MARVELL_SHEEVAPLUG_PID 0x9e8f
+
+/*
+ * Evolution Robotics products (http://www.evolution.com/).
+ * Submitted by Shawn M. Lavelle.
+ */
+#define EVOLUTION_VID 0xDEEE /* Vendor ID */
+#define EVOLUTION_ER1_PID 0x0300 /* ER1 Control Module */
+#define EVO_8U232AM_PID 0x02FF /* Evolution robotics RCM2 (FT232AM)*/
+#define EVO_HYBRID_PID 0x0302 /* Evolution robotics RCM4 PID (FT232BM)*/
+#define EVO_RCM4_PID 0x0303 /* Evolution robotics RCM4 PID */
+
+/*
+ * MJS Gadgets HD Radio / XM Radio / Sirius Radio interfaces (using VID 0x0403)
+ */
+#define MJSG_GENERIC_PID 0x9378
+#define MJSG_SR_RADIO_PID 0x9379
+#define MJSG_XM_RADIO_PID 0x937A
+#define MJSG_HD_RADIO_PID 0x937C
+
+/*
+ * D.O.Tec products (http://www.directout.eu)
+ */
+#define FTDI_DOTEC_PID 0x9868
+
+/*
+ * Xverve Signalyzer tools (http://www.signalyzer.com/)
+ */
+#define XVERVE_SIGNALYZER_ST_PID 0xBCA0
+#define XVERVE_SIGNALYZER_SLITE_PID 0xBCA1
+#define XVERVE_SIGNALYZER_SH2_PID 0xBCA2
+#define XVERVE_SIGNALYZER_SH4_PID 0xBCA4
+
+/*
+ * Segway Robotic Mobility Platform USB interface (using VID 0x0403)
+ * Submitted by John G. Rogers
+ */
+#define SEGWAY_RMP200_PID 0xe729
+
+
+/*
+ * Accesio USB Data Acquisition products (http://www.accesio.com/)
+ */
+#define ACCESIO_COM4SM_PID 0xD578
+
+/* www.sciencescope.co.uk educational dataloggers */
+#define FTDI_SCIENCESCOPE_LOGBOOKML_PID 0xFF18
+#define FTDI_SCIENCESCOPE_LS_LOGBOOK_PID 0xFF1C
+#define FTDI_SCIENCESCOPE_HS_LOGBOOK_PID 0xFF1D
+
+/*
+ * Milkymist One JTAG/Serial
+ */
+#define QIHARDWARE_VID 0x20B7
+#define MILKYMISTONE_JTAGSERIAL_PID 0x0713
+
+/*
+ * CTI GmbH RS485 Converter http://www.cti-lean.com/
+ */
+/* USB-485-Mini*/
+#define FTDI_CTI_MINI_PID 0xF608
+/* USB-Nano-485*/
+#define FTDI_CTI_NANO_PID 0xF60B
+
+/*
+ * ZeitControl cardsystems GmbH rfid-readers http://zeitcontrol.de
+ */
+/* TagTracer MIFARE*/
+#define FTDI_ZEITCONTROL_TAGTRACE_MIFARE_PID 0xF7C0
+
+/*
+ * Rainforest Automation
+ */
+/* ZigBee controller */
+#define FTDI_RF_R106 0x8A28
+
+/*
+ * Product: HCP HIT GPRS modem
+ * Manufacturer: HCP d.o.o.
+ * ATI command output: Cinterion MC55i
+ */
+#define FTDI_CINTERION_MC55I_PID 0xA951
+
+/*
+ * Product: FirmwareHubEmulator
+ * Manufacturer: Harman Becker Automotive Systems
+ */
+#define FTDI_FHE_PID 0xA9A0
+
+/*
+ * Product: Comet Caller ID decoder
+ * Manufacturer: Crucible Technologies
+ */
+#define FTDI_CT_COMET_PID 0x8e08
+
+/*
+ * Product: Z3X Box
+ * Manufacturer: Smart GSM Team
+ */
+#define FTDI_Z3X_PID 0x0011
+
+/*
+ * Product: Cressi PC Interface
+ * Manufacturer: Cressi
+ */
+#define FTDI_CRESSI_PID 0x87d0
+
+/*
+ * Brainboxes devices
+ */
+#define BRAINBOXES_VID 0x05d1
+#define BRAINBOXES_VX_001_PID 0x1001 /* VX-001 ExpressCard 1 Port RS232 */
+#define BRAINBOXES_VX_012_PID 0x1002 /* VX-012 ExpressCard 2 Port RS232 */
+#define BRAINBOXES_VX_023_PID 0x1003 /* VX-023 ExpressCard 1 Port RS422/485 */
+#define BRAINBOXES_VX_034_PID 0x1004 /* VX-034 ExpressCard 2 Port RS422/485 */
+#define BRAINBOXES_US_101_PID 0x1011 /* US-101 1xRS232 */
+#define BRAINBOXES_US_159_PID 0x1021 /* US-159 1xRS232 */
+#define BRAINBOXES_US_235_PID 0x1017 /* US-235 1xRS232 */
+#define BRAINBOXES_US_320_PID 0x1019 /* US-320 1xRS422/485 */
+#define BRAINBOXES_US_324_PID 0x1013 /* US-324 1xRS422/485 1Mbaud */
+#define BRAINBOXES_US_606_1_PID 0x2001 /* US-606 6 Port RS232 Serial Port 1 and 2 */
+#define BRAINBOXES_US_606_2_PID 0x2002 /* US-606 6 Port RS232 Serial Port 3 and 4 */
+#define BRAINBOXES_US_606_3_PID 0x2003 /* US-606 6 Port RS232 Serial Port 4 and 6 */
+#define BRAINBOXES_US_701_1_PID 0x2011 /* US-701 4xRS232 1Mbaud Port 1 and 2 */
+#define BRAINBOXES_US_701_2_PID 0x2012 /* US-701 4xRS422 1Mbaud Port 3 and 4 */
+#define BRAINBOXES_US_279_1_PID 0x2021 /* US-279 8xRS422 1Mbaud Port 1 and 2 */
+#define BRAINBOXES_US_279_2_PID 0x2022 /* US-279 8xRS422 1Mbaud Port 3 and 4 */
+#define BRAINBOXES_US_279_3_PID 0x2023 /* US-279 8xRS422 1Mbaud Port 5 and 6 */
+#define BRAINBOXES_US_279_4_PID 0x2024 /* US-279 8xRS422 1Mbaud Port 7 and 8 */
+#define BRAINBOXES_US_346_1_PID 0x3011 /* US-346 4xRS422/485 1Mbaud Port 1 and 2 */
+#define BRAINBOXES_US_346_2_PID 0x3012 /* US-346 4xRS422/485 1Mbaud Port 3 and 4 */
+#define BRAINBOXES_US_257_PID 0x5001 /* US-257 2xRS232 1Mbaud */
+#define BRAINBOXES_US_313_PID 0x6001 /* US-313 2xRS422/485 1Mbaud */
+#define BRAINBOXES_US_357_PID 0x7001 /* US_357 1xRS232/422/485 */
+#define BRAINBOXES_US_842_1_PID 0x8001 /* US-842 8xRS422/485 1Mbaud Port 1 and 2 */
+#define BRAINBOXES_US_842_2_PID 0x8002 /* US-842 8xRS422/485 1Mbaud Port 3 and 4 */
+#define BRAINBOXES_US_842_3_PID 0x8003 /* US-842 8xRS422/485 1Mbaud Port 5 and 6 */
+#define BRAINBOXES_US_842_4_PID 0x8004 /* US-842 8xRS422/485 1Mbaud Port 7 and 8 */
+#define BRAINBOXES_US_160_1_PID 0x9001 /* US-160 16xRS232 1Mbaud Port 1 and 2 */
+#define BRAINBOXES_US_160_2_PID 0x9002 /* US-160 16xRS232 1Mbaud Port 3 and 4 */
+#define BRAINBOXES_US_160_3_PID 0x9003 /* US-160 16xRS232 1Mbaud Port 5 and 6 */
+#define BRAINBOXES_US_160_4_PID 0x9004 /* US-160 16xRS232 1Mbaud Port 7 and 8 */
+#define BRAINBOXES_US_160_5_PID 0x9005 /* US-160 16xRS232 1Mbaud Port 9 and 10 */
+#define BRAINBOXES_US_160_6_PID 0x9006 /* US-160 16xRS232 1Mbaud Port 11 and 12 */
+#define BRAINBOXES_US_160_7_PID 0x9007 /* US-160 16xRS232 1Mbaud Port 13 and 14 */
+#define BRAINBOXES_US_160_8_PID 0x9008 /* US-160 16xRS232 1Mbaud Port 15 and 16 */
+
+/*
+ * ekey biometric systems GmbH (http://ekey.net/)
+ */
+#define FTDI_EKEY_CONV_USB_PID 0xCB08 /* Converter USB */
+
+/*
+ * GE Healthcare devices
+ */
+#define GE_HEALTHCARE_VID 0x1901
+#define GE_HEALTHCARE_NEMO_TRACKER_PID 0x0015
+
+/*
+ * Active Research (Actisense) devices
+ */
+#define ACTISENSE_NDC_PID 0xD9A8 /* NDC USB Serial Adapter */
+#define ACTISENSE_USG_PID 0xD9A9 /* USG USB Serial Adapter */
+#define ACTISENSE_NGT_PID 0xD9AA /* NGT NMEA2000 Interface */
+#define ACTISENSE_NGW_PID 0xD9AB /* NGW NMEA2000 Gateway */
+#define ACTISENSE_UID_PID 0xD9AC /* USB Isolating Device */
+#define ACTISENSE_USA_PID 0xD9AD /* USB to Serial Adapter */
+#define ACTISENSE_NGX_PID 0xD9AE /* NGX NMEA2000 Gateway */
+#define ACTISENSE_D9AF_PID 0xD9AF /* Actisense Reserved */
+#define CHETCO_SEAGAUGE_PID 0xA548 /* SeaGauge USB Adapter */
+#define CHETCO_SEASWITCH_PID 0xA549 /* SeaSwitch USB Adapter */
+#define CHETCO_SEASMART_NMEA2000_PID 0xA54A /* SeaSmart NMEA2000 Gateway */
+#define CHETCO_SEASMART_ETHERNET_PID 0xA54B /* SeaSmart Ethernet Gateway */
+#define CHETCO_SEASMART_WIFI_PID 0xA5AC /* SeaSmart Wifi Gateway */
+#define CHETCO_SEASMART_DISPLAY_PID 0xA5AD /* SeaSmart NMEA2000 Display */
+#define CHETCO_SEASMART_LITE_PID 0xA5AE /* SeaSmart Lite USB Adapter */
+#define CHETCO_SEASMART_ANALOG_PID 0xA5AF /* SeaSmart Analog Adapter */
+
+/*
+ * Belimo Automation
+ */
+#define BELIMO_ZTH_PID 0x8050
+#define BELIMO_ZIP_PID 0xC811
+
+/*
+ * Unjo AB
+ */
+#define UNJO_VID 0x22B7
+#define UNJO_ISODEBUG_V1_PID 0x150D
+
+/*
+ * IDS GmbH
+ */
+#define IDS_VID 0x2CAF
+#define IDS_SI31A_PID 0x13A2
+#define IDS_CM31A_PID 0x13A3
+
+/*
+ * U-Blox products (http://www.u-blox.com).
+ */
+#define UBLOX_VID 0x1546
+#define UBLOX_C099F9P_ZED_PID 0x0502
+#define UBLOX_C099F9P_ODIN_PID 0x0503
+#define UBLOX_EVK_M101_PID 0x0506
+
+/*
+ * GMC devices
+ */
+#define GMC_VID 0x1cd7
+#define GMC_Z216C_PID 0x0217 /* GMC Z216C Adapter IR-USB */
+
+/*
+ * Altera USB Blaster 3 (http://www.altera.com).
+ */
+#define ALTERA_VID 0x09fb
+#define ALTERA_UB3_6022_PID 0x6022
+#define ALTERA_UB3_6025_PID 0x6025
+#define ALTERA_UB3_6026_PID 0x6026
+#define ALTERA_UB3_6029_PID 0x6029
+#define ALTERA_UB3_602A_PID 0x602a
+#define ALTERA_UB3_602C_PID 0x602c
+#define ALTERA_UB3_602D_PID 0x602d
+#define ALTERA_UB3_602E_PID 0x602e
diff --git a/drivers/usb/serial/garmin_gps.c b/drivers/usb/serial/garmin_gps.c
new file mode 100644
index 000000000..8020149f5
--- /dev/null
+++ b/drivers/usb/serial/garmin_gps.c
@@ -0,0 +1,1442 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Garmin GPS driver
+ *
+ * Copyright (C) 2006-2011 Hermann Kneissel herkne@gmx.de
+ *
+ * The latest version of the driver can be found at
+ * http://sourceforge.net/projects/garmin-gps/
+ *
+ * This driver has been derived from v2.1 of the visor driver.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+/* the mode to be set when the port ist opened */
+static int initial_mode = 1;
+
+#define GARMIN_VENDOR_ID 0x091E
+
+/*
+ * Version Information
+ */
+
+#define VERSION_MAJOR 0
+#define VERSION_MINOR 36
+
+#define _STR(s) #s
+#define _DRIVER_VERSION(a, b) "v" _STR(a) "." _STR(b)
+#define DRIVER_VERSION _DRIVER_VERSION(VERSION_MAJOR, VERSION_MINOR)
+#define DRIVER_AUTHOR "hermann kneissel"
+#define DRIVER_DESC "garmin gps driver"
+
+/* error codes returned by the driver */
+#define EINVPKT 1000 /* invalid packet structure */
+
+
+/* size of the header of a packet using the usb protocol */
+#define GARMIN_PKTHDR_LENGTH 12
+
+/* max. possible size of a packet using the serial protocol */
+#define MAX_SERIAL_PKT_SIZ (3 + 255 + 3)
+
+/* max. possible size of a packet with worst case stuffing */
+#define MAX_SERIAL_PKT_SIZ_STUFFED (MAX_SERIAL_PKT_SIZ + 256)
+
+/* size of a buffer able to hold a complete (no stuffing) packet
+ * (the document protocol does not contain packets with a larger
+ * size, but in theory a packet may be 64k+12 bytes - if in
+ * later protocol versions larger packet sizes occur, this value
+ * should be increased accordingly, so the input buffer is always
+ * large enough the store a complete packet inclusive header) */
+#define GPS_IN_BUFSIZ (GARMIN_PKTHDR_LENGTH+MAX_SERIAL_PKT_SIZ)
+
+/* size of a buffer able to hold a complete (incl. stuffing) packet */
+#define GPS_OUT_BUFSIZ (GARMIN_PKTHDR_LENGTH+MAX_SERIAL_PKT_SIZ_STUFFED)
+
+/* where to place the packet id of a serial packet, so we can
+ * prepend the usb-packet header without the need to move the
+ * packets data */
+#define GSP_INITIAL_OFFSET (GARMIN_PKTHDR_LENGTH-2)
+
+/* max. size of incoming private packets (header+1 param) */
+#define PRIVPKTSIZ (GARMIN_PKTHDR_LENGTH+4)
+
+#define GARMIN_LAYERID_TRANSPORT 0
+#define GARMIN_LAYERID_APPL 20
+/* our own layer-id to use for some control mechanisms */
+#define GARMIN_LAYERID_PRIVATE 0x01106E4B
+
+#define GARMIN_PKTID_PVT_DATA 51
+#define GARMIN_PKTID_L001_COMMAND_DATA 10
+
+#define CMND_ABORT_TRANSFER 0
+
+/* packet ids used in private layer */
+#define PRIV_PKTID_SET_DEBUG 1
+#define PRIV_PKTID_SET_MODE 2
+#define PRIV_PKTID_INFO_REQ 3
+#define PRIV_PKTID_INFO_RESP 4
+#define PRIV_PKTID_RESET_REQ 5
+#define PRIV_PKTID_SET_DEF_MODE 6
+
+
+#define ETX 0x03
+#define DLE 0x10
+#define ACK 0x06
+#define NAK 0x15
+
+/* structure used to queue incoming packets */
+struct garmin_packet {
+ struct list_head list;
+ int seq;
+ /* the real size of the data array, always > 0 */
+ int size;
+ __u8 data[] __counted_by(size);
+};
+
+/* structure used to keep the current state of the driver */
+struct garmin_data {
+ __u8 state;
+ __u16 flags;
+ __u8 mode;
+ __u8 count;
+ __u8 pkt_id;
+ __u32 serial_num;
+ struct timer_list timer;
+ struct usb_serial_port *port;
+ int seq_counter;
+ int insize;
+ int outsize;
+ __u8 inbuffer [GPS_IN_BUFSIZ]; /* tty -> usb */
+ __u8 outbuffer[GPS_OUT_BUFSIZ]; /* usb -> tty */
+ __u8 privpkt[4*6];
+ spinlock_t lock;
+ struct list_head pktlist;
+ struct usb_anchor write_urbs;
+};
+
+
+#define STATE_NEW 0
+#define STATE_INITIAL_DELAY 1
+#define STATE_TIMEOUT 2
+#define STATE_SESSION_REQ1 3
+#define STATE_SESSION_REQ2 4
+#define STATE_ACTIVE 5
+
+#define STATE_RESET 8
+#define STATE_DISCONNECTED 9
+#define STATE_WAIT_TTY_ACK 10
+#define STATE_GSP_WAIT_DATA 11
+
+#define MODE_NATIVE 0
+#define MODE_GARMIN_SERIAL 1
+
+/* Flags used in garmin_data.flags: */
+#define FLAGS_SESSION_REPLY_MASK 0x00C0
+#define FLAGS_SESSION_REPLY1_SEEN 0x0080
+#define FLAGS_SESSION_REPLY2_SEEN 0x0040
+#define FLAGS_BULK_IN_ACTIVE 0x0020
+#define FLAGS_BULK_IN_RESTART 0x0010
+#define FLAGS_THROTTLED 0x0008
+#define APP_REQ_SEEN 0x0004
+#define APP_RESP_SEEN 0x0002
+#define CLEAR_HALT_REQUIRED 0x0001
+
+#define FLAGS_QUEUING 0x0100
+#define FLAGS_DROP_DATA 0x0800
+
+#define FLAGS_GSP_SKIP 0x1000
+#define FLAGS_GSP_DLESEEN 0x2000
+
+
+
+
+
+
+/* function prototypes */
+static int gsp_next_packet(struct garmin_data *garmin_data_p);
+static int garmin_write_bulk(struct usb_serial_port *port,
+ const unsigned char *buf, int count,
+ int dismiss_ack);
+
+/* some special packets to be send or received */
+static unsigned char const GARMIN_START_SESSION_REQ[]
+ = { 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0 };
+static unsigned char const GARMIN_START_SESSION_REPLY[]
+ = { 0, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0 };
+static unsigned char const GARMIN_BULK_IN_AVAIL_REPLY[]
+ = { 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0 };
+static unsigned char const GARMIN_STOP_TRANSFER_REQ[]
+ = { 20, 0, 0, 0, 10, 0, 0, 0, 2, 0, 0, 0, 0, 0 };
+static unsigned char const GARMIN_STOP_TRANSFER_REQ_V2[]
+ = { 20, 0, 0, 0, 10, 0, 0, 0, 1, 0, 0, 0, 0 };
+
+/* packets currently unused, left as documentation */
+#if 0
+static unsigned char const GARMIN_APP_LAYER_REPLY[]
+ = { 0x14, 0, 0, 0 };
+static unsigned char const GARMIN_START_PVT_REQ[]
+ = { 20, 0, 0, 0, 10, 0, 0, 0, 2, 0, 0, 0, 49, 0 };
+static unsigned char const GARMIN_STOP_PVT_REQ[]
+ = { 20, 0, 0, 0, 10, 0, 0, 0, 2, 0, 0, 0, 50, 0 };
+static unsigned char const PRIVATE_REQ[]
+ = { 0x4B, 0x6E, 0x10, 0x01, 0xFF, 0, 0, 0, 0xFF, 0, 0, 0 };
+#endif
+
+
+static const struct usb_device_id id_table[] = {
+ /* the same device id seems to be used by all
+ usb enabled GPS devices */
+ { USB_DEVICE(GARMIN_VENDOR_ID, 3) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+
+static inline int getLayerId(const __u8 *usbPacket)
+{
+ return __le32_to_cpup((__le32 *)(usbPacket));
+}
+
+static inline int getPacketId(const __u8 *usbPacket)
+{
+ return __le32_to_cpup((__le32 *)(usbPacket+4));
+}
+
+static inline int getDataLength(const __u8 *usbPacket)
+{
+ return __le32_to_cpup((__le32 *)(usbPacket+8));
+}
+
+
+/*
+ * check if the usb-packet in buf contains an abort-transfer command.
+ * (if yes, all queued data will be dropped)
+ */
+static inline int isAbortTrfCmnd(const unsigned char *buf)
+{
+ if (memcmp(buf, GARMIN_STOP_TRANSFER_REQ,
+ sizeof(GARMIN_STOP_TRANSFER_REQ)) == 0 ||
+ memcmp(buf, GARMIN_STOP_TRANSFER_REQ_V2,
+ sizeof(GARMIN_STOP_TRANSFER_REQ_V2)) == 0)
+ return 1;
+ else
+ return 0;
+}
+
+
+
+static void send_to_tty(struct usb_serial_port *port,
+ char *data, unsigned int actual_length)
+{
+ if (actual_length) {
+ usb_serial_debug_data(&port->dev, __func__, actual_length, data);
+ tty_insert_flip_string(&port->port, data, actual_length);
+ tty_flip_buffer_push(&port->port);
+ }
+}
+
+
+/******************************************************************************
+ * packet queue handling
+ ******************************************************************************/
+
+/*
+ * queue a received (usb-)packet for later processing
+ */
+static int pkt_add(struct garmin_data *garmin_data_p,
+ unsigned char *data, unsigned int data_length)
+{
+ int state = 0;
+ int result = 0;
+ unsigned long flags;
+ struct garmin_packet *pkt;
+
+ /* process only packets containing data ... */
+ if (data_length) {
+ pkt = kmalloc_flex(*pkt, data, data_length, GFP_ATOMIC);
+ if (!pkt)
+ return 0;
+
+ pkt->size = data_length;
+ memcpy(pkt->data, data, data_length);
+
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->flags |= FLAGS_QUEUING;
+ result = list_empty(&garmin_data_p->pktlist);
+ pkt->seq = garmin_data_p->seq_counter++;
+ list_add_tail(&pkt->list, &garmin_data_p->pktlist);
+ state = garmin_data_p->state;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+
+ dev_dbg(&garmin_data_p->port->dev,
+ "%s - added: pkt: %d - %d bytes\n", __func__,
+ pkt->seq, data_length);
+
+ /* in serial mode, if someone is waiting for data from
+ the device, convert and send the next packet to tty. */
+ if (result && (state == STATE_GSP_WAIT_DATA))
+ gsp_next_packet(garmin_data_p);
+ }
+ return result;
+}
+
+
+/* get the next pending packet */
+static struct garmin_packet *pkt_pop(struct garmin_data *garmin_data_p)
+{
+ unsigned long flags;
+ struct garmin_packet *result = NULL;
+
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ if (!list_empty(&garmin_data_p->pktlist)) {
+ result = (struct garmin_packet *)garmin_data_p->pktlist.next;
+ list_del(&result->list);
+ }
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+ return result;
+}
+
+
+/* free up all queued data */
+static void pkt_clear(struct garmin_data *garmin_data_p)
+{
+ unsigned long flags;
+ struct garmin_packet *result = NULL;
+
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ while (!list_empty(&garmin_data_p->pktlist)) {
+ result = (struct garmin_packet *)garmin_data_p->pktlist.next;
+ list_del(&result->list);
+ kfree(result);
+ }
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+}
+
+
+/******************************************************************************
+ * garmin serial protocol handling handling
+ ******************************************************************************/
+
+/* send an ack packet back to the tty */
+static int gsp_send_ack(struct garmin_data *garmin_data_p, __u8 pkt_id)
+{
+ __u8 pkt[10];
+ __u8 cksum = 0;
+ __u8 *ptr = pkt;
+ unsigned l = 0;
+
+ dev_dbg(&garmin_data_p->port->dev, "%s - pkt-id: 0x%X.\n", __func__,
+ pkt_id);
+
+ *ptr++ = DLE;
+ *ptr++ = ACK;
+ cksum += ACK;
+
+ *ptr++ = 2;
+ cksum += 2;
+
+ *ptr++ = pkt_id;
+ cksum += pkt_id;
+
+ if (pkt_id == DLE)
+ *ptr++ = DLE;
+
+ *ptr++ = 0;
+ *ptr++ = (-cksum) & 0xFF;
+ *ptr++ = DLE;
+ *ptr++ = ETX;
+
+ l = ptr-pkt;
+
+ send_to_tty(garmin_data_p->port, pkt, l);
+ return 0;
+}
+
+
+
+/*
+ * called for a complete packet received from tty layer
+ *
+ * the complete packet (pktid ... cksum) is in garmin_data_p->inbuf starting
+ * at GSP_INITIAL_OFFSET.
+ *
+ * count - number of bytes in the input buffer including space reserved for
+ * the usb header: GSP_INITIAL_OFFSET + number of bytes in packet
+ * (including pkt-id, data-length a. cksum)
+ */
+static int gsp_rec_packet(struct garmin_data *garmin_data_p, int count)
+{
+ struct device *dev = &garmin_data_p->port->dev;
+ unsigned long flags;
+ const __u8 *recpkt = garmin_data_p->inbuffer+GSP_INITIAL_OFFSET;
+ __le32 *usbdata = (__le32 *) garmin_data_p->inbuffer;
+ int cksum = 0;
+ int n = 0;
+ int pktid = recpkt[0];
+ int size = recpkt[1];
+
+ usb_serial_debug_data(&garmin_data_p->port->dev, __func__,
+ count-GSP_INITIAL_OFFSET, recpkt);
+
+ if (size != (count-GSP_INITIAL_OFFSET-3)) {
+ dev_dbg(dev, "%s - invalid size, expected %d bytes, got %d\n",
+ __func__, size, (count-GSP_INITIAL_OFFSET-3));
+ return -EINVPKT;
+ }
+
+ cksum += *recpkt++;
+ cksum += *recpkt++;
+
+ /* sanity check, remove after test ... */
+ if ((__u8 *)&(usbdata[3]) != recpkt) {
+ dev_dbg(dev, "%s - ptr mismatch %p - %p\n", __func__,
+ &(usbdata[4]), recpkt);
+ return -EINVPKT;
+ }
+
+ while (n < size) {
+ cksum += *recpkt++;
+ n++;
+ }
+
+ if (((cksum + *recpkt) & 0xff) != 0) {
+ dev_dbg(dev, "%s - invalid checksum, expected %02x, got %02x\n",
+ __func__, -cksum & 0xff, *recpkt);
+ return -EINVPKT;
+ }
+
+ usbdata[0] = __cpu_to_le32(GARMIN_LAYERID_APPL);
+ usbdata[1] = __cpu_to_le32(pktid);
+ usbdata[2] = __cpu_to_le32(size);
+
+ garmin_write_bulk(garmin_data_p->port, garmin_data_p->inbuffer,
+ GARMIN_PKTHDR_LENGTH+size, 0);
+
+ /* if this was an abort-transfer command, flush all
+ queued data. */
+ if (isAbortTrfCmnd(garmin_data_p->inbuffer)) {
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->flags |= FLAGS_DROP_DATA;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+ pkt_clear(garmin_data_p);
+ }
+
+ return count;
+}
+
+
+
+/*
+ * Called for data received from tty
+ *
+ * buf contains the data read, it may span more than one packet or even
+ * incomplete packets
+ *
+ * input record should be a serial-record, but it may not be complete.
+ * Copy it into our local buffer, until an etx is seen (or an error
+ * occurs).
+ * Once the record is complete, convert into a usb packet and send it
+ * to the bulk pipe, send an ack back to the tty.
+ *
+ * If the input is an ack, just send the last queued packet to the
+ * tty layer.
+ *
+ * if the input is an abort command, drop all queued data.
+ */
+
+static int gsp_receive(struct garmin_data *garmin_data_p,
+ const unsigned char *buf, int count)
+{
+ struct device *dev = &garmin_data_p->port->dev;
+ unsigned long flags;
+ int offs = 0;
+ int ack_or_nak_seen = 0;
+ __u8 *dest;
+ int size;
+ /* dleSeen: set if last byte read was a DLE */
+ int dleSeen;
+ /* skip: if set, skip incoming data until possible start of
+ * new packet
+ */
+ int skip;
+ __u8 data;
+
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ dest = garmin_data_p->inbuffer;
+ size = garmin_data_p->insize;
+ dleSeen = garmin_data_p->flags & FLAGS_GSP_DLESEEN;
+ skip = garmin_data_p->flags & FLAGS_GSP_SKIP;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+
+ /* dev_dbg(dev, "%s - dle=%d skip=%d size=%d count=%d\n",
+ __func__, dleSeen, skip, size, count); */
+
+ if (size == 0)
+ size = GSP_INITIAL_OFFSET;
+
+ while (offs < count) {
+
+ data = *(buf+offs);
+ offs++;
+
+ if (data == DLE) {
+ if (skip) { /* start of a new pkt */
+ skip = 0;
+ size = GSP_INITIAL_OFFSET;
+ dleSeen = 1;
+ } else if (dleSeen) {
+ dest[size++] = data;
+ dleSeen = 0;
+ } else {
+ dleSeen = 1;
+ }
+ } else if (data == ETX) {
+ if (dleSeen) {
+ /* packet complete */
+
+ data = dest[GSP_INITIAL_OFFSET];
+
+ if (data == ACK) {
+ ack_or_nak_seen = ACK;
+ dev_dbg(dev, "ACK packet complete.\n");
+ } else if (data == NAK) {
+ ack_or_nak_seen = NAK;
+ dev_dbg(dev, "NAK packet complete.\n");
+ } else {
+ dev_dbg(dev, "packet complete - id=0x%X.\n",
+ data);
+ gsp_rec_packet(garmin_data_p, size);
+ }
+
+ skip = 1;
+ size = GSP_INITIAL_OFFSET;
+ dleSeen = 0;
+ } else {
+ dest[size++] = data;
+ }
+ } else if (!skip) {
+
+ if (dleSeen) {
+ size = GSP_INITIAL_OFFSET;
+ dleSeen = 0;
+ }
+
+ dest[size++] = data;
+ }
+
+ if (size >= GPS_IN_BUFSIZ) {
+ dev_dbg(dev, "%s - packet too large.\n", __func__);
+ skip = 1;
+ size = GSP_INITIAL_OFFSET;
+ dleSeen = 0;
+ }
+ }
+
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+
+ garmin_data_p->insize = size;
+
+ /* copy flags back to structure */
+ if (skip)
+ garmin_data_p->flags |= FLAGS_GSP_SKIP;
+ else
+ garmin_data_p->flags &= ~FLAGS_GSP_SKIP;
+
+ if (dleSeen)
+ garmin_data_p->flags |= FLAGS_GSP_DLESEEN;
+ else
+ garmin_data_p->flags &= ~FLAGS_GSP_DLESEEN;
+
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+
+ if (ack_or_nak_seen) {
+ if (gsp_next_packet(garmin_data_p) > 0)
+ garmin_data_p->state = STATE_ACTIVE;
+ else
+ garmin_data_p->state = STATE_GSP_WAIT_DATA;
+ }
+ return count;
+}
+
+
+
+/*
+ * Sends a usb packet to the tty
+ *
+ * Assumes, that all packages and at an usb-packet boundary.
+ *
+ * return <0 on error, 0 if packet is incomplete or > 0 if packet was sent
+ */
+static int gsp_send(struct garmin_data *garmin_data_p,
+ const unsigned char *buf, int count)
+{
+ struct device *dev = &garmin_data_p->port->dev;
+ const unsigned char *src;
+ unsigned char *dst;
+ int pktid = 0;
+ int datalen = 0;
+ int cksum = 0;
+ int i = 0;
+ int k;
+
+ dev_dbg(dev, "%s - state %d - %d bytes.\n", __func__,
+ garmin_data_p->state, count);
+
+ k = garmin_data_p->outsize;
+ if ((k+count) > GPS_OUT_BUFSIZ) {
+ dev_dbg(dev, "packet too large\n");
+ garmin_data_p->outsize = 0;
+ return -4;
+ }
+
+ memcpy(garmin_data_p->outbuffer+k, buf, count);
+ k += count;
+ garmin_data_p->outsize = k;
+
+ if (k >= GARMIN_PKTHDR_LENGTH) {
+ pktid = getPacketId(garmin_data_p->outbuffer);
+ datalen = getDataLength(garmin_data_p->outbuffer);
+ i = GARMIN_PKTHDR_LENGTH + datalen;
+ if (k < i)
+ return 0;
+ } else {
+ return 0;
+ }
+
+ dev_dbg(dev, "%s - %d bytes in buffer, %d bytes in pkt.\n", __func__, k, i);
+
+ /* garmin_data_p->outbuffer now contains a complete packet */
+
+ usb_serial_debug_data(&garmin_data_p->port->dev, __func__, k,
+ garmin_data_p->outbuffer);
+
+ garmin_data_p->outsize = 0;
+
+ if (getLayerId(garmin_data_p->outbuffer) != GARMIN_LAYERID_APPL) {
+ dev_dbg(dev, "not an application packet (%d)\n",
+ getLayerId(garmin_data_p->outbuffer));
+ return -1;
+ }
+
+ if (pktid > 255) {
+ dev_dbg(dev, "packet-id %d too large\n", pktid);
+ return -2;
+ }
+
+ if (datalen > 255) {
+ dev_dbg(dev, "packet-size %d too large\n", datalen);
+ return -3;
+ }
+
+ /* the serial protocol should be able to handle this packet */
+
+ k = 0;
+ src = garmin_data_p->outbuffer+GARMIN_PKTHDR_LENGTH;
+ for (i = 0; i < datalen; i++) {
+ if (*src++ == DLE)
+ k++;
+ }
+
+ src = garmin_data_p->outbuffer+GARMIN_PKTHDR_LENGTH;
+ if (k > (GARMIN_PKTHDR_LENGTH-2)) {
+ /* can't add stuffing DLEs in place, move data to end
+ of buffer ... */
+ dst = garmin_data_p->outbuffer+GPS_OUT_BUFSIZ-datalen;
+ memcpy(dst, src, datalen);
+ src = dst;
+ }
+
+ dst = garmin_data_p->outbuffer;
+
+ *dst++ = DLE;
+ *dst++ = pktid;
+ cksum += pktid;
+ *dst++ = datalen;
+ cksum += datalen;
+ if (datalen == DLE)
+ *dst++ = DLE;
+
+ for (i = 0; i < datalen; i++) {
+ __u8 c = *src++;
+ *dst++ = c;
+ cksum += c;
+ if (c == DLE)
+ *dst++ = DLE;
+ }
+
+ cksum = -cksum & 0xFF;
+ *dst++ = cksum;
+ if (cksum == DLE)
+ *dst++ = DLE;
+ *dst++ = DLE;
+ *dst++ = ETX;
+
+ i = dst-garmin_data_p->outbuffer;
+
+ send_to_tty(garmin_data_p->port, garmin_data_p->outbuffer, i);
+
+ garmin_data_p->pkt_id = pktid;
+ garmin_data_p->state = STATE_WAIT_TTY_ACK;
+
+ return i;
+}
+
+
+/*
+ * Process the next pending data packet - if there is one
+ */
+static int gsp_next_packet(struct garmin_data *garmin_data_p)
+{
+ int result = 0;
+ struct garmin_packet *pkt = NULL;
+
+ while ((pkt = pkt_pop(garmin_data_p)) != NULL) {
+ dev_dbg(&garmin_data_p->port->dev, "%s - next pkt: %d\n", __func__, pkt->seq);
+ result = gsp_send(garmin_data_p, pkt->data, pkt->size);
+ if (result > 0) {
+ kfree(pkt);
+ return result;
+ }
+ kfree(pkt);
+ }
+ return result;
+}
+
+
+
+/******************************************************************************
+ * garmin native mode
+ ******************************************************************************/
+
+
+/*
+ * Called for data received from tty
+ *
+ * The input data is expected to be in garmin usb-packet format.
+ *
+ * buf contains the data read, it may span more than one packet
+ * or even incomplete packets
+ */
+static int nat_receive(struct garmin_data *garmin_data_p,
+ const unsigned char *buf, int count)
+{
+ unsigned long flags;
+ __u8 *dest;
+ int offs = 0;
+ int result = count;
+ int len;
+
+ while (offs < count) {
+ /* if buffer contains header, copy rest of data */
+ if (garmin_data_p->insize >= GARMIN_PKTHDR_LENGTH)
+ len = GARMIN_PKTHDR_LENGTH
+ +getDataLength(garmin_data_p->inbuffer);
+ else
+ len = GARMIN_PKTHDR_LENGTH;
+
+ if (len >= GPS_IN_BUFSIZ) {
+ /* seems to be an invalid packet, ignore rest
+ of input */
+ dev_dbg(&garmin_data_p->port->dev,
+ "%s - packet size too large: %d\n",
+ __func__, len);
+ garmin_data_p->insize = 0;
+ count = 0;
+ result = -EINVPKT;
+ } else {
+ len -= garmin_data_p->insize;
+ if (len > (count-offs))
+ len = (count-offs);
+ if (len > 0) {
+ dest = garmin_data_p->inbuffer
+ + garmin_data_p->insize;
+ memcpy(dest, buf+offs, len);
+ garmin_data_p->insize += len;
+ offs += len;
+ }
+ }
+
+ /* do we have a complete packet ? */
+ if (garmin_data_p->insize >= GARMIN_PKTHDR_LENGTH) {
+ len = GARMIN_PKTHDR_LENGTH+
+ getDataLength(garmin_data_p->inbuffer);
+ if (garmin_data_p->insize >= len) {
+ garmin_write_bulk(garmin_data_p->port,
+ garmin_data_p->inbuffer,
+ len, 0);
+ garmin_data_p->insize = 0;
+
+ /* if this was an abort-transfer command,
+ flush all queued data. */
+ if (isAbortTrfCmnd(garmin_data_p->inbuffer)) {
+ spin_lock_irqsave(&garmin_data_p->lock,
+ flags);
+ garmin_data_p->flags |= FLAGS_DROP_DATA;
+ spin_unlock_irqrestore(
+ &garmin_data_p->lock, flags);
+ pkt_clear(garmin_data_p);
+ }
+ }
+ }
+ }
+ return result;
+}
+
+
+/******************************************************************************
+ * private packets
+ ******************************************************************************/
+
+static void priv_status_resp(struct usb_serial_port *port)
+{
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+ __le32 *pkt = (__le32 *)garmin_data_p->privpkt;
+
+ pkt[0] = __cpu_to_le32(GARMIN_LAYERID_PRIVATE);
+ pkt[1] = __cpu_to_le32(PRIV_PKTID_INFO_RESP);
+ pkt[2] = __cpu_to_le32(12);
+ pkt[3] = __cpu_to_le32(VERSION_MAJOR << 16 | VERSION_MINOR);
+ pkt[4] = __cpu_to_le32(garmin_data_p->mode);
+ pkt[5] = __cpu_to_le32(garmin_data_p->serial_num);
+
+ send_to_tty(port, (__u8 *)pkt, 6 * 4);
+}
+
+
+/******************************************************************************
+ * Garmin specific driver functions
+ ******************************************************************************/
+
+static int process_resetdev_request(struct usb_serial_port *port)
+{
+ unsigned long flags;
+ int status;
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->flags &= ~(CLEAR_HALT_REQUIRED);
+ garmin_data_p->state = STATE_RESET;
+ garmin_data_p->serial_num = 0;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+
+ usb_kill_urb(port->interrupt_in_urb);
+ dev_dbg(&port->dev, "%s - usb_reset_device\n", __func__);
+ status = usb_reset_device(port->serial->dev);
+ if (status)
+ dev_dbg(&port->dev, "%s - usb_reset_device failed: %d\n",
+ __func__, status);
+ return status;
+}
+
+
+
+/*
+ * clear all cached data
+ */
+static int garmin_clear(struct garmin_data *garmin_data_p)
+{
+ unsigned long flags;
+
+ /* flush all queued data */
+ pkt_clear(garmin_data_p);
+
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->insize = 0;
+ garmin_data_p->outsize = 0;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+
+ return 0;
+}
+
+
+static int garmin_init_session(struct usb_serial_port *port)
+{
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+ int status;
+ int i;
+
+ usb_kill_urb(port->interrupt_in_urb);
+
+ status = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (status) {
+ dev_err(&port->dev, "failed to submit interrupt urb: %d\n",
+ status);
+ return status;
+ }
+
+ /*
+ * using the initialization method from gpsbabel. See comments in
+ * gpsbabel/jeeps/gpslibusb.c gusb_reset_toggles()
+ */
+ dev_dbg(&port->dev, "%s - starting session ...\n", __func__);
+ garmin_data_p->state = STATE_ACTIVE;
+
+ for (i = 0; i < 3; i++) {
+ status = garmin_write_bulk(port, GARMIN_START_SESSION_REQ,
+ sizeof(GARMIN_START_SESSION_REQ), 0);
+ if (status < 0)
+ goto err_kill_urbs;
+ }
+
+ return 0;
+
+err_kill_urbs:
+ usb_kill_anchored_urbs(&garmin_data_p->write_urbs);
+ usb_kill_urb(port->interrupt_in_urb);
+
+ return status;
+}
+
+
+
+static int garmin_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ unsigned long flags;
+ int status = 0;
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->mode = initial_mode;
+ garmin_data_p->count = 0;
+ garmin_data_p->flags &= FLAGS_SESSION_REPLY1_SEEN;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+
+ /* shutdown any bulk reads that might be going on */
+ usb_kill_urb(port->read_urb);
+
+ if (garmin_data_p->state == STATE_RESET)
+ status = garmin_init_session(port);
+
+ garmin_data_p->state = STATE_ACTIVE;
+ return status;
+}
+
+
+static void garmin_close(struct usb_serial_port *port)
+{
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+
+ dev_dbg(&port->dev, "%s - mode=%d state=%d flags=0x%X\n",
+ __func__, garmin_data_p->mode, garmin_data_p->state,
+ garmin_data_p->flags);
+
+ garmin_clear(garmin_data_p);
+
+ /* shutdown our urbs */
+ usb_kill_urb(port->read_urb);
+ usb_kill_anchored_urbs(&garmin_data_p->write_urbs);
+
+ /* keep reset state so we know that we must start a new session */
+ if (garmin_data_p->state != STATE_RESET)
+ garmin_data_p->state = STATE_DISCONNECTED;
+}
+
+
+static void garmin_write_bulk_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+
+ if (port) {
+ struct garmin_data *garmin_data_p =
+ usb_get_serial_port_data(port);
+
+ if (getLayerId(urb->transfer_buffer) == GARMIN_LAYERID_APPL) {
+
+ if (garmin_data_p->mode == MODE_GARMIN_SERIAL) {
+ gsp_send_ack(garmin_data_p,
+ ((__u8 *)urb->transfer_buffer)[4]);
+ }
+ }
+ usb_serial_port_softint(port);
+ }
+
+ /* Ignore errors that resulted from garmin_write_bulk with
+ dismiss_ack = 1 */
+
+ /* free up the transfer buffer, as usb_free_urb() does not do this */
+ kfree(urb->transfer_buffer);
+}
+
+
+static int garmin_write_bulk(struct usb_serial_port *port,
+ const unsigned char *buf, int count,
+ int dismiss_ack)
+{
+ unsigned long flags;
+ struct usb_serial *serial = port->serial;
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+ struct urb *urb;
+ unsigned char *buffer;
+ int status;
+
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->flags &= ~FLAGS_DROP_DATA;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+
+ buffer = kmemdup(buf, count, GFP_ATOMIC);
+ if (!buffer)
+ return -ENOMEM;
+
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!urb) {
+ kfree(buffer);
+ return -ENOMEM;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__, count, buffer);
+
+ usb_fill_bulk_urb(urb, serial->dev,
+ usb_sndbulkpipe(serial->dev,
+ port->bulk_out_endpointAddress),
+ buffer, count,
+ garmin_write_bulk_callback,
+ dismiss_ack ? NULL : port);
+ urb->transfer_flags |= URB_ZERO_PACKET;
+
+ if (getLayerId(buffer) == GARMIN_LAYERID_APPL) {
+
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->flags |= APP_REQ_SEEN;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+
+ if (garmin_data_p->mode == MODE_GARMIN_SERIAL) {
+ pkt_clear(garmin_data_p);
+ garmin_data_p->state = STATE_GSP_WAIT_DATA;
+ }
+ }
+
+ /* send it down the pipe */
+ usb_anchor_urb(urb, &garmin_data_p->write_urbs);
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status) {
+ dev_err(&port->dev,
+ "%s - usb_submit_urb(write bulk) failed with status = %d\n",
+ __func__, status);
+ count = status;
+ usb_unanchor_urb(urb);
+ kfree(buffer);
+ }
+
+ /* we are done with this urb, so let the host driver
+ * really free it when it is finished with it */
+ usb_free_urb(urb);
+
+ return count;
+}
+
+static int garmin_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count)
+{
+ struct device *dev = &port->dev;
+ int pktid, pktsiz, len;
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+ __le32 *privpkt = (__le32 *)garmin_data_p->privpkt;
+
+ usb_serial_debug_data(dev, __func__, count, buf);
+
+ if (garmin_data_p->state == STATE_RESET)
+ return -EIO;
+
+ /* check for our private packets */
+ if (count >= GARMIN_PKTHDR_LENGTH) {
+ len = PRIVPKTSIZ;
+ if (count < len)
+ len = count;
+
+ memcpy(garmin_data_p->privpkt, buf, len);
+
+ pktsiz = getDataLength(garmin_data_p->privpkt);
+ pktid = getPacketId(garmin_data_p->privpkt);
+
+ if (count == (GARMIN_PKTHDR_LENGTH + pktsiz) &&
+ getLayerId(garmin_data_p->privpkt) ==
+ GARMIN_LAYERID_PRIVATE) {
+
+ dev_dbg(dev, "%s - processing private request %d\n",
+ __func__, pktid);
+
+ /* drop all unfinished transfers */
+ garmin_clear(garmin_data_p);
+
+ switch (pktid) {
+ case PRIV_PKTID_SET_MODE:
+ if (pktsiz != 4)
+ return -EINVPKT;
+ garmin_data_p->mode = __le32_to_cpu(privpkt[3]);
+ dev_dbg(dev, "%s - mode set to %d\n",
+ __func__, garmin_data_p->mode);
+ break;
+
+ case PRIV_PKTID_INFO_REQ:
+ priv_status_resp(port);
+ break;
+
+ case PRIV_PKTID_RESET_REQ:
+ process_resetdev_request(port);
+ break;
+
+ case PRIV_PKTID_SET_DEF_MODE:
+ if (pktsiz != 4)
+ return -EINVPKT;
+ initial_mode = __le32_to_cpu(privpkt[3]);
+ dev_dbg(dev, "%s - initial_mode set to %d\n",
+ __func__,
+ garmin_data_p->mode);
+ break;
+ }
+ return count;
+ }
+ }
+
+ if (garmin_data_p->mode == MODE_GARMIN_SERIAL) {
+ return gsp_receive(garmin_data_p, buf, count);
+ } else { /* MODE_NATIVE */
+ return nat_receive(garmin_data_p, buf, count);
+ }
+}
+
+
+static unsigned int garmin_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ /*
+ * Report back the bytes currently available in the output buffer.
+ */
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+ return GPS_OUT_BUFSIZ-garmin_data_p->outsize;
+}
+
+
+static void garmin_read_process(struct garmin_data *garmin_data_p,
+ unsigned char *data, unsigned data_length,
+ int bulk_data)
+{
+ unsigned long flags;
+
+ if (garmin_data_p->flags & FLAGS_DROP_DATA) {
+ /* abort-transfer cmd is active */
+ dev_dbg(&garmin_data_p->port->dev, "%s - pkt dropped\n", __func__);
+ } else if (garmin_data_p->state != STATE_DISCONNECTED &&
+ garmin_data_p->state != STATE_RESET) {
+
+ /* if throttling is active or postprecessing is required
+ put the received data in the input queue, otherwise
+ send it directly to the tty port */
+ if (garmin_data_p->flags & FLAGS_QUEUING) {
+ pkt_add(garmin_data_p, data, data_length);
+ } else if (bulk_data || (data_length >= sizeof(u32) &&
+ getLayerId(data) == GARMIN_LAYERID_APPL)) {
+
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->flags |= APP_RESP_SEEN;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+
+ if (garmin_data_p->mode == MODE_GARMIN_SERIAL) {
+ pkt_add(garmin_data_p, data, data_length);
+ } else {
+ send_to_tty(garmin_data_p->port, data,
+ data_length);
+ }
+ }
+ /* ignore system layer packets ... */
+ }
+}
+
+
+static void garmin_read_bulk_callback(struct urb *urb)
+{
+ unsigned long flags;
+ struct usb_serial_port *port = urb->context;
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+ int retval;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero read bulk status received: %d\n",
+ __func__, status);
+ return;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
+
+ garmin_read_process(garmin_data_p, data, urb->actual_length, 1);
+
+ if (urb->actual_length == 0 &&
+ (garmin_data_p->flags & FLAGS_BULK_IN_RESTART) != 0) {
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->flags &= ~FLAGS_BULK_IN_RESTART;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+ retval = usb_submit_urb(port->read_urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(&port->dev,
+ "%s - failed resubmitting read urb, error %d\n",
+ __func__, retval);
+ } else if (urb->actual_length > 0) {
+ /* Continue trying to read until nothing more is received */
+ if ((garmin_data_p->flags & FLAGS_THROTTLED) == 0) {
+ retval = usb_submit_urb(port->read_urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(&port->dev,
+ "%s - failed resubmitting read urb, error %d\n",
+ __func__, retval);
+ }
+ } else {
+ dev_dbg(&port->dev, "%s - end of bulk data\n", __func__);
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->flags &= ~FLAGS_BULK_IN_ACTIVE;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+ }
+}
+
+
+static void garmin_read_int_callback(struct urb *urb)
+{
+ unsigned long flags;
+ int retval;
+ struct usb_serial_port *port = urb->context;
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(&urb->dev->dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ return;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length,
+ urb->transfer_buffer);
+
+ if (urb->actual_length == sizeof(GARMIN_BULK_IN_AVAIL_REPLY) &&
+ memcmp(data, GARMIN_BULK_IN_AVAIL_REPLY,
+ sizeof(GARMIN_BULK_IN_AVAIL_REPLY)) == 0) {
+
+ dev_dbg(&port->dev, "%s - bulk data available.\n", __func__);
+
+ if ((garmin_data_p->flags & FLAGS_BULK_IN_ACTIVE) == 0) {
+
+ /* bulk data available */
+ retval = usb_submit_urb(port->read_urb, GFP_ATOMIC);
+ if (retval) {
+ dev_err(&port->dev,
+ "%s - failed submitting read urb, error %d\n",
+ __func__, retval);
+ } else {
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->flags |= FLAGS_BULK_IN_ACTIVE;
+ spin_unlock_irqrestore(&garmin_data_p->lock,
+ flags);
+ }
+ } else {
+ /* bulk-in transfer still active */
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->flags |= FLAGS_BULK_IN_RESTART;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+ }
+
+ } else if (urb->actual_length == (4+sizeof(GARMIN_START_SESSION_REPLY))
+ && memcmp(data, GARMIN_START_SESSION_REPLY,
+ sizeof(GARMIN_START_SESSION_REPLY)) == 0) {
+
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->flags |= FLAGS_SESSION_REPLY1_SEEN;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+
+ /* save the serial number */
+ garmin_data_p->serial_num = __le32_to_cpup(
+ (__le32 *)(data+GARMIN_PKTHDR_LENGTH));
+
+ dev_dbg(&port->dev, "%s - start-of-session reply seen - serial %u.\n",
+ __func__, garmin_data_p->serial_num);
+ }
+
+ garmin_read_process(garmin_data_p, data, urb->actual_length, 0);
+
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(&urb->dev->dev,
+ "%s - Error %d submitting interrupt urb\n",
+ __func__, retval);
+}
+
+
+/*
+ * Sends the next queued packt to the tty port (garmin native mode only)
+ * and then sets a timer to call itself again until all queued data
+ * is sent.
+ */
+static int garmin_flush_queue(struct garmin_data *garmin_data_p)
+{
+ unsigned long flags;
+ struct garmin_packet *pkt;
+
+ if ((garmin_data_p->flags & FLAGS_THROTTLED) == 0) {
+ pkt = pkt_pop(garmin_data_p);
+ if (pkt != NULL) {
+ send_to_tty(garmin_data_p->port, pkt->data, pkt->size);
+ kfree(pkt);
+ mod_timer(&garmin_data_p->timer, (1)+jiffies);
+
+ } else {
+ spin_lock_irqsave(&garmin_data_p->lock, flags);
+ garmin_data_p->flags &= ~FLAGS_QUEUING;
+ spin_unlock_irqrestore(&garmin_data_p->lock, flags);
+ }
+ }
+ return 0;
+}
+
+
+static void garmin_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+
+ /* set flag, data received will be put into a queue
+ for later processing */
+ spin_lock_irq(&garmin_data_p->lock);
+ garmin_data_p->flags |= FLAGS_QUEUING|FLAGS_THROTTLED;
+ spin_unlock_irq(&garmin_data_p->lock);
+}
+
+
+static void garmin_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+ int status;
+
+ spin_lock_irq(&garmin_data_p->lock);
+ garmin_data_p->flags &= ~FLAGS_THROTTLED;
+ spin_unlock_irq(&garmin_data_p->lock);
+
+ /* in native mode send queued data to tty, in
+ serial mode nothing needs to be done here */
+ if (garmin_data_p->mode == MODE_NATIVE)
+ garmin_flush_queue(garmin_data_p);
+
+ if ((garmin_data_p->flags & FLAGS_BULK_IN_ACTIVE) != 0) {
+ status = usb_submit_urb(port->read_urb, GFP_KERNEL);
+ if (status)
+ dev_err(&port->dev,
+ "%s - failed resubmitting read urb, error %d\n",
+ __func__, status);
+ }
+}
+
+/*
+ * The timer is currently only used to send queued packets to
+ * the tty in cases where the protocol provides no own handshaking
+ * to initiate the transfer.
+ */
+static void timeout_handler(struct timer_list *t)
+{
+ struct garmin_data *garmin_data_p = timer_container_of(garmin_data_p,
+ t, timer);
+
+ /* send the next queued packet to the tty port */
+ if (garmin_data_p->mode == MODE_NATIVE)
+ if (garmin_data_p->flags & FLAGS_QUEUING)
+ garmin_flush_queue(garmin_data_p);
+}
+
+
+
+static int garmin_port_probe(struct usb_serial_port *port)
+{
+ int status;
+ struct garmin_data *garmin_data_p;
+
+ garmin_data_p = kzalloc_obj(struct garmin_data);
+ if (!garmin_data_p)
+ return -ENOMEM;
+
+ timer_setup(&garmin_data_p->timer, timeout_handler, 0);
+ spin_lock_init(&garmin_data_p->lock);
+ INIT_LIST_HEAD(&garmin_data_p->pktlist);
+ garmin_data_p->port = port;
+ garmin_data_p->state = 0;
+ garmin_data_p->flags = 0;
+ garmin_data_p->count = 0;
+ init_usb_anchor(&garmin_data_p->write_urbs);
+ usb_set_serial_port_data(port, garmin_data_p);
+
+ status = garmin_init_session(port);
+ if (status)
+ goto err_free;
+
+ return 0;
+err_free:
+ kfree(garmin_data_p);
+
+ return status;
+}
+
+
+static void garmin_port_remove(struct usb_serial_port *port)
+{
+ struct garmin_data *garmin_data_p = usb_get_serial_port_data(port);
+
+ usb_kill_anchored_urbs(&garmin_data_p->write_urbs);
+ usb_kill_urb(port->interrupt_in_urb);
+ timer_shutdown_sync(&garmin_data_p->timer);
+ kfree(garmin_data_p);
+}
+
+
+/* All of the device info needed */
+static struct usb_serial_driver garmin_device = {
+ .driver = {
+ .name = "garmin_gps",
+ },
+ .description = "Garmin GPS usb/tty",
+ .id_table = id_table,
+ .num_ports = 1,
+ .open = garmin_open,
+ .close = garmin_close,
+ .throttle = garmin_throttle,
+ .unthrottle = garmin_unthrottle,
+ .port_probe = garmin_port_probe,
+ .port_remove = garmin_port_remove,
+ .write = garmin_write,
+ .write_room = garmin_write_room,
+ .write_bulk_callback = garmin_write_bulk_callback,
+ .read_bulk_callback = garmin_read_bulk_callback,
+ .read_int_callback = garmin_read_int_callback,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &garmin_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+module_param(initial_mode, int, 0444);
+MODULE_PARM_DESC(initial_mode, "Initial mode");
diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c
new file mode 100644
index 000000000..6eaf74930
--- /dev/null
+++ b/drivers/usb/serial/generic.c
@@ -0,0 +1,656 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Serial Converter Generic functions
+ *
+ * Copyright (C) 2010 - 2013 Johan Hovold (jhovold@gmail.com)
+ * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched/signal.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/sysrq.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/kfifo.h>
+#include <linux/serial.h>
+
+#ifdef CONFIG_USB_SERIAL_GENERIC
+
+static __u16 vendor = 0x05f9;
+static __u16 product = 0xffff;
+
+module_param(vendor, ushort, 0);
+MODULE_PARM_DESC(vendor, "User specified USB idVendor");
+
+module_param(product, ushort, 0);
+MODULE_PARM_DESC(product, "User specified USB idProduct");
+
+static struct usb_device_id generic_device_ids[2]; /* Initially all zeroes. */
+
+static int usb_serial_generic_probe(struct usb_serial *serial,
+ const struct usb_device_id *id)
+{
+ struct device *dev = &serial->interface->dev;
+
+ dev_info(dev, "The \"generic\" usb-serial driver is only for testing and one-off prototypes.\n");
+ dev_info(dev, "Tell linux-usb@vger.kernel.org to add your device to a proper driver.\n");
+
+ return 0;
+}
+
+static int usb_serial_generic_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ struct device *dev = &serial->interface->dev;
+ int num_ports;
+
+ num_ports = max(epds->num_bulk_in, epds->num_bulk_out);
+
+ if (num_ports == 0) {
+ dev_err(dev, "device has no bulk endpoints\n");
+ return -ENODEV;
+ }
+
+ return num_ports;
+}
+
+static struct usb_serial_driver usb_serial_generic_device = {
+ .driver = {
+ .name = "generic",
+ },
+ .id_table = generic_device_ids,
+ .probe = usb_serial_generic_probe,
+ .calc_num_ports = usb_serial_generic_calc_num_ports,
+ .throttle = usb_serial_generic_throttle,
+ .unthrottle = usb_serial_generic_unthrottle,
+ .resume = usb_serial_generic_resume,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &usb_serial_generic_device, NULL
+};
+
+#endif
+
+int usb_serial_generic_register(void)
+{
+ int retval = 0;
+
+#ifdef CONFIG_USB_SERIAL_GENERIC
+ generic_device_ids[0].idVendor = vendor;
+ generic_device_ids[0].idProduct = product;
+ generic_device_ids[0].match_flags =
+ USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT;
+
+ retval = usb_serial_register_drivers(serial_drivers,
+ "usbserial_generic", generic_device_ids);
+#endif
+ return retval;
+}
+
+void usb_serial_generic_deregister(void)
+{
+#ifdef CONFIG_USB_SERIAL_GENERIC
+ usb_serial_deregister_drivers(serial_drivers);
+#endif
+}
+
+int usb_serial_generic_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ int result = 0;
+
+ clear_bit(USB_SERIAL_THROTTLED, &port->flags);
+
+ if (port->bulk_in_size)
+ result = usb_serial_generic_submit_read_urbs(port, GFP_KERNEL);
+
+ return result;
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_open);
+
+void usb_serial_generic_close(struct usb_serial_port *port)
+{
+ unsigned long flags;
+ int i;
+
+ if (port->bulk_out_size) {
+ for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i)
+ usb_kill_urb(port->write_urbs[i]);
+
+ spin_lock_irqsave(&port->lock, flags);
+ kfifo_reset_out(&port->write_fifo);
+ spin_unlock_irqrestore(&port->lock, flags);
+ }
+ if (port->bulk_in_size) {
+ for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i)
+ usb_kill_urb(port->read_urbs[i]);
+ }
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_close);
+
+int usb_serial_generic_prepare_write_buffer(struct usb_serial_port *port,
+ void *dest, size_t size)
+{
+ return kfifo_out_locked(&port->write_fifo, dest, size, &port->lock);
+}
+
+/**
+ * usb_serial_generic_write_start - start writing buffered data
+ * @port: usb-serial port
+ * @mem_flags: flags to use for memory allocations
+ *
+ * Serialised using USB_SERIAL_WRITE_BUSY flag.
+ *
+ * Return: Zero on success or if busy, otherwise a negative errno value.
+ */
+int usb_serial_generic_write_start(struct usb_serial_port *port,
+ gfp_t mem_flags)
+{
+ struct urb *urb;
+ int count, result;
+ unsigned long flags;
+ int i;
+
+ if (test_and_set_bit_lock(USB_SERIAL_WRITE_BUSY, &port->flags))
+ return 0;
+retry:
+ spin_lock_irqsave(&port->lock, flags);
+ if (!port->write_urbs_free || !kfifo_len(&port->write_fifo)) {
+ clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags);
+ spin_unlock_irqrestore(&port->lock, flags);
+ return 0;
+ }
+ i = (int)find_first_bit(&port->write_urbs_free,
+ ARRAY_SIZE(port->write_urbs));
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ urb = port->write_urbs[i];
+ count = port->serial->type->prepare_write_buffer(port,
+ urb->transfer_buffer,
+ port->bulk_out_size);
+ urb->transfer_buffer_length = count;
+ usb_serial_debug_data(&port->dev, __func__, count, urb->transfer_buffer);
+ spin_lock_irqsave(&port->lock, flags);
+ port->tx_bytes += count;
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ clear_bit(i, &port->write_urbs_free);
+ result = usb_submit_urb(urb, mem_flags);
+ if (result) {
+ dev_err_console(port, "%s - error submitting urb: %d\n",
+ __func__, result);
+ set_bit(i, &port->write_urbs_free);
+ spin_lock_irqsave(&port->lock, flags);
+ port->tx_bytes -= count;
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags);
+ return result;
+ }
+
+ goto retry; /* try sending off another urb */
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_write_start);
+
+/**
+ * usb_serial_generic_write - generic write function
+ * @tty: tty for the port
+ * @port: usb-serial port
+ * @buf: data to write
+ * @count: number of bytes to write
+ *
+ * Return: The number of characters buffered, which may be anything from
+ * zero to @count, or a negative errno value.
+ */
+int usb_serial_generic_write(struct tty_struct *tty,
+ struct usb_serial_port *port, const unsigned char *buf, int count)
+{
+ int result;
+
+ if (!port->bulk_out_size)
+ return -ENODEV;
+
+ if (!count)
+ return 0;
+
+ count = kfifo_in_locked(&port->write_fifo, buf, count, &port->lock);
+ result = usb_serial_generic_write_start(port, GFP_ATOMIC);
+ if (result)
+ return result;
+
+ return count;
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_write);
+
+unsigned int usb_serial_generic_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned long flags;
+ unsigned int room;
+
+ if (!port->bulk_out_size)
+ return 0;
+
+ spin_lock_irqsave(&port->lock, flags);
+ room = kfifo_avail(&port->write_fifo);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, room);
+ return room;
+}
+
+unsigned int usb_serial_generic_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned long flags;
+ unsigned int chars;
+
+ if (!port->bulk_out_size)
+ return 0;
+
+ spin_lock_irqsave(&port->lock, flags);
+ chars = kfifo_len(&port->write_fifo) + port->tx_bytes;
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, chars);
+ return chars;
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_chars_in_buffer);
+
+void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned int bps;
+ unsigned long period;
+ unsigned long expire;
+
+ bps = tty_get_baud_rate(tty);
+ if (!bps)
+ bps = 9600; /* B0 */
+ /*
+ * Use a poll-period of roughly the time it takes to send one
+ * character or at least one jiffy.
+ */
+ period = max_t(unsigned long, (10 * HZ / bps), 1);
+ if (timeout)
+ period = min_t(unsigned long, period, timeout);
+
+ dev_dbg(&port->dev, "%s - timeout = %u ms, period = %u ms\n",
+ __func__, jiffies_to_msecs(timeout),
+ jiffies_to_msecs(period));
+ expire = jiffies + timeout;
+ while (!port->serial->type->tx_empty(port)) {
+ schedule_timeout_interruptible(period);
+ if (signal_pending(current))
+ break;
+ if (timeout && time_after(jiffies, expire))
+ break;
+ }
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_wait_until_sent);
+
+static int usb_serial_generic_submit_read_urb(struct usb_serial_port *port,
+ int index, gfp_t mem_flags)
+{
+ int res;
+
+ if (!test_and_clear_bit(index, &port->read_urbs_free))
+ return 0;
+
+ dev_dbg(&port->dev, "%s - urb %d\n", __func__, index);
+
+ res = usb_submit_urb(port->read_urbs[index], mem_flags);
+ if (res) {
+ if (res != -EPERM && res != -ENODEV) {
+ dev_err(&port->dev,
+ "%s - usb_submit_urb failed: %d\n",
+ __func__, res);
+ }
+ set_bit(index, &port->read_urbs_free);
+ return res;
+ }
+
+ return 0;
+}
+
+int usb_serial_generic_submit_read_urbs(struct usb_serial_port *port,
+ gfp_t mem_flags)
+{
+ int res;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) {
+ res = usb_serial_generic_submit_read_urb(port, i, mem_flags);
+ if (res)
+ goto err;
+ }
+
+ return 0;
+err:
+ for (; i >= 0; --i)
+ usb_kill_urb(port->read_urbs[i]);
+
+ return res;
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_submit_read_urbs);
+
+void usb_serial_generic_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ char *ch = urb->transfer_buffer;
+ int i;
+
+ if (!urb->actual_length)
+ return;
+ /*
+ * The per character mucking around with sysrq path it too slow for
+ * stuff like 3G modems, so shortcircuit it in the 99.9999999% of
+ * cases where the USB serial is not a console anyway.
+ */
+ if (port->sysrq) {
+ for (i = 0; i < urb->actual_length; i++, ch++) {
+ if (!usb_serial_handle_sysrq_char(port, *ch))
+ tty_insert_flip_char(&port->port, *ch, TTY_NORMAL);
+ }
+ } else {
+ tty_insert_flip_string(&port->port, ch, urb->actual_length);
+ }
+ tty_flip_buffer_push(&port->port);
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_process_read_urb);
+
+void usb_serial_generic_read_bulk_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ bool stopped = false;
+ int status = urb->status;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) {
+ if (urb == port->read_urbs[i])
+ break;
+ }
+
+ dev_dbg(&port->dev, "%s - urb %d, len %d\n", __func__, i,
+ urb->actual_length);
+ switch (status) {
+ case 0:
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length,
+ data);
+ port->serial->type->process_read_urb(urb);
+ break;
+ case -ENOENT:
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ dev_dbg(&port->dev, "%s - urb stopped: %d\n",
+ __func__, status);
+ stopped = true;
+ break;
+ case -EPIPE:
+ dev_err(&port->dev, "%s - urb stopped: %d\n",
+ __func__, status);
+ stopped = true;
+ break;
+ default:
+ dev_dbg(&port->dev, "%s - nonzero urb status: %d\n",
+ __func__, status);
+ break;
+ }
+
+ /*
+ * Make sure URB processing is done before marking as free to avoid
+ * racing with unthrottle() on another CPU. Matches the barriers
+ * implied by the test_and_clear_bit() in
+ * usb_serial_generic_submit_read_urb().
+ */
+ smp_mb__before_atomic();
+ set_bit(i, &port->read_urbs_free);
+ /*
+ * Make sure URB is marked as free before checking the throttled flag
+ * to avoid racing with unthrottle() on another CPU. Matches the
+ * smp_mb__after_atomic() in unthrottle().
+ */
+ smp_mb__after_atomic();
+
+ if (stopped)
+ return;
+
+ if (test_bit(USB_SERIAL_THROTTLED, &port->flags))
+ return;
+
+ usb_serial_generic_submit_read_urb(port, i, GFP_ATOMIC);
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_read_bulk_callback);
+
+void usb_serial_generic_write_bulk_callback(struct urb *urb)
+{
+ unsigned long flags;
+ struct usb_serial_port *port = urb->context;
+ int status = urb->status;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) {
+ if (port->write_urbs[i] == urb)
+ break;
+ }
+ spin_lock_irqsave(&port->lock, flags);
+ port->tx_bytes -= urb->transfer_buffer_length;
+ set_bit(i, &port->write_urbs_free);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ switch (status) {
+ case 0:
+ break;
+ case -ENOENT:
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ dev_dbg(&port->dev, "%s - urb stopped: %d\n",
+ __func__, status);
+ return;
+ case -EPIPE:
+ dev_err_console(port, "%s - urb stopped: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_err_console(port, "%s - nonzero urb status: %d\n",
+ __func__, status);
+ break;
+ }
+
+ usb_serial_generic_write_start(port, GFP_ATOMIC);
+ usb_serial_port_softint(port);
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_write_bulk_callback);
+
+void usb_serial_generic_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ set_bit(USB_SERIAL_THROTTLED, &port->flags);
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_throttle);
+
+void usb_serial_generic_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ clear_bit(USB_SERIAL_THROTTLED, &port->flags);
+
+ /*
+ * Matches the smp_mb__after_atomic() in
+ * usb_serial_generic_read_bulk_callback().
+ */
+ smp_mb__after_atomic();
+
+ usb_serial_generic_submit_read_urbs(port, GFP_KERNEL);
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_unthrottle);
+
+static bool usb_serial_generic_msr_changed(struct tty_struct *tty,
+ unsigned long arg, struct async_icount *cprev)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct async_icount cnow;
+ unsigned long flags;
+ bool ret;
+
+ /*
+ * Use tty-port initialised flag to detect all hangups including the
+ * one generated at USB-device disconnect.
+ */
+ if (!tty_port_initialized(&port->port))
+ return true;
+
+ spin_lock_irqsave(&port->lock, flags);
+ cnow = port->icount; /* atomic copy*/
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ ret = ((arg & TIOCM_RNG) && (cnow.rng != cprev->rng)) ||
+ ((arg & TIOCM_DSR) && (cnow.dsr != cprev->dsr)) ||
+ ((arg & TIOCM_CD) && (cnow.dcd != cprev->dcd)) ||
+ ((arg & TIOCM_CTS) && (cnow.cts != cprev->cts));
+
+ *cprev = cnow;
+
+ return ret;
+}
+
+int usb_serial_generic_tiocmiwait(struct tty_struct *tty, unsigned long arg)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct async_icount cnow;
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&port->lock, flags);
+ cnow = port->icount; /* atomic copy */
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ ret = wait_event_interruptible(port->port.delta_msr_wait,
+ usb_serial_generic_msr_changed(tty, arg, &cnow));
+ if (!ret && !tty_port_initialized(&port->port))
+ ret = -EIO;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_tiocmiwait);
+
+int usb_serial_generic_get_icount(struct tty_struct *tty,
+ struct serial_icounter_struct *icount)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct async_icount cnow;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ cnow = port->icount; /* atomic copy */
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ icount->cts = cnow.cts;
+ icount->dsr = cnow.dsr;
+ icount->rng = cnow.rng;
+ icount->dcd = cnow.dcd;
+ icount->tx = cnow.tx;
+ icount->rx = cnow.rx;
+ icount->frame = cnow.frame;
+ icount->parity = cnow.parity;
+ icount->overrun = cnow.overrun;
+ icount->brk = cnow.brk;
+ icount->buf_overrun = cnow.buf_overrun;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_get_icount);
+
+#if defined(CONFIG_USB_SERIAL_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
+int usb_serial_handle_sysrq_char(struct usb_serial_port *port, unsigned int ch)
+{
+ if (port->sysrq) {
+ if (ch && time_before(jiffies, port->sysrq)) {
+ handle_sysrq(ch);
+ port->sysrq = 0;
+ return 1;
+ }
+ port->sysrq = 0;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_serial_handle_sysrq_char);
+
+int usb_serial_handle_break(struct usb_serial_port *port)
+{
+ if (!port->port.console)
+ return 0;
+
+ if (!port->sysrq) {
+ port->sysrq = jiffies + HZ*5;
+ return 1;
+ }
+ port->sysrq = 0;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_serial_handle_break);
+#endif
+
+/**
+ * usb_serial_handle_dcd_change - handle a change of carrier detect state
+ * @port: usb-serial port
+ * @tty: tty for the port
+ * @status: new carrier detect status, nonzero if active
+ */
+void usb_serial_handle_dcd_change(struct usb_serial_port *port,
+ struct tty_struct *tty, unsigned int status)
+{
+ dev_dbg(&port->dev, "%s - status %d\n", __func__, status);
+
+ if (tty) {
+ struct tty_ldisc *ld = tty_ldisc_ref(tty);
+
+ if (ld) {
+ if (ld->ops->dcd_change)
+ ld->ops->dcd_change(tty, status);
+ tty_ldisc_deref(ld);
+ }
+ }
+
+ if (status)
+ wake_up_interruptible(&port->port.open_wait);
+ else if (tty && !C_CLOCAL(tty))
+ tty_hangup(tty);
+}
+EXPORT_SYMBOL_GPL(usb_serial_handle_dcd_change);
+
+int usb_serial_generic_resume(struct usb_serial *serial)
+{
+ struct usb_serial_port *port;
+ int i, c = 0, r;
+
+ for (i = 0; i < serial->num_ports; i++) {
+ port = serial->port[i];
+ if (!tty_port_initialized(&port->port))
+ continue;
+
+ if (port->bulk_in_size) {
+ r = usb_serial_generic_submit_read_urbs(port,
+ GFP_NOIO);
+ if (r < 0)
+ c++;
+ }
+
+ if (port->bulk_out_size) {
+ r = usb_serial_generic_write_start(port, GFP_NOIO);
+ if (r < 0)
+ c++;
+ }
+ }
+
+ return c ? -EIO : 0;
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_resume);
diff --git a/drivers/usb/serial/io_16654.h b/drivers/usb/serial/io_16654.h
new file mode 100644
index 000000000..f18501f05
--- /dev/null
+++ b/drivers/usb/serial/io_16654.h
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/************************************************************************
+ *
+ * 16654.H Definitions for 16C654 UART used on EdgePorts
+ *
+ * Copyright (C) 1998 Inside Out Networks, Inc.
+ *
+ ************************************************************************/
+
+#if !defined(_16654_H)
+#define _16654_H
+
+/************************************************************************
+ *
+ * D e f i n e s / T y p e d e f s
+ *
+ ************************************************************************/
+
+ //
+ // UART register numbers
+ // Numbers 0-7 are passed to the Edgeport directly. Numbers 8 and
+ // above are used internally to indicate that we must enable access
+ // to them via LCR bit 0x80 or LCR = 0xBF.
+ // The register number sent to the Edgeport is then (x & 0x7).
+ //
+ // Driver must not access registers that affect operation of the
+ // the EdgePort firmware -- that includes THR, RHR, IER, FCR.
+
+
+#define THR 0 // ! Transmit Holding Register (Write)
+#define RDR 0 // ! Receive Holding Register (Read)
+#define IER 1 // ! Interrupt Enable Register
+#define FCR 2 // ! Fifo Control Register (Write)
+#define ISR 2 // Interrupt Status Register (Read)
+#define LCR 3 // Line Control Register
+#define MCR 4 // Modem Control Register
+#define LSR 5 // Line Status Register
+#define MSR 6 // Modem Status Register
+#define SPR 7 // ScratchPad Register
+#define DLL 8 // Bank2[ 0 ] Divisor Latch LSB
+#define DLM 9 // Bank2[ 1 ] Divisor Latch MSB
+#define EFR 10 // Bank2[ 2 ] Extended Function Register
+//efine unused 11 // Bank2[ 3 ]
+#define XON1 12 // Bank2[ 4 ] Xon-1
+#define XON2 13 // Bank2[ 5 ] Xon-2
+#define XOFF1 14 // Bank2[ 6 ] Xoff-1
+#define XOFF2 15 // Bank2[ 7 ] Xoff-2
+
+#define NUM_16654_REGS 16
+
+#define IS_REG_2ND_BANK(x) ((x) >= 8)
+
+ //
+ // Bit definitions for each register
+ //
+
+#define IER_RX 0x01 // Enable receive interrupt
+#define IER_TX 0x02 // Enable transmit interrupt
+#define IER_RXS 0x04 // Enable receive status interrupt
+#define IER_MDM 0x08 // Enable modem status interrupt
+#define IER_SLEEP 0x10 // Enable sleep mode
+#define IER_XOFF 0x20 // Enable s/w flow control (XOFF) interrupt
+#define IER_RTS 0x40 // Enable RTS interrupt
+#define IER_CTS 0x80 // Enable CTS interrupt
+#define IER_ENABLE_ALL 0xFF // Enable all ints
+
+
+#define FCR_FIFO_EN 0x01 // Enable FIFOs
+#define FCR_RXCLR 0x02 // Reset Rx FIFO
+#define FCR_TXCLR 0x04 // Reset Tx FIFO
+#define FCR_DMA_BLK 0x08 // Enable DMA block mode
+#define FCR_TX_LEVEL_MASK 0x30 // Mask for Tx FIFO Level
+#define FCR_TX_LEVEL_8 0x00 // Tx FIFO Level = 8 bytes
+#define FCR_TX_LEVEL_16 0x10 // Tx FIFO Level = 16 bytes
+#define FCR_TX_LEVEL_32 0x20 // Tx FIFO Level = 32 bytes
+#define FCR_TX_LEVEL_56 0x30 // Tx FIFO Level = 56 bytes
+#define FCR_RX_LEVEL_MASK 0xC0 // Mask for Rx FIFO Level
+#define FCR_RX_LEVEL_8 0x00 // Rx FIFO Level = 8 bytes
+#define FCR_RX_LEVEL_16 0x40 // Rx FIFO Level = 16 bytes
+#define FCR_RX_LEVEL_56 0x80 // Rx FIFO Level = 56 bytes
+#define FCR_RX_LEVEL_60 0xC0 // Rx FIFO Level = 60 bytes
+
+
+#define ISR_INT_MDM_STATUS 0x00 // Modem status int pending
+#define ISR_INT_NONE 0x01 // No interrupt pending
+#define ISR_INT_TXRDY 0x02 // Tx ready int pending
+#define ISR_INT_RXRDY 0x04 // Rx ready int pending
+#define ISR_INT_LINE_STATUS 0x06 // Line status int pending
+#define ISR_INT_RX_TIMEOUT 0x0C // Rx timeout int pending
+#define ISR_INT_RX_XOFF 0x10 // Rx Xoff int pending
+#define ISR_INT_RTS_CTS 0x20 // RTS/CTS change int pending
+#define ISR_FIFO_ENABLED 0xC0 // Bits set if FIFOs enabled
+#define ISR_INT_BITS_MASK 0x3E // Mask to isolate valid int causes
+
+
+#define LCR_BITS_5 0x00 // 5 bits/char
+#define LCR_BITS_6 0x01 // 6 bits/char
+#define LCR_BITS_7 0x02 // 7 bits/char
+#define LCR_BITS_8 0x03 // 8 bits/char
+#define LCR_BITS_MASK 0x03 // Mask for bits/char field
+
+#define LCR_STOP_1 0x00 // 1 stop bit
+#define LCR_STOP_1_5 0x04 // 1.5 stop bits (if 5 bits/char)
+#define LCR_STOP_2 0x04 // 2 stop bits (if 6-8 bits/char)
+#define LCR_STOP_MASK 0x04 // Mask for stop bits field
+
+#define LCR_PAR_NONE 0x00 // No parity
+#define LCR_PAR_ODD 0x08 // Odd parity
+#define LCR_PAR_EVEN 0x18 // Even parity
+#define LCR_PAR_MARK 0x28 // Force parity bit to 1
+#define LCR_PAR_SPACE 0x38 // Force parity bit to 0
+#define LCR_PAR_MASK 0x38 // Mask for parity field
+
+#define LCR_SET_BREAK 0x40 // Set Break condition
+#define LCR_DL_ENABLE 0x80 // Enable access to divisor latch
+
+#define LCR_ACCESS_EFR 0xBF // Load this value to access DLL,DLM,
+ // and also the '654-only registers
+ // EFR, XON1, XON2, XOFF1, XOFF2
+
+
+#define MCR_DTR 0x01 // Assert DTR
+#define MCR_RTS 0x02 // Assert RTS
+#define MCR_OUT1 0x04 // Loopback only: Sets state of RI
+#define MCR_MASTER_IE 0x08 // Enable interrupt outputs
+#define MCR_LOOPBACK 0x10 // Set internal (digital) loopback mode
+#define MCR_XON_ANY 0x20 // Enable any char to exit XOFF mode
+#define MCR_IR_ENABLE 0x40 // Enable IrDA functions
+#define MCR_BRG_DIV_4 0x80 // Divide baud rate clk by /4 instead of /1
+
+
+#define LSR_RX_AVAIL 0x01 // Rx data available
+#define LSR_OVER_ERR 0x02 // Rx overrun
+#define LSR_PAR_ERR 0x04 // Rx parity error
+#define LSR_FRM_ERR 0x08 // Rx framing error
+#define LSR_BREAK 0x10 // Rx break condition detected
+#define LSR_TX_EMPTY 0x20 // Tx Fifo empty
+#define LSR_TX_ALL_EMPTY 0x40 // Tx Fifo and shift register empty
+#define LSR_FIFO_ERR 0x80 // Rx Fifo contains at least 1 erred char
+
+
+#define EDGEPORT_MSR_DELTA_CTS 0x01 // CTS changed from last read
+#define EDGEPORT_MSR_DELTA_DSR 0x02 // DSR changed from last read
+#define EDGEPORT_MSR_DELTA_RI 0x04 // RI changed from 0 -> 1
+#define EDGEPORT_MSR_DELTA_CD 0x08 // CD changed from last read
+#define EDGEPORT_MSR_CTS 0x10 // Current state of CTS
+#define EDGEPORT_MSR_DSR 0x20 // Current state of DSR
+#define EDGEPORT_MSR_RI 0x40 // Current state of RI
+#define EDGEPORT_MSR_CD 0x80 // Current state of CD
+
+
+
+ // Tx Rx
+ //-------------------------------
+#define EFR_SWFC_NONE 0x00 // None None
+#define EFR_SWFC_RX1 0x02 // None XOFF1
+#define EFR_SWFC_RX2 0x01 // None XOFF2
+#define EFR_SWFC_RX12 0x03 // None XOFF1 & XOFF2
+#define EFR_SWFC_TX1 0x08 // XOFF1 None
+#define EFR_SWFC_TX1_RX1 0x0a // XOFF1 XOFF1
+#define EFR_SWFC_TX1_RX2 0x09 // XOFF1 XOFF2
+#define EFR_SWFC_TX1_RX12 0x0b // XOFF1 XOFF1 & XOFF2
+#define EFR_SWFC_TX2 0x04 // XOFF2 None
+#define EFR_SWFC_TX2_RX1 0x06 // XOFF2 XOFF1
+#define EFR_SWFC_TX2_RX2 0x05 // XOFF2 XOFF2
+#define EFR_SWFC_TX2_RX12 0x07 // XOFF2 XOFF1 & XOFF2
+#define EFR_SWFC_TX12 0x0c // XOFF1 & XOFF2 None
+#define EFR_SWFC_TX12_RX1 0x0e // XOFF1 & XOFF2 XOFF1
+#define EFR_SWFC_TX12_RX2 0x0d // XOFF1 & XOFF2 XOFF2
+#define EFR_SWFC_TX12_RX12 0x0f // XOFF1 & XOFF2 XOFF1 & XOFF2
+
+#define EFR_TX_FC_MASK 0x0c // Mask to isolate Rx flow control
+#define EFR_TX_FC_NONE 0x00 // No Tx Xon/Xoff flow control
+#define EFR_TX_FC_X1 0x08 // Transmit Xon1/Xoff1
+#define EFR_TX_FC_X2 0x04 // Transmit Xon2/Xoff2
+#define EFR_TX_FC_X1_2 0x0c // Transmit Xon1&2/Xoff1&2
+
+#define EFR_RX_FC_MASK 0x03 // Mask to isolate Rx flow control
+#define EFR_RX_FC_NONE 0x00 // No Rx Xon/Xoff flow control
+#define EFR_RX_FC_X1 0x02 // Receiver compares Xon1/Xoff1
+#define EFR_RX_FC_X2 0x01 // Receiver compares Xon2/Xoff2
+#define EFR_RX_FC_X1_2 0x03 // Receiver compares Xon1&2/Xoff1&2
+
+
+#define EFR_SWFC_MASK 0x0F // Mask for software flow control field
+#define EFR_ENABLE_16654 0x10 // Enable 16C654 features
+#define EFR_SPEC_DETECT 0x20 // Enable special character detect interrupt
+#define EFR_AUTO_RTS 0x40 // Use RTS for Rx flow control
+#define EFR_AUTO_CTS 0x80 // Use CTS for Tx flow control
+
+#endif // if !defined(_16654_H)
+
diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c
new file mode 100644
index 000000000..34ccf7820
--- /dev/null
+++ b/drivers/usb/serial/io_edgeport.c
@@ -0,0 +1,3131 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Edgeport USB Serial Converter driver
+ *
+ * Copyright (C) 2000 Inside Out Networks, All rights reserved.
+ * Copyright (C) 2001-2002 Greg Kroah-Hartman <greg@kroah.com>
+ *
+ * Supports the following devices:
+ * Edgeport/4
+ * Edgeport/4t
+ * Edgeport/2
+ * Edgeport/4i
+ * Edgeport/2i
+ * Edgeport/421
+ * Edgeport/21
+ * Rapidport/4
+ * Edgeport/8
+ * Edgeport/2D8
+ * Edgeport/4D8
+ * Edgeport/8i
+ *
+ * For questions or problems with this driver, contact Inside Out
+ * Networks technical support, or Peter Berger <pberger@brimson.com>,
+ * or Al Borchers <alborchers@steinerpoint.com>.
+ *
+ */
+
+#include <linux/atomic.h>
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/serial.h>
+#include <linux/ioctl.h>
+#include <linux/wait.h>
+#include <linux/firmware.h>
+#include <linux/ihex.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include "io_edgeport.h"
+#include "io_ionsp.h" /* info for the iosp messages */
+#include "io_16654.h" /* 16654 UART defines */
+
+#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com> and David Iacovelli"
+#define DRIVER_DESC "Edgeport USB Serial Driver"
+
+#define MAX_NAME_LEN 64
+
+#define OPEN_TIMEOUT (5*HZ) /* 5 seconds */
+
+static const struct usb_device_id edgeport_2port_id_table[] = {
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_2) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_2I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_421) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_21) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_2_DIN) },
+ { }
+};
+
+static const struct usb_device_id edgeport_4port_id_table[] = {
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_RAPIDPORT_4) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4T) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_MT4X56USB) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8_DUAL_CPU) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4_DIN) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_22I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_412_4) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_COMPATIBLE) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_BLACKBOX_IC135A) },
+ { }
+};
+
+static const struct usb_device_id edgeport_8port_id_table[] = {
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_16_DUAL_CPU) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8R) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8RR) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_412_8) },
+ { }
+};
+
+static const struct usb_device_id Epic_port_id_table[] = {
+ { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0202) },
+ { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0203) },
+ { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0310) },
+ { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0311) },
+ { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0312) },
+ { USB_DEVICE(USB_VENDOR_ID_AXIOHM, AXIOHM_DEVICE_ID_EPIC_A758) },
+ { USB_DEVICE(USB_VENDOR_ID_AXIOHM, AXIOHM_DEVICE_ID_EPIC_A794) },
+ { USB_DEVICE(USB_VENDOR_ID_AXIOHM, AXIOHM_DEVICE_ID_EPIC_A225) },
+ { }
+};
+
+/* Devices that this driver supports */
+static const struct usb_device_id id_table_combined[] = {
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_RAPIDPORT_4) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4T) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_MT4X56USB) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_2) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_2I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_421) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_21) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8_DUAL_CPU) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_2_DIN) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4_DIN) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_16_DUAL_CPU) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_22I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_412_4) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_COMPATIBLE) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8R) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8RR) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_412_8) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_BLACKBOX_IC135A) },
+ { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0202) },
+ { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0203) },
+ { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0310) },
+ { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0311) },
+ { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0312) },
+ { USB_DEVICE(USB_VENDOR_ID_AXIOHM, AXIOHM_DEVICE_ID_EPIC_A758) },
+ { USB_DEVICE(USB_VENDOR_ID_AXIOHM, AXIOHM_DEVICE_ID_EPIC_A794) },
+ { USB_DEVICE(USB_VENDOR_ID_AXIOHM, AXIOHM_DEVICE_ID_EPIC_A225) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table_combined);
+
+
+/* receive port state */
+enum RXSTATE {
+ EXPECT_HDR1 = 0, /* Expect header byte 1 */
+ EXPECT_HDR2 = 1, /* Expect header byte 2 */
+ EXPECT_DATA = 2, /* Expect 'RxBytesRemaining' data */
+ EXPECT_HDR3 = 3, /* Expect header byte 3 (for status hdrs only) */
+};
+
+
+/* Transmit Fifo
+ * This Transmit queue is an extension of the edgeport Rx buffer.
+ * The maximum amount of data buffered in both the edgeport
+ * Rx buffer (maxTxCredits) and this buffer will never exceed maxTxCredits.
+ */
+struct TxFifo {
+ unsigned int head; /* index to head pointer (write) */
+ unsigned int tail; /* index to tail pointer (read) */
+ unsigned int count; /* Bytes in queue */
+ unsigned int size; /* Max size of queue (equal to Max number of TxCredits) */
+ unsigned char *fifo; /* allocated Buffer */
+};
+
+/* This structure holds all of the local port information */
+struct edgeport_port {
+ __u16 txCredits; /* our current credits for this port */
+ __u16 maxTxCredits; /* the max size of the port */
+
+ struct TxFifo txfifo; /* transmit fifo -- size will be maxTxCredits */
+ struct urb *write_urb; /* write URB for this port */
+ bool write_in_progress; /* 'true' while a write URB is outstanding */
+ spinlock_t ep_lock;
+
+ __u8 shadowLCR; /* last LCR value received */
+ __u8 shadowMCR; /* last MCR value received */
+ __u8 shadowMSR; /* last MSR value received */
+ __u8 shadowLSR; /* last LSR value received */
+ __u8 shadowXonChar; /* last value set as XON char in Edgeport */
+ __u8 shadowXoffChar; /* last value set as XOFF char in Edgeport */
+ __u8 validDataMask;
+ __u32 baudRate;
+
+ bool open;
+ bool openPending;
+ bool commandPending;
+ bool closePending;
+ bool chaseResponsePending;
+
+ wait_queue_head_t wait_chase; /* for handling sleeping while waiting for chase to finish */
+ wait_queue_head_t wait_open; /* for handling sleeping while waiting for open to finish */
+ wait_queue_head_t wait_command; /* for handling sleeping while waiting for command to finish */
+
+ struct usb_serial_port *port; /* loop back to the owner of this object */
+};
+
+
+/* This structure holds all of the individual device information */
+struct edgeport_serial {
+ char name[MAX_NAME_LEN+2]; /* string name of this device */
+
+ struct edge_manuf_descriptor manuf_descriptor; /* the manufacturer descriptor */
+ struct edge_boot_descriptor boot_descriptor; /* the boot firmware descriptor */
+ struct edgeport_product_info product_info; /* Product Info */
+ struct edge_compatibility_descriptor epic_descriptor; /* Edgeport compatible descriptor */
+ int is_epic; /* flag if EPiC device or not */
+
+ __u8 interrupt_in_endpoint; /* the interrupt endpoint handle */
+ unsigned char *interrupt_in_buffer; /* the buffer we use for the interrupt endpoint */
+ struct urb *interrupt_read_urb; /* our interrupt urb */
+
+ __u8 bulk_in_endpoint; /* the bulk in endpoint handle */
+ unsigned char *bulk_in_buffer; /* the buffer we use for the bulk in endpoint */
+ struct urb *read_urb; /* our bulk read urb */
+ bool read_in_progress;
+ spinlock_t es_lock;
+
+ __u8 bulk_out_endpoint; /* the bulk out endpoint handle */
+
+ __s16 rxBytesAvail; /* the number of bytes that we need to read from this device */
+
+ enum RXSTATE rxState; /* the current state of the bulk receive processor */
+ __u8 rxHeader1; /* receive header byte 1 */
+ __u8 rxHeader2; /* receive header byte 2 */
+ __u8 rxHeader3; /* receive header byte 3 */
+ __u8 rxPort; /* the port that we are currently receiving data for */
+ __u8 rxStatusCode; /* the receive status code */
+ __u8 rxStatusParam; /* the receive status parameter */
+ __s16 rxBytesRemaining; /* the number of port bytes left to read */
+ struct usb_serial *serial; /* loop back to the owner of this object */
+};
+
+/* baud rate information */
+struct divisor_table_entry {
+ __u32 BaudRate;
+ __u16 Divisor;
+};
+
+/*
+ * Define table of divisors for Rev A EdgePort/4 hardware
+ * These assume a 3.6864MHz crystal, the standard /16, and
+ * MCR.7 = 0.
+ */
+
+static const struct divisor_table_entry divisor_table[] = {
+ { 50, 4608},
+ { 75, 3072},
+ { 110, 2095}, /* 2094.545455 => 230450 => .0217 % over */
+ { 134, 1713}, /* 1713.011152 => 230398.5 => .00065% under */
+ { 150, 1536},
+ { 300, 768},
+ { 600, 384},
+ { 1200, 192},
+ { 1800, 128},
+ { 2400, 96},
+ { 4800, 48},
+ { 7200, 32},
+ { 9600, 24},
+ { 14400, 16},
+ { 19200, 12},
+ { 38400, 6},
+ { 57600, 4},
+ { 115200, 2},
+ { 230400, 1},
+};
+
+/* Number of outstanding Command Write Urbs */
+static atomic_t CmdUrbs = ATOMIC_INIT(0);
+
+
+/* function prototypes */
+
+static void edge_close(struct usb_serial_port *port);
+
+static void process_rcvd_data(struct edgeport_serial *edge_serial,
+ unsigned char *buffer, __u16 bufferLength);
+static void process_rcvd_status(struct edgeport_serial *edge_serial,
+ __u8 byte2, __u8 byte3);
+static void edge_tty_recv(struct usb_serial_port *port, unsigned char *data,
+ int length);
+static void handle_new_msr(struct edgeport_port *edge_port, __u8 newMsr);
+static void handle_new_lsr(struct edgeport_port *edge_port, __u8 lsrData,
+ __u8 lsr, __u8 data);
+static int send_iosp_ext_cmd(struct edgeport_port *edge_port, __u8 command,
+ __u8 param);
+static int calc_baud_rate_divisor(struct device *dev, int baud_rate, int *divisor);
+static void change_port_settings(struct tty_struct *tty,
+ struct edgeport_port *edge_port,
+ const struct ktermios *old_termios);
+static int send_cmd_write_uart_register(struct edgeport_port *edge_port,
+ __u8 regNum, __u8 regValue);
+static int write_cmd_usb(struct edgeport_port *edge_port,
+ unsigned char *buffer, int writeLength);
+static void send_more_port_data(struct edgeport_serial *edge_serial,
+ struct edgeport_port *edge_port);
+
+static int rom_write(struct usb_serial *serial, __u16 extAddr, __u16 addr,
+ __u16 length, const __u8 *data);
+
+/* ************************************************************************ */
+/* ************************************************************************ */
+/* ************************************************************************ */
+/* ************************************************************************ */
+
+/************************************************************************
+ * *
+ * update_edgeport_E2PROM() Compare current versions of *
+ * Boot ROM and Manufacture *
+ * Descriptors with versions *
+ * embedded in this driver *
+ * *
+ ************************************************************************/
+static void update_edgeport_E2PROM(struct edgeport_serial *edge_serial)
+{
+ struct device *dev = &edge_serial->serial->dev->dev;
+ __u32 BootCurVer;
+ __u32 BootNewVer;
+ __u8 BootMajorVersion;
+ __u8 BootMinorVersion;
+ __u16 BootBuildNumber;
+ __u32 Bootaddr;
+ const struct ihex_binrec *rec;
+ const struct firmware *fw;
+ const char *fw_name;
+ int response;
+
+ switch (edge_serial->product_info.iDownloadFile) {
+ case EDGE_DOWNLOAD_FILE_I930:
+ fw_name = "edgeport/boot.fw";
+ break;
+ case EDGE_DOWNLOAD_FILE_80251:
+ fw_name = "edgeport/boot2.fw";
+ break;
+ default:
+ return;
+ }
+
+ response = request_ihex_firmware(&fw, fw_name,
+ &edge_serial->serial->dev->dev);
+ if (response) {
+ dev_err(dev, "Failed to load image \"%s\" err %d\n",
+ fw_name, response);
+ return;
+ }
+
+ rec = (const struct ihex_binrec *)fw->data;
+ BootMajorVersion = rec->data[0];
+ BootMinorVersion = rec->data[1];
+ BootBuildNumber = (rec->data[2] << 8) | rec->data[3];
+
+ /* Check Boot Image Version */
+ BootCurVer = (edge_serial->boot_descriptor.MajorVersion << 24) +
+ (edge_serial->boot_descriptor.MinorVersion << 16) +
+ le16_to_cpu(edge_serial->boot_descriptor.BuildNumber);
+
+ BootNewVer = (BootMajorVersion << 24) +
+ (BootMinorVersion << 16) +
+ BootBuildNumber;
+
+ dev_dbg(dev, "Current Boot Image version %d.%d.%d\n",
+ edge_serial->boot_descriptor.MajorVersion,
+ edge_serial->boot_descriptor.MinorVersion,
+ le16_to_cpu(edge_serial->boot_descriptor.BuildNumber));
+
+
+ if (BootNewVer > BootCurVer) {
+ dev_dbg(dev, "**Update Boot Image from %d.%d.%d to %d.%d.%d\n",
+ edge_serial->boot_descriptor.MajorVersion,
+ edge_serial->boot_descriptor.MinorVersion,
+ le16_to_cpu(edge_serial->boot_descriptor.BuildNumber),
+ BootMajorVersion, BootMinorVersion, BootBuildNumber);
+
+ dev_dbg(dev, "Downloading new Boot Image\n");
+
+ for (rec = ihex_next_binrec(rec); rec;
+ rec = ihex_next_binrec(rec)) {
+ Bootaddr = be32_to_cpu(rec->addr);
+ response = rom_write(edge_serial->serial,
+ Bootaddr >> 16,
+ Bootaddr & 0xFFFF,
+ be16_to_cpu(rec->len),
+ &rec->data[0]);
+ if (response < 0) {
+ dev_err(&edge_serial->serial->dev->dev,
+ "rom_write failed (%x, %x, %d)\n",
+ Bootaddr >> 16, Bootaddr & 0xFFFF,
+ be16_to_cpu(rec->len));
+ break;
+ }
+ }
+ } else {
+ dev_dbg(dev, "Boot Image -- already up to date\n");
+ }
+ release_firmware(fw);
+}
+
+static void dump_product_info(struct edgeport_serial *edge_serial,
+ struct edgeport_product_info *product_info)
+{
+ struct device *dev = &edge_serial->serial->dev->dev;
+
+ /* Dump Product Info structure */
+ dev_dbg(dev, "**Product Information:\n");
+ dev_dbg(dev, " ProductId %x\n", product_info->ProductId);
+ dev_dbg(dev, " NumPorts %d\n", product_info->NumPorts);
+ dev_dbg(dev, " ProdInfoVer %d\n", product_info->ProdInfoVer);
+ dev_dbg(dev, " IsServer %d\n", product_info->IsServer);
+ dev_dbg(dev, " IsRS232 %d\n", product_info->IsRS232);
+ dev_dbg(dev, " IsRS422 %d\n", product_info->IsRS422);
+ dev_dbg(dev, " IsRS485 %d\n", product_info->IsRS485);
+ dev_dbg(dev, " RomSize %d\n", product_info->RomSize);
+ dev_dbg(dev, " RamSize %d\n", product_info->RamSize);
+ dev_dbg(dev, " CpuRev %x\n", product_info->CpuRev);
+ dev_dbg(dev, " BoardRev %x\n", product_info->BoardRev);
+ dev_dbg(dev, " BootMajorVersion %d.%d.%d\n",
+ product_info->BootMajorVersion,
+ product_info->BootMinorVersion,
+ le16_to_cpu(product_info->BootBuildNumber));
+ dev_dbg(dev, " FirmwareMajorVersion %d.%d.%d\n",
+ product_info->FirmwareMajorVersion,
+ product_info->FirmwareMinorVersion,
+ le16_to_cpu(product_info->FirmwareBuildNumber));
+ dev_dbg(dev, " ManufactureDescDate %d/%d/%d\n",
+ product_info->ManufactureDescDate[0],
+ product_info->ManufactureDescDate[1],
+ product_info->ManufactureDescDate[2]+1900);
+ dev_dbg(dev, " iDownloadFile 0x%x\n",
+ product_info->iDownloadFile);
+ dev_dbg(dev, " EpicVer %d\n", product_info->EpicVer);
+}
+
+static void get_product_info(struct edgeport_serial *edge_serial)
+{
+ struct edgeport_product_info *product_info = &edge_serial->product_info;
+
+ memset(product_info, 0, sizeof(struct edgeport_product_info));
+
+ product_info->ProductId = (__u16)(le16_to_cpu(edge_serial->serial->dev->descriptor.idProduct) & ~ION_DEVICE_ID_80251_NETCHIP);
+ product_info->NumPorts = edge_serial->manuf_descriptor.NumPorts;
+ product_info->ProdInfoVer = 0;
+
+ product_info->RomSize = edge_serial->manuf_descriptor.RomSize;
+ product_info->RamSize = edge_serial->manuf_descriptor.RamSize;
+ product_info->CpuRev = edge_serial->manuf_descriptor.CpuRev;
+ product_info->BoardRev = edge_serial->manuf_descriptor.BoardRev;
+
+ product_info->BootMajorVersion =
+ edge_serial->boot_descriptor.MajorVersion;
+ product_info->BootMinorVersion =
+ edge_serial->boot_descriptor.MinorVersion;
+ product_info->BootBuildNumber =
+ edge_serial->boot_descriptor.BuildNumber;
+
+ memcpy(product_info->ManufactureDescDate,
+ edge_serial->manuf_descriptor.DescDate,
+ sizeof(edge_serial->manuf_descriptor.DescDate));
+
+ /* check if this is 2nd generation hardware */
+ if (le16_to_cpu(edge_serial->serial->dev->descriptor.idProduct)
+ & ION_DEVICE_ID_80251_NETCHIP)
+ product_info->iDownloadFile = EDGE_DOWNLOAD_FILE_80251;
+ else
+ product_info->iDownloadFile = EDGE_DOWNLOAD_FILE_I930;
+
+ /* Determine Product type and set appropriate flags */
+ switch (DEVICE_ID_FROM_USB_PRODUCT_ID(product_info->ProductId)) {
+ case ION_DEVICE_ID_EDGEPORT_COMPATIBLE:
+ case ION_DEVICE_ID_EDGEPORT_4T:
+ case ION_DEVICE_ID_EDGEPORT_4:
+ case ION_DEVICE_ID_EDGEPORT_2:
+ case ION_DEVICE_ID_EDGEPORT_8_DUAL_CPU:
+ case ION_DEVICE_ID_EDGEPORT_8:
+ case ION_DEVICE_ID_EDGEPORT_421:
+ case ION_DEVICE_ID_EDGEPORT_21:
+ case ION_DEVICE_ID_EDGEPORT_2_DIN:
+ case ION_DEVICE_ID_EDGEPORT_4_DIN:
+ case ION_DEVICE_ID_EDGEPORT_16_DUAL_CPU:
+ case ION_DEVICE_ID_BLACKBOX_IC135A:
+ product_info->IsRS232 = 1;
+ break;
+
+ case ION_DEVICE_ID_EDGEPORT_2I: /* Edgeport/2 RS422/RS485 */
+ product_info->IsRS422 = 1;
+ product_info->IsRS485 = 1;
+ break;
+
+ case ION_DEVICE_ID_EDGEPORT_8I: /* Edgeport/4 RS422 */
+ case ION_DEVICE_ID_EDGEPORT_4I: /* Edgeport/4 RS422 */
+ product_info->IsRS422 = 1;
+ break;
+ }
+
+ dump_product_info(edge_serial, product_info);
+}
+
+static int get_epic_descriptor(struct edgeport_serial *ep)
+{
+ int result;
+ struct usb_serial *serial = ep->serial;
+ struct edgeport_product_info *product_info = &ep->product_info;
+ struct edge_compatibility_descriptor *epic;
+ struct edge_compatibility_bits *bits;
+ struct device *dev = &serial->dev->dev;
+
+ ep->is_epic = 0;
+
+ epic = kmalloc_obj(*epic);
+ if (!epic)
+ return -ENOMEM;
+
+ result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+ USB_REQUEST_ION_GET_EPIC_DESC,
+ 0xC0, 0x00, 0x00,
+ epic, sizeof(*epic),
+ 300);
+ if (result == sizeof(*epic)) {
+ ep->is_epic = 1;
+ memcpy(&ep->epic_descriptor, epic, sizeof(*epic));
+ memset(product_info, 0, sizeof(struct edgeport_product_info));
+
+ product_info->NumPorts = epic->NumPorts;
+ product_info->ProdInfoVer = 0;
+ product_info->FirmwareMajorVersion = epic->MajorVersion;
+ product_info->FirmwareMinorVersion = epic->MinorVersion;
+ product_info->FirmwareBuildNumber = epic->BuildNumber;
+ product_info->iDownloadFile = epic->iDownloadFile;
+ product_info->EpicVer = epic->EpicVer;
+ product_info->Epic = epic->Supports;
+ product_info->ProductId = ION_DEVICE_ID_EDGEPORT_COMPATIBLE;
+ dump_product_info(ep, product_info);
+
+ bits = &ep->epic_descriptor.Supports;
+ dev_dbg(dev, "**EPIC descriptor:\n");
+ dev_dbg(dev, " VendEnableSuspend: %s\n", bits->VendEnableSuspend ? "TRUE": "FALSE");
+ dev_dbg(dev, " IOSPOpen : %s\n", bits->IOSPOpen ? "TRUE": "FALSE");
+ dev_dbg(dev, " IOSPClose : %s\n", bits->IOSPClose ? "TRUE": "FALSE");
+ dev_dbg(dev, " IOSPChase : %s\n", bits->IOSPChase ? "TRUE": "FALSE");
+ dev_dbg(dev, " IOSPSetRxFlow : %s\n", bits->IOSPSetRxFlow ? "TRUE": "FALSE");
+ dev_dbg(dev, " IOSPSetTxFlow : %s\n", bits->IOSPSetTxFlow ? "TRUE": "FALSE");
+ dev_dbg(dev, " IOSPSetXChar : %s\n", bits->IOSPSetXChar ? "TRUE": "FALSE");
+ dev_dbg(dev, " IOSPRxCheck : %s\n", bits->IOSPRxCheck ? "TRUE": "FALSE");
+ dev_dbg(dev, " IOSPSetClrBreak : %s\n", bits->IOSPSetClrBreak ? "TRUE": "FALSE");
+ dev_dbg(dev, " IOSPWriteMCR : %s\n", bits->IOSPWriteMCR ? "TRUE": "FALSE");
+ dev_dbg(dev, " IOSPWriteLCR : %s\n", bits->IOSPWriteLCR ? "TRUE": "FALSE");
+ dev_dbg(dev, " IOSPSetBaudRate : %s\n", bits->IOSPSetBaudRate ? "TRUE": "FALSE");
+ dev_dbg(dev, " TrueEdgeport : %s\n", bits->TrueEdgeport ? "TRUE": "FALSE");
+
+ result = 0;
+ } else if (result >= 0) {
+ dev_warn(&serial->interface->dev, "short epic descriptor received: %d\n",
+ result);
+ result = -EIO;
+ }
+
+ kfree(epic);
+
+ return result;
+}
+
+
+/************************************************************************/
+/************************************************************************/
+/* U S B C A L L B A C K F U N C T I O N S */
+/* U S B C A L L B A C K F U N C T I O N S */
+/************************************************************************/
+/************************************************************************/
+
+/*****************************************************************************
+ * edge_interrupt_callback
+ * this is the callback function for when we have received data on the
+ * interrupt endpoint.
+ *****************************************************************************/
+static void edge_interrupt_callback(struct urb *urb)
+{
+ struct edgeport_serial *edge_serial = urb->context;
+ struct device *dev;
+ struct edgeport_port *edge_port;
+ struct usb_serial_port *port;
+ unsigned char *data = urb->transfer_buffer;
+ int length = urb->actual_length;
+ unsigned long flags;
+ int bytes_avail;
+ int position;
+ int txCredits;
+ int portNumber;
+ int result;
+ int status = urb->status;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n", __func__, status);
+ return;
+ default:
+ dev_dbg(&urb->dev->dev, "%s - nonzero urb status received: %d\n", __func__, status);
+ goto exit;
+ }
+
+ dev = &edge_serial->serial->dev->dev;
+
+ /* process this interrupt-read even if there are no ports open */
+ if (length) {
+ usb_serial_debug_data(dev, __func__, length, data);
+
+ if (length > 1) {
+ bytes_avail = data[0] | (data[1] << 8);
+ if (bytes_avail) {
+ spin_lock_irqsave(&edge_serial->es_lock, flags);
+ edge_serial->rxBytesAvail += bytes_avail;
+ dev_dbg(dev,
+ "%s - bytes_avail=%d, rxBytesAvail=%d, read_in_progress=%d\n",
+ __func__, bytes_avail,
+ edge_serial->rxBytesAvail,
+ edge_serial->read_in_progress);
+
+ if (edge_serial->rxBytesAvail > 0 &&
+ !edge_serial->read_in_progress) {
+ dev_dbg(dev, "%s - posting a read\n", __func__);
+ edge_serial->read_in_progress = true;
+
+ /* we have pending bytes on the
+ bulk in pipe, send a request */
+ result = usb_submit_urb(edge_serial->read_urb, GFP_ATOMIC);
+ if (result) {
+ dev_err(dev,
+ "%s - usb_submit_urb(read bulk) failed with result = %d\n",
+ __func__, result);
+ edge_serial->read_in_progress = false;
+ }
+ }
+ spin_unlock_irqrestore(&edge_serial->es_lock,
+ flags);
+ }
+ }
+ /* grab the txcredits for the ports if available */
+ position = 2;
+ portNumber = 0;
+ while ((position < length - 1) &&
+ (portNumber < edge_serial->serial->num_ports)) {
+ txCredits = data[position] | (data[position+1] << 8);
+ if (txCredits) {
+ port = edge_serial->serial->port[portNumber];
+ edge_port = usb_get_serial_port_data(port);
+ if (edge_port && edge_port->open) {
+ spin_lock_irqsave(&edge_port->ep_lock,
+ flags);
+ edge_port->txCredits += txCredits;
+ spin_unlock_irqrestore(&edge_port->ep_lock,
+ flags);
+ dev_dbg(dev, "%s - txcredits for port%d = %d\n",
+ __func__, portNumber,
+ edge_port->txCredits);
+
+ /* tell the tty driver that something
+ has changed */
+ tty_port_tty_wakeup(&edge_port->port->port);
+ /* Since we have more credit, check
+ if more data can be sent */
+ send_more_port_data(edge_serial,
+ edge_port);
+ }
+ }
+ position += 2;
+ ++portNumber;
+ }
+ }
+
+exit:
+ result = usb_submit_urb(urb, GFP_ATOMIC);
+ if (result)
+ dev_err(&urb->dev->dev,
+ "%s - Error %d submitting control urb\n",
+ __func__, result);
+}
+
+
+/*****************************************************************************
+ * edge_bulk_in_callback
+ * this is the callback function for when we have received data on the
+ * bulk in endpoint.
+ *****************************************************************************/
+static void edge_bulk_in_callback(struct urb *urb)
+{
+ struct edgeport_serial *edge_serial = urb->context;
+ struct device *dev;
+ unsigned char *data = urb->transfer_buffer;
+ int retval;
+ __u16 raw_data_length;
+ int status = urb->status;
+ unsigned long flags;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero read bulk status received: %d\n",
+ __func__, status);
+ edge_serial->read_in_progress = false;
+ return;
+ }
+
+ if (urb->actual_length == 0) {
+ dev_dbg(&urb->dev->dev, "%s - read bulk callback with no data\n", __func__);
+ edge_serial->read_in_progress = false;
+ return;
+ }
+
+ dev = &edge_serial->serial->dev->dev;
+ raw_data_length = urb->actual_length;
+
+ usb_serial_debug_data(dev, __func__, raw_data_length, data);
+
+ spin_lock_irqsave(&edge_serial->es_lock, flags);
+
+ /* decrement our rxBytes available by the number that we just got */
+ edge_serial->rxBytesAvail -= raw_data_length;
+
+ dev_dbg(dev, "%s - Received = %d, rxBytesAvail %d\n", __func__,
+ raw_data_length, edge_serial->rxBytesAvail);
+
+ process_rcvd_data(edge_serial, data, urb->actual_length);
+
+ /* check to see if there's any more data for us to read */
+ if (edge_serial->rxBytesAvail > 0) {
+ dev_dbg(dev, "%s - posting a read\n", __func__);
+ retval = usb_submit_urb(edge_serial->read_urb, GFP_ATOMIC);
+ if (retval) {
+ dev_err(dev,
+ "%s - usb_submit_urb(read bulk) failed, retval = %d\n",
+ __func__, retval);
+ edge_serial->read_in_progress = false;
+ }
+ } else {
+ edge_serial->read_in_progress = false;
+ }
+
+ spin_unlock_irqrestore(&edge_serial->es_lock, flags);
+}
+
+
+/*****************************************************************************
+ * edge_bulk_out_data_callback
+ * this is the callback function for when we have finished sending
+ * serial data on the bulk out endpoint.
+ *****************************************************************************/
+static void edge_bulk_out_data_callback(struct urb *urb)
+{
+ struct edgeport_port *edge_port = urb->context;
+ int status = urb->status;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev,
+ "%s - nonzero write bulk status received: %d\n",
+ __func__, status);
+ }
+
+ if (edge_port->open)
+ tty_port_tty_wakeup(&edge_port->port->port);
+
+ /* Release the Write URB */
+ edge_port->write_in_progress = false;
+
+ /* Check if more data needs to be sent */
+ send_more_port_data((struct edgeport_serial *)
+ (usb_get_serial_data(edge_port->port->serial)), edge_port);
+}
+
+
+/*****************************************************************************
+ * BulkOutCmdCallback
+ * this is the callback function for when we have finished sending a
+ * command on the bulk out endpoint.
+ *****************************************************************************/
+static void edge_bulk_out_cmd_callback(struct urb *urb)
+{
+ struct edgeport_port *edge_port = urb->context;
+ struct device *dev = &urb->dev->dev;
+ int status = urb->status;
+
+ atomic_dec(&CmdUrbs);
+ dev_dbg(dev, "%s - FREE URB %p (outstanding %d)\n", __func__, urb,
+ atomic_read(&CmdUrbs));
+
+
+ /* clean up the transfer buffer */
+ kfree(urb->transfer_buffer);
+
+ /* Free the command urb */
+ usb_free_urb(urb);
+
+ if (status) {
+ dev_dbg(dev, "%s - nonzero write bulk status received: %d\n",
+ __func__, status);
+ return;
+ }
+
+ /* tell the tty driver that something has changed */
+ if (edge_port->open)
+ tty_port_tty_wakeup(&edge_port->port->port);
+
+ /* we have completed the command */
+ edge_port->commandPending = false;
+ wake_up(&edge_port->wait_command);
+}
+
+
+/*****************************************************************************
+ * Driver tty interface functions
+ *****************************************************************************/
+
+/*****************************************************************************
+ * SerialOpen
+ * this function is called by the tty driver when a port is opened
+ * If successful, we return 0
+ * Otherwise we return a negative error number.
+ *****************************************************************************/
+static int edge_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ struct device *dev = &port->dev;
+ struct usb_serial *serial;
+ struct edgeport_serial *edge_serial;
+ int response;
+
+ if (edge_port == NULL)
+ return -ENODEV;
+
+ /* see if we've set up our endpoint info yet (can't set it up
+ in edge_startup as the structures were not set up at that time.) */
+ serial = port->serial;
+ edge_serial = usb_get_serial_data(serial);
+ if (edge_serial == NULL)
+ return -ENODEV;
+ if (edge_serial->interrupt_in_buffer == NULL) {
+ struct usb_serial_port *port0 = serial->port[0];
+
+ /* not set up yet, so do it now */
+ edge_serial->interrupt_in_buffer =
+ port0->interrupt_in_buffer;
+ edge_serial->interrupt_in_endpoint =
+ port0->interrupt_in_endpointAddress;
+ edge_serial->interrupt_read_urb = port0->interrupt_in_urb;
+ edge_serial->bulk_in_buffer = port0->bulk_in_buffer;
+ edge_serial->bulk_in_endpoint =
+ port0->bulk_in_endpointAddress;
+ edge_serial->read_urb = port0->read_urb;
+ edge_serial->bulk_out_endpoint =
+ port0->bulk_out_endpointAddress;
+
+ /* set up our interrupt urb */
+ usb_fill_int_urb(edge_serial->interrupt_read_urb,
+ serial->dev,
+ usb_rcvintpipe(serial->dev,
+ port0->interrupt_in_endpointAddress),
+ port0->interrupt_in_buffer,
+ edge_serial->interrupt_read_urb->transfer_buffer_length,
+ edge_interrupt_callback, edge_serial,
+ edge_serial->interrupt_read_urb->interval);
+
+ /* set up our bulk in urb */
+ usb_fill_bulk_urb(edge_serial->read_urb, serial->dev,
+ usb_rcvbulkpipe(serial->dev,
+ port0->bulk_in_endpointAddress),
+ port0->bulk_in_buffer,
+ edge_serial->read_urb->transfer_buffer_length,
+ edge_bulk_in_callback, edge_serial);
+ edge_serial->read_in_progress = false;
+
+ /* start interrupt read for this edgeport
+ * this interrupt will continue as long
+ * as the edgeport is connected */
+ response = usb_submit_urb(edge_serial->interrupt_read_urb,
+ GFP_KERNEL);
+ if (response) {
+ dev_err(dev, "%s - Error %d submitting control urb\n",
+ __func__, response);
+ }
+ }
+
+ /* initialize our wait queues */
+ init_waitqueue_head(&edge_port->wait_open);
+ init_waitqueue_head(&edge_port->wait_chase);
+ init_waitqueue_head(&edge_port->wait_command);
+
+ /* initialize our port settings */
+ edge_port->txCredits = 0; /* Can't send any data yet */
+ /* Must always set this bit to enable ints! */
+ edge_port->shadowMCR = MCR_MASTER_IE;
+ edge_port->chaseResponsePending = false;
+
+ /* send a open port command */
+ edge_port->openPending = true;
+ edge_port->open = false;
+ response = send_iosp_ext_cmd(edge_port, IOSP_CMD_OPEN_PORT, 0);
+
+ if (response < 0) {
+ dev_err(dev, "%s - error sending open port command\n", __func__);
+ edge_port->openPending = false;
+ return -ENODEV;
+ }
+
+ /* now wait for the port to be completely opened */
+ wait_event_timeout(edge_port->wait_open, !edge_port->openPending,
+ OPEN_TIMEOUT);
+
+ if (!edge_port->open) {
+ /* open timed out */
+ dev_dbg(dev, "%s - open timeout\n", __func__);
+ edge_port->openPending = false;
+ return -ENODEV;
+ }
+
+ /* create the txfifo */
+ edge_port->txfifo.head = 0;
+ edge_port->txfifo.tail = 0;
+ edge_port->txfifo.count = 0;
+ edge_port->txfifo.size = edge_port->maxTxCredits;
+ edge_port->txfifo.fifo = kmalloc(edge_port->maxTxCredits, GFP_KERNEL);
+
+ if (!edge_port->txfifo.fifo) {
+ edge_close(port);
+ return -ENOMEM;
+ }
+
+ /* Allocate a URB for the write */
+ edge_port->write_urb = usb_alloc_urb(0, GFP_KERNEL);
+ edge_port->write_in_progress = false;
+
+ if (!edge_port->write_urb) {
+ edge_close(port);
+ return -ENOMEM;
+ }
+
+ dev_dbg(dev, "%s - Initialize TX fifo to %d bytes\n",
+ __func__, edge_port->maxTxCredits);
+
+ return 0;
+}
+
+
+/************************************************************************
+ *
+ * block_until_chase_response
+ *
+ * This function will block the close until one of the following:
+ * 1. Response to our Chase comes from Edgeport
+ * 2. A timeout of 10 seconds without activity has expired
+ * (1K of Edgeport data @ 2400 baud ==> 4 sec to empty)
+ *
+ ************************************************************************/
+static void block_until_chase_response(struct edgeport_port *edge_port)
+{
+ struct device *dev = &edge_port->port->dev;
+ DEFINE_WAIT(wait);
+ __u16 lastCredits;
+ int timeout = 1*HZ;
+ int loop = 10;
+
+ while (1) {
+ /* Save Last credits */
+ lastCredits = edge_port->txCredits;
+
+ /* Did we get our Chase response */
+ if (!edge_port->chaseResponsePending) {
+ dev_dbg(dev, "%s - Got Chase Response\n", __func__);
+
+ /* did we get all of our credit back? */
+ if (edge_port->txCredits == edge_port->maxTxCredits) {
+ dev_dbg(dev, "%s - Got all credits\n", __func__);
+ return;
+ }
+ }
+
+ /* Block the thread for a while */
+ prepare_to_wait(&edge_port->wait_chase, &wait,
+ TASK_UNINTERRUPTIBLE);
+ schedule_timeout(timeout);
+ finish_wait(&edge_port->wait_chase, &wait);
+
+ if (lastCredits == edge_port->txCredits) {
+ /* No activity.. count down. */
+ loop--;
+ if (loop == 0) {
+ edge_port->chaseResponsePending = false;
+ dev_dbg(dev, "%s - Chase TIMEOUT\n", __func__);
+ return;
+ }
+ } else {
+ /* Reset timeout value back to 10 seconds */
+ dev_dbg(dev, "%s - Last %d, Current %d\n", __func__,
+ lastCredits, edge_port->txCredits);
+ loop = 10;
+ }
+ }
+}
+
+
+/************************************************************************
+ *
+ * block_until_tx_empty
+ *
+ * This function will block the close until one of the following:
+ * 1. TX count are 0
+ * 2. The edgeport has stopped
+ * 3. A timeout of 3 seconds without activity has expired
+ *
+ ************************************************************************/
+static void block_until_tx_empty(struct edgeport_port *edge_port)
+{
+ struct device *dev = &edge_port->port->dev;
+ DEFINE_WAIT(wait);
+ struct TxFifo *fifo = &edge_port->txfifo;
+ __u32 lastCount;
+ int timeout = HZ/10;
+ int loop = 30;
+
+ while (1) {
+ /* Save Last count */
+ lastCount = fifo->count;
+
+ /* Is the Edgeport Buffer empty? */
+ if (lastCount == 0) {
+ dev_dbg(dev, "%s - TX Buffer Empty\n", __func__);
+ return;
+ }
+
+ /* Block the thread for a while */
+ prepare_to_wait(&edge_port->wait_chase, &wait,
+ TASK_UNINTERRUPTIBLE);
+ schedule_timeout(timeout);
+ finish_wait(&edge_port->wait_chase, &wait);
+
+ dev_dbg(dev, "%s wait\n", __func__);
+
+ if (lastCount == fifo->count) {
+ /* No activity.. count down. */
+ loop--;
+ if (loop == 0) {
+ dev_dbg(dev, "%s - TIMEOUT\n", __func__);
+ return;
+ }
+ } else {
+ /* Reset timeout value back to seconds */
+ loop = 30;
+ }
+ }
+}
+
+
+/*****************************************************************************
+ * edge_close
+ * this function is called by the tty driver when a port is closed
+ *****************************************************************************/
+static void edge_close(struct usb_serial_port *port)
+{
+ struct edgeport_serial *edge_serial;
+ struct edgeport_port *edge_port;
+ int status;
+
+ edge_serial = usb_get_serial_data(port->serial);
+ edge_port = usb_get_serial_port_data(port);
+ if (edge_serial == NULL || edge_port == NULL)
+ return;
+
+ /* block until tx is empty */
+ block_until_tx_empty(edge_port);
+
+ edge_port->closePending = true;
+
+ if (!edge_serial->is_epic ||
+ edge_serial->epic_descriptor.Supports.IOSPChase) {
+ /* flush and chase */
+ edge_port->chaseResponsePending = true;
+
+ dev_dbg(&port->dev, "%s - Sending IOSP_CMD_CHASE_PORT\n", __func__);
+ status = send_iosp_ext_cmd(edge_port, IOSP_CMD_CHASE_PORT, 0);
+ if (status == 0)
+ /* block until chase finished */
+ block_until_chase_response(edge_port);
+ else
+ edge_port->chaseResponsePending = false;
+ }
+
+ if (!edge_serial->is_epic ||
+ edge_serial->epic_descriptor.Supports.IOSPClose) {
+ /* close the port */
+ dev_dbg(&port->dev, "%s - Sending IOSP_CMD_CLOSE_PORT\n", __func__);
+ send_iosp_ext_cmd(edge_port, IOSP_CMD_CLOSE_PORT, 0);
+ }
+
+ /* port->close = true; */
+ edge_port->closePending = false;
+ edge_port->open = false;
+ edge_port->openPending = false;
+
+ usb_kill_urb(edge_port->write_urb);
+
+ if (edge_port->write_urb) {
+ /* if this urb had a transfer buffer already
+ (old transfer) free it */
+ kfree(edge_port->write_urb->transfer_buffer);
+ usb_free_urb(edge_port->write_urb);
+ edge_port->write_urb = NULL;
+ }
+ kfree(edge_port->txfifo.fifo);
+ edge_port->txfifo.fifo = NULL;
+}
+
+/*****************************************************************************
+ * SerialWrite
+ * this function is called by the tty driver when data should be written
+ * to the port.
+ * If successful, we return the number of bytes written, otherwise we
+ * return a negative error number.
+ *****************************************************************************/
+static int edge_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *data, int count)
+{
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ struct TxFifo *fifo;
+ int copySize;
+ int bytesleft;
+ int firsthalf;
+ int secondhalf;
+ unsigned long flags;
+
+ if (edge_port == NULL)
+ return -ENODEV;
+
+ /* get a pointer to the Tx fifo */
+ fifo = &edge_port->txfifo;
+
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+
+ /* calculate number of bytes to put in fifo */
+ copySize = min_t(unsigned int, count,
+ (edge_port->txCredits - fifo->count));
+
+ dev_dbg(&port->dev, "%s of %d byte(s) Fifo room %d -- will copy %d bytes\n",
+ __func__, count, edge_port->txCredits - fifo->count, copySize);
+
+ /* catch writes of 0 bytes which the tty driver likes to give us,
+ and when txCredits is empty */
+ if (copySize == 0) {
+ dev_dbg(&port->dev, "%s - copySize = Zero\n", __func__);
+ goto finish_write;
+ }
+
+ /* queue the data
+ * since we can never overflow the buffer we do not have to check for a
+ * full condition
+ *
+ * the copy is done is two parts -- first fill to the end of the buffer
+ * then copy the reset from the start of the buffer
+ */
+ bytesleft = fifo->size - fifo->head;
+ firsthalf = min(bytesleft, copySize);
+ dev_dbg(&port->dev, "%s - copy %d bytes of %d into fifo \n", __func__,
+ firsthalf, bytesleft);
+
+ /* now copy our data */
+ memcpy(&fifo->fifo[fifo->head], data, firsthalf);
+ usb_serial_debug_data(&port->dev, __func__, firsthalf, &fifo->fifo[fifo->head]);
+
+ /* update the index and size */
+ fifo->head += firsthalf;
+ fifo->count += firsthalf;
+
+ /* wrap the index */
+ if (fifo->head == fifo->size)
+ fifo->head = 0;
+
+ secondhalf = copySize-firsthalf;
+
+ if (secondhalf) {
+ dev_dbg(&port->dev, "%s - copy rest of data %d\n", __func__, secondhalf);
+ memcpy(&fifo->fifo[fifo->head], &data[firsthalf], secondhalf);
+ usb_serial_debug_data(&port->dev, __func__, secondhalf, &fifo->fifo[fifo->head]);
+ /* update the index and size */
+ fifo->count += secondhalf;
+ fifo->head += secondhalf;
+ /* No need to check for wrap since we can not get to end of
+ * the fifo in this part
+ */
+ }
+
+finish_write:
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+
+ send_more_port_data((struct edgeport_serial *)
+ usb_get_serial_data(port->serial), edge_port);
+
+ dev_dbg(&port->dev, "%s wrote %d byte(s) TxCredits %d, Fifo %d\n",
+ __func__, copySize, edge_port->txCredits, fifo->count);
+
+ return copySize;
+}
+
+
+/************************************************************************
+ *
+ * send_more_port_data()
+ *
+ * This routine attempts to write additional UART transmit data
+ * to a port over the USB bulk pipe. It is called (1) when new
+ * data has been written to a port's TxBuffer from higher layers
+ * (2) when the peripheral sends us additional TxCredits indicating
+ * that it can accept more Tx data for a given port; and (3) when
+ * a bulk write completes successfully and we want to see if we
+ * can transmit more.
+ *
+ ************************************************************************/
+static void send_more_port_data(struct edgeport_serial *edge_serial,
+ struct edgeport_port *edge_port)
+{
+ struct TxFifo *fifo = &edge_port->txfifo;
+ struct device *dev = &edge_port->port->dev;
+ struct urb *urb;
+ unsigned char *buffer;
+ int status;
+ int count;
+ int bytesleft;
+ int firsthalf;
+ int secondhalf;
+ unsigned long flags;
+
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+
+ if (edge_port->write_in_progress ||
+ !edge_port->open ||
+ (fifo->count == 0)) {
+ dev_dbg(dev, "%s EXIT - fifo %d, PendingWrite = %d\n",
+ __func__, fifo->count, edge_port->write_in_progress);
+ goto exit_send;
+ }
+
+ /* since the amount of data in the fifo will always fit into the
+ * edgeport buffer we do not need to check the write length
+ *
+ * Do we have enough credits for this port to make it worthwhile
+ * to bother queueing a write. If it's too small, say a few bytes,
+ * it's better to wait for more credits so we can do a larger write.
+ */
+ if (edge_port->txCredits < EDGE_FW_GET_TX_CREDITS_SEND_THRESHOLD(edge_port->maxTxCredits, EDGE_FW_BULK_MAX_PACKET_SIZE)) {
+ dev_dbg(dev, "%s Not enough credit - fifo %d TxCredit %d\n",
+ __func__, fifo->count, edge_port->txCredits);
+ goto exit_send;
+ }
+
+ /* lock this write */
+ edge_port->write_in_progress = true;
+
+ /* get a pointer to the write_urb */
+ urb = edge_port->write_urb;
+
+ /* make sure transfer buffer is freed */
+ kfree(urb->transfer_buffer);
+ urb->transfer_buffer = NULL;
+
+ /* build the data header for the buffer and port that we are about
+ to send out */
+ count = fifo->count;
+ buffer = kmalloc(count+2, GFP_ATOMIC);
+ if (!buffer) {
+ edge_port->write_in_progress = false;
+ goto exit_send;
+ }
+ buffer[0] = IOSP_BUILD_DATA_HDR1(edge_port->port->port_number, count);
+ buffer[1] = IOSP_BUILD_DATA_HDR2(edge_port->port->port_number, count);
+
+ /* now copy our data */
+ bytesleft = fifo->size - fifo->tail;
+ firsthalf = min(bytesleft, count);
+ memcpy(&buffer[2], &fifo->fifo[fifo->tail], firsthalf);
+ fifo->tail += firsthalf;
+ fifo->count -= firsthalf;
+ if (fifo->tail == fifo->size)
+ fifo->tail = 0;
+
+ secondhalf = count-firsthalf;
+ if (secondhalf) {
+ memcpy(&buffer[2+firsthalf], &fifo->fifo[fifo->tail],
+ secondhalf);
+ fifo->tail += secondhalf;
+ fifo->count -= secondhalf;
+ }
+
+ if (count)
+ usb_serial_debug_data(&edge_port->port->dev, __func__, count, &buffer[2]);
+
+ /* fill up the urb with all of our data and submit it */
+ usb_fill_bulk_urb(urb, edge_serial->serial->dev,
+ usb_sndbulkpipe(edge_serial->serial->dev,
+ edge_serial->bulk_out_endpoint),
+ buffer, count+2,
+ edge_bulk_out_data_callback, edge_port);
+
+ /* decrement the number of credits we have by the number we just sent */
+ edge_port->txCredits -= count;
+ edge_port->port->icount.tx += count;
+
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status) {
+ /* something went wrong */
+ dev_err_console(edge_port->port,
+ "%s - usb_submit_urb(write bulk) failed, status = %d, data lost\n",
+ __func__, status);
+ edge_port->write_in_progress = false;
+
+ /* revert the credits as something bad happened. */
+ edge_port->txCredits += count;
+ edge_port->port->icount.tx -= count;
+ }
+ dev_dbg(dev, "%s wrote %d byte(s) TxCredit %d, Fifo %d\n",
+ __func__, count, edge_port->txCredits, fifo->count);
+
+exit_send:
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+}
+
+
+/*****************************************************************************
+ * edge_write_room
+ * this function is called by the tty driver when it wants to know how
+ * many bytes of data we can accept for a specific port.
+ *****************************************************************************/
+static unsigned int edge_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ unsigned int room;
+ unsigned long flags;
+
+ /* total of both buffers is still txCredit */
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+ room = edge_port->txCredits - edge_port->txfifo.count;
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, room);
+ return room;
+}
+
+
+/*****************************************************************************
+ * edge_chars_in_buffer
+ * this function is called by the tty driver when it wants to know how
+ * many bytes of data we currently have outstanding in the port (data that
+ * has been written, but hasn't made it out the port yet)
+ *****************************************************************************/
+static unsigned int edge_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ unsigned int num_chars;
+ unsigned long flags;
+
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+ num_chars = edge_port->maxTxCredits - edge_port->txCredits +
+ edge_port->txfifo.count;
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+ if (num_chars) {
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, num_chars);
+ }
+
+ return num_chars;
+}
+
+
+/*****************************************************************************
+ * SerialThrottle
+ * this function is called by the tty driver when it wants to stop the data
+ * being read from the port.
+ *****************************************************************************/
+static void edge_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ int status;
+
+ if (edge_port == NULL)
+ return;
+
+ if (!edge_port->open) {
+ dev_dbg(&port->dev, "%s - port not opened\n", __func__);
+ return;
+ }
+
+ /* if we are implementing XON/XOFF, send the stop character */
+ if (I_IXOFF(tty)) {
+ unsigned char stop_char = STOP_CHAR(tty);
+ status = edge_write(tty, port, &stop_char, 1);
+ if (status <= 0)
+ return;
+ }
+
+ /* if we are implementing RTS/CTS, toggle that line */
+ if (C_CRTSCTS(tty)) {
+ edge_port->shadowMCR &= ~MCR_RTS;
+ status = send_cmd_write_uart_register(edge_port, MCR,
+ edge_port->shadowMCR);
+ if (status != 0)
+ return;
+ }
+}
+
+
+/*****************************************************************************
+ * edge_unthrottle
+ * this function is called by the tty driver when it wants to resume the
+ * data being read from the port (called after SerialThrottle is called)
+ *****************************************************************************/
+static void edge_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ int status;
+
+ if (edge_port == NULL)
+ return;
+
+ if (!edge_port->open) {
+ dev_dbg(&port->dev, "%s - port not opened\n", __func__);
+ return;
+ }
+
+ /* if we are implementing XON/XOFF, send the start character */
+ if (I_IXOFF(tty)) {
+ unsigned char start_char = START_CHAR(tty);
+ status = edge_write(tty, port, &start_char, 1);
+ if (status <= 0)
+ return;
+ }
+ /* if we are implementing RTS/CTS, toggle that line */
+ if (C_CRTSCTS(tty)) {
+ edge_port->shadowMCR |= MCR_RTS;
+ send_cmd_write_uart_register(edge_port, MCR,
+ edge_port->shadowMCR);
+ }
+}
+
+
+/*****************************************************************************
+ * SerialSetTermios
+ * this function is called by the tty driver when it wants to change
+ * the termios structure
+ *****************************************************************************/
+static void edge_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+
+ if (edge_port == NULL)
+ return;
+
+ if (!edge_port->open) {
+ dev_dbg(&port->dev, "%s - port not opened\n", __func__);
+ return;
+ }
+
+ /* change the port settings to the new ones specified */
+ change_port_settings(tty, edge_port, old_termios);
+}
+
+
+/*****************************************************************************
+ * get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ * is emptied. On bus types like RS485, the transmitter must
+ * release the bus after transmitting. This must be done when
+ * the transmit shift register is empty, not be done when the
+ * transmit holding register is empty. This functionality
+ * allows an RS485 driver to be written in user space.
+ *****************************************************************************/
+static int get_lsr_info(struct edgeport_port *edge_port,
+ unsigned int __user *value)
+{
+ unsigned int result = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+ if (edge_port->maxTxCredits == edge_port->txCredits &&
+ edge_port->txfifo.count == 0) {
+ dev_dbg(&edge_port->port->dev, "%s -- Empty\n", __func__);
+ result = TIOCSER_TEMT;
+ }
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+
+ if (copy_to_user(value, &result, sizeof(int)))
+ return -EFAULT;
+ return 0;
+}
+
+static int edge_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ unsigned int mcr;
+
+ mcr = edge_port->shadowMCR;
+ if (set & TIOCM_RTS)
+ mcr |= MCR_RTS;
+ if (set & TIOCM_DTR)
+ mcr |= MCR_DTR;
+ if (set & TIOCM_LOOP)
+ mcr |= MCR_LOOPBACK;
+
+ if (clear & TIOCM_RTS)
+ mcr &= ~MCR_RTS;
+ if (clear & TIOCM_DTR)
+ mcr &= ~MCR_DTR;
+ if (clear & TIOCM_LOOP)
+ mcr &= ~MCR_LOOPBACK;
+
+ edge_port->shadowMCR = mcr;
+
+ send_cmd_write_uart_register(edge_port, MCR, edge_port->shadowMCR);
+
+ return 0;
+}
+
+static int edge_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ unsigned int result = 0;
+ unsigned int msr;
+ unsigned int mcr;
+
+ msr = edge_port->shadowMSR;
+ mcr = edge_port->shadowMCR;
+ result = ((mcr & MCR_DTR) ? TIOCM_DTR: 0) /* 0x002 */
+ | ((mcr & MCR_RTS) ? TIOCM_RTS: 0) /* 0x004 */
+ | ((msr & EDGEPORT_MSR_CTS) ? TIOCM_CTS: 0) /* 0x020 */
+ | ((msr & EDGEPORT_MSR_CD) ? TIOCM_CAR: 0) /* 0x040 */
+ | ((msr & EDGEPORT_MSR_RI) ? TIOCM_RI: 0) /* 0x080 */
+ | ((msr & EDGEPORT_MSR_DSR) ? TIOCM_DSR: 0); /* 0x100 */
+
+ return result;
+}
+
+/*****************************************************************************
+ * SerialIoctl
+ * this function handles any ioctl calls to the driver
+ *****************************************************************************/
+static int edge_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+
+ switch (cmd) {
+ case TIOCSERGETLSR:
+ dev_dbg(&port->dev, "%s TIOCSERGETLSR\n", __func__);
+ return get_lsr_info(edge_port, (unsigned int __user *) arg);
+ }
+ return -ENOIOCTLCMD;
+}
+
+
+/*****************************************************************************
+ * SerialBreak
+ * this function sends a break to the port
+ *****************************************************************************/
+static int edge_break(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ struct edgeport_serial *edge_serial = usb_get_serial_data(port->serial);
+ int status = 0;
+
+ if (!edge_serial->is_epic ||
+ edge_serial->epic_descriptor.Supports.IOSPChase) {
+ /* flush and chase */
+ edge_port->chaseResponsePending = true;
+
+ dev_dbg(&port->dev, "%s - Sending IOSP_CMD_CHASE_PORT\n", __func__);
+ status = send_iosp_ext_cmd(edge_port, IOSP_CMD_CHASE_PORT, 0);
+ if (status == 0) {
+ /* block until chase finished */
+ block_until_chase_response(edge_port);
+ } else {
+ edge_port->chaseResponsePending = false;
+ }
+ }
+
+ if (!edge_serial->is_epic ||
+ edge_serial->epic_descriptor.Supports.IOSPSetClrBreak) {
+ if (break_state == -1) {
+ dev_dbg(&port->dev, "%s - Sending IOSP_CMD_SET_BREAK\n", __func__);
+ status = send_iosp_ext_cmd(edge_port,
+ IOSP_CMD_SET_BREAK, 0);
+ } else {
+ dev_dbg(&port->dev, "%s - Sending IOSP_CMD_CLEAR_BREAK\n", __func__);
+ status = send_iosp_ext_cmd(edge_port,
+ IOSP_CMD_CLEAR_BREAK, 0);
+ }
+ if (status)
+ dev_dbg(&port->dev, "%s - error sending break set/clear command.\n",
+ __func__);
+ }
+
+ return status;
+}
+
+
+/*****************************************************************************
+ * process_rcvd_data
+ * this function handles the data received on the bulk in pipe.
+ *****************************************************************************/
+static void process_rcvd_data(struct edgeport_serial *edge_serial,
+ unsigned char *buffer, __u16 bufferLength)
+{
+ struct usb_serial *serial = edge_serial->serial;
+ struct device *dev = &serial->dev->dev;
+ struct usb_serial_port *port;
+ struct edgeport_port *edge_port;
+ __u16 lastBufferLength;
+ __u16 rxLen;
+
+ lastBufferLength = bufferLength + 1;
+
+ while (bufferLength > 0) {
+ /* failsafe incase we get a message that we don't understand */
+ if (lastBufferLength == bufferLength) {
+ dev_dbg(dev, "%s - stuck in loop, exiting it.\n", __func__);
+ break;
+ }
+ lastBufferLength = bufferLength;
+
+ switch (edge_serial->rxState) {
+ case EXPECT_HDR1:
+ edge_serial->rxHeader1 = *buffer;
+ ++buffer;
+ --bufferLength;
+
+ if (bufferLength == 0) {
+ edge_serial->rxState = EXPECT_HDR2;
+ break;
+ }
+ fallthrough;
+ case EXPECT_HDR2:
+ edge_serial->rxHeader2 = *buffer;
+ ++buffer;
+ --bufferLength;
+
+ dev_dbg(dev, "%s - Hdr1=%02X Hdr2=%02X\n", __func__,
+ edge_serial->rxHeader1, edge_serial->rxHeader2);
+ /* Process depending on whether this header is
+ * data or status */
+
+ if (IS_CMD_STAT_HDR(edge_serial->rxHeader1)) {
+ /* Decode this status header and go to
+ * EXPECT_HDR1 (if we can process the status
+ * with only 2 bytes), or go to EXPECT_HDR3 to
+ * get the third byte. */
+ edge_serial->rxPort =
+ IOSP_GET_HDR_PORT(edge_serial->rxHeader1);
+ edge_serial->rxStatusCode =
+ IOSP_GET_STATUS_CODE(
+ edge_serial->rxHeader1);
+
+ if (!IOSP_STATUS_IS_2BYTE(
+ edge_serial->rxStatusCode)) {
+ /* This status needs additional bytes.
+ * Save what we have and then wait for
+ * more data.
+ */
+ edge_serial->rxStatusParam
+ = edge_serial->rxHeader2;
+ edge_serial->rxState = EXPECT_HDR3;
+ break;
+ }
+ /* We have all the header bytes, process the
+ status now */
+ process_rcvd_status(edge_serial,
+ edge_serial->rxHeader2, 0);
+ edge_serial->rxState = EXPECT_HDR1;
+ break;
+ }
+
+ edge_serial->rxPort = IOSP_GET_HDR_PORT(edge_serial->rxHeader1);
+ edge_serial->rxBytesRemaining = IOSP_GET_HDR_DATA_LEN(edge_serial->rxHeader1,
+ edge_serial->rxHeader2);
+ dev_dbg(dev, "%s - Data for Port %u Len %u\n", __func__,
+ edge_serial->rxPort,
+ edge_serial->rxBytesRemaining);
+
+ if (bufferLength == 0) {
+ edge_serial->rxState = EXPECT_DATA;
+ break;
+ }
+ fallthrough;
+ case EXPECT_DATA: /* Expect data */
+ if (bufferLength < edge_serial->rxBytesRemaining) {
+ rxLen = bufferLength;
+ /* Expect data to start next buffer */
+ edge_serial->rxState = EXPECT_DATA;
+ } else {
+ /* BufLen >= RxBytesRemaining */
+ rxLen = edge_serial->rxBytesRemaining;
+ /* Start another header next time */
+ edge_serial->rxState = EXPECT_HDR1;
+ }
+
+ bufferLength -= rxLen;
+ edge_serial->rxBytesRemaining -= rxLen;
+
+ /* spit this data back into the tty driver if this
+ port is open */
+ if (rxLen && edge_serial->rxPort < serial->num_ports) {
+ port = serial->port[edge_serial->rxPort];
+ edge_port = usb_get_serial_port_data(port);
+ if (edge_port && edge_port->open) {
+ dev_dbg(dev, "%s - Sending %d bytes to TTY for port %d\n",
+ __func__, rxLen,
+ edge_serial->rxPort);
+ edge_tty_recv(edge_port->port, buffer,
+ rxLen);
+ edge_port->port->icount.rx += rxLen;
+ }
+ }
+ buffer += rxLen;
+ break;
+
+ case EXPECT_HDR3: /* Expect 3rd byte of status header */
+ edge_serial->rxHeader3 = *buffer;
+ ++buffer;
+ --bufferLength;
+
+ /* We have all the header bytes, process the
+ status now */
+ process_rcvd_status(edge_serial,
+ edge_serial->rxStatusParam,
+ edge_serial->rxHeader3);
+ edge_serial->rxState = EXPECT_HDR1;
+ break;
+ }
+ }
+}
+
+
+/*****************************************************************************
+ * process_rcvd_status
+ * this function handles the any status messages received on the
+ * bulk in pipe.
+ *****************************************************************************/
+static void process_rcvd_status(struct edgeport_serial *edge_serial,
+ __u8 byte2, __u8 byte3)
+{
+ struct usb_serial_port *port;
+ struct edgeport_port *edge_port;
+ struct tty_struct *tty;
+ struct device *dev;
+ __u8 code = edge_serial->rxStatusCode;
+
+ /* switch the port pointer to the one being currently talked about */
+ if (edge_serial->rxPort >= edge_serial->serial->num_ports)
+ return;
+ port = edge_serial->serial->port[edge_serial->rxPort];
+ edge_port = usb_get_serial_port_data(port);
+ if (edge_port == NULL) {
+ dev_err(&edge_serial->serial->dev->dev,
+ "%s - edge_port == NULL for port %d\n",
+ __func__, edge_serial->rxPort);
+ return;
+ }
+ dev = &port->dev;
+
+ if (code == IOSP_EXT_STATUS) {
+ switch (byte2) {
+ case IOSP_EXT_STATUS_CHASE_RSP:
+ /* we want to do EXT status regardless of port
+ * open/closed */
+ dev_dbg(dev, "%s - Port %u EXT CHASE_RSP Data = %02x\n",
+ __func__, edge_serial->rxPort, byte3);
+ /* Currently, the only EXT_STATUS is Chase, so process
+ * here instead of one more call to one more subroutine
+ * If/when more EXT_STATUS, there'll be more work to do
+ * Also, we currently clear flag and close the port
+ * regardless of content of above's Byte3.
+ * We could choose to do something else when Byte3 says
+ * Timeout on Chase from Edgeport, like wait longer in
+ * block_until_chase_response, but for now we don't.
+ */
+ edge_port->chaseResponsePending = false;
+ wake_up(&edge_port->wait_chase);
+ return;
+
+ case IOSP_EXT_STATUS_RX_CHECK_RSP:
+ dev_dbg(dev, "%s ========== Port %u CHECK_RSP Sequence = %02x =============\n",
+ __func__, edge_serial->rxPort, byte3);
+ /* Port->RxCheckRsp = true; */
+ return;
+ }
+ }
+
+ if (code == IOSP_STATUS_OPEN_RSP) {
+ edge_port->txCredits = GET_TX_BUFFER_SIZE(byte3);
+ edge_port->maxTxCredits = edge_port->txCredits;
+ dev_dbg(dev, "%s - Port %u Open Response Initial MSR = %02x TxBufferSize = %d\n",
+ __func__, edge_serial->rxPort, byte2, edge_port->txCredits);
+ handle_new_msr(edge_port, byte2);
+
+ /* send the current line settings to the port so we are
+ in sync with any further termios calls */
+ tty = tty_port_tty_get(&edge_port->port->port);
+ if (tty) {
+ change_port_settings(tty,
+ edge_port, &tty->termios);
+ tty_kref_put(tty);
+ }
+
+ /* we have completed the open */
+ edge_port->openPending = false;
+ edge_port->open = true;
+ wake_up(&edge_port->wait_open);
+ return;
+ }
+
+ /* If port is closed, silently discard all rcvd status. We can
+ * have cases where buffered status is received AFTER the close
+ * port command is sent to the Edgeport.
+ */
+ if (!edge_port->open || edge_port->closePending)
+ return;
+
+ switch (code) {
+ /* Not currently sent by Edgeport */
+ case IOSP_STATUS_LSR:
+ dev_dbg(dev, "%s - Port %u LSR Status = %02x\n",
+ __func__, edge_serial->rxPort, byte2);
+ handle_new_lsr(edge_port, false, byte2, 0);
+ break;
+
+ case IOSP_STATUS_LSR_DATA:
+ dev_dbg(dev, "%s - Port %u LSR Status = %02x, Data = %02x\n",
+ __func__, edge_serial->rxPort, byte2, byte3);
+ /* byte2 is LSR Register */
+ /* byte3 is broken data byte */
+ handle_new_lsr(edge_port, true, byte2, byte3);
+ break;
+ /*
+ * case IOSP_EXT_4_STATUS:
+ * dev_dbg(dev, "%s - Port %u LSR Status = %02x Data = %02x\n",
+ * __func__, edge_serial->rxPort, byte2, byte3);
+ * break;
+ */
+ case IOSP_STATUS_MSR:
+ dev_dbg(dev, "%s - Port %u MSR Status = %02x\n",
+ __func__, edge_serial->rxPort, byte2);
+ /*
+ * Process this new modem status and generate appropriate
+ * events, etc, based on the new status. This routine
+ * also saves the MSR in Port->ShadowMsr.
+ */
+ handle_new_msr(edge_port, byte2);
+ break;
+
+ default:
+ dev_dbg(dev, "%s - Unrecognized IOSP status code %u\n", __func__, code);
+ break;
+ }
+}
+
+
+/*****************************************************************************
+ * edge_tty_recv
+ * this function passes data on to the tty flip buffer
+ *****************************************************************************/
+static void edge_tty_recv(struct usb_serial_port *port, unsigned char *data,
+ int length)
+{
+ int cnt;
+
+ cnt = tty_insert_flip_string(&port->port, data, length);
+ if (cnt < length) {
+ dev_err(&port->dev, "%s - dropping data, %d bytes lost\n",
+ __func__, length - cnt);
+ }
+ data += cnt;
+ length -= cnt;
+
+ tty_flip_buffer_push(&port->port);
+}
+
+
+/*****************************************************************************
+ * handle_new_msr
+ * this function handles any change to the msr register for a port.
+ *****************************************************************************/
+static void handle_new_msr(struct edgeport_port *edge_port, __u8 newMsr)
+{
+ struct async_icount *icount;
+
+ if (newMsr & (EDGEPORT_MSR_DELTA_CTS | EDGEPORT_MSR_DELTA_DSR |
+ EDGEPORT_MSR_DELTA_RI | EDGEPORT_MSR_DELTA_CD)) {
+ icount = &edge_port->port->icount;
+
+ /* update input line counters */
+ if (newMsr & EDGEPORT_MSR_DELTA_CTS)
+ icount->cts++;
+ if (newMsr & EDGEPORT_MSR_DELTA_DSR)
+ icount->dsr++;
+ if (newMsr & EDGEPORT_MSR_DELTA_CD)
+ icount->dcd++;
+ if (newMsr & EDGEPORT_MSR_DELTA_RI)
+ icount->rng++;
+ wake_up_interruptible(&edge_port->port->port.delta_msr_wait);
+ }
+
+ /* Save the new modem status */
+ edge_port->shadowMSR = newMsr & 0xf0;
+}
+
+
+/*****************************************************************************
+ * handle_new_lsr
+ * this function handles any change to the lsr register for a port.
+ *****************************************************************************/
+static void handle_new_lsr(struct edgeport_port *edge_port, __u8 lsrData,
+ __u8 lsr, __u8 data)
+{
+ __u8 newLsr = (__u8) (lsr & (__u8)
+ (LSR_OVER_ERR | LSR_PAR_ERR | LSR_FRM_ERR | LSR_BREAK));
+ struct async_icount *icount;
+
+ edge_port->shadowLSR = lsr;
+
+ if (newLsr & LSR_BREAK) {
+ /*
+ * Parity and Framing errors only count if they
+ * occur exclusive of a break being
+ * received.
+ */
+ newLsr &= (__u8)(LSR_OVER_ERR | LSR_BREAK);
+ }
+
+ /* Place LSR data byte into Rx buffer */
+ if (lsrData)
+ edge_tty_recv(edge_port->port, &data, 1);
+
+ /* update input line counters */
+ icount = &edge_port->port->icount;
+ if (newLsr & LSR_BREAK)
+ icount->brk++;
+ if (newLsr & LSR_OVER_ERR)
+ icount->overrun++;
+ if (newLsr & LSR_PAR_ERR)
+ icount->parity++;
+ if (newLsr & LSR_FRM_ERR)
+ icount->frame++;
+}
+
+
+/****************************************************************************
+ * sram_write
+ * writes a number of bytes to the Edgeport device's sram starting at the
+ * given address.
+ * If successful returns the number of bytes written, otherwise it returns
+ * a negative error number of the problem.
+ ****************************************************************************/
+static int sram_write(struct usb_serial *serial, __u16 extAddr, __u16 addr,
+ __u16 length, const __u8 *data)
+{
+ int result;
+ __u16 current_length;
+ unsigned char *transfer_buffer;
+
+ dev_dbg(&serial->dev->dev, "%s - %x, %x, %d\n", __func__, extAddr, addr, length);
+
+ transfer_buffer = kmalloc(64, GFP_KERNEL);
+ if (!transfer_buffer)
+ return -ENOMEM;
+
+ /* need to split these writes up into 64 byte chunks */
+ result = 0;
+ while (length > 0) {
+ if (length > 64)
+ current_length = 64;
+ else
+ current_length = length;
+
+/* dev_dbg(&serial->dev->dev, "%s - writing %x, %x, %d\n", __func__, extAddr, addr, current_length); */
+ memcpy(transfer_buffer, data, current_length);
+ result = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ USB_REQUEST_ION_WRITE_RAM,
+ 0x40, addr, extAddr, transfer_buffer,
+ current_length, 300);
+ if (result < 0)
+ break;
+ length -= current_length;
+ addr += current_length;
+ data += current_length;
+ }
+
+ kfree(transfer_buffer);
+ return result;
+}
+
+
+/****************************************************************************
+ * rom_write
+ * writes a number of bytes to the Edgeport device's ROM starting at the
+ * given address.
+ * If successful returns the number of bytes written, otherwise it returns
+ * a negative error number of the problem.
+ ****************************************************************************/
+static int rom_write(struct usb_serial *serial, __u16 extAddr, __u16 addr,
+ __u16 length, const __u8 *data)
+{
+ int result;
+ __u16 current_length;
+ unsigned char *transfer_buffer;
+
+ transfer_buffer = kmalloc(64, GFP_KERNEL);
+ if (!transfer_buffer)
+ return -ENOMEM;
+
+ /* need to split these writes up into 64 byte chunks */
+ result = 0;
+ while (length > 0) {
+ if (length > 64)
+ current_length = 64;
+ else
+ current_length = length;
+ memcpy(transfer_buffer, data, current_length);
+ result = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ USB_REQUEST_ION_WRITE_ROM, 0x40,
+ addr, extAddr,
+ transfer_buffer, current_length, 300);
+ if (result < 0)
+ break;
+ length -= current_length;
+ addr += current_length;
+ data += current_length;
+ }
+
+ kfree(transfer_buffer);
+ return result;
+}
+
+
+/****************************************************************************
+ * rom_read
+ * reads a number of bytes from the Edgeport device starting at the given
+ * address.
+ * Returns zero on success or a negative error number.
+ ****************************************************************************/
+static int rom_read(struct usb_serial *serial, __u16 extAddr,
+ __u16 addr, __u16 length, __u8 *data)
+{
+ int result;
+ __u16 current_length;
+ unsigned char *transfer_buffer;
+
+ transfer_buffer = kmalloc(64, GFP_KERNEL);
+ if (!transfer_buffer)
+ return -ENOMEM;
+
+ /* need to split these reads up into 64 byte chunks */
+ result = 0;
+ while (length > 0) {
+ if (length > 64)
+ current_length = 64;
+ else
+ current_length = length;
+ result = usb_control_msg(serial->dev,
+ usb_rcvctrlpipe(serial->dev, 0),
+ USB_REQUEST_ION_READ_ROM,
+ 0xC0, addr, extAddr, transfer_buffer,
+ current_length, 300);
+ if (result < current_length) {
+ if (result >= 0)
+ result = -EIO;
+ break;
+ }
+ memcpy(data, transfer_buffer, current_length);
+ length -= current_length;
+ addr += current_length;
+ data += current_length;
+
+ result = 0;
+ }
+
+ kfree(transfer_buffer);
+ return result;
+}
+
+
+/****************************************************************************
+ * send_iosp_ext_cmd
+ * Is used to send a IOSP message to the Edgeport device
+ ****************************************************************************/
+static int send_iosp_ext_cmd(struct edgeport_port *edge_port,
+ __u8 command, __u8 param)
+{
+ unsigned char *buffer;
+ unsigned char *currentCommand;
+ int length = 0;
+ int status = 0;
+
+ buffer = kmalloc(10, GFP_ATOMIC);
+ if (!buffer)
+ return -ENOMEM;
+
+ currentCommand = buffer;
+
+ MAKE_CMD_EXT_CMD(&currentCommand, &length, edge_port->port->port_number,
+ command, param);
+
+ status = write_cmd_usb(edge_port, buffer, length);
+ if (status) {
+ /* something bad happened, let's free up the memory */
+ kfree(buffer);
+ }
+
+ return status;
+}
+
+
+/*****************************************************************************
+ * write_cmd_usb
+ * this function writes the given buffer out to the bulk write endpoint.
+ *****************************************************************************/
+static int write_cmd_usb(struct edgeport_port *edge_port,
+ unsigned char *buffer, int length)
+{
+ struct edgeport_serial *edge_serial =
+ usb_get_serial_data(edge_port->port->serial);
+ struct device *dev = &edge_port->port->dev;
+ int status = 0;
+ struct urb *urb;
+
+ usb_serial_debug_data(dev, __func__, length, buffer);
+
+ /* Allocate our next urb */
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!urb)
+ return -ENOMEM;
+
+ atomic_inc(&CmdUrbs);
+ dev_dbg(dev, "%s - ALLOCATE URB %p (outstanding %d)\n",
+ __func__, urb, atomic_read(&CmdUrbs));
+
+ usb_fill_bulk_urb(urb, edge_serial->serial->dev,
+ usb_sndbulkpipe(edge_serial->serial->dev,
+ edge_serial->bulk_out_endpoint),
+ buffer, length, edge_bulk_out_cmd_callback, edge_port);
+
+ edge_port->commandPending = true;
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+
+ if (status) {
+ /* something went wrong */
+ dev_err(dev, "%s - usb_submit_urb(write command) failed, status = %d\n",
+ __func__, status);
+ usb_free_urb(urb);
+ atomic_dec(&CmdUrbs);
+ return status;
+ }
+
+#if 0
+ wait_event(&edge_port->wait_command, !edge_port->commandPending);
+
+ if (edge_port->commandPending) {
+ /* command timed out */
+ dev_dbg(dev, "%s - command timed out\n", __func__);
+ status = -EINVAL;
+ }
+#endif
+ return status;
+}
+
+
+/*****************************************************************************
+ * send_cmd_write_baud_rate
+ * this function sends the proper command to change the baud rate of the
+ * specified port.
+ *****************************************************************************/
+static int send_cmd_write_baud_rate(struct edgeport_port *edge_port,
+ int baudRate)
+{
+ struct edgeport_serial *edge_serial =
+ usb_get_serial_data(edge_port->port->serial);
+ struct device *dev = &edge_port->port->dev;
+ unsigned char *cmdBuffer;
+ unsigned char *currCmd;
+ int cmdLen = 0;
+ int divisor;
+ int status;
+ u32 number = edge_port->port->port_number;
+
+ if (edge_serial->is_epic &&
+ !edge_serial->epic_descriptor.Supports.IOSPSetBaudRate) {
+ dev_dbg(dev, "SendCmdWriteBaudRate - NOT Setting baud rate for port, baud = %d\n",
+ baudRate);
+ return 0;
+ }
+
+ dev_dbg(dev, "%s - baud = %d\n", __func__, baudRate);
+
+ status = calc_baud_rate_divisor(dev, baudRate, &divisor);
+ if (status) {
+ dev_err(dev, "%s - bad baud rate\n", __func__);
+ return status;
+ }
+
+ /* Alloc memory for the string of commands. */
+ cmdBuffer = kmalloc(0x100, GFP_ATOMIC);
+ if (!cmdBuffer)
+ return -ENOMEM;
+
+ currCmd = cmdBuffer;
+
+ /* Enable access to divisor latch */
+ MAKE_CMD_WRITE_REG(&currCmd, &cmdLen, number, LCR, LCR_DL_ENABLE);
+
+ /* Write the divisor itself */
+ MAKE_CMD_WRITE_REG(&currCmd, &cmdLen, number, DLL, LOW8(divisor));
+ MAKE_CMD_WRITE_REG(&currCmd, &cmdLen, number, DLM, HIGH8(divisor));
+
+ /* Restore original value to disable access to divisor latch */
+ MAKE_CMD_WRITE_REG(&currCmd, &cmdLen, number, LCR,
+ edge_port->shadowLCR);
+
+ status = write_cmd_usb(edge_port, cmdBuffer, cmdLen);
+ if (status) {
+ /* something bad happened, let's free up the memory */
+ kfree(cmdBuffer);
+ }
+
+ return status;
+}
+
+
+/*****************************************************************************
+ * calc_baud_rate_divisor
+ * this function calculates the proper baud rate divisor for the specified
+ * baud rate.
+ *****************************************************************************/
+static int calc_baud_rate_divisor(struct device *dev, int baudrate, int *divisor)
+{
+ int i;
+ __u16 custom;
+
+ for (i = 0; i < ARRAY_SIZE(divisor_table); i++) {
+ if (divisor_table[i].BaudRate == baudrate) {
+ *divisor = divisor_table[i].Divisor;
+ return 0;
+ }
+ }
+
+ /* We have tried all of the standard baud rates
+ * lets try to calculate the divisor for this baud rate
+ * Make sure the baud rate is reasonable */
+ if (baudrate > 50 && baudrate < 230400) {
+ /* get divisor */
+ custom = (__u16)((230400L + baudrate/2) / baudrate);
+
+ *divisor = custom;
+
+ dev_dbg(dev, "%s - Baud %d = %d\n", __func__, baudrate, custom);
+ return 0;
+ }
+
+ return -1;
+}
+
+
+/*****************************************************************************
+ * send_cmd_write_uart_register
+ * this function builds up a uart register message and sends to the device.
+ *****************************************************************************/
+static int send_cmd_write_uart_register(struct edgeport_port *edge_port,
+ __u8 regNum, __u8 regValue)
+{
+ struct edgeport_serial *edge_serial =
+ usb_get_serial_data(edge_port->port->serial);
+ struct device *dev = &edge_port->port->dev;
+ unsigned char *cmdBuffer;
+ unsigned char *currCmd;
+ unsigned long cmdLen = 0;
+ int status;
+
+ dev_dbg(dev, "%s - write to %s register 0x%02x\n",
+ (regNum == MCR) ? "MCR" : "LCR", __func__, regValue);
+
+ if (edge_serial->is_epic &&
+ !edge_serial->epic_descriptor.Supports.IOSPWriteMCR &&
+ regNum == MCR) {
+ dev_dbg(dev, "SendCmdWriteUartReg - Not writing to MCR Register\n");
+ return 0;
+ }
+
+ if (edge_serial->is_epic &&
+ !edge_serial->epic_descriptor.Supports.IOSPWriteLCR &&
+ regNum == LCR) {
+ dev_dbg(dev, "SendCmdWriteUartReg - Not writing to LCR Register\n");
+ return 0;
+ }
+
+ /* Alloc memory for the string of commands. */
+ cmdBuffer = kmalloc(0x10, GFP_ATOMIC);
+ if (cmdBuffer == NULL)
+ return -ENOMEM;
+
+ currCmd = cmdBuffer;
+
+ /* Build a cmd in the buffer to write the given register */
+ MAKE_CMD_WRITE_REG(&currCmd, &cmdLen, edge_port->port->port_number,
+ regNum, regValue);
+
+ status = write_cmd_usb(edge_port, cmdBuffer, cmdLen);
+ if (status) {
+ /* something bad happened, let's free up the memory */
+ kfree(cmdBuffer);
+ }
+
+ return status;
+}
+
+
+/*****************************************************************************
+ * change_port_settings
+ * This routine is called to set the UART on the device to match the
+ * specified new settings.
+ *****************************************************************************/
+
+static void change_port_settings(struct tty_struct *tty,
+ struct edgeport_port *edge_port, const struct ktermios *old_termios)
+{
+ struct device *dev = &edge_port->port->dev;
+ struct edgeport_serial *edge_serial =
+ usb_get_serial_data(edge_port->port->serial);
+ int baud;
+ unsigned cflag;
+ __u8 mask = 0xff;
+ __u8 lData;
+ __u8 lParity;
+ __u8 lStop;
+ __u8 rxFlow;
+ __u8 txFlow;
+ int status;
+
+ if (!edge_port->open &&
+ !edge_port->openPending) {
+ dev_dbg(dev, "%s - port not opened\n", __func__);
+ return;
+ }
+
+ cflag = tty->termios.c_cflag;
+
+ switch (cflag & CSIZE) {
+ case CS5:
+ lData = LCR_BITS_5; mask = 0x1f;
+ dev_dbg(dev, "%s - data bits = 5\n", __func__);
+ break;
+ case CS6:
+ lData = LCR_BITS_6; mask = 0x3f;
+ dev_dbg(dev, "%s - data bits = 6\n", __func__);
+ break;
+ case CS7:
+ lData = LCR_BITS_7; mask = 0x7f;
+ dev_dbg(dev, "%s - data bits = 7\n", __func__);
+ break;
+ default:
+ case CS8:
+ lData = LCR_BITS_8;
+ dev_dbg(dev, "%s - data bits = 8\n", __func__);
+ break;
+ }
+
+ lParity = LCR_PAR_NONE;
+ if (cflag & PARENB) {
+ if (cflag & CMSPAR) {
+ if (cflag & PARODD) {
+ lParity = LCR_PAR_MARK;
+ dev_dbg(dev, "%s - parity = mark\n", __func__);
+ } else {
+ lParity = LCR_PAR_SPACE;
+ dev_dbg(dev, "%s - parity = space\n", __func__);
+ }
+ } else if (cflag & PARODD) {
+ lParity = LCR_PAR_ODD;
+ dev_dbg(dev, "%s - parity = odd\n", __func__);
+ } else {
+ lParity = LCR_PAR_EVEN;
+ dev_dbg(dev, "%s - parity = even\n", __func__);
+ }
+ } else {
+ dev_dbg(dev, "%s - parity = none\n", __func__);
+ }
+
+ if (cflag & CSTOPB) {
+ lStop = LCR_STOP_2;
+ dev_dbg(dev, "%s - stop bits = 2\n", __func__);
+ } else {
+ lStop = LCR_STOP_1;
+ dev_dbg(dev, "%s - stop bits = 1\n", __func__);
+ }
+
+ /* figure out the flow control settings */
+ rxFlow = txFlow = 0x00;
+ if (cflag & CRTSCTS) {
+ rxFlow |= IOSP_RX_FLOW_RTS;
+ txFlow |= IOSP_TX_FLOW_CTS;
+ dev_dbg(dev, "%s - RTS/CTS is enabled\n", __func__);
+ } else {
+ dev_dbg(dev, "%s - RTS/CTS is disabled\n", __func__);
+ }
+
+ /* if we are implementing XON/XOFF, set the start and stop character
+ in the device */
+ if (I_IXOFF(tty) || I_IXON(tty)) {
+ unsigned char stop_char = STOP_CHAR(tty);
+ unsigned char start_char = START_CHAR(tty);
+
+ if (!edge_serial->is_epic ||
+ edge_serial->epic_descriptor.Supports.IOSPSetXChar) {
+ send_iosp_ext_cmd(edge_port,
+ IOSP_CMD_SET_XON_CHAR, start_char);
+ send_iosp_ext_cmd(edge_port,
+ IOSP_CMD_SET_XOFF_CHAR, stop_char);
+ }
+
+ /* if we are implementing INBOUND XON/XOFF */
+ if (I_IXOFF(tty)) {
+ rxFlow |= IOSP_RX_FLOW_XON_XOFF;
+ dev_dbg(dev, "%s - INBOUND XON/XOFF is enabled, XON = %2x, XOFF = %2x\n",
+ __func__, start_char, stop_char);
+ } else {
+ dev_dbg(dev, "%s - INBOUND XON/XOFF is disabled\n", __func__);
+ }
+
+ /* if we are implementing OUTBOUND XON/XOFF */
+ if (I_IXON(tty)) {
+ txFlow |= IOSP_TX_FLOW_XON_XOFF;
+ dev_dbg(dev, "%s - OUTBOUND XON/XOFF is enabled, XON = %2x, XOFF = %2x\n",
+ __func__, start_char, stop_char);
+ } else {
+ dev_dbg(dev, "%s - OUTBOUND XON/XOFF is disabled\n", __func__);
+ }
+ }
+
+ /* Set flow control to the configured value */
+ if (!edge_serial->is_epic ||
+ edge_serial->epic_descriptor.Supports.IOSPSetRxFlow)
+ send_iosp_ext_cmd(edge_port, IOSP_CMD_SET_RX_FLOW, rxFlow);
+ if (!edge_serial->is_epic ||
+ edge_serial->epic_descriptor.Supports.IOSPSetTxFlow)
+ send_iosp_ext_cmd(edge_port, IOSP_CMD_SET_TX_FLOW, txFlow);
+
+
+ edge_port->shadowLCR &= ~(LCR_BITS_MASK | LCR_STOP_MASK | LCR_PAR_MASK);
+ edge_port->shadowLCR |= (lData | lParity | lStop);
+
+ edge_port->validDataMask = mask;
+
+ /* Send the updated LCR value to the EdgePort */
+ status = send_cmd_write_uart_register(edge_port, LCR,
+ edge_port->shadowLCR);
+ if (status != 0)
+ return;
+
+ /* set up the MCR register and send it to the EdgePort */
+ edge_port->shadowMCR = MCR_MASTER_IE;
+ if (cflag & CBAUD)
+ edge_port->shadowMCR |= (MCR_DTR | MCR_RTS);
+
+ status = send_cmd_write_uart_register(edge_port, MCR,
+ edge_port->shadowMCR);
+ if (status != 0)
+ return;
+
+ /* Determine divisor based on baud rate */
+ baud = tty_get_baud_rate(tty);
+ if (!baud) {
+ /* pick a default, any default... */
+ baud = 9600;
+ }
+
+ dev_dbg(dev, "%s - baud rate = %d\n", __func__, baud);
+ status = send_cmd_write_baud_rate(edge_port, baud);
+ if (status == -1) {
+ /* Speed change was not possible - put back the old speed */
+ baud = tty_termios_baud_rate(old_termios);
+ tty_encode_baud_rate(tty, baud, baud);
+ }
+}
+
+
+/****************************************************************************
+ * unicode_to_ascii
+ * Turns a string from Unicode into ASCII.
+ * Doesn't do a good job with any characters that are outside the normal
+ * ASCII range, but it's only for debugging...
+ * NOTE: expects the unicode in LE format
+ ****************************************************************************/
+static void unicode_to_ascii(char *string, int buflen,
+ __le16 *unicode, int unicode_size)
+{
+ int i;
+
+ if (buflen <= 0) /* never happens, but... */
+ return;
+ --buflen; /* space for nul */
+
+ for (i = 0; i < unicode_size; i++) {
+ if (i >= buflen)
+ break;
+ string[i] = (char)(le16_to_cpu(unicode[i]));
+ }
+ string[i] = 0x00;
+}
+
+
+/****************************************************************************
+ * get_manufacturing_desc
+ * reads in the manufacturing descriptor and stores it into the serial
+ * structure.
+ ****************************************************************************/
+static void get_manufacturing_desc(struct edgeport_serial *edge_serial)
+{
+ struct device *dev = &edge_serial->serial->dev->dev;
+ int response;
+
+ dev_dbg(dev, "getting manufacturer descriptor\n");
+
+ response = rom_read(edge_serial->serial,
+ (EDGE_MANUF_DESC_ADDR & 0xffff0000) >> 16,
+ (__u16)(EDGE_MANUF_DESC_ADDR & 0x0000ffff),
+ EDGE_MANUF_DESC_LEN,
+ (__u8 *)(&edge_serial->manuf_descriptor));
+
+ if (response < 0) {
+ dev_err(dev, "error in getting manufacturer descriptor: %d\n",
+ response);
+ } else {
+ char string[30];
+ dev_dbg(dev, "**Manufacturer Descriptor\n");
+ dev_dbg(dev, " RomSize: %dK\n",
+ edge_serial->manuf_descriptor.RomSize);
+ dev_dbg(dev, " RamSize: %dK\n",
+ edge_serial->manuf_descriptor.RamSize);
+ dev_dbg(dev, " CpuRev: %d\n",
+ edge_serial->manuf_descriptor.CpuRev);
+ dev_dbg(dev, " BoardRev: %d\n",
+ edge_serial->manuf_descriptor.BoardRev);
+ dev_dbg(dev, " NumPorts: %d\n",
+ edge_serial->manuf_descriptor.NumPorts);
+ dev_dbg(dev, " DescDate: %d/%d/%d\n",
+ edge_serial->manuf_descriptor.DescDate[0],
+ edge_serial->manuf_descriptor.DescDate[1],
+ edge_serial->manuf_descriptor.DescDate[2]+1900);
+ unicode_to_ascii(string, sizeof(string),
+ edge_serial->manuf_descriptor.SerialNumber,
+ edge_serial->manuf_descriptor.SerNumLength/2);
+ dev_dbg(dev, " SerialNumber: %s\n", string);
+ unicode_to_ascii(string, sizeof(string),
+ edge_serial->manuf_descriptor.AssemblyNumber,
+ edge_serial->manuf_descriptor.AssemblyNumLength/2);
+ dev_dbg(dev, " AssemblyNumber: %s\n", string);
+ unicode_to_ascii(string, sizeof(string),
+ edge_serial->manuf_descriptor.OemAssyNumber,
+ edge_serial->manuf_descriptor.OemAssyNumLength/2);
+ dev_dbg(dev, " OemAssyNumber: %s\n", string);
+ dev_dbg(dev, " UartType: %d\n",
+ edge_serial->manuf_descriptor.UartType);
+ dev_dbg(dev, " IonPid: %d\n",
+ edge_serial->manuf_descriptor.IonPid);
+ dev_dbg(dev, " IonConfig: %d\n",
+ edge_serial->manuf_descriptor.IonConfig);
+ }
+}
+
+
+/****************************************************************************
+ * get_boot_desc
+ * reads in the bootloader descriptor and stores it into the serial
+ * structure.
+ ****************************************************************************/
+static void get_boot_desc(struct edgeport_serial *edge_serial)
+{
+ struct device *dev = &edge_serial->serial->dev->dev;
+ int response;
+
+ dev_dbg(dev, "getting boot descriptor\n");
+
+ response = rom_read(edge_serial->serial,
+ (EDGE_BOOT_DESC_ADDR & 0xffff0000) >> 16,
+ (__u16)(EDGE_BOOT_DESC_ADDR & 0x0000ffff),
+ EDGE_BOOT_DESC_LEN,
+ (__u8 *)(&edge_serial->boot_descriptor));
+
+ if (response < 0) {
+ dev_err(dev, "error in getting boot descriptor: %d\n",
+ response);
+ } else {
+ dev_dbg(dev, "**Boot Descriptor:\n");
+ dev_dbg(dev, " BootCodeLength: %d\n",
+ le16_to_cpu(edge_serial->boot_descriptor.BootCodeLength));
+ dev_dbg(dev, " MajorVersion: %d\n",
+ edge_serial->boot_descriptor.MajorVersion);
+ dev_dbg(dev, " MinorVersion: %d\n",
+ edge_serial->boot_descriptor.MinorVersion);
+ dev_dbg(dev, " BuildNumber: %d\n",
+ le16_to_cpu(edge_serial->boot_descriptor.BuildNumber));
+ dev_dbg(dev, " Capabilities: 0x%x\n",
+ le16_to_cpu(edge_serial->boot_descriptor.Capabilities));
+ dev_dbg(dev, " UConfig0: %d\n",
+ edge_serial->boot_descriptor.UConfig0);
+ dev_dbg(dev, " UConfig1: %d\n",
+ edge_serial->boot_descriptor.UConfig1);
+ }
+}
+
+
+/****************************************************************************
+ * load_application_firmware
+ * This is called to load the application firmware to the device
+ ****************************************************************************/
+static void load_application_firmware(struct edgeport_serial *edge_serial)
+{
+ struct device *dev = &edge_serial->serial->dev->dev;
+ const struct ihex_binrec *rec;
+ const struct firmware *fw;
+ const char *fw_name;
+ const char *fw_info;
+ int response;
+ __u32 Operaddr;
+ __u16 build;
+
+ switch (edge_serial->product_info.iDownloadFile) {
+ case EDGE_DOWNLOAD_FILE_I930:
+ fw_info = "downloading firmware version (930)";
+ fw_name = "edgeport/down.fw";
+ break;
+
+ case EDGE_DOWNLOAD_FILE_80251:
+ fw_info = "downloading firmware version (80251)";
+ fw_name = "edgeport/down2.fw";
+ break;
+
+ case EDGE_DOWNLOAD_FILE_NONE:
+ dev_dbg(dev, "No download file specified, skipping download\n");
+ return;
+
+ default:
+ return;
+ }
+
+ response = request_ihex_firmware(&fw, fw_name,
+ &edge_serial->serial->dev->dev);
+ if (response) {
+ dev_err(dev, "Failed to load image \"%s\" err %d\n",
+ fw_name, response);
+ return;
+ }
+
+ rec = (const struct ihex_binrec *)fw->data;
+ build = (rec->data[2] << 8) | rec->data[3];
+
+ dev_dbg(dev, "%s %d.%d.%d\n", fw_info, rec->data[0], rec->data[1], build);
+
+ edge_serial->product_info.FirmwareMajorVersion = rec->data[0];
+ edge_serial->product_info.FirmwareMinorVersion = rec->data[1];
+ edge_serial->product_info.FirmwareBuildNumber = cpu_to_le16(build);
+
+ for (rec = ihex_next_binrec(rec); rec;
+ rec = ihex_next_binrec(rec)) {
+ Operaddr = be32_to_cpu(rec->addr);
+ response = sram_write(edge_serial->serial,
+ Operaddr >> 16,
+ Operaddr & 0xFFFF,
+ be16_to_cpu(rec->len),
+ &rec->data[0]);
+ if (response < 0) {
+ dev_err(&edge_serial->serial->dev->dev,
+ "sram_write failed (%x, %x, %d)\n",
+ Operaddr >> 16, Operaddr & 0xFFFF,
+ be16_to_cpu(rec->len));
+ break;
+ }
+ }
+
+ dev_dbg(dev, "sending exec_dl_code\n");
+ response = usb_control_msg (edge_serial->serial->dev,
+ usb_sndctrlpipe(edge_serial->serial->dev, 0),
+ USB_REQUEST_ION_EXEC_DL_CODE,
+ 0x40, 0x4000, 0x0001, NULL, 0, 3000);
+
+ release_firmware(fw);
+}
+
+
+/****************************************************************************
+ * edge_startup
+ ****************************************************************************/
+static int edge_startup(struct usb_serial *serial)
+{
+ struct edgeport_serial *edge_serial;
+ struct usb_device *dev;
+ struct device *ddev = &serial->dev->dev;
+ int i;
+ int response;
+ bool interrupt_in_found;
+ bool bulk_in_found;
+ bool bulk_out_found;
+ static const __u32 descriptor[3] = { EDGE_COMPATIBILITY_MASK0,
+ EDGE_COMPATIBILITY_MASK1,
+ EDGE_COMPATIBILITY_MASK2 };
+
+ dev = serial->dev;
+
+ /* create our private serial structure */
+ edge_serial = kzalloc_obj(struct edgeport_serial);
+ if (!edge_serial)
+ return -ENOMEM;
+
+ spin_lock_init(&edge_serial->es_lock);
+ edge_serial->serial = serial;
+ usb_set_serial_data(serial, edge_serial);
+
+ /* get the name for the device from the device */
+ i = usb_string(dev, dev->descriptor.iManufacturer,
+ &edge_serial->name[0], MAX_NAME_LEN+1);
+ if (i < 0)
+ i = 0;
+ edge_serial->name[i++] = ' ';
+ usb_string(dev, dev->descriptor.iProduct,
+ &edge_serial->name[i], MAX_NAME_LEN+2 - i);
+
+ dev_info(&serial->dev->dev, "%s detected\n", edge_serial->name);
+
+ /* Read the epic descriptor */
+ if (get_epic_descriptor(edge_serial) < 0) {
+ /* memcpy descriptor to Supports structures */
+ memcpy(&edge_serial->epic_descriptor.Supports, descriptor,
+ sizeof(struct edge_compatibility_bits));
+
+ /* get the manufacturing descriptor for this device */
+ get_manufacturing_desc(edge_serial);
+
+ /* get the boot descriptor */
+ get_boot_desc(edge_serial);
+
+ get_product_info(edge_serial);
+ }
+
+ /* set the number of ports from the manufacturing description */
+ /* serial->num_ports = serial->product_info.NumPorts; */
+ if ((!edge_serial->is_epic) &&
+ (edge_serial->product_info.NumPorts != serial->num_ports)) {
+ dev_warn(ddev,
+ "Device Reported %d serial ports vs. core thinking we have %d ports, email greg@kroah.com this information.\n",
+ edge_serial->product_info.NumPorts,
+ serial->num_ports);
+ }
+
+ dev_dbg(ddev, "%s - time 1 %ld\n", __func__, jiffies);
+
+ /* If not an EPiC device */
+ if (!edge_serial->is_epic) {
+ /* now load the application firmware into this device */
+ load_application_firmware(edge_serial);
+
+ dev_dbg(ddev, "%s - time 2 %ld\n", __func__, jiffies);
+
+ /* Check current Edgeport EEPROM and update if necessary */
+ update_edgeport_E2PROM(edge_serial);
+
+ dev_dbg(ddev, "%s - time 3 %ld\n", __func__, jiffies);
+
+ /* set the configuration to use #1 */
+/* dev_dbg(ddev, "set_configuration 1\n"); */
+/* usb_set_configuration (dev, 1); */
+ }
+ dev_dbg(ddev, " FirmwareMajorVersion %d.%d.%d\n",
+ edge_serial->product_info.FirmwareMajorVersion,
+ edge_serial->product_info.FirmwareMinorVersion,
+ le16_to_cpu(edge_serial->product_info.FirmwareBuildNumber));
+
+ /* we set up the pointers to the endpoints in the edge_open function,
+ * as the structures aren't created yet. */
+
+ response = 0;
+
+ if (edge_serial->is_epic) {
+ struct usb_host_interface *alt;
+
+ alt = serial->interface->cur_altsetting;
+
+ /* EPIC thing, set up our interrupt polling now and our read
+ * urb, so that the device knows it really is connected. */
+ interrupt_in_found = bulk_in_found = bulk_out_found = false;
+ for (i = 0; i < alt->desc.bNumEndpoints; ++i) {
+ struct usb_endpoint_descriptor *endpoint;
+ int buffer_size;
+
+ endpoint = &alt->endpoint[i].desc;
+ buffer_size = usb_endpoint_maxp(endpoint);
+ if (!interrupt_in_found &&
+ (usb_endpoint_is_int_in(endpoint))) {
+ /* we found a interrupt in endpoint */
+ dev_dbg(ddev, "found interrupt in\n");
+
+ /* not set up yet, so do it now */
+ edge_serial->interrupt_read_urb =
+ usb_alloc_urb(0, GFP_KERNEL);
+ if (!edge_serial->interrupt_read_urb) {
+ response = -ENOMEM;
+ break;
+ }
+
+ edge_serial->interrupt_in_buffer =
+ kmalloc(buffer_size, GFP_KERNEL);
+ if (!edge_serial->interrupt_in_buffer) {
+ response = -ENOMEM;
+ break;
+ }
+ edge_serial->interrupt_in_endpoint =
+ endpoint->bEndpointAddress;
+
+ /* set up our interrupt urb */
+ usb_fill_int_urb(
+ edge_serial->interrupt_read_urb,
+ dev,
+ usb_rcvintpipe(dev,
+ endpoint->bEndpointAddress),
+ edge_serial->interrupt_in_buffer,
+ buffer_size,
+ edge_interrupt_callback,
+ edge_serial,
+ endpoint->bInterval);
+
+ interrupt_in_found = true;
+ }
+
+ if (!bulk_in_found &&
+ (usb_endpoint_is_bulk_in(endpoint))) {
+ /* we found a bulk in endpoint */
+ dev_dbg(ddev, "found bulk in\n");
+
+ /* not set up yet, so do it now */
+ edge_serial->read_urb =
+ usb_alloc_urb(0, GFP_KERNEL);
+ if (!edge_serial->read_urb) {
+ response = -ENOMEM;
+ break;
+ }
+
+ edge_serial->bulk_in_buffer =
+ kmalloc(buffer_size, GFP_KERNEL);
+ if (!edge_serial->bulk_in_buffer) {
+ response = -ENOMEM;
+ break;
+ }
+ edge_serial->bulk_in_endpoint =
+ endpoint->bEndpointAddress;
+
+ /* set up our bulk in urb */
+ usb_fill_bulk_urb(edge_serial->read_urb, dev,
+ usb_rcvbulkpipe(dev,
+ endpoint->bEndpointAddress),
+ edge_serial->bulk_in_buffer,
+ usb_endpoint_maxp(endpoint),
+ edge_bulk_in_callback,
+ edge_serial);
+ bulk_in_found = true;
+ }
+
+ if (!bulk_out_found &&
+ (usb_endpoint_is_bulk_out(endpoint))) {
+ /* we found a bulk out endpoint */
+ dev_dbg(ddev, "found bulk out\n");
+ edge_serial->bulk_out_endpoint =
+ endpoint->bEndpointAddress;
+ bulk_out_found = true;
+ }
+ }
+
+ if (response || !interrupt_in_found || !bulk_in_found ||
+ !bulk_out_found) {
+ if (!response) {
+ dev_err(ddev, "expected endpoints not found\n");
+ response = -ENODEV;
+ }
+
+ goto error;
+ }
+
+ /* start interrupt read for this edgeport this interrupt will
+ * continue as long as the edgeport is connected */
+ response = usb_submit_urb(edge_serial->interrupt_read_urb,
+ GFP_KERNEL);
+ if (response) {
+ dev_err(ddev, "%s - Error %d submitting control urb\n",
+ __func__, response);
+
+ goto error;
+ }
+ }
+ return response;
+
+error:
+ usb_free_urb(edge_serial->interrupt_read_urb);
+ kfree(edge_serial->interrupt_in_buffer);
+
+ usb_free_urb(edge_serial->read_urb);
+ kfree(edge_serial->bulk_in_buffer);
+
+ kfree(edge_serial);
+
+ return response;
+}
+
+
+/****************************************************************************
+ * edge_disconnect
+ * This function is called whenever the device is removed from the usb bus.
+ ****************************************************************************/
+static void edge_disconnect(struct usb_serial *serial)
+{
+ struct edgeport_serial *edge_serial = usb_get_serial_data(serial);
+
+ if (edge_serial->is_epic) {
+ usb_kill_urb(edge_serial->interrupt_read_urb);
+ usb_kill_urb(edge_serial->read_urb);
+ }
+}
+
+
+/****************************************************************************
+ * edge_release
+ * This function is called when the device structure is deallocated.
+ ****************************************************************************/
+static void edge_release(struct usb_serial *serial)
+{
+ struct edgeport_serial *edge_serial = usb_get_serial_data(serial);
+
+ if (edge_serial->is_epic) {
+ usb_kill_urb(edge_serial->interrupt_read_urb);
+ usb_free_urb(edge_serial->interrupt_read_urb);
+ kfree(edge_serial->interrupt_in_buffer);
+
+ usb_kill_urb(edge_serial->read_urb);
+ usb_free_urb(edge_serial->read_urb);
+ kfree(edge_serial->bulk_in_buffer);
+ }
+
+ kfree(edge_serial);
+}
+
+static int edge_port_probe(struct usb_serial_port *port)
+{
+ struct edgeport_port *edge_port;
+
+ edge_port = kzalloc_obj(*edge_port);
+ if (!edge_port)
+ return -ENOMEM;
+
+ spin_lock_init(&edge_port->ep_lock);
+ edge_port->port = port;
+
+ usb_set_serial_port_data(port, edge_port);
+
+ return 0;
+}
+
+static void edge_port_remove(struct usb_serial_port *port)
+{
+ struct edgeport_port *edge_port;
+
+ edge_port = usb_get_serial_port_data(port);
+ kfree(edge_port);
+}
+
+static struct usb_serial_driver edgeport_2port_device = {
+ .driver = {
+ .name = "edgeport_2",
+ },
+ .description = "Edgeport 2 port adapter",
+ .id_table = edgeport_2port_id_table,
+ .num_ports = 2,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ .num_interrupt_in = 1,
+ .open = edge_open,
+ .close = edge_close,
+ .throttle = edge_throttle,
+ .unthrottle = edge_unthrottle,
+ .attach = edge_startup,
+ .disconnect = edge_disconnect,
+ .release = edge_release,
+ .port_probe = edge_port_probe,
+ .port_remove = edge_port_remove,
+ .ioctl = edge_ioctl,
+ .set_termios = edge_set_termios,
+ .tiocmget = edge_tiocmget,
+ .tiocmset = edge_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .write = edge_write,
+ .write_room = edge_write_room,
+ .chars_in_buffer = edge_chars_in_buffer,
+ .break_ctl = edge_break,
+ .read_int_callback = edge_interrupt_callback,
+ .read_bulk_callback = edge_bulk_in_callback,
+ .write_bulk_callback = edge_bulk_out_data_callback,
+};
+
+static struct usb_serial_driver edgeport_4port_device = {
+ .driver = {
+ .name = "edgeport_4",
+ },
+ .description = "Edgeport 4 port adapter",
+ .id_table = edgeport_4port_id_table,
+ .num_ports = 4,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ .num_interrupt_in = 1,
+ .open = edge_open,
+ .close = edge_close,
+ .throttle = edge_throttle,
+ .unthrottle = edge_unthrottle,
+ .attach = edge_startup,
+ .disconnect = edge_disconnect,
+ .release = edge_release,
+ .port_probe = edge_port_probe,
+ .port_remove = edge_port_remove,
+ .ioctl = edge_ioctl,
+ .set_termios = edge_set_termios,
+ .tiocmget = edge_tiocmget,
+ .tiocmset = edge_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .write = edge_write,
+ .write_room = edge_write_room,
+ .chars_in_buffer = edge_chars_in_buffer,
+ .break_ctl = edge_break,
+ .read_int_callback = edge_interrupt_callback,
+ .read_bulk_callback = edge_bulk_in_callback,
+ .write_bulk_callback = edge_bulk_out_data_callback,
+};
+
+static struct usb_serial_driver edgeport_8port_device = {
+ .driver = {
+ .name = "edgeport_8",
+ },
+ .description = "Edgeport 8 port adapter",
+ .id_table = edgeport_8port_id_table,
+ .num_ports = 8,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ .num_interrupt_in = 1,
+ .open = edge_open,
+ .close = edge_close,
+ .throttle = edge_throttle,
+ .unthrottle = edge_unthrottle,
+ .attach = edge_startup,
+ .disconnect = edge_disconnect,
+ .release = edge_release,
+ .port_probe = edge_port_probe,
+ .port_remove = edge_port_remove,
+ .ioctl = edge_ioctl,
+ .set_termios = edge_set_termios,
+ .tiocmget = edge_tiocmget,
+ .tiocmset = edge_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .write = edge_write,
+ .write_room = edge_write_room,
+ .chars_in_buffer = edge_chars_in_buffer,
+ .break_ctl = edge_break,
+ .read_int_callback = edge_interrupt_callback,
+ .read_bulk_callback = edge_bulk_in_callback,
+ .write_bulk_callback = edge_bulk_out_data_callback,
+};
+
+static struct usb_serial_driver epic_device = {
+ .driver = {
+ .name = "epic",
+ },
+ .description = "EPiC device",
+ .id_table = Epic_port_id_table,
+ .num_ports = 1,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ .num_interrupt_in = 1,
+ .open = edge_open,
+ .close = edge_close,
+ .throttle = edge_throttle,
+ .unthrottle = edge_unthrottle,
+ .attach = edge_startup,
+ .disconnect = edge_disconnect,
+ .release = edge_release,
+ .port_probe = edge_port_probe,
+ .port_remove = edge_port_remove,
+ .ioctl = edge_ioctl,
+ .set_termios = edge_set_termios,
+ .tiocmget = edge_tiocmget,
+ .tiocmset = edge_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .write = edge_write,
+ .write_room = edge_write_room,
+ .chars_in_buffer = edge_chars_in_buffer,
+ .break_ctl = edge_break,
+ .read_int_callback = edge_interrupt_callback,
+ .read_bulk_callback = edge_bulk_in_callback,
+ .write_bulk_callback = edge_bulk_out_data_callback,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &edgeport_2port_device, &edgeport_4port_device,
+ &edgeport_8port_device, &epic_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table_combined);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("edgeport/boot.fw");
+MODULE_FIRMWARE("edgeport/boot2.fw");
+MODULE_FIRMWARE("edgeport/down.fw");
+MODULE_FIRMWARE("edgeport/down2.fw");
diff --git a/drivers/usb/serial/io_edgeport.h b/drivers/usb/serial/io_edgeport.h
new file mode 100644
index 000000000..7c9f62af5
--- /dev/null
+++ b/drivers/usb/serial/io_edgeport.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/************************************************************************
+ *
+ * io_edgeport.h Edgeport Linux Interface definitions
+ *
+ * Copyright (C) 2000 Inside Out Networks, Inc.
+ *
+ ************************************************************************/
+
+#if !defined(_IO_EDGEPORT_H_)
+#define _IO_EDGEPORT_H_
+
+#define MAX_RS232_PORTS 8 /* Max # of RS-232 ports per device */
+
+/* typedefs that the insideout headers need */
+#ifndef LOW8
+ #define LOW8(a) ((unsigned char)(a & 0xff))
+#endif
+#ifndef HIGH8
+ #define HIGH8(a) ((unsigned char)((a & 0xff00) >> 8))
+#endif
+
+#include "io_usbvend.h"
+
+/*
+ * Product information read from the Edgeport
+ */
+struct edgeport_product_info {
+ __u16 ProductId; /* Product Identifier */
+ __u8 NumPorts; /* Number of ports on edgeport */
+ __u8 ProdInfoVer; /* What version of structure is this? */
+
+ __u32 IsServer :1; /* Set if Server */
+ __u32 IsRS232 :1; /* Set if RS-232 ports exist */
+ __u32 IsRS422 :1; /* Set if RS-422 ports exist */
+ __u32 IsRS485 :1; /* Set if RS-485 ports exist */
+ __u32 IsReserved :28; /* Reserved for later expansion */
+
+ __u8 RomSize; /* Size of ROM/E2PROM in K */
+ __u8 RamSize; /* Size of external RAM in K */
+ __u8 CpuRev; /* CPU revision level (chg only if s/w visible) */
+ __u8 BoardRev; /* PCB revision level (chg only if s/w visible) */
+
+ __u8 BootMajorVersion; /* Boot Firmware version: xx. */
+ __u8 BootMinorVersion; /* yy. */
+ __le16 BootBuildNumber; /* zzzz (LE format) */
+
+ __u8 FirmwareMajorVersion; /* Operational Firmware version:xx. */
+ __u8 FirmwareMinorVersion; /* yy. */
+ __le16 FirmwareBuildNumber; /* zzzz (LE format) */
+
+ __u8 ManufactureDescDate[3]; /* MM/DD/YY when descriptor template was compiled */
+ __u8 HardwareType;
+
+ __u8 iDownloadFile; /* What to download to EPiC device */
+ __u8 EpicVer; /* What version of EPiC spec this device supports */
+
+ struct edge_compatibility_bits Epic;
+};
+
+#endif
diff --git a/drivers/usb/serial/io_ionsp.h b/drivers/usb/serial/io_ionsp.h
new file mode 100644
index 000000000..db4fce815
--- /dev/null
+++ b/drivers/usb/serial/io_ionsp.h
@@ -0,0 +1,451 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/************************************************************************
+ *
+ * IONSP.H Definitions for I/O Networks Serial Protocol
+ *
+ * Copyright (C) 1997-1998 Inside Out Networks, Inc.
+ *
+ * These definitions are used by both kernel-mode driver and the
+ * peripheral firmware and MUST be kept in sync.
+ *
+ ************************************************************************/
+
+/************************************************************************
+
+The data to and from all ports on the peripheral is multiplexed
+through a single endpoint pair (EP1 since it supports 64-byte
+MaxPacketSize). Therefore, the data, commands, and status for
+each port must be preceded by a short header identifying the
+destination port. The header also identifies the bytes that follow
+as data or as command/status info.
+
+Header format, first byte:
+
+ CLLLLPPP
+ --------
+ | | |------ Port Number: 0-7
+ | |--------- Length: MSB bits of length
+ |----------- Data/Command: 0 = Data header
+ 1 = Cmd / Status (Cmd if OUT, Status if IN)
+
+This gives 2 possible formats:
+
+
+ Data header: 0LLLLPPP LLLLLLLL
+ ============
+
+ Where (LLLL,LLLLLLL) is 12-bit length of data that follows for
+ port number (PPP). The length is 0-based (0-FFF means 0-4095
+ bytes). The ~4K limit allows the host driver (which deals in
+ transfer requests instead of individual packets) to write a
+ large chunk of data in a single request. Note, however, that
+ the length must always be <= the current TxCredits for a given
+ port due to buffering limitations on the peripheral.
+
+
+ Cmd/Status header: 1ccccPPP [ CCCCCCCC, Params ]...
+ ==================
+
+ Where (cccc) or (cccc,CCCCCCCC) is the cmd or status identifier.
+ Frequently-used values are encoded as (cccc), longer ones using
+ (cccc,CCCCCCCC). Subsequent bytes are optional parameters and are
+ specific to the cmd or status code. This may include a length
+ for command and status codes that need variable-length parameters.
+
+
+In addition, we use another interrupt pipe (endpoint) which the host polls
+periodically for flow control information. The peripheral, when there has
+been a change, sends the following 10-byte packet:
+
+ RRRRRRRRRRRRRRRR
+ T0T0T0T0T0T0T0T0
+ T1T1T1T1T1T1T1T1
+ T2T2T2T2T2T2T2T2
+ T3T3T3T3T3T3T3T3
+
+The first field is the 16-bit RxBytesAvail field, which indicates the
+number of bytes which may be read by the host from EP1. This is necessary:
+(a) because OSR2.1 has a bug which causes data loss if the peripheral returns
+fewer bytes than the host expects to read, and (b) because, on Microsoft
+platforms at least, an outstanding read posted on EP1 consumes about 35% of
+the CPU just polling the device for data.
+
+The next 4 fields are the 16-bit TxCredits for each port, which indicate how
+many bytes the host is allowed to send on EP1 for transmit to a given port.
+After an OPEN_PORT command, the Edgeport sends the initial TxCredits for that
+port.
+
+All 16-bit fields are sent in little-endian (Intel) format.
+
+************************************************************************/
+
+//
+// Define format of InterruptStatus packet returned from the
+// Interrupt pipe
+//
+
+struct int_status_pkt {
+ __u16 RxBytesAvail; // Additional bytes available to
+ // be read from Bulk IN pipe
+ __u16 TxCredits[MAX_RS232_PORTS]; // Additional space available in
+ // given port's TxBuffer
+};
+
+
+#define GET_INT_STATUS_SIZE(NumPorts) (sizeof(__u16) + (sizeof(__u16) * (NumPorts)))
+
+
+
+//
+// Define cmd/status header values and macros to extract them.
+//
+// Data: 0LLLLPPP LLLLLLLL
+// Cmd/Stat: 1ccccPPP CCCCCCCC
+
+#define IOSP_DATA_HDR_SIZE 2
+#define IOSP_CMD_HDR_SIZE 2
+
+#define IOSP_MAX_DATA_LENGTH 0x0FFF // 12 bits -> 4K
+
+#define IOSP_PORT_MASK 0x07 // Mask to isolate port number
+#define IOSP_CMD_STAT_BIT 0x80 // If set, this is command/status header
+
+#define IS_CMD_STAT_HDR(Byte1) ((Byte1) & IOSP_CMD_STAT_BIT)
+#define IS_DATA_HDR(Byte1) (!IS_CMD_STAT_HDR(Byte1))
+
+#define IOSP_GET_HDR_PORT(Byte1) ((__u8) ((Byte1) & IOSP_PORT_MASK))
+#define IOSP_GET_HDR_DATA_LEN(Byte1, Byte2) ((__u16) (((__u16)((Byte1) & 0x78)) << 5) | (Byte2))
+#define IOSP_GET_STATUS_CODE(Byte1) ((__u8) (((Byte1) & 0x78) >> 3))
+
+
+//
+// These macros build the 1st and 2nd bytes for a data header
+//
+#define IOSP_BUILD_DATA_HDR1(Port, Len) ((__u8) (((Port) | ((__u8) (((__u16) (Len)) >> 5) & 0x78))))
+#define IOSP_BUILD_DATA_HDR2(Port, Len) ((__u8) (Len))
+
+
+//
+// These macros build the 1st and 2nd bytes for a command header
+//
+#define IOSP_BUILD_CMD_HDR1(Port, Cmd) ((__u8) (IOSP_CMD_STAT_BIT | (Port) | ((__u8) ((Cmd) << 3))))
+
+
+//--------------------------------------------------------------
+//
+// Define values for commands and command parameters
+// (sent from Host to Edgeport)
+//
+// 1ccccPPP P1P1P1P1 [ P2P2P2P2P2 ]...
+//
+// cccc: 00-07 2-byte commands. Write UART register 0-7 with
+// value in P1. See 16650.H for definitions of
+// UART register numbers and contents.
+//
+// 08-0B 3-byte commands: ==== P1 ==== ==== P2 ====
+// 08 available for expansion
+// 09 1-param commands Command Code Param
+// 0A available for expansion
+// 0B available for expansion
+//
+// 0C-0D 4-byte commands. P1 = extended cmd and P2,P3 = params
+// Currently unimplemented.
+//
+// 0E-0F N-byte commands: P1 = num bytes after P1 (ie, TotalLen - 2)
+// P2 = extended cmd, P3..Pn = parameters.
+// Currently unimplemented.
+//
+
+#define IOSP_WRITE_UART_REG(n) ((n) & 0x07) // UartReg[ n ] := P1
+
+// Register numbers and contents
+// defined in 16554.H.
+
+// 0x08 // Available for expansion.
+#define IOSP_EXT_CMD 0x09 // P1 = Command code (defined below)
+
+// P2 = Parameter
+
+//
+// Extended Command values, used with IOSP_EXT_CMD, may
+// or may not use parameter P2.
+//
+
+#define IOSP_CMD_OPEN_PORT 0x00 // Enable ints, init UART. (NO PARAM)
+#define IOSP_CMD_CLOSE_PORT 0x01 // Disable ints, flush buffers. (NO PARAM)
+#define IOSP_CMD_CHASE_PORT 0x02 // Wait for Edgeport TX buffers to empty. (NO PARAM)
+#define IOSP_CMD_SET_RX_FLOW 0x03 // Set Rx Flow Control in Edgeport
+#define IOSP_CMD_SET_TX_FLOW 0x04 // Set Tx Flow Control in Edgeport
+#define IOSP_CMD_SET_XON_CHAR 0x05 // Set XON Character in Edgeport
+#define IOSP_CMD_SET_XOFF_CHAR 0x06 // Set XOFF Character in Edgeport
+#define IOSP_CMD_RX_CHECK_REQ 0x07 // Request Edgeport to insert a Checkpoint into
+
+// the receive data stream (Parameter = 1 byte sequence number)
+
+#define IOSP_CMD_SET_BREAK 0x08 // Turn on the BREAK (LCR bit 6)
+#define IOSP_CMD_CLEAR_BREAK 0x09 // Turn off the BREAK (LCR bit 6)
+
+
+//
+// Define macros to simplify building of IOSP cmds
+//
+
+#define MAKE_CMD_WRITE_REG(ppBuf, pLen, Port, Reg, Val) \
+do { \
+ (*(ppBuf))[0] = IOSP_BUILD_CMD_HDR1((Port), \
+ IOSP_WRITE_UART_REG(Reg)); \
+ (*(ppBuf))[1] = (Val); \
+ \
+ *ppBuf += 2; \
+ *pLen += 2; \
+} while (0)
+
+#define MAKE_CMD_EXT_CMD(ppBuf, pLen, Port, ExtCmd, Param) \
+do { \
+ (*(ppBuf))[0] = IOSP_BUILD_CMD_HDR1((Port), IOSP_EXT_CMD); \
+ (*(ppBuf))[1] = (ExtCmd); \
+ (*(ppBuf))[2] = (Param); \
+ \
+ *ppBuf += 3; \
+ *pLen += 3; \
+} while (0)
+
+
+
+//--------------------------------------------------------------
+//
+// Define format of flow control commands
+// (sent from Host to Edgeport)
+//
+// 11001PPP FlowCmd FlowTypes
+//
+// Note that the 'FlowTypes' parameter is a bit mask; that is,
+// more than one flow control type can be active at the same time.
+// FlowTypes = 0 means 'no flow control'.
+//
+
+//
+// IOSP_CMD_SET_RX_FLOW
+//
+// Tells Edgeport how it can stop incoming UART data
+//
+// Example for Port 0
+// P0 = 11001000
+// P1 = IOSP_CMD_SET_RX_FLOW
+// P2 = Bit mask as follows:
+
+#define IOSP_RX_FLOW_RTS 0x01 // Edgeport drops RTS to stop incoming data
+#define IOSP_RX_FLOW_DTR 0x02 // Edgeport drops DTR to stop incoming data
+#define IOSP_RX_FLOW_DSR_SENSITIVITY 0x04 // Ignores Rx data unless DSR high
+
+// Not currently implemented by firmware.
+#define IOSP_RX_FLOW_XON_XOFF 0x08 // Edgeport sends XOFF char to stop incoming data.
+
+// Host must have previously programmed the
+// XON/XOFF values with SET_XON/SET_XOFF
+// before enabling this bit.
+
+//
+// IOSP_CMD_SET_TX_FLOW
+//
+// Tells Edgeport what signal(s) will stop it from transmitting UART data
+//
+// Example for Port 0
+// P0 = 11001000
+// P1 = IOSP_CMD_SET_TX_FLOW
+// P2 = Bit mask as follows:
+
+#define IOSP_TX_FLOW_CTS 0x01 // Edgeport stops Tx if CTS low
+#define IOSP_TX_FLOW_DSR 0x02 // Edgeport stops Tx if DSR low
+#define IOSP_TX_FLOW_DCD 0x04 // Edgeport stops Tx if DCD low
+#define IOSP_TX_FLOW_XON_XOFF 0x08 // Edgeport stops Tx upon receiving XOFF char.
+
+// Host must have previously programmed the
+// XON/XOFF values with SET_XON/SET_XOFF
+// before enabling this bit.
+#define IOSP_TX_FLOW_XOFF_CONTINUE 0x10 // If not set, Edgeport stops Tx when
+
+// sending XOFF in order to fix broken
+// systems that interpret the next
+// received char as XON.
+// If set, Edgeport continues Tx
+// normally after transmitting XOFF.
+// Not currently implemented by firmware.
+#define IOSP_TX_TOGGLE_RTS 0x20 // Edgeport drives RTS as a true half-duplex
+
+// Request-to-Send signal: it is raised before
+// beginning transmission and lowered after
+// the last Tx char leaves the UART.
+// Not currently implemented by firmware.
+
+//
+// IOSP_CMD_SET_XON_CHAR
+//
+// Sets the character which Edgeport transmits/interprets as XON.
+// Note: This command MUST be sent before sending a SET_RX_FLOW or
+// SET_TX_FLOW with the XON_XOFF bit set.
+//
+// Example for Port 0
+// P0 = 11001000
+// P1 = IOSP_CMD_SET_XON_CHAR
+// P2 = 0x11
+
+
+//
+// IOSP_CMD_SET_XOFF_CHAR
+//
+// Sets the character which Edgeport transmits/interprets as XOFF.
+// Note: This command must be sent before sending a SET_RX_FLOW or
+// SET_TX_FLOW with the XON_XOFF bit set.
+//
+// Example for Port 0
+// P0 = 11001000
+// P1 = IOSP_CMD_SET_XOFF_CHAR
+// P2 = 0x13
+
+
+//
+// IOSP_CMD_RX_CHECK_REQ
+//
+// This command is used to assist in the implementation of the
+// IOCTL_SERIAL_PURGE Windows IOCTL.
+// This IOSP command tries to place a marker at the end of the RX
+// queue in the Edgeport. If the Edgeport RX queue is full then
+// the Check will be discarded.
+// It is up to the device driver to timeout waiting for the
+// RX_CHECK_RSP. If a RX_CHECK_RSP is received, the driver is
+// sure that all data has been received from the edgeport and
+// may now purge any internal RX buffers.
+// Note tat the sequence numbers may be used to detect lost
+// CHECK_REQs.
+
+// Example for Port 0
+// P0 = 11001000
+// P1 = IOSP_CMD_RX_CHECK_REQ
+// P2 = Sequence number
+
+
+// Response will be:
+// P1 = IOSP_EXT_RX_CHECK_RSP
+// P2 = Request Sequence number
+
+
+
+//--------------------------------------------------------------
+//
+// Define values for status and status parameters
+// (received by Host from Edgeport)
+//
+// 1ssssPPP P1P1P1P1 [ P2P2P2P2P2 ]...
+//
+// ssss: 00-07 2-byte status. ssss identifies which UART register
+// has changed value, and the new value is in P1.
+// Note that the ssss values do not correspond to the
+// 16554 register numbers given in 16554.H. Instead,
+// see below for definitions of the ssss numbers
+// used in this status message.
+//
+// 08-0B 3-byte status: ==== P1 ==== ==== P2 ====
+// 08 LSR_DATA: New LSR Errored byte
+// 09 1-param responses Response Code Param
+// 0A OPEN_RSP: InitialMsr TxBufferSize
+// 0B available for expansion
+//
+// 0C-0D 4-byte status. P1 = extended status code and P2,P3 = params
+// Not currently implemented.
+//
+// 0E-0F N-byte status: P1 = num bytes after P1 (ie, TotalLen - 2)
+// P2 = extended status, P3..Pn = parameters.
+// Not currently implemented.
+//
+
+/****************************************************
+ * SSSS values for 2-byte status messages (0-8)
+ ****************************************************/
+
+#define IOSP_STATUS_LSR 0x00 // P1 is new value of LSR register.
+
+// Bits defined in 16554.H. Edgeport
+// returns this in order to report
+// line status errors (overrun,
+// parity, framing, break). This form
+// is used when a errored receive data
+// character was NOT present in the
+// UART when the LSR error occurred
+// (ie, when LSR bit 0 = 0).
+
+#define IOSP_STATUS_MSR 0x01 // P1 is new value of MSR register.
+
+// Bits defined in 16554.H. Edgeport
+// returns this in order to report
+// changes in modem status lines
+// (CTS, DSR, RI, CD)
+//
+
+// 0x02 // Available for future expansion
+// 0x03 //
+// 0x04 //
+// 0x05 //
+// 0x06 //
+// 0x07 //
+
+
+/****************************************************
+ * SSSS values for 3-byte status messages (8-A)
+ ****************************************************/
+
+#define IOSP_STATUS_LSR_DATA 0x08 // P1 is new value of LSR register (same as STATUS_LSR)
+
+// P2 is errored character read from
+// RxFIFO after LSR reported an error.
+
+#define IOSP_EXT_STATUS 0x09 // P1 is status/response code, param in P2.
+
+
+// Response Codes (P1 values) for 3-byte status messages
+
+#define IOSP_EXT_STATUS_CHASE_RSP 0 // Reply to CHASE_PORT cmd. P2 is outcome:
+#define IOSP_EXT_STATUS_CHASE_PASS 0 // P2 = 0: All Tx data drained successfully
+#define IOSP_EXT_STATUS_CHASE_FAIL 1 // P2 = 1: Timed out (stuck due to flow
+
+// control from remote device).
+
+#define IOSP_EXT_STATUS_RX_CHECK_RSP 1 // Reply to RX_CHECK cmd. P2 is sequence number
+
+
+#define IOSP_STATUS_OPEN_RSP 0x0A // Reply to OPEN_PORT cmd.
+
+// P1 is Initial MSR value
+// P2 is encoded TxBuffer Size:
+// TxBufferSize = (P2 + 1) * 64
+
+// 0x0B // Available for future expansion
+
+#define GET_TX_BUFFER_SIZE(P2) (((P2) + 1) * 64)
+
+
+
+
+/****************************************************
+ * SSSS values for 4-byte status messages
+ ****************************************************/
+
+#define IOSP_EXT4_STATUS 0x0C // Extended status code in P1,
+
+// Params in P2, P3
+// Currently unimplemented.
+
+// 0x0D // Currently unused, available.
+
+
+
+//
+// Macros to parse status messages
+//
+
+#define IOSP_GET_STATUS_LEN(code) ((code) < 8 ? 2 : ((code) < 0x0A ? 3 : 4))
+
+#define IOSP_STATUS_IS_2BYTE(code) ((code) < 0x08)
+#define IOSP_STATUS_IS_3BYTE(code) (((code) >= 0x08) && ((code) <= 0x0B))
+#define IOSP_STATUS_IS_4BYTE(code) (((code) >= 0x0C) && ((code) <= 0x0D))
+
diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c
new file mode 100644
index 000000000..07c0eff3b
--- /dev/null
+++ b/drivers/usb/serial/io_ti.c
@@ -0,0 +1,2769 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Edgeport USB Serial Converter driver
+ *
+ * Copyright (C) 2000-2002 Inside Out Networks, All rights reserved.
+ * Copyright (C) 2001-2002 Greg Kroah-Hartman <greg@kroah.com>
+ *
+ * Supports the following devices:
+ * EP/1 EP/2 EP/4 EP/21 EP/22 EP/221 EP/42 EP/421 WATCHPORT
+ *
+ * For questions or problems with this driver, contact Inside Out
+ * Networks technical support, or Peter Berger <pberger@brimson.com>,
+ * or Al Borchers <alborchers@steinerpoint.com>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/serial.h>
+#include <linux/swab.h>
+#include <linux/kfifo.h>
+#include <linux/ioctl.h>
+#include <linux/firmware.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#include "io_16654.h"
+#include "io_usbvend.h"
+#include "io_ti.h"
+
+#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com> and David Iacovelli"
+#define DRIVER_DESC "Edgeport USB Serial Driver"
+
+#define EPROM_PAGE_SIZE 64
+
+
+/* different hardware types */
+#define HARDWARE_TYPE_930 0
+#define HARDWARE_TYPE_TIUMP 1
+
+/* IOCTL_PRIVATE_TI_GET_MODE Definitions */
+#define TI_MODE_CONFIGURING 0 /* Device has not entered start device */
+#define TI_MODE_BOOT 1 /* Staying in boot mode */
+#define TI_MODE_DOWNLOAD 2 /* Made it to download mode */
+#define TI_MODE_TRANSITIONING 3 /*
+ * Currently in boot mode but
+ * transitioning to download mode
+ */
+
+/* read urb state */
+#define EDGE_READ_URB_RUNNING 0
+#define EDGE_READ_URB_STOPPING 1
+#define EDGE_READ_URB_STOPPED 2
+
+
+/* Product information read from the Edgeport */
+struct product_info {
+ int TiMode; /* Current TI Mode */
+ u8 hardware_type; /* Type of hardware */
+} __packed;
+
+/*
+ * Edgeport firmware header
+ *
+ * "build_number" has been set to 0 in all three of the images I have
+ * seen, and Digi Tech Support suggests that it is safe to ignore it.
+ *
+ * "length" is the number of bytes of actual data following the header.
+ *
+ * "checksum" is the low order byte resulting from adding the values of
+ * all the data bytes.
+ */
+struct edgeport_fw_hdr {
+ u8 major_version;
+ u8 minor_version;
+ __le16 build_number;
+ __le16 length;
+ u8 checksum;
+} __packed;
+
+struct edgeport_port {
+ u16 uart_base;
+ u16 dma_address;
+ u8 shadow_msr;
+ u8 shadow_mcr;
+ u8 shadow_lsr;
+ u8 lsr_mask;
+ u32 ump_read_timeout; /*
+ * Number of milliseconds the UMP will
+ * wait without data before completing
+ * a read short
+ */
+ int baud_rate;
+ int close_pending;
+ int lsr_event;
+
+ struct edgeport_serial *edge_serial;
+ struct usb_serial_port *port;
+ u8 bUartMode; /* Port type, 0: RS232, etc. */
+ spinlock_t ep_lock;
+ int ep_read_urb_state;
+ int ep_write_urb_in_use;
+};
+
+struct edgeport_serial {
+ struct product_info product_info;
+ u8 TI_I2C_Type; /* Type of I2C in UMP */
+ u8 TiReadI2C; /*
+ * Set to TRUE if we have read the
+ * I2c in Boot Mode
+ */
+ struct mutex es_lock;
+ int num_ports_open;
+ struct usb_serial *serial;
+ struct delayed_work heartbeat_work;
+ int fw_version;
+ bool use_heartbeat;
+};
+
+
+/* Devices that this driver supports */
+static const struct usb_device_id edgeport_1port_id_table[] = {
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_1) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_TI3410_EDGEPORT_1) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_TI3410_EDGEPORT_1I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_PROXIMITY) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_MOTION) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_MOISTURE) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_TEMPERATURE) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_HUMIDITY) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_POWER) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_LIGHT) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_RADIATION) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_DISTANCE) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_ACCELERATION) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_PROX_DIST) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_PLUS_PWR_HP4CD) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_PLUS_PWR_PCI) },
+ { }
+};
+
+static const struct usb_device_id edgeport_2port_id_table[] = {
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_2) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_2C) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_2I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_421) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_21) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_42) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_4) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_4I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_22I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_221C) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_22C) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_21C) },
+ /* The 4, 8 and 16 port devices show up as multiple 2 port devices */
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_4S) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_8) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_8S) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_416) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_416B) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_E5805A) },
+ { }
+};
+
+/* Devices that this driver supports */
+static const struct usb_device_id id_table_combined[] = {
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_1) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_TI3410_EDGEPORT_1) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_TI3410_EDGEPORT_1I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_PROXIMITY) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_MOTION) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_MOISTURE) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_TEMPERATURE) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_HUMIDITY) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_POWER) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_LIGHT) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_RADIATION) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_DISTANCE) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_ACCELERATION) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_PROX_DIST) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_PLUS_PWR_HP4CD) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_PLUS_PWR_PCI) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_2) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_2C) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_2I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_421) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_21) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_42) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_4) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_4I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_22I) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_221C) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_22C) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_21C) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_4S) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_8) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_8S) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_416) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_416B) },
+ { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_E5805A) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(usb, id_table_combined);
+
+static bool ignore_cpu_rev;
+static int default_uart_mode; /* RS232 */
+
+static void edge_tty_recv(struct usb_serial_port *port, unsigned char *data,
+ int length);
+
+static void stop_read(struct edgeport_port *edge_port);
+static int restart_read(struct edgeport_port *edge_port);
+
+static void edge_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+static void edge_send(struct usb_serial_port *port, struct tty_struct *tty);
+
+static int do_download_mode(struct edgeport_serial *serial,
+ const struct firmware *fw);
+static int do_boot_mode(struct edgeport_serial *serial,
+ const struct firmware *fw);
+
+/* sysfs attributes */
+static int edge_create_sysfs_attrs(struct usb_serial_port *port);
+static int edge_remove_sysfs_attrs(struct usb_serial_port *port);
+
+/*
+ * Some release of Edgeport firmware "down3.bin" after version 4.80
+ * introduced code to automatically disconnect idle devices on some
+ * Edgeport models after periods of inactivity, typically ~60 seconds.
+ * This occurs without regard to whether ports on the device are open
+ * or not. Digi International Tech Support suggested:
+ *
+ * 1. Adding driver "heartbeat" code to reset the firmware timer by
+ * requesting a descriptor record every 15 seconds, which should be
+ * effective with newer firmware versions that require it, and benign
+ * with older versions that do not. In practice 40 seconds seems often
+ * enough.
+ * 2. The heartbeat code is currently required only on Edgeport/416 models.
+ */
+#define FW_HEARTBEAT_VERSION_CUTOFF ((4 << 8) + 80)
+#define FW_HEARTBEAT_SECS 40
+
+/* Timeouts in msecs: firmware downloads take longer */
+#define TI_VSEND_TIMEOUT_DEFAULT 1000
+#define TI_VSEND_TIMEOUT_FW_DOWNLOAD 10000
+
+static int ti_vread_sync(struct usb_device *dev, u8 request, u16 value,
+ u16 index, void *data, int size)
+{
+ int status;
+
+ status = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), request,
+ (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN),
+ value, index, data, size, 1000);
+ if (status < 0)
+ return status;
+ if (status != size) {
+ dev_dbg(&dev->dev, "%s - wanted to read %d, but only read %d\n",
+ __func__, size, status);
+ return -ECOMM;
+ }
+ return 0;
+}
+
+static int ti_vsend_sync(struct usb_device *dev, u8 request, u16 value,
+ u16 index, void *data, int size, int timeout)
+{
+ int status;
+
+ status = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request,
+ (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT),
+ value, index, data, size, timeout);
+ if (status < 0)
+ return status;
+
+ return 0;
+}
+
+static int read_port_cmd(struct usb_serial_port *port, u8 command, u16 value,
+ void *data, int size)
+{
+ return ti_vread_sync(port->serial->dev, command, value,
+ UMPM_UART1_PORT + port->port_number,
+ data, size);
+}
+
+static int send_port_cmd(struct usb_serial_port *port, u8 command, u16 value,
+ void *data, int size)
+{
+ return ti_vsend_sync(port->serial->dev, command, value,
+ UMPM_UART1_PORT + port->port_number,
+ data, size, TI_VSEND_TIMEOUT_DEFAULT);
+}
+
+/* clear tx/rx buffers and fifo in TI UMP */
+static int purge_port(struct usb_serial_port *port, u16 mask)
+{
+ int port_number = port->port_number;
+
+ dev_dbg(&port->dev, "%s - port %d, mask %x\n", __func__, port_number, mask);
+
+ return send_port_cmd(port, UMPC_PURGE_PORT, mask, NULL, 0);
+}
+
+/**
+ * read_download_mem - Read edgeport memory from TI chip
+ * @dev: usb device pointer
+ * @start_address: Device CPU address at which to read
+ * @length: Length of above data
+ * @address_type: Can read both XDATA and I2C
+ * @buffer: pointer to input data buffer
+ */
+static int read_download_mem(struct usb_device *dev, int start_address,
+ int length, u8 address_type, u8 *buffer)
+{
+ int status = 0;
+ u8 read_length;
+ u16 be_start_address;
+
+ dev_dbg(&dev->dev, "%s - @ %x for %d\n", __func__, start_address, length);
+
+ /*
+ * Read in blocks of 64 bytes
+ * (TI firmware can't handle more than 64 byte reads)
+ */
+ while (length) {
+ if (length > 64)
+ read_length = 64;
+ else
+ read_length = (u8)length;
+
+ if (read_length > 1) {
+ dev_dbg(&dev->dev, "%s - @ %x for %d\n", __func__, start_address, read_length);
+ }
+ /*
+ * NOTE: Must use swab as wIndex is sent in little-endian
+ * byte order regardless of host byte order.
+ */
+ be_start_address = swab16((u16)start_address);
+ status = ti_vread_sync(dev, UMPC_MEMORY_READ,
+ (u16)address_type,
+ be_start_address,
+ buffer, read_length);
+
+ if (status) {
+ dev_dbg(&dev->dev, "%s - ERROR %x\n", __func__, status);
+ return status;
+ }
+
+ if (read_length > 1)
+ usb_serial_debug_data(&dev->dev, __func__, read_length, buffer);
+
+ /* Update pointers/length */
+ start_address += read_length;
+ buffer += read_length;
+ length -= read_length;
+ }
+
+ return status;
+}
+
+static int read_ram(struct usb_device *dev, int start_address,
+ int length, u8 *buffer)
+{
+ return read_download_mem(dev, start_address, length,
+ DTK_ADDR_SPACE_XDATA, buffer);
+}
+
+/* Read edgeport memory to a given block */
+static int read_boot_mem(struct edgeport_serial *serial,
+ int start_address, int length, u8 *buffer)
+{
+ int status = 0;
+ int i;
+
+ for (i = 0; i < length; i++) {
+ status = ti_vread_sync(serial->serial->dev,
+ UMPC_MEMORY_READ, serial->TI_I2C_Type,
+ (u16)(start_address+i), &buffer[i], 0x01);
+ if (status) {
+ dev_dbg(&serial->serial->dev->dev, "%s - ERROR %x\n", __func__, status);
+ return status;
+ }
+ }
+
+ dev_dbg(&serial->serial->dev->dev, "%s - start_address = %x, length = %d\n",
+ __func__, start_address, length);
+ usb_serial_debug_data(&serial->serial->dev->dev, __func__, length, buffer);
+
+ serial->TiReadI2C = 1;
+
+ return status;
+}
+
+/* Write given block to TI EPROM memory */
+static int write_boot_mem(struct edgeport_serial *serial,
+ int start_address, int length, u8 *buffer)
+{
+ int status = 0;
+ int i;
+ u8 *temp;
+
+ /* Must do a read before write */
+ if (!serial->TiReadI2C) {
+ temp = kmalloc(1, GFP_KERNEL);
+ if (!temp)
+ return -ENOMEM;
+
+ status = read_boot_mem(serial, 0, 1, temp);
+ kfree(temp);
+ if (status)
+ return status;
+ }
+
+ for (i = 0; i < length; ++i) {
+ status = ti_vsend_sync(serial->serial->dev, UMPC_MEMORY_WRITE,
+ buffer[i], (u16)(i + start_address), NULL,
+ 0, TI_VSEND_TIMEOUT_DEFAULT);
+ if (status)
+ return status;
+ }
+
+ dev_dbg(&serial->serial->dev->dev, "%s - start_sddr = %x, length = %d\n", __func__, start_address, length);
+ usb_serial_debug_data(&serial->serial->dev->dev, __func__, length, buffer);
+
+ return status;
+}
+
+/* Write edgeport I2C memory to TI chip */
+static int write_i2c_mem(struct edgeport_serial *serial,
+ int start_address, int length, u8 address_type, u8 *buffer)
+{
+ struct device *dev = &serial->serial->dev->dev;
+ int status = 0;
+ int write_length;
+ u16 be_start_address;
+
+ /* We can only send a maximum of 1 aligned byte page at a time */
+
+ /* calculate the number of bytes left in the first page */
+ write_length = EPROM_PAGE_SIZE -
+ (start_address & (EPROM_PAGE_SIZE - 1));
+
+ if (write_length > length)
+ write_length = length;
+
+ dev_dbg(dev, "%s - BytesInFirstPage Addr = %x, length = %d\n",
+ __func__, start_address, write_length);
+ usb_serial_debug_data(dev, __func__, write_length, buffer);
+
+ /*
+ * Write first page.
+ *
+ * NOTE: Must use swab as wIndex is sent in little-endian byte order
+ * regardless of host byte order.
+ */
+ be_start_address = swab16((u16)start_address);
+ status = ti_vsend_sync(serial->serial->dev, UMPC_MEMORY_WRITE,
+ (u16)address_type, be_start_address,
+ buffer, write_length, TI_VSEND_TIMEOUT_DEFAULT);
+ if (status) {
+ dev_dbg(dev, "%s - ERROR %d\n", __func__, status);
+ return status;
+ }
+
+ length -= write_length;
+ start_address += write_length;
+ buffer += write_length;
+
+ /*
+ * We should be aligned now -- can write max page size bytes at a
+ * time.
+ */
+ while (length) {
+ if (length > EPROM_PAGE_SIZE)
+ write_length = EPROM_PAGE_SIZE;
+ else
+ write_length = length;
+
+ dev_dbg(dev, "%s - Page Write Addr = %x, length = %d\n",
+ __func__, start_address, write_length);
+ usb_serial_debug_data(dev, __func__, write_length, buffer);
+
+ /*
+ * Write next page.
+ *
+ * NOTE: Must use swab as wIndex is sent in little-endian byte
+ * order regardless of host byte order.
+ */
+ be_start_address = swab16((u16)start_address);
+ status = ti_vsend_sync(serial->serial->dev, UMPC_MEMORY_WRITE,
+ (u16)address_type, be_start_address, buffer,
+ write_length, TI_VSEND_TIMEOUT_DEFAULT);
+ if (status) {
+ dev_err(dev, "%s - ERROR %d\n", __func__, status);
+ return status;
+ }
+
+ length -= write_length;
+ start_address += write_length;
+ buffer += write_length;
+ }
+ return status;
+}
+
+/*
+ * Examine the UMP DMA registers and LSR
+ *
+ * Check the MSBit of the X and Y DMA byte count registers.
+ * A zero in this bit indicates that the TX DMA buffers are empty
+ * then check the TX Empty bit in the UART.
+ */
+static int tx_active(struct edgeport_port *port)
+{
+ int status;
+ struct out_endpoint_desc_block *oedb;
+ u8 *lsr;
+ int bytes_left = 0;
+
+ oedb = kmalloc_obj(*oedb);
+ if (!oedb)
+ return -ENOMEM;
+
+ /*
+ * Sigh, that's right, just one byte, as not all platforms can
+ * do DMA from stack
+ */
+ lsr = kmalloc(1, GFP_KERNEL);
+ if (!lsr) {
+ kfree(oedb);
+ return -ENOMEM;
+ }
+ /* Read the DMA Count Registers */
+ status = read_ram(port->port->serial->dev, port->dma_address,
+ sizeof(*oedb), (void *)oedb);
+ if (status)
+ goto exit_is_tx_active;
+
+ dev_dbg(&port->port->dev, "%s - XByteCount 0x%X\n", __func__, oedb->XByteCount);
+
+ /* and the LSR */
+ status = read_ram(port->port->serial->dev,
+ port->uart_base + UMPMEM_OFFS_UART_LSR, 1, lsr);
+
+ if (status)
+ goto exit_is_tx_active;
+ dev_dbg(&port->port->dev, "%s - LSR = 0x%X\n", __func__, *lsr);
+
+ /* If either buffer has data or we are transmitting then return TRUE */
+ if ((oedb->XByteCount & 0x80) != 0)
+ bytes_left += 64;
+
+ if ((*lsr & UMP_UART_LSR_TX_MASK) == 0)
+ bytes_left += 1;
+
+ /* We return Not Active if we get any kind of error */
+exit_is_tx_active:
+ dev_dbg(&port->port->dev, "%s - return %d\n", __func__, bytes_left);
+
+ kfree(lsr);
+ kfree(oedb);
+ return bytes_left;
+}
+
+static int choose_config(struct usb_device *dev)
+{
+ /*
+ * There may be multiple configurations on this device, in which case
+ * we would need to read and parse all of them to find out which one
+ * we want. However, we just support one config at this point,
+ * configuration # 1, which is Config Descriptor 0.
+ */
+
+ dev_dbg(&dev->dev, "%s - Number of Interfaces = %d\n",
+ __func__, dev->config->desc.bNumInterfaces);
+ dev_dbg(&dev->dev, "%s - MAX Power = %d\n",
+ __func__, dev->config->desc.bMaxPower * 2);
+
+ if (dev->config->desc.bNumInterfaces != 1) {
+ dev_err(&dev->dev, "%s - bNumInterfaces is not 1, ERROR!\n", __func__);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int read_rom(struct edgeport_serial *serial,
+ int start_address, int length, u8 *buffer)
+{
+ int status;
+
+ if (serial->product_info.TiMode == TI_MODE_DOWNLOAD) {
+ status = read_download_mem(serial->serial->dev,
+ start_address,
+ length,
+ serial->TI_I2C_Type,
+ buffer);
+ } else {
+ status = read_boot_mem(serial, start_address, length,
+ buffer);
+ }
+ return status;
+}
+
+static int write_rom(struct edgeport_serial *serial, int start_address,
+ int length, u8 *buffer)
+{
+ if (serial->product_info.TiMode == TI_MODE_BOOT)
+ return write_boot_mem(serial, start_address, length,
+ buffer);
+
+ if (serial->product_info.TiMode == TI_MODE_DOWNLOAD)
+ return write_i2c_mem(serial, start_address, length,
+ serial->TI_I2C_Type, buffer);
+ return -EINVAL;
+}
+
+/* Read a descriptor header from I2C based on type */
+static int get_descriptor_addr(struct edgeport_serial *serial,
+ int desc_type, struct ti_i2c_desc *rom_desc)
+{
+ int start_address;
+ int status;
+
+ /* Search for requested descriptor in I2C */
+ start_address = 2;
+ do {
+ status = read_rom(serial,
+ start_address,
+ sizeof(struct ti_i2c_desc),
+ (u8 *)rom_desc);
+ if (status)
+ return 0;
+
+ if (rom_desc->Type == desc_type)
+ return start_address;
+
+ start_address = start_address + sizeof(struct ti_i2c_desc) +
+ le16_to_cpu(rom_desc->Size);
+
+ } while ((start_address < TI_MAX_I2C_SIZE) && rom_desc->Type);
+
+ return 0;
+}
+
+/* Validate descriptor checksum */
+static int valid_csum(struct ti_i2c_desc *rom_desc, u8 *buffer)
+{
+ u16 i;
+ u8 cs = 0;
+
+ for (i = 0; i < le16_to_cpu(rom_desc->Size); i++)
+ cs = (u8)(cs + buffer[i]);
+
+ if (cs != rom_desc->CheckSum) {
+ pr_debug("%s - Mismatch %x - %x", __func__, rom_desc->CheckSum, cs);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* Make sure that the I2C image is good */
+static int check_i2c_image(struct edgeport_serial *serial)
+{
+ struct device *dev = &serial->serial->dev->dev;
+ int status = 0;
+ struct ti_i2c_desc *rom_desc;
+ int start_address = 2;
+ u8 *buffer;
+ u16 ttype;
+
+ rom_desc = kmalloc_obj(*rom_desc);
+ if (!rom_desc)
+ return -ENOMEM;
+
+ buffer = kmalloc(TI_MAX_I2C_SIZE, GFP_KERNEL);
+ if (!buffer) {
+ kfree(rom_desc);
+ return -ENOMEM;
+ }
+
+ /* Read the first byte (Signature0) must be 0x52 or 0x10 */
+ status = read_rom(serial, 0, 1, buffer);
+ if (status)
+ goto out;
+
+ if (*buffer != UMP5152 && *buffer != UMP3410) {
+ dev_err(dev, "%s - invalid buffer signature\n", __func__);
+ status = -ENODEV;
+ goto out;
+ }
+
+ do {
+ /* Validate the I2C */
+ status = read_rom(serial,
+ start_address,
+ sizeof(struct ti_i2c_desc),
+ (u8 *)rom_desc);
+ if (status)
+ break;
+
+ if ((start_address + sizeof(struct ti_i2c_desc) +
+ le16_to_cpu(rom_desc->Size)) > TI_MAX_I2C_SIZE) {
+ status = -ENODEV;
+ dev_dbg(dev, "%s - structure too big, erroring out.\n", __func__);
+ break;
+ }
+
+ dev_dbg(dev, "%s Type = 0x%x\n", __func__, rom_desc->Type);
+
+ /* Skip type 2 record */
+ ttype = rom_desc->Type & 0x0f;
+ if (ttype != I2C_DESC_TYPE_FIRMWARE_BASIC
+ && ttype != I2C_DESC_TYPE_FIRMWARE_AUTO) {
+ /* Read the descriptor data */
+ status = read_rom(serial, start_address +
+ sizeof(struct ti_i2c_desc),
+ le16_to_cpu(rom_desc->Size),
+ buffer);
+ if (status)
+ break;
+
+ status = valid_csum(rom_desc, buffer);
+ if (status)
+ break;
+ }
+ start_address = start_address + sizeof(struct ti_i2c_desc) +
+ le16_to_cpu(rom_desc->Size);
+
+ } while ((rom_desc->Type != I2C_DESC_TYPE_ION) &&
+ (start_address < TI_MAX_I2C_SIZE));
+
+ if ((rom_desc->Type != I2C_DESC_TYPE_ION) ||
+ (start_address > TI_MAX_I2C_SIZE))
+ status = -ENODEV;
+
+out:
+ kfree(buffer);
+ kfree(rom_desc);
+ return status;
+}
+
+static int get_manuf_info(struct edgeport_serial *serial, u8 *buffer)
+{
+ int status;
+ int start_address;
+ struct ti_i2c_desc *rom_desc;
+ struct edge_ti_manuf_descriptor *desc;
+ struct device *dev = &serial->serial->dev->dev;
+
+ rom_desc = kmalloc_obj(*rom_desc);
+ if (!rom_desc)
+ return -ENOMEM;
+
+ start_address = get_descriptor_addr(serial, I2C_DESC_TYPE_ION,
+ rom_desc);
+
+ if (!start_address) {
+ dev_dbg(dev, "%s - Edge Descriptor not found in I2C\n", __func__);
+ status = -ENODEV;
+ goto exit;
+ }
+
+ /* Read the descriptor data */
+ if (le16_to_cpu(rom_desc->Size) != sizeof(struct edge_ti_manuf_descriptor)) {
+ dev_err(dev, "unexpected Edge descriptor length: %u\n",
+ le16_to_cpu(rom_desc->Size));
+ status = -EINVAL;
+ goto exit;
+ }
+ status = read_rom(serial, start_address+sizeof(struct ti_i2c_desc),
+ le16_to_cpu(rom_desc->Size), buffer);
+ if (status)
+ goto exit;
+
+ status = valid_csum(rom_desc, buffer);
+
+ desc = (struct edge_ti_manuf_descriptor *)buffer;
+ dev_dbg(dev, "%s - IonConfig 0x%x\n", __func__, desc->IonConfig);
+ dev_dbg(dev, "%s - Version %d\n", __func__, desc->Version);
+ dev_dbg(dev, "%s - Cpu/Board 0x%x\n", __func__, desc->CpuRev_BoardRev);
+ dev_dbg(dev, "%s - NumPorts %d\n", __func__, desc->NumPorts);
+ dev_dbg(dev, "%s - NumVirtualPorts %d\n", __func__, desc->NumVirtualPorts);
+ dev_dbg(dev, "%s - TotalPorts %d\n", __func__, desc->TotalPorts);
+
+exit:
+ kfree(rom_desc);
+ return status;
+}
+
+/* Build firmware header used for firmware update */
+static int build_i2c_fw_hdr(u8 *header, const struct firmware *fw)
+{
+ u8 *buffer;
+ int buffer_size;
+ int i;
+ u8 cs = 0;
+ struct ti_i2c_desc *i2c_header;
+ struct ti_i2c_image_header *img_header;
+ struct ti_i2c_firmware_rec *firmware_rec;
+ struct edgeport_fw_hdr *fw_hdr = (struct edgeport_fw_hdr *)fw->data;
+
+ /*
+ * In order to update the I2C firmware we must change the type 2 record
+ * to type 0xF2. This will force the UMP to come up in Boot Mode.
+ * Then while in boot mode, the driver will download the latest
+ * firmware (padded to 15.5k) into the UMP ram. And finally when the
+ * device comes back up in download mode the driver will cause the new
+ * firmware to be copied from the UMP Ram to I2C and the firmware will
+ * update the record type from 0xf2 to 0x02.
+ */
+
+ /*
+ * Allocate a 15.5k buffer + 2 bytes for version number (Firmware
+ * Record)
+ */
+ buffer_size = (((1024 * 16) - 512 ) +
+ sizeof(struct ti_i2c_firmware_rec));
+
+ buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ /* Set entire image of 0xffs */
+ memset(buffer, 0xff, buffer_size);
+
+ /* Copy version number into firmware record */
+ firmware_rec = (struct ti_i2c_firmware_rec *)buffer;
+
+ firmware_rec->Ver_Major = fw_hdr->major_version;
+ firmware_rec->Ver_Minor = fw_hdr->minor_version;
+
+ /* Pointer to fw_down memory image */
+ img_header = (struct ti_i2c_image_header *)&fw->data[4];
+
+ if (le16_to_cpu(img_header->Length) >
+ buffer_size - sizeof(struct ti_i2c_firmware_rec)) {
+ kfree(buffer);
+ return -EINVAL;
+ }
+ memcpy(buffer + sizeof(struct ti_i2c_firmware_rec),
+ &fw->data[4 + sizeof(struct ti_i2c_image_header)],
+ le16_to_cpu(img_header->Length));
+
+ for (i=0; i < buffer_size; i++) {
+ cs = (u8)(cs + buffer[i]);
+ }
+
+ kfree(buffer);
+
+ /* Build new header */
+ i2c_header = (struct ti_i2c_desc *)header;
+ firmware_rec = (struct ti_i2c_firmware_rec*)i2c_header->Data;
+
+ i2c_header->Type = I2C_DESC_TYPE_FIRMWARE_BLANK;
+ i2c_header->Size = cpu_to_le16(buffer_size);
+ i2c_header->CheckSum = cs;
+ firmware_rec->Ver_Major = fw_hdr->major_version;
+ firmware_rec->Ver_Minor = fw_hdr->minor_version;
+
+ return 0;
+}
+
+/* Try to figure out what type of I2c we have */
+static int i2c_type_bootmode(struct edgeport_serial *serial)
+{
+ struct device *dev = &serial->serial->dev->dev;
+ int status;
+ u8 *data;
+
+ data = kmalloc(1, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ /* Try to read type 2 */
+ status = ti_vread_sync(serial->serial->dev, UMPC_MEMORY_READ,
+ DTK_ADDR_SPACE_I2C_TYPE_II, 0, data, 0x01);
+ if (status)
+ dev_dbg(dev, "%s - read 2 status error = %d\n", __func__, status);
+ else
+ dev_dbg(dev, "%s - read 2 data = 0x%x\n", __func__, *data);
+ if ((!status) && (*data == UMP5152 || *data == UMP3410)) {
+ dev_dbg(dev, "%s - ROM_TYPE_II\n", __func__);
+ serial->TI_I2C_Type = DTK_ADDR_SPACE_I2C_TYPE_II;
+ goto out;
+ }
+
+ /* Try to read type 3 */
+ status = ti_vread_sync(serial->serial->dev, UMPC_MEMORY_READ,
+ DTK_ADDR_SPACE_I2C_TYPE_III, 0, data, 0x01);
+ if (status)
+ dev_dbg(dev, "%s - read 3 status error = %d\n", __func__, status);
+ else
+ dev_dbg(dev, "%s - read 2 data = 0x%x\n", __func__, *data);
+ if ((!status) && (*data == UMP5152 || *data == UMP3410)) {
+ dev_dbg(dev, "%s - ROM_TYPE_III\n", __func__);
+ serial->TI_I2C_Type = DTK_ADDR_SPACE_I2C_TYPE_III;
+ goto out;
+ }
+
+ dev_dbg(dev, "%s - Unknown\n", __func__);
+ serial->TI_I2C_Type = DTK_ADDR_SPACE_I2C_TYPE_II;
+ status = -ENODEV;
+out:
+ kfree(data);
+ return status;
+}
+
+static int bulk_xfer(struct usb_serial *serial, void *buffer,
+ int length, int *num_sent)
+{
+ int status;
+
+ status = usb_bulk_msg(serial->dev,
+ usb_sndbulkpipe(serial->dev,
+ serial->port[0]->bulk_out_endpointAddress),
+ buffer, length, num_sent, 1000);
+ return status;
+}
+
+/* Download given firmware image to the device (IN BOOT MODE) */
+static int download_code(struct edgeport_serial *serial, u8 *image,
+ int image_length)
+{
+ int status = 0;
+ int pos;
+ int transfer;
+ int done;
+
+ /* Transfer firmware image */
+ for (pos = 0; pos < image_length; ) {
+ /* Read the next buffer from file */
+ transfer = image_length - pos;
+ if (transfer > EDGE_FW_BULK_MAX_PACKET_SIZE)
+ transfer = EDGE_FW_BULK_MAX_PACKET_SIZE;
+
+ /* Transfer data */
+ status = bulk_xfer(serial->serial, &image[pos],
+ transfer, &done);
+ if (status)
+ break;
+ /* Advance buffer pointer */
+ pos += done;
+ }
+
+ return status;
+}
+
+/* FIXME!!! */
+static int config_boot_dev(struct usb_device *dev)
+{
+ return 0;
+}
+
+static int ti_cpu_rev(struct edge_ti_manuf_descriptor *desc)
+{
+ return TI_GET_CPU_REVISION(desc->CpuRev_BoardRev);
+}
+
+static int check_fw_sanity(struct edgeport_serial *serial,
+ const struct firmware *fw)
+{
+ u16 length_total;
+ u8 checksum = 0;
+ int pos;
+ struct device *dev = &serial->serial->interface->dev;
+ struct edgeport_fw_hdr *fw_hdr = (struct edgeport_fw_hdr *)fw->data;
+
+ if (fw->size < sizeof(struct edgeport_fw_hdr)) {
+ dev_err(dev, "incomplete fw header\n");
+ return -EINVAL;
+ }
+
+ length_total = le16_to_cpu(fw_hdr->length) +
+ sizeof(struct edgeport_fw_hdr);
+
+ if (fw->size != length_total) {
+ dev_err(dev, "bad fw size (expected: %u, got: %zu)\n",
+ length_total, fw->size);
+ return -EINVAL;
+ }
+
+ for (pos = sizeof(struct edgeport_fw_hdr); pos < fw->size; ++pos)
+ checksum += fw->data[pos];
+
+ if (checksum != fw_hdr->checksum) {
+ dev_err(dev, "bad fw checksum (expected: 0x%x, got: 0x%x)\n",
+ fw_hdr->checksum, checksum);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * DownloadTIFirmware - Download run-time operating firmware to the TI5052
+ *
+ * This routine downloads the main operating code into the TI5052, using the
+ * boot code already burned into E2PROM or ROM.
+ */
+static int download_fw(struct edgeport_serial *serial)
+{
+ struct device *dev = &serial->serial->interface->dev;
+ int status = 0;
+ struct usb_interface_descriptor *interface;
+ const struct firmware *fw;
+ const char *fw_name = "edgeport/down3.bin";
+ struct edgeport_fw_hdr *fw_hdr;
+
+ status = request_firmware(&fw, fw_name, dev);
+ if (status) {
+ dev_err(dev, "Failed to load image \"%s\" err %d\n",
+ fw_name, status);
+ return status;
+ }
+
+ if (check_fw_sanity(serial, fw)) {
+ status = -EINVAL;
+ goto out;
+ }
+
+ fw_hdr = (struct edgeport_fw_hdr *)fw->data;
+
+ /* If on-board version is newer, "fw_version" will be updated later. */
+ serial->fw_version = (fw_hdr->major_version << 8) +
+ fw_hdr->minor_version;
+
+ /*
+ * This routine is entered by both the BOOT mode and the Download mode
+ * We can determine which code is running by the reading the config
+ * descriptor and if we have only one bulk pipe it is in boot mode
+ */
+ serial->product_info.hardware_type = HARDWARE_TYPE_TIUMP;
+
+ /* Default to type 2 i2c */
+ serial->TI_I2C_Type = DTK_ADDR_SPACE_I2C_TYPE_II;
+
+ status = choose_config(serial->serial->dev);
+ if (status)
+ goto out;
+
+ interface = &serial->serial->interface->cur_altsetting->desc;
+ if (!interface) {
+ dev_err(dev, "%s - no interface set, error!\n", __func__);
+ status = -ENODEV;
+ goto out;
+ }
+
+ /*
+ * Setup initial mode -- the default mode 0 is TI_MODE_CONFIGURING
+ * if we have more than one endpoint we are definitely in download
+ * mode
+ */
+ if (interface->bNumEndpoints > 1) {
+ serial->product_info.TiMode = TI_MODE_DOWNLOAD;
+ status = do_download_mode(serial, fw);
+ } else {
+ /* Otherwise we will remain in configuring mode */
+ serial->product_info.TiMode = TI_MODE_CONFIGURING;
+ status = do_boot_mode(serial, fw);
+ }
+
+out:
+ release_firmware(fw);
+ return status;
+}
+
+static int do_download_mode(struct edgeport_serial *serial,
+ const struct firmware *fw)
+{
+ struct device *dev = &serial->serial->interface->dev;
+ int status = 0;
+ int start_address;
+ struct edge_ti_manuf_descriptor *ti_manuf_desc;
+ int download_cur_ver;
+ int download_new_ver;
+ struct edgeport_fw_hdr *fw_hdr = (struct edgeport_fw_hdr *)fw->data;
+ struct ti_i2c_desc *rom_desc;
+
+ dev_dbg(dev, "%s - RUNNING IN DOWNLOAD MODE\n", __func__);
+
+ status = check_i2c_image(serial);
+ if (status) {
+ dev_dbg(dev, "%s - DOWNLOAD MODE -- BAD I2C\n", __func__);
+ return status;
+ }
+
+ /*
+ * Validate Hardware version number
+ * Read Manufacturing Descriptor from TI Based Edgeport
+ */
+ ti_manuf_desc = kmalloc_obj(*ti_manuf_desc);
+ if (!ti_manuf_desc)
+ return -ENOMEM;
+
+ status = get_manuf_info(serial, (u8 *)ti_manuf_desc);
+ if (status) {
+ kfree(ti_manuf_desc);
+ return status;
+ }
+
+ /* Check version number of ION descriptor */
+ if (!ignore_cpu_rev && ti_cpu_rev(ti_manuf_desc) < 2) {
+ dev_dbg(dev, "%s - Wrong CPU Rev %d (Must be 2)\n",
+ __func__, ti_cpu_rev(ti_manuf_desc));
+ kfree(ti_manuf_desc);
+ return -EINVAL;
+ }
+
+ rom_desc = kmalloc_obj(*rom_desc);
+ if (!rom_desc) {
+ kfree(ti_manuf_desc);
+ return -ENOMEM;
+ }
+
+ /* Search for type 2 record (firmware record) */
+ start_address = get_descriptor_addr(serial,
+ I2C_DESC_TYPE_FIRMWARE_BASIC, rom_desc);
+ if (start_address != 0) {
+ struct ti_i2c_firmware_rec *firmware_version;
+ u8 *record;
+
+ dev_dbg(dev, "%s - Found Type FIRMWARE (Type 2) record\n",
+ __func__);
+
+ firmware_version = kmalloc_obj(*firmware_version);
+ if (!firmware_version) {
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return -ENOMEM;
+ }
+
+ /*
+ * Validate version number
+ * Read the descriptor data
+ */
+ status = read_rom(serial, start_address +
+ sizeof(struct ti_i2c_desc),
+ sizeof(struct ti_i2c_firmware_rec),
+ (u8 *)firmware_version);
+ if (status) {
+ kfree(firmware_version);
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return status;
+ }
+
+ /*
+ * Check version number of download with current
+ * version in I2c
+ */
+ download_cur_ver = (firmware_version->Ver_Major << 8) +
+ (firmware_version->Ver_Minor);
+ download_new_ver = (fw_hdr->major_version << 8) +
+ (fw_hdr->minor_version);
+
+ dev_dbg(dev, "%s - >> FW Versions Device %d.%d Driver %d.%d\n",
+ __func__, firmware_version->Ver_Major,
+ firmware_version->Ver_Minor,
+ fw_hdr->major_version, fw_hdr->minor_version);
+
+ /*
+ * Check if we have an old version in the I2C and
+ * update if necessary
+ */
+ if (download_cur_ver < download_new_ver) {
+ dev_dbg(dev, "%s - Update I2C dld from %d.%d to %d.%d\n",
+ __func__,
+ firmware_version->Ver_Major,
+ firmware_version->Ver_Minor,
+ fw_hdr->major_version,
+ fw_hdr->minor_version);
+
+ record = kmalloc(1, GFP_KERNEL);
+ if (!record) {
+ kfree(firmware_version);
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return -ENOMEM;
+ }
+ /*
+ * In order to update the I2C firmware we must
+ * change the type 2 record to type 0xF2. This
+ * will force the UMP to come up in Boot Mode.
+ * Then while in boot mode, the driver will
+ * download the latest firmware (padded to
+ * 15.5k) into the UMP ram. Finally when the
+ * device comes back up in download mode the
+ * driver will cause the new firmware to be
+ * copied from the UMP Ram to I2C and the
+ * firmware will update the record type from
+ * 0xf2 to 0x02.
+ */
+ *record = I2C_DESC_TYPE_FIRMWARE_BLANK;
+
+ /*
+ * Change the I2C Firmware record type to
+ * 0xf2 to trigger an update
+ */
+ status = write_rom(serial, start_address,
+ sizeof(*record), record);
+ if (status) {
+ kfree(record);
+ kfree(firmware_version);
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return status;
+ }
+
+ /*
+ * verify the write -- must do this in order
+ * for write to complete before we do the
+ * hardware reset
+ */
+ status = read_rom(serial,
+ start_address,
+ sizeof(*record),
+ record);
+ if (status) {
+ kfree(record);
+ kfree(firmware_version);
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return status;
+ }
+
+ if (*record != I2C_DESC_TYPE_FIRMWARE_BLANK) {
+ dev_err(dev, "%s - error resetting device\n",
+ __func__);
+ kfree(record);
+ kfree(firmware_version);
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return -ENODEV;
+ }
+
+ dev_dbg(dev, "%s - HARDWARE RESET\n", __func__);
+
+ /* Reset UMP -- Back to BOOT MODE */
+ status = ti_vsend_sync(serial->serial->dev,
+ UMPC_HARDWARE_RESET,
+ 0, 0, NULL, 0,
+ TI_VSEND_TIMEOUT_DEFAULT);
+
+ dev_dbg(dev, "%s - HARDWARE RESET return %d\n",
+ __func__, status);
+
+ /* return an error on purpose. */
+ kfree(record);
+ kfree(firmware_version);
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return -ENODEV;
+ }
+ /* Same or newer fw version is already loaded */
+ serial->fw_version = download_cur_ver;
+ kfree(firmware_version);
+ }
+ /* Search for type 0xF2 record (firmware blank record) */
+ else {
+ start_address = get_descriptor_addr(serial,
+ I2C_DESC_TYPE_FIRMWARE_BLANK, rom_desc);
+ if (start_address != 0) {
+#define HEADER_SIZE (sizeof(struct ti_i2c_desc) + \
+ sizeof(struct ti_i2c_firmware_rec))
+ u8 *header;
+ u8 *vheader;
+
+ header = kmalloc(HEADER_SIZE, GFP_KERNEL);
+ if (!header) {
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return -ENOMEM;
+ }
+
+ vheader = kmalloc(HEADER_SIZE, GFP_KERNEL);
+ if (!vheader) {
+ kfree(header);
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return -ENOMEM;
+ }
+
+ dev_dbg(dev, "%s - Found Type BLANK FIRMWARE (Type F2) record\n",
+ __func__);
+
+ /*
+ * In order to update the I2C firmware we must change
+ * the type 2 record to type 0xF2. This will force the
+ * UMP to come up in Boot Mode. Then while in boot
+ * mode, the driver will download the latest firmware
+ * (padded to 15.5k) into the UMP ram. Finally when the
+ * device comes back up in download mode the driver
+ * will cause the new firmware to be copied from the
+ * UMP Ram to I2C and the firmware will update the
+ * record type from 0xf2 to 0x02.
+ */
+ status = build_i2c_fw_hdr(header, fw);
+ if (status) {
+ kfree(vheader);
+ kfree(header);
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return -EINVAL;
+ }
+
+ /*
+ * Update I2C with type 0xf2 record with correct
+ * size and checksum
+ */
+ status = write_rom(serial,
+ start_address,
+ HEADER_SIZE,
+ header);
+ if (status) {
+ kfree(vheader);
+ kfree(header);
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return -EINVAL;
+ }
+
+ /*
+ * verify the write -- must do this in order for
+ * write to complete before we do the hardware reset
+ */
+ status = read_rom(serial, start_address,
+ HEADER_SIZE, vheader);
+
+ if (status) {
+ dev_dbg(dev, "%s - can't read header back\n",
+ __func__);
+ kfree(vheader);
+ kfree(header);
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return status;
+ }
+ if (memcmp(vheader, header, HEADER_SIZE)) {
+ dev_dbg(dev, "%s - write download record failed\n",
+ __func__);
+ kfree(vheader);
+ kfree(header);
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return -EINVAL;
+ }
+
+ kfree(vheader);
+ kfree(header);
+
+ dev_dbg(dev, "%s - Start firmware update\n", __func__);
+
+ /* Tell firmware to copy download image into I2C */
+ status = ti_vsend_sync(serial->serial->dev,
+ UMPC_COPY_DNLD_TO_I2C,
+ 0, 0, NULL, 0,
+ TI_VSEND_TIMEOUT_FW_DOWNLOAD);
+
+ dev_dbg(dev, "%s - Update complete 0x%x\n", __func__,
+ status);
+ if (status) {
+ dev_err(dev,
+ "%s - UMPC_COPY_DNLD_TO_I2C failed\n",
+ __func__);
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return status;
+ }
+ }
+ }
+
+ /* The device is running the download code */
+ kfree(rom_desc);
+ kfree(ti_manuf_desc);
+ return 0;
+}
+
+static int do_boot_mode(struct edgeport_serial *serial,
+ const struct firmware *fw)
+{
+ struct device *dev = &serial->serial->interface->dev;
+ int status = 0;
+ struct edge_ti_manuf_descriptor *ti_manuf_desc;
+ struct edgeport_fw_hdr *fw_hdr = (struct edgeport_fw_hdr *)fw->data;
+
+ dev_dbg(dev, "%s - RUNNING IN BOOT MODE\n", __func__);
+
+ /* Configure the TI device so we can use the BULK pipes for download */
+ status = config_boot_dev(serial->serial->dev);
+ if (status)
+ return status;
+
+ if (le16_to_cpu(serial->serial->dev->descriptor.idVendor)
+ != USB_VENDOR_ID_ION) {
+ dev_dbg(dev, "%s - VID = 0x%x\n", __func__,
+ le16_to_cpu(serial->serial->dev->descriptor.idVendor));
+ serial->TI_I2C_Type = DTK_ADDR_SPACE_I2C_TYPE_II;
+ goto stayinbootmode;
+ }
+
+ /*
+ * We have an ION device (I2c Must be programmed)
+ * Determine I2C image type
+ */
+ if (i2c_type_bootmode(serial))
+ goto stayinbootmode;
+
+ /* Check for ION Vendor ID and that the I2C is valid */
+ if (!check_i2c_image(serial)) {
+ struct ti_i2c_image_header *header;
+ int i;
+ u8 cs = 0;
+ u8 *buffer;
+ int buffer_size;
+
+ /*
+ * Validate Hardware version number
+ * Read Manufacturing Descriptor from TI Based Edgeport
+ */
+ ti_manuf_desc = kmalloc_obj(*ti_manuf_desc);
+ if (!ti_manuf_desc)
+ return -ENOMEM;
+
+ status = get_manuf_info(serial, (u8 *)ti_manuf_desc);
+ if (status) {
+ kfree(ti_manuf_desc);
+ goto stayinbootmode;
+ }
+
+ /* Check for version 2 */
+ if (!ignore_cpu_rev && ti_cpu_rev(ti_manuf_desc) < 2) {
+ dev_dbg(dev, "%s - Wrong CPU Rev %d (Must be 2)\n",
+ __func__, ti_cpu_rev(ti_manuf_desc));
+ kfree(ti_manuf_desc);
+ goto stayinbootmode;
+ }
+
+ kfree(ti_manuf_desc);
+
+ /*
+ * In order to update the I2C firmware we must change the type
+ * 2 record to type 0xF2. This will force the UMP to come up
+ * in Boot Mode. Then while in boot mode, the driver will
+ * download the latest firmware (padded to 15.5k) into the
+ * UMP ram. Finally when the device comes back up in download
+ * mode the driver will cause the new firmware to be copied
+ * from the UMP Ram to I2C and the firmware will update the
+ * record type from 0xf2 to 0x02.
+ *
+ * Do we really have to copy the whole firmware image,
+ * or could we do this in place!
+ */
+
+ /* Allocate a 15.5k buffer + 3 byte header */
+ buffer_size = (((1024 * 16) - 512) +
+ sizeof(struct ti_i2c_image_header));
+ buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ /* Initialize the buffer to 0xff (pad the buffer) */
+ memset(buffer, 0xff, buffer_size);
+ memcpy(buffer, &fw->data[4], fw->size - 4);
+
+ for (i = sizeof(struct ti_i2c_image_header);
+ i < buffer_size; i++) {
+ cs = (u8)(cs + buffer[i]);
+ }
+
+ header = (struct ti_i2c_image_header *)buffer;
+
+ /* update length and checksum after padding */
+ header->Length = cpu_to_le16((u16)(buffer_size -
+ sizeof(struct ti_i2c_image_header)));
+ header->CheckSum = cs;
+
+ /* Download the operational code */
+ dev_dbg(dev, "%s - Downloading operational code image version %d.%d (TI UMP)\n",
+ __func__,
+ fw_hdr->major_version, fw_hdr->minor_version);
+ status = download_code(serial, buffer, buffer_size);
+
+ kfree(buffer);
+
+ if (status) {
+ dev_dbg(dev, "%s - Error downloading operational code image\n", __func__);
+ return status;
+ }
+
+ /* Device will reboot */
+ serial->product_info.TiMode = TI_MODE_TRANSITIONING;
+
+ dev_dbg(dev, "%s - Download successful -- Device rebooting...\n", __func__);
+
+ return 1;
+ }
+
+stayinbootmode:
+ /* Eprom is invalid or blank stay in boot mode */
+ dev_dbg(dev, "%s - STAYING IN BOOT MODE\n", __func__);
+ serial->product_info.TiMode = TI_MODE_BOOT;
+
+ return 1;
+}
+
+static int ti_do_config(struct edgeport_port *port, int feature, int on)
+{
+ on = !!on; /* 1 or 0 not bitmask */
+
+ return send_port_cmd(port->port, feature, on, NULL, 0);
+}
+
+static int restore_mcr(struct edgeport_port *port, u8 mcr)
+{
+ int status = 0;
+
+ dev_dbg(&port->port->dev, "%s - %x\n", __func__, mcr);
+
+ status = ti_do_config(port, UMPC_SET_CLR_DTR, mcr & MCR_DTR);
+ if (status)
+ return status;
+ status = ti_do_config(port, UMPC_SET_CLR_RTS, mcr & MCR_RTS);
+ if (status)
+ return status;
+ return ti_do_config(port, UMPC_SET_CLR_LOOPBACK, mcr & MCR_LOOPBACK);
+}
+
+/* Convert TI LSR to standard UART flags */
+static u8 map_line_status(u8 ti_lsr)
+{
+ u8 lsr = 0;
+
+#define MAP_FLAG(flagUmp, flagUart) \
+ if (ti_lsr & flagUmp) \
+ lsr |= flagUart;
+
+ MAP_FLAG(UMP_UART_LSR_OV_MASK, LSR_OVER_ERR) /* overrun */
+ MAP_FLAG(UMP_UART_LSR_PE_MASK, LSR_PAR_ERR) /* parity error */
+ MAP_FLAG(UMP_UART_LSR_FE_MASK, LSR_FRM_ERR) /* framing error */
+ MAP_FLAG(UMP_UART_LSR_BR_MASK, LSR_BREAK) /* break detected */
+ MAP_FLAG(UMP_UART_LSR_RX_MASK, LSR_RX_AVAIL) /* rx data available */
+ MAP_FLAG(UMP_UART_LSR_TX_MASK, LSR_TX_EMPTY) /* tx hold reg empty */
+
+#undef MAP_FLAG
+
+ return lsr;
+}
+
+static void handle_new_msr(struct edgeport_port *edge_port, u8 msr)
+{
+ struct async_icount *icount;
+ struct tty_struct *tty;
+
+ dev_dbg(&edge_port->port->dev, "%s - %02x\n", __func__, msr);
+
+ if (msr & (EDGEPORT_MSR_DELTA_CTS | EDGEPORT_MSR_DELTA_DSR |
+ EDGEPORT_MSR_DELTA_RI | EDGEPORT_MSR_DELTA_CD)) {
+ icount = &edge_port->port->icount;
+
+ /* update input line counters */
+ if (msr & EDGEPORT_MSR_DELTA_CTS)
+ icount->cts++;
+ if (msr & EDGEPORT_MSR_DELTA_DSR)
+ icount->dsr++;
+ if (msr & EDGEPORT_MSR_DELTA_CD)
+ icount->dcd++;
+ if (msr & EDGEPORT_MSR_DELTA_RI)
+ icount->rng++;
+ wake_up_interruptible(&edge_port->port->port.delta_msr_wait);
+ }
+
+ /* Save the new modem status */
+ edge_port->shadow_msr = msr & 0xf0;
+
+ tty = tty_port_tty_get(&edge_port->port->port);
+ /* handle CTS flow control */
+ if (tty && C_CRTSCTS(tty)) {
+ if (msr & EDGEPORT_MSR_CTS)
+ tty_wakeup(tty);
+ }
+ tty_kref_put(tty);
+}
+
+static void handle_new_lsr(struct edgeport_port *edge_port, int lsr_data,
+ u8 lsr, u8 data)
+{
+ struct async_icount *icount;
+ u8 new_lsr = (u8)(lsr & (u8)(LSR_OVER_ERR | LSR_PAR_ERR |
+ LSR_FRM_ERR | LSR_BREAK));
+
+ dev_dbg(&edge_port->port->dev, "%s - %02x\n", __func__, new_lsr);
+
+ edge_port->shadow_lsr = lsr;
+
+ if (new_lsr & LSR_BREAK)
+ /*
+ * Parity and Framing errors only count if they
+ * occur exclusive of a break being received.
+ */
+ new_lsr &= (u8)(LSR_OVER_ERR | LSR_BREAK);
+
+ /* Place LSR data byte into Rx buffer */
+ if (lsr_data)
+ edge_tty_recv(edge_port->port, &data, 1);
+
+ /* update input line counters */
+ icount = &edge_port->port->icount;
+ if (new_lsr & LSR_BREAK)
+ icount->brk++;
+ if (new_lsr & LSR_OVER_ERR)
+ icount->overrun++;
+ if (new_lsr & LSR_PAR_ERR)
+ icount->parity++;
+ if (new_lsr & LSR_FRM_ERR)
+ icount->frame++;
+}
+
+static void edge_interrupt_callback(struct urb *urb)
+{
+ struct edgeport_serial *edge_serial = urb->context;
+ struct usb_serial_port *port;
+ struct edgeport_port *edge_port;
+ struct device *dev;
+ unsigned char *data = urb->transfer_buffer;
+ int length = urb->actual_length;
+ int port_number;
+ int function;
+ int retval;
+ u8 lsr;
+ u8 msr;
+ int status = urb->status;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_err(&urb->dev->dev, "%s - nonzero urb status received: "
+ "%d\n", __func__, status);
+ goto exit;
+ }
+
+ if (!length) {
+ dev_dbg(&urb->dev->dev, "%s - no data in urb\n", __func__);
+ goto exit;
+ }
+
+ dev = &edge_serial->serial->dev->dev;
+ usb_serial_debug_data(dev, __func__, length, data);
+
+ if (length != 2) {
+ dev_dbg(dev, "%s - expecting packet of size 2, got %d\n", __func__, length);
+ goto exit;
+ }
+
+ port_number = TIUMP_GET_PORT_FROM_CODE(data[0]);
+ function = TIUMP_GET_FUNC_FROM_CODE(data[0]);
+ dev_dbg(dev, "%s - port_number %d, function %d, info 0x%x\n", __func__,
+ port_number, function, data[1]);
+
+ if (port_number >= edge_serial->serial->num_ports) {
+ dev_err(dev, "bad port number %d\n", port_number);
+ goto exit;
+ }
+
+ port = edge_serial->serial->port[port_number];
+ edge_port = usb_get_serial_port_data(port);
+ if (!edge_port) {
+ dev_dbg(dev, "%s - edge_port not found\n", __func__);
+ return;
+ }
+ switch (function) {
+ case TIUMP_INTERRUPT_CODE_LSR:
+ lsr = map_line_status(data[1]);
+ if (lsr & UMP_UART_LSR_DATA_MASK) {
+ /*
+ * Save the LSR event for bulk read completion routine
+ */
+ dev_dbg(dev, "%s - LSR Event Port %u LSR Status = %02x\n",
+ __func__, port_number, lsr);
+ edge_port->lsr_event = 1;
+ edge_port->lsr_mask = lsr;
+ } else {
+ dev_dbg(dev, "%s - ===== Port %d LSR Status = %02x ======\n",
+ __func__, port_number, lsr);
+ handle_new_lsr(edge_port, 0, lsr, 0);
+ }
+ break;
+
+ case TIUMP_INTERRUPT_CODE_MSR: /* MSR */
+ /* Copy MSR from UMP */
+ msr = data[1];
+ dev_dbg(dev, "%s - ===== Port %u MSR Status = %02x ======\n",
+ __func__, port_number, msr);
+ handle_new_msr(edge_port, msr);
+ break;
+
+ default:
+ dev_err(&urb->dev->dev,
+ "%s - Unknown Interrupt code from UMP %x\n",
+ __func__, data[1]);
+ break;
+
+ }
+
+exit:
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(&urb->dev->dev,
+ "%s - usb_submit_urb failed with result %d\n",
+ __func__, retval);
+}
+
+static void edge_bulk_in_callback(struct urb *urb)
+{
+ struct edgeport_port *edge_port = urb->context;
+ struct device *dev = &edge_port->port->dev;
+ unsigned char *data = urb->transfer_buffer;
+ unsigned long flags;
+ int retval = 0;
+ int port_number;
+ int status = urb->status;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n", __func__, status);
+ return;
+ default:
+ dev_err(&urb->dev->dev, "%s - nonzero read bulk status received: %d\n", __func__, status);
+ }
+
+ if (status == -EPIPE)
+ goto exit;
+
+ if (status) {
+ dev_err(&urb->dev->dev, "%s - stopping read!\n", __func__);
+ return;
+ }
+
+ port_number = edge_port->port->port_number;
+
+ if (urb->actual_length > 0 && edge_port->lsr_event) {
+ edge_port->lsr_event = 0;
+ dev_dbg(dev, "%s ===== Port %u LSR Status = %02x, Data = %02x ======\n",
+ __func__, port_number, edge_port->lsr_mask, *data);
+ handle_new_lsr(edge_port, 1, edge_port->lsr_mask, *data);
+ /* Adjust buffer length/pointer */
+ --urb->actual_length;
+ ++data;
+ }
+
+ if (urb->actual_length) {
+ usb_serial_debug_data(dev, __func__, urb->actual_length, data);
+ if (edge_port->close_pending)
+ dev_dbg(dev, "%s - close pending, dropping data on the floor\n",
+ __func__);
+ else
+ edge_tty_recv(edge_port->port, data,
+ urb->actual_length);
+ edge_port->port->icount.rx += urb->actual_length;
+ }
+
+exit:
+ /* continue read unless stopped */
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+ if (edge_port->ep_read_urb_state == EDGE_READ_URB_RUNNING)
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ else if (edge_port->ep_read_urb_state == EDGE_READ_URB_STOPPING)
+ edge_port->ep_read_urb_state = EDGE_READ_URB_STOPPED;
+
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+ if (retval)
+ dev_err(dev, "%s - usb_submit_urb failed with result %d\n", __func__, retval);
+}
+
+static void edge_tty_recv(struct usb_serial_port *port, unsigned char *data,
+ int length)
+{
+ int queued;
+
+ queued = tty_insert_flip_string(&port->port, data, length);
+ if (queued < length)
+ dev_err(&port->dev, "%s - dropping data, %d bytes lost\n",
+ __func__, length - queued);
+ tty_flip_buffer_push(&port->port);
+}
+
+static void edge_bulk_out_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ int status = urb->status;
+ struct tty_struct *tty;
+
+ edge_port->ep_write_urb_in_use = 0;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_err_console(port, "%s - nonzero write bulk status "
+ "received: %d\n", __func__, status);
+ }
+
+ /* send any buffered data */
+ tty = tty_port_tty_get(&port->port);
+ edge_send(port, tty);
+ tty_kref_put(tty);
+}
+
+static int edge_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ struct edgeport_serial *edge_serial;
+ struct usb_device *dev;
+ struct urb *urb;
+ int status;
+ u16 open_settings;
+ u8 transaction_timeout;
+
+ if (edge_port == NULL)
+ return -ENODEV;
+
+ dev = port->serial->dev;
+
+ /* turn off loopback */
+ status = ti_do_config(edge_port, UMPC_SET_CLR_LOOPBACK, 0);
+ if (status) {
+ dev_err(&port->dev,
+ "%s - cannot send clear loopback command, %d\n",
+ __func__, status);
+ return status;
+ }
+
+ /* set up the port settings */
+ if (tty)
+ edge_set_termios(tty, port, &tty->termios);
+
+ /* open up the port */
+
+ /* milliseconds to timeout for DMA transfer */
+ transaction_timeout = 2;
+
+ edge_port->ump_read_timeout =
+ max(20, ((transaction_timeout * 3) / 2));
+
+ /* milliseconds to timeout for DMA transfer */
+ open_settings = (u8)(UMP_DMA_MODE_CONTINOUS |
+ UMP_PIPE_TRANS_TIMEOUT_ENA |
+ (transaction_timeout << 2));
+
+ dev_dbg(&port->dev, "%s - Sending UMPC_OPEN_PORT\n", __func__);
+
+ /* Tell TI to open and start the port */
+ status = send_port_cmd(port, UMPC_OPEN_PORT, open_settings, NULL, 0);
+ if (status) {
+ dev_err(&port->dev, "%s - cannot send open command, %d\n",
+ __func__, status);
+ return status;
+ }
+
+ /* Start the DMA? */
+ status = send_port_cmd(port, UMPC_START_PORT, 0, NULL, 0);
+ if (status) {
+ dev_err(&port->dev, "%s - cannot send start DMA command, %d\n",
+ __func__, status);
+ return status;
+ }
+
+ /* Clear TX and RX buffers in UMP */
+ status = purge_port(port, UMP_PORT_DIR_OUT | UMP_PORT_DIR_IN);
+ if (status) {
+ dev_err(&port->dev,
+ "%s - cannot send clear buffers command, %d\n",
+ __func__, status);
+ return status;
+ }
+
+ /* Read Initial MSR */
+ status = read_port_cmd(port, UMPC_READ_MSR, 0, &edge_port->shadow_msr, 1);
+ if (status) {
+ dev_err(&port->dev, "%s - cannot send read MSR command, %d\n",
+ __func__, status);
+ return status;
+ }
+
+ dev_dbg(&port->dev, "ShadowMSR 0x%X\n", edge_port->shadow_msr);
+
+ /* Set Initial MCR */
+ edge_port->shadow_mcr = MCR_RTS | MCR_DTR;
+ dev_dbg(&port->dev, "ShadowMCR 0x%X\n", edge_port->shadow_mcr);
+
+ edge_serial = edge_port->edge_serial;
+ if (mutex_lock_interruptible(&edge_serial->es_lock))
+ return -ERESTARTSYS;
+ if (edge_serial->num_ports_open == 0) {
+ /* we are the first port to open, post the interrupt urb */
+ urb = edge_serial->serial->port[0]->interrupt_in_urb;
+ urb->context = edge_serial;
+ status = usb_submit_urb(urb, GFP_KERNEL);
+ if (status) {
+ dev_err(&port->dev,
+ "%s - usb_submit_urb failed with value %d\n",
+ __func__, status);
+ goto release_es_lock;
+ }
+ }
+
+ /*
+ * reset the data toggle on the bulk endpoints to work around bug in
+ * host controllers where things get out of sync some times
+ */
+ usb_clear_halt(dev, port->write_urb->pipe);
+ usb_clear_halt(dev, port->read_urb->pipe);
+
+ /* start up our bulk read urb */
+ urb = port->read_urb;
+ edge_port->ep_read_urb_state = EDGE_READ_URB_RUNNING;
+ urb->context = edge_port;
+ status = usb_submit_urb(urb, GFP_KERNEL);
+ if (status) {
+ dev_err(&port->dev,
+ "%s - read bulk usb_submit_urb failed with value %d\n",
+ __func__, status);
+ goto unlink_int_urb;
+ }
+
+ ++edge_serial->num_ports_open;
+
+ goto release_es_lock;
+
+unlink_int_urb:
+ if (edge_port->edge_serial->num_ports_open == 0)
+ usb_kill_urb(port->serial->port[0]->interrupt_in_urb);
+release_es_lock:
+ mutex_unlock(&edge_serial->es_lock);
+ return status;
+}
+
+static void edge_close(struct usb_serial_port *port)
+{
+ struct edgeport_serial *edge_serial;
+ struct edgeport_port *edge_port;
+ unsigned long flags;
+
+ edge_serial = usb_get_serial_data(port->serial);
+ edge_port = usb_get_serial_port_data(port);
+ if (edge_serial == NULL || edge_port == NULL)
+ return;
+
+ /*
+ * The bulkreadcompletion routine will check
+ * this flag and dump add read data
+ */
+ edge_port->close_pending = 1;
+
+ usb_kill_urb(port->read_urb);
+ usb_kill_urb(port->write_urb);
+ edge_port->ep_write_urb_in_use = 0;
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+ kfifo_reset_out(&port->write_fifo);
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+
+ dev_dbg(&port->dev, "%s - send umpc_close_port\n", __func__);
+ send_port_cmd(port, UMPC_CLOSE_PORT, 0, NULL, 0);
+
+ mutex_lock(&edge_serial->es_lock);
+ --edge_port->edge_serial->num_ports_open;
+ if (edge_port->edge_serial->num_ports_open <= 0) {
+ /* last port is now closed, let's shut down our interrupt urb */
+ usb_kill_urb(port->serial->port[0]->interrupt_in_urb);
+ edge_port->edge_serial->num_ports_open = 0;
+ }
+ mutex_unlock(&edge_serial->es_lock);
+ edge_port->close_pending = 0;
+}
+
+static int edge_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *data, int count)
+{
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+
+ if (count == 0) {
+ dev_dbg(&port->dev, "%s - write request of 0 bytes\n", __func__);
+ return 0;
+ }
+
+ if (edge_port == NULL)
+ return -ENODEV;
+ if (edge_port->close_pending == 1)
+ return -ENODEV;
+
+ count = kfifo_in_locked(&port->write_fifo, data, count,
+ &edge_port->ep_lock);
+ edge_send(port, tty);
+
+ return count;
+}
+
+static void edge_send(struct usb_serial_port *port, struct tty_struct *tty)
+{
+ int count, result;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+
+ if (edge_port->ep_write_urb_in_use) {
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+ return;
+ }
+
+ count = kfifo_out(&port->write_fifo,
+ port->write_urb->transfer_buffer,
+ port->bulk_out_size);
+
+ if (count == 0) {
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+ return;
+ }
+
+ edge_port->ep_write_urb_in_use = 1;
+
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+
+ usb_serial_debug_data(&port->dev, __func__, count, port->write_urb->transfer_buffer);
+
+ /* set up our urb */
+ port->write_urb->transfer_buffer_length = count;
+
+ /* send the data out the bulk port */
+ result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ if (result) {
+ dev_err_console(port,
+ "%s - failed submitting write urb, error %d\n",
+ __func__, result);
+ edge_port->ep_write_urb_in_use = 0;
+ /* TODO: reschedule edge_send */
+ } else
+ edge_port->port->icount.tx += count;
+
+ /*
+ * wakeup any process waiting for writes to complete
+ * there is now more room in the buffer for new writes
+ */
+ if (tty)
+ tty_wakeup(tty);
+}
+
+static unsigned int edge_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ unsigned int room;
+ unsigned long flags;
+
+ if (edge_port == NULL)
+ return 0;
+ if (edge_port->close_pending == 1)
+ return 0;
+
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+ room = kfifo_avail(&port->write_fifo);
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, room);
+ return room;
+}
+
+static unsigned int edge_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ unsigned int chars;
+ unsigned long flags;
+ if (edge_port == NULL)
+ return 0;
+
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+ chars = kfifo_len(&port->write_fifo);
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, chars);
+ return chars;
+}
+
+static bool edge_tx_empty(struct usb_serial_port *port)
+{
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ int ret;
+
+ ret = tx_active(edge_port);
+ if (ret > 0)
+ return false;
+
+ return true;
+}
+
+static void edge_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ int status;
+
+ if (edge_port == NULL)
+ return;
+
+ /* if we are implementing XON/XOFF, send the stop character */
+ if (I_IXOFF(tty)) {
+ unsigned char stop_char = STOP_CHAR(tty);
+ status = edge_write(tty, port, &stop_char, 1);
+ if (status <= 0) {
+ dev_err(&port->dev, "%s - failed to write stop character, %d\n", __func__, status);
+ }
+ }
+
+ /*
+ * if we are implementing RTS/CTS, stop reads
+ * and the Edgeport will clear the RTS line
+ */
+ if (C_CRTSCTS(tty))
+ stop_read(edge_port);
+
+}
+
+static void edge_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ int status;
+
+ if (edge_port == NULL)
+ return;
+
+ /* if we are implementing XON/XOFF, send the start character */
+ if (I_IXOFF(tty)) {
+ unsigned char start_char = START_CHAR(tty);
+ status = edge_write(tty, port, &start_char, 1);
+ if (status <= 0) {
+ dev_err(&port->dev, "%s - failed to write start character, %d\n", __func__, status);
+ }
+ }
+ /*
+ * if we are implementing RTS/CTS, restart reads
+ * are the Edgeport will assert the RTS line
+ */
+ if (C_CRTSCTS(tty)) {
+ status = restart_read(edge_port);
+ if (status)
+ dev_err(&port->dev,
+ "%s - read bulk usb_submit_urb failed: %d\n",
+ __func__, status);
+ }
+
+}
+
+static void stop_read(struct edgeport_port *edge_port)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+
+ if (edge_port->ep_read_urb_state == EDGE_READ_URB_RUNNING)
+ edge_port->ep_read_urb_state = EDGE_READ_URB_STOPPING;
+ edge_port->shadow_mcr &= ~MCR_RTS;
+
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+}
+
+static int restart_read(struct edgeport_port *edge_port)
+{
+ struct urb *urb;
+ int status = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+
+ if (edge_port->ep_read_urb_state == EDGE_READ_URB_STOPPED) {
+ urb = edge_port->port->read_urb;
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ }
+ edge_port->ep_read_urb_state = EDGE_READ_URB_RUNNING;
+ edge_port->shadow_mcr |= MCR_RTS;
+
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+
+ return status;
+}
+
+static void change_port_settings(struct tty_struct *tty,
+ struct edgeport_port *edge_port, const struct ktermios *old_termios)
+{
+ struct device *dev = &edge_port->port->dev;
+ struct ump_uart_config *config;
+ int baud;
+ unsigned cflag;
+ int status;
+
+ config = kmalloc_obj(*config);
+ if (!config) {
+ tty->termios = *old_termios;
+ return;
+ }
+
+ cflag = tty->termios.c_cflag;
+
+ config->wFlags = 0;
+
+ /* These flags must be set */
+ config->wFlags |= UMP_MASK_UART_FLAGS_RECEIVE_MS_INT;
+ config->wFlags |= UMP_MASK_UART_FLAGS_AUTO_START_ON_ERR;
+ config->bUartMode = (u8)(edge_port->bUartMode);
+
+ switch (cflag & CSIZE) {
+ case CS5:
+ config->bDataBits = UMP_UART_CHAR5BITS;
+ dev_dbg(dev, "%s - data bits = 5\n", __func__);
+ break;
+ case CS6:
+ config->bDataBits = UMP_UART_CHAR6BITS;
+ dev_dbg(dev, "%s - data bits = 6\n", __func__);
+ break;
+ case CS7:
+ config->bDataBits = UMP_UART_CHAR7BITS;
+ dev_dbg(dev, "%s - data bits = 7\n", __func__);
+ break;
+ default:
+ case CS8:
+ config->bDataBits = UMP_UART_CHAR8BITS;
+ dev_dbg(dev, "%s - data bits = 8\n", __func__);
+ break;
+ }
+
+ if (cflag & PARENB) {
+ if (cflag & PARODD) {
+ config->wFlags |= UMP_MASK_UART_FLAGS_PARITY;
+ config->bParity = UMP_UART_ODDPARITY;
+ dev_dbg(dev, "%s - parity = odd\n", __func__);
+ } else {
+ config->wFlags |= UMP_MASK_UART_FLAGS_PARITY;
+ config->bParity = UMP_UART_EVENPARITY;
+ dev_dbg(dev, "%s - parity = even\n", __func__);
+ }
+ } else {
+ config->bParity = UMP_UART_NOPARITY;
+ dev_dbg(dev, "%s - parity = none\n", __func__);
+ }
+
+ if (cflag & CSTOPB) {
+ config->bStopBits = UMP_UART_STOPBIT2;
+ dev_dbg(dev, "%s - stop bits = 2\n", __func__);
+ } else {
+ config->bStopBits = UMP_UART_STOPBIT1;
+ dev_dbg(dev, "%s - stop bits = 1\n", __func__);
+ }
+
+ /* figure out the flow control settings */
+ if (cflag & CRTSCTS) {
+ config->wFlags |= UMP_MASK_UART_FLAGS_OUT_X_CTS_FLOW;
+ config->wFlags |= UMP_MASK_UART_FLAGS_RTS_FLOW;
+ dev_dbg(dev, "%s - RTS/CTS is enabled\n", __func__);
+ } else {
+ dev_dbg(dev, "%s - RTS/CTS is disabled\n", __func__);
+ restart_read(edge_port);
+ }
+
+ /*
+ * if we are implementing XON/XOFF, set the start and stop
+ * character in the device
+ */
+ config->cXon = START_CHAR(tty);
+ config->cXoff = STOP_CHAR(tty);
+
+ /* if we are implementing INBOUND XON/XOFF */
+ if (I_IXOFF(tty)) {
+ config->wFlags |= UMP_MASK_UART_FLAGS_IN_X;
+ dev_dbg(dev, "%s - INBOUND XON/XOFF is enabled, XON = %2x, XOFF = %2x\n",
+ __func__, config->cXon, config->cXoff);
+ } else
+ dev_dbg(dev, "%s - INBOUND XON/XOFF is disabled\n", __func__);
+
+ /* if we are implementing OUTBOUND XON/XOFF */
+ if (I_IXON(tty)) {
+ config->wFlags |= UMP_MASK_UART_FLAGS_OUT_X;
+ dev_dbg(dev, "%s - OUTBOUND XON/XOFF is enabled, XON = %2x, XOFF = %2x\n",
+ __func__, config->cXon, config->cXoff);
+ } else
+ dev_dbg(dev, "%s - OUTBOUND XON/XOFF is disabled\n", __func__);
+
+ tty->termios.c_cflag &= ~CMSPAR;
+
+ /* Round the baud rate */
+ baud = tty_get_baud_rate(tty);
+ if (!baud) {
+ /* pick a default, any default... */
+ baud = 9600;
+ } else {
+ /* Avoid a zero divisor. */
+ baud = min(baud, 461550);
+ tty_encode_baud_rate(tty, baud, baud);
+ }
+
+ edge_port->baud_rate = baud;
+ config->wBaudRate = (u16)((461550L + baud/2) / baud);
+
+ /* FIXME: Recompute actual baud from divisor here */
+
+ dev_dbg(dev, "%s - baud rate = %d, wBaudRate = %d\n", __func__, baud, config->wBaudRate);
+
+ dev_dbg(dev, "wBaudRate: %d\n", (int)(461550L / config->wBaudRate));
+ dev_dbg(dev, "wFlags: 0x%x\n", config->wFlags);
+ dev_dbg(dev, "bDataBits: %d\n", config->bDataBits);
+ dev_dbg(dev, "bParity: %d\n", config->bParity);
+ dev_dbg(dev, "bStopBits: %d\n", config->bStopBits);
+ dev_dbg(dev, "cXon: %d\n", config->cXon);
+ dev_dbg(dev, "cXoff: %d\n", config->cXoff);
+ dev_dbg(dev, "bUartMode: %d\n", config->bUartMode);
+
+ /* move the word values into big endian mode */
+ cpu_to_be16s(&config->wFlags);
+ cpu_to_be16s(&config->wBaudRate);
+
+ status = send_port_cmd(edge_port->port, UMPC_SET_CONFIG, 0, config,
+ sizeof(*config));
+ if (status)
+ dev_dbg(dev, "%s - error %d when trying to write config to device\n",
+ __func__, status);
+ kfree(config);
+}
+
+static void edge_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+
+ if (edge_port == NULL)
+ return;
+ /* change the port settings to the new ones specified */
+ change_port_settings(tty, edge_port, old_termios);
+}
+
+static int edge_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ unsigned int mcr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+ mcr = edge_port->shadow_mcr;
+ if (set & TIOCM_RTS)
+ mcr |= MCR_RTS;
+ if (set & TIOCM_DTR)
+ mcr |= MCR_DTR;
+ if (set & TIOCM_LOOP)
+ mcr |= MCR_LOOPBACK;
+
+ if (clear & TIOCM_RTS)
+ mcr &= ~MCR_RTS;
+ if (clear & TIOCM_DTR)
+ mcr &= ~MCR_DTR;
+ if (clear & TIOCM_LOOP)
+ mcr &= ~MCR_LOOPBACK;
+
+ edge_port->shadow_mcr = mcr;
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+
+ restore_mcr(edge_port, mcr);
+ return 0;
+}
+
+static int edge_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ unsigned int result = 0;
+ unsigned int msr;
+ unsigned int mcr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&edge_port->ep_lock, flags);
+
+ msr = edge_port->shadow_msr;
+ mcr = edge_port->shadow_mcr;
+ result = ((mcr & MCR_DTR) ? TIOCM_DTR: 0) /* 0x002 */
+ | ((mcr & MCR_RTS) ? TIOCM_RTS: 0) /* 0x004 */
+ | ((msr & EDGEPORT_MSR_CTS) ? TIOCM_CTS: 0) /* 0x020 */
+ | ((msr & EDGEPORT_MSR_CD) ? TIOCM_CAR: 0) /* 0x040 */
+ | ((msr & EDGEPORT_MSR_RI) ? TIOCM_RI: 0) /* 0x080 */
+ | ((msr & EDGEPORT_MSR_DSR) ? TIOCM_DSR: 0); /* 0x100 */
+
+
+ dev_dbg(&port->dev, "%s -- %x\n", __func__, result);
+ spin_unlock_irqrestore(&edge_port->ep_lock, flags);
+
+ return result;
+}
+
+static int edge_break(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ int status;
+ int bv = 0; /* Off */
+
+ if (break_state == -1)
+ bv = 1; /* On */
+
+ status = ti_do_config(edge_port, UMPC_SET_CLR_BREAK, bv);
+ if (status) {
+ dev_dbg(&port->dev, "%s - error %d sending break set/clear command.\n",
+ __func__, status);
+ return status;
+ }
+
+ return 0;
+}
+
+static void edge_heartbeat_schedule(struct edgeport_serial *edge_serial)
+{
+ if (!edge_serial->use_heartbeat)
+ return;
+
+ schedule_delayed_work(&edge_serial->heartbeat_work,
+ FW_HEARTBEAT_SECS * HZ);
+}
+
+static void edge_heartbeat_work(struct work_struct *work)
+{
+ struct edgeport_serial *serial;
+ struct ti_i2c_desc *rom_desc;
+
+ serial = container_of(work, struct edgeport_serial,
+ heartbeat_work.work);
+
+ rom_desc = kmalloc_obj(*rom_desc);
+
+ /* Descriptor address request is enough to reset the firmware timer */
+ if (!rom_desc || !get_descriptor_addr(serial, I2C_DESC_TYPE_ION,
+ rom_desc)) {
+ dev_err(&serial->serial->interface->dev,
+ "%s - Incomplete heartbeat\n", __func__);
+ }
+ kfree(rom_desc);
+
+ edge_heartbeat_schedule(serial);
+}
+
+static int edge_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ struct device *dev = &serial->interface->dev;
+ unsigned char num_ports = serial->type->num_ports;
+
+ /* Make sure we have the required endpoints when in download mode. */
+ if (serial->interface->cur_altsetting->desc.bNumEndpoints > 1) {
+ if (epds->num_bulk_in < num_ports ||
+ epds->num_bulk_out < num_ports ||
+ epds->num_interrupt_in < 1) {
+ dev_err(dev, "required endpoints missing\n");
+ return -ENODEV;
+ }
+ }
+
+ return num_ports;
+}
+
+static int edge_startup(struct usb_serial *serial)
+{
+ struct edgeport_serial *edge_serial;
+ int status;
+ u16 product_id;
+
+ /* create our private serial structure */
+ edge_serial = kzalloc_obj(struct edgeport_serial);
+ if (!edge_serial)
+ return -ENOMEM;
+
+ mutex_init(&edge_serial->es_lock);
+ edge_serial->serial = serial;
+ INIT_DELAYED_WORK(&edge_serial->heartbeat_work, edge_heartbeat_work);
+ usb_set_serial_data(serial, edge_serial);
+
+ status = download_fw(edge_serial);
+ if (status < 0) {
+ kfree(edge_serial);
+ return status;
+ }
+
+ if (status > 0)
+ return 1; /* bind but do not register any ports */
+
+ product_id = le16_to_cpu(
+ edge_serial->serial->dev->descriptor.idProduct);
+
+ /* Currently only the EP/416 models require heartbeat support */
+ if (edge_serial->fw_version > FW_HEARTBEAT_VERSION_CUTOFF) {
+ if (product_id == ION_DEVICE_ID_TI_EDGEPORT_416 ||
+ product_id == ION_DEVICE_ID_TI_EDGEPORT_416B) {
+ edge_serial->use_heartbeat = true;
+ }
+ }
+
+ edge_heartbeat_schedule(edge_serial);
+
+ return 0;
+}
+
+static void edge_disconnect(struct usb_serial *serial)
+{
+ struct edgeport_serial *edge_serial = usb_get_serial_data(serial);
+
+ cancel_delayed_work_sync(&edge_serial->heartbeat_work);
+}
+
+static void edge_release(struct usb_serial *serial)
+{
+ struct edgeport_serial *edge_serial = usb_get_serial_data(serial);
+
+ cancel_delayed_work_sync(&edge_serial->heartbeat_work);
+ kfree(edge_serial);
+}
+
+static int edge_port_probe(struct usb_serial_port *port)
+{
+ struct edgeport_port *edge_port;
+ int ret;
+
+ edge_port = kzalloc_obj(*edge_port);
+ if (!edge_port)
+ return -ENOMEM;
+
+ spin_lock_init(&edge_port->ep_lock);
+ edge_port->port = port;
+ edge_port->edge_serial = usb_get_serial_data(port->serial);
+ edge_port->bUartMode = default_uart_mode;
+
+ switch (port->port_number) {
+ case 0:
+ edge_port->uart_base = UMPMEM_BASE_UART1;
+ edge_port->dma_address = UMPD_OEDB1_ADDRESS;
+ break;
+ case 1:
+ edge_port->uart_base = UMPMEM_BASE_UART2;
+ edge_port->dma_address = UMPD_OEDB2_ADDRESS;
+ break;
+ default:
+ dev_err(&port->dev, "unknown port number\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ dev_dbg(&port->dev,
+ "%s - port_number = %d, uart_base = %04x, dma_address = %04x\n",
+ __func__, port->port_number, edge_port->uart_base,
+ edge_port->dma_address);
+
+ usb_set_serial_port_data(port, edge_port);
+
+ ret = edge_create_sysfs_attrs(port);
+ if (ret)
+ goto err;
+
+ /*
+ * The LSR does not tell when the transmitter shift register has
+ * emptied so add a one-character drain delay.
+ */
+ port->port.drain_delay = 1;
+
+ return 0;
+err:
+ kfree(edge_port);
+
+ return ret;
+}
+
+static void edge_port_remove(struct usb_serial_port *port)
+{
+ struct edgeport_port *edge_port;
+
+ edge_port = usb_get_serial_port_data(port);
+ edge_remove_sysfs_attrs(port);
+ kfree(edge_port);
+}
+
+/* Sysfs Attributes */
+
+static ssize_t uart_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_serial_port *port = to_usb_serial_port(dev);
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+
+ return sprintf(buf, "%d\n", edge_port->bUartMode);
+}
+
+static ssize_t uart_mode_store(struct device *dev,
+ struct device_attribute *attr, const char *valbuf, size_t count)
+{
+ struct usb_serial_port *port = to_usb_serial_port(dev);
+ struct edgeport_port *edge_port = usb_get_serial_port_data(port);
+ unsigned int v = simple_strtoul(valbuf, NULL, 0);
+
+ dev_dbg(dev, "%s: setting uart_mode = %d\n", __func__, v);
+
+ if (v < 256)
+ edge_port->bUartMode = v;
+ else
+ dev_err(dev, "%s - uart_mode %d is invalid\n", __func__, v);
+
+ return count;
+}
+static DEVICE_ATTR_RW(uart_mode);
+
+static int edge_create_sysfs_attrs(struct usb_serial_port *port)
+{
+ return device_create_file(&port->dev, &dev_attr_uart_mode);
+}
+
+static int edge_remove_sysfs_attrs(struct usb_serial_port *port)
+{
+ device_remove_file(&port->dev, &dev_attr_uart_mode);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int edge_suspend(struct usb_serial *serial, pm_message_t message)
+{
+ struct edgeport_serial *edge_serial = usb_get_serial_data(serial);
+
+ cancel_delayed_work_sync(&edge_serial->heartbeat_work);
+
+ return 0;
+}
+
+static int edge_resume(struct usb_serial *serial)
+{
+ struct edgeport_serial *edge_serial = usb_get_serial_data(serial);
+
+ edge_heartbeat_schedule(edge_serial);
+
+ return 0;
+}
+#endif
+
+static struct usb_serial_driver edgeport_1port_device = {
+ .driver = {
+ .name = "edgeport_ti_1",
+ },
+ .description = "Edgeport TI 1 port adapter",
+ .id_table = edgeport_1port_id_table,
+ .num_ports = 1,
+ .num_bulk_out = 1,
+ .open = edge_open,
+ .close = edge_close,
+ .throttle = edge_throttle,
+ .unthrottle = edge_unthrottle,
+ .attach = edge_startup,
+ .calc_num_ports = edge_calc_num_ports,
+ .disconnect = edge_disconnect,
+ .release = edge_release,
+ .port_probe = edge_port_probe,
+ .port_remove = edge_port_remove,
+ .set_termios = edge_set_termios,
+ .tiocmget = edge_tiocmget,
+ .tiocmset = edge_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .write = edge_write,
+ .write_room = edge_write_room,
+ .chars_in_buffer = edge_chars_in_buffer,
+ .tx_empty = edge_tx_empty,
+ .break_ctl = edge_break,
+ .read_int_callback = edge_interrupt_callback,
+ .read_bulk_callback = edge_bulk_in_callback,
+ .write_bulk_callback = edge_bulk_out_callback,
+#ifdef CONFIG_PM
+ .suspend = edge_suspend,
+ .resume = edge_resume,
+#endif
+};
+
+static struct usb_serial_driver edgeport_2port_device = {
+ .driver = {
+ .name = "edgeport_ti_2",
+ },
+ .description = "Edgeport TI 2 port adapter",
+ .id_table = edgeport_2port_id_table,
+ .num_ports = 2,
+ .num_bulk_out = 1,
+ .open = edge_open,
+ .close = edge_close,
+ .throttle = edge_throttle,
+ .unthrottle = edge_unthrottle,
+ .attach = edge_startup,
+ .calc_num_ports = edge_calc_num_ports,
+ .disconnect = edge_disconnect,
+ .release = edge_release,
+ .port_probe = edge_port_probe,
+ .port_remove = edge_port_remove,
+ .set_termios = edge_set_termios,
+ .tiocmget = edge_tiocmget,
+ .tiocmset = edge_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .write = edge_write,
+ .write_room = edge_write_room,
+ .chars_in_buffer = edge_chars_in_buffer,
+ .tx_empty = edge_tx_empty,
+ .break_ctl = edge_break,
+ .read_int_callback = edge_interrupt_callback,
+ .read_bulk_callback = edge_bulk_in_callback,
+ .write_bulk_callback = edge_bulk_out_callback,
+#ifdef CONFIG_PM
+ .suspend = edge_suspend,
+ .resume = edge_resume,
+#endif
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &edgeport_1port_device, &edgeport_2port_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table_combined);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("edgeport/down3.bin");
+
+module_param(ignore_cpu_rev, bool, 0644);
+MODULE_PARM_DESC(ignore_cpu_rev,
+ "Ignore the cpu revision when connecting to a device");
+
+module_param(default_uart_mode, int, 0644);
+MODULE_PARM_DESC(default_uart_mode, "Default uart_mode, 0=RS232, ...");
diff --git a/drivers/usb/serial/io_ti.h b/drivers/usb/serial/io_ti.h
new file mode 100644
index 000000000..24fe1312c
--- /dev/null
+++ b/drivers/usb/serial/io_ti.h
@@ -0,0 +1,181 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*****************************************************************************
+ *
+ * Copyright (C) 1997-2002 Inside Out Networks, Inc.
+ *
+ * Feb-16-2001 DMI Added I2C structure definitions
+ * May-29-2002 gkh Ported to Linux
+ *
+ *
+ ******************************************************************************/
+
+#ifndef _IO_TI_H_
+#define _IO_TI_H_
+
+/* Address Space */
+#define DTK_ADDR_SPACE_XDATA 0x03 /* Addr is placed in XDATA space */
+#define DTK_ADDR_SPACE_I2C_TYPE_II 0x82 /* Addr is placed in I2C area */
+#define DTK_ADDR_SPACE_I2C_TYPE_III 0x83 /* Addr is placed in I2C area */
+
+/* UART Defines */
+#define UMPMEM_BASE_UART1 0xFFA0 /* UMP UART1 base address */
+#define UMPMEM_BASE_UART2 0xFFB0 /* UMP UART2 base address */
+#define UMPMEM_OFFS_UART_LSR 0x05 /* UMP UART LSR register offset */
+
+/* Bits per character */
+#define UMP_UART_CHAR5BITS 0x00
+#define UMP_UART_CHAR6BITS 0x01
+#define UMP_UART_CHAR7BITS 0x02
+#define UMP_UART_CHAR8BITS 0x03
+
+/* Parity */
+#define UMP_UART_NOPARITY 0x00
+#define UMP_UART_ODDPARITY 0x01
+#define UMP_UART_EVENPARITY 0x02
+#define UMP_UART_MARKPARITY 0x03
+#define UMP_UART_SPACEPARITY 0x04
+
+/* Stop bits */
+#define UMP_UART_STOPBIT1 0x00
+#define UMP_UART_STOPBIT15 0x01
+#define UMP_UART_STOPBIT2 0x02
+
+/* Line status register masks */
+#define UMP_UART_LSR_OV_MASK 0x01
+#define UMP_UART_LSR_PE_MASK 0x02
+#define UMP_UART_LSR_FE_MASK 0x04
+#define UMP_UART_LSR_BR_MASK 0x08
+#define UMP_UART_LSR_ER_MASK 0x0F
+#define UMP_UART_LSR_RX_MASK 0x10
+#define UMP_UART_LSR_TX_MASK 0x20
+
+#define UMP_UART_LSR_DATA_MASK (LSR_PAR_ERR | LSR_FRM_ERR | LSR_BREAK)
+
+/* Port Settings Constants) */
+#define UMP_MASK_UART_FLAGS_RTS_FLOW 0x0001
+#define UMP_MASK_UART_FLAGS_RTS_DISABLE 0x0002
+#define UMP_MASK_UART_FLAGS_PARITY 0x0008
+#define UMP_MASK_UART_FLAGS_OUT_X_DSR_FLOW 0x0010
+#define UMP_MASK_UART_FLAGS_OUT_X_CTS_FLOW 0x0020
+#define UMP_MASK_UART_FLAGS_OUT_X 0x0040
+#define UMP_MASK_UART_FLAGS_OUT_XA 0x0080
+#define UMP_MASK_UART_FLAGS_IN_X 0x0100
+#define UMP_MASK_UART_FLAGS_DTR_FLOW 0x0800
+#define UMP_MASK_UART_FLAGS_DTR_DISABLE 0x1000
+#define UMP_MASK_UART_FLAGS_RECEIVE_MS_INT 0x2000
+#define UMP_MASK_UART_FLAGS_AUTO_START_ON_ERR 0x4000
+
+#define UMP_DMA_MODE_CONTINOUS 0x01
+#define UMP_PIPE_TRANS_TIMEOUT_ENA 0x80
+#define UMP_PIPE_TRANSFER_MODE_MASK 0x03
+#define UMP_PIPE_TRANS_TIMEOUT_MASK 0x7C
+
+/* Purge port Direction Mask Bits */
+#define UMP_PORT_DIR_OUT 0x01
+#define UMP_PORT_DIR_IN 0x02
+
+/* Address of Port 0 */
+#define UMPM_UART1_PORT 0x03
+
+/* Commands */
+#define UMPC_SET_CONFIG 0x05
+#define UMPC_OPEN_PORT 0x06
+#define UMPC_CLOSE_PORT 0x07
+#define UMPC_START_PORT 0x08
+#define UMPC_STOP_PORT 0x09
+#define UMPC_TEST_PORT 0x0A
+#define UMPC_PURGE_PORT 0x0B
+
+/* Force the Firmware to complete the current Read */
+#define UMPC_COMPLETE_READ 0x80
+/* Force UMP back into BOOT Mode */
+#define UMPC_HARDWARE_RESET 0x81
+/*
+ * Copy current download image to type 0xf2 record in 16k I2C
+ * firmware will change 0xff record to type 2 record when complete
+ */
+#define UMPC_COPY_DNLD_TO_I2C 0x82
+
+/*
+ * Special function register commands
+ * wIndex is register address
+ * wValue is MSB/LSB mask/data
+ */
+#define UMPC_WRITE_SFR 0x83 /* Write SFR Register */
+
+/* wIndex is register address */
+#define UMPC_READ_SFR 0x84 /* Read SRF Register */
+
+/* Set or Clear DTR (wValue bit 0 Set/Clear) wIndex ModuleID (port) */
+#define UMPC_SET_CLR_DTR 0x85
+
+/* Set or Clear RTS (wValue bit 0 Set/Clear) wIndex ModuleID (port) */
+#define UMPC_SET_CLR_RTS 0x86
+
+/* Set or Clear LOOPBACK (wValue bit 0 Set/Clear) wIndex ModuleID (port) */
+#define UMPC_SET_CLR_LOOPBACK 0x87
+
+/* Set or Clear BREAK (wValue bit 0 Set/Clear) wIndex ModuleID (port) */
+#define UMPC_SET_CLR_BREAK 0x88
+
+/* Read MSR wIndex ModuleID (port) */
+#define UMPC_READ_MSR 0x89
+
+/* Toolkit commands */
+/* Read-write group */
+#define UMPC_MEMORY_READ 0x92
+#define UMPC_MEMORY_WRITE 0x93
+
+/*
+ * UMP DMA Definitions
+ */
+#define UMPD_OEDB1_ADDRESS 0xFF08
+#define UMPD_OEDB2_ADDRESS 0xFF10
+
+struct out_endpoint_desc_block {
+ u8 Configuration;
+ u8 XBufAddr;
+ u8 XByteCount;
+ u8 Unused1;
+ u8 Unused2;
+ u8 YBufAddr;
+ u8 YByteCount;
+ u8 BufferSize;
+};
+
+
+/*
+ * TYPE DEFINITIONS
+ * Structures for Firmware commands
+ */
+/* UART settings */
+struct ump_uart_config {
+ u16 wBaudRate; /* Baud rate */
+ u16 wFlags; /* Bitmap mask of flags */
+ u8 bDataBits; /* 5..8 - data bits per character */
+ u8 bParity; /* Parity settings */
+ u8 bStopBits; /* Stop bits settings */
+ char cXon; /* XON character */
+ char cXoff; /* XOFF character */
+ u8 bUartMode; /* Will be updated when a user */
+ /* interface is defined */
+};
+
+
+/*
+ * TYPE DEFINITIONS
+ * Structures for USB interrupts
+ */
+/* Interrupt packet structure */
+struct ump_interrupt {
+ u8 bICode; /* Interrupt code (interrupt num) */
+ u8 bIInfo; /* Interrupt information */
+};
+
+
+#define TIUMP_GET_PORT_FROM_CODE(c) (((c) >> 6) & 0x01)
+#define TIUMP_GET_FUNC_FROM_CODE(c) ((c) & 0x0f)
+#define TIUMP_INTERRUPT_CODE_LSR 0x03
+#define TIUMP_INTERRUPT_CODE_MSR 0x04
+
+#endif
diff --git a/drivers/usb/serial/io_usbvend.h b/drivers/usb/serial/io_usbvend.h
new file mode 100644
index 000000000..c82a275e8
--- /dev/null
+++ b/drivers/usb/serial/io_usbvend.h
@@ -0,0 +1,682 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/************************************************************************
+ *
+ * USBVEND.H Vendor-specific USB definitions
+ *
+ * NOTE: This must be kept in sync with the Edgeport firmware and
+ * must be kept backward-compatible with older firmware.
+ *
+ ************************************************************************
+ *
+ * Copyright (C) 1998 Inside Out Networks, Inc.
+ *
+ ************************************************************************/
+
+#if !defined(_USBVEND_H)
+#define _USBVEND_H
+
+/************************************************************************
+ *
+ * D e f i n e s / T y p e d e f s
+ *
+ ************************************************************************/
+
+//
+// Definitions of USB product IDs
+//
+
+#define USB_VENDOR_ID_ION 0x1608 // Our VID
+#define USB_VENDOR_ID_TI 0x0451 // TI VID
+#define USB_VENDOR_ID_AXIOHM 0x05D9 /* Axiohm VID */
+
+//
+// Definitions of USB product IDs (PID)
+// We break the USB-defined PID into an OEM Id field (upper 6 bits)
+// and a Device Id (bottom 10 bits). The Device Id defines what
+// device this actually is regardless of what the OEM wants to
+// call it.
+//
+
+// ION-device OEM IDs
+#define ION_OEM_ID_ION 0 // 00h Inside Out Networks
+#define ION_OEM_ID_NLYNX 1 // 01h NLynx Systems
+#define ION_OEM_ID_GENERIC 2 // 02h Generic OEM
+#define ION_OEM_ID_MAC 3 // 03h Mac Version
+#define ION_OEM_ID_MEGAWOLF 4 // 04h Lupusb OEM Mac version (MegaWolf)
+#define ION_OEM_ID_MULTITECH 5 // 05h Multitech Rapidports
+#define ION_OEM_ID_AGILENT 6 // 06h AGILENT board
+
+
+// ION-device Device IDs
+// Product IDs - assigned to match middle digit of serial number (No longer true)
+
+#define ION_DEVICE_ID_80251_NETCHIP 0x020 // This bit is set in the PID if this edgeport hardware$
+ // is based on the 80251+Netchip.
+
+#define ION_DEVICE_ID_GENERATION_1 0x00 // Value for 930 based edgeports
+#define ION_DEVICE_ID_GENERATION_2 0x01 // Value for 80251+Netchip.
+#define ION_DEVICE_ID_GENERATION_3 0x02 // Value for Texas Instruments TUSB5052 chip
+#define ION_DEVICE_ID_GENERATION_4 0x03 // Watchport Family of products
+#define ION_GENERATION_MASK 0x03
+
+#define ION_DEVICE_ID_HUB_MASK 0x0080 // This bit in the PID designates a HUB device
+ // for example 8C would be a 421 4 port hub
+ // and 8D would be a 2 port embedded hub
+
+#define EDGEPORT_DEVICE_ID_MASK 0x0ff // Not including OEM or GENERATION fields
+
+#define ION_DEVICE_ID_UNCONFIGURED_EDGE_DEVICE 0x000 // In manufacturing only
+#define ION_DEVICE_ID_EDGEPORT_4 0x001 // Edgeport/4 RS232
+#define ION_DEVICE_ID_EDGEPORT_8R 0x002 // Edgeport with RJ45 no Ring
+#define ION_DEVICE_ID_RAPIDPORT_4 0x003 // Rapidport/4
+#define ION_DEVICE_ID_EDGEPORT_4T 0x004 // Edgeport/4 RS232 for Telxon (aka "Fleetport")
+#define ION_DEVICE_ID_EDGEPORT_2 0x005 // Edgeport/2 RS232
+#define ION_DEVICE_ID_EDGEPORT_4I 0x006 // Edgeport/4 RS422
+#define ION_DEVICE_ID_EDGEPORT_2I 0x007 // Edgeport/2 RS422/RS485
+#define ION_DEVICE_ID_EDGEPORT_8RR 0x008 // Edgeport with RJ45 with Data and RTS/CTS only
+// ION_DEVICE_ID_EDGEPORT_8_HANDBUILT 0x009 // Hand-built Edgeport/8 (Placeholder, used in middle digit of serial number only!)
+// ION_DEVICE_ID_MULTIMODEM_4X56 0x00A // MultiTech version of RP/4 (Placeholder, used in middle digit of serial number only!)
+#define ION_DEVICE_ID_EDGEPORT_PARALLEL_PORT 0x00B // Edgeport/(4)21 Parallel port (USS720)
+#define ION_DEVICE_ID_EDGEPORT_421 0x00C // Edgeport/421 Hub+RS232+Parallel
+#define ION_DEVICE_ID_EDGEPORT_21 0x00D // Edgeport/21 RS232+Parallel
+#define ION_DEVICE_ID_EDGEPORT_8_DUAL_CPU 0x00E // Half of an Edgeport/8 (the kind with 2 EP/4s on 1 PCB)
+#define ION_DEVICE_ID_EDGEPORT_8 0x00F // Edgeport/8 (single-CPU)
+#define ION_DEVICE_ID_EDGEPORT_2_DIN 0x010 // Edgeport/2 RS232 with Apple DIN connector
+#define ION_DEVICE_ID_EDGEPORT_4_DIN 0x011 // Edgeport/4 RS232 with Apple DIN connector
+#define ION_DEVICE_ID_EDGEPORT_16_DUAL_CPU 0x012 // Half of an Edgeport/16 (the kind with 2 EP/8s)
+#define ION_DEVICE_ID_EDGEPORT_COMPATIBLE 0x013 // Edgeport Compatible, for NCR, Axiohm etc. testing
+#define ION_DEVICE_ID_EDGEPORT_8I 0x014 // Edgeport/8 RS422 (single-CPU)
+#define ION_DEVICE_ID_EDGEPORT_1 0x015 // Edgeport/1 RS232
+#define ION_DEVICE_ID_EPOS44 0x016 // Half of an EPOS/44 (TIUMP BASED)
+#define ION_DEVICE_ID_EDGEPORT_42 0x017 // Edgeport/42
+#define ION_DEVICE_ID_EDGEPORT_412_8 0x018 // Edgeport/412 8 port part
+#define ION_DEVICE_ID_EDGEPORT_412_4 0x019 // Edgeport/412 4 port part
+#define ION_DEVICE_ID_EDGEPORT_22I 0x01A // Edgeport/22I is an Edgeport/4 with ports 1&2 RS422 and ports 3&4 RS232
+
+// Compact Form factor TI based devices 2c, 21c, 22c, 221c
+#define ION_DEVICE_ID_EDGEPORT_2C 0x01B // Edgeport/2c is a TI based Edgeport/2 - Small I2c
+#define ION_DEVICE_ID_EDGEPORT_221C 0x01C // Edgeport/221c is a TI based Edgeport/2 with lucent chip and
+ // 2 external hub ports - Large I2C
+#define ION_DEVICE_ID_EDGEPORT_22C 0x01D // Edgeport/22c is a TI based Edgeport/2 with
+ // 2 external hub ports - Large I2C
+#define ION_DEVICE_ID_EDGEPORT_21C 0x01E // Edgeport/21c is a TI based Edgeport/2 with lucent chip
+ // Small I2C
+
+
+/*
+ * DANGER DANGER The 0x20 bit was used to indicate a 8251/netchip GEN 2 device.
+ * Since the MAC, Linux, and Optimal drivers still used the old code
+ * I suggest that you skip the 0x20 bit when creating new PIDs
+ */
+
+
+// Generation 3 devices -- 3410 based edgport/1 (256 byte I2C)
+#define ION_DEVICE_ID_TI3410_EDGEPORT_1 0x040 // Edgeport/1 RS232
+#define ION_DEVICE_ID_TI3410_EDGEPORT_1I 0x041 // Edgeport/1i- RS422 model
+
+// Ti based software switchable RS232/RS422/RS485 devices
+#define ION_DEVICE_ID_EDGEPORT_4S 0x042 // Edgeport/4s - software switchable model
+#define ION_DEVICE_ID_EDGEPORT_8S 0x043 // Edgeport/8s - software switchable model
+
+// Usb to Ethernet dongle
+#define ION_DEVICE_ID_EDGEPORT_E 0x0E0 // Edgeport/E Usb to Ethernet
+
+// Edgeport TI based devices
+#define ION_DEVICE_ID_TI_EDGEPORT_4 0x0201 // Edgeport/4 RS232
+#define ION_DEVICE_ID_TI_EDGEPORT_2 0x0205 // Edgeport/2 RS232
+#define ION_DEVICE_ID_TI_EDGEPORT_4I 0x0206 // Edgeport/4i RS422
+#define ION_DEVICE_ID_TI_EDGEPORT_2I 0x0207 // Edgeport/2i RS422/RS485
+#define ION_DEVICE_ID_TI_EDGEPORT_421 0x020C // Edgeport/421 4 hub 2 RS232 + Parallel (lucent on a different hub port)
+#define ION_DEVICE_ID_TI_EDGEPORT_21 0x020D // Edgeport/21 2 RS232 + Parallel (lucent on a different hub port)
+#define ION_DEVICE_ID_TI_EDGEPORT_416 0x0212 // Edgeport/416
+#define ION_DEVICE_ID_TI_EDGEPORT_1 0x0215 // Edgeport/1 RS232
+#define ION_DEVICE_ID_TI_EDGEPORT_42 0x0217 // Edgeport/42 4 hub 2 RS232
+#define ION_DEVICE_ID_TI_EDGEPORT_22I 0x021A // Edgeport/22I is an Edgeport/4 with ports 1&2 RS422 and ports 3&4 RS232
+#define ION_DEVICE_ID_TI_EDGEPORT_2C 0x021B // Edgeport/2c RS232
+#define ION_DEVICE_ID_TI_EDGEPORT_221C 0x021C // Edgeport/221c is a TI based Edgeport/2 with lucent chip and
+ // 2 external hub ports - Large I2C
+#define ION_DEVICE_ID_TI_EDGEPORT_22C 0x021D // Edgeport/22c is a TI based Edgeport/2 with
+ // 2 external hub ports - Large I2C
+#define ION_DEVICE_ID_TI_EDGEPORT_21C 0x021E // Edgeport/21c is a TI based Edgeport/2 with lucent chip
+
+// Generation 3 devices -- 3410 based edgport/1 (256 byte I2C)
+#define ION_DEVICE_ID_TI_TI3410_EDGEPORT_1 0x0240 // Edgeport/1 RS232
+#define ION_DEVICE_ID_TI_TI3410_EDGEPORT_1I 0x0241 // Edgeport/1i- RS422 model
+
+// Ti based software switchable RS232/RS422/RS485 devices
+#define ION_DEVICE_ID_TI_EDGEPORT_4S 0x0242 // Edgeport/4s - software switchable model
+#define ION_DEVICE_ID_TI_EDGEPORT_8S 0x0243 // Edgeport/8s - software switchable model
+#define ION_DEVICE_ID_TI_EDGEPORT_8 0x0244 // Edgeport/8 (single-CPU)
+#define ION_DEVICE_ID_TI_EDGEPORT_416B 0x0247 // Edgeport/416
+
+
+/************************************************************************
+ *
+ * Generation 4 devices
+ *
+ ************************************************************************/
+
+// Watchport based on 3410 both 1-wire and binary products (16K I2C)
+#define ION_DEVICE_ID_WP_UNSERIALIZED 0x300 // Watchport based on 3410 both 1-wire and binary products
+#define ION_DEVICE_ID_WP_PROXIMITY 0x301 // Watchport/P Discontinued
+#define ION_DEVICE_ID_WP_MOTION 0x302 // Watchport/M
+#define ION_DEVICE_ID_WP_MOISTURE 0x303 // Watchport/W
+#define ION_DEVICE_ID_WP_TEMPERATURE 0x304 // Watchport/T
+#define ION_DEVICE_ID_WP_HUMIDITY 0x305 // Watchport/H
+
+#define ION_DEVICE_ID_WP_POWER 0x306 // Watchport
+#define ION_DEVICE_ID_WP_LIGHT 0x307 // Watchport
+#define ION_DEVICE_ID_WP_RADIATION 0x308 // Watchport
+#define ION_DEVICE_ID_WP_ACCELERATION 0x309 // Watchport/A
+#define ION_DEVICE_ID_WP_DISTANCE 0x30A // Watchport/D Discontinued
+#define ION_DEVICE_ID_WP_PROX_DIST 0x30B // Watchport/D uses distance sensor
+ // Default to /P function
+
+#define ION_DEVICE_ID_PLUS_PWR_HP4CD 0x30C // 5052 Plus Power HubPort/4CD+ (for Dell)
+#define ION_DEVICE_ID_PLUS_PWR_HP4C 0x30D // 5052 Plus Power HubPort/4C+
+#define ION_DEVICE_ID_PLUS_PWR_PCI 0x30E // 3410 Plus Power PCI Host Controller 4 port
+
+
+//
+// Definitions for AXIOHM USB product IDs
+//
+#define USB_VENDOR_ID_AXIOHM 0x05D9 // Axiohm VID
+
+#define AXIOHM_DEVICE_ID_MASK 0xffff
+#define AXIOHM_DEVICE_ID_EPIC_A758 0xA758
+#define AXIOHM_DEVICE_ID_EPIC_A794 0xA794
+#define AXIOHM_DEVICE_ID_EPIC_A225 0xA225
+
+
+//
+// Definitions for NCR USB product IDs
+//
+#define USB_VENDOR_ID_NCR 0x0404 // NCR VID
+
+#define NCR_DEVICE_ID_MASK 0xffff
+#define NCR_DEVICE_ID_EPIC_0202 0x0202
+#define NCR_DEVICE_ID_EPIC_0203 0x0203
+#define NCR_DEVICE_ID_EPIC_0310 0x0310
+#define NCR_DEVICE_ID_EPIC_0311 0x0311
+#define NCR_DEVICE_ID_EPIC_0312 0x0312
+
+
+//
+// Definitions for SYMBOL USB product IDs
+//
+#define USB_VENDOR_ID_SYMBOL 0x05E0 // Symbol VID
+#define SYMBOL_DEVICE_ID_MASK 0xffff
+#define SYMBOL_DEVICE_ID_KEYFOB 0x0700
+
+
+//
+// Definitions for other product IDs
+#define ION_DEVICE_ID_BLACKBOX_IC135A 0x0801 // OEM device (rebranded Edgeport/4)
+#define ION_DEVICE_ID_MT4X56USB 0x1403 // OEM device
+#define ION_DEVICE_ID_E5805A 0x1A01 // OEM device (rebranded Edgeport/4)
+
+
+#define GENERATION_ID_FROM_USB_PRODUCT_ID(ProductId) \
+ ((__u16) ((ProductId >> 8) & (ION_GENERATION_MASK)))
+
+#define MAKE_USB_PRODUCT_ID(OemId, DeviceId) \
+ ((__u16) (((OemId) << 10) || (DeviceId)))
+
+#define DEVICE_ID_FROM_USB_PRODUCT_ID(ProductId) \
+ ((__u16) ((ProductId) & (EDGEPORT_DEVICE_ID_MASK)))
+
+#define OEM_ID_FROM_USB_PRODUCT_ID(ProductId) \
+ ((__u16) (((ProductId) >> 10) & 0x3F))
+
+//
+// Definitions of parameters for download code. Note that these are
+// specific to a given version of download code and must change if the
+// corresponding download code changes.
+//
+
+// TxCredits value below which driver won't bother sending (to prevent too many small writes).
+// Send only if above 25%
+#define EDGE_FW_GET_TX_CREDITS_SEND_THRESHOLD(InitialCredit, MaxPacketSize) (max(((InitialCredit) / 4), (MaxPacketSize)))
+
+#define EDGE_FW_BULK_MAX_PACKET_SIZE 64 // Max Packet Size for Bulk In Endpoint (EP1)
+#define EDGE_FW_BULK_READ_BUFFER_SIZE 1024 // Size to use for Bulk reads
+
+#define EDGE_FW_INT_MAX_PACKET_SIZE 32 // Max Packet Size for Interrupt In Endpoint
+ // Note that many units were shipped with MPS=16, we
+ // force an upgrade to this value).
+#define EDGE_FW_INT_INTERVAL 2 // 2ms polling on IntPipe
+
+
+//
+// Definitions of I/O Networks vendor-specific requests
+// for default endpoint
+//
+// bmRequestType = 01000000 Set vendor-specific, to device
+// bmRequestType = 11000000 Get vendor-specific, to device
+//
+// These are the definitions for the bRequest field for the
+// above bmRequestTypes.
+//
+// For the read/write Edgeport memory commands, the parameters
+// are as follows:
+// wValue = 16-bit address
+// wIndex = unused (though we could put segment 00: or FF: here)
+// wLength = # bytes to read/write (max 64)
+//
+
+#define USB_REQUEST_ION_RESET_DEVICE 0 // Warm reboot Edgeport, retaining USB address
+#define USB_REQUEST_ION_GET_EPIC_DESC 1 // Get Edgeport Compatibility Descriptor
+// unused 2 // Unused, available
+#define USB_REQUEST_ION_READ_RAM 3 // Read EdgePort RAM at specified addr
+#define USB_REQUEST_ION_WRITE_RAM 4 // Write EdgePort RAM at specified addr
+#define USB_REQUEST_ION_READ_ROM 5 // Read EdgePort ROM at specified addr
+#define USB_REQUEST_ION_WRITE_ROM 6 // Write EdgePort ROM at specified addr
+#define USB_REQUEST_ION_EXEC_DL_CODE 7 // Begin execution of RAM-based download
+ // code by jumping to address in wIndex:wValue
+// 8 // Unused, available
+#define USB_REQUEST_ION_ENABLE_SUSPEND 9 // Enable/Disable suspend feature
+ // (wValue != 0: Enable; wValue = 0: Disable)
+
+#define USB_REQUEST_ION_SEND_IOSP 10 // Send an IOSP command to the edgeport over the control pipe
+#define USB_REQUEST_ION_RECV_IOSP 11 // Receive an IOSP command from the edgeport over the control pipe
+
+
+#define USB_REQUEST_ION_DIS_INT_TIMER 0x80 // Sent to Axiohm to enable/ disable
+ // interrupt token timer
+ // wValue = 1, enable (default)
+ // wValue = 0, disable
+
+//
+// Define parameter values for our vendor-specific commands
+//
+
+//
+// Edgeport Compatibility Descriptor
+//
+// This descriptor is only returned by Edgeport-compatible devices
+// supporting the EPiC spec. True ION devices do not return this
+// descriptor, but instead return STALL on receipt of the
+// GET_EPIC_DESC command. The driver interprets a STALL to mean that
+// this is a "real" Edgeport.
+//
+
+struct edge_compatibility_bits {
+ // This __u32 defines which Vendor-specific commands/functionality
+ // the device supports on the default EP0 pipe.
+
+ __u32 VendEnableSuspend : 1; // 0001 Set if device supports ION_ENABLE_SUSPEND
+ __u32 VendUnused : 31; // Available for future expansion, must be 0
+
+ // This __u32 defines which IOSP commands are supported over the
+ // bulk pipe EP1.
+
+ // xxxx Set if device supports:
+ __u32 IOSPOpen : 1; // 0001 OPEN / OPEN_RSP (Currently must be 1)
+ __u32 IOSPClose : 1; // 0002 CLOSE
+ __u32 IOSPChase : 1; // 0004 CHASE / CHASE_RSP
+ __u32 IOSPSetRxFlow : 1; // 0008 SET_RX_FLOW
+ __u32 IOSPSetTxFlow : 1; // 0010 SET_TX_FLOW
+ __u32 IOSPSetXChar : 1; // 0020 SET_XON_CHAR/SET_XOFF_CHAR
+ __u32 IOSPRxCheck : 1; // 0040 RX_CHECK_REQ/RX_CHECK_RSP
+ __u32 IOSPSetClrBreak : 1; // 0080 SET_BREAK/CLEAR_BREAK
+ __u32 IOSPWriteMCR : 1; // 0100 MCR register writes (set/clr DTR/RTS)
+ __u32 IOSPWriteLCR : 1; // 0200 LCR register writes (wordlen/stop/parity)
+ __u32 IOSPSetBaudRate : 1; // 0400 setting Baud rate (writes to LCR.80h and DLL/DLM register)
+ __u32 IOSPDisableIntPipe : 1; // 0800 Do not use the interrupt pipe for TxCredits or RxButesAvailable
+ __u32 IOSPRxDataAvail : 1; // 1000 Return status of RX Fifo (Data available in Fifo)
+ __u32 IOSPTxPurge : 1; // 2000 Purge TXBuffer and/or Fifo in Edgeport hardware
+ __u32 IOSPUnused : 18; // Available for future expansion, must be 0
+
+ // This __u32 defines which 'general' features are supported
+
+ __u32 TrueEdgeport : 1; // 0001 Set if device is a 'real' Edgeport
+ // (Used only by driver, NEVER set by an EPiC device)
+ __u32 GenUnused : 31; // Available for future expansion, must be 0
+};
+
+#define EDGE_COMPATIBILITY_MASK0 0x0001
+#define EDGE_COMPATIBILITY_MASK1 0x3FFF
+#define EDGE_COMPATIBILITY_MASK2 0x0001
+
+struct edge_compatibility_descriptor {
+ __u8 Length; // Descriptor Length (per USB spec)
+ __u8 DescType; // Descriptor Type (per USB spec, =DEVICE type)
+ __u8 EpicVer; // Version of EPiC spec supported
+ // (Currently must be 1)
+ __u8 NumPorts; // Number of serial ports supported
+ __u8 iDownloadFile; // Index of string containing download code filename
+ // 0=no download, FF=download compiled into driver.
+ __u8 Unused[3]; // Available for future expansion, must be 0
+ // (Currently must be 0).
+ __u8 MajorVersion; // Firmware version: xx.
+ __u8 MinorVersion; // yy.
+ __le16 BuildNumber; // zzzz (LE format)
+
+ // The following structure contains __u32s, with each bit
+ // specifying whether the EPiC device supports the given
+ // command or functionality.
+ struct edge_compatibility_bits Supports;
+};
+
+// Values for iDownloadFile
+#define EDGE_DOWNLOAD_FILE_NONE 0 // No download requested
+#define EDGE_DOWNLOAD_FILE_INTERNAL 0xFF // Download the file compiled into driver (930 version)
+#define EDGE_DOWNLOAD_FILE_I930 0xFF // Download the file compiled into driver (930 version)
+#define EDGE_DOWNLOAD_FILE_80251 0xFE // Download the file compiled into driver (80251 version)
+
+
+
+/*
+ * Special addresses for READ/WRITE_RAM/ROM
+ */
+
+// Version 1 (original) format of DeviceParams
+#define EDGE_MANUF_DESC_ADDR_V1 0x00FF7F00
+#define EDGE_MANUF_DESC_LEN_V1 sizeof(EDGE_MANUF_DESCRIPTOR_V1)
+
+// Version 2 format of DeviceParams. This format is longer (3C0h)
+// and starts lower in memory, at the uppermost 1K in ROM.
+#define EDGE_MANUF_DESC_ADDR 0x00FF7C00
+#define EDGE_MANUF_DESC_LEN sizeof(struct edge_manuf_descriptor)
+
+// Boot params descriptor
+#define EDGE_BOOT_DESC_ADDR 0x00FF7FC0
+#define EDGE_BOOT_DESC_LEN sizeof(struct edge_boot_descriptor)
+
+// Define the max block size that may be read or written
+// in a read/write RAM/ROM command.
+#define MAX_SIZE_REQ_ION_READ_MEM ((__u16)64)
+#define MAX_SIZE_REQ_ION_WRITE_MEM ((__u16)64)
+
+
+//
+// Notes for the following two ION vendor-specific param descriptors:
+//
+// 1. These have a standard USB descriptor header so they look like a
+// normal descriptor.
+// 2. Any strings in the structures are in USB-defined string
+// descriptor format, so that they may be separately retrieved,
+// if necessary, with a minimum of work on the 930. This also
+// requires them to be in UNICODE format, which, for English at
+// least, simply means extending each __u8 into a __u16.
+// 3. For all fields, 00 means 'uninitialized'.
+// 4. All unused areas should be set to 00 for future expansion.
+//
+
+// This structure is ver 2 format. It contains ALL USB descriptors as
+// well as the configuration parameters that were in the original V1
+// structure. It is NOT modified when new boot code is downloaded; rather,
+// these values are set or modified by manufacturing. It is located at
+// xC00-xFBF (length 3C0h) in the ROM.
+// This structure is a superset of the v1 structure and is arranged so
+// that all of the v1 fields remain at the same address. We are just
+// adding more room to the front of the structure to hold the descriptors.
+//
+// The actual contents of this structure are defined in a 930 assembly
+// file, converted to a binary image, and then written by the serialization
+// program. The C definition of this structure just defines a dummy
+// area for general USB descriptors and the descriptor tables (the root
+// descriptor starts at xC00). At the bottom of the structure are the
+// fields inherited from the v1 structure.
+
+#define MAX_SERIALNUMBER_LEN 12
+#define MAX_ASSEMBLYNUMBER_LEN 14
+
+struct edge_manuf_descriptor {
+
+ __u16 RootDescTable[0x10]; // C00 Root of descriptor tables (just a placeholder)
+ __u8 DescriptorArea[0x2E0]; // C20 Descriptors go here, up to 2E0h (just a placeholder)
+
+ // Start of v1-compatible section
+ __u8 Length; // F00 Desc length for what follows, per USB (= C0h )
+ __u8 DescType; // F01 Desc type, per USB (=DEVICE type)
+ __u8 DescVer; // F02 Desc version/format (currently 2)
+ __u8 NumRootDescEntries; // F03 # entries in RootDescTable
+
+ __u8 RomSize; // F04 Size of ROM/E2PROM in K
+ __u8 RamSize; // F05 Size of external RAM in K
+ __u8 CpuRev; // F06 CPU revision level (chg only if s/w visible)
+ __u8 BoardRev; // F07 PCB revision level (chg only if s/w visible)
+
+ __u8 NumPorts; // F08 Number of ports
+ __u8 DescDate[3]; // F09 MM/DD/YY when descriptor template was compiler,
+ // so host can track changes to USB-only descriptors.
+
+ __u8 SerNumLength; // F0C USB string descriptor len
+ __u8 SerNumDescType; // F0D USB descriptor type (=STRING type)
+ __le16 SerialNumber[MAX_SERIALNUMBER_LEN]; // F0E "01-01-000100" Unicode Serial Number
+
+ __u8 AssemblyNumLength; // F26 USB string descriptor len
+ __u8 AssemblyNumDescType; // F27 USB descriptor type (=STRING type)
+ __le16 AssemblyNumber[MAX_ASSEMBLYNUMBER_LEN]; // F28 "350-1000-01-A " assembly number
+
+ __u8 OemAssyNumLength; // F44 USB string descriptor len
+ __u8 OemAssyNumDescType; // F45 USB descriptor type (=STRING type)
+ __le16 OemAssyNumber[MAX_ASSEMBLYNUMBER_LEN]; // F46 "xxxxxxxxxxxxxx" OEM assembly number
+
+ __u8 ManufDateLength; // F62 USB string descriptor len
+ __u8 ManufDateDescType; // F63 USB descriptor type (=STRING type)
+ __le16 ManufDate[6]; // F64 "MMDDYY" manufacturing date
+
+ __u8 Reserved3[0x4D]; // F70 -- unused, set to 0 --
+
+ __u8 UartType; // FBD Uart Type
+ __u8 IonPid; // FBE Product ID, == LSB of USB DevDesc.PID
+ // (Note: Edgeport/4s before 11/98 will have
+ // 00 here instead of 01)
+ __u8 IonConfig; // FBF Config byte for ION manufacturing use
+ // FBF end of structure, total len = 3C0h
+
+};
+
+
+#define MANUF_DESC_VER_1 1 // Original definition of MANUF_DESC
+#define MANUF_DESC_VER_2 2 // Ver 2, starts at xC00h len 3C0h
+
+
+// Uart Types
+// Note: Since this field was added only recently, all Edgeport/4 units
+// shipped before 11/98 will have 00 in this field. Therefore,
+// both 00 and 01 values mean '654.
+#define MANUF_UART_EXAR_654_EARLY 0 // Exar 16C654 in Edgeport/4s before 11/98
+#define MANUF_UART_EXAR_654 1 // Exar 16C654
+#define MANUF_UART_EXAR_2852 2 // Exar 16C2852
+
+//
+// Note: The CpuRev and BoardRev values do not conform to manufacturing
+// revisions; they are to be incremented only when the CPU or hardware
+// changes in a software-visible way, such that the 930 software or
+// the host driver needs to handle the hardware differently.
+//
+
+// Values of bottom 5 bits of CpuRev & BoardRev for
+// Implementation 0 (ie, 930-based)
+#define MANUF_CPU_REV_AD4 1 // 930 AD4, with EP1 Rx bug (needs RXSPM)
+#define MANUF_CPU_REV_AD5 2 // 930 AD5, with above bug (supposedly) fixed
+#define MANUF_CPU_80251 0x20 // Intel 80251
+
+
+#define MANUF_BOARD_REV_A 1 // Original version, == Manuf Rev A
+#define MANUF_BOARD_REV_B 2 // Manuf Rev B, wakeup interrupt works
+#define MANUF_BOARD_REV_C 3 // Manuf Rev C, 2/4 ports, rs232/rs422
+#define MANUF_BOARD_REV_GENERATION_2 0x20 // Second generaiton edgeport
+
+
+// Values of bottom 5 bits of CpuRev & BoardRev for
+// Implementation 1 (ie, 251+Netchip-based)
+#define MANUF_CPU_REV_1 1 // C251TB Rev 1 (Need actual Intel rev here)
+
+#define MANUF_BOARD_REV_A 1 // First rev of 251+Netchip design
+
+#define MANUF_SERNUM_LENGTH sizeof(((struct edge_manuf_descriptor *)0)->SerialNumber)
+#define MANUF_ASSYNUM_LENGTH sizeof(((struct edge_manuf_descriptor *)0)->AssemblyNumber)
+#define MANUF_OEMASSYNUM_LENGTH sizeof(((struct edge_manuf_descriptor *)0)->OemAssyNumber)
+#define MANUF_MANUFDATE_LENGTH sizeof(((struct edge_manuf_descriptor *)0)->ManufDate)
+
+#define MANUF_ION_CONFIG_DIAG_NO_LOOP 0x20 // As below but no ext loopback test
+#define MANUF_ION_CONFIG_DIAG 0x40 // 930 based device: 1=Run h/w diags, 0=norm
+ // TIUMP Device : 1=IONSERIAL needs to run Final Test
+#define MANUF_ION_CONFIG_MASTER 0x80 // 930 based device: 1=Master mode, 0=Normal
+ // TIUMP Device : 1=First device on a multi TIUMP Device
+
+//
+// This structure describes parameters for the boot code, and
+// is programmed along with new boot code. These are values
+// which are specific to a given build of the boot code. It
+// is exactly 64 bytes long and is fixed at address FF:xFC0
+// - FF:xFFF. Note that the 930-mandated UCONFIG bytes are
+// included in this structure.
+//
+struct edge_boot_descriptor {
+ __u8 Length; // C0 Desc length, per USB (= 40h)
+ __u8 DescType; // C1 Desc type, per USB (= DEVICE type)
+ __u8 DescVer; // C2 Desc version/format
+ __u8 Reserved1; // C3 -- unused, set to 0 --
+
+ __le16 BootCodeLength; // C4 Boot code goes from FF:0000 to FF:(len-1)
+ // (LE format)
+
+ __u8 MajorVersion; // C6 Firmware version: xx.
+ __u8 MinorVersion; // C7 yy.
+ __le16 BuildNumber; // C8 zzzz (LE format)
+
+ __u16 EnumRootDescTable; // CA Root of ROM-based descriptor table
+ __u8 NumDescTypes; // CC Number of supported descriptor types
+
+ __u8 Reserved4; // CD Fix Compiler Packing
+
+ __le16 Capabilities; // CE-CF Capabilities flags (LE format)
+ __u8 Reserved2[0x28]; // D0 -- unused, set to 0 --
+ __u8 UConfig0; // F8 930-defined CPU configuration byte 0
+ __u8 UConfig1; // F9 930-defined CPU configuration byte 1
+ __u8 Reserved3[6]; // FA -- unused, set to 0 --
+ // FF end of structure, total len = 80
+};
+
+
+#define BOOT_DESC_VER_1 1 // Original definition of BOOT_PARAMS
+#define BOOT_DESC_VER_2 2 // 2nd definition, descriptors not included in boot
+
+
+ // Capabilities flags
+
+#define BOOT_CAP_RESET_CMD 0x0001 // If set, boot correctly supports ION_RESET_DEVICE
+
+
+/************************************************************************
+ T I U M P D E F I N I T I O N S
+ ***********************************************************************/
+
+// Chip definitions in I2C
+#define UMP5152 0x52
+#define UMP3410 0x10
+
+
+//************************************************************************
+// TI I2C Format Definitions
+//************************************************************************
+#define I2C_DESC_TYPE_INFO_BASIC 0x01
+#define I2C_DESC_TYPE_FIRMWARE_BASIC 0x02
+#define I2C_DESC_TYPE_DEVICE 0x03
+#define I2C_DESC_TYPE_CONFIG 0x04
+#define I2C_DESC_TYPE_STRING 0x05
+#define I2C_DESC_TYPE_FIRMWARE_AUTO 0x07 // for 3410 download
+#define I2C_DESC_TYPE_CONFIG_KLUDGE 0x14 // for 3410
+#define I2C_DESC_TYPE_WATCHPORT_VERSION 0x15 // firmware version number for watchport
+#define I2C_DESC_TYPE_WATCHPORT_CALIBRATION_DATA 0x16 // Watchport Calibration Data
+
+#define I2C_DESC_TYPE_FIRMWARE_BLANK 0xf2
+
+// Special section defined by ION
+#define I2C_DESC_TYPE_ION 0 // Not defined by TI
+
+
+struct ti_i2c_desc {
+ __u8 Type; // Type of descriptor
+ __le16 Size; // Size of data only not including header
+ __u8 CheckSum; // Checksum (8 bit sum of data only)
+ __u8 Data[]; // Data starts here
+} __attribute__((packed));
+
+// for 5152 devices only (type 2 record)
+// for 3410 the version is stored in the WATCHPORT_FIRMWARE_VERSION descriptor
+struct ti_i2c_firmware_rec {
+ __u8 Ver_Major; // Firmware Major version number
+ __u8 Ver_Minor; // Firmware Minor version number
+ __u8 Data[]; // Download starts here
+} __attribute__((packed));
+
+
+struct watchport_firmware_version {
+// Added 2 bytes for version number
+ __u8 Version_Major; // Download Version (for Watchport)
+ __u8 Version_Minor;
+} __attribute__((packed));
+
+
+// Structure of header of download image in fw_down.h
+struct ti_i2c_image_header {
+ __le16 Length;
+ __u8 CheckSum;
+} __attribute__((packed));
+
+struct ti_basic_descriptor {
+ __u8 Power; // Self powered
+ // bit 7: 1 - power switching supported
+ // 0 - power switching not supported
+ //
+ // bit 0: 1 - self powered
+ // 0 - bus powered
+ //
+ //
+ __u16 HubVid; // VID HUB
+ __u16 HubPid; // PID HUB
+ __u16 DevPid; // PID Edgeport
+ __u8 HubTime; // Time for power on to power good
+ __u8 HubCurrent; // HUB Current = 100ma
+} __attribute__((packed));
+
+
+// CPU / Board Rev Definitions
+#define TI_CPU_REV_5052 2 // 5052 based edgeports
+#define TI_CPU_REV_3410 3 // 3410 based edgeports
+
+#define TI_BOARD_REV_TI_EP 0 // Basic ti based edgeport
+#define TI_BOARD_REV_COMPACT 1 // Compact board
+#define TI_BOARD_REV_WATCHPORT 2 // Watchport
+
+
+#define TI_GET_CPU_REVISION(x) (__u8)((((x)>>4)&0x0f))
+#define TI_GET_BOARD_REVISION(x) (__u8)(((x)&0x0f))
+
+#define TI_I2C_SIZE_MASK 0x1f // 5 bits
+#define TI_GET_I2C_SIZE(x) ((((x) & TI_I2C_SIZE_MASK)+1)*256)
+
+#define TI_MAX_I2C_SIZE (16 * 1024)
+
+#define TI_MANUF_VERSION_0 0
+
+// IonConig2 flags
+#define TI_CONFIG2_RS232 0x01
+#define TI_CONFIG2_RS422 0x02
+#define TI_CONFIG2_RS485 0x04
+#define TI_CONFIG2_SWITCHABLE 0x08
+
+#define TI_CONFIG2_WATCHPORT 0x10
+
+
+struct edge_ti_manuf_descriptor {
+ __u8 IonConfig; // Config byte for ION manufacturing use
+ __u8 IonConfig2; // Expansion
+ __u8 Version; // Version
+ __u8 CpuRev_BoardRev; // CPU revision level (0xF0) and Board Rev Level (0x0F)
+ __u8 NumPorts; // Number of ports for this UMP
+ __u8 NumVirtualPorts; // Number of Virtual ports
+ __u8 HubConfig1; // Used to configure the Hub
+ __u8 HubConfig2; // Used to configure the Hub
+ __u8 TotalPorts; // Total Number of Com Ports for the entire device (All UMPs)
+ __u8 Reserved; // Reserved
+} __attribute__((packed));
+
+
+#endif // if !defined(_USBVEND_H)
diff --git a/drivers/usb/serial/ipaq.c b/drivers/usb/serial/ipaq.c
new file mode 100644
index 000000000..c01bdbcb3
--- /dev/null
+++ b/drivers/usb/serial/ipaq.c
@@ -0,0 +1,604 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * USB Compaq iPAQ driver
+ *
+ * Copyright (C) 2001 - 2002
+ * Ganesh Varadarajan <ganesh@veritas.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#define KP_RETRIES 100
+
+#define DRIVER_AUTHOR "Ganesh Varadarajan <ganesh@veritas.com>"
+#define DRIVER_DESC "USB PocketPC PDA driver"
+
+static int connect_retries = KP_RETRIES;
+static int initial_wait;
+
+/* Function prototypes for an ipaq */
+static int ipaq_open(struct tty_struct *tty,
+ struct usb_serial_port *port);
+static int ipaq_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds);
+static int ipaq_startup(struct usb_serial *serial);
+
+static const struct usb_device_id ipaq_id_table[] = {
+ { USB_DEVICE(0x0104, 0x00BE) }, /* Socket USB Sync */
+ { USB_DEVICE(0x03F0, 0x1016) }, /* HP USB Sync */
+ { USB_DEVICE(0x03F0, 0x1116) }, /* HP USB Sync 1611 */
+ { USB_DEVICE(0x03F0, 0x1216) }, /* HP USB Sync 1612 */
+ { USB_DEVICE(0x03F0, 0x2016) }, /* HP USB Sync 1620 */
+ { USB_DEVICE(0x03F0, 0x2116) }, /* HP USB Sync 1621 */
+ { USB_DEVICE(0x03F0, 0x2216) }, /* HP USB Sync 1622 */
+ { USB_DEVICE(0x03F0, 0x3016) }, /* HP USB Sync 1630 */
+ { USB_DEVICE(0x03F0, 0x3116) }, /* HP USB Sync 1631 */
+ { USB_DEVICE(0x03F0, 0x3216) }, /* HP USB Sync 1632 */
+ { USB_DEVICE(0x03F0, 0x4016) }, /* HP USB Sync 1640 */
+ { USB_DEVICE(0x03F0, 0x4116) }, /* HP USB Sync 1641 */
+ { USB_DEVICE(0x03F0, 0x4216) }, /* HP USB Sync 1642 */
+ { USB_DEVICE(0x03F0, 0x5016) }, /* HP USB Sync 1650 */
+ { USB_DEVICE(0x03F0, 0x5116) }, /* HP USB Sync 1651 */
+ { USB_DEVICE(0x03F0, 0x5216) }, /* HP USB Sync 1652 */
+ { USB_DEVICE(0x0409, 0x00D5) }, /* NEC USB Sync */
+ { USB_DEVICE(0x0409, 0x00D6) }, /* NEC USB Sync */
+ { USB_DEVICE(0x0409, 0x00D7) }, /* NEC USB Sync */
+ { USB_DEVICE(0x0409, 0x8024) }, /* NEC USB Sync */
+ { USB_DEVICE(0x0409, 0x8025) }, /* NEC USB Sync */
+ { USB_DEVICE(0x043E, 0x9C01) }, /* LGE USB Sync */
+ { USB_DEVICE(0x045E, 0x00CE) }, /* Microsoft USB Sync */
+ { USB_DEVICE(0x045E, 0x0400) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0401) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0402) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0403) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0404) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0405) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0406) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0407) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0408) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0409) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x040A) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x040B) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x040C) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x040D) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x040E) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x040F) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0410) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0411) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0412) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0413) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0414) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0415) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0416) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0417) }, /* Windows Powered Pocket PC 2002 */
+ { USB_DEVICE(0x045E, 0x0432) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0433) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0434) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0435) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0436) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0437) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0438) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0439) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x043A) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x043B) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x043C) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x043D) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x043E) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x043F) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0440) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0441) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0442) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0443) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0444) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0445) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0446) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0447) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0448) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0449) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x044A) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x044B) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x044C) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x044D) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x044E) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x044F) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0450) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0451) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0452) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0453) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0454) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0455) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0456) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0457) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0458) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0459) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x045A) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x045B) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x045C) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x045D) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x045E) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x045F) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0460) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0461) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0462) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0463) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0464) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0465) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0466) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0467) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0468) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0469) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x046A) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x046B) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x046C) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x046D) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x046E) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x046F) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0470) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0471) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0472) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0473) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0474) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0475) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0476) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0477) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0478) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x0479) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x047A) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x047B) }, /* Windows Powered Pocket PC 2003 */
+ { USB_DEVICE(0x045E, 0x04C8) }, /* Windows Powered Smartphone 2002 */
+ { USB_DEVICE(0x045E, 0x04C9) }, /* Windows Powered Smartphone 2002 */
+ { USB_DEVICE(0x045E, 0x04CA) }, /* Windows Powered Smartphone 2002 */
+ { USB_DEVICE(0x045E, 0x04CB) }, /* Windows Powered Smartphone 2002 */
+ { USB_DEVICE(0x045E, 0x04CC) }, /* Windows Powered Smartphone 2002 */
+ { USB_DEVICE(0x045E, 0x04CD) }, /* Windows Powered Smartphone 2002 */
+ { USB_DEVICE(0x045E, 0x04CE) }, /* Windows Powered Smartphone 2002 */
+ { USB_DEVICE(0x045E, 0x04D7) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04D8) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04D9) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04DA) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04DB) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04DC) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04DD) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04DE) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04DF) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04E0) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04E1) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04E2) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04E3) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04E4) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04E5) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04E6) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04E7) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04E8) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04E9) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x045E, 0x04EA) }, /* Windows Powered Smartphone 2003 */
+ { USB_DEVICE(0x049F, 0x0003) }, /* Compaq iPAQ USB Sync */
+ { USB_DEVICE(0x049F, 0x0032) }, /* Compaq iPAQ USB Sync */
+ { USB_DEVICE(0x04A4, 0x0014) }, /* Hitachi USB Sync */
+ { USB_DEVICE(0x04AD, 0x0301) }, /* USB Sync 0301 */
+ { USB_DEVICE(0x04AD, 0x0302) }, /* USB Sync 0302 */
+ { USB_DEVICE(0x04AD, 0x0303) }, /* USB Sync 0303 */
+ { USB_DEVICE(0x04AD, 0x0306) }, /* GPS Pocket PC USB Sync */
+ { USB_DEVICE(0x04B7, 0x0531) }, /* MyGuide 7000 XL USB Sync */
+ { USB_DEVICE(0x04C5, 0x1058) }, /* FUJITSU USB Sync */
+ { USB_DEVICE(0x04C5, 0x1079) }, /* FUJITSU USB Sync */
+ { USB_DEVICE(0x04DA, 0x2500) }, /* Panasonic USB Sync */
+ { USB_DEVICE(0x04DD, 0x9102) }, /* SHARP WS003SH USB Modem */
+ { USB_DEVICE(0x04DD, 0x9121) }, /* SHARP WS004SH USB Modem */
+ { USB_DEVICE(0x04DD, 0x9123) }, /* SHARP WS007SH USB Modem */
+ { USB_DEVICE(0x04DD, 0x9151) }, /* SHARP S01SH USB Modem */
+ { USB_DEVICE(0x04DD, 0x91AC) }, /* SHARP WS011SH USB Modem */
+ { USB_DEVICE(0x04E8, 0x5F00) }, /* Samsung NEXiO USB Sync */
+ { USB_DEVICE(0x04E8, 0x5F01) }, /* Samsung NEXiO USB Sync */
+ { USB_DEVICE(0x04E8, 0x5F02) }, /* Samsung NEXiO USB Sync */
+ { USB_DEVICE(0x04E8, 0x5F03) }, /* Samsung NEXiO USB Sync */
+ { USB_DEVICE(0x04E8, 0x5F04) }, /* Samsung NEXiO USB Sync */
+ { USB_DEVICE(0x04E8, 0x6611) }, /* Samsung MITs USB Sync */
+ { USB_DEVICE(0x04E8, 0x6613) }, /* Samsung MITs USB Sync */
+ { USB_DEVICE(0x04E8, 0x6615) }, /* Samsung MITs USB Sync */
+ { USB_DEVICE(0x04E8, 0x6617) }, /* Samsung MITs USB Sync */
+ { USB_DEVICE(0x04E8, 0x6619) }, /* Samsung MITs USB Sync */
+ { USB_DEVICE(0x04E8, 0x661B) }, /* Samsung MITs USB Sync */
+ { USB_DEVICE(0x04E8, 0x662E) }, /* Samsung MITs USB Sync */
+ { USB_DEVICE(0x04E8, 0x6630) }, /* Samsung MITs USB Sync */
+ { USB_DEVICE(0x04E8, 0x6632) }, /* Samsung MITs USB Sync */
+ { USB_DEVICE(0x04f1, 0x3011) }, /* JVC USB Sync */
+ { USB_DEVICE(0x04F1, 0x3012) }, /* JVC USB Sync */
+ { USB_DEVICE(0x0502, 0x1631) }, /* c10 Series */
+ { USB_DEVICE(0x0502, 0x1632) }, /* c20 Series */
+ { USB_DEVICE(0x0502, 0x16E1) }, /* Acer n10 Handheld USB Sync */
+ { USB_DEVICE(0x0502, 0x16E2) }, /* Acer n20 Handheld USB Sync */
+ { USB_DEVICE(0x0502, 0x16E3) }, /* Acer n30 Handheld USB Sync */
+ { USB_DEVICE(0x0536, 0x01A0) }, /* HHP PDT */
+ { USB_DEVICE(0x0543, 0x0ED9) }, /* ViewSonic Color Pocket PC V35 */
+ { USB_DEVICE(0x0543, 0x1527) }, /* ViewSonic Color Pocket PC V36 */
+ { USB_DEVICE(0x0543, 0x1529) }, /* ViewSonic Color Pocket PC V37 */
+ { USB_DEVICE(0x0543, 0x152B) }, /* ViewSonic Color Pocket PC V38 */
+ { USB_DEVICE(0x0543, 0x152E) }, /* ViewSonic Pocket PC */
+ { USB_DEVICE(0x0543, 0x1921) }, /* ViewSonic Communicator Pocket PC */
+ { USB_DEVICE(0x0543, 0x1922) }, /* ViewSonic Smartphone */
+ { USB_DEVICE(0x0543, 0x1923) }, /* ViewSonic Pocket PC V30 */
+ { USB_DEVICE(0x05E0, 0x2000) }, /* Symbol USB Sync */
+ { USB_DEVICE(0x05E0, 0x2001) }, /* Symbol USB Sync 0x2001 */
+ { USB_DEVICE(0x05E0, 0x2002) }, /* Symbol USB Sync 0x2002 */
+ { USB_DEVICE(0x05E0, 0x2003) }, /* Symbol USB Sync 0x2003 */
+ { USB_DEVICE(0x05E0, 0x2004) }, /* Symbol USB Sync 0x2004 */
+ { USB_DEVICE(0x05E0, 0x2005) }, /* Symbol USB Sync 0x2005 */
+ { USB_DEVICE(0x05E0, 0x2006) }, /* Symbol USB Sync 0x2006 */
+ { USB_DEVICE(0x05E0, 0x2007) }, /* Symbol USB Sync 0x2007 */
+ { USB_DEVICE(0x05E0, 0x2008) }, /* Symbol USB Sync 0x2008 */
+ { USB_DEVICE(0x05E0, 0x2009) }, /* Symbol USB Sync 0x2009 */
+ { USB_DEVICE(0x05E0, 0x200A) }, /* Symbol USB Sync 0x200A */
+ { USB_DEVICE(0x067E, 0x1001) }, /* Intermec Mobile Computer */
+ { USB_DEVICE(0x07CF, 0x2001) }, /* CASIO USB Sync 2001 */
+ { USB_DEVICE(0x07CF, 0x2002) }, /* CASIO USB Sync 2002 */
+ { USB_DEVICE(0x07CF, 0x2003) }, /* CASIO USB Sync 2003 */
+ { USB_DEVICE(0x0930, 0x0700) }, /* TOSHIBA USB Sync 0700 */
+ { USB_DEVICE(0x0930, 0x0705) }, /* TOSHIBA Pocket PC e310 */
+ { USB_DEVICE(0x0930, 0x0706) }, /* TOSHIBA Pocket PC e740 */
+ { USB_DEVICE(0x0930, 0x0707) }, /* TOSHIBA Pocket PC e330 Series */
+ { USB_DEVICE(0x0930, 0x0708) }, /* TOSHIBA Pocket PC e350 Series */
+ { USB_DEVICE(0x0930, 0x0709) }, /* TOSHIBA Pocket PC e750 Series */
+ { USB_DEVICE(0x0930, 0x070A) }, /* TOSHIBA Pocket PC e400 Series */
+ { USB_DEVICE(0x0930, 0x070B) }, /* TOSHIBA Pocket PC e800 Series */
+ { USB_DEVICE(0x094B, 0x0001) }, /* Linkup Systems USB Sync */
+ { USB_DEVICE(0x0960, 0x0065) }, /* BCOM USB Sync 0065 */
+ { USB_DEVICE(0x0960, 0x0066) }, /* BCOM USB Sync 0066 */
+ { USB_DEVICE(0x0960, 0x0067) }, /* BCOM USB Sync 0067 */
+ { USB_DEVICE(0x0961, 0x0010) }, /* Portatec USB Sync */
+ { USB_DEVICE(0x099E, 0x0052) }, /* Trimble GeoExplorer */
+ { USB_DEVICE(0x099E, 0x4000) }, /* TDS Data Collector */
+ { USB_DEVICE(0x0B05, 0x4200) }, /* ASUS USB Sync */
+ { USB_DEVICE(0x0B05, 0x4201) }, /* ASUS USB Sync */
+ { USB_DEVICE(0x0B05, 0x4202) }, /* ASUS USB Sync */
+ { USB_DEVICE(0x0B05, 0x420F) }, /* ASUS USB Sync */
+ { USB_DEVICE(0x0B05, 0x9200) }, /* ASUS USB Sync */
+ { USB_DEVICE(0x0B05, 0x9202) }, /* ASUS USB Sync */
+ { USB_DEVICE(0x0BB4, 0x00CE) }, /* HTC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x00CF) }, /* HTC USB Modem */
+ { USB_DEVICE(0x0BB4, 0x0A01) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A02) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A03) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A04) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A05) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A06) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A07) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A08) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A09) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A0A) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A0B) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A0C) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A0D) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A0E) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A0F) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A10) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A11) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A12) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A13) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A14) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A15) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A16) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A17) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A18) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A19) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A1A) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A1B) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A1C) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A1D) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A1E) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A1F) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A20) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A21) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A22) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A23) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A24) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A25) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A26) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A27) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A28) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A29) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A2A) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A2B) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A2C) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A2D) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A2E) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A2F) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A30) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A31) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A32) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A33) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A34) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A35) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A36) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A37) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A38) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A39) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A3A) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A3B) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A3C) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A3D) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A3E) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A3F) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A40) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A41) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A42) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A43) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A44) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A45) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A46) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A47) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A48) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A49) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A4A) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A4B) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A4C) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A4D) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A4E) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A4F) }, /* PocketPC USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A50) }, /* HTC SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A51) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A52) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A53) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A54) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A55) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A56) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A57) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A58) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A59) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A5A) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A5B) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A5C) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A5D) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A5E) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A5F) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A60) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A61) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A62) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A63) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A64) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A65) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A66) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A67) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A68) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A69) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A6A) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A6B) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A6C) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A6D) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A6E) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A6F) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A70) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A71) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A72) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A73) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A74) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A75) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A76) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A77) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A78) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A79) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A7A) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A7B) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A7C) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A7D) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A7E) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A7F) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A80) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A81) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A82) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A83) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A84) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A85) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A86) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A87) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A88) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A89) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A8A) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A8B) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A8C) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A8D) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A8E) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A8F) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A90) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A91) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A92) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A93) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A94) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A95) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A96) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A97) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A98) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A99) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A9A) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A9B) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A9C) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A9D) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A9E) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0A9F) }, /* SmartPhone USB Sync */
+ { USB_DEVICE(0x0BB4, 0x0BCE) }, /* "High Tech Computer Corp" */
+ { USB_DEVICE(0x0BF8, 0x1001) }, /* Fujitsu Siemens Computers USB Sync */
+ { USB_DEVICE(0x0C44, 0x03A2) }, /* Motorola iDEN Smartphone */
+ { USB_DEVICE(0x0C8E, 0x6000) }, /* Cesscom Luxian Series */
+ { USB_DEVICE(0x0CAD, 0x9001) }, /* Motorola PowerPad Pocket PC Device */
+ { USB_DEVICE(0x0F4E, 0x0200) }, /* Freedom Scientific USB Sync */
+ { USB_DEVICE(0x0F98, 0x0201) }, /* Cyberbank USB Sync */
+ { USB_DEVICE(0x0FB8, 0x3001) }, /* Wistron USB Sync */
+ { USB_DEVICE(0x0FB8, 0x3002) }, /* Wistron USB Sync */
+ { USB_DEVICE(0x0FB8, 0x3003) }, /* Wistron USB Sync */
+ { USB_DEVICE(0x0FB8, 0x4001) }, /* Wistron USB Sync */
+ { USB_DEVICE(0x1066, 0x00CE) }, /* E-TEN USB Sync */
+ { USB_DEVICE(0x1066, 0x0300) }, /* E-TEN P3XX Pocket PC */
+ { USB_DEVICE(0x1066, 0x0500) }, /* E-TEN P5XX Pocket PC */
+ { USB_DEVICE(0x1066, 0x0600) }, /* E-TEN P6XX Pocket PC */
+ { USB_DEVICE(0x1066, 0x0700) }, /* E-TEN P7XX Pocket PC */
+ { USB_DEVICE(0x1114, 0x0001) }, /* Psion Teklogix Sync 753x */
+ { USB_DEVICE(0x1114, 0x0004) }, /* Psion Teklogix Sync netBookPro */
+ { USB_DEVICE(0x1114, 0x0006) }, /* Psion Teklogix Sync 7525 */
+ { USB_DEVICE(0x1182, 0x1388) }, /* VES USB Sync */
+ { USB_DEVICE(0x11D9, 0x1002) }, /* Rugged Pocket PC 2003 */
+ { USB_DEVICE(0x11D9, 0x1003) }, /* Rugged Pocket PC 2003 */
+ { USB_DEVICE(0x1231, 0xCE01) }, /* USB Sync 03 */
+ { USB_DEVICE(0x1231, 0xCE02) }, /* USB Sync 03 */
+ { USB_DEVICE(0x1690, 0x0601) }, /* Askey USB Sync */
+ { USB_DEVICE(0x22B8, 0x4204) }, /* Motorola MPx200 Smartphone */
+ { USB_DEVICE(0x22B8, 0x4214) }, /* Motorola MPc GSM */
+ { USB_DEVICE(0x22B8, 0x4224) }, /* Motorola MPx220 Smartphone */
+ { USB_DEVICE(0x22B8, 0x4234) }, /* Motorola MPc CDMA */
+ { USB_DEVICE(0x22B8, 0x4244) }, /* Motorola MPx100 Smartphone */
+ { USB_DEVICE(0x3340, 0x011C) }, /* Mio DigiWalker PPC StrongARM */
+ { USB_DEVICE(0x3340, 0x0326) }, /* Mio DigiWalker 338 */
+ { USB_DEVICE(0x3340, 0x0426) }, /* Mio DigiWalker 338 */
+ { USB_DEVICE(0x3340, 0x043A) }, /* Mio DigiWalker USB Sync */
+ { USB_DEVICE(0x3340, 0x051C) }, /* MiTAC USB Sync 528 */
+ { USB_DEVICE(0x3340, 0x053A) }, /* Mio DigiWalker SmartPhone USB Sync */
+ { USB_DEVICE(0x3340, 0x071C) }, /* MiTAC USB Sync */
+ { USB_DEVICE(0x3340, 0x0B1C) }, /* Generic PPC StrongARM */
+ { USB_DEVICE(0x3340, 0x0E3A) }, /* Generic PPC USB Sync */
+ { USB_DEVICE(0x3340, 0x0F1C) }, /* Itautec USB Sync */
+ { USB_DEVICE(0x3340, 0x0F3A) }, /* Generic SmartPhone USB Sync */
+ { USB_DEVICE(0x3340, 0x1326) }, /* Itautec USB Sync */
+ { USB_DEVICE(0x3340, 0x191C) }, /* YAKUMO USB Sync */
+ { USB_DEVICE(0x3340, 0x2326) }, /* Vobis USB Sync */
+ { USB_DEVICE(0x3340, 0x3326) }, /* MEDION Winodws Moble USB Sync */
+ { USB_DEVICE(0x3708, 0x20CE) }, /* Legend USB Sync */
+ { USB_DEVICE(0x3708, 0x21CE) }, /* Lenovo USB Sync */
+ { USB_DEVICE(0x4113, 0x0210) }, /* Mobile Media Technology USB Sync */
+ { USB_DEVICE(0x4113, 0x0211) }, /* Mobile Media Technology USB Sync */
+ { USB_DEVICE(0x4113, 0x0400) }, /* Mobile Media Technology USB Sync */
+ { USB_DEVICE(0x4113, 0x0410) }, /* Mobile Media Technology USB Sync */
+ { USB_DEVICE(0x413C, 0x4001) }, /* Dell Axim USB Sync */
+ { USB_DEVICE(0x413C, 0x4002) }, /* Dell Axim USB Sync */
+ { USB_DEVICE(0x413C, 0x4003) }, /* Dell Axim USB Sync */
+ { USB_DEVICE(0x413C, 0x4004) }, /* Dell Axim USB Sync */
+ { USB_DEVICE(0x413C, 0x4005) }, /* Dell Axim USB Sync */
+ { USB_DEVICE(0x413C, 0x4006) }, /* Dell Axim USB Sync */
+ { USB_DEVICE(0x413C, 0x4007) }, /* Dell Axim USB Sync */
+ { USB_DEVICE(0x413C, 0x4008) }, /* Dell Axim USB Sync */
+ { USB_DEVICE(0x413C, 0x4009) }, /* Dell Axim USB Sync */
+ { USB_DEVICE(0x4505, 0x0010) }, /* Smartphone */
+ { USB_DEVICE(0x5E04, 0xCE00) }, /* SAGEM Wireless Assistant */
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, ipaq_id_table);
+
+
+/* All of the device info needed for the Compaq iPAQ */
+static struct usb_serial_driver ipaq_device = {
+ .driver = {
+ .name = "ipaq",
+ },
+ .description = "PocketPC PDA",
+ .id_table = ipaq_id_table,
+ .bulk_in_size = 256,
+ .bulk_out_size = 256,
+ .open = ipaq_open,
+ .attach = ipaq_startup,
+ .calc_num_ports = ipaq_calc_num_ports,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &ipaq_device, NULL
+};
+
+static int ipaq_open(struct tty_struct *tty,
+ struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ int result = 0;
+ int retries = connect_retries;
+
+ msleep(1000*initial_wait);
+
+ /*
+ * Send out control message observed in win98 sniffs. Not sure what
+ * it does, but from empirical observations, it seems that the device
+ * will start the chat sequence once one of these messages gets
+ * through. Since this has a reasonably high failure rate, we retry
+ * several times.
+ */
+ while (retries) {
+ retries--;
+ result = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0), 0x22, 0x21,
+ 0x1, 0, NULL, 0, 100);
+ if (!result)
+ break;
+
+ msleep(1000);
+ }
+ if (!retries && result) {
+ dev_err(&port->dev, "%s - failed doing control urb, error %d\n",
+ __func__, result);
+ return result;
+ }
+
+ return usb_serial_generic_open(tty, port);
+}
+
+static int ipaq_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ /*
+ * Some of the devices in ipaq_id_table[] are composite, and we
+ * shouldn't bind to all the interfaces. This test will rule out
+ * some obviously invalid possibilities.
+ */
+ if (epds->num_bulk_in == 0 || epds->num_bulk_out == 0)
+ return -ENODEV;
+
+ /*
+ * A few devices have four endpoints, seemingly Yakuma devices, and
+ * we need the second pair.
+ */
+ if (epds->num_bulk_in > 1 && epds->num_bulk_out > 1) {
+ epds->bulk_in[0] = epds->bulk_in[1];
+ epds->bulk_out[0] = epds->bulk_out[1];
+ }
+
+ /*
+ * Other devices have 3 endpoints, but we only use the first bulk in
+ * and out endpoints.
+ */
+ epds->num_bulk_in = 1;
+ epds->num_bulk_out = 1;
+
+ return 1;
+}
+
+static int ipaq_startup(struct usb_serial *serial)
+{
+ if (serial->dev->actconfig->desc.bConfigurationValue != 1) {
+ /*
+ * FIXME: HP iPaq rx3715, possibly others, have 1 config that
+ * is labeled as 2
+ */
+
+ dev_err(&serial->dev->dev, "active config #%d != 1 ??\n",
+ serial->dev->actconfig->desc.bConfigurationValue);
+ return -ENODEV;
+ }
+
+ return usb_reset_configuration(serial->dev);
+}
+
+module_usb_serial_driver(serial_drivers, ipaq_id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+module_param(connect_retries, int, 0644);
+MODULE_PARM_DESC(connect_retries,
+ "Maximum number of connect retries (one second each)");
+
+module_param(initial_wait, int, 0644);
+MODULE_PARM_DESC(initial_wait,
+ "Time to wait before attempting a connection (in seconds)");
diff --git a/drivers/usb/serial/ipw.c b/drivers/usb/serial/ipw.c
new file mode 100644
index 000000000..5be153f2c
--- /dev/null
+++ b/drivers/usb/serial/ipw.c
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * IPWireless 3G UMTS TDD Modem driver (USB connected)
+ *
+ * Copyright (C) 2004 Roelf Diedericks <roelfd@inet.co.za>
+ * Copyright (C) 2004 Greg Kroah-Hartman <greg@kroah.com>
+ *
+ * All information about the device was acquired using SnoopyPro
+ * on MSFT's O/S, and examing the MSFT drivers' debug output
+ * (insanely left _on_ in the enduser version)
+ *
+ * It was written out of frustration with the IPWireless USB modem
+ * supplied by Axity3G/Sentech South Africa not supporting
+ * Linux whatsoever.
+ *
+ * Nobody provided any proprietary information that was not already
+ * available for this device.
+ *
+ * The modem adheres to the "3GPP TS 27.007 AT command set for 3G
+ * User Equipment (UE)" standard, available from
+ * http://www.3gpp.org/ftp/Specs/html-info/27007.htm
+ *
+ * The code was only tested the IPWireless handheld modem distributed
+ * in South Africa by Sentech.
+ *
+ * It may work for Woosh Inc in .nz too, as it appears they use the
+ * same kit.
+ *
+ * There is still some work to be done in terms of handling
+ * DCD, DTR, RTS, CTS which are currently faked.
+ * It's good enough for PPP at this point. It's based off all kinds of
+ * code found in usb/serial and usb/class
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include "usb-wwan.h"
+
+#define DRIVER_AUTHOR "Roelf Diedericks"
+#define DRIVER_DESC "IPWireless tty driver"
+
+#define IPW_TTY_MAJOR 240 /* real device node major id, experimental range */
+#define IPW_TTY_MINORS 256 /* we support 256 devices, dunno why, it'd be insane :) */
+
+#define USB_IPW_MAGIC 0x6d02 /* magic number for ipw struct */
+
+
+/* Message sizes */
+#define EVENT_BUFFER_SIZE 0xFF
+#define CHAR2INT16(c1, c0) (((u32)((c1) & 0xff) << 8) + (u32)((c0) & 0xff))
+
+/* vendor/product pairs that are known work with this driver*/
+#define IPW_VID 0x0bc3
+#define IPW_PID 0x0001
+
+
+/* Vendor commands: */
+
+/* baud rates */
+enum {
+ ipw_sio_b256000 = 0x000e,
+ ipw_sio_b128000 = 0x001d,
+ ipw_sio_b115200 = 0x0020,
+ ipw_sio_b57600 = 0x0040,
+ ipw_sio_b56000 = 0x0042,
+ ipw_sio_b38400 = 0x0060,
+ ipw_sio_b19200 = 0x00c0,
+ ipw_sio_b14400 = 0x0100,
+ ipw_sio_b9600 = 0x0180,
+ ipw_sio_b4800 = 0x0300,
+ ipw_sio_b2400 = 0x0600,
+ ipw_sio_b1200 = 0x0c00,
+ ipw_sio_b600 = 0x1800
+};
+
+/* data bits */
+#define ipw_dtb_7 0x700
+#define ipw_dtb_8 0x810 /* ok so the define is misleading, I know, but forces 8,n,1 */
+ /* I mean, is there a point to any other setting these days? :) */
+
+/* usb control request types : */
+#define IPW_SIO_RXCTL 0x00 /* control bulk rx channel transmissions, value=1/0 (on/off) */
+#define IPW_SIO_SET_BAUD 0x01 /* set baud, value=requested ipw_sio_bxxxx */
+#define IPW_SIO_SET_LINE 0x03 /* set databits, parity. value=ipw_dtb_x */
+#define IPW_SIO_SET_PIN 0x03 /* set/clear dtr/rts value=ipw_pin_xxx */
+#define IPW_SIO_POLL 0x08 /* get serial port status byte, call with value=0 */
+#define IPW_SIO_INIT 0x11 /* initializes ? value=0 (appears as first thing todo on open) */
+#define IPW_SIO_PURGE 0x12 /* purge all transmissions?, call with value=numchar_to_purge */
+#define IPW_SIO_HANDFLOW 0x13 /* set xon/xoff limits value=0, and a buffer of 0x10 bytes */
+#define IPW_SIO_SETCHARS 0x13 /* set the flowcontrol special chars, value=0, buf=6 bytes, */
+ /* last 2 bytes contain flowcontrol chars e.g. 00 00 00 00 11 13 */
+
+/* values used for request IPW_SIO_SET_PIN */
+#define IPW_PIN_SETDTR 0x101
+#define IPW_PIN_SETRTS 0x202
+#define IPW_PIN_CLRDTR 0x100
+#define IPW_PIN_CLRRTS 0x200 /* unconfirmed */
+
+/* values used for request IPW_SIO_RXCTL */
+#define IPW_RXBULK_ON 1
+#define IPW_RXBULK_OFF 0
+
+/* various 16 byte hardcoded transferbuffers used by flow control */
+#define IPW_BYTES_FLOWINIT { 0x01, 0, 0, 0, 0x40, 0, 0, 0, \
+ 0, 0, 0, 0, 0, 0, 0, 0 }
+
+/* Interpretation of modem status lines */
+/* These need sorting out by individually connecting pins and checking
+ * results. FIXME!
+ * When data is being sent we see 0x30 in the lower byte; this must
+ * contain DSR and CTS ...
+ */
+#define IPW_DSR ((1<<4) | (1<<5))
+#define IPW_CTS ((1<<5) | (1<<4))
+
+#define IPW_WANTS_TO_SEND 0x30
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(IPW_VID, IPW_PID) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static int ipw_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct usb_device *udev = port->serial->dev;
+ struct device *dev = &port->dev;
+ u8 buf_flow_static[16] = IPW_BYTES_FLOWINIT;
+ u8 *buf_flow_init;
+ int result;
+
+ buf_flow_init = kmemdup(buf_flow_static, 16, GFP_KERNEL);
+ if (!buf_flow_init)
+ return -ENOMEM;
+
+ /* --1: Tell the modem to initialize (we think) From sniffs this is
+ * always the first thing that gets sent to the modem during
+ * opening of the device */
+ dev_dbg(dev, "%s: Sending SIO_INIT (we guess)\n", __func__);
+ result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ IPW_SIO_INIT,
+ USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT,
+ 0,
+ 0, /* index */
+ NULL,
+ 0,
+ 100000);
+ if (result < 0)
+ dev_err(dev, "Init of modem failed (error = %d)\n", result);
+
+ /* reset the bulk pipes */
+ usb_clear_halt(udev, usb_rcvbulkpipe(udev, port->bulk_in_endpointAddress));
+ usb_clear_halt(udev, usb_sndbulkpipe(udev, port->bulk_out_endpointAddress));
+
+ /*--2: Start reading from the device */
+ dev_dbg(dev, "%s: setting up bulk read callback\n", __func__);
+ usb_wwan_open(tty, port);
+
+ /*--3: Tell the modem to open the floodgates on the rx bulk channel */
+ dev_dbg(dev, "%s:asking modem for RxRead (RXBULK_ON)\n", __func__);
+ result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ IPW_SIO_RXCTL,
+ USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT,
+ IPW_RXBULK_ON,
+ 0, /* index */
+ NULL,
+ 0,
+ 100000);
+ if (result < 0)
+ dev_err(dev, "Enabling bulk RxRead failed (error = %d)\n", result);
+
+ /*--4: setup the initial flowcontrol */
+ dev_dbg(dev, "%s:setting init flowcontrol (%s)\n", __func__, buf_flow_init);
+ result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ IPW_SIO_HANDFLOW,
+ USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT,
+ 0,
+ 0,
+ buf_flow_init,
+ 0x10,
+ 200000);
+ if (result < 0)
+ dev_err(dev, "initial flowcontrol failed (error = %d)\n", result);
+
+ kfree(buf_flow_init);
+ return 0;
+}
+
+static int ipw_attach(struct usb_serial *serial)
+{
+ struct usb_wwan_intf_private *data;
+
+ data = kzalloc_obj(struct usb_wwan_intf_private);
+ if (!data)
+ return -ENOMEM;
+
+ spin_lock_init(&data->susp_lock);
+ usb_set_serial_data(serial, data);
+ return 0;
+}
+
+static void ipw_release(struct usb_serial *serial)
+{
+ struct usb_wwan_intf_private *data = usb_get_serial_data(serial);
+
+ usb_set_serial_data(serial, NULL);
+ kfree(data);
+}
+
+static void ipw_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct usb_device *udev = port->serial->dev;
+ struct device *dev = &port->dev;
+ int result;
+
+ dev_dbg(dev, "%s: on = %d\n", __func__, on);
+
+ result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ IPW_SIO_SET_PIN,
+ USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT,
+ on ? IPW_PIN_SETDTR : IPW_PIN_CLRDTR,
+ 0,
+ NULL,
+ 0,
+ 200000);
+ if (result < 0)
+ dev_err(dev, "setting dtr failed (error = %d)\n", result);
+
+ result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ IPW_SIO_SET_PIN, USB_TYPE_VENDOR |
+ USB_RECIP_INTERFACE | USB_DIR_OUT,
+ on ? IPW_PIN_SETRTS : IPW_PIN_CLRRTS,
+ 0,
+ NULL,
+ 0,
+ 200000);
+ if (result < 0)
+ dev_err(dev, "setting rts failed (error = %d)\n", result);
+}
+
+static void ipw_close(struct usb_serial_port *port)
+{
+ struct usb_device *udev = port->serial->dev;
+ struct device *dev = &port->dev;
+ int result;
+
+ /*--3: purge */
+ dev_dbg(dev, "%s:sending purge\n", __func__);
+ result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ IPW_SIO_PURGE, USB_TYPE_VENDOR |
+ USB_RECIP_INTERFACE | USB_DIR_OUT,
+ 0x03,
+ 0,
+ NULL,
+ 0,
+ 200000);
+ if (result < 0)
+ dev_err(dev, "purge failed (error = %d)\n", result);
+
+
+ /* send RXBULK_off (tell modem to stop transmitting bulk data on
+ rx chan) */
+ result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ IPW_SIO_RXCTL,
+ USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT,
+ IPW_RXBULK_OFF,
+ 0, /* index */
+ NULL,
+ 0,
+ 100000);
+
+ if (result < 0)
+ dev_err(dev, "Disabling bulk RxRead failed (error = %d)\n", result);
+
+ usb_wwan_close(port);
+}
+
+static struct usb_serial_driver ipw_device = {
+ .driver = {
+ .name = "ipw",
+ },
+ .description = "IPWireless converter",
+ .id_table = id_table,
+ .num_ports = 1,
+ .open = ipw_open,
+ .close = ipw_close,
+ .attach = ipw_attach,
+ .release = ipw_release,
+ .port_probe = usb_wwan_port_probe,
+ .port_remove = usb_wwan_port_remove,
+ .dtr_rts = ipw_dtr_rts,
+ .write = usb_wwan_write,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &ipw_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+/* Module information */
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/ir-usb.c b/drivers/usb/serial/ir-usb.c
new file mode 100644
index 000000000..aac845397
--- /dev/null
+++ b/drivers/usb/serial/ir-usb.c
@@ -0,0 +1,485 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * USB IR Dongle driver
+ *
+ * Copyright (C) 2001-2002 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2002 Gary Brubaker (xavyer@ix.netcom.com)
+ * Copyright (C) 2010 Johan Hovold (jhovold@gmail.com)
+ *
+ * This driver allows a USB IrDA device to be used as a "dumb" serial device.
+ * This can be useful if you do not have access to a full IrDA stack on the
+ * other side of the connection. If you do have an IrDA stack on both devices,
+ * please use the usb-irda driver, as it contains the proper error checking and
+ * other goodness of a full IrDA stack.
+ *
+ * Portions of this driver were taken from drivers/net/irda/irda-usb.c, which
+ * was written by Roman Weissgaerber <weissg@vienna.at>, Dag Brattli
+ * <dag@brattli.net>, and Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/usb/irda.h>
+
+#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Johan Hovold <jhovold@gmail.com>"
+#define DRIVER_DESC "USB IR Dongle driver"
+
+/* if overridden by the user, then use their value for the size of the read and
+ * write urbs */
+static int buffer_size;
+
+/* if overridden by the user, then use the specified number of XBOFs */
+static int xbof = -1;
+
+static int ir_startup (struct usb_serial *serial);
+static int ir_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count);
+static unsigned int ir_write_room(struct tty_struct *tty);
+static void ir_write_bulk_callback(struct urb *urb);
+static void ir_process_read_urb(struct urb *urb);
+static void ir_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+
+/* Not that this lot means you can only have one per system */
+static u8 ir_baud;
+static u8 ir_xbof;
+static u8 ir_add_bof;
+
+static const struct usb_device_id ir_id_table[] = {
+ { USB_DEVICE(0x050f, 0x0180) }, /* KC Technology, KC-180 */
+ { USB_DEVICE(0x08e9, 0x0100) }, /* XTNDAccess */
+ { USB_DEVICE(0x09c4, 0x0011) }, /* ACTiSys ACT-IR2000U */
+ { USB_INTERFACE_INFO(USB_CLASS_APP_SPEC, USB_SUBCLASS_IRDA, 0) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, ir_id_table);
+
+static struct usb_serial_driver ir_device = {
+ .driver = {
+ .name = "ir-usb",
+ },
+ .description = "IR Dongle",
+ .id_table = ir_id_table,
+ .num_ports = 1,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ .set_termios = ir_set_termios,
+ .attach = ir_startup,
+ .write = ir_write,
+ .write_room = ir_write_room,
+ .write_bulk_callback = ir_write_bulk_callback,
+ .process_read_urb = ir_process_read_urb,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &ir_device, NULL
+};
+
+static inline void irda_usb_dump_class_desc(struct usb_serial *serial,
+ struct usb_irda_cs_descriptor *desc)
+{
+ struct device *dev = &serial->dev->dev;
+
+ dev_dbg(dev, "bLength=%x\n", desc->bLength);
+ dev_dbg(dev, "bDescriptorType=%x\n", desc->bDescriptorType);
+ dev_dbg(dev, "bcdSpecRevision=%x\n", __le16_to_cpu(desc->bcdSpecRevision));
+ dev_dbg(dev, "bmDataSize=%x\n", desc->bmDataSize);
+ dev_dbg(dev, "bmWindowSize=%x\n", desc->bmWindowSize);
+ dev_dbg(dev, "bmMinTurnaroundTime=%d\n", desc->bmMinTurnaroundTime);
+ dev_dbg(dev, "wBaudRate=%x\n", __le16_to_cpu(desc->wBaudRate));
+ dev_dbg(dev, "bmAdditionalBOFs=%x\n", desc->bmAdditionalBOFs);
+ dev_dbg(dev, "bIrdaRateSniff=%x\n", desc->bIrdaRateSniff);
+ dev_dbg(dev, "bMaxUnicastList=%x\n", desc->bMaxUnicastList);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irda_usb_find_class_desc(dev, ifnum)
+ *
+ * Returns instance of IrDA class descriptor, or NULL if not found
+ *
+ * The class descriptor is some extra info that IrDA USB devices will
+ * offer to us, describing their IrDA characteristics. We will use that in
+ * irda_usb_init_qos()
+ *
+ * Based on the same function in drivers/net/irda/irda-usb.c
+ */
+static struct usb_irda_cs_descriptor *
+irda_usb_find_class_desc(struct usb_serial *serial, unsigned int ifnum)
+{
+ struct usb_device *dev = serial->dev;
+ struct usb_irda_cs_descriptor *desc;
+ int ret;
+
+ desc = kzalloc_obj(*desc);
+ if (!desc)
+ return NULL;
+
+ ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+ USB_REQ_CS_IRDA_GET_CLASS_DESC,
+ USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, ifnum, desc, sizeof(*desc), 1000);
+
+ dev_dbg(&serial->dev->dev, "%s - ret=%d\n", __func__, ret);
+ if (ret < (int)sizeof(*desc)) {
+ dev_dbg(&serial->dev->dev,
+ "%s - class descriptor read %s (%d)\n", __func__,
+ (ret < 0) ? "failed" : "too short", ret);
+ goto error;
+ }
+ if (desc->bDescriptorType != USB_DT_CS_IRDA) {
+ dev_dbg(&serial->dev->dev, "%s - bad class descriptor type\n",
+ __func__);
+ goto error;
+ }
+
+ irda_usb_dump_class_desc(serial, desc);
+ return desc;
+
+error:
+ kfree(desc);
+ return NULL;
+}
+
+static u8 ir_xbof_change(u8 xbof)
+{
+ u8 result;
+
+ /* reference irda-usb.c */
+ switch (xbof) {
+ case 48:
+ result = 0x10;
+ break;
+ case 28:
+ case 24:
+ result = 0x20;
+ break;
+ default:
+ case 12:
+ result = 0x30;
+ break;
+ case 5:
+ case 6:
+ result = 0x40;
+ break;
+ case 3:
+ result = 0x50;
+ break;
+ case 2:
+ result = 0x60;
+ break;
+ case 1:
+ result = 0x70;
+ break;
+ case 0:
+ result = 0x80;
+ break;
+ }
+
+ return(result);
+}
+
+static int ir_startup(struct usb_serial *serial)
+{
+ struct usb_irda_cs_descriptor *irda_desc;
+ int rates;
+
+ irda_desc = irda_usb_find_class_desc(serial, 0);
+ if (!irda_desc) {
+ dev_err(&serial->dev->dev,
+ "IRDA class descriptor not found, device not bound\n");
+ return -ENODEV;
+ }
+
+ rates = le16_to_cpu(irda_desc->wBaudRate);
+
+ dev_dbg(&serial->dev->dev,
+ "%s - Baud rates supported:%s%s%s%s%s%s%s%s%s\n",
+ __func__,
+ (rates & USB_IRDA_BR_2400) ? " 2400" : "",
+ (rates & USB_IRDA_BR_9600) ? " 9600" : "",
+ (rates & USB_IRDA_BR_19200) ? " 19200" : "",
+ (rates & USB_IRDA_BR_38400) ? " 38400" : "",
+ (rates & USB_IRDA_BR_57600) ? " 57600" : "",
+ (rates & USB_IRDA_BR_115200) ? " 115200" : "",
+ (rates & USB_IRDA_BR_576000) ? " 576000" : "",
+ (rates & USB_IRDA_BR_1152000) ? " 1152000" : "",
+ (rates & USB_IRDA_BR_4000000) ? " 4000000" : "");
+
+ switch (irda_desc->bmAdditionalBOFs) {
+ case USB_IRDA_AB_48:
+ ir_add_bof = 48;
+ break;
+ case USB_IRDA_AB_24:
+ ir_add_bof = 24;
+ break;
+ case USB_IRDA_AB_12:
+ ir_add_bof = 12;
+ break;
+ case USB_IRDA_AB_6:
+ ir_add_bof = 6;
+ break;
+ case USB_IRDA_AB_3:
+ ir_add_bof = 3;
+ break;
+ case USB_IRDA_AB_2:
+ ir_add_bof = 2;
+ break;
+ case USB_IRDA_AB_1:
+ ir_add_bof = 1;
+ break;
+ case USB_IRDA_AB_0:
+ ir_add_bof = 0;
+ break;
+ default:
+ break;
+ }
+
+ kfree(irda_desc);
+
+ return 0;
+}
+
+static int ir_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count)
+{
+ struct urb *urb = NULL;
+ unsigned long flags;
+ int ret;
+
+ if (port->bulk_out_size == 0)
+ return -EINVAL;
+
+ if (count == 0)
+ return 0;
+
+ count = min(count, port->bulk_out_size - 1);
+
+ spin_lock_irqsave(&port->lock, flags);
+ if (__test_and_clear_bit(0, &port->write_urbs_free)) {
+ urb = port->write_urbs[0];
+ port->tx_bytes += count;
+ }
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ if (!urb)
+ return 0;
+
+ /*
+ * The first byte of the packet we send to the device contains an
+ * outbound header which indicates an additional number of BOFs and
+ * a baud rate change.
+ *
+ * See section 5.4.2.2 of the USB IrDA spec.
+ */
+ *(u8 *)urb->transfer_buffer = ir_xbof | ir_baud;
+
+ memcpy(urb->transfer_buffer + 1, buf, count);
+
+ urb->transfer_buffer_length = count + 1;
+ urb->transfer_flags = URB_ZERO_PACKET;
+
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret) {
+ dev_err(&port->dev, "failed to submit write urb: %d\n", ret);
+
+ spin_lock_irqsave(&port->lock, flags);
+ __set_bit(0, &port->write_urbs_free);
+ port->tx_bytes -= count;
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ return ret;
+ }
+
+ return count;
+}
+
+static void ir_write_bulk_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ int status = urb->status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ __set_bit(0, &port->write_urbs_free);
+ port->tx_bytes -= urb->transfer_buffer_length - 1;
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ switch (status) {
+ case 0:
+ break;
+ case -ENOENT:
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ dev_dbg(&port->dev, "write urb stopped: %d\n", status);
+ return;
+ case -EPIPE:
+ dev_err(&port->dev, "write urb stopped: %d\n", status);
+ return;
+ default:
+ dev_err(&port->dev, "nonzero write-urb status: %d\n", status);
+ break;
+ }
+
+ usb_serial_port_softint(port);
+}
+
+static unsigned int ir_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned int count = 0;
+
+ if (port->bulk_out_size == 0)
+ return 0;
+
+ if (test_bit(0, &port->write_urbs_free))
+ count = port->bulk_out_size - 1;
+
+ return count;
+}
+
+static void ir_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+
+ if (!urb->actual_length)
+ return;
+ /*
+ * The first byte of the packet we get from the device
+ * contains a busy indicator and baud rate change.
+ * See section 5.4.1.2 of the USB IrDA spec.
+ */
+ if (*data & 0x0f)
+ ir_baud = *data & 0x0f;
+
+ if (urb->actual_length == 1)
+ return;
+
+ tty_insert_flip_string(&port->port, data + 1, urb->actual_length - 1);
+ tty_flip_buffer_push(&port->port);
+}
+
+static void ir_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct usb_device *udev = port->serial->dev;
+ unsigned char *transfer_buffer;
+ int actual_length;
+ speed_t baud;
+ int ir_baud;
+ int ret;
+
+ baud = tty_get_baud_rate(tty);
+
+ /*
+ * FIXME, we should compare the baud request against the
+ * capability stated in the IR header that we got in the
+ * startup function.
+ */
+
+ switch (baud) {
+ case 2400:
+ ir_baud = USB_IRDA_LS_2400;
+ break;
+ case 9600:
+ ir_baud = USB_IRDA_LS_9600;
+ break;
+ case 19200:
+ ir_baud = USB_IRDA_LS_19200;
+ break;
+ case 38400:
+ ir_baud = USB_IRDA_LS_38400;
+ break;
+ case 57600:
+ ir_baud = USB_IRDA_LS_57600;
+ break;
+ case 115200:
+ ir_baud = USB_IRDA_LS_115200;
+ break;
+ case 576000:
+ ir_baud = USB_IRDA_LS_576000;
+ break;
+ case 1152000:
+ ir_baud = USB_IRDA_LS_1152000;
+ break;
+ case 4000000:
+ ir_baud = USB_IRDA_LS_4000000;
+ break;
+ default:
+ ir_baud = USB_IRDA_LS_9600;
+ baud = 9600;
+ }
+
+ if (xbof == -1)
+ ir_xbof = ir_xbof_change(ir_add_bof);
+ else
+ ir_xbof = ir_xbof_change(xbof) ;
+
+ /* Only speed changes are supported */
+ tty_termios_copy_hw(&tty->termios, old_termios);
+ tty_encode_baud_rate(tty, baud, baud);
+
+ /*
+ * send the baud change out on an "empty" data packet
+ */
+ transfer_buffer = kmalloc(1, GFP_KERNEL);
+ if (!transfer_buffer)
+ return;
+
+ *transfer_buffer = ir_xbof | ir_baud;
+
+ ret = usb_bulk_msg(udev,
+ usb_sndbulkpipe(udev, port->bulk_out_endpointAddress),
+ transfer_buffer, 1, &actual_length, 5000);
+ if (ret || actual_length != 1) {
+ if (!ret)
+ ret = -EIO;
+ dev_err(&port->dev, "failed to change line speed: %d\n", ret);
+ }
+
+ kfree(transfer_buffer);
+}
+
+static int __init ir_init(void)
+{
+ if (buffer_size) {
+ ir_device.bulk_in_size = buffer_size;
+ ir_device.bulk_out_size = buffer_size;
+ }
+
+ return usb_serial_register_drivers(serial_drivers, KBUILD_MODNAME, ir_id_table);
+}
+
+static void __exit ir_exit(void)
+{
+ usb_serial_deregister_drivers(serial_drivers);
+}
+
+
+module_init(ir_init);
+module_exit(ir_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+module_param(xbof, int, 0);
+MODULE_PARM_DESC(xbof, "Force specific number of XBOFs");
+module_param(buffer_size, int, 0);
+MODULE_PARM_DESC(buffer_size, "Size of the transfer buffers");
+
diff --git a/drivers/usb/serial/iuu_phoenix.c b/drivers/usb/serial/iuu_phoenix.c
new file mode 100644
index 000000000..3b1f7546f
--- /dev/null
+++ b/drivers/usb/serial/iuu_phoenix.c
@@ -0,0 +1,1205 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Infinity Unlimited USB Phoenix driver
+ *
+ * Copyright (C) 2010 James Courtier-Dutton (James@superbug.co.uk)
+
+ * Copyright (C) 2007 Alain Degreffe (eczema@ecze.com)
+ *
+ * Original code taken from iuutool (Copyright (C) 2006 Juan Carlos Borrás)
+ *
+ * And tested with help of WB Electronics
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include "iuu_phoenix.h"
+#include <linux/random.h>
+
+#define DRIVER_DESC "Infinity USB Unlimited Phoenix driver"
+
+static const struct usb_device_id id_table[] = {
+ {USB_DEVICE(IUU_USB_VENDOR_ID, IUU_USB_PRODUCT_ID)},
+ {} /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+/* turbo parameter */
+static int boost = 100;
+static int clockmode = 1;
+static int cdmode = 1;
+static int iuu_cardin;
+static int iuu_cardout;
+static bool xmas;
+static int vcc_default = 5;
+
+static int iuu_create_sysfs_attrs(struct usb_serial_port *port);
+static int iuu_remove_sysfs_attrs(struct usb_serial_port *port);
+static void read_rxcmd_callback(struct urb *urb);
+
+struct iuu_private {
+ spinlock_t lock; /* store irq state */
+ u8 line_status;
+ int tiostatus; /* store IUART SIGNAL for tiocmget call */
+ u8 reset; /* if 1 reset is needed */
+ int poll; /* number of poll */
+ u8 *writebuf; /* buffer for writing to device */
+ int writelen; /* num of byte to write to device */
+ u8 *buf; /* used for initialize speed */
+ u8 len;
+ int vcc; /* vcc (either 3 or 5 V) */
+ u32 boost;
+ u32 clk;
+};
+
+static int iuu_port_probe(struct usb_serial_port *port)
+{
+ struct iuu_private *priv;
+ int ret;
+
+ priv = kzalloc_obj(struct iuu_private);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->buf = kzalloc(256, GFP_KERNEL);
+ if (!priv->buf) {
+ kfree(priv);
+ return -ENOMEM;
+ }
+
+ priv->writebuf = kzalloc(256, GFP_KERNEL);
+ if (!priv->writebuf) {
+ kfree(priv->buf);
+ kfree(priv);
+ return -ENOMEM;
+ }
+
+ priv->vcc = vcc_default;
+ spin_lock_init(&priv->lock);
+
+ usb_set_serial_port_data(port, priv);
+
+ ret = iuu_create_sysfs_attrs(port);
+ if (ret) {
+ kfree(priv->writebuf);
+ kfree(priv->buf);
+ kfree(priv);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void iuu_port_remove(struct usb_serial_port *port)
+{
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+
+ iuu_remove_sysfs_attrs(port);
+ kfree(priv->writebuf);
+ kfree(priv->buf);
+ kfree(priv);
+}
+
+static int iuu_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ /* FIXME: locking on tiomstatus */
+ dev_dbg(&port->dev, "%s msg : SET = 0x%04x, CLEAR = 0x%04x\n",
+ __func__, set, clear);
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ if ((set & TIOCM_RTS) && !(priv->tiostatus == TIOCM_RTS)) {
+ dev_dbg(&port->dev, "%s TIOCMSET RESET called !!!\n", __func__);
+ priv->reset = 1;
+ }
+ if (set & TIOCM_RTS)
+ priv->tiostatus = TIOCM_RTS;
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return 0;
+}
+
+/* This is used to provide a carrier detect mechanism
+ * When a card is present, the response is 0x00
+ * When no card , the reader respond with TIOCM_CD
+ * This is known as CD autodetect mechanism
+ */
+static int iuu_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ rc = priv->tiostatus;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return rc;
+}
+
+static void iuu_rxcmd(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ int status = urb->status;
+
+ if (status) {
+ dev_dbg(&port->dev, "%s - status = %d\n", __func__, status);
+ /* error stop all */
+ return;
+ }
+
+
+ memset(port->write_urb->transfer_buffer, IUU_UART_RX, 1);
+ usb_fill_bulk_urb(port->write_urb, port->serial->dev,
+ usb_sndbulkpipe(port->serial->dev,
+ port->bulk_out_endpointAddress),
+ port->write_urb->transfer_buffer, 1,
+ read_rxcmd_callback, port);
+ usb_submit_urb(port->write_urb, GFP_ATOMIC);
+}
+
+static int iuu_reset(struct usb_serial_port *port, u8 wt)
+{
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+ int result;
+ char *buf_ptr = port->write_urb->transfer_buffer;
+
+ /* Prepare the reset sequence */
+
+ *buf_ptr++ = IUU_RST_SET;
+ *buf_ptr++ = IUU_DELAY_MS;
+ *buf_ptr++ = wt;
+ *buf_ptr = IUU_RST_CLEAR;
+
+ /* send the sequence */
+
+ usb_fill_bulk_urb(port->write_urb,
+ port->serial->dev,
+ usb_sndbulkpipe(port->serial->dev,
+ port->bulk_out_endpointAddress),
+ port->write_urb->transfer_buffer, 4, iuu_rxcmd, port);
+ result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ priv->reset = 0;
+ return result;
+}
+
+/* Status Function
+ * Return value is
+ * 0x00 = no card
+ * 0x01 = smartcard
+ * 0x02 = sim card
+ */
+static void iuu_update_status_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+ u8 *st;
+ int status = urb->status;
+
+ if (status) {
+ dev_dbg(&port->dev, "%s - status = %d\n", __func__, status);
+ /* error stop all */
+ return;
+ }
+
+ st = urb->transfer_buffer;
+ dev_dbg(&port->dev, "%s - enter\n", __func__);
+ if (urb->actual_length == 1) {
+ switch (st[0]) {
+ case 0x1:
+ priv->tiostatus = iuu_cardout;
+ break;
+ case 0x0:
+ priv->tiostatus = iuu_cardin;
+ break;
+ default:
+ priv->tiostatus = iuu_cardin;
+ }
+ }
+ iuu_rxcmd(urb);
+}
+
+static void iuu_status_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ int status = urb->status;
+
+ dev_dbg(&port->dev, "%s - status = %d\n", __func__, status);
+ usb_fill_bulk_urb(port->read_urb, port->serial->dev,
+ usb_rcvbulkpipe(port->serial->dev,
+ port->bulk_in_endpointAddress),
+ port->read_urb->transfer_buffer, 256,
+ iuu_update_status_callback, port);
+ usb_submit_urb(port->read_urb, GFP_ATOMIC);
+}
+
+static int iuu_status(struct usb_serial_port *port)
+{
+ int result;
+
+ memset(port->write_urb->transfer_buffer, IUU_GET_STATE_REGISTER, 1);
+ usb_fill_bulk_urb(port->write_urb, port->serial->dev,
+ usb_sndbulkpipe(port->serial->dev,
+ port->bulk_out_endpointAddress),
+ port->write_urb->transfer_buffer, 1,
+ iuu_status_callback, port);
+ result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ return result;
+
+}
+
+static int bulk_immediate(struct usb_serial_port *port, u8 *buf, u8 count)
+{
+ int status;
+ struct usb_serial *serial = port->serial;
+ int actual = 0;
+
+ /* send the data out the bulk port */
+
+ status =
+ usb_bulk_msg(serial->dev,
+ usb_sndbulkpipe(serial->dev,
+ port->bulk_out_endpointAddress), buf,
+ count, &actual, 1000);
+
+ if (status != IUU_OPERATION_OK)
+ dev_dbg(&port->dev, "%s - error = %2x\n", __func__, status);
+ else
+ dev_dbg(&port->dev, "%s - write OK !\n", __func__);
+ return status;
+}
+
+static int read_immediate(struct usb_serial_port *port, u8 *buf, u8 count)
+{
+ int status;
+ struct usb_serial *serial = port->serial;
+ int actual = 0;
+
+ /* send the data out the bulk port */
+ status =
+ usb_bulk_msg(serial->dev,
+ usb_rcvbulkpipe(serial->dev,
+ port->bulk_in_endpointAddress), buf,
+ count, &actual, 1000);
+
+ if (status != IUU_OPERATION_OK)
+ dev_dbg(&port->dev, "%s - error = %2x\n", __func__, status);
+ else
+ dev_dbg(&port->dev, "%s - read OK !\n", __func__);
+ return status;
+}
+
+static int iuu_led(struct usb_serial_port *port, unsigned int R,
+ unsigned int G, unsigned int B, u8 f)
+{
+ int status;
+ u8 *buf;
+ buf = kmalloc(8, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = IUU_SET_LED;
+ buf[1] = R & 0xFF;
+ buf[2] = (R >> 8) & 0xFF;
+ buf[3] = G & 0xFF;
+ buf[4] = (G >> 8) & 0xFF;
+ buf[5] = B & 0xFF;
+ buf[6] = (B >> 8) & 0xFF;
+ buf[7] = f;
+ status = bulk_immediate(port, buf, 8);
+ kfree(buf);
+ if (status != IUU_OPERATION_OK)
+ dev_dbg(&port->dev, "%s - led error status = %2x\n", __func__, status);
+ else
+ dev_dbg(&port->dev, "%s - led OK !\n", __func__);
+ return IUU_OPERATION_OK;
+}
+
+static void iuu_rgbf_fill_buffer(u8 *buf, u8 r1, u8 r2, u8 g1, u8 g2, u8 b1,
+ u8 b2, u8 freq)
+{
+ *buf++ = IUU_SET_LED;
+ *buf++ = r1;
+ *buf++ = r2;
+ *buf++ = g1;
+ *buf++ = g2;
+ *buf++ = b1;
+ *buf++ = b2;
+ *buf = freq;
+}
+
+static void iuu_led_activity_on(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ char *buf_ptr = port->write_urb->transfer_buffer;
+
+ if (xmas) {
+ buf_ptr[0] = IUU_SET_LED;
+ get_random_bytes(buf_ptr + 1, 6);
+ buf_ptr[7] = 1;
+ } else {
+ iuu_rgbf_fill_buffer(buf_ptr, 255, 255, 0, 0, 0, 0, 255);
+ }
+
+ usb_fill_bulk_urb(port->write_urb, port->serial->dev,
+ usb_sndbulkpipe(port->serial->dev,
+ port->bulk_out_endpointAddress),
+ port->write_urb->transfer_buffer, 8 ,
+ iuu_rxcmd, port);
+ usb_submit_urb(port->write_urb, GFP_ATOMIC);
+}
+
+static void iuu_led_activity_off(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ char *buf_ptr = port->write_urb->transfer_buffer;
+
+ if (xmas) {
+ iuu_rxcmd(urb);
+ return;
+ }
+
+ iuu_rgbf_fill_buffer(buf_ptr, 0, 0, 255, 255, 0, 0, 255);
+
+ usb_fill_bulk_urb(port->write_urb, port->serial->dev,
+ usb_sndbulkpipe(port->serial->dev,
+ port->bulk_out_endpointAddress),
+ port->write_urb->transfer_buffer, 8 ,
+ iuu_rxcmd, port);
+ usb_submit_urb(port->write_urb, GFP_ATOMIC);
+}
+
+
+
+static int iuu_clk(struct usb_serial_port *port, int dwFrq)
+{
+ int status;
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+ int Count = 0;
+ u8 FrqGenAdr = 0x69;
+ u8 DIV = 0; /* 8bit */
+ u8 XDRV = 0; /* 8bit */
+ u8 PUMP = 0; /* 3bit */
+ u8 PBmsb = 0; /* 2bit */
+ u8 PBlsb = 0; /* 8bit */
+ u8 PO = 0; /* 1bit */
+ u8 Q = 0; /* 7bit */
+ /* 24bit = 3bytes */
+ unsigned int P = 0;
+ unsigned int P2 = 0;
+ int frq = (int)dwFrq;
+
+ if (frq == 0) {
+ priv->buf[Count++] = IUU_UART_WRITE_I2C;
+ priv->buf[Count++] = FrqGenAdr << 1;
+ priv->buf[Count++] = 0x09;
+ priv->buf[Count++] = 0x00;
+
+ status = bulk_immediate(port, (u8 *) priv->buf, Count);
+ if (status != 0) {
+ dev_dbg(&port->dev, "%s - write error\n", __func__);
+ return status;
+ }
+ } else if (frq == 3579000) {
+ DIV = 100;
+ P = 1193;
+ Q = 40;
+ XDRV = 0;
+ } else if (frq == 3680000) {
+ DIV = 105;
+ P = 161;
+ Q = 5;
+ XDRV = 0;
+ } else if (frq == 6000000) {
+ DIV = 66;
+ P = 66;
+ Q = 2;
+ XDRV = 0x28;
+ } else {
+ unsigned int result = 0;
+ unsigned int tmp = 0;
+ unsigned int check;
+ unsigned int check2;
+ char found = 0x00;
+ unsigned int lQ = 2;
+ unsigned int lP = 2055;
+ unsigned int lDiv = 4;
+
+ for (lQ = 2; lQ <= 47 && !found; lQ++)
+ for (lP = 2055; lP >= 8 && !found; lP--)
+ for (lDiv = 4; lDiv <= 127 && !found; lDiv++) {
+ tmp = (12000000 / lDiv) * (lP / lQ);
+ if (abs((int)(tmp - frq)) <
+ abs((int)(frq - result))) {
+ check2 = (12000000 / lQ);
+ if (check2 < 250000)
+ continue;
+ check = (12000000 / lQ) * lP;
+ if (check > 400000000)
+ continue;
+ if (check < 100000000)
+ continue;
+ if (lDiv < 4 || lDiv > 127)
+ continue;
+ result = tmp;
+ P = lP;
+ DIV = lDiv;
+ Q = lQ;
+ if (result == frq)
+ found = 0x01;
+ }
+ }
+ }
+ P2 = ((P - PO) / 2) - 4;
+ PUMP = 0x04;
+ PBmsb = (P2 >> 8 & 0x03);
+ PBlsb = P2 & 0xFF;
+ PO = (P >> 10) & 0x01;
+ Q = Q - 2;
+
+ priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */
+ priv->buf[Count++] = FrqGenAdr << 1;
+ priv->buf[Count++] = 0x09;
+ priv->buf[Count++] = 0x20; /* Adr = 0x09 */
+ priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */
+ priv->buf[Count++] = FrqGenAdr << 1;
+ priv->buf[Count++] = 0x0C;
+ priv->buf[Count++] = DIV; /* Adr = 0x0C */
+ priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */
+ priv->buf[Count++] = FrqGenAdr << 1;
+ priv->buf[Count++] = 0x12;
+ priv->buf[Count++] = XDRV; /* Adr = 0x12 */
+ priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */
+ priv->buf[Count++] = FrqGenAdr << 1;
+ priv->buf[Count++] = 0x13;
+ priv->buf[Count++] = 0x6B; /* Adr = 0x13 */
+ priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */
+ priv->buf[Count++] = FrqGenAdr << 1;
+ priv->buf[Count++] = 0x40;
+ priv->buf[Count++] = (0xC0 | ((PUMP & 0x07) << 2)) |
+ (PBmsb & 0x03); /* Adr = 0x40 */
+ priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */
+ priv->buf[Count++] = FrqGenAdr << 1;
+ priv->buf[Count++] = 0x41;
+ priv->buf[Count++] = PBlsb; /* Adr = 0x41 */
+ priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */
+ priv->buf[Count++] = FrqGenAdr << 1;
+ priv->buf[Count++] = 0x42;
+ priv->buf[Count++] = Q | (((PO & 0x01) << 7)); /* Adr = 0x42 */
+ priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */
+ priv->buf[Count++] = FrqGenAdr << 1;
+ priv->buf[Count++] = 0x44;
+ priv->buf[Count++] = (char)0xFF; /* Adr = 0x44 */
+ priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */
+ priv->buf[Count++] = FrqGenAdr << 1;
+ priv->buf[Count++] = 0x45;
+ priv->buf[Count++] = (char)0xFE; /* Adr = 0x45 */
+ priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */
+ priv->buf[Count++] = FrqGenAdr << 1;
+ priv->buf[Count++] = 0x46;
+ priv->buf[Count++] = 0x7F; /* Adr = 0x46 */
+ priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */
+ priv->buf[Count++] = FrqGenAdr << 1;
+ priv->buf[Count++] = 0x47;
+ priv->buf[Count++] = (char)0x84; /* Adr = 0x47 */
+
+ status = bulk_immediate(port, (u8 *) priv->buf, Count);
+ if (status != IUU_OPERATION_OK)
+ dev_dbg(&port->dev, "%s - write error\n", __func__);
+ return status;
+}
+
+static int iuu_uart_flush(struct usb_serial_port *port)
+{
+ struct device *dev = &port->dev;
+ int i;
+ int status;
+ u8 *rxcmd;
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+
+ if (iuu_led(port, 0xF000, 0, 0, 0xFF) < 0)
+ return -EIO;
+
+ rxcmd = kmalloc(1, GFP_KERNEL);
+ if (!rxcmd)
+ return -ENOMEM;
+
+ rxcmd[0] = IUU_UART_RX;
+
+ for (i = 0; i < 2; i++) {
+ status = bulk_immediate(port, rxcmd, 1);
+ if (status != IUU_OPERATION_OK) {
+ dev_dbg(dev, "%s - uart_flush_write error\n", __func__);
+ goto out_free;
+ }
+
+ status = read_immediate(port, &priv->len, 1);
+ if (status != IUU_OPERATION_OK) {
+ dev_dbg(dev, "%s - uart_flush_read error\n", __func__);
+ goto out_free;
+ }
+
+ if (priv->len > 0) {
+ dev_dbg(dev, "%s - uart_flush datalen is : %i\n", __func__, priv->len);
+ status = read_immediate(port, priv->buf, priv->len);
+ if (status != IUU_OPERATION_OK) {
+ dev_dbg(dev, "%s - uart_flush_read error\n", __func__);
+ goto out_free;
+ }
+ }
+ }
+ dev_dbg(dev, "%s - uart_flush_read OK!\n", __func__);
+ iuu_led(port, 0, 0xF000, 0, 0xFF);
+
+out_free:
+ kfree(rxcmd);
+
+ return status;
+}
+
+static void read_buf_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+
+ if (status) {
+ if (status == -EPROTO) {
+ /* reschedule needed */
+ }
+ return;
+ }
+
+ dev_dbg(&port->dev, "%s - %i chars to write\n", __func__, urb->actual_length);
+
+ if (urb->actual_length) {
+ tty_insert_flip_string(&port->port, data, urb->actual_length);
+ tty_flip_buffer_push(&port->port);
+ }
+ iuu_led_activity_on(urb);
+}
+
+static int iuu_bulk_write(struct usb_serial_port *port)
+{
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ int result;
+ int buf_len;
+ char *buf_ptr = port->write_urb->transfer_buffer;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ *buf_ptr++ = IUU_UART_ESC;
+ *buf_ptr++ = IUU_UART_TX;
+ *buf_ptr++ = priv->writelen;
+
+ memcpy(buf_ptr, priv->writebuf, priv->writelen);
+ buf_len = priv->writelen;
+ priv->writelen = 0;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ dev_dbg(&port->dev, "%s - writing %i chars : %*ph\n", __func__,
+ buf_len, buf_len, buf_ptr);
+ usb_fill_bulk_urb(port->write_urb, port->serial->dev,
+ usb_sndbulkpipe(port->serial->dev,
+ port->bulk_out_endpointAddress),
+ port->write_urb->transfer_buffer, buf_len + 3,
+ iuu_rxcmd, port);
+ result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ usb_serial_port_softint(port);
+ return result;
+}
+
+static int iuu_read_buf(struct usb_serial_port *port, int len)
+{
+ int result;
+
+ usb_fill_bulk_urb(port->read_urb, port->serial->dev,
+ usb_rcvbulkpipe(port->serial->dev,
+ port->bulk_in_endpointAddress),
+ port->read_urb->transfer_buffer, len,
+ read_buf_callback, port);
+ result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
+ return result;
+}
+
+static void iuu_uart_read_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ int status = urb->status;
+ int len = 0;
+ unsigned char *data = urb->transfer_buffer;
+ priv->poll++;
+
+ if (status) {
+ dev_dbg(&port->dev, "%s - status = %d\n", __func__, status);
+ /* error stop all */
+ return;
+ }
+
+ if (urb->actual_length == 1)
+ len = (int) data[0];
+
+ if (urb->actual_length > 1) {
+ dev_dbg(&port->dev, "%s - urb->actual_length = %i\n", __func__,
+ urb->actual_length);
+ return;
+ }
+ /* if len > 0 call readbuf */
+
+ if (len > 0) {
+ dev_dbg(&port->dev, "%s - call read buf - len to read is %i\n",
+ __func__, len);
+ status = iuu_read_buf(port, len);
+ return;
+ }
+ /* need to update status ? */
+ if (priv->poll > 99) {
+ status = iuu_status(port);
+ priv->poll = 0;
+ return;
+ }
+
+ /* reset waiting ? */
+
+ if (priv->reset == 1) {
+ status = iuu_reset(port, 0xC);
+ return;
+ }
+ /* Writebuf is waiting */
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->writelen > 0) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ status = iuu_bulk_write(port);
+ return;
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+ /* if nothing to write call again rxcmd */
+ dev_dbg(&port->dev, "%s - rxcmd recall\n", __func__);
+ iuu_led_activity_off(urb);
+}
+
+static int iuu_uart_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const u8 *buf, int count)
+{
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ count = min(count, 256 - priv->writelen);
+ if (count == 0)
+ goto out;
+
+ /* fill the buffer */
+ memcpy(priv->writebuf + priv->writelen, buf, count);
+ priv->writelen += count;
+out:
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return count;
+}
+
+static void read_rxcmd_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ int result;
+ int status = urb->status;
+
+ if (status) {
+ /* error stop all */
+ return;
+ }
+
+ usb_fill_bulk_urb(port->read_urb, port->serial->dev,
+ usb_rcvbulkpipe(port->serial->dev,
+ port->bulk_in_endpointAddress),
+ port->read_urb->transfer_buffer, 256,
+ iuu_uart_read_callback, port);
+ result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
+ dev_dbg(&port->dev, "%s - submit result = %d\n", __func__, result);
+}
+
+static int iuu_uart_on(struct usb_serial_port *port)
+{
+ int status;
+ u8 *buf;
+
+ buf = kmalloc(4, GFP_KERNEL);
+
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = IUU_UART_ENABLE;
+ buf[1] = (u8) ((IUU_BAUD_9600 >> 8) & 0x00FF);
+ buf[2] = (u8) (0x00FF & IUU_BAUD_9600);
+ buf[3] = (u8) (0x0F0 & IUU_ONE_STOP_BIT) | (0x07 & IUU_PARITY_EVEN);
+
+ status = bulk_immediate(port, buf, 4);
+ if (status != IUU_OPERATION_OK) {
+ dev_dbg(&port->dev, "%s - uart_on error\n", __func__);
+ goto uart_enable_failed;
+ }
+ /* iuu_reset() the card after iuu_uart_on() */
+ status = iuu_uart_flush(port);
+ if (status != IUU_OPERATION_OK)
+ dev_dbg(&port->dev, "%s - uart_flush error\n", __func__);
+uart_enable_failed:
+ kfree(buf);
+ return status;
+}
+
+/* Disables the IUU UART (a.k.a. the Phoenix voiderface) */
+static int iuu_uart_off(struct usb_serial_port *port)
+{
+ int status;
+ u8 *buf;
+ buf = kmalloc(1, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ buf[0] = IUU_UART_DISABLE;
+
+ status = bulk_immediate(port, buf, 1);
+ if (status != IUU_OPERATION_OK)
+ dev_dbg(&port->dev, "%s - uart_off error\n", __func__);
+
+ kfree(buf);
+ return status;
+}
+
+static int iuu_uart_baud(struct usb_serial_port *port, u32 baud_base,
+ u32 *actual, u8 parity)
+{
+ int status;
+ u32 baud;
+ u8 *dataout;
+ u8 DataCount = 0;
+ u8 T1Frekvens = 0;
+ u8 T1reload = 0;
+ unsigned int T1FrekvensHZ = 0;
+
+ dev_dbg(&port->dev, "%s - enter baud_base=%d\n", __func__, baud_base);
+ dataout = kmalloc(5, GFP_KERNEL);
+
+ if (!dataout)
+ return -ENOMEM;
+ /*baud = (((priv->clk / 35) * baud_base) / 100000); */
+ baud = baud_base;
+
+ if (baud < 1200 || baud > 230400) {
+ kfree(dataout);
+ return IUU_INVALID_PARAMETER;
+ }
+ if (baud > 977) {
+ T1Frekvens = 3;
+ T1FrekvensHZ = 500000;
+ }
+
+ if (baud > 3906) {
+ T1Frekvens = 2;
+ T1FrekvensHZ = 2000000;
+ }
+
+ if (baud > 11718) {
+ T1Frekvens = 1;
+ T1FrekvensHZ = 6000000;
+ }
+
+ if (baud > 46875) {
+ T1Frekvens = 0;
+ T1FrekvensHZ = 24000000;
+ }
+
+ T1reload = 256 - (u8) (T1FrekvensHZ / (baud * 2));
+
+ /* magic number here: ENTER_FIRMWARE_UPDATE; */
+ dataout[DataCount++] = IUU_UART_ESC;
+ /* magic number here: CHANGE_BAUD; */
+ dataout[DataCount++] = IUU_UART_CHANGE;
+ dataout[DataCount++] = T1Frekvens;
+ dataout[DataCount++] = T1reload;
+
+ *actual = (T1FrekvensHZ / (256 - T1reload)) / 2;
+
+ switch (parity & 0x0F) {
+ case IUU_PARITY_NONE:
+ dataout[DataCount++] = 0x00;
+ break;
+ case IUU_PARITY_EVEN:
+ dataout[DataCount++] = 0x01;
+ break;
+ case IUU_PARITY_ODD:
+ dataout[DataCount++] = 0x02;
+ break;
+ case IUU_PARITY_MARK:
+ dataout[DataCount++] = 0x03;
+ break;
+ case IUU_PARITY_SPACE:
+ dataout[DataCount++] = 0x04;
+ break;
+ default:
+ kfree(dataout);
+ return IUU_INVALID_PARAMETER;
+ }
+
+ switch (parity & 0xF0) {
+ case IUU_ONE_STOP_BIT:
+ dataout[DataCount - 1] |= IUU_ONE_STOP_BIT;
+ break;
+
+ case IUU_TWO_STOP_BITS:
+ dataout[DataCount - 1] |= IUU_TWO_STOP_BITS;
+ break;
+ default:
+ kfree(dataout);
+ return IUU_INVALID_PARAMETER;
+ }
+
+ status = bulk_immediate(port, dataout, DataCount);
+ if (status != IUU_OPERATION_OK)
+ dev_dbg(&port->dev, "%s - uart_off error\n", __func__);
+ kfree(dataout);
+ return status;
+}
+
+static void iuu_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ const u32 supported_mask = CMSPAR|PARENB|PARODD;
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+ unsigned int cflag = tty->termios.c_cflag;
+ int status;
+ u32 actual;
+ u32 parity;
+ int csize = CS7;
+ int baud;
+ u32 newval = cflag & supported_mask;
+
+ /* Just use the ospeed. ispeed should be the same. */
+ baud = tty->termios.c_ospeed;
+
+ dev_dbg(&port->dev, "%s - enter c_ospeed or baud=%d\n", __func__, baud);
+
+ /* compute the parity parameter */
+ parity = 0;
+ if (cflag & CMSPAR) { /* Using mark space */
+ if (cflag & PARODD)
+ parity |= IUU_PARITY_SPACE;
+ else
+ parity |= IUU_PARITY_MARK;
+ } else if (!(cflag & PARENB)) {
+ parity |= IUU_PARITY_NONE;
+ csize = CS8;
+ } else if (cflag & PARODD)
+ parity |= IUU_PARITY_ODD;
+ else
+ parity |= IUU_PARITY_EVEN;
+
+ parity |= (cflag & CSTOPB ? IUU_TWO_STOP_BITS : IUU_ONE_STOP_BIT);
+
+ /* set it */
+ status = iuu_uart_baud(port,
+ baud * priv->boost / 100,
+ &actual, parity);
+
+ /* set the termios value to the real one, so the user now what has
+ * changed. We support few fields so its easies to copy the old hw
+ * settings back over and then adjust them
+ */
+ if (old_termios)
+ tty_termios_copy_hw(&tty->termios, old_termios);
+ if (status != 0) /* Set failed - return old bits */
+ return;
+ /* Re-encode speed, parity and csize */
+ tty_encode_baud_rate(tty, baud, baud);
+ tty->termios.c_cflag &= ~(supported_mask|CSIZE);
+ tty->termios.c_cflag |= newval | csize;
+}
+
+static void iuu_close(struct usb_serial_port *port)
+{
+ /* iuu_led (port,255,0,0,0); */
+
+ iuu_uart_off(port);
+
+ usb_kill_urb(port->write_urb);
+ usb_kill_urb(port->read_urb);
+
+ iuu_led(port, 0, 0, 0xF000, 0xFF);
+}
+
+static void iuu_init_termios(struct tty_struct *tty)
+{
+ tty->termios.c_cflag = B9600 | CS8 | CSTOPB | CREAD | PARENB | CLOCAL;
+ tty->termios.c_ispeed = 9600;
+ tty->termios.c_ospeed = 9600;
+ tty->termios.c_lflag = 0;
+ tty->termios.c_oflag = 0;
+ tty->termios.c_iflag = 0;
+}
+
+static int iuu_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct device *dev = &port->dev;
+ int result;
+ int baud;
+ u32 actual;
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+
+ baud = tty->termios.c_ospeed;
+
+ dev_dbg(dev, "%s - baud %d\n", __func__, baud);
+ usb_clear_halt(serial->dev, port->write_urb->pipe);
+ usb_clear_halt(serial->dev, port->read_urb->pipe);
+
+ priv->poll = 0;
+
+#define SOUP(a, b, c, d) do { \
+ result = usb_control_msg(port->serial->dev, \
+ usb_sndctrlpipe(port->serial->dev, 0), \
+ b, a, c, d, NULL, 0, 1000); \
+ dev_dbg(dev, "0x%x:0x%x:0x%x:0x%x %d\n", a, b, c, d, result); } while (0)
+
+ /* This is not UART related but IUU USB driver related or something */
+ /* like that. Basically no IUU will accept any commands from the USB */
+ /* host unless it has received the following message */
+ /* sprintf(buf ,"%c%c%c%c",0x03,0x02,0x02,0x0); */
+
+ SOUP(0x03, 0x02, 0x02, 0x0);
+
+ iuu_led(port, 0xF000, 0xF000, 0, 0xFF);
+ iuu_uart_on(port);
+ if (boost < 100)
+ boost = 100;
+ priv->boost = boost;
+ switch (clockmode) {
+ case 2: /* 3.680 Mhz */
+ priv->clk = IUU_CLK_3680000;
+ iuu_clk(port, IUU_CLK_3680000 * boost / 100);
+ result =
+ iuu_uart_baud(port, baud * boost / 100, &actual,
+ IUU_PARITY_EVEN);
+ break;
+ case 3: /* 6.00 Mhz */
+ iuu_clk(port, IUU_CLK_6000000 * boost / 100);
+ priv->clk = IUU_CLK_6000000;
+ /* Ratio of 6000000 to 3500000 for baud 9600 */
+ result =
+ iuu_uart_baud(port, 16457 * boost / 100, &actual,
+ IUU_PARITY_EVEN);
+ break;
+ default: /* 3.579 Mhz */
+ iuu_clk(port, IUU_CLK_3579000 * boost / 100);
+ priv->clk = IUU_CLK_3579000;
+ result =
+ iuu_uart_baud(port, baud * boost / 100, &actual,
+ IUU_PARITY_EVEN);
+ }
+
+ /* set the cardin cardout signals */
+ switch (cdmode) {
+ case 0:
+ iuu_cardin = 0;
+ iuu_cardout = 0;
+ break;
+ case 1:
+ iuu_cardin = TIOCM_CD;
+ iuu_cardout = 0;
+ break;
+ case 2:
+ iuu_cardin = 0;
+ iuu_cardout = TIOCM_CD;
+ break;
+ case 3:
+ iuu_cardin = TIOCM_DSR;
+ iuu_cardout = 0;
+ break;
+ case 4:
+ iuu_cardin = 0;
+ iuu_cardout = TIOCM_DSR;
+ break;
+ case 5:
+ iuu_cardin = TIOCM_CTS;
+ iuu_cardout = 0;
+ break;
+ case 6:
+ iuu_cardin = 0;
+ iuu_cardout = TIOCM_CTS;
+ break;
+ case 7:
+ iuu_cardin = TIOCM_RNG;
+ iuu_cardout = 0;
+ break;
+ case 8:
+ iuu_cardin = 0;
+ iuu_cardout = TIOCM_RNG;
+ }
+
+ iuu_uart_flush(port);
+
+ dev_dbg(dev, "%s - initialization done\n", __func__);
+
+ memset(port->write_urb->transfer_buffer, IUU_UART_RX, 1);
+ usb_fill_bulk_urb(port->write_urb, port->serial->dev,
+ usb_sndbulkpipe(port->serial->dev,
+ port->bulk_out_endpointAddress),
+ port->write_urb->transfer_buffer, 1,
+ read_rxcmd_callback, port);
+ result = usb_submit_urb(port->write_urb, GFP_KERNEL);
+ if (result) {
+ dev_err(dev, "%s - failed submitting read urb, error %d\n", __func__, result);
+ iuu_close(port);
+ } else {
+ dev_dbg(dev, "%s - rxcmd OK\n", __func__);
+ }
+
+ return result;
+}
+
+/* how to change VCC */
+static int iuu_vcc_set(struct usb_serial_port *port, unsigned int vcc)
+{
+ int status;
+ u8 *buf;
+
+ buf = kmalloc(5, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = IUU_SET_VCC;
+ buf[1] = vcc & 0xFF;
+ buf[2] = (vcc >> 8) & 0xFF;
+ buf[3] = (vcc >> 16) & 0xFF;
+ buf[4] = (vcc >> 24) & 0xFF;
+
+ status = bulk_immediate(port, buf, 5);
+ kfree(buf);
+
+ if (status != IUU_OPERATION_OK)
+ dev_dbg(&port->dev, "%s - vcc error status = %2x\n", __func__, status);
+ else
+ dev_dbg(&port->dev, "%s - vcc OK !\n", __func__);
+
+ return status;
+}
+
+/*
+ * Sysfs Attributes
+ */
+
+static ssize_t vcc_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_serial_port *port = to_usb_serial_port(dev);
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+
+ return sprintf(buf, "%d\n", priv->vcc);
+}
+
+static ssize_t vcc_mode_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_serial_port *port = to_usb_serial_port(dev);
+ struct iuu_private *priv = usb_get_serial_port_data(port);
+ unsigned long v;
+
+ if (kstrtoul(buf, 10, &v)) {
+ dev_err(dev, "%s - vcc_mode: %s is not a unsigned long\n",
+ __func__, buf);
+ goto fail_store_vcc_mode;
+ }
+
+ dev_dbg(dev, "%s: setting vcc_mode = %ld\n", __func__, v);
+
+ if ((v != 3) && (v != 5)) {
+ dev_err(dev, "%s - vcc_mode %ld is invalid\n", __func__, v);
+ } else {
+ iuu_vcc_set(port, v);
+ priv->vcc = v;
+ }
+fail_store_vcc_mode:
+ return count;
+}
+static DEVICE_ATTR_RW(vcc_mode);
+
+static int iuu_create_sysfs_attrs(struct usb_serial_port *port)
+{
+ return device_create_file(&port->dev, &dev_attr_vcc_mode);
+}
+
+static int iuu_remove_sysfs_attrs(struct usb_serial_port *port)
+{
+ device_remove_file(&port->dev, &dev_attr_vcc_mode);
+ return 0;
+}
+
+/*
+ * End Sysfs Attributes
+ */
+
+static struct usb_serial_driver iuu_device = {
+ .driver = {
+ .name = "iuu_phoenix",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ .bulk_in_size = 512,
+ .bulk_out_size = 512,
+ .open = iuu_open,
+ .close = iuu_close,
+ .write = iuu_uart_write,
+ .read_bulk_callback = iuu_uart_read_callback,
+ .tiocmget = iuu_tiocmget,
+ .tiocmset = iuu_tiocmset,
+ .set_termios = iuu_set_termios,
+ .init_termios = iuu_init_termios,
+ .port_probe = iuu_port_probe,
+ .port_remove = iuu_port_remove,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &iuu_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR("Alain Degreffe eczema@ecze.com");
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+module_param(xmas, bool, 0644);
+MODULE_PARM_DESC(xmas, "Xmas colors enabled or not");
+
+module_param(boost, int, 0644);
+MODULE_PARM_DESC(boost, "Card overclock boost (in percent 100-500)");
+
+module_param(clockmode, int, 0644);
+MODULE_PARM_DESC(clockmode, "Card clock mode (1=3.579 MHz, 2=3.680 MHz, "
+ "3=6 Mhz)");
+
+module_param(cdmode, int, 0644);
+MODULE_PARM_DESC(cdmode, "Card detect mode (0=none, 1=CD, 2=!CD, 3=DSR, "
+ "4=!DSR, 5=CTS, 6=!CTS, 7=RING, 8=!RING)");
+
+module_param(vcc_default, int, 0644);
+MODULE_PARM_DESC(vcc_default, "Set default VCC (either 3 for 3.3V or 5 "
+ "for 5V). Default to 5.");
diff --git a/drivers/usb/serial/iuu_phoenix.h b/drivers/usb/serial/iuu_phoenix.h
new file mode 100644
index 000000000..87992b24d
--- /dev/null
+++ b/drivers/usb/serial/iuu_phoenix.h
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Infinity Unlimited USB Phoenix driver
+ *
+ * Copyright (C) 2007 Alain Degreffe (eczema@ecze.com)
+ *
+ *
+ * Original code taken from iuutool ( Copyright (C) 2006 Juan Carlos Borrás )
+ *
+ * And tested with help of WB Electronics
+ */
+
+#define IUU_USB_VENDOR_ID 0x104f
+#define IUU_USB_PRODUCT_ID 0x0004
+#define IUU_USB_OP_TIMEOUT 0x0200
+
+/* Programmer commands */
+
+#define IUU_NO_OPERATION 0x00
+#define IUU_GET_FIRMWARE_VERSION 0x01
+#define IUU_GET_PRODUCT_NAME 0x02
+#define IUU_GET_STATE_REGISTER 0x03
+#define IUU_SET_LED 0x04
+#define IUU_WAIT_MUS 0x05
+#define IUU_WAIT_MS 0x06
+#define IUU_GET_LOADER_VERSION 0x50
+#define IUU_RST_SET 0x52
+#define IUU_RST_CLEAR 0x53
+#define IUU_SET_VCC 0x59
+#define IUU_UART_ENABLE 0x49
+#define IUU_UART_DISABLE 0x4A
+#define IUU_UART_WRITE_I2C 0x4C
+#define IUU_UART_ESC 0x5E
+#define IUU_UART_TRAP 0x54
+#define IUU_UART_TRAP_BREAK 0x5B
+#define IUU_UART_RX 0x56
+#define IUU_AVR_ON 0x21
+#define IUU_AVR_OFF 0x22
+#define IUU_AVR_1CLK 0x23
+#define IUU_AVR_RESET 0x24
+#define IUU_AVR_RESET_PC 0x25
+#define IUU_AVR_INC_PC 0x26
+#define IUU_AVR_INCN_PC 0x27
+#define IUU_AVR_PREAD 0x29
+#define IUU_AVR_PREADN 0x2A
+#define IUU_AVR_PWRITE 0x28
+#define IUU_AVR_DREAD 0x2C
+#define IUU_AVR_DREADN 0x2D
+#define IUU_AVR_DWRITE 0x2B
+#define IUU_AVR_PWRITEN 0x2E
+#define IUU_EEPROM_ON 0x37
+#define IUU_EEPROM_OFF 0x38
+#define IUU_EEPROM_WRITE 0x39
+#define IUU_EEPROM_WRITEX 0x3A
+#define IUU_EEPROM_WRITE8 0x3B
+#define IUU_EEPROM_WRITE16 0x3C
+#define IUU_EEPROM_WRITEX32 0x3D
+#define IUU_EEPROM_WRITEX64 0x3E
+#define IUU_EEPROM_READ 0x3F
+#define IUU_EEPROM_READX 0x40
+#define IUU_EEPROM_BREAD 0x41
+#define IUU_EEPROM_BREADX 0x42
+#define IUU_PIC_CMD 0x0A
+#define IUU_PIC_CMD_LOAD 0x0B
+#define IUU_PIC_CMD_READ 0x0C
+#define IUU_PIC_ON 0x0D
+#define IUU_PIC_OFF 0x0E
+#define IUU_PIC_RESET 0x16
+#define IUU_PIC_INC_PC 0x0F
+#define IUU_PIC_INCN_PC 0x10
+#define IUU_PIC_PWRITE 0x11
+#define IUU_PIC_PREAD 0x12
+#define IUU_PIC_PREADN 0x13
+#define IUU_PIC_DWRITE 0x14
+#define IUU_PIC_DREAD 0x15
+#define IUU_UART_NOP 0x00
+#define IUU_UART_CHANGE 0x02
+#define IUU_UART_TX 0x04
+#define IUU_DELAY_MS 0x06
+
+#define IUU_OPERATION_OK 0x00
+#define IUU_DEVICE_NOT_FOUND 0x01
+#define IUU_INVALID_HANDLE 0x02
+#define IUU_INVALID_PARAMETER 0x03
+#define IUU_INVALID_voidERFACE 0x04
+#define IUU_INVALID_REQUEST_LENGTH 0x05
+#define IUU_UART_NOT_ENABLED 0x06
+#define IUU_WRITE_ERROR 0x07
+#define IUU_READ_ERROR 0x08
+#define IUU_TX_ERROR 0x09
+#define IUU_RX_ERROR 0x0A
+
+#define IUU_PARITY_NONE 0x00
+#define IUU_PARITY_EVEN 0x01
+#define IUU_PARITY_ODD 0x02
+#define IUU_PARITY_MARK 0x03
+#define IUU_PARITY_SPACE 0x04
+#define IUU_SC_INSERTED 0x01
+#define IUU_VERIFY_ERROR 0x02
+#define IUU_SIM_INSERTED 0x04
+#define IUU_TWO_STOP_BITS 0x00
+#define IUU_ONE_STOP_BIT 0x20
+#define IUU_BAUD_2400 0x0398
+#define IUU_BAUD_9600 0x0298
+#define IUU_BAUD_19200 0x0164
+#define IUU_BAUD_28800 0x0198
+#define IUU_BAUD_38400 0x01B2
+#define IUU_BAUD_57600 0x0030
+#define IUU_BAUD_115200 0x0098
+#define IUU_CLK_3579000 3579000
+#define IUU_CLK_3680000 3680000
+#define IUU_CLK_6000000 6000000
+#define IUU_FULLCARD_IN 0x01
+#define IUU_DEV_ERROR 0x02
+#define IUU_MINICARD_IN 0x04
+#define IUU_VCC_5V 0x00
+#define IUU_VCC_3V 0x01
diff --git a/drivers/usb/serial/keyspan.c b/drivers/usb/serial/keyspan.c
new file mode 100644
index 000000000..4d3746c7a
--- /dev/null
+++ b/drivers/usb/serial/keyspan.c
@@ -0,0 +1,3105 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ Keyspan USB to Serial Converter driver
+
+ (C) Copyright (C) 2000-2001 Hugh Blemings <hugh@blemings.org>
+ (C) Copyright (C) 2002 Greg Kroah-Hartman <greg@kroah.com>
+
+ See http://blemings.org/hugh/keyspan.html for more information.
+
+ Code in this driver inspired by and in a number of places taken
+ from Brian Warner's original Keyspan-PDA driver.
+
+ This driver has been put together with the support of Innosys, Inc.
+ and Keyspan, Inc the manufacturers of the Keyspan USB-serial products.
+ Thanks Guys :)
+
+ Thanks to Paulus for miscellaneous tidy ups, some largish chunks
+ of much nicer and/or completely new code and (perhaps most uniquely)
+ having the patience to sit down and explain why and where he'd changed
+ stuff.
+
+ Tip 'o the hat to IBM (and previously Linuxcare :) for supporting
+ staff in their work on open source projects.
+*/
+
+
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/usb/ezusb.h>
+
+#define DRIVER_AUTHOR "Hugh Blemings <hugh@misc.nu"
+#define DRIVER_DESC "Keyspan USB to Serial Converter Driver"
+
+static void keyspan_send_setup(struct usb_serial_port *port, int reset_port);
+
+static int keyspan_usa19_calc_baud(struct usb_serial_port *port,
+ u32 baud_rate, u32 baudclk,
+ u8 *rate_hi, u8 *rate_low,
+ u8 *prescaler, int portnum);
+static int keyspan_usa19w_calc_baud(struct usb_serial_port *port,
+ u32 baud_rate, u32 baudclk,
+ u8 *rate_hi, u8 *rate_low,
+ u8 *prescaler, int portnum);
+static int keyspan_usa28_calc_baud(struct usb_serial_port *port,
+ u32 baud_rate, u32 baudclk,
+ u8 *rate_hi, u8 *rate_low,
+ u8 *prescaler, int portnum);
+static int keyspan_usa19hs_calc_baud(struct usb_serial_port *port,
+ u32 baud_rate, u32 baudclk,
+ u8 *rate_hi, u8 *rate_low,
+ u8 *prescaler, int portnum);
+
+static int keyspan_usa28_send_setup(struct usb_serial *serial,
+ struct usb_serial_port *port,
+ int reset_port);
+static int keyspan_usa26_send_setup(struct usb_serial *serial,
+ struct usb_serial_port *port,
+ int reset_port);
+static int keyspan_usa49_send_setup(struct usb_serial *serial,
+ struct usb_serial_port *port,
+ int reset_port);
+static int keyspan_usa90_send_setup(struct usb_serial *serial,
+ struct usb_serial_port *port,
+ int reset_port);
+static int keyspan_usa67_send_setup(struct usb_serial *serial,
+ struct usb_serial_port *port,
+ int reset_port);
+
+/* Values used for baud rate calculation - device specific */
+#define KEYSPAN_INVALID_BAUD_RATE (-1)
+#define KEYSPAN_BAUD_RATE_OK (0)
+#define KEYSPAN_USA18X_BAUDCLK (12000000L) /* a guess */
+#define KEYSPAN_USA19_BAUDCLK (12000000L)
+#define KEYSPAN_USA19W_BAUDCLK (24000000L)
+#define KEYSPAN_USA19HS_BAUDCLK (14769231L)
+#define KEYSPAN_USA28_BAUDCLK (1843200L)
+#define KEYSPAN_USA28X_BAUDCLK (12000000L)
+#define KEYSPAN_USA49W_BAUDCLK (48000000L)
+
+/* Some constants used to characterise each device. */
+#define KEYSPAN_MAX_NUM_PORTS (4)
+#define KEYSPAN_MAX_FLIPS (2)
+
+/*
+ * Device info for the Keyspan serial converter, used by the overall
+ * usb-serial probe function.
+ */
+#define KEYSPAN_VENDOR_ID (0x06cd)
+
+/* Product IDs for the products supported, pre-renumeration */
+#define keyspan_usa18x_pre_product_id 0x0105
+#define keyspan_usa19_pre_product_id 0x0103
+#define keyspan_usa19qi_pre_product_id 0x010b
+#define keyspan_mpr_pre_product_id 0x011b
+#define keyspan_usa19qw_pre_product_id 0x0118
+#define keyspan_usa19w_pre_product_id 0x0106
+#define keyspan_usa28_pre_product_id 0x0101
+#define keyspan_usa28x_pre_product_id 0x0102
+#define keyspan_usa28xa_pre_product_id 0x0114
+#define keyspan_usa28xb_pre_product_id 0x0113
+#define keyspan_usa49w_pre_product_id 0x0109
+#define keyspan_usa49wlc_pre_product_id 0x011a
+
+/*
+ * Product IDs post-renumeration. Note that the 28x and 28xb have the same
+ * id's post-renumeration but behave identically so it's not an issue. As
+ * such, the 28xb is not listed in any of the device tables.
+ */
+#define keyspan_usa18x_product_id 0x0112
+#define keyspan_usa19_product_id 0x0107
+#define keyspan_usa19qi_product_id 0x010c
+#define keyspan_usa19hs_product_id 0x0121
+#define keyspan_mpr_product_id 0x011c
+#define keyspan_usa19qw_product_id 0x0119
+#define keyspan_usa19w_product_id 0x0108
+#define keyspan_usa28_product_id 0x010f
+#define keyspan_usa28x_product_id 0x0110
+#define keyspan_usa28xa_product_id 0x0115
+#define keyspan_usa28xb_product_id 0x0110
+#define keyspan_usa28xg_product_id 0x0135
+#define keyspan_usa49w_product_id 0x010a
+#define keyspan_usa49wlc_product_id 0x012a
+#define keyspan_usa49wg_product_id 0x0131
+
+struct keyspan_device_details {
+ /* product ID value */
+ int product_id;
+
+ enum {msg_usa26, msg_usa28, msg_usa49, msg_usa90, msg_usa67} msg_format;
+
+ /* Number of physical ports */
+ int num_ports;
+
+ /* 1 if endpoint flipping used on input, 0 if not */
+ int indat_endp_flip;
+
+ /* 1 if endpoint flipping used on output, 0 if not */
+ int outdat_endp_flip;
+
+ /*
+ * Table mapping input data endpoint IDs to physical port
+ * number and flip if used
+ */
+ int indat_endpoints[KEYSPAN_MAX_NUM_PORTS];
+
+ /* Same for output endpoints */
+ int outdat_endpoints[KEYSPAN_MAX_NUM_PORTS];
+
+ /* Input acknowledge endpoints */
+ int inack_endpoints[KEYSPAN_MAX_NUM_PORTS];
+
+ /* Output control endpoints */
+ int outcont_endpoints[KEYSPAN_MAX_NUM_PORTS];
+
+ /* Endpoint used for input status */
+ int instat_endpoint;
+
+ /* Endpoint used for input data 49WG only */
+ int indat_endpoint;
+
+ /* Endpoint used for global control functions */
+ int glocont_endpoint;
+
+ int (*calculate_baud_rate)(struct usb_serial_port *port,
+ u32 baud_rate, u32 baudclk,
+ u8 *rate_hi, u8 *rate_low, u8 *prescaler,
+ int portnum);
+ u32 baudclk;
+};
+
+/*
+ * Now for each device type we setup the device detail structure with the
+ * appropriate information (provided in Keyspan's documentation)
+ */
+
+static const struct keyspan_device_details usa18x_device_details = {
+ .product_id = keyspan_usa18x_product_id,
+ .msg_format = msg_usa26,
+ .num_ports = 1,
+ .indat_endp_flip = 0,
+ .outdat_endp_flip = 1,
+ .indat_endpoints = {0x81},
+ .outdat_endpoints = {0x01},
+ .inack_endpoints = {0x85},
+ .outcont_endpoints = {0x05},
+ .instat_endpoint = 0x87,
+ .indat_endpoint = -1,
+ .glocont_endpoint = 0x07,
+ .calculate_baud_rate = keyspan_usa19w_calc_baud,
+ .baudclk = KEYSPAN_USA18X_BAUDCLK,
+};
+
+static const struct keyspan_device_details usa19_device_details = {
+ .product_id = keyspan_usa19_product_id,
+ .msg_format = msg_usa28,
+ .num_ports = 1,
+ .indat_endp_flip = 1,
+ .outdat_endp_flip = 1,
+ .indat_endpoints = {0x81},
+ .outdat_endpoints = {0x01},
+ .inack_endpoints = {0x83},
+ .outcont_endpoints = {0x03},
+ .instat_endpoint = 0x84,
+ .indat_endpoint = -1,
+ .glocont_endpoint = -1,
+ .calculate_baud_rate = keyspan_usa19_calc_baud,
+ .baudclk = KEYSPAN_USA19_BAUDCLK,
+};
+
+static const struct keyspan_device_details usa19qi_device_details = {
+ .product_id = keyspan_usa19qi_product_id,
+ .msg_format = msg_usa28,
+ .num_ports = 1,
+ .indat_endp_flip = 1,
+ .outdat_endp_flip = 1,
+ .indat_endpoints = {0x81},
+ .outdat_endpoints = {0x01},
+ .inack_endpoints = {0x83},
+ .outcont_endpoints = {0x03},
+ .instat_endpoint = 0x84,
+ .indat_endpoint = -1,
+ .glocont_endpoint = -1,
+ .calculate_baud_rate = keyspan_usa28_calc_baud,
+ .baudclk = KEYSPAN_USA19_BAUDCLK,
+};
+
+static const struct keyspan_device_details mpr_device_details = {
+ .product_id = keyspan_mpr_product_id,
+ .msg_format = msg_usa28,
+ .num_ports = 1,
+ .indat_endp_flip = 1,
+ .outdat_endp_flip = 1,
+ .indat_endpoints = {0x81},
+ .outdat_endpoints = {0x01},
+ .inack_endpoints = {0x83},
+ .outcont_endpoints = {0x03},
+ .instat_endpoint = 0x84,
+ .indat_endpoint = -1,
+ .glocont_endpoint = -1,
+ .calculate_baud_rate = keyspan_usa28_calc_baud,
+ .baudclk = KEYSPAN_USA19_BAUDCLK,
+};
+
+static const struct keyspan_device_details usa19qw_device_details = {
+ .product_id = keyspan_usa19qw_product_id,
+ .msg_format = msg_usa26,
+ .num_ports = 1,
+ .indat_endp_flip = 0,
+ .outdat_endp_flip = 1,
+ .indat_endpoints = {0x81},
+ .outdat_endpoints = {0x01},
+ .inack_endpoints = {0x85},
+ .outcont_endpoints = {0x05},
+ .instat_endpoint = 0x87,
+ .indat_endpoint = -1,
+ .glocont_endpoint = 0x07,
+ .calculate_baud_rate = keyspan_usa19w_calc_baud,
+ .baudclk = KEYSPAN_USA19W_BAUDCLK,
+};
+
+static const struct keyspan_device_details usa19w_device_details = {
+ .product_id = keyspan_usa19w_product_id,
+ .msg_format = msg_usa26,
+ .num_ports = 1,
+ .indat_endp_flip = 0,
+ .outdat_endp_flip = 1,
+ .indat_endpoints = {0x81},
+ .outdat_endpoints = {0x01},
+ .inack_endpoints = {0x85},
+ .outcont_endpoints = {0x05},
+ .instat_endpoint = 0x87,
+ .indat_endpoint = -1,
+ .glocont_endpoint = 0x07,
+ .calculate_baud_rate = keyspan_usa19w_calc_baud,
+ .baudclk = KEYSPAN_USA19W_BAUDCLK,
+};
+
+static const struct keyspan_device_details usa19hs_device_details = {
+ .product_id = keyspan_usa19hs_product_id,
+ .msg_format = msg_usa90,
+ .num_ports = 1,
+ .indat_endp_flip = 0,
+ .outdat_endp_flip = 0,
+ .indat_endpoints = {0x81},
+ .outdat_endpoints = {0x01},
+ .inack_endpoints = {-1},
+ .outcont_endpoints = {0x02},
+ .instat_endpoint = 0x82,
+ .indat_endpoint = -1,
+ .glocont_endpoint = -1,
+ .calculate_baud_rate = keyspan_usa19hs_calc_baud,
+ .baudclk = KEYSPAN_USA19HS_BAUDCLK,
+};
+
+static const struct keyspan_device_details usa28_device_details = {
+ .product_id = keyspan_usa28_product_id,
+ .msg_format = msg_usa28,
+ .num_ports = 2,
+ .indat_endp_flip = 1,
+ .outdat_endp_flip = 1,
+ .indat_endpoints = {0x81, 0x83},
+ .outdat_endpoints = {0x01, 0x03},
+ .inack_endpoints = {0x85, 0x86},
+ .outcont_endpoints = {0x05, 0x06},
+ .instat_endpoint = 0x87,
+ .indat_endpoint = -1,
+ .glocont_endpoint = 0x07,
+ .calculate_baud_rate = keyspan_usa28_calc_baud,
+ .baudclk = KEYSPAN_USA28_BAUDCLK,
+};
+
+static const struct keyspan_device_details usa28x_device_details = {
+ .product_id = keyspan_usa28x_product_id,
+ .msg_format = msg_usa26,
+ .num_ports = 2,
+ .indat_endp_flip = 0,
+ .outdat_endp_flip = 1,
+ .indat_endpoints = {0x81, 0x83},
+ .outdat_endpoints = {0x01, 0x03},
+ .inack_endpoints = {0x85, 0x86},
+ .outcont_endpoints = {0x05, 0x06},
+ .instat_endpoint = 0x87,
+ .indat_endpoint = -1,
+ .glocont_endpoint = 0x07,
+ .calculate_baud_rate = keyspan_usa19w_calc_baud,
+ .baudclk = KEYSPAN_USA28X_BAUDCLK,
+};
+
+static const struct keyspan_device_details usa28xa_device_details = {
+ .product_id = keyspan_usa28xa_product_id,
+ .msg_format = msg_usa26,
+ .num_ports = 2,
+ .indat_endp_flip = 0,
+ .outdat_endp_flip = 1,
+ .indat_endpoints = {0x81, 0x83},
+ .outdat_endpoints = {0x01, 0x03},
+ .inack_endpoints = {0x85, 0x86},
+ .outcont_endpoints = {0x05, 0x06},
+ .instat_endpoint = 0x87,
+ .indat_endpoint = -1,
+ .glocont_endpoint = 0x07,
+ .calculate_baud_rate = keyspan_usa19w_calc_baud,
+ .baudclk = KEYSPAN_USA28X_BAUDCLK,
+};
+
+static const struct keyspan_device_details usa28xg_device_details = {
+ .product_id = keyspan_usa28xg_product_id,
+ .msg_format = msg_usa67,
+ .num_ports = 2,
+ .indat_endp_flip = 0,
+ .outdat_endp_flip = 0,
+ .indat_endpoints = {0x84, 0x88},
+ .outdat_endpoints = {0x02, 0x06},
+ .inack_endpoints = {-1, -1},
+ .outcont_endpoints = {-1, -1},
+ .instat_endpoint = 0x81,
+ .indat_endpoint = -1,
+ .glocont_endpoint = 0x01,
+ .calculate_baud_rate = keyspan_usa19w_calc_baud,
+ .baudclk = KEYSPAN_USA28X_BAUDCLK,
+};
+/*
+ * We don't need a separate entry for the usa28xb as it appears as a 28x
+ * anyway.
+ */
+
+static const struct keyspan_device_details usa49w_device_details = {
+ .product_id = keyspan_usa49w_product_id,
+ .msg_format = msg_usa49,
+ .num_ports = 4,
+ .indat_endp_flip = 0,
+ .outdat_endp_flip = 0,
+ .indat_endpoints = {0x81, 0x82, 0x83, 0x84},
+ .outdat_endpoints = {0x01, 0x02, 0x03, 0x04},
+ .inack_endpoints = {-1, -1, -1, -1},
+ .outcont_endpoints = {-1, -1, -1, -1},
+ .instat_endpoint = 0x87,
+ .indat_endpoint = -1,
+ .glocont_endpoint = 0x07,
+ .calculate_baud_rate = keyspan_usa19w_calc_baud,
+ .baudclk = KEYSPAN_USA49W_BAUDCLK,
+};
+
+static const struct keyspan_device_details usa49wlc_device_details = {
+ .product_id = keyspan_usa49wlc_product_id,
+ .msg_format = msg_usa49,
+ .num_ports = 4,
+ .indat_endp_flip = 0,
+ .outdat_endp_flip = 0,
+ .indat_endpoints = {0x81, 0x82, 0x83, 0x84},
+ .outdat_endpoints = {0x01, 0x02, 0x03, 0x04},
+ .inack_endpoints = {-1, -1, -1, -1},
+ .outcont_endpoints = {-1, -1, -1, -1},
+ .instat_endpoint = 0x87,
+ .indat_endpoint = -1,
+ .glocont_endpoint = 0x07,
+ .calculate_baud_rate = keyspan_usa19w_calc_baud,
+ .baudclk = KEYSPAN_USA19W_BAUDCLK,
+};
+
+static const struct keyspan_device_details usa49wg_device_details = {
+ .product_id = keyspan_usa49wg_product_id,
+ .msg_format = msg_usa49,
+ .num_ports = 4,
+ .indat_endp_flip = 0,
+ .outdat_endp_flip = 0,
+ .indat_endpoints = {-1, -1, -1, -1}, /* single 'global' data in EP */
+ .outdat_endpoints = {0x01, 0x02, 0x04, 0x06},
+ .inack_endpoints = {-1, -1, -1, -1},
+ .outcont_endpoints = {-1, -1, -1, -1},
+ .instat_endpoint = 0x81,
+ .indat_endpoint = 0x88,
+ .glocont_endpoint = 0x00, /* uses control EP */
+ .calculate_baud_rate = keyspan_usa19w_calc_baud,
+ .baudclk = KEYSPAN_USA19W_BAUDCLK,
+};
+
+static const struct keyspan_device_details *keyspan_devices[] = {
+ &usa18x_device_details,
+ &usa19_device_details,
+ &usa19qi_device_details,
+ &mpr_device_details,
+ &usa19qw_device_details,
+ &usa19w_device_details,
+ &usa19hs_device_details,
+ &usa28_device_details,
+ &usa28x_device_details,
+ &usa28xa_device_details,
+ &usa28xg_device_details,
+ /* 28xb not required as it renumerates as a 28x */
+ &usa49w_device_details,
+ &usa49wlc_device_details,
+ &usa49wg_device_details,
+ NULL,
+};
+
+static const struct usb_device_id keyspan_ids_combined[] = {
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa18x_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19w_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qi_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qw_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_mpr_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28x_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xa_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xb_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49w_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49wlc_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa18x_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19w_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qi_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qw_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19hs_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_mpr_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28x_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xa_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xg_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49w_product_id)},
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49wlc_product_id)},
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49wg_product_id)},
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, keyspan_ids_combined);
+
+/* usb_device_id table for the pre-firmware download keyspan devices */
+static const struct usb_device_id keyspan_pre_ids[] = {
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa18x_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qi_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qw_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19w_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_mpr_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28x_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xa_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xb_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49w_pre_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49wlc_pre_product_id) },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id keyspan_1port_ids[] = {
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa18x_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qi_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qw_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19w_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19hs_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_mpr_product_id) },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id keyspan_2port_ids[] = {
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28x_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xa_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xg_product_id) },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id keyspan_4port_ids[] = {
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49w_product_id) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49wlc_product_id)},
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49wg_product_id)},
+ { } /* Terminating entry */
+};
+
+#define INSTAT_BUFLEN 32
+#define GLOCONT_BUFLEN 64
+#define INDAT49W_BUFLEN 512
+#define IN_BUFLEN 64
+#define OUT_BUFLEN 64
+#define INACK_BUFLEN 1
+#define OUTCONT_BUFLEN 64
+
+ /* Per device and per port private data */
+struct keyspan_serial_private {
+ const struct keyspan_device_details *device_details;
+
+ struct urb *instat_urb;
+ char *instat_buf;
+
+ /* added to support 49wg, where data from all 4 ports comes in
+ on 1 EP and high-speed supported */
+ struct urb *indat_urb;
+ char *indat_buf;
+
+ /* XXX this one probably will need a lock */
+ struct urb *glocont_urb;
+ char *glocont_buf;
+ char *ctrl_buf; /* for EP0 control message */
+};
+
+struct keyspan_port_private {
+ /* Keep track of which input & output endpoints to use */
+ int in_flip;
+ int out_flip;
+
+ /* Keep duplicate of device details in each port
+ structure as well - simplifies some of the
+ callback functions etc. */
+ const struct keyspan_device_details *device_details;
+
+ /* Input endpoints and buffer for this port */
+ struct urb *in_urbs[2];
+ char *in_buffer[2];
+ /* Output endpoints and buffer for this port */
+ struct urb *out_urbs[2];
+ char *out_buffer[2];
+
+ /* Input ack endpoint */
+ struct urb *inack_urb;
+ char *inack_buffer;
+
+ /* Output control endpoint */
+ struct urb *outcont_urb;
+ char *outcont_buffer;
+
+ /* Settings for the port */
+ int baud;
+ int old_baud;
+ unsigned int cflag;
+ unsigned int old_cflag;
+ enum {flow_none, flow_cts, flow_xon} flow_control;
+ int rts_state; /* Handshaking pins (outputs) */
+ int dtr_state;
+ int cts_state; /* Handshaking pins (inputs) */
+ int dsr_state;
+ int dcd_state;
+ int ri_state;
+ int break_on;
+
+ unsigned long tx_start_time[2];
+ int resend_cont; /* need to resend control packet */
+};
+
+/* Include Keyspan message headers. All current Keyspan Adapters
+ make use of one of five message formats which are referred
+ to as USA-26, USA-28, USA-49, USA-90, USA-67 by Keyspan and
+ within this driver. */
+#include "keyspan_usa26msg.h"
+#include "keyspan_usa28msg.h"
+#include "keyspan_usa49msg.h"
+#include "keyspan_usa90msg.h"
+#include "keyspan_usa67msg.h"
+
+
+static int keyspan_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct keyspan_port_private *p_priv;
+
+ p_priv = usb_get_serial_port_data(port);
+
+ if (break_state == -1)
+ p_priv->break_on = 1;
+ else
+ p_priv->break_on = 0;
+
+ /* FIXME: return errors */
+ keyspan_send_setup(port, 0);
+
+ return 0;
+}
+
+
+static void keyspan_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ int baud_rate, device_port;
+ struct keyspan_port_private *p_priv;
+ const struct keyspan_device_details *d_details;
+ unsigned int cflag;
+
+ p_priv = usb_get_serial_port_data(port);
+ d_details = p_priv->device_details;
+ cflag = tty->termios.c_cflag;
+ device_port = port->port_number;
+
+ /* Baud rate calculation takes baud rate as an integer
+ so other rates can be generated if desired. */
+ baud_rate = tty_get_baud_rate(tty);
+ /* If no match or invalid, don't change */
+ if (d_details->calculate_baud_rate(port, baud_rate, d_details->baudclk,
+ NULL, NULL, NULL, device_port) == KEYSPAN_BAUD_RATE_OK) {
+ /* FIXME - more to do here to ensure rate changes cleanly */
+ /* FIXME - calculate exact rate from divisor ? */
+ p_priv->baud = baud_rate;
+ } else
+ baud_rate = tty_termios_baud_rate(old_termios);
+
+ tty_encode_baud_rate(tty, baud_rate, baud_rate);
+ /* set CTS/RTS handshake etc. */
+ p_priv->cflag = cflag;
+ p_priv->flow_control = (cflag & CRTSCTS) ? flow_cts : flow_none;
+
+ /* Mark/Space not supported */
+ tty->termios.c_cflag &= ~CMSPAR;
+
+ keyspan_send_setup(port, 0);
+}
+
+static int keyspan_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct keyspan_port_private *p_priv = usb_get_serial_port_data(port);
+ unsigned int value;
+
+ value = ((p_priv->rts_state) ? TIOCM_RTS : 0) |
+ ((p_priv->dtr_state) ? TIOCM_DTR : 0) |
+ ((p_priv->cts_state) ? TIOCM_CTS : 0) |
+ ((p_priv->dsr_state) ? TIOCM_DSR : 0) |
+ ((p_priv->dcd_state) ? TIOCM_CAR : 0) |
+ ((p_priv->ri_state) ? TIOCM_RNG : 0);
+
+ return value;
+}
+
+static int keyspan_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct keyspan_port_private *p_priv = usb_get_serial_port_data(port);
+
+ if (set & TIOCM_RTS)
+ p_priv->rts_state = 1;
+ if (set & TIOCM_DTR)
+ p_priv->dtr_state = 1;
+ if (clear & TIOCM_RTS)
+ p_priv->rts_state = 0;
+ if (clear & TIOCM_DTR)
+ p_priv->dtr_state = 0;
+ keyspan_send_setup(port, 0);
+ return 0;
+}
+
+/* Write function is similar for the four protocols used
+ with only a minor change for usa90 (usa19hs) required */
+static int keyspan_write(struct tty_struct *tty,
+ struct usb_serial_port *port, const unsigned char *buf, int count)
+{
+ struct keyspan_port_private *p_priv;
+ const struct keyspan_device_details *d_details;
+ int flip;
+ int left, todo;
+ struct urb *this_urb;
+ int err, maxDataLen, dataOffset;
+
+ p_priv = usb_get_serial_port_data(port);
+ d_details = p_priv->device_details;
+
+ if (d_details->msg_format == msg_usa90) {
+ maxDataLen = 64;
+ dataOffset = 0;
+ } else {
+ maxDataLen = 63;
+ dataOffset = 1;
+ }
+
+ dev_dbg(&port->dev, "%s - %d chars, flip=%d\n", __func__, count,
+ p_priv->out_flip);
+
+ for (left = count; left > 0; left -= todo) {
+ todo = left;
+ if (todo > maxDataLen)
+ todo = maxDataLen;
+
+ flip = p_priv->out_flip;
+
+ /* Check we have a valid urb/endpoint before we use it... */
+ this_urb = p_priv->out_urbs[flip];
+ if (this_urb == NULL) {
+ /* no bulk out, so return 0 bytes written */
+ dev_dbg(&port->dev, "%s - no output urb :(\n", __func__);
+ return count;
+ }
+
+ dev_dbg(&port->dev, "%s - endpoint %x flip %d\n",
+ __func__, usb_pipeendpoint(this_urb->pipe), flip);
+
+ if (this_urb->status == -EINPROGRESS) {
+ if (time_before(jiffies,
+ p_priv->tx_start_time[flip] + 10 * HZ))
+ break;
+ usb_unlink_urb(this_urb);
+ break;
+ }
+
+ /* First byte in buffer is "last flag" (except for usa19hx)
+ - unused so for now so set to zero */
+ ((char *)this_urb->transfer_buffer)[0] = 0;
+
+ memcpy(this_urb->transfer_buffer + dataOffset, buf, todo);
+ buf += todo;
+
+ /* send the data out the bulk port */
+ this_urb->transfer_buffer_length = todo + dataOffset;
+
+ err = usb_submit_urb(this_urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "usb_submit_urb(write bulk) failed (%d)\n", err);
+ p_priv->tx_start_time[flip] = jiffies;
+
+ /* Flip for next time if usa26 or usa28 interface
+ (not used on usa49) */
+ p_priv->out_flip = (flip + 1) & d_details->outdat_endp_flip;
+ }
+
+ return count - left;
+}
+
+static void usa26_indat_callback(struct urb *urb)
+{
+ int i, err;
+ int endpoint;
+ struct usb_serial_port *port;
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+
+ endpoint = usb_pipeendpoint(urb->pipe);
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero status %d on endpoint %x\n",
+ __func__, status, endpoint);
+ return;
+ }
+
+ port = urb->context;
+ if (urb->actual_length) {
+ /* 0x80 bit is error flag */
+ if ((data[0] & 0x80) == 0) {
+ /* no errors on individual bytes, only
+ possible overrun err */
+ if (data[0] & RXERROR_OVERRUN) {
+ tty_insert_flip_char(&port->port, 0,
+ TTY_OVERRUN);
+ }
+ for (i = 1; i < urb->actual_length ; ++i)
+ tty_insert_flip_char(&port->port, data[i],
+ TTY_NORMAL);
+ } else {
+ /* some bytes had errors, every byte has status */
+ dev_dbg(&port->dev, "%s - RX error!!!!\n", __func__);
+ for (i = 0; i + 1 < urb->actual_length; i += 2) {
+ int stat = data[i];
+ int flag = TTY_NORMAL;
+
+ if (stat & RXERROR_OVERRUN) {
+ tty_insert_flip_char(&port->port, 0,
+ TTY_OVERRUN);
+ }
+ /* XXX should handle break (0x10) */
+ if (stat & RXERROR_PARITY)
+ flag = TTY_PARITY;
+ else if (stat & RXERROR_FRAMING)
+ flag = TTY_FRAME;
+
+ tty_insert_flip_char(&port->port, data[i+1],
+ flag);
+ }
+ }
+ tty_flip_buffer_push(&port->port);
+ }
+
+ /* Resubmit urb so we continue receiving */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err);
+}
+
+/* Outdat handling is common for all devices */
+static void usa2x_outdat_callback(struct urb *urb)
+{
+ struct usb_serial_port *port;
+ struct keyspan_port_private *p_priv;
+
+ port = urb->context;
+ p_priv = usb_get_serial_port_data(port);
+ dev_dbg(&port->dev, "%s - urb %d\n", __func__, urb == p_priv->out_urbs[1]);
+
+ usb_serial_port_softint(port);
+}
+
+static void usa26_inack_callback(struct urb *urb)
+{
+}
+
+static void usa26_outcont_callback(struct urb *urb)
+{
+ struct usb_serial_port *port;
+ struct keyspan_port_private *p_priv;
+
+ port = urb->context;
+ p_priv = usb_get_serial_port_data(port);
+
+ if (p_priv->resend_cont) {
+ dev_dbg(&port->dev, "%s - sending setup\n", __func__);
+ keyspan_usa26_send_setup(port->serial, port,
+ p_priv->resend_cont - 1);
+ }
+}
+
+static void usa26_instat_callback(struct urb *urb)
+{
+ unsigned char *data = urb->transfer_buffer;
+ struct keyspan_usa26_portStatusMessage *msg;
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+ struct keyspan_port_private *p_priv;
+ int old_dcd_state, err;
+ int status = urb->status;
+
+ serial = urb->context;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero status: %d\n",
+ __func__, status);
+ return;
+ }
+ if (urb->actual_length != 9) {
+ dev_dbg(&urb->dev->dev, "%s - %d byte report??\n", __func__, urb->actual_length);
+ goto exit;
+ }
+
+ msg = (struct keyspan_usa26_portStatusMessage *)data;
+
+ /* Check port number from message and retrieve private data */
+ if (msg->port >= serial->num_ports) {
+ dev_dbg(&urb->dev->dev, "%s - Unexpected port number %d\n", __func__, msg->port);
+ goto exit;
+ }
+ port = serial->port[msg->port];
+ p_priv = usb_get_serial_port_data(port);
+ if (!p_priv)
+ goto resubmit;
+
+ /* Update handshaking pin state information */
+ old_dcd_state = p_priv->dcd_state;
+ p_priv->cts_state = ((msg->hskia_cts) ? 1 : 0);
+ p_priv->dsr_state = ((msg->dsr) ? 1 : 0);
+ p_priv->dcd_state = ((msg->gpia_dcd) ? 1 : 0);
+ p_priv->ri_state = ((msg->ri) ? 1 : 0);
+
+ if (old_dcd_state != p_priv->dcd_state)
+ tty_port_tty_hangup(&port->port, true);
+resubmit:
+ /* Resubmit urb so we continue receiving */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err);
+exit: ;
+}
+
+static void usa26_glocont_callback(struct urb *urb)
+{
+}
+
+
+static void usa28_indat_callback(struct urb *urb)
+{
+ int err;
+ struct usb_serial_port *port;
+ unsigned char *data;
+ struct keyspan_port_private *p_priv;
+ int status = urb->status;
+
+ port = urb->context;
+ p_priv = usb_get_serial_port_data(port);
+
+ if (urb != p_priv->in_urbs[p_priv->in_flip])
+ return;
+
+ do {
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero status %d on endpoint %x\n",
+ __func__, status, usb_pipeendpoint(urb->pipe));
+ return;
+ }
+
+ port = urb->context;
+ p_priv = usb_get_serial_port_data(port);
+ data = urb->transfer_buffer;
+
+ if (urb->actual_length) {
+ tty_insert_flip_string(&port->port, data,
+ urb->actual_length);
+ tty_flip_buffer_push(&port->port);
+ }
+
+ /* Resubmit urb so we continue receiving */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n",
+ __func__, err);
+ p_priv->in_flip ^= 1;
+
+ urb = p_priv->in_urbs[p_priv->in_flip];
+ } while (urb->status != -EINPROGRESS);
+}
+
+static void usa28_inack_callback(struct urb *urb)
+{
+}
+
+static void usa28_outcont_callback(struct urb *urb)
+{
+ struct usb_serial_port *port;
+ struct keyspan_port_private *p_priv;
+
+ port = urb->context;
+ p_priv = usb_get_serial_port_data(port);
+
+ if (p_priv->resend_cont) {
+ dev_dbg(&port->dev, "%s - sending setup\n", __func__);
+ keyspan_usa28_send_setup(port->serial, port,
+ p_priv->resend_cont - 1);
+ }
+}
+
+static void usa28_instat_callback(struct urb *urb)
+{
+ int err;
+ unsigned char *data = urb->transfer_buffer;
+ struct keyspan_usa28_portStatusMessage *msg;
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+ struct keyspan_port_private *p_priv;
+ int old_dcd_state;
+ int status = urb->status;
+
+ serial = urb->context;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero status: %d\n",
+ __func__, status);
+ return;
+ }
+
+ if (urb->actual_length != sizeof(struct keyspan_usa28_portStatusMessage)) {
+ dev_dbg(&urb->dev->dev, "%s - bad length %d\n", __func__, urb->actual_length);
+ goto exit;
+ }
+
+ msg = (struct keyspan_usa28_portStatusMessage *)data;
+
+ /* Check port number from message and retrieve private data */
+ if (msg->port >= serial->num_ports) {
+ dev_dbg(&urb->dev->dev, "%s - Unexpected port number %d\n", __func__, msg->port);
+ goto exit;
+ }
+ port = serial->port[msg->port];
+ p_priv = usb_get_serial_port_data(port);
+ if (!p_priv)
+ goto resubmit;
+
+ /* Update handshaking pin state information */
+ old_dcd_state = p_priv->dcd_state;
+ p_priv->cts_state = ((msg->cts) ? 1 : 0);
+ p_priv->dsr_state = ((msg->dsr) ? 1 : 0);
+ p_priv->dcd_state = ((msg->dcd) ? 1 : 0);
+ p_priv->ri_state = ((msg->ri) ? 1 : 0);
+
+ if (old_dcd_state != p_priv->dcd_state && old_dcd_state)
+ tty_port_tty_hangup(&port->port, true);
+resubmit:
+ /* Resubmit urb so we continue receiving */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err);
+exit: ;
+}
+
+static void usa28_glocont_callback(struct urb *urb)
+{
+}
+
+
+static void usa49_glocont_callback(struct urb *urb)
+{
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+ struct keyspan_port_private *p_priv;
+ int i;
+
+ serial = urb->context;
+ for (i = 0; i < serial->num_ports; ++i) {
+ port = serial->port[i];
+ p_priv = usb_get_serial_port_data(port);
+ if (!p_priv)
+ continue;
+
+ if (p_priv->resend_cont) {
+ dev_dbg(&port->dev, "%s - sending setup\n", __func__);
+ keyspan_usa49_send_setup(serial, port,
+ p_priv->resend_cont - 1);
+ break;
+ }
+ }
+}
+
+ /* This is actually called glostat in the Keyspan
+ doco */
+static void usa49_instat_callback(struct urb *urb)
+{
+ int err;
+ unsigned char *data = urb->transfer_buffer;
+ struct keyspan_usa49_portStatusMessage *msg;
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+ struct keyspan_port_private *p_priv;
+ int old_dcd_state;
+ int status = urb->status;
+
+ serial = urb->context;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero status: %d\n",
+ __func__, status);
+ return;
+ }
+
+ if (urb->actual_length !=
+ sizeof(struct keyspan_usa49_portStatusMessage)) {
+ dev_dbg(&urb->dev->dev, "%s - bad length %d\n", __func__, urb->actual_length);
+ goto exit;
+ }
+
+ msg = (struct keyspan_usa49_portStatusMessage *)data;
+
+ /* Check port number from message and retrieve private data */
+ if (msg->portNumber >= serial->num_ports) {
+ dev_dbg(&urb->dev->dev, "%s - Unexpected port number %d\n",
+ __func__, msg->portNumber);
+ goto exit;
+ }
+ port = serial->port[msg->portNumber];
+ p_priv = usb_get_serial_port_data(port);
+ if (!p_priv)
+ goto resubmit;
+
+ /* Update handshaking pin state information */
+ old_dcd_state = p_priv->dcd_state;
+ p_priv->cts_state = ((msg->cts) ? 1 : 0);
+ p_priv->dsr_state = ((msg->dsr) ? 1 : 0);
+ p_priv->dcd_state = ((msg->dcd) ? 1 : 0);
+ p_priv->ri_state = ((msg->ri) ? 1 : 0);
+
+ if (old_dcd_state != p_priv->dcd_state && old_dcd_state)
+ tty_port_tty_hangup(&port->port, true);
+resubmit:
+ /* Resubmit urb so we continue receiving */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err);
+exit: ;
+}
+
+static void usa49_inack_callback(struct urb *urb)
+{
+}
+
+static void usa49_indat_callback(struct urb *urb)
+{
+ int i, err;
+ int endpoint;
+ struct usb_serial_port *port;
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+
+ endpoint = usb_pipeendpoint(urb->pipe);
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero status %d on endpoint %x\n",
+ __func__, status, endpoint);
+ return;
+ }
+
+ port = urb->context;
+ if (urb->actual_length) {
+ /* 0x80 bit is error flag */
+ if ((data[0] & 0x80) == 0) {
+ /* no error on any byte */
+ tty_insert_flip_string(&port->port, data + 1,
+ urb->actual_length - 1);
+ } else {
+ /* some bytes had errors, every byte has status */
+ for (i = 0; i + 1 < urb->actual_length; i += 2) {
+ int stat = data[i];
+ int flag = TTY_NORMAL;
+
+ if (stat & RXERROR_OVERRUN) {
+ tty_insert_flip_char(&port->port, 0,
+ TTY_OVERRUN);
+ }
+ /* XXX should handle break (0x10) */
+ if (stat & RXERROR_PARITY)
+ flag = TTY_PARITY;
+ else if (stat & RXERROR_FRAMING)
+ flag = TTY_FRAME;
+
+ tty_insert_flip_char(&port->port, data[i+1],
+ flag);
+ }
+ }
+ tty_flip_buffer_push(&port->port);
+ }
+
+ /* Resubmit urb so we continue receiving */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err);
+}
+
+static void usa49wg_indat_callback(struct urb *urb)
+{
+ int i, len, x, err;
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+
+ serial = urb->context;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero status: %d\n",
+ __func__, status);
+ return;
+ }
+
+ /* inbound data is in the form P#, len, status, data */
+ i = 0;
+ len = 0;
+
+ while (i < urb->actual_length) {
+ if (urb->actual_length - i < 3) {
+ dev_warn_ratelimited(&urb->dev->dev, "malformed indat packet\n");
+ break;
+ }
+
+ /* Check port number from message */
+ if (data[i] >= serial->num_ports) {
+ dev_dbg(&urb->dev->dev, "%s - Unexpected port number %d\n",
+ __func__, data[i]);
+ return;
+ }
+ port = serial->port[data[i++]];
+ len = data[i++];
+
+ /* 0x80 bit is error flag */
+ if ((data[i] & 0x80) == 0) {
+ /* no error on any byte */
+ i++;
+ for (x = 1; x < len && i < urb->actual_length; ++x)
+ tty_insert_flip_char(&port->port,
+ data[i++], 0);
+ } else {
+ /*
+ * some bytes had errors, every byte has status
+ */
+ for (x = 0; x + 1 < len &&
+ i + 1 < urb->actual_length; x += 2) {
+ int stat = data[i];
+ int flag = TTY_NORMAL;
+
+ if (stat & RXERROR_OVERRUN) {
+ tty_insert_flip_char(&port->port, 0,
+ TTY_OVERRUN);
+ }
+ /* XXX should handle break (0x10) */
+ if (stat & RXERROR_PARITY)
+ flag = TTY_PARITY;
+ else if (stat & RXERROR_FRAMING)
+ flag = TTY_FRAME;
+
+ tty_insert_flip_char(&port->port, data[i+1],
+ flag);
+ i += 2;
+ }
+ }
+ tty_flip_buffer_push(&port->port);
+ }
+
+ /* Resubmit urb so we continue receiving */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&urb->dev->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err);
+}
+
+/* not used, usa-49 doesn't have per-port control endpoints */
+static void usa49_outcont_callback(struct urb *urb)
+{
+}
+
+static void usa90_indat_callback(struct urb *urb)
+{
+ int i, err;
+ int endpoint;
+ struct usb_serial_port *port;
+ struct keyspan_port_private *p_priv;
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+
+ endpoint = usb_pipeendpoint(urb->pipe);
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero status %d on endpoint %x\n",
+ __func__, status, endpoint);
+ return;
+ }
+
+ port = urb->context;
+ p_priv = usb_get_serial_port_data(port);
+
+ if (urb->actual_length) {
+ /* if current mode is DMA, looks like usa28 format
+ otherwise looks like usa26 data format */
+
+ if (p_priv->baud > 57600)
+ tty_insert_flip_string(&port->port, data,
+ urb->actual_length);
+ else {
+ /* 0x80 bit is error flag */
+ if ((data[0] & 0x80) == 0) {
+ /* no errors on individual bytes, only
+ possible overrun err*/
+ if (data[0] & RXERROR_OVERRUN) {
+ tty_insert_flip_char(&port->port, 0,
+ TTY_OVERRUN);
+ }
+ for (i = 1; i < urb->actual_length ; ++i)
+ tty_insert_flip_char(&port->port,
+ data[i], TTY_NORMAL);
+ } else {
+ /* some bytes had errors, every byte has status */
+ dev_dbg(&port->dev, "%s - RX error!!!!\n", __func__);
+ for (i = 0; i + 1 < urb->actual_length; i += 2) {
+ int stat = data[i];
+ int flag = TTY_NORMAL;
+
+ if (stat & RXERROR_OVERRUN) {
+ tty_insert_flip_char(
+ &port->port, 0,
+ TTY_OVERRUN);
+ }
+ /* XXX should handle break (0x10) */
+ if (stat & RXERROR_PARITY)
+ flag = TTY_PARITY;
+ else if (stat & RXERROR_FRAMING)
+ flag = TTY_FRAME;
+
+ tty_insert_flip_char(&port->port,
+ data[i+1], flag);
+ }
+ }
+ }
+ tty_flip_buffer_push(&port->port);
+ }
+
+ /* Resubmit urb so we continue receiving */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err);
+}
+
+
+static void usa90_instat_callback(struct urb *urb)
+{
+ unsigned char *data = urb->transfer_buffer;
+ struct keyspan_usa90_portStatusMessage *msg;
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+ struct keyspan_port_private *p_priv;
+ int old_dcd_state, err;
+ int status = urb->status;
+
+ serial = urb->context;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero status: %d\n",
+ __func__, status);
+ return;
+ }
+ if (urb->actual_length < 14) {
+ dev_dbg(&urb->dev->dev, "%s - %d byte report??\n", __func__, urb->actual_length);
+ goto exit;
+ }
+
+ msg = (struct keyspan_usa90_portStatusMessage *)data;
+
+ /* Now do something useful with the data */
+
+ port = serial->port[0];
+ p_priv = usb_get_serial_port_data(port);
+ if (!p_priv)
+ goto resubmit;
+
+ /* Update handshaking pin state information */
+ old_dcd_state = p_priv->dcd_state;
+ p_priv->cts_state = ((msg->cts) ? 1 : 0);
+ p_priv->dsr_state = ((msg->dsr) ? 1 : 0);
+ p_priv->dcd_state = ((msg->dcd) ? 1 : 0);
+ p_priv->ri_state = ((msg->ri) ? 1 : 0);
+
+ if (old_dcd_state != p_priv->dcd_state && old_dcd_state)
+ tty_port_tty_hangup(&port->port, true);
+resubmit:
+ /* Resubmit urb so we continue receiving */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err);
+exit:
+ ;
+}
+
+static void usa90_outcont_callback(struct urb *urb)
+{
+ struct usb_serial_port *port;
+ struct keyspan_port_private *p_priv;
+
+ port = urb->context;
+ p_priv = usb_get_serial_port_data(port);
+
+ if (p_priv->resend_cont) {
+ dev_dbg(&urb->dev->dev, "%s - sending setup\n", __func__);
+ keyspan_usa90_send_setup(port->serial, port,
+ p_priv->resend_cont - 1);
+ }
+}
+
+/* Status messages from the 28xg */
+static void usa67_instat_callback(struct urb *urb)
+{
+ int err;
+ unsigned char *data = urb->transfer_buffer;
+ struct keyspan_usa67_portStatusMessage *msg;
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+ struct keyspan_port_private *p_priv;
+ int old_dcd_state;
+ int status = urb->status;
+
+ serial = urb->context;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero status: %d\n",
+ __func__, status);
+ return;
+ }
+
+ if (urb->actual_length !=
+ sizeof(struct keyspan_usa67_portStatusMessage)) {
+ dev_dbg(&urb->dev->dev, "%s - bad length %d\n", __func__, urb->actual_length);
+ return;
+ }
+
+
+ /* Now do something useful with the data */
+ msg = (struct keyspan_usa67_portStatusMessage *)data;
+
+ /* Check port number from message and retrieve private data */
+ if (msg->port >= serial->num_ports) {
+ dev_dbg(&urb->dev->dev, "%s - Unexpected port number %d\n", __func__, msg->port);
+ return;
+ }
+
+ port = serial->port[msg->port];
+ p_priv = usb_get_serial_port_data(port);
+ if (!p_priv)
+ goto resubmit;
+
+ /* Update handshaking pin state information */
+ old_dcd_state = p_priv->dcd_state;
+ p_priv->cts_state = ((msg->hskia_cts) ? 1 : 0);
+ p_priv->dcd_state = ((msg->gpia_dcd) ? 1 : 0);
+
+ if (old_dcd_state != p_priv->dcd_state && old_dcd_state)
+ tty_port_tty_hangup(&port->port, true);
+resubmit:
+ /* Resubmit urb so we continue receiving */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err);
+}
+
+static void usa67_glocont_callback(struct urb *urb)
+{
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+ struct keyspan_port_private *p_priv;
+ int i;
+
+ serial = urb->context;
+ for (i = 0; i < serial->num_ports; ++i) {
+ port = serial->port[i];
+ p_priv = usb_get_serial_port_data(port);
+ if (!p_priv)
+ continue;
+
+ if (p_priv->resend_cont) {
+ dev_dbg(&port->dev, "%s - sending setup\n", __func__);
+ keyspan_usa67_send_setup(serial, port,
+ p_priv->resend_cont - 1);
+ break;
+ }
+ }
+}
+
+static unsigned int keyspan_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct keyspan_port_private *p_priv;
+ const struct keyspan_device_details *d_details;
+ int flip;
+ unsigned int data_len;
+ struct urb *this_urb;
+
+ p_priv = usb_get_serial_port_data(port);
+ d_details = p_priv->device_details;
+
+ /* FIXME: locking */
+ if (d_details->msg_format == msg_usa90)
+ data_len = 64;
+ else
+ data_len = 63;
+
+ flip = p_priv->out_flip;
+
+ /* Check both endpoints to see if any are available. */
+ this_urb = p_priv->out_urbs[flip];
+ if (this_urb != NULL) {
+ if (this_urb->status != -EINPROGRESS)
+ return data_len;
+ flip = (flip + 1) & d_details->outdat_endp_flip;
+ this_urb = p_priv->out_urbs[flip];
+ if (this_urb != NULL) {
+ if (this_urb->status != -EINPROGRESS)
+ return data_len;
+ }
+ }
+ return 0;
+}
+
+
+static int keyspan_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct keyspan_port_private *p_priv;
+ const struct keyspan_device_details *d_details;
+ int i, err;
+ int baud_rate, device_port;
+ struct urb *urb;
+ unsigned int cflag = 0;
+
+ p_priv = usb_get_serial_port_data(port);
+ d_details = p_priv->device_details;
+
+ /* Set some sane defaults */
+ p_priv->rts_state = 1;
+ p_priv->dtr_state = 1;
+ p_priv->baud = 9600;
+
+ /* force baud and lcr to be set on open */
+ p_priv->old_baud = 0;
+ p_priv->old_cflag = 0;
+
+ p_priv->out_flip = 0;
+ p_priv->in_flip = 0;
+
+ /* Reset low level data toggle and start reading from endpoints */
+ for (i = 0; i < 2; i++) {
+ urb = p_priv->in_urbs[i];
+ if (urb == NULL)
+ continue;
+
+ /* make sure endpoint data toggle is synchronized
+ with the device */
+ usb_clear_halt(urb->dev, urb->pipe);
+ err = usb_submit_urb(urb, GFP_KERNEL);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - submit urb %d failed (%d)\n", __func__, i, err);
+ }
+
+ /* Reset low level data toggle on out endpoints */
+ for (i = 0; i < 2; i++) {
+ urb = p_priv->out_urbs[i];
+ if (urb == NULL)
+ continue;
+ /* usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe),
+ usb_pipeout(urb->pipe), 0); */
+ }
+
+ /* get the terminal config for the setup message now so we don't
+ * need to send 2 of them */
+
+ device_port = port->port_number;
+ if (tty) {
+ cflag = tty->termios.c_cflag;
+ /* Baud rate calculation takes baud rate as an integer
+ so other rates can be generated if desired. */
+ baud_rate = tty_get_baud_rate(tty);
+ /* If no match or invalid, leave as default */
+ if (baud_rate >= 0
+ && d_details->calculate_baud_rate(port, baud_rate, d_details->baudclk,
+ NULL, NULL, NULL, device_port) == KEYSPAN_BAUD_RATE_OK) {
+ p_priv->baud = baud_rate;
+ }
+ }
+ /* set CTS/RTS handshake etc. */
+ p_priv->cflag = cflag;
+ p_priv->flow_control = (cflag & CRTSCTS) ? flow_cts : flow_none;
+
+ keyspan_send_setup(port, 1);
+ /* mdelay(100); */
+ /* keyspan_set_termios(port, NULL); */
+
+ return 0;
+}
+
+static void keyspan_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct keyspan_port_private *p_priv = usb_get_serial_port_data(port);
+
+ p_priv->rts_state = on;
+ p_priv->dtr_state = on;
+ keyspan_send_setup(port, 0);
+}
+
+static void keyspan_close(struct usb_serial_port *port)
+{
+ int i;
+ struct keyspan_port_private *p_priv;
+
+ p_priv = usb_get_serial_port_data(port);
+
+ p_priv->rts_state = 0;
+ p_priv->dtr_state = 0;
+
+ keyspan_send_setup(port, 2);
+ /* pilot-xfer seems to work best with this delay */
+ mdelay(100);
+
+ p_priv->out_flip = 0;
+ p_priv->in_flip = 0;
+
+ usb_kill_urb(p_priv->inack_urb);
+ for (i = 0; i < 2; i++) {
+ usb_kill_urb(p_priv->in_urbs[i]);
+ usb_kill_urb(p_priv->out_urbs[i]);
+ }
+}
+
+/* download the firmware to a pre-renumeration device */
+static int keyspan_fake_startup(struct usb_serial *serial)
+{
+ char *fw_name;
+
+ dev_dbg(&serial->dev->dev, "Keyspan startup version %04x product %04x\n",
+ le16_to_cpu(serial->dev->descriptor.bcdDevice),
+ le16_to_cpu(serial->dev->descriptor.idProduct));
+
+ if ((le16_to_cpu(serial->dev->descriptor.bcdDevice) & 0x8000)
+ != 0x8000) {
+ dev_dbg(&serial->dev->dev, "Firmware already loaded. Quitting.\n");
+ return 1;
+ }
+
+ /* Select firmware image on the basis of idProduct */
+ switch (le16_to_cpu(serial->dev->descriptor.idProduct)) {
+ case keyspan_usa28_pre_product_id:
+ fw_name = "keyspan/usa28.fw";
+ break;
+
+ case keyspan_usa28x_pre_product_id:
+ fw_name = "keyspan/usa28x.fw";
+ break;
+
+ case keyspan_usa28xa_pre_product_id:
+ fw_name = "keyspan/usa28xa.fw";
+ break;
+
+ case keyspan_usa28xb_pre_product_id:
+ fw_name = "keyspan/usa28xb.fw";
+ break;
+
+ case keyspan_usa19_pre_product_id:
+ fw_name = "keyspan/usa19.fw";
+ break;
+
+ case keyspan_usa19qi_pre_product_id:
+ fw_name = "keyspan/usa19qi.fw";
+ break;
+
+ case keyspan_mpr_pre_product_id:
+ fw_name = "keyspan/mpr.fw";
+ break;
+
+ case keyspan_usa19qw_pre_product_id:
+ fw_name = "keyspan/usa19qw.fw";
+ break;
+
+ case keyspan_usa18x_pre_product_id:
+ fw_name = "keyspan/usa18x.fw";
+ break;
+
+ case keyspan_usa19w_pre_product_id:
+ fw_name = "keyspan/usa19w.fw";
+ break;
+
+ case keyspan_usa49w_pre_product_id:
+ fw_name = "keyspan/usa49w.fw";
+ break;
+
+ case keyspan_usa49wlc_pre_product_id:
+ fw_name = "keyspan/usa49wlc.fw";
+ break;
+
+ default:
+ dev_err(&serial->dev->dev, "Unknown product ID (%04x)\n",
+ le16_to_cpu(serial->dev->descriptor.idProduct));
+ return 1;
+ }
+
+ dev_dbg(&serial->dev->dev, "Uploading Keyspan %s firmware.\n", fw_name);
+
+ if (ezusb_fx1_ihex_firmware_download(serial->dev, fw_name) < 0) {
+ dev_err(&serial->dev->dev, "failed to load firmware \"%s\"\n",
+ fw_name);
+ return -ENOENT;
+ }
+
+ /* after downloading firmware Renumeration will occur in a
+ moment and the new device will bind to the real driver */
+
+ /* we don't want this device to have a driver assigned to it. */
+ return 1;
+}
+
+/* Helper functions used by keyspan_setup_urbs */
+static struct usb_endpoint_descriptor const *find_ep(struct usb_serial const *serial,
+ int endpoint)
+{
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *ep;
+ int i;
+
+ iface_desc = serial->interface->cur_altsetting;
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+ ep = &iface_desc->endpoint[i].desc;
+ if (ep->bEndpointAddress == endpoint)
+ return ep;
+ }
+ dev_warn(&serial->interface->dev, "found no endpoint descriptor for endpoint %x\n",
+ endpoint);
+ return NULL;
+}
+
+static struct urb *keyspan_setup_urb(struct usb_serial *serial, int endpoint,
+ int dir, void *ctx, char *buf, int len,
+ void (*callback)(struct urb *))
+{
+ struct urb *urb;
+ struct usb_endpoint_descriptor const *ep_desc;
+ char const *ep_type_name;
+
+ if (endpoint == -1)
+ return NULL; /* endpoint not needed */
+
+ dev_dbg(&serial->interface->dev, "%s - alloc for endpoint %x\n",
+ __func__, endpoint);
+ urb = usb_alloc_urb(0, GFP_KERNEL); /* No ISO */
+ if (!urb)
+ return NULL;
+
+ if (endpoint == 0) {
+ /* control EP filled in when used */
+ return urb;
+ }
+
+ ep_desc = find_ep(serial, endpoint);
+ if (!ep_desc) {
+ usb_free_urb(urb);
+ return NULL;
+ }
+ if (usb_endpoint_xfer_int(ep_desc)) {
+ ep_type_name = "INT";
+ usb_fill_int_urb(urb, serial->dev,
+ usb_sndintpipe(serial->dev, endpoint) | dir,
+ buf, len, callback, ctx,
+ ep_desc->bInterval);
+ } else if (usb_endpoint_xfer_bulk(ep_desc)) {
+ ep_type_name = "BULK";
+ usb_fill_bulk_urb(urb, serial->dev,
+ usb_sndbulkpipe(serial->dev, endpoint) | dir,
+ buf, len, callback, ctx);
+ } else {
+ dev_warn(&serial->interface->dev,
+ "unsupported endpoint type %x\n",
+ usb_endpoint_type(ep_desc));
+ usb_free_urb(urb);
+ return NULL;
+ }
+
+ dev_dbg(&serial->interface->dev, "%s - using urb %p for %s endpoint %x\n",
+ __func__, urb, ep_type_name, endpoint);
+ return urb;
+}
+
+static struct callbacks {
+ void (*instat_callback)(struct urb *);
+ void (*glocont_callback)(struct urb *);
+ void (*indat_callback)(struct urb *);
+ void (*outdat_callback)(struct urb *);
+ void (*inack_callback)(struct urb *);
+ void (*outcont_callback)(struct urb *);
+} keyspan_callbacks[] = {
+ {
+ /* msg_usa26 callbacks */
+ .instat_callback = usa26_instat_callback,
+ .glocont_callback = usa26_glocont_callback,
+ .indat_callback = usa26_indat_callback,
+ .outdat_callback = usa2x_outdat_callback,
+ .inack_callback = usa26_inack_callback,
+ .outcont_callback = usa26_outcont_callback,
+ }, {
+ /* msg_usa28 callbacks */
+ .instat_callback = usa28_instat_callback,
+ .glocont_callback = usa28_glocont_callback,
+ .indat_callback = usa28_indat_callback,
+ .outdat_callback = usa2x_outdat_callback,
+ .inack_callback = usa28_inack_callback,
+ .outcont_callback = usa28_outcont_callback,
+ }, {
+ /* msg_usa49 callbacks */
+ .instat_callback = usa49_instat_callback,
+ .glocont_callback = usa49_glocont_callback,
+ .indat_callback = usa49_indat_callback,
+ .outdat_callback = usa2x_outdat_callback,
+ .inack_callback = usa49_inack_callback,
+ .outcont_callback = usa49_outcont_callback,
+ }, {
+ /* msg_usa90 callbacks */
+ .instat_callback = usa90_instat_callback,
+ .glocont_callback = usa28_glocont_callback,
+ .indat_callback = usa90_indat_callback,
+ .outdat_callback = usa2x_outdat_callback,
+ .inack_callback = usa28_inack_callback,
+ .outcont_callback = usa90_outcont_callback,
+ }, {
+ /* msg_usa67 callbacks */
+ .instat_callback = usa67_instat_callback,
+ .glocont_callback = usa67_glocont_callback,
+ .indat_callback = usa26_indat_callback,
+ .outdat_callback = usa2x_outdat_callback,
+ .inack_callback = usa26_inack_callback,
+ .outcont_callback = usa26_outcont_callback,
+ }
+};
+
+ /* Generic setup urbs function that uses
+ data in device_details */
+static void keyspan_setup_urbs(struct usb_serial *serial)
+{
+ struct keyspan_serial_private *s_priv;
+ const struct keyspan_device_details *d_details;
+ struct callbacks *cback;
+
+ s_priv = usb_get_serial_data(serial);
+ d_details = s_priv->device_details;
+
+ /* Setup values for the various callback routines */
+ cback = &keyspan_callbacks[d_details->msg_format];
+
+ /* Allocate and set up urbs for each one that is in use,
+ starting with instat endpoints */
+ s_priv->instat_urb = keyspan_setup_urb
+ (serial, d_details->instat_endpoint, USB_DIR_IN,
+ serial, s_priv->instat_buf, INSTAT_BUFLEN,
+ cback->instat_callback);
+
+ s_priv->indat_urb = keyspan_setup_urb
+ (serial, d_details->indat_endpoint, USB_DIR_IN,
+ serial, s_priv->indat_buf, INDAT49W_BUFLEN,
+ usa49wg_indat_callback);
+
+ s_priv->glocont_urb = keyspan_setup_urb
+ (serial, d_details->glocont_endpoint, USB_DIR_OUT,
+ serial, s_priv->glocont_buf, GLOCONT_BUFLEN,
+ cback->glocont_callback);
+}
+
+/* usa19 function doesn't require prescaler */
+static int keyspan_usa19_calc_baud(struct usb_serial_port *port,
+ u32 baud_rate, u32 baudclk, u8 *rate_hi,
+ u8 *rate_low, u8 *prescaler, int portnum)
+{
+ u32 b16, /* baud rate times 16 (actual rate used internally) */
+ div, /* divisor */
+ cnt; /* inverse of divisor (programmed into 8051) */
+
+ dev_dbg(&port->dev, "%s - %d.\n", __func__, baud_rate);
+
+ /* prevent divide by zero... */
+ b16 = baud_rate * 16L;
+ if (b16 == 0)
+ return KEYSPAN_INVALID_BAUD_RATE;
+ /* Any "standard" rate over 57k6 is marginal on the USA-19
+ as we run out of divisor resolution. */
+ if (baud_rate > 57600)
+ return KEYSPAN_INVALID_BAUD_RATE;
+
+ /* calculate the divisor and the counter (its inverse) */
+ div = baudclk / b16;
+ if (div == 0)
+ return KEYSPAN_INVALID_BAUD_RATE;
+ else
+ cnt = 0 - div;
+
+ if (div > 0xffff)
+ return KEYSPAN_INVALID_BAUD_RATE;
+
+ /* return the counter values if non-null */
+ if (rate_low)
+ *rate_low = (u8) (cnt & 0xff);
+ if (rate_hi)
+ *rate_hi = (u8) ((cnt >> 8) & 0xff);
+ if (rate_low && rate_hi)
+ dev_dbg(&port->dev, "%s - %d %02x %02x.\n",
+ __func__, baud_rate, *rate_hi, *rate_low);
+ return KEYSPAN_BAUD_RATE_OK;
+}
+
+/* usa19hs function doesn't require prescaler */
+static int keyspan_usa19hs_calc_baud(struct usb_serial_port *port,
+ u32 baud_rate, u32 baudclk, u8 *rate_hi,
+ u8 *rate_low, u8 *prescaler, int portnum)
+{
+ u32 b16, /* baud rate times 16 (actual rate used internally) */
+ div; /* divisor */
+
+ dev_dbg(&port->dev, "%s - %d.\n", __func__, baud_rate);
+
+ /* prevent divide by zero... */
+ b16 = baud_rate * 16L;
+ if (b16 == 0)
+ return KEYSPAN_INVALID_BAUD_RATE;
+
+ /* calculate the divisor */
+ div = baudclk / b16;
+ if (div == 0)
+ return KEYSPAN_INVALID_BAUD_RATE;
+
+ if (div > 0xffff)
+ return KEYSPAN_INVALID_BAUD_RATE;
+
+ /* return the counter values if non-null */
+ if (rate_low)
+ *rate_low = (u8) (div & 0xff);
+
+ if (rate_hi)
+ *rate_hi = (u8) ((div >> 8) & 0xff);
+
+ if (rate_low && rate_hi)
+ dev_dbg(&port->dev, "%s - %d %02x %02x.\n",
+ __func__, baud_rate, *rate_hi, *rate_low);
+
+ return KEYSPAN_BAUD_RATE_OK;
+}
+
+static int keyspan_usa19w_calc_baud(struct usb_serial_port *port,
+ u32 baud_rate, u32 baudclk, u8 *rate_hi,
+ u8 *rate_low, u8 *prescaler, int portnum)
+{
+ u32 b16, /* baud rate times 16 (actual rate used internally) */
+ clk, /* clock with 13/8 prescaler */
+ div, /* divisor using 13/8 prescaler */
+ res, /* resulting baud rate using 13/8 prescaler */
+ diff, /* error using 13/8 prescaler */
+ smallest_diff;
+ u8 best_prescaler;
+ int i;
+
+ dev_dbg(&port->dev, "%s - %d.\n", __func__, baud_rate);
+
+ /* prevent divide by zero */
+ b16 = baud_rate * 16L;
+ if (b16 == 0)
+ return KEYSPAN_INVALID_BAUD_RATE;
+
+ /* Calculate prescaler by trying them all and looking
+ for best fit */
+
+ /* start with largest possible difference */
+ smallest_diff = 0xffffffff;
+
+ /* 0 is an invalid prescaler, used as a flag */
+ best_prescaler = 0;
+
+ for (i = 8; i <= 0xff; ++i) {
+ clk = (baudclk * 8) / (u32) i;
+
+ div = clk / b16;
+ if (div == 0)
+ continue;
+
+ res = clk / div;
+ diff = (res > b16) ? (res-b16) : (b16-res);
+
+ if (diff < smallest_diff) {
+ best_prescaler = i;
+ smallest_diff = diff;
+ }
+ }
+
+ if (best_prescaler == 0)
+ return KEYSPAN_INVALID_BAUD_RATE;
+
+ clk = (baudclk * 8) / (u32) best_prescaler;
+ div = clk / b16;
+
+ /* return the divisor and prescaler if non-null */
+ if (rate_low)
+ *rate_low = (u8) (div & 0xff);
+ if (rate_hi)
+ *rate_hi = (u8) ((div >> 8) & 0xff);
+ if (prescaler) {
+ *prescaler = best_prescaler;
+ /* dev_dbg(&port->dev, "%s - %d %d\n", __func__, *prescaler, div); */
+ }
+ return KEYSPAN_BAUD_RATE_OK;
+}
+
+ /* USA-28 supports different maximum baud rates on each port */
+static int keyspan_usa28_calc_baud(struct usb_serial_port *port,
+ u32 baud_rate, u32 baudclk, u8 *rate_hi,
+ u8 *rate_low, u8 *prescaler, int portnum)
+{
+ u32 b16, /* baud rate times 16 (actual rate used internally) */
+ div, /* divisor */
+ cnt; /* inverse of divisor (programmed into 8051) */
+
+ dev_dbg(&port->dev, "%s - %d.\n", __func__, baud_rate);
+
+ /* prevent divide by zero */
+ b16 = baud_rate * 16L;
+ if (b16 == 0)
+ return KEYSPAN_INVALID_BAUD_RATE;
+
+ /* calculate the divisor and the counter (its inverse) */
+ div = KEYSPAN_USA28_BAUDCLK / b16;
+ if (div == 0)
+ return KEYSPAN_INVALID_BAUD_RATE;
+ else
+ cnt = 0 - div;
+
+ /* check for out of range, based on portnum,
+ and return result */
+ if (portnum == 0) {
+ if (div > 0xffff)
+ return KEYSPAN_INVALID_BAUD_RATE;
+ } else {
+ if (portnum == 1) {
+ if (div > 0xff)
+ return KEYSPAN_INVALID_BAUD_RATE;
+ } else
+ return KEYSPAN_INVALID_BAUD_RATE;
+ }
+
+ /* return the counter values if not NULL
+ (port 1 will ignore retHi) */
+ if (rate_low)
+ *rate_low = (u8) (cnt & 0xff);
+ if (rate_hi)
+ *rate_hi = (u8) ((cnt >> 8) & 0xff);
+ dev_dbg(&port->dev, "%s - %d OK.\n", __func__, baud_rate);
+ return KEYSPAN_BAUD_RATE_OK;
+}
+
+static int keyspan_usa26_send_setup(struct usb_serial *serial,
+ struct usb_serial_port *port,
+ int reset_port)
+{
+ struct keyspan_usa26_portControlMessage msg;
+ struct keyspan_serial_private *s_priv;
+ struct keyspan_port_private *p_priv;
+ const struct keyspan_device_details *d_details;
+ struct urb *this_urb;
+ int device_port, err;
+
+ dev_dbg(&port->dev, "%s reset=%d\n", __func__, reset_port);
+
+ s_priv = usb_get_serial_data(serial);
+ p_priv = usb_get_serial_port_data(port);
+ d_details = s_priv->device_details;
+ device_port = port->port_number;
+
+ this_urb = p_priv->outcont_urb;
+
+ /* Make sure we have an urb then send the message */
+ if (this_urb == NULL) {
+ dev_dbg(&port->dev, "%s - oops no urb.\n", __func__);
+ return -1;
+ }
+
+ dev_dbg(&port->dev, "%s - endpoint %x\n",
+ __func__, usb_pipeendpoint(this_urb->pipe));
+
+ /* Save reset port val for resend.
+ Don't overwrite resend for open/close condition. */
+ if ((reset_port + 1) > p_priv->resend_cont)
+ p_priv->resend_cont = reset_port + 1;
+ if (this_urb->status == -EINPROGRESS) {
+ /* dev_dbg(&port->dev, "%s - already writing\n", __func__); */
+ mdelay(5);
+ return -1;
+ }
+
+ memset(&msg, 0, sizeof(struct keyspan_usa26_portControlMessage));
+
+ /* Only set baud rate if it's changed */
+ if (p_priv->old_baud != p_priv->baud) {
+ p_priv->old_baud = p_priv->baud;
+ msg.setClocking = 0xff;
+ if (d_details->calculate_baud_rate(port, p_priv->baud, d_details->baudclk,
+ &msg.baudHi, &msg.baudLo, &msg.prescaler,
+ device_port) == KEYSPAN_INVALID_BAUD_RATE) {
+ dev_dbg(&port->dev, "%s - Invalid baud rate %d requested, using 9600.\n",
+ __func__, p_priv->baud);
+ msg.baudLo = 0;
+ msg.baudHi = 125; /* Values for 9600 baud */
+ msg.prescaler = 10;
+ }
+ msg.setPrescaler = 0xff;
+ }
+
+ msg.lcr = (p_priv->cflag & CSTOPB) ? STOPBITS_678_2 : STOPBITS_5678_1;
+ switch (p_priv->cflag & CSIZE) {
+ case CS5:
+ msg.lcr |= USA_DATABITS_5;
+ break;
+ case CS6:
+ msg.lcr |= USA_DATABITS_6;
+ break;
+ case CS7:
+ msg.lcr |= USA_DATABITS_7;
+ break;
+ case CS8:
+ msg.lcr |= USA_DATABITS_8;
+ break;
+ }
+ if (p_priv->cflag & PARENB) {
+ /* note USA_PARITY_NONE == 0 */
+ msg.lcr |= (p_priv->cflag & PARODD) ?
+ USA_PARITY_ODD : USA_PARITY_EVEN;
+ }
+ msg.setLcr = 0xff;
+
+ msg.ctsFlowControl = (p_priv->flow_control == flow_cts);
+ msg.xonFlowControl = 0;
+ msg.setFlowControl = 0xff;
+ msg.forwardingLength = 16;
+ msg.xonChar = 17;
+ msg.xoffChar = 19;
+
+ /* Opening port */
+ if (reset_port == 1) {
+ msg._txOn = 1;
+ msg._txOff = 0;
+ msg.txFlush = 0;
+ msg.txBreak = 0;
+ msg.rxOn = 1;
+ msg.rxOff = 0;
+ msg.rxFlush = 1;
+ msg.rxForward = 0;
+ msg.returnStatus = 0;
+ msg.resetDataToggle = 0xff;
+ }
+
+ /* Closing port */
+ else if (reset_port == 2) {
+ msg._txOn = 0;
+ msg._txOff = 1;
+ msg.txFlush = 0;
+ msg.txBreak = 0;
+ msg.rxOn = 0;
+ msg.rxOff = 1;
+ msg.rxFlush = 1;
+ msg.rxForward = 0;
+ msg.returnStatus = 0;
+ msg.resetDataToggle = 0;
+ }
+
+ /* Sending intermediate configs */
+ else {
+ msg._txOn = (!p_priv->break_on);
+ msg._txOff = 0;
+ msg.txFlush = 0;
+ msg.txBreak = (p_priv->break_on);
+ msg.rxOn = 0;
+ msg.rxOff = 0;
+ msg.rxFlush = 0;
+ msg.rxForward = 0;
+ msg.returnStatus = 0;
+ msg.resetDataToggle = 0x0;
+ }
+
+ /* Do handshaking outputs */
+ msg.setTxTriState_setRts = 0xff;
+ msg.txTriState_rts = p_priv->rts_state;
+
+ msg.setHskoa_setDtr = 0xff;
+ msg.hskoa_dtr = p_priv->dtr_state;
+
+ p_priv->resend_cont = 0;
+ memcpy(this_urb->transfer_buffer, &msg, sizeof(msg));
+
+ /* send the data out the device on control endpoint */
+ this_urb->transfer_buffer_length = sizeof(msg);
+
+ err = usb_submit_urb(this_urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - usb_submit_urb(setup) failed (%d)\n", __func__, err);
+ return 0;
+}
+
+static int keyspan_usa28_send_setup(struct usb_serial *serial,
+ struct usb_serial_port *port,
+ int reset_port)
+{
+ struct keyspan_usa28_portControlMessage msg;
+ struct keyspan_serial_private *s_priv;
+ struct keyspan_port_private *p_priv;
+ const struct keyspan_device_details *d_details;
+ struct urb *this_urb;
+ int device_port, err;
+
+ s_priv = usb_get_serial_data(serial);
+ p_priv = usb_get_serial_port_data(port);
+ d_details = s_priv->device_details;
+ device_port = port->port_number;
+
+ /* only do something if we have a bulk out endpoint */
+ this_urb = p_priv->outcont_urb;
+ if (this_urb == NULL) {
+ dev_dbg(&port->dev, "%s - oops no urb.\n", __func__);
+ return -1;
+ }
+
+ /* Save reset port val for resend.
+ Don't overwrite resend for open/close condition. */
+ if ((reset_port + 1) > p_priv->resend_cont)
+ p_priv->resend_cont = reset_port + 1;
+ if (this_urb->status == -EINPROGRESS) {
+ dev_dbg(&port->dev, "%s already writing\n", __func__);
+ mdelay(5);
+ return -1;
+ }
+
+ memset(&msg, 0, sizeof(struct keyspan_usa28_portControlMessage));
+
+ msg.setBaudRate = 1;
+ if (d_details->calculate_baud_rate(port, p_priv->baud, d_details->baudclk,
+ &msg.baudHi, &msg.baudLo, NULL,
+ device_port) == KEYSPAN_INVALID_BAUD_RATE) {
+ dev_dbg(&port->dev, "%s - Invalid baud rate requested %d.\n",
+ __func__, p_priv->baud);
+ msg.baudLo = 0xff;
+ msg.baudHi = 0xb2; /* Values for 9600 baud */
+ }
+
+ /* If parity is enabled, we must calculate it ourselves. */
+ msg.parity = 0; /* XXX for now */
+
+ msg.ctsFlowControl = (p_priv->flow_control == flow_cts);
+ msg.xonFlowControl = 0;
+
+ /* Do handshaking outputs, DTR is inverted relative to RTS */
+ msg.rts = p_priv->rts_state;
+ msg.dtr = p_priv->dtr_state;
+
+ msg.forwardingLength = 16;
+ msg.forwardMs = 10;
+ msg.breakThreshold = 45;
+ msg.xonChar = 17;
+ msg.xoffChar = 19;
+
+ /*msg.returnStatus = 1;
+ msg.resetDataToggle = 0xff;*/
+ /* Opening port */
+ if (reset_port == 1) {
+ msg._txOn = 1;
+ msg._txOff = 0;
+ msg.txFlush = 0;
+ msg.txForceXoff = 0;
+ msg.txBreak = 0;
+ msg.rxOn = 1;
+ msg.rxOff = 0;
+ msg.rxFlush = 1;
+ msg.rxForward = 0;
+ msg.returnStatus = 0;
+ msg.resetDataToggle = 0xff;
+ }
+ /* Closing port */
+ else if (reset_port == 2) {
+ msg._txOn = 0;
+ msg._txOff = 1;
+ msg.txFlush = 0;
+ msg.txForceXoff = 0;
+ msg.txBreak = 0;
+ msg.rxOn = 0;
+ msg.rxOff = 1;
+ msg.rxFlush = 1;
+ msg.rxForward = 0;
+ msg.returnStatus = 0;
+ msg.resetDataToggle = 0;
+ }
+ /* Sending intermediate configs */
+ else {
+ msg._txOn = (!p_priv->break_on);
+ msg._txOff = 0;
+ msg.txFlush = 0;
+ msg.txForceXoff = 0;
+ msg.txBreak = (p_priv->break_on);
+ msg.rxOn = 0;
+ msg.rxOff = 0;
+ msg.rxFlush = 0;
+ msg.rxForward = 0;
+ msg.returnStatus = 0;
+ msg.resetDataToggle = 0x0;
+ }
+
+ p_priv->resend_cont = 0;
+ memcpy(this_urb->transfer_buffer, &msg, sizeof(msg));
+
+ /* send the data out the device on control endpoint */
+ this_urb->transfer_buffer_length = sizeof(msg);
+
+ err = usb_submit_urb(this_urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - usb_submit_urb(setup) failed\n", __func__);
+
+ return 0;
+}
+
+static int keyspan_usa49_send_setup(struct usb_serial *serial,
+ struct usb_serial_port *port,
+ int reset_port)
+{
+ struct keyspan_usa49_portControlMessage msg;
+ struct usb_ctrlrequest *dr = NULL;
+ struct keyspan_serial_private *s_priv;
+ struct keyspan_port_private *p_priv;
+ const struct keyspan_device_details *d_details;
+ struct urb *this_urb;
+ int err, device_port;
+
+ s_priv = usb_get_serial_data(serial);
+ p_priv = usb_get_serial_port_data(port);
+ d_details = s_priv->device_details;
+
+ this_urb = s_priv->glocont_urb;
+
+ /* Work out which port within the device is being setup */
+ device_port = port->port_number;
+
+ /* Make sure we have an urb then send the message */
+ if (this_urb == NULL) {
+ dev_dbg(&port->dev, "%s - oops no urb for port.\n", __func__);
+ return -1;
+ }
+
+ dev_dbg(&port->dev, "%s - endpoint %x (%d)\n",
+ __func__, usb_pipeendpoint(this_urb->pipe), device_port);
+
+ /* Save reset port val for resend.
+ Don't overwrite resend for open/close condition. */
+ if ((reset_port + 1) > p_priv->resend_cont)
+ p_priv->resend_cont = reset_port + 1;
+
+ if (this_urb->status == -EINPROGRESS) {
+ /* dev_dbg(&port->dev, "%s - already writing\n", __func__); */
+ mdelay(5);
+ return -1;
+ }
+
+ memset(&msg, 0, sizeof(struct keyspan_usa49_portControlMessage));
+
+ msg.portNumber = device_port;
+
+ /* Only set baud rate if it's changed */
+ if (p_priv->old_baud != p_priv->baud) {
+ p_priv->old_baud = p_priv->baud;
+ msg.setClocking = 0xff;
+ if (d_details->calculate_baud_rate(port, p_priv->baud, d_details->baudclk,
+ &msg.baudHi, &msg.baudLo, &msg.prescaler,
+ device_port) == KEYSPAN_INVALID_BAUD_RATE) {
+ dev_dbg(&port->dev, "%s - Invalid baud rate %d requested, using 9600.\n",
+ __func__, p_priv->baud);
+ msg.baudLo = 0;
+ msg.baudHi = 125; /* Values for 9600 baud */
+ msg.prescaler = 10;
+ }
+ /* msg.setPrescaler = 0xff; */
+ }
+
+ msg.lcr = (p_priv->cflag & CSTOPB) ? STOPBITS_678_2 : STOPBITS_5678_1;
+ switch (p_priv->cflag & CSIZE) {
+ case CS5:
+ msg.lcr |= USA_DATABITS_5;
+ break;
+ case CS6:
+ msg.lcr |= USA_DATABITS_6;
+ break;
+ case CS7:
+ msg.lcr |= USA_DATABITS_7;
+ break;
+ case CS8:
+ msg.lcr |= USA_DATABITS_8;
+ break;
+ }
+ if (p_priv->cflag & PARENB) {
+ /* note USA_PARITY_NONE == 0 */
+ msg.lcr |= (p_priv->cflag & PARODD) ?
+ USA_PARITY_ODD : USA_PARITY_EVEN;
+ }
+ msg.setLcr = 0xff;
+
+ msg.ctsFlowControl = (p_priv->flow_control == flow_cts);
+ msg.xonFlowControl = 0;
+ msg.setFlowControl = 0xff;
+
+ msg.forwardingLength = 16;
+ msg.xonChar = 17;
+ msg.xoffChar = 19;
+
+ /* Opening port */
+ if (reset_port == 1) {
+ msg._txOn = 1;
+ msg._txOff = 0;
+ msg.txFlush = 0;
+ msg.txBreak = 0;
+ msg.rxOn = 1;
+ msg.rxOff = 0;
+ msg.rxFlush = 1;
+ msg.rxForward = 0;
+ msg.returnStatus = 0;
+ msg.resetDataToggle = 0xff;
+ msg.enablePort = 1;
+ msg.disablePort = 0;
+ }
+ /* Closing port */
+ else if (reset_port == 2) {
+ msg._txOn = 0;
+ msg._txOff = 1;
+ msg.txFlush = 0;
+ msg.txBreak = 0;
+ msg.rxOn = 0;
+ msg.rxOff = 1;
+ msg.rxFlush = 1;
+ msg.rxForward = 0;
+ msg.returnStatus = 0;
+ msg.resetDataToggle = 0;
+ msg.enablePort = 0;
+ msg.disablePort = 1;
+ }
+ /* Sending intermediate configs */
+ else {
+ msg._txOn = (!p_priv->break_on);
+ msg._txOff = 0;
+ msg.txFlush = 0;
+ msg.txBreak = (p_priv->break_on);
+ msg.rxOn = 0;
+ msg.rxOff = 0;
+ msg.rxFlush = 0;
+ msg.rxForward = 0;
+ msg.returnStatus = 0;
+ msg.resetDataToggle = 0x0;
+ msg.enablePort = 0;
+ msg.disablePort = 0;
+ }
+
+ /* Do handshaking outputs */
+ msg.setRts = 0xff;
+ msg.rts = p_priv->rts_state;
+
+ msg.setDtr = 0xff;
+ msg.dtr = p_priv->dtr_state;
+
+ p_priv->resend_cont = 0;
+
+ /* if the device is a 49wg, we send control message on usb
+ control EP 0 */
+
+ if (d_details->product_id == keyspan_usa49wg_product_id) {
+ dr = (void *)(s_priv->ctrl_buf);
+ dr->bRequestType = USB_TYPE_VENDOR | USB_DIR_OUT;
+ dr->bRequest = 0xB0; /* 49wg control message */
+ dr->wValue = 0;
+ dr->wIndex = 0;
+ dr->wLength = cpu_to_le16(sizeof(msg));
+
+ memcpy(s_priv->glocont_buf, &msg, sizeof(msg));
+
+ usb_fill_control_urb(this_urb, serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ (unsigned char *)dr, s_priv->glocont_buf,
+ sizeof(msg), usa49_glocont_callback, serial);
+
+ } else {
+ memcpy(this_urb->transfer_buffer, &msg, sizeof(msg));
+
+ /* send the data out the device on control endpoint */
+ this_urb->transfer_buffer_length = sizeof(msg);
+ }
+ err = usb_submit_urb(this_urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - usb_submit_urb(setup) failed (%d)\n", __func__, err);
+
+ return 0;
+}
+
+static int keyspan_usa90_send_setup(struct usb_serial *serial,
+ struct usb_serial_port *port,
+ int reset_port)
+{
+ struct keyspan_usa90_portControlMessage msg;
+ struct keyspan_serial_private *s_priv;
+ struct keyspan_port_private *p_priv;
+ const struct keyspan_device_details *d_details;
+ struct urb *this_urb;
+ int err;
+ u8 prescaler;
+
+ s_priv = usb_get_serial_data(serial);
+ p_priv = usb_get_serial_port_data(port);
+ d_details = s_priv->device_details;
+
+ /* only do something if we have a bulk out endpoint */
+ this_urb = p_priv->outcont_urb;
+ if (this_urb == NULL) {
+ dev_dbg(&port->dev, "%s - oops no urb.\n", __func__);
+ return -1;
+ }
+
+ /* Save reset port val for resend.
+ Don't overwrite resend for open/close condition. */
+ if ((reset_port + 1) > p_priv->resend_cont)
+ p_priv->resend_cont = reset_port + 1;
+ if (this_urb->status == -EINPROGRESS) {
+ dev_dbg(&port->dev, "%s already writing\n", __func__);
+ mdelay(5);
+ return -1;
+ }
+
+ memset(&msg, 0, sizeof(struct keyspan_usa90_portControlMessage));
+
+ /* Only set baud rate if it's changed */
+ if (p_priv->old_baud != p_priv->baud) {
+ p_priv->old_baud = p_priv->baud;
+ msg.setClocking = 0x01;
+ if (d_details->calculate_baud_rate(port, p_priv->baud, d_details->baudclk,
+ &msg.baudHi, &msg.baudLo, &prescaler, 0) == KEYSPAN_INVALID_BAUD_RATE) {
+ dev_dbg(&port->dev, "%s - Invalid baud rate %d requested, using 9600.\n",
+ __func__, p_priv->baud);
+ p_priv->baud = 9600;
+ d_details->calculate_baud_rate(port, p_priv->baud, d_details->baudclk,
+ &msg.baudHi, &msg.baudLo, &prescaler, 0);
+ }
+ msg.setRxMode = 1;
+ msg.setTxMode = 1;
+ }
+
+ /* modes must always be correctly specified */
+ if (p_priv->baud > 57600) {
+ msg.rxMode = RXMODE_DMA;
+ msg.txMode = TXMODE_DMA;
+ } else {
+ msg.rxMode = RXMODE_BYHAND;
+ msg.txMode = TXMODE_BYHAND;
+ }
+
+ msg.lcr = (p_priv->cflag & CSTOPB) ? STOPBITS_678_2 : STOPBITS_5678_1;
+ switch (p_priv->cflag & CSIZE) {
+ case CS5:
+ msg.lcr |= USA_DATABITS_5;
+ break;
+ case CS6:
+ msg.lcr |= USA_DATABITS_6;
+ break;
+ case CS7:
+ msg.lcr |= USA_DATABITS_7;
+ break;
+ case CS8:
+ msg.lcr |= USA_DATABITS_8;
+ break;
+ }
+ if (p_priv->cflag & PARENB) {
+ /* note USA_PARITY_NONE == 0 */
+ msg.lcr |= (p_priv->cflag & PARODD) ?
+ USA_PARITY_ODD : USA_PARITY_EVEN;
+ }
+ if (p_priv->old_cflag != p_priv->cflag) {
+ p_priv->old_cflag = p_priv->cflag;
+ msg.setLcr = 0x01;
+ }
+
+ if (p_priv->flow_control == flow_cts)
+ msg.txFlowControl = TXFLOW_CTS;
+ msg.setTxFlowControl = 0x01;
+ msg.setRxFlowControl = 0x01;
+
+ msg.rxForwardingLength = 16;
+ msg.rxForwardingTimeout = 16;
+ msg.txAckSetting = 0;
+ msg.xonChar = 17;
+ msg.xoffChar = 19;
+
+ /* Opening port */
+ if (reset_port == 1) {
+ msg.portEnabled = 1;
+ msg.rxFlush = 1;
+ msg.txBreak = (p_priv->break_on);
+ }
+ /* Closing port */
+ else if (reset_port == 2)
+ msg.portEnabled = 0;
+ /* Sending intermediate configs */
+ else {
+ msg.portEnabled = 1;
+ msg.txBreak = (p_priv->break_on);
+ }
+
+ /* Do handshaking outputs */
+ msg.setRts = 0x01;
+ msg.rts = p_priv->rts_state;
+
+ msg.setDtr = 0x01;
+ msg.dtr = p_priv->dtr_state;
+
+ p_priv->resend_cont = 0;
+ memcpy(this_urb->transfer_buffer, &msg, sizeof(msg));
+
+ /* send the data out the device on control endpoint */
+ this_urb->transfer_buffer_length = sizeof(msg);
+
+ err = usb_submit_urb(this_urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - usb_submit_urb(setup) failed (%d)\n", __func__, err);
+ return 0;
+}
+
+static int keyspan_usa67_send_setup(struct usb_serial *serial,
+ struct usb_serial_port *port,
+ int reset_port)
+{
+ struct keyspan_usa67_portControlMessage msg;
+ struct keyspan_serial_private *s_priv;
+ struct keyspan_port_private *p_priv;
+ const struct keyspan_device_details *d_details;
+ struct urb *this_urb;
+ int err, device_port;
+
+ s_priv = usb_get_serial_data(serial);
+ p_priv = usb_get_serial_port_data(port);
+ d_details = s_priv->device_details;
+
+ this_urb = s_priv->glocont_urb;
+
+ /* Work out which port within the device is being setup */
+ device_port = port->port_number;
+
+ /* Make sure we have an urb then send the message */
+ if (this_urb == NULL) {
+ dev_dbg(&port->dev, "%s - oops no urb for port.\n", __func__);
+ return -1;
+ }
+
+ /* Save reset port val for resend.
+ Don't overwrite resend for open/close condition. */
+ if ((reset_port + 1) > p_priv->resend_cont)
+ p_priv->resend_cont = reset_port + 1;
+ if (this_urb->status == -EINPROGRESS) {
+ /* dev_dbg(&port->dev, "%s - already writing\n", __func__); */
+ mdelay(5);
+ return -1;
+ }
+
+ memset(&msg, 0, sizeof(struct keyspan_usa67_portControlMessage));
+
+ msg.port = device_port;
+
+ /* Only set baud rate if it's changed */
+ if (p_priv->old_baud != p_priv->baud) {
+ p_priv->old_baud = p_priv->baud;
+ msg.setClocking = 0xff;
+ if (d_details->calculate_baud_rate(port, p_priv->baud, d_details->baudclk,
+ &msg.baudHi, &msg.baudLo, &msg.prescaler,
+ device_port) == KEYSPAN_INVALID_BAUD_RATE) {
+ dev_dbg(&port->dev, "%s - Invalid baud rate %d requested, using 9600.\n",
+ __func__, p_priv->baud);
+ msg.baudLo = 0;
+ msg.baudHi = 125; /* Values for 9600 baud */
+ msg.prescaler = 10;
+ }
+ msg.setPrescaler = 0xff;
+ }
+
+ msg.lcr = (p_priv->cflag & CSTOPB) ? STOPBITS_678_2 : STOPBITS_5678_1;
+ switch (p_priv->cflag & CSIZE) {
+ case CS5:
+ msg.lcr |= USA_DATABITS_5;
+ break;
+ case CS6:
+ msg.lcr |= USA_DATABITS_6;
+ break;
+ case CS7:
+ msg.lcr |= USA_DATABITS_7;
+ break;
+ case CS8:
+ msg.lcr |= USA_DATABITS_8;
+ break;
+ }
+ if (p_priv->cflag & PARENB) {
+ /* note USA_PARITY_NONE == 0 */
+ msg.lcr |= (p_priv->cflag & PARODD) ?
+ USA_PARITY_ODD : USA_PARITY_EVEN;
+ }
+ msg.setLcr = 0xff;
+
+ msg.ctsFlowControl = (p_priv->flow_control == flow_cts);
+ msg.xonFlowControl = 0;
+ msg.setFlowControl = 0xff;
+ msg.forwardingLength = 16;
+ msg.xonChar = 17;
+ msg.xoffChar = 19;
+
+ if (reset_port == 1) {
+ /* Opening port */
+ msg._txOn = 1;
+ msg._txOff = 0;
+ msg.txFlush = 0;
+ msg.txBreak = 0;
+ msg.rxOn = 1;
+ msg.rxOff = 0;
+ msg.rxFlush = 1;
+ msg.rxForward = 0;
+ msg.returnStatus = 0;
+ msg.resetDataToggle = 0xff;
+ } else if (reset_port == 2) {
+ /* Closing port */
+ msg._txOn = 0;
+ msg._txOff = 1;
+ msg.txFlush = 0;
+ msg.txBreak = 0;
+ msg.rxOn = 0;
+ msg.rxOff = 1;
+ msg.rxFlush = 1;
+ msg.rxForward = 0;
+ msg.returnStatus = 0;
+ msg.resetDataToggle = 0;
+ } else {
+ /* Sending intermediate configs */
+ msg._txOn = (!p_priv->break_on);
+ msg._txOff = 0;
+ msg.txFlush = 0;
+ msg.txBreak = (p_priv->break_on);
+ msg.rxOn = 0;
+ msg.rxOff = 0;
+ msg.rxFlush = 0;
+ msg.rxForward = 0;
+ msg.returnStatus = 0;
+ msg.resetDataToggle = 0x0;
+ }
+
+ /* Do handshaking outputs */
+ msg.setTxTriState_setRts = 0xff;
+ msg.txTriState_rts = p_priv->rts_state;
+
+ msg.setHskoa_setDtr = 0xff;
+ msg.hskoa_dtr = p_priv->dtr_state;
+
+ p_priv->resend_cont = 0;
+
+ memcpy(this_urb->transfer_buffer, &msg, sizeof(msg));
+
+ /* send the data out the device on control endpoint */
+ this_urb->transfer_buffer_length = sizeof(msg);
+
+ err = usb_submit_urb(this_urb, GFP_ATOMIC);
+ if (err != 0)
+ dev_dbg(&port->dev, "%s - usb_submit_urb(setup) failed (%d)\n", __func__, err);
+ return 0;
+}
+
+static void keyspan_send_setup(struct usb_serial_port *port, int reset_port)
+{
+ struct usb_serial *serial = port->serial;
+ struct keyspan_serial_private *s_priv;
+ const struct keyspan_device_details *d_details;
+
+ s_priv = usb_get_serial_data(serial);
+ d_details = s_priv->device_details;
+
+ switch (d_details->msg_format) {
+ case msg_usa26:
+ keyspan_usa26_send_setup(serial, port, reset_port);
+ break;
+ case msg_usa28:
+ keyspan_usa28_send_setup(serial, port, reset_port);
+ break;
+ case msg_usa49:
+ keyspan_usa49_send_setup(serial, port, reset_port);
+ break;
+ case msg_usa90:
+ keyspan_usa90_send_setup(serial, port, reset_port);
+ break;
+ case msg_usa67:
+ keyspan_usa67_send_setup(serial, port, reset_port);
+ break;
+ }
+}
+
+
+/* Gets called by the "real" driver (ie once firmware is loaded
+ and renumeration has taken place. */
+static int keyspan_startup(struct usb_serial *serial)
+{
+ int i, err;
+ struct keyspan_serial_private *s_priv;
+ const struct keyspan_device_details *d_details;
+
+ for (i = 0; (d_details = keyspan_devices[i]) != NULL; ++i)
+ if (d_details->product_id ==
+ le16_to_cpu(serial->dev->descriptor.idProduct))
+ break;
+ if (d_details == NULL) {
+ dev_err(&serial->dev->dev, "%s - unknown product id %x\n",
+ __func__, le16_to_cpu(serial->dev->descriptor.idProduct));
+ return -ENODEV;
+ }
+
+ /* Setup private data for serial driver */
+ s_priv = kzalloc_obj(struct keyspan_serial_private);
+ if (!s_priv)
+ return -ENOMEM;
+
+ s_priv->instat_buf = kzalloc(INSTAT_BUFLEN, GFP_KERNEL);
+ if (!s_priv->instat_buf)
+ goto err_instat_buf;
+
+ s_priv->indat_buf = kzalloc(INDAT49W_BUFLEN, GFP_KERNEL);
+ if (!s_priv->indat_buf)
+ goto err_indat_buf;
+
+ s_priv->glocont_buf = kzalloc(GLOCONT_BUFLEN, GFP_KERNEL);
+ if (!s_priv->glocont_buf)
+ goto err_glocont_buf;
+
+ s_priv->ctrl_buf = kzalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
+ if (!s_priv->ctrl_buf)
+ goto err_ctrl_buf;
+
+ s_priv->device_details = d_details;
+ usb_set_serial_data(serial, s_priv);
+
+ keyspan_setup_urbs(serial);
+
+ if (s_priv->instat_urb != NULL) {
+ err = usb_submit_urb(s_priv->instat_urb, GFP_KERNEL);
+ if (err != 0)
+ dev_dbg(&serial->dev->dev, "%s - submit instat urb failed %d\n", __func__, err);
+ }
+ if (s_priv->indat_urb != NULL) {
+ err = usb_submit_urb(s_priv->indat_urb, GFP_KERNEL);
+ if (err != 0)
+ dev_dbg(&serial->dev->dev, "%s - submit indat urb failed %d\n", __func__, err);
+ }
+
+ return 0;
+
+err_ctrl_buf:
+ kfree(s_priv->glocont_buf);
+err_glocont_buf:
+ kfree(s_priv->indat_buf);
+err_indat_buf:
+ kfree(s_priv->instat_buf);
+err_instat_buf:
+ kfree(s_priv);
+
+ return -ENOMEM;
+}
+
+static void keyspan_disconnect(struct usb_serial *serial)
+{
+ struct keyspan_serial_private *s_priv;
+
+ s_priv = usb_get_serial_data(serial);
+
+ usb_kill_urb(s_priv->instat_urb);
+ usb_kill_urb(s_priv->glocont_urb);
+ usb_kill_urb(s_priv->indat_urb);
+}
+
+static void keyspan_release(struct usb_serial *serial)
+{
+ struct keyspan_serial_private *s_priv;
+
+ s_priv = usb_get_serial_data(serial);
+
+ /* Make sure to unlink the URBs submitted in attach. */
+ usb_kill_urb(s_priv->instat_urb);
+ usb_kill_urb(s_priv->indat_urb);
+
+ usb_free_urb(s_priv->instat_urb);
+ usb_free_urb(s_priv->indat_urb);
+ usb_free_urb(s_priv->glocont_urb);
+
+ kfree(s_priv->ctrl_buf);
+ kfree(s_priv->glocont_buf);
+ kfree(s_priv->indat_buf);
+ kfree(s_priv->instat_buf);
+
+ kfree(s_priv);
+}
+
+static int keyspan_port_probe(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct keyspan_serial_private *s_priv;
+ struct keyspan_port_private *p_priv;
+ const struct keyspan_device_details *d_details;
+ struct callbacks *cback;
+ int endp;
+ int port_num;
+ int i;
+
+ s_priv = usb_get_serial_data(serial);
+ d_details = s_priv->device_details;
+
+ p_priv = kzalloc_obj(*p_priv);
+ if (!p_priv)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(p_priv->in_buffer); ++i) {
+ p_priv->in_buffer[i] = kzalloc(IN_BUFLEN, GFP_KERNEL);
+ if (!p_priv->in_buffer[i])
+ goto err_free_in_buffer;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(p_priv->out_buffer); ++i) {
+ p_priv->out_buffer[i] = kzalloc(OUT_BUFLEN, GFP_KERNEL);
+ if (!p_priv->out_buffer[i])
+ goto err_free_out_buffer;
+ }
+
+ p_priv->inack_buffer = kzalloc(INACK_BUFLEN, GFP_KERNEL);
+ if (!p_priv->inack_buffer)
+ goto err_free_out_buffer;
+
+ p_priv->outcont_buffer = kzalloc(OUTCONT_BUFLEN, GFP_KERNEL);
+ if (!p_priv->outcont_buffer)
+ goto err_free_inack_buffer;
+
+ p_priv->device_details = d_details;
+
+ /* Setup values for the various callback routines */
+ cback = &keyspan_callbacks[d_details->msg_format];
+
+ port_num = port->port_number;
+
+ /* Do indat endpoints first, once for each flip */
+ endp = d_details->indat_endpoints[port_num];
+ for (i = 0; i <= d_details->indat_endp_flip; ++i, ++endp) {
+ p_priv->in_urbs[i] = keyspan_setup_urb(serial, endp,
+ USB_DIR_IN, port,
+ p_priv->in_buffer[i],
+ IN_BUFLEN,
+ cback->indat_callback);
+ }
+ /* outdat endpoints also have flip */
+ endp = d_details->outdat_endpoints[port_num];
+ for (i = 0; i <= d_details->outdat_endp_flip; ++i, ++endp) {
+ p_priv->out_urbs[i] = keyspan_setup_urb(serial, endp,
+ USB_DIR_OUT, port,
+ p_priv->out_buffer[i],
+ OUT_BUFLEN,
+ cback->outdat_callback);
+ }
+ /* inack endpoint */
+ p_priv->inack_urb = keyspan_setup_urb(serial,
+ d_details->inack_endpoints[port_num],
+ USB_DIR_IN, port,
+ p_priv->inack_buffer,
+ INACK_BUFLEN,
+ cback->inack_callback);
+ /* outcont endpoint */
+ p_priv->outcont_urb = keyspan_setup_urb(serial,
+ d_details->outcont_endpoints[port_num],
+ USB_DIR_OUT, port,
+ p_priv->outcont_buffer,
+ OUTCONT_BUFLEN,
+ cback->outcont_callback);
+
+ usb_set_serial_port_data(port, p_priv);
+
+ return 0;
+
+err_free_inack_buffer:
+ kfree(p_priv->inack_buffer);
+err_free_out_buffer:
+ for (i = 0; i < ARRAY_SIZE(p_priv->out_buffer); ++i)
+ kfree(p_priv->out_buffer[i]);
+err_free_in_buffer:
+ for (i = 0; i < ARRAY_SIZE(p_priv->in_buffer); ++i)
+ kfree(p_priv->in_buffer[i]);
+ kfree(p_priv);
+
+ return -ENOMEM;
+}
+
+static void keyspan_port_remove(struct usb_serial_port *port)
+{
+ struct keyspan_port_private *p_priv;
+ int i;
+
+ p_priv = usb_get_serial_port_data(port);
+
+ usb_kill_urb(p_priv->inack_urb);
+ usb_kill_urb(p_priv->outcont_urb);
+ for (i = 0; i < 2; i++) {
+ usb_kill_urb(p_priv->in_urbs[i]);
+ usb_kill_urb(p_priv->out_urbs[i]);
+ }
+
+ usb_free_urb(p_priv->inack_urb);
+ usb_free_urb(p_priv->outcont_urb);
+ for (i = 0; i < 2; i++) {
+ usb_free_urb(p_priv->in_urbs[i]);
+ usb_free_urb(p_priv->out_urbs[i]);
+ }
+
+ kfree(p_priv->outcont_buffer);
+ kfree(p_priv->inack_buffer);
+ for (i = 0; i < ARRAY_SIZE(p_priv->out_buffer); ++i)
+ kfree(p_priv->out_buffer[i]);
+ for (i = 0; i < ARRAY_SIZE(p_priv->in_buffer); ++i)
+ kfree(p_priv->in_buffer[i]);
+
+ kfree(p_priv);
+}
+
+/* Structs for the devices, pre and post renumeration. */
+static struct usb_serial_driver keyspan_pre_device = {
+ .driver = {
+ .name = "keyspan_no_firm",
+ },
+ .description = "Keyspan - (without firmware)",
+ .id_table = keyspan_pre_ids,
+ .num_ports = 1,
+ .attach = keyspan_fake_startup,
+};
+
+static struct usb_serial_driver keyspan_1port_device = {
+ .driver = {
+ .name = "keyspan_1",
+ },
+ .description = "Keyspan 1 port adapter",
+ .id_table = keyspan_1port_ids,
+ .num_ports = 1,
+ .open = keyspan_open,
+ .close = keyspan_close,
+ .dtr_rts = keyspan_dtr_rts,
+ .write = keyspan_write,
+ .write_room = keyspan_write_room,
+ .set_termios = keyspan_set_termios,
+ .break_ctl = keyspan_break_ctl,
+ .tiocmget = keyspan_tiocmget,
+ .tiocmset = keyspan_tiocmset,
+ .attach = keyspan_startup,
+ .disconnect = keyspan_disconnect,
+ .release = keyspan_release,
+ .port_probe = keyspan_port_probe,
+ .port_remove = keyspan_port_remove,
+};
+
+static struct usb_serial_driver keyspan_2port_device = {
+ .driver = {
+ .name = "keyspan_2",
+ },
+ .description = "Keyspan 2 port adapter",
+ .id_table = keyspan_2port_ids,
+ .num_ports = 2,
+ .open = keyspan_open,
+ .close = keyspan_close,
+ .dtr_rts = keyspan_dtr_rts,
+ .write = keyspan_write,
+ .write_room = keyspan_write_room,
+ .set_termios = keyspan_set_termios,
+ .break_ctl = keyspan_break_ctl,
+ .tiocmget = keyspan_tiocmget,
+ .tiocmset = keyspan_tiocmset,
+ .attach = keyspan_startup,
+ .disconnect = keyspan_disconnect,
+ .release = keyspan_release,
+ .port_probe = keyspan_port_probe,
+ .port_remove = keyspan_port_remove,
+};
+
+static struct usb_serial_driver keyspan_4port_device = {
+ .driver = {
+ .name = "keyspan_4",
+ },
+ .description = "Keyspan 4 port adapter",
+ .id_table = keyspan_4port_ids,
+ .num_ports = 4,
+ .open = keyspan_open,
+ .close = keyspan_close,
+ .dtr_rts = keyspan_dtr_rts,
+ .write = keyspan_write,
+ .write_room = keyspan_write_room,
+ .set_termios = keyspan_set_termios,
+ .break_ctl = keyspan_break_ctl,
+ .tiocmget = keyspan_tiocmget,
+ .tiocmset = keyspan_tiocmset,
+ .attach = keyspan_startup,
+ .disconnect = keyspan_disconnect,
+ .release = keyspan_release,
+ .port_probe = keyspan_port_probe,
+ .port_remove = keyspan_port_remove,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &keyspan_pre_device, &keyspan_1port_device,
+ &keyspan_2port_device, &keyspan_4port_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, keyspan_ids_combined);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+MODULE_FIRMWARE("keyspan/usa28.fw");
+MODULE_FIRMWARE("keyspan/usa28x.fw");
+MODULE_FIRMWARE("keyspan/usa28xa.fw");
+MODULE_FIRMWARE("keyspan/usa28xb.fw");
+MODULE_FIRMWARE("keyspan/usa19.fw");
+MODULE_FIRMWARE("keyspan/usa19qi.fw");
+MODULE_FIRMWARE("keyspan/mpr.fw");
+MODULE_FIRMWARE("keyspan/usa19qw.fw");
+MODULE_FIRMWARE("keyspan/usa18x.fw");
+MODULE_FIRMWARE("keyspan/usa19w.fw");
+MODULE_FIRMWARE("keyspan/usa49w.fw");
+MODULE_FIRMWARE("keyspan/usa49wlc.fw");
diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c
new file mode 100644
index 000000000..f05bcce60
--- /dev/null
+++ b/drivers/usb/serial/keyspan_pda.c
@@ -0,0 +1,720 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * USB Keyspan PDA / Xircom / Entrega Converter driver
+ *
+ * Copyright (C) 1999 - 2001 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 1999, 2000 Brian Warner <warner@lothar.com>
+ * Copyright (C) 2000 Al Borchers <borchers@steinerpoint.com>
+ * Copyright (C) 2020 Johan Hovold <johan@kernel.org>
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/usb/ezusb.h>
+
+#define DRIVER_AUTHOR "Brian Warner <warner@lothar.com>, Johan Hovold <johan@kernel.org>"
+#define DRIVER_DESC "USB Keyspan PDA Converter driver"
+
+#define KEYSPAN_TX_THRESHOLD 128
+
+struct keyspan_pda_private {
+ int tx_room;
+ struct work_struct unthrottle_work;
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+};
+
+static int keyspan_pda_write_start(struct usb_serial_port *port);
+
+#define KEYSPAN_VENDOR_ID 0x06cd
+#define KEYSPAN_PDA_FAKE_ID 0x0103
+#define KEYSPAN_PDA_ID 0x0104 /* no clue */
+
+/* For Xircom PGSDB9 and older Entrega version of the same device */
+#define XIRCOM_VENDOR_ID 0x085a
+#define XIRCOM_FAKE_ID 0x8027
+#define XIRCOM_FAKE_ID_2 0x8025 /* "PGMFHUB" serial */
+#define ENTREGA_VENDOR_ID 0x1645
+#define ENTREGA_FAKE_ID 0x8093
+
+static const struct usb_device_id id_table_combined[] = {
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_FAKE_ID) },
+ { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID) },
+ { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID_2) },
+ { USB_DEVICE(ENTREGA_VENDOR_ID, ENTREGA_FAKE_ID) },
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_ID) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table_combined);
+
+static const struct usb_device_id id_table_std[] = {
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_ID) },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id id_table_fake[] = {
+ { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_FAKE_ID) },
+ { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID) },
+ { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID_2) },
+ { USB_DEVICE(ENTREGA_VENDOR_ID, ENTREGA_FAKE_ID) },
+ { } /* Terminating entry */
+};
+
+static int keyspan_pda_get_write_room(struct keyspan_pda_private *priv)
+{
+ struct usb_serial_port *port = priv->port;
+ struct usb_serial *serial = port->serial;
+ u8 room;
+ int rc;
+
+ rc = usb_control_msg_recv(serial->dev,
+ 0,
+ 6, /* write_room */
+ USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_IN,
+ 0, /* value: 0 means "remaining room" */
+ 0, /* index */
+ &room,
+ 1,
+ 2000,
+ GFP_KERNEL);
+ if (rc) {
+ dev_dbg(&port->dev, "roomquery failed: %d\n", rc);
+ return rc;
+ }
+
+ dev_dbg(&port->dev, "roomquery says %d\n", room);
+
+ return room;
+}
+
+static void keyspan_pda_request_unthrottle(struct work_struct *work)
+{
+ struct keyspan_pda_private *priv =
+ container_of(work, struct keyspan_pda_private, unthrottle_work);
+ struct usb_serial_port *port = priv->port;
+ struct usb_serial *serial = port->serial;
+ unsigned long flags;
+ int result;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ /*
+ * Ask the device to tell us when the tx buffer becomes
+ * sufficiently empty.
+ */
+ result = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ 7, /* request_unthrottle */
+ USB_TYPE_VENDOR | USB_RECIP_INTERFACE
+ | USB_DIR_OUT,
+ KEYSPAN_TX_THRESHOLD,
+ 0, /* index */
+ NULL,
+ 0,
+ 2000);
+ if (result < 0)
+ dev_dbg(&serial->dev->dev, "%s - error %d from usb_control_msg\n",
+ __func__, result);
+ /*
+ * Need to check available space after requesting notification in case
+ * buffer is already empty so that no notification is sent.
+ */
+ result = keyspan_pda_get_write_room(priv);
+ if (result > KEYSPAN_TX_THRESHOLD) {
+ spin_lock_irqsave(&port->lock, flags);
+ priv->tx_room = max(priv->tx_room, result);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ usb_serial_port_softint(port);
+ }
+}
+
+static void keyspan_pda_rx_interrupt(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ unsigned int len = urb->actual_length;
+ int retval;
+ int status = urb->status;
+ struct keyspan_pda_private *priv;
+ unsigned long flags;
+
+ priv = usb_get_serial_port_data(port);
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n", __func__, status);
+ return;
+ default:
+ dev_dbg(&urb->dev->dev, "%s - nonzero urb status received: %d\n", __func__, status);
+ goto exit;
+ }
+
+ if (len < 1) {
+ dev_warn(&port->dev, "short message received\n");
+ goto exit;
+ }
+
+ /* see if the message is data or a status interrupt */
+ switch (data[0]) {
+ case 0:
+ /* rest of message is rx data */
+ if (len < 2)
+ break;
+ tty_insert_flip_string(&port->port, data + 1, len - 1);
+ tty_flip_buffer_push(&port->port);
+ break;
+ case 1:
+ /* status interrupt */
+ if (len < 2) {
+ dev_warn(&port->dev, "short interrupt message received\n");
+ break;
+ }
+ dev_dbg(&port->dev, "rx int, d1=%d\n", data[1]);
+ switch (data[1]) {
+ case 1: /* modemline change */
+ break;
+ case 2: /* tx unthrottle interrupt */
+ spin_lock_irqsave(&port->lock, flags);
+ priv->tx_room = max(priv->tx_room, KEYSPAN_TX_THRESHOLD);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ keyspan_pda_write_start(port);
+
+ usb_serial_port_softint(port);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+exit:
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(&port->dev,
+ "%s - usb_submit_urb failed with result %d\n",
+ __func__, retval);
+}
+
+static void keyspan_pda_rx_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ /*
+ * Stop receiving characters. We just turn off the URB request, and
+ * let chars pile up in the device. If we're doing hardware
+ * flowcontrol, the device will signal the other end when its buffer
+ * fills up. If we're doing XON/XOFF, this would be a good time to
+ * send an XOFF, although it might make sense to foist that off upon
+ * the device too.
+ */
+ usb_kill_urb(port->interrupt_in_urb);
+}
+
+static void keyspan_pda_rx_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ /* just restart the receive interrupt URB */
+ if (usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL))
+ dev_dbg(&port->dev, "usb_submit_urb(read urb) failed\n");
+}
+
+static speed_t keyspan_pda_setbaud(struct usb_serial *serial, speed_t baud)
+{
+ int rc;
+ int bindex;
+
+ switch (baud) {
+ case 110:
+ bindex = 0;
+ break;
+ case 300:
+ bindex = 1;
+ break;
+ case 1200:
+ bindex = 2;
+ break;
+ case 2400:
+ bindex = 3;
+ break;
+ case 4800:
+ bindex = 4;
+ break;
+ case 9600:
+ bindex = 5;
+ break;
+ case 19200:
+ bindex = 6;
+ break;
+ case 38400:
+ bindex = 7;
+ break;
+ case 57600:
+ bindex = 8;
+ break;
+ case 115200:
+ bindex = 9;
+ break;
+ default:
+ bindex = 5; /* Default to 9600 */
+ baud = 9600;
+ }
+
+ rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ 0, /* set baud */
+ USB_TYPE_VENDOR
+ | USB_RECIP_INTERFACE
+ | USB_DIR_OUT, /* type */
+ bindex, /* value */
+ 0, /* index */
+ NULL, /* &data */
+ 0, /* size */
+ 2000); /* timeout */
+ if (rc < 0)
+ return 0;
+
+ return baud;
+}
+
+static int keyspan_pda_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_serial *serial = port->serial;
+ int value;
+ int result;
+
+ if (break_state == -1)
+ value = 1; /* start break */
+ else
+ value = 0; /* clear break */
+
+ result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ 4, /* set break */
+ USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT,
+ value, 0, NULL, 0, 2000);
+ if (result < 0) {
+ dev_dbg(&port->dev, "%s - error %d from usb_control_msg\n",
+ __func__, result);
+ return result;
+ }
+
+ return 0;
+}
+
+static void keyspan_pda_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct usb_serial *serial = port->serial;
+ speed_t speed;
+
+ /*
+ * cflag specifies lots of stuff: number of stop bits, parity, number
+ * of data bits, baud. What can the device actually handle?:
+ * CSTOPB (1 stop bit or 2)
+ * PARENB (parity)
+ * CSIZE (5bit .. 8bit)
+ * There is minimal hw support for parity (a PSW bit seems to hold the
+ * parity of whatever is in the accumulator). The UART either deals
+ * with 10 bits (start, 8 data, stop) or 11 bits (start, 8 data,
+ * 1 special, stop). So, with firmware changes, we could do:
+ * 8N1: 10 bit
+ * 8N2: 11 bit, extra bit always (mark?)
+ * 8[EOMS]1: 11 bit, extra bit is parity
+ * 7[EOMS]1: 10 bit, b0/b7 is parity
+ * 7[EOMS]2: 11 bit, b0/b7 is parity, extra bit always (mark?)
+ *
+ * HW flow control is dictated by the tty->termios.c_cflags & CRTSCTS
+ * bit.
+ *
+ * For now, just do baud.
+ */
+ speed = tty_get_baud_rate(tty);
+ speed = keyspan_pda_setbaud(serial, speed);
+
+ if (speed == 0) {
+ dev_dbg(&port->dev, "can't handle requested baud rate\n");
+ /* It hasn't changed so.. */
+ speed = tty_termios_baud_rate(old_termios);
+ }
+ /*
+ * Only speed can change so copy the old h/w parameters then encode
+ * the new speed.
+ */
+ tty_termios_copy_hw(&tty->termios, old_termios);
+ tty_encode_baud_rate(tty, speed, speed);
+}
+
+/*
+ * Modem control pins: DTR and RTS are outputs and can be controlled.
+ * DCD, RI, DSR, CTS are inputs and can be read. All outputs can also be
+ * read. The byte passed is: DTR(b7) DCD RI DSR CTS RTS(b2) unused unused.
+ */
+static int keyspan_pda_get_modem_info(struct usb_serial *serial,
+ unsigned char *value)
+{
+ int rc;
+ u8 data;
+
+ rc = usb_control_msg_recv(serial->dev, 0,
+ 3, /* get pins */
+ USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_IN,
+ 0,
+ 0,
+ &data,
+ 1,
+ 2000,
+ GFP_KERNEL);
+ if (rc == 0)
+ *value = data;
+
+ return rc;
+}
+
+static int keyspan_pda_set_modem_info(struct usb_serial *serial,
+ unsigned char value)
+{
+ int rc;
+ rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ 3, /* set pins */
+ USB_TYPE_VENDOR|USB_RECIP_INTERFACE|USB_DIR_OUT,
+ value, 0, NULL, 0, 2000);
+ return rc;
+}
+
+static int keyspan_pda_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_serial *serial = port->serial;
+ int rc;
+ unsigned char status;
+ int value;
+
+ rc = keyspan_pda_get_modem_info(serial, &status);
+ if (rc < 0)
+ return rc;
+
+ value = ((status & BIT(7)) ? TIOCM_DTR : 0) |
+ ((status & BIT(6)) ? TIOCM_CAR : 0) |
+ ((status & BIT(5)) ? TIOCM_RNG : 0) |
+ ((status & BIT(4)) ? TIOCM_DSR : 0) |
+ ((status & BIT(3)) ? TIOCM_CTS : 0) |
+ ((status & BIT(2)) ? TIOCM_RTS : 0);
+
+ return value;
+}
+
+static int keyspan_pda_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_serial *serial = port->serial;
+ int rc;
+ unsigned char status;
+
+ rc = keyspan_pda_get_modem_info(serial, &status);
+ if (rc < 0)
+ return rc;
+
+ if (set & TIOCM_RTS)
+ status |= BIT(2);
+ if (set & TIOCM_DTR)
+ status |= BIT(7);
+
+ if (clear & TIOCM_RTS)
+ status &= ~BIT(2);
+ if (clear & TIOCM_DTR)
+ status &= ~BIT(7);
+ rc = keyspan_pda_set_modem_info(serial, status);
+ return rc;
+}
+
+static int keyspan_pda_write_start(struct usb_serial_port *port)
+{
+ struct keyspan_pda_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ struct urb *urb;
+ int count;
+ int room;
+ int rc;
+
+ /*
+ * Guess how much room is left in the device's ring buffer. If our
+ * write will result in no room left, ask the device to give us an
+ * interrupt when the room available rises above a threshold but also
+ * query how much room is currently available (in case our guess was
+ * too conservative and the buffer is already empty when the
+ * unthrottle work is scheduled).
+ */
+
+ /*
+ * We might block because of:
+ * the TX urb is in-flight (wait until it completes)
+ * the device is full (wait until it says there is room)
+ */
+ spin_lock_irqsave(&port->lock, flags);
+
+ room = priv->tx_room;
+ count = kfifo_len(&port->write_fifo);
+
+ if (!test_bit(0, &port->write_urbs_free) || count == 0 || room == 0) {
+ spin_unlock_irqrestore(&port->lock, flags);
+ return 0;
+ }
+ __clear_bit(0, &port->write_urbs_free);
+
+ if (count > room)
+ count = room;
+ if (count > port->bulk_out_size)
+ count = port->bulk_out_size;
+
+ urb = port->write_urb;
+ count = kfifo_out(&port->write_fifo, urb->transfer_buffer, count);
+ urb->transfer_buffer_length = count;
+
+ port->tx_bytes += count;
+ priv->tx_room -= count;
+
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ dev_dbg(&port->dev, "%s - count = %d, txroom = %d\n", __func__, count, room);
+
+ rc = usb_submit_urb(urb, GFP_ATOMIC);
+ if (rc) {
+ dev_dbg(&port->dev, "usb_submit_urb(write bulk) failed\n");
+
+ spin_lock_irqsave(&port->lock, flags);
+ port->tx_bytes -= count;
+ priv->tx_room = max(priv->tx_room, room + count);
+ __set_bit(0, &port->write_urbs_free);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ return rc;
+ }
+
+ if (count == room)
+ schedule_work(&priv->unthrottle_work);
+
+ return 0;
+}
+
+static void keyspan_pda_write_bulk_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ port->tx_bytes -= urb->transfer_buffer_length;
+ __set_bit(0, &port->write_urbs_free);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ keyspan_pda_write_start(port);
+
+ usb_serial_port_softint(port);
+}
+
+static int keyspan_pda_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count)
+{
+ int rc;
+
+ dev_dbg(&port->dev, "%s - count = %d\n", __func__, count);
+
+ if (!count)
+ return 0;
+
+ count = kfifo_in_locked(&port->write_fifo, buf, count, &port->lock);
+
+ rc = keyspan_pda_write_start(port);
+ if (rc)
+ return rc;
+
+ return count;
+}
+
+static void keyspan_pda_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct usb_serial *serial = port->serial;
+
+ if (on)
+ keyspan_pda_set_modem_info(serial, BIT(7) | BIT(2));
+ else
+ keyspan_pda_set_modem_info(serial, 0);
+}
+
+
+static int keyspan_pda_open(struct tty_struct *tty,
+ struct usb_serial_port *port)
+{
+ struct keyspan_pda_private *priv = usb_get_serial_port_data(port);
+ int rc;
+
+ /* find out how much room is in the Tx ring */
+ rc = keyspan_pda_get_write_room(priv);
+ if (rc < 0)
+ return rc;
+
+ spin_lock_irq(&port->lock);
+ priv->tx_room = rc;
+ spin_unlock_irq(&port->lock);
+
+ rc = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (rc) {
+ dev_dbg(&port->dev, "%s - usb_submit_urb(read int) failed\n", __func__);
+ return rc;
+ }
+
+ return 0;
+}
+
+static void keyspan_pda_close(struct usb_serial_port *port)
+{
+ struct keyspan_pda_private *priv = usb_get_serial_port_data(port);
+
+ /*
+ * Stop the interrupt URB first as its completion handler may submit
+ * the write URB.
+ */
+ usb_kill_urb(port->interrupt_in_urb);
+ usb_kill_urb(port->write_urb);
+
+ cancel_work_sync(&priv->unthrottle_work);
+
+ spin_lock_irq(&port->lock);
+ kfifo_reset(&port->write_fifo);
+ spin_unlock_irq(&port->lock);
+}
+
+/* download the firmware to a "fake" device (pre-renumeration) */
+static int keyspan_pda_fake_startup(struct usb_serial *serial)
+{
+ unsigned int vid = le16_to_cpu(serial->dev->descriptor.idVendor);
+ const char *fw_name;
+
+ /* download the firmware here ... */
+ ezusb_fx1_set_reset(serial->dev, 1);
+
+ switch (vid) {
+ case KEYSPAN_VENDOR_ID:
+ fw_name = "keyspan_pda/keyspan_pda.fw";
+ break;
+ case XIRCOM_VENDOR_ID:
+ case ENTREGA_VENDOR_ID:
+ fw_name = "keyspan_pda/xircom_pgs.fw";
+ break;
+ default:
+ dev_err(&serial->dev->dev, "%s: unknown vendor, aborting.\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ if (ezusb_fx1_ihex_firmware_download(serial->dev, fw_name) < 0) {
+ dev_err(&serial->dev->dev, "failed to load firmware \"%s\"\n",
+ fw_name);
+ return -ENOENT;
+ }
+
+ /*
+ * After downloading firmware renumeration will occur in a moment and
+ * the new device will bind to the real driver.
+ */
+
+ /* We want this device to fail to have a driver assigned to it. */
+ return 1;
+}
+
+MODULE_FIRMWARE("keyspan_pda/keyspan_pda.fw");
+MODULE_FIRMWARE("keyspan_pda/xircom_pgs.fw");
+
+static int keyspan_pda_port_probe(struct usb_serial_port *port)
+{
+
+ struct keyspan_pda_private *priv;
+
+ priv = kmalloc_obj(struct keyspan_pda_private);
+ if (!priv)
+ return -ENOMEM;
+
+ INIT_WORK(&priv->unthrottle_work, keyspan_pda_request_unthrottle);
+ priv->port = port;
+
+ usb_set_serial_port_data(port, priv);
+
+ return 0;
+}
+
+static void keyspan_pda_port_remove(struct usb_serial_port *port)
+{
+ struct keyspan_pda_private *priv;
+
+ priv = usb_get_serial_port_data(port);
+ kfree(priv);
+}
+
+static struct usb_serial_driver keyspan_pda_fake_device = {
+ .driver = {
+ .name = "keyspan_pda_pre",
+ },
+ .description = "Keyspan PDA - (prerenumeration)",
+ .id_table = id_table_fake,
+ .num_ports = 1,
+ .attach = keyspan_pda_fake_startup,
+};
+
+static struct usb_serial_driver keyspan_pda_device = {
+ .driver = {
+ .name = "keyspan_pda",
+ },
+ .description = "Keyspan PDA",
+ .id_table = id_table_std,
+ .num_ports = 1,
+ .num_bulk_out = 1,
+ .num_interrupt_in = 1,
+ .dtr_rts = keyspan_pda_dtr_rts,
+ .open = keyspan_pda_open,
+ .close = keyspan_pda_close,
+ .write = keyspan_pda_write,
+ .write_bulk_callback = keyspan_pda_write_bulk_callback,
+ .read_int_callback = keyspan_pda_rx_interrupt,
+ .throttle = keyspan_pda_rx_throttle,
+ .unthrottle = keyspan_pda_rx_unthrottle,
+ .set_termios = keyspan_pda_set_termios,
+ .break_ctl = keyspan_pda_break_ctl,
+ .tiocmget = keyspan_pda_tiocmget,
+ .tiocmset = keyspan_pda_tiocmset,
+ .port_probe = keyspan_pda_port_probe,
+ .port_remove = keyspan_pda_port_remove,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &keyspan_pda_device,
+ &keyspan_pda_fake_device,
+ NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table_combined);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/keyspan_usa26msg.h b/drivers/usb/serial/keyspan_usa26msg.h
new file mode 100644
index 000000000..a68f1fb25
--- /dev/null
+++ b/drivers/usb/serial/keyspan_usa26msg.h
@@ -0,0 +1,261 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ usa26msg.h
+
+ Copyright (C) 1998-2000 InnoSys Incorporated. All Rights Reserved
+ This file is available under a BSD-style copyright
+
+ Keyspan USB Async Message Formats for the USA28X
+
+ 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 this licence text
+ without modification, this list of conditions, and the following
+ disclaimer. The following copyright notice must appear immediately at
+ the beginning of all source files:
+
+ Copyright (C) 1998-2000 InnoSys Incorporated. All Rights Reserved
+
+ This file is available under a BSD-style copyright
+
+ 2. The name of InnoSys Incorporated may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY INNOSYS CORP. ``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 OR CONTRIBUTORS 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.
+
+ Third revision: USA28X version (aka USA26)
+
+ Buffer formats for RX/TX data messages are not defined by
+ a structure, but are described here:
+
+ USB OUT (host -> USAxx, transmit) messages contain a
+ REQUEST_ACK indicator (set to 0xff to request an ACK at the
+ completion of transmit; 0x00 otherwise), followed by data:
+
+ RQSTACK DAT DAT DAT ...
+
+ with a total data length of 63.
+
+ USB IN (USAxx -> host, receive) messages begin with a status
+ byte in which the 0x80 bit is either:
+
+ (a) 0x80 bit clear
+ indicates that the bytes following it are all data
+ bytes:
+
+ STAT DATA DATA DATA DATA DATA ...
+
+ for a total of up to 63 DATA bytes,
+
+ or:
+
+ (b) 0x80 bit set
+ indicates that the bytes following alternate data and
+ status bytes:
+
+ STAT DATA STAT DATA STAT DATA STAT DATA ...
+
+ for a total of up to 32 DATA bytes.
+
+ The valid bits in the STAT bytes are:
+
+ OVERRUN 0x02
+ PARITY 0x04
+ FRAMING 0x08
+ BREAK 0x10
+
+ Notes:
+
+ (1) The OVERRUN bit can appear in either (a) or (b) format
+ messages, but the but the PARITY/FRAMING/BREAK bits
+ only appear in (b) format messages.
+ (2) For the host to determine the exact point at which the
+ overrun occurred (to identify the point in the data
+ stream at which the data was lost), it needs to count
+ 128 characters, starting at the first character of the
+ message in which OVERRUN was reported; the lost character(s)
+ would have been received between the 128th and 129th
+ characters.
+ (3) An RX data message in which the first byte has 0x80 clear
+ serves as a "break off" indicator.
+
+ revision history:
+
+ 1999feb10 add reportHskiaChanges to allow us to ignore them
+ 1999feb10 add txAckThreshold for fast+loose throughput enhancement
+ 1999mar30 beef up support for RX error reporting
+ 1999apr14 add resetDataToggle to control message
+ 2000jan04 merge with usa17msg.h
+ 2000jun01 add extended BSD-style copyright text
+ 2001jul05 change message format to improve OVERRUN case
+
+ Note on shared names:
+
+ In the case of fields which have been merged between the USA17
+ and USA26 definitions, the USA26 definition is the first part
+ of the name and the USA17 definition is the second part of the
+ name; both meanings are described below.
+*/
+
+#ifndef __USA26MSG__
+#define __USA26MSG__
+
+
+struct keyspan_usa26_portControlMessage
+{
+ /*
+ there are three types of "commands" sent in the control message:
+
+ 1. configuration changes which must be requested by setting
+ the corresponding "set" flag (and should only be requested
+ when necessary, to reduce overhead on the USA26):
+ */
+ u8 setClocking, // BOTH: host requests baud rate be set
+ baudLo, // BOTH: host does baud divisor calculation
+ baudHi, // BOTH: baudHi is only used for first port (gives lower rates)
+ externalClock_txClocking,
+ // USA26: 0=internal, other=external
+ // USA17: 0=internal, other=external/RI
+ rxClocking, // USA17: 0=internal, 1=external/RI, other=external/DSR
+
+
+ setLcr, // BOTH: host requests lcr be set
+ lcr, // BOTH: use PARITY, STOPBITS, DATABITS below
+
+ setFlowControl, // BOTH: host requests flow control be set
+ ctsFlowControl, // BOTH: 1=use CTS flow control, 0=don't
+ xonFlowControl, // BOTH: 1=use XON/XOFF flow control, 0=don't
+ xonChar, // BOTH: specified in current character format
+ xoffChar, // BOTH: specified in current character format
+
+ setTxTriState_setRts,
+ // USA26: host requests TX tri-state be set
+ // USA17: host requests RTS output be set
+ txTriState_rts, // BOTH: 1=active (normal), 0=tristate (off)
+
+ setHskoa_setDtr,
+ // USA26: host requests HSKOA output be set
+ // USA17: host requests DTR output be set
+ hskoa_dtr, // BOTH: 1=on, 0=off
+
+ setPrescaler, // USA26: host requests prescalar be set (default: 13)
+ prescaler; // BOTH: specified as N/8; values 8-ff are valid
+ // must be set any time internal baud rate is set;
+ // must not be set when external clocking is used
+ // note: in USA17, prescaler is applied whenever
+ // setClocking is requested
+
+ /*
+ 3. configuration data which is simply used as is (no overhead,
+ but must be specified correctly in every host message).
+ */
+ u8 forwardingLength, // BOTH: forward when this number of chars available
+ reportHskiaChanges_dsrFlowControl,
+ // USA26: 1=normal; 0=ignore external clock
+ // USA17: 1=use DSR flow control, 0=don't
+ txAckThreshold, // BOTH: 0=not allowed, 1=normal, 2-255 deliver ACK faster
+ loopbackMode; // BOTH: 0=no loopback, 1=loopback enabled
+
+ /*
+ 4. commands which are flags only; these are processed in order
+ (so that, e.g., if both _txOn and _txOff flags are set, the
+ port ends in a TX_OFF state); any non-zero value is respected
+ */
+ u8 _txOn, // BOTH: enable transmitting (and continue if there's data)
+ _txOff, // BOTH: stop transmitting
+ txFlush, // BOTH: toss outbound data
+ txBreak, // BOTH: turn on break (cleared by _txOn)
+ rxOn, // BOTH: turn on receiver
+ rxOff, // BOTH: turn off receiver
+ rxFlush, // BOTH: toss inbound data
+ rxForward, // BOTH: forward all inbound data, NOW (as if fwdLen==1)
+ returnStatus, // BOTH: return current status (even if it hasn't changed)
+ resetDataToggle;// BOTH: reset data toggle state to DATA0
+
+};
+
+// defines for bits in lcr
+#define USA_DATABITS_5 0x00
+#define USA_DATABITS_6 0x01
+#define USA_DATABITS_7 0x02
+#define USA_DATABITS_8 0x03
+#define STOPBITS_5678_1 0x00 // 1 stop bit for all byte sizes
+#define STOPBITS_5_1p5 0x04 // 1.5 stop bits for 5-bit byte
+#define STOPBITS_678_2 0x04 // 2 stop bits for 6/7/8-bit byte
+#define USA_PARITY_NONE 0x00
+#define USA_PARITY_ODD 0x08
+#define USA_PARITY_EVEN 0x18
+#define PARITY_1 0x28
+#define PARITY_0 0x38
+
+// all things called "StatusMessage" are sent on the status endpoint
+
+struct keyspan_usa26_portStatusMessage // one for each port
+{
+ u8 port, // BOTH: 0=first, 1=second, other=see below
+ hskia_cts, // USA26: reports HSKIA pin
+ // USA17: reports CTS pin
+ gpia_dcd, // USA26: reports GPIA pin
+ // USA17: reports DCD pin
+ dsr, // USA17: reports DSR pin
+ ri, // USA17: reports RI pin
+ _txOff, // port has been disabled (by host)
+ _txXoff, // port is in XOFF state (either host or RX XOFF)
+ rxEnabled, // as configured by rxOn/rxOff 1=on, 0=off
+ controlResponse;// 1=a control message has been processed
+};
+
+// bits in RX data message when STAT byte is included
+#define RXERROR_OVERRUN 0x02
+#define RXERROR_PARITY 0x04
+#define RXERROR_FRAMING 0x08
+#define RXERROR_BREAK 0x10
+
+struct keyspan_usa26_globalControlMessage
+{
+ u8 sendGlobalStatus, // 2=request for two status responses
+ resetStatusToggle, // 1=reset global status toggle
+ resetStatusCount; // a cycling value
+};
+
+struct keyspan_usa26_globalStatusMessage
+{
+ u8 port, // 3
+ sendGlobalStatus, // from request, decremented
+ resetStatusCount; // as in request
+};
+
+struct keyspan_usa26_globalDebugMessage
+{
+ u8 port, // 2
+ a,
+ b,
+ c,
+ d;
+};
+
+// ie: the maximum length of an EZUSB endpoint buffer
+#define MAX_DATA_LEN 64
+
+// update status approx. 60 times a second (16.6666 ms)
+#define STATUS_UPDATE_INTERVAL 16
+
+// status rationing tuning value (each port gets checked each n ms)
+#define STATUS_RATION 10
+
+#endif
+
+
diff --git a/drivers/usb/serial/keyspan_usa28msg.h b/drivers/usb/serial/keyspan_usa28msg.h
new file mode 100644
index 000000000..a19f3fe5d
--- /dev/null
+++ b/drivers/usb/serial/keyspan_usa28msg.h
@@ -0,0 +1,202 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ usa28msg.h
+
+ Copyright (C) 1998-2000 InnoSys Incorporated. All Rights Reserved
+ This file is available under a BSD-style copyright
+
+ Keyspan USB Async Message Formats for the USA26X
+
+ 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 this licence text
+ without modification, this list of conditions, and the following
+ disclaimer. The following copyright notice must appear immediately at
+ the beginning of all source files:
+
+ Copyright (C) 1998-2000 InnoSys Incorporated. All Rights Reserved
+
+ This file is available under a BSD-style copyright
+
+ 2. The name of InnoSys Incorporated may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY INNOSYS CORP. ``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 OR CONTRIBUTORS 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.
+
+ Note: these message formats are common to USA18, USA19, and USA28;
+ (for USA28X, see usa26msg.h)
+
+ Buffer formats for RX/TX data messages are not defined by
+ a structure, but are described here:
+
+ USB OUT (host -> USA28, transmit) messages contain a
+ REQUEST_ACK indicator (set to 0xff to request an ACK at the
+ completion of transmit; 0x00 otherwise), followed by data.
+ If the port is configured for parity, the data will be an
+ alternating string of parity and data bytes, so the message
+ format will be:
+
+ RQSTACK PAR DAT PAR DAT ...
+
+ so the maximum length is 63 bytes (1 + 62, or 31 data bytes);
+ always an odd number for the total message length.
+
+ If there is no parity, the format is simply:
+
+ RQSTACK DAT DAT DAT ...
+
+ with a total data length of 63.
+
+ USB IN (USA28 -> host, receive) messages contain data and parity
+ if parity is configred, thusly:
+
+ DAT PAR DAT PAR DAT PAR ...
+
+ for a total of 32 data bytes;
+
+ If parity is not configured, the format is:
+
+ DAT DAT DAT ...
+
+ for a total of 64 data bytes.
+
+ In the TX messages (USB OUT), the 0x01 bit of the PARity byte is
+ the parity bit. In the RX messages (USB IN), the PARity byte is
+ the content of the 8051's status register; the parity bit
+ (RX_PARITY_BIT) is the 0x04 bit.
+
+ revision history:
+
+ 1999may06 add resetDataToggle to control message
+ 2000mar21 add rs232invalid to status response message
+ 2000apr04 add 230.4Kb definition to setBaudRate
+ 2000apr13 add/remove loopbackMode switch
+ 2000apr13 change definition of setBaudRate to cover 115.2Kb, too
+ 2000jun01 add extended BSD-style copyright text
+*/
+
+#ifndef __USA28MSG__
+#define __USA28MSG__
+
+
+struct keyspan_usa28_portControlMessage
+{
+ /*
+ there are four types of "commands" sent in the control message:
+
+ 1. configuration changes which must be requested by setting
+ the corresponding "set" flag (and should only be requested
+ when necessary, to reduce overhead on the USA28):
+ */
+ u8 setBaudRate, // 0=don't set, 1=baudLo/Hi, 2=115.2K, 3=230.4K
+ baudLo, // host does baud divisor calculation
+ baudHi; // baudHi is only used for first port (gives lower rates)
+
+ /*
+ 2. configuration changes which are done every time (because it's
+ hardly more trouble to do them than to check whether to do them):
+ */
+ u8 parity, // 1=use parity, 0=don't
+ ctsFlowControl, // all except 19Q: 1=use CTS flow control, 0=don't
+ // 19Q: 0x08:CTSflowControl 0x10:DSRflowControl
+ xonFlowControl, // 1=use XON/XOFF flow control, 0=don't
+ rts, // 1=on, 0=off
+ dtr; // 1=on, 0=off
+
+ /*
+ 3. configuration data which is simply used as is (no overhead,
+ but must be correct in every host message).
+ */
+ u8 forwardingLength, // forward when this number of chars available
+ forwardMs, // forward this many ms after last rx data
+ breakThreshold, // specified in ms, 1-255 (see note below)
+ xonChar, // specified in current character format
+ xoffChar; // specified in current character format
+
+ /*
+ 4. commands which are flags only; these are processed in order
+ (so that, e.g., if both _txOn and _txOff flags are set, the
+ port ends in a TX_OFF state); any non-zero value is respected
+ */
+ u8 _txOn, // enable transmitting (and continue if there's data)
+ _txOff, // stop transmitting
+ txFlush, // toss outbound data
+ txForceXoff, // pretend we've received XOFF
+ txBreak, // turn on break (leave on until txOn clears it)
+ rxOn, // turn on receiver
+ rxOff, // turn off receiver
+ rxFlush, // toss inbound data
+ rxForward, // forward all inbound data, NOW
+ returnStatus, // return current status n times (1 or 2)
+ resetDataToggle;// reset data toggle state to DATA0
+
+};
+
+struct keyspan_usa28_portStatusMessage
+{
+ u8 port, // 0=first, 1=second, 2=global (see below)
+ cts,
+ dsr, // (not used in all products)
+ dcd,
+
+ ri, // (not used in all products)
+ _txOff, // port has been disabled (by host)
+ _txXoff, // port is in XOFF state (either host or RX XOFF)
+ dataLost, // count of lost chars; wraps; not guaranteed exact
+
+ rxEnabled, // as configured by rxOn/rxOff 1=on, 0=off
+ rxBreak, // 1=we're in break state
+ rs232invalid, // 1=no valid signals on rs-232 inputs
+ controlResponse;// 1=a control messages has been processed
+};
+
+// bit defines in txState
+#define TX_OFF 0x01 // requested by host txOff command
+#define TX_XOFF 0x02 // either real, or simulated by host
+
+struct keyspan_usa28_globalControlMessage
+{
+ u8 sendGlobalStatus, // 2=request for two status responses
+ resetStatusToggle, // 1=reset global status toggle
+ resetStatusCount; // a cycling value
+};
+
+struct keyspan_usa28_globalStatusMessage
+{
+ u8 port, // 3
+ sendGlobalStatus, // from request, decremented
+ resetStatusCount; // as in request
+};
+
+struct keyspan_usa28_globalDebugMessage
+{
+ u8 port, // 2
+ n, // typically a count/status byte
+ b; // typically a data byte
+};
+
+// ie: the maximum length of an EZUSB endpoint buffer
+#define MAX_DATA_LEN 64
+
+// the parity bytes have only one significant bit
+#define RX_PARITY_BIT 0x04
+#define TX_PARITY_BIT 0x01
+
+// update status approx. 60 times a second (16.6666 ms)
+#define STATUS_UPDATE_INTERVAL 16
+
+#endif
+
diff --git a/drivers/usb/serial/keyspan_usa49msg.h b/drivers/usb/serial/keyspan_usa49msg.h
new file mode 100644
index 000000000..8c3970fdd
--- /dev/null
+++ b/drivers/usb/serial/keyspan_usa49msg.h
@@ -0,0 +1,283 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ usa49msg.h
+
+ Copyright (C) 1998-2000 InnoSys Incorporated. All Rights Reserved
+ This file is available under a BSD-style copyright
+
+ Keyspan USB Async Message Formats for the USA49W
+
+ 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 this licence text
+ without modification, this list of conditions, and the following
+ disclaimer. The following copyright notice must appear immediately at
+ the beginning of all source files:
+
+ Copyright (C) 1998-2000 InnoSys Incorporated. All Rights Reserved
+
+ This file is available under a BSD-style copyright
+
+ 2. The name of InnoSys Incorporated may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY INNOSYS CORP. ``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 OR CONTRIBUTORS 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.
+
+ 4th revision: USA49W version
+
+ Buffer formats for RX/TX data messages are not defined by
+ a structure, but are described here:
+
+ USB OUT (host -> USAxx, transmit) messages contain a
+ REQUEST_ACK indicator (set to 0xff to request an ACK at the
+ completion of transmit; 0x00 otherwise), followed by data:
+
+ RQSTACK DAT DAT DAT ...
+
+ with a total data length of 63.
+
+ USB IN (USAxx -> host, receive) messages begin with a status
+ byte in which the 0x80 bit is either:
+
+ (a) 0x80 bit clear
+ indicates that the bytes following it are all data
+ bytes:
+
+ STAT DATA DATA DATA DATA DATA ...
+
+ for a total of up to 63 DATA bytes,
+
+ or:
+
+ (b) 0x80 bit set
+ indiates that the bytes following alternate data and
+ status bytes:
+
+ STAT DATA STAT DATA STAT DATA STAT DATA ...
+
+ for a total of up to 32 DATA bytes.
+
+ The valid bits in the STAT bytes are:
+
+ OVERRUN 0x02
+ PARITY 0x04
+ FRAMING 0x08
+ BREAK 0x10
+
+ Notes:
+
+ (1) The OVERRUN bit can appear in either (a) or (b) format
+ messages, but the but the PARITY/FRAMING/BREAK bits
+ only appear in (b) format messages.
+ (2) For the host to determine the exact point at which the
+ overrun occurred (to identify the point in the data
+ stream at which the data was lost), it needs to count
+ 128 characters, starting at the first character of the
+ message in which OVERRUN was reported; the lost character(s)
+ would have been received between the 128th and 129th
+ characters.
+ (3) An RX data message in which the first byte has 0x80 clear
+ serves as a "break off" indicator.
+ (4) a control message specifying disablePort will be answered
+ with a status message, but no further status will be sent
+ until a control messages with enablePort is sent
+
+ revision history:
+
+ 1999feb10 add reportHskiaChanges to allow us to ignore them
+ 1999feb10 add txAckThreshold for fast+loose throughput enhancement
+ 1999mar30 beef up support for RX error reporting
+ 1999apr14 add resetDataToggle to control message
+ 2000jan04 merge with usa17msg.h
+ 2000mar08 clone from usa26msg.h -> usa49msg.h
+ 2000mar09 change to support 4 ports
+ 2000may03 change external clocking to match USA-49W hardware
+ 2000jun01 add extended BSD-style copyright text
+ 2001jul05 change message format to improve OVERRUN case
+*/
+
+#ifndef __USA49MSG__
+#define __USA49MSG__
+
+
+/*
+ Host->device messages sent on the global control endpoint:
+
+ portNumber message
+ ---------- --------------------
+ 0,1,2,3 portControlMessage
+ 0x80 globalControlMessage
+*/
+
+struct keyspan_usa49_portControlMessage
+{
+ /*
+ 0. 0/1/2/3 port control message follows
+ 0x80 set non-port control message follows
+ */
+ u8 portNumber,
+
+ /*
+ there are three types of "commands" sent in the control message:
+
+ 1. configuration changes which must be requested by setting
+ the corresponding "set" flag (and should only be requested
+ when necessary, to reduce overhead on the USA26):
+ */
+ setClocking, // host requests baud rate be set
+ baudLo, // host does baud divisor calculation
+ baudHi, // baudHi is only used for first port (gives lower rates)
+ prescaler, // specified as N/8; values 8-ff are valid
+ // must be set any time internal baud rate is set;
+ txClocking, // 0=internal, 1=external/DSR
+ rxClocking, // 0=internal, 1=external/DSR
+
+ setLcr, // host requests lcr be set
+ lcr, // use PARITY, STOPBITS, DATABITS below
+
+ setFlowControl, // host requests flow control be set
+ ctsFlowControl, // 1=use CTS flow control, 0=don't
+ xonFlowControl, // 1=use XON/XOFF flow control, 0=don't
+ xonChar, // specified in current character format
+ xoffChar, // specified in current character format
+
+ setRts, // host requests RTS output be set
+ rts, // 1=active, 0=inactive
+
+ setDtr, // host requests DTR output be set
+ dtr; // 1=on, 0=off
+
+
+ /*
+ 3. configuration data which is simply used as is (no overhead,
+ but must be specified correctly in every host message).
+ */
+ u8 forwardingLength, // forward when this number of chars available
+ dsrFlowControl, // 1=use DSR flow control, 0=don't
+ txAckThreshold, // 0=not allowed, 1=normal, 2-255 deliver ACK faster
+ loopbackMode; // 0=no loopback, 1=loopback enabled
+
+ /*
+ 4. commands which are flags only; these are processed in order
+ (so that, e.g., if both _txOn and _txOff flags are set, the
+ port ends in a TX_OFF state); any non-zero value is respected
+ */
+ u8 _txOn, // enable transmitting (and continue if there's data)
+ _txOff, // stop transmitting
+ txFlush, // toss outbound data
+ txBreak, // turn on break (cleared by _txOn)
+ rxOn, // turn on receiver
+ rxOff, // turn off receiver
+ rxFlush, // toss inbound data
+ rxForward, // forward all inbound data, NOW (as if fwdLen==1)
+ returnStatus, // return current status (even if it hasn't changed)
+ resetDataToggle,// reset data toggle state to DATA0
+ enablePort, // start servicing port (move data, check status)
+ disablePort; // stop servicing port (does implicit tx/rx flush/off)
+
+};
+
+// defines for bits in lcr
+#define USA_DATABITS_5 0x00
+#define USA_DATABITS_6 0x01
+#define USA_DATABITS_7 0x02
+#define USA_DATABITS_8 0x03
+#define STOPBITS_5678_1 0x00 // 1 stop bit for all byte sizes
+#define STOPBITS_5_1p5 0x04 // 1.5 stop bits for 5-bit byte
+#define STOPBITS_678_2 0x04 // 2 stop bits for 6/7/8-bit byte
+#define USA_PARITY_NONE 0x00
+#define USA_PARITY_ODD 0x08
+#define USA_PARITY_EVEN 0x18
+#define PARITY_1 0x28
+#define PARITY_0 0x38
+
+/*
+ during normal operation, status messages are returned
+ to the host whenever the board detects changes. In some
+ circumstances (e.g. Windows), status messages from the
+ device cause problems; to shut them off, the host issues
+ a control message with the disableStatusMessages flags
+ set (to any non-zero value). The device will respond to
+ this message, and then suppress further status messages;
+ it will resume sending status messages any time the host
+ sends any control message (either global or port-specific).
+*/
+
+struct keyspan_usa49_globalControlMessage
+{
+ u8 portNumber, // 0x80
+ sendGlobalStatus, // 1/2=number of status responses requested
+ resetStatusToggle, // 1=reset global status toggle
+ resetStatusCount, // a cycling value
+ remoteWakeupEnable, // 0x10=P1, 0x20=P2, 0x40=P3, 0x80=P4
+ disableStatusMessages; // 1=send no status until host talks
+};
+
+/*
+ Device->host messages send on the global status endpoint
+
+ portNumber message
+ ---------- --------------------
+ 0x00,0x01,0x02,0x03 portStatusMessage
+ 0x80 globalStatusMessage
+ 0x81 globalDebugMessage
+*/
+
+struct keyspan_usa49_portStatusMessage // one for each port
+{
+ u8 portNumber, // 0,1,2,3
+ cts, // reports CTS pin
+ dcd, // reports DCD pin
+ dsr, // reports DSR pin
+ ri, // reports RI pin
+ _txOff, // transmit has been disabled (by host)
+ _txXoff, // transmit is in XOFF state (either host or RX XOFF)
+ rxEnabled, // as configured by rxOn/rxOff 1=on, 0=off
+ controlResponse,// 1=a control message has been processed
+ txAck, // ACK (data TX complete)
+ rs232valid; // RS-232 signal valid
+};
+
+// bits in RX data message when STAT byte is included
+#define RXERROR_OVERRUN 0x02
+#define RXERROR_PARITY 0x04
+#define RXERROR_FRAMING 0x08
+#define RXERROR_BREAK 0x10
+
+struct keyspan_usa49_globalStatusMessage
+{
+ u8 portNumber, // 0x80=globalStatusMessage
+ sendGlobalStatus, // from request, decremented
+ resetStatusCount; // as in request
+};
+
+struct keyspan_usa49_globalDebugMessage
+{
+ u8 portNumber, // 0x81=globalDebugMessage
+ n, // typically a count/status byte
+ b; // typically a data byte
+};
+
+// ie: the maximum length of an EZUSB endpoint buffer
+#define MAX_DATA_LEN 64
+
+// update status approx. 60 times a second (16.6666 ms)
+#define STATUS_UPDATE_INTERVAL 16
+
+// status rationing tuning value (each port gets checked each n ms)
+#define STATUS_RATION 10
+
+#endif
diff --git a/drivers/usb/serial/keyspan_usa67msg.h b/drivers/usb/serial/keyspan_usa67msg.h
new file mode 100644
index 000000000..dcf502fdb
--- /dev/null
+++ b/drivers/usb/serial/keyspan_usa67msg.h
@@ -0,0 +1,255 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ usa67msg.h
+
+ Copyright (c) 1998-2007 InnoSys Incorporated. All Rights Reserved
+ This file is available under a BSD-style copyright
+
+ Keyspan USB Async Firmware to run on Anchor FX1
+
+ 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 this licence text
+ without modification, this list of conditions, and the following
+ disclaimer. The following copyright notice must appear immediately at
+ the beginning of all source files:
+
+ Copyright (c) 1998-2007 InnoSys Incorporated. All Rights Reserved
+
+ This file is available under a BSD-style copyright
+
+ 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 InnoSys Incorprated may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY INNOSYS CORP. ``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 OR CONTRIBUTORS 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.
+
+ Fourth revision: This message format supports the USA28XG
+
+ Buffer formats for RX/TX data messages are not defined by
+ a structure, but are described here:
+
+ USB OUT (host -> USAxx, transmit) messages contain a
+ REQUEST_ACK indicator (set to 0xff to request an ACK at the
+ completion of transmit; 0x00 otherwise), followed by data:
+
+ RQSTACK DAT DAT DAT ...
+
+ with a total data length of up to 63.
+
+ USB IN (USAxx -> host, receive) messages begin with a status
+ byte in which the 0x80 bit is either:
+
+ (a) 0x80 bit clear
+ indicates that the bytes following it are all data
+ bytes:
+
+ STAT DATA DATA DATA DATA DATA ...
+
+ for a total of up to 63 DATA bytes,
+
+ or:
+
+ (b) 0x80 bit set
+ indiates that the bytes following alternate data and
+ status bytes:
+
+ STAT DATA STAT DATA STAT DATA STAT DATA ...
+
+ for a total of up to 32 DATA bytes.
+
+ The valid bits in the STAT bytes are:
+
+ OVERRUN 0x02
+ PARITY 0x04
+ FRAMING 0x08
+ BREAK 0x10
+
+ Notes:
+
+ (1) The OVERRUN bit can appear in either (a) or (b) format
+ messages, but the but the PARITY/FRAMING/BREAK bits
+ only appear in (b) format messages.
+ (2) For the host to determine the exact point at which the
+ overrun occurred (to identify the point in the data
+ stream at which the data was lost), it needs to count
+ 128 characters, starting at the first character of the
+ message in which OVERRUN was reported; the lost character(s)
+ would have been received between the 128th and 129th
+ characters.
+ (3) An RX data message in which the first byte has 0x80 clear
+ serves as a "break off" indicator.
+
+ revision history:
+
+ 1999feb10 add reportHskiaChanges to allow us to ignore them
+ 1999feb10 add txAckThreshold for fast+loose throughput enhancement
+ 1999mar30 beef up support for RX error reporting
+ 1999apr14 add resetDataToggle to control message
+ 2000jan04 merge with usa17msg.h
+ 2000jun01 add extended BSD-style copyright text
+ 2001jul05 change message format to improve OVERRUN case
+ 2002jun05 update copyright date, improve comments
+ 2006feb06 modify for FX1 chip
+
+*/
+
+#ifndef __USA67MSG__
+#define __USA67MSG__
+
+
+// all things called "ControlMessage" are sent on the 'control' endpoint
+
+typedef struct keyspan_usa67_portControlMessage
+{
+ u8 port; // 0 or 1 (selects port)
+ /*
+ there are three types of "commands" sent in the control message:
+
+ 1. configuration changes which must be requested by setting
+ the corresponding "set" flag (and should only be requested
+ when necessary, to reduce overhead on the device):
+ */
+ u8 setClocking, // host requests baud rate be set
+ baudLo, // host does baud divisor calculation
+ baudHi, // baudHi is only used for first port (gives lower rates)
+ externalClock_txClocking,
+ // 0=internal, other=external
+
+ setLcr, // host requests lcr be set
+ lcr, // use PARITY, STOPBITS, DATABITS below
+
+ setFlowControl, // host requests flow control be set
+ ctsFlowControl, // 1=use CTS flow control, 0=don't
+ xonFlowControl, // 1=use XON/XOFF flow control, 0=don't
+ xonChar, // specified in current character format
+ xoffChar, // specified in current character format
+
+ setTxTriState_setRts,
+ // host requests TX tri-state be set
+ txTriState_rts, // 1=active (normal), 0=tristate (off)
+
+ setHskoa_setDtr,
+ // host requests HSKOA output be set
+ hskoa_dtr, // 1=on, 0=off
+
+ setPrescaler, // host requests prescalar be set (default: 13)
+ prescaler; // specified as N/8; values 8-ff are valid
+ // must be set any time internal baud rate is set;
+ // must not be set when external clocking is used
+
+ /*
+ 3. configuration data which is simply used as is (no overhead,
+ but must be specified correctly in every host message).
+ */
+ u8 forwardingLength, // forward when this number of chars available
+ reportHskiaChanges_dsrFlowControl,
+ // 1=normal; 0=ignore external clock
+ // 1=use DSR flow control, 0=don't
+ txAckThreshold, // 0=not allowed, 1=normal, 2-255 deliver ACK faster
+ loopbackMode; // 0=no loopback, 1=loopback enabled
+
+ /*
+ 4. commands which are flags only; these are processed in order
+ (so that, e.g., if both _txOn and _txOff flags are set, the
+ port ends in a TX_OFF state); any non-zero value is respected
+ */
+ u8 _txOn, // enable transmitting (and continue if there's data)
+ _txOff, // stop transmitting
+ txFlush, // toss outbound data
+ txBreak, // turn on break (cleared by _txOn)
+ rxOn, // turn on receiver
+ rxOff, // turn off receiver
+ rxFlush, // toss inbound data
+ rxForward, // forward all inbound data, NOW (as if fwdLen==1)
+ returnStatus, // return current status (even if it hasn't changed)
+ resetDataToggle;// reset data toggle state to DATA0
+
+} keyspan_usa67_portControlMessage;
+
+// defines for bits in lcr
+#define USA_DATABITS_5 0x00
+#define USA_DATABITS_6 0x01
+#define USA_DATABITS_7 0x02
+#define USA_DATABITS_8 0x03
+#define STOPBITS_5678_1 0x00 // 1 stop bit for all byte sizes
+#define STOPBITS_5_1p5 0x04 // 1.5 stop bits for 5-bit byte
+#define STOPBITS_678_2 0x04 // 2 stop bits for 6/7/8-bit byte
+#define USA_PARITY_NONE 0x00
+#define USA_PARITY_ODD 0x08
+#define USA_PARITY_EVEN 0x18
+#define PARITY_1 0x28
+#define PARITY_0 0x38
+
+// all things called "StatusMessage" are sent on the status endpoint
+
+typedef struct keyspan_usa67_portStatusMessage // one for each port
+{
+ u8 port, // 0=first, 1=second, other=see below
+ hskia_cts, // reports HSKIA pin
+ gpia_dcd, // reports GPIA pin
+ _txOff, // port has been disabled (by host)
+ _txXoff, // port is in XOFF state (either host or RX XOFF)
+ txAck, // indicates a TX message acknowledgement
+ rxEnabled, // as configured by rxOn/rxOff 1=on, 0=off
+ controlResponse;// 1=a control message has been processed
+} keyspan_usa67_portStatusMessage;
+
+// bits in RX data message when STAT byte is included
+#define RXERROR_OVERRUN 0x02
+#define RXERROR_PARITY 0x04
+#define RXERROR_FRAMING 0x08
+#define RXERROR_BREAK 0x10
+
+typedef struct keyspan_usa67_globalControlMessage
+{
+ u8 port, // 3
+ sendGlobalStatus, // 2=request for two status responses
+ resetStatusToggle, // 1=reset global status toggle
+ resetStatusCount; // a cycling value
+} keyspan_usa67_globalControlMessage;
+
+typedef struct keyspan_usa67_globalStatusMessage
+{
+ u8 port, // 3
+ sendGlobalStatus, // from request, decremented
+ resetStatusCount; // as in request
+} keyspan_usa67_globalStatusMessage;
+
+typedef struct keyspan_usa67_globalDebugMessage
+{
+ u8 port, // 2
+ a,
+ b,
+ c,
+ d;
+} keyspan_usa67_globalDebugMessage;
+
+// ie: the maximum length of an FX1 endpoint buffer
+#define MAX_DATA_LEN 64
+
+// update status approx. 60 times a second (16.6666 ms)
+#define STATUS_UPDATE_INTERVAL 16
+
+// status rationing tuning value (each port gets checked each n ms)
+#define STATUS_RATION 10
+
+#endif
+
+
diff --git a/drivers/usb/serial/keyspan_usa90msg.h b/drivers/usb/serial/keyspan_usa90msg.h
new file mode 100644
index 000000000..c4ca0f631
--- /dev/null
+++ b/drivers/usb/serial/keyspan_usa90msg.h
@@ -0,0 +1,199 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ usa90msg.h
+
+ Copyright (c) 1998-2003 InnoSys Incorporated. All Rights Reserved
+ This file is available under a BSD-style copyright
+
+ Keyspan USB Async Message Formats for the USA19HS
+
+ 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 this licence text
+ without modification, this list of conditions, and the following
+ disclaimer. The following copyright notice must appear immediately at
+ the beginning of all source files:
+
+ Copyright (c) 1998-2003 InnoSys Incorporated. All Rights Reserved
+
+ This file is available under a BSD-style copyright
+
+ 2. The name of InnoSys Incorporated may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY INNOSYS CORP. ``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 OR CONTRIBUTORS 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.
+
+ Revisions:
+
+ 2003feb14 add setTxMode/txMode and cancelRxXoff to portControl
+ 2003mar21 change name of PARITY_0/1 to add MARK/SPACE
+*/
+
+#ifndef __USA90MSG__
+#define __USA90MSG__
+
+struct keyspan_usa90_portControlMessage
+{
+ /*
+ there are three types of "commands" sent in the control message:
+
+ 1. configuration changes which must be requested by setting
+ the corresponding "set" flag (and should only be requested
+ when necessary, to reduce overhead on the device):
+ */
+
+ u8 setClocking, // host requests baud rate be set
+ baudLo, // host does baud divisor calculation
+ baudHi, // host does baud divisor calculation
+
+ setLcr, // host requests lcr be set
+ lcr, // use PARITY, STOPBITS, DATABITS below
+
+ setRxMode, // set receive mode
+ rxMode, // RXMODE_DMA or RXMODE_BYHAND
+
+ setTxMode, // set transmit mode
+ txMode, // TXMODE_DMA or TXMODE_BYHAND
+
+ setTxFlowControl, // host requests tx flow control be set
+ txFlowControl , // use TX_FLOW... bits below
+ setRxFlowControl, // host requests rx flow control be set
+ rxFlowControl, // use RX_FLOW... bits below
+ sendXoff, // host requests XOFF transmitted immediately
+ sendXon, // host requests XON char transmitted
+ xonChar, // specified in current character format
+ xoffChar, // specified in current character format
+
+ sendChar, // host requests char transmitted immediately
+ txChar, // character to send
+
+ setRts, // host requests RTS output be set
+ rts, // 1=on, 0=off
+ setDtr, // host requests DTR output be set
+ dtr; // 1=on, 0=off
+
+
+ /*
+ 2. configuration data which is simply used as is
+ and must be specified correctly in every host message.
+ */
+
+ u8 rxForwardingLength, // forward when this number of chars available
+ rxForwardingTimeout, // (1-31 in ms)
+ txAckSetting; // 0=don't ack, 1=normal, 2-255 TBD...
+ /*
+ 3. Firmware states which cause actions if they change
+ and must be specified correctly in every host message.
+ */
+
+ u8 portEnabled, // 0=disabled, 1=enabled
+ txFlush, // 0=normal, 1=toss outbound data
+ txBreak, // 0=break off, 1=break on
+ loopbackMode; // 0=no loopback, 1=loopback enabled
+
+ /*
+ 4. commands which are flags only; these are processed in order
+ (so that, e.g., if rxFlush and rxForward flags are set, the
+ port will have no data to forward); any non-zero value
+ is respected
+ */
+
+ u8 rxFlush, // toss inbound data
+ rxForward, // forward all inbound data, NOW (as if fwdLen==1)
+ cancelRxXoff, // cancel any receive XOFF state (_txXoff)
+ returnStatus; // return current status NOW
+};
+
+// defines for bits in lcr
+#define USA_DATABITS_5 0x00
+#define USA_DATABITS_6 0x01
+#define USA_DATABITS_7 0x02
+#define USA_DATABITS_8 0x03
+#define STOPBITS_5678_1 0x00 // 1 stop bit for all byte sizes
+#define STOPBITS_5_1p5 0x04 // 1.5 stop bits for 5-bit byte
+#define STOPBITS_678_2 0x04 // 2 stop bits for 6-8 bit byte
+#define USA_PARITY_NONE 0x00
+#define USA_PARITY_ODD 0x08
+#define USA_PARITY_EVEN 0x18
+#define PARITY_MARK_1 0x28 // force parity MARK
+#define PARITY_SPACE_0 0x38 // force parity SPACE
+
+#define TXFLOW_CTS 0x04
+#define TXFLOW_DSR 0x08
+#define TXFLOW_XOFF 0x01
+#define TXFLOW_XOFF_ANY 0x02
+#define TXFLOW_XOFF_BITS (TXFLOW_XOFF | TXFLOW_XOFF_ANY)
+
+#define RXFLOW_XOFF 0x10
+#define RXFLOW_RTS 0x20
+#define RXFLOW_DTR 0x40
+#define RXFLOW_DSR_SENSITIVITY 0x80
+
+#define RXMODE_BYHAND 0x00
+#define RXMODE_DMA 0x02
+
+#define TXMODE_BYHAND 0x00
+#define TXMODE_DMA 0x02
+
+
+// all things called "StatusMessage" are sent on the status endpoint
+
+struct keyspan_usa90_portStatusMessage
+{
+ u8 msr, // reports the actual MSR register
+ cts, // reports CTS pin
+ dcd, // reports DCD pin
+ dsr, // reports DSR pin
+ ri, // reports RI pin
+ _txXoff, // port is in XOFF state (we received XOFF)
+ rxBreak, // reports break state
+ rxOverrun, // count of overrun errors (since last reported)
+ rxParity, // count of parity errors (since last reported)
+ rxFrame, // count of frame errors (since last reported)
+ portState, // PORTSTATE_xxx bits (useful for debugging)
+ messageAck, // message acknowledgement
+ charAck, // character acknowledgement
+ controlResponse; // (value = returnStatus) a control message has been processed
+};
+
+// bits in RX data message when STAT byte is included
+
+#define RXERROR_OVERRUN 0x02
+#define RXERROR_PARITY 0x04
+#define RXERROR_FRAMING 0x08
+#define RXERROR_BREAK 0x10
+
+#define PORTSTATE_ENABLED 0x80
+#define PORTSTATE_TXFLUSH 0x01
+#define PORTSTATE_TXBREAK 0x02
+#define PORTSTATE_LOOPBACK 0x04
+
+// MSR bits
+
+#define USA_MSR_dCTS 0x01 // CTS has changed since last report
+#define USA_MSR_dDSR 0x02
+#define USA_MSR_dRI 0x04
+#define USA_MSR_dDCD 0x08
+
+#define USA_MSR_CTS 0x10 // current state of CTS
+#define USA_MSR_DSR 0x20
+#define USA_USA_MSR_RI 0x40
+#define MSR_DCD 0x80
+
+// ie: the maximum length of an endpoint buffer
+#define MAX_DATA_LEN 64
+
+#endif
diff --git a/drivers/usb/serial/kl5kusb105.c b/drivers/usb/serial/kl5kusb105.c
new file mode 100644
index 000000000..b74c88fec
--- /dev/null
+++ b/drivers/usb/serial/kl5kusb105.c
@@ -0,0 +1,512 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * KLSI KL5KUSB105 chip RS232 converter driver
+ *
+ * Copyright (C) 2010 Johan Hovold <jhovold@gmail.com>
+ * Copyright (C) 2001 Utz-Uwe Haus <haus@uuhaus.de>
+ *
+ * All information about the device was acquired using SniffUSB ans snoopUSB
+ * on Windows98.
+ * It was written out of frustration with the PalmConnect USB Serial adapter
+ * sold by Palm Inc.
+ * Neither Palm, nor their contractor (MCCI) or their supplier (KLSI) provided
+ * information that was not already available.
+ *
+ * It seems that KLSI bought some silicon-design information from ScanLogic,
+ * whose SL11R processor is at the core of the KL5KUSB chipset from KLSI.
+ * KLSI has firmware available for their devices; it is probable that the
+ * firmware differs from that used by KLSI in their products. If you have an
+ * original KLSI device and can provide some information on it, I would be
+ * most interested in adding support for it here. If you have any information
+ * on the protocol used (or find errors in my reverse-engineered stuff), please
+ * let me know.
+ *
+ * The code was only tested with a PalmConnect USB adapter; if you
+ * are adventurous, try it with any KLSI-based device and let me know how it
+ * breaks so that I can fix it!
+ */
+
+/* TODO:
+ * check modem line signals
+ * implement handshaking or decide that we do not support it
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/unaligned.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include "kl5kusb105.h"
+
+#define DRIVER_AUTHOR "Utz-Uwe Haus <haus@uuhaus.de>, Johan Hovold <jhovold@gmail.com>"
+#define DRIVER_DESC "KLSI KL5KUSB105 chipset USB->Serial Converter driver"
+
+
+/*
+ * Function prototypes
+ */
+static int klsi_105_port_probe(struct usb_serial_port *port);
+static void klsi_105_port_remove(struct usb_serial_port *port);
+static int klsi_105_open(struct tty_struct *tty, struct usb_serial_port *port);
+static void klsi_105_close(struct usb_serial_port *port);
+static void klsi_105_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+static int klsi_105_tiocmget(struct tty_struct *tty);
+static void klsi_105_process_read_urb(struct urb *urb);
+static int klsi_105_prepare_write_buffer(struct usb_serial_port *port,
+ void *dest, size_t size);
+
+/*
+ * All of the device info needed for the KLSI converters.
+ */
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(PALMCONNECT_VID, PALMCONNECT_PID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_serial_driver kl5kusb105d_device = {
+ .driver = {
+ .name = "kl5kusb105d",
+ },
+ .description = "KL5KUSB105D / PalmConnect",
+ .id_table = id_table,
+ .num_ports = 1,
+ .bulk_out_size = 64,
+ .open = klsi_105_open,
+ .close = klsi_105_close,
+ .set_termios = klsi_105_set_termios,
+ .tiocmget = klsi_105_tiocmget,
+ .port_probe = klsi_105_port_probe,
+ .port_remove = klsi_105_port_remove,
+ .throttle = usb_serial_generic_throttle,
+ .unthrottle = usb_serial_generic_unthrottle,
+ .process_read_urb = klsi_105_process_read_urb,
+ .prepare_write_buffer = klsi_105_prepare_write_buffer,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &kl5kusb105d_device, NULL
+};
+
+struct klsi_105_port_settings {
+ u8 pktlen; /* always 5, it seems */
+ u8 baudrate;
+ u8 databits;
+ u8 unknown1;
+ u8 unknown2;
+};
+
+struct klsi_105_private {
+ struct klsi_105_port_settings cfg;
+ unsigned long line_state; /* modem line settings */
+ spinlock_t lock;
+};
+
+
+/*
+ * Handle vendor specific USB requests
+ */
+
+
+#define KLSI_TIMEOUT 5000 /* default urb timeout */
+
+static int klsi_105_chg_port_settings(struct usb_serial_port *port,
+ struct klsi_105_port_settings *settings)
+{
+ int rc;
+
+ rc = usb_control_msg_send(port->serial->dev,
+ 0,
+ KL5KUSB105A_SIO_SET_DATA,
+ USB_TYPE_VENDOR | USB_DIR_OUT |
+ USB_RECIP_INTERFACE,
+ 0, /* value */
+ 0, /* index */
+ settings,
+ sizeof(struct klsi_105_port_settings),
+ KLSI_TIMEOUT,
+ GFP_KERNEL);
+ if (rc)
+ dev_err(&port->dev,
+ "Change port settings failed (error = %d)\n", rc);
+
+ dev_dbg(&port->dev,
+ "pktlen %u, baudrate 0x%02x, databits %u, u1 %u, u2 %u\n",
+ settings->pktlen, settings->baudrate, settings->databits,
+ settings->unknown1, settings->unknown2);
+
+ return rc;
+}
+
+/*
+ * Read line control via vendor command and return result through
+ * the state pointer.
+ */
+static int klsi_105_get_line_state(struct usb_serial_port *port,
+ unsigned long *state)
+{
+ u16 status;
+ int rc;
+
+ rc = usb_control_msg_recv(port->serial->dev, 0,
+ KL5KUSB105A_SIO_POLL,
+ USB_TYPE_VENDOR | USB_DIR_IN,
+ 0, /* value */
+ 0, /* index */
+ &status, sizeof(status),
+ 10000,
+ GFP_KERNEL);
+ if (rc) {
+ dev_err(&port->dev, "reading line status failed: %d\n", rc);
+ return rc;
+ }
+
+ le16_to_cpus(&status);
+
+ dev_dbg(&port->dev, "read status %04x\n", status);
+
+ *state = ((status & KL5KUSB105A_DSR) ? TIOCM_DSR : 0) |
+ ((status & KL5KUSB105A_CTS) ? TIOCM_CTS : 0);
+
+ return 0;
+}
+
+
+/*
+ * Driver's tty interface functions
+ */
+
+static int klsi_105_port_probe(struct usb_serial_port *port)
+{
+ struct klsi_105_private *priv;
+
+ priv = kmalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ /* set initial values for control structures */
+ priv->cfg.pktlen = 5;
+ priv->cfg.baudrate = kl5kusb105a_sio_b9600;
+ priv->cfg.databits = kl5kusb105a_dtb_8;
+ priv->cfg.unknown1 = 0;
+ priv->cfg.unknown2 = 1;
+
+ priv->line_state = 0;
+
+ spin_lock_init(&priv->lock);
+
+ usb_set_serial_port_data(port, priv);
+
+ return 0;
+}
+
+static void klsi_105_port_remove(struct usb_serial_port *port)
+{
+ struct klsi_105_private *priv;
+
+ priv = usb_get_serial_port_data(port);
+ kfree(priv);
+}
+
+static int klsi_105_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct klsi_105_private *priv = usb_get_serial_port_data(port);
+ int retval = 0;
+ int rc;
+ unsigned long line_state;
+ struct klsi_105_port_settings cfg;
+ unsigned long flags;
+
+ /* Do a defined restart:
+ * Set up sane default baud rate and send the 'READ_ON'
+ * vendor command.
+ * FIXME: set modem line control (how?)
+ * Then read the modem line control and store values in
+ * priv->line_state.
+ */
+
+ cfg.pktlen = 5;
+ cfg.baudrate = kl5kusb105a_sio_b9600;
+ cfg.databits = kl5kusb105a_dtb_8;
+ cfg.unknown1 = 0;
+ cfg.unknown2 = 1;
+ klsi_105_chg_port_settings(port, &cfg);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->cfg.pktlen = cfg.pktlen;
+ priv->cfg.baudrate = cfg.baudrate;
+ priv->cfg.databits = cfg.databits;
+ priv->cfg.unknown1 = cfg.unknown1;
+ priv->cfg.unknown2 = cfg.unknown2;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /* READ_ON and urb submission */
+ rc = usb_serial_generic_open(tty, port);
+ if (rc)
+ return rc;
+
+ rc = usb_control_msg(port->serial->dev,
+ usb_sndctrlpipe(port->serial->dev, 0),
+ KL5KUSB105A_SIO_CONFIGURE,
+ USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,
+ KL5KUSB105A_SIO_CONFIGURE_READ_ON,
+ 0, /* index */
+ NULL,
+ 0,
+ KLSI_TIMEOUT);
+ if (rc < 0) {
+ dev_err(&port->dev, "Enabling read failed (error = %d)\n", rc);
+ retval = rc;
+ goto err_generic_close;
+ } else
+ dev_dbg(&port->dev, "%s - enabled reading\n", __func__);
+
+ rc = klsi_105_get_line_state(port, &line_state);
+ if (rc < 0) {
+ retval = rc;
+ goto err_disable_read;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->line_state = line_state;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ dev_dbg(&port->dev, "%s - read line state 0x%lx\n", __func__,
+ line_state);
+
+ return 0;
+
+err_disable_read:
+ usb_control_msg(port->serial->dev,
+ usb_sndctrlpipe(port->serial->dev, 0),
+ KL5KUSB105A_SIO_CONFIGURE,
+ USB_TYPE_VENDOR | USB_DIR_OUT,
+ KL5KUSB105A_SIO_CONFIGURE_READ_OFF,
+ 0, /* index */
+ NULL, 0,
+ KLSI_TIMEOUT);
+err_generic_close:
+ usb_serial_generic_close(port);
+
+ return retval;
+}
+
+static void klsi_105_close(struct usb_serial_port *port)
+{
+ int rc;
+
+ /* send READ_OFF */
+ rc = usb_control_msg(port->serial->dev,
+ usb_sndctrlpipe(port->serial->dev, 0),
+ KL5KUSB105A_SIO_CONFIGURE,
+ USB_TYPE_VENDOR | USB_DIR_OUT,
+ KL5KUSB105A_SIO_CONFIGURE_READ_OFF,
+ 0, /* index */
+ NULL, 0,
+ KLSI_TIMEOUT);
+ if (rc < 0)
+ dev_err(&port->dev, "failed to disable read: %d\n", rc);
+
+ /* shutdown our bulk reads and writes */
+ usb_serial_generic_close(port);
+}
+
+/* We need to write a complete 64-byte data block and encode the
+ * number actually sent in the first double-byte, LSB-order. That
+ * leaves at most 62 bytes of payload.
+ */
+#define KLSI_HDR_LEN 2
+static int klsi_105_prepare_write_buffer(struct usb_serial_port *port,
+ void *dest, size_t size)
+{
+ unsigned char *buf = dest;
+ int count;
+
+ count = kfifo_out_locked(&port->write_fifo, buf + KLSI_HDR_LEN,
+ size - KLSI_HDR_LEN, &port->lock);
+ put_unaligned_le16(count, buf);
+
+ return count + KLSI_HDR_LEN;
+}
+
+/* The data received is preceded by a length double-byte in LSB-first order.
+ */
+static void klsi_105_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ unsigned len;
+
+ /* empty urbs seem to happen, we ignore them */
+ if (!urb->actual_length)
+ return;
+
+ if (urb->actual_length <= KLSI_HDR_LEN) {
+ dev_dbg(&port->dev, "%s - malformed packet\n", __func__);
+ return;
+ }
+
+ len = get_unaligned_le16(data);
+ if (len > urb->actual_length - KLSI_HDR_LEN) {
+ dev_dbg(&port->dev, "%s - packet length mismatch\n", __func__);
+ len = urb->actual_length - KLSI_HDR_LEN;
+ }
+
+ tty_insert_flip_string(&port->port, data + KLSI_HDR_LEN, len);
+ tty_flip_buffer_push(&port->port);
+}
+
+static void klsi_105_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct klsi_105_private *priv = usb_get_serial_port_data(port);
+ struct device *dev = &port->dev;
+ unsigned int iflag = tty->termios.c_iflag;
+ unsigned int old_iflag = old_termios->c_iflag;
+ unsigned int cflag = tty->termios.c_cflag;
+ unsigned int old_cflag = old_termios->c_cflag;
+ struct klsi_105_port_settings *cfg;
+ unsigned long flags;
+ speed_t baud;
+
+ cfg = kmalloc_obj(*cfg);
+ if (!cfg)
+ return;
+
+ /* lock while we are modifying the settings */
+ spin_lock_irqsave(&priv->lock, flags);
+
+ /*
+ * Update baud rate
+ */
+ baud = tty_get_baud_rate(tty);
+
+ switch (baud) {
+ case 0: /* handled below */
+ break;
+ case 1200:
+ priv->cfg.baudrate = kl5kusb105a_sio_b1200;
+ break;
+ case 2400:
+ priv->cfg.baudrate = kl5kusb105a_sio_b2400;
+ break;
+ case 4800:
+ priv->cfg.baudrate = kl5kusb105a_sio_b4800;
+ break;
+ case 9600:
+ priv->cfg.baudrate = kl5kusb105a_sio_b9600;
+ break;
+ case 19200:
+ priv->cfg.baudrate = kl5kusb105a_sio_b19200;
+ break;
+ case 38400:
+ priv->cfg.baudrate = kl5kusb105a_sio_b38400;
+ break;
+ case 57600:
+ priv->cfg.baudrate = kl5kusb105a_sio_b57600;
+ break;
+ case 115200:
+ priv->cfg.baudrate = kl5kusb105a_sio_b115200;
+ break;
+ default:
+ dev_dbg(dev, "unsupported baudrate, using 9600\n");
+ priv->cfg.baudrate = kl5kusb105a_sio_b9600;
+ baud = 9600;
+ break;
+ }
+
+ /*
+ * FIXME: implement B0 handling
+ *
+ * Maybe this should be simulated by sending read disable and read
+ * enable messages?
+ */
+
+ tty_encode_baud_rate(tty, baud, baud);
+
+ if ((cflag & CSIZE) != (old_cflag & CSIZE)) {
+ /* set the number of data bits */
+ switch (cflag & CSIZE) {
+ case CS5:
+ dev_dbg(dev, "%s - 5 bits/byte not supported\n", __func__);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ goto err;
+ case CS6:
+ dev_dbg(dev, "%s - 6 bits/byte not supported\n", __func__);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ goto err;
+ case CS7:
+ priv->cfg.databits = kl5kusb105a_dtb_7;
+ break;
+ case CS8:
+ priv->cfg.databits = kl5kusb105a_dtb_8;
+ break;
+ default:
+ dev_err(dev, "CSIZE was not CS5-CS8, using default of 8\n");
+ priv->cfg.databits = kl5kusb105a_dtb_8;
+ break;
+ }
+ }
+
+ /*
+ * Update line control register (LCR)
+ */
+ if ((cflag & (PARENB|PARODD)) != (old_cflag & (PARENB|PARODD))
+ || (cflag & CSTOPB) != (old_cflag & CSTOPB)) {
+ /* Not currently supported */
+ tty->termios.c_cflag &= ~(PARENB|PARODD|CSTOPB);
+ }
+ /*
+ * Set flow control: well, I do not really now how to handle DTR/RTS.
+ * Just do what we have seen with SniffUSB on Win98.
+ */
+ if ((iflag & IXOFF) != (old_iflag & IXOFF)
+ || (iflag & IXON) != (old_iflag & IXON)
+ || (cflag & CRTSCTS) != (old_cflag & CRTSCTS)) {
+ /* Not currently supported */
+ tty->termios.c_cflag &= ~CRTSCTS;
+ }
+ memcpy(cfg, &priv->cfg, sizeof(*cfg));
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /* now commit changes to device */
+ klsi_105_chg_port_settings(port, cfg);
+err:
+ kfree(cfg);
+}
+
+static int klsi_105_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct klsi_105_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ int rc;
+ unsigned long line_state;
+
+ rc = klsi_105_get_line_state(port, &line_state);
+ if (rc < 0) {
+ dev_err(&port->dev,
+ "Reading line control failed (error = %d)\n", rc);
+ /* better return value? EAGAIN? */
+ return rc;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->line_state = line_state;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ dev_dbg(&port->dev, "%s - read line state 0x%lx\n", __func__, line_state);
+ return (int)line_state;
+}
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/kl5kusb105.h b/drivers/usb/serial/kl5kusb105.h
new file mode 100644
index 000000000..dbe98d85c
--- /dev/null
+++ b/drivers/usb/serial/kl5kusb105.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Definitions for the KLSI KL5KUSB105 serial port adapter
+ */
+
+/* vendor/product pairs that are known to contain this chipset */
+#define PALMCONNECT_VID 0x0830
+#define PALMCONNECT_PID 0x0080
+
+/* Vendor commands: */
+
+
+/* port table -- the chip supports up to 4 channels */
+
+/* baud rates */
+
+enum {
+ kl5kusb105a_sio_b115200 = 0,
+ kl5kusb105a_sio_b57600 = 1,
+ kl5kusb105a_sio_b38400 = 2,
+ kl5kusb105a_sio_b19200 = 4,
+ kl5kusb105a_sio_b14400 = 5,
+ kl5kusb105a_sio_b9600 = 6,
+ kl5kusb105a_sio_b4800 = 8, /* unchecked */
+ kl5kusb105a_sio_b2400 = 9, /* unchecked */
+ kl5kusb105a_sio_b1200 = 0xa, /* unchecked */
+ kl5kusb105a_sio_b600 = 0xb /* unchecked */
+};
+
+/* data bits */
+#define kl5kusb105a_dtb_7 7
+#define kl5kusb105a_dtb_8 8
+
+
+
+/* requests: */
+#define KL5KUSB105A_SIO_SET_DATA 1
+#define KL5KUSB105A_SIO_POLL 2
+#define KL5KUSB105A_SIO_CONFIGURE 3
+/* values used for request KL5KUSB105A_SIO_CONFIGURE */
+#define KL5KUSB105A_SIO_CONFIGURE_READ_ON 3
+#define KL5KUSB105A_SIO_CONFIGURE_READ_OFF 2
+
+/* Interpretation of modem status lines */
+/* These need sorting out by individually connecting pins and checking
+ * results. FIXME!
+ * When data is being sent we see 0x30 in the lower byte; this must
+ * contain DSR and CTS ...
+ */
+#define KL5KUSB105A_DSR ((1<<4) | (1<<5))
+#define KL5KUSB105A_CTS ((1<<5) | (1<<4))
+
+#define KL5KUSB105A_WANTS_TO_SEND 0x30
+#if 0
+#define KL5KUSB105A_DTR /* Data Terminal Ready */
+#define KL5KUSB105A_CTS /* Clear To Send */
+#define KL5KUSB105A_CD /* Carrier Detect */
+#define KL5KUSB105A_DSR /* Data Set Ready */
+#define KL5KUSB105A_RxD /* Receive pin */
+
+#define KL5KUSB105A_LE
+#define KL5KUSB105A_RTS
+#define KL5KUSB105A_ST
+#define KL5KUSB105A_SR
+#define KL5KUSB105A_RI /* Ring Indicator */
+#endif
diff --git a/drivers/usb/serial/kobil_sct.c b/drivers/usb/serial/kobil_sct.c
new file mode 100644
index 000000000..4a622cf8e
--- /dev/null
+++ b/drivers/usb/serial/kobil_sct.c
@@ -0,0 +1,511 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * KOBIL USB Smart Card Terminal Driver
+ *
+ * Copyright (C) 2002 KOBIL Systems GmbH
+ * Author: Thomas Wahrenbruch
+ *
+ * Contact: linuxusb@kobil.de
+ *
+ * This program is largely derived from work by the linux-usb group
+ * and associated source files. Please see the usb/serial files for
+ * individual credits and copyrights.
+ *
+ * Thanks to Greg Kroah-Hartman (greg@kroah.com) for his help and
+ * patience.
+ *
+ * Supported readers: USB TWIN, KAAN Standard Plus and SecOVID Reader Plus
+ * (Adapter K), B1 Professional and KAAN Professional (Adapter B)
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/ioctl.h>
+#include "kobil_sct.h"
+
+#define DRIVER_AUTHOR "KOBIL Systems GmbH - http://www.kobil.com"
+#define DRIVER_DESC "KOBIL USB Smart Card Terminal Driver (experimental)"
+
+#define KOBIL_VENDOR_ID 0x0D46
+#define KOBIL_ADAPTER_B_PRODUCT_ID 0x2011
+#define KOBIL_ADAPTER_K_PRODUCT_ID 0x2012
+#define KOBIL_USBTWIN_PRODUCT_ID 0x0078
+#define KOBIL_KAAN_SIM_PRODUCT_ID 0x0081
+
+#define KOBIL_TIMEOUT 500
+#define KOBIL_BUF_LENGTH 300
+
+
+/* Function prototypes */
+static int kobil_port_probe(struct usb_serial_port *probe);
+static void kobil_port_remove(struct usb_serial_port *probe);
+static int kobil_open(struct tty_struct *tty, struct usb_serial_port *port);
+static void kobil_close(struct usb_serial_port *port);
+static int kobil_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count);
+static unsigned int kobil_write_room(struct tty_struct *tty);
+static int kobil_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg);
+static int kobil_tiocmget(struct tty_struct *tty);
+static int kobil_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear);
+static void kobil_read_int_callback(struct urb *urb);
+static void kobil_write_int_callback(struct urb *urb);
+static void kobil_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old);
+static void kobil_init_termios(struct tty_struct *tty);
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_ADAPTER_B_PRODUCT_ID) },
+ { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_ADAPTER_K_PRODUCT_ID) },
+ { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_USBTWIN_PRODUCT_ID) },
+ { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_KAAN_SIM_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_serial_driver kobil_device = {
+ .driver = {
+ .name = "kobil",
+ },
+ .description = "KOBIL USB smart card terminal",
+ .id_table = id_table,
+ .num_ports = 1,
+ .num_interrupt_out = 1,
+ .port_probe = kobil_port_probe,
+ .port_remove = kobil_port_remove,
+ .ioctl = kobil_ioctl,
+ .set_termios = kobil_set_termios,
+ .init_termios = kobil_init_termios,
+ .tiocmget = kobil_tiocmget,
+ .tiocmset = kobil_tiocmset,
+ .open = kobil_open,
+ .close = kobil_close,
+ .write = kobil_write,
+ .write_room = kobil_write_room,
+ .read_int_callback = kobil_read_int_callback,
+ .write_int_callback = kobil_write_int_callback,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &kobil_device, NULL
+};
+
+struct kobil_private {
+ unsigned char buf[KOBIL_BUF_LENGTH]; /* buffer for the APDU to send */
+ int filled; /* index of the last char in buf */
+ int cur_pos; /* index of the next char to send in buf */
+ __u16 device_type;
+};
+
+static int kobil_ctrl_send(struct usb_serial_port *port, u8 req, u16 val)
+{
+ return usb_control_msg(port->serial->dev,
+ usb_sndctrlpipe(port->serial->dev, 0),
+ req, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT,
+ val, 0, NULL, 0, KOBIL_TIMEOUT);
+}
+
+static int kobil_ctrl_recv(struct usb_serial_port *port, u8 req, u16 val, void *buf, u16 size)
+{
+ return usb_control_msg(port->serial->dev,
+ usb_rcvctrlpipe(port->serial->dev, 0),
+ req, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_IN,
+ val, 0, buf, size, KOBIL_TIMEOUT);
+}
+
+static int kobil_port_probe(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct kobil_private *priv;
+
+ priv = kmalloc_obj(struct kobil_private);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->filled = 0;
+ priv->cur_pos = 0;
+ priv->device_type = le16_to_cpu(serial->dev->descriptor.idProduct);
+
+ switch (priv->device_type) {
+ case KOBIL_ADAPTER_B_PRODUCT_ID:
+ dev_dbg(&serial->dev->dev, "KOBIL B1 PRO / KAAN PRO detected\n");
+ break;
+ case KOBIL_ADAPTER_K_PRODUCT_ID:
+ dev_dbg(&serial->dev->dev, "KOBIL KAAN Standard Plus / SecOVID Reader Plus detected\n");
+ break;
+ case KOBIL_USBTWIN_PRODUCT_ID:
+ dev_dbg(&serial->dev->dev, "KOBIL USBTWIN detected\n");
+ break;
+ case KOBIL_KAAN_SIM_PRODUCT_ID:
+ dev_dbg(&serial->dev->dev, "KOBIL KAAN SIM detected\n");
+ break;
+ }
+ usb_set_serial_port_data(port, priv);
+
+ return 0;
+}
+
+
+static void kobil_port_remove(struct usb_serial_port *port)
+{
+ struct kobil_private *priv;
+
+ priv = usb_get_serial_port_data(port);
+ kfree(priv);
+}
+
+static void kobil_init_termios(struct tty_struct *tty)
+{
+ /* Default to echo off and other sane device settings */
+ tty->termios.c_lflag = 0;
+ tty->termios.c_iflag = IGNBRK | IGNPAR | IXOFF;
+ /* do NOT translate CR to CR-NL (0x0A -> 0x0A 0x0D) */
+ tty->termios.c_oflag &= ~ONLCR;
+}
+
+static int kobil_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct device *dev = &port->dev;
+ struct kobil_private *priv;
+ unsigned char *transfer_buffer;
+ int transfer_buffer_length = 8;
+ int result;
+
+ priv = usb_get_serial_port_data(port);
+
+ /* allocate memory for transfer buffer */
+ transfer_buffer = kzalloc(transfer_buffer_length, GFP_KERNEL);
+ if (!transfer_buffer)
+ return -ENOMEM;
+
+ /* get hardware version */
+ result = kobil_ctrl_recv(port, SUSBCRequest_GetMisc, SUSBCR_MSC_GetHWVersion,
+ transfer_buffer, transfer_buffer_length);
+ dev_dbg(dev, "%s - Send get_HW_version URB returns: %i\n", __func__, result);
+ if (result >= 3) {
+ dev_dbg(dev, "Hardware version: %i.%i.%i\n", transfer_buffer[0],
+ transfer_buffer[1], transfer_buffer[2]);
+ }
+
+ /* get firmware version */
+ result = kobil_ctrl_recv(port, SUSBCRequest_GetMisc, SUSBCR_MSC_GetFWVersion,
+ transfer_buffer, transfer_buffer_length);
+ dev_dbg(dev, "%s - Send get_FW_version URB returns: %i\n", __func__, result);
+ if (result >= 3) {
+ dev_dbg(dev, "Firmware version: %i.%i.%i\n", transfer_buffer[0],
+ transfer_buffer[1], transfer_buffer[2]);
+ }
+
+ if (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID ||
+ priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID) {
+ /* Setting Baudrate, Parity and Stopbits */
+ result = kobil_ctrl_send(port, SUSBCRequest_SetBaudRateParityAndStopBits,
+ SUSBCR_SBR_9600 | SUSBCR_SPASB_EvenParity | SUSBCR_SPASB_1StopBit);
+ dev_dbg(dev, "%s - Send set_baudrate URB returns: %i\n", __func__, result);
+
+ /* reset all queues */
+ result = kobil_ctrl_send(port, SUSBCRequest_Misc, SUSBCR_MSC_ResetAllQueues);
+ dev_dbg(dev, "%s - Send reset_all_queues URB returns: %i\n", __func__, result);
+ }
+ if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID ||
+ priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID ||
+ priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) {
+ /* start reading (Adapter B 'cause PNP string) */
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ dev_dbg(dev, "%s - Send read URB returns: %i\n", __func__, result);
+ }
+
+ kfree(transfer_buffer);
+ return 0;
+}
+
+
+static void kobil_close(struct usb_serial_port *port)
+{
+ /* FIXME: Add rts/dtr methods */
+ usb_kill_urb(port->interrupt_out_urb);
+ usb_kill_urb(port->interrupt_in_urb);
+}
+
+
+static void kobil_read_int_callback(struct urb *urb)
+{
+ int result;
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+
+ if (status) {
+ dev_dbg(&port->dev, "%s - Read int status not zero: %d\n", __func__, status);
+ return;
+ }
+
+ if (urb->actual_length) {
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length,
+ data);
+ tty_insert_flip_string(&port->port, data, urb->actual_length);
+ tty_flip_buffer_push(&port->port);
+ }
+
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
+ dev_dbg(&port->dev, "%s - Send read URB returns: %i\n", __func__, result);
+}
+
+
+static void kobil_write_int_callback(struct urb *urb)
+{
+}
+
+
+static int kobil_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count)
+{
+ struct kobil_private *priv;
+ int length, todo, result;
+
+ if (count == 0) {
+ dev_dbg(&port->dev, "%s - write request of 0 bytes\n", __func__);
+ return 0;
+ }
+
+ priv = usb_get_serial_port_data(port);
+
+ if (count > (KOBIL_BUF_LENGTH - priv->filled)) {
+ dev_dbg(&port->dev, "%s - Error: write request bigger than buffer size\n", __func__);
+ return -ENOMEM;
+ }
+
+ /* Copy data to buffer */
+ memcpy(priv->buf + priv->filled, buf, count);
+ usb_serial_debug_data(&port->dev, __func__, count, priv->buf + priv->filled);
+ priv->filled = priv->filled + count;
+
+ /* only send complete block. TWIN, KAAN SIM and adapter K
+ use the same protocol. */
+ if (((priv->device_type != KOBIL_ADAPTER_B_PRODUCT_ID) && (priv->filled > 2) && (priv->filled >= (priv->buf[1] + 3))) ||
+ ((priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) && (priv->filled > 3) && (priv->filled >= (priv->buf[2] + 4)))) {
+ /* stop reading (except TWIN and KAAN SIM) */
+ if (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID ||
+ priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID) {
+ usb_kill_urb(port->interrupt_in_urb);
+ }
+
+ todo = priv->filled - priv->cur_pos;
+
+ while (todo > 0) {
+ /* max 8 byte in one urb (endpoint size) */
+ length = min(todo, port->interrupt_out_size);
+ /* copy data to transfer buffer */
+ memcpy(port->interrupt_out_buffer,
+ priv->buf + priv->cur_pos, length);
+ port->interrupt_out_urb->transfer_buffer_length = length;
+
+ priv->cur_pos = priv->cur_pos + length;
+ result = usb_submit_urb(port->interrupt_out_urb,
+ GFP_ATOMIC);
+ dev_dbg(&port->dev, "%s - Send write URB returns: %i\n", __func__, result);
+ todo = priv->filled - priv->cur_pos;
+
+ if (todo > 0)
+ msleep(24);
+ }
+
+ priv->filled = 0;
+ priv->cur_pos = 0;
+
+ /* start reading (except TWIN and KAAN SIM) */
+ if (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID ||
+ priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID) {
+ result = usb_submit_urb(port->interrupt_in_urb,
+ GFP_ATOMIC);
+ dev_dbg(&port->dev, "%s - Send read URB returns: %i\n", __func__, result);
+ }
+ }
+ return count;
+}
+
+
+static unsigned int kobil_write_room(struct tty_struct *tty)
+{
+ /* FIXME */
+ return 8;
+}
+
+
+static int kobil_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct kobil_private *priv;
+ int result;
+ unsigned char *transfer_buffer;
+ int transfer_buffer_length = 8;
+
+ priv = usb_get_serial_port_data(port);
+ if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID ||
+ priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) {
+ /* This device doesn't support ioctl calls */
+ return -EINVAL;
+ }
+
+ /* allocate memory for transfer buffer */
+ transfer_buffer = kzalloc(transfer_buffer_length, GFP_KERNEL);
+ if (!transfer_buffer)
+ return -ENOMEM;
+
+ result = kobil_ctrl_recv(port, SUSBCRequest_GetStatusLineState, 0,
+ transfer_buffer, transfer_buffer_length);
+ dev_dbg(&port->dev, "Send get_status_line_state URB returns: %i\n",
+ result);
+ if (result < 1) {
+ if (result >= 0)
+ result = -EIO;
+ goto out_free;
+ }
+
+ dev_dbg(&port->dev, "Statusline: %02x\n", transfer_buffer[0]);
+
+ result = 0;
+ if ((transfer_buffer[0] & SUSBCR_GSL_DSR) != 0)
+ result = TIOCM_DSR;
+out_free:
+ kfree(transfer_buffer);
+ return result;
+}
+
+static int kobil_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct device *dev = &port->dev;
+ struct kobil_private *priv;
+ int dtr, rts;
+ int result;
+ u16 val = 0;
+
+ priv = usb_get_serial_port_data(port);
+ if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID ||
+ priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) {
+ /* This device doesn't support ioctl calls */
+ return -EINVAL;
+ }
+
+ dtr = (set | clear) & TIOCM_DTR;
+ rts = (set | clear) & TIOCM_RTS;
+
+ if (dtr && priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) {
+ if (set & TIOCM_DTR)
+ val = SUSBCR_SSL_SETDTR;
+ else
+ val = SUSBCR_SSL_CLRDTR;
+ } else if (rts) {
+ if (set & TIOCM_RTS)
+ val = SUSBCR_SSL_SETRTS;
+ else
+ val = SUSBCR_SSL_CLRRTS;
+ }
+
+ if (val) {
+ result = kobil_ctrl_send(port, SUSBCRequest_SetStatusLinesOrQueues, val);
+ if (result < 0) {
+ dev_err(dev, "failed to set status lines: %d\n", result);
+ return result;
+ }
+ }
+
+ return 0;
+}
+
+static void kobil_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old)
+{
+ struct kobil_private *priv;
+ int result;
+ int c_cflag = tty->termios.c_cflag;
+ speed_t speed;
+ u16 val;
+
+ priv = usb_get_serial_port_data(port);
+ if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID ||
+ priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) {
+ /* This device doesn't support ioctl calls */
+ tty_termios_copy_hw(&tty->termios, old);
+ return;
+ }
+
+ speed = tty_get_baud_rate(tty);
+ switch (speed) {
+ case 1200:
+ val = SUSBCR_SBR_1200;
+ break;
+ default:
+ speed = 9600;
+ fallthrough;
+ case 9600:
+ val = SUSBCR_SBR_9600;
+ break;
+ }
+
+ if (c_cflag & CSTOPB)
+ val |= SUSBCR_SPASB_2StopBits;
+ else
+ val |= SUSBCR_SPASB_1StopBit;
+
+ if (c_cflag & PARENB) {
+ if (c_cflag & PARODD)
+ val |= SUSBCR_SPASB_OddParity;
+ else
+ val |= SUSBCR_SPASB_EvenParity;
+ } else {
+ val |= SUSBCR_SPASB_NoParity;
+ }
+
+ tty->termios.c_cflag &= ~CMSPAR;
+ tty_encode_baud_rate(tty, speed, speed);
+
+ result = kobil_ctrl_send(port, SUSBCRequest_SetBaudRateParityAndStopBits, val);
+ if (result) {
+ dev_err(&port->dev, "failed to update line settings: %d\n",
+ result);
+ }
+}
+
+static int kobil_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct kobil_private *priv = usb_get_serial_port_data(port);
+ int result;
+
+ if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID ||
+ priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID)
+ /* This device doesn't support ioctl calls */
+ return -ENOIOCTLCMD;
+
+ switch (cmd) {
+ case TCFLSH:
+ result = kobil_ctrl_send(port, SUSBCRequest_Misc, SUSBCR_MSC_ResetAllQueues);
+ dev_dbg(&port->dev,
+ "%s - Send reset_all_queues (FLUSH) URB returns: %i\n",
+ __func__, result);
+ return (result < 0) ? -EIO: 0;
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/kobil_sct.h b/drivers/usb/serial/kobil_sct.h
new file mode 100644
index 000000000..030c1b426
--- /dev/null
+++ b/drivers/usb/serial/kobil_sct.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#define SUSBCRequest_SetBaudRateParityAndStopBits 1
+#define SUSBCR_SBR_MASK 0xFF00
+#define SUSBCR_SBR_1200 0x0100
+#define SUSBCR_SBR_9600 0x0200
+#define SUSBCR_SBR_19200 0x0400
+#define SUSBCR_SBR_28800 0x0800
+#define SUSBCR_SBR_38400 0x1000
+#define SUSBCR_SBR_57600 0x2000
+#define SUSBCR_SBR_115200 0x4000
+
+#define SUSBCR_SPASB_MASK 0x0070
+#define SUSBCR_SPASB_NoParity 0x0010
+#define SUSBCR_SPASB_OddParity 0x0020
+#define SUSBCR_SPASB_EvenParity 0x0040
+
+#define SUSBCR_SPASB_STPMASK 0x0003
+#define SUSBCR_SPASB_1StopBit 0x0001
+#define SUSBCR_SPASB_2StopBits 0x0002
+
+#define SUSBCRequest_SetStatusLinesOrQueues 2
+#define SUSBCR_SSL_SETRTS 0x0001
+#define SUSBCR_SSL_CLRRTS 0x0002
+#define SUSBCR_SSL_SETDTR 0x0004
+#define SUSBCR_SSL_CLRDTR 0x0010
+
+/* Kill the pending/current writes to the comm port. */
+#define SUSBCR_SSL_PURGE_TXABORT 0x0100
+/* Kill the pending/current reads to the comm port. */
+#define SUSBCR_SSL_PURGE_RXABORT 0x0200
+/* Kill the transmit queue if there. */
+#define SUSBCR_SSL_PURGE_TXCLEAR 0x0400
+/* Kill the typeahead buffer if there. */
+#define SUSBCR_SSL_PURGE_RXCLEAR 0x0800
+
+#define SUSBCRequest_GetStatusLineState 4
+/* Any Character received */
+#define SUSBCR_GSL_RXCHAR 0x0001
+/* Transmitt Queue Empty */
+#define SUSBCR_GSL_TXEMPTY 0x0004
+/* CTS changed state */
+#define SUSBCR_GSL_CTS 0x0008
+/* DSR changed state */
+#define SUSBCR_GSL_DSR 0x0010
+/* RLSD changed state */
+#define SUSBCR_GSL_RLSD 0x0020
+/* BREAK received */
+#define SUSBCR_GSL_BREAK 0x0040
+/* Line status error occurred */
+#define SUSBCR_GSL_ERR 0x0080
+/* Ring signal detected */
+#define SUSBCR_GSL_RING 0x0100
+
+#define SUSBCRequest_Misc 8
+/* use a predefined reset sequence */
+#define SUSBCR_MSC_ResetReader 0x0001
+/* use a predefined sequence to reset the internal queues */
+#define SUSBCR_MSC_ResetAllQueues 0x0002
+
+#define SUSBCRequest_GetMisc 0x10
+
+/*
+ * get the firmware version from device, coded like this 0xHHLLBBPP with
+ * HH = Firmware Version High Byte
+ * LL = Firmware Version Low Byte
+ * BB = Build Number
+ * PP = Further Attributes
+ */
+#define SUSBCR_MSC_GetFWVersion 0x0001
+
+/*
+ * get the hardware version from device coded like this 0xHHLLPPRR with
+ * HH = Software Version High Byte
+ * LL = Software Version Low Byte
+ * PP = Further Attributes
+ * RR = Reserved for the hardware ID
+ */
+#define SUSBCR_MSC_GetHWVersion 0x0002
diff --git a/drivers/usb/serial/mct_u232.c b/drivers/usb/serial/mct_u232.c
new file mode 100644
index 000000000..a6de18917
--- /dev/null
+++ b/drivers/usb/serial/mct_u232.c
@@ -0,0 +1,780 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * MCT (Magic Control Technology Corp.) USB RS232 Converter Driver
+ *
+ * Copyright (C) 2000 Wolfgang Grandegger (wolfgang@ces.ch)
+ *
+ * This program is largely derived from the Belkin USB Serial Adapter Driver
+ * (see belkin_sa.[ch]). All of the information about the device was acquired
+ * by using SniffUSB on Windows98. For technical details see mct_u232.h.
+ *
+ * William G. Greathouse and Greg Kroah-Hartman provided great help on how to
+ * do the reverse engineering and how to write a USB serial device driver.
+ *
+ * TO BE DONE, TO BE CHECKED:
+ * DTR/RTS signal handling may be incomplete or incorrect. I have mainly
+ * implemented what I have seen with SniffUSB or found in belkin_sa.c.
+ * For further TODOs check also belkin_sa.c.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/unaligned.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/serial.h>
+#include "mct_u232.h"
+
+#define DRIVER_AUTHOR "Wolfgang Grandegger <wolfgang@ces.ch>"
+#define DRIVER_DESC "Magic Control Technology USB-RS232 converter driver"
+
+/*
+ * Function prototypes
+ */
+static int mct_u232_port_probe(struct usb_serial_port *port);
+static void mct_u232_port_remove(struct usb_serial_port *remove);
+static int mct_u232_open(struct tty_struct *tty, struct usb_serial_port *port);
+static void mct_u232_close(struct usb_serial_port *port);
+static void mct_u232_dtr_rts(struct usb_serial_port *port, int on);
+static void mct_u232_read_int_callback(struct urb *urb);
+static void mct_u232_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+static int mct_u232_break_ctl(struct tty_struct *tty, int break_state);
+static int mct_u232_tiocmget(struct tty_struct *tty);
+static int mct_u232_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear);
+static void mct_u232_throttle(struct tty_struct *tty);
+static void mct_u232_unthrottle(struct tty_struct *tty);
+
+
+/*
+ * All of the device info needed for the MCT USB-RS232 converter.
+ */
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(MCT_U232_VID, MCT_U232_PID) },
+ { USB_DEVICE(MCT_U232_VID, MCT_U232_SITECOM_PID) },
+ { USB_DEVICE(MCT_U232_VID, MCT_U232_DU_H3SP_PID) },
+ { USB_DEVICE(MCT_U232_BELKIN_F5U109_VID, MCT_U232_BELKIN_F5U109_PID) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_serial_driver mct_u232_device = {
+ .driver = {
+ .name = "mct_u232",
+ },
+ .description = "MCT U232",
+ .id_table = id_table,
+ .num_ports = 1,
+ .open = mct_u232_open,
+ .close = mct_u232_close,
+ .dtr_rts = mct_u232_dtr_rts,
+ .throttle = mct_u232_throttle,
+ .unthrottle = mct_u232_unthrottle,
+ .read_int_callback = mct_u232_read_int_callback,
+ .set_termios = mct_u232_set_termios,
+ .break_ctl = mct_u232_break_ctl,
+ .tiocmget = mct_u232_tiocmget,
+ .tiocmset = mct_u232_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .port_probe = mct_u232_port_probe,
+ .port_remove = mct_u232_port_remove,
+ .get_icount = usb_serial_generic_get_icount,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &mct_u232_device, NULL
+};
+
+struct mct_u232_private {
+ struct urb *read_urb;
+ spinlock_t lock;
+ unsigned int control_state; /* Modem Line Setting (TIOCM) */
+ unsigned char last_lcr; /* Line Control Register */
+ unsigned char last_lsr; /* Line Status Register */
+ unsigned char last_msr; /* Modem Status Register */
+ unsigned int rx_flags; /* Throttling flags */
+};
+
+#define THROTTLED 0x01
+
+/*
+ * Handle vendor specific USB requests
+ */
+
+#define WDR_TIMEOUT 5000 /* default urb timeout */
+
+/*
+ * Later day 2.6.0-test kernels have new baud rates like B230400 which
+ * we do not know how to support. We ignore them for the moment.
+ */
+static int mct_u232_calculate_baud_rate(struct usb_serial *serial,
+ speed_t value, speed_t *result)
+{
+ *result = value;
+
+ if (le16_to_cpu(serial->dev->descriptor.idProduct) == MCT_U232_SITECOM_PID
+ || le16_to_cpu(serial->dev->descriptor.idProduct) == MCT_U232_BELKIN_F5U109_PID) {
+ switch (value) {
+ case 300:
+ return 0x01;
+ case 600:
+ return 0x02; /* this one not tested */
+ case 1200:
+ return 0x03;
+ case 2400:
+ return 0x04;
+ case 4800:
+ return 0x06;
+ case 9600:
+ return 0x08;
+ case 19200:
+ return 0x09;
+ case 38400:
+ return 0x0a;
+ case 57600:
+ return 0x0b;
+ case 115200:
+ return 0x0c;
+ default:
+ *result = 9600;
+ return 0x08;
+ }
+ } else {
+ /* FIXME: Can we use any divider - should we do
+ divider = 115200/value;
+ real baud = 115200/divider */
+ switch (value) {
+ case 300: break;
+ case 600: break;
+ case 1200: break;
+ case 2400: break;
+ case 4800: break;
+ case 9600: break;
+ case 19200: break;
+ case 38400: break;
+ case 57600: break;
+ case 115200: break;
+ default:
+ value = 9600;
+ *result = 9600;
+ }
+ return 115200/value;
+ }
+}
+
+static int mct_u232_set_baud_rate(struct tty_struct *tty,
+ struct usb_serial *serial, struct usb_serial_port *port, speed_t value)
+{
+ unsigned int divisor;
+ int rc;
+ unsigned char *buf;
+ unsigned char cts_enable_byte = 0;
+ speed_t speed;
+
+ buf = kmalloc(MCT_U232_MAX_SIZE, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ divisor = mct_u232_calculate_baud_rate(serial, value, &speed);
+ put_unaligned_le32(divisor, buf);
+ rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ MCT_U232_SET_BAUD_RATE_REQUEST,
+ MCT_U232_SET_REQUEST_TYPE,
+ 0, 0, buf, MCT_U232_SET_BAUD_RATE_SIZE,
+ WDR_TIMEOUT);
+ if (rc < 0) /*FIXME: What value speed results */
+ dev_err(&port->dev, "Set BAUD RATE %d failed (error = %d)\n",
+ value, rc);
+ else
+ tty_encode_baud_rate(tty, speed, speed);
+ dev_dbg(&port->dev, "set_baud_rate: value: 0x%x, divisor: 0x%x\n", value, divisor);
+
+ /* Mimic the MCT-supplied Windows driver (version 1.21P.0104), which
+ always sends two extra USB 'device request' messages after the
+ 'baud rate change' message. The actual functionality of the
+ request codes in these messages is not fully understood but these
+ particular codes are never seen in any operation besides a baud
+ rate change. Both of these messages send a single byte of data.
+ In the first message, the value of this byte is always zero.
+
+ The second message has been determined experimentally to control
+ whether data will be transmitted to a device which is not asserting
+ the 'CTS' signal. If the second message's data byte is zero, data
+ will be transmitted even if 'CTS' is not asserted (i.e. no hardware
+ flow control). if the second message's data byte is nonzero (a
+ value of 1 is used by this driver), data will not be transmitted to
+ a device which is not asserting 'CTS'.
+ */
+
+ buf[0] = 0;
+ rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ MCT_U232_SET_UNKNOWN1_REQUEST,
+ MCT_U232_SET_REQUEST_TYPE,
+ 0, 0, buf, MCT_U232_SET_UNKNOWN1_SIZE,
+ WDR_TIMEOUT);
+ if (rc < 0)
+ dev_err(&port->dev, "Sending USB device request code %d "
+ "failed (error = %d)\n", MCT_U232_SET_UNKNOWN1_REQUEST,
+ rc);
+
+ if (port && C_CRTSCTS(tty))
+ cts_enable_byte = 1;
+
+ dev_dbg(&port->dev, "set_baud_rate: send second control message, data = %02X\n",
+ cts_enable_byte);
+ buf[0] = cts_enable_byte;
+ rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ MCT_U232_SET_CTS_REQUEST,
+ MCT_U232_SET_REQUEST_TYPE,
+ 0, 0, buf, MCT_U232_SET_CTS_SIZE,
+ WDR_TIMEOUT);
+ if (rc < 0)
+ dev_err(&port->dev, "Sending USB device request code %d "
+ "failed (error = %d)\n", MCT_U232_SET_CTS_REQUEST, rc);
+
+ kfree(buf);
+ return rc;
+} /* mct_u232_set_baud_rate */
+
+static int mct_u232_set_line_ctrl(struct usb_serial_port *port,
+ unsigned char lcr)
+{
+ int rc;
+ unsigned char *buf;
+
+ buf = kmalloc(MCT_U232_MAX_SIZE, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ buf[0] = lcr;
+ rc = usb_control_msg(port->serial->dev, usb_sndctrlpipe(port->serial->dev, 0),
+ MCT_U232_SET_LINE_CTRL_REQUEST,
+ MCT_U232_SET_REQUEST_TYPE,
+ 0, 0, buf, MCT_U232_SET_LINE_CTRL_SIZE,
+ WDR_TIMEOUT);
+ if (rc < 0)
+ dev_err(&port->dev, "Set LINE CTRL 0x%x failed (error = %d)\n", lcr, rc);
+ dev_dbg(&port->dev, "set_line_ctrl: 0x%x\n", lcr);
+ kfree(buf);
+ return rc;
+} /* mct_u232_set_line_ctrl */
+
+static int mct_u232_set_modem_ctrl(struct usb_serial_port *port,
+ unsigned int control_state)
+{
+ int rc;
+ unsigned char mcr;
+ unsigned char *buf;
+
+ buf = kmalloc(MCT_U232_MAX_SIZE, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ mcr = MCT_U232_MCR_NONE;
+ if (control_state & TIOCM_DTR)
+ mcr |= MCT_U232_MCR_DTR;
+ if (control_state & TIOCM_RTS)
+ mcr |= MCT_U232_MCR_RTS;
+
+ buf[0] = mcr;
+ rc = usb_control_msg(port->serial->dev, usb_sndctrlpipe(port->serial->dev, 0),
+ MCT_U232_SET_MODEM_CTRL_REQUEST,
+ MCT_U232_SET_REQUEST_TYPE,
+ 0, 0, buf, MCT_U232_SET_MODEM_CTRL_SIZE,
+ WDR_TIMEOUT);
+ kfree(buf);
+
+ dev_dbg(&port->dev, "set_modem_ctrl: state=0x%x ==> mcr=0x%x\n", control_state, mcr);
+
+ if (rc < 0) {
+ dev_err(&port->dev, "Set MODEM CTRL 0x%x failed (error = %d)\n", mcr, rc);
+ return rc;
+ }
+ return 0;
+} /* mct_u232_set_modem_ctrl */
+
+static int mct_u232_get_modem_stat(struct usb_serial_port *port,
+ unsigned char *msr)
+{
+ int rc;
+ unsigned char *buf;
+
+ buf = kmalloc(MCT_U232_MAX_SIZE, GFP_KERNEL);
+ if (buf == NULL) {
+ *msr = 0;
+ return -ENOMEM;
+ }
+ rc = usb_control_msg(port->serial->dev, usb_rcvctrlpipe(port->serial->dev, 0),
+ MCT_U232_GET_MODEM_STAT_REQUEST,
+ MCT_U232_GET_REQUEST_TYPE,
+ 0, 0, buf, MCT_U232_GET_MODEM_STAT_SIZE,
+ WDR_TIMEOUT);
+ if (rc < MCT_U232_GET_MODEM_STAT_SIZE) {
+ dev_err(&port->dev, "Get MODEM STATus failed (error = %d)\n", rc);
+
+ if (rc >= 0)
+ rc = -EIO;
+
+ *msr = 0;
+ } else {
+ *msr = buf[0];
+ }
+ dev_dbg(&port->dev, "get_modem_stat: 0x%x\n", *msr);
+ kfree(buf);
+ return rc;
+} /* mct_u232_get_modem_stat */
+
+static void mct_u232_msr_to_icount(struct async_icount *icount,
+ unsigned char msr)
+{
+ /* Translate Control Line states */
+ if (msr & MCT_U232_MSR_DDSR)
+ icount->dsr++;
+ if (msr & MCT_U232_MSR_DCTS)
+ icount->cts++;
+ if (msr & MCT_U232_MSR_DRI)
+ icount->rng++;
+ if (msr & MCT_U232_MSR_DCD)
+ icount->dcd++;
+} /* mct_u232_msr_to_icount */
+
+static void mct_u232_msr_to_state(struct usb_serial_port *port,
+ unsigned int *control_state, unsigned char msr)
+{
+ /* Translate Control Line states */
+ if (msr & MCT_U232_MSR_DSR)
+ *control_state |= TIOCM_DSR;
+ else
+ *control_state &= ~TIOCM_DSR;
+ if (msr & MCT_U232_MSR_CTS)
+ *control_state |= TIOCM_CTS;
+ else
+ *control_state &= ~TIOCM_CTS;
+ if (msr & MCT_U232_MSR_RI)
+ *control_state |= TIOCM_RI;
+ else
+ *control_state &= ~TIOCM_RI;
+ if (msr & MCT_U232_MSR_CD)
+ *control_state |= TIOCM_CD;
+ else
+ *control_state &= ~TIOCM_CD;
+ dev_dbg(&port->dev, "msr_to_state: msr=0x%x ==> state=0x%x\n", msr, *control_state);
+} /* mct_u232_msr_to_state */
+
+/*
+ * Driver's tty interface functions
+ */
+
+static int mct_u232_port_probe(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct mct_u232_private *priv;
+ u16 pid;
+
+ /* check first to simplify error handling */
+ if (!serial->port[1] || !serial->port[1]->interrupt_in_urb) {
+ dev_err(&port->dev, "expected endpoint missing\n");
+ return -ENODEV;
+ }
+
+ /*
+ * Compensate for a hardware bug: although the Sitecom U232-P25
+ * device reports a maximum output packet size of 32 bytes,
+ * it seems to be able to accept only 16 bytes (and that's what
+ * SniffUSB says too...)
+ */
+ pid = le16_to_cpu(serial->dev->descriptor.idProduct);
+ if (pid == MCT_U232_SITECOM_PID)
+ port->bulk_out_size = min(16, port->bulk_out_size);
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ /* Use second interrupt-in endpoint for reading. */
+ priv->read_urb = serial->port[1]->interrupt_in_urb;
+ priv->read_urb->context = port;
+
+ spin_lock_init(&priv->lock);
+
+ usb_set_serial_port_data(port, priv);
+
+ return 0;
+}
+
+static void mct_u232_port_remove(struct usb_serial_port *port)
+{
+ struct mct_u232_private *priv;
+
+ priv = usb_get_serial_port_data(port);
+ kfree(priv);
+}
+
+static int mct_u232_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct mct_u232_private *priv = usb_get_serial_port_data(port);
+ int retval = 0;
+ unsigned int control_state;
+ unsigned long flags;
+ unsigned char last_lcr;
+ unsigned char last_msr;
+
+ /* Do a defined restart: the normal serial device seems to
+ * always turn on DTR and RTS here, so do the same. I'm not
+ * sure if this is really necessary. But it should not harm
+ * either.
+ */
+ spin_lock_irqsave(&priv->lock, flags);
+ if (tty && C_BAUD(tty))
+ priv->control_state = TIOCM_DTR | TIOCM_RTS;
+ else
+ priv->control_state = 0;
+
+ priv->last_lcr = (MCT_U232_DATA_BITS_8 |
+ MCT_U232_PARITY_NONE |
+ MCT_U232_STOP_BITS_1);
+ control_state = priv->control_state;
+ last_lcr = priv->last_lcr;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ mct_u232_set_modem_ctrl(port, control_state);
+ mct_u232_set_line_ctrl(port, last_lcr);
+
+ /* Read modem status and update control state */
+ mct_u232_get_modem_stat(port, &last_msr);
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->last_msr = last_msr;
+ mct_u232_msr_to_state(port, &priv->control_state, priv->last_msr);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ retval = usb_submit_urb(priv->read_urb, GFP_KERNEL);
+ if (retval) {
+ dev_err(&port->dev,
+ "usb_submit_urb(read) failed pipe 0x%x err %d\n",
+ port->read_urb->pipe, retval);
+ goto error;
+ }
+
+ retval = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (retval) {
+ usb_kill_urb(priv->read_urb);
+ dev_err(&port->dev,
+ "usb_submit_urb(read int) failed pipe 0x%x err %d",
+ port->interrupt_in_urb->pipe, retval);
+ goto error;
+ }
+ return 0;
+
+error:
+ return retval;
+} /* mct_u232_open */
+
+static void mct_u232_dtr_rts(struct usb_serial_port *port, int on)
+{
+ unsigned int control_state;
+ struct mct_u232_private *priv = usb_get_serial_port_data(port);
+
+ spin_lock_irq(&priv->lock);
+ if (on)
+ priv->control_state |= TIOCM_DTR | TIOCM_RTS;
+ else
+ priv->control_state &= ~(TIOCM_DTR | TIOCM_RTS);
+ control_state = priv->control_state;
+ spin_unlock_irq(&priv->lock);
+
+ mct_u232_set_modem_ctrl(port, control_state);
+}
+
+static void mct_u232_close(struct usb_serial_port *port)
+{
+ struct mct_u232_private *priv = usb_get_serial_port_data(port);
+
+ usb_kill_urb(priv->read_urb);
+ usb_kill_urb(port->interrupt_in_urb);
+
+ usb_serial_generic_close(port);
+} /* mct_u232_close */
+
+
+static void mct_u232_read_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct mct_u232_private *priv = usb_get_serial_port_data(port);
+ unsigned char *data = urb->transfer_buffer;
+ int retval;
+ int status = urb->status;
+ unsigned long flags;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ goto exit;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
+
+ /*
+ * Work-a-round: handle the 'usual' bulk-in pipe here
+ */
+ if (urb->transfer_buffer_length > 2) {
+ if (urb->actual_length) {
+ tty_insert_flip_string(&port->port, data,
+ urb->actual_length);
+ tty_flip_buffer_push(&port->port);
+ }
+ goto exit;
+ }
+
+ if (urb->actual_length < 2) {
+ dev_warn_ratelimited(&port->dev, "short interrupt-in packet\n");
+ goto exit;
+ }
+
+ /*
+ * The interrupt-in pipe signals exceptional conditions (modem line
+ * signal changes and errors). data[0] holds MSR, data[1] holds LSR.
+ */
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->last_msr = data[MCT_U232_MSR_INDEX];
+
+ /* Record Control Line states */
+ mct_u232_msr_to_state(port, &priv->control_state, priv->last_msr);
+
+ mct_u232_msr_to_icount(&port->icount, priv->last_msr);
+
+#if 0
+ /* Not yet handled. See belkin_sa.c for further information */
+ /* Now to report any errors */
+ priv->last_lsr = data[MCT_U232_LSR_INDEX];
+ /*
+ * fill in the flip buffer here, but I do not know the relation
+ * to the current/next receive buffer or characters. I need
+ * to look in to this before committing any code.
+ */
+ if (priv->last_lsr & MCT_U232_LSR_ERR) {
+ tty = tty_port_tty_get(&port->port);
+ /* Overrun Error */
+ if (priv->last_lsr & MCT_U232_LSR_OE) {
+ }
+ /* Parity Error */
+ if (priv->last_lsr & MCT_U232_LSR_PE) {
+ }
+ /* Framing Error */
+ if (priv->last_lsr & MCT_U232_LSR_FE) {
+ }
+ /* Break Indicator */
+ if (priv->last_lsr & MCT_U232_LSR_BI) {
+ }
+ tty_kref_put(tty);
+ }
+#endif
+ wake_up_interruptible(&port->port.delta_msr_wait);
+ spin_unlock_irqrestore(&priv->lock, flags);
+exit:
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(&port->dev,
+ "%s - usb_submit_urb failed with result %d\n",
+ __func__, retval);
+} /* mct_u232_read_int_callback */
+
+static void mct_u232_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct usb_serial *serial = port->serial;
+ struct mct_u232_private *priv = usb_get_serial_port_data(port);
+ struct ktermios *termios = &tty->termios;
+ unsigned int cflag = termios->c_cflag;
+ unsigned int old_cflag = old_termios->c_cflag;
+ unsigned long flags;
+ unsigned int control_state;
+ unsigned char last_lcr;
+
+ /* get a local copy of the current port settings */
+ spin_lock_irqsave(&priv->lock, flags);
+ control_state = priv->control_state;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ last_lcr = 0;
+
+ /*
+ * Update baud rate.
+ * Do not attempt to cache old rates and skip settings,
+ * disconnects screw such tricks up completely.
+ * Premature optimization is the root of all evil.
+ */
+
+ /* reassert DTR and RTS on transition from B0 */
+ if ((old_cflag & CBAUD) == B0) {
+ dev_dbg(&port->dev, "%s: baud was B0\n", __func__);
+ control_state |= TIOCM_DTR | TIOCM_RTS;
+ mct_u232_set_modem_ctrl(port, control_state);
+ }
+
+ mct_u232_set_baud_rate(tty, serial, port, tty_get_baud_rate(tty));
+
+ if ((cflag & CBAUD) == B0) {
+ dev_dbg(&port->dev, "%s: baud is B0\n", __func__);
+ /* Drop RTS and DTR */
+ control_state &= ~(TIOCM_DTR | TIOCM_RTS);
+ mct_u232_set_modem_ctrl(port, control_state);
+ }
+
+ /*
+ * Update line control register (LCR)
+ */
+
+ /* set the parity */
+ if (cflag & PARENB)
+ last_lcr |= (cflag & PARODD) ?
+ MCT_U232_PARITY_ODD : MCT_U232_PARITY_EVEN;
+ else
+ last_lcr |= MCT_U232_PARITY_NONE;
+
+ /* set the number of data bits */
+ switch (cflag & CSIZE) {
+ case CS5:
+ last_lcr |= MCT_U232_DATA_BITS_5; break;
+ case CS6:
+ last_lcr |= MCT_U232_DATA_BITS_6; break;
+ case CS7:
+ last_lcr |= MCT_U232_DATA_BITS_7; break;
+ case CS8:
+ last_lcr |= MCT_U232_DATA_BITS_8; break;
+ default:
+ dev_err(&port->dev,
+ "CSIZE was not CS5-CS8, using default of 8\n");
+ last_lcr |= MCT_U232_DATA_BITS_8;
+ break;
+ }
+
+ termios->c_cflag &= ~CMSPAR;
+
+ /* set the number of stop bits */
+ last_lcr |= (cflag & CSTOPB) ?
+ MCT_U232_STOP_BITS_2 : MCT_U232_STOP_BITS_1;
+
+ mct_u232_set_line_ctrl(port, last_lcr);
+
+ /* save off the modified port settings */
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->control_state = control_state;
+ priv->last_lcr = last_lcr;
+ spin_unlock_irqrestore(&priv->lock, flags);
+} /* mct_u232_set_termios */
+
+static int mct_u232_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct mct_u232_private *priv = usb_get_serial_port_data(port);
+ unsigned char lcr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ lcr = priv->last_lcr;
+
+ if (break_state)
+ lcr |= MCT_U232_SET_BREAK;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return mct_u232_set_line_ctrl(port, lcr);
+} /* mct_u232_break_ctl */
+
+
+static int mct_u232_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct mct_u232_private *priv = usb_get_serial_port_data(port);
+ unsigned int control_state;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ control_state = priv->control_state;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return control_state;
+}
+
+static int mct_u232_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct mct_u232_private *priv = usb_get_serial_port_data(port);
+ unsigned int control_state;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ control_state = priv->control_state;
+
+ if (set & TIOCM_RTS)
+ control_state |= TIOCM_RTS;
+ if (set & TIOCM_DTR)
+ control_state |= TIOCM_DTR;
+ if (clear & TIOCM_RTS)
+ control_state &= ~TIOCM_RTS;
+ if (clear & TIOCM_DTR)
+ control_state &= ~TIOCM_DTR;
+
+ priv->control_state = control_state;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return mct_u232_set_modem_ctrl(port, control_state);
+}
+
+static void mct_u232_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct mct_u232_private *priv = usb_get_serial_port_data(port);
+ unsigned int control_state;
+
+ spin_lock_irq(&priv->lock);
+ priv->rx_flags |= THROTTLED;
+ if (C_CRTSCTS(tty)) {
+ priv->control_state &= ~TIOCM_RTS;
+ control_state = priv->control_state;
+ spin_unlock_irq(&priv->lock);
+ mct_u232_set_modem_ctrl(port, control_state);
+ } else {
+ spin_unlock_irq(&priv->lock);
+ }
+}
+
+static void mct_u232_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct mct_u232_private *priv = usb_get_serial_port_data(port);
+ unsigned int control_state;
+
+ spin_lock_irq(&priv->lock);
+ if ((priv->rx_flags & THROTTLED) && C_CRTSCTS(tty)) {
+ priv->rx_flags &= ~THROTTLED;
+ priv->control_state |= TIOCM_RTS;
+ control_state = priv->control_state;
+ spin_unlock_irq(&priv->lock);
+ mct_u232_set_modem_ctrl(port, control_state);
+ } else {
+ spin_unlock_irq(&priv->lock);
+ }
+}
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/mct_u232.h b/drivers/usb/serial/mct_u232.h
new file mode 100644
index 000000000..e3d09a83c
--- /dev/null
+++ b/drivers/usb/serial/mct_u232.h
@@ -0,0 +1,463 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Definitions for MCT (Magic Control Technology) USB-RS232 Converter Driver
+ *
+ * Copyright (C) 2000 Wolfgang Grandegger (wolfgang@ces.ch)
+ *
+ * This driver is for the device MCT USB-RS232 Converter (25 pin, Model No.
+ * U232-P25) from Magic Control Technology Corp. (there is also a 9 pin
+ * Model No. U232-P9). See http://www.mct.com.tw/products/product_us232.html
+ * for further information. The properties of this device are listed at the end
+ * of this file. This device was used in the Dlink DSB-S25.
+ *
+ * All of the information about the device was acquired by using SniffUSB
+ * on Windows98. The technical details of the reverse engineering are
+ * summarized at the end of this file.
+ */
+
+#ifndef __LINUX_USB_SERIAL_MCT_U232_H
+#define __LINUX_USB_SERIAL_MCT_U232_H
+
+#define MCT_U232_VID 0x0711 /* Vendor Id */
+#define MCT_U232_PID 0x0210 /* Original MCT Product Id */
+
+/* U232-P25, Sitecom */
+#define MCT_U232_SITECOM_PID 0x0230 /* Sitecom Product Id */
+
+/* DU-H3SP USB BAY hub */
+#define MCT_U232_DU_H3SP_PID 0x0200 /* D-Link DU-H3SP USB BAY */
+
+/* Belkin badge the MCT U232-P9 as the F5U109 */
+#define MCT_U232_BELKIN_F5U109_VID 0x050d /* Vendor Id */
+#define MCT_U232_BELKIN_F5U109_PID 0x0109 /* Product Id */
+
+/*
+ * Vendor Request Interface
+ */
+#define MCT_U232_SET_REQUEST_TYPE 0x40
+#define MCT_U232_GET_REQUEST_TYPE 0xc0
+
+/* Get Modem Status Register (MSR) */
+#define MCT_U232_GET_MODEM_STAT_REQUEST 2
+#define MCT_U232_GET_MODEM_STAT_SIZE 1
+
+/* Get Line Control Register (LCR) */
+/* ... not used by this driver */
+#define MCT_U232_GET_LINE_CTRL_REQUEST 6
+#define MCT_U232_GET_LINE_CTRL_SIZE 1
+
+/* Set Baud Rate Divisor */
+#define MCT_U232_SET_BAUD_RATE_REQUEST 5
+#define MCT_U232_SET_BAUD_RATE_SIZE 4
+
+/* Set Line Control Register (LCR) */
+#define MCT_U232_SET_LINE_CTRL_REQUEST 7
+#define MCT_U232_SET_LINE_CTRL_SIZE 1
+
+/* Set Modem Control Register (MCR) */
+#define MCT_U232_SET_MODEM_CTRL_REQUEST 10
+#define MCT_U232_SET_MODEM_CTRL_SIZE 1
+
+/*
+ * This USB device request code is not well understood. It is transmitted by
+ * the MCT-supplied Windows driver whenever the baud rate changes.
+ */
+#define MCT_U232_SET_UNKNOWN1_REQUEST 11 /* Unknown functionality */
+#define MCT_U232_SET_UNKNOWN1_SIZE 1
+
+/*
+ * This USB device request code appears to control whether CTS is required
+ * during transmission.
+ *
+ * Sending a zero byte allows data transmission to a device which is not
+ * asserting CTS. Sending a '1' byte will cause transmission to be deferred
+ * until the device asserts CTS.
+ */
+#define MCT_U232_SET_CTS_REQUEST 12
+#define MCT_U232_SET_CTS_SIZE 1
+
+#define MCT_U232_MAX_SIZE 4 /* of MCT_XXX_SIZE */
+
+/*
+ * Baud rate (divisor)
+ * Actually, there are two of them, MCT website calls them "Philips solution"
+ * and "Intel solution". They are the regular MCT and "Sitecom" for us.
+ * This is pointless to document in the header, see the code for the bits.
+ */
+static int mct_u232_calculate_baud_rate(struct usb_serial *serial,
+ speed_t value, speed_t *result);
+
+/*
+ * Line Control Register (LCR)
+ */
+#define MCT_U232_SET_BREAK 0x40
+
+#define MCT_U232_PARITY_SPACE 0x38
+#define MCT_U232_PARITY_MARK 0x28
+#define MCT_U232_PARITY_EVEN 0x18
+#define MCT_U232_PARITY_ODD 0x08
+#define MCT_U232_PARITY_NONE 0x00
+
+#define MCT_U232_DATA_BITS_5 0x00
+#define MCT_U232_DATA_BITS_6 0x01
+#define MCT_U232_DATA_BITS_7 0x02
+#define MCT_U232_DATA_BITS_8 0x03
+
+#define MCT_U232_STOP_BITS_2 0x04
+#define MCT_U232_STOP_BITS_1 0x00
+
+/*
+ * Modem Control Register (MCR)
+ */
+#define MCT_U232_MCR_NONE 0x8 /* Deactivate DTR and RTS */
+#define MCT_U232_MCR_RTS 0xa /* Activate RTS */
+#define MCT_U232_MCR_DTR 0x9 /* Activate DTR */
+
+/*
+ * Modem Status Register (MSR)
+ */
+#define MCT_U232_MSR_INDEX 0x0 /* data[index] */
+#define MCT_U232_MSR_CD 0x80 /* Current CD */
+#define MCT_U232_MSR_RI 0x40 /* Current RI */
+#define MCT_U232_MSR_DSR 0x20 /* Current DSR */
+#define MCT_U232_MSR_CTS 0x10 /* Current CTS */
+#define MCT_U232_MSR_DCD 0x08 /* Delta CD */
+#define MCT_U232_MSR_DRI 0x04 /* Delta RI */
+#define MCT_U232_MSR_DDSR 0x02 /* Delta DSR */
+#define MCT_U232_MSR_DCTS 0x01 /* Delta CTS */
+
+/*
+ * Line Status Register (LSR)
+ */
+#define MCT_U232_LSR_INDEX 1 /* data[index] */
+#define MCT_U232_LSR_ERR 0x80 /* OE | PE | FE | BI */
+#define MCT_U232_LSR_TEMT 0x40 /* transmit register empty */
+#define MCT_U232_LSR_THRE 0x20 /* transmit holding register empty */
+#define MCT_U232_LSR_BI 0x10 /* break indicator */
+#define MCT_U232_LSR_FE 0x08 /* framing error */
+#define MCT_U232_LSR_OE 0x02 /* overrun error */
+#define MCT_U232_LSR_PE 0x04 /* parity error */
+#define MCT_U232_LSR_OE 0x02 /* overrun error */
+#define MCT_U232_LSR_DR 0x01 /* receive data ready */
+
+
+/* -----------------------------------------------------------------------------
+ * Technical Specification reverse engineered with SniffUSB on Windows98
+ * =====================================================================
+ *
+ * The technical details of the device have been acquired be using "SniffUSB"
+ * and the vendor-supplied device driver (version 2.3A) under Windows98. To
+ * identify the USB vendor-specific requests and to assign them to terminal
+ * settings (flow control, baud rate, etc.) the program "SerialSettings" from
+ * William G. Greathouse has been proven to be very useful. I also used the
+ * Win98 "HyperTerminal" and "usb-robot" on Linux for testing. The results and
+ * observations are summarized below:
+ *
+ * The USB requests seem to be directly mapped to the registers of a 8250,
+ * 16450 or 16550 UART. The FreeBSD handbook (appendix F.4 "Input/Output
+ * devices") contains a comprehensive description of UARTs and its registers.
+ * The bit descriptions are actually taken from there.
+ *
+ *
+ * Baud rate (divisor)
+ * -------------------
+ *
+ * BmRequestType: 0x40 (0100 0000B)
+ * bRequest: 0x05
+ * wValue: 0x0000
+ * wIndex: 0x0000
+ * wLength: 0x0004
+ * Data: divisor = 115200 / baud_rate
+ *
+ * SniffUSB observations (Nov 2003): Contrary to the 'wLength' value of 4
+ * shown above, observations with a Belkin F5U109 adapter, using the
+ * MCT-supplied Windows98 driver (U2SPORT.VXD, "File version: 1.21P.0104 for
+ * Win98/Me"), show this request has a length of 1 byte, presumably because
+ * of the fact that the Belkin adapter and the 'Sitecom U232-P25' adapter
+ * use a baud-rate code instead of a conventional RS-232 baud rate divisor.
+ * The current source code for this driver does not reflect this fact, but
+ * the driver works fine with this adapter/driver combination nonetheless.
+ *
+ *
+ * Line Control Register (LCR)
+ * ---------------------------
+ *
+ * BmRequestType: 0x40 (0100 0000B) 0xc0 (1100 0000B)
+ * bRequest: 0x07 0x06
+ * wValue: 0x0000
+ * wIndex: 0x0000
+ * wLength: 0x0001
+ * Data: LCR (see below)
+ *
+ * Bit 7: Divisor Latch Access Bit (DLAB). When set, access to the data
+ * transmit/receive register (THR/RBR) and the Interrupt Enable Register
+ * (IER) is disabled. Any access to these ports is now redirected to the
+ * Divisor Latch Registers. Setting this bit, loading the Divisor
+ * Registers, and clearing DLAB should be done with interrupts disabled.
+ * Bit 6: Set Break. When set to "1", the transmitter begins to transmit
+ * continuous Spacing until this bit is set to "0". This overrides any
+ * bits of characters that are being transmitted.
+ * Bit 5: Stick Parity. When parity is enabled, setting this bit causes parity
+ * to always be "1" or "0", based on the value of Bit 4.
+ * Bit 4: Even Parity Select (EPS). When parity is enabled and Bit 5 is "0",
+ * setting this bit causes even parity to be transmitted and expected.
+ * Otherwise, odd parity is used.
+ * Bit 3: Parity Enable (PEN). When set to "1", a parity bit is inserted
+ * between the last bit of the data and the Stop Bit. The UART will also
+ * expect parity to be present in the received data.
+ * Bit 2: Number of Stop Bits (STB). If set to "1" and using 5-bit data words,
+ * 1.5 Stop Bits are transmitted and expected in each data word. For
+ * 6, 7 and 8-bit data words, 2 Stop Bits are transmitted and expected.
+ * When this bit is set to "0", one Stop Bit is used on each data word.
+ * Bit 1: Word Length Select Bit #1 (WLSB1)
+ * Bit 0: Word Length Select Bit #0 (WLSB0)
+ * Together these bits specify the number of bits in each data word.
+ * 1 0 Word Length
+ * 0 0 5 Data Bits
+ * 0 1 6 Data Bits
+ * 1 0 7 Data Bits
+ * 1 1 8 Data Bits
+ *
+ * SniffUSB observations: Bit 7 seems not to be used. There seem to be two bugs
+ * in the Win98 driver: the break does not work (bit 6 is not asserted) and the
+ * stick parity bit is not cleared when set once. The LCR can also be read
+ * back with USB request 6 but this has never been observed with SniffUSB.
+ *
+ *
+ * Modem Control Register (MCR)
+ * ----------------------------
+ *
+ * BmRequestType: 0x40 (0100 0000B)
+ * bRequest: 0x0a
+ * wValue: 0x0000
+ * wIndex: 0x0000
+ * wLength: 0x0001
+ * Data: MCR (Bit 4..7, see below)
+ *
+ * Bit 7: Reserved, always 0.
+ * Bit 6: Reserved, always 0.
+ * Bit 5: Reserved, always 0.
+ * Bit 4: Loop-Back Enable. When set to "1", the UART transmitter and receiver
+ * are internally connected together to allow diagnostic operations. In
+ * addition, the UART modem control outputs are connected to the UART
+ * modem control inputs. CTS is connected to RTS, DTR is connected to
+ * DSR, OUT1 is connected to RI, and OUT 2 is connected to DCD.
+ * Bit 3: OUT 2. An auxiliary output that the host processor may set high or
+ * low. In the IBM PC serial adapter (and most clones), OUT 2 is used
+ * to tri-state (disable) the interrupt signal from the
+ * 8250/16450/16550 UART.
+ * Bit 2: OUT 1. An auxiliary output that the host processor may set high or
+ * low. This output is not used on the IBM PC serial adapter.
+ * Bit 1: Request to Send (RTS). When set to "1", the output of the UART -RTS
+ * line is Low (Active).
+ * Bit 0: Data Terminal Ready (DTR). When set to "1", the output of the UART
+ * -DTR line is Low (Active).
+ *
+ * SniffUSB observations: Bit 2 and 4 seem not to be used but bit 3 has been
+ * seen _always_ set.
+ *
+ *
+ * Modem Status Register (MSR)
+ * ---------------------------
+ *
+ * BmRequestType: 0xc0 (1100 0000B)
+ * bRequest: 0x02
+ * wValue: 0x0000
+ * wIndex: 0x0000
+ * wLength: 0x0001
+ * Data: MSR (see below)
+ *
+ * Bit 7: Data Carrier Detect (CD). Reflects the state of the DCD line on the
+ * UART.
+ * Bit 6: Ring Indicator (RI). Reflects the state of the RI line on the UART.
+ * Bit 5: Data Set Ready (DSR). Reflects the state of the DSR line on the UART.
+ * Bit 4: Clear To Send (CTS). Reflects the state of the CTS line on the UART.
+ * Bit 3: Delta Data Carrier Detect (DDCD). Set to "1" if the -DCD line has
+ * changed state one more more times since the last time the MSR was
+ * read by the host.
+ * Bit 2: Trailing Edge Ring Indicator (TERI). Set to "1" if the -RI line has
+ * had a low to high transition since the last time the MSR was read by
+ * the host.
+ * Bit 1: Delta Data Set Ready (DDSR). Set to "1" if the -DSR line has changed
+ * state one more more times since the last time the MSR was read by the
+ * host.
+ * Bit 0: Delta Clear To Send (DCTS). Set to "1" if the -CTS line has changed
+ * state one more times since the last time the MSR was read by the
+ * host.
+ *
+ * SniffUSB observations: the MSR is also returned as first byte on the
+ * interrupt-in endpoint 0x83 to signal changes of modem status lines. The USB
+ * request to read MSR cannot be applied during normal device operation.
+ *
+ *
+ * Line Status Register (LSR)
+ * --------------------------
+ *
+ * Bit 7 Error in Receiver FIFO. On the 8250/16450 UART, this bit is zero.
+ * This bit is set to "1" when any of the bytes in the FIFO have one
+ * or more of the following error conditions: PE, FE, or BI.
+ * Bit 6 Transmitter Empty (TEMT). When set to "1", there are no words
+ * remaining in the transmit FIFO or the transmit shift register. The
+ * transmitter is completely idle.
+ * Bit 5 Transmitter Holding Register Empty (THRE). When set to "1", the
+ * FIFO (or holding register) now has room for at least one additional
+ * word to transmit. The transmitter may still be transmitting when
+ * this bit is set to "1".
+ * Bit 4 Break Interrupt (BI). The receiver has detected a Break signal.
+ * Bit 3 Framing Error (FE). A Start Bit was detected but the Stop Bit did
+ * not appear at the expected time. The received word is probably
+ * garbled.
+ * Bit 2 Parity Error (PE). The parity bit was incorrect for the word
+ * received.
+ * Bit 1 Overrun Error (OE). A new word was received and there was no room
+ * in the receive buffer. The newly-arrived word in the shift register
+ * is discarded. On 8250/16450 UARTs, the word in the holding register
+ * is discarded and the newly- arrived word is put in the holding
+ * register.
+ * Bit 0 Data Ready (DR). One or more words are in the receive FIFO that the
+ * host may read. A word must be completely received and moved from
+ * the shift register into the FIFO (or holding register for
+ * 8250/16450 designs) before this bit is set.
+ *
+ * SniffUSB observations: the LSR is returned as second byte on the
+ * interrupt-in endpoint 0x83 to signal error conditions. Such errors have
+ * been seen with minicom/zmodem transfers (CRC errors).
+ *
+ *
+ * Unknown #1
+ * -------------------
+ *
+ * BmRequestType: 0x40 (0100 0000B)
+ * bRequest: 0x0b
+ * wValue: 0x0000
+ * wIndex: 0x0000
+ * wLength: 0x0001
+ * Data: 0x00
+ *
+ * SniffUSB observations (Nov 2003): With the MCT-supplied Windows98 driver
+ * (U2SPORT.VXD, "File version: 1.21P.0104 for Win98/Me"), this request
+ * occurs immediately after a "Baud rate (divisor)" message. It was not
+ * observed at any other time. It is unclear what purpose this message
+ * serves.
+ *
+ *
+ * Unknown #2
+ * -------------------
+ *
+ * BmRequestType: 0x40 (0100 0000B)
+ * bRequest: 0x0c
+ * wValue: 0x0000
+ * wIndex: 0x0000
+ * wLength: 0x0001
+ * Data: 0x00
+ *
+ * SniffUSB observations (Nov 2003): With the MCT-supplied Windows98 driver
+ * (U2SPORT.VXD, "File version: 1.21P.0104 for Win98/Me"), this request
+ * occurs immediately after the 'Unknown #1' message (see above). It was
+ * not observed at any other time. It is unclear what other purpose (if
+ * any) this message might serve, but without it, the USB/RS-232 adapter
+ * will not write to RS-232 devices which do not assert the 'CTS' signal.
+ *
+ *
+ * Flow control
+ * ------------
+ *
+ * SniffUSB observations: no flow control specific requests have been realized
+ * apart from DTR/RTS settings. Both signals are dropped for no flow control
+ * but asserted for hardware or software flow control.
+ *
+ *
+ * Endpoint usage
+ * --------------
+ *
+ * SniffUSB observations: the bulk-out endpoint 0x1 and interrupt-in endpoint
+ * 0x81 is used to transmit and receive characters. The second interrupt-in
+ * endpoint 0x83 signals exceptional conditions like modem line changes and
+ * errors. The first byte returned is the MSR and the second byte the LSR.
+ *
+ *
+ * Other observations
+ * ------------------
+ *
+ * Queued bulk transfers like used in visor.c did not work.
+ *
+ *
+ * Properties of the USB device used (as found in /var/log/messages)
+ * -----------------------------------------------------------------
+ *
+ * Manufacturer: MCT Corporation.
+ * Product: USB-232 Interfact Controller
+ * SerialNumber: U2S22050
+ *
+ * Length = 18
+ * DescriptorType = 01
+ * USB version = 1.00
+ * Vendor:Product = 0711:0210
+ * MaxPacketSize0 = 8
+ * NumConfigurations = 1
+ * Device version = 1.02
+ * Device Class:SubClass:Protocol = 00:00:00
+ * Per-interface classes
+ * Configuration:
+ * bLength = 9
+ * bDescriptorType = 02
+ * wTotalLength = 0027
+ * bNumInterfaces = 01
+ * bConfigurationValue = 01
+ * iConfiguration = 00
+ * bmAttributes = c0
+ * MaxPower = 100mA
+ *
+ * Interface: 0
+ * Alternate Setting: 0
+ * bLength = 9
+ * bDescriptorType = 04
+ * bInterfaceNumber = 00
+ * bAlternateSetting = 00
+ * bNumEndpoints = 03
+ * bInterface Class:SubClass:Protocol = 00:00:00
+ * iInterface = 00
+ * Endpoint:
+ * bLength = 7
+ * bDescriptorType = 05
+ * bEndpointAddress = 81 (in)
+ * bmAttributes = 03 (Interrupt)
+ * wMaxPacketSize = 0040
+ * bInterval = 02
+ * Endpoint:
+ * bLength = 7
+ * bDescriptorType = 05
+ * bEndpointAddress = 01 (out)
+ * bmAttributes = 02 (Bulk)
+ * wMaxPacketSize = 0040
+ * bInterval = 00
+ * Endpoint:
+ * bLength = 7
+ * bDescriptorType = 05
+ * bEndpointAddress = 83 (in)
+ * bmAttributes = 03 (Interrupt)
+ * wMaxPacketSize = 0002
+ * bInterval = 02
+ *
+ *
+ * Hardware details (added by Martin Hamilton, 2001/12/06)
+ * -----------------------------------------------------------------
+ *
+ * This info was gleaned from opening a Belkin F5U109 DB9 USB serial
+ * adaptor, which turns out to simply be a re-badged U232-P9. We
+ * know this because there is a sticky label on the circuit board
+ * which says "U232-P9" ;-)
+ *
+ * The circuit board inside the adaptor contains a Philips PDIUSBD12
+ * USB endpoint chip and a Philips P87C52UBAA microcontroller with
+ * embedded UART. Exhaustive documentation for these is available at:
+ *
+ * http://www.semiconductors.philips.com/pip/p87c52ubaa
+ * http://www.nxp.com/acrobat_download/various/PDIUSBD12_PROGRAMMING_GUIDE.pdf
+ *
+ * Thanks to Julian Highfield for the pointer to the Philips database.
+ *
+ */
+
+#endif /* __LINUX_USB_SERIAL_MCT_U232_H */
+
diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c
new file mode 100644
index 000000000..35473544f
--- /dev/null
+++ b/drivers/usb/serial/metro-usb.c
@@ -0,0 +1,368 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ Some of this code is credited to Linux USB open source files that are
+ distributed with Linux.
+
+ Copyright: 2007 Metrologic Instruments. All rights reserved.
+ Copyright: 2011 Azimut Ltd. <http://azimutrzn.ru/>
+*/
+
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty_flip.h>
+#include <linux/spinlock.h>
+#include <linux/usb/serial.h>
+
+#define DRIVER_DESC "Metrologic Instruments Inc. - USB-POS driver"
+
+/* Product information. */
+#define FOCUS_VENDOR_ID 0x0C2E
+#define FOCUS_PRODUCT_ID_BI 0x0720
+#define FOCUS_PRODUCT_ID_UNI 0x0700
+
+#define METROUSB_SET_REQUEST_TYPE 0x40
+#define METROUSB_SET_MODEM_CTRL_REQUEST 10
+#define METROUSB_SET_BREAK_REQUEST 0x40
+#define METROUSB_MCR_NONE 0x08 /* Deactivate DTR and RTS. */
+#define METROUSB_MCR_RTS 0x0a /* Activate RTS. */
+#define METROUSB_MCR_DTR 0x09 /* Activate DTR. */
+#define WDR_TIMEOUT 5000 /* default urb timeout. */
+
+/* Private data structure. */
+struct metrousb_private {
+ spinlock_t lock;
+ int throttled;
+ unsigned long control_state;
+};
+
+/* Device table list. */
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(FOCUS_VENDOR_ID, FOCUS_PRODUCT_ID_BI) },
+ { USB_DEVICE(FOCUS_VENDOR_ID, FOCUS_PRODUCT_ID_UNI) },
+ { USB_DEVICE_INTERFACE_CLASS(0x0c2e, 0x0730, 0xff) }, /* MS7820 */
+ { }, /* Terminating entry. */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+/* UNI-Directional mode commands for device configure */
+#define UNI_CMD_OPEN 0x80
+#define UNI_CMD_CLOSE 0xFF
+
+static int metrousb_is_unidirectional_mode(struct usb_serial *serial)
+{
+ u16 product_id = le16_to_cpu(serial->dev->descriptor.idProduct);
+
+ return product_id == FOCUS_PRODUCT_ID_UNI;
+}
+
+static int metrousb_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ if (metrousb_is_unidirectional_mode(serial)) {
+ if (epds->num_interrupt_out == 0) {
+ dev_err(&serial->interface->dev, "interrupt-out endpoint missing\n");
+ return -ENODEV;
+ }
+ }
+
+ return 1;
+}
+
+static int metrousb_send_unidirectional_cmd(u8 cmd, struct usb_serial_port *port)
+{
+ int ret;
+ int actual_len;
+ u8 *buffer_cmd = NULL;
+
+ if (!metrousb_is_unidirectional_mode(port->serial))
+ return 0;
+
+ buffer_cmd = kzalloc(sizeof(cmd), GFP_KERNEL);
+ if (!buffer_cmd)
+ return -ENOMEM;
+
+ *buffer_cmd = cmd;
+
+ ret = usb_interrupt_msg(port->serial->dev,
+ usb_sndintpipe(port->serial->dev, port->interrupt_out_endpointAddress),
+ buffer_cmd, sizeof(cmd),
+ &actual_len, USB_CTRL_SET_TIMEOUT);
+
+ kfree(buffer_cmd);
+
+ if (ret < 0)
+ return ret;
+ else if (actual_len != sizeof(cmd))
+ return -EIO;
+ return 0;
+}
+
+static void metrousb_read_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct metrousb_private *metro_priv = usb_get_serial_port_data(port);
+ unsigned char *data = urb->transfer_buffer;
+ unsigned long flags;
+ int throttled = 0;
+ int result = 0;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ switch (urb->status) {
+ case 0:
+ /* Success status, read from the port. */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* urb has been terminated. */
+ dev_dbg(&port->dev,
+ "%s - urb shutting down, error code=%d\n",
+ __func__, urb->status);
+ return;
+ default:
+ dev_dbg(&port->dev,
+ "%s - non-zero urb received, error code=%d\n",
+ __func__, urb->status);
+ goto exit;
+ }
+
+
+ /* Set the data read from the usb port into the serial port buffer. */
+ if (urb->actual_length) {
+ /* Loop through the data copying each byte to the tty layer. */
+ tty_insert_flip_string(&port->port, data, urb->actual_length);
+
+ /* Force the data to the tty layer. */
+ tty_flip_buffer_push(&port->port);
+ }
+
+ /* Set any port variables. */
+ spin_lock_irqsave(&metro_priv->lock, flags);
+ throttled = metro_priv->throttled;
+ spin_unlock_irqrestore(&metro_priv->lock, flags);
+
+ if (throttled)
+ return;
+exit:
+ /* Try to resubmit the urb. */
+ result = usb_submit_urb(urb, GFP_ATOMIC);
+ if (result)
+ dev_err(&port->dev,
+ "%s - failed submitting interrupt in urb, error code=%d\n",
+ __func__, result);
+}
+
+static void metrousb_cleanup(struct usb_serial_port *port)
+{
+ usb_kill_urb(port->interrupt_in_urb);
+
+ metrousb_send_unidirectional_cmd(UNI_CMD_CLOSE, port);
+}
+
+static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct metrousb_private *metro_priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ int result = 0;
+
+ /* Set the private data information for the port. */
+ spin_lock_irqsave(&metro_priv->lock, flags);
+ metro_priv->control_state = 0;
+ metro_priv->throttled = 0;
+ spin_unlock_irqrestore(&metro_priv->lock, flags);
+
+ /* Clear the urb pipe. */
+ usb_clear_halt(serial->dev, port->interrupt_in_urb->pipe);
+
+ /* Start reading from the device */
+ usb_fill_int_urb(port->interrupt_in_urb, serial->dev,
+ usb_rcvintpipe(serial->dev, port->interrupt_in_endpointAddress),
+ port->interrupt_in_urb->transfer_buffer,
+ port->interrupt_in_urb->transfer_buffer_length,
+ metrousb_read_int_callback, port, 1);
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+
+ if (result) {
+ dev_err(&port->dev,
+ "%s - failed submitting interrupt in urb, error code=%d\n",
+ __func__, result);
+ return result;
+ }
+
+ /* Send activate cmd to device */
+ result = metrousb_send_unidirectional_cmd(UNI_CMD_OPEN, port);
+ if (result) {
+ dev_err(&port->dev,
+ "%s - failed to configure device, error code=%d\n",
+ __func__, result);
+ goto err_kill_urb;
+ }
+
+ return 0;
+
+err_kill_urb:
+ usb_kill_urb(port->interrupt_in_urb);
+
+ return result;
+}
+
+static int metrousb_set_modem_ctrl(struct usb_serial *serial, unsigned int control_state)
+{
+ int retval = 0;
+ unsigned char mcr = METROUSB_MCR_NONE;
+
+ dev_dbg(&serial->dev->dev, "%s - control state = %d\n",
+ __func__, control_state);
+
+ /* Set the modem control value. */
+ if (control_state & TIOCM_DTR)
+ mcr |= METROUSB_MCR_DTR;
+ if (control_state & TIOCM_RTS)
+ mcr |= METROUSB_MCR_RTS;
+
+ /* Send the command to the usb port. */
+ retval = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ METROUSB_SET_REQUEST_TYPE, METROUSB_SET_MODEM_CTRL_REQUEST,
+ control_state, 0, NULL, 0, WDR_TIMEOUT);
+ if (retval < 0)
+ dev_err(&serial->dev->dev,
+ "%s - set modem ctrl=0x%x failed, error code=%d\n",
+ __func__, mcr, retval);
+
+ return retval;
+}
+
+static int metrousb_port_probe(struct usb_serial_port *port)
+{
+ struct metrousb_private *metro_priv;
+
+ metro_priv = kzalloc_obj(*metro_priv);
+ if (!metro_priv)
+ return -ENOMEM;
+
+ spin_lock_init(&metro_priv->lock);
+
+ usb_set_serial_port_data(port, metro_priv);
+
+ return 0;
+}
+
+static void metrousb_port_remove(struct usb_serial_port *port)
+{
+ struct metrousb_private *metro_priv;
+
+ metro_priv = usb_get_serial_port_data(port);
+ kfree(metro_priv);
+}
+
+static void metrousb_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct metrousb_private *metro_priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ /* Set the private information for the port to stop reading data. */
+ spin_lock_irqsave(&metro_priv->lock, flags);
+ metro_priv->throttled = 1;
+ spin_unlock_irqrestore(&metro_priv->lock, flags);
+}
+
+static int metrousb_tiocmget(struct tty_struct *tty)
+{
+ unsigned long control_state = 0;
+ struct usb_serial_port *port = tty->driver_data;
+ struct metrousb_private *metro_priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&metro_priv->lock, flags);
+ control_state = metro_priv->control_state;
+ spin_unlock_irqrestore(&metro_priv->lock, flags);
+
+ return control_state;
+}
+
+static int metrousb_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_serial *serial = port->serial;
+ struct metrousb_private *metro_priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ unsigned long control_state = 0;
+
+ dev_dbg(&port->dev, "%s - set=%d, clear=%d\n", __func__, set, clear);
+
+ spin_lock_irqsave(&metro_priv->lock, flags);
+ control_state = metro_priv->control_state;
+
+ /* Set the RTS and DTR values. */
+ if (set & TIOCM_RTS)
+ control_state |= TIOCM_RTS;
+ if (set & TIOCM_DTR)
+ control_state |= TIOCM_DTR;
+ if (clear & TIOCM_RTS)
+ control_state &= ~TIOCM_RTS;
+ if (clear & TIOCM_DTR)
+ control_state &= ~TIOCM_DTR;
+
+ metro_priv->control_state = control_state;
+ spin_unlock_irqrestore(&metro_priv->lock, flags);
+ return metrousb_set_modem_ctrl(serial, control_state);
+}
+
+static void metrousb_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct metrousb_private *metro_priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ int result = 0;
+
+ /* Set the private information for the port to resume reading data. */
+ spin_lock_irqsave(&metro_priv->lock, flags);
+ metro_priv->throttled = 0;
+ spin_unlock_irqrestore(&metro_priv->lock, flags);
+
+ /* Submit the urb to read from the port. */
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
+ if (result)
+ dev_err(&port->dev,
+ "failed submitting interrupt in urb error code=%d\n",
+ result);
+}
+
+static struct usb_serial_driver metrousb_device = {
+ .driver = {
+ .name = "metro-usb",
+ },
+ .description = "Metrologic USB to Serial",
+ .id_table = id_table,
+ .num_interrupt_in = 1,
+ .calc_num_ports = metrousb_calc_num_ports,
+ .open = metrousb_open,
+ .close = metrousb_cleanup,
+ .read_int_callback = metrousb_read_int_callback,
+ .port_probe = metrousb_port_probe,
+ .port_remove = metrousb_port_remove,
+ .throttle = metrousb_throttle,
+ .unthrottle = metrousb_unthrottle,
+ .tiocmget = metrousb_tiocmget,
+ .tiocmset = metrousb_tiocmset,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &metrousb_device,
+ NULL,
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Philip Nicastro");
+MODULE_AUTHOR("Aleksey Babahin <tamerlan311@gmail.com>");
+MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c
new file mode 100644
index 000000000..104f43a8f
--- /dev/null
+++ b/drivers/usb/serial/mos7720.c
@@ -0,0 +1,1764 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mos7720.c
+ * Controls the Moschip 7720 usb to dual port serial converter
+ *
+ * Copyright 2006 Moschip Semiconductor Tech. Ltd.
+ *
+ * Developed by:
+ * Vijaya Kumar <vijaykumar.gn@gmail.com>
+ * Ajay Kumar <naanuajay@yahoo.com>
+ * Gurudeva <ngurudeva@yahoo.com>
+ *
+ * Cleaned up from the original by:
+ * Greg Kroah-Hartman <gregkh@suse.de>
+ *
+ * Originally based on drivers/usb/serial/io_edgeport.c which is:
+ * Copyright (C) 2000 Inside Out Networks, All rights reserved.
+ * Copyright (C) 2001-2002 Greg Kroah-Hartman <greg@kroah.com>
+ */
+
+#include <linux/atomic.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/uaccess.h>
+#include <linux/parport.h>
+
+#define DRIVER_AUTHOR "Aspire Communications pvt Ltd."
+#define DRIVER_DESC "Moschip USB Serial Driver"
+
+/* default urb timeout */
+#define MOS_WDR_TIMEOUT 5000
+
+#define MOS_MAX_PORT 0x02
+#define MOS_WRITE 0x0E
+#define MOS_READ 0x0D
+
+/* Interrupt Routines Defines */
+#define SERIAL_IIR_RLS 0x06
+#define SERIAL_IIR_RDA 0x04
+#define SERIAL_IIR_CTI 0x0c
+#define SERIAL_IIR_THR 0x02
+#define SERIAL_IIR_MS 0x00
+
+#define NUM_URBS 16 /* URB Count */
+#define URB_TRANSFER_BUFFER_SIZE 32 /* URB Size */
+
+/* This structure holds all of the local serial port information */
+struct moschip_port {
+ __u8 shadowLCR; /* last LCR value received */
+ __u8 shadowMCR; /* last MCR value received */
+ __u8 shadowMSR; /* last MSR value received */
+ char open;
+ struct usb_serial_port *port; /* loop back to the owner */
+ struct urb *write_urb_pool[NUM_URBS];
+};
+
+#define USB_VENDOR_ID_MOSCHIP 0x9710
+#define MOSCHIP_DEVICE_ID_7720 0x7720
+#define MOSCHIP_DEVICE_ID_7715 0x7715
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7720) },
+ { USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7715) },
+ { } /* terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT
+
+/* initial values for parport regs */
+#define DCR_INIT_VAL 0x0c /* SLCTIN, nINIT */
+#define ECR_INIT_VAL 0x00 /* SPP mode */
+
+enum mos7715_pp_modes {
+ SPP = 0<<5,
+ PS2 = 1<<5, /* moschip calls this 'NIBBLE' mode */
+ PPF = 2<<5, /* moschip calls this 'CB-FIFO mode */
+};
+
+struct mos7715_parport {
+ struct parport *pp; /* back to containing struct */
+ struct kref ref_count; /* to instance of this struct */
+ bool msg_pending; /* usb sync call pending */
+ struct completion syncmsg_compl; /* usb sync call completed */
+ struct work_struct work; /* restore deferred writes */
+ struct usb_serial *serial; /* back to containing struct */
+ __u8 shadowECR; /* parallel port regs... */
+ __u8 shadowDCR;
+ atomic_t shadowDSR; /* updated in int-in callback */
+};
+
+/* lock guards against dereferencing NULL ptr in parport ops callbacks */
+static DEFINE_SPINLOCK(release_lock);
+
+#endif /* CONFIG_USB_SERIAL_MOS7715_PARPORT */
+
+static const unsigned int dummy; /* for clarity in register access fns */
+
+enum mos_regs {
+ MOS7720_THR, /* serial port regs */
+ MOS7720_RHR,
+ MOS7720_IER,
+ MOS7720_FCR,
+ MOS7720_ISR,
+ MOS7720_LCR,
+ MOS7720_MCR,
+ MOS7720_LSR,
+ MOS7720_MSR,
+ MOS7720_SPR,
+ MOS7720_DLL,
+ MOS7720_DLM,
+ MOS7720_DPR, /* parallel port regs */
+ MOS7720_DSR,
+ MOS7720_DCR,
+ MOS7720_ECR,
+ MOS7720_SP1_REG, /* device control regs */
+ MOS7720_SP2_REG, /* serial port 2 (7720 only) */
+ MOS7720_PP_REG,
+ MOS7720_SP_CONTROL_REG,
+};
+
+/*
+ * Return the correct value for the Windex field of the setup packet
+ * for a control endpoint message. See the 7715 datasheet.
+ */
+static inline __u16 get_reg_index(enum mos_regs reg)
+{
+ static const __u16 mos7715_index_lookup_table[] = {
+ 0x00, /* MOS7720_THR */
+ 0x00, /* MOS7720_RHR */
+ 0x01, /* MOS7720_IER */
+ 0x02, /* MOS7720_FCR */
+ 0x02, /* MOS7720_ISR */
+ 0x03, /* MOS7720_LCR */
+ 0x04, /* MOS7720_MCR */
+ 0x05, /* MOS7720_LSR */
+ 0x06, /* MOS7720_MSR */
+ 0x07, /* MOS7720_SPR */
+ 0x00, /* MOS7720_DLL */
+ 0x01, /* MOS7720_DLM */
+ 0x00, /* MOS7720_DPR */
+ 0x01, /* MOS7720_DSR */
+ 0x02, /* MOS7720_DCR */
+ 0x0a, /* MOS7720_ECR */
+ 0x01, /* MOS7720_SP1_REG */
+ 0x02, /* MOS7720_SP2_REG (7720 only) */
+ 0x04, /* MOS7720_PP_REG (7715 only) */
+ 0x08, /* MOS7720_SP_CONTROL_REG */
+ };
+ return mos7715_index_lookup_table[reg];
+}
+
+/*
+ * Return the correct value for the upper byte of the Wvalue field of
+ * the setup packet for a control endpoint message.
+ */
+static inline __u16 get_reg_value(enum mos_regs reg,
+ unsigned int serial_portnum)
+{
+ if (reg >= MOS7720_SP1_REG) /* control reg */
+ return 0x0000;
+
+ else if (reg >= MOS7720_DPR) /* parallel port reg (7715 only) */
+ return 0x0100;
+
+ else /* serial port reg */
+ return (serial_portnum + 2) << 8;
+}
+
+/*
+ * Write data byte to the specified device register. The data is embedded in
+ * the value field of the setup packet. serial_portnum is ignored for registers
+ * not specific to a particular serial port.
+ */
+static int write_mos_reg(struct usb_serial *serial, unsigned int serial_portnum,
+ enum mos_regs reg, __u8 data)
+{
+ struct usb_device *usbdev = serial->dev;
+ unsigned int pipe = usb_sndctrlpipe(usbdev, 0);
+ __u8 request = (__u8)0x0e;
+ __u8 requesttype = (__u8)0x40;
+ __u16 index = get_reg_index(reg);
+ __u16 value = get_reg_value(reg, serial_portnum) + data;
+ int status = usb_control_msg(usbdev, pipe, request, requesttype, value,
+ index, NULL, 0, MOS_WDR_TIMEOUT);
+ if (status < 0)
+ dev_err(&usbdev->dev,
+ "mos7720: usb_control_msg() failed: %d\n", status);
+ return status;
+}
+
+/*
+ * Read data byte from the specified device register. The data returned by the
+ * device is embedded in the value field of the setup packet. serial_portnum is
+ * ignored for registers that are not specific to a particular serial port.
+ */
+static int read_mos_reg(struct usb_serial *serial, unsigned int serial_portnum,
+ enum mos_regs reg, __u8 *data)
+{
+ struct usb_device *usbdev = serial->dev;
+ unsigned int pipe = usb_rcvctrlpipe(usbdev, 0);
+ __u8 request = (__u8)0x0d;
+ __u8 requesttype = (__u8)0xc0;
+ __u16 index = get_reg_index(reg);
+ __u16 value = get_reg_value(reg, serial_portnum);
+ u8 *buf;
+ int status;
+
+ buf = kmalloc(1, GFP_KERNEL);
+ if (!buf) {
+ *data = 0;
+ return -ENOMEM;
+ }
+
+ status = usb_control_msg(usbdev, pipe, request, requesttype, value,
+ index, buf, 1, MOS_WDR_TIMEOUT);
+ if (status == 1) {
+ *data = *buf;
+ } else {
+ dev_err(&usbdev->dev,
+ "mos7720: usb_control_msg() failed: %d\n", status);
+ if (status >= 0)
+ status = -EIO;
+ *data = 0;
+ }
+
+ kfree(buf);
+
+ return status;
+}
+
+#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT
+
+static inline int mos7715_change_mode(struct mos7715_parport *mos_parport,
+ enum mos7715_pp_modes mode)
+{
+ mos_parport->shadowECR = mode;
+ write_mos_reg(mos_parport->serial, dummy, MOS7720_ECR,
+ mos_parport->shadowECR);
+ return 0;
+}
+
+static void destroy_mos_parport(struct kref *kref)
+{
+ struct mos7715_parport *mos_parport =
+ container_of(kref, struct mos7715_parport, ref_count);
+
+ kfree(mos_parport);
+}
+
+/*
+ * This is the common top part of all parallel port callback operations that
+ * send synchronous messages to the device. This implements convoluted locking
+ * that avoids two scenarios: (1) a port operation is called after usbserial
+ * has called our release function, at which point struct mos7715_parport has
+ * been destroyed, and (2) the device has been disconnected, but usbserial has
+ * not called the release function yet because someone has a serial port open.
+ * The shared release_lock prevents the first, and the mutex and disconnected
+ * flag maintained by usbserial covers the second. We also use the msg_pending
+ * flag to ensure that all synchronous usb message calls have completed before
+ * our release function can return.
+ */
+static int parport_prologue(struct parport *pp)
+{
+ struct mos7715_parport *mos_parport;
+
+ spin_lock(&release_lock);
+ mos_parport = pp->private_data;
+ if (unlikely(mos_parport == NULL)) {
+ /* release fn called, port struct destroyed */
+ spin_unlock(&release_lock);
+ return -1;
+ }
+ mos_parport->msg_pending = true; /* synch usb call pending */
+ reinit_completion(&mos_parport->syncmsg_compl);
+ spin_unlock(&release_lock);
+
+ /* ensure writes from restore are submitted before new requests */
+ if (work_pending(&mos_parport->work))
+ flush_work(&mos_parport->work);
+
+ mutex_lock(&mos_parport->serial->disc_mutex);
+ if (mos_parport->serial->disconnected) {
+ /* device disconnected */
+ mutex_unlock(&mos_parport->serial->disc_mutex);
+ mos_parport->msg_pending = false;
+ complete(&mos_parport->syncmsg_compl);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * This is the common bottom part of all parallel port functions that send
+ * synchronous messages to the device.
+ */
+static inline void parport_epilogue(struct parport *pp)
+{
+ struct mos7715_parport *mos_parport = pp->private_data;
+ mutex_unlock(&mos_parport->serial->disc_mutex);
+ mos_parport->msg_pending = false;
+ complete(&mos_parport->syncmsg_compl);
+}
+
+static void deferred_restore_writes(struct work_struct *work)
+{
+ struct mos7715_parport *mos_parport;
+
+ mos_parport = container_of(work, struct mos7715_parport, work);
+
+ mutex_lock(&mos_parport->serial->disc_mutex);
+
+ /* if device disconnected, game over */
+ if (mos_parport->serial->disconnected)
+ goto done;
+
+ write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR,
+ mos_parport->shadowDCR);
+ write_mos_reg(mos_parport->serial, dummy, MOS7720_ECR,
+ mos_parport->shadowECR);
+done:
+ mutex_unlock(&mos_parport->serial->disc_mutex);
+}
+
+static void parport_mos7715_write_data(struct parport *pp, unsigned char d)
+{
+ struct mos7715_parport *mos_parport = pp->private_data;
+
+ if (parport_prologue(pp) < 0)
+ return;
+ mos7715_change_mode(mos_parport, SPP);
+ write_mos_reg(mos_parport->serial, dummy, MOS7720_DPR, (__u8)d);
+ parport_epilogue(pp);
+}
+
+static unsigned char parport_mos7715_read_data(struct parport *pp)
+{
+ struct mos7715_parport *mos_parport = pp->private_data;
+ unsigned char d;
+
+ if (parport_prologue(pp) < 0)
+ return 0;
+ read_mos_reg(mos_parport->serial, dummy, MOS7720_DPR, &d);
+ parport_epilogue(pp);
+ return d;
+}
+
+static void parport_mos7715_write_control(struct parport *pp, unsigned char d)
+{
+ struct mos7715_parport *mos_parport = pp->private_data;
+ __u8 data;
+
+ if (parport_prologue(pp) < 0)
+ return;
+ data = ((__u8)d & 0x0f) | (mos_parport->shadowDCR & 0xf0);
+ write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR, data);
+ mos_parport->shadowDCR = data;
+ parport_epilogue(pp);
+}
+
+static unsigned char parport_mos7715_read_control(struct parport *pp)
+{
+ struct mos7715_parport *mos_parport;
+ __u8 dcr;
+
+ spin_lock(&release_lock);
+ mos_parport = pp->private_data;
+ if (unlikely(mos_parport == NULL)) {
+ spin_unlock(&release_lock);
+ return 0;
+ }
+ dcr = mos_parport->shadowDCR & 0x0f;
+ spin_unlock(&release_lock);
+ return dcr;
+}
+
+static unsigned char parport_mos7715_frob_control(struct parport *pp,
+ unsigned char mask,
+ unsigned char val)
+{
+ struct mos7715_parport *mos_parport = pp->private_data;
+ __u8 dcr;
+
+ mask &= 0x0f;
+ val &= 0x0f;
+ if (parport_prologue(pp) < 0)
+ return 0;
+ mos_parport->shadowDCR = (mos_parport->shadowDCR & (~mask)) ^ val;
+ write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR,
+ mos_parport->shadowDCR);
+ dcr = mos_parport->shadowDCR & 0x0f;
+ parport_epilogue(pp);
+ return dcr;
+}
+
+static unsigned char parport_mos7715_read_status(struct parport *pp)
+{
+ unsigned char status;
+ struct mos7715_parport *mos_parport;
+
+ spin_lock(&release_lock);
+ mos_parport = pp->private_data;
+ if (unlikely(mos_parport == NULL)) { /* release called */
+ spin_unlock(&release_lock);
+ return 0;
+ }
+ status = atomic_read(&mos_parport->shadowDSR) & 0xf8;
+ spin_unlock(&release_lock);
+ return status;
+}
+
+static void parport_mos7715_enable_irq(struct parport *pp)
+{
+}
+
+static void parport_mos7715_disable_irq(struct parport *pp)
+{
+}
+
+static void parport_mos7715_data_forward(struct parport *pp)
+{
+ struct mos7715_parport *mos_parport = pp->private_data;
+
+ if (parport_prologue(pp) < 0)
+ return;
+ mos7715_change_mode(mos_parport, PS2);
+ mos_parport->shadowDCR &= ~0x20;
+ write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR,
+ mos_parport->shadowDCR);
+ parport_epilogue(pp);
+}
+
+static void parport_mos7715_data_reverse(struct parport *pp)
+{
+ struct mos7715_parport *mos_parport = pp->private_data;
+
+ if (parport_prologue(pp) < 0)
+ return;
+ mos7715_change_mode(mos_parport, PS2);
+ mos_parport->shadowDCR |= 0x20;
+ write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR,
+ mos_parport->shadowDCR);
+ parport_epilogue(pp);
+}
+
+static void parport_mos7715_init_state(struct pardevice *dev,
+ struct parport_state *s)
+{
+ s->u.pc.ctr = DCR_INIT_VAL;
+ s->u.pc.ecr = ECR_INIT_VAL;
+}
+
+/* N.B. Parport core code requires that this function not block */
+static void parport_mos7715_save_state(struct parport *pp,
+ struct parport_state *s)
+{
+ struct mos7715_parport *mos_parport;
+
+ spin_lock(&release_lock);
+ mos_parport = pp->private_data;
+ if (unlikely(mos_parport == NULL)) { /* release called */
+ spin_unlock(&release_lock);
+ return;
+ }
+ s->u.pc.ctr = mos_parport->shadowDCR;
+ s->u.pc.ecr = mos_parport->shadowECR;
+ spin_unlock(&release_lock);
+}
+
+/* N.B. Parport core code requires that this function not block */
+static void parport_mos7715_restore_state(struct parport *pp,
+ struct parport_state *s)
+{
+ struct mos7715_parport *mos_parport;
+
+ spin_lock(&release_lock);
+ mos_parport = pp->private_data;
+ if (unlikely(mos_parport == NULL)) { /* release called */
+ spin_unlock(&release_lock);
+ return;
+ }
+ mos_parport->shadowDCR = s->u.pc.ctr;
+ mos_parport->shadowECR = s->u.pc.ecr;
+
+ schedule_work(&mos_parport->work);
+ spin_unlock(&release_lock);
+}
+
+static size_t parport_mos7715_write_compat(struct parport *pp,
+ const void *buffer,
+ size_t len, int flags)
+{
+ int retval;
+ struct mos7715_parport *mos_parport = pp->private_data;
+ int actual_len;
+
+ if (parport_prologue(pp) < 0)
+ return 0;
+ mos7715_change_mode(mos_parport, PPF);
+ retval = usb_bulk_msg(mos_parport->serial->dev,
+ usb_sndbulkpipe(mos_parport->serial->dev, 2),
+ (void *)buffer, len, &actual_len,
+ MOS_WDR_TIMEOUT);
+ parport_epilogue(pp);
+ if (retval) {
+ dev_err(&mos_parport->serial->dev->dev,
+ "mos7720: usb_bulk_msg() failed: %d\n", retval);
+ return 0;
+ }
+ return actual_len;
+}
+
+static struct parport_operations parport_mos7715_ops = {
+ .owner = THIS_MODULE,
+ .write_data = parport_mos7715_write_data,
+ .read_data = parport_mos7715_read_data,
+
+ .write_control = parport_mos7715_write_control,
+ .read_control = parport_mos7715_read_control,
+ .frob_control = parport_mos7715_frob_control,
+
+ .read_status = parport_mos7715_read_status,
+
+ .enable_irq = parport_mos7715_enable_irq,
+ .disable_irq = parport_mos7715_disable_irq,
+
+ .data_forward = parport_mos7715_data_forward,
+ .data_reverse = parport_mos7715_data_reverse,
+
+ .init_state = parport_mos7715_init_state,
+ .save_state = parport_mos7715_save_state,
+ .restore_state = parport_mos7715_restore_state,
+
+ .compat_write_data = parport_mos7715_write_compat,
+
+ .nibble_read_data = parport_ieee1284_read_nibble,
+ .byte_read_data = parport_ieee1284_read_byte,
+};
+
+/*
+ * Allocate and initialize parallel port control struct, initialize
+ * the parallel port hardware device, and register with the parport subsystem.
+ */
+static int mos7715_parport_init(struct usb_serial *serial)
+{
+ struct mos7715_parport *mos_parport;
+
+ /* allocate and initialize parallel port control struct */
+ mos_parport = kzalloc_obj(struct mos7715_parport);
+ if (!mos_parport)
+ return -ENOMEM;
+
+ mos_parport->msg_pending = false;
+ kref_init(&mos_parport->ref_count);
+ usb_set_serial_data(serial, mos_parport); /* hijack private pointer */
+ mos_parport->serial = serial;
+ INIT_WORK(&mos_parport->work, deferred_restore_writes);
+ init_completion(&mos_parport->syncmsg_compl);
+
+ /* cycle parallel port reset bit */
+ write_mos_reg(mos_parport->serial, dummy, MOS7720_PP_REG, (__u8)0x80);
+ write_mos_reg(mos_parport->serial, dummy, MOS7720_PP_REG, (__u8)0x00);
+
+ /* initialize device registers */
+ mos_parport->shadowDCR = DCR_INIT_VAL;
+ write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR,
+ mos_parport->shadowDCR);
+ mos_parport->shadowECR = ECR_INIT_VAL;
+ write_mos_reg(mos_parport->serial, dummy, MOS7720_ECR,
+ mos_parport->shadowECR);
+
+ /* register with parport core */
+ mos_parport->pp = parport_register_port(0, PARPORT_IRQ_NONE,
+ PARPORT_DMA_NONE,
+ &parport_mos7715_ops);
+ if (mos_parport->pp == NULL) {
+ dev_err(&serial->interface->dev,
+ "Could not register parport\n");
+ kref_put(&mos_parport->ref_count, destroy_mos_parport);
+ return -EIO;
+ }
+ mos_parport->pp->private_data = mos_parport;
+ mos_parport->pp->modes = PARPORT_MODE_COMPAT | PARPORT_MODE_PCSPP;
+ mos_parport->pp->dev = &serial->interface->dev;
+ parport_announce_port(mos_parport->pp);
+
+ return 0;
+}
+#endif /* CONFIG_USB_SERIAL_MOS7715_PARPORT */
+
+/*
+ * mos7720_interrupt_callback
+ * this is the callback function for when we have received data on the
+ * interrupt endpoint.
+ */
+static void mos7720_interrupt_callback(struct urb *urb)
+{
+ int result;
+ int length;
+ int status = urb->status;
+ struct device *dev = &urb->dev->dev;
+ __u8 *data;
+ __u8 sp1;
+ __u8 sp2;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(dev, "%s - urb shutting down with status: %d\n", __func__, status);
+ return;
+ default:
+ dev_dbg(dev, "%s - nonzero urb status received: %d\n", __func__, status);
+ goto exit;
+ }
+
+ length = urb->actual_length;
+ data = urb->transfer_buffer;
+
+ /* Moschip get 4 bytes
+ * Byte 1 IIR Port 1 (port.number is 0)
+ * Byte 2 IIR Port 2 (port.number is 1)
+ * Byte 3 --------------
+ * Byte 4 FIFO status for both */
+
+ /* the above description is inverted
+ * oneukum 2007-03-14 */
+
+ if (unlikely(length != 4)) {
+ dev_dbg(dev, "Wrong data !!!\n");
+ return;
+ }
+
+ sp1 = data[3];
+ sp2 = data[2];
+
+ if ((sp1 | sp2) & 0x01) {
+ /* No Interrupt Pending in both the ports */
+ dev_dbg(dev, "No Interrupt !!!\n");
+ } else {
+ switch (sp1 & 0x0f) {
+ case SERIAL_IIR_RLS:
+ dev_dbg(dev, "Serial Port 1: Receiver status error or address bit detected in 9-bit mode\n");
+ break;
+ case SERIAL_IIR_CTI:
+ dev_dbg(dev, "Serial Port 1: Receiver time out\n");
+ break;
+ case SERIAL_IIR_MS:
+ /* dev_dbg(dev, "Serial Port 1: Modem status change\n"); */
+ break;
+ }
+
+ switch (sp2 & 0x0f) {
+ case SERIAL_IIR_RLS:
+ dev_dbg(dev, "Serial Port 2: Receiver status error or address bit detected in 9-bit mode\n");
+ break;
+ case SERIAL_IIR_CTI:
+ dev_dbg(dev, "Serial Port 2: Receiver time out\n");
+ break;
+ case SERIAL_IIR_MS:
+ /* dev_dbg(dev, "Serial Port 2: Modem status change\n"); */
+ break;
+ }
+ }
+
+exit:
+ result = usb_submit_urb(urb, GFP_ATOMIC);
+ if (result)
+ dev_err(dev, "%s - Error %d submitting control urb\n", __func__, result);
+}
+
+/*
+ * mos7715_interrupt_callback
+ * this is the 7715's callback function for when we have received data on
+ * the interrupt endpoint.
+ */
+static void mos7715_interrupt_callback(struct urb *urb)
+{
+ int result;
+ int length;
+ int status = urb->status;
+ struct device *dev = &urb->dev->dev;
+ __u8 *data;
+ __u8 iir;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ENODEV:
+ /* this urb is terminated, clean up */
+ dev_dbg(dev, "%s - urb shutting down with status: %d\n", __func__, status);
+ return;
+ default:
+ dev_dbg(dev, "%s - nonzero urb status received: %d\n", __func__, status);
+ goto exit;
+ }
+
+ length = urb->actual_length;
+ data = urb->transfer_buffer;
+
+ /* Structure of data from 7715 device:
+ * Byte 1: IIR serial Port
+ * Byte 2: unused
+ * Byte 2: DSR parallel port
+ * Byte 4: FIFO status for both */
+
+ if (unlikely(length != 4)) {
+ dev_dbg(dev, "Wrong data !!!\n");
+ return;
+ }
+
+ iir = data[0];
+ if (!(iir & 0x01)) { /* serial port interrupt pending */
+ switch (iir & 0x0f) {
+ case SERIAL_IIR_RLS:
+ dev_dbg(dev, "Serial Port: Receiver status error or address bit detected in 9-bit mode\n");
+ break;
+ case SERIAL_IIR_CTI:
+ dev_dbg(dev, "Serial Port: Receiver time out\n");
+ break;
+ case SERIAL_IIR_MS:
+ /* dev_dbg(dev, "Serial Port: Modem status change\n"); */
+ break;
+ }
+ }
+
+#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT
+ { /* update local copy of DSR reg */
+ struct usb_serial_port *port = urb->context;
+ struct mos7715_parport *mos_parport = port->serial->private;
+ if (unlikely(mos_parport == NULL))
+ return;
+ atomic_set(&mos_parport->shadowDSR, data[2]);
+ }
+#endif
+
+exit:
+ result = usb_submit_urb(urb, GFP_ATOMIC);
+ if (result)
+ dev_err(dev, "%s - Error %d submitting control urb\n", __func__, result);
+}
+
+/*
+ * mos7720_bulk_in_callback
+ * this is the callback function for when we have received data on the
+ * bulk in endpoint.
+ */
+static void mos7720_bulk_in_callback(struct urb *urb)
+{
+ int retval;
+ unsigned char *data ;
+ struct usb_serial_port *port;
+ int status = urb->status;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "nonzero read bulk status received: %d\n", status);
+ return;
+ }
+
+ port = urb->context;
+
+ dev_dbg(&port->dev, "Entering...%s\n", __func__);
+
+ data = urb->transfer_buffer;
+
+ if (urb->actual_length) {
+ tty_insert_flip_string(&port->port, data, urb->actual_length);
+ tty_flip_buffer_push(&port->port);
+ }
+
+ if (port->read_urb->status != -EINPROGRESS) {
+ retval = usb_submit_urb(port->read_urb, GFP_ATOMIC);
+ if (retval)
+ dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, retval = %d\n", retval);
+ }
+}
+
+/*
+ * mos7720_bulk_out_data_callback
+ * this is the callback function for when we have finished sending serial
+ * data on the bulk out endpoint.
+ */
+static void mos7720_bulk_out_data_callback(struct urb *urb)
+{
+ struct moschip_port *mos7720_port;
+ int status = urb->status;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "nonzero write bulk status received:%d\n", status);
+ return;
+ }
+
+ mos7720_port = urb->context;
+ if (!mos7720_port) {
+ dev_dbg(&urb->dev->dev, "NULL mos7720_port pointer\n");
+ return ;
+ }
+
+ if (mos7720_port->open)
+ tty_port_tty_wakeup(&mos7720_port->port->port);
+}
+
+static int mos77xx_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ u16 product = le16_to_cpu(serial->dev->descriptor.idProduct);
+
+ if (product == MOSCHIP_DEVICE_ID_7715) {
+ /*
+ * The 7715 uses the first bulk in/out endpoint pair for the
+ * parallel port, and the second for the serial port. We swap
+ * the endpoint descriptors here so that the first and
+ * only registered port structure uses the serial-port
+ * endpoints.
+ */
+ swap(epds->bulk_in[0], epds->bulk_in[1]);
+ swap(epds->bulk_out[0], epds->bulk_out[1]);
+
+ return 1;
+ }
+
+ return 2;
+}
+
+static int mos7720_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct usb_serial *serial;
+ struct urb *urb;
+ struct moschip_port *mos7720_port;
+ int response;
+ int port_number;
+ __u8 data;
+ int allocated_urbs = 0;
+ int j;
+
+ serial = port->serial;
+
+ mos7720_port = usb_get_serial_port_data(port);
+ if (mos7720_port == NULL)
+ return -ENODEV;
+
+ usb_clear_halt(serial->dev, port->write_urb->pipe);
+ usb_clear_halt(serial->dev, port->read_urb->pipe);
+
+ /* Initialising the write urb pool */
+ for (j = 0; j < NUM_URBS; ++j) {
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ mos7720_port->write_urb_pool[j] = urb;
+ if (!urb)
+ continue;
+
+ urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE,
+ GFP_KERNEL);
+ if (!urb->transfer_buffer) {
+ usb_free_urb(mos7720_port->write_urb_pool[j]);
+ mos7720_port->write_urb_pool[j] = NULL;
+ continue;
+ }
+ allocated_urbs++;
+ }
+
+ if (!allocated_urbs)
+ return -ENOMEM;
+
+ /* Initialize MCS7720 -- Write Init values to corresponding Registers
+ *
+ * Register Index
+ * 0 : MOS7720_THR/MOS7720_RHR
+ * 1 : MOS7720_IER
+ * 2 : MOS7720_FCR
+ * 3 : MOS7720_LCR
+ * 4 : MOS7720_MCR
+ * 5 : MOS7720_LSR
+ * 6 : MOS7720_MSR
+ * 7 : MOS7720_SPR
+ *
+ * 0x08 : SP1/2 Control Reg
+ */
+ port_number = port->port_number;
+ read_mos_reg(serial, port_number, MOS7720_LSR, &data);
+
+ dev_dbg(&port->dev, "SS::%p LSR:%x\n", mos7720_port, data);
+
+ write_mos_reg(serial, dummy, MOS7720_SP1_REG, 0x02);
+ write_mos_reg(serial, dummy, MOS7720_SP2_REG, 0x02);
+
+ write_mos_reg(serial, port_number, MOS7720_IER, 0x00);
+ write_mos_reg(serial, port_number, MOS7720_FCR, 0x00);
+
+ write_mos_reg(serial, port_number, MOS7720_FCR, 0xcf);
+ mos7720_port->shadowLCR = 0x03;
+ write_mos_reg(serial, port_number, MOS7720_LCR,
+ mos7720_port->shadowLCR);
+ mos7720_port->shadowMCR = 0x0b;
+ write_mos_reg(serial, port_number, MOS7720_MCR,
+ mos7720_port->shadowMCR);
+
+ write_mos_reg(serial, port_number, MOS7720_SP_CONTROL_REG, 0x00);
+ read_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG, &data);
+ data = data | (port->port_number + 1);
+ write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG, data);
+ mos7720_port->shadowLCR = 0x83;
+ write_mos_reg(serial, port_number, MOS7720_LCR,
+ mos7720_port->shadowLCR);
+ write_mos_reg(serial, port_number, MOS7720_THR, 0x0c);
+ write_mos_reg(serial, port_number, MOS7720_IER, 0x00);
+ mos7720_port->shadowLCR = 0x03;
+ write_mos_reg(serial, port_number, MOS7720_LCR,
+ mos7720_port->shadowLCR);
+ write_mos_reg(serial, port_number, MOS7720_IER, 0x0c);
+
+ response = usb_submit_urb(port->read_urb, GFP_KERNEL);
+ if (response)
+ dev_err(&port->dev, "%s - Error %d submitting read urb\n",
+ __func__, response);
+
+ /* initialize our port settings */
+ mos7720_port->shadowMCR = UART_MCR_OUT2; /* Must set to enable ints! */
+
+ /* send a open port command */
+ mos7720_port->open = 1;
+
+ return 0;
+}
+
+/*
+ * mos7720_chars_in_buffer
+ * this function is called by the tty driver when it wants to know how many
+ * bytes of data we currently have outstanding in the port (data that has
+ * been written, but hasn't made it out the port yet)
+ */
+static unsigned int mos7720_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7720_port = usb_get_serial_port_data(port);
+ int i;
+ unsigned int chars = 0;
+
+ for (i = 0; i < NUM_URBS; ++i) {
+ if (mos7720_port->write_urb_pool[i] &&
+ mos7720_port->write_urb_pool[i]->status == -EINPROGRESS)
+ chars += URB_TRANSFER_BUFFER_SIZE;
+ }
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, chars);
+ return chars;
+}
+
+static void mos7720_close(struct usb_serial_port *port)
+{
+ struct usb_serial *serial;
+ struct moschip_port *mos7720_port;
+ int j;
+
+ serial = port->serial;
+
+ mos7720_port = usb_get_serial_port_data(port);
+ if (mos7720_port == NULL)
+ return;
+
+ for (j = 0; j < NUM_URBS; ++j)
+ usb_kill_urb(mos7720_port->write_urb_pool[j]);
+
+ /* Freeing Write URBs */
+ for (j = 0; j < NUM_URBS; ++j) {
+ if (mos7720_port->write_urb_pool[j]) {
+ kfree(mos7720_port->write_urb_pool[j]->transfer_buffer);
+ usb_free_urb(mos7720_port->write_urb_pool[j]);
+ }
+ }
+
+ /* While closing port, shutdown all bulk read, write *
+ * and interrupt read if they exists, otherwise nop */
+ usb_kill_urb(port->write_urb);
+ usb_kill_urb(port->read_urb);
+
+ write_mos_reg(serial, port->port_number, MOS7720_MCR, 0x00);
+ write_mos_reg(serial, port->port_number, MOS7720_IER, 0x00);
+
+ mos7720_port->open = 0;
+}
+
+static int mos7720_break(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned char data;
+ struct usb_serial *serial;
+ struct moschip_port *mos7720_port;
+
+ serial = port->serial;
+
+ mos7720_port = usb_get_serial_port_data(port);
+ if (mos7720_port == NULL)
+ return -ENODEV;
+
+ if (break_state == -1)
+ data = mos7720_port->shadowLCR | UART_LCR_SBC;
+ else
+ data = mos7720_port->shadowLCR & ~UART_LCR_SBC;
+
+ mos7720_port->shadowLCR = data;
+
+ return write_mos_reg(serial, port->port_number, MOS7720_LCR,
+ mos7720_port->shadowLCR);
+}
+
+/*
+ * mos7720_write_room
+ * this function is called by the tty driver when it wants to know how many
+ * bytes of data we can accept for a specific port.
+ */
+static unsigned int mos7720_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7720_port = usb_get_serial_port_data(port);
+ unsigned int room = 0;
+ int i;
+
+ /* FIXME: Locking */
+ for (i = 0; i < NUM_URBS; ++i) {
+ if (mos7720_port->write_urb_pool[i] &&
+ mos7720_port->write_urb_pool[i]->status != -EINPROGRESS)
+ room += URB_TRANSFER_BUFFER_SIZE;
+ }
+
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, room);
+ return room;
+}
+
+static int mos7720_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *data, int count)
+{
+ int status;
+ int i;
+ int bytes_sent = 0;
+ int transfer_size;
+
+ struct moschip_port *mos7720_port;
+ struct usb_serial *serial;
+ struct urb *urb;
+ const unsigned char *current_position = data;
+
+ serial = port->serial;
+
+ mos7720_port = usb_get_serial_port_data(port);
+ if (mos7720_port == NULL)
+ return -ENODEV;
+
+ /* try to find a free urb in the list */
+ urb = NULL;
+
+ for (i = 0; i < NUM_URBS; ++i) {
+ if (mos7720_port->write_urb_pool[i] &&
+ mos7720_port->write_urb_pool[i]->status != -EINPROGRESS) {
+ urb = mos7720_port->write_urb_pool[i];
+ dev_dbg(&port->dev, "URB:%d\n", i);
+ break;
+ }
+ }
+
+ if (urb == NULL) {
+ dev_dbg(&port->dev, "%s - no more free urbs\n", __func__);
+ goto exit;
+ }
+
+ if (urb->transfer_buffer == NULL) {
+ urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE,
+ GFP_ATOMIC);
+ if (!urb->transfer_buffer) {
+ bytes_sent = -ENOMEM;
+ goto exit;
+ }
+ }
+ transfer_size = min(count, URB_TRANSFER_BUFFER_SIZE);
+
+ memcpy(urb->transfer_buffer, current_position, transfer_size);
+ usb_serial_debug_data(&port->dev, __func__, transfer_size,
+ urb->transfer_buffer);
+
+ /* fill urb with data and submit */
+ usb_fill_bulk_urb(urb, serial->dev,
+ usb_sndbulkpipe(serial->dev,
+ port->bulk_out_endpointAddress),
+ urb->transfer_buffer, transfer_size,
+ mos7720_bulk_out_data_callback, mos7720_port);
+
+ /* send it down the pipe */
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status) {
+ dev_err_console(port, "%s - usb_submit_urb(write bulk) failed "
+ "with status = %d\n", __func__, status);
+ bytes_sent = status;
+ goto exit;
+ }
+ bytes_sent = transfer_size;
+
+exit:
+ return bytes_sent;
+}
+
+static void mos7720_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7720_port;
+ int status;
+
+ mos7720_port = usb_get_serial_port_data(port);
+
+ if (mos7720_port == NULL)
+ return;
+
+ if (!mos7720_port->open) {
+ dev_dbg(&port->dev, "%s - port not opened\n", __func__);
+ return;
+ }
+
+ /* if we are implementing XON/XOFF, send the stop character */
+ if (I_IXOFF(tty)) {
+ unsigned char stop_char = STOP_CHAR(tty);
+ status = mos7720_write(tty, port, &stop_char, 1);
+ if (status <= 0)
+ return;
+ }
+
+ /* if we are implementing RTS/CTS, toggle that line */
+ if (C_CRTSCTS(tty)) {
+ mos7720_port->shadowMCR &= ~UART_MCR_RTS;
+ write_mos_reg(port->serial, port->port_number, MOS7720_MCR,
+ mos7720_port->shadowMCR);
+ }
+}
+
+static void mos7720_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7720_port = usb_get_serial_port_data(port);
+ int status;
+
+ if (mos7720_port == NULL)
+ return;
+
+ if (!mos7720_port->open) {
+ dev_dbg(&port->dev, "%s - port not opened\n", __func__);
+ return;
+ }
+
+ /* if we are implementing XON/XOFF, send the start character */
+ if (I_IXOFF(tty)) {
+ unsigned char start_char = START_CHAR(tty);
+ status = mos7720_write(tty, port, &start_char, 1);
+ if (status <= 0)
+ return;
+ }
+
+ /* if we are implementing RTS/CTS, toggle that line */
+ if (C_CRTSCTS(tty)) {
+ mos7720_port->shadowMCR |= UART_MCR_RTS;
+ write_mos_reg(port->serial, port->port_number, MOS7720_MCR,
+ mos7720_port->shadowMCR);
+ }
+}
+
+/* FIXME: this function does not work */
+static int set_higher_rates(struct moschip_port *mos7720_port,
+ unsigned int baud)
+{
+ struct usb_serial_port *port;
+ struct usb_serial *serial;
+ int port_number;
+ enum mos_regs sp_reg;
+ if (mos7720_port == NULL)
+ return -EINVAL;
+
+ port = mos7720_port->port;
+ serial = port->serial;
+
+ /***********************************************
+ * Init Sequence for higher rates
+ ***********************************************/
+ dev_dbg(&port->dev, "Sending Setting Commands ..........\n");
+ port_number = port->port_number;
+
+ write_mos_reg(serial, port_number, MOS7720_IER, 0x00);
+ write_mos_reg(serial, port_number, MOS7720_FCR, 0x00);
+ write_mos_reg(serial, port_number, MOS7720_FCR, 0xcf);
+ mos7720_port->shadowMCR = 0x0b;
+ write_mos_reg(serial, port_number, MOS7720_MCR,
+ mos7720_port->shadowMCR);
+ write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG, 0x00);
+
+ /***********************************************
+ * Set for higher rates *
+ ***********************************************/
+ /* writing baud rate verbatum into uart clock field clearly not right */
+ if (port_number == 0)
+ sp_reg = MOS7720_SP1_REG;
+ else
+ sp_reg = MOS7720_SP2_REG;
+ write_mos_reg(serial, dummy, sp_reg, baud * 0x10);
+ write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG, 0x03);
+ mos7720_port->shadowMCR = 0x2b;
+ write_mos_reg(serial, port_number, MOS7720_MCR,
+ mos7720_port->shadowMCR);
+
+ /***********************************************
+ * Set DLL/DLM
+ ***********************************************/
+ mos7720_port->shadowLCR = mos7720_port->shadowLCR | UART_LCR_DLAB;
+ write_mos_reg(serial, port_number, MOS7720_LCR,
+ mos7720_port->shadowLCR);
+ write_mos_reg(serial, port_number, MOS7720_DLL, 0x01);
+ write_mos_reg(serial, port_number, MOS7720_DLM, 0x00);
+ mos7720_port->shadowLCR = mos7720_port->shadowLCR & ~UART_LCR_DLAB;
+ write_mos_reg(serial, port_number, MOS7720_LCR,
+ mos7720_port->shadowLCR);
+
+ return 0;
+}
+
+/* baud rate information */
+struct divisor_table_entry {
+ __u32 baudrate;
+ __u16 divisor;
+};
+
+/* Define table of divisors for moschip 7720 hardware *
+ * These assume a 3.6864MHz crystal, the standard /16, and *
+ * MCR.7 = 0. */
+static const struct divisor_table_entry divisor_table[] = {
+ { 50, 2304},
+ { 110, 1047}, /* 2094.545455 => 230450 => .0217 % over */
+ { 134, 857}, /* 1713.011152 => 230398.5 => .00065% under */
+ { 150, 768},
+ { 300, 384},
+ { 600, 192},
+ { 1200, 96},
+ { 1800, 64},
+ { 2400, 48},
+ { 4800, 24},
+ { 7200, 16},
+ { 9600, 12},
+ { 19200, 6},
+ { 38400, 3},
+ { 57600, 2},
+ { 115200, 1},
+};
+
+/*****************************************************************************
+ * calc_baud_rate_divisor
+ * this function calculates the proper baud rate divisor for the specified
+ * baud rate.
+ *****************************************************************************/
+static int calc_baud_rate_divisor(struct usb_serial_port *port, int baudrate, int *divisor)
+{
+ int i;
+ __u16 custom;
+ __u16 round1;
+ __u16 round;
+
+
+ dev_dbg(&port->dev, "%s - %d\n", __func__, baudrate);
+
+ for (i = 0; i < ARRAY_SIZE(divisor_table); i++) {
+ if (divisor_table[i].baudrate == baudrate) {
+ *divisor = divisor_table[i].divisor;
+ return 0;
+ }
+ }
+
+ /* After trying for all the standard baud rates *
+ * Try calculating the divisor for this baud rate */
+ if (baudrate > 75 && baudrate < 230400) {
+ /* get the divisor */
+ custom = (__u16)(230400L / baudrate);
+
+ /* Check for round off */
+ round1 = (__u16)(2304000L / baudrate);
+ round = (__u16)(round1 - (custom * 10));
+ if (round > 4)
+ custom++;
+ *divisor = custom;
+
+ dev_dbg(&port->dev, "Baud %d = %d\n", baudrate, custom);
+ return 0;
+ }
+
+ dev_dbg(&port->dev, "Baud calculation Failed...\n");
+ return -EINVAL;
+}
+
+/*
+ * send_cmd_write_baud_rate
+ * this function sends the proper command to change the baud rate of the
+ * specified port.
+ */
+static int send_cmd_write_baud_rate(struct moschip_port *mos7720_port,
+ int baudrate)
+{
+ struct usb_serial_port *port;
+ struct usb_serial *serial;
+ int divisor;
+ int status;
+ unsigned char number;
+
+ if (mos7720_port == NULL)
+ return -1;
+
+ port = mos7720_port->port;
+ serial = port->serial;
+
+ number = port->port_number;
+ dev_dbg(&port->dev, "%s - baud = %d\n", __func__, baudrate);
+
+ /* Calculate the Divisor */
+ status = calc_baud_rate_divisor(port, baudrate, &divisor);
+ if (status) {
+ dev_err(&port->dev, "%s - bad baud rate\n", __func__);
+ return status;
+ }
+
+ /* Enable access to divisor latch */
+ mos7720_port->shadowLCR = mos7720_port->shadowLCR | UART_LCR_DLAB;
+ write_mos_reg(serial, number, MOS7720_LCR, mos7720_port->shadowLCR);
+
+ /* Write the divisor */
+ write_mos_reg(serial, number, MOS7720_DLL, (__u8)(divisor & 0xff));
+ write_mos_reg(serial, number, MOS7720_DLM,
+ (__u8)((divisor & 0xff00) >> 8));
+
+ /* Disable access to divisor latch */
+ mos7720_port->shadowLCR = mos7720_port->shadowLCR & ~UART_LCR_DLAB;
+ write_mos_reg(serial, number, MOS7720_LCR, mos7720_port->shadowLCR);
+
+ return status;
+}
+
+/*
+ * change_port_settings
+ * This routine is called to set the UART on the device to match
+ * the specified new settings.
+ */
+static void change_port_settings(struct tty_struct *tty,
+ struct moschip_port *mos7720_port,
+ const struct ktermios *old_termios)
+{
+ struct usb_serial_port *port;
+ struct usb_serial *serial;
+ int baud;
+ unsigned cflag;
+ __u8 lData;
+ __u8 lParity;
+ __u8 lStop;
+ int status;
+ int port_number;
+
+ if (mos7720_port == NULL)
+ return ;
+
+ port = mos7720_port->port;
+ serial = port->serial;
+ port_number = port->port_number;
+
+ if (!mos7720_port->open) {
+ dev_dbg(&port->dev, "%s - port not opened\n", __func__);
+ return;
+ }
+
+ lStop = 0x00; /* 1 stop bit */
+ lParity = 0x00; /* No parity */
+
+ cflag = tty->termios.c_cflag;
+
+ lData = UART_LCR_WLEN(tty_get_char_size(cflag));
+
+ /* Change the Parity bit */
+ if (cflag & PARENB) {
+ if (cflag & PARODD) {
+ lParity = UART_LCR_PARITY;
+ dev_dbg(&port->dev, "%s - parity = odd\n", __func__);
+ } else {
+ lParity = (UART_LCR_EPAR | UART_LCR_PARITY);
+ dev_dbg(&port->dev, "%s - parity = even\n", __func__);
+ }
+
+ } else {
+ dev_dbg(&port->dev, "%s - parity = none\n", __func__);
+ }
+
+ if (cflag & CMSPAR)
+ lParity = lParity | 0x20;
+
+ /* Change the Stop bit */
+ if (cflag & CSTOPB) {
+ lStop = UART_LCR_STOP;
+ dev_dbg(&port->dev, "%s - stop bits = 2\n", __func__);
+ } else {
+ lStop = 0x00;
+ dev_dbg(&port->dev, "%s - stop bits = 1\n", __func__);
+ }
+
+#define LCR_BITS_MASK 0x03 /* Mask for bits/char field */
+#define LCR_STOP_MASK 0x04 /* Mask for stop bits field */
+#define LCR_PAR_MASK 0x38 /* Mask for parity field */
+
+ /* Update the LCR with the correct value */
+ mos7720_port->shadowLCR &=
+ ~(LCR_BITS_MASK | LCR_STOP_MASK | LCR_PAR_MASK);
+ mos7720_port->shadowLCR |= (lData | lParity | lStop);
+
+
+ /* Disable Interrupts */
+ write_mos_reg(serial, port_number, MOS7720_IER, 0x00);
+ write_mos_reg(serial, port_number, MOS7720_FCR, 0x00);
+ write_mos_reg(serial, port_number, MOS7720_FCR, 0xcf);
+
+ /* Send the updated LCR value to the mos7720 */
+ write_mos_reg(serial, port_number, MOS7720_LCR,
+ mos7720_port->shadowLCR);
+ mos7720_port->shadowMCR = 0x0b;
+ write_mos_reg(serial, port_number, MOS7720_MCR,
+ mos7720_port->shadowMCR);
+
+ /* set up the MCR register and send it to the mos7720 */
+ mos7720_port->shadowMCR = UART_MCR_OUT2;
+ if (cflag & CBAUD)
+ mos7720_port->shadowMCR |= (UART_MCR_DTR | UART_MCR_RTS);
+
+ if (cflag & CRTSCTS) {
+ mos7720_port->shadowMCR |= (UART_MCR_XONANY);
+ /* To set hardware flow control to the specified *
+ * serial port, in SP1/2_CONTROL_REG */
+ if (port_number)
+ write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG,
+ 0x01);
+ else
+ write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG,
+ 0x02);
+
+ } else
+ mos7720_port->shadowMCR &= ~(UART_MCR_XONANY);
+
+ write_mos_reg(serial, port_number, MOS7720_MCR,
+ mos7720_port->shadowMCR);
+
+ /* Determine divisor based on baud rate */
+ baud = tty_get_baud_rate(tty);
+ if (!baud) {
+ /* pick a default, any default... */
+ dev_dbg(&port->dev, "Picked default baud...\n");
+ baud = 9600;
+ }
+
+ if (baud >= 230400) {
+ set_higher_rates(mos7720_port, baud);
+ /* Enable Interrupts */
+ write_mos_reg(serial, port_number, MOS7720_IER, 0x0c);
+ return;
+ }
+
+ dev_dbg(&port->dev, "%s - baud rate = %d\n", __func__, baud);
+ status = send_cmd_write_baud_rate(mos7720_port, baud);
+ /* FIXME: needs to write actual resulting baud back not just
+ blindly do so */
+ if (cflag & CBAUD)
+ tty_encode_baud_rate(tty, baud, baud);
+ /* Enable Interrupts */
+ write_mos_reg(serial, port_number, MOS7720_IER, 0x0c);
+
+ if (port->read_urb->status != -EINPROGRESS) {
+ status = usb_submit_urb(port->read_urb, GFP_KERNEL);
+ if (status)
+ dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, status = %d\n", status);
+ }
+}
+
+/*
+ * mos7720_set_termios
+ * this function is called by the tty driver when it wants to change the
+ * termios structure.
+ */
+static void mos7720_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ int status;
+ struct moschip_port *mos7720_port;
+
+ mos7720_port = usb_get_serial_port_data(port);
+
+ if (mos7720_port == NULL)
+ return;
+
+ if (!mos7720_port->open) {
+ dev_dbg(&port->dev, "%s - port not opened\n", __func__);
+ return;
+ }
+
+ /* change the port settings to the new ones specified */
+ change_port_settings(tty, mos7720_port, old_termios);
+
+ if (port->read_urb->status != -EINPROGRESS) {
+ status = usb_submit_urb(port->read_urb, GFP_KERNEL);
+ if (status)
+ dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, status = %d\n", status);
+ }
+}
+
+/*
+ * get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ * is emptied. On bus types like RS485, the transmitter must
+ * release the bus after transmitting. This must be done when
+ * the transmit shift register is empty, not be done when the
+ * transmit holding register is empty. This functionality
+ * allows an RS485 driver to be written in user space.
+ */
+static int get_lsr_info(struct tty_struct *tty,
+ struct moschip_port *mos7720_port, unsigned int __user *value)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned int result = 0;
+ unsigned char data = 0;
+ int port_number = port->port_number;
+ int count;
+
+ count = mos7720_chars_in_buffer(tty);
+ if (count == 0) {
+ read_mos_reg(port->serial, port_number, MOS7720_LSR, &data);
+ if ((data & (UART_LSR_TEMT | UART_LSR_THRE))
+ == (UART_LSR_TEMT | UART_LSR_THRE)) {
+ dev_dbg(&port->dev, "%s -- Empty\n", __func__);
+ result = TIOCSER_TEMT;
+ }
+ }
+ if (copy_to_user(value, &result, sizeof(int)))
+ return -EFAULT;
+ return 0;
+}
+
+static int mos7720_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7720_port = usb_get_serial_port_data(port);
+ unsigned int result = 0;
+ unsigned int mcr ;
+ unsigned int msr ;
+
+ mcr = mos7720_port->shadowMCR;
+ msr = mos7720_port->shadowMSR;
+
+ result = ((mcr & UART_MCR_DTR) ? TIOCM_DTR : 0) /* 0x002 */
+ | ((mcr & UART_MCR_RTS) ? TIOCM_RTS : 0) /* 0x004 */
+ | ((msr & UART_MSR_CTS) ? TIOCM_CTS : 0) /* 0x020 */
+ | ((msr & UART_MSR_DCD) ? TIOCM_CAR : 0) /* 0x040 */
+ | ((msr & UART_MSR_RI) ? TIOCM_RI : 0) /* 0x080 */
+ | ((msr & UART_MSR_DSR) ? TIOCM_DSR : 0); /* 0x100 */
+
+ return result;
+}
+
+static int mos7720_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7720_port = usb_get_serial_port_data(port);
+ unsigned int mcr ;
+
+ mcr = mos7720_port->shadowMCR;
+
+ if (set & TIOCM_RTS)
+ mcr |= UART_MCR_RTS;
+ if (set & TIOCM_DTR)
+ mcr |= UART_MCR_DTR;
+ if (set & TIOCM_LOOP)
+ mcr |= UART_MCR_LOOP;
+
+ if (clear & TIOCM_RTS)
+ mcr &= ~UART_MCR_RTS;
+ if (clear & TIOCM_DTR)
+ mcr &= ~UART_MCR_DTR;
+ if (clear & TIOCM_LOOP)
+ mcr &= ~UART_MCR_LOOP;
+
+ mos7720_port->shadowMCR = mcr;
+ write_mos_reg(port->serial, port->port_number, MOS7720_MCR,
+ mos7720_port->shadowMCR);
+
+ return 0;
+}
+
+static int mos7720_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7720_port;
+
+ mos7720_port = usb_get_serial_port_data(port);
+ if (mos7720_port == NULL)
+ return -ENODEV;
+
+ switch (cmd) {
+ case TIOCSERGETLSR:
+ dev_dbg(&port->dev, "%s TIOCSERGETLSR\n", __func__);
+ return get_lsr_info(tty, mos7720_port,
+ (unsigned int __user *)arg);
+ }
+
+ return -ENOIOCTLCMD;
+}
+
+static int mos7720_startup(struct usb_serial *serial)
+{
+ struct usb_device *dev;
+ char data;
+ u16 product;
+ int ret_val;
+
+ product = le16_to_cpu(serial->dev->descriptor.idProduct);
+ dev = serial->dev;
+
+ if (product == MOSCHIP_DEVICE_ID_7715) {
+ struct urb *urb = serial->port[0]->interrupt_in_urb;
+
+ urb->complete = mos7715_interrupt_callback;
+
+#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT
+ ret_val = mos7715_parport_init(serial);
+ if (ret_val < 0)
+ return ret_val;
+#endif
+ }
+ /* start the interrupt urb */
+ ret_val = usb_submit_urb(serial->port[0]->interrupt_in_urb, GFP_KERNEL);
+ if (ret_val) {
+ dev_err(&dev->dev, "failed to submit interrupt urb: %d\n",
+ ret_val);
+ }
+
+ /* LSR For Port 1 */
+ read_mos_reg(serial, 0, MOS7720_LSR, &data);
+ dev_dbg(&dev->dev, "LSR:%x\n", data);
+
+ return 0;
+}
+
+static void mos7720_release(struct usb_serial *serial)
+{
+ usb_kill_urb(serial->port[0]->interrupt_in_urb);
+
+#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT
+ /* close the parallel port */
+
+ if (le16_to_cpu(serial->dev->descriptor.idProduct)
+ == MOSCHIP_DEVICE_ID_7715) {
+ struct mos7715_parport *mos_parport =
+ usb_get_serial_data(serial);
+
+ /* prevent NULL ptr dereference in port callbacks */
+ spin_lock(&release_lock);
+ mos_parport->pp->private_data = NULL;
+ spin_unlock(&release_lock);
+
+ /* wait for synchronous usb calls to return */
+ if (mos_parport->msg_pending)
+ wait_for_completion_timeout(&mos_parport->syncmsg_compl,
+ msecs_to_jiffies(MOS_WDR_TIMEOUT));
+ /*
+ * If delayed work is currently scheduled, wait for it to
+ * complete. This also implies barriers that ensure the
+ * below serial clearing is not hoisted above the ->work.
+ */
+ cancel_work_sync(&mos_parport->work);
+
+ parport_remove_port(mos_parport->pp);
+ usb_set_serial_data(serial, NULL);
+ mos_parport->serial = NULL;
+
+ parport_del_port(mos_parport->pp);
+
+ kref_put(&mos_parport->ref_count, destroy_mos_parport);
+ }
+#endif
+}
+
+static int mos7720_port_probe(struct usb_serial_port *port)
+{
+ struct moschip_port *mos7720_port;
+
+ mos7720_port = kzalloc_obj(*mos7720_port);
+ if (!mos7720_port)
+ return -ENOMEM;
+
+ mos7720_port->port = port;
+
+ usb_set_serial_port_data(port, mos7720_port);
+
+ return 0;
+}
+
+static void mos7720_port_remove(struct usb_serial_port *port)
+{
+ struct moschip_port *mos7720_port;
+
+ mos7720_port = usb_get_serial_port_data(port);
+ kfree(mos7720_port);
+}
+
+static struct usb_serial_driver moschip7720_2port_driver = {
+ .driver = {
+ .name = "moschip7720",
+ },
+ .description = "Moschip 2 port adapter",
+ .id_table = id_table,
+ .num_bulk_in = 2,
+ .num_bulk_out = 2,
+ .num_interrupt_in = 1,
+ .calc_num_ports = mos77xx_calc_num_ports,
+ .open = mos7720_open,
+ .close = mos7720_close,
+ .throttle = mos7720_throttle,
+ .unthrottle = mos7720_unthrottle,
+ .attach = mos7720_startup,
+ .release = mos7720_release,
+ .port_probe = mos7720_port_probe,
+ .port_remove = mos7720_port_remove,
+ .ioctl = mos7720_ioctl,
+ .tiocmget = mos7720_tiocmget,
+ .tiocmset = mos7720_tiocmset,
+ .set_termios = mos7720_set_termios,
+ .write = mos7720_write,
+ .write_room = mos7720_write_room,
+ .chars_in_buffer = mos7720_chars_in_buffer,
+ .break_ctl = mos7720_break,
+ .read_bulk_callback = mos7720_bulk_in_callback,
+ .read_int_callback = mos7720_interrupt_callback,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &moschip7720_2port_driver, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c
new file mode 100644
index 000000000..ef803e74a
--- /dev/null
+++ b/drivers/usb/serial/mos7840.c
@@ -0,0 +1,1805 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Clean ups from Moschip version and a few ioctl implementations by:
+ * Paul B Schroeder <pschroeder "at" uplogix "dot" com>
+ *
+ * Originally based on drivers/usb/serial/io_edgeport.c which is:
+ * Copyright (C) 2000 Inside Out Networks, All rights reserved.
+ * Copyright (C) 2001-2002 Greg Kroah-Hartman <greg@kroah.com>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/serial.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/uaccess.h>
+
+#define DRIVER_DESC "Moschip 7840/7820 USB Serial Driver"
+
+/*
+ * 16C50 UART register defines
+ */
+
+#define LCR_BITS_5 0x00 /* 5 bits/char */
+#define LCR_BITS_6 0x01 /* 6 bits/char */
+#define LCR_BITS_7 0x02 /* 7 bits/char */
+#define LCR_BITS_8 0x03 /* 8 bits/char */
+#define LCR_BITS_MASK 0x03 /* Mask for bits/char field */
+
+#define LCR_STOP_1 0x00 /* 1 stop bit */
+#define LCR_STOP_1_5 0x04 /* 1.5 stop bits (if 5 bits/char) */
+#define LCR_STOP_2 0x04 /* 2 stop bits (if 6-8 bits/char) */
+#define LCR_STOP_MASK 0x04 /* Mask for stop bits field */
+
+#define LCR_PAR_NONE 0x00 /* No parity */
+#define LCR_PAR_ODD 0x08 /* Odd parity */
+#define LCR_PAR_EVEN 0x18 /* Even parity */
+#define LCR_PAR_MARK 0x28 /* Force parity bit to 1 */
+#define LCR_PAR_SPACE 0x38 /* Force parity bit to 0 */
+#define LCR_PAR_MASK 0x38 /* Mask for parity field */
+
+#define LCR_SET_BREAK 0x40 /* Set Break condition */
+#define LCR_DL_ENABLE 0x80 /* Enable access to divisor latch */
+
+#define MCR_DTR 0x01 /* Assert DTR */
+#define MCR_RTS 0x02 /* Assert RTS */
+#define MCR_OUT1 0x04 /* Loopback only: Sets state of RI */
+#define MCR_MASTER_IE 0x08 /* Enable interrupt outputs */
+#define MCR_LOOPBACK 0x10 /* Set internal (digital) loopback mode */
+#define MCR_XON_ANY 0x20 /* Enable any char to exit XOFF mode */
+
+#define MOS7840_MSR_CTS 0x10 /* Current state of CTS */
+#define MOS7840_MSR_DSR 0x20 /* Current state of DSR */
+#define MOS7840_MSR_RI 0x40 /* Current state of RI */
+#define MOS7840_MSR_CD 0x80 /* Current state of CD */
+
+/*
+ * Defines used for sending commands to port
+ */
+
+#define MOS_WDR_TIMEOUT 5000 /* default urb timeout */
+
+/* Requests */
+#define MCS_RD_RTYPE 0xC0
+#define MCS_WR_RTYPE 0x40
+#define MCS_RDREQ 0x0D
+#define MCS_WRREQ 0x0E
+#define VENDOR_READ_LENGTH (0x01)
+
+#define ZLP_REG1 0x3A /* Zero_Flag_Reg1 58 */
+#define ZLP_REG5 0x3E /* Zero_Flag_Reg5 62 */
+
+/*
+ * Vendor id and device id defines
+ *
+ * NOTE: Do not add new defines, add entries directly to the id_table instead.
+ */
+#define USB_VENDOR_ID_BANDB 0x0856
+#define BANDB_DEVICE_ID_USO9ML2_2 0xAC22
+#define BANDB_DEVICE_ID_USO9ML2_2P 0xBC00
+#define BANDB_DEVICE_ID_USO9ML2_4 0xAC24
+#define BANDB_DEVICE_ID_USO9ML2_4P 0xBC01
+#define BANDB_DEVICE_ID_US9ML2_2 0xAC29
+#define BANDB_DEVICE_ID_US9ML2_4 0xAC30
+#define BANDB_DEVICE_ID_USPTL4_2 0xAC31
+#define BANDB_DEVICE_ID_USPTL4_4 0xAC32
+#define BANDB_DEVICE_ID_USOPTL4_2 0xAC42
+#define BANDB_DEVICE_ID_USOPTL4_2P 0xBC02
+#define BANDB_DEVICE_ID_USOPTL4_4 0xAC44
+#define BANDB_DEVICE_ID_USOPTL4_4P 0xBC03
+
+/* Interrupt Routine Defines */
+
+#define SERIAL_IIR_RLS 0x06
+#define SERIAL_IIR_MS 0x00
+
+/*
+ * Emulation of the bit mask on the LINE STATUS REGISTER.
+ */
+#define SERIAL_LSR_DR 0x0001
+#define SERIAL_LSR_OE 0x0002
+#define SERIAL_LSR_PE 0x0004
+#define SERIAL_LSR_FE 0x0008
+#define SERIAL_LSR_BI 0x0010
+
+#define MOS_MSR_DELTA_CTS 0x10
+#define MOS_MSR_DELTA_DSR 0x20
+#define MOS_MSR_DELTA_RI 0x40
+#define MOS_MSR_DELTA_CD 0x80
+
+/* Serial Port register Address */
+#define INTERRUPT_ENABLE_REGISTER ((__u16)(0x01))
+#define FIFO_CONTROL_REGISTER ((__u16)(0x02))
+#define LINE_CONTROL_REGISTER ((__u16)(0x03))
+#define MODEM_CONTROL_REGISTER ((__u16)(0x04))
+#define LINE_STATUS_REGISTER ((__u16)(0x05))
+#define MODEM_STATUS_REGISTER ((__u16)(0x06))
+#define SCRATCH_PAD_REGISTER ((__u16)(0x07))
+#define DIVISOR_LATCH_LSB ((__u16)(0x00))
+#define DIVISOR_LATCH_MSB ((__u16)(0x01))
+
+#define CLK_MULTI_REGISTER ((__u16)(0x02))
+#define CLK_START_VALUE_REGISTER ((__u16)(0x03))
+#define GPIO_REGISTER ((__u16)(0x07))
+
+#define SERIAL_LCR_DLAB ((__u16)(0x0080))
+
+/*
+ * URB POOL related defines
+ */
+#define NUM_URBS 16 /* URB Count */
+#define URB_TRANSFER_BUFFER_SIZE 32 /* URB Size */
+
+/* LED on/off milliseconds*/
+#define LED_ON_MS 500
+#define LED_OFF_MS 500
+
+enum mos7840_flag {
+ MOS7840_FLAG_LED_BUSY,
+};
+
+#define MCS_PORT_MASK GENMASK(2, 0)
+#define MCS_PORTS(nr) ((nr) & MCS_PORT_MASK)
+#define MCS_LED BIT(3)
+
+#define MCS_DEVICE(vid, pid, flags) \
+ USB_DEVICE((vid), (pid)), .driver_info = (flags)
+
+static const struct usb_device_id id_table[] = {
+ { MCS_DEVICE(0x0557, 0x2011, MCS_PORTS(4)) }, /* ATEN UC2324 */
+ { MCS_DEVICE(0x0557, 0x7820, MCS_PORTS(2)) }, /* ATEN UC2322 */
+ { MCS_DEVICE(0x110a, 0x2210, MCS_PORTS(2)) }, /* Moxa UPort 2210 */
+ { MCS_DEVICE(0x9710, 0x7810, MCS_PORTS(1) | MCS_LED) }, /* ASIX MCS7810 */
+ { MCS_DEVICE(0x9710, 0x7820, MCS_PORTS(2)) }, /* MosChip MCS7820 */
+ { MCS_DEVICE(0x9710, 0x7840, MCS_PORTS(4)) }, /* MosChip MCS7840 */
+ { MCS_DEVICE(0x9710, 0x7843, MCS_PORTS(3)) }, /* ASIX MCS7840 3 port */
+ { USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_2) },
+ { USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_2P) },
+ { USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_4) },
+ { USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_4P) },
+ { USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_US9ML2_2) },
+ { USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_US9ML2_4) },
+ { USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USPTL4_2) },
+ { USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USPTL4_4) },
+ { USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USOPTL4_2) },
+ { USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USOPTL4_2P) },
+ { USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USOPTL4_4) },
+ { USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USOPTL4_4P) },
+ {} /* terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+/* This structure holds all of the local port information */
+
+struct moschip_port {
+ int port_num; /*Actual port number in the device(1,2,etc) */
+ struct urb *read_urb; /* read URB for this port */
+ __u8 shadowLCR; /* last LCR value received */
+ __u8 shadowMCR; /* last MCR value received */
+ struct usb_serial_port *port; /* loop back to the owner of this object */
+
+ /* Offsets */
+ __u8 SpRegOffset;
+ __u8 ControlRegOffset;
+ __u8 DcrRegOffset;
+
+ spinlock_t pool_lock;
+ struct urb *write_urb_pool[NUM_URBS];
+ char busy[NUM_URBS];
+ bool read_urb_busy;
+
+ /* For device(s) with LED indicator */
+ bool has_led;
+ struct timer_list led_timer1; /* Timer for LED on */
+ struct timer_list led_timer2; /* Timer for LED off */
+ struct urb *led_urb;
+ struct usb_ctrlrequest *led_dr;
+
+ unsigned long flags;
+};
+
+/*
+ * mos7840_set_reg_sync
+ * To set the Control register by calling usb_fill_control_urb function
+ * by passing usb_sndctrlpipe function as parameter.
+ */
+
+static int mos7840_set_reg_sync(struct usb_serial_port *port, __u16 reg,
+ __u16 val)
+{
+ struct usb_device *dev = port->serial->dev;
+ val = val & 0x00ff;
+ dev_dbg(&port->dev, "mos7840_set_reg_sync offset is %x, value %x\n", reg, val);
+
+ return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), MCS_WRREQ,
+ MCS_WR_RTYPE, val, reg, NULL, 0,
+ MOS_WDR_TIMEOUT);
+}
+
+/*
+ * mos7840_get_reg_sync
+ * To set the Uart register by calling usb_fill_control_urb function by
+ * passing usb_rcvctrlpipe function as parameter.
+ */
+
+static int mos7840_get_reg_sync(struct usb_serial_port *port, __u16 reg,
+ __u16 *val)
+{
+ struct usb_device *dev = port->serial->dev;
+ int ret = 0;
+ u8 *buf;
+
+ buf = kmalloc(VENDOR_READ_LENGTH, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), MCS_RDREQ,
+ MCS_RD_RTYPE, 0, reg, buf, VENDOR_READ_LENGTH,
+ MOS_WDR_TIMEOUT);
+ if (ret < VENDOR_READ_LENGTH) {
+ if (ret >= 0)
+ ret = -EIO;
+ goto out;
+ }
+
+ *val = buf[0];
+ dev_dbg(&port->dev, "%s offset is %x, return val %x\n", __func__, reg, *val);
+out:
+ kfree(buf);
+ return ret;
+}
+
+/*
+ * mos7840_set_uart_reg
+ * To set the Uart register by calling usb_fill_control_urb function by
+ * passing usb_sndctrlpipe function as parameter.
+ */
+
+static int mos7840_set_uart_reg(struct usb_serial_port *port, __u16 reg,
+ __u16 val)
+{
+
+ struct usb_device *dev = port->serial->dev;
+ val = val & 0x00ff;
+ /* For the UART control registers, the application number need
+ to be Or'ed */
+ if (port->serial->num_ports == 2 && port->port_number != 0)
+ val |= ((__u16)port->port_number + 2) << 8;
+ else
+ val |= ((__u16)port->port_number + 1) << 8;
+ dev_dbg(&port->dev, "%s application number is %x\n", __func__, val);
+ return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), MCS_WRREQ,
+ MCS_WR_RTYPE, val, reg, NULL, 0,
+ MOS_WDR_TIMEOUT);
+
+}
+
+/*
+ * mos7840_get_uart_reg
+ * To set the Control register by calling usb_fill_control_urb function
+ * by passing usb_rcvctrlpipe function as parameter.
+ */
+static int mos7840_get_uart_reg(struct usb_serial_port *port, __u16 reg,
+ __u16 *val)
+{
+ struct usb_device *dev = port->serial->dev;
+ int ret = 0;
+ __u16 Wval;
+ u8 *buf;
+
+ buf = kmalloc(VENDOR_READ_LENGTH, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* Wval is same as application number */
+ if (port->serial->num_ports == 2 && port->port_number != 0)
+ Wval = ((__u16)port->port_number + 2) << 8;
+ else
+ Wval = ((__u16)port->port_number + 1) << 8;
+ dev_dbg(&port->dev, "%s application number is %x\n", __func__, Wval);
+ ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), MCS_RDREQ,
+ MCS_RD_RTYPE, Wval, reg, buf, VENDOR_READ_LENGTH,
+ MOS_WDR_TIMEOUT);
+ if (ret < VENDOR_READ_LENGTH) {
+ if (ret >= 0)
+ ret = -EIO;
+ goto out;
+ }
+ *val = buf[0];
+out:
+ kfree(buf);
+ return ret;
+}
+
+static void mos7840_dump_serial_port(struct usb_serial_port *port,
+ struct moschip_port *mos7840_port)
+{
+
+ dev_dbg(&port->dev, "SpRegOffset is %2x\n", mos7840_port->SpRegOffset);
+ dev_dbg(&port->dev, "ControlRegOffset is %2x\n", mos7840_port->ControlRegOffset);
+ dev_dbg(&port->dev, "DCRRegOffset is %2x\n", mos7840_port->DcrRegOffset);
+
+}
+
+/************************************************************************/
+/************************************************************************/
+/* U S B C A L L B A C K F U N C T I O N S */
+/* U S B C A L L B A C K F U N C T I O N S */
+/************************************************************************/
+/************************************************************************/
+
+static void mos7840_set_led_callback(struct urb *urb)
+{
+ switch (urb->status) {
+ case 0:
+ /* Success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* This urb is terminated, clean up */
+ dev_dbg(&urb->dev->dev, "%s - urb shutting down: %d\n",
+ __func__, urb->status);
+ break;
+ default:
+ dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n",
+ __func__, urb->status);
+ }
+}
+
+static void mos7840_set_led_async(struct moschip_port *mcs, __u16 wval,
+ __u16 reg)
+{
+ struct usb_device *dev = mcs->port->serial->dev;
+ struct usb_ctrlrequest *dr = mcs->led_dr;
+
+ dr->bRequestType = MCS_WR_RTYPE;
+ dr->bRequest = MCS_WRREQ;
+ dr->wValue = cpu_to_le16(wval);
+ dr->wIndex = cpu_to_le16(reg);
+ dr->wLength = cpu_to_le16(0);
+
+ usb_fill_control_urb(mcs->led_urb, dev, usb_sndctrlpipe(dev, 0),
+ (unsigned char *)dr, NULL, 0, mos7840_set_led_callback, NULL);
+
+ usb_submit_urb(mcs->led_urb, GFP_ATOMIC);
+}
+
+static void mos7840_set_led_sync(struct usb_serial_port *port, __u16 reg,
+ __u16 val)
+{
+ struct usb_device *dev = port->serial->dev;
+
+ usb_control_msg(dev, usb_sndctrlpipe(dev, 0), MCS_WRREQ, MCS_WR_RTYPE,
+ val, reg, NULL, 0, MOS_WDR_TIMEOUT);
+}
+
+static void mos7840_led_off(struct timer_list *t)
+{
+ struct moschip_port *mcs = timer_container_of(mcs, t, led_timer1);
+
+ /* Turn off LED */
+ mos7840_set_led_async(mcs, 0x0300, MODEM_CONTROL_REGISTER);
+ mod_timer(&mcs->led_timer2,
+ jiffies + msecs_to_jiffies(LED_OFF_MS));
+}
+
+static void mos7840_led_flag_off(struct timer_list *t)
+{
+ struct moschip_port *mcs = timer_container_of(mcs, t, led_timer2);
+
+ clear_bit_unlock(MOS7840_FLAG_LED_BUSY, &mcs->flags);
+}
+
+static void mos7840_led_activity(struct usb_serial_port *port)
+{
+ struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
+
+ if (test_and_set_bit_lock(MOS7840_FLAG_LED_BUSY, &mos7840_port->flags))
+ return;
+
+ mos7840_set_led_async(mos7840_port, 0x0301, MODEM_CONTROL_REGISTER);
+ mod_timer(&mos7840_port->led_timer1,
+ jiffies + msecs_to_jiffies(LED_ON_MS));
+}
+
+/*****************************************************************************
+ * mos7840_bulk_in_callback
+ * this is the callback function for when we have received data on the
+ * bulk in endpoint.
+ *****************************************************************************/
+
+static void mos7840_bulk_in_callback(struct urb *urb)
+{
+ struct moschip_port *mos7840_port = urb->context;
+ struct usb_serial_port *port = mos7840_port->port;
+ int retval;
+ unsigned char *data;
+ int status = urb->status;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "nonzero read bulk status received: %d\n", status);
+ mos7840_port->read_urb_busy = false;
+ return;
+ }
+
+ data = urb->transfer_buffer;
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
+
+ if (urb->actual_length) {
+ struct tty_port *tport = &mos7840_port->port->port;
+ tty_insert_flip_string(tport, data, urb->actual_length);
+ tty_flip_buffer_push(tport);
+ port->icount.rx += urb->actual_length;
+ dev_dbg(&port->dev, "icount.rx is %d:\n", port->icount.rx);
+ }
+
+ if (mos7840_port->has_led)
+ mos7840_led_activity(port);
+
+ mos7840_port->read_urb_busy = true;
+ retval = usb_submit_urb(mos7840_port->read_urb, GFP_ATOMIC);
+
+ if (retval) {
+ dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, retval = %d\n", retval);
+ mos7840_port->read_urb_busy = false;
+ }
+}
+
+/*****************************************************************************
+ * mos7840_bulk_out_data_callback
+ * this is the callback function for when we have finished sending
+ * serial data on the bulk out endpoint.
+ *****************************************************************************/
+
+static void mos7840_bulk_out_data_callback(struct urb *urb)
+{
+ struct moschip_port *mos7840_port = urb->context;
+ struct usb_serial_port *port = mos7840_port->port;
+ int status = urb->status;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&mos7840_port->pool_lock, flags);
+ for (i = 0; i < NUM_URBS; i++) {
+ if (urb == mos7840_port->write_urb_pool[i]) {
+ mos7840_port->busy[i] = 0;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&mos7840_port->pool_lock, flags);
+
+ if (status) {
+ dev_dbg(&port->dev, "nonzero write bulk status received:%d\n", status);
+ return;
+ }
+
+ tty_port_tty_wakeup(&port->port);
+
+}
+
+/************************************************************************/
+/* D R I V E R T T Y I N T E R F A C E F U N C T I O N S */
+/************************************************************************/
+
+/*****************************************************************************
+ * mos7840_open
+ * this function is called by the tty driver when a port is opened
+ * If successful, we return 0
+ * Otherwise we return a negative error number.
+ *****************************************************************************/
+
+static int mos7840_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ int response;
+ int j;
+ struct urb *urb;
+ __u16 Data;
+ int status;
+
+ usb_clear_halt(serial->dev, port->write_urb->pipe);
+ usb_clear_halt(serial->dev, port->read_urb->pipe);
+
+ /* Initialising the write urb pool */
+ for (j = 0; j < NUM_URBS; ++j) {
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ mos7840_port->write_urb_pool[j] = urb;
+ if (!urb)
+ continue;
+
+ urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE,
+ GFP_KERNEL);
+ if (!urb->transfer_buffer) {
+ usb_free_urb(urb);
+ mos7840_port->write_urb_pool[j] = NULL;
+ continue;
+ }
+ }
+
+/*****************************************************************************
+ * Initialize MCS7840 -- Write Init values to corresponding Registers
+ *
+ * Register Index
+ * 1 : IER
+ * 2 : FCR
+ * 3 : LCR
+ * 4 : MCR
+ *
+ * 0x08 : SP1/2 Control Reg
+ *****************************************************************************/
+
+ /* NEED to check the following Block */
+
+ Data = 0x0;
+ status = mos7840_get_reg_sync(port, mos7840_port->SpRegOffset, &Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Reading Spreg failed\n");
+ goto err;
+ }
+ Data |= 0x80;
+ status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset, Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "writing Spreg failed\n");
+ goto err;
+ }
+
+ Data &= ~0x80;
+ status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset, Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "writing Spreg failed\n");
+ goto err;
+ }
+ /* End of block to be checked */
+
+ Data = 0x0;
+ status = mos7840_get_reg_sync(port, mos7840_port->ControlRegOffset,
+ &Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Reading Controlreg failed\n");
+ goto err;
+ }
+ Data |= 0x08; /* Driver done bit */
+ Data |= 0x20; /* rx_disable */
+ status = mos7840_set_reg_sync(port,
+ mos7840_port->ControlRegOffset, Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "writing Controlreg failed\n");
+ goto err;
+ }
+ /* do register settings here */
+ /* Set all regs to the device default values. */
+ /***********************************
+ * First Disable all interrupts.
+ ***********************************/
+ Data = 0x00;
+ status = mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "disabling interrupts failed\n");
+ goto err;
+ }
+ /* Set FIFO_CONTROL_REGISTER to the default value */
+ Data = 0x00;
+ status = mos7840_set_uart_reg(port, FIFO_CONTROL_REGISTER, Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing FIFO_CONTROL_REGISTER failed\n");
+ goto err;
+ }
+
+ Data = 0xcf;
+ status = mos7840_set_uart_reg(port, FIFO_CONTROL_REGISTER, Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing FIFO_CONTROL_REGISTER failed\n");
+ goto err;
+ }
+
+ Data = 0x03;
+ status = mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data);
+ mos7840_port->shadowLCR = Data;
+
+ Data = 0x0b;
+ status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data);
+ mos7840_port->shadowMCR = Data;
+
+ Data = 0x00;
+ status = mos7840_get_uart_reg(port, LINE_CONTROL_REGISTER, &Data);
+ mos7840_port->shadowLCR = Data;
+
+ Data |= SERIAL_LCR_DLAB; /* data latch enable in LCR 0x80 */
+ status = mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data);
+
+ Data = 0x0c;
+ status = mos7840_set_uart_reg(port, DIVISOR_LATCH_LSB, Data);
+
+ Data = 0x0;
+ status = mos7840_set_uart_reg(port, DIVISOR_LATCH_MSB, Data);
+
+ Data = 0x00;
+ status = mos7840_get_uart_reg(port, LINE_CONTROL_REGISTER, &Data);
+
+ Data = Data & ~SERIAL_LCR_DLAB;
+ status = mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data);
+ mos7840_port->shadowLCR = Data;
+
+ /* clearing Bulkin and Bulkout Fifo */
+ Data = 0x0;
+ status = mos7840_get_reg_sync(port, mos7840_port->SpRegOffset, &Data);
+
+ Data = Data | 0x0c;
+ status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset, Data);
+
+ Data = Data & ~0x0c;
+ status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset, Data);
+ /* Finally enable all interrupts */
+ Data = 0x0c;
+ status = mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data);
+
+ /* clearing rx_disable */
+ Data = 0x0;
+ status = mos7840_get_reg_sync(port, mos7840_port->ControlRegOffset,
+ &Data);
+ Data = Data & ~0x20;
+ status = mos7840_set_reg_sync(port, mos7840_port->ControlRegOffset,
+ Data);
+
+ /* rx_negate */
+ Data = 0x0;
+ status = mos7840_get_reg_sync(port, mos7840_port->ControlRegOffset,
+ &Data);
+ Data = Data | 0x10;
+ status = mos7840_set_reg_sync(port, mos7840_port->ControlRegOffset,
+ Data);
+
+ dev_dbg(&port->dev, "port number is %d\n", port->port_number);
+ dev_dbg(&port->dev, "minor number is %d\n", port->minor);
+ dev_dbg(&port->dev, "Bulkin endpoint is %d\n", port->bulk_in_endpointAddress);
+ dev_dbg(&port->dev, "BulkOut endpoint is %d\n", port->bulk_out_endpointAddress);
+ dev_dbg(&port->dev, "Interrupt endpoint is %d\n", port->interrupt_in_endpointAddress);
+ dev_dbg(&port->dev, "port's number in the device is %d\n", mos7840_port->port_num);
+ mos7840_port->read_urb = port->read_urb;
+
+ /* set up our bulk in urb */
+ if ((serial->num_ports == 2) && (((__u16)port->port_number % 2) != 0)) {
+ usb_fill_bulk_urb(mos7840_port->read_urb,
+ serial->dev,
+ usb_rcvbulkpipe(serial->dev,
+ (port->bulk_in_endpointAddress) + 2),
+ port->bulk_in_buffer,
+ mos7840_port->read_urb->transfer_buffer_length,
+ mos7840_bulk_in_callback, mos7840_port);
+ } else {
+ usb_fill_bulk_urb(mos7840_port->read_urb,
+ serial->dev,
+ usb_rcvbulkpipe(serial->dev,
+ port->bulk_in_endpointAddress),
+ port->bulk_in_buffer,
+ mos7840_port->read_urb->transfer_buffer_length,
+ mos7840_bulk_in_callback, mos7840_port);
+ }
+
+ dev_dbg(&port->dev, "%s: bulkin endpoint is %d\n", __func__, port->bulk_in_endpointAddress);
+ mos7840_port->read_urb_busy = true;
+ response = usb_submit_urb(mos7840_port->read_urb, GFP_KERNEL);
+ if (response) {
+ dev_err(&port->dev, "%s - Error %d submitting control urb\n",
+ __func__, response);
+ mos7840_port->read_urb_busy = false;
+ }
+
+ /* initialize our port settings */
+ /* Must set to enable ints! */
+ mos7840_port->shadowMCR = MCR_MASTER_IE;
+
+ return 0;
+err:
+ for (j = 0; j < NUM_URBS; ++j) {
+ urb = mos7840_port->write_urb_pool[j];
+ if (!urb)
+ continue;
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ return status;
+}
+
+/*****************************************************************************
+ * mos7840_chars_in_buffer
+ * this function is called by the tty driver when it wants to know how many
+ * bytes of data we currently have outstanding in the port (data that has
+ * been written, but hasn't made it out the port yet)
+ *****************************************************************************/
+
+static unsigned int mos7840_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
+ int i;
+ unsigned int chars = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mos7840_port->pool_lock, flags);
+ for (i = 0; i < NUM_URBS; ++i) {
+ if (mos7840_port->busy[i]) {
+ struct urb *urb = mos7840_port->write_urb_pool[i];
+ chars += urb->transfer_buffer_length;
+ }
+ }
+ spin_unlock_irqrestore(&mos7840_port->pool_lock, flags);
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, chars);
+ return chars;
+
+}
+
+/*****************************************************************************
+ * mos7840_close
+ * this function is called by the tty driver when a port is closed
+ *****************************************************************************/
+
+static void mos7840_close(struct usb_serial_port *port)
+{
+ struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
+ int j;
+ __u16 Data;
+
+ for (j = 0; j < NUM_URBS; ++j)
+ usb_kill_urb(mos7840_port->write_urb_pool[j]);
+
+ /* Freeing Write URBs */
+ for (j = 0; j < NUM_URBS; ++j) {
+ if (mos7840_port->write_urb_pool[j]) {
+ kfree(mos7840_port->write_urb_pool[j]->transfer_buffer);
+ usb_free_urb(mos7840_port->write_urb_pool[j]);
+ }
+ }
+
+ usb_kill_urb(mos7840_port->read_urb);
+ mos7840_port->read_urb_busy = false;
+
+ Data = 0x0;
+ mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data);
+
+ Data = 0x00;
+ mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data);
+}
+
+/*****************************************************************************
+ * mos7840_break
+ * this function sends a break to the port
+ *****************************************************************************/
+static int mos7840_break(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
+ unsigned char data;
+
+ if (break_state == -1)
+ data = mos7840_port->shadowLCR | LCR_SET_BREAK;
+ else
+ data = mos7840_port->shadowLCR & ~LCR_SET_BREAK;
+
+ /* FIXME: no locking on shadowLCR anywhere in driver */
+ mos7840_port->shadowLCR = data;
+ dev_dbg(&port->dev, "%s mos7840_port->shadowLCR is %x\n", __func__, mos7840_port->shadowLCR);
+
+ return mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER,
+ mos7840_port->shadowLCR);
+}
+
+/*****************************************************************************
+ * mos7840_write_room
+ * this function is called by the tty driver when it wants to know how many
+ * bytes of data we can accept for a specific port.
+ *****************************************************************************/
+
+static unsigned int mos7840_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
+ int i;
+ unsigned int room = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mos7840_port->pool_lock, flags);
+ for (i = 0; i < NUM_URBS; ++i) {
+ if (!mos7840_port->busy[i])
+ room += URB_TRANSFER_BUFFER_SIZE;
+ }
+ spin_unlock_irqrestore(&mos7840_port->pool_lock, flags);
+
+ room = (room == 0) ? 0 : room - URB_TRANSFER_BUFFER_SIZE + 1;
+ dev_dbg(&mos7840_port->port->dev, "%s - returns %u\n", __func__, room);
+ return room;
+
+}
+
+/*****************************************************************************
+ * mos7840_write
+ * this function is called by the tty driver when data should be written to
+ * the port.
+ * If successful, we return the number of bytes written, otherwise we
+ * return a negative error number.
+ *****************************************************************************/
+
+static int mos7840_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *data, int count)
+{
+ struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ int status;
+ int i;
+ int bytes_sent = 0;
+ int transfer_size;
+ unsigned long flags;
+ struct urb *urb;
+ /* __u16 Data; */
+ const unsigned char *current_position = data;
+
+ /* try to find a free urb in the list */
+ urb = NULL;
+
+ spin_lock_irqsave(&mos7840_port->pool_lock, flags);
+ for (i = 0; i < NUM_URBS; ++i) {
+ if (!mos7840_port->busy[i]) {
+ mos7840_port->busy[i] = 1;
+ urb = mos7840_port->write_urb_pool[i];
+ dev_dbg(&port->dev, "URB:%d\n", i);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&mos7840_port->pool_lock, flags);
+
+ if (urb == NULL) {
+ dev_dbg(&port->dev, "%s - no more free urbs\n", __func__);
+ goto exit;
+ }
+
+ if (urb->transfer_buffer == NULL) {
+ urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE,
+ GFP_ATOMIC);
+ if (!urb->transfer_buffer) {
+ bytes_sent = -ENOMEM;
+ goto exit;
+ }
+ }
+ transfer_size = min(count, URB_TRANSFER_BUFFER_SIZE);
+
+ memcpy(urb->transfer_buffer, current_position, transfer_size);
+
+ /* fill urb with data and submit */
+ if ((serial->num_ports == 2) && (((__u16)port->port_number % 2) != 0)) {
+ usb_fill_bulk_urb(urb,
+ serial->dev,
+ usb_sndbulkpipe(serial->dev,
+ (port->bulk_out_endpointAddress) + 2),
+ urb->transfer_buffer,
+ transfer_size,
+ mos7840_bulk_out_data_callback, mos7840_port);
+ } else {
+ usb_fill_bulk_urb(urb,
+ serial->dev,
+ usb_sndbulkpipe(serial->dev,
+ port->bulk_out_endpointAddress),
+ urb->transfer_buffer,
+ transfer_size,
+ mos7840_bulk_out_data_callback, mos7840_port);
+ }
+
+ dev_dbg(&port->dev, "bulkout endpoint is %d\n", port->bulk_out_endpointAddress);
+
+ if (mos7840_port->has_led)
+ mos7840_led_activity(port);
+
+ /* send it down the pipe */
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+
+ if (status) {
+ mos7840_port->busy[i] = 0;
+ dev_err_console(port, "%s - usb_submit_urb(write bulk) failed "
+ "with status = %d\n", __func__, status);
+ bytes_sent = status;
+ goto exit;
+ }
+ bytes_sent = transfer_size;
+ port->icount.tx += transfer_size;
+ dev_dbg(&port->dev, "icount.tx is %d:\n", port->icount.tx);
+exit:
+ return bytes_sent;
+
+}
+
+/*****************************************************************************
+ * mos7840_throttle
+ * this function is called by the tty driver when it wants to stop the data
+ * being read from the port.
+ *****************************************************************************/
+
+static void mos7840_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
+ int status;
+
+ /* if we are implementing XON/XOFF, send the stop character */
+ if (I_IXOFF(tty)) {
+ unsigned char stop_char = STOP_CHAR(tty);
+ status = mos7840_write(tty, port, &stop_char, 1);
+ if (status <= 0)
+ return;
+ }
+ /* if we are implementing RTS/CTS, toggle that line */
+ if (C_CRTSCTS(tty)) {
+ mos7840_port->shadowMCR &= ~MCR_RTS;
+ status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER,
+ mos7840_port->shadowMCR);
+ if (status < 0)
+ return;
+ }
+}
+
+/*****************************************************************************
+ * mos7840_unthrottle
+ * this function is called by the tty driver when it wants to resume
+ * the data being read from the port (called after mos7840_throttle is
+ * called)
+ *****************************************************************************/
+static void mos7840_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
+ int status;
+
+ /* if we are implementing XON/XOFF, send the start character */
+ if (I_IXOFF(tty)) {
+ unsigned char start_char = START_CHAR(tty);
+ status = mos7840_write(tty, port, &start_char, 1);
+ if (status <= 0)
+ return;
+ }
+
+ /* if we are implementing RTS/CTS, toggle that line */
+ if (C_CRTSCTS(tty)) {
+ mos7840_port->shadowMCR |= MCR_RTS;
+ status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER,
+ mos7840_port->shadowMCR);
+ if (status < 0)
+ return;
+ }
+}
+
+static int mos7840_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned int result;
+ __u16 msr;
+ __u16 mcr;
+ int status;
+
+ status = mos7840_get_uart_reg(port, MODEM_STATUS_REGISTER, &msr);
+ if (status < 0)
+ return -EIO;
+ status = mos7840_get_uart_reg(port, MODEM_CONTROL_REGISTER, &mcr);
+ if (status < 0)
+ return -EIO;
+ result = ((mcr & MCR_DTR) ? TIOCM_DTR : 0)
+ | ((mcr & MCR_RTS) ? TIOCM_RTS : 0)
+ | ((mcr & MCR_LOOPBACK) ? TIOCM_LOOP : 0)
+ | ((msr & MOS7840_MSR_CTS) ? TIOCM_CTS : 0)
+ | ((msr & MOS7840_MSR_CD) ? TIOCM_CAR : 0)
+ | ((msr & MOS7840_MSR_RI) ? TIOCM_RI : 0)
+ | ((msr & MOS7840_MSR_DSR) ? TIOCM_DSR : 0);
+
+ dev_dbg(&port->dev, "%s - 0x%04X\n", __func__, result);
+
+ return result;
+}
+
+static int mos7840_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
+ unsigned int mcr;
+ int status;
+
+ /* FIXME: What locks the port registers ? */
+ mcr = mos7840_port->shadowMCR;
+ if (clear & TIOCM_RTS)
+ mcr &= ~MCR_RTS;
+ if (clear & TIOCM_DTR)
+ mcr &= ~MCR_DTR;
+ if (clear & TIOCM_LOOP)
+ mcr &= ~MCR_LOOPBACK;
+
+ if (set & TIOCM_RTS)
+ mcr |= MCR_RTS;
+ if (set & TIOCM_DTR)
+ mcr |= MCR_DTR;
+ if (set & TIOCM_LOOP)
+ mcr |= MCR_LOOPBACK;
+
+ mos7840_port->shadowMCR = mcr;
+
+ status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, mcr);
+ if (status < 0) {
+ dev_dbg(&port->dev, "setting MODEM_CONTROL_REGISTER Failed\n");
+ return status;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************
+ * mos7840_calc_baud_rate_divisor
+ * this function calculates the proper baud rate divisor for the specified
+ * baud rate.
+ *****************************************************************************/
+static int mos7840_calc_baud_rate_divisor(struct usb_serial_port *port,
+ int baudRate, int *divisor,
+ __u16 *clk_sel_val)
+{
+ dev_dbg(&port->dev, "%s - %d\n", __func__, baudRate);
+
+ if (baudRate <= 115200) {
+ *divisor = 115200 / baudRate;
+ *clk_sel_val = 0x0;
+ }
+ if ((baudRate > 115200) && (baudRate <= 230400)) {
+ *divisor = 230400 / baudRate;
+ *clk_sel_val = 0x10;
+ } else if ((baudRate > 230400) && (baudRate <= 403200)) {
+ *divisor = 403200 / baudRate;
+ *clk_sel_val = 0x20;
+ } else if ((baudRate > 403200) && (baudRate <= 460800)) {
+ *divisor = 460800 / baudRate;
+ *clk_sel_val = 0x30;
+ } else if ((baudRate > 460800) && (baudRate <= 806400)) {
+ *divisor = 806400 / baudRate;
+ *clk_sel_val = 0x40;
+ } else if ((baudRate > 806400) && (baudRate <= 921600)) {
+ *divisor = 921600 / baudRate;
+ *clk_sel_val = 0x50;
+ } else if ((baudRate > 921600) && (baudRate <= 1572864)) {
+ *divisor = 1572864 / baudRate;
+ *clk_sel_val = 0x60;
+ } else if ((baudRate > 1572864) && (baudRate <= 3145728)) {
+ *divisor = 3145728 / baudRate;
+ *clk_sel_val = 0x70;
+ }
+ return 0;
+}
+
+/*****************************************************************************
+ * mos7840_send_cmd_write_baud_rate
+ * this function sends the proper command to change the baud rate of the
+ * specified port.
+ *****************************************************************************/
+
+static int mos7840_send_cmd_write_baud_rate(struct moschip_port *mos7840_port,
+ int baudRate)
+{
+ struct usb_serial_port *port = mos7840_port->port;
+ int divisor = 0;
+ int status;
+ __u16 Data;
+ __u16 clk_sel_val;
+
+ dev_dbg(&port->dev, "%s - baud = %d\n", __func__, baudRate);
+ /* reset clk_uart_sel in spregOffset */
+ if (baudRate > 115200) {
+#ifdef HW_flow_control
+ /* NOTE: need to see the pther register to modify */
+ /* setting h/w flow control bit to 1 */
+ Data = 0x2b;
+ mos7840_port->shadowMCR = Data;
+ status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER,
+ Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing spreg failed in set_serial_baud\n");
+ return -1;
+ }
+#endif
+
+ } else {
+#ifdef HW_flow_control
+ /* setting h/w flow control bit to 0 */
+ Data = 0xb;
+ mos7840_port->shadowMCR = Data;
+ status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER,
+ Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing spreg failed in set_serial_baud\n");
+ return -1;
+ }
+#endif
+
+ }
+
+ if (1) { /* baudRate <= 115200) */
+ clk_sel_val = 0x0;
+ Data = 0x0;
+ status = mos7840_calc_baud_rate_divisor(port, baudRate, &divisor,
+ &clk_sel_val);
+ status = mos7840_get_reg_sync(port, mos7840_port->SpRegOffset,
+ &Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "reading spreg failed in set_serial_baud\n");
+ return -1;
+ }
+ Data = (Data & 0x8f) | clk_sel_val;
+ status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset,
+ Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing spreg failed in set_serial_baud\n");
+ return -1;
+ }
+ /* Calculate the Divisor */
+
+ if (status) {
+ dev_err(&port->dev, "%s - bad baud rate\n", __func__);
+ return status;
+ }
+ /* Enable access to divisor latch */
+ Data = mos7840_port->shadowLCR | SERIAL_LCR_DLAB;
+ mos7840_port->shadowLCR = Data;
+ mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data);
+
+ /* Write the divisor */
+ Data = (unsigned char)(divisor & 0xff);
+ dev_dbg(&port->dev, "set_serial_baud Value to write DLL is %x\n", Data);
+ mos7840_set_uart_reg(port, DIVISOR_LATCH_LSB, Data);
+
+ Data = (unsigned char)((divisor & 0xff00) >> 8);
+ dev_dbg(&port->dev, "set_serial_baud Value to write DLM is %x\n", Data);
+ mos7840_set_uart_reg(port, DIVISOR_LATCH_MSB, Data);
+
+ /* Disable access to divisor latch */
+ Data = mos7840_port->shadowLCR & ~SERIAL_LCR_DLAB;
+ mos7840_port->shadowLCR = Data;
+ mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data);
+
+ }
+ return status;
+}
+
+/*****************************************************************************
+ * mos7840_change_port_settings
+ * This routine is called to set the UART on the device to match
+ * the specified new settings.
+ *****************************************************************************/
+
+static void mos7840_change_port_settings(struct tty_struct *tty,
+ struct moschip_port *mos7840_port,
+ const struct ktermios *old_termios)
+{
+ struct usb_serial_port *port = mos7840_port->port;
+ int baud;
+ unsigned cflag;
+ __u8 lData;
+ __u8 lParity;
+ __u8 lStop;
+ int status;
+ __u16 Data;
+
+ lData = LCR_BITS_8;
+ lStop = LCR_STOP_1;
+ lParity = LCR_PAR_NONE;
+
+ cflag = tty->termios.c_cflag;
+
+ /* Change the number of bits */
+ switch (cflag & CSIZE) {
+ case CS5:
+ lData = LCR_BITS_5;
+ break;
+
+ case CS6:
+ lData = LCR_BITS_6;
+ break;
+
+ case CS7:
+ lData = LCR_BITS_7;
+ break;
+
+ default:
+ case CS8:
+ lData = LCR_BITS_8;
+ break;
+ }
+
+ /* Change the Parity bit */
+ if (cflag & PARENB) {
+ if (cflag & PARODD) {
+ lParity = LCR_PAR_ODD;
+ dev_dbg(&port->dev, "%s - parity = odd\n", __func__);
+ } else {
+ lParity = LCR_PAR_EVEN;
+ dev_dbg(&port->dev, "%s - parity = even\n", __func__);
+ }
+
+ } else {
+ dev_dbg(&port->dev, "%s - parity = none\n", __func__);
+ }
+
+ if (cflag & CMSPAR)
+ lParity = lParity | 0x20;
+
+ /* Change the Stop bit */
+ if (cflag & CSTOPB) {
+ lStop = LCR_STOP_2;
+ dev_dbg(&port->dev, "%s - stop bits = 2\n", __func__);
+ } else {
+ lStop = LCR_STOP_1;
+ dev_dbg(&port->dev, "%s - stop bits = 1\n", __func__);
+ }
+
+ /* Update the LCR with the correct value */
+ mos7840_port->shadowLCR &=
+ ~(LCR_BITS_MASK | LCR_STOP_MASK | LCR_PAR_MASK);
+ mos7840_port->shadowLCR |= (lData | lParity | lStop);
+
+ dev_dbg(&port->dev, "%s - mos7840_port->shadowLCR is %x\n", __func__,
+ mos7840_port->shadowLCR);
+ /* Disable Interrupts */
+ Data = 0x00;
+ mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data);
+
+ Data = 0x00;
+ mos7840_set_uart_reg(port, FIFO_CONTROL_REGISTER, Data);
+
+ Data = 0xcf;
+ mos7840_set_uart_reg(port, FIFO_CONTROL_REGISTER, Data);
+
+ /* Send the updated LCR value to the mos7840 */
+ Data = mos7840_port->shadowLCR;
+
+ mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data);
+
+ Data = 0x00b;
+ mos7840_port->shadowMCR = Data;
+ mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data);
+ Data = 0x00b;
+ mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data);
+
+ /* set up the MCR register and send it to the mos7840 */
+
+ mos7840_port->shadowMCR = MCR_MASTER_IE;
+ if (cflag & CBAUD)
+ mos7840_port->shadowMCR |= (MCR_DTR | MCR_RTS);
+
+ if (cflag & CRTSCTS)
+ mos7840_port->shadowMCR |= (MCR_XON_ANY);
+ else
+ mos7840_port->shadowMCR &= ~(MCR_XON_ANY);
+
+ Data = mos7840_port->shadowMCR;
+ mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data);
+
+ /* Determine divisor based on baud rate */
+ baud = tty_get_baud_rate(tty);
+
+ if (!baud) {
+ /* pick a default, any default... */
+ dev_dbg(&port->dev, "%s", "Picked default baud...\n");
+ baud = 9600;
+ }
+
+ dev_dbg(&port->dev, "%s - baud rate = %d\n", __func__, baud);
+ status = mos7840_send_cmd_write_baud_rate(mos7840_port, baud);
+
+ /* Enable Interrupts */
+ Data = 0x0c;
+ mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data);
+
+ if (!mos7840_port->read_urb_busy) {
+ mos7840_port->read_urb_busy = true;
+ status = usb_submit_urb(mos7840_port->read_urb, GFP_KERNEL);
+ if (status) {
+ dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, status = %d\n",
+ status);
+ mos7840_port->read_urb_busy = false;
+ }
+ }
+ dev_dbg(&port->dev, "%s - mos7840_port->shadowLCR is End %x\n", __func__,
+ mos7840_port->shadowLCR);
+}
+
+/*****************************************************************************
+ * mos7840_set_termios
+ * this function is called by the tty driver when it wants to change
+ * the termios structure
+ *****************************************************************************/
+
+static void mos7840_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
+ int status;
+
+ /* change the port settings to the new ones specified */
+
+ mos7840_change_port_settings(tty, mos7840_port, old_termios);
+
+ if (!mos7840_port->read_urb_busy) {
+ mos7840_port->read_urb_busy = true;
+ status = usb_submit_urb(mos7840_port->read_urb, GFP_KERNEL);
+ if (status) {
+ dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, status = %d\n",
+ status);
+ mos7840_port->read_urb_busy = false;
+ }
+ }
+}
+
+/*****************************************************************************
+ * mos7840_get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ * is emptied. On bus types like RS485, the transmitter must
+ * release the bus after transmitting. This must be done when
+ * the transmit shift register is empty, not be done when the
+ * transmit holding register is empty. This functionality
+ * allows an RS485 driver to be written in user space.
+ *****************************************************************************/
+
+static int mos7840_get_lsr_info(struct tty_struct *tty,
+ unsigned int __user *value)
+{
+ int count;
+ unsigned int result = 0;
+
+ count = mos7840_chars_in_buffer(tty);
+ if (count == 0)
+ result = TIOCSER_TEMT;
+
+ if (copy_to_user(value, &result, sizeof(int)))
+ return -EFAULT;
+ return 0;
+}
+
+/*****************************************************************************
+ * SerialIoctl
+ * this function handles any ioctl calls to the driver
+ *****************************************************************************/
+
+static int mos7840_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ void __user *argp = (void __user *)arg;
+
+ switch (cmd) {
+ /* return number of bytes available */
+
+ case TIOCSERGETLSR:
+ dev_dbg(&port->dev, "%s TIOCSERGETLSR\n", __func__);
+ return mos7840_get_lsr_info(tty, argp);
+
+ default:
+ break;
+ }
+ return -ENOIOCTLCMD;
+}
+
+/*
+ * Check if GPO (pin 42) is connected to GPI (pin 33) as recommended by ASIX
+ * for MCS7810 by bit-banging a 16-bit word.
+ *
+ * Note that GPO is really RTS of the third port so this will toggle RTS of
+ * port two or three on two- and four-port devices.
+ */
+static int mos7810_check(struct usb_serial *serial)
+{
+ int i, pass_count = 0;
+ u8 *buf;
+ __u16 data = 0, mcr_data = 0;
+ __u16 test_pattern = 0x55AA;
+ int res;
+
+ buf = kmalloc(VENDOR_READ_LENGTH, GFP_KERNEL);
+ if (!buf)
+ return 0; /* failed to identify 7810 */
+
+ /* Store MCR setting */
+ res = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+ MCS_RDREQ, MCS_RD_RTYPE, 0x0300, MODEM_CONTROL_REGISTER,
+ buf, VENDOR_READ_LENGTH, MOS_WDR_TIMEOUT);
+ if (res == VENDOR_READ_LENGTH)
+ mcr_data = *buf;
+
+ for (i = 0; i < 16; i++) {
+ /* Send the 1-bit test pattern out to MCS7810 test pin */
+ usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ MCS_WRREQ, MCS_WR_RTYPE,
+ (0x0300 | (((test_pattern >> i) & 0x0001) << 1)),
+ MODEM_CONTROL_REGISTER, NULL, 0, MOS_WDR_TIMEOUT);
+
+ /* Read the test pattern back */
+ res = usb_control_msg(serial->dev,
+ usb_rcvctrlpipe(serial->dev, 0), MCS_RDREQ,
+ MCS_RD_RTYPE, 0, GPIO_REGISTER, buf,
+ VENDOR_READ_LENGTH, MOS_WDR_TIMEOUT);
+ if (res == VENDOR_READ_LENGTH)
+ data = *buf;
+
+ /* If this is a MCS7810 device, both test patterns must match */
+ if (((test_pattern >> i) ^ (~data >> 1)) & 0x0001)
+ break;
+
+ pass_count++;
+ }
+
+ /* Restore MCR setting */
+ usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), MCS_WRREQ,
+ MCS_WR_RTYPE, 0x0300 | mcr_data, MODEM_CONTROL_REGISTER, NULL,
+ 0, MOS_WDR_TIMEOUT);
+
+ kfree(buf);
+
+ if (pass_count == 16)
+ return 1;
+
+ return 0;
+}
+
+static int mos7840_probe(struct usb_serial *serial,
+ const struct usb_device_id *id)
+{
+ unsigned long device_flags = id->driver_info;
+ u8 *buf;
+
+ /* Skip device-type detection if we already have device flags. */
+ if (device_flags)
+ goto out;
+
+ buf = kzalloc(VENDOR_READ_LENGTH, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+ MCS_RDREQ, MCS_RD_RTYPE, 0, GPIO_REGISTER, buf,
+ VENDOR_READ_LENGTH, MOS_WDR_TIMEOUT);
+
+ /* For a MCS7840 device GPIO0 must be set to 1 */
+ if (buf[0] & 0x01)
+ device_flags = MCS_PORTS(4);
+ else if (mos7810_check(serial))
+ device_flags = MCS_PORTS(1) | MCS_LED;
+ else
+ device_flags = MCS_PORTS(2);
+
+ kfree(buf);
+out:
+ usb_set_serial_data(serial, (void *)device_flags);
+
+ return 0;
+}
+
+static int mos7840_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ unsigned long device_flags = (unsigned long)usb_get_serial_data(serial);
+ int num_ports = MCS_PORTS(device_flags);
+
+ if (num_ports == 0 || num_ports > 4)
+ return -ENODEV;
+
+ if (epds->num_bulk_in < num_ports || epds->num_bulk_out < num_ports) {
+ dev_err(&serial->interface->dev, "missing endpoints\n");
+ return -ENODEV;
+ }
+
+ return num_ports;
+}
+
+static int mos7840_attach(struct usb_serial *serial)
+{
+ struct device *dev = &serial->interface->dev;
+ int status;
+ u16 val;
+
+ /* Zero Length flag enable */
+ val = 0x0f;
+ status = mos7840_set_reg_sync(serial->port[0], ZLP_REG5, val);
+ if (status < 0)
+ dev_dbg(dev, "Writing ZLP_REG5 failed status-0x%x\n", status);
+ else
+ dev_dbg(dev, "ZLP_REG5 Writing success status%d\n", status);
+
+ return status;
+}
+
+static int mos7840_port_probe(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ unsigned long device_flags = (unsigned long)usb_get_serial_data(serial);
+ struct moschip_port *mos7840_port;
+ int status;
+ int pnum;
+ __u16 Data;
+
+ /* we set up the pointers to the endpoints in the mos7840_open *
+ * function, as the structures aren't created yet. */
+
+ pnum = port->port_number;
+
+ dev_dbg(&port->dev, "mos7840_startup: configuring port %d\n", pnum);
+ mos7840_port = kzalloc_obj(struct moschip_port);
+ if (!mos7840_port)
+ return -ENOMEM;
+
+ /* Initialize all port interrupt end point to port 0 int
+ * endpoint. Our device has only one interrupt end point
+ * common to all port */
+
+ mos7840_port->port = port;
+ spin_lock_init(&mos7840_port->pool_lock);
+
+ /* minor is not initialised until later by
+ * usb-serial.c:get_free_serial() and cannot therefore be used
+ * to index device instances */
+ mos7840_port->port_num = pnum + 1;
+ dev_dbg(&port->dev, "port->minor = %d\n", port->minor);
+ dev_dbg(&port->dev, "mos7840_port->port_num = %d\n", mos7840_port->port_num);
+
+ if (mos7840_port->port_num == 1) {
+ mos7840_port->SpRegOffset = 0x0;
+ mos7840_port->ControlRegOffset = 0x1;
+ mos7840_port->DcrRegOffset = 0x4;
+ } else {
+ u8 phy_num = mos7840_port->port_num;
+
+ /* Port 2 in the 2-port case uses registers of port 3 */
+ if (serial->num_ports == 2)
+ phy_num = 3;
+
+ mos7840_port->SpRegOffset = 0x8 + 2 * (phy_num - 2);
+ mos7840_port->ControlRegOffset = 0x9 + 2 * (phy_num - 2);
+ mos7840_port->DcrRegOffset = 0x16 + 3 * (phy_num - 2);
+ }
+ mos7840_dump_serial_port(port, mos7840_port);
+ usb_set_serial_port_data(port, mos7840_port);
+
+ /* enable rx_disable bit in control register */
+ status = mos7840_get_reg_sync(port,
+ mos7840_port->ControlRegOffset, &Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Reading ControlReg failed status-0x%x\n", status);
+ goto error;
+ } else
+ dev_dbg(&port->dev, "ControlReg Reading success val is %x, status%d\n", Data, status);
+ Data |= 0x08; /* setting driver done bit */
+ Data |= 0x04; /* sp1_bit to have cts change reflect in
+ modem status reg */
+
+ /* Data |= 0x20; //rx_disable bit */
+ status = mos7840_set_reg_sync(port,
+ mos7840_port->ControlRegOffset, Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing ControlReg failed(rx_disable) status-0x%x\n", status);
+ goto error;
+ } else
+ dev_dbg(&port->dev, "ControlReg Writing success(rx_disable) status%d\n", status);
+
+ /* Write default values in DCR (i.e 0x01 in DCR0, 0x05 in DCR2
+ and 0x24 in DCR3 */
+ Data = 0x01;
+ status = mos7840_set_reg_sync(port,
+ (__u16) (mos7840_port->DcrRegOffset + 0), Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing DCR0 failed status-0x%x\n", status);
+ goto error;
+ } else
+ dev_dbg(&port->dev, "DCR0 Writing success status%d\n", status);
+
+ Data = 0x05;
+ status = mos7840_set_reg_sync(port,
+ (__u16) (mos7840_port->DcrRegOffset + 1), Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing DCR1 failed status-0x%x\n", status);
+ goto error;
+ } else
+ dev_dbg(&port->dev, "DCR1 Writing success status%d\n", status);
+
+ Data = 0x24;
+ status = mos7840_set_reg_sync(port,
+ (__u16) (mos7840_port->DcrRegOffset + 2), Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing DCR2 failed status-0x%x\n", status);
+ goto error;
+ } else
+ dev_dbg(&port->dev, "DCR2 Writing success status%d\n", status);
+
+ /* write values in clkstart0x0 and clkmulti 0x20 */
+ Data = 0x0;
+ status = mos7840_set_reg_sync(port, CLK_START_VALUE_REGISTER, Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing CLK_START_VALUE_REGISTER failed status-0x%x\n", status);
+ goto error;
+ } else
+ dev_dbg(&port->dev, "CLK_START_VALUE_REGISTER Writing success status%d\n", status);
+
+ Data = 0x20;
+ status = mos7840_set_reg_sync(port, CLK_MULTI_REGISTER, Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing CLK_MULTI_REGISTER failed status-0x%x\n", status);
+ goto error;
+ } else
+ dev_dbg(&port->dev, "CLK_MULTI_REGISTER Writing success status%d\n", status);
+
+ /* write value 0x0 to scratchpad register */
+ Data = 0x00;
+ status = mos7840_set_uart_reg(port, SCRATCH_PAD_REGISTER, Data);
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing SCRATCH_PAD_REGISTER failed status-0x%x\n", status);
+ goto error;
+ } else
+ dev_dbg(&port->dev, "SCRATCH_PAD_REGISTER Writing success status%d\n", status);
+
+ /* Zero Length flag register */
+ if ((mos7840_port->port_num != 1) && (serial->num_ports == 2)) {
+ Data = 0xff;
+ status = mos7840_set_reg_sync(port,
+ (__u16) (ZLP_REG1 +
+ ((__u16)mos7840_port->port_num)), Data);
+ dev_dbg(&port->dev, "ZLIP offset %x\n",
+ (__u16)(ZLP_REG1 + ((__u16) mos7840_port->port_num)));
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing ZLP_REG%d failed status-0x%x\n", pnum + 2, status);
+ goto error;
+ } else
+ dev_dbg(&port->dev, "ZLP_REG%d Writing success status%d\n", pnum + 2, status);
+ } else {
+ Data = 0xff;
+ status = mos7840_set_reg_sync(port,
+ (__u16) (ZLP_REG1 +
+ ((__u16)mos7840_port->port_num) - 0x1), Data);
+ dev_dbg(&port->dev, "ZLIP offset %x\n",
+ (__u16)(ZLP_REG1 + ((__u16) mos7840_port->port_num) - 0x1));
+ if (status < 0) {
+ dev_dbg(&port->dev, "Writing ZLP_REG%d failed status-0x%x\n", pnum + 1, status);
+ goto error;
+ } else
+ dev_dbg(&port->dev, "ZLP_REG%d Writing success status%d\n", pnum + 1, status);
+
+ }
+
+ mos7840_port->has_led = device_flags & MCS_LED;
+
+ /* Initialize LED timers */
+ if (mos7840_port->has_led) {
+ mos7840_port->led_urb = usb_alloc_urb(0, GFP_KERNEL);
+ mos7840_port->led_dr = kmalloc_obj(*mos7840_port->led_dr);
+ if (!mos7840_port->led_urb || !mos7840_port->led_dr) {
+ status = -ENOMEM;
+ goto error;
+ }
+
+ timer_setup(&mos7840_port->led_timer1, mos7840_led_off, 0);
+ mos7840_port->led_timer1.expires =
+ jiffies + msecs_to_jiffies(LED_ON_MS);
+ timer_setup(&mos7840_port->led_timer2, mos7840_led_flag_off,
+ 0);
+ mos7840_port->led_timer2.expires =
+ jiffies + msecs_to_jiffies(LED_OFF_MS);
+
+ /* Turn off LED */
+ mos7840_set_led_sync(port, MODEM_CONTROL_REGISTER, 0x0300);
+ }
+
+ return 0;
+error:
+ kfree(mos7840_port->led_dr);
+ usb_free_urb(mos7840_port->led_urb);
+ kfree(mos7840_port);
+
+ return status;
+}
+
+static void mos7840_port_remove(struct usb_serial_port *port)
+{
+ struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
+
+ if (mos7840_port->has_led) {
+ /* Turn off LED */
+ mos7840_set_led_sync(port, MODEM_CONTROL_REGISTER, 0x0300);
+
+ timer_shutdown_sync(&mos7840_port->led_timer1);
+ timer_shutdown_sync(&mos7840_port->led_timer2);
+
+ usb_kill_urb(mos7840_port->led_urb);
+ usb_free_urb(mos7840_port->led_urb);
+ kfree(mos7840_port->led_dr);
+ }
+
+ kfree(mos7840_port);
+}
+
+static int mos7840_suspend(struct usb_serial *serial, pm_message_t message)
+{
+ struct moschip_port *mos7840_port;
+ struct usb_serial_port *port;
+ int i;
+
+ for (i = 0; i < serial->num_ports; ++i) {
+ port = serial->port[i];
+ if (!tty_port_initialized(&port->port))
+ continue;
+
+ mos7840_port = usb_get_serial_port_data(port);
+
+ usb_kill_urb(mos7840_port->read_urb);
+ mos7840_port->read_urb_busy = false;
+ }
+
+ return 0;
+}
+
+static int mos7840_resume(struct usb_serial *serial)
+{
+ struct moschip_port *mos7840_port;
+ struct usb_serial_port *port;
+ int res;
+ int i;
+
+ for (i = 0; i < serial->num_ports; ++i) {
+ port = serial->port[i];
+ if (!tty_port_initialized(&port->port))
+ continue;
+
+ mos7840_port = usb_get_serial_port_data(port);
+
+ mos7840_port->read_urb_busy = true;
+ res = usb_submit_urb(mos7840_port->read_urb, GFP_NOIO);
+ if (res)
+ mos7840_port->read_urb_busy = false;
+ }
+
+ return 0;
+}
+
+static struct usb_serial_driver moschip7840_4port_device = {
+ .driver = {
+ .name = "mos7840",
+ },
+ .description = DRIVER_DESC,
+ .id_table = id_table,
+ .num_interrupt_in = 1,
+ .open = mos7840_open,
+ .close = mos7840_close,
+ .write = mos7840_write,
+ .write_room = mos7840_write_room,
+ .chars_in_buffer = mos7840_chars_in_buffer,
+ .throttle = mos7840_throttle,
+ .unthrottle = mos7840_unthrottle,
+ .calc_num_ports = mos7840_calc_num_ports,
+ .probe = mos7840_probe,
+ .attach = mos7840_attach,
+ .ioctl = mos7840_ioctl,
+ .set_termios = mos7840_set_termios,
+ .break_ctl = mos7840_break,
+ .tiocmget = mos7840_tiocmget,
+ .tiocmset = mos7840_tiocmset,
+ .get_icount = usb_serial_generic_get_icount,
+ .port_probe = mos7840_port_probe,
+ .port_remove = mos7840_port_remove,
+ .read_bulk_callback = mos7840_bulk_in_callback,
+ .suspend = mos7840_suspend,
+ .resume = mos7840_resume,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &moschip7840_4port_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c
new file mode 100644
index 000000000..e3c5a1b97
--- /dev/null
+++ b/drivers/usb/serial/mxuport.c
@@ -0,0 +1,1318 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * mxuport.c - MOXA UPort series driver
+ *
+ * Copyright (c) 2006 Moxa Technologies Co., Ltd.
+ * Copyright (c) 2013 Andrew Lunn <andrew@lunn.ch>
+ *
+ * Supports the following Moxa USB to serial converters:
+ * 2 ports : UPort 1250, UPort 1250I
+ * 4 ports : UPort 1410, UPort 1450, UPort 1450I
+ * 8 ports : UPort 1610-8, UPort 1650-8
+ * 16 ports : UPort 1610-16, UPort 1650-16
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/jiffies.h>
+#include <linux/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/unaligned.h>
+
+/* Definitions for the vendor ID and device ID */
+#define MX_USBSERIAL_VID 0x110A
+#define MX_UPORT1250_PID 0x1250
+#define MX_UPORT1251_PID 0x1251
+#define MX_UPORT1410_PID 0x1410
+#define MX_UPORT1450_PID 0x1450
+#define MX_UPORT1451_PID 0x1451
+#define MX_UPORT1618_PID 0x1618
+#define MX_UPORT1658_PID 0x1658
+#define MX_UPORT1613_PID 0x1613
+#define MX_UPORT1653_PID 0x1653
+
+/* Definitions for USB info */
+#define HEADER_SIZE 4
+#define EVENT_LENGTH 8
+#define DOWN_BLOCK_SIZE 64
+
+/* Definitions for firmware info */
+#define VER_ADDR_1 0x20
+#define VER_ADDR_2 0x24
+#define VER_ADDR_3 0x28
+
+/* Definitions for USB vendor request */
+#define RQ_VENDOR_NONE 0x00
+#define RQ_VENDOR_SET_BAUD 0x01 /* Set baud rate */
+#define RQ_VENDOR_SET_LINE 0x02 /* Set line status */
+#define RQ_VENDOR_SET_CHARS 0x03 /* Set Xon/Xoff chars */
+#define RQ_VENDOR_SET_RTS 0x04 /* Set RTS */
+#define RQ_VENDOR_SET_DTR 0x05 /* Set DTR */
+#define RQ_VENDOR_SET_XONXOFF 0x06 /* Set auto Xon/Xoff */
+#define RQ_VENDOR_SET_RX_HOST_EN 0x07 /* Set RX host enable */
+#define RQ_VENDOR_SET_OPEN 0x08 /* Set open/close port */
+#define RQ_VENDOR_PURGE 0x09 /* Purge Rx/Tx buffer */
+#define RQ_VENDOR_SET_MCR 0x0A /* Set MCR register */
+#define RQ_VENDOR_SET_BREAK 0x0B /* Set Break signal */
+
+#define RQ_VENDOR_START_FW_DOWN 0x0C /* Start firmware download */
+#define RQ_VENDOR_STOP_FW_DOWN 0x0D /* Stop firmware download */
+#define RQ_VENDOR_QUERY_FW_READY 0x0E /* Query if new firmware ready */
+
+#define RQ_VENDOR_SET_FIFO_DISABLE 0x0F /* Set fifo disable */
+#define RQ_VENDOR_SET_INTERFACE 0x10 /* Set interface */
+#define RQ_VENDOR_SET_HIGH_PERFOR 0x11 /* Set hi-performance */
+
+#define RQ_VENDOR_ERASE_BLOCK 0x12 /* Erase flash block */
+#define RQ_VENDOR_WRITE_PAGE 0x13 /* Write flash page */
+#define RQ_VENDOR_PREPARE_WRITE 0x14 /* Prepare write flash */
+#define RQ_VENDOR_CONFIRM_WRITE 0x15 /* Confirm write flash */
+#define RQ_VENDOR_LOCATE 0x16 /* Locate the device */
+
+#define RQ_VENDOR_START_ROM_DOWN 0x17 /* Start firmware download */
+#define RQ_VENDOR_ROM_DATA 0x18 /* Rom file data */
+#define RQ_VENDOR_STOP_ROM_DOWN 0x19 /* Stop firmware download */
+#define RQ_VENDOR_FW_DATA 0x20 /* Firmware data */
+
+#define RQ_VENDOR_RESET_DEVICE 0x23 /* Try to reset the device */
+#define RQ_VENDOR_QUERY_FW_CONFIG 0x24
+
+#define RQ_VENDOR_GET_VERSION 0x81 /* Get firmware version */
+#define RQ_VENDOR_GET_PAGE 0x82 /* Read flash page */
+#define RQ_VENDOR_GET_ROM_PROC 0x83 /* Get ROM process state */
+
+#define RQ_VENDOR_GET_INQUEUE 0x84 /* Data in input buffer */
+#define RQ_VENDOR_GET_OUTQUEUE 0x85 /* Data in output buffer */
+
+#define RQ_VENDOR_GET_MSR 0x86 /* Get modem status register */
+
+/* Definitions for UPort event type */
+#define UPORT_EVENT_NONE 0 /* None */
+#define UPORT_EVENT_TXBUF_THRESHOLD 1 /* Tx buffer threshold */
+#define UPORT_EVENT_SEND_NEXT 2 /* Send next */
+#define UPORT_EVENT_MSR 3 /* Modem status */
+#define UPORT_EVENT_LSR 4 /* Line status */
+#define UPORT_EVENT_MCR 5 /* Modem control */
+
+/* Definitions for serial event type */
+#define SERIAL_EV_CTS 0x0008 /* CTS changed state */
+#define SERIAL_EV_DSR 0x0010 /* DSR changed state */
+#define SERIAL_EV_RLSD 0x0020 /* RLSD changed state */
+
+/* Definitions for modem control event type */
+#define SERIAL_EV_XOFF 0x40 /* XOFF received */
+
+/* Definitions for line control of communication */
+#define MX_WORDLENGTH_5 5
+#define MX_WORDLENGTH_6 6
+#define MX_WORDLENGTH_7 7
+#define MX_WORDLENGTH_8 8
+
+#define MX_PARITY_NONE 0
+#define MX_PARITY_ODD 1
+#define MX_PARITY_EVEN 2
+#define MX_PARITY_MARK 3
+#define MX_PARITY_SPACE 4
+
+#define MX_STOP_BITS_1 0
+#define MX_STOP_BITS_1_5 1
+#define MX_STOP_BITS_2 2
+
+#define MX_RTS_DISABLE 0x0
+#define MX_RTS_ENABLE 0x1
+#define MX_RTS_HW 0x2
+#define MX_RTS_NO_CHANGE 0x3 /* Flag, not valid register value*/
+
+#define MX_INT_RS232 0
+#define MX_INT_2W_RS485 1
+#define MX_INT_RS422 2
+#define MX_INT_4W_RS485 3
+
+/* Definitions for holding reason */
+#define MX_WAIT_FOR_CTS 0x0001
+#define MX_WAIT_FOR_DSR 0x0002
+#define MX_WAIT_FOR_DCD 0x0004
+#define MX_WAIT_FOR_XON 0x0008
+#define MX_WAIT_FOR_START_TX 0x0010
+#define MX_WAIT_FOR_UNTHROTTLE 0x0020
+#define MX_WAIT_FOR_LOW_WATER 0x0040
+#define MX_WAIT_FOR_SEND_NEXT 0x0080
+
+/* This structure holds all of the local port information */
+struct mxuport_port {
+ u8 mcr_state; /* Last MCR state */
+ u8 msr_state; /* Last MSR state */
+ struct mutex mutex; /* Protects mcr_state */
+ spinlock_t spinlock; /* Protects msr_state */
+};
+
+/* Encode number of ports (2..16 or undefined) */
+#define MX_PORTS_MASK GENMASK(3, 0)
+#define MX_PORTS_OFFSET 1
+#define MX_PORTS(n) (((n) - MX_PORTS_OFFSET) & MX_PORTS_MASK)
+
+/* Table of devices that work with this driver */
+static const struct usb_device_id mxuport_idtable[] = {
+ { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1250_PID),
+ .driver_info = MX_PORTS(2) },
+ { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1251_PID),
+ .driver_info = MX_PORTS(2) },
+ { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1410_PID),
+ .driver_info = MX_PORTS(4) },
+ { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1450_PID),
+ .driver_info = MX_PORTS(4) },
+ { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1451_PID),
+ .driver_info = MX_PORTS(4) },
+ { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1618_PID),
+ .driver_info = MX_PORTS(8) },
+ { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1658_PID),
+ .driver_info = MX_PORTS(8) },
+ { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1613_PID),
+ .driver_info = MX_PORTS(16) },
+ { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1653_PID),
+ .driver_info = MX_PORTS(16) },
+ {} /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, mxuport_idtable);
+
+/*
+ * Add a four byte header containing the port number and the number of
+ * bytes of data in the message. Return the number of bytes in the
+ * buffer.
+ */
+static int mxuport_prepare_write_buffer(struct usb_serial_port *port,
+ void *dest, size_t size)
+{
+ u8 *buf = dest;
+ int count;
+
+ count = kfifo_out_locked(&port->write_fifo, buf + HEADER_SIZE,
+ size - HEADER_SIZE,
+ &port->lock);
+
+ put_unaligned_be16(port->port_number, buf);
+ put_unaligned_be16(count, buf + 2);
+
+ dev_dbg(&port->dev, "%s - size %zd count %d\n", __func__,
+ size, count);
+
+ return count + HEADER_SIZE;
+}
+
+/* Read the given buffer in from the control pipe. */
+static int mxuport_recv_ctrl_urb(struct usb_serial *serial,
+ u8 request, u16 value, u16 index,
+ u8 *data, size_t size)
+{
+ int status;
+
+ status = usb_control_msg(serial->dev,
+ usb_rcvctrlpipe(serial->dev, 0),
+ request,
+ (USB_DIR_IN | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE), value, index,
+ data, size,
+ USB_CTRL_GET_TIMEOUT);
+ if (status < 0) {
+ dev_err(&serial->interface->dev,
+ "%s - usb_control_msg failed (%d)\n",
+ __func__, status);
+ return status;
+ }
+
+ if (status != size) {
+ dev_err(&serial->interface->dev,
+ "%s - short read (%d / %zd)\n",
+ __func__, status, size);
+ return -EIO;
+ }
+
+ return status;
+}
+
+/* Write the given buffer out to the control pipe. */
+static int mxuport_send_ctrl_data_urb(struct usb_serial *serial,
+ u8 request,
+ u16 value, u16 index,
+ u8 *data, size_t size)
+{
+ int status;
+
+ status = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ request,
+ (USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE), value, index,
+ data, size,
+ USB_CTRL_SET_TIMEOUT);
+ if (status < 0) {
+ dev_err(&serial->interface->dev,
+ "%s - usb_control_msg failed (%d)\n",
+ __func__, status);
+ return status;
+ }
+
+ return 0;
+}
+
+/* Send a vendor request without any data */
+static int mxuport_send_ctrl_urb(struct usb_serial *serial,
+ u8 request, u16 value, u16 index)
+{
+ return mxuport_send_ctrl_data_urb(serial, request, value, index,
+ NULL, 0);
+}
+
+/*
+ * mxuport_throttle - throttle function of driver
+ *
+ * This function is called by the tty driver when it wants to stop the
+ * data being read from the port. Since all the data comes over one
+ * bulk in endpoint, we cannot stop submitting urbs by setting
+ * port->throttle. Instead tell the device to stop sending us data for
+ * the port.
+ */
+static void mxuport_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_serial *serial = port->serial;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_RX_HOST_EN,
+ 0, port->port_number);
+}
+
+/*
+ * mxuport_unthrottle - unthrottle function of driver
+ *
+ * This function is called by the tty driver when it wants to resume
+ * the data being read from the port. Tell the device it can resume
+ * sending us received data from the port.
+ */
+static void mxuport_unthrottle(struct tty_struct *tty)
+{
+
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_serial *serial = port->serial;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_RX_HOST_EN,
+ 1, port->port_number);
+}
+
+/*
+ * Processes one chunk of data received for a port. Mostly a copy of
+ * usb_serial_generic_process_read_urb().
+ */
+static void mxuport_process_read_urb_data(struct usb_serial_port *port,
+ char *data, int size)
+{
+ int i;
+
+ if (port->sysrq) {
+ for (i = 0; i < size; i++, data++) {
+ if (!usb_serial_handle_sysrq_char(port, *data))
+ tty_insert_flip_char(&port->port, *data,
+ TTY_NORMAL);
+ }
+ } else {
+ tty_insert_flip_string(&port->port, data, size);
+ }
+ tty_flip_buffer_push(&port->port);
+}
+
+static void mxuport_msr_event(struct usb_serial_port *port, u8 buf[4])
+{
+ struct mxuport_port *mxport = usb_get_serial_port_data(port);
+ u8 rcv_msr_hold = buf[2] & 0xF0;
+ u16 rcv_msr_event = get_unaligned_be16(buf);
+ unsigned long flags;
+
+ if (rcv_msr_event == 0)
+ return;
+
+ /* Update MSR status */
+ spin_lock_irqsave(&mxport->spinlock, flags);
+
+ dev_dbg(&port->dev, "%s - current MSR status = 0x%x\n",
+ __func__, mxport->msr_state);
+
+ if (rcv_msr_hold & UART_MSR_CTS) {
+ mxport->msr_state |= UART_MSR_CTS;
+ dev_dbg(&port->dev, "%s - CTS high\n", __func__);
+ } else {
+ mxport->msr_state &= ~UART_MSR_CTS;
+ dev_dbg(&port->dev, "%s - CTS low\n", __func__);
+ }
+
+ if (rcv_msr_hold & UART_MSR_DSR) {
+ mxport->msr_state |= UART_MSR_DSR;
+ dev_dbg(&port->dev, "%s - DSR high\n", __func__);
+ } else {
+ mxport->msr_state &= ~UART_MSR_DSR;
+ dev_dbg(&port->dev, "%s - DSR low\n", __func__);
+ }
+
+ if (rcv_msr_hold & UART_MSR_DCD) {
+ mxport->msr_state |= UART_MSR_DCD;
+ dev_dbg(&port->dev, "%s - DCD high\n", __func__);
+ } else {
+ mxport->msr_state &= ~UART_MSR_DCD;
+ dev_dbg(&port->dev, "%s - DCD low\n", __func__);
+ }
+ spin_unlock_irqrestore(&mxport->spinlock, flags);
+
+ if (rcv_msr_event &
+ (SERIAL_EV_CTS | SERIAL_EV_DSR | SERIAL_EV_RLSD)) {
+
+ if (rcv_msr_event & SERIAL_EV_CTS) {
+ port->icount.cts++;
+ dev_dbg(&port->dev, "%s - CTS change\n", __func__);
+ }
+
+ if (rcv_msr_event & SERIAL_EV_DSR) {
+ port->icount.dsr++;
+ dev_dbg(&port->dev, "%s - DSR change\n", __func__);
+ }
+
+ if (rcv_msr_event & SERIAL_EV_RLSD) {
+ port->icount.dcd++;
+ dev_dbg(&port->dev, "%s - DCD change\n", __func__);
+ }
+ wake_up_interruptible(&port->port.delta_msr_wait);
+ }
+}
+
+static void mxuport_lsr_event(struct usb_serial_port *port, u8 buf[4])
+{
+ u8 lsr_event = buf[2];
+
+ if (lsr_event & UART_LSR_BI) {
+ port->icount.brk++;
+ dev_dbg(&port->dev, "%s - break error\n", __func__);
+ }
+
+ if (lsr_event & UART_LSR_FE) {
+ port->icount.frame++;
+ dev_dbg(&port->dev, "%s - frame error\n", __func__);
+ }
+
+ if (lsr_event & UART_LSR_PE) {
+ port->icount.parity++;
+ dev_dbg(&port->dev, "%s - parity error\n", __func__);
+ }
+
+ if (lsr_event & UART_LSR_OE) {
+ port->icount.overrun++;
+ dev_dbg(&port->dev, "%s - overrun error\n", __func__);
+ }
+}
+
+/*
+ * When something interesting happens, modem control lines XON/XOFF
+ * etc, the device sends an event. Process these events.
+ */
+static void mxuport_process_read_urb_event(struct usb_serial_port *port,
+ u8 buf[4], u32 event)
+{
+ dev_dbg(&port->dev, "%s - receive event : %04x\n", __func__, event);
+
+ switch (event) {
+ case UPORT_EVENT_SEND_NEXT:
+ /*
+ * Sent as part of the flow control on device buffers.
+ * Not currently used.
+ */
+ break;
+ case UPORT_EVENT_MSR:
+ mxuport_msr_event(port, buf);
+ break;
+ case UPORT_EVENT_LSR:
+ mxuport_lsr_event(port, buf);
+ break;
+ case UPORT_EVENT_MCR:
+ /*
+ * Event to indicate a change in XON/XOFF from the
+ * peer. Currently not used. We just continue
+ * sending the device data and it will buffer it if
+ * needed. This event could be used for flow control
+ * between the host and the device.
+ */
+ break;
+ default:
+ dev_dbg(&port->dev, "Unexpected event\n");
+ break;
+ }
+}
+
+/*
+ * One URB can contain data for multiple ports. Demultiplex the data,
+ * checking the port exists, is opened and the message is valid.
+ */
+static void mxuport_process_read_urb_demux_data(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct usb_serial *serial = port->serial;
+ u8 *data = urb->transfer_buffer;
+ u8 *end = data + urb->actual_length;
+ struct usb_serial_port *demux_port;
+ u8 *ch;
+ u16 rcv_port;
+ u16 rcv_len;
+
+ while (data < end) {
+ if (data + HEADER_SIZE > end) {
+ dev_warn(&port->dev, "%s - message with short header\n",
+ __func__);
+ return;
+ }
+
+ rcv_port = get_unaligned_be16(data);
+ if (rcv_port >= serial->num_ports) {
+ dev_warn(&port->dev, "%s - message for invalid port\n",
+ __func__);
+ return;
+ }
+
+ demux_port = serial->port[rcv_port];
+ rcv_len = get_unaligned_be16(data + 2);
+ if (!rcv_len || data + HEADER_SIZE + rcv_len > end) {
+ dev_warn(&port->dev, "%s - short data\n", __func__);
+ return;
+ }
+
+ if (tty_port_initialized(&demux_port->port)) {
+ ch = data + HEADER_SIZE;
+ mxuport_process_read_urb_data(demux_port, ch, rcv_len);
+ } else {
+ dev_dbg(&demux_port->dev, "%s - data for closed port\n",
+ __func__);
+ }
+ data += HEADER_SIZE + rcv_len;
+ }
+}
+
+/*
+ * One URB can contain events for multiple ports. Demultiplex the event,
+ * checking the port exists, and is opened.
+ */
+static void mxuport_process_read_urb_demux_event(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct usb_serial *serial = port->serial;
+ u8 *data = urb->transfer_buffer;
+ u8 *end = data + urb->actual_length;
+ struct usb_serial_port *demux_port;
+ u8 *ch;
+ u16 rcv_port;
+ u16 rcv_event;
+
+ while (data < end) {
+ if (data + EVENT_LENGTH > end) {
+ dev_warn(&port->dev, "%s - message with short event\n",
+ __func__);
+ return;
+ }
+
+ rcv_port = get_unaligned_be16(data);
+ if (rcv_port >= serial->num_ports) {
+ dev_warn(&port->dev, "%s - message for invalid port\n",
+ __func__);
+ return;
+ }
+
+ demux_port = serial->port[rcv_port];
+ if (tty_port_initialized(&demux_port->port)) {
+ ch = data + HEADER_SIZE;
+ rcv_event = get_unaligned_be16(data + 2);
+ mxuport_process_read_urb_event(demux_port, ch,
+ rcv_event);
+ } else {
+ dev_dbg(&demux_port->dev,
+ "%s - event for closed port\n", __func__);
+ }
+ data += EVENT_LENGTH;
+ }
+}
+
+/*
+ * This is called when we have received data on the bulk in
+ * endpoint. Depending on which port it was received on, it can
+ * contain serial data or events.
+ */
+static void mxuport_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct usb_serial *serial = port->serial;
+
+ if (port == serial->port[0])
+ mxuport_process_read_urb_demux_data(urb);
+
+ if (port == serial->port[1])
+ mxuport_process_read_urb_demux_event(urb);
+}
+
+/*
+ * Ask the device how many bytes it has queued to be sent out. If
+ * there are none, return true.
+ */
+static bool mxuport_tx_empty(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ bool is_empty = true;
+ u32 txlen;
+ u8 *len_buf;
+ int err;
+
+ len_buf = kzalloc(4, GFP_KERNEL);
+ if (!len_buf)
+ goto out;
+
+ err = mxuport_recv_ctrl_urb(serial, RQ_VENDOR_GET_OUTQUEUE, 0,
+ port->port_number, len_buf, 4);
+ if (err < 0)
+ goto out;
+
+ txlen = get_unaligned_be32(len_buf);
+ dev_dbg(&port->dev, "%s - tx len = %u\n", __func__, txlen);
+
+ if (txlen != 0)
+ is_empty = false;
+
+out:
+ kfree(len_buf);
+ return is_empty;
+}
+
+static int mxuport_set_mcr(struct usb_serial_port *port, u8 mcr_state)
+{
+ struct usb_serial *serial = port->serial;
+ int err;
+
+ dev_dbg(&port->dev, "%s - %02x\n", __func__, mcr_state);
+
+ err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_MCR,
+ mcr_state, port->port_number);
+ if (err)
+ dev_err(&port->dev, "%s - failed to change MCR\n", __func__);
+
+ return err;
+}
+
+static int mxuport_set_dtr(struct usb_serial_port *port, int on)
+{
+ struct mxuport_port *mxport = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ int err;
+
+ mutex_lock(&mxport->mutex);
+
+ err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_DTR,
+ !!on, port->port_number);
+ if (!err) {
+ if (on)
+ mxport->mcr_state |= UART_MCR_DTR;
+ else
+ mxport->mcr_state &= ~UART_MCR_DTR;
+ }
+
+ mutex_unlock(&mxport->mutex);
+
+ return err;
+}
+
+static int mxuport_set_rts(struct usb_serial_port *port, u8 state)
+{
+ struct mxuport_port *mxport = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ int err;
+ u8 mcr_state;
+
+ mutex_lock(&mxport->mutex);
+ mcr_state = mxport->mcr_state;
+
+ switch (state) {
+ case MX_RTS_DISABLE:
+ mcr_state &= ~UART_MCR_RTS;
+ break;
+ case MX_RTS_ENABLE:
+ mcr_state |= UART_MCR_RTS;
+ break;
+ case MX_RTS_HW:
+ /*
+ * Do not update mxport->mcr_state when doing hardware
+ * flow control.
+ */
+ break;
+ default:
+ /*
+ * Should not happen, but somebody might try passing
+ * MX_RTS_NO_CHANGE, which is not valid.
+ */
+ err = -EINVAL;
+ goto out;
+ }
+ err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_RTS,
+ state, port->port_number);
+ if (!err)
+ mxport->mcr_state = mcr_state;
+
+out:
+ mutex_unlock(&mxport->mutex);
+
+ return err;
+}
+
+static void mxuport_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct mxuport_port *mxport = usb_get_serial_port_data(port);
+ u8 mcr_state;
+ int err;
+
+ mutex_lock(&mxport->mutex);
+ mcr_state = mxport->mcr_state;
+
+ if (on)
+ mcr_state |= (UART_MCR_RTS | UART_MCR_DTR);
+ else
+ mcr_state &= ~(UART_MCR_RTS | UART_MCR_DTR);
+
+ err = mxuport_set_mcr(port, mcr_state);
+ if (!err)
+ mxport->mcr_state = mcr_state;
+
+ mutex_unlock(&mxport->mutex);
+}
+
+static int mxuport_tiocmset(struct tty_struct *tty, unsigned int set,
+ unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct mxuport_port *mxport = usb_get_serial_port_data(port);
+ int err;
+ u8 mcr_state;
+
+ mutex_lock(&mxport->mutex);
+ mcr_state = mxport->mcr_state;
+
+ if (set & TIOCM_RTS)
+ mcr_state |= UART_MCR_RTS;
+
+ if (set & TIOCM_DTR)
+ mcr_state |= UART_MCR_DTR;
+
+ if (clear & TIOCM_RTS)
+ mcr_state &= ~UART_MCR_RTS;
+
+ if (clear & TIOCM_DTR)
+ mcr_state &= ~UART_MCR_DTR;
+
+ err = mxuport_set_mcr(port, mcr_state);
+ if (!err)
+ mxport->mcr_state = mcr_state;
+
+ mutex_unlock(&mxport->mutex);
+
+ return err;
+}
+
+static int mxuport_tiocmget(struct tty_struct *tty)
+{
+ struct mxuport_port *mxport;
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned int result;
+ unsigned long flags;
+ unsigned int msr;
+ unsigned int mcr;
+
+ mxport = usb_get_serial_port_data(port);
+
+ mutex_lock(&mxport->mutex);
+ spin_lock_irqsave(&mxport->spinlock, flags);
+
+ msr = mxport->msr_state;
+ mcr = mxport->mcr_state;
+
+ spin_unlock_irqrestore(&mxport->spinlock, flags);
+ mutex_unlock(&mxport->mutex);
+
+ result = (((mcr & UART_MCR_DTR) ? TIOCM_DTR : 0) | /* 0x002 */
+ ((mcr & UART_MCR_RTS) ? TIOCM_RTS : 0) | /* 0x004 */
+ ((msr & UART_MSR_CTS) ? TIOCM_CTS : 0) | /* 0x020 */
+ ((msr & UART_MSR_DCD) ? TIOCM_CAR : 0) | /* 0x040 */
+ ((msr & UART_MSR_RI) ? TIOCM_RI : 0) | /* 0x080 */
+ ((msr & UART_MSR_DSR) ? TIOCM_DSR : 0)); /* 0x100 */
+
+ dev_dbg(&port->dev, "%s - 0x%04x\n", __func__, result);
+
+ return result;
+}
+
+static int mxuport_set_termios_flow(struct tty_struct *tty,
+ const struct ktermios *old_termios,
+ struct usb_serial_port *port,
+ struct usb_serial *serial)
+{
+ u8 xon = START_CHAR(tty);
+ u8 xoff = STOP_CHAR(tty);
+ int enable;
+ int err;
+ u8 *buf;
+ u8 rts;
+
+ buf = kmalloc(2, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* S/W flow control settings */
+ if (I_IXOFF(tty) || I_IXON(tty)) {
+ enable = 1;
+ buf[0] = xon;
+ buf[1] = xoff;
+
+ err = mxuport_send_ctrl_data_urb(serial, RQ_VENDOR_SET_CHARS,
+ 0, port->port_number,
+ buf, 2);
+ if (err)
+ goto out;
+
+ dev_dbg(&port->dev, "%s - XON = 0x%02x, XOFF = 0x%02x\n",
+ __func__, xon, xoff);
+ } else {
+ enable = 0;
+ }
+
+ err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_XONXOFF,
+ enable, port->port_number);
+ if (err)
+ goto out;
+
+ rts = MX_RTS_NO_CHANGE;
+
+ /* H/W flow control settings */
+ if (!old_termios ||
+ C_CRTSCTS(tty) != (old_termios->c_cflag & CRTSCTS)) {
+ if (C_CRTSCTS(tty))
+ rts = MX_RTS_HW;
+ else
+ rts = MX_RTS_ENABLE;
+ }
+
+ if (C_BAUD(tty)) {
+ if (old_termios && (old_termios->c_cflag & CBAUD) == B0) {
+ /* Raise DTR and RTS */
+ if (C_CRTSCTS(tty))
+ rts = MX_RTS_HW;
+ else
+ rts = MX_RTS_ENABLE;
+ mxuport_set_dtr(port, 1);
+ }
+ } else {
+ /* Drop DTR and RTS */
+ rts = MX_RTS_DISABLE;
+ mxuport_set_dtr(port, 0);
+ }
+
+ if (rts != MX_RTS_NO_CHANGE)
+ err = mxuport_set_rts(port, rts);
+
+out:
+ kfree(buf);
+ return err;
+}
+
+static void mxuport_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct usb_serial *serial = port->serial;
+ u8 *buf;
+ u8 data_bits;
+ u8 stop_bits;
+ u8 parity;
+ int baud;
+ int err;
+
+ if (old_termios &&
+ !tty_termios_hw_change(&tty->termios, old_termios) &&
+ tty->termios.c_iflag == old_termios->c_iflag) {
+ dev_dbg(&port->dev, "%s - nothing to change\n", __func__);
+ return;
+ }
+
+ buf = kmalloc(4, GFP_KERNEL);
+ if (!buf)
+ return;
+
+ /* Set data bit of termios */
+ switch (C_CSIZE(tty)) {
+ case CS5:
+ data_bits = MX_WORDLENGTH_5;
+ break;
+ case CS6:
+ data_bits = MX_WORDLENGTH_6;
+ break;
+ case CS7:
+ data_bits = MX_WORDLENGTH_7;
+ break;
+ case CS8:
+ default:
+ data_bits = MX_WORDLENGTH_8;
+ break;
+ }
+
+ /* Set parity of termios */
+ if (C_PARENB(tty)) {
+ if (C_CMSPAR(tty)) {
+ if (C_PARODD(tty))
+ parity = MX_PARITY_MARK;
+ else
+ parity = MX_PARITY_SPACE;
+ } else {
+ if (C_PARODD(tty))
+ parity = MX_PARITY_ODD;
+ else
+ parity = MX_PARITY_EVEN;
+ }
+ } else {
+ parity = MX_PARITY_NONE;
+ }
+
+ /* Set stop bit of termios */
+ if (C_CSTOPB(tty))
+ stop_bits = MX_STOP_BITS_2;
+ else
+ stop_bits = MX_STOP_BITS_1;
+
+ buf[0] = data_bits;
+ buf[1] = parity;
+ buf[2] = stop_bits;
+ buf[3] = 0;
+
+ err = mxuport_send_ctrl_data_urb(serial, RQ_VENDOR_SET_LINE,
+ 0, port->port_number, buf, 4);
+ if (err)
+ goto out;
+
+ err = mxuport_set_termios_flow(tty, old_termios, port, serial);
+ if (err)
+ goto out;
+
+ baud = tty_get_baud_rate(tty);
+ if (!baud)
+ baud = 9600;
+
+ /* Note: Little Endian */
+ put_unaligned_le32(baud, buf);
+
+ err = mxuport_send_ctrl_data_urb(serial, RQ_VENDOR_SET_BAUD,
+ 0, port->port_number,
+ buf, 4);
+ if (err)
+ goto out;
+
+ dev_dbg(&port->dev, "baud_rate : %d\n", baud);
+ dev_dbg(&port->dev, "data_bits : %d\n", data_bits);
+ dev_dbg(&port->dev, "parity : %d\n", parity);
+ dev_dbg(&port->dev, "stop_bits : %d\n", stop_bits);
+
+out:
+ kfree(buf);
+}
+
+/*
+ * Determine how many ports this device has dynamically. It will be
+ * called after the probe() callback is called, but before attach().
+ */
+static int mxuport_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ unsigned long features = (unsigned long)usb_get_serial_data(serial);
+ int num_ports;
+ int i;
+
+ if (features & MX_PORTS_MASK) {
+ num_ports = (features & MX_PORTS_MASK) + MX_PORTS_OFFSET;
+ } else {
+ dev_warn(&serial->interface->dev,
+ "unknown device, assuming two ports\n");
+ num_ports = 2;
+ }
+
+ /*
+ * Setup bulk-out endpoint multiplexing. All ports share the same
+ * bulk-out endpoint.
+ */
+ BUILD_BUG_ON(ARRAY_SIZE(epds->bulk_out) < 16);
+
+ /*
+ * The bulk-out buffers must be large enough for the four-byte header
+ * (and following data), but assume anything smaller than eight bytes
+ * is broken.
+ */
+ if (usb_endpoint_maxp(epds->bulk_out[0]) < 8)
+ return -EINVAL;
+
+ for (i = 1; i < num_ports; ++i)
+ epds->bulk_out[i] = epds->bulk_out[0];
+
+ epds->num_bulk_out = num_ports;
+
+ return num_ports;
+}
+
+/* Get the version of the firmware currently running. */
+static int mxuport_get_fw_version(struct usb_serial *serial, u32 *version)
+{
+ u8 *ver_buf;
+ int err;
+
+ ver_buf = kzalloc(4, GFP_KERNEL);
+ if (!ver_buf)
+ return -ENOMEM;
+
+ /* Get firmware version from SDRAM */
+ err = mxuport_recv_ctrl_urb(serial, RQ_VENDOR_GET_VERSION, 0, 0,
+ ver_buf, 4);
+ if (err != 4) {
+ err = -EIO;
+ goto out;
+ }
+
+ *version = (ver_buf[0] << 16) | (ver_buf[1] << 8) | ver_buf[2];
+ err = 0;
+out:
+ kfree(ver_buf);
+ return err;
+}
+
+/* Given a firmware blob, download it to the device. */
+static int mxuport_download_fw(struct usb_serial *serial,
+ const struct firmware *fw_p)
+{
+ u8 *fw_buf;
+ size_t txlen;
+ size_t fwidx;
+ int err;
+
+ fw_buf = kmalloc(DOWN_BLOCK_SIZE, GFP_KERNEL);
+ if (!fw_buf)
+ return -ENOMEM;
+
+ dev_dbg(&serial->interface->dev, "Starting firmware download...\n");
+ err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_START_FW_DOWN, 0, 0);
+ if (err)
+ goto out;
+
+ fwidx = 0;
+ do {
+ txlen = min_t(size_t, (fw_p->size - fwidx), DOWN_BLOCK_SIZE);
+
+ memcpy(fw_buf, &fw_p->data[fwidx], txlen);
+ err = mxuport_send_ctrl_data_urb(serial, RQ_VENDOR_FW_DATA,
+ 0, 0, fw_buf, txlen);
+ if (err) {
+ mxuport_send_ctrl_urb(serial, RQ_VENDOR_STOP_FW_DOWN,
+ 0, 0);
+ goto out;
+ }
+
+ fwidx += txlen;
+ usleep_range(1000, 2000);
+
+ } while (fwidx < fw_p->size);
+
+ msleep(1000);
+ err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_STOP_FW_DOWN, 0, 0);
+ if (err)
+ goto out;
+
+ msleep(1000);
+ err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_QUERY_FW_READY, 0, 0);
+
+out:
+ kfree(fw_buf);
+ return err;
+}
+
+static int mxuport_probe(struct usb_serial *serial,
+ const struct usb_device_id *id)
+{
+ u16 productid = le16_to_cpu(serial->dev->descriptor.idProduct);
+ const struct firmware *fw_p = NULL;
+ u32 version;
+ int local_ver;
+ char buf[32];
+ int err;
+
+ /* Load our firmware */
+ err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_QUERY_FW_CONFIG, 0, 0);
+ if (err) {
+ mxuport_send_ctrl_urb(serial, RQ_VENDOR_RESET_DEVICE, 0, 0);
+ return err;
+ }
+
+ err = mxuport_get_fw_version(serial, &version);
+ if (err < 0)
+ return err;
+
+ dev_dbg(&serial->interface->dev, "Device firmware version v%x.%x.%x\n",
+ (version & 0xff0000) >> 16,
+ (version & 0xff00) >> 8,
+ (version & 0xff));
+
+ snprintf(buf, sizeof(buf) - 1, "moxa/moxa-%04x.fw", productid);
+
+ err = request_firmware(&fw_p, buf, &serial->interface->dev);
+ if (err) {
+ dev_warn(&serial->interface->dev, "Firmware %s not found\n",
+ buf);
+
+ /* Use the firmware already in the device */
+ err = 0;
+ } else {
+ local_ver = ((fw_p->data[VER_ADDR_1] << 16) |
+ (fw_p->data[VER_ADDR_2] << 8) |
+ fw_p->data[VER_ADDR_3]);
+ dev_dbg(&serial->interface->dev,
+ "Available firmware version v%x.%x.%x\n",
+ fw_p->data[VER_ADDR_1], fw_p->data[VER_ADDR_2],
+ fw_p->data[VER_ADDR_3]);
+ if (local_ver > version) {
+ err = mxuport_download_fw(serial, fw_p);
+ if (err)
+ goto out;
+ err = mxuport_get_fw_version(serial, &version);
+ if (err < 0)
+ goto out;
+ }
+ }
+
+ dev_info(&serial->interface->dev,
+ "Using device firmware version v%x.%x.%x\n",
+ (version & 0xff0000) >> 16,
+ (version & 0xff00) >> 8,
+ (version & 0xff));
+
+ /*
+ * Contains the features of this hardware. Store away for
+ * later use, eg, number of ports.
+ */
+ usb_set_serial_data(serial, (void *)id->driver_info);
+out:
+ if (fw_p)
+ release_firmware(fw_p);
+ return err;
+}
+
+
+static int mxuport_port_probe(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct mxuport_port *mxport;
+ int err;
+
+ mxport = devm_kzalloc(&port->dev, sizeof(struct mxuport_port),
+ GFP_KERNEL);
+ if (!mxport)
+ return -ENOMEM;
+
+ mutex_init(&mxport->mutex);
+ spin_lock_init(&mxport->spinlock);
+
+ /* Set the port private data */
+ usb_set_serial_port_data(port, mxport);
+
+ /* Set FIFO (Enable) */
+ err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_FIFO_DISABLE,
+ 0, port->port_number);
+ if (err)
+ return err;
+
+ /* Set transmission mode (Hi-Performance) */
+ err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_HIGH_PERFOR,
+ 0, port->port_number);
+ if (err)
+ return err;
+
+ /* Set interface (RS-232) */
+ return mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_INTERFACE,
+ MX_INT_RS232,
+ port->port_number);
+}
+
+static int mxuport_attach(struct usb_serial *serial)
+{
+ struct usb_serial_port *port0 = serial->port[0];
+ struct usb_serial_port *port1 = serial->port[1];
+ int err;
+
+ /*
+ * All data from the ports is received on the first bulk in
+ * endpoint, with a multiplex header. The second bulk in is
+ * used for events.
+ *
+ * Start to read from the device.
+ */
+ err = usb_serial_generic_submit_read_urbs(port0, GFP_KERNEL);
+ if (err)
+ return err;
+
+ err = usb_serial_generic_submit_read_urbs(port1, GFP_KERNEL);
+ if (err) {
+ usb_serial_generic_close(port0);
+ return err;
+ }
+
+ return 0;
+}
+
+static void mxuport_release(struct usb_serial *serial)
+{
+ struct usb_serial_port *port0 = serial->port[0];
+ struct usb_serial_port *port1 = serial->port[1];
+
+ usb_serial_generic_close(port1);
+ usb_serial_generic_close(port0);
+}
+
+static int mxuport_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct mxuport_port *mxport = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ int err;
+
+ /* Set receive host (enable) */
+ err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_RX_HOST_EN,
+ 1, port->port_number);
+ if (err)
+ return err;
+
+ err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_OPEN,
+ 1, port->port_number);
+ if (err) {
+ mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_RX_HOST_EN,
+ 0, port->port_number);
+ return err;
+ }
+
+ /* Initial port termios */
+ if (tty)
+ mxuport_set_termios(tty, port, NULL);
+
+ /*
+ * TODO: use RQ_VENDOR_GET_MSR, once we know what it
+ * returns.
+ */
+ mxport->msr_state = 0;
+
+ return err;
+}
+
+static void mxuport_close(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+
+ mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_OPEN, 0,
+ port->port_number);
+
+ mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_RX_HOST_EN, 0,
+ port->port_number);
+}
+
+/* Send a break to the port. */
+static int mxuport_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_serial *serial = port->serial;
+ int enable;
+
+ if (break_state == -1) {
+ enable = 1;
+ dev_dbg(&port->dev, "%s - sending break\n", __func__);
+ } else {
+ enable = 0;
+ dev_dbg(&port->dev, "%s - clearing break\n", __func__);
+ }
+
+ return mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_BREAK,
+ enable, port->port_number);
+}
+
+static int mxuport_resume(struct usb_serial *serial)
+{
+ struct usb_serial_port *port;
+ int c = 0;
+ int i;
+ int r;
+
+ for (i = 0; i < 2; i++) {
+ port = serial->port[i];
+
+ r = usb_serial_generic_submit_read_urbs(port, GFP_NOIO);
+ if (r < 0)
+ c++;
+ }
+
+ for (i = 0; i < serial->num_ports; i++) {
+ port = serial->port[i];
+ if (!tty_port_initialized(&port->port))
+ continue;
+
+ r = usb_serial_generic_write_start(port, GFP_NOIO);
+ if (r < 0)
+ c++;
+ }
+
+ return c ? -EIO : 0;
+}
+
+static struct usb_serial_driver mxuport_device = {
+ .driver = {
+ .name = "mxuport",
+ },
+ .description = "MOXA UPort",
+ .id_table = mxuport_idtable,
+ .num_bulk_in = 2,
+ .num_bulk_out = 1,
+ .probe = mxuport_probe,
+ .port_probe = mxuport_port_probe,
+ .attach = mxuport_attach,
+ .release = mxuport_release,
+ .calc_num_ports = mxuport_calc_num_ports,
+ .open = mxuport_open,
+ .close = mxuport_close,
+ .set_termios = mxuport_set_termios,
+ .break_ctl = mxuport_break_ctl,
+ .tx_empty = mxuport_tx_empty,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .throttle = mxuport_throttle,
+ .unthrottle = mxuport_unthrottle,
+ .tiocmget = mxuport_tiocmget,
+ .tiocmset = mxuport_tiocmset,
+ .dtr_rts = mxuport_dtr_rts,
+ .process_read_urb = mxuport_process_read_urb,
+ .prepare_write_buffer = mxuport_prepare_write_buffer,
+ .resume = mxuport_resume,
+};
+
+static struct usb_serial_driver *const serial_drivers[] = {
+ &mxuport_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, mxuport_idtable);
+
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
+MODULE_AUTHOR("<support@moxa.com>");
+MODULE_DESCRIPTION("Moxa UPORT USB Serial driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/navman.c b/drivers/usb/serial/navman.c
new file mode 100644
index 000000000..4d57a5902
--- /dev/null
+++ b/drivers/usb/serial/navman.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Navman Serial USB driver
+ *
+ * Copyright (C) 2006 Greg Kroah-Hartman <gregkh@suse.de>
+ *
+ * TODO:
+ * Add termios method that uses copy_hw but also kills all echo
+ * flags as the navman is rx only so cannot echo.
+ */
+
+#include <linux/gfp.h>
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x0a99, 0x0001) }, /* Talon Technology device */
+ { USB_DEVICE(0x0df7, 0x0900) }, /* Mobile Action i-gotU */
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static void navman_read_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+ int result;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ goto exit;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
+
+ if (urb->actual_length) {
+ tty_insert_flip_string(&port->port, data, urb->actual_length);
+ tty_flip_buffer_push(&port->port);
+ }
+
+exit:
+ result = usb_submit_urb(urb, GFP_ATOMIC);
+ if (result)
+ dev_err(&urb->dev->dev,
+ "%s - Error %d submitting interrupt urb\n",
+ __func__, result);
+}
+
+static int navman_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ int result = 0;
+
+ if (port->interrupt_in_urb) {
+ dev_dbg(&port->dev, "%s - adding interrupt input for treo\n",
+ __func__);
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (result)
+ dev_err(&port->dev,
+ "%s - failed submitting interrupt urb, error %d\n",
+ __func__, result);
+ }
+ return result;
+}
+
+static void navman_close(struct usb_serial_port *port)
+{
+ usb_kill_urb(port->interrupt_in_urb);
+}
+
+static int navman_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count)
+{
+ /*
+ * This device can't write any data, only read from the device
+ */
+ return -EOPNOTSUPP;
+}
+
+static struct usb_serial_driver navman_device = {
+ .driver = {
+ .name = "navman",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .open = navman_open,
+ .close = navman_close,
+ .write = navman_write,
+ .read_int_callback = navman_read_int_callback,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &navman_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION("Navman USB Serial driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/omninet.c b/drivers/usb/serial/omninet.c
new file mode 100644
index 000000000..363b8432c
--- /dev/null
+++ b/drivers/usb/serial/omninet.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB ZyXEL omni.net driver
+ *
+ * Copyright (C) 2013,2017 Johan Hovold <johan@kernel.org>
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ *
+ * Please report both successes and troubles to the author at omninet@kroah.com
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#define DRIVER_AUTHOR "Alessandro Zummo"
+#define DRIVER_DESC "USB ZyXEL omni.net Driver"
+
+#define ZYXEL_VENDOR_ID 0x0586
+#define ZYXEL_OMNINET_ID 0x1000
+#define ZYXEL_OMNI_56K_PLUS_ID 0x1500
+/* This one seems to be a re-branded ZyXEL device */
+#define BT_IGNITIONPRO_ID 0x2000
+
+#define OMNINET_HEADERLEN 4
+#define OMNINET_BULKOUTSIZE 64
+#define OMNINET_PAYLOADSIZE (OMNINET_BULKOUTSIZE - OMNINET_HEADERLEN)
+
+/* function prototypes */
+static void omninet_process_read_urb(struct urb *urb);
+static int omninet_prepare_write_buffer(struct usb_serial_port *port,
+ void *buf, size_t count);
+static int omninet_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds);
+static int omninet_port_probe(struct usb_serial_port *port);
+static void omninet_port_remove(struct usb_serial_port *port);
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(ZYXEL_VENDOR_ID, ZYXEL_OMNINET_ID) },
+ { USB_DEVICE(ZYXEL_VENDOR_ID, ZYXEL_OMNI_56K_PLUS_ID) },
+ { USB_DEVICE(ZYXEL_VENDOR_ID, BT_IGNITIONPRO_ID) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_serial_driver zyxel_omninet_device = {
+ .driver = {
+ .name = "omninet",
+ },
+ .description = "ZyXEL - omni.net usb",
+ .id_table = id_table,
+ .num_bulk_out = 2,
+ .bulk_out_size = OMNINET_BULKOUTSIZE,
+ .calc_num_ports = omninet_calc_num_ports,
+ .port_probe = omninet_port_probe,
+ .port_remove = omninet_port_remove,
+ .process_read_urb = omninet_process_read_urb,
+ .prepare_write_buffer = omninet_prepare_write_buffer,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &zyxel_omninet_device, NULL
+};
+
+
+/*
+ * The protocol.
+ *
+ * The omni.net always exchange 64 bytes of data with the host. The first
+ * four bytes are the control header.
+ *
+ * oh_seq is a sequence number. Don't know if/how it's used.
+ * oh_len is the length of the data bytes in the packet.
+ * oh_xxx Bit-mapped, related to handshaking and status info.
+ * I normally set it to 0x03 in transmitted frames.
+ * 7: Active when the TA is in a CONNECTed state.
+ * 6: unknown
+ * 5: handshaking, unknown
+ * 4: handshaking, unknown
+ * 3: unknown, usually 0
+ * 2: unknown, usually 0
+ * 1: handshaking, unknown, usually set to 1 in transmitted frames
+ * 0: handshaking, unknown, usually set to 1 in transmitted frames
+ * oh_pad Probably a pad byte.
+ *
+ * After the header you will find data bytes if oh_len was greater than zero.
+ */
+struct omninet_header {
+ __u8 oh_seq;
+ __u8 oh_len;
+ __u8 oh_xxx;
+ __u8 oh_pad;
+};
+
+struct omninet_data {
+ __u8 od_outseq; /* Sequence number for bulk_out URBs */
+};
+
+static int omninet_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ /* We need only the second bulk-out for our single-port device. */
+ epds->bulk_out[0] = epds->bulk_out[1];
+ epds->num_bulk_out = 1;
+
+ return 1;
+}
+
+static int omninet_port_probe(struct usb_serial_port *port)
+{
+ struct omninet_data *od;
+
+ od = kzalloc_obj(*od);
+ if (!od)
+ return -ENOMEM;
+
+ usb_set_serial_port_data(port, od);
+
+ return 0;
+}
+
+static void omninet_port_remove(struct usb_serial_port *port)
+{
+ struct omninet_data *od;
+
+ od = usb_get_serial_port_data(port);
+ kfree(od);
+}
+
+static void omninet_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ const struct omninet_header *hdr = urb->transfer_buffer;
+ const unsigned char *data;
+ size_t data_len;
+
+ if (urb->actual_length <= OMNINET_HEADERLEN || !hdr->oh_len)
+ return;
+
+ data = (char *)urb->transfer_buffer + OMNINET_HEADERLEN;
+ data_len = min_t(size_t, urb->actual_length - OMNINET_HEADERLEN,
+ hdr->oh_len);
+ tty_insert_flip_string(&port->port, data, data_len);
+ tty_flip_buffer_push(&port->port);
+}
+
+static int omninet_prepare_write_buffer(struct usb_serial_port *port,
+ void *buf, size_t count)
+{
+ struct omninet_data *od = usb_get_serial_port_data(port);
+ struct omninet_header *header = buf;
+
+ count = min_t(size_t, count, OMNINET_PAYLOADSIZE);
+
+ count = kfifo_out_locked(&port->write_fifo, buf + OMNINET_HEADERLEN,
+ count, &port->lock);
+
+ header->oh_seq = od->od_outseq++;
+ header->oh_len = count;
+ header->oh_xxx = 0x03;
+ header->oh_pad = 0x00;
+
+ /* always 64 bytes */
+ return OMNINET_BULKOUTSIZE;
+}
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/opticon.c b/drivers/usb/serial/opticon.c
new file mode 100644
index 000000000..152dfc901
--- /dev/null
+++ b/drivers/usb/serial/opticon.c
@@ -0,0 +1,403 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Opticon USB barcode to serial driver
+ *
+ * Copyright (C) 2011 - 2012 Johan Hovold <jhovold@gmail.com>
+ * Copyright (C) 2011 Martin Jansen <martin.jansen@opticon.com>
+ * Copyright (C) 2008 - 2009 Greg Kroah-Hartman <gregkh@suse.de>
+ * Copyright (C) 2008 - 2009 Novell Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/slab.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#define CONTROL_RTS 0x02
+#define RESEND_CTS_STATE 0x03
+
+/* max number of write urbs in flight */
+#define URB_UPPER_LIMIT 8
+
+/* This driver works for the Opticon 1D barcode reader
+ * an examples of 1D barcode types are EAN, UPC, Code39, IATA etc.. */
+#define DRIVER_DESC "Opticon USB barcode to serial driver (1D)"
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x065a, 0x0009) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+/* This structure holds all of the individual device information */
+struct opticon_private {
+ spinlock_t lock; /* protects the following flags */
+ bool rts;
+ bool cts;
+ int outstanding_urbs;
+ int outstanding_bytes;
+
+ struct usb_anchor anchor;
+};
+
+
+static void opticon_process_data_packet(struct usb_serial_port *port,
+ const unsigned char *buf, size_t len)
+{
+ tty_insert_flip_string(&port->port, buf, len);
+ tty_flip_buffer_push(&port->port);
+}
+
+static void opticon_process_status_packet(struct usb_serial_port *port,
+ const unsigned char *buf, size_t len)
+{
+ struct opticon_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (buf[0] == 0x00)
+ priv->cts = false;
+ else
+ priv->cts = true;
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static void opticon_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ const unsigned char *hdr = urb->transfer_buffer;
+ const unsigned char *data = hdr + 2;
+ size_t data_len = urb->actual_length - 2;
+
+ if (urb->actual_length <= 2) {
+ dev_dbg(&port->dev, "malformed packet received: %d bytes\n",
+ urb->actual_length);
+ return;
+ }
+ /*
+ * Data from the device comes with a 2 byte header:
+ *
+ * <0x00><0x00>data...
+ * This is real data to be sent to the tty layer
+ * <0x00><0x01>level
+ * This is a CTS level change, the third byte is the CTS
+ * value (0 for low, 1 for high).
+ */
+ if ((hdr[0] == 0x00) && (hdr[1] == 0x00)) {
+ opticon_process_data_packet(port, data, data_len);
+ } else if ((hdr[0] == 0x00) && (hdr[1] == 0x01)) {
+ opticon_process_status_packet(port, data, data_len);
+ } else {
+ dev_dbg(&port->dev, "unknown packet received: %02x %02x\n",
+ hdr[0], hdr[1]);
+ }
+}
+
+static int send_control_msg(struct usb_serial_port *port, u8 requesttype,
+ u8 val)
+{
+ struct usb_serial *serial = port->serial;
+ int retval;
+ u8 *buffer;
+
+ buffer = kzalloc(1, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ buffer[0] = val;
+ /* Send the message to the vendor control endpoint
+ * of the connected device */
+ retval = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ requesttype,
+ USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
+ 0, 0, buffer, 1, USB_CTRL_SET_TIMEOUT);
+ kfree(buffer);
+
+ if (retval < 0)
+ return retval;
+
+ return 0;
+}
+
+static int opticon_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct opticon_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ int res;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->rts = false;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /* Clear RTS line */
+ send_control_msg(port, CONTROL_RTS, 0);
+
+ /* clear the halt status of the endpoint */
+ usb_clear_halt(port->serial->dev, port->read_urb->pipe);
+
+ res = usb_serial_generic_open(tty, port);
+ if (res)
+ return res;
+
+ /* Request CTS line state, sometimes during opening the current
+ * CTS state can be missed. */
+ send_control_msg(port, RESEND_CTS_STATE, 1);
+
+ return res;
+}
+
+static void opticon_close(struct usb_serial_port *port)
+{
+ struct opticon_private *priv = usb_get_serial_port_data(port);
+
+ usb_kill_anchored_urbs(&priv->anchor);
+
+ usb_serial_generic_close(port);
+}
+
+static void opticon_write_control_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct opticon_private *priv = usb_get_serial_port_data(port);
+ int status = urb->status;
+ unsigned long flags;
+
+ /* free up the transfer buffer, as usb_free_urb() does not do this */
+ kfree(urb->transfer_buffer);
+
+ /* setup packet may be set if we're using it for writing */
+ kfree(urb->setup_packet);
+
+ if (status)
+ dev_dbg(&port->dev,
+ "%s - non-zero urb status received: %d\n",
+ __func__, status);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ --priv->outstanding_urbs;
+ priv->outstanding_bytes -= urb->transfer_buffer_length;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ usb_serial_port_softint(port);
+}
+
+static int opticon_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count)
+{
+ struct opticon_private *priv = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ struct urb *urb;
+ unsigned char *buffer;
+ unsigned long flags;
+ struct usb_ctrlrequest *dr;
+ int ret = -ENOMEM;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->outstanding_urbs > URB_UPPER_LIMIT) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ dev_dbg(&port->dev, "%s - write limit hit\n", __func__);
+ return 0;
+ }
+ priv->outstanding_urbs++;
+ priv->outstanding_bytes += count;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ buffer = kmemdup(buf, count, GFP_ATOMIC);
+ if (!buffer)
+ goto error_no_buffer;
+
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!urb)
+ goto error_no_urb;
+
+ usb_serial_debug_data(&port->dev, __func__, count, buffer);
+
+ /* The connected devices do not have a bulk write endpoint,
+ * to transmit data to de barcode device the control endpoint is used */
+ dr = kmalloc_obj(struct usb_ctrlrequest, GFP_ATOMIC);
+ if (!dr)
+ goto error_no_dr;
+
+ dr->bRequestType = USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT;
+ dr->bRequest = 0x01;
+ dr->wValue = 0;
+ dr->wIndex = 0;
+ dr->wLength = cpu_to_le16(count);
+
+ usb_fill_control_urb(urb, serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ (unsigned char *)dr, buffer, count,
+ opticon_write_control_callback, port);
+
+ usb_anchor_urb(urb, &priv->anchor);
+
+ /* send it down the pipe */
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret) {
+ dev_err(&port->dev, "failed to submit write urb: %d\n", ret);
+ usb_unanchor_urb(urb);
+ goto error;
+ }
+
+ /* we are done with this urb, so let the host driver
+ * really free it when it is finished with it */
+ usb_free_urb(urb);
+
+ return count;
+error:
+ kfree(dr);
+error_no_dr:
+ usb_free_urb(urb);
+error_no_urb:
+ kfree(buffer);
+error_no_buffer:
+ spin_lock_irqsave(&priv->lock, flags);
+ --priv->outstanding_urbs;
+ priv->outstanding_bytes -= count;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return ret;
+}
+
+static unsigned int opticon_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct opticon_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ /*
+ * We really can take almost anything the user throws at us
+ * but let's pick a nice big number to tell the tty
+ * layer that we have lots of free space, unless we don't.
+ */
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->outstanding_urbs > URB_UPPER_LIMIT * 2 / 3) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ dev_dbg(&port->dev, "%s - write limit hit\n", __func__);
+ return 0;
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 2048;
+}
+
+static unsigned int opticon_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct opticon_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ unsigned int count;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ count = priv->outstanding_bytes;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return count;
+}
+
+static int opticon_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct opticon_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ int result = 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->rts)
+ result |= TIOCM_RTS;
+ if (priv->cts)
+ result |= TIOCM_CTS;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ dev_dbg(&port->dev, "%s - %x\n", __func__, result);
+ return result;
+}
+
+static int opticon_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct opticon_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ bool rts;
+ bool changed = false;
+ int ret;
+
+ /* We only support RTS so we only handle that */
+ spin_lock_irqsave(&priv->lock, flags);
+
+ rts = priv->rts;
+ if (set & TIOCM_RTS)
+ priv->rts = true;
+ if (clear & TIOCM_RTS)
+ priv->rts = false;
+ changed = rts ^ priv->rts;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (!changed)
+ return 0;
+
+ ret = send_control_msg(port, CONTROL_RTS, !rts);
+ if (ret)
+ return usb_translate_errors(ret);
+
+ return 0;
+}
+
+static int opticon_port_probe(struct usb_serial_port *port)
+{
+ struct opticon_private *priv;
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->lock);
+ init_usb_anchor(&priv->anchor);
+
+ usb_set_serial_port_data(port, priv);
+
+ return 0;
+}
+
+static void opticon_port_remove(struct usb_serial_port *port)
+{
+ struct opticon_private *priv = usb_get_serial_port_data(port);
+
+ kfree(priv);
+}
+
+static struct usb_serial_driver opticon_device = {
+ .driver = {
+ .name = "opticon",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .num_bulk_in = 1,
+ .bulk_in_size = 256,
+ .port_probe = opticon_port_probe,
+ .port_remove = opticon_port_remove,
+ .open = opticon_open,
+ .close = opticon_close,
+ .write = opticon_write,
+ .write_room = opticon_write_room,
+ .chars_in_buffer = opticon_chars_in_buffer,
+ .throttle = usb_serial_generic_throttle,
+ .unthrottle = usb_serial_generic_unthrottle,
+ .tiocmget = opticon_tiocmget,
+ .tiocmset = opticon_tiocmset,
+ .process_read_urb = opticon_process_read_urb,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &opticon_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
new file mode 100644
index 000000000..7275f4e7f
--- /dev/null
+++ b/drivers/usb/serial/option.c
@@ -0,0 +1,2729 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ USB Driver for GSM modems
+
+ Copyright (C) 2005 Matthias Urlichs <smurf@smurf.noris.de>
+
+ Portions copied from the Keyspan driver by Hugh Blemings <hugh@blemings.org>
+
+ History: see the git log.
+
+ Work sponsored by: Sigos GmbH, Germany <info@sigos.de>
+
+ This driver exists because the "normal" serial driver doesn't work too well
+ with GSM modems. Issues:
+ - data loss -- one single Receive URB is not nearly enough
+ - nonstandard flow (Option devices) control
+ - controlling the baud rate doesn't make sense
+
+ This driver is named "option" because the most common device it's
+ used for is a PC-Card (with an internal OHCI-USB interface, behind
+ which the GSM interface sits), made by Option Inc.
+
+ Some of the "one port" devices actually exhibit multiple USB instances
+ on the USB bus. This is not a bug, these ports are used for different
+ device features.
+*/
+
+#define DRIVER_AUTHOR "Matthias Urlichs <smurf@smurf.noris.de>"
+#define DRIVER_DESC "USB Driver for GSM modems"
+
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include "usb-wwan.h"
+
+/* Function prototypes */
+static int option_probe(struct usb_serial *serial,
+ const struct usb_device_id *id);
+static int option_attach(struct usb_serial *serial);
+static void option_release(struct usb_serial *serial);
+static void option_instat_callback(struct urb *urb);
+
+/* Vendor and product IDs */
+#define OPTION_VENDOR_ID 0x0AF0
+#define OPTION_PRODUCT_COLT 0x5000
+#define OPTION_PRODUCT_RICOLA 0x6000
+#define OPTION_PRODUCT_RICOLA_LIGHT 0x6100
+#define OPTION_PRODUCT_RICOLA_QUAD 0x6200
+#define OPTION_PRODUCT_RICOLA_QUAD_LIGHT 0x6300
+#define OPTION_PRODUCT_RICOLA_NDIS 0x6050
+#define OPTION_PRODUCT_RICOLA_NDIS_LIGHT 0x6150
+#define OPTION_PRODUCT_RICOLA_NDIS_QUAD 0x6250
+#define OPTION_PRODUCT_RICOLA_NDIS_QUAD_LIGHT 0x6350
+#define OPTION_PRODUCT_COBRA 0x6500
+#define OPTION_PRODUCT_COBRA_BUS 0x6501
+#define OPTION_PRODUCT_VIPER 0x6600
+#define OPTION_PRODUCT_VIPER_BUS 0x6601
+#define OPTION_PRODUCT_GT_MAX_READY 0x6701
+#define OPTION_PRODUCT_FUJI_MODEM_LIGHT 0x6721
+#define OPTION_PRODUCT_FUJI_MODEM_GT 0x6741
+#define OPTION_PRODUCT_FUJI_MODEM_EX 0x6761
+#define OPTION_PRODUCT_KOI_MODEM 0x6800
+#define OPTION_PRODUCT_SCORPION_MODEM 0x6901
+#define OPTION_PRODUCT_ETNA_MODEM 0x7001
+#define OPTION_PRODUCT_ETNA_MODEM_LITE 0x7021
+#define OPTION_PRODUCT_ETNA_MODEM_GT 0x7041
+#define OPTION_PRODUCT_ETNA_MODEM_EX 0x7061
+#define OPTION_PRODUCT_ETNA_KOI_MODEM 0x7100
+#define OPTION_PRODUCT_GTM380_MODEM 0x7201
+
+#define HUAWEI_VENDOR_ID 0x12D1
+#define HUAWEI_PRODUCT_E173 0x140C
+#define HUAWEI_PRODUCT_E1750 0x1406
+#define HUAWEI_PRODUCT_K4505 0x1464
+#define HUAWEI_PRODUCT_K3765 0x1465
+#define HUAWEI_PRODUCT_K4605 0x14C6
+#define HUAWEI_PRODUCT_E173S6 0x1C07
+
+#define QUANTA_VENDOR_ID 0x0408
+#define QUANTA_PRODUCT_Q101 0xEA02
+#define QUANTA_PRODUCT_Q111 0xEA03
+#define QUANTA_PRODUCT_GLX 0xEA04
+#define QUANTA_PRODUCT_GKE 0xEA05
+#define QUANTA_PRODUCT_GLE 0xEA06
+
+#define NOVATELWIRELESS_VENDOR_ID 0x1410
+
+/* YISO PRODUCTS */
+
+#define YISO_VENDOR_ID 0x0EAB
+#define YISO_PRODUCT_U893 0xC893
+
+/*
+ * NOVATEL WIRELESS PRODUCTS
+ *
+ * Note from Novatel Wireless:
+ * If your Novatel modem does not work on linux, don't
+ * change the option module, but check our website. If
+ * that does not help, contact ddeschepper@nvtl.com
+*/
+/* MERLIN EVDO PRODUCTS */
+#define NOVATELWIRELESS_PRODUCT_V640 0x1100
+#define NOVATELWIRELESS_PRODUCT_V620 0x1110
+#define NOVATELWIRELESS_PRODUCT_V740 0x1120
+#define NOVATELWIRELESS_PRODUCT_V720 0x1130
+
+/* MERLIN HSDPA/HSPA PRODUCTS */
+#define NOVATELWIRELESS_PRODUCT_U730 0x1400
+#define NOVATELWIRELESS_PRODUCT_U740 0x1410
+#define NOVATELWIRELESS_PRODUCT_U870 0x1420
+#define NOVATELWIRELESS_PRODUCT_XU870 0x1430
+#define NOVATELWIRELESS_PRODUCT_X950D 0x1450
+
+/* EXPEDITE PRODUCTS */
+#define NOVATELWIRELESS_PRODUCT_EV620 0x2100
+#define NOVATELWIRELESS_PRODUCT_ES720 0x2110
+#define NOVATELWIRELESS_PRODUCT_E725 0x2120
+#define NOVATELWIRELESS_PRODUCT_ES620 0x2130
+#define NOVATELWIRELESS_PRODUCT_EU730 0x2400
+#define NOVATELWIRELESS_PRODUCT_EU740 0x2410
+#define NOVATELWIRELESS_PRODUCT_EU870D 0x2420
+/* OVATION PRODUCTS */
+#define NOVATELWIRELESS_PRODUCT_MC727 0x4100
+#define NOVATELWIRELESS_PRODUCT_MC950D 0x4400
+/*
+ * Note from Novatel Wireless:
+ * All PID in the 5xxx range are currently reserved for
+ * auto-install CDROMs, and should not be added to this
+ * module.
+ *
+ * #define NOVATELWIRELESS_PRODUCT_U727 0x5010
+ * #define NOVATELWIRELESS_PRODUCT_MC727_NEW 0x5100
+*/
+#define NOVATELWIRELESS_PRODUCT_OVMC760 0x6002
+#define NOVATELWIRELESS_PRODUCT_MC780 0x6010
+#define NOVATELWIRELESS_PRODUCT_EVDO_FULLSPEED 0x6000
+#define NOVATELWIRELESS_PRODUCT_EVDO_HIGHSPEED 0x6001
+#define NOVATELWIRELESS_PRODUCT_HSPA_FULLSPEED 0x7000
+#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED 0x7001
+#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED3 0x7003
+#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED4 0x7004
+#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED5 0x7005
+#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED6 0x7006
+#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED7 0x7007
+#define NOVATELWIRELESS_PRODUCT_MC996D 0x7030
+#define NOVATELWIRELESS_PRODUCT_MF3470 0x7041
+#define NOVATELWIRELESS_PRODUCT_MC547 0x7042
+#define NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_FULLSPEED 0x8000
+#define NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_HIGHSPEED 0x8001
+#define NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_FULLSPEED 0x9000
+#define NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_HIGHSPEED 0x9001
+#define NOVATELWIRELESS_PRODUCT_E362 0x9010
+#define NOVATELWIRELESS_PRODUCT_E371 0x9011
+#define NOVATELWIRELESS_PRODUCT_U620L 0x9022
+#define NOVATELWIRELESS_PRODUCT_G2 0xA010
+#define NOVATELWIRELESS_PRODUCT_MC551 0xB001
+
+#define UBLOX_VENDOR_ID 0x1546
+
+/* AMOI PRODUCTS */
+#define AMOI_VENDOR_ID 0x1614
+#define AMOI_PRODUCT_H01 0x0800
+#define AMOI_PRODUCT_H01A 0x7002
+#define AMOI_PRODUCT_H02 0x0802
+#define AMOI_PRODUCT_SKYPEPHONE_S2 0x0407
+
+#define DELL_VENDOR_ID 0x413C
+
+/* Dell modems */
+#define DELL_PRODUCT_5700_MINICARD 0x8114
+#define DELL_PRODUCT_5500_MINICARD 0x8115
+#define DELL_PRODUCT_5505_MINICARD 0x8116
+#define DELL_PRODUCT_5700_EXPRESSCARD 0x8117
+#define DELL_PRODUCT_5510_EXPRESSCARD 0x8118
+
+#define DELL_PRODUCT_5700_MINICARD_SPRINT 0x8128
+#define DELL_PRODUCT_5700_MINICARD_TELUS 0x8129
+
+#define DELL_PRODUCT_5720_MINICARD_VZW 0x8133
+#define DELL_PRODUCT_5720_MINICARD_SPRINT 0x8134
+#define DELL_PRODUCT_5720_MINICARD_TELUS 0x8135
+#define DELL_PRODUCT_5520_MINICARD_CINGULAR 0x8136
+#define DELL_PRODUCT_5520_MINICARD_GENERIC_L 0x8137
+#define DELL_PRODUCT_5520_MINICARD_GENERIC_I 0x8138
+
+#define DELL_PRODUCT_5730_MINICARD_SPRINT 0x8180
+#define DELL_PRODUCT_5730_MINICARD_TELUS 0x8181
+#define DELL_PRODUCT_5730_MINICARD_VZW 0x8182
+
+#define DELL_PRODUCT_5800_MINICARD_VZW 0x8195 /* Novatel E362 */
+#define DELL_PRODUCT_5800_V2_MINICARD_VZW 0x8196 /* Novatel E362 */
+#define DELL_PRODUCT_5804_MINICARD_ATT 0x819b /* Novatel E371 */
+
+#define DELL_PRODUCT_5821E 0x81d7
+#define DELL_PRODUCT_5821E_ESIM 0x81e0
+#define DELL_PRODUCT_5829E_ESIM 0x81e4
+#define DELL_PRODUCT_5829E 0x81e6
+#define DELL_PRODUCT_5826E_ESIM 0x81ea
+
+#define DELL_PRODUCT_FM101R_ESIM 0x8213
+#define DELL_PRODUCT_FM101R 0x8215
+
+#define KYOCERA_VENDOR_ID 0x0c88
+#define KYOCERA_PRODUCT_KPC650 0x17da
+#define KYOCERA_PRODUCT_KPC680 0x180a
+
+#define ANYDATA_VENDOR_ID 0x16d5
+#define ANYDATA_PRODUCT_ADU_620UW 0x6202
+#define ANYDATA_PRODUCT_ADU_E100A 0x6501
+#define ANYDATA_PRODUCT_ADU_500A 0x6502
+
+#define AXESSTEL_VENDOR_ID 0x1726
+#define AXESSTEL_PRODUCT_MV110H 0x1000
+
+#define BANDRICH_VENDOR_ID 0x1A8D
+#define BANDRICH_PRODUCT_C100_1 0x1002
+#define BANDRICH_PRODUCT_C100_2 0x1003
+#define BANDRICH_PRODUCT_1004 0x1004
+#define BANDRICH_PRODUCT_1005 0x1005
+#define BANDRICH_PRODUCT_1006 0x1006
+#define BANDRICH_PRODUCT_1007 0x1007
+#define BANDRICH_PRODUCT_1008 0x1008
+#define BANDRICH_PRODUCT_1009 0x1009
+#define BANDRICH_PRODUCT_100A 0x100a
+
+#define BANDRICH_PRODUCT_100B 0x100b
+#define BANDRICH_PRODUCT_100C 0x100c
+#define BANDRICH_PRODUCT_100D 0x100d
+#define BANDRICH_PRODUCT_100E 0x100e
+
+#define BANDRICH_PRODUCT_100F 0x100f
+#define BANDRICH_PRODUCT_1010 0x1010
+#define BANDRICH_PRODUCT_1011 0x1011
+#define BANDRICH_PRODUCT_1012 0x1012
+
+#define QUALCOMM_VENDOR_ID 0x05C6
+/* These Quectel products use Qualcomm's vendor ID */
+#define QUECTEL_PRODUCT_UC20 0x9003
+#define QUECTEL_PRODUCT_UC15 0x9090
+/* These u-blox products use Qualcomm's vendor ID */
+#define UBLOX_PRODUCT_R410M 0x90b2
+/* These Yuga products use Qualcomm's vendor ID */
+#define YUGA_PRODUCT_CLM920_NC5 0x9625
+
+#define QUECTEL_VENDOR_ID 0x2c7c
+/* These Quectel products use Quectel's vendor ID */
+#define QUECTEL_PRODUCT_EC21 0x0121
+#define QUECTEL_PRODUCT_RG650V 0x0122
+#define QUECTEL_PRODUCT_EM061K_LTA 0x0123
+#define QUECTEL_PRODUCT_EM061K_LMS 0x0124
+#define QUECTEL_PRODUCT_EC25 0x0125
+#define QUECTEL_PRODUCT_EM060K_128 0x0128
+#define QUECTEL_PRODUCT_EM060K_129 0x0129
+#define QUECTEL_PRODUCT_EM060K_12a 0x012a
+#define QUECTEL_PRODUCT_EM060K_12b 0x012b
+#define QUECTEL_PRODUCT_EM060K_12c 0x012c
+#define QUECTEL_PRODUCT_EG91 0x0191
+#define QUECTEL_PRODUCT_EG95 0x0195
+#define QUECTEL_PRODUCT_BG96 0x0296
+#define QUECTEL_PRODUCT_EP06 0x0306
+#define QUECTEL_PRODUCT_EM05G 0x030a
+#define QUECTEL_PRODUCT_EM060K 0x030b
+#define QUECTEL_PRODUCT_EM05G_CS 0x030c
+#define QUECTEL_PRODUCT_EM05GV2 0x030e
+#define QUECTEL_PRODUCT_EM05CN_SG 0x0310
+#define QUECTEL_PRODUCT_EM05G_SG 0x0311
+#define QUECTEL_PRODUCT_EM05CN 0x0312
+#define QUECTEL_PRODUCT_EM05G_GR 0x0313
+#define QUECTEL_PRODUCT_EM05G_RS 0x0314
+#define QUECTEL_PRODUCT_RG255C 0x0316
+#define QUECTEL_PRODUCT_EM12 0x0512
+#define QUECTEL_PRODUCT_RM500Q 0x0800
+#define QUECTEL_PRODUCT_RM520N 0x0801
+#define QUECTEL_PRODUCT_EC200U 0x0901
+#define QUECTEL_PRODUCT_EG912Y 0x6001
+#define QUECTEL_PRODUCT_EC200S_CN 0x6002
+#define QUECTEL_PRODUCT_EC200A 0x6005
+#define QUECTEL_PRODUCT_EG916Q 0x6007
+#define QUECTEL_PRODUCT_EM061K_LWW 0x6008
+#define QUECTEL_PRODUCT_EM061K_LCN 0x6009
+#define QUECTEL_PRODUCT_EC200T 0x6026
+#define QUECTEL_PRODUCT_RM500K 0x7001
+
+#define CMOTECH_VENDOR_ID 0x16d8
+#define CMOTECH_PRODUCT_6001 0x6001
+#define CMOTECH_PRODUCT_CMU_300 0x6002
+#define CMOTECH_PRODUCT_6003 0x6003
+#define CMOTECH_PRODUCT_6004 0x6004
+#define CMOTECH_PRODUCT_6005 0x6005
+#define CMOTECH_PRODUCT_CGU_628A 0x6006
+#define CMOTECH_PRODUCT_CHE_628S 0x6007
+#define CMOTECH_PRODUCT_CMU_301 0x6008
+#define CMOTECH_PRODUCT_CHU_628 0x6280
+#define CMOTECH_PRODUCT_CHU_628S 0x6281
+#define CMOTECH_PRODUCT_CDU_680 0x6803
+#define CMOTECH_PRODUCT_CDU_685A 0x6804
+#define CMOTECH_PRODUCT_CHU_720S 0x7001
+#define CMOTECH_PRODUCT_7002 0x7002
+#define CMOTECH_PRODUCT_CHU_629K 0x7003
+#define CMOTECH_PRODUCT_7004 0x7004
+#define CMOTECH_PRODUCT_7005 0x7005
+#define CMOTECH_PRODUCT_CGU_629 0x7006
+#define CMOTECH_PRODUCT_CHU_629S 0x700a
+#define CMOTECH_PRODUCT_CHU_720I 0x7211
+#define CMOTECH_PRODUCT_7212 0x7212
+#define CMOTECH_PRODUCT_7213 0x7213
+#define CMOTECH_PRODUCT_7251 0x7251
+#define CMOTECH_PRODUCT_7252 0x7252
+#define CMOTECH_PRODUCT_7253 0x7253
+
+#define TELIT_VENDOR_ID 0x1bc7
+#define TELIT_PRODUCT_UC864E 0x1003
+#define TELIT_PRODUCT_UC864G 0x1004
+#define TELIT_PRODUCT_CC864_DUAL 0x1005
+#define TELIT_PRODUCT_CC864_SINGLE 0x1006
+#define TELIT_PRODUCT_DE910_DUAL 0x1010
+#define TELIT_PRODUCT_UE910_V2 0x1012
+#define TELIT_PRODUCT_LE922_USBCFG1 0x1040
+#define TELIT_PRODUCT_LE922_USBCFG2 0x1041
+#define TELIT_PRODUCT_LE922_USBCFG0 0x1042
+#define TELIT_PRODUCT_LE922_USBCFG3 0x1043
+#define TELIT_PRODUCT_LE922_USBCFG5 0x1045
+#define TELIT_PRODUCT_ME910 0x1100
+#define TELIT_PRODUCT_ME910_DUAL_MODEM 0x1101
+#define TELIT_PRODUCT_LE920 0x1200
+#define TELIT_PRODUCT_LE910 0x1201
+#define TELIT_PRODUCT_LE910_USBCFG4 0x1206
+#define TELIT_PRODUCT_LE920A4_1207 0x1207
+#define TELIT_PRODUCT_LE920A4_1208 0x1208
+#define TELIT_PRODUCT_LE920A4_1211 0x1211
+#define TELIT_PRODUCT_LE920A4_1212 0x1212
+#define TELIT_PRODUCT_LE920A4_1213 0x1213
+#define TELIT_PRODUCT_LE920A4_1214 0x1214
+
+/* ZTE PRODUCTS */
+#define ZTE_VENDOR_ID 0x19d2
+#define ZTE_PRODUCT_MF622 0x0001
+#define ZTE_PRODUCT_MF628 0x0015
+#define ZTE_PRODUCT_MF626 0x0031
+#define ZTE_PRODUCT_ZM8620_X 0x0396
+#define ZTE_PRODUCT_ME3620_MBIM 0x0426
+#define ZTE_PRODUCT_ME3620_X 0x1432
+#define ZTE_PRODUCT_ME3620_L 0x1433
+#define ZTE_PRODUCT_AC2726 0xfff1
+#define ZTE_PRODUCT_MG880 0xfffd
+#define ZTE_PRODUCT_CDMA_TECH 0xfffe
+#define ZTE_PRODUCT_AC8710T 0xffff
+#define ZTE_PRODUCT_MC2718 0xffe8
+#define ZTE_PRODUCT_AD3812 0xffeb
+#define ZTE_PRODUCT_MC2716 0xffed
+
+#define BENQ_VENDOR_ID 0x04a5
+#define BENQ_PRODUCT_H10 0x4068
+
+#define DLINK_VENDOR_ID 0x1186
+#define DLINK_PRODUCT_DWM_652 0x3e04
+#define DLINK_PRODUCT_DWM_652_U5 0xce16
+#define DLINK_PRODUCT_DWM_652_U5A 0xce1e
+
+#define QISDA_VENDOR_ID 0x1da5
+#define QISDA_PRODUCT_H21_4512 0x4512
+#define QISDA_PRODUCT_H21_4523 0x4523
+#define QISDA_PRODUCT_H20_4515 0x4515
+#define QISDA_PRODUCT_H20_4518 0x4518
+#define QISDA_PRODUCT_H20_4519 0x4519
+
+/* TLAYTECH PRODUCTS */
+#define TLAYTECH_VENDOR_ID 0x20B9
+#define TLAYTECH_PRODUCT_TEU800 0x1682
+
+/* TOSHIBA PRODUCTS */
+#define TOSHIBA_VENDOR_ID 0x0930
+#define TOSHIBA_PRODUCT_HSDPA_MINICARD 0x1302
+#define TOSHIBA_PRODUCT_G450 0x0d45
+
+#define ALINK_VENDOR_ID 0x1e0e
+#define SIMCOM_PRODUCT_SIM7100E 0x9001 /* Yes, ALINK_VENDOR_ID */
+#define ALINK_PRODUCT_PH300 0x9100
+#define ALINK_PRODUCT_3GU 0x9200
+
+/* ALCATEL PRODUCTS */
+#define ALCATEL_VENDOR_ID 0x1bbb
+#define ALCATEL_PRODUCT_X060S_X200 0x0000
+#define ALCATEL_PRODUCT_X220_X500D 0x0017
+#define ALCATEL_PRODUCT_L100V 0x011e
+#define ALCATEL_PRODUCT_L800MA 0x0203
+
+#define PIRELLI_VENDOR_ID 0x1266
+#define PIRELLI_PRODUCT_C100_1 0x1002
+#define PIRELLI_PRODUCT_C100_2 0x1003
+#define PIRELLI_PRODUCT_1004 0x1004
+#define PIRELLI_PRODUCT_1005 0x1005
+#define PIRELLI_PRODUCT_1006 0x1006
+#define PIRELLI_PRODUCT_1007 0x1007
+#define PIRELLI_PRODUCT_1008 0x1008
+#define PIRELLI_PRODUCT_1009 0x1009
+#define PIRELLI_PRODUCT_100A 0x100a
+#define PIRELLI_PRODUCT_100B 0x100b
+#define PIRELLI_PRODUCT_100C 0x100c
+#define PIRELLI_PRODUCT_100D 0x100d
+#define PIRELLI_PRODUCT_100E 0x100e
+#define PIRELLI_PRODUCT_100F 0x100f
+#define PIRELLI_PRODUCT_1011 0x1011
+#define PIRELLI_PRODUCT_1012 0x1012
+
+/* Airplus products */
+#define AIRPLUS_VENDOR_ID 0x1011
+#define AIRPLUS_PRODUCT_MCD650 0x3198
+
+/* Longcheer/Longsung vendor ID; makes whitelabel devices that
+ * many other vendors like 4G Systems, Alcatel, ChinaBird,
+ * Mobidata, etc sell under their own brand names.
+ */
+#define LONGCHEER_VENDOR_ID 0x1c9e
+
+/* 4G Systems products */
+/* This one was sold as the VW and Skoda "Carstick LTE" */
+#define FOUR_G_SYSTEMS_PRODUCT_CARSTICK_LTE 0x7605
+/* This is the 4G XS Stick W14 a.k.a. Mobilcom Debitel Surf-Stick *
+ * It seems to contain a Qualcomm QSC6240/6290 chipset */
+#define FOUR_G_SYSTEMS_PRODUCT_W14 0x9603
+#define FOUR_G_SYSTEMS_PRODUCT_W100 0x9b01
+
+/* Fujisoft products */
+#define FUJISOFT_PRODUCT_FS040U 0x9b02
+
+/* iBall 3.5G connect wireless modem */
+#define IBALL_3_5G_CONNECT 0x9605
+
+/* Zoom */
+#define ZOOM_PRODUCT_4597 0x9607
+
+/* SpeedUp SU9800 usb 3g modem */
+#define SPEEDUP_PRODUCT_SU9800 0x9800
+
+/* Haier products */
+#define HAIER_VENDOR_ID 0x201e
+#define HAIER_PRODUCT_CE81B 0x10f8
+#define HAIER_PRODUCT_CE100 0x2009
+
+/* Gemalto's Cinterion products (formerly Siemens) */
+#define SIEMENS_VENDOR_ID 0x0681
+#define CINTERION_VENDOR_ID 0x1e2d
+#define CINTERION_PRODUCT_HC25_MDMNET 0x0040
+#define CINTERION_PRODUCT_HC25_MDM 0x0047
+#define CINTERION_PRODUCT_HC28_MDMNET 0x004A /* same for HC28J */
+#define CINTERION_PRODUCT_HC28_MDM 0x004C
+#define CINTERION_PRODUCT_EU3_E 0x0051
+#define CINTERION_PRODUCT_EU3_P 0x0052
+#define CINTERION_PRODUCT_PH8 0x0053
+#define CINTERION_PRODUCT_AHXX 0x0055
+#define CINTERION_PRODUCT_PLXX 0x0060
+#define CINTERION_PRODUCT_EXS82 0x006c
+#define CINTERION_PRODUCT_PH8_2RMNET 0x0082
+#define CINTERION_PRODUCT_PH8_AUDIO 0x0083
+#define CINTERION_PRODUCT_AHXX_2RMNET 0x0084
+#define CINTERION_PRODUCT_AHXX_AUDIO 0x0085
+#define CINTERION_PRODUCT_CLS8 0x00b0
+#define CINTERION_PRODUCT_MV31_MBIM 0x00b3
+#define CINTERION_PRODUCT_MV31_RMNET 0x00b7
+#define CINTERION_PRODUCT_MV31_2_MBIM 0x00b8
+#define CINTERION_PRODUCT_MV31_2_RMNET 0x00b9
+#define CINTERION_PRODUCT_MV32_WA 0x00f1
+#define CINTERION_PRODUCT_MV32_WB 0x00f2
+#define CINTERION_PRODUCT_MV32_WA_RMNET 0x00f3
+#define CINTERION_PRODUCT_MV32_WB_RMNET 0x00f4
+
+/* Olivetti products */
+#define OLIVETTI_VENDOR_ID 0x0b3c
+#define OLIVETTI_PRODUCT_OLICARD100 0xc000
+#define OLIVETTI_PRODUCT_OLICARD120 0xc001
+#define OLIVETTI_PRODUCT_OLICARD140 0xc002
+#define OLIVETTI_PRODUCT_OLICARD145 0xc003
+#define OLIVETTI_PRODUCT_OLICARD155 0xc004
+#define OLIVETTI_PRODUCT_OLICARD200 0xc005
+#define OLIVETTI_PRODUCT_OLICARD160 0xc00a
+#define OLIVETTI_PRODUCT_OLICARD500 0xc00b
+
+/* Celot products */
+#define CELOT_VENDOR_ID 0x211f
+#define CELOT_PRODUCT_CT680M 0x6801
+
+/* Samsung products */
+#define SAMSUNG_VENDOR_ID 0x04e8
+#define SAMSUNG_PRODUCT_GT_B3730 0x6889
+
+/* YUGA products www.yuga-info.com gavin.kx@qq.com */
+#define YUGA_VENDOR_ID 0x257A
+#define YUGA_PRODUCT_CEM600 0x1601
+#define YUGA_PRODUCT_CEM610 0x1602
+#define YUGA_PRODUCT_CEM500 0x1603
+#define YUGA_PRODUCT_CEM510 0x1604
+#define YUGA_PRODUCT_CEM800 0x1605
+#define YUGA_PRODUCT_CEM900 0x1606
+
+#define YUGA_PRODUCT_CEU818 0x1607
+#define YUGA_PRODUCT_CEU816 0x1608
+#define YUGA_PRODUCT_CEU828 0x1609
+#define YUGA_PRODUCT_CEU826 0x160A
+#define YUGA_PRODUCT_CEU518 0x160B
+#define YUGA_PRODUCT_CEU516 0x160C
+#define YUGA_PRODUCT_CEU528 0x160D
+#define YUGA_PRODUCT_CEU526 0x160F
+#define YUGA_PRODUCT_CEU881 0x161F
+#define YUGA_PRODUCT_CEU882 0x162F
+
+#define YUGA_PRODUCT_CWM600 0x2601
+#define YUGA_PRODUCT_CWM610 0x2602
+#define YUGA_PRODUCT_CWM500 0x2603
+#define YUGA_PRODUCT_CWM510 0x2604
+#define YUGA_PRODUCT_CWM800 0x2605
+#define YUGA_PRODUCT_CWM900 0x2606
+
+#define YUGA_PRODUCT_CWU718 0x2607
+#define YUGA_PRODUCT_CWU716 0x2608
+#define YUGA_PRODUCT_CWU728 0x2609
+#define YUGA_PRODUCT_CWU726 0x260A
+#define YUGA_PRODUCT_CWU518 0x260B
+#define YUGA_PRODUCT_CWU516 0x260C
+#define YUGA_PRODUCT_CWU528 0x260D
+#define YUGA_PRODUCT_CWU581 0x260E
+#define YUGA_PRODUCT_CWU526 0x260F
+#define YUGA_PRODUCT_CWU582 0x261F
+#define YUGA_PRODUCT_CWU583 0x262F
+
+#define YUGA_PRODUCT_CLM600 0x3601
+#define YUGA_PRODUCT_CLM610 0x3602
+#define YUGA_PRODUCT_CLM500 0x3603
+#define YUGA_PRODUCT_CLM510 0x3604
+#define YUGA_PRODUCT_CLM800 0x3605
+#define YUGA_PRODUCT_CLM900 0x3606
+
+#define YUGA_PRODUCT_CLU718 0x3607
+#define YUGA_PRODUCT_CLU716 0x3608
+#define YUGA_PRODUCT_CLU728 0x3609
+#define YUGA_PRODUCT_CLU726 0x360A
+#define YUGA_PRODUCT_CLU518 0x360B
+#define YUGA_PRODUCT_CLU516 0x360C
+#define YUGA_PRODUCT_CLU528 0x360D
+#define YUGA_PRODUCT_CLU526 0x360F
+
+/* Viettel products */
+#define VIETTEL_VENDOR_ID 0x2262
+#define VIETTEL_PRODUCT_VT1000 0x0002
+
+/* ZD Incorporated */
+#define ZD_VENDOR_ID 0x0685
+#define ZD_PRODUCT_7000 0x7000
+
+/* LG products */
+#define LG_VENDOR_ID 0x1004
+#define LG_PRODUCT_L02C 0x618f
+
+/* MediaTek products */
+#define MEDIATEK_VENDOR_ID 0x0e8d
+#define MEDIATEK_PRODUCT_DC_1COM 0x00a0
+#define MEDIATEK_PRODUCT_DC_4COM 0x00a5
+#define MEDIATEK_PRODUCT_DC_4COM2 0x00a7
+#define MEDIATEK_PRODUCT_DC_5COM 0x00a4
+#define MEDIATEK_PRODUCT_7208_1COM 0x7101
+#define MEDIATEK_PRODUCT_7208_2COM 0x7102
+#define MEDIATEK_PRODUCT_7103_2COM 0x7103
+#define MEDIATEK_PRODUCT_7106_2COM 0x7106
+#define MEDIATEK_PRODUCT_FP_1COM 0x0003
+#define MEDIATEK_PRODUCT_FP_2COM 0x0023
+#define MEDIATEK_PRODUCT_FPDC_1COM 0x0043
+#define MEDIATEK_PRODUCT_FPDC_2COM 0x0033
+
+/* Cellient products */
+#define CELLIENT_VENDOR_ID 0x2692
+#define CELLIENT_PRODUCT_MEN200 0x9005
+#define CELLIENT_PRODUCT_MPL200 0x9025
+
+/* Hyundai Petatel Inc. products */
+#define PETATEL_VENDOR_ID 0x1ff4
+#define PETATEL_PRODUCT_NP10T_600A 0x600a
+#define PETATEL_PRODUCT_NP10T_600E 0x600e
+
+/* TP-LINK Incorporated products */
+#define TPLINK_VENDOR_ID 0x2357
+#define TPLINK_PRODUCT_LTE 0x000D
+#define TPLINK_PRODUCT_MA180 0x0201
+
+/* Changhong products */
+#define CHANGHONG_VENDOR_ID 0x2077
+#define CHANGHONG_PRODUCT_CH690 0x7001
+
+/* Inovia */
+#define INOVIA_VENDOR_ID 0x20a6
+#define INOVIA_SEW858 0x1105
+
+/* VIA Telecom */
+#define VIATELECOM_VENDOR_ID 0x15eb
+#define VIATELECOM_PRODUCT_CDS7 0x0001
+
+/* WeTelecom products */
+#define WETELECOM_VENDOR_ID 0x22de
+#define WETELECOM_PRODUCT_WMD200 0x6801
+#define WETELECOM_PRODUCT_6802 0x6802
+#define WETELECOM_PRODUCT_WMD300 0x6803
+
+/* OPPO products */
+#define OPPO_VENDOR_ID 0x22d9
+#define OPPO_PRODUCT_R11 0x276c
+
+/* Sierra Wireless products */
+#define SIERRA_VENDOR_ID 0x1199
+#define SIERRA_PRODUCT_EM9191 0x90d3
+#define SIERRA_PRODUCT_EM9291 0x90e3
+
+/* UNISOC (Spreadtrum) products */
+#define UNISOC_VENDOR_ID 0x1782
+/* TOZED LT70-C based on UNISOC SL8563 uses UNISOC's vendor ID */
+#define TOZED_PRODUCT_LT70C 0x4055
+#define UNISOC_PRODUCT_UIS7720 0x4064
+/* Luat Air72*U series based on UNISOC UIS8910 uses UNISOC's vendor ID */
+#define LUAT_PRODUCT_AIR720U 0x4e00
+
+/* Device flags */
+
+/* Highest interface number which can be used with NCTRL() and RSVD() */
+#define FLAG_IFNUM_MAX 7
+
+/* Interface does not support modem-control requests */
+#define NCTRL(ifnum) ((BIT(ifnum) & 0xff) << 8)
+
+/* Interface is reserved */
+#define RSVD(ifnum) ((BIT(ifnum) & 0xff) << 0)
+
+/* Interface must have two endpoints */
+#define NUMEP2 BIT(16)
+
+/* Device needs ZLP */
+#define ZLP BIT(17)
+
+
+static const struct usb_device_id option_ids[] = {
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COLT) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_LIGHT) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_QUAD) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_QUAD_LIGHT) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_NDIS) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_NDIS_LIGHT) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_NDIS_QUAD) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_NDIS_QUAD_LIGHT) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COBRA) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COBRA_BUS) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_VIPER) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_VIPER_BUS) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_GT_MAX_READY) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUJI_MODEM_LIGHT) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUJI_MODEM_GT) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUJI_MODEM_EX) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_KOI_MODEM) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_SCORPION_MODEM) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_MODEM) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_MODEM_LITE) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_MODEM_GT) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_MODEM_EX) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_KOI_MODEM) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_GTM380_MODEM) },
+ { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_Q101) },
+ { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_Q111) },
+ { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GLX) },
+ { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GKE) },
+ { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GLE) },
+ { USB_DEVICE(QUANTA_VENDOR_ID, 0xea42),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c05, USB_CLASS_COMM, 0x02, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c1f, USB_CLASS_COMM, 0x02, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c23, USB_CLASS_COMM, 0x02, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E173, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(1) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E173S6, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(1) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1750, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(2) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1441, USB_CLASS_COMM, 0x02, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1442, USB_CLASS_COMM, 0x02, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4505, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(1) | RSVD(2) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K3765, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(1) | RSVD(2) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x14ac, 0xff, 0xff, 0xff), /* Huawei E1820 */
+ .driver_info = RSVD(1) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4605, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(1) | RSVD(2) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0xff, 0xff) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x01) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x02) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x03) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x04) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x05) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x06) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x10) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x12) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x13) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x14) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x15) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x17) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x18) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x19) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x1A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x1B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x1C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x31) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x32) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x33) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x34) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x35) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x36) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x48) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x49) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x4A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x4B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x4C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x61) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x62) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x63) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x64) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x65) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x66) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x72) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x73) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x74) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x75) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x78) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x79) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x7A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x7B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x7C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x01) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x02) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x03) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x04) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x05) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x06) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x10) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x12) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x13) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x14) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x15) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x17) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x18) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x19) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x1A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x1B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x1C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x31) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x32) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x33) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x34) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x35) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x36) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x48) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x49) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x4A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x4B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x4C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x61) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x62) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x63) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x64) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x65) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x66) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x72) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x73) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x74) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x75) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x78) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x79) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x7A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x7B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x7C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x01) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x02) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x03) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x04) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x05) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x06) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x0A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x0B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x0D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x0E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x0F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x10) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x12) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x13) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x14) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x15) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x17) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x18) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x19) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x1A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x1B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x1C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x31) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x32) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x33) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x34) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x35) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x36) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x3A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x3B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x3D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x3E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x3F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x48) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x49) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x4A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x4B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x4C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x61) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x62) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x63) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x64) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x65) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x66) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x6A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x6B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x6D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x6E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x6F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x72) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x73) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x74) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x75) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x78) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x79) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x7A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x7B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x7C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x01) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x02) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x03) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x04) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x05) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x06) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x0A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x0B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x0D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x0E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x0F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x10) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x12) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x13) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x14) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x15) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x17) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x18) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x19) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x1A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x1B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x1C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x31) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x32) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x33) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x34) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x35) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x36) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x3A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x3B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x3D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x3E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x3F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x48) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x49) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x4A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x4B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x4C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x61) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x62) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x63) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x64) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x65) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x66) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x6A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x6B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x6D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x6E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x6F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x72) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x73) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x74) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x75) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x78) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x79) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x7A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x7B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x7C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x01) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x02) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x03) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x04) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x05) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x06) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x0A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x0B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x0D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x0E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x0F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x10) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x12) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x13) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x14) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x15) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x17) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x18) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x19) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x1A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x1B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x1C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x31) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x32) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x33) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x34) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x35) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x36) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x3A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x3B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x3D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x3E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x3F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x48) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x49) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x4A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x4B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x4C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x61) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x62) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x63) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x64) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x65) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x66) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x6A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x6B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x6D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x6E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x6F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x72) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x73) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x74) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x75) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x78) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x79) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x7A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x7B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x7C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x01) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x02) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x03) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x04) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x05) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x06) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x0A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x0B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x0D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x0E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x0F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x10) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x12) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x13) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x14) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x15) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x17) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x18) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x19) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x1A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x1B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x1C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x31) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x32) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x33) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x34) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x35) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x36) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x3A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x3B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x3D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x3E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x3F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x48) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x49) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x4A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x4B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x4C) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x61) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x62) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x63) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x64) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x65) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x66) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x6A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x6B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x6D) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x6E) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x6F) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x72) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x73) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x74) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x75) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x78) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x79) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x7A) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x7B) },
+ { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x7C) },
+
+ /* Motorola devices */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x2a70, 0xff, 0xff, 0xff) }, /* mdm6600 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x2e0a, 0xff, 0xff, 0xff) }, /* mdm9600 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x4281, 0x0a, 0x00, 0xfc) }, /* mdm ram dl */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x900e, 0xff, 0xff, 0xff) }, /* mdm qc dl */
+
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V640) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V620) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V740) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V720) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U730) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U740) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U870) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_XU870) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_X950D) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EV620) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_ES720) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_E725) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_ES620) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EU730) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EU740) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EU870D) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC950D) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC727) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_OVMC760) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC780) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_FULLSPEED) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_FULLSPEED) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_FULLSPEED) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_FULLSPEED) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_HIGHSPEED) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED3) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED4) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED5) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED6) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED7) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC996D) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MF3470) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC547) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_HIGHSPEED) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_HIGHSPEED) },
+ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_G2) },
+ /* Novatel Ovation MC551 a.k.a. Verizon USB551L */
+ { USB_DEVICE_AND_INTERFACE_INFO(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC551, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_E362, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_E371, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U620L, 0xff, 0x00, 0x00) },
+
+ { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H01) },
+ { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H01A) },
+ { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H02) },
+ { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_SKYPEPHONE_S2) },
+
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5700_MINICARD) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite EV620 CDMA/EV-DO */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5500_MINICARD) }, /* Dell Wireless 5500 Mobile Broadband HSDPA Mini-Card == Novatel Expedite EU740 HSDPA/3G */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5505_MINICARD) }, /* Dell Wireless 5505 Mobile Broadband HSDPA Mini-Card == Novatel Expedite EU740 HSDPA/3G */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5700_EXPRESSCARD) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO ExpressCard == Novatel Merlin XV620 CDMA/EV-DO */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5510_EXPRESSCARD) }, /* Dell Wireless 5510 Mobile Broadband HSDPA ExpressCard == Novatel Merlin XU870 HSDPA/3G */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5700_MINICARD_SPRINT) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite E720 CDMA/EV-DO */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5700_MINICARD_TELUS) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite ET620 CDMA/EV-DO */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5720_MINICARD_VZW) }, /* Dell Wireless 5720 == Novatel EV620 CDMA/EV-DO */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5720_MINICARD_SPRINT) }, /* Dell Wireless 5720 == Novatel EV620 CDMA/EV-DO */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5720_MINICARD_TELUS) }, /* Dell Wireless 5720 == Novatel EV620 CDMA/EV-DO */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5520_MINICARD_CINGULAR) }, /* Dell Wireless HSDPA 5520 == Novatel Expedite EU860D */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5520_MINICARD_GENERIC_L) }, /* Dell Wireless HSDPA 5520 */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5520_MINICARD_GENERIC_I) }, /* Dell Wireless 5520 Voda I Mobile Broadband (3G HSDPA) Minicard */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5730_MINICARD_SPRINT) }, /* Dell Wireless 5730 Mobile Broadband EVDO/HSPA Mini-Card */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5730_MINICARD_TELUS) }, /* Dell Wireless 5730 Mobile Broadband EVDO/HSPA Mini-Card */
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5730_MINICARD_VZW) }, /* Dell Wireless 5730 Mobile Broadband EVDO/HSPA Mini-Card */
+ { USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, DELL_PRODUCT_5800_MINICARD_VZW, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, DELL_PRODUCT_5800_V2_MINICARD_VZW, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, DELL_PRODUCT_5804_MINICARD_ATT, 0xff, 0xff, 0xff) },
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5821E),
+ .driver_info = RSVD(0) | RSVD(1) | RSVD(6) },
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5821E_ESIM),
+ .driver_info = RSVD(0) | RSVD(1) | RSVD(6) },
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5829E),
+ .driver_info = RSVD(0) | RSVD(6) },
+ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5829E_ESIM),
+ .driver_info = RSVD(0) | RSVD(6) },
+ { USB_DEVICE_INTERFACE_CLASS(DELL_VENDOR_ID, DELL_PRODUCT_5826E_ESIM, 0xff),
+ .driver_info = RSVD(1) | RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(DELL_VENDOR_ID, DELL_PRODUCT_FM101R, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(DELL_VENDOR_ID, DELL_PRODUCT_FM101R_ESIM, 0xff) },
+ { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_E100A) }, /* ADU-E100, ADU-310 */
+ { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_500A) },
+ { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_620UW) },
+ { USB_DEVICE(AXESSTEL_VENDOR_ID, AXESSTEL_PRODUCT_MV110H) },
+ { USB_DEVICE(YISO_VENDOR_ID, YISO_PRODUCT_U893) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_C100_1, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_C100_2, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1004, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1005, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1006, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1007, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1008, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1009, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100A, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100B, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100C, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100D, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100E, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100F, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1010, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1011, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1012, 0xff) },
+ { USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_PRODUCT_KPC650) },
+ { USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_PRODUCT_KPC680) },
+ { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6000)}, /* ZTE AC8700 */
+ { USB_DEVICE_AND_INTERFACE_INFO(QUALCOMM_VENDOR_ID, 0x6001, 0xff, 0xff, 0xff), /* 4G LTE usb-modem U901 */
+ .driver_info = RSVD(3) },
+ { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6613)}, /* Onda H600/ZTE MF330 */
+ { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x0023)}, /* ONYX 3G device */
+ { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x9000), /* SIMCom SIM5218 */
+ .driver_info = NCTRL(0) | NCTRL(1) | NCTRL(2) | NCTRL(3) | RSVD(4) },
+ /* Quectel products using Qualcomm vendor ID */
+ { USB_DEVICE(QUALCOMM_VENDOR_ID, QUECTEL_PRODUCT_UC15)},
+ { USB_DEVICE(QUALCOMM_VENDOR_ID, QUECTEL_PRODUCT_UC20),
+ .driver_info = RSVD(4) },
+ /* Yuga products use Qualcomm vendor ID */
+ { USB_DEVICE(QUALCOMM_VENDOR_ID, YUGA_PRODUCT_CLM920_NC5),
+ .driver_info = RSVD(1) | RSVD(4) },
+ /* u-blox products using Qualcomm vendor ID */
+ { USB_DEVICE(QUALCOMM_VENDOR_ID, UBLOX_PRODUCT_R410M),
+ .driver_info = RSVD(1) | RSVD(3) },
+ { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x908b), /* u-blox LARA-R6 00B */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x90fa),
+ .driver_info = RSVD(3) },
+ /* u-blox products */
+ { USB_DEVICE(UBLOX_VENDOR_ID, 0x1311) }, /* u-blox LARA-R6 01B */
+ { USB_DEVICE(UBLOX_VENDOR_ID, 0x1312), /* u-blox LARA-R6 01B (RMNET) */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(UBLOX_VENDOR_ID, 0x1313, 0xff) }, /* u-blox LARA-R6 01B (ECM) */
+ { USB_DEVICE(UBLOX_VENDOR_ID, 0x1341) }, /* u-blox LARA-L6 */
+ { USB_DEVICE(UBLOX_VENDOR_ID, 0x1342), /* u-blox LARA-L6 (RMNET) */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE(UBLOX_VENDOR_ID, 0x1343), /* u-blox LARA-L6 (ECM) */
+ .driver_info = RSVD(4) },
+ /* Quectel products using Quectel vendor ID */
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC21, 0xff, 0xff, 0xff),
+ .driver_info = NUMEP2 },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC21, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC25, 0xff, 0xff, 0xff),
+ .driver_info = NUMEP2 },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC25, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG91, 0xff, 0xff, 0xff),
+ .driver_info = NUMEP2 },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG91, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG95, 0xff, 0xff, 0xff),
+ .driver_info = NUMEP2 },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG95, 0xff, 0, 0) },
+ { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, 0x0203, 0xff), /* BG95-M3 */
+ .driver_info = ZLP },
+ { USB_DEVICE(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_BG96),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EP06, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(1) | RSVD(2) | RSVD(3) | RSVD(4) | NUMEP2 },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EP06, 0xff, 0, 0) },
+ { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05CN, 0xff),
+ .driver_info = RSVD(6) | ZLP },
+ { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05CN_SG, 0xff),
+ .driver_info = RSVD(6) | ZLP },
+ { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05G, 0xff),
+ .driver_info = RSVD(6) | ZLP },
+ { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05GV2, 0xff),
+ .driver_info = RSVD(4) | ZLP },
+ { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05G_CS, 0xff),
+ .driver_info = RSVD(6) | ZLP },
+ { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05G_GR, 0xff),
+ .driver_info = RSVD(6) | ZLP },
+ { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05G_RS, 0xff),
+ .driver_info = RSVD(6) | ZLP },
+ { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05G_SG, 0xff),
+ .driver_info = RSVD(6) | ZLP },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_128, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_128, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_128, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_129, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_129, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_129, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12a, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12a, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12a, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12b, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12b, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12b, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12c, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12c, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12c, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LCN, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LCN, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LCN, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LMS, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LMS, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LMS, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LTA, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LTA, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LTA, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LWW, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LWW, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LWW, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM12, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(1) | RSVD(2) | RSVD(3) | RSVD(4) | NUMEP2 },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM12, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, 0x0620, 0xff, 0xff, 0x30) }, /* EM160R-GL */
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, 0x0620, 0xff, 0, 0) },
+ { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, 0x0700, 0xff), /* BG95 */
+ .driver_info = RSVD(3) | ZLP },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0xff, 0x10),
+ .driver_info = ZLP },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM520N, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM520N, 0xff, 0, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM520N, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, 0x0900, 0xff, 0, 0), /* RM500U-CN */
+ .driver_info = ZLP },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200A, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200U, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200S_CN, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200T, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG912Y, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG916Q, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500K, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x40) },
+
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6001) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CMU_300) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6003),
+ .driver_info = RSVD(0) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6004) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6005) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CGU_628A) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHE_628S),
+ .driver_info = RSVD(0) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CMU_301),
+ .driver_info = RSVD(0) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_628),
+ .driver_info = RSVD(0) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_628S) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CDU_680) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CDU_685A) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_720S),
+ .driver_info = RSVD(0) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7002),
+ .driver_info = RSVD(0) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_629K),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7004),
+ .driver_info = RSVD(3) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7005) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CGU_629),
+ .driver_info = RSVD(5) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_629S),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_720I),
+ .driver_info = RSVD(0) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7212),
+ .driver_info = RSVD(0) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7213),
+ .driver_info = RSVD(0) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7251),
+ .driver_info = RSVD(1) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7252),
+ .driver_info = RSVD(1) },
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7253),
+ .driver_info = RSVD(1) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864E) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864G) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_CC864_DUAL) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_CC864_SINGLE) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_DE910_DUAL) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UE910_V2) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0990, 0xff, 0xff, 0x30), /* Telit FE990D50 (RNDIS) */
+ .driver_info = NCTRL(6) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0990, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0990, 0xff, 0xff, 0x60) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0991, 0xff, 0xff, 0x30), /* Telit FE990D50 (rmnet) */
+ .driver_info = NCTRL(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0991, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0991, 0xff, 0xff, 0x60) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0992, 0xff, 0xff, 0x30), /* Telit FE990D50 (MBIM) */
+ .driver_info = NCTRL(6) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0992, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0992, 0xff, 0xff, 0x60) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0993, 0xff, 0xff, 0x30), /* Telit FE990D50 (ECM) */
+ .driver_info = NCTRL(6) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0993, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0993, 0xff, 0xff, 0x60) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1031, 0xff), /* Telit LE910C1-EUX */
+ .driver_info = NCTRL(0) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1033, 0xff), /* Telit LE910C1-EUX (ECM) */
+ .driver_info = NCTRL(0) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1034, 0xff), /* Telit LE910C4-WWX (rmnet) */
+ .driver_info = RSVD(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1035, 0xff) }, /* Telit LE910C4-WWX (ECM) */
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1036, 0xff) }, /* Telit LE910C4-WWX */
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1037, 0xff), /* Telit LE910C4-WWX (rmnet) */
+ .driver_info = NCTRL(0) | NCTRL(1) | RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1038, 0xff), /* Telit LE910C4-WWX (rmnet) */
+ .driver_info = NCTRL(0) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x103b, 0xff), /* Telit LE910C4-WWX */
+ .driver_info = NCTRL(0) | NCTRL(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x103c, 0xff), /* Telit LE910C4-WWX */
+ .driver_info = NCTRL(0) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG0),
+ .driver_info = RSVD(0) | RSVD(1) | NCTRL(2) | RSVD(3) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG1),
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG2),
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG3),
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG5, 0xff),
+ .driver_info = RSVD(0) | RSVD(1) | NCTRL(2) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1050, 0xff), /* Telit FN980 (rmnet) */
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1051, 0xff), /* Telit FN980 (MBIM) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1052, 0xff), /* Telit FN980 (RNDIS) */
+ .driver_info = NCTRL(2) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1053, 0xff), /* Telit FN980 (ECM) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1054, 0xff), /* Telit FT980-KS */
+ .driver_info = NCTRL(2) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1055, 0xff), /* Telit FN980 (PCIe) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1056, 0xff), /* Telit FD980 */
+ .driver_info = NCTRL(2) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1057, 0xff), /* Telit FN980 */
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1058, 0xff), /* Telit FN980 (PCIe) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1060, 0xff), /* Telit LN920 (rmnet) */
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1061, 0xff), /* Telit LN920 (MBIM) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1062, 0xff), /* Telit LN920 (RNDIS) */
+ .driver_info = NCTRL(2) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1063, 0xff), /* Telit LN920 (ECM) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1070, 0xff), /* Telit FN990A (rmnet) */
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1071, 0xff), /* Telit FN990A (MBIM) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1072, 0xff), /* Telit FN990A (RNDIS) */
+ .driver_info = NCTRL(2) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1073, 0xff), /* Telit FN990A (ECM) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1074, 0xff), /* Telit FN990A (MBIM) */
+ .driver_info = NCTRL(5) | RSVD(6) | RSVD(7) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1075, 0xff), /* Telit FN990A (PCIe) */
+ .driver_info = RSVD(0) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1077, 0xff), /* Telit FN990A (rmnet + audio) */
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1078, 0xff), /* Telit FN990A (MBIM + audio) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1079, 0xff), /* Telit FN990A (RNDIS + audio) */
+ .driver_info = NCTRL(2) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1080, 0xff), /* Telit FE990A (rmnet) */
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1081, 0xff), /* Telit FE990A (MBIM) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1082, 0xff), /* Telit FE990A (RNDIS) */
+ .driver_info = NCTRL(2) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1083, 0xff), /* Telit FE990A (ECM) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a0, 0xff), /* Telit FN20C04 (rmnet) */
+ .driver_info = RSVD(0) | NCTRL(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a1, 0xff), /* Telit FN20C04 (RNDIS) */
+ .driver_info = NCTRL(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a2, 0xff), /* Telit FN920C04 (MBIM) */
+ .driver_info = NCTRL(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a3, 0xff), /* Telit FN920C04 (ECM) */
+ .driver_info = NCTRL(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a4, 0xff), /* Telit FN20C04 (rmnet) */
+ .driver_info = RSVD(0) | NCTRL(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a6, 0xff), /* Telit FN920C04 (RNDIS) */
+ .driver_info = NCTRL(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a7, 0xff), /* Telit FN920C04 (MBIM) */
+ .driver_info = NCTRL(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a8, 0xff), /* Telit FN920C04 (ECM) */
+ .driver_info = NCTRL(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a9, 0xff), /* Telit FN20C04 (rmnet) */
+ .driver_info = RSVD(0) | NCTRL(2) | RSVD(3) | RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10aa, 0xff), /* Telit FN920C04 (MBIM) */
+ .driver_info = NCTRL(3) | RSVD(4) | RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10ab, 0xff), /* Telit FN920C04 (RNDIS) */
+ .driver_info = NCTRL(3) | RSVD(4) | RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b0, 0xff, 0xff, 0x30), /* Telit FE990B (rmnet) */
+ .driver_info = NCTRL(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b0, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b0, 0xff, 0xff, 0x60) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b1, 0xff, 0xff, 0x30), /* Telit FE990B (MBIM) */
+ .driver_info = NCTRL(6) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b1, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b1, 0xff, 0xff, 0x60) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b2, 0xff, 0xff, 0x30), /* Telit FE990B (RNDIS) */
+ .driver_info = NCTRL(6) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b2, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b2, 0xff, 0xff, 0x60) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b3, 0xff, 0xff, 0x30), /* Telit FE990B (ECM) */
+ .driver_info = NCTRL(6) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b3, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b3, 0xff, 0xff, 0x60) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c0, 0xff), /* Telit FE910C04 (rmnet) */
+ .driver_info = RSVD(0) | NCTRL(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c1, 0xff), /* Telit FE910C04 (RNDIS) */
+ .driver_info = NCTRL(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c2, 0xff), /* Telit FE910C04 (MBIM) */
+ .driver_info = NCTRL(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c3, 0xff), /* Telit FE910C04 (ECM) */
+ .driver_info = NCTRL(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c4, 0xff), /* Telit FE910C04 (rmnet) */
+ .driver_info = RSVD(0) | NCTRL(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c5, 0xff), /* Telit FE910C04 (RNDIS) */
+ .driver_info = NCTRL(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c6, 0xff), /* Telit FE910C04 (MBIM) */
+ .driver_info = NCTRL(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10c7, 0xff, 0xff, 0x30), /* Telit FE910C04 (ECM) */
+ .driver_info = NCTRL(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10c7, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c8, 0xff), /* Telit FE910C04 (rmnet) */
+ .driver_info = RSVD(0) | NCTRL(2) | RSVD(3) | RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c9, 0xff), /* Telit FE910C04 (MBIM) */
+ .driver_info = NCTRL(3) | RSVD(4) | RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10cb, 0xff), /* Telit FE910C04 (RNDIS) */
+ .driver_info = NCTRL(3) | RSVD(4) | RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d0, 0xff, 0xff, 0x30), /* Telit FN990B (rmnet) */
+ .driver_info = NCTRL(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d0, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d0, 0xff, 0xff, 0x60) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d1, 0xff, 0xff, 0x30), /* Telit FN990B (MBIM) */
+ .driver_info = NCTRL(6) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d1, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d1, 0xff, 0xff, 0x60) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d2, 0xff, 0xff, 0x30), /* Telit FN990B (RNDIS) */
+ .driver_info = NCTRL(6) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d2, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d2, 0xff, 0xff, 0x60) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d3, 0xff, 0xff, 0x30), /* Telit FN990B (ECM) */
+ .driver_info = NCTRL(6) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d3, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d3, 0xff, 0xff, 0x60) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_ME910),
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(3) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_ME910_DUAL_MODEM),
+ .driver_info = NCTRL(0) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1102, 0xff), /* Telit ME910 (ECM) */
+ .driver_info = NCTRL(0) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x110a, 0xff), /* Telit ME910G1 */
+ .driver_info = NCTRL(0) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x110b, 0xff), /* Telit ME910G1 (ECM) */
+ .driver_info = NCTRL(0) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE910),
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1203, 0xff), /* Telit LE910Cx (RNDIS) */
+ .driver_info = NCTRL(2) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1204, 0xff), /* Telit LE910Cx (MBIM) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE910_USBCFG4),
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920),
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(5) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1207) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1208),
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1211),
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1212),
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1213, 0xff) },
+ { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1214),
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1230, 0xff), /* Telit LE910Cx (rmnet) */
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1231, 0xff), /* Telit LE910Cx (RNDIS) */
+ .driver_info = NCTRL(2) | RSVD(3) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x1250, 0xff, 0x00, 0x00) }, /* Telit LE910Cx (rmnet) */
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1251, 0xff) }, /* Telit LE910Cx (RNDIS) */
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1252, 0xff) }, /* Telit LE910Cx (MBIM) */
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1253, 0xff) }, /* Telit LE910Cx (ECM) */
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1254, 0xff) }, /* Telit LE910Cx */
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1255, 0xff) }, /* Telit LE910Cx */
+ { USB_DEVICE(TELIT_VENDOR_ID, 0x1260),
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) },
+ { USB_DEVICE(TELIT_VENDOR_ID, 0x1261),
+ .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) },
+ { USB_DEVICE(TELIT_VENDOR_ID, 0x1900), /* Telit LN940 (QMI) */
+ .driver_info = NCTRL(0) | RSVD(1) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1901, 0xff), /* Telit LN940 (MBIM) */
+ .driver_info = NCTRL(0) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x3000, 0xff), /* Telit FN912 */
+ .driver_info = RSVD(0) | NCTRL(3) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x3001, 0xff), /* Telit FN912 */
+ .driver_info = RSVD(0) | NCTRL(2) | RSVD(3) | RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x7010, 0xff), /* Telit LE910-S1 (RNDIS) */
+ .driver_info = NCTRL(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x7011, 0xff), /* Telit LE910-S1 (ECM) */
+ .driver_info = NCTRL(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x701a, 0xff), /* Telit LE910R1 (RNDIS) */
+ .driver_info = NCTRL(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x701b, 0xff), /* Telit LE910R1 (ECM) */
+ .driver_info = NCTRL(2) },
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x9000, 0xff), /* Telit generic core-dump device */
+ .driver_info = NCTRL(0) },
+ { USB_DEVICE(TELIT_VENDOR_ID, 0x9010), /* Telit SBL FN980 flashing device */
+ .driver_info = NCTRL(0) | ZLP },
+ { USB_DEVICE(TELIT_VENDOR_ID, 0x9200), /* Telit LE910S1 flashing device */
+ .driver_info = NCTRL(0) | ZLP },
+ { USB_DEVICE(TELIT_VENDOR_ID, 0x9201), /* Telit LE910R1 flashing device */
+ .driver_info = NCTRL(0) | ZLP },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF622, 0xff, 0xff, 0xff) }, /* ZTE WCDMA products */
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0002, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(1) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0003, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0004, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0005, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0006, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0008, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0009, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x000a, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x000b, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x000c, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x000d, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x000e, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x000f, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0010, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0011, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0012, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(1) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0013, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF628, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0016, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0017, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(3) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0018, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0019, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(3) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0020, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0021, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0022, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0023, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0024, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0025, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(1) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0028, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0029, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0030, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF626, 0xff, 0xff, 0xff),
+ .driver_info = NCTRL(0) | NCTRL(1) | RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0032, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0033, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0034, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0037, 0xff, 0xff, 0xff),
+ .driver_info = NCTRL(0) | NCTRL(1) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0038, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0039, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0040, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0042, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0043, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0044, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0048, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0049, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0050, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0051, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0052, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0054, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0055, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(1) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0056, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0057, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0058, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0061, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0062, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0063, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0064, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0065, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0066, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0067, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0069, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0076, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0077, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0078, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0079, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0082, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0083, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0086, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0087, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0088, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0089, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0090, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0091, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0092, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0093, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0094, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0095, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0096, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0097, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0104, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0105, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0106, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0108, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0113, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0117, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0118, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0121, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0122, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0123, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0124, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0125, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(6) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0126, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0128, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0135, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0136, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0137, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0139, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0142, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0143, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0144, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0145, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0148, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0151, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0153, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0155, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0156, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0157, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0158, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(3) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0159, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0161, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0162, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0164, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0165, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0167, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0189, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0191, 0xff, 0xff, 0xff), /* ZTE EuFi890 */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0196, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0197, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0199, 0xff, 0xff, 0xff), /* ZTE MF820S */
+ .driver_info = RSVD(1) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0200, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0201, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0254, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0257, 0xff, 0xff, 0xff), /* ZTE MF821 */
+ .driver_info = RSVD(3) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0265, 0xff, 0xff, 0xff), /* ONDA MT8205 */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0284, 0xff, 0xff, 0xff), /* ZTE MF880 */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0317, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0326, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0330, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0395, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0412, 0xff, 0xff, 0xff), /* Telewell TW-LTE 4G */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0414, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0417, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(ZTE_VENDOR_ID, 0x0601, 0xff) }, /* GosunCn ZTE WeLink ME3630 (RNDIS mode) */
+ { USB_DEVICE_INTERFACE_CLASS(ZTE_VENDOR_ID, 0x0602, 0xff) }, /* GosunCn ZTE WeLink ME3630 (MBIM mode) */
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1008, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1010, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1012, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1018, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1021, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(2) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1057, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1058, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1059, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1060, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1061, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1062, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1063, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1064, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1065, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1066, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1067, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1068, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1069, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1070, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1071, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1072, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1073, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1074, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1075, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1076, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1077, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1078, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1079, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1080, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1081, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1082, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1083, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1084, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1085, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1086, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1087, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1088, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1089, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1090, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1091, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1092, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1093, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1094, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1095, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1096, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1097, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1098, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1099, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1100, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1101, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1102, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1103, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1104, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1105, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1106, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1107, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1108, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1109, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1110, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1111, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1112, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1113, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1114, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1115, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1116, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1117, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1118, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1119, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1120, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1121, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1122, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1123, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1124, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1125, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1126, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1127, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1128, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1129, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1130, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1131, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1132, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1133, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1134, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1135, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1136, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1137, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1138, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1139, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1140, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1141, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1142, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1143, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1144, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1145, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1146, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1147, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1148, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1149, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1150, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1151, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1152, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1153, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1154, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1155, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1156, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1157, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1158, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1159, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1160, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1161, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1162, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1163, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1164, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1165, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1166, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1167, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1168, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1169, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1170, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1244, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1245, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1246, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1247, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1248, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1249, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1250, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1251, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1252, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1253, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1254, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1255, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(3) | RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1256, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1257, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1258, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1259, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1260, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1261, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1262, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1263, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1264, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1265, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1266, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1267, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1268, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1269, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1270, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1271, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1272, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1273, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1274, 0xff, 0xff, 0xff) },
+ { USB_DEVICE(ZTE_VENDOR_ID, 0x1275), /* ZTE P685M */
+ .driver_info = RSVD(3) | RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1276, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1277, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1278, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1279, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1280, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1281, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1282, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1283, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1284, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1285, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1286, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1287, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1288, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1289, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1290, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1291, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1292, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1293, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1294, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1295, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1296, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1297, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1298, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1299, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1300, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1301, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1302, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1303, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1333, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1401, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(2) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1402, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(2) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1424, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(2) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1425, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(2) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1426, 0xff, 0xff, 0xff), /* ZTE MF91 */
+ .driver_info = RSVD(2) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1428, 0xff, 0xff, 0xff), /* Telewell TW-LTE 4G v2 */
+ .driver_info = RSVD(2) },
+ { USB_DEVICE_INTERFACE_CLASS(ZTE_VENDOR_ID, 0x1476, 0xff) }, /* GosunCn ZTE WeLink ME3630 (ECM/NCM mode) */
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1481, 0xff, 0x00, 0x00) }, /* ZTE MF871A */
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1485, 0xff, 0xff, 0xff), /* ZTE MF286D */
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1533, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1534, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1535, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1545, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1546, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1547, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1565, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1566, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1567, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1589, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1590, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1591, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1592, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1594, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1596, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1598, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1600, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x2002, 0xff, 0xff, 0xff),
+ .driver_info = NCTRL(0) | NCTRL(1) | NCTRL(2) | RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x2003, 0xff, 0xff, 0xff) },
+
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0014, 0xff, 0xff, 0xff) }, /* ZTE CDMA products */
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0027, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0059, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0060, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0070, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0073, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0130, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(1) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0133, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(3) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0141, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0147, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0152, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0168, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0170, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0176, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(3) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0178, 0xff, 0xff, 0xff),
+ .driver_info = RSVD(3) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff42, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff43, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff44, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff45, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff46, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff47, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff48, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff49, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff4a, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff4b, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff4c, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff4d, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff4e, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff4f, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff50, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff51, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff52, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff53, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff54, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff55, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff56, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff57, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff58, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff59, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff5a, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff5b, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff5c, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff5d, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff5e, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff5f, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff60, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff61, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff62, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff63, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff64, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff65, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff66, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff67, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff68, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff69, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff6a, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff6b, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff6c, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff6d, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff6e, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff6f, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff70, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff71, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff72, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff73, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff74, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff75, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff76, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff77, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff78, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff79, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff7a, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff7b, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff7c, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff7d, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff7e, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff7f, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff80, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff81, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff82, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff83, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff84, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff85, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff86, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff87, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff88, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff89, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff8a, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff8b, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff8c, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff8d, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff8e, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff8f, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff90, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff91, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff92, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff93, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff94, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff9f, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa0, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa1, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa2, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa3, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa4, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa5, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa6, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa7, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa8, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa9, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffaa, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffab, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffac, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffae, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffaf, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb0, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb1, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb2, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb3, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb4, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb5, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb6, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb7, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb8, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb9, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffba, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffbb, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffbc, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffbd, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffbe, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffbf, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc0, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc1, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc2, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc3, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc4, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc5, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc6, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc7, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc8, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc9, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffca, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffcb, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffcc, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffcd, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffce, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffcf, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffd0, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffd1, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffd2, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffd3, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffd4, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffd5, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffe9, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffec, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffee, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xfff6, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xfff7, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xfff8, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xfff9, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xfffb, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xfffc, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MG880, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_CDMA_TECH, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_AC2726, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_AC8710T, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MC2718, 0xff, 0xff, 0xff),
+ .driver_info = NCTRL(1) | NCTRL(2) | NCTRL(3) | NCTRL(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_AD3812, 0xff, 0xff, 0xff),
+ .driver_info = NCTRL(0) | NCTRL(1) | NCTRL(2) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MC2716, 0xff, 0xff, 0xff),
+ .driver_info = NCTRL(1) | NCTRL(2) | NCTRL(3) },
+ { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_ME3620_L),
+ .driver_info = RSVD(3) | RSVD(4) | RSVD(5) },
+ { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_ME3620_MBIM),
+ .driver_info = RSVD(2) | RSVD(3) | RSVD(4) },
+ { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_ME3620_X),
+ .driver_info = RSVD(3) | RSVD(4) | RSVD(5) },
+ { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_ZM8620_X),
+ .driver_info = RSVD(3) | RSVD(4) | RSVD(5) },
+ { USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff, 0x02, 0x01) },
+ { USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff, 0x02, 0x05) },
+ { USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff, 0x86, 0x10) },
+
+ { USB_DEVICE(BENQ_VENDOR_ID, BENQ_PRODUCT_H10) },
+ { USB_DEVICE(DLINK_VENDOR_ID, DLINK_PRODUCT_DWM_652) },
+ { USB_DEVICE(ALINK_VENDOR_ID, DLINK_PRODUCT_DWM_652_U5) }, /* Yes, ALINK_VENDOR_ID */
+ { USB_DEVICE(ALINK_VENDOR_ID, DLINK_PRODUCT_DWM_652_U5A) },
+ { USB_DEVICE(QISDA_VENDOR_ID, QISDA_PRODUCT_H21_4512) },
+ { USB_DEVICE(QISDA_VENDOR_ID, QISDA_PRODUCT_H21_4523) },
+ { USB_DEVICE(QISDA_VENDOR_ID, QISDA_PRODUCT_H20_4515) },
+ { USB_DEVICE(QISDA_VENDOR_ID, QISDA_PRODUCT_H20_4518) },
+ { USB_DEVICE(QISDA_VENDOR_ID, QISDA_PRODUCT_H20_4519) },
+ { USB_DEVICE(TOSHIBA_VENDOR_ID, TOSHIBA_PRODUCT_G450) },
+ { USB_DEVICE(TOSHIBA_VENDOR_ID, TOSHIBA_PRODUCT_HSDPA_MINICARD ) }, /* Toshiba 3G HSDPA == Novatel Expedite EU870D MiniCard */
+ { USB_DEVICE(ALINK_VENDOR_ID, 0x9000) },
+ { USB_DEVICE(ALINK_VENDOR_ID, ALINK_PRODUCT_PH300) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ALINK_VENDOR_ID, ALINK_PRODUCT_3GU, 0xff, 0xff, 0xff) },
+ { USB_DEVICE(ALINK_VENDOR_ID, SIMCOM_PRODUCT_SIM7100E),
+ .driver_info = RSVD(5) | RSVD(6) },
+ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */
+ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */
+ .driver_info = RSVD(7) },
+ { USB_DEVICE(0x1e0e, 0x9071), /* Simcom SIM8230 RMNET mode */
+ .driver_info = RSVD(3) | RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9078, 0xff), /* Simcom SIM8230 ECM mode */
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x907b, 0xff), /* Simcom SIM8230 RNDIS mode */
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9205, 0xff) }, /* Simcom SIM7070/SIM7080/SIM7090 AT+ECM mode */
+ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9206, 0xff) }, /* Simcom SIM7070/SIM7080/SIM7090 AT-only mode */
+ { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_X060S_X200),
+ .driver_info = NCTRL(0) | NCTRL(1) | RSVD(4) },
+ { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_X220_X500D),
+ .driver_info = RSVD(6) },
+ { USB_DEVICE(ALCATEL_VENDOR_ID, 0x0052),
+ .driver_info = RSVD(6) },
+ { USB_DEVICE(ALCATEL_VENDOR_ID, 0x00b6),
+ .driver_info = RSVD(3) },
+ { USB_DEVICE(ALCATEL_VENDOR_ID, 0x00b7),
+ .driver_info = RSVD(5) },
+ { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_L100V),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_L800MA),
+ .driver_info = RSVD(2) },
+ { USB_DEVICE(AIRPLUS_VENDOR_ID, AIRPLUS_PRODUCT_MCD650) },
+ { USB_DEVICE(TLAYTECH_VENDOR_ID, TLAYTECH_PRODUCT_TEU800) },
+ { USB_DEVICE(LONGCHEER_VENDOR_ID, FOUR_G_SYSTEMS_PRODUCT_CARSTICK_LTE),
+ .driver_info = RSVD(0) },
+ { USB_DEVICE(LONGCHEER_VENDOR_ID, FOUR_G_SYSTEMS_PRODUCT_W14),
+ .driver_info = NCTRL(0) | NCTRL(1) },
+ { USB_DEVICE(LONGCHEER_VENDOR_ID, FOUR_G_SYSTEMS_PRODUCT_W100),
+ .driver_info = NCTRL(1) | NCTRL(2) | RSVD(3) },
+ {USB_DEVICE(LONGCHEER_VENDOR_ID, FUJISOFT_PRODUCT_FS040U),
+ .driver_info = RSVD(3)},
+ { USB_DEVICE_INTERFACE_CLASS(LONGCHEER_VENDOR_ID, SPEEDUP_PRODUCT_SU9800, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(LONGCHEER_VENDOR_ID, 0x9801, 0xff),
+ .driver_info = RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(LONGCHEER_VENDOR_ID, 0x9803, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE(LONGCHEER_VENDOR_ID, 0x9b05), /* Longsung U8300 */
+ .driver_info = RSVD(4) | RSVD(5) },
+ { USB_DEVICE(LONGCHEER_VENDOR_ID, 0x9b3c), /* Longsung U9300 */
+ .driver_info = RSVD(0) | RSVD(4) },
+ { USB_DEVICE(LONGCHEER_VENDOR_ID, ZOOM_PRODUCT_4597) },
+ { USB_DEVICE(LONGCHEER_VENDOR_ID, IBALL_3_5G_CONNECT) },
+ { USB_DEVICE(HAIER_VENDOR_ID, HAIER_PRODUCT_CE100) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HAIER_VENDOR_ID, HAIER_PRODUCT_CE81B, 0xff, 0xff, 0xff) },
+ /* Pirelli */
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_C100_1, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_C100_2, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1004, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1005, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1006, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1007, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1008, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1009, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_100A, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_100B, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_100C, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_100D, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_100E, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_100F, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1011, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1012, 0xff) },
+ /* Cinterion */
+ { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_EU3_E) },
+ { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_EU3_P) },
+ { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PH8),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_AHXX, 0xff) },
+ { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PLXX),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PH8_2RMNET, 0xff),
+ .driver_info = RSVD(4) | RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PH8_AUDIO, 0xff),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_AHXX_2RMNET, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_AHXX_AUDIO, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_CLS8, 0xff),
+ .driver_info = RSVD(0) | RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_EXS82, 0xff) },
+ { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_HC28_MDM) },
+ { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_HC28_MDMNET) },
+ { USB_DEVICE(SIEMENS_VENDOR_ID, CINTERION_PRODUCT_HC25_MDM) },
+ { USB_DEVICE(SIEMENS_VENDOR_ID, CINTERION_PRODUCT_HC25_MDMNET) },
+ { USB_DEVICE(SIEMENS_VENDOR_ID, CINTERION_PRODUCT_HC28_MDM) }, /* HC28 enumerates with Siemens or Cinterion VID depending on FW revision */
+ { USB_DEVICE(SIEMENS_VENDOR_ID, CINTERION_PRODUCT_HC28_MDMNET) },
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV31_MBIM, 0xff),
+ .driver_info = RSVD(3)},
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV31_RMNET, 0xff),
+ .driver_info = RSVD(0)},
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV31_2_MBIM, 0xff),
+ .driver_info = RSVD(3)},
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV31_2_RMNET, 0xff),
+ .driver_info = RSVD(0)},
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV32_WA, 0xff),
+ .driver_info = RSVD(3)},
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV32_WA_RMNET, 0xff),
+ .driver_info = RSVD(0) },
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV32_WB, 0xff),
+ .driver_info = RSVD(3)},
+ { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV32_WB_RMNET, 0xff),
+ .driver_info = RSVD(0) },
+ { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD100),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD120),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD140),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD145) },
+ { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD155),
+ .driver_info = RSVD(6) },
+ { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD200),
+ .driver_info = RSVD(6) },
+ { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD160),
+ .driver_info = RSVD(6) },
+ { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD500),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE(CELOT_VENDOR_ID, CELOT_PRODUCT_CT680M) }, /* CT-650 CDMA 450 1xEVDO modem */
+ { USB_DEVICE_AND_INTERFACE_INFO(SAMSUNG_VENDOR_ID, SAMSUNG_PRODUCT_GT_B3730, USB_CLASS_CDC_DATA, 0x00, 0x00) }, /* Samsung GT-B3730 LTE USB modem.*/
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM600) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM610) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM500) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM510) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM800) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM900) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU818) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU816) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU828) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU826) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU518) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU516) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU528) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU526) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWM600) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWM610) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWM500) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWM510) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWM800) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWM900) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU718) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU716) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU728) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU726) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU518) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU516) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU528) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU526) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLM600) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLM610) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLM500) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLM510) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLM800) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLM900) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU718) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU716) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU728) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU726) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU518) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU516) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU528) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU526) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU881) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU882) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU581) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU582) },
+ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU583) },
+ { USB_DEVICE_AND_INTERFACE_INFO(VIETTEL_VENDOR_ID, VIETTEL_PRODUCT_VT1000, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(ZD_VENDOR_ID, ZD_PRODUCT_7000, 0xff, 0xff, 0xff) },
+ { USB_DEVICE(LG_VENDOR_ID, LG_PRODUCT_L02C) }, /* docomo L-02C modem */
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x00a1, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x00a1, 0xff, 0x02, 0x01) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x00a2, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x00a2, 0xff, 0x02, 0x01) }, /* MediaTek MT6276M modem & app port */
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_1COM, 0x0a, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_5COM, 0xff, 0x02, 0x01) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_5COM, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_4COM, 0xff, 0x02, 0x01) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_4COM, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_7208_1COM, 0x02, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_7208_2COM, 0x02, 0x02, 0x01) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_FP_1COM, 0x0a, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_FP_2COM, 0x0a, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_FPDC_1COM, 0x0a, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_FPDC_2COM, 0x0a, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_7103_2COM, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_7106_2COM, 0x02, 0x02, 0x01) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_4COM2, 0xff, 0x02, 0x01) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_4COM2, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x7126, 0xff, 0x00, 0x00),
+ .driver_info = NCTRL(2) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x7127, 0xff, 0x00, 0x00),
+ .driver_info = NCTRL(2) | NCTRL(3) | NCTRL(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x7129, 0xff, 0x00, 0x00), /* MediaTek T7XX */
+ .driver_info = NCTRL(2) | NCTRL(3) | NCTRL(4) },
+ { USB_DEVICE(CELLIENT_VENDOR_ID, CELLIENT_PRODUCT_MEN200) },
+ { USB_DEVICE(CELLIENT_VENDOR_ID, CELLIENT_PRODUCT_MPL200),
+ .driver_info = RSVD(1) | RSVD(4) },
+ { USB_DEVICE(PETATEL_VENDOR_ID, PETATEL_PRODUCT_NP10T_600A) },
+ { USB_DEVICE(PETATEL_VENDOR_ID, PETATEL_PRODUCT_NP10T_600E) },
+ { USB_DEVICE_AND_INTERFACE_INFO(TPLINK_VENDOR_ID, TPLINK_PRODUCT_LTE, 0xff, 0x00, 0x00) }, /* TP-Link LTE Module */
+ { USB_DEVICE(TPLINK_VENDOR_ID, TPLINK_PRODUCT_MA180),
+ .driver_info = RSVD(4) },
+ { USB_DEVICE(TPLINK_VENDOR_ID, 0x9000), /* TP-Link MA260 */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE(CHANGHONG_VENDOR_ID, CHANGHONG_PRODUCT_CH690) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d01, 0xff) }, /* D-Link DWM-156 (variant) */
+ { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d02, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d03, 0xff) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d04, 0xff), /* D-Link DWM-158 */
+ .driver_info = RSVD(4) | RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d0e, 0xff) }, /* D-Link DWM-157 C1 */
+ { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7e19, 0xff), /* D-Link DWM-221 B1 */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7e35, 0xff), /* D-Link DWM-222 */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7e3d, 0xff), /* D-Link DWM-222 A2 */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3e01, 0xff, 0xff, 0xff) }, /* D-Link DWM-152/C1 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3e02, 0xff, 0xff, 0xff) }, /* D-Link DWM-156/C1 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x7e11, 0xff, 0xff, 0xff) }, /* D-Link DWM-156/A3 */
+ { USB_DEVICE_INTERFACE_CLASS(0x1435, 0xd191, 0xff), /* Wistron Neweb D19Q1 */
+ .driver_info = RSVD(1) | RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x1690, 0x7588, 0xff), /* ASKEY WWHC050 */
+ .driver_info = RSVD(1) | RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2020, 0x2031, 0xff), /* Olicard 600 */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2020, 0x2033, 0xff), /* BroadMobi BM806U */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2020, 0x2060, 0xff), /* BroadMobi BM818 */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2020, 0x4000, 0xff) }, /* OLICARD300 - MT6225 */
+ { USB_DEVICE(INOVIA_VENDOR_ID, INOVIA_SEW858) },
+ { USB_DEVICE(VIATELECOM_VENDOR_ID, VIATELECOM_PRODUCT_CDS7) },
+ { USB_DEVICE_AND_INTERFACE_INFO(WETELECOM_VENDOR_ID, WETELECOM_PRODUCT_WMD200, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(WETELECOM_VENDOR_ID, WETELECOM_PRODUCT_6802, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(WETELECOM_VENDOR_ID, WETELECOM_PRODUCT_WMD300, 0xff, 0xff, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0x421d, 0xff, 0xff, 0xff) }, /* HP lt2523 (Novatel E371) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, 0xff, 0x06, 0x10) }, /* HP lt4132 (Huawei ME906s-158) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, 0xff, 0x06, 0x12) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, 0xff, 0x06, 0x13) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, 0xff, 0x06, 0x14) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, 0xff, 0x06, 0x1b) },
+ { USB_DEVICE(0x0489, 0xe0b4), /* Foxconn T77W968 */
+ .driver_info = RSVD(0) | RSVD(1) | RSVD(6) },
+ { USB_DEVICE(0x0489, 0xe0b5), /* Foxconn T77W968 ESIM */
+ .driver_info = RSVD(0) | RSVD(1) | RSVD(6) },
+ { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe0da, 0xff), /* Foxconn T99W265 MBIM variant */
+ .driver_info = RSVD(3) | RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe0db, 0xff), /* Foxconn T99W265 MBIM */
+ .driver_info = RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe0ee, 0xff), /* Foxconn T99W368 MBIM */
+ .driver_info = RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe0f0, 0xff), /* Foxconn T99W373 MBIM */
+ .driver_info = RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe123, 0xff), /* Foxconn T99W760 MBIM */
+ .driver_info = RSVD(3) },
+ { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe145, 0xff), /* Foxconn T99W651 RNDIS */
+ .driver_info = RSVD(5) | RSVD(6) },
+ { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe15f, 0xff), /* Foxconn T99W709 */
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe167, 0xff), /* Foxconn T99W640 MBIM */
+ .driver_info = RSVD(3) },
+ { USB_DEVICE(0x1508, 0x1001), /* Fibocom NL668 (IOT version) */
+ .driver_info = RSVD(4) | RSVD(5) | RSVD(6) },
+ { USB_DEVICE(0x1782, 0x4d10) }, /* Fibocom L610 (AT mode) */
+ { USB_DEVICE_INTERFACE_CLASS(0x1782, 0x4d11, 0xff) }, /* Fibocom L610 (ECM/RNDIS mode) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x0001, 0xff, 0xff, 0xff) }, /* Fibocom L716-EU (ECM/RNDIS mode) */
+ { USB_DEVICE(0x2cb7, 0x0104), /* Fibocom NL678 series */
+ .driver_info = RSVD(4) | RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0105, 0xff), /* Fibocom NL678 series */
+ .driver_info = RSVD(6) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0106, 0xff) }, /* Fibocom MA510 (ECM mode w/ diag intf.) */
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x010a, 0xff) }, /* Fibocom MA510 (ECM mode) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x010b, 0xff, 0xff, 0x30) }, /* Fibocom FG150 Diag */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x010b, 0xff, 0, 0) }, /* Fibocom FG150 AT */
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0111, 0xff) }, /* Fibocom FM160 (MBIM mode) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x0112, 0xff, 0xff, 0x30) }, /* Fibocom FG132 Diag */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x0112, 0xff, 0xff, 0x40) }, /* Fibocom FG132 AT */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x0112, 0xff, 0, 0) }, /* Fibocom FG132 NMEA */
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0115, 0xff), /* Fibocom FM135 (laptop MBIM) */
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a0, 0xff) }, /* Fibocom NL668-AM/NL652-EU (laptop MBIM) */
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a2, 0xff) }, /* Fibocom FM101-GL (laptop MBIM) */
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a3, 0xff) }, /* Fibocom FM101-GL (laptop MBIM) */
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a4, 0xff), /* Fibocom FM101-GL (laptop MBIM) */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0a04, 0xff) }, /* Fibocom FM650-CN (ECM mode) */
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0a05, 0xff) }, /* Fibocom FM650-CN (NCM mode) */
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0a06, 0xff) }, /* Fibocom FM650-CN (RNDIS mode) */
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0a07, 0xff) }, /* Fibocom FM650-CN (MBIM mode) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d41, 0xff, 0, 0) }, /* MeiG Smart SLM320 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d57, 0xff, 0, 0) }, /* MeiG Smart SLM770A */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0, 0) }, /* MeiG Smart SRM815 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0x10, 0x02) }, /* MeiG Smart SLM828 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0x10, 0x03) }, /* MeiG Smart SLM828 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0xff, 0x30) }, /* MeiG Smart SRM815 and SRM825L */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0xff, 0x40) }, /* MeiG Smart SRM825L */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0xff, 0x60) }, /* MeiG Smart SRM825L */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d38, 0xff, 0xff, 0x30) }, /* MeiG Smart SRM825WN (Diag) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d38, 0xff, 0xff, 0x40) }, /* MeiG Smart SRM825WN (AT) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d38, 0xff, 0xff, 0x60) }, /* MeiG Smart SRM825WN (NMEA) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d63, 0xff, 0xff, 0x30) }, /* MeiG SRM813Q (Diag) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d63, 0xff, 0xff, 0x40) }, /* MeiG SRM813Q (AT) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d64, 0xff, 0xff, 0x30) }, /* MeiG SRM813Q (Diag) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d64, 0xff, 0xff, 0x40) }, /* MeiG SRM813Q (AT) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d64, 0xff, 0xff, 0x60) }, /* MeiG SRM813Q (NMEA) */
+
+ { USB_DEVICE_INTERFACE_CLASS(0x2df3, 0x9d03, 0xff) }, /* LongSung M5710 */
+ { USB_DEVICE_INTERFACE_CLASS(0x305a, 0x1404, 0xff) }, /* GosunCn GM500 RNDIS */
+ { USB_DEVICE_INTERFACE_CLASS(0x305a, 0x1405, 0xff) }, /* GosunCn GM500 MBIM */
+ { USB_DEVICE_INTERFACE_CLASS(0x305a, 0x1406, 0xff) }, /* GosunCn GM500 ECM/NCM */
+ { USB_DEVICE(0x33f8, 0x0104), /* Rolling RW101-GL (laptop RMNET) */
+ .driver_info = RSVD(4) | RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x0115, 0xff), /* Rolling RW135-GL (laptop MBIM) */
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a2, 0xff) }, /* Rolling RW101-GL (laptop MBIM) */
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a3, 0xff) }, /* Rolling RW101-GL (laptop MBIM) */
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a4, 0xff), /* Rolling RW101-GL (laptop MBIM) */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a8, 0xff), /* Rolling RW101R-GL (laptop MBIM) */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a9, 0xff), /* Rolling RW101R-GL (laptop MBIM) */
+ .driver_info = RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x0301, 0xff) }, /* Rolling RW101R-GL (laptop MBIM) */
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x0302, 0xff) }, /* Rolling RW101R-GL (laptop MBIM) */
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x0802, 0xff), /* Rolling RW350-GL (laptop MBIM) */
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x1003, 0xff), /* Rolling RW135R-GL (laptop MBIM) */
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for Global */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0101, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WRD for Global SKU */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0101, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0101, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0106, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WRD for China SKU */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0106, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0106, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0111, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for SA */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0111, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0111, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0112, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for EU */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0112, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0112, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0113, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for NA */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0113, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0113, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0115, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for China EDU */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0115, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0115, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0116, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for Golbal EDU */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0116, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0116, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010a, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WRD for WWAN Ready */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010a, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010a, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010b, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for WWAN Ready */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010b, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010b, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010c, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WRD for WWAN Ready */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010c, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010c, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010d, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for WWAN Ready */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010d, 0xff, 0x00, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010d, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(OPPO_VENDOR_ID, OPPO_PRODUCT_R11, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(SIERRA_VENDOR_ID, SIERRA_PRODUCT_EM9191, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(SIERRA_VENDOR_ID, SIERRA_PRODUCT_EM9191, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(SIERRA_VENDOR_ID, SIERRA_PRODUCT_EM9191, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(SIERRA_VENDOR_ID, SIERRA_PRODUCT_EM9291, 0xff, 0xff, 0x30) },
+ { USB_DEVICE_AND_INTERFACE_INFO(SIERRA_VENDOR_ID, SIERRA_PRODUCT_EM9291, 0xff, 0xff, 0x40) },
+ { USB_DEVICE_AND_INTERFACE_INFO(UNISOC_VENDOR_ID, TOZED_PRODUCT_LT70C, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(UNISOC_VENDOR_ID, UNISOC_PRODUCT_UIS7720, 0xff, 0, 0) },
+ { USB_DEVICE_AND_INTERFACE_INFO(UNISOC_VENDOR_ID, LUAT_PRODUCT_AIR720U, 0xff, 0, 0) },
+ { USB_DEVICE_INTERFACE_CLASS(0x1bbb, 0x0530, 0xff), /* TCL IK512 MBIM */
+ .driver_info = NCTRL(1) },
+ { USB_DEVICE_INTERFACE_CLASS(0x1bbb, 0x0640, 0xff), /* TCL IK512 ECM */
+ .driver_info = NCTRL(3) },
+ { USB_DEVICE_INTERFACE_CLASS(0x2949, 0x8700, 0xff) }, /* Neoway N723-EA */
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, option_ids);
+
+/* The card has three separate interfaces, which the serial driver
+ * recognizes separately, thus num_port=1.
+ */
+
+static struct usb_serial_driver option_1port_device = {
+ .driver = {
+ .name = "option1",
+ },
+ .description = "GSM modem (1-port)",
+ .id_table = option_ids,
+ .num_ports = 1,
+ .probe = option_probe,
+ .open = usb_wwan_open,
+ .close = usb_wwan_close,
+ .dtr_rts = usb_wwan_dtr_rts,
+ .write = usb_wwan_write,
+ .write_room = usb_wwan_write_room,
+ .chars_in_buffer = usb_wwan_chars_in_buffer,
+ .tiocmget = usb_wwan_tiocmget,
+ .tiocmset = usb_wwan_tiocmset,
+ .attach = option_attach,
+ .release = option_release,
+ .port_probe = usb_wwan_port_probe,
+ .port_remove = usb_wwan_port_remove,
+ .read_int_callback = option_instat_callback,
+#ifdef CONFIG_PM
+ .suspend = usb_wwan_suspend,
+ .resume = usb_wwan_resume,
+#endif
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &option_1port_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, option_ids);
+
+static bool iface_is_reserved(unsigned long device_flags, u8 ifnum)
+{
+ if (ifnum > FLAG_IFNUM_MAX)
+ return false;
+
+ return device_flags & RSVD(ifnum);
+}
+
+static int option_probe(struct usb_serial *serial,
+ const struct usb_device_id *id)
+{
+ struct usb_interface_descriptor *iface_desc =
+ &serial->interface->cur_altsetting->desc;
+ unsigned long device_flags = id->driver_info;
+
+ /* Never bind to the CD-Rom emulation interface */
+ if (iface_desc->bInterfaceClass == USB_CLASS_MASS_STORAGE)
+ return -ENODEV;
+
+ /*
+ * Don't bind reserved interfaces (like network ones) which often have
+ * the same class/subclass/protocol as the serial interfaces. Look at
+ * the Windows driver .INF files for reserved interface numbers.
+ */
+ if (iface_is_reserved(device_flags, iface_desc->bInterfaceNumber))
+ return -ENODEV;
+
+ /*
+ * Allow matching on bNumEndpoints for devices whose interface numbers
+ * can change (e.g. Quectel EP06).
+ */
+ if (device_flags & NUMEP2 && iface_desc->bNumEndpoints != 2)
+ return -ENODEV;
+
+ /* Store the device flags so we can use them during attach. */
+ usb_set_serial_data(serial, (void *)device_flags);
+
+ return 0;
+}
+
+static bool iface_no_modem_control(unsigned long device_flags, u8 ifnum)
+{
+ if (ifnum > FLAG_IFNUM_MAX)
+ return false;
+
+ return device_flags & NCTRL(ifnum);
+}
+
+static int option_attach(struct usb_serial *serial)
+{
+ struct usb_interface_descriptor *iface_desc;
+ struct usb_wwan_intf_private *data;
+ unsigned long device_flags;
+
+ data = kzalloc_obj(struct usb_wwan_intf_private);
+ if (!data)
+ return -ENOMEM;
+
+ /* Retrieve device flags stored at probe. */
+ device_flags = (unsigned long)usb_get_serial_data(serial);
+
+ iface_desc = &serial->interface->cur_altsetting->desc;
+
+ if (!iface_no_modem_control(device_flags, iface_desc->bInterfaceNumber))
+ data->use_send_setup = 1;
+
+ if (device_flags & ZLP)
+ data->use_zlp = 1;
+
+ spin_lock_init(&data->susp_lock);
+
+ usb_set_serial_data(serial, data);
+
+ return 0;
+}
+
+static void option_release(struct usb_serial *serial)
+{
+ struct usb_wwan_intf_private *intfdata = usb_get_serial_data(serial);
+
+ kfree(intfdata);
+}
+
+static void option_instat_callback(struct urb *urb)
+{
+ int err;
+ int status = urb->status;
+ struct usb_serial_port *port = urb->context;
+ struct device *dev = &port->dev;
+ struct usb_wwan_port_private *portdata =
+ usb_get_serial_port_data(port);
+
+ dev_dbg(dev, "%s: urb %p port %p has data %p\n", __func__, urb, port, portdata);
+
+ if (status == 0) {
+ struct usb_ctrlrequest *req_pkt = urb->transfer_buffer;
+
+ if (!req_pkt) {
+ dev_dbg(dev, "%s: NULL req_pkt\n", __func__);
+ return;
+ }
+ if ((req_pkt->bRequestType == 0xA1) &&
+ (req_pkt->bRequest == 0x20)) {
+ int old_dcd_state;
+ unsigned char signals = *((unsigned char *)
+ urb->transfer_buffer +
+ sizeof(struct usb_ctrlrequest));
+
+ dev_dbg(dev, "%s: signal x%x\n", __func__, signals);
+
+ old_dcd_state = portdata->dcd_state;
+ portdata->cts_state = 1;
+ portdata->dcd_state = ((signals & 0x01) ? 1 : 0);
+ portdata->dsr_state = ((signals & 0x02) ? 1 : 0);
+ portdata->ri_state = ((signals & 0x08) ? 1 : 0);
+
+ if (old_dcd_state && !portdata->dcd_state)
+ tty_port_tty_hangup(&port->port, true);
+ } else {
+ dev_dbg(dev, "%s: type %x req %x\n", __func__,
+ req_pkt->bRequestType, req_pkt->bRequest);
+ }
+ } else if (status == -ENOENT || status == -ESHUTDOWN) {
+ dev_dbg(dev, "%s: urb stopped: %d\n", __func__, status);
+ } else
+ dev_dbg(dev, "%s: error %d\n", __func__, status);
+
+ /* Resubmit urb so we continue receiving IRQ data */
+ if (status != -ESHUTDOWN && status != -ENOENT) {
+ usb_mark_last_busy(port->serial->dev);
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err)
+ dev_dbg(dev, "%s: resubmit intr urb failed. (%d)\n",
+ __func__, err);
+ }
+}
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/oti6858.c b/drivers/usb/serial/oti6858.c
new file mode 100644
index 000000000..c6364e8ff
--- /dev/null
+++ b/drivers/usb/serial/oti6858.c
@@ -0,0 +1,839 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Ours Technology Inc. OTi-6858 USB to serial adapter driver.
+ *
+ * Copyleft (C) 2007 Kees Lemmens (adapted for kernel 2.6.20)
+ * Copyright (C) 2006 Tomasz Michal Lukaszewski (FIXME: add e-mail)
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2003 IBM Corp.
+ *
+ * Many thanks to the authors of pl2303 driver: all functions in this file
+ * are heavily based on pl2303 code, buffering code is a 1-to-1 copy.
+ *
+ * Warning! You use this driver on your own risk! The only official
+ * description of this device I have is datasheet from manufacturer,
+ * and it doesn't contain almost any information needed to write a driver.
+ * Almost all knowlegde used while writing this driver was gathered by:
+ * - analyzing traffic between device and the M$ Windows 2000 driver,
+ * - trying different bit combinations and checking pin states
+ * with a voltmeter,
+ * - receiving malformed frames and producing buffer overflows
+ * to learn how errors are reported,
+ * So, THIS CODE CAN DESTROY OTi-6858 AND ANY OTHER DEVICES, THAT ARE
+ * CONNECTED TO IT!
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ *
+ * TODO:
+ * - implement correct flushing for ioctls and oti6858_close()
+ * - check how errors (rx overflow, parity error, framing error) are reported
+ * - implement oti6858_break_ctl()
+ * - implement more ioctls
+ * - test/implement flow control
+ * - allow setting custom baud rates
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/kfifo.h>
+#include "oti6858.h"
+
+#define OTI6858_DESCRIPTION \
+ "Ours Technology Inc. OTi-6858 USB to serial adapter driver"
+#define OTI6858_AUTHOR "Tomasz Michal Lukaszewski <FIXME@FIXME>"
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(OTI6858_VENDOR_ID, OTI6858_PRODUCT_ID) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+/* requests */
+#define OTI6858_REQ_GET_STATUS (USB_DIR_IN | USB_TYPE_VENDOR | 0x00)
+#define OTI6858_REQ_T_GET_STATUS 0x01
+
+#define OTI6858_REQ_SET_LINE (USB_DIR_OUT | USB_TYPE_VENDOR | 0x00)
+#define OTI6858_REQ_T_SET_LINE 0x00
+
+#define OTI6858_REQ_CHECK_TXBUFF (USB_DIR_IN | USB_TYPE_VENDOR | 0x01)
+#define OTI6858_REQ_T_CHECK_TXBUFF 0x00
+
+/* format of the control packet */
+struct oti6858_control_pkt {
+ __le16 divisor; /* baud rate = 96000000 / (16 * divisor), LE */
+#define OTI6858_MAX_BAUD_RATE 3000000
+ u8 frame_fmt;
+#define FMT_STOP_BITS_MASK 0xc0
+#define FMT_STOP_BITS_1 0x00
+#define FMT_STOP_BITS_2 0x40 /* 1.5 stop bits if FMT_DATA_BITS_5 */
+#define FMT_PARITY_MASK 0x38
+#define FMT_PARITY_NONE 0x00
+#define FMT_PARITY_ODD 0x08
+#define FMT_PARITY_EVEN 0x18
+#define FMT_PARITY_MARK 0x28
+#define FMT_PARITY_SPACE 0x38
+#define FMT_DATA_BITS_MASK 0x03
+#define FMT_DATA_BITS_5 0x00
+#define FMT_DATA_BITS_6 0x01
+#define FMT_DATA_BITS_7 0x02
+#define FMT_DATA_BITS_8 0x03
+ u8 something; /* always equals 0x43 */
+ u8 control; /* settings of flow control lines */
+#define CONTROL_MASK 0x0c
+#define CONTROL_DTR_HIGH 0x08
+#define CONTROL_RTS_HIGH 0x04
+ u8 tx_status;
+#define TX_BUFFER_EMPTIED 0x09
+ u8 pin_state;
+#define PIN_MASK 0x3f
+#define PIN_MSR_MASK 0x1b
+#define PIN_RTS 0x20 /* output pin */
+#define PIN_CTS 0x10 /* input pin, active low */
+#define PIN_DSR 0x08 /* input pin, active low */
+#define PIN_DTR 0x04 /* output pin */
+#define PIN_RI 0x02 /* input pin, active low */
+#define PIN_DCD 0x01 /* input pin, active low */
+ u8 rx_bytes_avail; /* number of bytes in rx buffer */
+};
+
+#define OTI6858_CTRL_PKT_SIZE sizeof(struct oti6858_control_pkt)
+#define OTI6858_CTRL_EQUALS_PENDING(a, priv) \
+ (((a)->divisor == (priv)->pending_setup.divisor) \
+ && ((a)->control == (priv)->pending_setup.control) \
+ && ((a)->frame_fmt == (priv)->pending_setup.frame_fmt))
+
+/* function prototypes */
+static int oti6858_open(struct tty_struct *tty, struct usb_serial_port *port);
+static void oti6858_close(struct usb_serial_port *port);
+static void oti6858_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+static void oti6858_init_termios(struct tty_struct *tty);
+static void oti6858_read_int_callback(struct urb *urb);
+static void oti6858_read_bulk_callback(struct urb *urb);
+static void oti6858_write_bulk_callback(struct urb *urb);
+static int oti6858_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count);
+static unsigned int oti6858_write_room(struct tty_struct *tty);
+static unsigned int oti6858_chars_in_buffer(struct tty_struct *tty);
+static int oti6858_tiocmget(struct tty_struct *tty);
+static int oti6858_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear);
+static int oti6858_port_probe(struct usb_serial_port *port);
+static void oti6858_port_remove(struct usb_serial_port *port);
+
+/* device info */
+static struct usb_serial_driver oti6858_device = {
+ .driver = {
+ .name = "oti6858",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ .num_interrupt_in = 1,
+ .open = oti6858_open,
+ .close = oti6858_close,
+ .write = oti6858_write,
+ .set_termios = oti6858_set_termios,
+ .init_termios = oti6858_init_termios,
+ .tiocmget = oti6858_tiocmget,
+ .tiocmset = oti6858_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .read_bulk_callback = oti6858_read_bulk_callback,
+ .read_int_callback = oti6858_read_int_callback,
+ .write_bulk_callback = oti6858_write_bulk_callback,
+ .write_room = oti6858_write_room,
+ .chars_in_buffer = oti6858_chars_in_buffer,
+ .port_probe = oti6858_port_probe,
+ .port_remove = oti6858_port_remove,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &oti6858_device, NULL
+};
+
+struct oti6858_private {
+ spinlock_t lock;
+
+ struct oti6858_control_pkt status;
+
+ struct {
+ u8 read_urb_in_use;
+ u8 write_urb_in_use;
+ } flags;
+ struct delayed_work delayed_write_work;
+
+ struct {
+ __le16 divisor;
+ u8 frame_fmt;
+ u8 control;
+ } pending_setup;
+ u8 transient;
+ u8 setup_done;
+ struct delayed_work delayed_setup_work;
+
+ struct usb_serial_port *port; /* USB port with which associated */
+};
+
+static void setup_line(struct work_struct *work)
+{
+ struct oti6858_private *priv = container_of(work,
+ struct oti6858_private, delayed_setup_work.work);
+ struct usb_serial_port *port = priv->port;
+ struct oti6858_control_pkt *new_setup;
+ unsigned long flags;
+ int result;
+
+ new_setup = kmalloc(OTI6858_CTRL_PKT_SIZE, GFP_KERNEL);
+ if (!new_setup) {
+ /* we will try again */
+ schedule_delayed_work(&priv->delayed_setup_work,
+ msecs_to_jiffies(2));
+ return;
+ }
+
+ result = usb_control_msg(port->serial->dev,
+ usb_rcvctrlpipe(port->serial->dev, 0),
+ OTI6858_REQ_T_GET_STATUS,
+ OTI6858_REQ_GET_STATUS,
+ 0, 0,
+ new_setup, OTI6858_CTRL_PKT_SIZE,
+ 100);
+
+ if (result != OTI6858_CTRL_PKT_SIZE) {
+ dev_err(&port->dev, "%s(): error reading status\n", __func__);
+ kfree(new_setup);
+ /* we will try again */
+ schedule_delayed_work(&priv->delayed_setup_work,
+ msecs_to_jiffies(2));
+ return;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (!OTI6858_CTRL_EQUALS_PENDING(new_setup, priv)) {
+ new_setup->divisor = priv->pending_setup.divisor;
+ new_setup->control = priv->pending_setup.control;
+ new_setup->frame_fmt = priv->pending_setup.frame_fmt;
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+ result = usb_control_msg(port->serial->dev,
+ usb_sndctrlpipe(port->serial->dev, 0),
+ OTI6858_REQ_T_SET_LINE,
+ OTI6858_REQ_SET_LINE,
+ 0, 0,
+ new_setup, OTI6858_CTRL_PKT_SIZE,
+ 100);
+ } else {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ result = 0;
+ }
+ kfree(new_setup);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (result != OTI6858_CTRL_PKT_SIZE)
+ priv->transient = 0;
+ priv->setup_done = 1;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ dev_dbg(&port->dev, "%s(): submitting interrupt urb\n", __func__);
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (result != 0) {
+ dev_err(&port->dev, "%s(): usb_submit_urb() failed with error %d\n",
+ __func__, result);
+ }
+}
+
+static void send_data(struct work_struct *work)
+{
+ struct oti6858_private *priv = container_of(work,
+ struct oti6858_private, delayed_write_work.work);
+ struct usb_serial_port *port = priv->port;
+ int count = 0, result;
+ unsigned long flags;
+ u8 *allow;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->flags.write_urb_in_use) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ schedule_delayed_work(&priv->delayed_write_work,
+ msecs_to_jiffies(2));
+ return;
+ }
+ priv->flags.write_urb_in_use = 1;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ spin_lock_irqsave(&port->lock, flags);
+ count = kfifo_len(&port->write_fifo);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ if (count > port->bulk_out_size)
+ count = port->bulk_out_size;
+
+ if (count != 0) {
+ allow = kmalloc(1, GFP_KERNEL);
+ if (!allow)
+ return;
+
+ result = usb_control_msg(port->serial->dev,
+ usb_rcvctrlpipe(port->serial->dev, 0),
+ OTI6858_REQ_T_CHECK_TXBUFF,
+ OTI6858_REQ_CHECK_TXBUFF,
+ count, 0, allow, 1, 100);
+ if (result != 1 || *allow != 0)
+ count = 0;
+ kfree(allow);
+ }
+
+ if (count == 0) {
+ priv->flags.write_urb_in_use = 0;
+
+ dev_dbg(&port->dev, "%s(): submitting interrupt urb\n", __func__);
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
+ if (result != 0) {
+ dev_err(&port->dev, "%s(): usb_submit_urb() failed with error %d\n",
+ __func__, result);
+ }
+ return;
+ }
+
+ count = kfifo_out_locked(&port->write_fifo,
+ port->write_urb->transfer_buffer,
+ count, &port->lock);
+ port->write_urb->transfer_buffer_length = count;
+ result = usb_submit_urb(port->write_urb, GFP_NOIO);
+ if (result != 0) {
+ dev_err_console(port, "%s(): usb_submit_urb() failed with error %d\n",
+ __func__, result);
+ priv->flags.write_urb_in_use = 0;
+ }
+
+ usb_serial_port_softint(port);
+}
+
+static int oti6858_port_probe(struct usb_serial_port *port)
+{
+ struct oti6858_private *priv;
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->lock);
+ priv->port = port;
+ INIT_DELAYED_WORK(&priv->delayed_setup_work, setup_line);
+ INIT_DELAYED_WORK(&priv->delayed_write_work, send_data);
+
+ usb_set_serial_port_data(port, priv);
+
+ port->port.drain_delay = 256; /* FIXME: check the FIFO length */
+
+ return 0;
+}
+
+static void oti6858_port_remove(struct usb_serial_port *port)
+{
+ struct oti6858_private *priv;
+
+ priv = usb_get_serial_port_data(port);
+ kfree(priv);
+}
+
+static int oti6858_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count)
+{
+ if (!count)
+ return count;
+
+ count = kfifo_in_locked(&port->write_fifo, buf, count, &port->lock);
+
+ return count;
+}
+
+static unsigned int oti6858_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned int room;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ room = kfifo_avail(&port->write_fifo);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ return room;
+}
+
+static unsigned int oti6858_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned int chars;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ chars = kfifo_len(&port->write_fifo);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ return chars;
+}
+
+static void oti6858_init_termios(struct tty_struct *tty)
+{
+ tty_encode_baud_rate(tty, 38400, 38400);
+}
+
+static void oti6858_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct oti6858_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ unsigned int cflag;
+ u8 frame_fmt, control;
+ __le16 divisor;
+ int br;
+
+ cflag = tty->termios.c_cflag;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ frame_fmt = priv->pending_setup.frame_fmt;
+ control = priv->pending_setup.control;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ frame_fmt &= ~FMT_DATA_BITS_MASK;
+ switch (cflag & CSIZE) {
+ case CS5:
+ frame_fmt |= FMT_DATA_BITS_5;
+ break;
+ case CS6:
+ frame_fmt |= FMT_DATA_BITS_6;
+ break;
+ case CS7:
+ frame_fmt |= FMT_DATA_BITS_7;
+ break;
+ default:
+ case CS8:
+ frame_fmt |= FMT_DATA_BITS_8;
+ break;
+ }
+
+ /* manufacturer claims that this device can work with baud rates
+ * up to 3 Mbps; I've tested it only on 115200 bps, so I can't
+ * guarantee that any other baud rate will work (especially
+ * the higher ones)
+ */
+ br = tty_get_baud_rate(tty);
+ if (br == 0) {
+ divisor = 0;
+ } else {
+ int real_br;
+ int new_divisor;
+ br = min(br, OTI6858_MAX_BAUD_RATE);
+
+ new_divisor = (96000000 + 8 * br) / (16 * br);
+ real_br = 96000000 / (16 * new_divisor);
+ divisor = cpu_to_le16(new_divisor);
+ tty_encode_baud_rate(tty, real_br, real_br);
+ }
+
+ frame_fmt &= ~FMT_STOP_BITS_MASK;
+ if ((cflag & CSTOPB) != 0)
+ frame_fmt |= FMT_STOP_BITS_2;
+ else
+ frame_fmt |= FMT_STOP_BITS_1;
+
+ frame_fmt &= ~FMT_PARITY_MASK;
+ if ((cflag & PARENB) != 0) {
+ if ((cflag & PARODD) != 0)
+ frame_fmt |= FMT_PARITY_ODD;
+ else
+ frame_fmt |= FMT_PARITY_EVEN;
+ } else {
+ frame_fmt |= FMT_PARITY_NONE;
+ }
+
+ control &= ~CONTROL_MASK;
+ if ((cflag & CRTSCTS) != 0)
+ control |= (CONTROL_DTR_HIGH | CONTROL_RTS_HIGH);
+
+ /* change control lines if we are switching to or from B0 */
+ /* FIXME:
+ spin_lock_irqsave(&priv->lock, flags);
+ control = priv->line_control;
+ if ((cflag & CBAUD) == B0)
+ priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS);
+ else
+ priv->line_control |= (CONTROL_DTR | CONTROL_RTS);
+ if (control != priv->line_control) {
+ control = priv->line_control;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ set_control_lines(serial->dev, control);
+ } else {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ }
+ */
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (divisor != priv->pending_setup.divisor
+ || control != priv->pending_setup.control
+ || frame_fmt != priv->pending_setup.frame_fmt) {
+ priv->pending_setup.divisor = divisor;
+ priv->pending_setup.control = control;
+ priv->pending_setup.frame_fmt = frame_fmt;
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static int oti6858_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct oti6858_private *priv = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+ struct oti6858_control_pkt *buf;
+ unsigned long flags;
+ int result;
+
+ usb_clear_halt(serial->dev, port->write_urb->pipe);
+ usb_clear_halt(serial->dev, port->read_urb->pipe);
+
+ buf = kmalloc(OTI6858_CTRL_PKT_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+ OTI6858_REQ_T_GET_STATUS,
+ OTI6858_REQ_GET_STATUS,
+ 0, 0,
+ buf, OTI6858_CTRL_PKT_SIZE,
+ 100);
+ if (result != OTI6858_CTRL_PKT_SIZE) {
+ /* assume default (after power-on reset) values */
+ buf->divisor = cpu_to_le16(0x009c); /* 38400 bps */
+ buf->frame_fmt = 0x03; /* 8N1 */
+ buf->something = 0x43;
+ buf->control = 0x4c; /* DTR, RTS */
+ buf->tx_status = 0x00;
+ buf->pin_state = 0x5b; /* RTS, CTS, DSR, DTR, RI, DCD */
+ buf->rx_bytes_avail = 0x00;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ memcpy(&priv->status, buf, OTI6858_CTRL_PKT_SIZE);
+ priv->pending_setup.divisor = buf->divisor;
+ priv->pending_setup.frame_fmt = buf->frame_fmt;
+ priv->pending_setup.control = buf->control;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ kfree(buf);
+
+ dev_dbg(&port->dev, "%s(): submitting interrupt urb\n", __func__);
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (result != 0) {
+ dev_err(&port->dev, "%s(): usb_submit_urb() failed with error %d\n",
+ __func__, result);
+ oti6858_close(port);
+ return result;
+ }
+
+ /* setup termios */
+ if (tty)
+ oti6858_set_termios(tty, port, NULL);
+
+ return 0;
+}
+
+static void oti6858_close(struct usb_serial_port *port)
+{
+ struct oti6858_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ /* clear out any remaining data in the buffer */
+ kfifo_reset_out(&port->write_fifo);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ dev_dbg(&port->dev, "%s(): after buf_clear()\n", __func__);
+
+ /* cancel scheduled setup */
+ cancel_delayed_work_sync(&priv->delayed_setup_work);
+ cancel_delayed_work_sync(&priv->delayed_write_work);
+
+ /* shutdown our urbs */
+ dev_dbg(&port->dev, "%s(): shutting down urbs\n", __func__);
+ usb_kill_urb(port->write_urb);
+ usb_kill_urb(port->read_urb);
+ usb_kill_urb(port->interrupt_in_urb);
+}
+
+static int oti6858_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct oti6858_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ u8 control;
+
+ dev_dbg(&port->dev, "%s(set = 0x%08x, clear = 0x%08x)\n",
+ __func__, set, clear);
+
+ /* FIXME: check if this is correct (active high/low) */
+ spin_lock_irqsave(&priv->lock, flags);
+ control = priv->pending_setup.control;
+ if ((set & TIOCM_RTS) != 0)
+ control |= CONTROL_RTS_HIGH;
+ if ((set & TIOCM_DTR) != 0)
+ control |= CONTROL_DTR_HIGH;
+ if ((clear & TIOCM_RTS) != 0)
+ control &= ~CONTROL_RTS_HIGH;
+ if ((clear & TIOCM_DTR) != 0)
+ control &= ~CONTROL_DTR_HIGH;
+
+ if (control != priv->pending_setup.control)
+ priv->pending_setup.control = control;
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return 0;
+}
+
+static int oti6858_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct oti6858_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ unsigned pin_state;
+ unsigned result = 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ pin_state = priv->status.pin_state & PIN_MASK;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /* FIXME: check if this is correct (active high/low) */
+ if ((pin_state & PIN_RTS) != 0)
+ result |= TIOCM_RTS;
+ if ((pin_state & PIN_CTS) != 0)
+ result |= TIOCM_CTS;
+ if ((pin_state & PIN_DSR) != 0)
+ result |= TIOCM_DSR;
+ if ((pin_state & PIN_DTR) != 0)
+ result |= TIOCM_DTR;
+ if ((pin_state & PIN_RI) != 0)
+ result |= TIOCM_RI;
+ if ((pin_state & PIN_DCD) != 0)
+ result |= TIOCM_CD;
+
+ dev_dbg(&port->dev, "%s() = 0x%08x\n", __func__, result);
+
+ return result;
+}
+
+static void oti6858_read_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct oti6858_private *priv = usb_get_serial_port_data(port);
+ int transient = 0, can_recv = 0, resubmit = 1;
+ int status = urb->status;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&urb->dev->dev, "%s(): urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(&urb->dev->dev, "%s(): nonzero urb status received: %d\n",
+ __func__, status);
+ break;
+ }
+
+ if (status == 0 && urb->actual_length == OTI6858_CTRL_PKT_SIZE) {
+ struct oti6858_control_pkt *xs = urb->transfer_buffer;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ if (!priv->transient) {
+ if (!OTI6858_CTRL_EQUALS_PENDING(xs, priv)) {
+ if (xs->rx_bytes_avail == 0) {
+ priv->transient = 4;
+ priv->setup_done = 0;
+ resubmit = 0;
+ dev_dbg(&port->dev, "%s(): scheduling setup_line()\n", __func__);
+ schedule_delayed_work(&priv->delayed_setup_work, 0);
+ }
+ }
+ } else {
+ if (OTI6858_CTRL_EQUALS_PENDING(xs, priv)) {
+ priv->transient = 0;
+ } else if (!priv->setup_done) {
+ resubmit = 0;
+ } else if (--priv->transient == 0) {
+ if (xs->rx_bytes_avail == 0) {
+ priv->transient = 4;
+ priv->setup_done = 0;
+ resubmit = 0;
+ dev_dbg(&port->dev, "%s(): scheduling setup_line()\n", __func__);
+ schedule_delayed_work(&priv->delayed_setup_work, 0);
+ }
+ }
+ }
+
+ if (!priv->transient) {
+ u8 delta = xs->pin_state ^ priv->status.pin_state;
+
+ if (delta & PIN_MSR_MASK) {
+ if (delta & PIN_CTS)
+ port->icount.cts++;
+ if (delta & PIN_DSR)
+ port->icount.dsr++;
+ if (delta & PIN_RI)
+ port->icount.rng++;
+ if (delta & PIN_DCD)
+ port->icount.dcd++;
+
+ wake_up_interruptible(&port->port.delta_msr_wait);
+ }
+
+ memcpy(&priv->status, xs, OTI6858_CTRL_PKT_SIZE);
+ }
+
+ if (!priv->transient && xs->rx_bytes_avail != 0) {
+ can_recv = xs->rx_bytes_avail;
+ priv->flags.read_urb_in_use = 1;
+ }
+
+ transient = priv->transient;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ }
+
+ if (can_recv) {
+ int result;
+
+ result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
+ if (result != 0) {
+ priv->flags.read_urb_in_use = 0;
+ dev_err(&port->dev, "%s(): usb_submit_urb() failed,"
+ " error %d\n", __func__, result);
+ } else {
+ resubmit = 0;
+ }
+ } else if (!transient) {
+ unsigned long flags;
+ int count;
+
+ spin_lock_irqsave(&port->lock, flags);
+ count = kfifo_len(&port->write_fifo);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->flags.write_urb_in_use == 0 && count != 0) {
+ schedule_delayed_work(&priv->delayed_write_work, 0);
+ resubmit = 0;
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+ }
+
+ if (resubmit) {
+ int result;
+
+/* dev_dbg(&urb->dev->dev, "%s(): submitting interrupt urb\n", __func__); */
+ result = usb_submit_urb(urb, GFP_ATOMIC);
+ if (result != 0) {
+ dev_err(&urb->dev->dev,
+ "%s(): usb_submit_urb() failed with"
+ " error %d\n", __func__, result);
+ }
+ }
+}
+
+static void oti6858_read_bulk_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct oti6858_private *priv = usb_get_serial_port_data(port);
+ unsigned char *data = urb->transfer_buffer;
+ unsigned long flags;
+ int status = urb->status;
+ int result;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->flags.read_urb_in_use = 0;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (status != 0) {
+ dev_dbg(&urb->dev->dev, "%s(): unable to handle the error, exiting\n", __func__);
+ return;
+ }
+
+ if (urb->actual_length > 0) {
+ tty_insert_flip_string(&port->port, data, urb->actual_length);
+ tty_flip_buffer_push(&port->port);
+ }
+
+ /* schedule the interrupt urb */
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
+ if (result != 0 && result != -EPERM) {
+ dev_err(&port->dev, "%s(): usb_submit_urb() failed,"
+ " error %d\n", __func__, result);
+ }
+}
+
+static void oti6858_write_bulk_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct oti6858_private *priv = usb_get_serial_port_data(port);
+ int status = urb->status;
+ int result;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&urb->dev->dev, "%s(): urb shutting down with status: %d\n", __func__, status);
+ priv->flags.write_urb_in_use = 0;
+ return;
+ default:
+ /* error in the urb, so we have to resubmit it */
+ dev_dbg(&urb->dev->dev, "%s(): nonzero write bulk status received: %d\n", __func__, status);
+ dev_dbg(&urb->dev->dev, "%s(): overflow in write\n", __func__);
+
+ port->write_urb->transfer_buffer_length = 1;
+ result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ if (result) {
+ dev_err_console(port, "%s(): usb_submit_urb() failed,"
+ " error %d\n", __func__, result);
+ } else {
+ return;
+ }
+ }
+
+ priv->flags.write_urb_in_use = 0;
+
+ /* schedule the interrupt urb if we are still open */
+ dev_dbg(&port->dev, "%s(): submitting interrupt urb\n", __func__);
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
+ if (result != 0) {
+ dev_err(&port->dev, "%s(): failed submitting int urb,"
+ " error %d\n", __func__, result);
+ }
+}
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION(OTI6858_DESCRIPTION);
+MODULE_AUTHOR(OTI6858_AUTHOR);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/oti6858.h b/drivers/usb/serial/oti6858.h
new file mode 100644
index 000000000..5c25836fd
--- /dev/null
+++ b/drivers/usb/serial/oti6858.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Ours Technology Inc. OTi-6858 USB to serial adapter driver.
+ */
+#ifndef __LINUX_USB_SERIAL_OTI6858_H
+#define __LINUX_USB_SERIAL_OTI6858_H
+
+#define OTI6858_VENDOR_ID 0x0ea0
+#define OTI6858_PRODUCT_ID 0x6858
+
+#endif
diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c
new file mode 100644
index 000000000..0bcbdcea5
--- /dev/null
+++ b/drivers/usb/serial/pl2303.c
@@ -0,0 +1,1307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Prolific PL2303 USB to serial adaptor driver
+ *
+ * Copyright (C) 2001-2007 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2003 IBM Corp.
+ *
+ * Original driver for 2.2.x by anonymous
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/unaligned.h>
+#include "pl2303.h"
+
+
+#define PL2303_QUIRK_UART_STATE_IDX0 BIT(0)
+#define PL2303_QUIRK_LEGACY BIT(1)
+#define PL2303_QUIRK_ENDPOINT_HACK BIT(2)
+#define PL2303_QUIRK_NO_BREAK_GETLINE BIT(3)
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID),
+ .driver_info = PL2303_QUIRK_ENDPOINT_HACK },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ2) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_DCU11) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ3) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_CHILITAG) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_PHAROS) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_ALDIGA) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_MMX) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GPRS) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_HCR331) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_MOTOROLA) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_ZTEK) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_TB) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GC) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GB) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GT) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GL) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GE) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GS) },
+ { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) },
+ { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) },
+ { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID),
+ .driver_info = PL2303_QUIRK_ENDPOINT_HACK },
+ { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_UC485),
+ .driver_info = PL2303_QUIRK_ENDPOINT_HACK },
+ { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_UC232B),
+ .driver_info = PL2303_QUIRK_ENDPOINT_HACK },
+ { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID2) },
+ { USB_DEVICE(ATEN_VENDOR_ID2, ATEN_PRODUCT_ID) },
+ { USB_DEVICE(ELCOM_VENDOR_ID, ELCOM_PRODUCT_ID) },
+ { USB_DEVICE(ELCOM_VENDOR_ID, ELCOM_PRODUCT_ID_UCSGT) },
+ { USB_DEVICE(ITEGNO_VENDOR_ID, ITEGNO_PRODUCT_ID) },
+ { USB_DEVICE(ITEGNO_VENDOR_ID, ITEGNO_PRODUCT_ID_2080) },
+ { USB_DEVICE(MA620_VENDOR_ID, MA620_PRODUCT_ID) },
+ { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID) },
+ { USB_DEVICE(TRIPP_VENDOR_ID, TRIPP_PRODUCT_ID) },
+ { USB_DEVICE(RADIOSHACK_VENDOR_ID, RADIOSHACK_PRODUCT_ID) },
+ { USB_DEVICE(DCU10_VENDOR_ID, DCU10_PRODUCT_ID) },
+ { USB_DEVICE(SITECOM_VENDOR_ID, SITECOM_PRODUCT_ID) },
+ { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_ID) },
+ { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_SX1),
+ .driver_info = PL2303_QUIRK_UART_STATE_IDX0 },
+ { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X65),
+ .driver_info = PL2303_QUIRK_UART_STATE_IDX0 },
+ { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X75),
+ .driver_info = PL2303_QUIRK_UART_STATE_IDX0 },
+ { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_EF81),
+ .driver_info = PL2303_QUIRK_ENDPOINT_HACK },
+ { USB_DEVICE(BENQ_VENDOR_ID, BENQ_PRODUCT_ID_S81) }, /* Benq/Siemens S81 */
+ { USB_DEVICE(SYNTECH_VENDOR_ID, SYNTECH_PRODUCT_ID) },
+ { USB_DEVICE(NOKIA_CA42_VENDOR_ID, NOKIA_CA42_PRODUCT_ID) },
+ { USB_DEVICE(CA_42_CA42_VENDOR_ID, CA_42_CA42_PRODUCT_ID) },
+ { USB_DEVICE(SAGEM_VENDOR_ID, SAGEM_PRODUCT_ID) },
+ { USB_DEVICE(LEADTEK_VENDOR_ID, LEADTEK_9531_PRODUCT_ID) },
+ { USB_DEVICE(SPEEDDRAGON_VENDOR_ID, SPEEDDRAGON_PRODUCT_ID) },
+ { USB_DEVICE(DATAPILOT_U2_VENDOR_ID, DATAPILOT_U2_PRODUCT_ID) },
+ { USB_DEVICE(BELKIN_VENDOR_ID, BELKIN_PRODUCT_ID) },
+ { USB_DEVICE(ALCOR_VENDOR_ID, ALCOR_PRODUCT_ID),
+ .driver_info = PL2303_QUIRK_ENDPOINT_HACK },
+ { USB_DEVICE(WS002IN_VENDOR_ID, WS002IN_PRODUCT_ID) },
+ { USB_DEVICE(COREGA_VENDOR_ID, COREGA_PRODUCT_ID) },
+ { USB_DEVICE(YCCABLE_VENDOR_ID, YCCABLE_PRODUCT_ID) },
+ { USB_DEVICE(SUPERIAL_VENDOR_ID, SUPERIAL_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_LD220_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_LD220TA_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_LD381_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_LD381GC_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_LD960_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_LD960TA_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_LCM220_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_LCM960_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_LM920_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_LM930_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_LM940_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_TD620_PRODUCT_ID) },
+ { USB_DEVICE(CRESSI_VENDOR_ID, CRESSI_EDY_PRODUCT_ID) },
+ { USB_DEVICE(ZEAGLE_VENDOR_ID, ZEAGLE_N2ITION3_PRODUCT_ID) },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_QN3USB_PRODUCT_ID) },
+ { USB_DEVICE(SANWA_VENDOR_ID, SANWA_PRODUCT_ID) },
+ { USB_DEVICE(ADLINK_VENDOR_ID, ADLINK_ND6530_PRODUCT_ID) },
+ { USB_DEVICE(ADLINK_VENDOR_ID, ADLINK_ND6530GC_PRODUCT_ID) },
+ { USB_DEVICE(SMART_VENDOR_ID, SMART_PRODUCT_ID) },
+ { USB_DEVICE(AT_VENDOR_ID, AT_VTKIT3_PRODUCT_ID) },
+ { USB_DEVICE(IBM_VENDOR_ID, IBM_PRODUCT_ID) },
+ { USB_DEVICE(MACROSILICON_VENDOR_ID, MACROSILICON_MS3020_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+#define SET_LINE_REQUEST_TYPE 0x21
+#define SET_LINE_REQUEST 0x20
+
+#define SET_CONTROL_REQUEST_TYPE 0x21
+#define SET_CONTROL_REQUEST 0x22
+#define CONTROL_DTR 0x01
+#define CONTROL_RTS 0x02
+
+#define BREAK_REQUEST_TYPE 0x21
+#define BREAK_REQUEST 0x23
+#define BREAK_ON 0xffff
+#define BREAK_OFF 0x0000
+
+#define GET_LINE_REQUEST_TYPE 0xa1
+#define GET_LINE_REQUEST 0x21
+
+#define VENDOR_WRITE_REQUEST_TYPE 0x40
+#define VENDOR_WRITE_REQUEST 0x01
+#define VENDOR_WRITE_NREQUEST 0x80
+
+#define VENDOR_READ_REQUEST_TYPE 0xc0
+#define VENDOR_READ_REQUEST 0x01
+#define VENDOR_READ_NREQUEST 0x81
+
+#define UART_STATE_INDEX 8
+#define UART_STATE_MSR_MASK 0x8b
+#define UART_STATE_TRANSIENT_MASK 0x74
+#define UART_DCD 0x01
+#define UART_DSR 0x02
+#define UART_BREAK_ERROR 0x04
+#define UART_RING 0x08
+#define UART_FRAME_ERROR 0x10
+#define UART_PARITY_ERROR 0x20
+#define UART_OVERRUN_ERROR 0x40
+#define UART_CTS 0x80
+
+#define PL2303_FLOWCTRL_MASK 0xf0
+
+#define PL2303_READ_TYPE_HX_STATUS 0x8080
+
+#define PL2303_HXN_RESET_REG 0x07
+#define PL2303_HXN_RESET_UPSTREAM_PIPE 0x02
+#define PL2303_HXN_RESET_DOWNSTREAM_PIPE 0x01
+
+#define PL2303_HXN_FLOWCTRL_REG 0x0a
+#define PL2303_HXN_FLOWCTRL_MASK 0x1c
+#define PL2303_HXN_FLOWCTRL_NONE 0x1c
+#define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18
+#define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c
+
+static int pl2303_set_break(struct usb_serial_port *port, bool enable);
+
+enum pl2303_type {
+ TYPE_H,
+ TYPE_HX,
+ TYPE_TA,
+ TYPE_TB,
+ TYPE_HXD,
+ TYPE_HXN,
+ TYPE_COUNT
+};
+
+struct pl2303_type_data {
+ const char *name;
+ speed_t max_baud_rate;
+ unsigned long quirks;
+ unsigned int no_autoxonxoff:1;
+ unsigned int no_divisors:1;
+ unsigned int alt_divisors:1;
+};
+
+struct pl2303_serial_private {
+ const struct pl2303_type_data *type;
+ unsigned long quirks;
+};
+
+struct pl2303_private {
+ spinlock_t lock;
+ u8 line_control;
+ u8 line_status;
+
+ u8 line_settings[7];
+};
+
+static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = {
+ [TYPE_H] = {
+ .name = "H",
+ .max_baud_rate = 1228800,
+ .quirks = PL2303_QUIRK_LEGACY,
+ .no_autoxonxoff = true,
+ },
+ [TYPE_HX] = {
+ .name = "HX",
+ .max_baud_rate = 6000000,
+ },
+ [TYPE_TA] = {
+ .name = "TA",
+ .max_baud_rate = 6000000,
+ .alt_divisors = true,
+ },
+ [TYPE_TB] = {
+ .name = "TB",
+ .max_baud_rate = 12000000,
+ .alt_divisors = true,
+ },
+ [TYPE_HXD] = {
+ .name = "HXD",
+ .max_baud_rate = 12000000,
+ },
+ [TYPE_HXN] = {
+ .name = "G",
+ .max_baud_rate = 12000000,
+ .no_divisors = true,
+ },
+};
+
+static int pl2303_vendor_read(struct usb_serial *serial, u16 value,
+ unsigned char buf[1])
+{
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
+ struct device *dev = &serial->interface->dev;
+ u8 request;
+ int res;
+
+ if (spriv->type == &pl2303_type_data[TYPE_HXN])
+ request = VENDOR_READ_NREQUEST;
+ else
+ request = VENDOR_READ_REQUEST;
+
+ res = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+ request, VENDOR_READ_REQUEST_TYPE,
+ value, 0, buf, 1, 100);
+ if (res != 1) {
+ dev_err(dev, "%s - failed to read [%04x]: %d\n", __func__,
+ value, res);
+ if (res >= 0)
+ res = -EIO;
+
+ return res;
+ }
+
+ dev_dbg(dev, "%s - [%04x] = %02x\n", __func__, value, buf[0]);
+
+ return 0;
+}
+
+static int pl2303_vendor_write(struct usb_serial *serial, u16 value, u16 index)
+{
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
+ struct device *dev = &serial->interface->dev;
+ u8 request;
+ int res;
+
+ dev_dbg(dev, "%s - [%04x] = %02x\n", __func__, value, index);
+
+ if (spriv->type == &pl2303_type_data[TYPE_HXN])
+ request = VENDOR_WRITE_NREQUEST;
+ else
+ request = VENDOR_WRITE_REQUEST;
+
+ res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ request, VENDOR_WRITE_REQUEST_TYPE,
+ value, index, NULL, 0, 100);
+ if (res) {
+ dev_err(dev, "%s - failed to write [%04x]: %d\n", __func__,
+ value, res);
+ return res;
+ }
+
+ return 0;
+}
+
+static int pl2303_update_reg(struct usb_serial *serial, u8 reg, u8 mask, u8 val)
+{
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
+ int ret = 0;
+ u8 *buf;
+
+ buf = kmalloc(1, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ if (spriv->type == &pl2303_type_data[TYPE_HXN])
+ ret = pl2303_vendor_read(serial, reg, buf);
+ else
+ ret = pl2303_vendor_read(serial, reg | 0x80, buf);
+
+ if (ret)
+ goto out_free;
+
+ *buf &= ~mask;
+ *buf |= val & mask;
+
+ ret = pl2303_vendor_write(serial, reg, *buf);
+out_free:
+ kfree(buf);
+
+ return ret;
+}
+
+static int pl2303_probe(struct usb_serial *serial,
+ const struct usb_device_id *id)
+{
+ usb_set_serial_data(serial, (void *)id->driver_info);
+
+ return 0;
+}
+
+/*
+ * Use interrupt endpoint from first interface if available.
+ *
+ * This is needed due to the looney way its endpoints are set up.
+ */
+static int pl2303_endpoint_hack(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ struct usb_interface *interface = serial->interface;
+ struct usb_device *dev = serial->dev;
+ struct device *ddev = &interface->dev;
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ unsigned int i;
+
+ if (interface == dev->actconfig->interface[0])
+ return 0;
+
+ /* check out the endpoints of the other interface */
+ iface_desc = dev->actconfig->interface[0]->cur_altsetting;
+
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+ endpoint = &iface_desc->endpoint[i].desc;
+
+ if (!usb_endpoint_is_int_in(endpoint))
+ continue;
+
+ dev_dbg(ddev, "found interrupt in on separate interface\n");
+ if (epds->num_interrupt_in < ARRAY_SIZE(epds->interrupt_in))
+ epds->interrupt_in[epds->num_interrupt_in++] = endpoint;
+ }
+
+ return 0;
+}
+
+static int pl2303_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ unsigned long quirks = (unsigned long)usb_get_serial_data(serial);
+ struct device *dev = &serial->interface->dev;
+ int ret;
+
+ if (quirks & PL2303_QUIRK_ENDPOINT_HACK) {
+ ret = pl2303_endpoint_hack(serial, epds);
+ if (ret)
+ return ret;
+ }
+
+ if (epds->num_interrupt_in < 1) {
+ dev_err(dev, "required interrupt-in endpoint missing\n");
+ return -ENODEV;
+ }
+
+ return 1;
+}
+
+static bool pl2303_supports_hx_status(struct usb_serial *serial)
+{
+ int ret;
+ u8 buf;
+
+ ret = usb_control_msg_recv(serial->dev, 0, VENDOR_READ_REQUEST,
+ VENDOR_READ_REQUEST_TYPE, PL2303_READ_TYPE_HX_STATUS,
+ 0, &buf, 1, 100, GFP_KERNEL);
+
+ return ret == 0;
+}
+
+static int pl2303_detect_type(struct usb_serial *serial)
+{
+ struct usb_device_descriptor *desc = &serial->dev->descriptor;
+ u16 bcdDevice, bcdUSB;
+
+ /*
+ * Legacy PL2303H, variants 0 and 1 (difference unknown).
+ */
+ if (desc->bDeviceClass == 0x02)
+ return TYPE_H; /* variant 0 */
+
+ if (desc->bMaxPacketSize0 != 0x40) {
+ if (desc->bDeviceClass == 0x00 || desc->bDeviceClass == 0xff)
+ return TYPE_H; /* variant 1 */
+
+ return TYPE_H; /* variant 0 */
+ }
+
+ bcdDevice = le16_to_cpu(desc->bcdDevice);
+ bcdUSB = le16_to_cpu(desc->bcdUSB);
+
+ switch (bcdUSB) {
+ case 0x101:
+ /* USB 1.0.1? Let's assume they meant 1.1... */
+ fallthrough;
+ case 0x110:
+ switch (bcdDevice) {
+ case 0x300:
+ return TYPE_HX;
+ case 0x400:
+ return TYPE_HXD;
+ default:
+ return TYPE_HX;
+ }
+ break;
+ case 0x200:
+ switch (bcdDevice) {
+ case 0x100: /* GC */
+ case 0x105:
+ return TYPE_HXN;
+ case 0x300: /* GT / TA */
+ if (pl2303_supports_hx_status(serial))
+ return TYPE_TA;
+ fallthrough;
+ case 0x305:
+ case 0x400: /* GL */
+ case 0x405:
+ return TYPE_HXN;
+ case 0x500: /* GE / TB */
+ if (pl2303_supports_hx_status(serial))
+ return TYPE_TB;
+ fallthrough;
+ case 0x505:
+ case 0x600: /* GS */
+ case 0x605:
+ case 0x700: /* GR */
+ case 0x705:
+ case 0x905: /* GT-2AB */
+ case 0x1005: /* GC-Q20 */
+ return TYPE_HXN;
+ }
+ break;
+ }
+
+ dev_err(&serial->interface->dev,
+ "unknown device type, please report to linux-usb@vger.kernel.org\n");
+ return -ENODEV;
+}
+
+static bool pl2303_is_hxd_clone(struct usb_serial *serial)
+{
+ struct usb_device *udev = serial->dev;
+ unsigned char *buf;
+ int ret;
+
+ buf = kmalloc(7, GFP_KERNEL);
+ if (!buf)
+ return false;
+
+ ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE,
+ 0, 0, buf, 7, 100);
+
+ kfree(buf);
+
+ return ret == -EPIPE;
+}
+
+static int pl2303_startup(struct usb_serial *serial)
+{
+ struct pl2303_serial_private *spriv;
+ enum pl2303_type type;
+ unsigned char *buf;
+ int ret;
+
+ ret = pl2303_detect_type(serial);
+ if (ret < 0)
+ return ret;
+
+ type = ret;
+ dev_dbg(&serial->interface->dev, "device type: %s\n", pl2303_type_data[type].name);
+
+ spriv = kzalloc_obj(*spriv);
+ if (!spriv)
+ return -ENOMEM;
+
+ spriv->type = &pl2303_type_data[type];
+ spriv->quirks = (unsigned long)usb_get_serial_data(serial);
+ spriv->quirks |= spriv->type->quirks;
+
+ if (type == TYPE_HXD && pl2303_is_hxd_clone(serial))
+ spriv->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE;
+
+ usb_set_serial_data(serial, spriv);
+
+ if (type != TYPE_HXN) {
+ buf = kmalloc(1, GFP_KERNEL);
+ if (!buf) {
+ kfree(spriv);
+ return -ENOMEM;
+ }
+
+ pl2303_vendor_read(serial, 0x8484, buf);
+ pl2303_vendor_write(serial, 0x0404, 0);
+ pl2303_vendor_read(serial, 0x8484, buf);
+ pl2303_vendor_read(serial, 0x8383, buf);
+ pl2303_vendor_read(serial, 0x8484, buf);
+ pl2303_vendor_write(serial, 0x0404, 1);
+ pl2303_vendor_read(serial, 0x8484, buf);
+ pl2303_vendor_read(serial, 0x8383, buf);
+ pl2303_vendor_write(serial, 0, 1);
+ pl2303_vendor_write(serial, 1, 0);
+ if (spriv->quirks & PL2303_QUIRK_LEGACY)
+ pl2303_vendor_write(serial, 2, 0x24);
+ else
+ pl2303_vendor_write(serial, 2, 0x44);
+
+ kfree(buf);
+ }
+
+ return 0;
+}
+
+static void pl2303_release(struct usb_serial *serial)
+{
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
+
+ kfree(spriv);
+}
+
+static int pl2303_port_probe(struct usb_serial_port *port)
+{
+ struct pl2303_private *priv;
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->lock);
+
+ usb_set_serial_port_data(port, priv);
+
+ port->port.drain_delay = 256;
+
+ return 0;
+}
+
+static void pl2303_port_remove(struct usb_serial_port *port)
+{
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+
+ kfree(priv);
+}
+
+static int pl2303_set_control_lines(struct usb_serial_port *port, u8 value)
+{
+ struct usb_device *dev = port->serial->dev;
+ int retval;
+
+ dev_dbg(&port->dev, "%s - %02x\n", __func__, value);
+
+ retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ SET_CONTROL_REQUEST, SET_CONTROL_REQUEST_TYPE,
+ value, 0, NULL, 0, 100);
+ if (retval)
+ dev_err(&port->dev, "%s - failed: %d\n", __func__, retval);
+
+ return retval;
+}
+
+/*
+ * Returns the nearest supported baud rate that can be set directly without
+ * using divisors.
+ */
+static speed_t pl2303_get_supported_baud_rate(speed_t baud)
+{
+ static const speed_t baud_sup[] = {
+ 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600,
+ 14400, 19200, 28800, 38400, 57600, 115200, 230400, 460800,
+ 614400, 921600, 1228800, 2457600, 3000000, 6000000
+ };
+
+ unsigned i;
+
+ for (i = 0; i < ARRAY_SIZE(baud_sup); ++i) {
+ if (baud_sup[i] > baud)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(baud_sup))
+ baud = baud_sup[i - 1];
+ else if (i > 0 && (baud_sup[i] - baud) > (baud - baud_sup[i - 1]))
+ baud = baud_sup[i - 1];
+ else
+ baud = baud_sup[i];
+
+ return baud;
+}
+
+/*
+ * NOTE: If unsupported baud rates are set directly, the PL2303 seems to
+ * use 9600 baud.
+ */
+static speed_t pl2303_encode_baud_rate_direct(unsigned char buf[4],
+ speed_t baud)
+{
+ put_unaligned_le32(baud, buf);
+
+ return baud;
+}
+
+static speed_t pl2303_encode_baud_rate_divisor(unsigned char buf[4],
+ speed_t baud)
+{
+ unsigned int baseline, mantissa, exponent;
+
+ /*
+ * Apparently the formula is:
+ * baudrate = 12M * 32 / (mantissa * 4^exponent)
+ * where
+ * mantissa = buf[8:0]
+ * exponent = buf[11:9]
+ */
+ baseline = 12000000 * 32;
+ mantissa = baseline / baud;
+ if (mantissa == 0)
+ mantissa = 1; /* Avoid dividing by zero if baud > 32*12M. */
+ exponent = 0;
+ while (mantissa >= 512) {
+ if (exponent < 7) {
+ mantissa >>= 2; /* divide by 4 */
+ exponent++;
+ } else {
+ /* Exponent is maxed. Trim mantissa and leave. */
+ mantissa = 511;
+ break;
+ }
+ }
+
+ buf[3] = 0x80;
+ buf[2] = 0;
+ buf[1] = exponent << 1 | mantissa >> 8;
+ buf[0] = mantissa & 0xff;
+
+ /* Calculate and return the exact baud rate. */
+ baud = (baseline / mantissa) >> (exponent << 1);
+
+ return baud;
+}
+
+static speed_t pl2303_encode_baud_rate_divisor_alt(unsigned char buf[4],
+ speed_t baud)
+{
+ unsigned int baseline, mantissa, exponent;
+
+ /*
+ * Apparently, for the TA version the formula is:
+ * baudrate = 12M * 32 / (mantissa * 2^exponent)
+ * where
+ * mantissa = buf[10:0]
+ * exponent = buf[15:13 16]
+ */
+ baseline = 12000000 * 32;
+ mantissa = baseline / baud;
+ if (mantissa == 0)
+ mantissa = 1; /* Avoid dividing by zero if baud > 32*12M. */
+ exponent = 0;
+ while (mantissa >= 2048) {
+ if (exponent < 15) {
+ mantissa >>= 1; /* divide by 2 */
+ exponent++;
+ } else {
+ /* Exponent is maxed. Trim mantissa and leave. */
+ mantissa = 2047;
+ break;
+ }
+ }
+
+ buf[3] = 0x80;
+ buf[2] = exponent & 0x01;
+ buf[1] = (exponent & ~0x01) << 4 | mantissa >> 8;
+ buf[0] = mantissa & 0xff;
+
+ /* Calculate and return the exact baud rate. */
+ baud = (baseline / mantissa) >> exponent;
+
+ return baud;
+}
+
+static void pl2303_encode_baud_rate(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ u8 buf[4])
+{
+ struct usb_serial *serial = port->serial;
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
+ speed_t baud_sup;
+ speed_t baud;
+
+ baud = tty_get_baud_rate(tty);
+ dev_dbg(&port->dev, "baud requested = %u\n", baud);
+ if (!baud)
+ return;
+
+ if (spriv->type->max_baud_rate)
+ baud = min_t(speed_t, baud, spriv->type->max_baud_rate);
+ /*
+ * Use direct method for supported baud rates, otherwise use divisors.
+ * Newer chip types do not support divisor encoding.
+ */
+ if (spriv->type->no_divisors)
+ baud_sup = baud;
+ else
+ baud_sup = pl2303_get_supported_baud_rate(baud);
+
+ if (baud == baud_sup)
+ baud = pl2303_encode_baud_rate_direct(buf, baud);
+ else if (spriv->type->alt_divisors)
+ baud = pl2303_encode_baud_rate_divisor_alt(buf, baud);
+ else
+ baud = pl2303_encode_baud_rate_divisor(buf, baud);
+
+ /* Save resulting baud rate */
+ tty_encode_baud_rate(tty, baud, baud);
+ dev_dbg(&port->dev, "baud set = %u\n", baud);
+}
+
+static int pl2303_get_line_request(struct usb_serial_port *port,
+ unsigned char buf[7])
+{
+ struct usb_serial *serial = port->serial;
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
+ struct usb_device *udev = serial->dev;
+ int ret;
+
+ if (spriv->quirks & PL2303_QUIRK_NO_BREAK_GETLINE) {
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+
+ memcpy(buf, priv->line_settings, 7);
+ return 0;
+ }
+
+ ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE,
+ 0, 0, buf, 7, 100);
+ if (ret != 7) {
+ dev_err(&port->dev, "%s - failed: %d\n", __func__, ret);
+
+ if (ret >= 0)
+ ret = -EIO;
+
+ return ret;
+ }
+
+ dev_dbg(&port->dev, "%s - %7ph\n", __func__, buf);
+
+ return 0;
+}
+
+static int pl2303_set_line_request(struct usb_serial_port *port,
+ unsigned char buf[7])
+{
+ struct usb_device *udev = port->serial->dev;
+ int ret;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE,
+ 0, 0, buf, 7, 100);
+ if (ret < 0) {
+ dev_err(&port->dev, "%s - failed: %d\n", __func__, ret);
+ return ret;
+ }
+
+ dev_dbg(&port->dev, "%s - %7ph\n", __func__, buf);
+
+ return 0;
+}
+
+static bool pl2303_termios_change(const struct ktermios *a, const struct ktermios *b)
+{
+ bool ixon_change;
+
+ ixon_change = ((a->c_iflag ^ b->c_iflag) & (IXON | IXANY)) ||
+ a->c_cc[VSTART] != b->c_cc[VSTART] ||
+ a->c_cc[VSTOP] != b->c_cc[VSTOP];
+
+ return tty_termios_hw_change(a, b) || ixon_change;
+}
+
+static bool pl2303_enable_xonxoff(struct tty_struct *tty, const struct pl2303_type_data *type)
+{
+ if (!I_IXON(tty) || I_IXANY(tty))
+ return false;
+
+ if (START_CHAR(tty) != 0x11 || STOP_CHAR(tty) != 0x13)
+ return false;
+
+ if (type->no_autoxonxoff)
+ return false;
+
+ return true;
+}
+
+static void pl2303_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct usb_serial *serial = port->serial;
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ unsigned char *buf;
+ int ret;
+ u8 control;
+
+ if (old_termios && !pl2303_termios_change(&tty->termios, old_termios))
+ return;
+
+ buf = kzalloc(7, GFP_KERNEL);
+ if (!buf) {
+ /* Report back no change occurred */
+ if (old_termios)
+ tty->termios = *old_termios;
+ return;
+ }
+
+ pl2303_get_line_request(port, buf);
+
+ buf[6] = tty_get_char_size(tty->termios.c_cflag);
+ dev_dbg(&port->dev, "data bits = %d\n", buf[6]);
+
+ /* For reference buf[0]:buf[3] baud rate value */
+ pl2303_encode_baud_rate(tty, port, &buf[0]);
+
+ /* For reference buf[4]=0 is 1 stop bits */
+ /* For reference buf[4]=1 is 1.5 stop bits */
+ /* For reference buf[4]=2 is 2 stop bits */
+ if (C_CSTOPB(tty)) {
+ /*
+ * NOTE: Comply with "real" UARTs / RS232:
+ * use 1.5 instead of 2 stop bits with 5 data bits
+ */
+ if (C_CSIZE(tty) == CS5) {
+ buf[4] = 1;
+ dev_dbg(&port->dev, "stop bits = 1.5\n");
+ } else {
+ buf[4] = 2;
+ dev_dbg(&port->dev, "stop bits = 2\n");
+ }
+ } else {
+ buf[4] = 0;
+ dev_dbg(&port->dev, "stop bits = 1\n");
+ }
+
+ if (C_PARENB(tty)) {
+ /* For reference buf[5]=0 is none parity */
+ /* For reference buf[5]=1 is odd parity */
+ /* For reference buf[5]=2 is even parity */
+ /* For reference buf[5]=3 is mark parity */
+ /* For reference buf[5]=4 is space parity */
+ if (C_PARODD(tty)) {
+ if (C_CMSPAR(tty)) {
+ buf[5] = 3;
+ dev_dbg(&port->dev, "parity = mark\n");
+ } else {
+ buf[5] = 1;
+ dev_dbg(&port->dev, "parity = odd\n");
+ }
+ } else {
+ if (C_CMSPAR(tty)) {
+ buf[5] = 4;
+ dev_dbg(&port->dev, "parity = space\n");
+ } else {
+ buf[5] = 2;
+ dev_dbg(&port->dev, "parity = even\n");
+ }
+ }
+ } else {
+ buf[5] = 0;
+ dev_dbg(&port->dev, "parity = none\n");
+ }
+
+ /*
+ * Some PL2303 are known to lose bytes if you change serial settings
+ * even to the same values as before. Thus we actually need to filter
+ * in this specific case.
+ *
+ * Note that the tty_termios_hw_change check above is not sufficient
+ * as a previously requested baud rate may differ from the one
+ * actually used (and stored in old_termios).
+ *
+ * NOTE: No additional locking needed for line_settings as it is
+ * only used in set_termios, which is serialised against itself.
+ */
+ if (!old_termios || memcmp(buf, priv->line_settings, 7)) {
+ ret = pl2303_set_line_request(port, buf);
+ if (!ret)
+ memcpy(priv->line_settings, buf, 7);
+ }
+
+ /* change control lines if we are switching to or from B0 */
+ spin_lock_irqsave(&priv->lock, flags);
+ control = priv->line_control;
+ if (C_BAUD(tty) == B0)
+ priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS);
+ else if (old_termios && (old_termios->c_cflag & CBAUD) == B0)
+ priv->line_control |= (CONTROL_DTR | CONTROL_RTS);
+ if (control != priv->line_control) {
+ control = priv->line_control;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ pl2303_set_control_lines(port, control);
+ } else {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ }
+
+ if (C_CRTSCTS(tty)) {
+ if (spriv->quirks & PL2303_QUIRK_LEGACY) {
+ pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x40);
+ } else if (spriv->type == &pl2303_type_data[TYPE_HXN]) {
+ pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG,
+ PL2303_HXN_FLOWCTRL_MASK,
+ PL2303_HXN_FLOWCTRL_RTS_CTS);
+ } else {
+ pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60);
+ }
+ } else if (pl2303_enable_xonxoff(tty, spriv->type)) {
+ if (spriv->type == &pl2303_type_data[TYPE_HXN]) {
+ pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG,
+ PL2303_HXN_FLOWCTRL_MASK,
+ PL2303_HXN_FLOWCTRL_XON_XOFF);
+ } else {
+ pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0xc0);
+ }
+ } else {
+ if (spriv->type == &pl2303_type_data[TYPE_HXN]) {
+ pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG,
+ PL2303_HXN_FLOWCTRL_MASK,
+ PL2303_HXN_FLOWCTRL_NONE);
+ } else {
+ pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0);
+ }
+ }
+
+ kfree(buf);
+}
+
+static void pl2303_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ u8 control;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (on)
+ priv->line_control |= (CONTROL_DTR | CONTROL_RTS);
+ else
+ priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS);
+ control = priv->line_control;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ pl2303_set_control_lines(port, control);
+}
+
+static void pl2303_close(struct usb_serial_port *port)
+{
+ usb_serial_generic_close(port);
+ usb_kill_urb(port->interrupt_in_urb);
+ pl2303_set_break(port, false);
+}
+
+static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
+ int result;
+
+ if (spriv->quirks & PL2303_QUIRK_LEGACY) {
+ usb_clear_halt(serial->dev, port->write_urb->pipe);
+ usb_clear_halt(serial->dev, port->read_urb->pipe);
+ } else {
+ /* reset upstream data pipes */
+ if (spriv->type == &pl2303_type_data[TYPE_HXN]) {
+ pl2303_vendor_write(serial, PL2303_HXN_RESET_REG,
+ PL2303_HXN_RESET_UPSTREAM_PIPE |
+ PL2303_HXN_RESET_DOWNSTREAM_PIPE);
+ } else {
+ pl2303_vendor_write(serial, 8, 0);
+ pl2303_vendor_write(serial, 9, 0);
+ }
+ }
+
+ /* Setup termios */
+ if (tty)
+ pl2303_set_termios(tty, port, NULL);
+
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (result) {
+ dev_err(&port->dev, "failed to submit interrupt urb: %d\n",
+ result);
+ return result;
+ }
+
+ result = usb_serial_generic_open(tty, port);
+ if (result) {
+ usb_kill_urb(port->interrupt_in_urb);
+ return result;
+ }
+
+ return 0;
+}
+
+static int pl2303_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ u8 control;
+ int ret;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (set & TIOCM_RTS)
+ priv->line_control |= CONTROL_RTS;
+ if (set & TIOCM_DTR)
+ priv->line_control |= CONTROL_DTR;
+ if (clear & TIOCM_RTS)
+ priv->line_control &= ~CONTROL_RTS;
+ if (clear & TIOCM_DTR)
+ priv->line_control &= ~CONTROL_DTR;
+ control = priv->line_control;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ ret = pl2303_set_control_lines(port, control);
+ if (ret)
+ return usb_translate_errors(ret);
+
+ return 0;
+}
+
+static int pl2303_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ unsigned int mcr;
+ unsigned int status;
+ unsigned int result;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ mcr = priv->line_control;
+ status = priv->line_status;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ result = ((mcr & CONTROL_DTR) ? TIOCM_DTR : 0)
+ | ((mcr & CONTROL_RTS) ? TIOCM_RTS : 0)
+ | ((status & UART_CTS) ? TIOCM_CTS : 0)
+ | ((status & UART_DSR) ? TIOCM_DSR : 0)
+ | ((status & UART_RING) ? TIOCM_RI : 0)
+ | ((status & UART_DCD) ? TIOCM_CD : 0);
+
+ dev_dbg(&port->dev, "%s - result = %x\n", __func__, result);
+
+ return result;
+}
+
+static int pl2303_carrier_raised(struct usb_serial_port *port)
+{
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+
+ if (priv->line_status & UART_DCD)
+ return 1;
+
+ return 0;
+}
+
+static int pl2303_set_break(struct usb_serial_port *port, bool enable)
+{
+ struct usb_serial *serial = port->serial;
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
+ u16 state;
+ int result;
+
+ if (spriv->quirks & PL2303_QUIRK_NO_BREAK_GETLINE)
+ return -ENOTTY;
+
+ if (enable)
+ state = BREAK_ON;
+ else
+ state = BREAK_OFF;
+
+ dev_dbg(&port->dev, "%s - turning break %s\n", __func__,
+ state == BREAK_OFF ? "off" : "on");
+
+ result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ BREAK_REQUEST, BREAK_REQUEST_TYPE, state,
+ 0, NULL, 0, 100);
+ if (result) {
+ dev_err(&port->dev, "error sending break = %d\n", result);
+ return result;
+ }
+
+ return 0;
+}
+
+static int pl2303_break_ctl(struct tty_struct *tty, int state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ return pl2303_set_break(port, state);
+}
+
+static void pl2303_update_line_status(struct usb_serial_port *port,
+ unsigned char *data,
+ unsigned int actual_length)
+{
+ struct usb_serial *serial = port->serial;
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+ struct tty_struct *tty;
+ unsigned long flags;
+ unsigned int status_idx = UART_STATE_INDEX;
+ u8 status;
+ u8 delta;
+
+ if (spriv->quirks & PL2303_QUIRK_UART_STATE_IDX0)
+ status_idx = 0;
+
+ if (actual_length < status_idx + 1)
+ return;
+
+ status = data[status_idx];
+
+ /* Save off the uart status for others to look at */
+ spin_lock_irqsave(&priv->lock, flags);
+ delta = priv->line_status ^ status;
+ priv->line_status = status;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (status & UART_BREAK_ERROR)
+ usb_serial_handle_break(port);
+
+ if (delta & UART_STATE_MSR_MASK) {
+ if (delta & UART_CTS)
+ port->icount.cts++;
+ if (delta & UART_DSR)
+ port->icount.dsr++;
+ if (delta & UART_RING)
+ port->icount.rng++;
+ if (delta & UART_DCD) {
+ port->icount.dcd++;
+ tty = tty_port_tty_get(&port->port);
+ if (tty) {
+ usb_serial_handle_dcd_change(port, tty,
+ status & UART_DCD);
+ tty_kref_put(tty);
+ }
+ }
+
+ wake_up_interruptible(&port->port.delta_msr_wait);
+ }
+}
+
+static void pl2303_read_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ unsigned int actual_length = urb->actual_length;
+ int status = urb->status;
+ int retval;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ goto exit;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__,
+ urb->actual_length, urb->transfer_buffer);
+
+ pl2303_update_line_status(port, data, actual_length);
+
+exit:
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval) {
+ dev_err(&port->dev,
+ "%s - usb_submit_urb failed with result %d\n",
+ __func__, retval);
+ }
+}
+
+static void pl2303_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+ unsigned char *data = urb->transfer_buffer;
+ char tty_flag = TTY_NORMAL;
+ unsigned long flags;
+ u8 line_status;
+ int i;
+
+ /* update line status */
+ spin_lock_irqsave(&priv->lock, flags);
+ line_status = priv->line_status;
+ priv->line_status &= ~UART_STATE_TRANSIENT_MASK;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (!urb->actual_length)
+ return;
+
+ /*
+ * Break takes precedence over parity, which takes precedence over
+ * framing errors.
+ */
+ if (line_status & UART_BREAK_ERROR)
+ tty_flag = TTY_BREAK;
+ else if (line_status & UART_PARITY_ERROR)
+ tty_flag = TTY_PARITY;
+ else if (line_status & UART_FRAME_ERROR)
+ tty_flag = TTY_FRAME;
+
+ if (tty_flag != TTY_NORMAL)
+ dev_dbg(&port->dev, "%s - tty_flag = %d\n", __func__,
+ tty_flag);
+ /* overrun is special, not associated with a char */
+ if (line_status & UART_OVERRUN_ERROR)
+ tty_insert_flip_char(&port->port, 0, TTY_OVERRUN);
+
+ if (port->sysrq) {
+ for (i = 0; i < urb->actual_length; ++i)
+ if (!usb_serial_handle_sysrq_char(port, data[i]))
+ tty_insert_flip_char(&port->port, data[i],
+ tty_flag);
+ } else {
+ tty_insert_flip_string_fixed_flag(&port->port, data, tty_flag,
+ urb->actual_length);
+ }
+
+ tty_flip_buffer_push(&port->port);
+}
+
+static struct usb_serial_driver pl2303_device = {
+ .driver = {
+ .name = "pl2303",
+ },
+ .id_table = id_table,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ .num_interrupt_in = 0, /* see pl2303_calc_num_ports */
+ .bulk_in_size = 256,
+ .bulk_out_size = 256,
+ .open = pl2303_open,
+ .close = pl2303_close,
+ .dtr_rts = pl2303_dtr_rts,
+ .carrier_raised = pl2303_carrier_raised,
+ .break_ctl = pl2303_break_ctl,
+ .set_termios = pl2303_set_termios,
+ .tiocmget = pl2303_tiocmget,
+ .tiocmset = pl2303_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .process_read_urb = pl2303_process_read_urb,
+ .read_int_callback = pl2303_read_int_callback,
+ .probe = pl2303_probe,
+ .calc_num_ports = pl2303_calc_num_ports,
+ .attach = pl2303_startup,
+ .release = pl2303_release,
+ .port_probe = pl2303_port_probe,
+ .port_remove = pl2303_port_remove,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &pl2303_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION("Prolific PL2303 USB to serial adaptor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h
new file mode 100644
index 000000000..d60eda7f6
--- /dev/null
+++ b/drivers/usb/serial/pl2303.h
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Prolific PL2303 USB to serial adaptor driver header file
+ */
+
+#define BENQ_VENDOR_ID 0x04a5
+#define BENQ_PRODUCT_ID_S81 0x4027
+
+#define PL2303_VENDOR_ID 0x067b
+#define PL2303_PRODUCT_ID 0x2303
+#define PL2303_PRODUCT_ID_TB 0x2304
+#define PL2303_PRODUCT_ID_GC 0x23a3
+#define PL2303_PRODUCT_ID_GB 0x23b3
+#define PL2303_PRODUCT_ID_GT 0x23c3
+#define PL2303_PRODUCT_ID_GL 0x23d3
+#define PL2303_PRODUCT_ID_GE 0x23e3
+#define PL2303_PRODUCT_ID_GS 0x23f3
+#define PL2303_PRODUCT_ID_RSAQ2 0x04bb
+#define PL2303_PRODUCT_ID_DCU11 0x1234
+#define PL2303_PRODUCT_ID_PHAROS 0xaaa0
+#define PL2303_PRODUCT_ID_RSAQ3 0xaaa2
+#define PL2303_PRODUCT_ID_CHILITAG 0xaaa8
+#define PL2303_PRODUCT_ID_ALDIGA 0x0611
+#define PL2303_PRODUCT_ID_MMX 0x0612
+#define PL2303_PRODUCT_ID_GPRS 0x0609
+#define PL2303_PRODUCT_ID_HCR331 0x331a
+#define PL2303_PRODUCT_ID_MOTOROLA 0x0307
+#define PL2303_PRODUCT_ID_ZTEK 0xe1f1
+
+
+#define ATEN_VENDOR_ID 0x0557
+#define ATEN_VENDOR_ID2 0x0547
+#define ATEN_PRODUCT_ID 0x2008
+#define ATEN_PRODUCT_UC485 0x2021
+#define ATEN_PRODUCT_UC232B 0x2022
+#define ATEN_PRODUCT_ID2 0x2118
+
+#define IBM_VENDOR_ID 0x04b3
+#define IBM_PRODUCT_ID 0x4016
+
+#define IODATA_VENDOR_ID 0x04bb
+#define IODATA_PRODUCT_ID 0x0a03
+#define IODATA_PRODUCT_ID_RSAQ5 0x0a0e
+
+#define ELCOM_VENDOR_ID 0x056e
+#define ELCOM_PRODUCT_ID 0x5003
+#define ELCOM_PRODUCT_ID_UCSGT 0x5004
+
+#define ITEGNO_VENDOR_ID 0x0eba
+#define ITEGNO_PRODUCT_ID 0x1080
+#define ITEGNO_PRODUCT_ID_2080 0x2080
+
+#define MA620_VENDOR_ID 0x0df7
+#define MA620_PRODUCT_ID 0x0620
+
+#define RATOC_VENDOR_ID 0x0584
+#define RATOC_PRODUCT_ID 0xb000
+
+#define TRIPP_VENDOR_ID 0x2478
+#define TRIPP_PRODUCT_ID 0x2008
+
+#define RADIOSHACK_VENDOR_ID 0x1453
+#define RADIOSHACK_PRODUCT_ID 0x4026
+
+#define DCU10_VENDOR_ID 0x0731
+#define DCU10_PRODUCT_ID 0x0528
+
+#define SITECOM_VENDOR_ID 0x6189
+#define SITECOM_PRODUCT_ID 0x2068
+
+/* Alcatel OT535/735 USB cable */
+#define ALCATEL_VENDOR_ID 0x11f7
+#define ALCATEL_PRODUCT_ID 0x02df
+
+#define SIEMENS_VENDOR_ID 0x11f5
+#define SIEMENS_PRODUCT_ID_SX1 0x0001
+#define SIEMENS_PRODUCT_ID_X65 0x0003
+#define SIEMENS_PRODUCT_ID_X75 0x0004
+#define SIEMENS_PRODUCT_ID_EF81 0x0005
+
+#define SYNTECH_VENDOR_ID 0x0745
+#define SYNTECH_PRODUCT_ID 0x0001
+
+/* Nokia CA-42 Cable */
+#define NOKIA_CA42_VENDOR_ID 0x078b
+#define NOKIA_CA42_PRODUCT_ID 0x1234
+
+/* CA-42 CLONE Cable www.ca-42.com chipset: Prolific Technology Inc */
+#define CA_42_CA42_VENDOR_ID 0x10b5
+#define CA_42_CA42_PRODUCT_ID 0xac70
+
+#define SAGEM_VENDOR_ID 0x079b
+#define SAGEM_PRODUCT_ID 0x0027
+
+/* Leadtek GPS 9531 (ID 0413:2101) */
+#define LEADTEK_VENDOR_ID 0x0413
+#define LEADTEK_9531_PRODUCT_ID 0x2101
+
+/* USB GSM cable from Speed Dragon Multimedia, Ltd */
+#define SPEEDDRAGON_VENDOR_ID 0x0e55
+#define SPEEDDRAGON_PRODUCT_ID 0x110b
+
+/* DATAPILOT Universal-2 Phone Cable */
+#define DATAPILOT_U2_VENDOR_ID 0x0731
+#define DATAPILOT_U2_PRODUCT_ID 0x2003
+
+/* Belkin "F5U257" Serial Adapter */
+#define BELKIN_VENDOR_ID 0x050d
+#define BELKIN_PRODUCT_ID 0x0257
+
+/* Alcor Micro Corp. USB 2.0 TO RS-232 */
+#define ALCOR_VENDOR_ID 0x058F
+#define ALCOR_PRODUCT_ID 0x9720
+
+/* Willcom WS002IN Data Driver (by NetIndex Inc.) */
+#define WS002IN_VENDOR_ID 0x11f6
+#define WS002IN_PRODUCT_ID 0x2001
+
+/* Corega CG-USBRS232R Serial Adapter */
+#define COREGA_VENDOR_ID 0x07aa
+#define COREGA_PRODUCT_ID 0x002a
+
+/* Y.C. Cable U.S.A., Inc - USB to RS-232 */
+#define YCCABLE_VENDOR_ID 0x05ad
+#define YCCABLE_PRODUCT_ID 0x0fba
+
+/* "Superial" USB - Serial */
+#define SUPERIAL_VENDOR_ID 0x5372
+#define SUPERIAL_PRODUCT_ID 0x2303
+
+/* Hewlett-Packard POS Pole Displays */
+#define HP_VENDOR_ID 0x03f0
+#define HP_LD381GC_PRODUCT_ID 0x0183
+#define HP_LM920_PRODUCT_ID 0x026b
+#define HP_TD620_PRODUCT_ID 0x0956
+#define HP_LD960_PRODUCT_ID 0x0b39
+#define HP_LD381_PRODUCT_ID 0x0f7f
+#define HP_LM930_PRODUCT_ID 0x0f9b
+#define HP_LCM220_PRODUCT_ID 0x3139
+#define HP_LCM960_PRODUCT_ID 0x3239
+#define HP_LD220_PRODUCT_ID 0x3524
+#define HP_LD220TA_PRODUCT_ID 0x4349
+#define HP_LD960TA_PRODUCT_ID 0x4439
+#define HP_LM940_PRODUCT_ID 0x5039
+
+/* Cressi Edy (diving computer) PC interface */
+#define CRESSI_VENDOR_ID 0x04b8
+#define CRESSI_EDY_PRODUCT_ID 0x0521
+
+/* Zeagle dive computer interface */
+#define ZEAGLE_VENDOR_ID 0x04b8
+#define ZEAGLE_N2ITION3_PRODUCT_ID 0x0522
+
+/* Sony, USB data cable for CMD-Jxx mobile phones */
+#define SONY_VENDOR_ID 0x054c
+#define SONY_QN3USB_PRODUCT_ID 0x0437
+
+/* Sanwa KB-USB2 multimeter cable (ID: 11ad:0001) */
+#define SANWA_VENDOR_ID 0x11ad
+#define SANWA_PRODUCT_ID 0x0001
+
+/* ADLINK ND-6530 RS232,RS485 and RS422 adapter */
+#define ADLINK_VENDOR_ID 0x0b63
+#define ADLINK_ND6530_PRODUCT_ID 0x6530
+#define ADLINK_ND6530GC_PRODUCT_ID 0x653a
+
+/* SMART USB Serial Adapter */
+#define SMART_VENDOR_ID 0x0b8c
+#define SMART_PRODUCT_ID 0x2303
+
+/* Allied Telesis VT-Kit3 */
+#define AT_VENDOR_ID 0x0caa
+#define AT_VTKIT3_PRODUCT_ID 0x3001
+
+/* Macrosilicon MS3020 */
+#define MACROSILICON_VENDOR_ID 0x345f
+#define MACROSILICON_MS3020_PRODUCT_ID 0x3020
diff --git a/drivers/usb/serial/qcaux.c b/drivers/usb/serial/qcaux.c
new file mode 100644
index 000000000..485ec5b07
--- /dev/null
+++ b/drivers/usb/serial/qcaux.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Qualcomm USB Auxiliary Serial Port driver
+ *
+ * Copyright (C) 2008 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2010 Dan Williams <dcbw@redhat.com>
+ *
+ * Devices listed here usually provide a CDC ACM port on which normal modem
+ * AT commands and PPP can be used. But when that port is in-use by PPP it
+ * cannot be used simultaneously for status or signal strength. Instead, the
+ * ports here can be queried for that information using the Qualcomm DM
+ * protocol.
+ */
+
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+/* NOTE: for now, only use this driver for devices that provide a CDC-ACM port
+ * for normal AT commands, but also provide secondary USB interfaces for the
+ * QCDM-capable ports. Devices that do not provide a CDC-ACM port should
+ * probably be driven by option.ko.
+ */
+
+/* UTStarcom/Pantech/Curitel devices */
+#define UTSTARCOM_VENDOR_ID 0x106c
+#define UTSTARCOM_PRODUCT_PC5740 0x3701
+#define UTSTARCOM_PRODUCT_PC5750 0x3702 /* aka Pantech PX-500 */
+#define UTSTARCOM_PRODUCT_UM150 0x3711
+#define UTSTARCOM_PRODUCT_UM175_V1 0x3712
+#define UTSTARCOM_PRODUCT_UM175_V2 0x3714
+#define UTSTARCOM_PRODUCT_UM175_ALLTEL 0x3715
+
+/* CMOTECH devices */
+#define CMOTECH_VENDOR_ID 0x16d8
+#define CMOTECH_PRODUCT_CDU550 0x5553
+#define CMOTECH_PRODUCT_CDX650 0x6512
+
+/* LG devices */
+#define LG_VENDOR_ID 0x1004
+#define LG_PRODUCT_VX4400_6000 0x6000 /* VX4400/VX6000/Rumor */
+
+/* Sanyo devices */
+#define SANYO_VENDOR_ID 0x0474
+#define SANYO_PRODUCT_KATANA_LX 0x0754 /* SCP-3800 (Katana LX) */
+
+/* Samsung devices */
+#define SAMSUNG_VENDOR_ID 0x04e8
+#define SAMSUNG_PRODUCT_U520 0x6640 /* SCH-U520 */
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, UTSTARCOM_PRODUCT_PC5740, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, UTSTARCOM_PRODUCT_PC5750, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, UTSTARCOM_PRODUCT_UM150, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, UTSTARCOM_PRODUCT_UM175_V1, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, UTSTARCOM_PRODUCT_UM175_V2, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, UTSTARCOM_PRODUCT_UM175_ALLTEL, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CDU550, 0xff, 0xff, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CDX650, 0xff, 0xff, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(LG_VENDOR_ID, LG_PRODUCT_VX4400_6000, 0xff, 0xff, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(SANYO_VENDOR_ID, SANYO_PRODUCT_KATANA_LX, 0xff, 0xff, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(SAMSUNG_VENDOR_ID, SAMSUNG_PRODUCT_U520, 0xff, 0x00, 0x00) },
+ { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xfd, 0xff) }, /* NMEA */
+ { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xfe, 0xff) }, /* WMC */
+ { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xff, 0xff) }, /* DIAG */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x1fac, 0x0151, 0xff, 0xff, 0xff) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_serial_driver qcaux_device = {
+ .driver = {
+ .name = "qcaux",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &qcaux_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+MODULE_DESCRIPTION("Qualcomm USB Auxiliary Serial Port driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c
new file mode 100644
index 000000000..105c0a0ea
--- /dev/null
+++ b/drivers/usb/serial/qcserial.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Qualcomm Serial USB driver
+ *
+ * Copyright (c) 2008 QUALCOMM Incorporated.
+ * Copyright (c) 2009 Greg Kroah-Hartman <gregkh@suse.de>
+ * Copyright (c) 2009 Novell Inc.
+ */
+
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/slab.h>
+#include "usb-wwan.h"
+
+#define DRIVER_AUTHOR "Qualcomm Inc"
+#define DRIVER_DESC "Qualcomm USB Serial driver"
+
+#define QUECTEL_EC20_PID 0x9215
+
+/* standard device layouts supported by this driver */
+enum qcserial_layouts {
+ QCSERIAL_G2K = 0, /* Gobi 2000 */
+ QCSERIAL_G1K = 1, /* Gobi 1000 */
+ QCSERIAL_SWI = 2, /* Sierra Wireless */
+ QCSERIAL_HWI = 3, /* Huawei */
+};
+
+#define DEVICE_G1K(v, p) \
+ USB_DEVICE(v, p), .driver_info = QCSERIAL_G1K
+#define DEVICE_SWI(v, p) \
+ USB_DEVICE(v, p), .driver_info = QCSERIAL_SWI
+#define DEVICE_HWI(v, p) \
+ USB_DEVICE(v, p), .driver_info = QCSERIAL_HWI
+
+static const struct usb_device_id id_table[] = {
+ /* Gobi 1000 devices */
+ {DEVICE_G1K(0x05c6, 0x9211)}, /* Acer Gobi QDL device */
+ {DEVICE_G1K(0x05c6, 0x9212)}, /* Acer Gobi Modem Device */
+ {DEVICE_G1K(0x03f0, 0x1f1d)}, /* HP un2400 Gobi Modem Device */
+ {DEVICE_G1K(0x03f0, 0x201d)}, /* HP un2400 Gobi QDL Device */
+ {DEVICE_G1K(0x04da, 0x250d)}, /* Panasonic Gobi Modem device */
+ {DEVICE_G1K(0x04da, 0x250c)}, /* Panasonic Gobi QDL device */
+ {DEVICE_G1K(0x413c, 0x8172)}, /* Dell Gobi Modem device */
+ {DEVICE_G1K(0x413c, 0x8171)}, /* Dell Gobi QDL device */
+ {DEVICE_G1K(0x1410, 0xa001)}, /* Novatel/Verizon USB-1000 */
+ {DEVICE_G1K(0x1410, 0xa002)}, /* Novatel Gobi Modem device */
+ {DEVICE_G1K(0x1410, 0xa003)}, /* Novatel Gobi Modem device */
+ {DEVICE_G1K(0x1410, 0xa004)}, /* Novatel Gobi Modem device */
+ {DEVICE_G1K(0x1410, 0xa005)}, /* Novatel Gobi Modem device */
+ {DEVICE_G1K(0x1410, 0xa006)}, /* Novatel Gobi Modem device */
+ {DEVICE_G1K(0x1410, 0xa007)}, /* Novatel Gobi Modem device */
+ {DEVICE_G1K(0x1410, 0xa008)}, /* Novatel Gobi QDL device */
+ {DEVICE_G1K(0x0b05, 0x1776)}, /* Asus Gobi Modem device */
+ {DEVICE_G1K(0x0b05, 0x1774)}, /* Asus Gobi QDL device */
+ {DEVICE_G1K(0x19d2, 0xfff3)}, /* ONDA Gobi Modem device */
+ {DEVICE_G1K(0x19d2, 0xfff2)}, /* ONDA Gobi QDL device */
+ {DEVICE_G1K(0x1557, 0x0a80)}, /* OQO Gobi QDL device */
+ {DEVICE_G1K(0x05c6, 0x9001)}, /* Generic Gobi Modem device */
+ {DEVICE_G1K(0x05c6, 0x9002)}, /* Generic Gobi Modem device */
+ {DEVICE_G1K(0x05c6, 0x9202)}, /* Generic Gobi Modem device */
+ {DEVICE_G1K(0x05c6, 0x9203)}, /* Generic Gobi Modem device */
+ {DEVICE_G1K(0x05c6, 0x9222)}, /* Generic Gobi Modem device */
+ {DEVICE_G1K(0x05c6, 0x9008)}, /* Generic Gobi QDL device */
+ {DEVICE_G1K(0x05c6, 0x9009)}, /* Generic Gobi Modem device */
+ {DEVICE_G1K(0x05c6, 0x9201)}, /* Generic Gobi QDL device */
+ {DEVICE_G1K(0x05c6, 0x9221)}, /* Generic Gobi QDL device */
+ {DEVICE_G1K(0x05c6, 0x9231)}, /* Generic Gobi QDL device */
+ {DEVICE_G1K(0x1f45, 0x0001)}, /* Unknown Gobi QDL device */
+ {DEVICE_G1K(0x1bc7, 0x900e)}, /* Telit Gobi QDL device */
+
+ /* Gobi 2000 devices */
+ {USB_DEVICE(0x1410, 0xa010)}, /* Novatel Gobi 2000 QDL device */
+ {USB_DEVICE(0x1410, 0xa011)}, /* Novatel Gobi 2000 QDL device */
+ {USB_DEVICE(0x1410, 0xa012)}, /* Novatel Gobi 2000 QDL device */
+ {USB_DEVICE(0x1410, 0xa013)}, /* Novatel Gobi 2000 QDL device */
+ {USB_DEVICE(0x1410, 0xa014)}, /* Novatel Gobi 2000 QDL device */
+ {USB_DEVICE(0x413c, 0x8185)}, /* Dell Gobi 2000 QDL device (N0218, VU936) */
+ {USB_DEVICE(0x413c, 0x8186)}, /* Dell Gobi 2000 Modem device (N0218, VU936) */
+ {USB_DEVICE(0x05c6, 0x9208)}, /* Generic Gobi 2000 QDL device */
+ {USB_DEVICE(0x05c6, 0x920b)}, /* Generic Gobi 2000 Modem device */
+ {USB_DEVICE(0x05c6, 0x9224)}, /* Sony Gobi 2000 QDL device (N0279, VU730) */
+ {USB_DEVICE(0x05c6, 0x9225)}, /* Sony Gobi 2000 Modem device (N0279, VU730) */
+ {USB_DEVICE(0x05c6, 0x9244)}, /* Samsung Gobi 2000 QDL device (VL176) */
+ {USB_DEVICE(0x05c6, 0x9245)}, /* Samsung Gobi 2000 Modem device (VL176) */
+ {USB_DEVICE(0x03f0, 0x241d)}, /* HP Gobi 2000 QDL device (VP412) */
+ {USB_DEVICE(0x03f0, 0x251d)}, /* HP Gobi 2000 Modem device (VP412) */
+ {USB_DEVICE(0x05c6, 0x9214)}, /* Acer Gobi 2000 QDL device (VP413) */
+ {USB_DEVICE(0x05c6, 0x9215)}, /* Acer Gobi 2000 Modem device (VP413) */
+ {USB_DEVICE(0x05c6, 0x9264)}, /* Asus Gobi 2000 QDL device (VR305) */
+ {USB_DEVICE(0x05c6, 0x9265)}, /* Asus Gobi 2000 Modem device (VR305) */
+ {USB_DEVICE(0x05c6, 0x9234)}, /* Top Global Gobi 2000 QDL device (VR306) */
+ {USB_DEVICE(0x05c6, 0x9235)}, /* Top Global Gobi 2000 Modem device (VR306) */
+ {USB_DEVICE(0x05c6, 0x9274)}, /* iRex Technologies Gobi 2000 QDL device (VR307) */
+ {USB_DEVICE(0x05c6, 0x9275)}, /* iRex Technologies Gobi 2000 Modem device (VR307) */
+ {USB_DEVICE(0x1199, 0x9000)}, /* Sierra Wireless Gobi 2000 QDL device (VT773) */
+ {USB_DEVICE(0x1199, 0x9001)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {USB_DEVICE(0x1199, 0x9002)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {USB_DEVICE(0x1199, 0x9003)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {USB_DEVICE(0x1199, 0x9004)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {USB_DEVICE(0x1199, 0x9005)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {USB_DEVICE(0x1199, 0x9006)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {USB_DEVICE(0x1199, 0x9007)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {USB_DEVICE(0x1199, 0x9008)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {USB_DEVICE(0x1199, 0x9009)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {USB_DEVICE(0x1199, 0x900a)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {USB_DEVICE(0x1199, 0x9011)}, /* Sierra Wireless Gobi 2000 Modem device (MC8305) */
+ {USB_DEVICE(0x16d8, 0x8001)}, /* CMDTech Gobi 2000 QDL device (VU922) */
+ {USB_DEVICE(0x16d8, 0x8002)}, /* CMDTech Gobi 2000 Modem device (VU922) */
+ {USB_DEVICE(0x05c6, 0x9204)}, /* Gobi 2000 QDL device */
+ {USB_DEVICE(0x05c6, 0x9205)}, /* Gobi 2000 Modem device */
+
+ /* Gobi 3000 devices */
+ {USB_DEVICE(0x03f0, 0x371d)}, /* HP un2430 Gobi 3000 QDL */
+ {USB_DEVICE(0x05c6, 0x920c)}, /* Gobi 3000 QDL */
+ {USB_DEVICE(0x05c6, 0x920d)}, /* Gobi 3000 Composite */
+ {USB_DEVICE(0x1410, 0xa020)}, /* Novatel Gobi 3000 QDL */
+ {USB_DEVICE(0x1410, 0xa021)}, /* Novatel Gobi 3000 Composite */
+ {USB_DEVICE(0x413c, 0x8193)}, /* Dell Gobi 3000 QDL */
+ {USB_DEVICE(0x413c, 0x8194)}, /* Dell Gobi 3000 Composite */
+ {USB_DEVICE(0x413c, 0x81a6)}, /* Dell DW5570 QDL (MC8805) */
+ {USB_DEVICE(0x1199, 0x68a4)}, /* Sierra Wireless QDL */
+ {USB_DEVICE(0x1199, 0x68a5)}, /* Sierra Wireless Modem */
+ {USB_DEVICE(0x1199, 0x68a8)}, /* Sierra Wireless QDL */
+ {USB_DEVICE(0x1199, 0x68a9)}, /* Sierra Wireless Modem */
+ {USB_DEVICE(0x1199, 0x9010)}, /* Sierra Wireless Gobi 3000 QDL */
+ {USB_DEVICE(0x1199, 0x9012)}, /* Sierra Wireless Gobi 3000 QDL */
+ {USB_DEVICE(0x1199, 0x9013)}, /* Sierra Wireless Gobi 3000 Modem device (MC8355) */
+ {USB_DEVICE(0x1199, 0x9014)}, /* Sierra Wireless Gobi 3000 QDL */
+ {USB_DEVICE(0x1199, 0x9015)}, /* Sierra Wireless Gobi 3000 Modem device */
+ {USB_DEVICE(0x1199, 0x9018)}, /* Sierra Wireless Gobi 3000 QDL */
+ {USB_DEVICE(0x1199, 0x9019)}, /* Sierra Wireless Gobi 3000 Modem device */
+ {USB_DEVICE(0x1199, 0x901b)}, /* Sierra Wireless MC7770 */
+ {USB_DEVICE(0x12D1, 0x14F0)}, /* Sony Gobi 3000 QDL */
+ {USB_DEVICE(0x12D1, 0x14F1)}, /* Sony Gobi 3000 Composite */
+ {USB_DEVICE(0x0AF0, 0x8120)}, /* Option GTM681W */
+
+ /* non-Gobi Sierra Wireless devices */
+ {DEVICE_SWI(0x03f0, 0x4e1d)}, /* HP lt4111 LTE/EV-DO/HSPA+ Gobi 4G Module */
+ {DEVICE_SWI(0x0f3d, 0x68a2)}, /* Sierra Wireless MC7700 */
+ {DEVICE_SWI(0x114f, 0x68a2)}, /* Sierra Wireless MC7750 */
+ {DEVICE_SWI(0x1199, 0x68a2)}, /* Sierra Wireless MC7710 */
+ {DEVICE_SWI(0x1199, 0x68c0)}, /* Sierra Wireless MC7304/MC7354 */
+ {DEVICE_SWI(0x1199, 0x901c)}, /* Sierra Wireless EM7700 */
+ {DEVICE_SWI(0x1199, 0x901e)}, /* Sierra Wireless EM7355 QDL */
+ {DEVICE_SWI(0x1199, 0x901f)}, /* Sierra Wireless EM7355 */
+ {DEVICE_SWI(0x1199, 0x9040)}, /* Sierra Wireless Modem */
+ {DEVICE_SWI(0x1199, 0x9041)}, /* Sierra Wireless MC7305/MC7355 */
+ {DEVICE_SWI(0x1199, 0x9051)}, /* Netgear AirCard 340U */
+ {DEVICE_SWI(0x1199, 0x9053)}, /* Sierra Wireless Modem */
+ {DEVICE_SWI(0x1199, 0x9054)}, /* Sierra Wireless Modem */
+ {DEVICE_SWI(0x1199, 0x9055)}, /* Netgear AirCard 341U */
+ {DEVICE_SWI(0x1199, 0x9056)}, /* Sierra Wireless Modem */
+ {DEVICE_SWI(0x1199, 0x9060)}, /* Sierra Wireless Modem */
+ {DEVICE_SWI(0x1199, 0x9061)}, /* Sierra Wireless Modem */
+ {DEVICE_SWI(0x1199, 0x9062)}, /* Sierra Wireless EM7305 QDL */
+ {DEVICE_SWI(0x1199, 0x9063)}, /* Sierra Wireless EM7305 */
+ {DEVICE_SWI(0x1199, 0x9070)}, /* Sierra Wireless MC74xx */
+ {DEVICE_SWI(0x1199, 0x9071)}, /* Sierra Wireless MC74xx */
+ {DEVICE_SWI(0x1199, 0x9078)}, /* Sierra Wireless EM74xx */
+ {DEVICE_SWI(0x1199, 0x9079)}, /* Sierra Wireless EM74xx */
+ {DEVICE_SWI(0x1199, 0x907a)}, /* Sierra Wireless EM74xx QDL */
+ {DEVICE_SWI(0x1199, 0x907b)}, /* Sierra Wireless EM74xx */
+ {DEVICE_SWI(0x1199, 0x9090)}, /* Sierra Wireless EM7565 QDL */
+ {DEVICE_SWI(0x1199, 0x9091)}, /* Sierra Wireless EM7565 */
+ {DEVICE_SWI(0x1199, 0x90d2)}, /* Sierra Wireless EM9191 QDL */
+ {DEVICE_SWI(0x1199, 0x90e4)}, /* Sierra Wireless EM86xx QDL*/
+ {DEVICE_SWI(0x1199, 0x90e5)}, /* Sierra Wireless EM86xx */
+ {DEVICE_SWI(0x1199, 0xc080)}, /* Sierra Wireless EM7590 QDL */
+ {DEVICE_SWI(0x1199, 0xc081)}, /* Sierra Wireless EM7590 */
+ {DEVICE_SWI(0x413c, 0x81a2)}, /* Dell Wireless 5806 Gobi(TM) 4G LTE Mobile Broadband Card */
+ {DEVICE_SWI(0x413c, 0x81a3)}, /* Dell Wireless 5570 HSPA+ (42Mbps) Mobile Broadband Card */
+ {DEVICE_SWI(0x413c, 0x81a4)}, /* Dell Wireless 5570e HSPA+ (42Mbps) Mobile Broadband Card */
+ {DEVICE_SWI(0x413c, 0x81a8)}, /* Dell Wireless 5808 Gobi(TM) 4G LTE Mobile Broadband Card */
+ {DEVICE_SWI(0x413c, 0x81a9)}, /* Dell Wireless 5808e Gobi(TM) 4G LTE Mobile Broadband Card */
+ {DEVICE_SWI(0x413c, 0x81b1)}, /* Dell Wireless 5809e Gobi(TM) 4G LTE Mobile Broadband Card */
+ {DEVICE_SWI(0x413c, 0x81b3)}, /* Dell Wireless 5809e Gobi(TM) 4G LTE Mobile Broadband Card (rev3) */
+ {DEVICE_SWI(0x413c, 0x81b5)}, /* Dell Wireless 5811e QDL */
+ {DEVICE_SWI(0x413c, 0x81b6)}, /* Dell Wireless 5811e QDL */
+ {DEVICE_SWI(0x413c, 0x81c2)}, /* Dell Wireless 5811e */
+ {DEVICE_SWI(0x413c, 0x81cb)}, /* Dell Wireless 5816e QDL */
+ {DEVICE_SWI(0x413c, 0x81cc)}, /* Dell Wireless 5816e */
+ {DEVICE_SWI(0x413c, 0x81cf)}, /* Dell Wireless 5819 */
+ {DEVICE_SWI(0x413c, 0x81d0)}, /* Dell Wireless 5819 */
+ {DEVICE_SWI(0x413c, 0x81d1)}, /* Dell Wireless 5818 */
+ {DEVICE_SWI(0x413c, 0x81d2)}, /* Dell Wireless 5818 */
+ {DEVICE_SWI(0x413c, 0x8217)}, /* Dell Wireless DW5826e */
+ {DEVICE_SWI(0x413c, 0x8218)}, /* Dell Wireless DW5826e QDL */
+
+ /* Huawei devices */
+ {DEVICE_HWI(0x03f0, 0x581d)}, /* HP lt4112 LTE/HSPA+ Gobi 4G Modem (Huawei me906e) */
+
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static int handle_quectel_ec20(struct device *dev, int ifnum)
+{
+ int altsetting = 0;
+
+ /*
+ * Quectel EC20 Mini PCIe LTE module layout:
+ * 0: DM/DIAG (use libqcdm from ModemManager for communication)
+ * 1: NMEA
+ * 2: AT-capable modem port
+ * 3: Modem interface
+ * 4: NDIS
+ */
+ switch (ifnum) {
+ case 0:
+ dev_dbg(dev, "Quectel EC20 DM/DIAG interface found\n");
+ break;
+ case 1:
+ dev_dbg(dev, "Quectel EC20 NMEA GPS interface found\n");
+ break;
+ case 2:
+ case 3:
+ dev_dbg(dev, "Quectel EC20 Modem port found\n");
+ break;
+ case 4:
+ /* Don't claim the QMI/net interface */
+ altsetting = -1;
+ break;
+ }
+
+ return altsetting;
+}
+
+static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)
+{
+ struct usb_host_interface *intf = serial->interface->cur_altsetting;
+ struct device *dev = &serial->dev->dev;
+ int retval = -ENODEV;
+ __u8 nintf;
+ __u8 ifnum;
+ int altsetting = -1;
+ bool sendsetup = false;
+
+ /* we only support vendor specific functions */
+ if (intf->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC)
+ goto done;
+
+ nintf = serial->dev->actconfig->desc.bNumInterfaces;
+ dev_dbg(dev, "Num Interfaces = %d\n", nintf);
+ ifnum = intf->desc.bInterfaceNumber;
+ dev_dbg(dev, "This Interface = %d\n", ifnum);
+
+ if (nintf == 1) {
+ /* QDL mode */
+ /* Gobi 2000 has a single altsetting, older ones have two */
+ if (serial->interface->num_altsetting == 2)
+ intf = usb_altnum_to_altsetting(serial->interface, 1);
+ else if (serial->interface->num_altsetting > 2)
+ goto done;
+
+ if (intf && intf->desc.bNumEndpoints == 2 &&
+ usb_endpoint_is_bulk_in(&intf->endpoint[0].desc) &&
+ usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {
+ dev_dbg(dev, "QDL port found\n");
+
+ if (serial->interface->num_altsetting == 1)
+ retval = 0; /* Success */
+ else
+ altsetting = 1;
+ }
+ goto done;
+
+ }
+
+ /* default to enabling interface */
+ altsetting = 0;
+
+ /*
+ * Composite mode; don't bind to the QMI/net interface as that
+ * gets handled by other drivers.
+ */
+
+ switch (id->driver_info) {
+ case QCSERIAL_G1K:
+ /*
+ * Gobi 1K USB layout:
+ * 0: DM/DIAG (use libqcdm from ModemManager for communication)
+ * 1: serial port (doesn't respond)
+ * 2: AT-capable modem port
+ * 3: QMI/net
+ */
+ if (nintf < 3 || nintf > 4) {
+ dev_err(dev, "unknown number of interfaces: %d\n", nintf);
+ altsetting = -1;
+ goto done;
+ }
+
+ if (ifnum == 0) {
+ dev_dbg(dev, "Gobi 1K DM/DIAG interface found\n");
+ altsetting = 1;
+ } else if (ifnum == 2)
+ dev_dbg(dev, "Modem port found\n");
+ else
+ altsetting = -1;
+ break;
+ case QCSERIAL_G2K:
+ /* handle non-standard layouts */
+ if (nintf == 5 && id->idProduct == QUECTEL_EC20_PID) {
+ altsetting = handle_quectel_ec20(dev, ifnum);
+ goto done;
+ }
+
+ /*
+ * Gobi 2K+ USB layout:
+ * 0: QMI/net
+ * 1: DM/DIAG (use libqcdm from ModemManager for communication)
+ * 2: AT-capable modem port
+ * 3: NMEA
+ */
+ if (nintf < 3 || nintf > 4) {
+ dev_err(dev, "unknown number of interfaces: %d\n", nintf);
+ altsetting = -1;
+ goto done;
+ }
+
+ switch (ifnum) {
+ case 0:
+ /* Don't claim the QMI/net interface */
+ altsetting = -1;
+ break;
+ case 1:
+ dev_dbg(dev, "Gobi 2K+ DM/DIAG interface found\n");
+ break;
+ case 2:
+ dev_dbg(dev, "Modem port found\n");
+ break;
+ case 3:
+ /*
+ * NMEA (serial line 9600 8N1)
+ * # echo "\$GPS_START" > /dev/ttyUSBx
+ * # echo "\$GPS_STOP" > /dev/ttyUSBx
+ */
+ dev_dbg(dev, "Gobi 2K+ NMEA GPS interface found\n");
+ break;
+ }
+ break;
+ case QCSERIAL_SWI:
+ /*
+ * Sierra Wireless layout:
+ * 0: DM/DIAG (use libqcdm from ModemManager for communication)
+ * 2: NMEA
+ * 3: AT-capable modem port
+ * 8: QMI/net
+ */
+ switch (ifnum) {
+ case 0:
+ dev_dbg(dev, "DM/DIAG interface found\n");
+ break;
+ case 2:
+ dev_dbg(dev, "NMEA GPS interface found\n");
+ sendsetup = true;
+ break;
+ case 3:
+ dev_dbg(dev, "Modem port found\n");
+ sendsetup = true;
+ break;
+ default:
+ /* don't claim any unsupported interface */
+ altsetting = -1;
+ break;
+ }
+ break;
+ case QCSERIAL_HWI:
+ /*
+ * Huawei devices map functions by subclass + protocol
+ * instead of interface numbers. The protocol identify
+ * a specific function, while the subclass indicate a
+ * specific firmware source
+ *
+ * This is a list of functions known to be non-serial. The rest
+ * are assumed to be serial and will be handled by this driver
+ */
+ switch (intf->desc.bInterfaceProtocol) {
+ /* QMI combined (qmi_wwan) */
+ case 0x07:
+ case 0x37:
+ case 0x67:
+ /* QMI data (qmi_wwan) */
+ case 0x08:
+ case 0x38:
+ case 0x68:
+ /* QMI control (qmi_wwan) */
+ case 0x09:
+ case 0x39:
+ case 0x69:
+ /* NCM like (huawei_cdc_ncm) */
+ case 0x16:
+ case 0x46:
+ case 0x76:
+ altsetting = -1;
+ break;
+ default:
+ dev_dbg(dev, "Huawei type serial port found (%02x/%02x/%02x)\n",
+ intf->desc.bInterfaceClass,
+ intf->desc.bInterfaceSubClass,
+ intf->desc.bInterfaceProtocol);
+ }
+ break;
+ default:
+ dev_err(dev, "unsupported device layout type: %lu\n",
+ id->driver_info);
+ break;
+ }
+
+done:
+ if (altsetting >= 0) {
+ retval = usb_set_interface(serial->dev, ifnum, altsetting);
+ if (retval < 0) {
+ dev_err(dev,
+ "Could not set interface, error %d\n",
+ retval);
+ retval = -ENODEV;
+ }
+ }
+
+ if (!retval)
+ usb_set_serial_data(serial, (void *)(unsigned long)sendsetup);
+
+ return retval;
+}
+
+static int qc_attach(struct usb_serial *serial)
+{
+ struct usb_wwan_intf_private *data;
+ bool sendsetup;
+
+ data = kzalloc_obj(*data);
+ if (!data)
+ return -ENOMEM;
+
+ sendsetup = !!(unsigned long)(usb_get_serial_data(serial));
+ if (sendsetup)
+ data->use_send_setup = 1;
+
+ spin_lock_init(&data->susp_lock);
+
+ usb_set_serial_data(serial, data);
+
+ return 0;
+}
+
+static void qc_release(struct usb_serial *serial)
+{
+ struct usb_wwan_intf_private *priv = usb_get_serial_data(serial);
+
+ usb_set_serial_data(serial, NULL);
+ kfree(priv);
+}
+
+static struct usb_serial_driver qcdevice = {
+ .driver = {
+ .name = "qcserial",
+ },
+ .description = "Qualcomm USB modem",
+ .id_table = id_table,
+ .num_ports = 1,
+ .probe = qcprobe,
+ .open = usb_wwan_open,
+ .close = usb_wwan_close,
+ .dtr_rts = usb_wwan_dtr_rts,
+ .write = usb_wwan_write,
+ .write_room = usb_wwan_write_room,
+ .chars_in_buffer = usb_wwan_chars_in_buffer,
+ .tiocmget = usb_wwan_tiocmget,
+ .tiocmset = usb_wwan_tiocmset,
+ .attach = qc_attach,
+ .release = qc_release,
+ .port_probe = usb_wwan_port_probe,
+ .port_remove = usb_wwan_port_remove,
+#ifdef CONFIG_PM
+ .suspend = usb_wwan_suspend,
+ .resume = usb_wwan_resume,
+#endif
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &qcdevice, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c
new file mode 100644
index 000000000..dc2b39810
--- /dev/null
+++ b/drivers/usb/serial/quatech2.c
@@ -0,0 +1,955 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * usb-serial driver for Quatech USB 2 devices
+ *
+ * Copyright (C) 2012 Bill Pemberton (wfp5p@virginia.edu)
+ *
+ * These devices all have only 1 bulk in and 1 bulk out that is shared
+ * for all serial ports.
+ *
+ */
+
+#include <linux/unaligned.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/serial.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/serial_reg.h>
+
+/* default urb timeout for usb operations */
+#define QT2_USB_TIMEOUT USB_CTRL_SET_TIMEOUT
+
+#define QT_OPEN_CLOSE_CHANNEL 0xca
+#define QT_SET_GET_DEVICE 0xc2
+#define QT_SET_GET_REGISTER 0xc0
+#define QT_GET_SET_PREBUF_TRIG_LVL 0xcc
+#define QT_SET_ATF 0xcd
+#define QT_TRANSFER_IN 0xc0
+#define QT_HW_FLOW_CONTROL_MASK 0xc5
+#define QT_SW_FLOW_CONTROL_MASK 0xc6
+#define QT2_BREAK_CONTROL 0xc8
+#define QT2_GET_SET_UART 0xc1
+#define QT2_FLUSH_DEVICE 0xc4
+#define QT2_GET_SET_QMCR 0xe1
+#define QT2_QMCR_RS232 0x40
+#define QT2_QMCR_RS422 0x10
+
+#define SERIAL_CRTSCTS ((UART_MCR_RTS << 8) | UART_MSR_CTS)
+
+#define SERIAL_EVEN_PARITY (UART_LCR_PARITY | UART_LCR_EPAR)
+
+/* status bytes for the device */
+#define QT2_CONTROL_BYTE 0x1b
+#define QT2_LINE_STATUS 0x00 /* following 1 byte is line status */
+#define QT2_MODEM_STATUS 0x01 /* following 1 byte is modem status */
+#define QT2_XMIT_HOLD 0x02 /* following 2 bytes are ?? */
+#define QT2_CHANGE_PORT 0x03 /* following 1 byte is port to change to */
+#define QT2_REC_FLUSH 0x04 /* no following info */
+#define QT2_XMIT_FLUSH 0x05 /* no following info */
+#define QT2_CONTROL_ESCAPE 0xff /* pass through previous 2 control bytes */
+
+#define MAX_BAUD_RATE 921600
+#define DEFAULT_BAUD_RATE 9600
+
+#define QT2_READ_BUFFER_SIZE 512 /* size of read buffer */
+#define QT2_WRITE_BUFFER_SIZE 512 /* size of write buffer */
+#define QT2_WRITE_CONTROL_SIZE 5 /* control bytes used for a write */
+
+#define DRIVER_DESC "Quatech 2nd gen USB to Serial Driver"
+
+#define USB_VENDOR_ID_QUATECH 0x061d
+#define QUATECH_SSU2_100 0xC120 /* RS232 single port */
+#define QUATECH_DSU2_100 0xC140 /* RS232 dual port */
+#define QUATECH_DSU2_400 0xC150 /* RS232/422/485 dual port */
+#define QUATECH_QSU2_100 0xC160 /* RS232 four port */
+#define QUATECH_QSU2_400 0xC170 /* RS232/422/485 four port */
+#define QUATECH_ESU2_100 0xC1A0 /* RS232 eight port */
+#define QUATECH_ESU2_400 0xC180 /* RS232/422/485 eight port */
+
+struct qt2_device_detail {
+ int product_id;
+ int num_ports;
+};
+
+#define QT_DETAILS(prod, ports) \
+ .product_id = (prod), \
+ .num_ports = (ports)
+
+static const struct qt2_device_detail qt2_device_details[] = {
+ {QT_DETAILS(QUATECH_SSU2_100, 1)},
+ {QT_DETAILS(QUATECH_DSU2_400, 2)},
+ {QT_DETAILS(QUATECH_DSU2_100, 2)},
+ {QT_DETAILS(QUATECH_QSU2_400, 4)},
+ {QT_DETAILS(QUATECH_QSU2_100, 4)},
+ {QT_DETAILS(QUATECH_ESU2_400, 8)},
+ {QT_DETAILS(QUATECH_ESU2_100, 8)},
+ {QT_DETAILS(0, 0)} /* Terminating entry */
+};
+
+static const struct usb_device_id id_table[] = {
+ {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_SSU2_100)},
+ {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_DSU2_100)},
+ {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_DSU2_400)},
+ {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_QSU2_100)},
+ {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_QSU2_400)},
+ {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_ESU2_100)},
+ {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_ESU2_400)},
+ {} /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+struct qt2_serial_private {
+ unsigned char current_port; /* current port for incoming data */
+
+ struct urb *read_urb; /* shared among all ports */
+ char *read_buffer;
+};
+
+struct qt2_port_private {
+ u8 device_port;
+
+ spinlock_t urb_lock;
+ bool urb_in_use;
+ struct urb *write_urb;
+ char *write_buffer;
+
+ spinlock_t lock;
+ u8 shadowLSR;
+ u8 shadowMSR;
+
+ struct usb_serial_port *port;
+};
+
+static void qt2_update_lsr(struct usb_serial_port *port, unsigned char *ch);
+static void qt2_update_msr(struct usb_serial_port *port, unsigned char *ch);
+static void qt2_write_bulk_callback(struct urb *urb);
+static void qt2_read_bulk_callback(struct urb *urb);
+
+static void qt2_release(struct usb_serial *serial)
+{
+ struct qt2_serial_private *serial_priv;
+
+ serial_priv = usb_get_serial_data(serial);
+
+ usb_kill_urb(serial_priv->read_urb);
+ usb_free_urb(serial_priv->read_urb);
+ kfree(serial_priv->read_buffer);
+ kfree(serial_priv);
+}
+
+static inline int calc_baud_divisor(int baudrate)
+{
+ int divisor, rem;
+
+ divisor = MAX_BAUD_RATE / baudrate;
+ rem = MAX_BAUD_RATE % baudrate;
+ /* Round to nearest divisor */
+ if (((rem * 2) >= baudrate) && (baudrate != 110))
+ divisor++;
+
+ return divisor;
+}
+
+static inline int qt2_set_port_config(struct usb_device *dev,
+ unsigned char port_number,
+ u16 baudrate, u16 lcr)
+{
+ int divisor = calc_baud_divisor(baudrate);
+ u16 index = ((u16) (lcr << 8) | (u16) (port_number));
+
+ return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ QT2_GET_SET_UART, 0x40,
+ divisor, index, NULL, 0, QT2_USB_TIMEOUT);
+}
+
+static inline int qt2_control_msg(struct usb_device *dev,
+ u8 request, u16 data, u16 index)
+{
+ return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ request, 0x40, data, index,
+ NULL, 0, QT2_USB_TIMEOUT);
+}
+
+static inline int qt2_getregister(struct usb_device *dev,
+ u8 uart,
+ u8 reg,
+ u8 *data)
+{
+ int ret;
+
+ ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+ QT_SET_GET_REGISTER, 0xc0, reg,
+ uart, data, sizeof(*data), QT2_USB_TIMEOUT);
+ if (ret < (int)sizeof(*data)) {
+ if (ret >= 0)
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static inline int qt2_setregister(struct usb_device *dev,
+ u8 uart, u8 reg, u16 data)
+{
+ u16 value = (data << 8) | reg;
+
+ return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ QT_SET_GET_REGISTER, 0x40, value, uart,
+ NULL, 0, QT2_USB_TIMEOUT);
+}
+
+static inline int update_mctrl(struct qt2_port_private *port_priv,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = port_priv->port;
+ struct usb_device *dev = port->serial->dev;
+ unsigned urb_value;
+ int status;
+
+ if (((set | clear) & (TIOCM_DTR | TIOCM_RTS)) == 0) {
+ dev_dbg(&port->dev,
+ "update_mctrl - DTR|RTS not being set|cleared\n");
+ return 0; /* no change */
+ }
+
+ clear &= ~set; /* 'set' takes precedence over 'clear' */
+ urb_value = 0;
+ if (set & TIOCM_DTR)
+ urb_value |= UART_MCR_DTR;
+ if (set & TIOCM_RTS)
+ urb_value |= UART_MCR_RTS;
+
+ status = qt2_setregister(dev, port_priv->device_port, UART_MCR,
+ urb_value);
+ if (status < 0)
+ dev_err(&port->dev,
+ "update_mctrl - Error from MODEM_CTRL urb: %i\n",
+ status);
+ return status;
+}
+
+static int qt2_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ struct qt2_device_detail d;
+ int i;
+
+ for (i = 0; d = qt2_device_details[i], d.product_id != 0; i++) {
+ if (d.product_id == le16_to_cpu(serial->dev->descriptor.idProduct))
+ return d.num_ports;
+ }
+
+ /* we didn't recognize the device */
+ dev_err(&serial->dev->dev,
+ "don't know the number of ports, assuming 1\n");
+
+ return 1;
+}
+
+static void qt2_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct usb_device *dev = port->serial->dev;
+ struct qt2_port_private *port_priv;
+ struct ktermios *termios = &tty->termios;
+ u16 baud;
+ unsigned int cflag = termios->c_cflag;
+ u16 new_lcr = 0;
+ int status;
+
+ port_priv = usb_get_serial_port_data(port);
+
+ if (cflag & PARENB) {
+ if (cflag & PARODD)
+ new_lcr |= UART_LCR_PARITY;
+ else
+ new_lcr |= SERIAL_EVEN_PARITY;
+ }
+
+ new_lcr |= UART_LCR_WLEN(tty_get_char_size(cflag));
+
+ baud = tty_get_baud_rate(tty);
+ if (!baud)
+ baud = 9600;
+
+ status = qt2_set_port_config(dev, port_priv->device_port, baud,
+ new_lcr);
+ if (status < 0)
+ dev_err(&port->dev, "%s - qt2_set_port_config failed: %i\n",
+ __func__, status);
+
+ if (cflag & CRTSCTS)
+ status = qt2_control_msg(dev, QT_HW_FLOW_CONTROL_MASK,
+ SERIAL_CRTSCTS,
+ port_priv->device_port);
+ else
+ status = qt2_control_msg(dev, QT_HW_FLOW_CONTROL_MASK,
+ 0, port_priv->device_port);
+ if (status < 0)
+ dev_err(&port->dev, "%s - set HW flow control failed: %i\n",
+ __func__, status);
+
+ if (I_IXOFF(tty) || I_IXON(tty)) {
+ u16 x = ((u16) (START_CHAR(tty) << 8) | (u16) (STOP_CHAR(tty)));
+
+ status = qt2_control_msg(dev, QT_SW_FLOW_CONTROL_MASK,
+ x, port_priv->device_port);
+ } else
+ status = qt2_control_msg(dev, QT_SW_FLOW_CONTROL_MASK,
+ 0, port_priv->device_port);
+
+ if (status < 0)
+ dev_err(&port->dev, "%s - set SW flow control failed: %i\n",
+ __func__, status);
+
+}
+
+static int qt2_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct usb_serial *serial;
+ struct qt2_port_private *port_priv;
+ u8 *data;
+ u16 device_port;
+ int status;
+ unsigned long flags;
+
+ device_port = port->port_number;
+
+ serial = port->serial;
+
+ port_priv = usb_get_serial_port_data(port);
+
+ /* set the port to RS232 mode */
+ status = qt2_control_msg(serial->dev, QT2_GET_SET_QMCR,
+ QT2_QMCR_RS232, device_port);
+ if (status < 0) {
+ dev_err(&port->dev,
+ "%s failed to set RS232 mode for port %i error %i\n",
+ __func__, device_port, status);
+ return status;
+ }
+
+ data = kzalloc(2, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ /* open the port */
+ status = usb_control_msg(serial->dev,
+ usb_rcvctrlpipe(serial->dev, 0),
+ QT_OPEN_CLOSE_CHANNEL,
+ 0xc0, 0,
+ device_port, data, 2, QT2_USB_TIMEOUT);
+
+ if (status < 2) {
+ dev_err(&port->dev, "%s - open port failed %i\n", __func__,
+ status);
+ if (status >= 0)
+ status = -EIO;
+ kfree(data);
+ return status;
+ }
+
+ spin_lock_irqsave(&port_priv->lock, flags);
+ port_priv->shadowLSR = data[0];
+ port_priv->shadowMSR = data[1];
+ spin_unlock_irqrestore(&port_priv->lock, flags);
+
+ kfree(data);
+
+ /* set to default speed and 8bit word size */
+ status = qt2_set_port_config(serial->dev, device_port,
+ DEFAULT_BAUD_RATE, UART_LCR_WLEN8);
+ if (status < 0) {
+ dev_err(&port->dev, "%s - initial setup failed (%i)\n",
+ __func__, device_port);
+ return status;
+ }
+
+ port_priv->device_port = (u8) device_port;
+
+ if (tty)
+ qt2_set_termios(tty, port, &tty->termios);
+
+ return 0;
+
+}
+
+static void qt2_close(struct usb_serial_port *port)
+{
+ struct usb_serial *serial;
+ struct qt2_port_private *port_priv;
+ int i;
+
+ serial = port->serial;
+ port_priv = usb_get_serial_port_data(port);
+
+ usb_kill_urb(port_priv->write_urb);
+
+ /* flush the port transmit buffer */
+ i = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ QT2_FLUSH_DEVICE, 0x40, 1,
+ port_priv->device_port, NULL, 0, QT2_USB_TIMEOUT);
+
+ if (i < 0)
+ dev_err(&port->dev, "%s - transmit buffer flush failed: %i\n",
+ __func__, i);
+
+ /* flush the port receive buffer */
+ i = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ QT2_FLUSH_DEVICE, 0x40, 0,
+ port_priv->device_port, NULL, 0, QT2_USB_TIMEOUT);
+
+ if (i < 0)
+ dev_err(&port->dev, "%s - receive buffer flush failed: %i\n",
+ __func__, i);
+
+ /* close the port */
+ i = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ QT_OPEN_CLOSE_CHANNEL,
+ 0x40, 0,
+ port_priv->device_port, NULL, 0, QT2_USB_TIMEOUT);
+
+ if (i < 0)
+ dev_err(&port->dev, "%s - close port failed %i\n",
+ __func__, i);
+}
+
+static void qt2_disconnect(struct usb_serial *serial)
+{
+ struct qt2_serial_private *serial_priv = usb_get_serial_data(serial);
+
+ usb_kill_urb(serial_priv->read_urb);
+}
+
+static void qt2_process_status(struct usb_serial_port *port, unsigned char *ch)
+{
+ switch (*ch) {
+ case QT2_LINE_STATUS:
+ qt2_update_lsr(port, ch + 1);
+ break;
+ case QT2_MODEM_STATUS:
+ qt2_update_msr(port, ch + 1);
+ break;
+ }
+}
+
+static void qt2_process_read_urb(struct urb *urb)
+{
+ struct usb_serial *serial;
+ struct qt2_serial_private *serial_priv;
+ struct usb_serial_port *port;
+ bool escapeflag;
+ unsigned char *ch;
+ int i;
+ unsigned char newport;
+ int len = urb->actual_length;
+
+ if (!len)
+ return;
+
+ ch = urb->transfer_buffer;
+ serial = urb->context;
+ serial_priv = usb_get_serial_data(serial);
+ port = serial->port[serial_priv->current_port];
+
+ for (i = 0; i < urb->actual_length; i++) {
+ ch = (unsigned char *)urb->transfer_buffer + i;
+ if ((i <= (len - 3)) &&
+ (*ch == QT2_CONTROL_BYTE) &&
+ (*(ch + 1) == QT2_CONTROL_BYTE)) {
+ escapeflag = false;
+ switch (*(ch + 2)) {
+ case QT2_LINE_STATUS:
+ case QT2_MODEM_STATUS:
+ if (i > (len - 4)) {
+ dev_warn(&port->dev,
+ "%s - status message too short\n",
+ __func__);
+ break;
+ }
+ qt2_process_status(port, ch + 2);
+ i += 3;
+ escapeflag = true;
+ break;
+ case QT2_XMIT_HOLD:
+ if (i > (len - 5)) {
+ dev_warn(&port->dev,
+ "%s - xmit_empty message too short\n",
+ __func__);
+ break;
+ }
+ /* bytes_written = (ch[1] << 4) + ch[0]; */
+ i += 4;
+ escapeflag = true;
+ break;
+ case QT2_CHANGE_PORT:
+ if (i > (len - 4)) {
+ dev_warn(&port->dev,
+ "%s - change_port message too short\n",
+ __func__);
+ break;
+ }
+ tty_flip_buffer_push(&port->port);
+
+ newport = *(ch + 3);
+
+ if (newport >= serial->num_ports) {
+ dev_err(&port->dev,
+ "%s - port change to invalid port: %i\n",
+ __func__, newport);
+ break;
+ }
+
+ serial_priv->current_port = newport;
+ port = serial->port[serial_priv->current_port];
+ i += 3;
+ escapeflag = true;
+ break;
+ case QT2_REC_FLUSH:
+ case QT2_XMIT_FLUSH:
+ i += 2;
+ escapeflag = true;
+ break;
+ case QT2_CONTROL_ESCAPE:
+ tty_insert_flip_string(&port->port, ch, 2);
+ i += 2;
+ escapeflag = true;
+ break;
+ default:
+ dev_warn(&port->dev,
+ "%s - unsupported command %i\n",
+ __func__, *(ch + 2));
+ break;
+ }
+ if (escapeflag)
+ continue;
+ }
+
+ tty_insert_flip_char(&port->port, *ch, TTY_NORMAL);
+ }
+
+ tty_flip_buffer_push(&port->port);
+}
+
+static void qt2_write_bulk_callback(struct urb *urb)
+{
+ struct usb_serial_port *port;
+ struct qt2_port_private *port_priv;
+ unsigned long flags;
+
+ port = urb->context;
+ port_priv = usb_get_serial_port_data(port);
+
+ spin_lock_irqsave(&port_priv->urb_lock, flags);
+
+ port_priv->urb_in_use = false;
+ usb_serial_port_softint(port);
+
+ spin_unlock_irqrestore(&port_priv->urb_lock, flags);
+
+}
+
+static void qt2_read_bulk_callback(struct urb *urb)
+{
+ struct usb_serial *serial = urb->context;
+ int status;
+
+ if (urb->status) {
+ dev_warn(&serial->dev->dev,
+ "%s - non-zero urb status: %i\n", __func__,
+ urb->status);
+ return;
+ }
+
+ qt2_process_read_urb(urb);
+
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status != 0)
+ dev_err(&serial->dev->dev,
+ "%s - resubmit read urb failed: %i\n",
+ __func__, status);
+}
+
+static int qt2_setup_urbs(struct usb_serial *serial)
+{
+ struct usb_serial_port *port0;
+ struct qt2_serial_private *serial_priv;
+ int status;
+
+ port0 = serial->port[0];
+
+ serial_priv = usb_get_serial_data(serial);
+ serial_priv->read_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!serial_priv->read_urb)
+ return -ENOMEM;
+
+ usb_fill_bulk_urb(serial_priv->read_urb, serial->dev,
+ usb_rcvbulkpipe(serial->dev,
+ port0->bulk_in_endpointAddress),
+ serial_priv->read_buffer,
+ QT2_READ_BUFFER_SIZE,
+ qt2_read_bulk_callback, serial);
+
+ status = usb_submit_urb(serial_priv->read_urb, GFP_KERNEL);
+ if (status != 0) {
+ dev_err(&serial->dev->dev,
+ "%s - submit read urb failed %i\n", __func__, status);
+ usb_free_urb(serial_priv->read_urb);
+ return status;
+ }
+
+ return 0;
+}
+
+static int qt2_attach(struct usb_serial *serial)
+{
+ struct qt2_serial_private *serial_priv;
+ int status;
+
+ /* power on unit */
+ status = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ 0xc2, 0x40, 0x8000, 0, NULL, 0,
+ QT2_USB_TIMEOUT);
+ if (status < 0) {
+ dev_err(&serial->dev->dev,
+ "%s - failed to power on unit: %i\n", __func__, status);
+ return status;
+ }
+
+ serial_priv = kzalloc_obj(*serial_priv);
+ if (!serial_priv)
+ return -ENOMEM;
+
+ serial_priv->read_buffer = kmalloc(QT2_READ_BUFFER_SIZE, GFP_KERNEL);
+ if (!serial_priv->read_buffer) {
+ status = -ENOMEM;
+ goto err_buf;
+ }
+
+ usb_set_serial_data(serial, serial_priv);
+
+ status = qt2_setup_urbs(serial);
+ if (status != 0)
+ goto attach_failed;
+
+ return 0;
+
+attach_failed:
+ kfree(serial_priv->read_buffer);
+err_buf:
+ kfree(serial_priv);
+ return status;
+}
+
+static int qt2_port_probe(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct qt2_port_private *port_priv;
+ u8 bEndpointAddress;
+
+ port_priv = kzalloc_obj(*port_priv);
+ if (!port_priv)
+ return -ENOMEM;
+
+ spin_lock_init(&port_priv->lock);
+ spin_lock_init(&port_priv->urb_lock);
+ port_priv->port = port;
+
+ port_priv->write_buffer = kmalloc(QT2_WRITE_BUFFER_SIZE, GFP_KERNEL);
+ if (!port_priv->write_buffer)
+ goto err_buf;
+
+ port_priv->write_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!port_priv->write_urb)
+ goto err_urb;
+
+ bEndpointAddress = serial->port[0]->bulk_out_endpointAddress;
+ usb_fill_bulk_urb(port_priv->write_urb, serial->dev,
+ usb_sndbulkpipe(serial->dev, bEndpointAddress),
+ port_priv->write_buffer,
+ QT2_WRITE_BUFFER_SIZE,
+ qt2_write_bulk_callback, port);
+
+ usb_set_serial_port_data(port, port_priv);
+
+ return 0;
+err_urb:
+ kfree(port_priv->write_buffer);
+err_buf:
+ kfree(port_priv);
+ return -ENOMEM;
+}
+
+static void qt2_port_remove(struct usb_serial_port *port)
+{
+ struct qt2_port_private *port_priv;
+
+ port_priv = usb_get_serial_port_data(port);
+ usb_free_urb(port_priv->write_urb);
+ kfree(port_priv->write_buffer);
+ kfree(port_priv);
+}
+
+static int qt2_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_device *dev = port->serial->dev;
+ struct qt2_port_private *port_priv = usb_get_serial_port_data(port);
+ u8 *d;
+ int r;
+
+ d = kzalloc(2, GFP_KERNEL);
+ if (!d)
+ return -ENOMEM;
+
+ r = qt2_getregister(dev, port_priv->device_port, UART_MCR, d);
+ if (r < 0)
+ goto mget_out;
+
+ r = qt2_getregister(dev, port_priv->device_port, UART_MSR, d + 1);
+ if (r < 0)
+ goto mget_out;
+
+ r = (d[0] & UART_MCR_DTR ? TIOCM_DTR : 0) |
+ (d[0] & UART_MCR_RTS ? TIOCM_RTS : 0) |
+ (d[1] & UART_MSR_CTS ? TIOCM_CTS : 0) |
+ (d[1] & UART_MSR_DCD ? TIOCM_CAR : 0) |
+ (d[1] & UART_MSR_RI ? TIOCM_RI : 0) |
+ (d[1] & UART_MSR_DSR ? TIOCM_DSR : 0);
+
+mget_out:
+ kfree(d);
+ return r;
+}
+
+static int qt2_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct qt2_port_private *port_priv;
+
+ port_priv = usb_get_serial_port_data(tty->driver_data);
+ return update_mctrl(port_priv, set, clear);
+}
+
+static int qt2_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct qt2_port_private *port_priv;
+ int status;
+ u16 val;
+
+ port_priv = usb_get_serial_port_data(port);
+
+ val = (break_state == -1) ? 1 : 0;
+
+ status = qt2_control_msg(port->serial->dev, QT2_BREAK_CONTROL,
+ val, port_priv->device_port);
+ if (status < 0) {
+ dev_warn(&port->dev,
+ "%s - failed to send control message: %i\n", __func__,
+ status);
+ return status;
+ }
+
+ return 0;
+}
+
+
+
+static void qt2_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct usb_device *dev = port->serial->dev;
+ struct qt2_port_private *port_priv = usb_get_serial_port_data(port);
+
+ /* Disable flow control */
+ if (!on) {
+ if (qt2_setregister(dev, port_priv->device_port,
+ UART_MCR, 0) < 0)
+ dev_warn(&port->dev, "error from flowcontrol urb\n");
+ }
+ /* drop RTS and DTR */
+ if (on)
+ update_mctrl(port_priv, TIOCM_DTR | TIOCM_RTS, 0);
+ else
+ update_mctrl(port_priv, 0, TIOCM_DTR | TIOCM_RTS);
+}
+
+static void qt2_update_msr(struct usb_serial_port *port, unsigned char *ch)
+{
+ struct qt2_port_private *port_priv;
+ u8 newMSR = (u8) *ch;
+ unsigned long flags;
+
+ /* May be called from qt2_process_read_urb() for an unbound port. */
+ port_priv = usb_get_serial_port_data(port);
+ if (!port_priv)
+ return;
+
+ spin_lock_irqsave(&port_priv->lock, flags);
+ port_priv->shadowMSR = newMSR;
+ spin_unlock_irqrestore(&port_priv->lock, flags);
+
+ if (newMSR & UART_MSR_ANY_DELTA) {
+ /* update input line counters */
+ if (newMSR & UART_MSR_DCTS)
+ port->icount.cts++;
+ if (newMSR & UART_MSR_DDSR)
+ port->icount.dsr++;
+ if (newMSR & UART_MSR_DDCD)
+ port->icount.dcd++;
+ if (newMSR & UART_MSR_TERI)
+ port->icount.rng++;
+
+ wake_up_interruptible(&port->port.delta_msr_wait);
+ }
+}
+
+static void qt2_update_lsr(struct usb_serial_port *port, unsigned char *ch)
+{
+ struct qt2_port_private *port_priv;
+ struct async_icount *icount;
+ unsigned long flags;
+ u8 newLSR = (u8) *ch;
+
+ /* May be called from qt2_process_read_urb() for an unbound port. */
+ port_priv = usb_get_serial_port_data(port);
+ if (!port_priv)
+ return;
+
+ if (newLSR & UART_LSR_BI)
+ newLSR &= (u8) (UART_LSR_OE | UART_LSR_BI);
+
+ spin_lock_irqsave(&port_priv->lock, flags);
+ port_priv->shadowLSR = newLSR;
+ spin_unlock_irqrestore(&port_priv->lock, flags);
+
+ icount = &port->icount;
+
+ if (newLSR & UART_LSR_BRK_ERROR_BITS) {
+
+ if (newLSR & UART_LSR_BI)
+ icount->brk++;
+
+ if (newLSR & UART_LSR_OE)
+ icount->overrun++;
+
+ if (newLSR & UART_LSR_PE)
+ icount->parity++;
+
+ if (newLSR & UART_LSR_FE)
+ icount->frame++;
+ }
+
+}
+
+static unsigned int qt2_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct qt2_port_private *port_priv;
+ unsigned long flags;
+ unsigned int r;
+
+ port_priv = usb_get_serial_port_data(port);
+
+ spin_lock_irqsave(&port_priv->urb_lock, flags);
+
+ if (port_priv->urb_in_use)
+ r = 0;
+ else
+ r = QT2_WRITE_BUFFER_SIZE - QT2_WRITE_CONTROL_SIZE;
+
+ spin_unlock_irqrestore(&port_priv->urb_lock, flags);
+
+ return r;
+}
+
+static int qt2_write(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const unsigned char *buf, int count)
+{
+ struct qt2_port_private *port_priv;
+ struct urb *write_urb;
+ unsigned char *data;
+ unsigned long flags;
+ int status;
+ int bytes_out = 0;
+
+ port_priv = usb_get_serial_port_data(port);
+
+ if (port_priv->write_urb == NULL) {
+ dev_err(&port->dev, "%s - no output urb\n", __func__);
+ return 0;
+ }
+ write_urb = port_priv->write_urb;
+
+ count = min(count, QT2_WRITE_BUFFER_SIZE - QT2_WRITE_CONTROL_SIZE);
+
+ data = write_urb->transfer_buffer;
+ spin_lock_irqsave(&port_priv->urb_lock, flags);
+ if (port_priv->urb_in_use) {
+ dev_err(&port->dev, "qt2_write - urb is in use\n");
+ goto write_out;
+ }
+
+ *data++ = QT2_CONTROL_BYTE;
+ *data++ = QT2_CONTROL_BYTE;
+ *data++ = port_priv->device_port;
+ put_unaligned_le16(count, data);
+ data += 2;
+ memcpy(data, buf, count);
+
+ write_urb->transfer_buffer_length = count + QT2_WRITE_CONTROL_SIZE;
+
+ status = usb_submit_urb(write_urb, GFP_ATOMIC);
+ if (status == 0) {
+ port_priv->urb_in_use = true;
+ bytes_out += count;
+ }
+
+write_out:
+ spin_unlock_irqrestore(&port_priv->urb_lock, flags);
+ return bytes_out;
+}
+
+
+static struct usb_serial_driver qt2_device = {
+ .driver = {
+ .name = "quatech-serial",
+ },
+ .description = DRIVER_DESC,
+ .id_table = id_table,
+ .open = qt2_open,
+ .close = qt2_close,
+ .write = qt2_write,
+ .write_room = qt2_write_room,
+ .calc_num_ports = qt2_calc_num_ports,
+ .attach = qt2_attach,
+ .release = qt2_release,
+ .disconnect = qt2_disconnect,
+ .port_probe = qt2_port_probe,
+ .port_remove = qt2_port_remove,
+ .dtr_rts = qt2_dtr_rts,
+ .break_ctl = qt2_break_ctl,
+ .tiocmget = qt2_tiocmget,
+ .tiocmset = qt2_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .set_termios = qt2_set_termios,
+};
+
+static struct usb_serial_driver *const serial_drivers[] = {
+ &qt2_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/safe_serial.c b/drivers/usb/serial/safe_serial.c
new file mode 100644
index 000000000..55e59e341
--- /dev/null
+++ b/drivers/usb/serial/safe_serial.c
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Safe Encapsulated USB Serial Driver
+ *
+ * Copyright (C) 2010 Johan Hovold <jhovold@gmail.com>
+ * Copyright (C) 2001 Lineo
+ * Copyright (C) 2001 Hewlett-Packard
+ *
+ * By:
+ * Stuart Lynne <sl@lineo.com>, Tom Rushworth <tbr@lineo.com>
+ */
+
+/*
+ * The encapsultaion is designed to overcome difficulties with some USB
+ * hardware.
+ *
+ * While the USB protocol has a CRC over the data while in transit, i.e. while
+ * being carried over the bus, there is no end to end protection. If the
+ * hardware has any problems getting the data into or out of the USB transmit
+ * and receive FIFO's then data can be lost.
+ *
+ * This protocol adds a two byte trailer to each USB packet to specify the
+ * number of bytes of valid data and a 10 bit CRC that will allow the receiver
+ * to verify that the entire USB packet was received without error.
+ *
+ * Because in this case the sender and receiver are the class and function
+ * drivers there is now end to end protection.
+ *
+ * There is an additional option that can be used to force all transmitted
+ * packets to be padded to the maximum packet size. This provides a work
+ * around for some devices which have problems with small USB packets.
+ *
+ * Assuming a packetsize of N:
+ *
+ * 0..N-2 data and optional padding
+ *
+ * N-2 bits 7-2 - number of bytes of valid data
+ * bits 1-0 top two bits of 10 bit CRC
+ * N-1 bottom 8 bits of 10 bit CRC
+ *
+ *
+ * | Data Length | 10 bit CRC |
+ * + 7 . 6 . 5 . 4 . 3 . 2 . 1 . 0 | 7 . 6 . 5 . 4 . 3 . 2 . 1 . 0 +
+ *
+ * The 10 bit CRC is computed across the sent data, followed by the trailer
+ * with the length set and the CRC set to zero. The CRC is then OR'd into
+ * the trailer.
+ *
+ * When received a 10 bit CRC is computed over the entire frame including
+ * the trailer and should be equal to zero.
+ *
+ * Two module parameters are used to control the encapsulation, if both are
+ * turned of the module works as a simple serial device with NO
+ * encapsulation.
+ *
+ * See linux/drivers/usbd/serial_fd for a device function driver
+ * implementation of this.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/gfp.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+static bool safe = true;
+static bool padded = IS_ENABLED(CONFIG_USB_SERIAL_SAFE_PADDED);
+
+#define DRIVER_AUTHOR "sl@lineo.com, tbr@lineo.com, Johan Hovold <jhovold@gmail.com>"
+#define DRIVER_DESC "USB Safe Encapsulated Serial"
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+module_param(safe, bool, 0);
+MODULE_PARM_DESC(safe, "Turn Safe Encapsulation On/Off");
+
+module_param(padded, bool, 0);
+MODULE_PARM_DESC(padded, "Pad to full wMaxPacketSize On/Off");
+
+#define CDC_DEVICE_CLASS 0x02
+
+#define CDC_INTERFACE_CLASS 0x02
+#define CDC_INTERFACE_SUBCLASS 0x06
+
+#define LINEO_INTERFACE_CLASS 0xff
+
+#define LINEO_INTERFACE_SUBCLASS_SAFENET 0x01
+#define LINEO_SAFENET_CRC 0x01
+#define LINEO_SAFENET_CRC_PADDED 0x02
+
+#define LINEO_INTERFACE_SUBCLASS_SAFESERIAL 0x02
+#define LINEO_SAFESERIAL_CRC 0x01
+#define LINEO_SAFESERIAL_CRC_PADDED 0x02
+
+
+#define MY_USB_DEVICE(vend, prod, dc, ic, isc) \
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
+ USB_DEVICE_ID_MATCH_DEV_CLASS | \
+ USB_DEVICE_ID_MATCH_INT_CLASS | \
+ USB_DEVICE_ID_MATCH_INT_SUBCLASS, \
+ .idVendor = (vend), \
+ .idProduct = (prod),\
+ .bDeviceClass = (dc),\
+ .bInterfaceClass = (ic), \
+ .bInterfaceSubClass = (isc),
+
+static const struct usb_device_id id_table[] = {
+ {MY_USB_DEVICE(0x49f, 0xffff, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Itsy */
+ {MY_USB_DEVICE(0x3f0, 0x2101, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Calypso */
+ {MY_USB_DEVICE(0x4dd, 0x8001, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Iris */
+ {MY_USB_DEVICE(0x4dd, 0x8002, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Collie */
+ {MY_USB_DEVICE(0x4dd, 0x8003, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Collie */
+ {MY_USB_DEVICE(0x4dd, 0x8004, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Collie */
+ {MY_USB_DEVICE(0x5f9, 0xffff, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Sharp tmp */
+ {} /* terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static const __u16 crc10_table[256] = {
+ 0x000, 0x233, 0x255, 0x066, 0x299, 0x0aa, 0x0cc, 0x2ff,
+ 0x301, 0x132, 0x154, 0x367, 0x198, 0x3ab, 0x3cd, 0x1fe,
+ 0x031, 0x202, 0x264, 0x057, 0x2a8, 0x09b, 0x0fd, 0x2ce,
+ 0x330, 0x103, 0x165, 0x356, 0x1a9, 0x39a, 0x3fc, 0x1cf,
+ 0x062, 0x251, 0x237, 0x004, 0x2fb, 0x0c8, 0x0ae, 0x29d,
+ 0x363, 0x150, 0x136, 0x305, 0x1fa, 0x3c9, 0x3af, 0x19c,
+ 0x053, 0x260, 0x206, 0x035, 0x2ca, 0x0f9, 0x09f, 0x2ac,
+ 0x352, 0x161, 0x107, 0x334, 0x1cb, 0x3f8, 0x39e, 0x1ad,
+ 0x0c4, 0x2f7, 0x291, 0x0a2, 0x25d, 0x06e, 0x008, 0x23b,
+ 0x3c5, 0x1f6, 0x190, 0x3a3, 0x15c, 0x36f, 0x309, 0x13a,
+ 0x0f5, 0x2c6, 0x2a0, 0x093, 0x26c, 0x05f, 0x039, 0x20a,
+ 0x3f4, 0x1c7, 0x1a1, 0x392, 0x16d, 0x35e, 0x338, 0x10b,
+ 0x0a6, 0x295, 0x2f3, 0x0c0, 0x23f, 0x00c, 0x06a, 0x259,
+ 0x3a7, 0x194, 0x1f2, 0x3c1, 0x13e, 0x30d, 0x36b, 0x158,
+ 0x097, 0x2a4, 0x2c2, 0x0f1, 0x20e, 0x03d, 0x05b, 0x268,
+ 0x396, 0x1a5, 0x1c3, 0x3f0, 0x10f, 0x33c, 0x35a, 0x169,
+ 0x188, 0x3bb, 0x3dd, 0x1ee, 0x311, 0x122, 0x144, 0x377,
+ 0x289, 0x0ba, 0x0dc, 0x2ef, 0x010, 0x223, 0x245, 0x076,
+ 0x1b9, 0x38a, 0x3ec, 0x1df, 0x320, 0x113, 0x175, 0x346,
+ 0x2b8, 0x08b, 0x0ed, 0x2de, 0x021, 0x212, 0x274, 0x047,
+ 0x1ea, 0x3d9, 0x3bf, 0x18c, 0x373, 0x140, 0x126, 0x315,
+ 0x2eb, 0x0d8, 0x0be, 0x28d, 0x072, 0x241, 0x227, 0x014,
+ 0x1db, 0x3e8, 0x38e, 0x1bd, 0x342, 0x171, 0x117, 0x324,
+ 0x2da, 0x0e9, 0x08f, 0x2bc, 0x043, 0x270, 0x216, 0x025,
+ 0x14c, 0x37f, 0x319, 0x12a, 0x3d5, 0x1e6, 0x180, 0x3b3,
+ 0x24d, 0x07e, 0x018, 0x22b, 0x0d4, 0x2e7, 0x281, 0x0b2,
+ 0x17d, 0x34e, 0x328, 0x11b, 0x3e4, 0x1d7, 0x1b1, 0x382,
+ 0x27c, 0x04f, 0x029, 0x21a, 0x0e5, 0x2d6, 0x2b0, 0x083,
+ 0x12e, 0x31d, 0x37b, 0x148, 0x3b7, 0x184, 0x1e2, 0x3d1,
+ 0x22f, 0x01c, 0x07a, 0x249, 0x0b6, 0x285, 0x2e3, 0x0d0,
+ 0x11f, 0x32c, 0x34a, 0x179, 0x386, 0x1b5, 0x1d3, 0x3e0,
+ 0x21e, 0x02d, 0x04b, 0x278, 0x087, 0x2b4, 0x2d2, 0x0e1,
+};
+
+#define CRC10_INITFCS 0x000 /* Initial FCS value */
+#define CRC10_GOODFCS 0x000 /* Good final FCS value */
+#define CRC10_FCS(fcs, c) ((((fcs) << 8) & 0x3ff) ^ crc10_table[((fcs) >> 2) & 0xff] ^ (c))
+
+/**
+ * fcs_compute10 - memcpy and calculate 10 bit CRC across buffer
+ * @sp: pointer to buffer
+ * @len: number of bytes
+ * @fcs: starting FCS
+ *
+ * Perform a memcpy and calculate fcs using ppp 10bit CRC algorithm. Return
+ * new 10 bit FCS.
+ */
+static inline __u16 fcs_compute10(unsigned char *sp, int len, __u16 fcs)
+{
+ for (; len-- > 0; fcs = CRC10_FCS(fcs, *sp++));
+ return fcs;
+}
+
+static void safe_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ unsigned char length = urb->actual_length;
+ int actual_length;
+ __u16 fcs;
+
+ if (!length)
+ return;
+
+ if (!safe)
+ goto out;
+
+ if (length < 2) {
+ dev_err(&port->dev, "malformed packet\n");
+ return;
+ }
+
+ fcs = fcs_compute10(data, length, CRC10_INITFCS);
+ if (fcs) {
+ dev_err(&port->dev, "%s - bad CRC %x\n", __func__, fcs);
+ return;
+ }
+
+ actual_length = data[length - 2] >> 2;
+ if (actual_length > (length - 2)) {
+ dev_err(&port->dev, "%s - inconsistent lengths %d:%d\n",
+ __func__, actual_length, length);
+ return;
+ }
+ dev_info(&urb->dev->dev, "%s - actual: %d\n", __func__, actual_length);
+ length = actual_length;
+out:
+ tty_insert_flip_string(&port->port, data, length);
+ tty_flip_buffer_push(&port->port);
+}
+
+static int safe_prepare_write_buffer(struct usb_serial_port *port,
+ void *dest, size_t size)
+{
+ unsigned char *buf = dest;
+ int count;
+ int trailer_len;
+ int pkt_len;
+ __u16 fcs;
+
+ trailer_len = safe ? 2 : 0;
+
+ count = kfifo_out_locked(&port->write_fifo, buf, size - trailer_len,
+ &port->lock);
+ if (!safe)
+ return count;
+
+ /* pad if necessary */
+ if (padded) {
+ pkt_len = size;
+ memset(buf + count, '0', pkt_len - count - trailer_len);
+ } else {
+ pkt_len = count + trailer_len;
+ }
+
+ /* set count */
+ buf[pkt_len - 2] = count << 2;
+ buf[pkt_len - 1] = 0;
+
+ /* compute fcs and insert into trailer */
+ fcs = fcs_compute10(buf, pkt_len, CRC10_INITFCS);
+ buf[pkt_len - 2] |= fcs >> 8;
+ buf[pkt_len - 1] |= fcs & 0xff;
+
+ return pkt_len;
+}
+
+static int safe_startup(struct usb_serial *serial)
+{
+ struct usb_interface_descriptor *desc;
+ int bulk_out_size;
+
+ if (serial->dev->descriptor.bDeviceClass != CDC_DEVICE_CLASS)
+ return -ENODEV;
+
+ desc = &serial->interface->cur_altsetting->desc;
+
+ if (desc->bInterfaceClass != LINEO_INTERFACE_CLASS)
+ return -ENODEV;
+ if (desc->bInterfaceSubClass != LINEO_INTERFACE_SUBCLASS_SAFESERIAL)
+ return -ENODEV;
+
+ switch (desc->bInterfaceProtocol) {
+ case LINEO_SAFESERIAL_CRC:
+ break;
+ case LINEO_SAFESERIAL_CRC_PADDED:
+ padded = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * The bulk-out buffer needs to be large enough for the two-byte
+ * trailer in safe mode, but assume anything smaller than eight bytes
+ * is broken.
+ */
+ bulk_out_size = serial->port[0]->bulk_out_size;
+ if (bulk_out_size > 0 && bulk_out_size < 8)
+ return -EINVAL;
+
+ return 0;
+}
+
+static struct usb_serial_driver safe_device = {
+ .driver = {
+ .name = "safe_serial",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .process_read_urb = safe_process_read_urb,
+ .prepare_write_buffer = safe_prepare_write_buffer,
+ .attach = safe_startup,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &safe_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c
new file mode 100644
index 000000000..6e443aaca
--- /dev/null
+++ b/drivers/usb/serial/sierra.c
@@ -0,0 +1,1058 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ USB Driver for Sierra Wireless
+
+ Copyright (C) 2006, 2007, 2008 Kevin Lloyd <klloyd@sierrawireless.com>,
+
+ Copyright (C) 2008, 2009 Elina Pasheva, Matthew Safar, Rory Filer
+ <linux@sierrawireless.com>
+
+ IMPORTANT DISCLAIMER: This driver is not commercially supported by
+ Sierra Wireless. Use at your own risk.
+
+ Portions based on the option driver by Matthias Urlichs <smurf@smurf.noris.de>
+ Whom based his on the Keyspan driver by Hugh Blemings <hugh@blemings.org>
+*/
+/* Uncomment to log function calls */
+/* #define DEBUG */
+
+#define DRIVER_AUTHOR "Kevin Lloyd, Elina Pasheva, Matthew Safar, Rory Filer"
+#define DRIVER_DESC "USB Driver for Sierra Wireless USB modems"
+
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/slab.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#define SWIMS_USB_REQUEST_SetPower 0x00
+#define SWIMS_USB_REQUEST_SetNmea 0x07
+
+#define N_IN_URB_HM 8
+#define N_OUT_URB_HM 64
+#define N_IN_URB 4
+#define N_OUT_URB 4
+#define IN_BUFLEN 4096
+
+#define MAX_TRANSFER (PAGE_SIZE - 512)
+/* MAX_TRANSFER is chosen so that the VM is not stressed by
+ allocations > PAGE_SIZE and the number of packets in a page
+ is an integer 512 is the largest possible packet on EHCI */
+
+static bool nmea;
+
+struct sierra_iface_list {
+ const u8 *nums; /* array of interface numbers */
+ size_t count; /* number of elements in array */
+};
+
+struct sierra_intf_private {
+ spinlock_t susp_lock;
+ unsigned int suspended:1;
+ int in_flight;
+ unsigned int open_ports;
+};
+
+static int sierra_set_power_state(struct usb_device *udev, __u16 swiState)
+{
+ return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ SWIMS_USB_REQUEST_SetPower, /* __u8 request */
+ USB_TYPE_VENDOR, /* __u8 request type */
+ swiState, /* __u16 value */
+ 0, /* __u16 index */
+ NULL, /* void *data */
+ 0, /* __u16 size */
+ USB_CTRL_SET_TIMEOUT); /* int timeout */
+}
+
+static int sierra_vsc_set_nmea(struct usb_device *udev, __u16 enable)
+{
+ return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ SWIMS_USB_REQUEST_SetNmea, /* __u8 request */
+ USB_TYPE_VENDOR, /* __u8 request type */
+ enable, /* __u16 value */
+ 0x0000, /* __u16 index */
+ NULL, /* void *data */
+ 0, /* __u16 size */
+ USB_CTRL_SET_TIMEOUT); /* int timeout */
+}
+
+static int sierra_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ int num_ports = 0;
+ u8 ifnum, numendpoints;
+
+ ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber;
+ numendpoints = serial->interface->cur_altsetting->desc.bNumEndpoints;
+
+ /* Dummy interface present on some SKUs should be ignored */
+ if (ifnum == 0x99)
+ num_ports = 0;
+ else if (numendpoints <= 3)
+ num_ports = 1;
+ else
+ num_ports = (numendpoints-1)/2;
+ return num_ports;
+}
+
+static bool is_listed(const u8 ifnum, const struct sierra_iface_list *list)
+{
+ int i;
+
+ if (!list)
+ return false;
+
+ for (i = 0; i < list->count; i++) {
+ if (list->nums[i] == ifnum)
+ return true;
+ }
+
+ return false;
+}
+
+static u8 sierra_interface_num(struct usb_serial *serial)
+{
+ return serial->interface->cur_altsetting->desc.bInterfaceNumber;
+}
+
+static int sierra_probe(struct usb_serial *serial,
+ const struct usb_device_id *id)
+{
+ const struct sierra_iface_list *ignore_list;
+ int result = 0;
+ struct usb_device *udev;
+ u8 ifnum;
+
+ udev = serial->dev;
+ ifnum = sierra_interface_num(serial);
+
+ /*
+ * If this interface supports more than 1 alternate
+ * select the 2nd one
+ */
+ if (serial->interface->num_altsetting == 2) {
+ dev_dbg(&udev->dev, "Selecting alt setting for interface %d\n",
+ ifnum);
+ /* We know the alternate setting is 1 for the MC8785 */
+ usb_set_interface(udev, ifnum, 1);
+ }
+
+ ignore_list = (const struct sierra_iface_list *)id->driver_info;
+
+ if (is_listed(ifnum, ignore_list)) {
+ dev_dbg(&serial->dev->dev, "Ignoring interface #%d\n", ifnum);
+ return -ENODEV;
+ }
+
+ return result;
+}
+
+/* interfaces with higher memory requirements */
+static const u8 hi_memory_typeA_ifaces[] = { 0, 2 };
+static const struct sierra_iface_list typeA_interface_list = {
+ .nums = hi_memory_typeA_ifaces,
+ .count = ARRAY_SIZE(hi_memory_typeA_ifaces),
+};
+
+static const u8 hi_memory_typeB_ifaces[] = { 3, 4, 5, 6 };
+static const struct sierra_iface_list typeB_interface_list = {
+ .nums = hi_memory_typeB_ifaces,
+ .count = ARRAY_SIZE(hi_memory_typeB_ifaces),
+};
+
+/* 'ignorelist' of interfaces not served by this driver */
+static const u8 direct_ip_non_serial_ifaces[] = { 7, 8, 9, 10, 11, 19, 20 };
+static const struct sierra_iface_list direct_ip_interface_ignore = {
+ .nums = direct_ip_non_serial_ifaces,
+ .count = ARRAY_SIZE(direct_ip_non_serial_ifaces),
+};
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x0F3D, 0x0112) }, /* Airprime/Sierra PC 5220 */
+ { USB_DEVICE(0x03F0, 0x1B1D) }, /* HP ev2200 a.k.a MC5720 */
+ { USB_DEVICE(0x03F0, 0x211D) }, /* HP ev2210 a.k.a MC5725 */
+ { USB_DEVICE(0x03F0, 0x1E1D) }, /* HP hs2300 a.k.a MC8775 */
+
+ { USB_DEVICE(0x1199, 0x0017) }, /* Sierra Wireless EM5625 */
+ { USB_DEVICE(0x1199, 0x0018) }, /* Sierra Wireless MC5720 */
+ { USB_DEVICE(0x1199, 0x0218) }, /* Sierra Wireless MC5720 */
+ { USB_DEVICE(0x1199, 0x0020) }, /* Sierra Wireless MC5725 */
+ { USB_DEVICE(0x1199, 0x0220) }, /* Sierra Wireless MC5725 */
+ { USB_DEVICE(0x1199, 0x0022) }, /* Sierra Wireless EM5725 */
+ { USB_DEVICE(0x1199, 0x0024) }, /* Sierra Wireless MC5727 */
+ { USB_DEVICE(0x1199, 0x0224) }, /* Sierra Wireless MC5727 */
+ { USB_DEVICE(0x1199, 0x0019) }, /* Sierra Wireless AirCard 595 */
+ { USB_DEVICE(0x1199, 0x0021) }, /* Sierra Wireless AirCard 597E */
+ { USB_DEVICE(0x1199, 0x0112) }, /* Sierra Wireless AirCard 580 */
+ { USB_DEVICE(0x1199, 0x0120) }, /* Sierra Wireless USB Dongle 595U */
+ { USB_DEVICE(0x1199, 0x0301) }, /* Sierra Wireless USB Dongle 250U */
+ /* Sierra Wireless C597 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x0023, 0xFF, 0xFF, 0xFF) },
+ /* Sierra Wireless T598 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x0025, 0xFF, 0xFF, 0xFF) },
+ { USB_DEVICE(0x1199, 0x0026) }, /* Sierra Wireless T11 */
+ { USB_DEVICE(0x1199, 0x0027) }, /* Sierra Wireless AC402 */
+ { USB_DEVICE(0x1199, 0x0028) }, /* Sierra Wireless MC5728 */
+ { USB_DEVICE(0x1199, 0x0029) }, /* Sierra Wireless Device */
+
+ { USB_DEVICE(0x1199, 0x6802) }, /* Sierra Wireless MC8755 */
+ { USB_DEVICE(0x1199, 0x6803) }, /* Sierra Wireless MC8765 */
+ { USB_DEVICE(0x1199, 0x6804) }, /* Sierra Wireless MC8755 */
+ { USB_DEVICE(0x1199, 0x6805) }, /* Sierra Wireless MC8765 */
+ { USB_DEVICE(0x1199, 0x6808) }, /* Sierra Wireless MC8755 */
+ { USB_DEVICE(0x1199, 0x6809) }, /* Sierra Wireless MC8765 */
+ { USB_DEVICE(0x1199, 0x6812) }, /* Sierra Wireless MC8775 & AC 875U */
+ { USB_DEVICE(0x1199, 0x6813) }, /* Sierra Wireless MC8775 */
+ { USB_DEVICE(0x1199, 0x6815) }, /* Sierra Wireless MC8775 */
+ { USB_DEVICE(0x1199, 0x6816) }, /* Sierra Wireless MC8775 */
+ { USB_DEVICE(0x1199, 0x6820) }, /* Sierra Wireless AirCard 875 */
+ { USB_DEVICE(0x1199, 0x6821) }, /* Sierra Wireless AirCard 875U */
+ { USB_DEVICE(0x1199, 0x6822) }, /* Sierra Wireless AirCard 875E */
+ { USB_DEVICE(0x1199, 0x6832) }, /* Sierra Wireless MC8780 */
+ { USB_DEVICE(0x1199, 0x6833) }, /* Sierra Wireless MC8781 */
+ { USB_DEVICE(0x1199, 0x6834) }, /* Sierra Wireless MC8780 */
+ { USB_DEVICE(0x1199, 0x6835) }, /* Sierra Wireless MC8781 */
+ { USB_DEVICE(0x1199, 0x6838) }, /* Sierra Wireless MC8780 */
+ { USB_DEVICE(0x1199, 0x6839) }, /* Sierra Wireless MC8781 */
+ { USB_DEVICE(0x1199, 0x683A) }, /* Sierra Wireless MC8785 */
+ { USB_DEVICE(0x1199, 0x683B) }, /* Sierra Wireless MC8785 Composite */
+ /* Sierra Wireless MC8790, MC8791, MC8792 Composite */
+ { USB_DEVICE(0x1199, 0x683C) },
+ { USB_DEVICE(0x1199, 0x683D) }, /* Sierra Wireless MC8791 Composite */
+ /* Sierra Wireless MC8790, MC8791, MC8792 */
+ { USB_DEVICE(0x1199, 0x683E) },
+ { USB_DEVICE(0x1199, 0x6850) }, /* Sierra Wireless AirCard 880 */
+ { USB_DEVICE(0x1199, 0x6851) }, /* Sierra Wireless AirCard 881 */
+ { USB_DEVICE(0x1199, 0x6852) }, /* Sierra Wireless AirCard 880 E */
+ { USB_DEVICE(0x1199, 0x6853) }, /* Sierra Wireless AirCard 881 E */
+ { USB_DEVICE(0x1199, 0x6855) }, /* Sierra Wireless AirCard 880 U */
+ { USB_DEVICE(0x1199, 0x6856) }, /* Sierra Wireless AirCard 881 U */
+ { USB_DEVICE(0x1199, 0x6859) }, /* Sierra Wireless AirCard 885 E */
+ { USB_DEVICE(0x1199, 0x685A) }, /* Sierra Wireless AirCard 885 E */
+ /* Sierra Wireless C885 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x6880, 0xFF, 0xFF, 0xFF)},
+ /* Sierra Wireless C888, Air Card 501, USB 303, USB 304 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x6890, 0xFF, 0xFF, 0xFF)},
+ /* Sierra Wireless C22/C33 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x6891, 0xFF, 0xFF, 0xFF)},
+ /* Sierra Wireless HSPA Non-Composite Device */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x6892, 0xFF, 0xFF, 0xFF)},
+ { USB_DEVICE(0x1199, 0x6893) }, /* Sierra Wireless Device */
+ /* Sierra Wireless Direct IP modems */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x68A3, 0xFF, 0xFF, 0xFF),
+ .driver_info = (kernel_ulong_t)&direct_ip_interface_ignore
+ },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x68AA, 0xFF, 0xFF, 0xFF),
+ .driver_info = (kernel_ulong_t)&direct_ip_interface_ignore
+ },
+ { USB_DEVICE(0x1199, 0x68AB) }, /* Sierra Wireless AR8550 */
+ /* AT&T Direct IP LTE modems */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x0F3D, 0x68AA, 0xFF, 0xFF, 0xFF),
+ .driver_info = (kernel_ulong_t)&direct_ip_interface_ignore
+ },
+ /* Airprime/Sierra Wireless Direct IP modems */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x0F3D, 0x68A3, 0xFF, 0xFF, 0xFF),
+ .driver_info = (kernel_ulong_t)&direct_ip_interface_ignore
+ },
+
+ { }
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+
+struct sierra_port_private {
+ spinlock_t lock; /* lock the structure */
+ int outstanding_urbs; /* number of out urbs in flight */
+ struct usb_anchor active;
+ struct usb_anchor delayed;
+
+ int num_out_urbs;
+ int num_in_urbs;
+ /* Input endpoints and buffers for this port */
+ struct urb *in_urbs[N_IN_URB_HM];
+
+ /* Settings for the port */
+ int rts_state; /* Handshaking pins (outputs) */
+ int dtr_state;
+ int cts_state; /* Handshaking pins (inputs) */
+ int dsr_state;
+ int dcd_state;
+ int ri_state;
+};
+
+static int sierra_send_setup(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct sierra_port_private *portdata;
+ __u16 interface = 0;
+ int val = 0;
+ int do_send = 0;
+ int retval;
+
+ portdata = usb_get_serial_port_data(port);
+
+ if (portdata->dtr_state)
+ val |= 0x01;
+ if (portdata->rts_state)
+ val |= 0x02;
+
+ /* If composite device then properly report interface */
+ if (serial->num_ports == 1) {
+ interface = sierra_interface_num(serial);
+ /* Control message is sent only to interfaces with
+ * interrupt_in endpoints
+ */
+ if (port->interrupt_in_urb) {
+ /* send control message */
+ do_send = 1;
+ }
+ }
+
+ /* Otherwise the need to do non-composite mapping */
+ else {
+ if (port->bulk_out_endpointAddress == 2)
+ interface = 0;
+ else if (port->bulk_out_endpointAddress == 4)
+ interface = 1;
+ else if (port->bulk_out_endpointAddress == 5)
+ interface = 2;
+
+ do_send = 1;
+ }
+ if (!do_send)
+ return 0;
+
+ retval = usb_autopm_get_interface(serial->interface);
+ if (retval < 0)
+ return retval;
+
+ retval = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ 0x22, 0x21, val, interface, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ usb_autopm_put_interface(serial->interface);
+
+ return retval;
+}
+
+static int sierra_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned int value;
+ struct sierra_port_private *portdata;
+
+ portdata = usb_get_serial_port_data(port);
+
+ value = ((portdata->rts_state) ? TIOCM_RTS : 0) |
+ ((portdata->dtr_state) ? TIOCM_DTR : 0) |
+ ((portdata->cts_state) ? TIOCM_CTS : 0) |
+ ((portdata->dsr_state) ? TIOCM_DSR : 0) |
+ ((portdata->dcd_state) ? TIOCM_CAR : 0) |
+ ((portdata->ri_state) ? TIOCM_RNG : 0);
+
+ return value;
+}
+
+static int sierra_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct sierra_port_private *portdata;
+
+ portdata = usb_get_serial_port_data(port);
+
+ if (set & TIOCM_RTS)
+ portdata->rts_state = 1;
+ if (set & TIOCM_DTR)
+ portdata->dtr_state = 1;
+
+ if (clear & TIOCM_RTS)
+ portdata->rts_state = 0;
+ if (clear & TIOCM_DTR)
+ portdata->dtr_state = 0;
+ return sierra_send_setup(port);
+}
+
+static void sierra_release_urb(struct urb *urb)
+{
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+}
+
+static void sierra_outdat_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct sierra_port_private *portdata = usb_get_serial_port_data(port);
+ struct sierra_intf_private *intfdata;
+ int status = urb->status;
+ unsigned long flags;
+
+ intfdata = usb_get_serial_data(port->serial);
+
+ /* free up the transfer buffer, as usb_free_urb() does not do this */
+ kfree(urb->transfer_buffer);
+ usb_autopm_put_interface_async(port->serial->interface);
+ if (status)
+ dev_dbg(&port->dev, "%s - nonzero write bulk status "
+ "received: %d\n", __func__, status);
+
+ spin_lock_irqsave(&portdata->lock, flags);
+ --portdata->outstanding_urbs;
+ spin_unlock_irqrestore(&portdata->lock, flags);
+ spin_lock_irqsave(&intfdata->susp_lock, flags);
+ --intfdata->in_flight;
+ spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+
+ usb_serial_port_softint(port);
+}
+
+/* Write */
+static int sierra_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count)
+{
+ struct sierra_port_private *portdata;
+ struct sierra_intf_private *intfdata;
+ struct usb_serial *serial = port->serial;
+ unsigned long flags;
+ unsigned char *buffer;
+ struct urb *urb;
+ size_t writesize = min_t(size_t, count, MAX_TRANSFER);
+ int retval = 0;
+
+ /* verify that we actually have some data to write */
+ if (count == 0)
+ return 0;
+
+ portdata = usb_get_serial_port_data(port);
+ intfdata = usb_get_serial_data(serial);
+
+ dev_dbg(&port->dev, "%s: write (%zd bytes)\n", __func__, writesize);
+ spin_lock_irqsave(&portdata->lock, flags);
+ dev_dbg(&port->dev, "%s - outstanding_urbs: %d\n", __func__,
+ portdata->outstanding_urbs);
+ if (portdata->outstanding_urbs > portdata->num_out_urbs) {
+ spin_unlock_irqrestore(&portdata->lock, flags);
+ dev_dbg(&port->dev, "%s - write limit hit\n", __func__);
+ return 0;
+ }
+ portdata->outstanding_urbs++;
+ dev_dbg(&port->dev, "%s - 1, outstanding_urbs: %d\n", __func__,
+ portdata->outstanding_urbs);
+ spin_unlock_irqrestore(&portdata->lock, flags);
+
+ retval = usb_autopm_get_interface_async(serial->interface);
+ if (retval < 0) {
+ spin_lock_irqsave(&portdata->lock, flags);
+ portdata->outstanding_urbs--;
+ spin_unlock_irqrestore(&portdata->lock, flags);
+ goto error_simple;
+ }
+
+ buffer = kmemdup(buf, writesize, GFP_ATOMIC);
+ if (!buffer) {
+ retval = -ENOMEM;
+ goto error_no_buffer;
+ }
+
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!urb) {
+ retval = -ENOMEM;
+ goto error_no_urb;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__, writesize, buffer);
+
+ usb_fill_bulk_urb(urb, serial->dev,
+ usb_sndbulkpipe(serial->dev,
+ port->bulk_out_endpointAddress),
+ buffer, writesize, sierra_outdat_callback, port);
+
+ /* Handle the need to send a zero length packet */
+ urb->transfer_flags |= URB_ZERO_PACKET;
+
+ spin_lock_irqsave(&intfdata->susp_lock, flags);
+
+ if (intfdata->suspended) {
+ usb_anchor_urb(urb, &portdata->delayed);
+ spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+ goto skip_power;
+ } else {
+ usb_anchor_urb(urb, &portdata->active);
+ }
+ /* send it down the pipe */
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval) {
+ usb_unanchor_urb(urb);
+ spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+ dev_err(&port->dev, "%s - usb_submit_urb(write bulk) failed "
+ "with status = %d\n", __func__, retval);
+ goto error;
+ } else {
+ intfdata->in_flight++;
+ spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+ }
+
+skip_power:
+ /* we are done with this urb, so let the host driver
+ * really free it when it is finished with it */
+ usb_free_urb(urb);
+
+ return writesize;
+error:
+ usb_free_urb(urb);
+error_no_urb:
+ kfree(buffer);
+error_no_buffer:
+ spin_lock_irqsave(&portdata->lock, flags);
+ --portdata->outstanding_urbs;
+ dev_dbg(&port->dev, "%s - 2. outstanding_urbs: %d\n", __func__,
+ portdata->outstanding_urbs);
+ spin_unlock_irqrestore(&portdata->lock, flags);
+ usb_autopm_put_interface_async(serial->interface);
+error_simple:
+ return retval;
+}
+
+static void sierra_indat_callback(struct urb *urb)
+{
+ int err;
+ int endpoint;
+ struct usb_serial_port *port;
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+
+ endpoint = usb_pipeendpoint(urb->pipe);
+ port = urb->context;
+
+ if (status) {
+ dev_dbg(&port->dev, "%s: nonzero status: %d on"
+ " endpoint %02x\n", __func__, status, endpoint);
+ } else {
+ if (urb->actual_length) {
+ tty_insert_flip_string(&port->port, data,
+ urb->actual_length);
+ tty_flip_buffer_push(&port->port);
+
+ usb_serial_debug_data(&port->dev, __func__,
+ urb->actual_length, data);
+ } else {
+ dev_dbg(&port->dev, "%s: empty read urb"
+ " received\n", __func__);
+ }
+ }
+
+ /* Resubmit urb so we continue receiving */
+ if (status != -ESHUTDOWN && status != -EPERM) {
+ usb_mark_last_busy(port->serial->dev);
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err && err != -EPERM)
+ dev_err(&port->dev, "resubmit read urb failed."
+ "(%d)\n", err);
+ }
+}
+
+static void sierra_instat_callback(struct urb *urb)
+{
+ int err;
+ int status = urb->status;
+ struct usb_serial_port *port = urb->context;
+ struct sierra_port_private *portdata = usb_get_serial_port_data(port);
+ struct usb_serial *serial = port->serial;
+
+ dev_dbg(&port->dev, "%s: urb %p port %p has data %p\n", __func__,
+ urb, port, portdata);
+
+ if (status == 0) {
+ struct usb_ctrlrequest *req_pkt = urb->transfer_buffer;
+
+ if (!req_pkt) {
+ dev_dbg(&port->dev, "%s: NULL req_pkt\n",
+ __func__);
+ return;
+ }
+ if ((req_pkt->bRequestType == 0xA1) &&
+ (req_pkt->bRequest == 0x20)) {
+ int old_dcd_state;
+ unsigned char signals = *((unsigned char *)
+ urb->transfer_buffer +
+ sizeof(struct usb_ctrlrequest));
+
+ dev_dbg(&port->dev, "%s: signal x%x\n", __func__,
+ signals);
+
+ old_dcd_state = portdata->dcd_state;
+ portdata->cts_state = 1;
+ portdata->dcd_state = ((signals & 0x01) ? 1 : 0);
+ portdata->dsr_state = ((signals & 0x02) ? 1 : 0);
+ portdata->ri_state = ((signals & 0x08) ? 1 : 0);
+
+ if (old_dcd_state && !portdata->dcd_state)
+ tty_port_tty_hangup(&port->port, true);
+ } else {
+ dev_dbg(&port->dev, "%s: type %x req %x\n",
+ __func__, req_pkt->bRequestType,
+ req_pkt->bRequest);
+ }
+ } else
+ dev_dbg(&port->dev, "%s: error %d\n", __func__, status);
+
+ /* Resubmit urb so we continue receiving IRQ data */
+ if (status != -ESHUTDOWN && status != -ENOENT) {
+ usb_mark_last_busy(serial->dev);
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err && err != -EPERM)
+ dev_err(&port->dev, "%s: resubmit intr urb "
+ "failed. (%d)\n", __func__, err);
+ }
+}
+
+static unsigned int sierra_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct sierra_port_private *portdata = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ /* try to give a good number back based on if we have any free urbs at
+ * this point in time */
+ spin_lock_irqsave(&portdata->lock, flags);
+ if (portdata->outstanding_urbs > (portdata->num_out_urbs * 2) / 3) {
+ spin_unlock_irqrestore(&portdata->lock, flags);
+ dev_dbg(&port->dev, "%s - write limit hit\n", __func__);
+ return 0;
+ }
+ spin_unlock_irqrestore(&portdata->lock, flags);
+
+ return 2048;
+}
+
+static unsigned int sierra_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct sierra_port_private *portdata = usb_get_serial_port_data(port);
+ unsigned long flags;
+ unsigned int chars;
+
+ /* NOTE: This overcounts somewhat. */
+ spin_lock_irqsave(&portdata->lock, flags);
+ chars = portdata->outstanding_urbs * MAX_TRANSFER;
+ spin_unlock_irqrestore(&portdata->lock, flags);
+
+ dev_dbg(&port->dev, "%s - %u\n", __func__, chars);
+
+ return chars;
+}
+
+static void sierra_stop_rx_urbs(struct usb_serial_port *port)
+{
+ int i;
+ struct sierra_port_private *portdata = usb_get_serial_port_data(port);
+
+ for (i = 0; i < portdata->num_in_urbs; i++)
+ usb_kill_urb(portdata->in_urbs[i]);
+
+ usb_kill_urb(port->interrupt_in_urb);
+}
+
+static int sierra_submit_rx_urbs(struct usb_serial_port *port, gfp_t mem_flags)
+{
+ int ok_cnt;
+ int err = -EINVAL;
+ int i;
+ struct urb *urb;
+ struct sierra_port_private *portdata = usb_get_serial_port_data(port);
+
+ ok_cnt = 0;
+ for (i = 0; i < portdata->num_in_urbs; i++) {
+ urb = portdata->in_urbs[i];
+ if (!urb)
+ continue;
+ err = usb_submit_urb(urb, mem_flags);
+ if (err) {
+ dev_err(&port->dev, "%s: submit urb failed: %d\n",
+ __func__, err);
+ } else {
+ ok_cnt++;
+ }
+ }
+
+ if (ok_cnt && port->interrupt_in_urb) {
+ err = usb_submit_urb(port->interrupt_in_urb, mem_flags);
+ if (err) {
+ dev_err(&port->dev, "%s: submit intr urb failed: %d\n",
+ __func__, err);
+ }
+ }
+
+ if (ok_cnt > 0) /* at least one rx urb submitted */
+ return 0;
+ else
+ return err;
+}
+
+static struct urb *sierra_setup_urb(struct usb_serial *serial, int endpoint,
+ int dir, void *ctx, int len,
+ gfp_t mem_flags,
+ usb_complete_t callback)
+{
+ struct urb *urb;
+ u8 *buf;
+
+ urb = usb_alloc_urb(0, mem_flags);
+ if (!urb)
+ return NULL;
+
+ buf = kmalloc(len, mem_flags);
+ if (buf) {
+ /* Fill URB using supplied data */
+ usb_fill_bulk_urb(urb, serial->dev,
+ usb_sndbulkpipe(serial->dev, endpoint) | dir,
+ buf, len, callback, ctx);
+
+ dev_dbg(&serial->dev->dev, "%s %c u : %p d:%p\n", __func__,
+ dir == USB_DIR_IN ? 'i' : 'o', urb, buf);
+ } else {
+ sierra_release_urb(urb);
+ urb = NULL;
+ }
+
+ return urb;
+}
+
+static void sierra_close(struct usb_serial_port *port)
+{
+ int i;
+ struct usb_serial *serial = port->serial;
+ struct sierra_port_private *portdata;
+ struct sierra_intf_private *intfdata = usb_get_serial_data(serial);
+ struct urb *urb;
+
+ portdata = usb_get_serial_port_data(port);
+
+ /*
+ * Need to take susp_lock to make sure port is not already being
+ * resumed, but no need to hold it due to the tty-port initialized
+ * flag.
+ */
+ spin_lock_irq(&intfdata->susp_lock);
+ if (--intfdata->open_ports == 0)
+ serial->interface->needs_remote_wakeup = 0;
+ spin_unlock_irq(&intfdata->susp_lock);
+
+ for (;;) {
+ urb = usb_get_from_anchor(&portdata->delayed);
+ if (!urb)
+ break;
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ usb_autopm_put_interface_async(serial->interface);
+ spin_lock_irq(&portdata->lock);
+ portdata->outstanding_urbs--;
+ spin_unlock_irq(&portdata->lock);
+ }
+
+ sierra_stop_rx_urbs(port);
+ usb_kill_anchored_urbs(&portdata->active);
+
+ for (i = 0; i < portdata->num_in_urbs; i++) {
+ sierra_release_urb(portdata->in_urbs[i]);
+ portdata->in_urbs[i] = NULL;
+ }
+
+ usb_autopm_get_interface_no_resume(serial->interface);
+}
+
+static int sierra_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct sierra_port_private *portdata;
+ struct usb_serial *serial = port->serial;
+ struct sierra_intf_private *intfdata = usb_get_serial_data(serial);
+ int i;
+ int err;
+ int endpoint;
+ struct urb *urb;
+
+ portdata = usb_get_serial_port_data(port);
+
+ endpoint = port->bulk_in_endpointAddress;
+ for (i = 0; i < portdata->num_in_urbs; i++) {
+ urb = sierra_setup_urb(serial, endpoint, USB_DIR_IN, port,
+ IN_BUFLEN, GFP_KERNEL,
+ sierra_indat_callback);
+ portdata->in_urbs[i] = urb;
+ }
+ /* clear halt condition */
+ usb_clear_halt(serial->dev,
+ usb_sndbulkpipe(serial->dev, endpoint) | USB_DIR_IN);
+
+ err = sierra_submit_rx_urbs(port, GFP_KERNEL);
+ if (err)
+ goto err_submit;
+
+ spin_lock_irq(&intfdata->susp_lock);
+ if (++intfdata->open_ports == 1)
+ serial->interface->needs_remote_wakeup = 1;
+ spin_unlock_irq(&intfdata->susp_lock);
+ usb_autopm_put_interface(serial->interface);
+
+ return 0;
+
+err_submit:
+ sierra_stop_rx_urbs(port);
+
+ for (i = 0; i < portdata->num_in_urbs; i++) {
+ sierra_release_urb(portdata->in_urbs[i]);
+ portdata->in_urbs[i] = NULL;
+ }
+
+ return err;
+}
+
+
+static void sierra_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct sierra_port_private *portdata;
+
+ portdata = usb_get_serial_port_data(port);
+ portdata->rts_state = on;
+ portdata->dtr_state = on;
+
+ sierra_send_setup(port);
+}
+
+static int sierra_startup(struct usb_serial *serial)
+{
+ struct sierra_intf_private *intfdata;
+
+ intfdata = kzalloc_obj(*intfdata);
+ if (!intfdata)
+ return -ENOMEM;
+
+ spin_lock_init(&intfdata->susp_lock);
+
+ usb_set_serial_data(serial, intfdata);
+
+ /* Set Device mode to D0 */
+ sierra_set_power_state(serial->dev, 0x0000);
+
+ /* Check NMEA and set */
+ if (nmea)
+ sierra_vsc_set_nmea(serial->dev, 1);
+
+ return 0;
+}
+
+static void sierra_release(struct usb_serial *serial)
+{
+ struct sierra_intf_private *intfdata;
+
+ intfdata = usb_get_serial_data(serial);
+ kfree(intfdata);
+}
+
+static int sierra_port_probe(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct sierra_port_private *portdata;
+ const struct sierra_iface_list *himemory_list;
+ u8 ifnum;
+
+ portdata = kzalloc_obj(*portdata);
+ if (!portdata)
+ return -ENOMEM;
+
+ spin_lock_init(&portdata->lock);
+ init_usb_anchor(&portdata->active);
+ init_usb_anchor(&portdata->delayed);
+
+ /* Assume low memory requirements */
+ portdata->num_out_urbs = N_OUT_URB;
+ portdata->num_in_urbs = N_IN_URB;
+
+ /* Determine actual memory requirements */
+ if (serial->num_ports == 1) {
+ /* Get interface number for composite device */
+ ifnum = sierra_interface_num(serial);
+ himemory_list = &typeB_interface_list;
+ } else {
+ /* This is really the usb-serial port number of the interface
+ * rather than the interface number.
+ */
+ ifnum = port->port_number;
+ himemory_list = &typeA_interface_list;
+ }
+
+ if (is_listed(ifnum, himemory_list)) {
+ portdata->num_out_urbs = N_OUT_URB_HM;
+ portdata->num_in_urbs = N_IN_URB_HM;
+ }
+
+ dev_dbg(&port->dev,
+ "Memory usage (urbs) interface #%d, in=%d, out=%d\n",
+ ifnum, portdata->num_in_urbs, portdata->num_out_urbs);
+
+ usb_set_serial_port_data(port, portdata);
+
+ return 0;
+}
+
+static void sierra_port_remove(struct usb_serial_port *port)
+{
+ struct sierra_port_private *portdata;
+
+ portdata = usb_get_serial_port_data(port);
+ usb_set_serial_port_data(port, NULL);
+ kfree(portdata);
+}
+
+#ifdef CONFIG_PM
+static void stop_read_write_urbs(struct usb_serial *serial)
+{
+ int i;
+ struct usb_serial_port *port;
+ struct sierra_port_private *portdata;
+
+ /* Stop reading/writing urbs */
+ for (i = 0; i < serial->num_ports; ++i) {
+ port = serial->port[i];
+ portdata = usb_get_serial_port_data(port);
+ if (!portdata)
+ continue;
+ sierra_stop_rx_urbs(port);
+ usb_kill_anchored_urbs(&portdata->active);
+ }
+}
+
+static int sierra_suspend(struct usb_serial *serial, pm_message_t message)
+{
+ struct sierra_intf_private *intfdata = usb_get_serial_data(serial);
+
+ spin_lock_irq(&intfdata->susp_lock);
+ if (PMSG_IS_AUTO(message)) {
+ if (intfdata->in_flight) {
+ spin_unlock_irq(&intfdata->susp_lock);
+ return -EBUSY;
+ }
+ }
+ intfdata->suspended = 1;
+ spin_unlock_irq(&intfdata->susp_lock);
+
+ stop_read_write_urbs(serial);
+
+ return 0;
+}
+
+/* Caller must hold susp_lock. */
+static int sierra_submit_delayed_urbs(struct usb_serial_port *port)
+{
+ struct sierra_port_private *portdata = usb_get_serial_port_data(port);
+ struct sierra_intf_private *intfdata;
+ struct urb *urb;
+ int ec = 0;
+ int err;
+
+ intfdata = usb_get_serial_data(port->serial);
+
+ for (;;) {
+ urb = usb_get_from_anchor(&portdata->delayed);
+ if (!urb)
+ break;
+
+ usb_anchor_urb(urb, &portdata->active);
+ intfdata->in_flight++;
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err) {
+ dev_err(&port->dev, "%s - submit urb failed: %d",
+ __func__, err);
+ ec++;
+ intfdata->in_flight--;
+ usb_unanchor_urb(urb);
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+
+ spin_lock(&portdata->lock);
+ portdata->outstanding_urbs--;
+ spin_unlock(&portdata->lock);
+ }
+ }
+
+ if (ec)
+ return -EIO;
+
+ return 0;
+}
+
+static int sierra_resume(struct usb_serial *serial)
+{
+ struct usb_serial_port *port;
+ struct sierra_intf_private *intfdata = usb_get_serial_data(serial);
+ int ec = 0;
+ int i, err;
+
+ spin_lock_irq(&intfdata->susp_lock);
+ for (i = 0; i < serial->num_ports; i++) {
+ port = serial->port[i];
+
+ if (!tty_port_initialized(&port->port))
+ continue;
+
+ err = sierra_submit_delayed_urbs(port);
+ if (err)
+ ec++;
+
+ err = sierra_submit_rx_urbs(port, GFP_ATOMIC);
+ if (err)
+ ec++;
+ }
+ intfdata->suspended = 0;
+ spin_unlock_irq(&intfdata->susp_lock);
+
+ return ec ? -EIO : 0;
+}
+
+#else
+#define sierra_suspend NULL
+#define sierra_resume NULL
+#endif
+
+static struct usb_serial_driver sierra_device = {
+ .driver = {
+ .name = "sierra",
+ },
+ .description = "Sierra USB modem",
+ .id_table = id_table,
+ .calc_num_ports = sierra_calc_num_ports,
+ .probe = sierra_probe,
+ .open = sierra_open,
+ .close = sierra_close,
+ .dtr_rts = sierra_dtr_rts,
+ .write = sierra_write,
+ .write_room = sierra_write_room,
+ .chars_in_buffer = sierra_chars_in_buffer,
+ .tiocmget = sierra_tiocmget,
+ .tiocmset = sierra_tiocmset,
+ .attach = sierra_startup,
+ .release = sierra_release,
+ .port_probe = sierra_port_probe,
+ .port_remove = sierra_port_remove,
+ .suspend = sierra_suspend,
+ .resume = sierra_resume,
+ .read_int_callback = sierra_instat_callback,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &sierra_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
+
+module_param(nmea, bool, 0644);
+MODULE_PARM_DESC(nmea, "NMEA streaming");
diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c
new file mode 100644
index 000000000..c11d64bf0
--- /dev/null
+++ b/drivers/usb/serial/spcp8x5.c
@@ -0,0 +1,478 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * spcp8x5 USB to serial adaptor driver
+ *
+ * Copyright (C) 2010-2013 Johan Hovold (jhovold@gmail.com)
+ * Copyright (C) 2006 Linxb (xubin.lin@worldplus.com.cn)
+ * Copyright (C) 2006 S1 Corp.
+ *
+ * Original driver for 2.6.10 pl2303 driver by
+ * Greg Kroah-Hartman (greg@kroah.com)
+ * Changes for 2.6.20 by Harald Klein <hari@vt100.at>
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#define DRIVER_DESC "SPCP8x5 USB to serial adaptor driver"
+
+#define SPCP825_QUIRK_NO_UART_STATUS 0x01
+#define SPCP825_QUIRK_NO_WORK_MODE 0x02
+
+#define SPCP8x5_007_VID 0x04FC
+#define SPCP8x5_007_PID 0x0201
+#define SPCP8x5_008_VID 0x04fc
+#define SPCP8x5_008_PID 0x0235
+#define SPCP8x5_PHILIPS_VID 0x0471
+#define SPCP8x5_PHILIPS_PID 0x081e
+#define SPCP8x5_INTERMATIC_VID 0x04FC
+#define SPCP8x5_INTERMATIC_PID 0x0204
+#define SPCP8x5_835_VID 0x04fc
+#define SPCP8x5_835_PID 0x0231
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(SPCP8x5_PHILIPS_VID , SPCP8x5_PHILIPS_PID)},
+ { USB_DEVICE(SPCP8x5_INTERMATIC_VID, SPCP8x5_INTERMATIC_PID)},
+ { USB_DEVICE(SPCP8x5_835_VID, SPCP8x5_835_PID)},
+ { USB_DEVICE(SPCP8x5_008_VID, SPCP8x5_008_PID)},
+ { USB_DEVICE(SPCP8x5_007_VID, SPCP8x5_007_PID),
+ .driver_info = SPCP825_QUIRK_NO_UART_STATUS |
+ SPCP825_QUIRK_NO_WORK_MODE },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+/* spcp8x5 spec register define */
+#define MCR_CONTROL_LINE_RTS 0x02
+#define MCR_CONTROL_LINE_DTR 0x01
+#define MCR_DTR 0x01
+#define MCR_RTS 0x02
+
+#define MSR_STATUS_LINE_DCD 0x80
+#define MSR_STATUS_LINE_RI 0x40
+#define MSR_STATUS_LINE_DSR 0x20
+#define MSR_STATUS_LINE_CTS 0x10
+
+/* verdor command here , we should define myself */
+#define SET_DEFAULT 0x40
+#define SET_DEFAULT_TYPE 0x20
+
+#define SET_UART_FORMAT 0x40
+#define SET_UART_FORMAT_TYPE 0x21
+#define SET_UART_FORMAT_SIZE_5 0x00
+#define SET_UART_FORMAT_SIZE_6 0x01
+#define SET_UART_FORMAT_SIZE_7 0x02
+#define SET_UART_FORMAT_SIZE_8 0x03
+#define SET_UART_FORMAT_STOP_1 0x00
+#define SET_UART_FORMAT_STOP_2 0x04
+#define SET_UART_FORMAT_PAR_NONE 0x00
+#define SET_UART_FORMAT_PAR_ODD 0x10
+#define SET_UART_FORMAT_PAR_EVEN 0x30
+#define SET_UART_FORMAT_PAR_MASK 0xD0
+#define SET_UART_FORMAT_PAR_SPACE 0x90
+
+#define GET_UART_STATUS_TYPE 0xc0
+#define GET_UART_STATUS 0x22
+#define GET_UART_STATUS_MSR 0x06
+
+#define SET_UART_STATUS 0x40
+#define SET_UART_STATUS_TYPE 0x23
+#define SET_UART_STATUS_MCR 0x0004
+#define SET_UART_STATUS_MCR_DTR 0x01
+#define SET_UART_STATUS_MCR_RTS 0x02
+#define SET_UART_STATUS_MCR_LOOP 0x10
+
+#define SET_WORKING_MODE 0x40
+#define SET_WORKING_MODE_TYPE 0x24
+#define SET_WORKING_MODE_U2C 0x00
+#define SET_WORKING_MODE_RS485 0x01
+#define SET_WORKING_MODE_PDMA 0x02
+#define SET_WORKING_MODE_SPP 0x03
+
+#define SET_FLOWCTL_CHAR 0x40
+#define SET_FLOWCTL_CHAR_TYPE 0x25
+
+#define GET_VERSION 0xc0
+#define GET_VERSION_TYPE 0x26
+
+#define SET_REGISTER 0x40
+#define SET_REGISTER_TYPE 0x27
+
+#define GET_REGISTER 0xc0
+#define GET_REGISTER_TYPE 0x28
+
+#define SET_RAM 0x40
+#define SET_RAM_TYPE 0x31
+
+#define GET_RAM 0xc0
+#define GET_RAM_TYPE 0x32
+
+/* how come ??? */
+#define UART_STATE 0x08
+#define UART_STATE_TRANSIENT_MASK 0x75
+#define UART_DCD 0x01
+#define UART_DSR 0x02
+#define UART_BREAK_ERROR 0x04
+#define UART_RING 0x08
+#define UART_FRAME_ERROR 0x10
+#define UART_PARITY_ERROR 0x20
+#define UART_OVERRUN_ERROR 0x40
+#define UART_CTS 0x80
+
+struct spcp8x5_private {
+ unsigned quirks;
+ spinlock_t lock;
+ u8 line_control;
+};
+
+static int spcp8x5_probe(struct usb_serial *serial,
+ const struct usb_device_id *id)
+{
+ usb_set_serial_data(serial, (void *)id);
+
+ return 0;
+}
+
+static int spcp8x5_port_probe(struct usb_serial_port *port)
+{
+ const struct usb_device_id *id = usb_get_serial_data(port->serial);
+ struct spcp8x5_private *priv;
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->lock);
+ priv->quirks = id->driver_info;
+
+ usb_set_serial_port_data(port, priv);
+
+ port->port.drain_delay = 256;
+
+ return 0;
+}
+
+static void spcp8x5_port_remove(struct usb_serial_port *port)
+{
+ struct spcp8x5_private *priv;
+
+ priv = usb_get_serial_port_data(port);
+ kfree(priv);
+}
+
+static int spcp8x5_set_ctrl_line(struct usb_serial_port *port, u8 mcr)
+{
+ struct spcp8x5_private *priv = usb_get_serial_port_data(port);
+ struct usb_device *dev = port->serial->dev;
+ int retval;
+
+ if (priv->quirks & SPCP825_QUIRK_NO_UART_STATUS)
+ return -EPERM;
+
+ retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ SET_UART_STATUS_TYPE, SET_UART_STATUS,
+ mcr, 0x04, NULL, 0, 100);
+ if (retval != 0) {
+ dev_err(&port->dev, "failed to set control lines: %d\n",
+ retval);
+ }
+ return retval;
+}
+
+static int spcp8x5_get_msr(struct usb_serial_port *port, u8 *status)
+{
+ struct spcp8x5_private *priv = usb_get_serial_port_data(port);
+ struct usb_device *dev = port->serial->dev;
+ u8 *buf;
+ int ret;
+
+ if (priv->quirks & SPCP825_QUIRK_NO_UART_STATUS)
+ return -EPERM;
+
+ buf = kzalloc(1, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+ GET_UART_STATUS, GET_UART_STATUS_TYPE,
+ 0, GET_UART_STATUS_MSR, buf, 1, 100);
+ if (ret < 1) {
+ dev_err(&port->dev, "failed to get modem status: %d\n", ret);
+ if (ret >= 0)
+ ret = -EIO;
+ goto out;
+ }
+
+ dev_dbg(&port->dev, "0xc0:0x22:0:6 %d - 0x02%x\n", ret, *buf);
+ *status = *buf;
+ ret = 0;
+out:
+ kfree(buf);
+
+ return ret;
+}
+
+static void spcp8x5_set_work_mode(struct usb_serial_port *port, u16 value,
+ u16 index)
+{
+ struct spcp8x5_private *priv = usb_get_serial_port_data(port);
+ struct usb_device *dev = port->serial->dev;
+ int ret;
+
+ if (priv->quirks & SPCP825_QUIRK_NO_WORK_MODE)
+ return;
+
+ ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ SET_WORKING_MODE_TYPE, SET_WORKING_MODE,
+ value, index, NULL, 0, 100);
+ dev_dbg(&port->dev, "value = %#x , index = %#x\n", value, index);
+ if (ret < 0)
+ dev_err(&port->dev, "failed to set work mode: %d\n", ret);
+}
+
+static int spcp8x5_carrier_raised(struct usb_serial_port *port)
+{
+ u8 msr;
+ int ret;
+
+ ret = spcp8x5_get_msr(port, &msr);
+ if (ret || msr & MSR_STATUS_LINE_DCD)
+ return 1;
+
+ return 0;
+}
+
+static void spcp8x5_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct spcp8x5_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ u8 control;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (on)
+ priv->line_control = MCR_CONTROL_LINE_DTR
+ | MCR_CONTROL_LINE_RTS;
+ else
+ priv->line_control &= ~ (MCR_CONTROL_LINE_DTR
+ | MCR_CONTROL_LINE_RTS);
+ control = priv->line_control;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ spcp8x5_set_ctrl_line(port, control);
+}
+
+static void spcp8x5_init_termios(struct tty_struct *tty)
+{
+ tty_encode_baud_rate(tty, 115200, 115200);
+}
+
+static void spcp8x5_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct usb_serial *serial = port->serial;
+ struct spcp8x5_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ unsigned int cflag = tty->termios.c_cflag;
+ unsigned short uartdata;
+ unsigned char buf[2] = {0, 0};
+ int baud;
+ int i;
+ u8 control;
+
+ /* check that they really want us to change something */
+ if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios))
+ return;
+
+ /* set DTR/RTS active */
+ spin_lock_irqsave(&priv->lock, flags);
+ control = priv->line_control;
+ if (old_termios && (old_termios->c_cflag & CBAUD) == B0) {
+ priv->line_control |= MCR_DTR;
+ if (!(old_termios->c_cflag & CRTSCTS))
+ priv->line_control |= MCR_RTS;
+ }
+ if (control != priv->line_control) {
+ control = priv->line_control;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ spcp8x5_set_ctrl_line(port, control);
+ } else {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ }
+
+ /* Set Baud Rate */
+ baud = tty_get_baud_rate(tty);
+ switch (baud) {
+ case 300: buf[0] = 0x00; break;
+ case 600: buf[0] = 0x01; break;
+ case 1200: buf[0] = 0x02; break;
+ case 2400: buf[0] = 0x03; break;
+ case 4800: buf[0] = 0x04; break;
+ case 9600: buf[0] = 0x05; break;
+ case 19200: buf[0] = 0x07; break;
+ case 38400: buf[0] = 0x09; break;
+ case 57600: buf[0] = 0x0a; break;
+ case 115200: buf[0] = 0x0b; break;
+ case 230400: buf[0] = 0x0c; break;
+ case 460800: buf[0] = 0x0d; break;
+ case 921600: buf[0] = 0x0e; break;
+/* case 1200000: buf[0] = 0x0f; break; */
+/* case 2400000: buf[0] = 0x10; break; */
+ case 3000000: buf[0] = 0x11; break;
+/* case 6000000: buf[0] = 0x12; break; */
+ case 0:
+ case 1000000:
+ buf[0] = 0x0b; break;
+ default:
+ dev_err(&port->dev, "unsupported baudrate, using 9600\n");
+ }
+
+ /* Set Data Length : 00:5bit, 01:6bit, 10:7bit, 11:8bit */
+ switch (cflag & CSIZE) {
+ case CS5:
+ buf[1] |= SET_UART_FORMAT_SIZE_5;
+ break;
+ case CS6:
+ buf[1] |= SET_UART_FORMAT_SIZE_6;
+ break;
+ case CS7:
+ buf[1] |= SET_UART_FORMAT_SIZE_7;
+ break;
+ default:
+ case CS8:
+ buf[1] |= SET_UART_FORMAT_SIZE_8;
+ break;
+ }
+
+ /* Set Stop bit2 : 0:1bit 1:2bit */
+ buf[1] |= (cflag & CSTOPB) ? SET_UART_FORMAT_STOP_2 :
+ SET_UART_FORMAT_STOP_1;
+
+ /* Set Parity bit3-4 01:Odd 11:Even */
+ if (cflag & PARENB) {
+ buf[1] |= (cflag & PARODD) ?
+ SET_UART_FORMAT_PAR_ODD : SET_UART_FORMAT_PAR_EVEN ;
+ } else {
+ buf[1] |= SET_UART_FORMAT_PAR_NONE;
+ }
+ uartdata = buf[0] | buf[1]<<8;
+
+ i = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ SET_UART_FORMAT_TYPE, SET_UART_FORMAT,
+ uartdata, 0, NULL, 0, 100);
+ if (i < 0)
+ dev_err(&port->dev, "Set UART format %#x failed (error = %d)\n",
+ uartdata, i);
+ dev_dbg(&port->dev, "0x21:0x40:0:0 %d\n", i);
+
+ if (cflag & CRTSCTS) {
+ /* enable hardware flow control */
+ spcp8x5_set_work_mode(port, 0x000a, SET_WORKING_MODE_U2C);
+ }
+}
+
+static int spcp8x5_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct spcp8x5_private *priv = usb_get_serial_port_data(port);
+ int ret;
+
+ usb_clear_halt(serial->dev, port->write_urb->pipe);
+ usb_clear_halt(serial->dev, port->read_urb->pipe);
+
+ ret = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ 0x09, 0x00,
+ 0x01, 0x00, NULL, 0x00, 100);
+ if (ret)
+ return ret;
+
+ spcp8x5_set_ctrl_line(port, priv->line_control);
+
+ if (tty)
+ spcp8x5_set_termios(tty, port, NULL);
+
+ return usb_serial_generic_open(tty, port);
+}
+
+static int spcp8x5_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct spcp8x5_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ u8 control;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (set & TIOCM_RTS)
+ priv->line_control |= MCR_RTS;
+ if (set & TIOCM_DTR)
+ priv->line_control |= MCR_DTR;
+ if (clear & TIOCM_RTS)
+ priv->line_control &= ~MCR_RTS;
+ if (clear & TIOCM_DTR)
+ priv->line_control &= ~MCR_DTR;
+ control = priv->line_control;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return spcp8x5_set_ctrl_line(port, control);
+}
+
+static int spcp8x5_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct spcp8x5_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ unsigned int mcr;
+ u8 status;
+ unsigned int result;
+
+ result = spcp8x5_get_msr(port, &status);
+ if (result)
+ return result;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ mcr = priv->line_control;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ result = ((mcr & MCR_DTR) ? TIOCM_DTR : 0)
+ | ((mcr & MCR_RTS) ? TIOCM_RTS : 0)
+ | ((status & MSR_STATUS_LINE_CTS) ? TIOCM_CTS : 0)
+ | ((status & MSR_STATUS_LINE_DSR) ? TIOCM_DSR : 0)
+ | ((status & MSR_STATUS_LINE_RI) ? TIOCM_RI : 0)
+ | ((status & MSR_STATUS_LINE_DCD) ? TIOCM_CD : 0);
+
+ return result;
+}
+
+static struct usb_serial_driver spcp8x5_device = {
+ .driver = {
+ .name = "SPCP8x5",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ .open = spcp8x5_open,
+ .dtr_rts = spcp8x5_dtr_rts,
+ .carrier_raised = spcp8x5_carrier_raised,
+ .set_termios = spcp8x5_set_termios,
+ .init_termios = spcp8x5_init_termios,
+ .tiocmget = spcp8x5_tiocmget,
+ .tiocmset = spcp8x5_tiocmset,
+ .probe = spcp8x5_probe,
+ .port_probe = spcp8x5_port_probe,
+ .port_remove = spcp8x5_port_remove,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &spcp8x5_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/ssu100.c b/drivers/usb/serial/ssu100.c
new file mode 100644
index 000000000..b0d51558b
--- /dev/null
+++ b/drivers/usb/serial/ssu100.c
@@ -0,0 +1,526 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * usb-serial driver for Quatech SSU-100
+ *
+ * based on ftdi_sio.c and the original serqt_usb.c from Quatech
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/serial.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/serial_reg.h>
+
+#define QT_OPEN_CLOSE_CHANNEL 0xca
+#define QT_SET_GET_DEVICE 0xc2
+#define QT_SET_GET_REGISTER 0xc0
+#define QT_GET_SET_PREBUF_TRIG_LVL 0xcc
+#define QT_SET_ATF 0xcd
+#define QT_GET_SET_UART 0xc1
+#define QT_TRANSFER_IN 0xc0
+#define QT_HW_FLOW_CONTROL_MASK 0xc5
+#define QT_SW_FLOW_CONTROL_MASK 0xc6
+
+#define SERIAL_MSR_MASK 0xf0
+
+#define SERIAL_CRTSCTS ((UART_MCR_RTS << 8) | UART_MSR_CTS)
+
+#define SERIAL_EVEN_PARITY (UART_LCR_PARITY | UART_LCR_EPAR)
+
+#define MAX_BAUD_RATE 460800
+
+#define ATC_DISABLED 0x00
+#define DUPMODE_BITS 0xc0
+#define RR_BITS 0x03
+#define LOOPMODE_BITS 0x41
+#define RS232_MODE 0x00
+#define RTSCTS_TO_CONNECTOR 0x40
+#define CLKS_X4 0x02
+#define FULLPWRBIT 0x00000080
+#define NEXT_BOARD_POWER_BIT 0x00000004
+
+#define DRIVER_DESC "Quatech SSU-100 USB to Serial Driver"
+
+#define USB_VENDOR_ID_QUATECH 0x061d /* Quatech VID */
+#define QUATECH_SSU100 0xC020 /* SSU100 */
+
+static const struct usb_device_id id_table[] = {
+ {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_SSU100)},
+ {} /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+struct ssu100_port_private {
+ spinlock_t status_lock;
+ u8 shadowLSR;
+ u8 shadowMSR;
+};
+
+static inline int ssu100_control_msg(struct usb_device *dev,
+ u8 request, u16 data, u16 index)
+{
+ return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ request, 0x40, data, index,
+ NULL, 0, 300);
+}
+
+static inline int ssu100_setdevice(struct usb_device *dev, u8 *data)
+{
+ u16 x = ((u16)(data[1] << 8) | (u16)(data[0]));
+
+ return ssu100_control_msg(dev, QT_SET_GET_DEVICE, x, 0);
+}
+
+
+static inline int ssu100_getdevice(struct usb_device *dev, u8 *data)
+{
+ int ret;
+
+ ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+ QT_SET_GET_DEVICE, 0xc0, 0, 0,
+ data, 3, 300);
+ if (ret < 3) {
+ if (ret >= 0)
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static inline int ssu100_getregister(struct usb_device *dev,
+ unsigned short uart,
+ unsigned short reg,
+ u8 *data)
+{
+ int ret;
+
+ ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+ QT_SET_GET_REGISTER, 0xc0, reg,
+ uart, data, sizeof(*data), 300);
+ if (ret < (int)sizeof(*data)) {
+ if (ret >= 0)
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+
+static inline int ssu100_setregister(struct usb_device *dev,
+ unsigned short uart,
+ unsigned short reg,
+ u16 data)
+{
+ u16 value = (data << 8) | reg;
+
+ return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ QT_SET_GET_REGISTER, 0x40, value, uart,
+ NULL, 0, 300);
+
+}
+
+#define set_mctrl(dev, set) update_mctrl((dev), (set), 0)
+#define clear_mctrl(dev, clear) update_mctrl((dev), 0, (clear))
+
+/* these do not deal with device that have more than 1 port */
+static inline int update_mctrl(struct usb_device *dev, unsigned int set,
+ unsigned int clear)
+{
+ unsigned urb_value;
+ int result;
+
+ if (((set | clear) & (TIOCM_DTR | TIOCM_RTS)) == 0) {
+ dev_dbg(&dev->dev, "%s - DTR|RTS not being set|cleared\n", __func__);
+ return 0; /* no change */
+ }
+
+ clear &= ~set; /* 'set' takes precedence over 'clear' */
+ urb_value = 0;
+ if (set & TIOCM_DTR)
+ urb_value |= UART_MCR_DTR;
+ if (set & TIOCM_RTS)
+ urb_value |= UART_MCR_RTS;
+
+ result = ssu100_setregister(dev, 0, UART_MCR, urb_value);
+ if (result < 0)
+ dev_dbg(&dev->dev, "%s Error from MODEM_CTRL urb\n", __func__);
+
+ return result;
+}
+
+static int ssu100_initdevice(struct usb_device *dev)
+{
+ u8 *data;
+ int result = 0;
+
+ data = kzalloc(3, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ result = ssu100_getdevice(dev, data);
+ if (result < 0) {
+ dev_dbg(&dev->dev, "%s - get_device failed %i\n", __func__, result);
+ goto out;
+ }
+
+ data[1] &= ~FULLPWRBIT;
+
+ result = ssu100_setdevice(dev, data);
+ if (result < 0) {
+ dev_dbg(&dev->dev, "%s - setdevice failed %i\n", __func__, result);
+ goto out;
+ }
+
+ result = ssu100_control_msg(dev, QT_GET_SET_PREBUF_TRIG_LVL, 128, 0);
+ if (result < 0) {
+ dev_dbg(&dev->dev, "%s - set prebuffer level failed %i\n", __func__, result);
+ goto out;
+ }
+
+ result = ssu100_control_msg(dev, QT_SET_ATF, ATC_DISABLED, 0);
+ if (result < 0) {
+ dev_dbg(&dev->dev, "%s - set ATFprebuffer level failed %i\n", __func__, result);
+ goto out;
+ }
+
+ result = ssu100_getdevice(dev, data);
+ if (result < 0) {
+ dev_dbg(&dev->dev, "%s - get_device failed %i\n", __func__, result);
+ goto out;
+ }
+
+ data[0] &= ~(RR_BITS | DUPMODE_BITS);
+ data[0] |= CLKS_X4;
+ data[1] &= ~(LOOPMODE_BITS);
+ data[1] |= RS232_MODE;
+
+ result = ssu100_setdevice(dev, data);
+ if (result < 0) {
+ dev_dbg(&dev->dev, "%s - setdevice failed %i\n", __func__, result);
+ goto out;
+ }
+
+out: kfree(data);
+ return result;
+
+}
+
+
+static void ssu100_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct usb_device *dev = port->serial->dev;
+ struct ktermios *termios = &tty->termios;
+ u16 baud, divisor, remainder;
+ unsigned int cflag = termios->c_cflag;
+ u16 urb_value = 0; /* will hold the new flags */
+ int result;
+
+ if (cflag & PARENB) {
+ if (cflag & PARODD)
+ urb_value |= UART_LCR_PARITY;
+ else
+ urb_value |= SERIAL_EVEN_PARITY;
+ }
+
+ urb_value |= UART_LCR_WLEN(tty_get_char_size(cflag));
+
+ baud = tty_get_baud_rate(tty);
+ if (!baud)
+ baud = 9600;
+
+ dev_dbg(&port->dev, "%s - got baud = %d\n", __func__, baud);
+
+
+ divisor = MAX_BAUD_RATE / baud;
+ remainder = MAX_BAUD_RATE % baud;
+ if (((remainder * 2) >= baud) && (baud != 110))
+ divisor++;
+
+ urb_value = urb_value << 8;
+
+ result = ssu100_control_msg(dev, QT_GET_SET_UART, divisor, urb_value);
+ if (result < 0)
+ dev_dbg(&port->dev, "%s - set uart failed\n", __func__);
+
+ if (cflag & CRTSCTS)
+ result = ssu100_control_msg(dev, QT_HW_FLOW_CONTROL_MASK,
+ SERIAL_CRTSCTS, 0);
+ else
+ result = ssu100_control_msg(dev, QT_HW_FLOW_CONTROL_MASK,
+ 0, 0);
+ if (result < 0)
+ dev_dbg(&port->dev, "%s - set HW flow control failed\n", __func__);
+
+ if (I_IXOFF(tty) || I_IXON(tty)) {
+ u16 x = ((u16)(START_CHAR(tty) << 8) | (u16)(STOP_CHAR(tty)));
+
+ result = ssu100_control_msg(dev, QT_SW_FLOW_CONTROL_MASK,
+ x, 0);
+ } else
+ result = ssu100_control_msg(dev, QT_SW_FLOW_CONTROL_MASK,
+ 0, 0);
+
+ if (result < 0)
+ dev_dbg(&port->dev, "%s - set SW flow control failed\n", __func__);
+
+}
+
+
+static int ssu100_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct usb_device *dev = port->serial->dev;
+ struct ssu100_port_private *priv = usb_get_serial_port_data(port);
+ u8 *data;
+ int result;
+ unsigned long flags;
+
+ data = kzalloc(2, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+ QT_OPEN_CLOSE_CHANNEL,
+ QT_TRANSFER_IN, 0x01,
+ 0, data, 2, 300);
+ if (result < 2) {
+ dev_dbg(&port->dev, "%s - open failed %i\n", __func__, result);
+ if (result >= 0)
+ result = -EIO;
+ kfree(data);
+ return result;
+ }
+
+ spin_lock_irqsave(&priv->status_lock, flags);
+ priv->shadowLSR = data[0];
+ priv->shadowMSR = data[1];
+ spin_unlock_irqrestore(&priv->status_lock, flags);
+
+ kfree(data);
+
+/* set to 9600 */
+ result = ssu100_control_msg(dev, QT_GET_SET_UART, 0x30, 0x0300);
+ if (result < 0)
+ dev_dbg(&port->dev, "%s - set uart failed\n", __func__);
+
+ if (tty)
+ ssu100_set_termios(tty, port, &tty->termios);
+
+ return usb_serial_generic_open(tty, port);
+}
+
+static int ssu100_attach(struct usb_serial *serial)
+{
+ return ssu100_initdevice(serial->dev);
+}
+
+static int ssu100_port_probe(struct usb_serial_port *port)
+{
+ struct ssu100_port_private *priv;
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->status_lock);
+
+ usb_set_serial_port_data(port, priv);
+
+ return 0;
+}
+
+static void ssu100_port_remove(struct usb_serial_port *port)
+{
+ struct ssu100_port_private *priv;
+
+ priv = usb_get_serial_port_data(port);
+ kfree(priv);
+}
+
+static int ssu100_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_device *dev = port->serial->dev;
+ u8 *d;
+ int r;
+
+ d = kzalloc(2, GFP_KERNEL);
+ if (!d)
+ return -ENOMEM;
+
+ r = ssu100_getregister(dev, 0, UART_MCR, d);
+ if (r < 0)
+ goto mget_out;
+
+ r = ssu100_getregister(dev, 0, UART_MSR, d+1);
+ if (r < 0)
+ goto mget_out;
+
+ r = (d[0] & UART_MCR_DTR ? TIOCM_DTR : 0) |
+ (d[0] & UART_MCR_RTS ? TIOCM_RTS : 0) |
+ (d[1] & UART_MSR_CTS ? TIOCM_CTS : 0) |
+ (d[1] & UART_MSR_DCD ? TIOCM_CAR : 0) |
+ (d[1] & UART_MSR_RI ? TIOCM_RI : 0) |
+ (d[1] & UART_MSR_DSR ? TIOCM_DSR : 0);
+
+mget_out:
+ kfree(d);
+ return r;
+}
+
+static int ssu100_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_device *dev = port->serial->dev;
+
+ return update_mctrl(dev, set, clear);
+}
+
+static void ssu100_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct usb_device *dev = port->serial->dev;
+
+ /* Disable flow control */
+ if (!on) {
+ if (ssu100_setregister(dev, 0, UART_MCR, 0) < 0)
+ dev_err(&port->dev, "error from flowcontrol urb\n");
+ }
+ /* drop RTS and DTR */
+ if (on)
+ set_mctrl(dev, TIOCM_DTR | TIOCM_RTS);
+ else
+ clear_mctrl(dev, TIOCM_DTR | TIOCM_RTS);
+}
+
+static void ssu100_update_msr(struct usb_serial_port *port, u8 msr)
+{
+ struct ssu100_port_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->status_lock, flags);
+ priv->shadowMSR = msr;
+ spin_unlock_irqrestore(&priv->status_lock, flags);
+
+ if (msr & UART_MSR_ANY_DELTA) {
+ /* update input line counters */
+ if (msr & UART_MSR_DCTS)
+ port->icount.cts++;
+ if (msr & UART_MSR_DDSR)
+ port->icount.dsr++;
+ if (msr & UART_MSR_DDCD)
+ port->icount.dcd++;
+ if (msr & UART_MSR_TERI)
+ port->icount.rng++;
+ wake_up_interruptible(&port->port.delta_msr_wait);
+ }
+}
+
+static void ssu100_update_lsr(struct usb_serial_port *port, u8 lsr,
+ char *tty_flag)
+{
+ struct ssu100_port_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->status_lock, flags);
+ priv->shadowLSR = lsr;
+ spin_unlock_irqrestore(&priv->status_lock, flags);
+
+ *tty_flag = TTY_NORMAL;
+ if (lsr & UART_LSR_BRK_ERROR_BITS) {
+ /* we always want to update icount, but we only want to
+ * update tty_flag for one case */
+ if (lsr & UART_LSR_BI) {
+ port->icount.brk++;
+ *tty_flag = TTY_BREAK;
+ usb_serial_handle_break(port);
+ }
+ if (lsr & UART_LSR_PE) {
+ port->icount.parity++;
+ if (*tty_flag == TTY_NORMAL)
+ *tty_flag = TTY_PARITY;
+ }
+ if (lsr & UART_LSR_FE) {
+ port->icount.frame++;
+ if (*tty_flag == TTY_NORMAL)
+ *tty_flag = TTY_FRAME;
+ }
+ if (lsr & UART_LSR_OE) {
+ port->icount.overrun++;
+ tty_insert_flip_char(&port->port, 0, TTY_OVERRUN);
+ }
+ }
+
+}
+
+static void ssu100_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ char *packet = urb->transfer_buffer;
+ char flag = TTY_NORMAL;
+ u32 len = urb->actual_length;
+ int i;
+ char *ch;
+
+ if ((len >= 4) &&
+ (packet[0] == 0x1b) && (packet[1] == 0x1b) &&
+ ((packet[2] == 0x00) || (packet[2] == 0x01))) {
+ if (packet[2] == 0x00)
+ ssu100_update_lsr(port, packet[3], &flag);
+ if (packet[2] == 0x01)
+ ssu100_update_msr(port, packet[3]);
+
+ len -= 4;
+ ch = packet + 4;
+ } else
+ ch = packet;
+
+ if (!len)
+ return; /* status only */
+
+ if (port->sysrq) {
+ for (i = 0; i < len; i++, ch++) {
+ if (!usb_serial_handle_sysrq_char(port, *ch))
+ tty_insert_flip_char(&port->port, *ch, flag);
+ }
+ } else {
+ tty_insert_flip_string_fixed_flag(&port->port, ch, flag, len);
+ }
+
+ tty_flip_buffer_push(&port->port);
+}
+
+static struct usb_serial_driver ssu100_device = {
+ .driver = {
+ .name = "ssu100",
+ },
+ .description = DRIVER_DESC,
+ .id_table = id_table,
+ .num_ports = 1,
+ .open = ssu100_open,
+ .attach = ssu100_attach,
+ .port_probe = ssu100_port_probe,
+ .port_remove = ssu100_port_remove,
+ .dtr_rts = ssu100_dtr_rts,
+ .process_read_urb = ssu100_process_read_urb,
+ .tiocmget = ssu100_tiocmget,
+ .tiocmset = ssu100_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .set_termios = ssu100_set_termios,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &ssu100_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/symbolserial.c b/drivers/usb/serial/symbolserial.c
new file mode 100644
index 000000000..8823671a9
--- /dev/null
+++ b/drivers/usb/serial/symbolserial.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Symbol USB barcode to serial driver
+ *
+ * Copyright (C) 2013 Johan Hovold <jhovold@gmail.com>
+ * Copyright (C) 2009 Greg Kroah-Hartman <gregkh@suse.de>
+ * Copyright (C) 2009 Novell Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/slab.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x05e0, 0x0600) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+struct symbol_private {
+ spinlock_t lock; /* protects the following flags */
+ bool throttled;
+ bool actually_throttled;
+};
+
+static void symbol_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct symbol_private *priv = usb_get_serial_port_data(port);
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+ unsigned long flags;
+ int result;
+ int data_length;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ goto exit;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
+
+ /*
+ * Data from the device comes with a 1 byte header:
+ *
+ * <size of data> <data>...
+ */
+ if (urb->actual_length > 1) {
+ data_length = data[0];
+ if (data_length > (urb->actual_length - 1))
+ data_length = urb->actual_length - 1;
+ tty_insert_flip_string(&port->port, &data[1], data_length);
+ tty_flip_buffer_push(&port->port);
+ } else {
+ dev_dbg(&port->dev, "%s - short packet\n", __func__);
+ }
+
+exit:
+ spin_lock_irqsave(&priv->lock, flags);
+
+ /* Continue trying to always read if we should */
+ if (!priv->throttled) {
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
+ if (result)
+ dev_err(&port->dev,
+ "%s - failed resubmitting read urb, error %d\n",
+ __func__, result);
+ } else
+ priv->actually_throttled = true;
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static int symbol_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct symbol_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ int result = 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->throttled = false;
+ priv->actually_throttled = false;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /* Start reading from the device */
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (result)
+ dev_err(&port->dev,
+ "%s - failed resubmitting read urb, error %d\n",
+ __func__, result);
+ return result;
+}
+
+static void symbol_close(struct usb_serial_port *port)
+{
+ usb_kill_urb(port->interrupt_in_urb);
+}
+
+static void symbol_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct symbol_private *priv = usb_get_serial_port_data(port);
+
+ spin_lock_irq(&priv->lock);
+ priv->throttled = true;
+ spin_unlock_irq(&priv->lock);
+}
+
+static void symbol_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct symbol_private *priv = usb_get_serial_port_data(port);
+ int result;
+ bool was_throttled;
+
+ spin_lock_irq(&priv->lock);
+ priv->throttled = false;
+ was_throttled = priv->actually_throttled;
+ priv->actually_throttled = false;
+ spin_unlock_irq(&priv->lock);
+
+ if (was_throttled) {
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (result)
+ dev_err(&port->dev,
+ "%s - failed submitting read urb, error %d\n",
+ __func__, result);
+ }
+}
+
+static int symbol_port_probe(struct usb_serial_port *port)
+{
+ struct symbol_private *priv;
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->lock);
+
+ usb_set_serial_port_data(port, priv);
+
+ return 0;
+}
+
+static void symbol_port_remove(struct usb_serial_port *port)
+{
+ struct symbol_private *priv = usb_get_serial_port_data(port);
+
+ kfree(priv);
+}
+
+static struct usb_serial_driver symbol_device = {
+ .driver = {
+ .name = "symbol",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .num_interrupt_in = 1,
+ .port_probe = symbol_port_probe,
+ .port_remove = symbol_port_remove,
+ .open = symbol_open,
+ .close = symbol_close,
+ .throttle = symbol_throttle,
+ .unthrottle = symbol_unthrottle,
+ .read_int_callback = symbol_int_callback,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &symbol_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION("Symbol USB barcode to serial driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c
new file mode 100644
index 000000000..b3f00e315
--- /dev/null
+++ b/drivers/usb/serial/ti_usb_3410_5052.c
@@ -0,0 +1,1659 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * TI 3410/5052 USB Serial Driver
+ *
+ * Copyright (C) 2004 Texas Instruments
+ *
+ * This driver is based on the Linux io_ti driver, which is
+ * Copyright (C) 2000-2002 Inside Out Networks
+ * Copyright (C) 2001-2002 Greg Kroah-Hartman
+ *
+ * For questions or problems with this driver, contact Texas Instruments
+ * technical support, or Al Borchers <alborchers@steinerpoint.com>, or
+ * Peter Berger <pberger@brimson.com>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/ioctl.h>
+#include <linux/serial.h>
+#include <linux/kfifo.h>
+#include <linux/mutex.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+/* Configuration ids */
+#define TI_BOOT_CONFIG 1
+#define TI_ACTIVE_CONFIG 2
+
+/* Vendor and product ids */
+#define TI_VENDOR_ID 0x0451
+#define IBM_VENDOR_ID 0x04b3
+#define STARTECH_VENDOR_ID 0x14b0
+#define TI_3410_PRODUCT_ID 0x3410
+#define IBM_4543_PRODUCT_ID 0x4543
+#define IBM_454B_PRODUCT_ID 0x454b
+#define IBM_454C_PRODUCT_ID 0x454c
+#define TI_3410_EZ430_ID 0xF430 /* TI ez430 development tool */
+#define TI_5052_BOOT_PRODUCT_ID 0x5052 /* no EEPROM, no firmware */
+#define TI_5152_BOOT_PRODUCT_ID 0x5152 /* no EEPROM, no firmware */
+#define TI_5052_EEPROM_PRODUCT_ID 0x505A /* EEPROM, no firmware */
+#define TI_5052_FIRMWARE_PRODUCT_ID 0x505F /* firmware is running */
+#define FRI2_PRODUCT_ID 0x5053 /* Fish River Island II */
+
+/* Multi-Tech vendor and product ids */
+#define MTS_VENDOR_ID 0x06E0
+#define MTS_GSM_NO_FW_PRODUCT_ID 0xF108
+#define MTS_CDMA_NO_FW_PRODUCT_ID 0xF109
+#define MTS_CDMA_PRODUCT_ID 0xF110
+#define MTS_GSM_PRODUCT_ID 0xF111
+#define MTS_EDGE_PRODUCT_ID 0xF112
+#define MTS_MT9234MU_PRODUCT_ID 0xF114
+#define MTS_MT9234ZBA_PRODUCT_ID 0xF115
+#define MTS_MT9234ZBAOLD_PRODUCT_ID 0x0319
+
+/* Abbott Diabetics vendor and product ids */
+#define ABBOTT_VENDOR_ID 0x1a61
+#define ABBOTT_STEREO_PLUG_ID 0x3410
+#define ABBOTT_PRODUCT_ID ABBOTT_STEREO_PLUG_ID
+#define ABBOTT_STRIP_PORT_ID 0x3420
+
+/* Honeywell vendor and product IDs */
+#define HONEYWELL_VENDOR_ID 0x10ac
+#define HONEYWELL_HGI80_PRODUCT_ID 0x0102 /* Honeywell HGI80 */
+
+/* Moxa UPORT 11x0 vendor and product IDs */
+#define MXU1_VENDOR_ID 0x110a
+#define MXU1_1110_PRODUCT_ID 0x1110
+#define MXU1_1130_PRODUCT_ID 0x1130
+#define MXU1_1150_PRODUCT_ID 0x1150
+#define MXU1_1151_PRODUCT_ID 0x1151
+#define MXU1_1131_PRODUCT_ID 0x1131
+
+/* Commands */
+#define TI_GET_VERSION 0x01
+#define TI_GET_PORT_STATUS 0x02
+#define TI_GET_PORT_DEV_INFO 0x03
+#define TI_GET_CONFIG 0x04
+#define TI_SET_CONFIG 0x05
+#define TI_OPEN_PORT 0x06
+#define TI_CLOSE_PORT 0x07
+#define TI_START_PORT 0x08
+#define TI_STOP_PORT 0x09
+#define TI_TEST_PORT 0x0A
+#define TI_PURGE_PORT 0x0B
+#define TI_RESET_EXT_DEVICE 0x0C
+#define TI_WRITE_DATA 0x80
+#define TI_READ_DATA 0x81
+#define TI_REQ_TYPE_CLASS 0x82
+
+/* Module identifiers */
+#define TI_I2C_PORT 0x01
+#define TI_IEEE1284_PORT 0x02
+#define TI_UART1_PORT 0x03
+#define TI_UART2_PORT 0x04
+#define TI_RAM_PORT 0x05
+
+/* Modem status */
+#define TI_MSR_DELTA_CTS 0x01
+#define TI_MSR_DELTA_DSR 0x02
+#define TI_MSR_DELTA_RI 0x04
+#define TI_MSR_DELTA_CD 0x08
+#define TI_MSR_CTS 0x10
+#define TI_MSR_DSR 0x20
+#define TI_MSR_RI 0x40
+#define TI_MSR_CD 0x80
+#define TI_MSR_DELTA_MASK 0x0F
+#define TI_MSR_MASK 0xF0
+
+/* Line status */
+#define TI_LSR_OVERRUN_ERROR 0x01
+#define TI_LSR_PARITY_ERROR 0x02
+#define TI_LSR_FRAMING_ERROR 0x04
+#define TI_LSR_BREAK 0x08
+#define TI_LSR_ERROR 0x0F
+#define TI_LSR_RX_FULL 0x10
+#define TI_LSR_TX_EMPTY 0x20
+#define TI_LSR_TX_EMPTY_BOTH 0x40
+
+/* Line control */
+#define TI_LCR_BREAK 0x40
+
+/* Modem control */
+#define TI_MCR_LOOP 0x04
+#define TI_MCR_DTR 0x10
+#define TI_MCR_RTS 0x20
+
+/* Mask settings */
+#define TI_UART_ENABLE_RTS_IN 0x0001
+#define TI_UART_DISABLE_RTS 0x0002
+#define TI_UART_ENABLE_PARITY_CHECKING 0x0008
+#define TI_UART_ENABLE_DSR_OUT 0x0010
+#define TI_UART_ENABLE_CTS_OUT 0x0020
+#define TI_UART_ENABLE_X_OUT 0x0040
+#define TI_UART_ENABLE_XA_OUT 0x0080
+#define TI_UART_ENABLE_X_IN 0x0100
+#define TI_UART_ENABLE_DTR_IN 0x0800
+#define TI_UART_DISABLE_DTR 0x1000
+#define TI_UART_ENABLE_MS_INTS 0x2000
+#define TI_UART_ENABLE_AUTO_START_DMA 0x4000
+
+/* Parity */
+#define TI_UART_NO_PARITY 0x00
+#define TI_UART_ODD_PARITY 0x01
+#define TI_UART_EVEN_PARITY 0x02
+#define TI_UART_MARK_PARITY 0x03
+#define TI_UART_SPACE_PARITY 0x04
+
+/* Stop bits */
+#define TI_UART_1_STOP_BITS 0x00
+#define TI_UART_1_5_STOP_BITS 0x01
+#define TI_UART_2_STOP_BITS 0x02
+
+/* Bits per character */
+#define TI_UART_5_DATA_BITS 0x00
+#define TI_UART_6_DATA_BITS 0x01
+#define TI_UART_7_DATA_BITS 0x02
+#define TI_UART_8_DATA_BITS 0x03
+
+/* 232/485 modes */
+#define TI_UART_232 0x00
+#define TI_UART_485_RECEIVER_DISABLED 0x01
+#define TI_UART_485_RECEIVER_ENABLED 0x02
+
+/* Pipe transfer mode and timeout */
+#define TI_PIPE_MODE_CONTINUOUS 0x01
+#define TI_PIPE_MODE_MASK 0x03
+#define TI_PIPE_TIMEOUT_MASK 0x7C
+#define TI_PIPE_TIMEOUT_ENABLE 0x80
+
+/* Config struct */
+struct ti_uart_config {
+ __be16 wBaudRate;
+ __be16 wFlags;
+ u8 bDataBits;
+ u8 bParity;
+ u8 bStopBits;
+ char cXon;
+ char cXoff;
+ u8 bUartMode;
+};
+
+/* Get port status */
+struct ti_port_status {
+ u8 bCmdCode;
+ u8 bModuleId;
+ u8 bErrorCode;
+ u8 bMSR;
+ u8 bLSR;
+};
+
+/* Purge modes */
+#define TI_PURGE_OUTPUT 0x00
+#define TI_PURGE_INPUT 0x80
+
+/* Read/Write data */
+#define TI_RW_DATA_ADDR_SFR 0x10
+#define TI_RW_DATA_ADDR_IDATA 0x20
+#define TI_RW_DATA_ADDR_XDATA 0x30
+#define TI_RW_DATA_ADDR_CODE 0x40
+#define TI_RW_DATA_ADDR_GPIO 0x50
+#define TI_RW_DATA_ADDR_I2C 0x60
+#define TI_RW_DATA_ADDR_FLASH 0x70
+#define TI_RW_DATA_ADDR_DSP 0x80
+
+#define TI_RW_DATA_UNSPECIFIED 0x00
+#define TI_RW_DATA_BYTE 0x01
+#define TI_RW_DATA_WORD 0x02
+#define TI_RW_DATA_DOUBLE_WORD 0x04
+
+struct ti_write_data_bytes {
+ u8 bAddrType;
+ u8 bDataType;
+ u8 bDataCounter;
+ __be16 wBaseAddrHi;
+ __be16 wBaseAddrLo;
+ u8 bData[];
+} __packed;
+
+struct ti_read_data_request {
+ u8 bAddrType;
+ u8 bDataType;
+ u8 bDataCounter;
+ __be16 wBaseAddrHi;
+ __be16 wBaseAddrLo;
+} __packed;
+
+struct ti_read_data_bytes {
+ u8 bCmdCode;
+ u8 bModuleId;
+ u8 bErrorCode;
+ u8 bData[];
+};
+
+/* Interrupt struct */
+struct ti_interrupt {
+ u8 bICode;
+ u8 bIInfo;
+};
+
+/* Interrupt codes */
+#define TI_CODE_HARDWARE_ERROR 0xFF
+#define TI_CODE_DATA_ERROR 0x03
+#define TI_CODE_MODEM_STATUS 0x04
+
+/* Download firmware max packet size */
+#define TI_DOWNLOAD_MAX_PACKET_SIZE 64
+
+/* Firmware image header */
+struct ti_firmware_header {
+ __le16 wLength;
+ u8 bCheckSum;
+} __packed;
+
+/* UART addresses */
+#define TI_UART1_BASE_ADDR 0xFFA0 /* UART 1 base address */
+#define TI_UART2_BASE_ADDR 0xFFB0 /* UART 2 base address */
+#define TI_UART_OFFSET_LCR 0x0002 /* UART MCR register offset */
+#define TI_UART_OFFSET_MCR 0x0004 /* UART MCR register offset */
+
+#define TI_DRIVER_AUTHOR "Al Borchers <alborchers@steinerpoint.com>"
+#define TI_DRIVER_DESC "TI USB 3410/5052 Serial Driver"
+
+#define TI_FIRMWARE_BUF_SIZE 16284
+
+#define TI_TRANSFER_TIMEOUT 2
+
+/* read urb states */
+#define TI_READ_URB_RUNNING 0
+#define TI_READ_URB_STOPPING 1
+#define TI_READ_URB_STOPPED 2
+
+#define TI_EXTRA_VID_PID_COUNT 5
+
+struct ti_port {
+ int tp_is_open;
+ u8 tp_msr;
+ u8 tp_shadow_mcr;
+ u8 tp_uart_mode; /* 232 or 485 modes */
+ unsigned int tp_uart_base_addr;
+ struct ti_device *tp_tdev;
+ struct usb_serial_port *tp_port;
+ spinlock_t tp_lock;
+ int tp_read_urb_state;
+ int tp_write_urb_in_use;
+};
+
+struct ti_device {
+ struct mutex td_open_close_lock;
+ int td_open_port_count;
+ struct usb_serial *td_serial;
+ int td_is_3410;
+ bool td_rs485_only;
+};
+
+static int ti_startup(struct usb_serial *serial);
+static void ti_release(struct usb_serial *serial);
+static int ti_port_probe(struct usb_serial_port *port);
+static void ti_port_remove(struct usb_serial_port *port);
+static int ti_open(struct tty_struct *tty, struct usb_serial_port *port);
+static void ti_close(struct usb_serial_port *port);
+static int ti_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *data, int count);
+static unsigned int ti_write_room(struct tty_struct *tty);
+static unsigned int ti_chars_in_buffer(struct tty_struct *tty);
+static bool ti_tx_empty(struct usb_serial_port *port);
+static void ti_throttle(struct tty_struct *tty);
+static void ti_unthrottle(struct tty_struct *tty);
+static void ti_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+static int ti_tiocmget(struct tty_struct *tty);
+static int ti_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear);
+static int ti_break(struct tty_struct *tty, int break_state);
+static void ti_interrupt_callback(struct urb *urb);
+static void ti_bulk_in_callback(struct urb *urb);
+static void ti_bulk_out_callback(struct urb *urb);
+
+static void ti_recv(struct usb_serial_port *port, unsigned char *data,
+ int length);
+static void ti_send(struct ti_port *tport);
+static int ti_set_mcr(struct ti_port *tport, unsigned int mcr);
+static int ti_get_lsr(struct ti_port *tport, u8 *lsr);
+static void ti_get_serial_info(struct tty_struct *tty, struct serial_struct *ss);
+static void ti_handle_new_msr(struct ti_port *tport, u8 msr);
+
+static void ti_stop_read(struct ti_port *tport, struct tty_struct *tty);
+static int ti_restart_read(struct ti_port *tport, struct tty_struct *tty);
+
+static int ti_command_out_sync(struct usb_device *udev, u8 command,
+ u16 moduleid, u16 value, void *data, int size);
+static int ti_command_in_sync(struct usb_device *udev, u8 command,
+ u16 moduleid, u16 value, void *data, int size);
+static int ti_port_cmd_out(struct usb_serial_port *port, u8 command,
+ u16 value, void *data, int size);
+static int ti_port_cmd_in(struct usb_serial_port *port, u8 command,
+ u16 value, void *data, int size);
+
+static int ti_write_byte(struct usb_serial_port *port, struct ti_device *tdev,
+ unsigned long addr, u8 mask, u8 byte);
+
+static int ti_download_firmware(struct ti_device *tdev);
+
+static const struct usb_device_id ti_id_table_3410[] = {
+ { USB_DEVICE(TI_VENDOR_ID, TI_3410_PRODUCT_ID) },
+ { USB_DEVICE(TI_VENDOR_ID, TI_3410_EZ430_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_GSM_NO_FW_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_CDMA_NO_FW_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_CDMA_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_GSM_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_EDGE_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_MT9234MU_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_MT9234ZBA_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_MT9234ZBAOLD_PRODUCT_ID) },
+ { USB_DEVICE(IBM_VENDOR_ID, IBM_4543_PRODUCT_ID) },
+ { USB_DEVICE(IBM_VENDOR_ID, IBM_454B_PRODUCT_ID) },
+ { USB_DEVICE(IBM_VENDOR_ID, IBM_454C_PRODUCT_ID) },
+ { USB_DEVICE(ABBOTT_VENDOR_ID, ABBOTT_STEREO_PLUG_ID) },
+ { USB_DEVICE(ABBOTT_VENDOR_ID, ABBOTT_STRIP_PORT_ID) },
+ { USB_DEVICE(TI_VENDOR_ID, FRI2_PRODUCT_ID) },
+ { USB_DEVICE(HONEYWELL_VENDOR_ID, HONEYWELL_HGI80_PRODUCT_ID) },
+ { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1110_PRODUCT_ID) },
+ { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1130_PRODUCT_ID) },
+ { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1131_PRODUCT_ID) },
+ { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1150_PRODUCT_ID) },
+ { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1151_PRODUCT_ID) },
+ { USB_DEVICE(STARTECH_VENDOR_ID, TI_3410_PRODUCT_ID) },
+ { } /* terminator */
+};
+
+static const struct usb_device_id ti_id_table_5052[] = {
+ { USB_DEVICE(TI_VENDOR_ID, TI_5052_BOOT_PRODUCT_ID) },
+ { USB_DEVICE(TI_VENDOR_ID, TI_5152_BOOT_PRODUCT_ID) },
+ { USB_DEVICE(TI_VENDOR_ID, TI_5052_EEPROM_PRODUCT_ID) },
+ { USB_DEVICE(TI_VENDOR_ID, TI_5052_FIRMWARE_PRODUCT_ID) },
+ { }
+};
+
+static const struct usb_device_id ti_id_table_combined[] = {
+ { USB_DEVICE(TI_VENDOR_ID, TI_3410_PRODUCT_ID) },
+ { USB_DEVICE(TI_VENDOR_ID, TI_3410_EZ430_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_GSM_NO_FW_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_CDMA_NO_FW_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_CDMA_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_GSM_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_EDGE_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_MT9234MU_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_MT9234ZBA_PRODUCT_ID) },
+ { USB_DEVICE(MTS_VENDOR_ID, MTS_MT9234ZBAOLD_PRODUCT_ID) },
+ { USB_DEVICE(TI_VENDOR_ID, TI_5052_BOOT_PRODUCT_ID) },
+ { USB_DEVICE(TI_VENDOR_ID, TI_5152_BOOT_PRODUCT_ID) },
+ { USB_DEVICE(TI_VENDOR_ID, TI_5052_EEPROM_PRODUCT_ID) },
+ { USB_DEVICE(TI_VENDOR_ID, TI_5052_FIRMWARE_PRODUCT_ID) },
+ { USB_DEVICE(IBM_VENDOR_ID, IBM_4543_PRODUCT_ID) },
+ { USB_DEVICE(IBM_VENDOR_ID, IBM_454B_PRODUCT_ID) },
+ { USB_DEVICE(IBM_VENDOR_ID, IBM_454C_PRODUCT_ID) },
+ { USB_DEVICE(ABBOTT_VENDOR_ID, ABBOTT_PRODUCT_ID) },
+ { USB_DEVICE(ABBOTT_VENDOR_ID, ABBOTT_STRIP_PORT_ID) },
+ { USB_DEVICE(TI_VENDOR_ID, FRI2_PRODUCT_ID) },
+ { USB_DEVICE(HONEYWELL_VENDOR_ID, HONEYWELL_HGI80_PRODUCT_ID) },
+ { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1110_PRODUCT_ID) },
+ { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1130_PRODUCT_ID) },
+ { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1131_PRODUCT_ID) },
+ { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1150_PRODUCT_ID) },
+ { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1151_PRODUCT_ID) },
+ { USB_DEVICE(STARTECH_VENDOR_ID, TI_3410_PRODUCT_ID) },
+ { } /* terminator */
+};
+
+static struct usb_serial_driver ti_1port_device = {
+ .driver = {
+ .name = "ti_usb_3410_5052_1",
+ },
+ .description = "TI USB 3410 1 port adapter",
+ .id_table = ti_id_table_3410,
+ .num_ports = 1,
+ .num_bulk_out = 1,
+ .attach = ti_startup,
+ .release = ti_release,
+ .port_probe = ti_port_probe,
+ .port_remove = ti_port_remove,
+ .open = ti_open,
+ .close = ti_close,
+ .write = ti_write,
+ .write_room = ti_write_room,
+ .chars_in_buffer = ti_chars_in_buffer,
+ .tx_empty = ti_tx_empty,
+ .throttle = ti_throttle,
+ .unthrottle = ti_unthrottle,
+ .get_serial = ti_get_serial_info,
+ .set_termios = ti_set_termios,
+ .tiocmget = ti_tiocmget,
+ .tiocmset = ti_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .break_ctl = ti_break,
+ .read_int_callback = ti_interrupt_callback,
+ .read_bulk_callback = ti_bulk_in_callback,
+ .write_bulk_callback = ti_bulk_out_callback,
+};
+
+static struct usb_serial_driver ti_2port_device = {
+ .driver = {
+ .name = "ti_usb_3410_5052_2",
+ },
+ .description = "TI USB 5052 2 port adapter",
+ .id_table = ti_id_table_5052,
+ .num_ports = 2,
+ .num_bulk_out = 1,
+ .attach = ti_startup,
+ .release = ti_release,
+ .port_probe = ti_port_probe,
+ .port_remove = ti_port_remove,
+ .open = ti_open,
+ .close = ti_close,
+ .write = ti_write,
+ .write_room = ti_write_room,
+ .chars_in_buffer = ti_chars_in_buffer,
+ .tx_empty = ti_tx_empty,
+ .throttle = ti_throttle,
+ .unthrottle = ti_unthrottle,
+ .get_serial = ti_get_serial_info,
+ .set_termios = ti_set_termios,
+ .tiocmget = ti_tiocmget,
+ .tiocmset = ti_tiocmset,
+ .tiocmiwait = usb_serial_generic_tiocmiwait,
+ .get_icount = usb_serial_generic_get_icount,
+ .break_ctl = ti_break,
+ .read_int_callback = ti_interrupt_callback,
+ .read_bulk_callback = ti_bulk_in_callback,
+ .write_bulk_callback = ti_bulk_out_callback,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &ti_1port_device, &ti_2port_device, NULL
+};
+
+MODULE_AUTHOR(TI_DRIVER_AUTHOR);
+MODULE_DESCRIPTION(TI_DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+MODULE_FIRMWARE("ti_3410.fw");
+MODULE_FIRMWARE("ti_5052.fw");
+MODULE_FIRMWARE("mts_cdma.fw");
+MODULE_FIRMWARE("mts_gsm.fw");
+MODULE_FIRMWARE("mts_edge.fw");
+MODULE_FIRMWARE("mts_mt9234mu.fw");
+MODULE_FIRMWARE("mts_mt9234zba.fw");
+MODULE_FIRMWARE("moxa/moxa-1110.fw");
+MODULE_FIRMWARE("moxa/moxa-1130.fw");
+MODULE_FIRMWARE("moxa/moxa-1131.fw");
+MODULE_FIRMWARE("moxa/moxa-1150.fw");
+MODULE_FIRMWARE("moxa/moxa-1151.fw");
+
+MODULE_DEVICE_TABLE(usb, ti_id_table_combined);
+
+module_usb_serial_driver(serial_drivers, ti_id_table_combined);
+
+static int ti_startup(struct usb_serial *serial)
+{
+ struct ti_device *tdev;
+ struct usb_device *dev = serial->dev;
+ struct usb_host_interface *cur_altsetting;
+ int num_endpoints;
+ u16 vid, pid;
+ int status;
+
+ dev_dbg(&dev->dev,
+ "%s - product 0x%4X, num configurations %d, configuration value %d\n",
+ __func__, le16_to_cpu(dev->descriptor.idProduct),
+ dev->descriptor.bNumConfigurations,
+ dev->actconfig->desc.bConfigurationValue);
+
+ tdev = kzalloc_obj(struct ti_device);
+ if (!tdev)
+ return -ENOMEM;
+
+ mutex_init(&tdev->td_open_close_lock);
+ tdev->td_serial = serial;
+ usb_set_serial_data(serial, tdev);
+
+ /* determine device type */
+ if (serial->type == &ti_1port_device)
+ tdev->td_is_3410 = 1;
+ dev_dbg(&dev->dev, "%s - device type is %s\n", __func__,
+ tdev->td_is_3410 ? "3410" : "5052");
+
+ vid = le16_to_cpu(dev->descriptor.idVendor);
+ pid = le16_to_cpu(dev->descriptor.idProduct);
+ if (vid == MXU1_VENDOR_ID) {
+ switch (pid) {
+ case MXU1_1130_PRODUCT_ID:
+ case MXU1_1131_PRODUCT_ID:
+ tdev->td_rs485_only = true;
+ break;
+ }
+ }
+
+ cur_altsetting = serial->interface->cur_altsetting;
+ num_endpoints = cur_altsetting->desc.bNumEndpoints;
+
+ /* if we have only 1 configuration and 1 endpoint, download firmware */
+ if (dev->descriptor.bNumConfigurations == 1 && num_endpoints == 1) {
+ status = ti_download_firmware(tdev);
+
+ if (status != 0)
+ goto free_tdev;
+
+ /* 3410 must be reset, 5052 resets itself */
+ if (tdev->td_is_3410) {
+ msleep_interruptible(100);
+ usb_reset_device(dev);
+ }
+
+ status = -ENODEV;
+ goto free_tdev;
+ }
+
+ /* the second configuration must be set */
+ if (dev->actconfig->desc.bConfigurationValue == TI_BOOT_CONFIG) {
+ status = usb_driver_set_configuration(dev, TI_ACTIVE_CONFIG);
+ status = status ? status : -ENODEV;
+ goto free_tdev;
+ }
+
+ if (serial->num_bulk_in < serial->num_ports ||
+ serial->num_bulk_out < serial->num_ports) {
+ dev_err(&serial->interface->dev, "missing endpoints\n");
+ status = -ENODEV;
+ goto free_tdev;
+ }
+
+ return 0;
+
+free_tdev:
+ kfree(tdev);
+ usb_set_serial_data(serial, NULL);
+ return status;
+}
+
+
+static void ti_release(struct usb_serial *serial)
+{
+ struct ti_device *tdev = usb_get_serial_data(serial);
+
+ kfree(tdev);
+}
+
+static int ti_port_probe(struct usb_serial_port *port)
+{
+ struct ti_port *tport;
+
+ tport = kzalloc_obj(*tport);
+ if (!tport)
+ return -ENOMEM;
+
+ spin_lock_init(&tport->tp_lock);
+ if (port == port->serial->port[0])
+ tport->tp_uart_base_addr = TI_UART1_BASE_ADDR;
+ else
+ tport->tp_uart_base_addr = TI_UART2_BASE_ADDR;
+ tport->tp_port = port;
+ tport->tp_tdev = usb_get_serial_data(port->serial);
+
+ if (tport->tp_tdev->td_rs485_only)
+ tport->tp_uart_mode = TI_UART_485_RECEIVER_DISABLED;
+ else
+ tport->tp_uart_mode = TI_UART_232;
+
+ usb_set_serial_port_data(port, tport);
+
+ /*
+ * The TUSB5052 LSR does not tell when the transmitter shift register
+ * has emptied so add a one-character drain delay.
+ */
+ if (!tport->tp_tdev->td_is_3410)
+ port->port.drain_delay = 1;
+
+ return 0;
+}
+
+static void ti_port_remove(struct usb_serial_port *port)
+{
+ struct ti_port *tport;
+
+ tport = usb_get_serial_port_data(port);
+ kfree(tport);
+}
+
+static int ti_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct ti_port *tport = usb_get_serial_port_data(port);
+ struct ti_device *tdev;
+ struct usb_device *dev;
+ struct urb *urb;
+ int status;
+ u16 open_settings;
+
+ open_settings = (TI_PIPE_MODE_CONTINUOUS |
+ TI_PIPE_TIMEOUT_ENABLE |
+ (TI_TRANSFER_TIMEOUT << 2));
+
+ dev = port->serial->dev;
+ tdev = tport->tp_tdev;
+
+ /* only one open on any port on a device at a time */
+ if (mutex_lock_interruptible(&tdev->td_open_close_lock))
+ return -ERESTARTSYS;
+
+ tport->tp_msr = 0;
+ tport->tp_shadow_mcr |= (TI_MCR_RTS | TI_MCR_DTR);
+
+ /* start interrupt urb the first time a port is opened on this device */
+ if (tdev->td_open_port_count == 0) {
+ dev_dbg(&port->dev, "%s - start interrupt in urb\n", __func__);
+ urb = tdev->td_serial->port[0]->interrupt_in_urb;
+ if (!urb) {
+ dev_err(&port->dev, "%s - no interrupt urb\n", __func__);
+ status = -EINVAL;
+ goto release_lock;
+ }
+ urb->context = tdev;
+ status = usb_submit_urb(urb, GFP_KERNEL);
+ if (status) {
+ dev_err(&port->dev, "%s - submit interrupt urb failed, %d\n", __func__, status);
+ goto release_lock;
+ }
+ }
+
+ if (tty)
+ ti_set_termios(tty, port, &tty->termios);
+
+ status = ti_port_cmd_out(port, TI_OPEN_PORT, open_settings, NULL, 0);
+ if (status) {
+ dev_err(&port->dev, "%s - cannot send open command, %d\n",
+ __func__, status);
+ goto unlink_int_urb;
+ }
+
+ status = ti_port_cmd_out(port, TI_START_PORT, 0, NULL, 0);
+ if (status) {
+ dev_err(&port->dev, "%s - cannot send start command, %d\n",
+ __func__, status);
+ goto unlink_int_urb;
+ }
+
+ status = ti_port_cmd_out(port, TI_PURGE_PORT, TI_PURGE_INPUT, NULL, 0);
+ if (status) {
+ dev_err(&port->dev, "%s - cannot clear input buffers, %d\n",
+ __func__, status);
+ goto unlink_int_urb;
+ }
+ status = ti_port_cmd_out(port, TI_PURGE_PORT, TI_PURGE_OUTPUT, NULL, 0);
+ if (status) {
+ dev_err(&port->dev, "%s - cannot clear output buffers, %d\n",
+ __func__, status);
+ goto unlink_int_urb;
+ }
+
+ /* reset the data toggle on the bulk endpoints to work around bug in
+ * host controllers where things get out of sync some times */
+ usb_clear_halt(dev, port->write_urb->pipe);
+ usb_clear_halt(dev, port->read_urb->pipe);
+
+ if (tty)
+ ti_set_termios(tty, port, &tty->termios);
+
+ status = ti_port_cmd_out(port, TI_OPEN_PORT, open_settings, NULL, 0);
+ if (status) {
+ dev_err(&port->dev, "%s - cannot send open command (2), %d\n",
+ __func__, status);
+ goto unlink_int_urb;
+ }
+
+ status = ti_port_cmd_out(port, TI_START_PORT, 0, NULL, 0);
+ if (status) {
+ dev_err(&port->dev, "%s - cannot send start command (2), %d\n",
+ __func__, status);
+ goto unlink_int_urb;
+ }
+
+ /* start read urb */
+ urb = port->read_urb;
+ tport->tp_read_urb_state = TI_READ_URB_RUNNING;
+ urb->context = tport;
+ status = usb_submit_urb(urb, GFP_KERNEL);
+ if (status) {
+ dev_err(&port->dev, "%s - submit read urb failed, %d\n",
+ __func__, status);
+ goto unlink_int_urb;
+ }
+
+ tport->tp_is_open = 1;
+ ++tdev->td_open_port_count;
+
+ goto release_lock;
+
+unlink_int_urb:
+ if (tdev->td_open_port_count == 0)
+ usb_kill_urb(port->serial->port[0]->interrupt_in_urb);
+release_lock:
+ mutex_unlock(&tdev->td_open_close_lock);
+ return status;
+}
+
+
+static void ti_close(struct usb_serial_port *port)
+{
+ struct ti_device *tdev;
+ struct ti_port *tport;
+ int status;
+ unsigned long flags;
+
+ tdev = usb_get_serial_data(port->serial);
+ tport = usb_get_serial_port_data(port);
+
+ tport->tp_is_open = 0;
+
+ usb_kill_urb(port->read_urb);
+ usb_kill_urb(port->write_urb);
+ tport->tp_write_urb_in_use = 0;
+ spin_lock_irqsave(&tport->tp_lock, flags);
+ kfifo_reset_out(&port->write_fifo);
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+
+ status = ti_port_cmd_out(port, TI_CLOSE_PORT, 0, NULL, 0);
+ if (status)
+ dev_err(&port->dev,
+ "%s - cannot send close port command, %d\n"
+ , __func__, status);
+
+ mutex_lock(&tdev->td_open_close_lock);
+ --tdev->td_open_port_count;
+ if (tdev->td_open_port_count == 0) {
+ /* last port is closed, shut down interrupt urb */
+ usb_kill_urb(port->serial->port[0]->interrupt_in_urb);
+ }
+ mutex_unlock(&tdev->td_open_close_lock);
+}
+
+
+static int ti_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *data, int count)
+{
+ struct ti_port *tport = usb_get_serial_port_data(port);
+
+ if (count == 0) {
+ return 0;
+ }
+
+ if (!tport->tp_is_open)
+ return -ENODEV;
+
+ count = kfifo_in_locked(&port->write_fifo, data, count,
+ &tport->tp_lock);
+ ti_send(tport);
+
+ return count;
+}
+
+
+static unsigned int ti_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ti_port *tport = usb_get_serial_port_data(port);
+ unsigned int room;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tport->tp_lock, flags);
+ room = kfifo_avail(&port->write_fifo);
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, room);
+ return room;
+}
+
+
+static unsigned int ti_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ti_port *tport = usb_get_serial_port_data(port);
+ unsigned int chars;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tport->tp_lock, flags);
+ chars = kfifo_len(&port->write_fifo);
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+
+ dev_dbg(&port->dev, "%s - returns %u\n", __func__, chars);
+ return chars;
+}
+
+static bool ti_tx_empty(struct usb_serial_port *port)
+{
+ struct ti_port *tport = usb_get_serial_port_data(port);
+ u8 lsr, mask;
+ int ret;
+
+ /*
+ * TUSB5052 does not have the TEMT bit to tell if the shift register
+ * is empty.
+ */
+ if (tport->tp_tdev->td_is_3410)
+ mask = TI_LSR_TX_EMPTY_BOTH;
+ else
+ mask = TI_LSR_TX_EMPTY;
+
+ ret = ti_get_lsr(tport, &lsr);
+ if (!ret && !(lsr & mask))
+ return false;
+
+ return true;
+}
+
+static void ti_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ti_port *tport = usb_get_serial_port_data(port);
+
+ if (I_IXOFF(tty) || C_CRTSCTS(tty))
+ ti_stop_read(tport, tty);
+
+}
+
+
+static void ti_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ti_port *tport = usb_get_serial_port_data(port);
+ int status;
+
+ if (I_IXOFF(tty) || C_CRTSCTS(tty)) {
+ status = ti_restart_read(tport, tty);
+ if (status)
+ dev_err(&port->dev, "%s - cannot restart read, %d\n",
+ __func__, status);
+ }
+}
+
+static void ti_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct ti_port *tport = usb_get_serial_port_data(port);
+ struct ti_uart_config *config;
+ int baud;
+ int status;
+ unsigned int mcr;
+ u16 wbaudrate;
+ u16 wflags = 0;
+
+ config = kmalloc_obj(*config);
+ if (!config)
+ return;
+
+ /* these flags must be set */
+ wflags |= TI_UART_ENABLE_MS_INTS;
+ wflags |= TI_UART_ENABLE_AUTO_START_DMA;
+ config->bUartMode = tport->tp_uart_mode;
+
+ switch (C_CSIZE(tty)) {
+ case CS5:
+ config->bDataBits = TI_UART_5_DATA_BITS;
+ break;
+ case CS6:
+ config->bDataBits = TI_UART_6_DATA_BITS;
+ break;
+ case CS7:
+ config->bDataBits = TI_UART_7_DATA_BITS;
+ break;
+ default:
+ case CS8:
+ config->bDataBits = TI_UART_8_DATA_BITS;
+ break;
+ }
+
+ /* CMSPAR isn't supported by this driver */
+ tty->termios.c_cflag &= ~CMSPAR;
+
+ if (C_PARENB(tty)) {
+ if (C_PARODD(tty)) {
+ wflags |= TI_UART_ENABLE_PARITY_CHECKING;
+ config->bParity = TI_UART_ODD_PARITY;
+ } else {
+ wflags |= TI_UART_ENABLE_PARITY_CHECKING;
+ config->bParity = TI_UART_EVEN_PARITY;
+ }
+ } else {
+ wflags &= ~TI_UART_ENABLE_PARITY_CHECKING;
+ config->bParity = TI_UART_NO_PARITY;
+ }
+
+ if (C_CSTOPB(tty))
+ config->bStopBits = TI_UART_2_STOP_BITS;
+ else
+ config->bStopBits = TI_UART_1_STOP_BITS;
+
+ if (C_CRTSCTS(tty)) {
+ /* RTS flow control must be off to drop RTS for baud rate B0 */
+ if ((C_BAUD(tty)) != B0)
+ wflags |= TI_UART_ENABLE_RTS_IN;
+ wflags |= TI_UART_ENABLE_CTS_OUT;
+ } else {
+ ti_restart_read(tport, tty);
+ }
+
+ if (I_IXOFF(tty) || I_IXON(tty)) {
+ config->cXon = START_CHAR(tty);
+ config->cXoff = STOP_CHAR(tty);
+
+ if (I_IXOFF(tty))
+ wflags |= TI_UART_ENABLE_X_IN;
+ else
+ ti_restart_read(tport, tty);
+
+ if (I_IXON(tty))
+ wflags |= TI_UART_ENABLE_X_OUT;
+ }
+
+ baud = tty_get_baud_rate(tty);
+ if (!baud)
+ baud = 9600;
+ if (tport->tp_tdev->td_is_3410)
+ wbaudrate = (923077 + baud/2) / baud;
+ else
+ wbaudrate = (461538 + baud/2) / baud;
+
+ /* FIXME: Should calculate resulting baud here and report it back */
+ if ((C_BAUD(tty)) != B0)
+ tty_encode_baud_rate(tty, baud, baud);
+
+ dev_dbg(&port->dev,
+ "%s - BaudRate=%d, wBaudRate=%d, wFlags=0x%04X, bDataBits=%d, bParity=%d, bStopBits=%d, cXon=%d, cXoff=%d, bUartMode=%d\n",
+ __func__, baud, wbaudrate, wflags,
+ config->bDataBits, config->bParity, config->bStopBits,
+ config->cXon, config->cXoff, config->bUartMode);
+
+ config->wBaudRate = cpu_to_be16(wbaudrate);
+ config->wFlags = cpu_to_be16(wflags);
+
+ status = ti_port_cmd_out(port, TI_SET_CONFIG, 0, config,
+ sizeof(*config));
+ if (status)
+ dev_err(&port->dev, "%s - cannot set config on port %d, %d\n",
+ __func__, port->port_number, status);
+
+ /* SET_CONFIG asserts RTS and DTR, reset them correctly */
+ mcr = tport->tp_shadow_mcr;
+ /* if baud rate is B0, clear RTS and DTR */
+ if (C_BAUD(tty) == B0)
+ mcr &= ~(TI_MCR_DTR | TI_MCR_RTS);
+ status = ti_set_mcr(tport, mcr);
+ if (status)
+ dev_err(&port->dev, "%s - cannot set modem control on port %d, %d\n",
+ __func__, port->port_number, status);
+
+ kfree(config);
+}
+
+
+static int ti_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ti_port *tport = usb_get_serial_port_data(port);
+ unsigned int result;
+ unsigned int msr;
+ unsigned int mcr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tport->tp_lock, flags);
+ msr = tport->tp_msr;
+ mcr = tport->tp_shadow_mcr;
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+
+ result = ((mcr & TI_MCR_DTR) ? TIOCM_DTR : 0)
+ | ((mcr & TI_MCR_RTS) ? TIOCM_RTS : 0)
+ | ((mcr & TI_MCR_LOOP) ? TIOCM_LOOP : 0)
+ | ((msr & TI_MSR_CTS) ? TIOCM_CTS : 0)
+ | ((msr & TI_MSR_CD) ? TIOCM_CAR : 0)
+ | ((msr & TI_MSR_RI) ? TIOCM_RI : 0)
+ | ((msr & TI_MSR_DSR) ? TIOCM_DSR : 0);
+
+ dev_dbg(&port->dev, "%s - 0x%04X\n", __func__, result);
+
+ return result;
+}
+
+
+static int ti_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ti_port *tport = usb_get_serial_port_data(port);
+ unsigned int mcr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tport->tp_lock, flags);
+ mcr = tport->tp_shadow_mcr;
+
+ if (set & TIOCM_RTS)
+ mcr |= TI_MCR_RTS;
+ if (set & TIOCM_DTR)
+ mcr |= TI_MCR_DTR;
+ if (set & TIOCM_LOOP)
+ mcr |= TI_MCR_LOOP;
+
+ if (clear & TIOCM_RTS)
+ mcr &= ~TI_MCR_RTS;
+ if (clear & TIOCM_DTR)
+ mcr &= ~TI_MCR_DTR;
+ if (clear & TIOCM_LOOP)
+ mcr &= ~TI_MCR_LOOP;
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+
+ return ti_set_mcr(tport, mcr);
+}
+
+
+static int ti_break(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ti_port *tport = usb_get_serial_port_data(port);
+ int status;
+
+ dev_dbg(&port->dev, "%s - state = %d\n", __func__, break_state);
+
+ status = ti_write_byte(port, tport->tp_tdev,
+ tport->tp_uart_base_addr + TI_UART_OFFSET_LCR,
+ TI_LCR_BREAK, break_state == -1 ? TI_LCR_BREAK : 0);
+
+ if (status) {
+ dev_dbg(&port->dev, "%s - error setting break, %d\n", __func__, status);
+ return status;
+ }
+
+ return 0;
+}
+
+static int ti_get_port_from_code(unsigned char code)
+{
+ return (code >> 6) & 0x01;
+}
+
+static int ti_get_func_from_code(unsigned char code)
+{
+ return code & 0x0f;
+}
+
+static void ti_interrupt_callback(struct urb *urb)
+{
+ struct ti_device *tdev = urb->context;
+ struct usb_serial_port *port;
+ struct usb_serial *serial = tdev->td_serial;
+ struct ti_port *tport;
+ struct device *dev = &urb->dev->dev;
+ unsigned char *data = urb->transfer_buffer;
+ int length = urb->actual_length;
+ int port_number;
+ int function;
+ int status = urb->status;
+ int retval;
+ u8 msr;
+
+ switch (status) {
+ case 0:
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ dev_dbg(dev, "%s - urb shutting down, %d\n", __func__, status);
+ return;
+ default:
+ dev_err(dev, "%s - nonzero urb status, %d\n", __func__, status);
+ goto exit;
+ }
+
+ if (length != 2) {
+ dev_dbg(dev, "%s - bad packet size, %d\n", __func__, length);
+ goto exit;
+ }
+
+ if (data[0] == TI_CODE_HARDWARE_ERROR) {
+ dev_err(dev, "%s - hardware error, %d\n", __func__, data[1]);
+ goto exit;
+ }
+
+ port_number = ti_get_port_from_code(data[0]);
+ function = ti_get_func_from_code(data[0]);
+
+ dev_dbg(dev, "%s - port_number %d, function %d, data 0x%02X\n",
+ __func__, port_number, function, data[1]);
+
+ if (port_number >= serial->num_ports) {
+ dev_err(dev, "%s - bad port number, %d\n",
+ __func__, port_number);
+ goto exit;
+ }
+
+ port = serial->port[port_number];
+
+ tport = usb_get_serial_port_data(port);
+ if (!tport)
+ goto exit;
+
+ switch (function) {
+ case TI_CODE_DATA_ERROR:
+ dev_err(dev, "%s - DATA ERROR, port %d, data 0x%02X\n",
+ __func__, port_number, data[1]);
+ break;
+
+ case TI_CODE_MODEM_STATUS:
+ msr = data[1];
+ dev_dbg(dev, "%s - port %d, msr 0x%02X\n", __func__, port_number, msr);
+ ti_handle_new_msr(tport, msr);
+ break;
+
+ default:
+ dev_err(dev, "%s - unknown interrupt code, 0x%02X\n",
+ __func__, data[1]);
+ break;
+ }
+
+exit:
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(dev, "%s - resubmit interrupt urb failed, %d\n",
+ __func__, retval);
+}
+
+
+static void ti_bulk_in_callback(struct urb *urb)
+{
+ struct ti_port *tport = urb->context;
+ struct usb_serial_port *port = tport->tp_port;
+ struct device *dev = &urb->dev->dev;
+ int status = urb->status;
+ unsigned long flags;
+ int retval = 0;
+
+ switch (status) {
+ case 0:
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ dev_dbg(dev, "%s - urb shutting down, %d\n", __func__, status);
+ return;
+ default:
+ dev_err(dev, "%s - nonzero urb status, %d\n",
+ __func__, status);
+ }
+
+ if (status == -EPIPE)
+ goto exit;
+
+ if (status) {
+ dev_err(dev, "%s - stopping read!\n", __func__);
+ return;
+ }
+
+ if (urb->actual_length) {
+ usb_serial_debug_data(dev, __func__, urb->actual_length,
+ urb->transfer_buffer);
+
+ if (!tport->tp_is_open)
+ dev_dbg(dev, "%s - port closed, dropping data\n",
+ __func__);
+ else
+ ti_recv(port, urb->transfer_buffer, urb->actual_length);
+ spin_lock_irqsave(&tport->tp_lock, flags);
+ port->icount.rx += urb->actual_length;
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+ }
+
+exit:
+ /* continue to read unless stopping */
+ spin_lock_irqsave(&tport->tp_lock, flags);
+ if (tport->tp_read_urb_state == TI_READ_URB_RUNNING)
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ else if (tport->tp_read_urb_state == TI_READ_URB_STOPPING)
+ tport->tp_read_urb_state = TI_READ_URB_STOPPED;
+
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+ if (retval)
+ dev_err(dev, "%s - resubmit read urb failed, %d\n",
+ __func__, retval);
+}
+
+
+static void ti_bulk_out_callback(struct urb *urb)
+{
+ struct ti_port *tport = urb->context;
+ struct usb_serial_port *port = tport->tp_port;
+ int status = urb->status;
+
+ tport->tp_write_urb_in_use = 0;
+
+ switch (status) {
+ case 0:
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ dev_dbg(&port->dev, "%s - urb shutting down, %d\n", __func__, status);
+ return;
+ default:
+ dev_err_console(port, "%s - nonzero urb status, %d\n",
+ __func__, status);
+ }
+
+ /* send any buffered data */
+ ti_send(tport);
+}
+
+
+static void ti_recv(struct usb_serial_port *port, unsigned char *data,
+ int length)
+{
+ int cnt;
+
+ do {
+ cnt = tty_insert_flip_string(&port->port, data, length);
+ if (cnt < length) {
+ dev_err(&port->dev, "%s - dropping data, %d bytes lost\n",
+ __func__, length - cnt);
+ if (cnt == 0)
+ break;
+ }
+ tty_flip_buffer_push(&port->port);
+ data += cnt;
+ length -= cnt;
+ } while (length > 0);
+}
+
+
+static void ti_send(struct ti_port *tport)
+{
+ int count, result;
+ struct usb_serial_port *port = tport->tp_port;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tport->tp_lock, flags);
+
+ if (tport->tp_write_urb_in_use)
+ goto unlock;
+
+ count = kfifo_out(&port->write_fifo,
+ port->write_urb->transfer_buffer,
+ port->bulk_out_size);
+
+ if (count == 0)
+ goto unlock;
+
+ tport->tp_write_urb_in_use = 1;
+
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+
+ usb_serial_debug_data(&port->dev, __func__, count,
+ port->write_urb->transfer_buffer);
+
+ usb_fill_bulk_urb(port->write_urb, port->serial->dev,
+ usb_sndbulkpipe(port->serial->dev,
+ port->bulk_out_endpointAddress),
+ port->write_urb->transfer_buffer, count,
+ ti_bulk_out_callback, tport);
+
+ result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ if (result) {
+ dev_err_console(port, "%s - submit write urb failed, %d\n",
+ __func__, result);
+ tport->tp_write_urb_in_use = 0;
+ /* TODO: reschedule ti_send */
+ } else {
+ spin_lock_irqsave(&tport->tp_lock, flags);
+ port->icount.tx += count;
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+ }
+
+ /* more room in the buffer for new writes, wakeup */
+ tty_port_tty_wakeup(&port->port);
+
+ return;
+unlock:
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+ return;
+}
+
+
+static int ti_set_mcr(struct ti_port *tport, unsigned int mcr)
+{
+ unsigned long flags;
+ int status;
+
+ status = ti_write_byte(tport->tp_port, tport->tp_tdev,
+ tport->tp_uart_base_addr + TI_UART_OFFSET_MCR,
+ TI_MCR_RTS | TI_MCR_DTR | TI_MCR_LOOP, mcr);
+
+ spin_lock_irqsave(&tport->tp_lock, flags);
+ if (!status)
+ tport->tp_shadow_mcr = mcr;
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+
+ return status;
+}
+
+
+static int ti_get_lsr(struct ti_port *tport, u8 *lsr)
+{
+ int size, status;
+ struct usb_serial_port *port = tport->tp_port;
+ struct ti_port_status *data;
+
+ size = sizeof(struct ti_port_status);
+ data = kmalloc(size, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ status = ti_port_cmd_in(port, TI_GET_PORT_STATUS, 0, data, size);
+ if (status) {
+ dev_err(&port->dev,
+ "%s - get port status command failed, %d\n",
+ __func__, status);
+ goto free_data;
+ }
+
+ dev_dbg(&port->dev, "%s - lsr 0x%02X\n", __func__, data->bLSR);
+
+ *lsr = data->bLSR;
+
+free_data:
+ kfree(data);
+ return status;
+}
+
+
+static void ti_get_serial_info(struct tty_struct *tty, struct serial_struct *ss)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ti_port *tport = usb_get_serial_port_data(port);
+
+ ss->baud_base = tport->tp_tdev->td_is_3410 ? 921600 : 460800;
+}
+
+
+static void ti_handle_new_msr(struct ti_port *tport, u8 msr)
+{
+ struct async_icount *icount;
+ struct tty_struct *tty;
+ unsigned long flags;
+
+ dev_dbg(&tport->tp_port->dev, "%s - msr 0x%02X\n", __func__, msr);
+
+ if (msr & TI_MSR_DELTA_MASK) {
+ spin_lock_irqsave(&tport->tp_lock, flags);
+ icount = &tport->tp_port->icount;
+ if (msr & TI_MSR_DELTA_CTS)
+ icount->cts++;
+ if (msr & TI_MSR_DELTA_DSR)
+ icount->dsr++;
+ if (msr & TI_MSR_DELTA_CD)
+ icount->dcd++;
+ if (msr & TI_MSR_DELTA_RI)
+ icount->rng++;
+ wake_up_interruptible(&tport->tp_port->port.delta_msr_wait);
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+ }
+
+ tport->tp_msr = msr & TI_MSR_MASK;
+
+ /* handle CTS flow control */
+ tty = tty_port_tty_get(&tport->tp_port->port);
+ if (tty && C_CRTSCTS(tty)) {
+ if (msr & TI_MSR_CTS)
+ tty_wakeup(tty);
+ }
+ tty_kref_put(tty);
+}
+
+
+static void ti_stop_read(struct ti_port *tport, struct tty_struct *tty)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&tport->tp_lock, flags);
+
+ if (tport->tp_read_urb_state == TI_READ_URB_RUNNING)
+ tport->tp_read_urb_state = TI_READ_URB_STOPPING;
+
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+}
+
+
+static int ti_restart_read(struct ti_port *tport, struct tty_struct *tty)
+{
+ struct urb *urb;
+ int status = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tport->tp_lock, flags);
+
+ if (tport->tp_read_urb_state == TI_READ_URB_STOPPED) {
+ tport->tp_read_urb_state = TI_READ_URB_RUNNING;
+ urb = tport->tp_port->read_urb;
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+ urb->context = tport;
+ status = usb_submit_urb(urb, GFP_KERNEL);
+ } else {
+ tport->tp_read_urb_state = TI_READ_URB_RUNNING;
+ spin_unlock_irqrestore(&tport->tp_lock, flags);
+ }
+
+ return status;
+}
+
+static int ti_command_out_sync(struct usb_device *udev, u8 command,
+ u16 moduleid, u16 value, void *data, int size)
+{
+ int status;
+
+ status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), command,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ value, moduleid, data, size, 1000);
+ if (status < 0)
+ return status;
+
+ return 0;
+}
+
+static int ti_command_in_sync(struct usb_device *udev, u8 command,
+ u16 moduleid, u16 value, void *data, int size)
+{
+ int status;
+
+ status = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), command,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ value, moduleid, data, size, 1000);
+ if (status == size)
+ status = 0;
+ else if (status >= 0)
+ status = -ECOMM;
+
+ return status;
+}
+
+static int ti_port_cmd_out(struct usb_serial_port *port, u8 command,
+ u16 value, void *data, int size)
+{
+ return ti_command_out_sync(port->serial->dev, command,
+ TI_UART1_PORT + port->port_number,
+ value, data, size);
+}
+
+static int ti_port_cmd_in(struct usb_serial_port *port, u8 command,
+ u16 value, void *data, int size)
+{
+ return ti_command_in_sync(port->serial->dev, command,
+ TI_UART1_PORT + port->port_number,
+ value, data, size);
+}
+
+static int ti_write_byte(struct usb_serial_port *port,
+ struct ti_device *tdev, unsigned long addr,
+ u8 mask, u8 byte)
+{
+ int status;
+ unsigned int size;
+ struct ti_write_data_bytes *data;
+
+ dev_dbg(&port->dev, "%s - addr 0x%08lX, mask 0x%02X, byte 0x%02X\n", __func__,
+ addr, mask, byte);
+
+ size = sizeof(struct ti_write_data_bytes) + 2;
+ data = kmalloc(size, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->bAddrType = TI_RW_DATA_ADDR_XDATA;
+ data->bDataType = TI_RW_DATA_BYTE;
+ data->bDataCounter = 1;
+ data->wBaseAddrHi = cpu_to_be16(addr>>16);
+ data->wBaseAddrLo = cpu_to_be16(addr);
+ data->bData[0] = mask;
+ data->bData[1] = byte;
+
+ status = ti_command_out_sync(port->serial->dev, TI_WRITE_DATA,
+ TI_RAM_PORT, 0, data, size);
+ if (status < 0)
+ dev_err(&port->dev, "%s - failed, %d\n", __func__, status);
+
+ kfree(data);
+
+ return status;
+}
+
+static int ti_do_download(struct usb_device *dev, int pipe,
+ u8 *buffer, int size)
+{
+ int pos;
+ u8 cs = 0;
+ int done;
+ struct ti_firmware_header *header;
+ int status = 0;
+ int len;
+
+ for (pos = sizeof(struct ti_firmware_header); pos < size; pos++)
+ cs = (u8)(cs + buffer[pos]);
+
+ header = (struct ti_firmware_header *)buffer;
+ header->wLength = cpu_to_le16(size - sizeof(*header));
+ header->bCheckSum = cs;
+
+ dev_dbg(&dev->dev, "%s - downloading firmware\n", __func__);
+ for (pos = 0; pos < size; pos += done) {
+ len = min(size - pos, TI_DOWNLOAD_MAX_PACKET_SIZE);
+ status = usb_bulk_msg(dev, pipe, buffer + pos, len,
+ &done, 1000);
+ if (status)
+ break;
+ }
+ return status;
+}
+
+static int ti_download_firmware(struct ti_device *tdev)
+{
+ int status;
+ int buffer_size;
+ u8 *buffer;
+ struct usb_device *dev = tdev->td_serial->dev;
+ unsigned int pipe = usb_sndbulkpipe(dev,
+ tdev->td_serial->port[0]->bulk_out_endpointAddress);
+ const struct firmware *fw_p;
+ char buf[32];
+
+ if (le16_to_cpu(dev->descriptor.idVendor) == MXU1_VENDOR_ID) {
+ snprintf(buf,
+ sizeof(buf),
+ "moxa/moxa-%04x.fw",
+ le16_to_cpu(dev->descriptor.idProduct));
+
+ status = request_firmware(&fw_p, buf, &dev->dev);
+ goto check_firmware;
+ }
+
+ /* try ID specific firmware first, then try generic firmware */
+ sprintf(buf, "ti_usb-v%04x-p%04x.fw",
+ le16_to_cpu(dev->descriptor.idVendor),
+ le16_to_cpu(dev->descriptor.idProduct));
+ status = request_firmware(&fw_p, buf, &dev->dev);
+
+ if (status != 0) {
+ buf[0] = '\0';
+ if (le16_to_cpu(dev->descriptor.idVendor) == MTS_VENDOR_ID) {
+ switch (le16_to_cpu(dev->descriptor.idProduct)) {
+ case MTS_CDMA_PRODUCT_ID:
+ strscpy(buf, "mts_cdma.fw");
+ break;
+ case MTS_GSM_PRODUCT_ID:
+ strscpy(buf, "mts_gsm.fw");
+ break;
+ case MTS_EDGE_PRODUCT_ID:
+ strscpy(buf, "mts_edge.fw");
+ break;
+ case MTS_MT9234MU_PRODUCT_ID:
+ strscpy(buf, "mts_mt9234mu.fw");
+ break;
+ case MTS_MT9234ZBA_PRODUCT_ID:
+ strscpy(buf, "mts_mt9234zba.fw");
+ break;
+ case MTS_MT9234ZBAOLD_PRODUCT_ID:
+ strscpy(buf, "mts_mt9234zba.fw");
+ break; }
+ }
+ if (buf[0] == '\0') {
+ if (tdev->td_is_3410)
+ strscpy(buf, "ti_3410.fw");
+ else
+ strscpy(buf, "ti_5052.fw");
+ }
+ status = request_firmware(&fw_p, buf, &dev->dev);
+ }
+
+check_firmware:
+ if (status) {
+ dev_err(&dev->dev, "%s - firmware not found\n", __func__);
+ return -ENOENT;
+ }
+ if (fw_p->size > TI_FIRMWARE_BUF_SIZE) {
+ dev_err(&dev->dev, "%s - firmware too large %zu\n", __func__, fw_p->size);
+ release_firmware(fw_p);
+ return -ENOENT;
+ }
+
+ buffer_size = TI_FIRMWARE_BUF_SIZE + sizeof(struct ti_firmware_header);
+ buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (buffer) {
+ memcpy(buffer, fw_p->data, fw_p->size);
+ memset(buffer + fw_p->size, 0xff, buffer_size - fw_p->size);
+ status = ti_do_download(dev, pipe, buffer, fw_p->size);
+ kfree(buffer);
+ } else {
+ status = -ENOMEM;
+ }
+ release_firmware(fw_p);
+ if (status) {
+ dev_err(&dev->dev, "%s - error downloading firmware, %d\n",
+ __func__, status);
+ return status;
+ }
+
+ dev_dbg(&dev->dev, "%s - download successful\n", __func__);
+
+ return 0;
+}
diff --git a/drivers/usb/serial/upd78f0730.c b/drivers/usb/serial/upd78f0730.c
new file mode 100644
index 000000000..78c4d3c19
--- /dev/null
+++ b/drivers/usb/serial/upd78f0730.c
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas Electronics uPD78F0730 USB to serial converter driver
+ *
+ * Copyright (C) 2014,2016 Maksim Salau <maksim.salau@gmail.com>
+ *
+ * Protocol of the adaptor is described in the application note U19660EJ1V0AN00
+ * μPD78F0730 8-bit Single-Chip Microcontroller
+ * USB-to-Serial Conversion Software
+ * <https://www.renesas.com/en-eu/doc/DocumentServer/026/U19660EJ1V0AN00.pdf>
+ *
+ * The adaptor functionality is limited to the following:
+ * - data bits: 7 or 8
+ * - stop bits: 1 or 2
+ * - parity: even, odd or none
+ * - flow control: none
+ * - baud rates: 0, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 153600
+ * - signals: DTR, RTS and BREAK
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#define DRIVER_DESC "Renesas uPD78F0730 USB to serial converter driver"
+
+#define DRIVER_AUTHOR "Maksim Salau <maksim.salau@gmail.com>"
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x0409, 0x0063) }, /* V850ESJX3-STICK */
+ { USB_DEVICE(0x045B, 0x0212) }, /* YRPBRL78G13, YRPBRL78G14 */
+ { USB_DEVICE(0x064B, 0x7825) }, /* Analog Devices EVAL-ADXL362Z-DB */
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+/*
+ * Each adaptor is associated with a private structure, that holds the current
+ * state of control signals (DTR, RTS and BREAK).
+ */
+struct upd78f0730_port_private {
+ struct mutex lock; /* mutex to protect line_signals */
+ u8 line_signals;
+};
+
+/* Op-codes of control commands */
+#define UPD78F0730_CMD_LINE_CONTROL 0x00
+#define UPD78F0730_CMD_SET_DTR_RTS 0x01
+#define UPD78F0730_CMD_SET_XON_XOFF_CHR 0x02
+#define UPD78F0730_CMD_OPEN_CLOSE 0x03
+#define UPD78F0730_CMD_SET_ERR_CHR 0x04
+
+/* Data sizes in UPD78F0730_CMD_LINE_CONTROL command */
+#define UPD78F0730_DATA_SIZE_7_BITS 0x00
+#define UPD78F0730_DATA_SIZE_8_BITS 0x01
+#define UPD78F0730_DATA_SIZE_MASK 0x01
+
+/* Stop-bit modes in UPD78F0730_CMD_LINE_CONTROL command */
+#define UPD78F0730_STOP_BIT_1_BIT 0x00
+#define UPD78F0730_STOP_BIT_2_BIT 0x02
+#define UPD78F0730_STOP_BIT_MASK 0x02
+
+/* Parity modes in UPD78F0730_CMD_LINE_CONTROL command */
+#define UPD78F0730_PARITY_NONE 0x00
+#define UPD78F0730_PARITY_EVEN 0x04
+#define UPD78F0730_PARITY_ODD 0x08
+#define UPD78F0730_PARITY_MASK 0x0C
+
+/* Flow control modes in UPD78F0730_CMD_LINE_CONTROL command */
+#define UPD78F0730_FLOW_CONTROL_NONE 0x00
+#define UPD78F0730_FLOW_CONTROL_HW 0x10
+#define UPD78F0730_FLOW_CONTROL_SW 0x20
+#define UPD78F0730_FLOW_CONTROL_MASK 0x30
+
+/* Control signal bits in UPD78F0730_CMD_SET_DTR_RTS command */
+#define UPD78F0730_RTS 0x01
+#define UPD78F0730_DTR 0x02
+#define UPD78F0730_BREAK 0x04
+
+/* Port modes in UPD78F0730_CMD_OPEN_CLOSE command */
+#define UPD78F0730_PORT_CLOSE 0x00
+#define UPD78F0730_PORT_OPEN 0x01
+
+/* Error character substitution modes in UPD78F0730_CMD_SET_ERR_CHR command */
+#define UPD78F0730_ERR_CHR_DISABLED 0x00
+#define UPD78F0730_ERR_CHR_ENABLED 0x01
+
+/*
+ * Declaration of command structures
+ */
+
+/* UPD78F0730_CMD_LINE_CONTROL command */
+struct upd78f0730_line_control {
+ u8 opcode;
+ __le32 baud_rate;
+ u8 params;
+} __packed;
+
+/* UPD78F0730_CMD_SET_DTR_RTS command */
+struct upd78f0730_set_dtr_rts {
+ u8 opcode;
+ u8 params;
+};
+
+/* UPD78F0730_CMD_SET_XON_OFF_CHR command */
+struct upd78f0730_set_xon_xoff_chr {
+ u8 opcode;
+ u8 xon;
+ u8 xoff;
+};
+
+/* UPD78F0730_CMD_OPEN_CLOSE command */
+struct upd78f0730_open_close {
+ u8 opcode;
+ u8 state;
+};
+
+/* UPD78F0730_CMD_SET_ERR_CHR command */
+struct upd78f0730_set_err_chr {
+ u8 opcode;
+ u8 state;
+ u8 err_char;
+};
+
+static int upd78f0730_send_ctl(struct usb_serial_port *port,
+ const void *data, int size)
+{
+ struct usb_device *usbdev = port->serial->dev;
+ void *buf;
+ int res;
+
+ if (size <= 0 || !data)
+ return -EINVAL;
+
+ buf = kmemdup(data, size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ res = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x00,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ 0x0000, 0x0000, buf, size, USB_CTRL_SET_TIMEOUT);
+
+ kfree(buf);
+
+ if (res < 0) {
+ struct device *dev = &port->dev;
+
+ dev_err(dev, "failed to send control request %02x: %d\n",
+ *(u8 *)data, res);
+
+ return res;
+ }
+
+ return 0;
+}
+
+static int upd78f0730_port_probe(struct usb_serial_port *port)
+{
+ struct upd78f0730_port_private *private;
+
+ private = kzalloc_obj(*private);
+ if (!private)
+ return -ENOMEM;
+
+ mutex_init(&private->lock);
+ usb_set_serial_port_data(port, private);
+
+ return 0;
+}
+
+static void upd78f0730_port_remove(struct usb_serial_port *port)
+{
+ struct upd78f0730_port_private *private;
+
+ private = usb_get_serial_port_data(port);
+ mutex_destroy(&private->lock);
+ kfree(private);
+}
+
+static int upd78f0730_tiocmget(struct tty_struct *tty)
+{
+ struct upd78f0730_port_private *private;
+ struct usb_serial_port *port = tty->driver_data;
+ int signals;
+ int res;
+
+ private = usb_get_serial_port_data(port);
+
+ mutex_lock(&private->lock);
+ signals = private->line_signals;
+ mutex_unlock(&private->lock);
+
+ res = ((signals & UPD78F0730_DTR) ? TIOCM_DTR : 0) |
+ ((signals & UPD78F0730_RTS) ? TIOCM_RTS : 0);
+
+ dev_dbg(&port->dev, "%s - res = %x\n", __func__, res);
+
+ return res;
+}
+
+static int upd78f0730_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct upd78f0730_port_private *private;
+ struct upd78f0730_set_dtr_rts request;
+ struct device *dev = &port->dev;
+ int res;
+
+ private = usb_get_serial_port_data(port);
+
+ mutex_lock(&private->lock);
+ if (set & TIOCM_DTR) {
+ private->line_signals |= UPD78F0730_DTR;
+ dev_dbg(dev, "%s - set DTR\n", __func__);
+ }
+ if (set & TIOCM_RTS) {
+ private->line_signals |= UPD78F0730_RTS;
+ dev_dbg(dev, "%s - set RTS\n", __func__);
+ }
+ if (clear & TIOCM_DTR) {
+ private->line_signals &= ~UPD78F0730_DTR;
+ dev_dbg(dev, "%s - clear DTR\n", __func__);
+ }
+ if (clear & TIOCM_RTS) {
+ private->line_signals &= ~UPD78F0730_RTS;
+ dev_dbg(dev, "%s - clear RTS\n", __func__);
+ }
+ request.opcode = UPD78F0730_CMD_SET_DTR_RTS;
+ request.params = private->line_signals;
+
+ res = upd78f0730_send_ctl(port, &request, sizeof(request));
+ mutex_unlock(&private->lock);
+
+ return res;
+}
+
+static int upd78f0730_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct upd78f0730_port_private *private;
+ struct usb_serial_port *port = tty->driver_data;
+ struct upd78f0730_set_dtr_rts request;
+ struct device *dev = &port->dev;
+ int res;
+
+ private = usb_get_serial_port_data(port);
+
+ mutex_lock(&private->lock);
+ if (break_state) {
+ private->line_signals |= UPD78F0730_BREAK;
+ dev_dbg(dev, "%s - set BREAK\n", __func__);
+ } else {
+ private->line_signals &= ~UPD78F0730_BREAK;
+ dev_dbg(dev, "%s - clear BREAK\n", __func__);
+ }
+ request.opcode = UPD78F0730_CMD_SET_DTR_RTS;
+ request.params = private->line_signals;
+
+ res = upd78f0730_send_ctl(port, &request, sizeof(request));
+ mutex_unlock(&private->lock);
+
+ return res;
+}
+
+static void upd78f0730_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct tty_struct *tty = port->port.tty;
+ unsigned int set = 0;
+ unsigned int clear = 0;
+
+ if (on)
+ set = TIOCM_DTR | TIOCM_RTS;
+ else
+ clear = TIOCM_DTR | TIOCM_RTS;
+
+ upd78f0730_tiocmset(tty, set, clear);
+}
+
+static speed_t upd78f0730_get_baud_rate(struct tty_struct *tty)
+{
+ const speed_t baud_rate = tty_get_baud_rate(tty);
+ static const speed_t supported[] = {
+ 0, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 153600
+ };
+ int i;
+
+ for (i = ARRAY_SIZE(supported) - 1; i >= 0; i--) {
+ if (baud_rate == supported[i])
+ return baud_rate;
+ }
+
+ /* If the baud rate is not supported, switch to the default one */
+ tty_encode_baud_rate(tty, 9600, 9600);
+
+ return tty_get_baud_rate(tty);
+}
+
+static void upd78f0730_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct device *dev = &port->dev;
+ struct upd78f0730_line_control request;
+ speed_t baud_rate;
+
+ if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios))
+ return;
+
+ if (C_BAUD(tty) == B0)
+ upd78f0730_dtr_rts(port, 0);
+ else if (old_termios && (old_termios->c_cflag & CBAUD) == B0)
+ upd78f0730_dtr_rts(port, 1);
+
+ baud_rate = upd78f0730_get_baud_rate(tty);
+ request.opcode = UPD78F0730_CMD_LINE_CONTROL;
+ request.baud_rate = cpu_to_le32(baud_rate);
+ request.params = 0;
+ dev_dbg(dev, "%s - baud rate = %d\n", __func__, baud_rate);
+
+ switch (C_CSIZE(tty)) {
+ case CS7:
+ request.params |= UPD78F0730_DATA_SIZE_7_BITS;
+ dev_dbg(dev, "%s - 7 data bits\n", __func__);
+ break;
+ default:
+ tty->termios.c_cflag &= ~CSIZE;
+ tty->termios.c_cflag |= CS8;
+ dev_warn(dev, "data size is not supported, using 8 bits\n");
+ fallthrough;
+ case CS8:
+ request.params |= UPD78F0730_DATA_SIZE_8_BITS;
+ dev_dbg(dev, "%s - 8 data bits\n", __func__);
+ break;
+ }
+
+ if (C_PARENB(tty)) {
+ if (C_PARODD(tty)) {
+ request.params |= UPD78F0730_PARITY_ODD;
+ dev_dbg(dev, "%s - odd parity\n", __func__);
+ } else {
+ request.params |= UPD78F0730_PARITY_EVEN;
+ dev_dbg(dev, "%s - even parity\n", __func__);
+ }
+
+ if (C_CMSPAR(tty)) {
+ tty->termios.c_cflag &= ~CMSPAR;
+ dev_warn(dev, "MARK/SPACE parity is not supported\n");
+ }
+ } else {
+ request.params |= UPD78F0730_PARITY_NONE;
+ dev_dbg(dev, "%s - no parity\n", __func__);
+ }
+
+ if (C_CSTOPB(tty)) {
+ request.params |= UPD78F0730_STOP_BIT_2_BIT;
+ dev_dbg(dev, "%s - 2 stop bits\n", __func__);
+ } else {
+ request.params |= UPD78F0730_STOP_BIT_1_BIT;
+ dev_dbg(dev, "%s - 1 stop bit\n", __func__);
+ }
+
+ if (C_CRTSCTS(tty)) {
+ tty->termios.c_cflag &= ~CRTSCTS;
+ dev_warn(dev, "RTSCTS flow control is not supported\n");
+ }
+ if (I_IXOFF(tty) || I_IXON(tty)) {
+ tty->termios.c_iflag &= ~(IXOFF | IXON);
+ dev_warn(dev, "XON/XOFF flow control is not supported\n");
+ }
+ request.params |= UPD78F0730_FLOW_CONTROL_NONE;
+ dev_dbg(dev, "%s - no flow control\n", __func__);
+
+ upd78f0730_send_ctl(port, &request, sizeof(request));
+}
+
+static int upd78f0730_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ static const struct upd78f0730_open_close request = {
+ .opcode = UPD78F0730_CMD_OPEN_CLOSE,
+ .state = UPD78F0730_PORT_OPEN
+ };
+ int res;
+
+ res = upd78f0730_send_ctl(port, &request, sizeof(request));
+ if (res)
+ return res;
+
+ if (tty)
+ upd78f0730_set_termios(tty, port, NULL);
+
+ return usb_serial_generic_open(tty, port);
+}
+
+static void upd78f0730_close(struct usb_serial_port *port)
+{
+ static const struct upd78f0730_open_close request = {
+ .opcode = UPD78F0730_CMD_OPEN_CLOSE,
+ .state = UPD78F0730_PORT_CLOSE
+ };
+
+ usb_serial_generic_close(port);
+ upd78f0730_send_ctl(port, &request, sizeof(request));
+}
+
+static struct usb_serial_driver upd78f0730_device = {
+ .driver = {
+ .name = "upd78f0730",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .port_probe = upd78f0730_port_probe,
+ .port_remove = upd78f0730_port_remove,
+ .open = upd78f0730_open,
+ .close = upd78f0730_close,
+ .set_termios = upd78f0730_set_termios,
+ .tiocmget = upd78f0730_tiocmget,
+ .tiocmset = upd78f0730_tiocmset,
+ .dtr_rts = upd78f0730_dtr_rts,
+ .break_ctl = upd78f0730_break_ctl,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &upd78f0730_device,
+ NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/usb-serial-simple.c b/drivers/usb/serial/usb-serial-simple.c
new file mode 100644
index 000000000..a0afaf254
--- /dev/null
+++ b/drivers/usb/serial/usb-serial-simple.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Serial "Simple" driver
+ *
+ * Copyright (C) 2001-2006,2008,2013 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2005 Arthur Huillet (ahuillet@users.sf.net)
+ * Copyright (C) 2005 Thomas Hergenhahn <thomas.hergenhahn@suse.de>
+ * Copyright (C) 2009 Outpost Embedded, LLC
+ * Copyright (C) 2010 Zilogic Systems <code@zilogic.com>
+ * Copyright (C) 2013 Wei Shuai <cpuwolf@gmail.com>
+ * Copyright (C) 2013 Linux Foundation
+ */
+
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#define DEVICE_N(vendor, IDS, nport) \
+static const struct usb_device_id vendor##_id_table[] = { \
+ IDS(), \
+ { }, \
+}; \
+static struct usb_serial_driver vendor##_device = { \
+ .driver = { \
+ .name = #vendor, \
+ }, \
+ .id_table = vendor##_id_table, \
+ .num_ports = nport, \
+};
+
+#define DEVICE(vendor, IDS) DEVICE_N(vendor, IDS, 1)
+
+/* Medtronic CareLink USB driver */
+#define CARELINK_IDS() \
+ { USB_DEVICE(0x0a21, 0x8001) } /* MMT-7305WW */
+DEVICE(carelink, CARELINK_IDS);
+
+/* Infineon Flashloader driver */
+#define FLASHLOADER_IDS() \
+ { USB_DEVICE_INTERFACE_CLASS(0x058b, 0x0041, USB_CLASS_CDC_DATA) }, \
+ { USB_DEVICE(0x8087, 0x0716) }, \
+ { USB_DEVICE(0x8087, 0x0801) }
+DEVICE(flashloader, FLASHLOADER_IDS);
+
+/* Funsoft Serial USB driver */
+#define FUNSOFT_IDS() \
+ { USB_DEVICE(0x1404, 0xcddc) }
+DEVICE(funsoft, FUNSOFT_IDS);
+
+/* Google Serial USB SubClass */
+#define GOOGLE_IDS() \
+ { USB_VENDOR_AND_INTERFACE_INFO(0x18d1, \
+ USB_CLASS_VENDOR_SPEC, \
+ 0x50, \
+ 0x01) }
+DEVICE(google, GOOGLE_IDS);
+
+/* HP4x (48/49) Generic Serial driver */
+#define HP4X_IDS() \
+ { USB_DEVICE(0x03f0, 0x0121) }
+DEVICE(hp4x, HP4X_IDS);
+
+/* KAUFMANN RKS+CAN VCP */
+#define KAUFMANN_IDS() \
+ { USB_DEVICE(0x16d0, 0x0870) }
+DEVICE(kaufmann, KAUFMANN_IDS);
+
+/* Libtransistor USB console */
+#define LIBTRANSISTOR_IDS() \
+ { USB_DEVICE(0x1209, 0x8b00) }
+DEVICE(libtransistor, LIBTRANSISTOR_IDS);
+
+/* Motorola USB Phone driver */
+#define MOTO_IDS() \
+ { USB_DEVICE(0x05c6, 0x3197) }, /* unknown Motorola phone */ \
+ { USB_DEVICE(0x0c44, 0x0022) }, /* unknown Motorola phone */ \
+ { USB_DEVICE(0x22b8, 0x2a64) }, /* Motorola KRZR K1m */ \
+ { USB_DEVICE(0x22b8, 0x2c84) }, /* Motorola VE240 phone */ \
+ { USB_DEVICE(0x22b8, 0x2c64) } /* Motorola V950 phone */
+DEVICE(moto_modem, MOTO_IDS);
+
+/* Motorola Tetra driver */
+#define MOTOROLA_TETRA_IDS() \
+ { USB_DEVICE(0x0cad, 0x9011) }, /* Motorola Solutions TETRA PEI */ \
+ { USB_DEVICE(0x0cad, 0x9012) }, /* MTP6550 */ \
+ { USB_DEVICE(0x0cad, 0x9013) }, /* MTP3xxx */ \
+ { USB_DEVICE(0x0cad, 0x9015) }, /* MTP85xx */ \
+ { USB_DEVICE(0x0cad, 0x9016) } /* TPG2200 */
+DEVICE(motorola_tetra, MOTOROLA_TETRA_IDS);
+
+/* Nokia mobile phone driver */
+#define NOKIA_IDS() \
+ { USB_DEVICE(0x0421, 0x069a) } /* Nokia 130 (RM-1035) */
+DEVICE(nokia, NOKIA_IDS);
+
+/* Novatel Wireless GPS driver */
+#define NOVATEL_IDS() \
+ { USB_DEVICE(0x09d7, 0x0100) } /* NovAtel FlexPack GPS */
+DEVICE_N(novatel_gps, NOVATEL_IDS, 3);
+
+/* OWON electronic test and measurement equipment driver */
+#define OWON_IDS() \
+ { USB_DEVICE(0x5345, 0x1234) } /* HDS200 oscilloscopes and others */
+DEVICE(owon, OWON_IDS);
+
+/* Siemens USB/MPI adapter */
+#define SIEMENS_IDS() \
+ { USB_DEVICE(0x908, 0x0004) }
+DEVICE(siemens_mpi, SIEMENS_IDS);
+
+/* Suunto ANT+ USB Driver */
+#define SUUNTO_IDS() \
+ { USB_DEVICE(0x0fcf, 0x1008) }, \
+ { USB_DEVICE(0x0fcf, 0x1009) } /* Dynastream ANT USB-m Stick */
+DEVICE(suunto, SUUNTO_IDS);
+
+/* ViVOpay USB Serial Driver */
+#define VIVOPAY_IDS() \
+ { USB_DEVICE(0x1d5f, 0x1004) } /* ViVOpay 8800 */
+DEVICE(vivopay, VIVOPAY_IDS);
+
+/* ZIO Motherboard USB driver */
+#define ZIO_IDS() \
+ { USB_DEVICE(0x1CBE, 0x0103) }
+DEVICE(zio, ZIO_IDS);
+
+/* All of the above structures mushed into two lists */
+static struct usb_serial_driver * const serial_drivers[] = {
+ &carelink_device,
+ &flashloader_device,
+ &funsoft_device,
+ &google_device,
+ &hp4x_device,
+ &kaufmann_device,
+ &libtransistor_device,
+ &moto_modem_device,
+ &motorola_tetra_device,
+ &nokia_device,
+ &novatel_gps_device,
+ &owon_device,
+ &siemens_mpi_device,
+ &suunto_device,
+ &vivopay_device,
+ &zio_device,
+ NULL
+};
+
+static const struct usb_device_id id_table[] = {
+ CARELINK_IDS(),
+ FLASHLOADER_IDS(),
+ FUNSOFT_IDS(),
+ GOOGLE_IDS(),
+ HP4X_IDS(),
+ KAUFMANN_IDS(),
+ LIBTRANSISTOR_IDS(),
+ MOTO_IDS(),
+ MOTOROLA_TETRA_IDS(),
+ NOKIA_IDS(),
+ NOVATEL_IDS(),
+ OWON_IDS(),
+ SIEMENS_IDS(),
+ SUUNTO_IDS(),
+ VIVOPAY_IDS(),
+ ZIO_IDS(),
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+module_usb_serial_driver(serial_drivers, id_table);
+MODULE_DESCRIPTION("USB Serial 'Simple' driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c
new file mode 100644
index 000000000..17edc057a
--- /dev/null
+++ b/drivers/usb/serial/usb-serial.c
@@ -0,0 +1,1550 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Serial Converter driver
+ *
+ * Copyright (C) 2009 - 2013 Johan Hovold (jhovold@gmail.com)
+ * Copyright (C) 1999 - 2012 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2000 Peter Berger (pberger@brimson.com)
+ * Copyright (C) 2000 Al Borchers (borchers@steinerpoint.com)
+ *
+ * This driver was originally based on the ACM driver by Armin Fuerst (which was
+ * based on a driver by Brad Keryan)
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/serial.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/kfifo.h>
+#include <linux/idr.h>
+
+#define DRIVER_AUTHOR "Greg Kroah-Hartman <gregkh@linuxfoundation.org>"
+#define DRIVER_DESC "USB Serial Driver core"
+
+#define USB_SERIAL_TTY_MAJOR 188
+#define USB_SERIAL_TTY_MINORS 512 /* should be enough for a while */
+
+/* There is no MODULE_DEVICE_TABLE for usbserial.c. Instead
+ the MODULE_DEVICE_TABLE declarations in each serial driver
+ cause the "hotplug" program to pull in whatever module is necessary
+ via modprobe, and modprobe will load usbserial because the serial
+ drivers depend on it.
+*/
+
+static DEFINE_IDR(serial_minors);
+static DEFINE_MUTEX(table_lock);
+static LIST_HEAD(usb_serial_driver_list);
+
+/*
+ * Look up the serial port structure. If it is found and it hasn't been
+ * disconnected, return with the parent usb_serial structure's disc_mutex held
+ * and its refcount incremented. Otherwise return NULL.
+ */
+struct usb_serial_port *usb_serial_port_get_by_minor(unsigned minor)
+{
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+
+ mutex_lock(&table_lock);
+ port = idr_find(&serial_minors, minor);
+ if (!port)
+ goto exit;
+
+ serial = port->serial;
+ mutex_lock(&serial->disc_mutex);
+ if (serial->disconnected) {
+ mutex_unlock(&serial->disc_mutex);
+ port = NULL;
+ } else {
+ kref_get(&serial->kref);
+ }
+exit:
+ mutex_unlock(&table_lock);
+ return port;
+}
+
+static int allocate_minors(struct usb_serial *serial, int num_ports)
+{
+ struct usb_serial_port *port;
+ unsigned int i, j;
+ int minor;
+
+ dev_dbg(&serial->interface->dev, "%s %d\n", __func__, num_ports);
+
+ mutex_lock(&table_lock);
+ for (i = 0; i < num_ports; ++i) {
+ port = serial->port[i];
+ minor = idr_alloc(&serial_minors, port, 0,
+ USB_SERIAL_TTY_MINORS, GFP_KERNEL);
+ if (minor < 0)
+ goto error;
+ port->minor = minor;
+ port->port_number = i;
+ }
+ serial->minors_reserved = 1;
+ mutex_unlock(&table_lock);
+ return 0;
+error:
+ /* unwind the already allocated minors */
+ for (j = 0; j < i; ++j)
+ idr_remove(&serial_minors, serial->port[j]->minor);
+ mutex_unlock(&table_lock);
+ return minor;
+}
+
+static void release_minors(struct usb_serial *serial)
+{
+ int i;
+
+ mutex_lock(&table_lock);
+ for (i = 0; i < serial->num_ports; ++i)
+ idr_remove(&serial_minors, serial->port[i]->minor);
+ mutex_unlock(&table_lock);
+ serial->minors_reserved = 0;
+}
+
+int usb_serial_claim_interface(struct usb_serial *serial, struct usb_interface *intf)
+{
+ struct usb_driver *driver = serial->type->usb_driver;
+ int ret;
+
+ if (serial->sibling)
+ return -EBUSY;
+
+ ret = usb_driver_claim_interface(driver, intf, serial);
+ if (ret) {
+ dev_err(&serial->interface->dev,
+ "failed to claim sibling interface: %d\n", ret);
+ return ret;
+ }
+
+ serial->sibling = intf;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_serial_claim_interface);
+
+static void release_sibling(struct usb_serial *serial, struct usb_interface *intf)
+{
+ struct usb_driver *driver = serial->type->usb_driver;
+ struct usb_interface *sibling;
+
+ if (!serial->sibling)
+ return;
+
+ if (intf == serial->sibling)
+ sibling = serial->interface;
+ else
+ sibling = serial->sibling;
+
+ usb_set_intfdata(sibling, NULL);
+ usb_driver_release_interface(driver, sibling);
+}
+
+static void destroy_serial(struct kref *kref)
+{
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+ int i;
+
+ serial = to_usb_serial(kref);
+
+ /* return the minor range that this device had */
+ if (serial->minors_reserved)
+ release_minors(serial);
+
+ if (serial->attached && serial->type->release)
+ serial->type->release(serial);
+
+ /* Now that nothing is using the ports, they can be freed */
+ for (i = 0; i < serial->num_port_pointers; ++i) {
+ port = serial->port[i];
+ if (port) {
+ port->serial = NULL;
+ put_device(&port->dev);
+ }
+ }
+
+ usb_put_intf(serial->interface);
+ usb_put_dev(serial->dev);
+ kfree(serial);
+}
+
+void usb_serial_put(struct usb_serial *serial)
+{
+ kref_put(&serial->kref, destroy_serial);
+}
+
+/*****************************************************************************
+ * Driver tty interface functions
+ *****************************************************************************/
+
+/**
+ * serial_install - install tty
+ * @driver: the driver (USB in our case)
+ * @tty: the tty being created
+ *
+ * Initialise the termios structure for this tty. We use the default
+ * USB serial settings but permit them to be overridden by
+ * serial->type->init_termios on first open.
+ *
+ * This is the first place a new tty gets used. Hence this is where we
+ * acquire references to the usb_serial structure and the driver module,
+ * where we store a pointer to the port. All these actions are reversed
+ * in serial_cleanup().
+ */
+static int serial_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+ int idx = tty->index;
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+ bool init_termios;
+ int retval = -ENODEV;
+
+ port = usb_serial_port_get_by_minor(idx);
+ if (!port)
+ return retval;
+
+ serial = port->serial;
+ if (!try_module_get(serial->type->driver.owner))
+ goto err_put_serial;
+
+ init_termios = (driver->termios[idx] == NULL);
+
+ retval = tty_standard_install(driver, tty);
+ if (retval)
+ goto err_put_module;
+
+ mutex_unlock(&serial->disc_mutex);
+
+ /* allow the driver to update the initial settings */
+ if (init_termios && serial->type->init_termios)
+ serial->type->init_termios(tty);
+
+ tty->driver_data = port;
+
+ return retval;
+
+err_put_module:
+ module_put(serial->type->driver.owner);
+err_put_serial:
+ usb_serial_put(serial);
+ mutex_unlock(&serial->disc_mutex);
+ return retval;
+}
+
+static int serial_port_activate(struct tty_port *tport, struct tty_struct *tty)
+{
+ struct usb_serial_port *port =
+ container_of(tport, struct usb_serial_port, port);
+ struct usb_serial *serial = port->serial;
+ int retval;
+
+ mutex_lock(&serial->disc_mutex);
+ if (serial->disconnected) {
+ retval = -ENODEV;
+ goto out_unlock;
+ }
+
+ retval = usb_autopm_get_interface(serial->interface);
+ if (retval)
+ goto out_unlock;
+
+ retval = port->serial->type->open(tty, port);
+ if (retval)
+ usb_autopm_put_interface(serial->interface);
+out_unlock:
+ mutex_unlock(&serial->disc_mutex);
+
+ if (retval < 0)
+ retval = usb_translate_errors(retval);
+
+ return retval;
+}
+
+static int serial_open(struct tty_struct *tty, struct file *filp)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ return tty_port_open(&port->port, tty, filp);
+}
+
+/**
+ * serial_port_shutdown - shut down hardware
+ * @tport: tty port to shut down
+ *
+ * Shut down a USB serial port. Serialized against activate by the
+ * tport mutex and kept to matching open/close pairs
+ * of calls by the tty-port initialized flag.
+ *
+ * Not called if tty is console.
+ */
+static void serial_port_shutdown(struct tty_port *tport)
+{
+ struct usb_serial_port *port =
+ container_of(tport, struct usb_serial_port, port);
+ struct usb_serial_driver *drv = port->serial->type;
+
+ if (drv->close)
+ drv->close(port);
+
+ usb_autopm_put_interface(port->serial->interface);
+}
+
+static void serial_hangup(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ tty_port_hangup(&port->port);
+}
+
+static void serial_close(struct tty_struct *tty, struct file *filp)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ tty_port_close(&port->port, tty, filp);
+}
+
+/**
+ * serial_cleanup - free resources post close/hangup
+ * @tty: tty to clean up
+ *
+ * Do the resource freeing and refcount dropping for the port.
+ * Avoid freeing the console.
+ *
+ * Called asynchronously after the last tty kref is dropped.
+ */
+static void serial_cleanup(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_serial *serial;
+ struct module *owner;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ /* The console is magical. Do not hang up the console hardware
+ * or there will be tears.
+ */
+ if (port->port.console)
+ return;
+
+ tty->driver_data = NULL;
+
+ serial = port->serial;
+ owner = serial->type->driver.owner;
+
+ usb_serial_put(serial);
+ module_put(owner);
+}
+
+static ssize_t serial_write(struct tty_struct *tty, const u8 *buf, size_t count)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ int retval = -ENODEV;
+
+ if (port->serial->dev->state == USB_STATE_NOTATTACHED)
+ goto exit;
+
+ dev_dbg(&port->dev, "%s - %zu byte(s)\n", __func__, count);
+
+ retval = port->serial->type->write(tty, port, buf, count);
+ if (retval < 0)
+ retval = usb_translate_errors(retval);
+exit:
+ return retval;
+}
+
+static unsigned int serial_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ return port->serial->type->write_room(tty);
+}
+
+static unsigned int serial_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_serial *serial = port->serial;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ if (serial->disconnected)
+ return 0;
+
+ return serial->type->chars_in_buffer(tty);
+}
+
+static void serial_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_serial *serial = port->serial;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ if (!port->serial->type->wait_until_sent)
+ return;
+
+ mutex_lock(&serial->disc_mutex);
+ if (!serial->disconnected)
+ port->serial->type->wait_until_sent(tty, timeout);
+ mutex_unlock(&serial->disc_mutex);
+}
+
+static void serial_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ if (port->serial->type->throttle)
+ port->serial->type->throttle(tty);
+}
+
+static void serial_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ if (port->serial->type->unthrottle)
+ port->serial->type->unthrottle(tty);
+}
+
+static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct tty_port *tport = &port->port;
+ unsigned int close_delay, closing_wait;
+
+ mutex_lock(&tport->mutex);
+
+ close_delay = jiffies_to_msecs(tport->close_delay) / 10;
+ closing_wait = tport->closing_wait;
+ if (closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ closing_wait = jiffies_to_msecs(closing_wait) / 10;
+
+ ss->line = port->minor;
+ ss->close_delay = close_delay;
+ ss->closing_wait = closing_wait;
+
+ if (port->serial->type->get_serial)
+ port->serial->type->get_serial(tty, ss);
+
+ mutex_unlock(&tport->mutex);
+
+ return 0;
+}
+
+static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct tty_port *tport = &port->port;
+ unsigned int close_delay, closing_wait;
+ int ret = 0;
+
+ close_delay = msecs_to_jiffies(ss->close_delay * 10);
+ closing_wait = ss->closing_wait;
+ if (closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ closing_wait = msecs_to_jiffies(closing_wait * 10);
+
+ mutex_lock(&tport->mutex);
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if (close_delay != tport->close_delay ||
+ closing_wait != tport->closing_wait) {
+ ret = -EPERM;
+ goto out_unlock;
+ }
+ }
+
+ if (port->serial->type->set_serial) {
+ ret = port->serial->type->set_serial(tty, ss);
+ if (ret)
+ goto out_unlock;
+ }
+
+ tport->close_delay = close_delay;
+ tport->closing_wait = closing_wait;
+out_unlock:
+ mutex_unlock(&tport->mutex);
+
+ return ret;
+}
+
+static int serial_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ int retval = -ENOIOCTLCMD;
+
+ dev_dbg(&port->dev, "%s - cmd 0x%04x\n", __func__, cmd);
+
+ switch (cmd) {
+ case TIOCMIWAIT:
+ if (port->serial->type->tiocmiwait)
+ retval = port->serial->type->tiocmiwait(tty, arg);
+ break;
+ default:
+ if (port->serial->type->ioctl)
+ retval = port->serial->type->ioctl(tty, cmd, arg);
+ }
+
+ return retval;
+}
+
+static void serial_set_termios(struct tty_struct *tty,
+ const struct ktermios *old)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ if (port->serial->type->set_termios)
+ port->serial->type->set_termios(tty, port, old);
+ else
+ tty_termios_copy_hw(&tty->termios, old);
+}
+
+static int serial_break(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ if (port->serial->type->break_ctl)
+ return port->serial->type->break_ctl(tty, break_state);
+
+ return -ENOTTY;
+}
+
+static int serial_proc_show(struct seq_file *m, void *v)
+{
+ struct usb_serial *serial;
+ struct usb_serial_port *port;
+ int i;
+ char tmp[40];
+
+ seq_puts(m, "usbserinfo:1.0 driver:2.0\n");
+ for (i = 0; i < USB_SERIAL_TTY_MINORS; ++i) {
+ port = usb_serial_port_get_by_minor(i);
+ if (port == NULL)
+ continue;
+ serial = port->serial;
+
+ seq_printf(m, "%d:", i);
+ if (serial->type->driver.owner)
+ seq_printf(m, " module:%s",
+ module_name(serial->type->driver.owner));
+ seq_printf(m, " name:\"%s\"",
+ serial->type->description);
+ seq_printf(m, " vendor:%04x product:%04x",
+ le16_to_cpu(serial->dev->descriptor.idVendor),
+ le16_to_cpu(serial->dev->descriptor.idProduct));
+ seq_printf(m, " num_ports:%d", serial->num_ports);
+ seq_printf(m, " port:%d", port->port_number);
+ usb_make_path(serial->dev, tmp, sizeof(tmp));
+ seq_printf(m, " path:%s", tmp);
+
+ seq_putc(m, '\n');
+ usb_serial_put(serial);
+ mutex_unlock(&serial->disc_mutex);
+ }
+ return 0;
+}
+
+static int serial_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ if (port->serial->type->tiocmget)
+ return port->serial->type->tiocmget(tty);
+ return -ENOTTY;
+}
+
+static int serial_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ if (port->serial->type->tiocmset)
+ return port->serial->type->tiocmset(tty, set, clear);
+ return -ENOTTY;
+}
+
+static int serial_get_icount(struct tty_struct *tty,
+ struct serial_icounter_struct *icount)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ if (port->serial->type->get_icount)
+ return port->serial->type->get_icount(tty, icount);
+ return -ENOTTY;
+}
+
+/*
+ * We would be calling tty_wakeup here, but unfortunately some line
+ * disciplines have an annoying habit of calling tty->write from
+ * the write wakeup callback (e.g. n_hdlc.c).
+ */
+void usb_serial_port_softint(struct usb_serial_port *port)
+{
+ schedule_work(&port->work);
+}
+EXPORT_SYMBOL_GPL(usb_serial_port_softint);
+
+static void usb_serial_port_work(struct work_struct *work)
+{
+ struct usb_serial_port *port =
+ container_of(work, struct usb_serial_port, work);
+
+ tty_port_tty_wakeup(&port->port);
+}
+
+static void usb_serial_port_poison_urbs(struct usb_serial_port *port)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i)
+ usb_poison_urb(port->read_urbs[i]);
+ for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i)
+ usb_poison_urb(port->write_urbs[i]);
+
+ usb_poison_urb(port->interrupt_in_urb);
+ usb_poison_urb(port->interrupt_out_urb);
+}
+
+static void usb_serial_port_unpoison_urbs(struct usb_serial_port *port)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i)
+ usb_unpoison_urb(port->read_urbs[i]);
+ for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i)
+ usb_unpoison_urb(port->write_urbs[i]);
+
+ usb_unpoison_urb(port->interrupt_in_urb);
+ usb_unpoison_urb(port->interrupt_out_urb);
+}
+
+static void usb_serial_port_release(struct device *dev)
+{
+ struct usb_serial_port *port = to_usb_serial_port(dev);
+ int i;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ usb_free_urb(port->interrupt_in_urb);
+ usb_free_urb(port->interrupt_out_urb);
+ for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) {
+ usb_free_urb(port->read_urbs[i]);
+ kfree(port->bulk_in_buffers[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) {
+ usb_free_urb(port->write_urbs[i]);
+ kfree(port->bulk_out_buffers[i]);
+ }
+ kfifo_free(&port->write_fifo);
+ kfree(port->interrupt_in_buffer);
+ kfree(port->interrupt_out_buffer);
+ tty_port_destroy(&port->port);
+ kfree(port);
+}
+
+static struct usb_serial *create_serial(struct usb_device *dev,
+ struct usb_interface *interface,
+ struct usb_serial_driver *driver)
+{
+ struct usb_serial *serial;
+
+ serial = kzalloc_obj(*serial);
+ if (!serial)
+ return NULL;
+ serial->dev = usb_get_dev(dev);
+ serial->type = driver;
+ serial->interface = usb_get_intf(interface);
+ kref_init(&serial->kref);
+ mutex_init(&serial->disc_mutex);
+ serial->minors_reserved = 0;
+
+ return serial;
+}
+
+static const struct usb_device_id *match_dynamic_id(struct usb_interface *intf,
+ struct usb_serial_driver *drv)
+{
+ struct usb_dynid *dynid;
+
+ guard(mutex)(&usb_dynids_lock);
+ list_for_each_entry(dynid, &drv->dynids.list, node) {
+ if (usb_match_one_id(intf, &dynid->id)) {
+ return &dynid->id;
+ }
+ }
+ return NULL;
+}
+
+static const struct usb_device_id *get_iface_id(struct usb_serial_driver *drv,
+ struct usb_interface *intf)
+{
+ const struct usb_device_id *id;
+
+ id = usb_match_id(intf, drv->id_table);
+ if (id) {
+ dev_dbg(&intf->dev, "static descriptor matches\n");
+ goto exit;
+ }
+ id = match_dynamic_id(intf, drv);
+ if (id)
+ dev_dbg(&intf->dev, "dynamic descriptor matches\n");
+exit:
+ return id;
+}
+
+/* Caller must hold table_lock */
+static struct usb_serial_driver *search_serial_device(
+ struct usb_interface *iface)
+{
+ const struct usb_device_id *id = NULL;
+ struct usb_serial_driver *drv;
+ struct usb_driver *driver = to_usb_driver(iface->dev.driver);
+
+ /* Check if the usb id matches a known device */
+ list_for_each_entry(drv, &usb_serial_driver_list, driver_list) {
+ if (drv->usb_driver == driver)
+ id = get_iface_id(drv, iface);
+ if (id)
+ return drv;
+ }
+
+ return NULL;
+}
+
+static bool serial_port_carrier_raised(struct tty_port *port)
+{
+ struct usb_serial_port *p = container_of(port, struct usb_serial_port, port);
+ struct usb_serial_driver *drv = p->serial->type;
+
+ if (drv->carrier_raised)
+ return drv->carrier_raised(p);
+ /* No carrier control - don't block */
+ return true;
+}
+
+static void serial_port_dtr_rts(struct tty_port *port, bool on)
+{
+ struct usb_serial_port *p = container_of(port, struct usb_serial_port, port);
+ struct usb_serial_driver *drv = p->serial->type;
+
+ if (drv->dtr_rts)
+ drv->dtr_rts(p, on);
+}
+
+static ssize_t port_number_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_serial_port *port = to_usb_serial_port(dev);
+
+ return sprintf(buf, "%u\n", port->port_number);
+}
+static DEVICE_ATTR_RO(port_number);
+
+static struct attribute *usb_serial_port_attrs[] = {
+ &dev_attr_port_number.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(usb_serial_port);
+
+static const struct tty_port_operations serial_port_ops = {
+ .carrier_raised = serial_port_carrier_raised,
+ .dtr_rts = serial_port_dtr_rts,
+ .activate = serial_port_activate,
+ .shutdown = serial_port_shutdown,
+};
+
+static void store_endpoint(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds,
+ struct usb_endpoint_descriptor *epd)
+{
+ struct device *dev = &serial->interface->dev;
+ u8 addr = epd->bEndpointAddress;
+
+ if (usb_endpoint_is_bulk_in(epd)) {
+ if (epds->num_bulk_in == ARRAY_SIZE(epds->bulk_in))
+ return;
+ dev_dbg(dev, "found bulk in endpoint %02x\n", addr);
+ epds->bulk_in[epds->num_bulk_in++] = epd;
+ } else if (usb_endpoint_is_bulk_out(epd)) {
+ if (epds->num_bulk_out == ARRAY_SIZE(epds->bulk_out))
+ return;
+ dev_dbg(dev, "found bulk out endpoint %02x\n", addr);
+ epds->bulk_out[epds->num_bulk_out++] = epd;
+ } else if (usb_endpoint_is_int_in(epd)) {
+ if (epds->num_interrupt_in == ARRAY_SIZE(epds->interrupt_in))
+ return;
+ dev_dbg(dev, "found interrupt in endpoint %02x\n", addr);
+ epds->interrupt_in[epds->num_interrupt_in++] = epd;
+ } else if (usb_endpoint_is_int_out(epd)) {
+ if (epds->num_interrupt_out == ARRAY_SIZE(epds->interrupt_out))
+ return;
+ dev_dbg(dev, "found interrupt out endpoint %02x\n", addr);
+ epds->interrupt_out[epds->num_interrupt_out++] = epd;
+ }
+}
+
+static void find_endpoints(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds,
+ struct usb_interface *intf)
+{
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *epd;
+ unsigned int i;
+
+ iface_desc = intf->cur_altsetting;
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+ epd = &iface_desc->endpoint[i].desc;
+ store_endpoint(serial, epds, epd);
+ }
+}
+
+static int setup_port_bulk_in(struct usb_serial_port *port,
+ struct usb_endpoint_descriptor *epd)
+{
+ struct usb_serial_driver *type = port->serial->type;
+ struct usb_device *udev = port->serial->dev;
+ int buffer_size;
+ int i;
+
+ buffer_size = max_t(int, type->bulk_in_size, usb_endpoint_maxp(epd));
+ port->bulk_in_size = buffer_size;
+ port->bulk_in_endpointAddress = epd->bEndpointAddress;
+
+ for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) {
+ set_bit(i, &port->read_urbs_free);
+ port->read_urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
+ if (!port->read_urbs[i])
+ return -ENOMEM;
+ port->bulk_in_buffers[i] = kmalloc(buffer_size, GFP_KERNEL);
+ if (!port->bulk_in_buffers[i])
+ return -ENOMEM;
+ usb_fill_bulk_urb(port->read_urbs[i], udev,
+ usb_rcvbulkpipe(udev, epd->bEndpointAddress),
+ port->bulk_in_buffers[i], buffer_size,
+ type->read_bulk_callback, port);
+ }
+
+ port->read_urb = port->read_urbs[0];
+ port->bulk_in_buffer = port->bulk_in_buffers[0];
+
+ return 0;
+}
+
+static int setup_port_bulk_out(struct usb_serial_port *port,
+ struct usb_endpoint_descriptor *epd)
+{
+ struct usb_serial_driver *type = port->serial->type;
+ struct usb_device *udev = port->serial->dev;
+ int buffer_size;
+ int i;
+
+ if (kfifo_alloc(&port->write_fifo, PAGE_SIZE, GFP_KERNEL))
+ return -ENOMEM;
+ if (type->bulk_out_size)
+ buffer_size = type->bulk_out_size;
+ else
+ buffer_size = usb_endpoint_maxp(epd);
+ port->bulk_out_size = buffer_size;
+ port->bulk_out_endpointAddress = epd->bEndpointAddress;
+
+ for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) {
+ set_bit(i, &port->write_urbs_free);
+ port->write_urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
+ if (!port->write_urbs[i])
+ return -ENOMEM;
+ port->bulk_out_buffers[i] = kmalloc(buffer_size, GFP_KERNEL);
+ if (!port->bulk_out_buffers[i])
+ return -ENOMEM;
+ usb_fill_bulk_urb(port->write_urbs[i], udev,
+ usb_sndbulkpipe(udev, epd->bEndpointAddress),
+ port->bulk_out_buffers[i], buffer_size,
+ type->write_bulk_callback, port);
+ }
+
+ port->write_urb = port->write_urbs[0];
+ port->bulk_out_buffer = port->bulk_out_buffers[0];
+
+ return 0;
+}
+
+static int setup_port_interrupt_in(struct usb_serial_port *port,
+ struct usb_endpoint_descriptor *epd)
+{
+ struct usb_serial_driver *type = port->serial->type;
+ struct usb_device *udev = port->serial->dev;
+ int buffer_size;
+
+ port->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!port->interrupt_in_urb)
+ return -ENOMEM;
+ buffer_size = usb_endpoint_maxp(epd);
+ port->interrupt_in_endpointAddress = epd->bEndpointAddress;
+ port->interrupt_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (!port->interrupt_in_buffer)
+ return -ENOMEM;
+ usb_fill_int_urb(port->interrupt_in_urb, udev,
+ usb_rcvintpipe(udev, epd->bEndpointAddress),
+ port->interrupt_in_buffer, buffer_size,
+ type->read_int_callback, port,
+ epd->bInterval);
+
+ return 0;
+}
+
+static int setup_port_interrupt_out(struct usb_serial_port *port,
+ struct usb_endpoint_descriptor *epd)
+{
+ struct usb_serial_driver *type = port->serial->type;
+ struct usb_device *udev = port->serial->dev;
+ int buffer_size;
+
+ port->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!port->interrupt_out_urb)
+ return -ENOMEM;
+ buffer_size = usb_endpoint_maxp(epd);
+ port->interrupt_out_size = buffer_size;
+ port->interrupt_out_endpointAddress = epd->bEndpointAddress;
+ port->interrupt_out_buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (!port->interrupt_out_buffer)
+ return -ENOMEM;
+ usb_fill_int_urb(port->interrupt_out_urb, udev,
+ usb_sndintpipe(udev, epd->bEndpointAddress),
+ port->interrupt_out_buffer, buffer_size,
+ type->write_int_callback, port,
+ epd->bInterval);
+
+ return 0;
+}
+
+static int usb_serial_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct device *ddev = &interface->dev;
+ struct usb_device *dev = interface_to_usbdev(interface);
+ struct usb_serial *serial = NULL;
+ struct usb_serial_port *port;
+ struct usb_serial_endpoints *epds;
+ struct usb_serial_driver *type = NULL;
+ int retval;
+ int i;
+ int num_ports = 0;
+ unsigned char max_endpoints;
+
+ mutex_lock(&table_lock);
+ type = search_serial_device(interface);
+ if (!type) {
+ mutex_unlock(&table_lock);
+ dev_dbg(ddev, "none matched\n");
+ return -ENODEV;
+ }
+
+ if (!try_module_get(type->driver.owner)) {
+ mutex_unlock(&table_lock);
+ dev_err(ddev, "module get failed, exiting\n");
+ return -EIO;
+ }
+ mutex_unlock(&table_lock);
+
+ serial = create_serial(dev, interface, type);
+ if (!serial) {
+ retval = -ENOMEM;
+ goto err_put_module;
+ }
+
+ /* if this device type has a probe function, call it */
+ if (type->probe) {
+ const struct usb_device_id *id;
+
+ id = get_iface_id(type, interface);
+ retval = type->probe(serial, id);
+
+ if (retval) {
+ dev_dbg(ddev, "sub driver rejected device\n");
+ goto err_release_sibling;
+ }
+ }
+
+ /* descriptor matches, let's find the endpoints needed */
+ epds = kzalloc_obj(*epds);
+ if (!epds) {
+ retval = -ENOMEM;
+ goto err_release_sibling;
+ }
+
+ find_endpoints(serial, epds, interface);
+ if (serial->sibling)
+ find_endpoints(serial, epds, serial->sibling);
+
+ if (epds->num_bulk_in < type->num_bulk_in ||
+ epds->num_bulk_out < type->num_bulk_out ||
+ epds->num_interrupt_in < type->num_interrupt_in ||
+ epds->num_interrupt_out < type->num_interrupt_out) {
+ dev_err(ddev, "required endpoints missing\n");
+ retval = -ENODEV;
+ goto err_free_epds;
+ }
+
+ if (type->calc_num_ports) {
+ retval = type->calc_num_ports(serial, epds);
+ if (retval < 0)
+ goto err_free_epds;
+ num_ports = retval;
+ }
+
+ if (!num_ports)
+ num_ports = type->num_ports;
+
+ if (num_ports > MAX_NUM_PORTS) {
+ dev_warn(ddev, "too many ports requested: %d\n", num_ports);
+ num_ports = MAX_NUM_PORTS;
+ }
+
+ serial->num_ports = (unsigned char)num_ports;
+ serial->num_bulk_in = epds->num_bulk_in;
+ serial->num_bulk_out = epds->num_bulk_out;
+ serial->num_interrupt_in = epds->num_interrupt_in;
+ serial->num_interrupt_out = epds->num_interrupt_out;
+
+ /* found all that we need */
+ dev_info(ddev, "%s converter detected\n", type->description);
+
+ /* create our ports, we need as many as the max endpoints */
+ /* we don't use num_ports here because some devices have more
+ endpoint pairs than ports */
+ max_endpoints = max(epds->num_bulk_in, epds->num_bulk_out);
+ max_endpoints = max(max_endpoints, epds->num_interrupt_in);
+ max_endpoints = max(max_endpoints, epds->num_interrupt_out);
+ max_endpoints = max(max_endpoints, serial->num_ports);
+ serial->num_port_pointers = max_endpoints;
+
+ dev_dbg(ddev, "setting up %d port structure(s)\n", max_endpoints);
+ for (i = 0; i < max_endpoints; ++i) {
+ port = kzalloc_obj(struct usb_serial_port);
+ if (!port) {
+ retval = -ENOMEM;
+ goto err_free_epds;
+ }
+ tty_port_init(&port->port);
+ port->port.ops = &serial_port_ops;
+ port->serial = serial;
+ spin_lock_init(&port->lock);
+ /* Keep this for private driver use for the moment but
+ should probably go away */
+ INIT_WORK(&port->work, usb_serial_port_work);
+ serial->port[i] = port;
+ port->dev.parent = &interface->dev;
+ port->dev.driver = NULL;
+ port->dev.bus = &usb_serial_bus_type;
+ port->dev.release = &usb_serial_port_release;
+ port->dev.groups = usb_serial_port_groups;
+ device_initialize(&port->dev);
+ }
+
+ /* set up the endpoint information */
+ for (i = 0; i < epds->num_bulk_in; ++i) {
+ retval = setup_port_bulk_in(serial->port[i], epds->bulk_in[i]);
+ if (retval)
+ goto err_free_epds;
+ }
+
+ for (i = 0; i < epds->num_bulk_out; ++i) {
+ retval = setup_port_bulk_out(serial->port[i],
+ epds->bulk_out[i]);
+ if (retval)
+ goto err_free_epds;
+ }
+
+ if (serial->type->read_int_callback) {
+ for (i = 0; i < epds->num_interrupt_in; ++i) {
+ retval = setup_port_interrupt_in(serial->port[i],
+ epds->interrupt_in[i]);
+ if (retval)
+ goto err_free_epds;
+ }
+ } else if (epds->num_interrupt_in) {
+ dev_dbg(ddev, "The device claims to support interrupt in transfers, but read_int_callback is not defined\n");
+ }
+
+ if (serial->type->write_int_callback) {
+ for (i = 0; i < epds->num_interrupt_out; ++i) {
+ retval = setup_port_interrupt_out(serial->port[i],
+ epds->interrupt_out[i]);
+ if (retval)
+ goto err_free_epds;
+ }
+ } else if (epds->num_interrupt_out) {
+ dev_dbg(ddev, "The device claims to support interrupt out transfers, but write_int_callback is not defined\n");
+ }
+
+ usb_set_intfdata(interface, serial);
+
+ /* if this device type has an attach function, call it */
+ if (type->attach) {
+ retval = type->attach(serial);
+ if (retval < 0)
+ goto err_free_epds;
+ serial->attached = 1;
+ if (retval > 0) {
+ /* quietly accept this device, but don't bind to a
+ serial port as it's about to disappear */
+ serial->num_ports = 0;
+ goto exit;
+ }
+ } else {
+ serial->attached = 1;
+ }
+
+ retval = allocate_minors(serial, num_ports);
+ if (retval) {
+ dev_err(ddev, "No more free serial minor numbers\n");
+ goto err_free_epds;
+ }
+
+ /* register all of the individual ports with the driver core */
+ for (i = 0; i < num_ports; ++i) {
+ port = serial->port[i];
+ dev_set_name(&port->dev, "ttyUSB%d", port->minor);
+ dev_dbg(ddev, "registering %s\n", dev_name(&port->dev));
+ device_enable_async_suspend(&port->dev);
+
+ retval = device_add(&port->dev);
+ if (retval)
+ dev_err(ddev, "Error registering port device, continuing\n");
+ }
+
+ if (num_ports > 0)
+ usb_serial_console_init(serial->port[0]->minor);
+exit:
+ kfree(epds);
+ module_put(type->driver.owner);
+ return 0;
+
+err_free_epds:
+ kfree(epds);
+err_release_sibling:
+ release_sibling(serial, interface);
+ usb_serial_put(serial);
+err_put_module:
+ module_put(type->driver.owner);
+
+ return retval;
+}
+
+static void usb_serial_disconnect(struct usb_interface *interface)
+{
+ int i;
+ struct usb_serial *serial = usb_get_intfdata(interface);
+ struct device *dev = &interface->dev;
+ struct usb_serial_port *port;
+
+ /* sibling interface is cleaning up */
+ if (!serial)
+ return;
+
+ usb_serial_console_disconnect(serial);
+
+ mutex_lock(&serial->disc_mutex);
+ /* must set a flag, to signal subdrivers */
+ serial->disconnected = 1;
+ mutex_unlock(&serial->disc_mutex);
+
+ for (i = 0; i < serial->num_ports; ++i) {
+ port = serial->port[i];
+ tty_port_tty_vhangup(&port->port);
+ usb_serial_port_poison_urbs(port);
+ wake_up_interruptible(&port->port.delta_msr_wait);
+ cancel_work_sync(&port->work);
+ if (device_is_registered(&port->dev))
+ device_del(&port->dev);
+ }
+ if (serial->type->disconnect)
+ serial->type->disconnect(serial);
+
+ release_sibling(serial, interface);
+
+ /* let the last holder of this object cause it to be cleaned up */
+ usb_serial_put(serial);
+ dev_info(dev, "device disconnected\n");
+}
+
+int usb_serial_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct usb_serial *serial = usb_get_intfdata(intf);
+ int i, r;
+
+ /* suspend when called for first sibling interface */
+ if (serial->suspend_count++)
+ return 0;
+
+ /*
+ * serial->type->suspend() MUST return 0 in system sleep context,
+ * otherwise, the resume callback has to recover device from
+ * previous suspend failure.
+ */
+ if (serial->type->suspend) {
+ r = serial->type->suspend(serial, message);
+ if (r < 0) {
+ serial->suspend_count--;
+ return r;
+ }
+ }
+
+ for (i = 0; i < serial->num_ports; ++i)
+ usb_serial_port_poison_urbs(serial->port[i]);
+
+ return 0;
+}
+EXPORT_SYMBOL(usb_serial_suspend);
+
+static void usb_serial_unpoison_port_urbs(struct usb_serial *serial)
+{
+ int i;
+
+ for (i = 0; i < serial->num_ports; ++i)
+ usb_serial_port_unpoison_urbs(serial->port[i]);
+}
+
+int usb_serial_resume(struct usb_interface *intf)
+{
+ struct usb_serial *serial = usb_get_intfdata(intf);
+ int rv;
+
+ /* resume when called for last sibling interface */
+ if (--serial->suspend_count)
+ return 0;
+
+ usb_serial_unpoison_port_urbs(serial);
+
+ if (serial->type->resume)
+ rv = serial->type->resume(serial);
+ else
+ rv = usb_serial_generic_resume(serial);
+
+ return rv;
+}
+EXPORT_SYMBOL(usb_serial_resume);
+
+static int usb_serial_reset_resume(struct usb_interface *intf)
+{
+ struct usb_serial *serial = usb_get_intfdata(intf);
+ int rv;
+
+ /* resume when called for last sibling interface */
+ if (--serial->suspend_count)
+ return 0;
+
+ usb_serial_unpoison_port_urbs(serial);
+
+ if (serial->type->reset_resume) {
+ rv = serial->type->reset_resume(serial);
+ } else {
+ rv = -EOPNOTSUPP;
+ intf->needs_binding = 1;
+ }
+
+ return rv;
+}
+
+static const struct tty_operations serial_ops = {
+ .open = serial_open,
+ .close = serial_close,
+ .write = serial_write,
+ .hangup = serial_hangup,
+ .write_room = serial_write_room,
+ .ioctl = serial_ioctl,
+ .set_termios = serial_set_termios,
+ .throttle = serial_throttle,
+ .unthrottle = serial_unthrottle,
+ .break_ctl = serial_break,
+ .chars_in_buffer = serial_chars_in_buffer,
+ .wait_until_sent = serial_wait_until_sent,
+ .tiocmget = serial_tiocmget,
+ .tiocmset = serial_tiocmset,
+ .get_icount = serial_get_icount,
+ .set_serial = serial_set_serial,
+ .get_serial = serial_get_serial,
+ .cleanup = serial_cleanup,
+ .install = serial_install,
+ .proc_show = serial_proc_show,
+};
+
+
+struct tty_driver *usb_serial_tty_driver;
+
+static int __init usb_serial_init(void)
+{
+ int result;
+
+ usb_serial_tty_driver = tty_alloc_driver(USB_SERIAL_TTY_MINORS,
+ TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV);
+ if (IS_ERR(usb_serial_tty_driver))
+ return PTR_ERR(usb_serial_tty_driver);
+
+ /* Initialize our global data */
+ result = bus_register(&usb_serial_bus_type);
+ if (result) {
+ pr_err("%s - registering bus driver failed\n", __func__);
+ goto err_put_driver;
+ }
+
+ usb_serial_tty_driver->driver_name = "usbserial";
+ usb_serial_tty_driver->name = "ttyUSB";
+ usb_serial_tty_driver->major = USB_SERIAL_TTY_MAJOR;
+ usb_serial_tty_driver->minor_start = 0;
+ usb_serial_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ usb_serial_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+ usb_serial_tty_driver->init_termios = tty_std_termios;
+ usb_serial_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD
+ | HUPCL | CLOCAL;
+ usb_serial_tty_driver->init_termios.c_ispeed = 9600;
+ usb_serial_tty_driver->init_termios.c_ospeed = 9600;
+ tty_set_operations(usb_serial_tty_driver, &serial_ops);
+ result = tty_register_driver(usb_serial_tty_driver);
+ if (result) {
+ pr_err("%s - tty_register_driver failed\n", __func__);
+ goto err_unregister_bus;
+ }
+
+ /* register the generic driver, if we should */
+ result = usb_serial_generic_register();
+ if (result < 0) {
+ pr_err("%s - registering generic driver failed\n", __func__);
+ goto err_unregister_driver;
+ }
+
+ return result;
+
+err_unregister_driver:
+ tty_unregister_driver(usb_serial_tty_driver);
+err_unregister_bus:
+ bus_unregister(&usb_serial_bus_type);
+err_put_driver:
+ pr_err("%s - returning with error %d\n", __func__, result);
+ tty_driver_kref_put(usb_serial_tty_driver);
+ return result;
+}
+
+
+static void __exit usb_serial_exit(void)
+{
+ usb_serial_console_exit();
+
+ usb_serial_generic_deregister();
+
+ tty_unregister_driver(usb_serial_tty_driver);
+ tty_driver_kref_put(usb_serial_tty_driver);
+ bus_unregister(&usb_serial_bus_type);
+ idr_destroy(&serial_minors);
+}
+
+
+module_init(usb_serial_init);
+module_exit(usb_serial_exit);
+
+#define set_to_generic_if_null(type, function) \
+ do { \
+ if (!type->function) { \
+ type->function = usb_serial_generic_##function; \
+ pr_debug("%s: using generic " #function "\n", \
+ type->driver.name); \
+ } \
+ } while (0)
+
+static void usb_serial_operations_init(struct usb_serial_driver *device)
+{
+ set_to_generic_if_null(device, open);
+ set_to_generic_if_null(device, write);
+ set_to_generic_if_null(device, close);
+ set_to_generic_if_null(device, write_room);
+ set_to_generic_if_null(device, chars_in_buffer);
+ if (device->tx_empty)
+ set_to_generic_if_null(device, wait_until_sent);
+ set_to_generic_if_null(device, read_bulk_callback);
+ set_to_generic_if_null(device, write_bulk_callback);
+ set_to_generic_if_null(device, process_read_urb);
+ set_to_generic_if_null(device, prepare_write_buffer);
+}
+
+static int usb_serial_register(struct usb_serial_driver *driver)
+{
+ int retval;
+
+ if (usb_disabled())
+ return -ENODEV;
+
+ if (!driver->description)
+ driver->description = driver->driver.name;
+ if (!driver->usb_driver) {
+ WARN(1, "Serial driver %s has no usb_driver\n",
+ driver->description);
+ return -EINVAL;
+ }
+
+ /* Prevent individual ports from being unbound. */
+ driver->driver.suppress_bind_attrs = true;
+
+ usb_serial_operations_init(driver);
+
+ /* Add this device to our list of devices */
+ mutex_lock(&table_lock);
+ list_add(&driver->driver_list, &usb_serial_driver_list);
+
+ retval = usb_serial_bus_register(driver);
+ if (retval) {
+ pr_err("problem %d when registering driver %s\n", retval, driver->description);
+ list_del(&driver->driver_list);
+ } else {
+ pr_info("USB Serial support registered for %s\n", driver->description);
+ }
+ mutex_unlock(&table_lock);
+ return retval;
+}
+
+static void usb_serial_deregister(struct usb_serial_driver *device)
+{
+ pr_info("USB Serial deregistering driver %s\n", device->description);
+
+ mutex_lock(&table_lock);
+ list_del(&device->driver_list);
+ mutex_unlock(&table_lock);
+
+ usb_serial_bus_deregister(device);
+}
+
+/**
+ * __usb_serial_register_drivers - register drivers for a usb-serial module
+ * @serial_drivers: NULL-terminated array of pointers to drivers to be registered
+ * @owner: owning module
+ * @name: name of the usb_driver for this set of @serial_drivers
+ * @id_table: list of all devices this @serial_drivers set binds to
+ *
+ * Registers all the drivers in the @serial_drivers array, and dynamically
+ * creates a struct usb_driver with the name @name and id_table of @id_table.
+ */
+int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers[],
+ struct module *owner, const char *name,
+ const struct usb_device_id *id_table)
+{
+ int rc;
+ struct usb_driver *udriver;
+ struct usb_serial_driver * const *sd;
+
+ /*
+ * udriver must be registered before any of the serial drivers,
+ * because the store_new_id() routine for the serial drivers (in
+ * bus.c) probes udriver.
+ *
+ * Performance hack: We don't want udriver to be probed until
+ * the serial drivers are registered, because the probe would
+ * simply fail for lack of a matching serial driver.
+ * So we leave udriver's id_table set to NULL until we are all set.
+ *
+ * Suspend/resume support is implemented in the usb-serial core,
+ * so fill in the PM-related fields in udriver.
+ */
+ udriver = kzalloc_obj(*udriver);
+ if (!udriver)
+ return -ENOMEM;
+
+ udriver->name = name;
+ udriver->no_dynamic_id = 1;
+ udriver->supports_autosuspend = 1;
+ udriver->suspend = usb_serial_suspend;
+ udriver->resume = usb_serial_resume;
+ udriver->probe = usb_serial_probe;
+ udriver->disconnect = usb_serial_disconnect;
+
+ /* we only set the reset_resume field if the serial_driver has one */
+ for (sd = serial_drivers; *sd; ++sd) {
+ if ((*sd)->reset_resume) {
+ udriver->reset_resume = usb_serial_reset_resume;
+ break;
+ }
+ }
+
+ rc = usb_register(udriver);
+ if (rc)
+ goto err_free_driver;
+
+ for (sd = serial_drivers; *sd; ++sd) {
+ (*sd)->usb_driver = udriver;
+ (*sd)->driver.owner = owner;
+ rc = usb_serial_register(*sd);
+ if (rc)
+ goto err_deregister_drivers;
+ }
+
+ /* Now set udriver's id_table and look for matches */
+ udriver->id_table = id_table;
+ rc = driver_attach(&udriver->driver);
+ return 0;
+
+err_deregister_drivers:
+ while (sd-- > serial_drivers)
+ usb_serial_deregister(*sd);
+ usb_deregister(udriver);
+err_free_driver:
+ kfree(udriver);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(__usb_serial_register_drivers);
+
+/**
+ * usb_serial_deregister_drivers - deregister drivers for a usb-serial module
+ * @serial_drivers: NULL-terminated array of pointers to drivers to be deregistered
+ *
+ * Deregisters all the drivers in the @serial_drivers array and deregisters and
+ * frees the struct usb_driver that was created by the call to
+ * usb_serial_register_drivers().
+ */
+void usb_serial_deregister_drivers(struct usb_serial_driver *const serial_drivers[])
+{
+ struct usb_driver *udriver = (*serial_drivers)->usb_driver;
+
+ for (; *serial_drivers; ++serial_drivers)
+ usb_serial_deregister(*serial_drivers);
+ usb_deregister(udriver);
+ kfree(udriver);
+}
+EXPORT_SYMBOL_GPL(usb_serial_deregister_drivers);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/usb-wwan.h b/drivers/usb/serial/usb-wwan.h
new file mode 100644
index 000000000..519101945
--- /dev/null
+++ b/drivers/usb/serial/usb-wwan.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Definitions for USB serial mobile broadband cards
+ */
+
+#ifndef __LINUX_USB_USB_WWAN
+#define __LINUX_USB_USB_WWAN
+
+extern void usb_wwan_dtr_rts(struct usb_serial_port *port, int on);
+extern int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port);
+extern void usb_wwan_close(struct usb_serial_port *port);
+extern int usb_wwan_port_probe(struct usb_serial_port *port);
+extern void usb_wwan_port_remove(struct usb_serial_port *port);
+extern unsigned int usb_wwan_write_room(struct tty_struct *tty);
+extern int usb_wwan_tiocmget(struct tty_struct *tty);
+extern int usb_wwan_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear);
+extern int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count);
+extern unsigned int usb_wwan_chars_in_buffer(struct tty_struct *tty);
+#ifdef CONFIG_PM
+extern int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message);
+extern int usb_wwan_resume(struct usb_serial *serial);
+#endif
+
+/* per port private data */
+
+#define N_IN_URB 4
+#define N_OUT_URB 4
+#define IN_BUFLEN 4096
+#define OUT_BUFLEN 4096
+
+struct usb_wwan_intf_private {
+ spinlock_t susp_lock;
+ unsigned int suspended:1;
+ unsigned int use_send_setup:1;
+ unsigned int use_zlp:1;
+ int in_flight;
+ unsigned int open_ports;
+ void *private;
+};
+
+struct usb_wwan_port_private {
+ /* Input endpoints and buffer for this port */
+ struct urb *in_urbs[N_IN_URB];
+ u8 *in_buffer[N_IN_URB];
+ /* Output endpoints and buffer for this port */
+ struct urb *out_urbs[N_OUT_URB];
+ u8 *out_buffer[N_OUT_URB];
+ unsigned long out_busy; /* Bit vector of URBs in use */
+ struct usb_anchor delayed;
+
+ /* Settings for the port */
+ int rts_state; /* Handshaking pins (outputs) */
+ int dtr_state;
+ int cts_state; /* Handshaking pins (inputs) */
+ int dsr_state;
+ int dcd_state;
+ int ri_state;
+
+ unsigned long tx_start_time[N_OUT_URB];
+};
+
+#endif /* __LINUX_USB_USB_WWAN */
diff --git a/drivers/usb/serial/usb_debug.c b/drivers/usb/serial/usb_debug.c
new file mode 100644
index 000000000..ec9fff794
--- /dev/null
+++ b/drivers/usb/serial/usb_debug.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Debug cable driver
+ *
+ * Copyright (C) 2006 Greg Kroah-Hartman <greg@kroah.com>
+ */
+
+#include <linux/gfp.h>
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#define USB_DEBUG_MAX_PACKET_SIZE 8
+#define USB_DEBUG_BRK_SIZE 8
+static const char USB_DEBUG_BRK[USB_DEBUG_BRK_SIZE] = {
+ 0x00,
+ 0xff,
+ 0x01,
+ 0xfe,
+ 0x00,
+ 0xfe,
+ 0x01,
+ 0xff,
+};
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x0525, 0x127a) },
+ { },
+};
+
+static const struct usb_device_id dbc_id_table[] = {
+ { USB_DEVICE(0x1d6b, 0x0010) },
+ { USB_DEVICE(0x1d6b, 0x0011) },
+ { },
+};
+
+static const struct usb_device_id id_table_combined[] = {
+ { USB_DEVICE(0x0525, 0x127a) },
+ { USB_DEVICE(0x1d6b, 0x0010) },
+ { USB_DEVICE(0x1d6b, 0x0011) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table_combined);
+
+/* This HW really does not support a serial break, so one will be
+ * emulated when ever the break state is set to true.
+ */
+static int usb_debug_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ int ret;
+
+ if (!break_state)
+ return 0;
+
+ ret = usb_serial_generic_write(tty, port, USB_DEBUG_BRK, USB_DEBUG_BRK_SIZE);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void usb_debug_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+
+ if (urb->actual_length == USB_DEBUG_BRK_SIZE &&
+ memcmp(urb->transfer_buffer, USB_DEBUG_BRK,
+ USB_DEBUG_BRK_SIZE) == 0) {
+ usb_serial_handle_break(port);
+ return;
+ }
+
+ usb_serial_generic_process_read_urb(urb);
+}
+
+static void usb_debug_init_termios(struct tty_struct *tty)
+{
+ tty->termios.c_lflag &= ~(ECHO | ECHONL);
+}
+
+static struct usb_serial_driver debug_device = {
+ .driver = {
+ .name = "debug",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .bulk_out_size = USB_DEBUG_MAX_PACKET_SIZE,
+ .break_ctl = usb_debug_break_ctl,
+ .init_termios = usb_debug_init_termios,
+ .process_read_urb = usb_debug_process_read_urb,
+};
+
+static struct usb_serial_driver dbc_device = {
+ .driver = {
+ .name = "xhci_dbc",
+ },
+ .id_table = dbc_id_table,
+ .num_ports = 1,
+ .break_ctl = usb_debug_break_ctl,
+ .init_termios = usb_debug_init_termios,
+ .process_read_urb = usb_debug_process_read_urb,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &debug_device, &dbc_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table_combined);
+MODULE_DESCRIPTION("USB Debug cable driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c
new file mode 100644
index 000000000..a183cc451
--- /dev/null
+++ b/drivers/usb/serial/usb_wwan.c
@@ -0,0 +1,657 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ USB Driver layer for GSM modems
+
+ Copyright (C) 2005 Matthias Urlichs <smurf@smurf.noris.de>
+
+ Portions copied from the Keyspan driver by Hugh Blemings <hugh@blemings.org>
+
+ History: see the git log.
+
+ Work sponsored by: Sigos GmbH, Germany <info@sigos.de>
+
+ This driver exists because the "normal" serial driver doesn't work too well
+ with GSM modems. Issues:
+ - data loss -- one single Receive URB is not nearly enough
+ - controlling the baud rate doesn't make sense
+*/
+
+#define DRIVER_AUTHOR "Matthias Urlichs <smurf@smurf.noris.de>"
+#define DRIVER_DESC "USB Driver for GSM modems"
+
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/serial.h>
+#include <linux/serial.h>
+#include "usb-wwan.h"
+
+/*
+ * Generate DTR/RTS signals on the port using the SET_CONTROL_LINE_STATE request
+ * in CDC ACM.
+ */
+static int usb_wwan_send_setup(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct usb_wwan_port_private *portdata;
+ int val = 0;
+ int ifnum;
+ int res;
+
+ portdata = usb_get_serial_port_data(port);
+
+ if (portdata->dtr_state)
+ val |= USB_CDC_CTRL_DTR;
+ if (portdata->rts_state)
+ val |= USB_CDC_CTRL_RTS;
+
+ ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber;
+
+ res = usb_autopm_get_interface(serial->interface);
+ if (res)
+ return res;
+
+ res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ USB_CDC_REQ_SET_CONTROL_LINE_STATE,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ val, ifnum, NULL, 0, USB_CTRL_SET_TIMEOUT);
+
+ usb_autopm_put_interface(port->serial->interface);
+
+ return res;
+}
+
+void usb_wwan_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct usb_wwan_port_private *portdata;
+ struct usb_wwan_intf_private *intfdata;
+
+ intfdata = usb_get_serial_data(port->serial);
+
+ if (!intfdata->use_send_setup)
+ return;
+
+ portdata = usb_get_serial_port_data(port);
+ /* FIXME: locking */
+ portdata->rts_state = on;
+ portdata->dtr_state = on;
+
+ usb_wwan_send_setup(port);
+}
+EXPORT_SYMBOL(usb_wwan_dtr_rts);
+
+int usb_wwan_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ unsigned int value;
+ struct usb_wwan_port_private *portdata;
+
+ portdata = usb_get_serial_port_data(port);
+
+ value = ((portdata->rts_state) ? TIOCM_RTS : 0) |
+ ((portdata->dtr_state) ? TIOCM_DTR : 0) |
+ ((portdata->cts_state) ? TIOCM_CTS : 0) |
+ ((portdata->dsr_state) ? TIOCM_DSR : 0) |
+ ((portdata->dcd_state) ? TIOCM_CAR : 0) |
+ ((portdata->ri_state) ? TIOCM_RNG : 0);
+
+ return value;
+}
+EXPORT_SYMBOL(usb_wwan_tiocmget);
+
+int usb_wwan_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_wwan_port_private *portdata;
+ struct usb_wwan_intf_private *intfdata;
+
+ portdata = usb_get_serial_port_data(port);
+ intfdata = usb_get_serial_data(port->serial);
+
+ if (!intfdata->use_send_setup)
+ return -EINVAL;
+
+ /* FIXME: what locks portdata fields ? */
+ if (set & TIOCM_RTS)
+ portdata->rts_state = 1;
+ if (set & TIOCM_DTR)
+ portdata->dtr_state = 1;
+
+ if (clear & TIOCM_RTS)
+ portdata->rts_state = 0;
+ if (clear & TIOCM_DTR)
+ portdata->dtr_state = 0;
+ return usb_wwan_send_setup(port);
+}
+EXPORT_SYMBOL(usb_wwan_tiocmset);
+
+int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
+ const unsigned char *buf, int count)
+{
+ struct usb_wwan_port_private *portdata;
+ struct usb_wwan_intf_private *intfdata;
+ int i;
+ int left, todo;
+ struct urb *this_urb = NULL; /* spurious */
+ int err;
+ unsigned long flags;
+
+ portdata = usb_get_serial_port_data(port);
+ intfdata = usb_get_serial_data(port->serial);
+
+ dev_dbg(&port->dev, "%s: write (%d chars)\n", __func__, count);
+
+ left = count;
+ for (i = 0; left > 0 && i < N_OUT_URB; i++) {
+ todo = left;
+ if (todo > OUT_BUFLEN)
+ todo = OUT_BUFLEN;
+
+ this_urb = portdata->out_urbs[i];
+ if (test_and_set_bit(i, &portdata->out_busy)) {
+ if (time_before(jiffies,
+ portdata->tx_start_time[i] + 10 * HZ))
+ continue;
+ usb_unlink_urb(this_urb);
+ continue;
+ }
+ dev_dbg(&port->dev, "%s: endpoint %d buf %d\n", __func__,
+ usb_pipeendpoint(this_urb->pipe), i);
+
+ err = usb_autopm_get_interface_async(port->serial->interface);
+ if (err < 0) {
+ clear_bit(i, &portdata->out_busy);
+ break;
+ }
+
+ /* send the data */
+ memcpy(this_urb->transfer_buffer, buf, todo);
+ this_urb->transfer_buffer_length = todo;
+
+ spin_lock_irqsave(&intfdata->susp_lock, flags);
+ if (intfdata->suspended) {
+ usb_anchor_urb(this_urb, &portdata->delayed);
+ spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+ } else {
+ intfdata->in_flight++;
+ spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+ err = usb_submit_urb(this_urb, GFP_ATOMIC);
+ if (err) {
+ dev_err(&port->dev,
+ "%s: submit urb %d failed: %d\n",
+ __func__, i, err);
+ clear_bit(i, &portdata->out_busy);
+ spin_lock_irqsave(&intfdata->susp_lock, flags);
+ intfdata->in_flight--;
+ spin_unlock_irqrestore(&intfdata->susp_lock,
+ flags);
+ usb_autopm_put_interface_async(port->serial->interface);
+ break;
+ }
+ }
+
+ portdata->tx_start_time[i] = jiffies;
+ buf += todo;
+ left -= todo;
+ }
+
+ count -= left;
+ dev_dbg(&port->dev, "%s: wrote (did %d)\n", __func__, count);
+ return count;
+}
+EXPORT_SYMBOL(usb_wwan_write);
+
+static void usb_wwan_indat_callback(struct urb *urb)
+{
+ int err;
+ int endpoint;
+ struct usb_serial_port *port;
+ struct device *dev;
+ unsigned char *data = urb->transfer_buffer;
+ int status = urb->status;
+
+ endpoint = usb_pipeendpoint(urb->pipe);
+ port = urb->context;
+ dev = &port->dev;
+
+ if (status) {
+ dev_dbg(dev, "%s: nonzero status: %d on endpoint %02x.\n",
+ __func__, status, endpoint);
+
+ /* don't resubmit on fatal errors */
+ if (status == -ESHUTDOWN || status == -ENOENT)
+ return;
+ } else {
+ if (urb->actual_length) {
+ tty_insert_flip_string(&port->port, data,
+ urb->actual_length);
+ tty_flip_buffer_push(&port->port);
+ } else
+ dev_dbg(dev, "%s: empty read urb received\n", __func__);
+ }
+ /* Resubmit urb so we continue receiving */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err) {
+ if (err != -EPERM && err != -ENODEV) {
+ dev_err(dev, "%s: resubmit read urb failed. (%d)\n",
+ __func__, err);
+ /* busy also in error unless we are killed */
+ usb_mark_last_busy(port->serial->dev);
+ }
+ } else {
+ usb_mark_last_busy(port->serial->dev);
+ }
+}
+
+static void usb_wwan_outdat_callback(struct urb *urb)
+{
+ struct usb_serial_port *port;
+ struct usb_wwan_port_private *portdata;
+ struct usb_wwan_intf_private *intfdata;
+ unsigned long flags;
+ int i;
+
+ port = urb->context;
+ intfdata = usb_get_serial_data(port->serial);
+
+ usb_serial_port_softint(port);
+ usb_autopm_put_interface_async(port->serial->interface);
+ portdata = usb_get_serial_port_data(port);
+ spin_lock_irqsave(&intfdata->susp_lock, flags);
+ intfdata->in_flight--;
+ spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+
+ for (i = 0; i < N_OUT_URB; ++i) {
+ if (portdata->out_urbs[i] == urb) {
+ smp_mb__before_atomic();
+ clear_bit(i, &portdata->out_busy);
+ break;
+ }
+ }
+}
+
+unsigned int usb_wwan_write_room(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_wwan_port_private *portdata;
+ int i;
+ unsigned int data_len = 0;
+ struct urb *this_urb;
+
+ portdata = usb_get_serial_port_data(port);
+
+ for (i = 0; i < N_OUT_URB; i++) {
+ this_urb = portdata->out_urbs[i];
+ if (this_urb && !test_bit(i, &portdata->out_busy))
+ data_len += OUT_BUFLEN;
+ }
+
+ dev_dbg(&port->dev, "%s: %u\n", __func__, data_len);
+ return data_len;
+}
+EXPORT_SYMBOL(usb_wwan_write_room);
+
+unsigned int usb_wwan_chars_in_buffer(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_wwan_port_private *portdata;
+ int i;
+ unsigned int data_len = 0;
+ struct urb *this_urb;
+
+ portdata = usb_get_serial_port_data(port);
+
+ for (i = 0; i < N_OUT_URB; i++) {
+ this_urb = portdata->out_urbs[i];
+ /* FIXME: This locking is insufficient as this_urb may
+ go unused during the test */
+ if (this_urb && test_bit(i, &portdata->out_busy))
+ data_len += this_urb->transfer_buffer_length;
+ }
+ dev_dbg(&port->dev, "%s: %u\n", __func__, data_len);
+ return data_len;
+}
+EXPORT_SYMBOL(usb_wwan_chars_in_buffer);
+
+int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ struct usb_wwan_port_private *portdata;
+ struct usb_wwan_intf_private *intfdata;
+ struct usb_serial *serial = port->serial;
+ int i, err;
+ struct urb *urb;
+
+ portdata = usb_get_serial_port_data(port);
+ intfdata = usb_get_serial_data(serial);
+
+ if (port->interrupt_in_urb) {
+ err = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (err) {
+ dev_err(&port->dev, "%s: submit int urb failed: %d\n",
+ __func__, err);
+ }
+ }
+
+ /* Start reading from the IN endpoint */
+ for (i = 0; i < N_IN_URB; i++) {
+ urb = portdata->in_urbs[i];
+ if (!urb)
+ continue;
+ err = usb_submit_urb(urb, GFP_KERNEL);
+ if (err) {
+ dev_err(&port->dev,
+ "%s: submit read urb %d failed: %d\n",
+ __func__, i, err);
+ }
+ }
+
+ spin_lock_irq(&intfdata->susp_lock);
+ if (++intfdata->open_ports == 1)
+ serial->interface->needs_remote_wakeup = 1;
+ spin_unlock_irq(&intfdata->susp_lock);
+ /* this balances a get in the generic USB serial code */
+ usb_autopm_put_interface(serial->interface);
+
+ return 0;
+}
+EXPORT_SYMBOL(usb_wwan_open);
+
+static void unbusy_queued_urb(struct urb *urb,
+ struct usb_wwan_port_private *portdata)
+{
+ int i;
+
+ for (i = 0; i < N_OUT_URB; i++) {
+ if (urb == portdata->out_urbs[i]) {
+ clear_bit(i, &portdata->out_busy);
+ break;
+ }
+ }
+}
+
+void usb_wwan_close(struct usb_serial_port *port)
+{
+ int i;
+ struct usb_serial *serial = port->serial;
+ struct usb_wwan_port_private *portdata;
+ struct usb_wwan_intf_private *intfdata = usb_get_serial_data(serial);
+ struct urb *urb;
+
+ portdata = usb_get_serial_port_data(port);
+
+ /*
+ * Need to take susp_lock to make sure port is not already being
+ * resumed, but no need to hold it due to the tty-port initialized
+ * flag.
+ */
+ spin_lock_irq(&intfdata->susp_lock);
+ if (--intfdata->open_ports == 0)
+ serial->interface->needs_remote_wakeup = 0;
+ spin_unlock_irq(&intfdata->susp_lock);
+
+ for (;;) {
+ urb = usb_get_from_anchor(&portdata->delayed);
+ if (!urb)
+ break;
+ unbusy_queued_urb(urb, portdata);
+ usb_autopm_put_interface_async(serial->interface);
+ }
+
+ for (i = 0; i < N_IN_URB; i++)
+ usb_kill_urb(portdata->in_urbs[i]);
+ for (i = 0; i < N_OUT_URB; i++)
+ usb_kill_urb(portdata->out_urbs[i]);
+ usb_kill_urb(port->interrupt_in_urb);
+
+ usb_autopm_get_interface_no_resume(serial->interface);
+}
+EXPORT_SYMBOL(usb_wwan_close);
+
+static struct urb *usb_wwan_setup_urb(struct usb_serial_port *port,
+ int endpoint,
+ int dir, void *ctx, char *buf, int len,
+ void (*callback) (struct urb *))
+{
+ struct usb_serial *serial = port->serial;
+ struct usb_wwan_intf_private *intfdata = usb_get_serial_data(serial);
+ struct urb *urb;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL); /* No ISO */
+ if (!urb)
+ return NULL;
+
+ usb_fill_bulk_urb(urb, serial->dev,
+ usb_sndbulkpipe(serial->dev, endpoint) | dir,
+ buf, len, callback, ctx);
+
+ if (intfdata->use_zlp && dir == USB_DIR_OUT)
+ urb->transfer_flags |= URB_ZERO_PACKET;
+
+ return urb;
+}
+
+int usb_wwan_port_probe(struct usb_serial_port *port)
+{
+ struct usb_wwan_port_private *portdata;
+ struct urb *urb;
+ u8 *buffer;
+ int i;
+
+ if (!port->bulk_in_size || !port->bulk_out_size)
+ return -ENODEV;
+
+ portdata = kzalloc_obj(*portdata);
+ if (!portdata)
+ return -ENOMEM;
+
+ init_usb_anchor(&portdata->delayed);
+
+ for (i = 0; i < N_IN_URB; i++) {
+ buffer = (u8 *)__get_free_page(GFP_KERNEL);
+ if (!buffer)
+ goto bail_out_error;
+ portdata->in_buffer[i] = buffer;
+
+ urb = usb_wwan_setup_urb(port, port->bulk_in_endpointAddress,
+ USB_DIR_IN, port,
+ buffer, IN_BUFLEN,
+ usb_wwan_indat_callback);
+ portdata->in_urbs[i] = urb;
+ }
+
+ for (i = 0; i < N_OUT_URB; i++) {
+ buffer = kmalloc(OUT_BUFLEN, GFP_KERNEL);
+ if (!buffer)
+ goto bail_out_error2;
+ portdata->out_buffer[i] = buffer;
+
+ urb = usb_wwan_setup_urb(port, port->bulk_out_endpointAddress,
+ USB_DIR_OUT, port,
+ buffer, OUT_BUFLEN,
+ usb_wwan_outdat_callback);
+ portdata->out_urbs[i] = urb;
+ }
+
+ usb_set_serial_port_data(port, portdata);
+
+ return 0;
+
+bail_out_error2:
+ for (i = 0; i < N_OUT_URB; i++) {
+ usb_free_urb(portdata->out_urbs[i]);
+ kfree(portdata->out_buffer[i]);
+ }
+bail_out_error:
+ for (i = 0; i < N_IN_URB; i++) {
+ usb_free_urb(portdata->in_urbs[i]);
+ free_page((unsigned long)portdata->in_buffer[i]);
+ }
+ kfree(portdata);
+
+ return -ENOMEM;
+}
+EXPORT_SYMBOL_GPL(usb_wwan_port_probe);
+
+void usb_wwan_port_remove(struct usb_serial_port *port)
+{
+ int i;
+ struct usb_wwan_port_private *portdata;
+
+ portdata = usb_get_serial_port_data(port);
+ usb_set_serial_port_data(port, NULL);
+
+ for (i = 0; i < N_IN_URB; i++) {
+ usb_free_urb(portdata->in_urbs[i]);
+ free_page((unsigned long)portdata->in_buffer[i]);
+ }
+ for (i = 0; i < N_OUT_URB; i++) {
+ usb_free_urb(portdata->out_urbs[i]);
+ kfree(portdata->out_buffer[i]);
+ }
+
+ kfree(portdata);
+}
+EXPORT_SYMBOL(usb_wwan_port_remove);
+
+#ifdef CONFIG_PM
+static void stop_urbs(struct usb_serial *serial)
+{
+ int i, j;
+ struct usb_serial_port *port;
+ struct usb_wwan_port_private *portdata;
+
+ for (i = 0; i < serial->num_ports; ++i) {
+ port = serial->port[i];
+ portdata = usb_get_serial_port_data(port);
+ if (!portdata)
+ continue;
+ for (j = 0; j < N_IN_URB; j++)
+ usb_kill_urb(portdata->in_urbs[j]);
+ for (j = 0; j < N_OUT_URB; j++)
+ usb_kill_urb(portdata->out_urbs[j]);
+ usb_kill_urb(port->interrupt_in_urb);
+ }
+}
+
+int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message)
+{
+ struct usb_wwan_intf_private *intfdata = usb_get_serial_data(serial);
+
+ spin_lock_irq(&intfdata->susp_lock);
+ if (PMSG_IS_AUTO(message)) {
+ if (intfdata->in_flight) {
+ spin_unlock_irq(&intfdata->susp_lock);
+ return -EBUSY;
+ }
+ }
+ intfdata->suspended = 1;
+ spin_unlock_irq(&intfdata->susp_lock);
+
+ stop_urbs(serial);
+
+ return 0;
+}
+EXPORT_SYMBOL(usb_wwan_suspend);
+
+/* Caller must hold susp_lock. */
+static int usb_wwan_submit_delayed_urbs(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct usb_wwan_intf_private *data = usb_get_serial_data(serial);
+ struct usb_wwan_port_private *portdata;
+ struct urb *urb;
+ int err_count = 0;
+ int err;
+
+ portdata = usb_get_serial_port_data(port);
+
+ for (;;) {
+ urb = usb_get_from_anchor(&portdata->delayed);
+ if (!urb)
+ break;
+
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err) {
+ dev_err(&port->dev, "%s: submit urb failed: %d\n",
+ __func__, err);
+ err_count++;
+ unbusy_queued_urb(urb, portdata);
+ usb_autopm_put_interface_async(serial->interface);
+ continue;
+ }
+ data->in_flight++;
+ }
+
+ if (err_count)
+ return -EIO;
+
+ return 0;
+}
+
+int usb_wwan_resume(struct usb_serial *serial)
+{
+ int i, j;
+ struct usb_serial_port *port;
+ struct usb_wwan_intf_private *intfdata = usb_get_serial_data(serial);
+ struct usb_wwan_port_private *portdata;
+ struct urb *urb;
+ int err;
+ int err_count = 0;
+
+ spin_lock_irq(&intfdata->susp_lock);
+ for (i = 0; i < serial->num_ports; i++) {
+ port = serial->port[i];
+
+ if (!tty_port_initialized(&port->port))
+ continue;
+
+ portdata = usb_get_serial_port_data(port);
+
+ if (port->interrupt_in_urb) {
+ err = usb_submit_urb(port->interrupt_in_urb,
+ GFP_ATOMIC);
+ if (err) {
+ dev_err(&port->dev,
+ "%s: submit int urb failed: %d\n",
+ __func__, err);
+ err_count++;
+ }
+ }
+
+ err = usb_wwan_submit_delayed_urbs(port);
+ if (err)
+ err_count++;
+
+ for (j = 0; j < N_IN_URB; j++) {
+ urb = portdata->in_urbs[j];
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err < 0) {
+ dev_err(&port->dev,
+ "%s: submit read urb %d failed: %d\n",
+ __func__, i, err);
+ err_count++;
+ }
+ }
+ }
+ intfdata->suspended = 0;
+ spin_unlock_irq(&intfdata->susp_lock);
+
+ if (err_count)
+ return -EIO;
+
+ return 0;
+}
+EXPORT_SYMBOL(usb_wwan_resume);
+#endif
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c
new file mode 100644
index 000000000..5706c46c4
--- /dev/null
+++ b/drivers/usb/serial/visor.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB HandSpring Visor, Palm m50x, and Sony Clie driver
+ * (supports all of the Palm OS USB devices)
+ *
+ * Copyright (C) 1999 - 2004
+ * Greg Kroah-Hartman (greg@kroah.com)
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/usb/cdc.h>
+#include "visor.h"
+
+/*
+ * Version Information
+ */
+#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>"
+#define DRIVER_DESC "USB HandSpring Visor / Palm OS driver"
+
+/* function prototypes for a handspring visor */
+static int visor_open(struct tty_struct *tty, struct usb_serial_port *port);
+static void visor_close(struct usb_serial_port *port);
+static int visor_probe(struct usb_serial *serial,
+ const struct usb_device_id *id);
+static int visor_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds);
+static int clie_5_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds);
+static void visor_read_int_callback(struct urb *urb);
+static int clie_3_5_startup(struct usb_serial *serial);
+static int palm_os_3_probe(struct usb_serial *serial,
+ const struct usb_device_id *id);
+static int palm_os_4_probe(struct usb_serial *serial,
+ const struct usb_device_id *id);
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_VISOR_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_3_probe },
+ { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_TREO_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_TREO600_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(GSPDA_VENDOR_ID, GSPDA_XPLORE_M68_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_M500_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_M505_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_M515_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_I705_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_M100_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_M125_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_M130_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_T_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_TREO_650),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_Z_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_ZIRE_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_0_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_S360_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_1_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NX60_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NZ90V_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_TJ25_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(ACER_VENDOR_ID, ACER_S10_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE_INTERFACE_CLASS(SAMSUNG_VENDOR_ID, SAMSUNG_SCH_I330_ID, 0xff),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_SPH_I500_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(TAPWAVE_VENDOR_ID, TAPWAVE_ZODIAC_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(GARMIN_VENDOR_ID, GARMIN_IQUE_3600_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(ACEECA_VENDOR_ID, ACEECA_MEZ1000_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_7135_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { USB_DEVICE(FOSSIL_VENDOR_ID, FOSSIL_ABACUS_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id clie_id_5_table[] = {
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_UX50_ID),
+ .driver_info = (kernel_ulong_t)&palm_os_4_probe },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id clie_id_3_5_table[] = {
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_3_5_ID) },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id id_table_combined[] = {
+ { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_VISOR_ID) },
+ { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_TREO_ID) },
+ { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_TREO600_ID) },
+ { USB_DEVICE(GSPDA_VENDOR_ID, GSPDA_XPLORE_M68_ID) },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_M500_ID) },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_M505_ID) },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_M515_ID) },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_I705_ID) },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_M100_ID) },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_M125_ID) },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_M130_ID) },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_T_ID) },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_TREO_650) },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_Z_ID) },
+ { USB_DEVICE(PALM_VENDOR_ID, PALM_ZIRE_ID) },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_3_5_ID) },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_0_ID) },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_S360_ID) },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_1_ID) },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NX60_ID) },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NZ90V_ID) },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_UX50_ID) },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_TJ25_ID) },
+ { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_SCH_I330_ID) },
+ { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_SPH_I500_ID) },
+ { USB_DEVICE(TAPWAVE_VENDOR_ID, TAPWAVE_ZODIAC_ID) },
+ { USB_DEVICE(GARMIN_VENDOR_ID, GARMIN_IQUE_3600_ID) },
+ { USB_DEVICE(ACEECA_VENDOR_ID, ACEECA_MEZ1000_ID) },
+ { USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_7135_ID) },
+ { USB_DEVICE(FOSSIL_VENDOR_ID, FOSSIL_ABACUS_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table_combined);
+
+/* All of the device info needed for the Handspring Visor,
+ and Palm 4.0 devices */
+static struct usb_serial_driver handspring_device = {
+ .driver = {
+ .name = "visor",
+ },
+ .description = "Handspring Visor / Palm OS",
+ .id_table = id_table,
+ .num_ports = 2,
+ .bulk_out_size = 256,
+ .open = visor_open,
+ .close = visor_close,
+ .throttle = usb_serial_generic_throttle,
+ .unthrottle = usb_serial_generic_unthrottle,
+ .probe = visor_probe,
+ .calc_num_ports = visor_calc_num_ports,
+ .read_int_callback = visor_read_int_callback,
+};
+
+/* All of the device info needed for the Clie UX50, TH55 Palm 5.0 devices */
+static struct usb_serial_driver clie_5_device = {
+ .driver = {
+ .name = "clie_5",
+ },
+ .description = "Sony Clie 5.0",
+ .id_table = clie_id_5_table,
+ .num_ports = 2,
+ .num_bulk_out = 2,
+ .bulk_out_size = 256,
+ .open = visor_open,
+ .close = visor_close,
+ .throttle = usb_serial_generic_throttle,
+ .unthrottle = usb_serial_generic_unthrottle,
+ .probe = visor_probe,
+ .calc_num_ports = clie_5_calc_num_ports,
+ .read_int_callback = visor_read_int_callback,
+};
+
+/* device info for the Sony Clie OS version 3.5 */
+static struct usb_serial_driver clie_3_5_device = {
+ .driver = {
+ .name = "clie_3.5",
+ },
+ .description = "Sony Clie 3.5",
+ .id_table = clie_id_3_5_table,
+ .num_ports = 1,
+ .bulk_out_size = 256,
+ .open = visor_open,
+ .close = visor_close,
+ .throttle = usb_serial_generic_throttle,
+ .unthrottle = usb_serial_generic_unthrottle,
+ .attach = clie_3_5_startup,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &handspring_device, &clie_5_device, &clie_3_5_device, NULL
+};
+
+/******************************************************************************
+ * Handspring Visor specific driver functions
+ ******************************************************************************/
+static int visor_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ int result = 0;
+
+ if (!port->read_urb) {
+ /* this is needed for some brain dead Sony devices */
+ dev_err(&port->dev, "Device lied about number of ports, please use a lower one.\n");
+ return -ENODEV;
+ }
+
+ /* Start reading from the device */
+ result = usb_serial_generic_open(tty, port);
+ if (result)
+ goto exit;
+
+ if (port->interrupt_in_urb) {
+ dev_dbg(&port->dev, "adding interrupt input for treo\n");
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (result)
+ dev_err(&port->dev,
+ "%s - failed submitting interrupt urb, error %d\n",
+ __func__, result);
+ }
+exit:
+ return result;
+}
+
+
+static void visor_close(struct usb_serial_port *port)
+{
+ unsigned char *transfer_buffer;
+
+ usb_serial_generic_close(port);
+ usb_kill_urb(port->interrupt_in_urb);
+
+ transfer_buffer = kmalloc(0x12, GFP_KERNEL);
+ if (!transfer_buffer)
+ return;
+ usb_control_msg(port->serial->dev,
+ usb_rcvctrlpipe(port->serial->dev, 0),
+ VISOR_CLOSE_NOTIFICATION, 0xc2,
+ 0x0000, 0x0000,
+ transfer_buffer, 0x12, 300);
+ kfree(transfer_buffer);
+}
+
+static void visor_read_int_callback(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ int status = urb->status;
+ int result;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ goto exit;
+ }
+
+ /*
+ * This information is still unknown what it can be used for.
+ * If anyone has an idea, please let the author know...
+ *
+ * Rumor has it this endpoint is used to notify when data
+ * is ready to be read from the bulk ones.
+ */
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length,
+ urb->transfer_buffer);
+
+exit:
+ result = usb_submit_urb(urb, GFP_ATOMIC);
+ if (result)
+ dev_err(&urb->dev->dev,
+ "%s - Error %d submitting interrupt urb\n",
+ __func__, result);
+}
+
+static int palm_os_3_probe(struct usb_serial *serial,
+ const struct usb_device_id *id)
+{
+ struct device *dev = &serial->dev->dev;
+ struct visor_connection_info *connection_info;
+ unsigned char *transfer_buffer;
+ char *string;
+ int retval = 0;
+ int i;
+ int num_ports = 0;
+
+ transfer_buffer = kmalloc(sizeof(*connection_info), GFP_KERNEL);
+ if (!transfer_buffer)
+ return -ENOMEM;
+
+ /* send a get connection info request */
+ retval = usb_control_msg(serial->dev,
+ usb_rcvctrlpipe(serial->dev, 0),
+ VISOR_GET_CONNECTION_INFORMATION,
+ 0xc2, 0x0000, 0x0000, transfer_buffer,
+ sizeof(*connection_info), 300);
+ if (retval < 0) {
+ dev_err(dev, "%s - error %d getting connection information\n",
+ __func__, retval);
+ goto exit;
+ }
+
+ if (retval != sizeof(*connection_info)) {
+ dev_err(dev, "Invalid connection information received from device\n");
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ connection_info = (struct visor_connection_info *)transfer_buffer;
+
+ num_ports = le16_to_cpu(connection_info->num_ports);
+
+ /* Handle devices that report invalid stuff here. */
+ if (num_ports == 0 || num_ports > 2) {
+ dev_warn(dev, "%s: No valid connect info available\n",
+ serial->type->description);
+ num_ports = 2;
+ }
+
+ for (i = 0; i < num_ports; ++i) {
+ switch (connection_info->connections[i].port_function_id) {
+ case VISOR_FUNCTION_GENERIC:
+ string = "Generic";
+ break;
+ case VISOR_FUNCTION_DEBUGGER:
+ string = "Debugger";
+ break;
+ case VISOR_FUNCTION_HOTSYNC:
+ string = "HotSync";
+ break;
+ case VISOR_FUNCTION_CONSOLE:
+ string = "Console";
+ break;
+ case VISOR_FUNCTION_REMOTE_FILE_SYS:
+ string = "Remote File System";
+ break;
+ default:
+ string = "unknown";
+ break;
+ }
+ dev_info(dev, "%s: port %d, is for %s use\n",
+ serial->type->description,
+ connection_info->connections[i].port, string);
+ }
+ dev_info(dev, "%s: Number of ports: %d\n", serial->type->description,
+ num_ports);
+
+ /*
+ * save off our num_ports info so that we can use it in the
+ * calc_num_ports callback
+ */
+ usb_set_serial_data(serial, (void *)(long)num_ports);
+
+ /* ask for the number of bytes available, but ignore the
+ response as it is broken */
+ retval = usb_control_msg(serial->dev,
+ usb_rcvctrlpipe(serial->dev, 0),
+ VISOR_REQUEST_BYTES_AVAILABLE,
+ 0xc2, 0x0000, 0x0005, transfer_buffer,
+ 0x02, 300);
+ if (retval < 0)
+ dev_err(dev, "%s - error %d getting bytes available request\n",
+ __func__, retval);
+ retval = 0;
+
+exit:
+ kfree(transfer_buffer);
+
+ return retval;
+}
+
+static int palm_os_4_probe(struct usb_serial *serial,
+ const struct usb_device_id *id)
+{
+ struct device *dev = &serial->dev->dev;
+ struct palm_ext_connection_info *connection_info;
+ unsigned char *transfer_buffer;
+ int retval;
+
+ transfer_buffer = kmalloc(sizeof(*connection_info), GFP_KERNEL);
+ if (!transfer_buffer)
+ return -ENOMEM;
+
+ retval = usb_control_msg(serial->dev,
+ usb_rcvctrlpipe(serial->dev, 0),
+ PALM_GET_EXT_CONNECTION_INFORMATION,
+ 0xc2, 0x0000, 0x0000, transfer_buffer,
+ sizeof(*connection_info), 300);
+ if (retval < 0)
+ dev_err(dev, "%s - error %d getting connection info\n",
+ __func__, retval);
+ else
+ usb_serial_debug_data(dev, __func__, retval, transfer_buffer);
+
+ kfree(transfer_buffer);
+ return 0;
+}
+
+
+static int visor_probe(struct usb_serial *serial,
+ const struct usb_device_id *id)
+{
+ int retval = 0;
+ int (*startup)(struct usb_serial *serial,
+ const struct usb_device_id *id);
+
+ /*
+ * some Samsung Android phones in modem mode have the same ID
+ * as SPH-I500, but they are ACM devices, so dont bind to them
+ */
+ if (id->idVendor == SAMSUNG_VENDOR_ID &&
+ id->idProduct == SAMSUNG_SPH_I500_ID &&
+ serial->dev->descriptor.bDeviceClass == USB_CLASS_COMM &&
+ serial->dev->descriptor.bDeviceSubClass ==
+ USB_CDC_SUBCLASS_ACM)
+ return -ENODEV;
+
+ if (serial->dev->actconfig->desc.bConfigurationValue != 1) {
+ dev_err(&serial->dev->dev, "active config #%d != 1 ??\n",
+ serial->dev->actconfig->desc.bConfigurationValue);
+ return -ENODEV;
+ }
+
+ if (id->driver_info) {
+ startup = (void *)id->driver_info;
+ retval = startup(serial, id);
+ }
+
+ return retval;
+}
+
+static int visor_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ unsigned int vid = le16_to_cpu(serial->dev->descriptor.idVendor);
+ int num_ports = (int)(long)(usb_get_serial_data(serial));
+
+ if (num_ports)
+ usb_set_serial_data(serial, NULL);
+
+ /*
+ * Only swap the bulk endpoints for the Handspring devices with
+ * interrupt in endpoints, which for now are the Treo devices.
+ */
+ if (!(vid == HANDSPRING_VENDOR_ID || vid == KYOCERA_VENDOR_ID) ||
+ epds->num_interrupt_in == 0)
+ goto out;
+
+ if (epds->num_bulk_in < 2 || epds->num_interrupt_in < 2) {
+ dev_err(&serial->interface->dev, "missing endpoints\n");
+ return -ENODEV;
+ }
+
+ /*
+ * It appears that Treos and Kyoceras want to use the
+ * 1st bulk in endpoint to communicate with the 2nd bulk out endpoint,
+ * so let's swap the 1st and 2nd bulk in and interrupt endpoints.
+ * Note that swapping the bulk out endpoints would break lots of
+ * apps that want to communicate on the second port.
+ */
+ swap(epds->bulk_in[0], epds->bulk_in[1]);
+ swap(epds->interrupt_in[0], epds->interrupt_in[1]);
+out:
+ return num_ports;
+}
+
+static int clie_5_calc_num_ports(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds)
+{
+ /*
+ * TH55 registers 2 ports.
+ * Communication in from the UX50/TH55 uses the first bulk-in
+ * endpoint, while communication out to the UX50/TH55 uses the second
+ * bulk-out endpoint.
+ */
+
+ /*
+ * FIXME: Should we swap the descriptors instead of using the same
+ * bulk-out endpoint for both ports?
+ */
+ epds->bulk_out[0] = epds->bulk_out[1];
+
+ return serial->type->num_ports;
+}
+
+static int clie_3_5_startup(struct usb_serial *serial)
+{
+ struct device *dev = &serial->dev->dev;
+ int result;
+ u8 *data;
+
+ data = kmalloc(1, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ /*
+ * Note that PEG-300 series devices expect the following two calls.
+ */
+
+ /* get the config number */
+ result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+ USB_REQ_GET_CONFIGURATION, USB_DIR_IN,
+ 0, 0, data, 1, 3000);
+ if (result < 0) {
+ dev_err(dev, "%s: get config number failed: %d\n",
+ __func__, result);
+ goto out;
+ }
+ if (result != 1) {
+ dev_err(dev, "%s: get config number bad return length: %d\n",
+ __func__, result);
+ result = -EIO;
+ goto out;
+ }
+
+ /* get the interface number */
+ result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+ USB_REQ_GET_INTERFACE,
+ USB_DIR_IN | USB_RECIP_INTERFACE,
+ 0, 0, data, 1, 3000);
+ if (result < 0) {
+ dev_err(dev, "%s: get interface number failed: %d\n",
+ __func__, result);
+ goto out;
+ }
+ if (result != 1) {
+ dev_err(dev,
+ "%s: get interface number bad return length: %d\n",
+ __func__, result);
+ result = -EIO;
+ goto out;
+ }
+
+ result = 0;
+out:
+ kfree(data);
+
+ return result;
+}
+
+module_usb_serial_driver(serial_drivers, id_table_combined);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/serial/visor.h b/drivers/usb/serial/visor.h
new file mode 100644
index 000000000..622d639ce
--- /dev/null
+++ b/drivers/usb/serial/visor.h
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * USB HandSpring Visor driver
+ *
+ * Copyright (C) 1999 - 2003
+ * Greg Kroah-Hartman (greg@kroah.com)
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver.
+ *
+ */
+
+#ifndef __LINUX_USB_SERIAL_VISOR_H
+#define __LINUX_USB_SERIAL_VISOR_H
+
+
+#define HANDSPRING_VENDOR_ID 0x082d
+#define HANDSPRING_VISOR_ID 0x0100
+#define HANDSPRING_TREO_ID 0x0200
+#define HANDSPRING_TREO600_ID 0x0300
+
+#define PALM_VENDOR_ID 0x0830
+#define PALM_M500_ID 0x0001
+#define PALM_M505_ID 0x0002
+#define PALM_M515_ID 0x0003
+#define PALM_I705_ID 0x0020
+#define PALM_M125_ID 0x0040
+#define PALM_M130_ID 0x0050
+#define PALM_TUNGSTEN_T_ID 0x0060
+#define PALM_TREO_650 0x0061
+#define PALM_TUNGSTEN_Z_ID 0x0031
+#define PALM_ZIRE_ID 0x0070
+#define PALM_M100_ID 0x0080
+
+#define GSPDA_VENDOR_ID 0x115e
+#define GSPDA_XPLORE_M68_ID 0xf100
+
+#define SONY_VENDOR_ID 0x054C
+#define SONY_CLIE_3_5_ID 0x0038
+#define SONY_CLIE_4_0_ID 0x0066
+#define SONY_CLIE_S360_ID 0x0095
+#define SONY_CLIE_4_1_ID 0x009A
+#define SONY_CLIE_NX60_ID 0x00DA
+#define SONY_CLIE_NZ90V_ID 0x00E9
+#define SONY_CLIE_UX50_ID 0x0144
+#define SONY_CLIE_TJ25_ID 0x0169
+
+#define ACER_VENDOR_ID 0x0502
+#define ACER_S10_ID 0x0001
+
+#define SAMSUNG_VENDOR_ID 0x04E8
+#define SAMSUNG_SCH_I330_ID 0x8001
+#define SAMSUNG_SPH_I500_ID 0x6601
+
+#define TAPWAVE_VENDOR_ID 0x12EF
+#define TAPWAVE_ZODIAC_ID 0x0100
+
+#define GARMIN_VENDOR_ID 0x091E
+#define GARMIN_IQUE_3600_ID 0x0004
+
+#define ACEECA_VENDOR_ID 0x4766
+#define ACEECA_MEZ1000_ID 0x0001
+
+#define KYOCERA_VENDOR_ID 0x0C88
+#define KYOCERA_7135_ID 0x0021
+
+#define FOSSIL_VENDOR_ID 0x0E67
+#define FOSSIL_ABACUS_ID 0x0002
+
+/****************************************************************************
+ * Handspring Visor Vendor specific request codes (bRequest values)
+ * A big thank you to Handspring for providing the following information.
+ * If anyone wants the original file where these values and structures came
+ * from, send email to <greg@kroah.com>.
+ ****************************************************************************/
+
+/****************************************************************************
+ * VISOR_REQUEST_BYTES_AVAILABLE asks the visor for the number of bytes that
+ * are available to be transferred to the host for the specified endpoint.
+ * Currently this is not used, and always returns 0x0001
+ ****************************************************************************/
+#define VISOR_REQUEST_BYTES_AVAILABLE 0x01
+
+/****************************************************************************
+ * VISOR_CLOSE_NOTIFICATION is set to the device to notify it that the host
+ * is now closing the pipe. An empty packet is sent in response.
+ ****************************************************************************/
+#define VISOR_CLOSE_NOTIFICATION 0x02
+
+/****************************************************************************
+ * VISOR_GET_CONNECTION_INFORMATION is sent by the host during enumeration to
+ * get the endpoints used by the connection.
+ ****************************************************************************/
+#define VISOR_GET_CONNECTION_INFORMATION 0x03
+
+
+/****************************************************************************
+ * VISOR_GET_CONNECTION_INFORMATION returns data in the following format
+ ****************************************************************************/
+struct visor_connection_info {
+ __le16 num_ports;
+ struct {
+ __u8 port_function_id;
+ __u8 port;
+ } connections[2];
+};
+
+
+/* struct visor_connection_info.connection[x].port defines: */
+#define VISOR_ENDPOINT_1 0x01
+#define VISOR_ENDPOINT_2 0x02
+
+/* struct visor_connection_info.connection[x].port_function_id defines: */
+#define VISOR_FUNCTION_GENERIC 0x00
+#define VISOR_FUNCTION_DEBUGGER 0x01
+#define VISOR_FUNCTION_HOTSYNC 0x02
+#define VISOR_FUNCTION_CONSOLE 0x03
+#define VISOR_FUNCTION_REMOTE_FILE_SYS 0x04
+
+
+/****************************************************************************
+ * PALM_GET_SOME_UNKNOWN_INFORMATION is sent by the host during enumeration to
+ * get some information from the M series devices, that is currently unknown.
+ ****************************************************************************/
+#define PALM_GET_EXT_CONNECTION_INFORMATION 0x04
+
+/**
+ * struct palm_ext_connection_info - return data from a PALM_GET_EXT_CONNECTION_INFORMATION request
+ * @num_ports: maximum number of functions/connections in use
+ * @endpoint_numbers_different: will be 1 if in and out endpoints numbers are
+ * different, otherwise it is 0. If value is 1, then
+ * connections.end_point_info is non-zero. If value is 0, then
+ * connections.port contains the endpoint number, which is the same for in
+ * and out.
+ * @port_function_id: contains the creator id of the application that opened
+ * this connection.
+ * @port: contains the in/out endpoint number. Is 0 if in and out endpoint
+ * numbers are different.
+ * @end_point_info: high nubbe is in endpoint and low nibble will indicate out
+ * endpoint. Is 0 if in and out endpoints are the same.
+ *
+ * The maximum number of connections currently supported is 2
+ */
+struct palm_ext_connection_info {
+ __u8 num_ports;
+ __u8 endpoint_numbers_different;
+ __le16 reserved1;
+ struct {
+ __u32 port_function_id;
+ __u8 port;
+ __u8 end_point_info;
+ __le16 reserved;
+ } connections[2];
+};
+
+#endif
+
diff --git a/drivers/usb/serial/whiteheat.c b/drivers/usb/serial/whiteheat.c
new file mode 100644
index 000000000..a2d83f69e
--- /dev/null
+++ b/drivers/usb/serial/whiteheat.c
@@ -0,0 +1,802 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * USB ConnectTech WhiteHEAT driver
+ *
+ * Copyright (C) 2002
+ * Connect Tech Inc.
+ *
+ * Copyright (C) 1999 - 2001
+ * Greg Kroah-Hartman (greg@kroah.com)
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/usb.h>
+#include <linux/serial_reg.h>
+#include <linux/serial.h>
+#include <linux/usb/serial.h>
+#include <linux/usb/ezusb.h>
+#include "whiteheat.h" /* WhiteHEAT specific commands */
+
+/*
+ * Version Information
+ */
+#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Stuart MacDonald <stuartm@connecttech.com>"
+#define DRIVER_DESC "USB ConnectTech WhiteHEAT driver"
+
+#define CONNECT_TECH_VENDOR_ID 0x0710
+#define CONNECT_TECH_FAKE_WHITE_HEAT_ID 0x0001
+#define CONNECT_TECH_WHITE_HEAT_ID 0x8001
+
+/*
+ ID tables for whiteheat are unusual, because we want to different
+ things for different versions of the device. Eventually, this
+ will be doable from a single table. But, for now, we define two
+ separate ID tables, and then a third table that combines them
+ just for the purpose of exporting the autoloading information.
+*/
+static const struct usb_device_id id_table_std[] = {
+ { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_WHITE_HEAT_ID) },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id id_table_prerenumeration[] = {
+ { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_FAKE_WHITE_HEAT_ID) },
+ { } /* Terminating entry */
+};
+
+static const struct usb_device_id id_table_combined[] = {
+ { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_WHITE_HEAT_ID) },
+ { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_FAKE_WHITE_HEAT_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table_combined);
+
+
+/* function prototypes for the Connect Tech WhiteHEAT prerenumeration device */
+static int whiteheat_firmware_download(struct usb_serial *serial,
+ const struct usb_device_id *id);
+static int whiteheat_firmware_attach(struct usb_serial *serial);
+
+/* function prototypes for the Connect Tech WhiteHEAT serial converter */
+static int whiteheat_attach(struct usb_serial *serial);
+static void whiteheat_release(struct usb_serial *serial);
+static int whiteheat_port_probe(struct usb_serial_port *port);
+static void whiteheat_port_remove(struct usb_serial_port *port);
+static int whiteheat_open(struct tty_struct *tty,
+ struct usb_serial_port *port);
+static void whiteheat_close(struct usb_serial_port *port);
+static void whiteheat_get_serial(struct tty_struct *tty,
+ struct serial_struct *ss);
+static void whiteheat_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+static int whiteheat_tiocmget(struct tty_struct *tty);
+static int whiteheat_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear);
+static int whiteheat_break_ctl(struct tty_struct *tty, int break_state);
+
+static struct usb_serial_driver whiteheat_fake_device = {
+ .driver = {
+ .name = "whiteheatnofirm",
+ },
+ .description = "Connect Tech - WhiteHEAT - (prerenumeration)",
+ .id_table = id_table_prerenumeration,
+ .num_ports = 1,
+ .probe = whiteheat_firmware_download,
+ .attach = whiteheat_firmware_attach,
+};
+
+static struct usb_serial_driver whiteheat_device = {
+ .driver = {
+ .name = "whiteheat",
+ },
+ .description = "Connect Tech - WhiteHEAT",
+ .id_table = id_table_std,
+ .num_ports = 4,
+ .num_bulk_in = 5,
+ .num_bulk_out = 5,
+ .attach = whiteheat_attach,
+ .release = whiteheat_release,
+ .port_probe = whiteheat_port_probe,
+ .port_remove = whiteheat_port_remove,
+ .open = whiteheat_open,
+ .close = whiteheat_close,
+ .get_serial = whiteheat_get_serial,
+ .set_termios = whiteheat_set_termios,
+ .break_ctl = whiteheat_break_ctl,
+ .tiocmget = whiteheat_tiocmget,
+ .tiocmset = whiteheat_tiocmset,
+ .throttle = usb_serial_generic_throttle,
+ .unthrottle = usb_serial_generic_unthrottle,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &whiteheat_fake_device, &whiteheat_device, NULL
+};
+
+struct whiteheat_command_private {
+ struct mutex mutex;
+ __u8 port_running;
+ __u8 command_finished;
+ wait_queue_head_t wait_command; /* for handling sleeping whilst
+ waiting for a command to
+ finish */
+ __u8 result_buffer[64];
+};
+
+struct whiteheat_private {
+ __u8 mcr; /* FIXME: no locking on mcr */
+};
+
+
+/* local function prototypes */
+static int start_command_port(struct usb_serial *serial);
+static void stop_command_port(struct usb_serial *serial);
+static void command_port_write_callback(struct urb *urb);
+static void command_port_read_callback(struct urb *urb);
+
+static int firm_send_command(struct usb_serial_port *port, __u8 command,
+ __u8 *data, __u8 datasize);
+static int firm_open(struct usb_serial_port *port);
+static int firm_close(struct usb_serial_port *port);
+static void firm_setup_port(struct tty_struct *tty);
+static int firm_set_rts(struct usb_serial_port *port, __u8 onoff);
+static int firm_set_dtr(struct usb_serial_port *port, __u8 onoff);
+static int firm_set_break(struct usb_serial_port *port, __u8 onoff);
+static int firm_purge(struct usb_serial_port *port, __u8 rxtx);
+static int firm_get_dtr_rts(struct usb_serial_port *port);
+static int firm_report_tx_done(struct usb_serial_port *port);
+
+
+#define COMMAND_PORT 4
+#define COMMAND_TIMEOUT (2*HZ) /* 2 second timeout for a command */
+#define COMMAND_TIMEOUT_MS 2000
+
+
+/*****************************************************************************
+ * Connect Tech's White Heat prerenumeration driver functions
+ *****************************************************************************/
+
+/* steps to download the firmware to the WhiteHEAT device:
+ - hold the reset (by writing to the reset bit of the CPUCS register)
+ - download the VEND_AX.HEX file to the chip using VENDOR_REQUEST-ANCHOR_LOAD
+ - release the reset (by writing to the CPUCS register)
+ - download the WH.HEX file for all addresses greater than 0x1b3f using
+ VENDOR_REQUEST-ANCHOR_EXTERNAL_RAM_LOAD
+ - hold the reset
+ - download the WH.HEX file for all addresses less than 0x1b40 using
+ VENDOR_REQUEST_ANCHOR_LOAD
+ - release the reset
+ - device renumerated itself and comes up as new device id with all
+ firmware download completed.
+*/
+static int whiteheat_firmware_download(struct usb_serial *serial,
+ const struct usb_device_id *id)
+{
+ int response;
+
+ response = ezusb_fx1_ihex_firmware_download(serial->dev, "whiteheat_loader.fw");
+ if (response >= 0) {
+ response = ezusb_fx1_ihex_firmware_download(serial->dev, "whiteheat.fw");
+ if (response >= 0)
+ return 0;
+ }
+ return -ENOENT;
+}
+
+
+static int whiteheat_firmware_attach(struct usb_serial *serial)
+{
+ /* We want this device to fail to have a driver assigned to it */
+ return 1;
+}
+
+
+/*****************************************************************************
+ * Connect Tech's White Heat serial driver functions
+ *****************************************************************************/
+
+static int whiteheat_attach(struct usb_serial *serial)
+{
+ struct usb_serial_port *command_port;
+ struct whiteheat_command_private *command_info;
+ struct whiteheat_hw_info *hw_info;
+ int pipe;
+ int ret;
+ int alen;
+ __u8 *command;
+ __u8 *result;
+
+ command_port = serial->port[COMMAND_PORT];
+
+ pipe = usb_sndbulkpipe(serial->dev,
+ command_port->bulk_out_endpointAddress);
+ command = kmalloc(2, GFP_KERNEL);
+ if (!command)
+ goto no_command_buffer;
+ command[0] = WHITEHEAT_GET_HW_INFO;
+ command[1] = 0;
+
+ result = kmalloc(sizeof(*hw_info) + 1, GFP_KERNEL);
+ if (!result)
+ goto no_result_buffer;
+ /*
+ * When the module is reloaded the firmware is still there and
+ * the endpoints are still in the usb core unchanged. This is the
+ * unlinking bug in disguise. Same for the call below.
+ */
+ usb_clear_halt(serial->dev, pipe);
+ ret = usb_bulk_msg(serial->dev, pipe, command, 2,
+ &alen, COMMAND_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&serial->dev->dev, "%s: Couldn't send command [%d]\n",
+ serial->type->description, ret);
+ goto no_firmware;
+ } else if (alen != 2) {
+ dev_err(&serial->dev->dev, "%s: Send command incomplete [%d]\n",
+ serial->type->description, alen);
+ goto no_firmware;
+ }
+
+ pipe = usb_rcvbulkpipe(serial->dev,
+ command_port->bulk_in_endpointAddress);
+ /* See the comment on the usb_clear_halt() above */
+ usb_clear_halt(serial->dev, pipe);
+ ret = usb_bulk_msg(serial->dev, pipe, result,
+ sizeof(*hw_info) + 1, &alen, COMMAND_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&serial->dev->dev, "%s: Couldn't get results [%d]\n",
+ serial->type->description, ret);
+ goto no_firmware;
+ } else if (alen != sizeof(*hw_info) + 1) {
+ dev_err(&serial->dev->dev, "%s: Get results incomplete [%d]\n",
+ serial->type->description, alen);
+ goto no_firmware;
+ } else if (result[0] != command[0]) {
+ dev_err(&serial->dev->dev, "%s: Command failed [%d]\n",
+ serial->type->description, result[0]);
+ goto no_firmware;
+ }
+
+ hw_info = (struct whiteheat_hw_info *)&result[1];
+
+ dev_info(&serial->dev->dev, "%s: Firmware v%d.%02d\n",
+ serial->type->description,
+ hw_info->sw_major_rev, hw_info->sw_minor_rev);
+
+ command_info = kmalloc_obj(struct whiteheat_command_private);
+ if (!command_info)
+ goto no_command_private;
+
+ mutex_init(&command_info->mutex);
+ command_info->port_running = 0;
+ init_waitqueue_head(&command_info->wait_command);
+ usb_set_serial_port_data(command_port, command_info);
+ command_port->write_urb->complete = command_port_write_callback;
+ command_port->read_urb->complete = command_port_read_callback;
+ kfree(result);
+ kfree(command);
+
+ return 0;
+
+no_firmware:
+ /* Firmware likely not running */
+ dev_err(&serial->dev->dev,
+ "%s: Unable to retrieve firmware version, try replugging\n",
+ serial->type->description);
+ dev_err(&serial->dev->dev,
+ "%s: If the firmware is not running (status led not blinking)\n",
+ serial->type->description);
+ dev_err(&serial->dev->dev,
+ "%s: please contact support@connecttech.com\n",
+ serial->type->description);
+ kfree(result);
+ kfree(command);
+ return -ENODEV;
+
+no_command_private:
+ kfree(result);
+no_result_buffer:
+ kfree(command);
+no_command_buffer:
+ return -ENOMEM;
+}
+
+static void whiteheat_release(struct usb_serial *serial)
+{
+ struct usb_serial_port *command_port;
+
+ /* free up our private data for our command port */
+ command_port = serial->port[COMMAND_PORT];
+ kfree(usb_get_serial_port_data(command_port));
+}
+
+static int whiteheat_port_probe(struct usb_serial_port *port)
+{
+ struct whiteheat_private *info;
+
+ info = kzalloc_obj(*info);
+ if (!info)
+ return -ENOMEM;
+
+ usb_set_serial_port_data(port, info);
+
+ return 0;
+}
+
+static void whiteheat_port_remove(struct usb_serial_port *port)
+{
+ struct whiteheat_private *info;
+
+ info = usb_get_serial_port_data(port);
+ kfree(info);
+}
+
+static int whiteheat_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ int retval;
+
+ retval = start_command_port(port->serial);
+ if (retval)
+ goto exit;
+
+ /* send an open port command */
+ retval = firm_open(port);
+ if (retval) {
+ stop_command_port(port->serial);
+ goto exit;
+ }
+
+ retval = firm_purge(port, WHITEHEAT_PURGE_RX | WHITEHEAT_PURGE_TX);
+ if (retval) {
+ firm_close(port);
+ stop_command_port(port->serial);
+ goto exit;
+ }
+
+ if (tty)
+ firm_setup_port(tty);
+
+ /* Work around HCD bugs */
+ usb_clear_halt(port->serial->dev, port->read_urb->pipe);
+ usb_clear_halt(port->serial->dev, port->write_urb->pipe);
+
+ retval = usb_serial_generic_open(tty, port);
+ if (retval) {
+ firm_close(port);
+ stop_command_port(port->serial);
+ goto exit;
+ }
+exit:
+ return retval;
+}
+
+
+static void whiteheat_close(struct usb_serial_port *port)
+{
+ firm_report_tx_done(port);
+ firm_close(port);
+
+ usb_serial_generic_close(port);
+
+ stop_command_port(port->serial);
+}
+
+static int whiteheat_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct whiteheat_private *info = usb_get_serial_port_data(port);
+ unsigned int modem_signals = 0;
+
+ firm_get_dtr_rts(port);
+ if (info->mcr & UART_MCR_DTR)
+ modem_signals |= TIOCM_DTR;
+ if (info->mcr & UART_MCR_RTS)
+ modem_signals |= TIOCM_RTS;
+
+ return modem_signals;
+}
+
+static int whiteheat_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct whiteheat_private *info = usb_get_serial_port_data(port);
+
+ if (set & TIOCM_RTS)
+ info->mcr |= UART_MCR_RTS;
+ if (set & TIOCM_DTR)
+ info->mcr |= UART_MCR_DTR;
+
+ if (clear & TIOCM_RTS)
+ info->mcr &= ~UART_MCR_RTS;
+ if (clear & TIOCM_DTR)
+ info->mcr &= ~UART_MCR_DTR;
+
+ firm_set_dtr(port, info->mcr & UART_MCR_DTR);
+ firm_set_rts(port, info->mcr & UART_MCR_RTS);
+ return 0;
+}
+
+
+static void whiteheat_get_serial(struct tty_struct *tty, struct serial_struct *ss)
+{
+ ss->baud_base = 460800;
+}
+
+
+static void whiteheat_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ firm_setup_port(tty);
+}
+
+static int whiteheat_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ return firm_set_break(port, break_state);
+}
+
+
+/*****************************************************************************
+ * Connect Tech's White Heat callback routines
+ *****************************************************************************/
+static void command_port_write_callback(struct urb *urb)
+{
+ int status = urb->status;
+
+ if (status) {
+ dev_dbg(&urb->dev->dev, "nonzero urb status: %d\n", status);
+ return;
+ }
+}
+
+
+static void command_port_read_callback(struct urb *urb)
+{
+ struct usb_serial_port *command_port = urb->context;
+ struct whiteheat_command_private *command_info;
+ int status = urb->status;
+ unsigned char *data = urb->transfer_buffer;
+ int result;
+
+ command_info = usb_get_serial_port_data(command_port);
+ if (!command_info) {
+ dev_dbg(&urb->dev->dev, "%s - command_info is NULL, exiting.\n", __func__);
+ return;
+ }
+ if (!urb->actual_length) {
+ dev_dbg(&urb->dev->dev, "%s - empty response, exiting.\n", __func__);
+ return;
+ }
+ if (status) {
+ dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n", __func__, status);
+ if (status != -ENOENT)
+ command_info->command_finished = WHITEHEAT_CMD_FAILURE;
+ wake_up(&command_info->wait_command);
+ return;
+ }
+
+ usb_serial_debug_data(&command_port->dev, __func__, urb->actual_length, data);
+
+ if (data[0] == WHITEHEAT_CMD_COMPLETE) {
+ command_info->command_finished = WHITEHEAT_CMD_COMPLETE;
+ wake_up(&command_info->wait_command);
+ } else if (data[0] == WHITEHEAT_CMD_FAILURE) {
+ command_info->command_finished = WHITEHEAT_CMD_FAILURE;
+ wake_up(&command_info->wait_command);
+ } else if (data[0] == WHITEHEAT_EVENT) {
+ /* These are unsolicited reports from the firmware, hence no
+ waiting command to wakeup */
+ dev_dbg(&urb->dev->dev, "%s - event received\n", __func__);
+ } else if ((data[0] == WHITEHEAT_GET_DTR_RTS) &&
+ (urb->actual_length - 1 <= sizeof(command_info->result_buffer))) {
+ memcpy(command_info->result_buffer, &data[1],
+ urb->actual_length - 1);
+ command_info->command_finished = WHITEHEAT_CMD_COMPLETE;
+ wake_up(&command_info->wait_command);
+ } else
+ dev_dbg(&urb->dev->dev, "%s - bad reply from firmware\n", __func__);
+
+ /* Continue trying to always read */
+ result = usb_submit_urb(command_port->read_urb, GFP_ATOMIC);
+ if (result)
+ dev_dbg(&urb->dev->dev, "%s - failed resubmitting read urb, error %d\n",
+ __func__, result);
+}
+
+
+/*****************************************************************************
+ * Connect Tech's White Heat firmware interface
+ *****************************************************************************/
+static int firm_send_command(struct usb_serial_port *port, __u8 command,
+ __u8 *data, __u8 datasize)
+{
+ struct usb_serial_port *command_port;
+ struct whiteheat_command_private *command_info;
+ struct whiteheat_private *info;
+ struct device *dev = &port->dev;
+ __u8 *transfer_buffer;
+ int retval = 0;
+ int t;
+
+ dev_dbg(dev, "%s - command %d\n", __func__, command);
+
+ command_port = port->serial->port[COMMAND_PORT];
+ command_info = usb_get_serial_port_data(command_port);
+
+ if (command_port->bulk_out_size < datasize + 1)
+ return -EIO;
+
+ mutex_lock(&command_info->mutex);
+ command_info->command_finished = false;
+
+ transfer_buffer = (__u8 *)command_port->write_urb->transfer_buffer;
+ transfer_buffer[0] = command;
+ memcpy(&transfer_buffer[1], data, datasize);
+ command_port->write_urb->transfer_buffer_length = datasize + 1;
+ retval = usb_submit_urb(command_port->write_urb, GFP_NOIO);
+ if (retval) {
+ dev_dbg(dev, "%s - submit urb failed\n", __func__);
+ goto exit;
+ }
+
+ /* wait for the command to complete */
+ t = wait_event_timeout(command_info->wait_command,
+ (bool)command_info->command_finished, COMMAND_TIMEOUT);
+ if (!t)
+ usb_kill_urb(command_port->write_urb);
+
+ if (command_info->command_finished == false) {
+ dev_dbg(dev, "%s - command timed out.\n", __func__);
+ retval = -ETIMEDOUT;
+ goto exit;
+ }
+
+ if (command_info->command_finished == WHITEHEAT_CMD_FAILURE) {
+ dev_dbg(dev, "%s - command failed.\n", __func__);
+ retval = -EIO;
+ goto exit;
+ }
+
+ if (command_info->command_finished == WHITEHEAT_CMD_COMPLETE) {
+ dev_dbg(dev, "%s - command completed.\n", __func__);
+ switch (command) {
+ case WHITEHEAT_GET_DTR_RTS:
+ info = usb_get_serial_port_data(port);
+ info->mcr = command_info->result_buffer[0];
+ break;
+ }
+ }
+exit:
+ mutex_unlock(&command_info->mutex);
+ return retval;
+}
+
+
+static int firm_open(struct usb_serial_port *port)
+{
+ struct whiteheat_simple open_command;
+
+ open_command.port = port->port_number + 1;
+ return firm_send_command(port, WHITEHEAT_OPEN,
+ (__u8 *)&open_command, sizeof(open_command));
+}
+
+
+static int firm_close(struct usb_serial_port *port)
+{
+ struct whiteheat_simple close_command;
+
+ close_command.port = port->port_number + 1;
+ return firm_send_command(port, WHITEHEAT_CLOSE,
+ (__u8 *)&close_command, sizeof(close_command));
+}
+
+
+static void firm_setup_port(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct device *dev = &port->dev;
+ struct whiteheat_port_settings port_settings;
+ unsigned int cflag = tty->termios.c_cflag;
+ speed_t baud;
+
+ port_settings.port = port->port_number + 1;
+
+ port_settings.bits = tty_get_char_size(cflag);
+ dev_dbg(dev, "%s - data bits = %d\n", __func__, port_settings.bits);
+
+ /* determine the parity */
+ if (cflag & PARENB)
+ if (cflag & CMSPAR)
+ if (cflag & PARODD)
+ port_settings.parity = WHITEHEAT_PAR_MARK;
+ else
+ port_settings.parity = WHITEHEAT_PAR_SPACE;
+ else
+ if (cflag & PARODD)
+ port_settings.parity = WHITEHEAT_PAR_ODD;
+ else
+ port_settings.parity = WHITEHEAT_PAR_EVEN;
+ else
+ port_settings.parity = WHITEHEAT_PAR_NONE;
+ dev_dbg(dev, "%s - parity = %c\n", __func__, port_settings.parity);
+
+ /* figure out the stop bits requested */
+ if (cflag & CSTOPB)
+ port_settings.stop = 2;
+ else
+ port_settings.stop = 1;
+ dev_dbg(dev, "%s - stop bits = %d\n", __func__, port_settings.stop);
+
+ /* figure out the flow control settings */
+ if (cflag & CRTSCTS)
+ port_settings.hflow = (WHITEHEAT_HFLOW_CTS |
+ WHITEHEAT_HFLOW_RTS);
+ else
+ port_settings.hflow = WHITEHEAT_HFLOW_NONE;
+ dev_dbg(dev, "%s - hardware flow control = %s %s %s %s\n", __func__,
+ (port_settings.hflow & WHITEHEAT_HFLOW_CTS) ? "CTS" : "",
+ (port_settings.hflow & WHITEHEAT_HFLOW_RTS) ? "RTS" : "",
+ (port_settings.hflow & WHITEHEAT_HFLOW_DSR) ? "DSR" : "",
+ (port_settings.hflow & WHITEHEAT_HFLOW_DTR) ? "DTR" : "");
+
+ /* determine software flow control */
+ if (I_IXOFF(tty))
+ port_settings.sflow = WHITEHEAT_SFLOW_RXTX;
+ else
+ port_settings.sflow = WHITEHEAT_SFLOW_NONE;
+ dev_dbg(dev, "%s - software flow control = %c\n", __func__, port_settings.sflow);
+
+ port_settings.xon = START_CHAR(tty);
+ port_settings.xoff = STOP_CHAR(tty);
+ dev_dbg(dev, "%s - XON = %2x, XOFF = %2x\n", __func__, port_settings.xon, port_settings.xoff);
+
+ /* get the baud rate wanted */
+ baud = tty_get_baud_rate(tty);
+ port_settings.baud = cpu_to_le32(baud);
+ dev_dbg(dev, "%s - baud rate = %u\n", __func__, baud);
+
+ /* fixme: should set validated settings */
+ tty_encode_baud_rate(tty, baud, baud);
+
+ /* handle any settings that aren't specified in the tty structure */
+ port_settings.lloop = 0;
+
+ /* now send the message to the device */
+ firm_send_command(port, WHITEHEAT_SETUP_PORT,
+ (__u8 *)&port_settings, sizeof(port_settings));
+}
+
+
+static int firm_set_rts(struct usb_serial_port *port, __u8 onoff)
+{
+ struct whiteheat_set_rdb rts_command;
+
+ rts_command.port = port->port_number + 1;
+ rts_command.state = onoff;
+ return firm_send_command(port, WHITEHEAT_SET_RTS,
+ (__u8 *)&rts_command, sizeof(rts_command));
+}
+
+
+static int firm_set_dtr(struct usb_serial_port *port, __u8 onoff)
+{
+ struct whiteheat_set_rdb dtr_command;
+
+ dtr_command.port = port->port_number + 1;
+ dtr_command.state = onoff;
+ return firm_send_command(port, WHITEHEAT_SET_DTR,
+ (__u8 *)&dtr_command, sizeof(dtr_command));
+}
+
+
+static int firm_set_break(struct usb_serial_port *port, __u8 onoff)
+{
+ struct whiteheat_set_rdb break_command;
+
+ break_command.port = port->port_number + 1;
+ break_command.state = onoff;
+ return firm_send_command(port, WHITEHEAT_SET_BREAK,
+ (__u8 *)&break_command, sizeof(break_command));
+}
+
+
+static int firm_purge(struct usb_serial_port *port, __u8 rxtx)
+{
+ struct whiteheat_purge purge_command;
+
+ purge_command.port = port->port_number + 1;
+ purge_command.what = rxtx;
+ return firm_send_command(port, WHITEHEAT_PURGE,
+ (__u8 *)&purge_command, sizeof(purge_command));
+}
+
+
+static int firm_get_dtr_rts(struct usb_serial_port *port)
+{
+ struct whiteheat_simple get_dr_command;
+
+ get_dr_command.port = port->port_number + 1;
+ return firm_send_command(port, WHITEHEAT_GET_DTR_RTS,
+ (__u8 *)&get_dr_command, sizeof(get_dr_command));
+}
+
+
+static int firm_report_tx_done(struct usb_serial_port *port)
+{
+ struct whiteheat_simple close_command;
+
+ close_command.port = port->port_number + 1;
+ return firm_send_command(port, WHITEHEAT_REPORT_TX_DONE,
+ (__u8 *)&close_command, sizeof(close_command));
+}
+
+
+/*****************************************************************************
+ * Connect Tech's White Heat utility functions
+ *****************************************************************************/
+static int start_command_port(struct usb_serial *serial)
+{
+ struct usb_serial_port *command_port;
+ struct whiteheat_command_private *command_info;
+ int retval = 0;
+
+ command_port = serial->port[COMMAND_PORT];
+ command_info = usb_get_serial_port_data(command_port);
+ mutex_lock(&command_info->mutex);
+ if (!command_info->port_running) {
+ /* Work around HCD bugs */
+ usb_clear_halt(serial->dev, command_port->read_urb->pipe);
+
+ retval = usb_submit_urb(command_port->read_urb, GFP_KERNEL);
+ if (retval) {
+ dev_err(&serial->dev->dev,
+ "%s - failed submitting read urb, error %d\n",
+ __func__, retval);
+ goto exit;
+ }
+ }
+ command_info->port_running++;
+
+exit:
+ mutex_unlock(&command_info->mutex);
+ return retval;
+}
+
+
+static void stop_command_port(struct usb_serial *serial)
+{
+ struct usb_serial_port *command_port;
+ struct whiteheat_command_private *command_info;
+
+ command_port = serial->port[COMMAND_PORT];
+ command_info = usb_get_serial_port_data(command_port);
+ mutex_lock(&command_info->mutex);
+ command_info->port_running--;
+ if (!command_info->port_running)
+ usb_kill_urb(command_port->read_urb);
+ mutex_unlock(&command_info->mutex);
+}
+
+module_usb_serial_driver(serial_drivers, id_table_combined);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+MODULE_FIRMWARE("whiteheat.fw");
+MODULE_FIRMWARE("whiteheat_loader.fw");
diff --git a/drivers/usb/serial/whiteheat.h b/drivers/usb/serial/whiteheat.h
new file mode 100644
index 000000000..7e63074c9
--- /dev/null
+++ b/drivers/usb/serial/whiteheat.h
@@ -0,0 +1,298 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * USB ConnectTech WhiteHEAT driver
+ *
+ * Copyright (C) 2002
+ * Connect Tech Inc.
+ *
+ * Copyright (C) 1999, 2000
+ * Greg Kroah-Hartman (greg@kroah.com)
+ *
+ * See Documentation/usb/usb-serial.rst for more information on using this
+ * driver
+ *
+ */
+
+#ifndef __LINUX_USB_SERIAL_WHITEHEAT_H
+#define __LINUX_USB_SERIAL_WHITEHEAT_H
+
+
+/* WhiteHEAT commands */
+#define WHITEHEAT_OPEN 1 /* open the port */
+#define WHITEHEAT_CLOSE 2 /* close the port */
+#define WHITEHEAT_SETUP_PORT 3 /* change port settings */
+#define WHITEHEAT_SET_RTS 4 /* turn RTS on or off */
+#define WHITEHEAT_SET_DTR 5 /* turn DTR on or off */
+#define WHITEHEAT_SET_BREAK 6 /* turn BREAK on or off */
+#define WHITEHEAT_DUMP 7 /* dump memory */
+#define WHITEHEAT_STATUS 8 /* get status */
+#define WHITEHEAT_PURGE 9 /* clear the UART fifos */
+#define WHITEHEAT_GET_DTR_RTS 10 /* get the state of DTR and RTS
+ for a port */
+#define WHITEHEAT_GET_HW_INFO 11 /* get EEPROM info and
+ hardware ID */
+#define WHITEHEAT_REPORT_TX_DONE 12 /* get the next TX done */
+#define WHITEHEAT_EVENT 13 /* unsolicited status events */
+#define WHITEHEAT_ECHO 14 /* send data to the indicated
+ IN endpoint */
+#define WHITEHEAT_DO_TEST 15 /* perform specified test */
+#define WHITEHEAT_CMD_COMPLETE 16 /* reply for some commands */
+#define WHITEHEAT_CMD_FAILURE 17 /* reply for failed commands */
+
+
+/*
+ * Commands to the firmware
+ */
+
+
+/*
+ * WHITEHEAT_OPEN
+ * WHITEHEAT_CLOSE
+ * WHITEHEAT_STATUS
+ * WHITEHEAT_GET_DTR_RTS
+ * WHITEHEAT_REPORT_TX_DONE
+*/
+struct whiteheat_simple {
+ __u8 port; /* port number (1 to N) */
+};
+
+
+/*
+ * WHITEHEAT_SETUP_PORT
+ */
+#define WHITEHEAT_PAR_NONE 'n' /* no parity */
+#define WHITEHEAT_PAR_EVEN 'e' /* even parity */
+#define WHITEHEAT_PAR_ODD 'o' /* odd parity */
+#define WHITEHEAT_PAR_SPACE '0' /* space (force 0) parity */
+#define WHITEHEAT_PAR_MARK '1' /* mark (force 1) parity */
+
+#define WHITEHEAT_SFLOW_NONE 'n' /* no software flow control */
+#define WHITEHEAT_SFLOW_RX 'r' /* XOFF/ON is sent when RX
+ fills/empties */
+#define WHITEHEAT_SFLOW_TX 't' /* when received XOFF/ON will
+ stop/start TX */
+#define WHITEHEAT_SFLOW_RXTX 'b' /* both SFLOW_RX and SFLOW_TX */
+
+#define WHITEHEAT_HFLOW_NONE 0x00 /* no hardware flow control */
+#define WHITEHEAT_HFLOW_RTS_TOGGLE 0x01 /* RTS is on during transmit,
+ off otherwise */
+#define WHITEHEAT_HFLOW_DTR 0x02 /* DTR is off/on when RX
+ fills/empties */
+#define WHITEHEAT_HFLOW_CTS 0x08 /* when received CTS off/on
+ will stop/start TX */
+#define WHITEHEAT_HFLOW_DSR 0x10 /* when received DSR off/on
+ will stop/start TX */
+#define WHITEHEAT_HFLOW_RTS 0x80 /* RTS is off/on when RX
+ fills/empties */
+
+struct whiteheat_port_settings {
+ __u8 port; /* port number (1 to N) */
+ __le32 baud; /* any value 7 - 460800, firmware calculates
+ best fit; arrives little endian */
+ __u8 bits; /* 5, 6, 7, or 8 */
+ __u8 stop; /* 1 or 2, default 1 (2 = 1.5 if bits = 5) */
+ __u8 parity; /* see WHITEHEAT_PAR_* above */
+ __u8 sflow; /* see WHITEHEAT_SFLOW_* above */
+ __u8 xoff; /* XOFF byte value */
+ __u8 xon; /* XON byte value */
+ __u8 hflow; /* see WHITEHEAT_HFLOW_* above */
+ __u8 lloop; /* 0/1 turns local loopback mode off/on */
+} __attribute__ ((packed));
+
+
+/*
+ * WHITEHEAT_SET_RTS
+ * WHITEHEAT_SET_DTR
+ * WHITEHEAT_SET_BREAK
+ */
+#define WHITEHEAT_RTS_OFF 0x00
+#define WHITEHEAT_RTS_ON 0x01
+#define WHITEHEAT_DTR_OFF 0x00
+#define WHITEHEAT_DTR_ON 0x01
+#define WHITEHEAT_BREAK_OFF 0x00
+#define WHITEHEAT_BREAK_ON 0x01
+
+struct whiteheat_set_rdb {
+ __u8 port; /* port number (1 to N) */
+ __u8 state; /* 0/1 turns signal off/on */
+};
+
+
+/*
+ * WHITEHEAT_DUMP
+ */
+#define WHITEHEAT_DUMP_MEM_DATA 'd' /* data */
+#define WHITEHEAT_DUMP_MEM_IDATA 'i' /* idata */
+#define WHITEHEAT_DUMP_MEM_BDATA 'b' /* bdata */
+#define WHITEHEAT_DUMP_MEM_XDATA 'x' /* xdata */
+
+/*
+ * Allowable address ranges (firmware checks address):
+ * Type DATA: 0x00 - 0xff
+ * Type IDATA: 0x80 - 0xff
+ * Type BDATA: 0x20 - 0x2f
+ * Type XDATA: 0x0000 - 0xffff
+ *
+ * B/I/DATA all read the local memory space
+ * XDATA reads the external memory space
+ * BDATA returns bits as bytes
+ *
+ * NOTE: 0x80 - 0xff (local space) are the Special Function Registers
+ * of the 8051, and some have on-read side-effects.
+ */
+
+struct whiteheat_dump {
+ __u8 mem_type; /* see WHITEHEAT_DUMP_* above */
+ __u16 addr; /* address, see restrictions above */
+ __u16 length; /* number of bytes to dump, max 63 bytes */
+};
+
+
+/*
+ * WHITEHEAT_PURGE
+ */
+#define WHITEHEAT_PURGE_RX 0x01 /* purge rx fifos */
+#define WHITEHEAT_PURGE_TX 0x02 /* purge tx fifos */
+
+struct whiteheat_purge {
+ __u8 port; /* port number (1 to N) */
+ __u8 what; /* bit pattern of what to purge */
+};
+
+
+/*
+ * WHITEHEAT_ECHO
+ */
+struct whiteheat_echo {
+ __u8 port; /* port number (1 to N) */
+ __u8 length; /* length of message to echo, max 61 bytes */
+ __u8 echo_data[61]; /* data to echo */
+};
+
+
+/*
+ * WHITEHEAT_DO_TEST
+ */
+#define WHITEHEAT_TEST_UART_RW 0x01 /* read/write uart registers */
+#define WHITEHEAT_TEST_UART_INTR 0x02 /* uart interrupt */
+#define WHITEHEAT_TEST_SETUP_CONT 0x03 /* setup for
+ PORT_CONT/PORT_DISCONT */
+#define WHITEHEAT_TEST_PORT_CONT 0x04 /* port connect */
+#define WHITEHEAT_TEST_PORT_DISCONT 0x05 /* port disconnect */
+#define WHITEHEAT_TEST_UART_CLK_START 0x06 /* uart clock test start */
+#define WHITEHEAT_TEST_UART_CLK_STOP 0x07 /* uart clock test stop */
+#define WHITEHEAT_TEST_MODEM_FT 0x08 /* modem signals, requires a
+ loopback cable/connector */
+#define WHITEHEAT_TEST_ERASE_EEPROM 0x09 /* erase eeprom */
+#define WHITEHEAT_TEST_READ_EEPROM 0x0a /* read eeprom */
+#define WHITEHEAT_TEST_PROGRAM_EEPROM 0x0b /* program eeprom */
+
+struct whiteheat_test {
+ __u8 port; /* port number (1 to n) */
+ __u8 test; /* see WHITEHEAT_TEST_* above*/
+ __u8 info[32]; /* additional info */
+};
+
+
+/*
+ * Replies from the firmware
+ */
+
+
+/*
+ * WHITEHEAT_STATUS
+ */
+#define WHITEHEAT_EVENT_MODEM 0x01 /* modem field is valid */
+#define WHITEHEAT_EVENT_ERROR 0x02 /* error field is valid */
+#define WHITEHEAT_EVENT_FLOW 0x04 /* flow field is valid */
+#define WHITEHEAT_EVENT_CONNECT 0x08 /* connect field is valid */
+
+#define WHITEHEAT_FLOW_NONE 0x00 /* no flow control active */
+#define WHITEHEAT_FLOW_HARD_OUT 0x01 /* TX is stopped by CTS
+ (waiting for CTS to go on) */
+#define WHITEHEAT_FLOW_HARD_IN 0x02 /* remote TX is stopped
+ by RTS */
+#define WHITEHEAT_FLOW_SOFT_OUT 0x04 /* TX is stopped by XOFF
+ received (waiting for XON) */
+#define WHITEHEAT_FLOW_SOFT_IN 0x08 /* remote TX is stopped by XOFF
+ transmitted */
+#define WHITEHEAT_FLOW_TX_DONE 0x80 /* TX has completed */
+
+struct whiteheat_status_info {
+ __u8 port; /* port number (1 to N) */
+ __u8 event; /* indicates what the current event is,
+ see WHITEHEAT_EVENT_* above */
+ __u8 modem; /* modem signal status (copy of uart's
+ MSR register) */
+ __u8 error; /* line status (copy of uart's LSR register) */
+ __u8 flow; /* flow control state, see WHITEHEAT_FLOW_*
+ above */
+ __u8 connect; /* 0 means not connected, non-zero means
+ connected */
+};
+
+
+/*
+ * WHITEHEAT_GET_DTR_RTS
+ */
+struct whiteheat_dr_info {
+ __u8 mcr; /* copy of uart's MCR register */
+};
+
+
+/*
+ * WHITEHEAT_GET_HW_INFO
+ */
+struct whiteheat_hw_info {
+ __u8 hw_id; /* hardware id number, WhiteHEAT = 0 */
+ __u8 sw_major_rev; /* major version number */
+ __u8 sw_minor_rev; /* minor version number */
+ struct whiteheat_hw_eeprom_info {
+ __u8 b0; /* B0 */
+ __u8 vendor_id_low; /* vendor id (low byte) */
+ __u8 vendor_id_high; /* vendor id (high byte) */
+ __u8 product_id_low; /* product id (low byte) */
+ __u8 product_id_high; /* product id (high byte) */
+ __u8 device_id_low; /* device id (low byte) */
+ __u8 device_id_high; /* device id (high byte) */
+ __u8 not_used_1;
+ __u8 serial_number_0; /* serial number (low byte) */
+ __u8 serial_number_1; /* serial number */
+ __u8 serial_number_2; /* serial number */
+ __u8 serial_number_3; /* serial number (high byte) */
+ __u8 not_used_2;
+ __u8 not_used_3;
+ __u8 checksum_low; /* checksum (low byte) */
+ __u8 checksum_high; /* checksum (high byte */
+ } hw_eeprom_info; /* EEPROM contents */
+};
+
+
+/*
+ * WHITEHEAT_EVENT
+ */
+struct whiteheat_event_info {
+ __u8 port; /* port number (1 to N) */
+ __u8 event; /* see whiteheat_status_info.event */
+ __u8 info; /* see whiteheat_status_info.modem, .error,
+ .flow, .connect */
+};
+
+
+/*
+ * WHITEHEAT_DO_TEST
+ */
+#define WHITEHEAT_TEST_FAIL 0x00 /* test failed */
+#define WHITEHEAT_TEST_UNKNOWN 0x01 /* unknown test requested */
+#define WHITEHEAT_TEST_PASS 0xff /* test passed */
+
+struct whiteheat_test_info {
+ __u8 port; /* port number (1 to N) */
+ __u8 test; /* indicates which test this is a response for,
+ see WHITEHEAT_DO_TEST above */
+ __u8 status; /* see WHITEHEAT_TEST_* above */
+ __u8 results[32]; /* test-dependent results */
+};
+
+
+#endif
diff --git a/drivers/usb/serial/wishbone-serial.c b/drivers/usb/serial/wishbone-serial.c
new file mode 100644
index 000000000..4a1fed4cb
--- /dev/null
+++ b/drivers/usb/serial/wishbone-serial.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * USB Wishbone-Serial adapter driver
+ *
+ * Copyright (C) 2013 Wesley W. Terpstra <w.terpstra@gsi.de>
+ * Copyright (C) 2013 GSI Helmholtz Centre for Heavy Ion Research GmbH
+ */
+
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#define GSI_VENDOR_OPENCLOSE 0xB0
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE_AND_INTERFACE_INFO(0x1D50, 0x6062, 0xFF, 0xFF, 0xFF) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+/*
+ * Etherbone must be told that a new stream has begun before data arrives.
+ * This is necessary to restart the negotiation of Wishbone bus parameters.
+ * Similarly, when the stream ends, Etherbone must be told so that the cycle
+ * line can be driven low in the case that userspace failed to do so.
+ */
+static int usb_gsi_openclose(struct usb_serial_port *port, int value)
+{
+ struct usb_device *dev = port->serial->dev;
+
+ return usb_control_msg(
+ dev,
+ usb_sndctrlpipe(dev, 0), /* Send to EP0OUT */
+ GSI_VENDOR_OPENCLOSE,
+ USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
+ value, /* wValue = device is open(1) or closed(0) */
+ port->serial->interface->cur_altsetting->desc.bInterfaceNumber,
+ NULL, 0, /* There is no data stage */
+ 5000); /* Timeout till operation fails */
+}
+
+static int wishbone_serial_open(struct tty_struct *tty,
+ struct usb_serial_port *port)
+{
+ int retval;
+
+ retval = usb_gsi_openclose(port, 1);
+ if (retval) {
+ dev_err(&port->serial->dev->dev,
+ "Could not mark device as open (%d)\n",
+ retval);
+ return retval;
+ }
+
+ retval = usb_serial_generic_open(tty, port);
+ if (retval)
+ usb_gsi_openclose(port, 0);
+
+ return retval;
+}
+
+static void wishbone_serial_close(struct usb_serial_port *port)
+{
+ usb_serial_generic_close(port);
+ usb_gsi_openclose(port, 0);
+}
+
+static struct usb_serial_driver wishbone_serial_device = {
+ .driver = {
+ .name = "wishbone_serial",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .open = &wishbone_serial_open,
+ .close = &wishbone_serial_close,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &wishbone_serial_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR("Wesley W. Terpstra <w.terpstra@gsi.de>");
+MODULE_DESCRIPTION("USB Wishbone-Serial adapter");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c
new file mode 100644
index 000000000..352c765d8
--- /dev/null
+++ b/drivers/usb/serial/xr_serial.c
@@ -0,0 +1,1111 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * MaxLinear/Exar USB to Serial driver
+ *
+ * Copyright (c) 2020 Manivannan Sadhasivam <mani@kernel.org>
+ * Copyright (c) 2021 Johan Hovold <johan@kernel.org>
+ *
+ * Based on the initial driver written by Patong Yang:
+ *
+ * https://lore.kernel.org/r/20180404070634.nhspvmxcjwfgjkcv@advantechmxl-desktop
+ *
+ * Copyright (c) 2018 Patong Yang <patong.mxl@gmail.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/serial.h>
+
+struct xr_txrx_clk_mask {
+ u16 tx;
+ u16 rx0;
+ u16 rx1;
+};
+
+#define XR_INT_OSC_HZ 48000000U
+#define XR21V141X_MIN_SPEED 46U
+#define XR21V141X_MAX_SPEED XR_INT_OSC_HZ
+
+/* XR21V141X register blocks */
+#define XR21V141X_UART_REG_BLOCK 0
+#define XR21V141X_UM_REG_BLOCK 4
+#define XR21V141X_UART_CUSTOM_BLOCK 0x66
+
+/* XR21V141X UART registers */
+#define XR21V141X_CLOCK_DIVISOR_0 0x04
+#define XR21V141X_CLOCK_DIVISOR_1 0x05
+#define XR21V141X_CLOCK_DIVISOR_2 0x06
+#define XR21V141X_TX_CLOCK_MASK_0 0x07
+#define XR21V141X_TX_CLOCK_MASK_1 0x08
+#define XR21V141X_RX_CLOCK_MASK_0 0x09
+#define XR21V141X_RX_CLOCK_MASK_1 0x0a
+#define XR21V141X_REG_FORMAT 0x0b
+
+/* XR21V141X UART Manager registers */
+#define XR21V141X_UM_FIFO_ENABLE_REG 0x10
+#define XR21V141X_UM_ENABLE_TX_FIFO 0x01
+#define XR21V141X_UM_ENABLE_RX_FIFO 0x02
+
+#define XR21V141X_UM_RX_FIFO_RESET 0x18
+#define XR21V141X_UM_TX_FIFO_RESET 0x1c
+
+#define XR_UART_ENABLE_TX 0x1
+#define XR_UART_ENABLE_RX 0x2
+
+#define XR_GPIO_RI BIT(0)
+#define XR_GPIO_CD BIT(1)
+#define XR_GPIO_DSR BIT(2)
+#define XR_GPIO_DTR BIT(3)
+#define XR_GPIO_CTS BIT(4)
+#define XR_GPIO_RTS BIT(5)
+#define XR_GPIO_CLK BIT(6)
+#define XR_GPIO_XEN BIT(7)
+#define XR_GPIO_TXT BIT(8)
+#define XR_GPIO_RXT BIT(9)
+
+#define XR_UART_DATA_MASK GENMASK(3, 0)
+#define XR_UART_DATA_7 0x7
+#define XR_UART_DATA_8 0x8
+
+#define XR_UART_PARITY_MASK GENMASK(6, 4)
+#define XR_UART_PARITY_SHIFT 4
+#define XR_UART_PARITY_NONE (0x0 << XR_UART_PARITY_SHIFT)
+#define XR_UART_PARITY_ODD (0x1 << XR_UART_PARITY_SHIFT)
+#define XR_UART_PARITY_EVEN (0x2 << XR_UART_PARITY_SHIFT)
+#define XR_UART_PARITY_MARK (0x3 << XR_UART_PARITY_SHIFT)
+#define XR_UART_PARITY_SPACE (0x4 << XR_UART_PARITY_SHIFT)
+
+#define XR_UART_STOP_MASK BIT(7)
+#define XR_UART_STOP_SHIFT 7
+#define XR_UART_STOP_1 (0x0 << XR_UART_STOP_SHIFT)
+#define XR_UART_STOP_2 (0x1 << XR_UART_STOP_SHIFT)
+
+#define XR_UART_FLOW_MODE_NONE 0x0
+#define XR_UART_FLOW_MODE_HW 0x1
+#define XR_UART_FLOW_MODE_SW 0x2
+
+#define XR_GPIO_MODE_SEL_MASK GENMASK(2, 0)
+#define XR_GPIO_MODE_SEL_RTS_CTS 0x1
+#define XR_GPIO_MODE_SEL_DTR_DSR 0x2
+#define XR_GPIO_MODE_SEL_RS485 0x3
+#define XR_GPIO_MODE_SEL_RS485_ADDR 0x4
+#define XR_GPIO_MODE_RS485_TX_H 0x8
+#define XR_GPIO_MODE_TX_TOGGLE 0x100
+#define XR_GPIO_MODE_RX_TOGGLE 0x200
+
+#define XR_FIFO_RESET 0x1
+
+#define XR_CUSTOM_DRIVER_ACTIVE 0x1
+
+static int xr21v141x_uart_enable(struct usb_serial_port *port);
+static int xr21v141x_uart_disable(struct usb_serial_port *port);
+static int xr21v141x_fifo_reset(struct usb_serial_port *port);
+static void xr21v141x_set_line_settings(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+
+struct xr_type {
+ int reg_width;
+ u8 reg_recipient;
+ u8 set_reg;
+ u8 get_reg;
+
+ u16 uart_enable;
+ u16 flow_control;
+ u16 xon_char;
+ u16 xoff_char;
+ u16 tx_break;
+ u16 gpio_mode;
+ u16 gpio_direction;
+ u16 gpio_set;
+ u16 gpio_clear;
+ u16 gpio_status;
+ u16 tx_fifo_reset;
+ u16 rx_fifo_reset;
+ u16 custom_driver;
+
+ bool have_5_6_bit_mode;
+ bool have_xmit_toggle;
+
+ int (*enable)(struct usb_serial_port *port);
+ int (*disable)(struct usb_serial_port *port);
+ int (*fifo_reset)(struct usb_serial_port *port);
+ void (*set_line_settings)(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios);
+};
+
+enum xr_type_id {
+ XR21V141X,
+ XR21B142X,
+ XR21B1411,
+ XR2280X,
+ XR_TYPE_COUNT,
+};
+
+static const struct xr_type xr_types[] = {
+ [XR21V141X] = {
+ .reg_width = 8,
+ .reg_recipient = USB_RECIP_DEVICE,
+ .set_reg = 0x00,
+ .get_reg = 0x01,
+
+ .uart_enable = 0x03,
+ .flow_control = 0x0c,
+ .xon_char = 0x10,
+ .xoff_char = 0x11,
+ .tx_break = 0x14,
+ .gpio_mode = 0x1a,
+ .gpio_direction = 0x1b,
+ .gpio_set = 0x1d,
+ .gpio_clear = 0x1e,
+ .gpio_status = 0x1f,
+
+ .enable = xr21v141x_uart_enable,
+ .disable = xr21v141x_uart_disable,
+ .fifo_reset = xr21v141x_fifo_reset,
+ .set_line_settings = xr21v141x_set_line_settings,
+ },
+ [XR21B142X] = {
+ .reg_width = 16,
+ .reg_recipient = USB_RECIP_INTERFACE,
+ .set_reg = 0x00,
+ .get_reg = 0x00,
+
+ .uart_enable = 0x00,
+ .flow_control = 0x06,
+ .xon_char = 0x07,
+ .xoff_char = 0x08,
+ .tx_break = 0x0a,
+ .gpio_mode = 0x0c,
+ .gpio_direction = 0x0d,
+ .gpio_set = 0x0e,
+ .gpio_clear = 0x0f,
+ .gpio_status = 0x10,
+ .tx_fifo_reset = 0x40,
+ .rx_fifo_reset = 0x43,
+ .custom_driver = 0x60,
+
+ .have_5_6_bit_mode = true,
+ .have_xmit_toggle = true,
+ },
+ [XR21B1411] = {
+ .reg_width = 12,
+ .reg_recipient = USB_RECIP_DEVICE,
+ .set_reg = 0x00,
+ .get_reg = 0x01,
+
+ .uart_enable = 0xc00,
+ .flow_control = 0xc06,
+ .xon_char = 0xc07,
+ .xoff_char = 0xc08,
+ .tx_break = 0xc0a,
+ .gpio_mode = 0xc0c,
+ .gpio_direction = 0xc0d,
+ .gpio_set = 0xc0e,
+ .gpio_clear = 0xc0f,
+ .gpio_status = 0xc10,
+ .tx_fifo_reset = 0xc80,
+ .rx_fifo_reset = 0xcc0,
+ .custom_driver = 0x20d,
+ },
+ [XR2280X] = {
+ .reg_width = 16,
+ .reg_recipient = USB_RECIP_DEVICE,
+ .set_reg = 0x05,
+ .get_reg = 0x05,
+
+ .uart_enable = 0x40,
+ .flow_control = 0x46,
+ .xon_char = 0x47,
+ .xoff_char = 0x48,
+ .tx_break = 0x4a,
+ .gpio_mode = 0x4c,
+ .gpio_direction = 0x4d,
+ .gpio_set = 0x4e,
+ .gpio_clear = 0x4f,
+ .gpio_status = 0x50,
+ .tx_fifo_reset = 0x60,
+ .rx_fifo_reset = 0x63,
+ .custom_driver = 0x81,
+ },
+};
+
+struct xr_data {
+ const struct xr_type *type;
+ u8 channel; /* zero-based index or interface number */
+ struct serial_rs485 rs485;
+};
+
+static int xr_set_reg(struct usb_serial_port *port, u8 channel, u16 reg, u16 val)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+ const struct xr_type *type = data->type;
+ struct usb_serial *serial = port->serial;
+ int ret;
+
+ ret = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ type->set_reg,
+ USB_DIR_OUT | USB_TYPE_VENDOR | type->reg_recipient,
+ val, (channel << 8) | reg, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (ret < 0) {
+ dev_err(&port->dev, "Failed to set reg 0x%02x: %d\n", reg, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int xr_get_reg(struct usb_serial_port *port, u8 channel, u16 reg, u16 *val)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+ const struct xr_type *type = data->type;
+ struct usb_serial *serial = port->serial;
+ u8 *dmabuf;
+ int ret, len;
+
+ if (type->reg_width == 8)
+ len = 1;
+ else
+ len = 2;
+
+ dmabuf = kmalloc(len, GFP_KERNEL);
+ if (!dmabuf)
+ return -ENOMEM;
+
+ ret = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+ type->get_reg,
+ USB_DIR_IN | USB_TYPE_VENDOR | type->reg_recipient,
+ 0, (channel << 8) | reg, dmabuf, len,
+ USB_CTRL_GET_TIMEOUT);
+ if (ret == len) {
+ if (len == 2)
+ *val = le16_to_cpup((__le16 *)dmabuf);
+ else
+ *val = *dmabuf;
+ ret = 0;
+ } else {
+ dev_err(&port->dev, "Failed to get reg 0x%02x: %d\n", reg, ret);
+ if (ret >= 0)
+ ret = -EIO;
+ }
+
+ kfree(dmabuf);
+
+ return ret;
+}
+
+static int xr_set_reg_uart(struct usb_serial_port *port, u16 reg, u16 val)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ return xr_set_reg(port, data->channel, reg, val);
+}
+
+static int xr_get_reg_uart(struct usb_serial_port *port, u16 reg, u16 *val)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ return xr_get_reg(port, data->channel, reg, val);
+}
+
+static int xr_set_reg_um(struct usb_serial_port *port, u8 reg_base, u8 val)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+ u8 reg;
+
+ reg = reg_base + data->channel;
+
+ return xr_set_reg(port, XR21V141X_UM_REG_BLOCK, reg, val);
+}
+
+static int __xr_uart_enable(struct usb_serial_port *port)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ return xr_set_reg_uart(port, data->type->uart_enable,
+ XR_UART_ENABLE_TX | XR_UART_ENABLE_RX);
+}
+
+static int __xr_uart_disable(struct usb_serial_port *port)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ return xr_set_reg_uart(port, data->type->uart_enable, 0);
+}
+
+/*
+ * According to datasheet, below is the recommended sequence for enabling UART
+ * module in XR21V141X:
+ *
+ * Enable Tx FIFO
+ * Enable Tx and Rx
+ * Enable Rx FIFO
+ */
+static int xr21v141x_uart_enable(struct usb_serial_port *port)
+{
+ int ret;
+
+ ret = xr_set_reg_um(port, XR21V141X_UM_FIFO_ENABLE_REG,
+ XR21V141X_UM_ENABLE_TX_FIFO);
+ if (ret)
+ return ret;
+
+ ret = __xr_uart_enable(port);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_um(port, XR21V141X_UM_FIFO_ENABLE_REG,
+ XR21V141X_UM_ENABLE_TX_FIFO | XR21V141X_UM_ENABLE_RX_FIFO);
+ if (ret)
+ __xr_uart_disable(port);
+
+ return ret;
+}
+
+static int xr21v141x_uart_disable(struct usb_serial_port *port)
+{
+ int ret;
+
+ ret = __xr_uart_disable(port);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_um(port, XR21V141X_UM_FIFO_ENABLE_REG, 0);
+
+ return ret;
+}
+
+static int xr_uart_enable(struct usb_serial_port *port)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ if (data->type->enable)
+ return data->type->enable(port);
+
+ return __xr_uart_enable(port);
+}
+
+static int xr_uart_disable(struct usb_serial_port *port)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ if (data->type->disable)
+ return data->type->disable(port);
+
+ return __xr_uart_disable(port);
+}
+
+static int xr21v141x_fifo_reset(struct usb_serial_port *port)
+{
+ int ret;
+
+ ret = xr_set_reg_um(port, XR21V141X_UM_TX_FIFO_RESET, XR_FIFO_RESET);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_um(port, XR21V141X_UM_RX_FIFO_RESET, XR_FIFO_RESET);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int xr_fifo_reset(struct usb_serial_port *port)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+ int ret;
+
+ if (data->type->fifo_reset)
+ return data->type->fifo_reset(port);
+
+ ret = xr_set_reg_uart(port, data->type->tx_fifo_reset, XR_FIFO_RESET);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_uart(port, data->type->rx_fifo_reset, XR_FIFO_RESET);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int xr_tiocmget(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct xr_data *data = usb_get_serial_port_data(port);
+ u16 status;
+ int ret;
+
+ ret = xr_get_reg_uart(port, data->type->gpio_status, &status);
+ if (ret)
+ return ret;
+
+ /*
+ * Modem control pins are active low, so reading '0' means it is active
+ * and '1' means not active.
+ */
+ ret = ((status & XR_GPIO_DTR) ? 0 : TIOCM_DTR) |
+ ((status & XR_GPIO_RTS) ? 0 : TIOCM_RTS) |
+ ((status & XR_GPIO_CTS) ? 0 : TIOCM_CTS) |
+ ((status & XR_GPIO_DSR) ? 0 : TIOCM_DSR) |
+ ((status & XR_GPIO_RI) ? 0 : TIOCM_RI) |
+ ((status & XR_GPIO_CD) ? 0 : TIOCM_CD);
+
+ return ret;
+}
+
+static int xr_tiocmset_port(struct usb_serial_port *port,
+ unsigned int set, unsigned int clear)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+ const struct xr_type *type = data->type;
+ u16 gpio_set = 0;
+ u16 gpio_clr = 0;
+ int ret = 0;
+
+ /* Modem control pins are active low, so set & clr are swapped */
+ if (set & TIOCM_RTS)
+ gpio_clr |= XR_GPIO_RTS;
+ if (set & TIOCM_DTR)
+ gpio_clr |= XR_GPIO_DTR;
+ if (clear & TIOCM_RTS)
+ gpio_set |= XR_GPIO_RTS;
+ if (clear & TIOCM_DTR)
+ gpio_set |= XR_GPIO_DTR;
+
+ /* Writing '0' to gpio_{set/clr} bits has no effect, so no need to do */
+ if (gpio_clr)
+ ret = xr_set_reg_uart(port, type->gpio_clear, gpio_clr);
+
+ if (gpio_set)
+ ret = xr_set_reg_uart(port, type->gpio_set, gpio_set);
+
+ return ret;
+}
+
+static int xr_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ return xr_tiocmset_port(port, set, clear);
+}
+
+static void xr_dtr_rts(struct usb_serial_port *port, int on)
+{
+ if (on)
+ xr_tiocmset_port(port, TIOCM_DTR | TIOCM_RTS, 0);
+ else
+ xr_tiocmset_port(port, 0, TIOCM_DTR | TIOCM_RTS);
+}
+
+static int xr_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct xr_data *data = usb_get_serial_port_data(port);
+ const struct xr_type *type = data->type;
+ u16 state;
+
+ if (break_state == 0)
+ state = 0;
+ else
+ state = GENMASK(type->reg_width - 1, 0);
+
+ dev_dbg(&port->dev, "Turning break %s\n", state == 0 ? "off" : "on");
+
+ return xr_set_reg_uart(port, type->tx_break, state);
+}
+
+/* Tx and Rx clock mask values obtained from section 3.3.4 of datasheet */
+static const struct xr_txrx_clk_mask xr21v141x_txrx_clk_masks[] = {
+ { 0x000, 0x000, 0x000 },
+ { 0x000, 0x000, 0x000 },
+ { 0x100, 0x000, 0x100 },
+ { 0x020, 0x400, 0x020 },
+ { 0x010, 0x100, 0x010 },
+ { 0x208, 0x040, 0x208 },
+ { 0x104, 0x820, 0x108 },
+ { 0x844, 0x210, 0x884 },
+ { 0x444, 0x110, 0x444 },
+ { 0x122, 0x888, 0x224 },
+ { 0x912, 0x448, 0x924 },
+ { 0x492, 0x248, 0x492 },
+ { 0x252, 0x928, 0x292 },
+ { 0x94a, 0x4a4, 0xa52 },
+ { 0x52a, 0xaa4, 0x54a },
+ { 0xaaa, 0x954, 0x4aa },
+ { 0xaaa, 0x554, 0xaaa },
+ { 0x555, 0xad4, 0x5aa },
+ { 0xb55, 0xab4, 0x55a },
+ { 0x6b5, 0x5ac, 0xb56 },
+ { 0x5b5, 0xd6c, 0x6d6 },
+ { 0xb6d, 0xb6a, 0xdb6 },
+ { 0x76d, 0x6da, 0xbb6 },
+ { 0xedd, 0xdda, 0x76e },
+ { 0xddd, 0xbba, 0xeee },
+ { 0x7bb, 0xf7a, 0xdde },
+ { 0xf7b, 0xef6, 0x7de },
+ { 0xdf7, 0xbf6, 0xf7e },
+ { 0x7f7, 0xfee, 0xefe },
+ { 0xfdf, 0xfbe, 0x7fe },
+ { 0xf7f, 0xefe, 0xffe },
+ { 0xfff, 0xffe, 0xffd },
+};
+
+static int xr21v141x_set_baudrate(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ u32 divisor, baud, idx;
+ u16 tx_mask, rx_mask;
+ int ret;
+
+ baud = tty->termios.c_ospeed;
+ if (!baud)
+ return 0;
+
+ baud = clamp(baud, XR21V141X_MIN_SPEED, XR21V141X_MAX_SPEED);
+ divisor = XR_INT_OSC_HZ / baud;
+ idx = ((32 * XR_INT_OSC_HZ) / baud) & 0x1f;
+ tx_mask = xr21v141x_txrx_clk_masks[idx].tx;
+
+ if (divisor & 0x01)
+ rx_mask = xr21v141x_txrx_clk_masks[idx].rx1;
+ else
+ rx_mask = xr21v141x_txrx_clk_masks[idx].rx0;
+
+ dev_dbg(&port->dev, "Setting baud rate: %u\n", baud);
+ /*
+ * XR21V141X uses fractional baud rate generator with 48MHz internal
+ * oscillator and 19-bit programmable divisor. So theoretically it can
+ * generate most commonly used baud rates with high accuracy.
+ */
+ ret = xr_set_reg_uart(port, XR21V141X_CLOCK_DIVISOR_0,
+ divisor & 0xff);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_uart(port, XR21V141X_CLOCK_DIVISOR_1,
+ (divisor >> 8) & 0xff);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_uart(port, XR21V141X_CLOCK_DIVISOR_2,
+ (divisor >> 16) & 0xff);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_uart(port, XR21V141X_TX_CLOCK_MASK_0,
+ tx_mask & 0xff);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_uart(port, XR21V141X_TX_CLOCK_MASK_1,
+ (tx_mask >> 8) & 0xff);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_uart(port, XR21V141X_RX_CLOCK_MASK_0,
+ rx_mask & 0xff);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_uart(port, XR21V141X_RX_CLOCK_MASK_1,
+ (rx_mask >> 8) & 0xff);
+ if (ret)
+ return ret;
+
+ tty_encode_baud_rate(tty, baud, baud);
+
+ return 0;
+}
+
+static void xr_set_flow_mode(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+ const struct xr_type *type = data->type;
+ u16 flow, gpio_mode;
+ bool rs485_enabled;
+ int ret;
+
+ ret = xr_get_reg_uart(port, type->gpio_mode, &gpio_mode);
+ if (ret)
+ return;
+
+ /*
+ * According to the datasheets, the UART needs to be disabled while
+ * writing to the FLOW_CONTROL register (XR21V141X), or any register
+ * but GPIO_SET, GPIO_CLEAR, TX_BREAK and ERROR_STATUS (XR21B142X).
+ */
+ xr_uart_disable(port);
+
+ /* Set GPIO mode for controlling the pins manually by default. */
+ gpio_mode &= ~XR_GPIO_MODE_SEL_MASK;
+
+ rs485_enabled = !!(data->rs485.flags & SER_RS485_ENABLED);
+ if (rs485_enabled) {
+ dev_dbg(&port->dev, "Enabling RS-485\n");
+ gpio_mode |= XR_GPIO_MODE_SEL_RS485;
+ if (data->rs485.flags & SER_RS485_RTS_ON_SEND)
+ gpio_mode &= ~XR_GPIO_MODE_RS485_TX_H;
+ else
+ gpio_mode |= XR_GPIO_MODE_RS485_TX_H;
+ }
+
+ if (C_CRTSCTS(tty) && C_BAUD(tty) != B0 && !rs485_enabled) {
+ dev_dbg(&port->dev, "Enabling hardware flow ctrl\n");
+ gpio_mode |= XR_GPIO_MODE_SEL_RTS_CTS;
+ flow = XR_UART_FLOW_MODE_HW;
+ } else if (I_IXON(tty)) {
+ u8 start_char = START_CHAR(tty);
+ u8 stop_char = STOP_CHAR(tty);
+
+ dev_dbg(&port->dev, "Enabling sw flow ctrl\n");
+ flow = XR_UART_FLOW_MODE_SW;
+
+ xr_set_reg_uart(port, type->xon_char, start_char);
+ xr_set_reg_uart(port, type->xoff_char, stop_char);
+ } else {
+ dev_dbg(&port->dev, "Disabling flow ctrl\n");
+ flow = XR_UART_FLOW_MODE_NONE;
+ }
+
+ xr_set_reg_uart(port, type->flow_control, flow);
+ xr_set_reg_uart(port, type->gpio_mode, gpio_mode);
+
+ xr_uart_enable(port);
+
+ if (C_BAUD(tty) == B0)
+ xr_dtr_rts(port, 0);
+ else if (old_termios && (old_termios->c_cflag & CBAUD) == B0)
+ xr_dtr_rts(port, 1);
+}
+
+static void xr21v141x_set_line_settings(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct ktermios *termios = &tty->termios;
+ u8 bits = 0;
+ int ret;
+
+ if (!old_termios || (tty->termios.c_ospeed != old_termios->c_ospeed))
+ xr21v141x_set_baudrate(tty, port);
+
+ switch (C_CSIZE(tty)) {
+ case CS5:
+ case CS6:
+ /* CS5 and CS6 are not supported, so just restore old setting */
+ termios->c_cflag &= ~CSIZE;
+ if (old_termios)
+ termios->c_cflag |= old_termios->c_cflag & CSIZE;
+ else
+ termios->c_cflag |= CS8;
+
+ if (C_CSIZE(tty) == CS7)
+ bits |= XR_UART_DATA_7;
+ else
+ bits |= XR_UART_DATA_8;
+ break;
+ case CS7:
+ bits |= XR_UART_DATA_7;
+ break;
+ case CS8:
+ default:
+ bits |= XR_UART_DATA_8;
+ break;
+ }
+
+ if (C_PARENB(tty)) {
+ if (C_CMSPAR(tty)) {
+ if (C_PARODD(tty))
+ bits |= XR_UART_PARITY_MARK;
+ else
+ bits |= XR_UART_PARITY_SPACE;
+ } else {
+ if (C_PARODD(tty))
+ bits |= XR_UART_PARITY_ODD;
+ else
+ bits |= XR_UART_PARITY_EVEN;
+ }
+ }
+
+ if (C_CSTOPB(tty))
+ bits |= XR_UART_STOP_2;
+ else
+ bits |= XR_UART_STOP_1;
+
+ ret = xr_set_reg_uart(port, XR21V141X_REG_FORMAT, bits);
+ if (ret)
+ return;
+}
+
+static void xr_cdc_set_line_coding(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+ struct usb_host_interface *alt = port->serial->interface->cur_altsetting;
+ struct usb_device *udev = port->serial->dev;
+ struct usb_cdc_line_coding *lc;
+ int ret;
+
+ lc = kzalloc_obj(*lc);
+ if (!lc)
+ return;
+
+ if (tty->termios.c_ospeed)
+ lc->dwDTERate = cpu_to_le32(tty->termios.c_ospeed);
+ else
+ lc->dwDTERate = cpu_to_le32(9600);
+
+ if (C_CSTOPB(tty))
+ lc->bCharFormat = USB_CDC_2_STOP_BITS;
+ else
+ lc->bCharFormat = USB_CDC_1_STOP_BITS;
+
+ if (C_PARENB(tty)) {
+ if (C_CMSPAR(tty)) {
+ if (C_PARODD(tty))
+ lc->bParityType = USB_CDC_MARK_PARITY;
+ else
+ lc->bParityType = USB_CDC_SPACE_PARITY;
+ } else {
+ if (C_PARODD(tty))
+ lc->bParityType = USB_CDC_ODD_PARITY;
+ else
+ lc->bParityType = USB_CDC_EVEN_PARITY;
+ }
+ } else {
+ lc->bParityType = USB_CDC_NO_PARITY;
+ }
+
+ if (!data->type->have_5_6_bit_mode &&
+ (C_CSIZE(tty) == CS5 || C_CSIZE(tty) == CS6)) {
+ tty->termios.c_cflag &= ~CSIZE;
+ if (old_termios)
+ tty->termios.c_cflag |= old_termios->c_cflag & CSIZE;
+ else
+ tty->termios.c_cflag |= CS8;
+ }
+
+ switch (C_CSIZE(tty)) {
+ case CS5:
+ lc->bDataBits = 5;
+ break;
+ case CS6:
+ lc->bDataBits = 6;
+ break;
+ case CS7:
+ lc->bDataBits = 7;
+ break;
+ case CS8:
+ default:
+ lc->bDataBits = 8;
+ break;
+ }
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ USB_CDC_REQ_SET_LINE_CODING,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, alt->desc.bInterfaceNumber,
+ lc, sizeof(*lc), USB_CTRL_SET_TIMEOUT);
+ if (ret < 0)
+ dev_err(&port->dev, "Failed to set line coding: %d\n", ret);
+
+ kfree(lc);
+}
+
+static void xr_sanitize_serial_rs485(struct serial_rs485 *rs485)
+{
+ if (!(rs485->flags & SER_RS485_ENABLED)) {
+ memset(rs485, 0, sizeof(*rs485));
+ return;
+ }
+
+ /* RTS always toggles after TX */
+ if (rs485->flags & SER_RS485_RTS_ON_SEND)
+ rs485->flags &= ~SER_RS485_RTS_AFTER_SEND;
+ else
+ rs485->flags |= SER_RS485_RTS_AFTER_SEND;
+
+ /* Only the flags are implemented at the moment */
+ rs485->flags &= SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND |
+ SER_RS485_RTS_AFTER_SEND;
+ rs485->delay_rts_before_send = 0;
+ rs485->delay_rts_after_send = 0;
+ memset(rs485->padding, 0, sizeof(rs485->padding));
+}
+
+static int xr_get_rs485_config(struct tty_struct *tty,
+ struct serial_rs485 __user *argp)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ down_read(&tty->termios_rwsem);
+ if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) {
+ up_read(&tty->termios_rwsem);
+ return -EFAULT;
+ }
+ up_read(&tty->termios_rwsem);
+
+ return 0;
+}
+
+static int xr_set_rs485_config(struct tty_struct *tty,
+ struct serial_rs485 __user *argp)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct xr_data *data = usb_get_serial_port_data(port);
+ struct serial_rs485 rs485;
+
+ if (copy_from_user(&rs485, argp, sizeof(rs485)))
+ return -EFAULT;
+ xr_sanitize_serial_rs485(&rs485);
+
+ down_write(&tty->termios_rwsem);
+ data->rs485 = rs485;
+ xr_set_flow_mode(tty, port, NULL);
+ up_write(&tty->termios_rwsem);
+
+ if (copy_to_user(argp, &rs485, sizeof(rs485)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int xr_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+
+ switch (cmd) {
+ case TIOCGRS485:
+ return xr_get_rs485_config(tty, argp);
+ case TIOCSRS485:
+ return xr_set_rs485_config(tty, argp);
+ }
+
+ return -ENOIOCTLCMD;
+}
+
+static void xr_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ /*
+ * XR21V141X does not have a CUSTOM_DRIVER flag and always enters CDC
+ * mode upon receiving CDC requests.
+ */
+ if (data->type->set_line_settings)
+ data->type->set_line_settings(tty, port, old_termios);
+ else
+ xr_cdc_set_line_coding(tty, port, old_termios);
+
+ xr_set_flow_mode(tty, port, old_termios);
+}
+
+static int xr_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+ int ret;
+
+ ret = xr_fifo_reset(port);
+ if (ret)
+ return ret;
+
+ ret = xr_uart_enable(port);
+ if (ret) {
+ dev_err(&port->dev, "Failed to enable UART\n");
+ return ret;
+ }
+
+ /* Setup termios */
+ if (tty)
+ xr_set_termios(tty, port, NULL);
+
+ ret = usb_serial_generic_open(tty, port);
+ if (ret) {
+ xr_uart_disable(port);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void xr_close(struct usb_serial_port *port)
+{
+ usb_serial_generic_close(port);
+
+ xr_uart_disable(port);
+}
+
+static int xr_probe(struct usb_serial *serial, const struct usb_device_id *id)
+{
+ struct usb_interface *control = serial->interface;
+ struct usb_host_interface *alt = control->cur_altsetting;
+ struct usb_cdc_parsed_header hdrs;
+ struct usb_cdc_union_desc *desc;
+ struct usb_interface *data;
+ int ret;
+
+ ret = cdc_parse_cdc_header(&hdrs, control, alt->extra, alt->extralen);
+ if (ret < 0)
+ return -ENODEV;
+
+ desc = hdrs.usb_cdc_union_desc;
+ if (!desc)
+ return -ENODEV;
+
+ data = usb_ifnum_to_if(serial->dev, desc->bSlaveInterface0);
+ if (!data)
+ return -ENODEV;
+
+ ret = usb_serial_claim_interface(serial, data);
+ if (ret)
+ return ret;
+
+ usb_set_serial_data(serial, (void *)id->driver_info);
+
+ return 0;
+}
+
+static int xr_gpio_init(struct usb_serial_port *port, const struct xr_type *type)
+{
+ u16 mask, mode;
+ int ret;
+
+ /*
+ * Configure all pins as GPIO except for Receive and Transmit Toggle.
+ */
+ mode = 0;
+ if (type->have_xmit_toggle)
+ mode |= XR_GPIO_MODE_RX_TOGGLE | XR_GPIO_MODE_TX_TOGGLE;
+
+ ret = xr_set_reg_uart(port, type->gpio_mode, mode);
+ if (ret)
+ return ret;
+
+ /*
+ * Configure DTR and RTS as outputs and make sure they are deasserted
+ * (active low), and configure RI, CD, DSR and CTS as inputs.
+ */
+ mask = XR_GPIO_DTR | XR_GPIO_RTS;
+ ret = xr_set_reg_uart(port, type->gpio_direction, mask);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_uart(port, type->gpio_set, mask);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int xr_port_probe(struct usb_serial_port *port)
+{
+ struct usb_interface_descriptor *desc;
+ const struct xr_type *type;
+ struct xr_data *data;
+ enum xr_type_id type_id;
+ int ret;
+
+ type_id = (int)(unsigned long)usb_get_serial_data(port->serial);
+ type = &xr_types[type_id];
+
+ data = kzalloc_obj(*data);
+ if (!data)
+ return -ENOMEM;
+
+ data->type = type;
+
+ desc = &port->serial->interface->cur_altsetting->desc;
+ if (type_id == XR21V141X)
+ data->channel = desc->bInterfaceNumber / 2;
+ else
+ data->channel = desc->bInterfaceNumber;
+
+ usb_set_serial_port_data(port, data);
+
+ if (type->custom_driver) {
+ ret = xr_set_reg_uart(port, type->custom_driver,
+ XR_CUSTOM_DRIVER_ACTIVE);
+ if (ret)
+ goto err_free;
+ }
+
+ ret = xr_gpio_init(port, type);
+ if (ret)
+ goto err_free;
+
+ return 0;
+
+err_free:
+ kfree(data);
+
+ return ret;
+}
+
+static void xr_port_remove(struct usb_serial_port *port)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ kfree(data);
+}
+
+#define XR_DEVICE(vid, pid, type) \
+ USB_DEVICE_INTERFACE_CLASS((vid), (pid), USB_CLASS_COMM), \
+ .driver_info = (type)
+
+static const struct usb_device_id id_table[] = {
+ { XR_DEVICE(0x04e2, 0x1400, XR2280X) },
+ { XR_DEVICE(0x04e2, 0x1401, XR2280X) },
+ { XR_DEVICE(0x04e2, 0x1402, XR2280X) },
+ { XR_DEVICE(0x04e2, 0x1403, XR2280X) },
+ { XR_DEVICE(0x04e2, 0x1410, XR21V141X) },
+ { XR_DEVICE(0x04e2, 0x1411, XR21B1411) },
+ { XR_DEVICE(0x04e2, 0x1412, XR21V141X) },
+ { XR_DEVICE(0x04e2, 0x1414, XR21V141X) },
+ { XR_DEVICE(0x04e2, 0x1420, XR21B142X) },
+ { XR_DEVICE(0x04e2, 0x1422, XR21B142X) },
+ { XR_DEVICE(0x04e2, 0x1424, XR21B142X) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_serial_driver xr_device = {
+ .driver = {
+ .name = "xr_serial",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+ .probe = xr_probe,
+ .port_probe = xr_port_probe,
+ .port_remove = xr_port_remove,
+ .open = xr_open,
+ .close = xr_close,
+ .break_ctl = xr_break_ctl,
+ .set_termios = xr_set_termios,
+ .tiocmget = xr_tiocmget,
+ .tiocmset = xr_tiocmset,
+ .ioctl = xr_ioctl,
+ .dtr_rts = xr_dtr_rts
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &xr_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR("Manivannan Sadhasivam <mani@kernel.org>");
+MODULE_DESCRIPTION("MaxLinear/Exar USB to Serial driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/xsens_mt.c b/drivers/usb/serial/xsens_mt.c
new file mode 100644
index 000000000..151a6e893
--- /dev/null
+++ b/drivers/usb/serial/xsens_mt.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xsens MT USB driver
+ *
+ * Copyright (C) 2013 Xsens <info@xsens.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#define XSENS_VID 0x2639
+
+#define MTi_10_IMU_PID 0x0001
+#define MTi_20_VRU_PID 0x0002
+#define MTi_30_AHRS_PID 0x0003
+
+#define MTi_100_IMU_PID 0x0011
+#define MTi_200_VRU_PID 0x0012
+#define MTi_300_AHRS_PID 0x0013
+
+#define MTi_G_700_GPS_INS_PID 0x0017
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(XSENS_VID, MTi_10_IMU_PID) },
+ { USB_DEVICE(XSENS_VID, MTi_20_VRU_PID) },
+ { USB_DEVICE(XSENS_VID, MTi_30_AHRS_PID) },
+
+ { USB_DEVICE(XSENS_VID, MTi_100_IMU_PID) },
+ { USB_DEVICE(XSENS_VID, MTi_200_VRU_PID) },
+ { USB_DEVICE(XSENS_VID, MTi_300_AHRS_PID) },
+
+ { USB_DEVICE(XSENS_VID, MTi_G_700_GPS_INS_PID) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static int xsens_mt_probe(struct usb_serial *serial,
+ const struct usb_device_id *id)
+{
+ if (serial->interface->cur_altsetting->desc.bInterfaceNumber == 1)
+ return 0;
+
+ return -ENODEV;
+}
+
+static struct usb_serial_driver xsens_mt_device = {
+ .driver = {
+ .name = "xsens_mt",
+ },
+ .id_table = id_table,
+ .num_ports = 1,
+
+ .probe = xsens_mt_probe,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+ &xsens_mt_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR("Frans Klaver <frans.klaver@xsens.com>");
+MODULE_DESCRIPTION("USB-serial driver for Xsens motion trackers");
+MODULE_LICENSE("GPL v2");