18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * USB ConnectTech WhiteHEAT driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *	Copyright (C) 2002
68c2ecf20Sopenharmony_ci *	    Connect Tech Inc.
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci *	Copyright (C) 1999 - 2001
98c2ecf20Sopenharmony_ci *	    Greg Kroah-Hartman (greg@kroah.com)
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * See Documentation/usb/usb-serial.rst for more information on using this
128c2ecf20Sopenharmony_ci * driver
138c2ecf20Sopenharmony_ci */
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/kernel.h>
168c2ecf20Sopenharmony_ci#include <linux/errno.h>
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci#include <linux/tty.h>
198c2ecf20Sopenharmony_ci#include <linux/tty_driver.h>
208c2ecf20Sopenharmony_ci#include <linux/tty_flip.h>
218c2ecf20Sopenharmony_ci#include <linux/module.h>
228c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
238c2ecf20Sopenharmony_ci#include <linux/mutex.h>
248c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
258c2ecf20Sopenharmony_ci#include <asm/termbits.h>
268c2ecf20Sopenharmony_ci#include <linux/usb.h>
278c2ecf20Sopenharmony_ci#include <linux/serial_reg.h>
288c2ecf20Sopenharmony_ci#include <linux/serial.h>
298c2ecf20Sopenharmony_ci#include <linux/usb/serial.h>
308c2ecf20Sopenharmony_ci#include <linux/usb/ezusb.h>
318c2ecf20Sopenharmony_ci#include "whiteheat.h"			/* WhiteHEAT specific commands */
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#ifndef CMSPAR
348c2ecf20Sopenharmony_ci#define CMSPAR 0
358c2ecf20Sopenharmony_ci#endif
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci/*
388c2ecf20Sopenharmony_ci * Version Information
398c2ecf20Sopenharmony_ci */
408c2ecf20Sopenharmony_ci#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Stuart MacDonald <stuartm@connecttech.com>"
418c2ecf20Sopenharmony_ci#define DRIVER_DESC "USB ConnectTech WhiteHEAT driver"
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci#define CONNECT_TECH_VENDOR_ID		0x0710
448c2ecf20Sopenharmony_ci#define CONNECT_TECH_FAKE_WHITE_HEAT_ID	0x0001
458c2ecf20Sopenharmony_ci#define CONNECT_TECH_WHITE_HEAT_ID	0x8001
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci/*
488c2ecf20Sopenharmony_ci   ID tables for whiteheat are unusual, because we want to different
498c2ecf20Sopenharmony_ci   things for different versions of the device.  Eventually, this
508c2ecf20Sopenharmony_ci   will be doable from a single table.  But, for now, we define two
518c2ecf20Sopenharmony_ci   separate ID tables, and then a third table that combines them
528c2ecf20Sopenharmony_ci   just for the purpose of exporting the autoloading information.
538c2ecf20Sopenharmony_ci*/
548c2ecf20Sopenharmony_cistatic const struct usb_device_id id_table_std[] = {
558c2ecf20Sopenharmony_ci	{ USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_WHITE_HEAT_ID) },
568c2ecf20Sopenharmony_ci	{ }						/* Terminating entry */
578c2ecf20Sopenharmony_ci};
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic const struct usb_device_id id_table_prerenumeration[] = {
608c2ecf20Sopenharmony_ci	{ USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_FAKE_WHITE_HEAT_ID) },
618c2ecf20Sopenharmony_ci	{ }						/* Terminating entry */
628c2ecf20Sopenharmony_ci};
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic const struct usb_device_id id_table_combined[] = {
658c2ecf20Sopenharmony_ci	{ USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_WHITE_HEAT_ID) },
668c2ecf20Sopenharmony_ci	{ USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_FAKE_WHITE_HEAT_ID) },
678c2ecf20Sopenharmony_ci	{ }						/* Terminating entry */
688c2ecf20Sopenharmony_ci};
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, id_table_combined);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci/* function prototypes for the Connect Tech WhiteHEAT prerenumeration device */
748c2ecf20Sopenharmony_cistatic int  whiteheat_firmware_download(struct usb_serial *serial,
758c2ecf20Sopenharmony_ci					const struct usb_device_id *id);
768c2ecf20Sopenharmony_cistatic int  whiteheat_firmware_attach(struct usb_serial *serial);
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci/* function prototypes for the Connect Tech WhiteHEAT serial converter */
798c2ecf20Sopenharmony_cistatic int  whiteheat_attach(struct usb_serial *serial);
808c2ecf20Sopenharmony_cistatic void whiteheat_release(struct usb_serial *serial);
818c2ecf20Sopenharmony_cistatic int  whiteheat_port_probe(struct usb_serial_port *port);
828c2ecf20Sopenharmony_cistatic int  whiteheat_port_remove(struct usb_serial_port *port);
838c2ecf20Sopenharmony_cistatic int  whiteheat_open(struct tty_struct *tty,
848c2ecf20Sopenharmony_ci			struct usb_serial_port *port);
858c2ecf20Sopenharmony_cistatic void whiteheat_close(struct usb_serial_port *port);
868c2ecf20Sopenharmony_cistatic int  whiteheat_get_serial(struct tty_struct *tty,
878c2ecf20Sopenharmony_ci			struct serial_struct *ss);
888c2ecf20Sopenharmony_cistatic void whiteheat_set_termios(struct tty_struct *tty,
898c2ecf20Sopenharmony_ci			struct usb_serial_port *port, struct ktermios *old);
908c2ecf20Sopenharmony_cistatic int  whiteheat_tiocmget(struct tty_struct *tty);
918c2ecf20Sopenharmony_cistatic int  whiteheat_tiocmset(struct tty_struct *tty,
928c2ecf20Sopenharmony_ci			unsigned int set, unsigned int clear);
938c2ecf20Sopenharmony_cistatic void whiteheat_break_ctl(struct tty_struct *tty, int break_state);
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic struct usb_serial_driver whiteheat_fake_device = {
968c2ecf20Sopenharmony_ci	.driver = {
978c2ecf20Sopenharmony_ci		.owner =	THIS_MODULE,
988c2ecf20Sopenharmony_ci		.name =		"whiteheatnofirm",
998c2ecf20Sopenharmony_ci	},
1008c2ecf20Sopenharmony_ci	.description =		"Connect Tech - WhiteHEAT - (prerenumeration)",
1018c2ecf20Sopenharmony_ci	.id_table =		id_table_prerenumeration,
1028c2ecf20Sopenharmony_ci	.num_ports =		1,
1038c2ecf20Sopenharmony_ci	.probe =		whiteheat_firmware_download,
1048c2ecf20Sopenharmony_ci	.attach =		whiteheat_firmware_attach,
1058c2ecf20Sopenharmony_ci};
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_cistatic struct usb_serial_driver whiteheat_device = {
1088c2ecf20Sopenharmony_ci	.driver = {
1098c2ecf20Sopenharmony_ci		.owner =	THIS_MODULE,
1108c2ecf20Sopenharmony_ci		.name =		"whiteheat",
1118c2ecf20Sopenharmony_ci	},
1128c2ecf20Sopenharmony_ci	.description =		"Connect Tech - WhiteHEAT",
1138c2ecf20Sopenharmony_ci	.id_table =		id_table_std,
1148c2ecf20Sopenharmony_ci	.num_ports =		4,
1158c2ecf20Sopenharmony_ci	.num_bulk_in =		5,
1168c2ecf20Sopenharmony_ci	.num_bulk_out =		5,
1178c2ecf20Sopenharmony_ci	.attach =		whiteheat_attach,
1188c2ecf20Sopenharmony_ci	.release =		whiteheat_release,
1198c2ecf20Sopenharmony_ci	.port_probe =		whiteheat_port_probe,
1208c2ecf20Sopenharmony_ci	.port_remove =		whiteheat_port_remove,
1218c2ecf20Sopenharmony_ci	.open =			whiteheat_open,
1228c2ecf20Sopenharmony_ci	.close =		whiteheat_close,
1238c2ecf20Sopenharmony_ci	.get_serial =		whiteheat_get_serial,
1248c2ecf20Sopenharmony_ci	.set_termios =		whiteheat_set_termios,
1258c2ecf20Sopenharmony_ci	.break_ctl =		whiteheat_break_ctl,
1268c2ecf20Sopenharmony_ci	.tiocmget =		whiteheat_tiocmget,
1278c2ecf20Sopenharmony_ci	.tiocmset =		whiteheat_tiocmset,
1288c2ecf20Sopenharmony_ci	.throttle =		usb_serial_generic_throttle,
1298c2ecf20Sopenharmony_ci	.unthrottle =		usb_serial_generic_unthrottle,
1308c2ecf20Sopenharmony_ci};
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_cistatic struct usb_serial_driver * const serial_drivers[] = {
1338c2ecf20Sopenharmony_ci	&whiteheat_fake_device, &whiteheat_device, NULL
1348c2ecf20Sopenharmony_ci};
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_cistruct whiteheat_command_private {
1378c2ecf20Sopenharmony_ci	struct mutex		mutex;
1388c2ecf20Sopenharmony_ci	__u8			port_running;
1398c2ecf20Sopenharmony_ci	__u8			command_finished;
1408c2ecf20Sopenharmony_ci	wait_queue_head_t	wait_command; /* for handling sleeping whilst
1418c2ecf20Sopenharmony_ci						 waiting for a command to
1428c2ecf20Sopenharmony_ci						 finish */
1438c2ecf20Sopenharmony_ci	__u8			result_buffer[64];
1448c2ecf20Sopenharmony_ci};
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_cistruct whiteheat_private {
1478c2ecf20Sopenharmony_ci	__u8			mcr;		/* FIXME: no locking on mcr */
1488c2ecf20Sopenharmony_ci};
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci/* local function prototypes */
1528c2ecf20Sopenharmony_cistatic int start_command_port(struct usb_serial *serial);
1538c2ecf20Sopenharmony_cistatic void stop_command_port(struct usb_serial *serial);
1548c2ecf20Sopenharmony_cistatic void command_port_write_callback(struct urb *urb);
1558c2ecf20Sopenharmony_cistatic void command_port_read_callback(struct urb *urb);
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic int firm_send_command(struct usb_serial_port *port, __u8 command,
1588c2ecf20Sopenharmony_ci						__u8 *data, __u8 datasize);
1598c2ecf20Sopenharmony_cistatic int firm_open(struct usb_serial_port *port);
1608c2ecf20Sopenharmony_cistatic int firm_close(struct usb_serial_port *port);
1618c2ecf20Sopenharmony_cistatic void firm_setup_port(struct tty_struct *tty);
1628c2ecf20Sopenharmony_cistatic int firm_set_rts(struct usb_serial_port *port, __u8 onoff);
1638c2ecf20Sopenharmony_cistatic int firm_set_dtr(struct usb_serial_port *port, __u8 onoff);
1648c2ecf20Sopenharmony_cistatic int firm_set_break(struct usb_serial_port *port, __u8 onoff);
1658c2ecf20Sopenharmony_cistatic int firm_purge(struct usb_serial_port *port, __u8 rxtx);
1668c2ecf20Sopenharmony_cistatic int firm_get_dtr_rts(struct usb_serial_port *port);
1678c2ecf20Sopenharmony_cistatic int firm_report_tx_done(struct usb_serial_port *port);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci#define COMMAND_PORT		4
1718c2ecf20Sopenharmony_ci#define COMMAND_TIMEOUT		(2*HZ)	/* 2 second timeout for a command */
1728c2ecf20Sopenharmony_ci#define	COMMAND_TIMEOUT_MS	2000
1738c2ecf20Sopenharmony_ci#define CLOSING_DELAY		(30 * HZ)
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci/*****************************************************************************
1778c2ecf20Sopenharmony_ci * Connect Tech's White Heat prerenumeration driver functions
1788c2ecf20Sopenharmony_ci *****************************************************************************/
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci/* steps to download the firmware to the WhiteHEAT device:
1818c2ecf20Sopenharmony_ci - hold the reset (by writing to the reset bit of the CPUCS register)
1828c2ecf20Sopenharmony_ci - download the VEND_AX.HEX file to the chip using VENDOR_REQUEST-ANCHOR_LOAD
1838c2ecf20Sopenharmony_ci - release the reset (by writing to the CPUCS register)
1848c2ecf20Sopenharmony_ci - download the WH.HEX file for all addresses greater than 0x1b3f using
1858c2ecf20Sopenharmony_ci   VENDOR_REQUEST-ANCHOR_EXTERNAL_RAM_LOAD
1868c2ecf20Sopenharmony_ci - hold the reset
1878c2ecf20Sopenharmony_ci - download the WH.HEX file for all addresses less than 0x1b40 using
1888c2ecf20Sopenharmony_ci   VENDOR_REQUEST_ANCHOR_LOAD
1898c2ecf20Sopenharmony_ci - release the reset
1908c2ecf20Sopenharmony_ci - device renumerated itself and comes up as new device id with all
1918c2ecf20Sopenharmony_ci   firmware download completed.
1928c2ecf20Sopenharmony_ci*/
1938c2ecf20Sopenharmony_cistatic int whiteheat_firmware_download(struct usb_serial *serial,
1948c2ecf20Sopenharmony_ci					const struct usb_device_id *id)
1958c2ecf20Sopenharmony_ci{
1968c2ecf20Sopenharmony_ci	int response;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	response = ezusb_fx1_ihex_firmware_download(serial->dev, "whiteheat_loader.fw");
1998c2ecf20Sopenharmony_ci	if (response >= 0) {
2008c2ecf20Sopenharmony_ci		response = ezusb_fx1_ihex_firmware_download(serial->dev, "whiteheat.fw");
2018c2ecf20Sopenharmony_ci		if (response >= 0)
2028c2ecf20Sopenharmony_ci			return 0;
2038c2ecf20Sopenharmony_ci	}
2048c2ecf20Sopenharmony_ci	return -ENOENT;
2058c2ecf20Sopenharmony_ci}
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_cistatic int whiteheat_firmware_attach(struct usb_serial *serial)
2098c2ecf20Sopenharmony_ci{
2108c2ecf20Sopenharmony_ci	/* We want this device to fail to have a driver assigned to it */
2118c2ecf20Sopenharmony_ci	return 1;
2128c2ecf20Sopenharmony_ci}
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci/*****************************************************************************
2168c2ecf20Sopenharmony_ci * Connect Tech's White Heat serial driver functions
2178c2ecf20Sopenharmony_ci *****************************************************************************/
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_cistatic int whiteheat_attach(struct usb_serial *serial)
2208c2ecf20Sopenharmony_ci{
2218c2ecf20Sopenharmony_ci	struct usb_serial_port *command_port;
2228c2ecf20Sopenharmony_ci	struct whiteheat_command_private *command_info;
2238c2ecf20Sopenharmony_ci	struct whiteheat_hw_info *hw_info;
2248c2ecf20Sopenharmony_ci	int pipe;
2258c2ecf20Sopenharmony_ci	int ret;
2268c2ecf20Sopenharmony_ci	int alen;
2278c2ecf20Sopenharmony_ci	__u8 *command;
2288c2ecf20Sopenharmony_ci	__u8 *result;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	command_port = serial->port[COMMAND_PORT];
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	pipe = usb_sndbulkpipe(serial->dev,
2338c2ecf20Sopenharmony_ci			command_port->bulk_out_endpointAddress);
2348c2ecf20Sopenharmony_ci	command = kmalloc(2, GFP_KERNEL);
2358c2ecf20Sopenharmony_ci	if (!command)
2368c2ecf20Sopenharmony_ci		goto no_command_buffer;
2378c2ecf20Sopenharmony_ci	command[0] = WHITEHEAT_GET_HW_INFO;
2388c2ecf20Sopenharmony_ci	command[1] = 0;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	result = kmalloc(sizeof(*hw_info) + 1, GFP_KERNEL);
2418c2ecf20Sopenharmony_ci	if (!result)
2428c2ecf20Sopenharmony_ci		goto no_result_buffer;
2438c2ecf20Sopenharmony_ci	/*
2448c2ecf20Sopenharmony_ci	 * When the module is reloaded the firmware is still there and
2458c2ecf20Sopenharmony_ci	 * the endpoints are still in the usb core unchanged. This is the
2468c2ecf20Sopenharmony_ci	 * unlinking bug in disguise. Same for the call below.
2478c2ecf20Sopenharmony_ci	 */
2488c2ecf20Sopenharmony_ci	usb_clear_halt(serial->dev, pipe);
2498c2ecf20Sopenharmony_ci	ret = usb_bulk_msg(serial->dev, pipe, command, 2,
2508c2ecf20Sopenharmony_ci						&alen, COMMAND_TIMEOUT_MS);
2518c2ecf20Sopenharmony_ci	if (ret) {
2528c2ecf20Sopenharmony_ci		dev_err(&serial->dev->dev, "%s: Couldn't send command [%d]\n",
2538c2ecf20Sopenharmony_ci			serial->type->description, ret);
2548c2ecf20Sopenharmony_ci		goto no_firmware;
2558c2ecf20Sopenharmony_ci	} else if (alen != 2) {
2568c2ecf20Sopenharmony_ci		dev_err(&serial->dev->dev, "%s: Send command incomplete [%d]\n",
2578c2ecf20Sopenharmony_ci			serial->type->description, alen);
2588c2ecf20Sopenharmony_ci		goto no_firmware;
2598c2ecf20Sopenharmony_ci	}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	pipe = usb_rcvbulkpipe(serial->dev,
2628c2ecf20Sopenharmony_ci				command_port->bulk_in_endpointAddress);
2638c2ecf20Sopenharmony_ci	/* See the comment on the usb_clear_halt() above */
2648c2ecf20Sopenharmony_ci	usb_clear_halt(serial->dev, pipe);
2658c2ecf20Sopenharmony_ci	ret = usb_bulk_msg(serial->dev, pipe, result,
2668c2ecf20Sopenharmony_ci			sizeof(*hw_info) + 1, &alen, COMMAND_TIMEOUT_MS);
2678c2ecf20Sopenharmony_ci	if (ret) {
2688c2ecf20Sopenharmony_ci		dev_err(&serial->dev->dev, "%s: Couldn't get results [%d]\n",
2698c2ecf20Sopenharmony_ci			serial->type->description, ret);
2708c2ecf20Sopenharmony_ci		goto no_firmware;
2718c2ecf20Sopenharmony_ci	} else if (alen != sizeof(*hw_info) + 1) {
2728c2ecf20Sopenharmony_ci		dev_err(&serial->dev->dev, "%s: Get results incomplete [%d]\n",
2738c2ecf20Sopenharmony_ci			serial->type->description, alen);
2748c2ecf20Sopenharmony_ci		goto no_firmware;
2758c2ecf20Sopenharmony_ci	} else if (result[0] != command[0]) {
2768c2ecf20Sopenharmony_ci		dev_err(&serial->dev->dev, "%s: Command failed [%d]\n",
2778c2ecf20Sopenharmony_ci			serial->type->description, result[0]);
2788c2ecf20Sopenharmony_ci		goto no_firmware;
2798c2ecf20Sopenharmony_ci	}
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	hw_info = (struct whiteheat_hw_info *)&result[1];
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	dev_info(&serial->dev->dev, "%s: Firmware v%d.%02d\n",
2848c2ecf20Sopenharmony_ci		 serial->type->description,
2858c2ecf20Sopenharmony_ci		 hw_info->sw_major_rev, hw_info->sw_minor_rev);
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	command_info = kmalloc(sizeof(struct whiteheat_command_private),
2888c2ecf20Sopenharmony_ci								GFP_KERNEL);
2898c2ecf20Sopenharmony_ci	if (!command_info)
2908c2ecf20Sopenharmony_ci		goto no_command_private;
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	mutex_init(&command_info->mutex);
2938c2ecf20Sopenharmony_ci	command_info->port_running = 0;
2948c2ecf20Sopenharmony_ci	init_waitqueue_head(&command_info->wait_command);
2958c2ecf20Sopenharmony_ci	usb_set_serial_port_data(command_port, command_info);
2968c2ecf20Sopenharmony_ci	command_port->write_urb->complete = command_port_write_callback;
2978c2ecf20Sopenharmony_ci	command_port->read_urb->complete = command_port_read_callback;
2988c2ecf20Sopenharmony_ci	kfree(result);
2998c2ecf20Sopenharmony_ci	kfree(command);
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	return 0;
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_cino_firmware:
3048c2ecf20Sopenharmony_ci	/* Firmware likely not running */
3058c2ecf20Sopenharmony_ci	dev_err(&serial->dev->dev,
3068c2ecf20Sopenharmony_ci		"%s: Unable to retrieve firmware version, try replugging\n",
3078c2ecf20Sopenharmony_ci		serial->type->description);
3088c2ecf20Sopenharmony_ci	dev_err(&serial->dev->dev,
3098c2ecf20Sopenharmony_ci		"%s: If the firmware is not running (status led not blinking)\n",
3108c2ecf20Sopenharmony_ci		serial->type->description);
3118c2ecf20Sopenharmony_ci	dev_err(&serial->dev->dev,
3128c2ecf20Sopenharmony_ci		"%s: please contact support@connecttech.com\n",
3138c2ecf20Sopenharmony_ci		serial->type->description);
3148c2ecf20Sopenharmony_ci	kfree(result);
3158c2ecf20Sopenharmony_ci	kfree(command);
3168c2ecf20Sopenharmony_ci	return -ENODEV;
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_cino_command_private:
3198c2ecf20Sopenharmony_ci	kfree(result);
3208c2ecf20Sopenharmony_cino_result_buffer:
3218c2ecf20Sopenharmony_ci	kfree(command);
3228c2ecf20Sopenharmony_cino_command_buffer:
3238c2ecf20Sopenharmony_ci	return -ENOMEM;
3248c2ecf20Sopenharmony_ci}
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_cistatic void whiteheat_release(struct usb_serial *serial)
3278c2ecf20Sopenharmony_ci{
3288c2ecf20Sopenharmony_ci	struct usb_serial_port *command_port;
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci	/* free up our private data for our command port */
3318c2ecf20Sopenharmony_ci	command_port = serial->port[COMMAND_PORT];
3328c2ecf20Sopenharmony_ci	kfree(usb_get_serial_port_data(command_port));
3338c2ecf20Sopenharmony_ci}
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_cistatic int whiteheat_port_probe(struct usb_serial_port *port)
3368c2ecf20Sopenharmony_ci{
3378c2ecf20Sopenharmony_ci	struct whiteheat_private *info;
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	info = kzalloc(sizeof(*info), GFP_KERNEL);
3408c2ecf20Sopenharmony_ci	if (!info)
3418c2ecf20Sopenharmony_ci		return -ENOMEM;
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	usb_set_serial_port_data(port, info);
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	return 0;
3468c2ecf20Sopenharmony_ci}
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_cistatic int whiteheat_port_remove(struct usb_serial_port *port)
3498c2ecf20Sopenharmony_ci{
3508c2ecf20Sopenharmony_ci	struct whiteheat_private *info;
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	info = usb_get_serial_port_data(port);
3538c2ecf20Sopenharmony_ci	kfree(info);
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_ci	return 0;
3568c2ecf20Sopenharmony_ci}
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_cistatic int whiteheat_open(struct tty_struct *tty, struct usb_serial_port *port)
3598c2ecf20Sopenharmony_ci{
3608c2ecf20Sopenharmony_ci	int retval;
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci	retval = start_command_port(port->serial);
3638c2ecf20Sopenharmony_ci	if (retval)
3648c2ecf20Sopenharmony_ci		goto exit;
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci	/* send an open port command */
3678c2ecf20Sopenharmony_ci	retval = firm_open(port);
3688c2ecf20Sopenharmony_ci	if (retval) {
3698c2ecf20Sopenharmony_ci		stop_command_port(port->serial);
3708c2ecf20Sopenharmony_ci		goto exit;
3718c2ecf20Sopenharmony_ci	}
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci	retval = firm_purge(port, WHITEHEAT_PURGE_RX | WHITEHEAT_PURGE_TX);
3748c2ecf20Sopenharmony_ci	if (retval) {
3758c2ecf20Sopenharmony_ci		firm_close(port);
3768c2ecf20Sopenharmony_ci		stop_command_port(port->serial);
3778c2ecf20Sopenharmony_ci		goto exit;
3788c2ecf20Sopenharmony_ci	}
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci	if (tty)
3818c2ecf20Sopenharmony_ci		firm_setup_port(tty);
3828c2ecf20Sopenharmony_ci
3838c2ecf20Sopenharmony_ci	/* Work around HCD bugs */
3848c2ecf20Sopenharmony_ci	usb_clear_halt(port->serial->dev, port->read_urb->pipe);
3858c2ecf20Sopenharmony_ci	usb_clear_halt(port->serial->dev, port->write_urb->pipe);
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	retval = usb_serial_generic_open(tty, port);
3888c2ecf20Sopenharmony_ci	if (retval) {
3898c2ecf20Sopenharmony_ci		firm_close(port);
3908c2ecf20Sopenharmony_ci		stop_command_port(port->serial);
3918c2ecf20Sopenharmony_ci		goto exit;
3928c2ecf20Sopenharmony_ci	}
3938c2ecf20Sopenharmony_ciexit:
3948c2ecf20Sopenharmony_ci	return retval;
3958c2ecf20Sopenharmony_ci}
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_cistatic void whiteheat_close(struct usb_serial_port *port)
3998c2ecf20Sopenharmony_ci{
4008c2ecf20Sopenharmony_ci	firm_report_tx_done(port);
4018c2ecf20Sopenharmony_ci	firm_close(port);
4028c2ecf20Sopenharmony_ci
4038c2ecf20Sopenharmony_ci	usb_serial_generic_close(port);
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_ci	stop_command_port(port->serial);
4068c2ecf20Sopenharmony_ci}
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_cistatic int whiteheat_tiocmget(struct tty_struct *tty)
4098c2ecf20Sopenharmony_ci{
4108c2ecf20Sopenharmony_ci	struct usb_serial_port *port = tty->driver_data;
4118c2ecf20Sopenharmony_ci	struct whiteheat_private *info = usb_get_serial_port_data(port);
4128c2ecf20Sopenharmony_ci	unsigned int modem_signals = 0;
4138c2ecf20Sopenharmony_ci
4148c2ecf20Sopenharmony_ci	firm_get_dtr_rts(port);
4158c2ecf20Sopenharmony_ci	if (info->mcr & UART_MCR_DTR)
4168c2ecf20Sopenharmony_ci		modem_signals |= TIOCM_DTR;
4178c2ecf20Sopenharmony_ci	if (info->mcr & UART_MCR_RTS)
4188c2ecf20Sopenharmony_ci		modem_signals |= TIOCM_RTS;
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_ci	return modem_signals;
4218c2ecf20Sopenharmony_ci}
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_cistatic int whiteheat_tiocmset(struct tty_struct *tty,
4248c2ecf20Sopenharmony_ci			       unsigned int set, unsigned int clear)
4258c2ecf20Sopenharmony_ci{
4268c2ecf20Sopenharmony_ci	struct usb_serial_port *port = tty->driver_data;
4278c2ecf20Sopenharmony_ci	struct whiteheat_private *info = usb_get_serial_port_data(port);
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	if (set & TIOCM_RTS)
4308c2ecf20Sopenharmony_ci		info->mcr |= UART_MCR_RTS;
4318c2ecf20Sopenharmony_ci	if (set & TIOCM_DTR)
4328c2ecf20Sopenharmony_ci		info->mcr |= UART_MCR_DTR;
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_ci	if (clear & TIOCM_RTS)
4358c2ecf20Sopenharmony_ci		info->mcr &= ~UART_MCR_RTS;
4368c2ecf20Sopenharmony_ci	if (clear & TIOCM_DTR)
4378c2ecf20Sopenharmony_ci		info->mcr &= ~UART_MCR_DTR;
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci	firm_set_dtr(port, info->mcr & UART_MCR_DTR);
4408c2ecf20Sopenharmony_ci	firm_set_rts(port, info->mcr & UART_MCR_RTS);
4418c2ecf20Sopenharmony_ci	return 0;
4428c2ecf20Sopenharmony_ci}
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci
4458c2ecf20Sopenharmony_cistatic int whiteheat_get_serial(struct tty_struct *tty,
4468c2ecf20Sopenharmony_ci				struct serial_struct *ss)
4478c2ecf20Sopenharmony_ci{
4488c2ecf20Sopenharmony_ci	struct usb_serial_port *port = tty->driver_data;
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	ss->type = PORT_16654;
4518c2ecf20Sopenharmony_ci	ss->line = port->minor;
4528c2ecf20Sopenharmony_ci	ss->port = port->port_number;
4538c2ecf20Sopenharmony_ci	ss->xmit_fifo_size = kfifo_size(&port->write_fifo);
4548c2ecf20Sopenharmony_ci	ss->custom_divisor = 0;
4558c2ecf20Sopenharmony_ci	ss->baud_base = 460800;
4568c2ecf20Sopenharmony_ci	ss->close_delay = CLOSING_DELAY;
4578c2ecf20Sopenharmony_ci	ss->closing_wait = CLOSING_DELAY;
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci	return 0;
4608c2ecf20Sopenharmony_ci}
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_cistatic void whiteheat_set_termios(struct tty_struct *tty,
4648c2ecf20Sopenharmony_ci	struct usb_serial_port *port, struct ktermios *old_termios)
4658c2ecf20Sopenharmony_ci{
4668c2ecf20Sopenharmony_ci	firm_setup_port(tty);
4678c2ecf20Sopenharmony_ci}
4688c2ecf20Sopenharmony_ci
4698c2ecf20Sopenharmony_cistatic void whiteheat_break_ctl(struct tty_struct *tty, int break_state)
4708c2ecf20Sopenharmony_ci{
4718c2ecf20Sopenharmony_ci	struct usb_serial_port *port = tty->driver_data;
4728c2ecf20Sopenharmony_ci	firm_set_break(port, break_state);
4738c2ecf20Sopenharmony_ci}
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci/*****************************************************************************
4778c2ecf20Sopenharmony_ci * Connect Tech's White Heat callback routines
4788c2ecf20Sopenharmony_ci *****************************************************************************/
4798c2ecf20Sopenharmony_cistatic void command_port_write_callback(struct urb *urb)
4808c2ecf20Sopenharmony_ci{
4818c2ecf20Sopenharmony_ci	int status = urb->status;
4828c2ecf20Sopenharmony_ci
4838c2ecf20Sopenharmony_ci	if (status) {
4848c2ecf20Sopenharmony_ci		dev_dbg(&urb->dev->dev, "nonzero urb status: %d\n", status);
4858c2ecf20Sopenharmony_ci		return;
4868c2ecf20Sopenharmony_ci	}
4878c2ecf20Sopenharmony_ci}
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_cistatic void command_port_read_callback(struct urb *urb)
4918c2ecf20Sopenharmony_ci{
4928c2ecf20Sopenharmony_ci	struct usb_serial_port *command_port = urb->context;
4938c2ecf20Sopenharmony_ci	struct whiteheat_command_private *command_info;
4948c2ecf20Sopenharmony_ci	int status = urb->status;
4958c2ecf20Sopenharmony_ci	unsigned char *data = urb->transfer_buffer;
4968c2ecf20Sopenharmony_ci	int result;
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_ci	command_info = usb_get_serial_port_data(command_port);
4998c2ecf20Sopenharmony_ci	if (!command_info) {
5008c2ecf20Sopenharmony_ci		dev_dbg(&urb->dev->dev, "%s - command_info is NULL, exiting.\n", __func__);
5018c2ecf20Sopenharmony_ci		return;
5028c2ecf20Sopenharmony_ci	}
5038c2ecf20Sopenharmony_ci	if (!urb->actual_length) {
5048c2ecf20Sopenharmony_ci		dev_dbg(&urb->dev->dev, "%s - empty response, exiting.\n", __func__);
5058c2ecf20Sopenharmony_ci		return;
5068c2ecf20Sopenharmony_ci	}
5078c2ecf20Sopenharmony_ci	if (status) {
5088c2ecf20Sopenharmony_ci		dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n", __func__, status);
5098c2ecf20Sopenharmony_ci		if (status != -ENOENT)
5108c2ecf20Sopenharmony_ci			command_info->command_finished = WHITEHEAT_CMD_FAILURE;
5118c2ecf20Sopenharmony_ci		wake_up(&command_info->wait_command);
5128c2ecf20Sopenharmony_ci		return;
5138c2ecf20Sopenharmony_ci	}
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci	usb_serial_debug_data(&command_port->dev, __func__, urb->actual_length, data);
5168c2ecf20Sopenharmony_ci
5178c2ecf20Sopenharmony_ci	if (data[0] == WHITEHEAT_CMD_COMPLETE) {
5188c2ecf20Sopenharmony_ci		command_info->command_finished = WHITEHEAT_CMD_COMPLETE;
5198c2ecf20Sopenharmony_ci		wake_up(&command_info->wait_command);
5208c2ecf20Sopenharmony_ci	} else if (data[0] == WHITEHEAT_CMD_FAILURE) {
5218c2ecf20Sopenharmony_ci		command_info->command_finished = WHITEHEAT_CMD_FAILURE;
5228c2ecf20Sopenharmony_ci		wake_up(&command_info->wait_command);
5238c2ecf20Sopenharmony_ci	} else if (data[0] == WHITEHEAT_EVENT) {
5248c2ecf20Sopenharmony_ci		/* These are unsolicited reports from the firmware, hence no
5258c2ecf20Sopenharmony_ci		   waiting command to wakeup */
5268c2ecf20Sopenharmony_ci		dev_dbg(&urb->dev->dev, "%s - event received\n", __func__);
5278c2ecf20Sopenharmony_ci	} else if ((data[0] == WHITEHEAT_GET_DTR_RTS) &&
5288c2ecf20Sopenharmony_ci		(urb->actual_length - 1 <= sizeof(command_info->result_buffer))) {
5298c2ecf20Sopenharmony_ci		memcpy(command_info->result_buffer, &data[1],
5308c2ecf20Sopenharmony_ci						urb->actual_length - 1);
5318c2ecf20Sopenharmony_ci		command_info->command_finished = WHITEHEAT_CMD_COMPLETE;
5328c2ecf20Sopenharmony_ci		wake_up(&command_info->wait_command);
5338c2ecf20Sopenharmony_ci	} else
5348c2ecf20Sopenharmony_ci		dev_dbg(&urb->dev->dev, "%s - bad reply from firmware\n", __func__);
5358c2ecf20Sopenharmony_ci
5368c2ecf20Sopenharmony_ci	/* Continue trying to always read */
5378c2ecf20Sopenharmony_ci	result = usb_submit_urb(command_port->read_urb, GFP_ATOMIC);
5388c2ecf20Sopenharmony_ci	if (result)
5398c2ecf20Sopenharmony_ci		dev_dbg(&urb->dev->dev, "%s - failed resubmitting read urb, error %d\n",
5408c2ecf20Sopenharmony_ci			__func__, result);
5418c2ecf20Sopenharmony_ci}
5428c2ecf20Sopenharmony_ci
5438c2ecf20Sopenharmony_ci
5448c2ecf20Sopenharmony_ci/*****************************************************************************
5458c2ecf20Sopenharmony_ci * Connect Tech's White Heat firmware interface
5468c2ecf20Sopenharmony_ci *****************************************************************************/
5478c2ecf20Sopenharmony_cistatic int firm_send_command(struct usb_serial_port *port, __u8 command,
5488c2ecf20Sopenharmony_ci						__u8 *data, __u8 datasize)
5498c2ecf20Sopenharmony_ci{
5508c2ecf20Sopenharmony_ci	struct usb_serial_port *command_port;
5518c2ecf20Sopenharmony_ci	struct whiteheat_command_private *command_info;
5528c2ecf20Sopenharmony_ci	struct whiteheat_private *info;
5538c2ecf20Sopenharmony_ci	struct device *dev = &port->dev;
5548c2ecf20Sopenharmony_ci	__u8 *transfer_buffer;
5558c2ecf20Sopenharmony_ci	int retval = 0;
5568c2ecf20Sopenharmony_ci	int t;
5578c2ecf20Sopenharmony_ci
5588c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s - command %d\n", __func__, command);
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_ci	command_port = port->serial->port[COMMAND_PORT];
5618c2ecf20Sopenharmony_ci	command_info = usb_get_serial_port_data(command_port);
5628c2ecf20Sopenharmony_ci
5638c2ecf20Sopenharmony_ci	if (command_port->bulk_out_size < datasize + 1)
5648c2ecf20Sopenharmony_ci		return -EIO;
5658c2ecf20Sopenharmony_ci
5668c2ecf20Sopenharmony_ci	mutex_lock(&command_info->mutex);
5678c2ecf20Sopenharmony_ci	command_info->command_finished = false;
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci	transfer_buffer = (__u8 *)command_port->write_urb->transfer_buffer;
5708c2ecf20Sopenharmony_ci	transfer_buffer[0] = command;
5718c2ecf20Sopenharmony_ci	memcpy(&transfer_buffer[1], data, datasize);
5728c2ecf20Sopenharmony_ci	command_port->write_urb->transfer_buffer_length = datasize + 1;
5738c2ecf20Sopenharmony_ci	retval = usb_submit_urb(command_port->write_urb, GFP_NOIO);
5748c2ecf20Sopenharmony_ci	if (retval) {
5758c2ecf20Sopenharmony_ci		dev_dbg(dev, "%s - submit urb failed\n", __func__);
5768c2ecf20Sopenharmony_ci		goto exit;
5778c2ecf20Sopenharmony_ci	}
5788c2ecf20Sopenharmony_ci
5798c2ecf20Sopenharmony_ci	/* wait for the command to complete */
5808c2ecf20Sopenharmony_ci	t = wait_event_timeout(command_info->wait_command,
5818c2ecf20Sopenharmony_ci		(bool)command_info->command_finished, COMMAND_TIMEOUT);
5828c2ecf20Sopenharmony_ci	if (!t)
5838c2ecf20Sopenharmony_ci		usb_kill_urb(command_port->write_urb);
5848c2ecf20Sopenharmony_ci
5858c2ecf20Sopenharmony_ci	if (command_info->command_finished == false) {
5868c2ecf20Sopenharmony_ci		dev_dbg(dev, "%s - command timed out.\n", __func__);
5878c2ecf20Sopenharmony_ci		retval = -ETIMEDOUT;
5888c2ecf20Sopenharmony_ci		goto exit;
5898c2ecf20Sopenharmony_ci	}
5908c2ecf20Sopenharmony_ci
5918c2ecf20Sopenharmony_ci	if (command_info->command_finished == WHITEHEAT_CMD_FAILURE) {
5928c2ecf20Sopenharmony_ci		dev_dbg(dev, "%s - command failed.\n", __func__);
5938c2ecf20Sopenharmony_ci		retval = -EIO;
5948c2ecf20Sopenharmony_ci		goto exit;
5958c2ecf20Sopenharmony_ci	}
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_ci	if (command_info->command_finished == WHITEHEAT_CMD_COMPLETE) {
5988c2ecf20Sopenharmony_ci		dev_dbg(dev, "%s - command completed.\n", __func__);
5998c2ecf20Sopenharmony_ci		switch (command) {
6008c2ecf20Sopenharmony_ci		case WHITEHEAT_GET_DTR_RTS:
6018c2ecf20Sopenharmony_ci			info = usb_get_serial_port_data(port);
6028c2ecf20Sopenharmony_ci			info->mcr = command_info->result_buffer[0];
6038c2ecf20Sopenharmony_ci			break;
6048c2ecf20Sopenharmony_ci		}
6058c2ecf20Sopenharmony_ci	}
6068c2ecf20Sopenharmony_ciexit:
6078c2ecf20Sopenharmony_ci	mutex_unlock(&command_info->mutex);
6088c2ecf20Sopenharmony_ci	return retval;
6098c2ecf20Sopenharmony_ci}
6108c2ecf20Sopenharmony_ci
6118c2ecf20Sopenharmony_ci
6128c2ecf20Sopenharmony_cistatic int firm_open(struct usb_serial_port *port)
6138c2ecf20Sopenharmony_ci{
6148c2ecf20Sopenharmony_ci	struct whiteheat_simple open_command;
6158c2ecf20Sopenharmony_ci
6168c2ecf20Sopenharmony_ci	open_command.port = port->port_number + 1;
6178c2ecf20Sopenharmony_ci	return firm_send_command(port, WHITEHEAT_OPEN,
6188c2ecf20Sopenharmony_ci		(__u8 *)&open_command, sizeof(open_command));
6198c2ecf20Sopenharmony_ci}
6208c2ecf20Sopenharmony_ci
6218c2ecf20Sopenharmony_ci
6228c2ecf20Sopenharmony_cistatic int firm_close(struct usb_serial_port *port)
6238c2ecf20Sopenharmony_ci{
6248c2ecf20Sopenharmony_ci	struct whiteheat_simple close_command;
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_ci	close_command.port = port->port_number + 1;
6278c2ecf20Sopenharmony_ci	return firm_send_command(port, WHITEHEAT_CLOSE,
6288c2ecf20Sopenharmony_ci			(__u8 *)&close_command, sizeof(close_command));
6298c2ecf20Sopenharmony_ci}
6308c2ecf20Sopenharmony_ci
6318c2ecf20Sopenharmony_ci
6328c2ecf20Sopenharmony_cistatic void firm_setup_port(struct tty_struct *tty)
6338c2ecf20Sopenharmony_ci{
6348c2ecf20Sopenharmony_ci	struct usb_serial_port *port = tty->driver_data;
6358c2ecf20Sopenharmony_ci	struct device *dev = &port->dev;
6368c2ecf20Sopenharmony_ci	struct whiteheat_port_settings port_settings;
6378c2ecf20Sopenharmony_ci	unsigned int cflag = tty->termios.c_cflag;
6388c2ecf20Sopenharmony_ci	speed_t baud;
6398c2ecf20Sopenharmony_ci
6408c2ecf20Sopenharmony_ci	port_settings.port = port->port_number + 1;
6418c2ecf20Sopenharmony_ci
6428c2ecf20Sopenharmony_ci	/* get the byte size */
6438c2ecf20Sopenharmony_ci	switch (cflag & CSIZE) {
6448c2ecf20Sopenharmony_ci	case CS5:	port_settings.bits = 5;   break;
6458c2ecf20Sopenharmony_ci	case CS6:	port_settings.bits = 6;   break;
6468c2ecf20Sopenharmony_ci	case CS7:	port_settings.bits = 7;   break;
6478c2ecf20Sopenharmony_ci	default:
6488c2ecf20Sopenharmony_ci	case CS8:	port_settings.bits = 8;   break;
6498c2ecf20Sopenharmony_ci	}
6508c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s - data bits = %d\n", __func__, port_settings.bits);
6518c2ecf20Sopenharmony_ci
6528c2ecf20Sopenharmony_ci	/* determine the parity */
6538c2ecf20Sopenharmony_ci	if (cflag & PARENB)
6548c2ecf20Sopenharmony_ci		if (cflag & CMSPAR)
6558c2ecf20Sopenharmony_ci			if (cflag & PARODD)
6568c2ecf20Sopenharmony_ci				port_settings.parity = WHITEHEAT_PAR_MARK;
6578c2ecf20Sopenharmony_ci			else
6588c2ecf20Sopenharmony_ci				port_settings.parity = WHITEHEAT_PAR_SPACE;
6598c2ecf20Sopenharmony_ci		else
6608c2ecf20Sopenharmony_ci			if (cflag & PARODD)
6618c2ecf20Sopenharmony_ci				port_settings.parity = WHITEHEAT_PAR_ODD;
6628c2ecf20Sopenharmony_ci			else
6638c2ecf20Sopenharmony_ci				port_settings.parity = WHITEHEAT_PAR_EVEN;
6648c2ecf20Sopenharmony_ci	else
6658c2ecf20Sopenharmony_ci		port_settings.parity = WHITEHEAT_PAR_NONE;
6668c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s - parity = %c\n", __func__, port_settings.parity);
6678c2ecf20Sopenharmony_ci
6688c2ecf20Sopenharmony_ci	/* figure out the stop bits requested */
6698c2ecf20Sopenharmony_ci	if (cflag & CSTOPB)
6708c2ecf20Sopenharmony_ci		port_settings.stop = 2;
6718c2ecf20Sopenharmony_ci	else
6728c2ecf20Sopenharmony_ci		port_settings.stop = 1;
6738c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s - stop bits = %d\n", __func__, port_settings.stop);
6748c2ecf20Sopenharmony_ci
6758c2ecf20Sopenharmony_ci	/* figure out the flow control settings */
6768c2ecf20Sopenharmony_ci	if (cflag & CRTSCTS)
6778c2ecf20Sopenharmony_ci		port_settings.hflow = (WHITEHEAT_HFLOW_CTS |
6788c2ecf20Sopenharmony_ci						WHITEHEAT_HFLOW_RTS);
6798c2ecf20Sopenharmony_ci	else
6808c2ecf20Sopenharmony_ci		port_settings.hflow = WHITEHEAT_HFLOW_NONE;
6818c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s - hardware flow control = %s %s %s %s\n", __func__,
6828c2ecf20Sopenharmony_ci	    (port_settings.hflow & WHITEHEAT_HFLOW_CTS) ? "CTS" : "",
6838c2ecf20Sopenharmony_ci	    (port_settings.hflow & WHITEHEAT_HFLOW_RTS) ? "RTS" : "",
6848c2ecf20Sopenharmony_ci	    (port_settings.hflow & WHITEHEAT_HFLOW_DSR) ? "DSR" : "",
6858c2ecf20Sopenharmony_ci	    (port_settings.hflow & WHITEHEAT_HFLOW_DTR) ? "DTR" : "");
6868c2ecf20Sopenharmony_ci
6878c2ecf20Sopenharmony_ci	/* determine software flow control */
6888c2ecf20Sopenharmony_ci	if (I_IXOFF(tty))
6898c2ecf20Sopenharmony_ci		port_settings.sflow = WHITEHEAT_SFLOW_RXTX;
6908c2ecf20Sopenharmony_ci	else
6918c2ecf20Sopenharmony_ci		port_settings.sflow = WHITEHEAT_SFLOW_NONE;
6928c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s - software flow control = %c\n", __func__, port_settings.sflow);
6938c2ecf20Sopenharmony_ci
6948c2ecf20Sopenharmony_ci	port_settings.xon = START_CHAR(tty);
6958c2ecf20Sopenharmony_ci	port_settings.xoff = STOP_CHAR(tty);
6968c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s - XON = %2x, XOFF = %2x\n", __func__, port_settings.xon, port_settings.xoff);
6978c2ecf20Sopenharmony_ci
6988c2ecf20Sopenharmony_ci	/* get the baud rate wanted */
6998c2ecf20Sopenharmony_ci	baud = tty_get_baud_rate(tty);
7008c2ecf20Sopenharmony_ci	port_settings.baud = cpu_to_le32(baud);
7018c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s - baud rate = %u\n", __func__, baud);
7028c2ecf20Sopenharmony_ci
7038c2ecf20Sopenharmony_ci	/* fixme: should set validated settings */
7048c2ecf20Sopenharmony_ci	tty_encode_baud_rate(tty, baud, baud);
7058c2ecf20Sopenharmony_ci
7068c2ecf20Sopenharmony_ci	/* handle any settings that aren't specified in the tty structure */
7078c2ecf20Sopenharmony_ci	port_settings.lloop = 0;
7088c2ecf20Sopenharmony_ci
7098c2ecf20Sopenharmony_ci	/* now send the message to the device */
7108c2ecf20Sopenharmony_ci	firm_send_command(port, WHITEHEAT_SETUP_PORT,
7118c2ecf20Sopenharmony_ci			(__u8 *)&port_settings, sizeof(port_settings));
7128c2ecf20Sopenharmony_ci}
7138c2ecf20Sopenharmony_ci
7148c2ecf20Sopenharmony_ci
7158c2ecf20Sopenharmony_cistatic int firm_set_rts(struct usb_serial_port *port, __u8 onoff)
7168c2ecf20Sopenharmony_ci{
7178c2ecf20Sopenharmony_ci	struct whiteheat_set_rdb rts_command;
7188c2ecf20Sopenharmony_ci
7198c2ecf20Sopenharmony_ci	rts_command.port = port->port_number + 1;
7208c2ecf20Sopenharmony_ci	rts_command.state = onoff;
7218c2ecf20Sopenharmony_ci	return firm_send_command(port, WHITEHEAT_SET_RTS,
7228c2ecf20Sopenharmony_ci			(__u8 *)&rts_command, sizeof(rts_command));
7238c2ecf20Sopenharmony_ci}
7248c2ecf20Sopenharmony_ci
7258c2ecf20Sopenharmony_ci
7268c2ecf20Sopenharmony_cistatic int firm_set_dtr(struct usb_serial_port *port, __u8 onoff)
7278c2ecf20Sopenharmony_ci{
7288c2ecf20Sopenharmony_ci	struct whiteheat_set_rdb dtr_command;
7298c2ecf20Sopenharmony_ci
7308c2ecf20Sopenharmony_ci	dtr_command.port = port->port_number + 1;
7318c2ecf20Sopenharmony_ci	dtr_command.state = onoff;
7328c2ecf20Sopenharmony_ci	return firm_send_command(port, WHITEHEAT_SET_DTR,
7338c2ecf20Sopenharmony_ci			(__u8 *)&dtr_command, sizeof(dtr_command));
7348c2ecf20Sopenharmony_ci}
7358c2ecf20Sopenharmony_ci
7368c2ecf20Sopenharmony_ci
7378c2ecf20Sopenharmony_cistatic int firm_set_break(struct usb_serial_port *port, __u8 onoff)
7388c2ecf20Sopenharmony_ci{
7398c2ecf20Sopenharmony_ci	struct whiteheat_set_rdb break_command;
7408c2ecf20Sopenharmony_ci
7418c2ecf20Sopenharmony_ci	break_command.port = port->port_number + 1;
7428c2ecf20Sopenharmony_ci	break_command.state = onoff;
7438c2ecf20Sopenharmony_ci	return firm_send_command(port, WHITEHEAT_SET_BREAK,
7448c2ecf20Sopenharmony_ci			(__u8 *)&break_command, sizeof(break_command));
7458c2ecf20Sopenharmony_ci}
7468c2ecf20Sopenharmony_ci
7478c2ecf20Sopenharmony_ci
7488c2ecf20Sopenharmony_cistatic int firm_purge(struct usb_serial_port *port, __u8 rxtx)
7498c2ecf20Sopenharmony_ci{
7508c2ecf20Sopenharmony_ci	struct whiteheat_purge purge_command;
7518c2ecf20Sopenharmony_ci
7528c2ecf20Sopenharmony_ci	purge_command.port = port->port_number + 1;
7538c2ecf20Sopenharmony_ci	purge_command.what = rxtx;
7548c2ecf20Sopenharmony_ci	return firm_send_command(port, WHITEHEAT_PURGE,
7558c2ecf20Sopenharmony_ci			(__u8 *)&purge_command, sizeof(purge_command));
7568c2ecf20Sopenharmony_ci}
7578c2ecf20Sopenharmony_ci
7588c2ecf20Sopenharmony_ci
7598c2ecf20Sopenharmony_cistatic int firm_get_dtr_rts(struct usb_serial_port *port)
7608c2ecf20Sopenharmony_ci{
7618c2ecf20Sopenharmony_ci	struct whiteheat_simple get_dr_command;
7628c2ecf20Sopenharmony_ci
7638c2ecf20Sopenharmony_ci	get_dr_command.port = port->port_number + 1;
7648c2ecf20Sopenharmony_ci	return firm_send_command(port, WHITEHEAT_GET_DTR_RTS,
7658c2ecf20Sopenharmony_ci			(__u8 *)&get_dr_command, sizeof(get_dr_command));
7668c2ecf20Sopenharmony_ci}
7678c2ecf20Sopenharmony_ci
7688c2ecf20Sopenharmony_ci
7698c2ecf20Sopenharmony_cistatic int firm_report_tx_done(struct usb_serial_port *port)
7708c2ecf20Sopenharmony_ci{
7718c2ecf20Sopenharmony_ci	struct whiteheat_simple close_command;
7728c2ecf20Sopenharmony_ci
7738c2ecf20Sopenharmony_ci	close_command.port = port->port_number + 1;
7748c2ecf20Sopenharmony_ci	return firm_send_command(port, WHITEHEAT_REPORT_TX_DONE,
7758c2ecf20Sopenharmony_ci			(__u8 *)&close_command, sizeof(close_command));
7768c2ecf20Sopenharmony_ci}
7778c2ecf20Sopenharmony_ci
7788c2ecf20Sopenharmony_ci
7798c2ecf20Sopenharmony_ci/*****************************************************************************
7808c2ecf20Sopenharmony_ci * Connect Tech's White Heat utility functions
7818c2ecf20Sopenharmony_ci *****************************************************************************/
7828c2ecf20Sopenharmony_cistatic int start_command_port(struct usb_serial *serial)
7838c2ecf20Sopenharmony_ci{
7848c2ecf20Sopenharmony_ci	struct usb_serial_port *command_port;
7858c2ecf20Sopenharmony_ci	struct whiteheat_command_private *command_info;
7868c2ecf20Sopenharmony_ci	int retval = 0;
7878c2ecf20Sopenharmony_ci
7888c2ecf20Sopenharmony_ci	command_port = serial->port[COMMAND_PORT];
7898c2ecf20Sopenharmony_ci	command_info = usb_get_serial_port_data(command_port);
7908c2ecf20Sopenharmony_ci	mutex_lock(&command_info->mutex);
7918c2ecf20Sopenharmony_ci	if (!command_info->port_running) {
7928c2ecf20Sopenharmony_ci		/* Work around HCD bugs */
7938c2ecf20Sopenharmony_ci		usb_clear_halt(serial->dev, command_port->read_urb->pipe);
7948c2ecf20Sopenharmony_ci
7958c2ecf20Sopenharmony_ci		retval = usb_submit_urb(command_port->read_urb, GFP_KERNEL);
7968c2ecf20Sopenharmony_ci		if (retval) {
7978c2ecf20Sopenharmony_ci			dev_err(&serial->dev->dev,
7988c2ecf20Sopenharmony_ci				"%s - failed submitting read urb, error %d\n",
7998c2ecf20Sopenharmony_ci				__func__, retval);
8008c2ecf20Sopenharmony_ci			goto exit;
8018c2ecf20Sopenharmony_ci		}
8028c2ecf20Sopenharmony_ci	}
8038c2ecf20Sopenharmony_ci	command_info->port_running++;
8048c2ecf20Sopenharmony_ci
8058c2ecf20Sopenharmony_ciexit:
8068c2ecf20Sopenharmony_ci	mutex_unlock(&command_info->mutex);
8078c2ecf20Sopenharmony_ci	return retval;
8088c2ecf20Sopenharmony_ci}
8098c2ecf20Sopenharmony_ci
8108c2ecf20Sopenharmony_ci
8118c2ecf20Sopenharmony_cistatic void stop_command_port(struct usb_serial *serial)
8128c2ecf20Sopenharmony_ci{
8138c2ecf20Sopenharmony_ci	struct usb_serial_port *command_port;
8148c2ecf20Sopenharmony_ci	struct whiteheat_command_private *command_info;
8158c2ecf20Sopenharmony_ci
8168c2ecf20Sopenharmony_ci	command_port = serial->port[COMMAND_PORT];
8178c2ecf20Sopenharmony_ci	command_info = usb_get_serial_port_data(command_port);
8188c2ecf20Sopenharmony_ci	mutex_lock(&command_info->mutex);
8198c2ecf20Sopenharmony_ci	command_info->port_running--;
8208c2ecf20Sopenharmony_ci	if (!command_info->port_running)
8218c2ecf20Sopenharmony_ci		usb_kill_urb(command_port->read_urb);
8228c2ecf20Sopenharmony_ci	mutex_unlock(&command_info->mutex);
8238c2ecf20Sopenharmony_ci}
8248c2ecf20Sopenharmony_ci
8258c2ecf20Sopenharmony_cimodule_usb_serial_driver(serial_drivers, id_table_combined);
8268c2ecf20Sopenharmony_ci
8278c2ecf20Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR);
8288c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC);
8298c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
8308c2ecf20Sopenharmony_ci
8318c2ecf20Sopenharmony_ciMODULE_FIRMWARE("whiteheat.fw");
8328c2ecf20Sopenharmony_ciMODULE_FIRMWARE("whiteheat_loader.fw");
833