162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * IPWireless 3G UMTS TDD Modem driver (USB connected)
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *   Copyright (C) 2004 Roelf Diedericks <roelfd@inet.co.za>
662306a36Sopenharmony_ci *   Copyright (C) 2004 Greg Kroah-Hartman <greg@kroah.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * All information about the device was acquired using SnoopyPro
962306a36Sopenharmony_ci * on MSFT's O/S, and examing the MSFT drivers' debug output
1062306a36Sopenharmony_ci * (insanely left _on_ in the enduser version)
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * It was written out of frustration with the IPWireless USB modem
1362306a36Sopenharmony_ci * supplied by Axity3G/Sentech South Africa not supporting
1462306a36Sopenharmony_ci * Linux whatsoever.
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * Nobody provided any proprietary information that was not already
1762306a36Sopenharmony_ci * available for this device.
1862306a36Sopenharmony_ci *
1962306a36Sopenharmony_ci * The modem adheres to the "3GPP TS  27.007 AT command set for 3G
2062306a36Sopenharmony_ci * User Equipment (UE)" standard, available from
2162306a36Sopenharmony_ci * http://www.3gpp.org/ftp/Specs/html-info/27007.htm
2262306a36Sopenharmony_ci *
2362306a36Sopenharmony_ci * The code was only tested the IPWireless handheld modem distributed
2462306a36Sopenharmony_ci * in South Africa by Sentech.
2562306a36Sopenharmony_ci *
2662306a36Sopenharmony_ci * It may work for Woosh Inc in .nz too, as it appears they use the
2762306a36Sopenharmony_ci * same kit.
2862306a36Sopenharmony_ci *
2962306a36Sopenharmony_ci * There is still some work to be done in terms of handling
3062306a36Sopenharmony_ci * DCD, DTR, RTS, CTS which are currently faked.
3162306a36Sopenharmony_ci * It's good enough for PPP at this point. It's based off all kinds of
3262306a36Sopenharmony_ci * code found in usb/serial and usb/class
3362306a36Sopenharmony_ci */
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#include <linux/kernel.h>
3662306a36Sopenharmony_ci#include <linux/errno.h>
3762306a36Sopenharmony_ci#include <linux/slab.h>
3862306a36Sopenharmony_ci#include <linux/tty.h>
3962306a36Sopenharmony_ci#include <linux/tty_flip.h>
4062306a36Sopenharmony_ci#include <linux/module.h>
4162306a36Sopenharmony_ci#include <linux/spinlock.h>
4262306a36Sopenharmony_ci#include <linux/usb.h>
4362306a36Sopenharmony_ci#include <linux/usb/serial.h>
4462306a36Sopenharmony_ci#include <linux/uaccess.h>
4562306a36Sopenharmony_ci#include "usb-wwan.h"
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define DRIVER_AUTHOR	"Roelf Diedericks"
4862306a36Sopenharmony_ci#define DRIVER_DESC	"IPWireless tty driver"
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci#define IPW_TTY_MAJOR	240	/* real device node major id, experimental range */
5162306a36Sopenharmony_ci#define IPW_TTY_MINORS	256	/* we support 256 devices, dunno why, it'd be insane :) */
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci#define USB_IPW_MAGIC	0x6d02	/* magic number for ipw struct */
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci/* Message sizes */
5762306a36Sopenharmony_ci#define EVENT_BUFFER_SIZE	0xFF
5862306a36Sopenharmony_ci#define CHAR2INT16(c1, c0)	(((u32)((c1) & 0xff) << 8) + (u32)((c0) & 0xff))
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci/* vendor/product pairs that are known work with this driver*/
6162306a36Sopenharmony_ci#define IPW_VID		0x0bc3
6262306a36Sopenharmony_ci#define IPW_PID		0x0001
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci/* Vendor commands: */
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci/* baud rates */
6862306a36Sopenharmony_cienum {
6962306a36Sopenharmony_ci	ipw_sio_b256000 = 0x000e,
7062306a36Sopenharmony_ci	ipw_sio_b128000 = 0x001d,
7162306a36Sopenharmony_ci	ipw_sio_b115200 = 0x0020,
7262306a36Sopenharmony_ci	ipw_sio_b57600  = 0x0040,
7362306a36Sopenharmony_ci	ipw_sio_b56000  = 0x0042,
7462306a36Sopenharmony_ci	ipw_sio_b38400  = 0x0060,
7562306a36Sopenharmony_ci	ipw_sio_b19200  = 0x00c0,
7662306a36Sopenharmony_ci	ipw_sio_b14400  = 0x0100,
7762306a36Sopenharmony_ci	ipw_sio_b9600   = 0x0180,
7862306a36Sopenharmony_ci	ipw_sio_b4800   = 0x0300,
7962306a36Sopenharmony_ci	ipw_sio_b2400   = 0x0600,
8062306a36Sopenharmony_ci	ipw_sio_b1200   = 0x0c00,
8162306a36Sopenharmony_ci	ipw_sio_b600    = 0x1800
8262306a36Sopenharmony_ci};
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci/* data bits */
8562306a36Sopenharmony_ci#define ipw_dtb_7		0x700
8662306a36Sopenharmony_ci#define ipw_dtb_8		0x810	/* ok so the define is misleading, I know, but forces 8,n,1 */
8762306a36Sopenharmony_ci					/* I mean, is there a point to any other setting these days? :) */
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci/* usb control request types : */
9062306a36Sopenharmony_ci#define IPW_SIO_RXCTL		0x00	/* control bulk rx channel transmissions, value=1/0 (on/off) */
9162306a36Sopenharmony_ci#define IPW_SIO_SET_BAUD	0x01	/* set baud, value=requested ipw_sio_bxxxx */
9262306a36Sopenharmony_ci#define IPW_SIO_SET_LINE	0x03	/* set databits, parity. value=ipw_dtb_x */
9362306a36Sopenharmony_ci#define IPW_SIO_SET_PIN		0x03	/* set/clear dtr/rts value=ipw_pin_xxx */
9462306a36Sopenharmony_ci#define IPW_SIO_POLL		0x08	/* get serial port status byte, call with value=0 */
9562306a36Sopenharmony_ci#define IPW_SIO_INIT		0x11	/* initializes ? value=0 (appears as first thing todo on open) */
9662306a36Sopenharmony_ci#define IPW_SIO_PURGE		0x12	/* purge all transmissions?, call with value=numchar_to_purge */
9762306a36Sopenharmony_ci#define IPW_SIO_HANDFLOW	0x13	/* set xon/xoff limits value=0, and a buffer of 0x10 bytes */
9862306a36Sopenharmony_ci#define IPW_SIO_SETCHARS	0x13	/* set the flowcontrol special chars, value=0, buf=6 bytes, */
9962306a36Sopenharmony_ci					/* last 2 bytes contain flowcontrol chars e.g. 00 00 00 00 11 13 */
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci/* values used for request IPW_SIO_SET_PIN */
10262306a36Sopenharmony_ci#define IPW_PIN_SETDTR		0x101
10362306a36Sopenharmony_ci#define IPW_PIN_SETRTS		0x202
10462306a36Sopenharmony_ci#define IPW_PIN_CLRDTR		0x100
10562306a36Sopenharmony_ci#define IPW_PIN_CLRRTS		0x200 /* unconfirmed */
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci/* values used for request IPW_SIO_RXCTL */
10862306a36Sopenharmony_ci#define IPW_RXBULK_ON		1
10962306a36Sopenharmony_ci#define IPW_RXBULK_OFF		0
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci/* various 16 byte hardcoded transferbuffers used by flow control */
11262306a36Sopenharmony_ci#define IPW_BYTES_FLOWINIT	{ 0x01, 0, 0, 0, 0x40, 0, 0, 0, \
11362306a36Sopenharmony_ci					0, 0, 0, 0, 0, 0, 0, 0 }
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci/* Interpretation of modem status lines */
11662306a36Sopenharmony_ci/* These need sorting out by individually connecting pins and checking
11762306a36Sopenharmony_ci * results. FIXME!
11862306a36Sopenharmony_ci * When data is being sent we see 0x30 in the lower byte; this must
11962306a36Sopenharmony_ci * contain DSR and CTS ...
12062306a36Sopenharmony_ci */
12162306a36Sopenharmony_ci#define IPW_DSR			((1<<4) | (1<<5))
12262306a36Sopenharmony_ci#define IPW_CTS			((1<<5) | (1<<4))
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci#define IPW_WANTS_TO_SEND	0x30
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic const struct usb_device_id id_table[] = {
12762306a36Sopenharmony_ci	{ USB_DEVICE(IPW_VID, IPW_PID) },
12862306a36Sopenharmony_ci	{ },
12962306a36Sopenharmony_ci};
13062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, id_table);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic int ipw_open(struct tty_struct *tty, struct usb_serial_port *port)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	struct usb_device *udev = port->serial->dev;
13562306a36Sopenharmony_ci	struct device *dev = &port->dev;
13662306a36Sopenharmony_ci	u8 buf_flow_static[16] = IPW_BYTES_FLOWINIT;
13762306a36Sopenharmony_ci	u8 *buf_flow_init;
13862306a36Sopenharmony_ci	int result;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	buf_flow_init = kmemdup(buf_flow_static, 16, GFP_KERNEL);
14162306a36Sopenharmony_ci	if (!buf_flow_init)
14262306a36Sopenharmony_ci		return -ENOMEM;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	/* --1: Tell the modem to initialize (we think) From sniffs this is
14562306a36Sopenharmony_ci	 *	always the first thing that gets sent to the modem during
14662306a36Sopenharmony_ci	 *	opening of the device */
14762306a36Sopenharmony_ci	dev_dbg(dev, "%s: Sending SIO_INIT (we guess)\n", __func__);
14862306a36Sopenharmony_ci	result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
14962306a36Sopenharmony_ci			 IPW_SIO_INIT,
15062306a36Sopenharmony_ci			 USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT,
15162306a36Sopenharmony_ci			 0,
15262306a36Sopenharmony_ci			 0, /* index */
15362306a36Sopenharmony_ci			 NULL,
15462306a36Sopenharmony_ci			 0,
15562306a36Sopenharmony_ci			 100000);
15662306a36Sopenharmony_ci	if (result < 0)
15762306a36Sopenharmony_ci		dev_err(dev, "Init of modem failed (error = %d)\n", result);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	/* reset the bulk pipes */
16062306a36Sopenharmony_ci	usb_clear_halt(udev, usb_rcvbulkpipe(udev, port->bulk_in_endpointAddress));
16162306a36Sopenharmony_ci	usb_clear_halt(udev, usb_sndbulkpipe(udev, port->bulk_out_endpointAddress));
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	/*--2: Start reading from the device */
16462306a36Sopenharmony_ci	dev_dbg(dev, "%s: setting up bulk read callback\n", __func__);
16562306a36Sopenharmony_ci	usb_wwan_open(tty, port);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	/*--3: Tell the modem to open the floodgates on the rx bulk channel */
16862306a36Sopenharmony_ci	dev_dbg(dev, "%s:asking modem for RxRead (RXBULK_ON)\n", __func__);
16962306a36Sopenharmony_ci	result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
17062306a36Sopenharmony_ci			 IPW_SIO_RXCTL,
17162306a36Sopenharmony_ci			 USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT,
17262306a36Sopenharmony_ci			 IPW_RXBULK_ON,
17362306a36Sopenharmony_ci			 0, /* index */
17462306a36Sopenharmony_ci			 NULL,
17562306a36Sopenharmony_ci			 0,
17662306a36Sopenharmony_ci			 100000);
17762306a36Sopenharmony_ci	if (result < 0)
17862306a36Sopenharmony_ci		dev_err(dev, "Enabling bulk RxRead failed (error = %d)\n", result);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	/*--4: setup the initial flowcontrol */
18162306a36Sopenharmony_ci	dev_dbg(dev, "%s:setting init flowcontrol (%s)\n", __func__, buf_flow_init);
18262306a36Sopenharmony_ci	result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
18362306a36Sopenharmony_ci			 IPW_SIO_HANDFLOW,
18462306a36Sopenharmony_ci			 USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT,
18562306a36Sopenharmony_ci			 0,
18662306a36Sopenharmony_ci			 0,
18762306a36Sopenharmony_ci			 buf_flow_init,
18862306a36Sopenharmony_ci			 0x10,
18962306a36Sopenharmony_ci			 200000);
19062306a36Sopenharmony_ci	if (result < 0)
19162306a36Sopenharmony_ci		dev_err(dev, "initial flowcontrol failed (error = %d)\n", result);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	kfree(buf_flow_init);
19462306a36Sopenharmony_ci	return 0;
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic int ipw_attach(struct usb_serial *serial)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	struct usb_wwan_intf_private *data;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	data = kzalloc(sizeof(struct usb_wwan_intf_private), GFP_KERNEL);
20262306a36Sopenharmony_ci	if (!data)
20362306a36Sopenharmony_ci		return -ENOMEM;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	spin_lock_init(&data->susp_lock);
20662306a36Sopenharmony_ci	usb_set_serial_data(serial, data);
20762306a36Sopenharmony_ci	return 0;
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistatic void ipw_release(struct usb_serial *serial)
21162306a36Sopenharmony_ci{
21262306a36Sopenharmony_ci	struct usb_wwan_intf_private *data = usb_get_serial_data(serial);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	usb_set_serial_data(serial, NULL);
21562306a36Sopenharmony_ci	kfree(data);
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistatic void ipw_dtr_rts(struct usb_serial_port *port, int on)
21962306a36Sopenharmony_ci{
22062306a36Sopenharmony_ci	struct usb_device *udev = port->serial->dev;
22162306a36Sopenharmony_ci	struct device *dev = &port->dev;
22262306a36Sopenharmony_ci	int result;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	dev_dbg(dev, "%s: on = %d\n", __func__, on);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
22762306a36Sopenharmony_ci			 IPW_SIO_SET_PIN,
22862306a36Sopenharmony_ci			 USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT,
22962306a36Sopenharmony_ci			 on ? IPW_PIN_SETDTR : IPW_PIN_CLRDTR,
23062306a36Sopenharmony_ci			 0,
23162306a36Sopenharmony_ci			 NULL,
23262306a36Sopenharmony_ci			 0,
23362306a36Sopenharmony_ci			 200000);
23462306a36Sopenharmony_ci	if (result < 0)
23562306a36Sopenharmony_ci		dev_err(dev, "setting dtr failed (error = %d)\n", result);
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
23862306a36Sopenharmony_ci			 IPW_SIO_SET_PIN, USB_TYPE_VENDOR |
23962306a36Sopenharmony_ci					USB_RECIP_INTERFACE | USB_DIR_OUT,
24062306a36Sopenharmony_ci			 on ? IPW_PIN_SETRTS : IPW_PIN_CLRRTS,
24162306a36Sopenharmony_ci			 0,
24262306a36Sopenharmony_ci			 NULL,
24362306a36Sopenharmony_ci			 0,
24462306a36Sopenharmony_ci			 200000);
24562306a36Sopenharmony_ci	if (result < 0)
24662306a36Sopenharmony_ci		dev_err(dev, "setting rts failed (error = %d)\n", result);
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistatic void ipw_close(struct usb_serial_port *port)
25062306a36Sopenharmony_ci{
25162306a36Sopenharmony_ci	struct usb_device *udev = port->serial->dev;
25262306a36Sopenharmony_ci	struct device *dev = &port->dev;
25362306a36Sopenharmony_ci	int result;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	/*--3: purge */
25662306a36Sopenharmony_ci	dev_dbg(dev, "%s:sending purge\n", __func__);
25762306a36Sopenharmony_ci	result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
25862306a36Sopenharmony_ci			 IPW_SIO_PURGE, USB_TYPE_VENDOR |
25962306a36Sopenharmony_ci			 		USB_RECIP_INTERFACE | USB_DIR_OUT,
26062306a36Sopenharmony_ci			 0x03,
26162306a36Sopenharmony_ci			 0,
26262306a36Sopenharmony_ci			 NULL,
26362306a36Sopenharmony_ci			 0,
26462306a36Sopenharmony_ci			 200000);
26562306a36Sopenharmony_ci	if (result < 0)
26662306a36Sopenharmony_ci		dev_err(dev, "purge failed (error = %d)\n", result);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	/* send RXBULK_off (tell modem to stop transmitting bulk data on
27062306a36Sopenharmony_ci	   rx chan) */
27162306a36Sopenharmony_ci	result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
27262306a36Sopenharmony_ci			 IPW_SIO_RXCTL,
27362306a36Sopenharmony_ci			 USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT,
27462306a36Sopenharmony_ci			 IPW_RXBULK_OFF,
27562306a36Sopenharmony_ci			 0, /* index */
27662306a36Sopenharmony_ci			 NULL,
27762306a36Sopenharmony_ci			 0,
27862306a36Sopenharmony_ci			 100000);
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	if (result < 0)
28162306a36Sopenharmony_ci		dev_err(dev, "Disabling bulk RxRead failed (error = %d)\n", result);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	usb_wwan_close(port);
28462306a36Sopenharmony_ci}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_cistatic struct usb_serial_driver ipw_device = {
28762306a36Sopenharmony_ci	.driver = {
28862306a36Sopenharmony_ci		.owner =	THIS_MODULE,
28962306a36Sopenharmony_ci		.name =		"ipw",
29062306a36Sopenharmony_ci	},
29162306a36Sopenharmony_ci	.description =		"IPWireless converter",
29262306a36Sopenharmony_ci	.id_table =		id_table,
29362306a36Sopenharmony_ci	.num_ports =		1,
29462306a36Sopenharmony_ci	.open =			ipw_open,
29562306a36Sopenharmony_ci	.close =		ipw_close,
29662306a36Sopenharmony_ci	.attach =		ipw_attach,
29762306a36Sopenharmony_ci	.release =		ipw_release,
29862306a36Sopenharmony_ci	.port_probe =		usb_wwan_port_probe,
29962306a36Sopenharmony_ci	.port_remove =		usb_wwan_port_remove,
30062306a36Sopenharmony_ci	.dtr_rts =		ipw_dtr_rts,
30162306a36Sopenharmony_ci	.write =		usb_wwan_write,
30262306a36Sopenharmony_ci};
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_cistatic struct usb_serial_driver * const serial_drivers[] = {
30562306a36Sopenharmony_ci	&ipw_device, NULL
30662306a36Sopenharmony_ci};
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cimodule_usb_serial_driver(serial_drivers, id_table);
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci/* Module information */
31162306a36Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR);
31262306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC);
31362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
314