18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#include <scsi/scsi.h>
38c2ecf20Sopenharmony_ci#include <scsi/scsi_host.h>
48c2ecf20Sopenharmony_ci#include <scsi/scsi_cmnd.h>
58c2ecf20Sopenharmony_ci#include <scsi/scsi_device.h>
68c2ecf20Sopenharmony_ci#include <linux/usb.h>
78c2ecf20Sopenharmony_ci#include <linux/module.h>
88c2ecf20Sopenharmony_ci#include <linux/slab.h>
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include "usb.h"
118c2ecf20Sopenharmony_ci#include "transport.h"
128c2ecf20Sopenharmony_ci#include "protocol.h"
138c2ecf20Sopenharmony_ci#include "scsiglue.h"
148c2ecf20Sopenharmony_ci#include "sierra_ms.h"
158c2ecf20Sopenharmony_ci#include "debug.h"
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#define SWIMS_USB_REQUEST_SetSwocMode	0x0B
188c2ecf20Sopenharmony_ci#define SWIMS_USB_REQUEST_GetSwocInfo	0x0A
198c2ecf20Sopenharmony_ci#define SWIMS_USB_INDEX_SetMode		0x0000
208c2ecf20Sopenharmony_ci#define SWIMS_SET_MODE_Modem		0x0001
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#define TRU_NORMAL 			0x01
238c2ecf20Sopenharmony_ci#define TRU_FORCE_MS 			0x02
248c2ecf20Sopenharmony_ci#define TRU_FORCE_MODEM 		0x03
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic unsigned int swi_tru_install = 1;
278c2ecf20Sopenharmony_cimodule_param(swi_tru_install, uint, S_IRUGO | S_IWUSR);
288c2ecf20Sopenharmony_ciMODULE_PARM_DESC(swi_tru_install, "TRU-Install mode (1=Full Logic (def),"
298c2ecf20Sopenharmony_ci		 " 2=Force CD-Rom, 3=Force Modem)");
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistruct swoc_info {
328c2ecf20Sopenharmony_ci	__u8 rev;
338c2ecf20Sopenharmony_ci	__u8 reserved[8];
348c2ecf20Sopenharmony_ci	__u16 LinuxSKU;
358c2ecf20Sopenharmony_ci	__u16 LinuxVer;
368c2ecf20Sopenharmony_ci	__u8 reserved2[47];
378c2ecf20Sopenharmony_ci} __attribute__((__packed__));
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic bool containsFullLinuxPackage(struct swoc_info *swocInfo)
408c2ecf20Sopenharmony_ci{
418c2ecf20Sopenharmony_ci	if ((swocInfo->LinuxSKU >= 0x2100 && swocInfo->LinuxSKU <= 0x2FFF) ||
428c2ecf20Sopenharmony_ci	   (swocInfo->LinuxSKU >= 0x7100 && swocInfo->LinuxSKU <= 0x7FFF))
438c2ecf20Sopenharmony_ci		return true;
448c2ecf20Sopenharmony_ci	else
458c2ecf20Sopenharmony_ci		return false;
468c2ecf20Sopenharmony_ci}
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic int sierra_set_ms_mode(struct usb_device *udev, __u16 eSWocMode)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	int result;
518c2ecf20Sopenharmony_ci	dev_dbg(&udev->dev, "SWIMS: %s", "DEVICE MODE SWITCH\n");
528c2ecf20Sopenharmony_ci	result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
538c2ecf20Sopenharmony_ci			SWIMS_USB_REQUEST_SetSwocMode,	/* __u8 request      */
548c2ecf20Sopenharmony_ci			USB_TYPE_VENDOR | USB_DIR_OUT,	/* __u8 request type */
558c2ecf20Sopenharmony_ci			eSWocMode,			/* __u16 value       */
568c2ecf20Sopenharmony_ci			0x0000,				/* __u16 index       */
578c2ecf20Sopenharmony_ci			NULL,				/* void *data        */
588c2ecf20Sopenharmony_ci			0,				/* __u16 size 	     */
598c2ecf20Sopenharmony_ci			USB_CTRL_SET_TIMEOUT);		/* int timeout       */
608c2ecf20Sopenharmony_ci	return result;
618c2ecf20Sopenharmony_ci}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic int sierra_get_swoc_info(struct usb_device *udev,
658c2ecf20Sopenharmony_ci				struct swoc_info *swocInfo)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	int result;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	dev_dbg(&udev->dev, "SWIMS: Attempting to get TRU-Install info\n");
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
728c2ecf20Sopenharmony_ci			SWIMS_USB_REQUEST_GetSwocInfo,	/* __u8 request      */
738c2ecf20Sopenharmony_ci			USB_TYPE_VENDOR | USB_DIR_IN,	/* __u8 request type */
748c2ecf20Sopenharmony_ci			0,				/* __u16 value       */
758c2ecf20Sopenharmony_ci			0,				/* __u16 index       */
768c2ecf20Sopenharmony_ci			(void *) swocInfo,		/* void *data        */
778c2ecf20Sopenharmony_ci			sizeof(struct swoc_info),	/* __u16 size 	     */
788c2ecf20Sopenharmony_ci			USB_CTRL_SET_TIMEOUT);		/* int timeout 	     */
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU);
818c2ecf20Sopenharmony_ci	swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer);
828c2ecf20Sopenharmony_ci	return result;
838c2ecf20Sopenharmony_ci}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_cistatic void debug_swoc(const struct device *dev, struct swoc_info *swocInfo)
868c2ecf20Sopenharmony_ci{
878c2ecf20Sopenharmony_ci	dev_dbg(dev, "SWIMS: SWoC Rev: %02d\n", swocInfo->rev);
888c2ecf20Sopenharmony_ci	dev_dbg(dev, "SWIMS: Linux SKU: %04X\n", swocInfo->LinuxSKU);
898c2ecf20Sopenharmony_ci	dev_dbg(dev, "SWIMS: Linux Version: %04X\n", swocInfo->LinuxVer);
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistatic ssize_t truinst_show(struct device *dev, struct device_attribute *attr,
948c2ecf20Sopenharmony_ci			char *buf)
958c2ecf20Sopenharmony_ci{
968c2ecf20Sopenharmony_ci	struct swoc_info *swocInfo;
978c2ecf20Sopenharmony_ci	struct usb_interface *intf = to_usb_interface(dev);
988c2ecf20Sopenharmony_ci	struct usb_device *udev = interface_to_usbdev(intf);
998c2ecf20Sopenharmony_ci	int result;
1008c2ecf20Sopenharmony_ci	if (swi_tru_install == TRU_FORCE_MS) {
1018c2ecf20Sopenharmony_ci		result = snprintf(buf, PAGE_SIZE, "Forced Mass Storage\n");
1028c2ecf20Sopenharmony_ci	} else {
1038c2ecf20Sopenharmony_ci		swocInfo = kmalloc(sizeof(struct swoc_info), GFP_KERNEL);
1048c2ecf20Sopenharmony_ci		if (!swocInfo) {
1058c2ecf20Sopenharmony_ci			snprintf(buf, PAGE_SIZE, "Error\n");
1068c2ecf20Sopenharmony_ci			return -ENOMEM;
1078c2ecf20Sopenharmony_ci		}
1088c2ecf20Sopenharmony_ci		result = sierra_get_swoc_info(udev, swocInfo);
1098c2ecf20Sopenharmony_ci		if (result < 0) {
1108c2ecf20Sopenharmony_ci			dev_dbg(dev, "SWIMS: failed SWoC query\n");
1118c2ecf20Sopenharmony_ci			kfree(swocInfo);
1128c2ecf20Sopenharmony_ci			snprintf(buf, PAGE_SIZE, "Error\n");
1138c2ecf20Sopenharmony_ci			return -EIO;
1148c2ecf20Sopenharmony_ci		}
1158c2ecf20Sopenharmony_ci		debug_swoc(dev, swocInfo);
1168c2ecf20Sopenharmony_ci		result = snprintf(buf, PAGE_SIZE,
1178c2ecf20Sopenharmony_ci			"REV=%02d SKU=%04X VER=%04X\n",
1188c2ecf20Sopenharmony_ci			swocInfo->rev,
1198c2ecf20Sopenharmony_ci			swocInfo->LinuxSKU,
1208c2ecf20Sopenharmony_ci			swocInfo->LinuxVer);
1218c2ecf20Sopenharmony_ci		kfree(swocInfo);
1228c2ecf20Sopenharmony_ci	}
1238c2ecf20Sopenharmony_ci	return result;
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(truinst);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ciint sierra_ms_init(struct us_data *us)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	int result, retries;
1308c2ecf20Sopenharmony_ci	struct swoc_info *swocInfo;
1318c2ecf20Sopenharmony_ci	struct usb_device *udev;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	retries = 3;
1348c2ecf20Sopenharmony_ci	result = 0;
1358c2ecf20Sopenharmony_ci	udev = us->pusb_dev;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	/* Force Modem mode */
1388c2ecf20Sopenharmony_ci	if (swi_tru_install == TRU_FORCE_MODEM) {
1398c2ecf20Sopenharmony_ci		usb_stor_dbg(us, "SWIMS: Forcing Modem Mode\n");
1408c2ecf20Sopenharmony_ci		result = sierra_set_ms_mode(udev, SWIMS_SET_MODE_Modem);
1418c2ecf20Sopenharmony_ci		if (result < 0)
1428c2ecf20Sopenharmony_ci			usb_stor_dbg(us, "SWIMS: Failed to switch to modem mode\n");
1438c2ecf20Sopenharmony_ci		return -EIO;
1448c2ecf20Sopenharmony_ci	}
1458c2ecf20Sopenharmony_ci	/* Force Mass Storage mode (keep CD-Rom) */
1468c2ecf20Sopenharmony_ci	else if (swi_tru_install == TRU_FORCE_MS) {
1478c2ecf20Sopenharmony_ci		usb_stor_dbg(us, "SWIMS: Forcing Mass Storage Mode\n");
1488c2ecf20Sopenharmony_ci		goto complete;
1498c2ecf20Sopenharmony_ci	}
1508c2ecf20Sopenharmony_ci	/* Normal TRU-Install Logic */
1518c2ecf20Sopenharmony_ci	else {
1528c2ecf20Sopenharmony_ci		usb_stor_dbg(us, "SWIMS: Normal SWoC Logic\n");
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci		swocInfo = kmalloc(sizeof(struct swoc_info),
1558c2ecf20Sopenharmony_ci				GFP_KERNEL);
1568c2ecf20Sopenharmony_ci		if (!swocInfo)
1578c2ecf20Sopenharmony_ci			return -ENOMEM;
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci		retries = 3;
1608c2ecf20Sopenharmony_ci		do {
1618c2ecf20Sopenharmony_ci			retries--;
1628c2ecf20Sopenharmony_ci			result = sierra_get_swoc_info(udev, swocInfo);
1638c2ecf20Sopenharmony_ci			if (result < 0) {
1648c2ecf20Sopenharmony_ci				usb_stor_dbg(us, "SWIMS: Failed SWoC query\n");
1658c2ecf20Sopenharmony_ci				schedule_timeout_uninterruptible(2*HZ);
1668c2ecf20Sopenharmony_ci			}
1678c2ecf20Sopenharmony_ci		} while (retries && result < 0);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci		if (result < 0) {
1708c2ecf20Sopenharmony_ci			usb_stor_dbg(us, "SWIMS: Completely failed SWoC query\n");
1718c2ecf20Sopenharmony_ci			kfree(swocInfo);
1728c2ecf20Sopenharmony_ci			return -EIO;
1738c2ecf20Sopenharmony_ci		}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci		debug_swoc(&us->pusb_dev->dev, swocInfo);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci		/*
1788c2ecf20Sopenharmony_ci		 * If there is not Linux software on the TRU-Install device
1798c2ecf20Sopenharmony_ci		 * then switch to modem mode
1808c2ecf20Sopenharmony_ci		 */
1818c2ecf20Sopenharmony_ci		if (!containsFullLinuxPackage(swocInfo)) {
1828c2ecf20Sopenharmony_ci			usb_stor_dbg(us, "SWIMS: Switching to Modem Mode\n");
1838c2ecf20Sopenharmony_ci			result = sierra_set_ms_mode(udev,
1848c2ecf20Sopenharmony_ci				SWIMS_SET_MODE_Modem);
1858c2ecf20Sopenharmony_ci			if (result < 0)
1868c2ecf20Sopenharmony_ci				usb_stor_dbg(us, "SWIMS: Failed to switch modem\n");
1878c2ecf20Sopenharmony_ci			kfree(swocInfo);
1888c2ecf20Sopenharmony_ci			return -EIO;
1898c2ecf20Sopenharmony_ci		}
1908c2ecf20Sopenharmony_ci		kfree(swocInfo);
1918c2ecf20Sopenharmony_ci	}
1928c2ecf20Sopenharmony_cicomplete:
1938c2ecf20Sopenharmony_ci	return device_create_file(&us->pusb_intf->dev, &dev_attr_truinst);
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
196