162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci#include <scsi/scsi.h> 362306a36Sopenharmony_ci#include <scsi/scsi_host.h> 462306a36Sopenharmony_ci#include <scsi/scsi_cmnd.h> 562306a36Sopenharmony_ci#include <scsi/scsi_device.h> 662306a36Sopenharmony_ci#include <linux/usb.h> 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/slab.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include "usb.h" 1162306a36Sopenharmony_ci#include "transport.h" 1262306a36Sopenharmony_ci#include "protocol.h" 1362306a36Sopenharmony_ci#include "scsiglue.h" 1462306a36Sopenharmony_ci#include "sierra_ms.h" 1562306a36Sopenharmony_ci#include "debug.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define SWIMS_USB_REQUEST_SetSwocMode 0x0B 1862306a36Sopenharmony_ci#define SWIMS_USB_REQUEST_GetSwocInfo 0x0A 1962306a36Sopenharmony_ci#define SWIMS_USB_INDEX_SetMode 0x0000 2062306a36Sopenharmony_ci#define SWIMS_SET_MODE_Modem 0x0001 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define TRU_NORMAL 0x01 2362306a36Sopenharmony_ci#define TRU_FORCE_MS 0x02 2462306a36Sopenharmony_ci#define TRU_FORCE_MODEM 0x03 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic unsigned int swi_tru_install = 1; 2762306a36Sopenharmony_cimodule_param(swi_tru_install, uint, S_IRUGO | S_IWUSR); 2862306a36Sopenharmony_ciMODULE_PARM_DESC(swi_tru_install, "TRU-Install mode (1=Full Logic (def)," 2962306a36Sopenharmony_ci " 2=Force CD-Rom, 3=Force Modem)"); 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistruct swoc_info { 3262306a36Sopenharmony_ci __u8 rev; 3362306a36Sopenharmony_ci __u8 reserved[8]; 3462306a36Sopenharmony_ci __u16 LinuxSKU; 3562306a36Sopenharmony_ci __u16 LinuxVer; 3662306a36Sopenharmony_ci __u8 reserved2[47]; 3762306a36Sopenharmony_ci} __attribute__((__packed__)); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic bool containsFullLinuxPackage(struct swoc_info *swocInfo) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci if ((swocInfo->LinuxSKU >= 0x2100 && swocInfo->LinuxSKU <= 0x2FFF) || 4262306a36Sopenharmony_ci (swocInfo->LinuxSKU >= 0x7100 && swocInfo->LinuxSKU <= 0x7FFF)) 4362306a36Sopenharmony_ci return true; 4462306a36Sopenharmony_ci else 4562306a36Sopenharmony_ci return false; 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic int sierra_set_ms_mode(struct usb_device *udev, __u16 eSWocMode) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci int result; 5162306a36Sopenharmony_ci dev_dbg(&udev->dev, "SWIMS: %s", "DEVICE MODE SWITCH\n"); 5262306a36Sopenharmony_ci result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 5362306a36Sopenharmony_ci SWIMS_USB_REQUEST_SetSwocMode, /* __u8 request */ 5462306a36Sopenharmony_ci USB_TYPE_VENDOR | USB_DIR_OUT, /* __u8 request type */ 5562306a36Sopenharmony_ci eSWocMode, /* __u16 value */ 5662306a36Sopenharmony_ci 0x0000, /* __u16 index */ 5762306a36Sopenharmony_ci NULL, /* void *data */ 5862306a36Sopenharmony_ci 0, /* __u16 size */ 5962306a36Sopenharmony_ci USB_CTRL_SET_TIMEOUT); /* int timeout */ 6062306a36Sopenharmony_ci return result; 6162306a36Sopenharmony_ci} 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistatic int sierra_get_swoc_info(struct usb_device *udev, 6562306a36Sopenharmony_ci struct swoc_info *swocInfo) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci int result; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci dev_dbg(&udev->dev, "SWIMS: Attempting to get TRU-Install info\n"); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 7262306a36Sopenharmony_ci SWIMS_USB_REQUEST_GetSwocInfo, /* __u8 request */ 7362306a36Sopenharmony_ci USB_TYPE_VENDOR | USB_DIR_IN, /* __u8 request type */ 7462306a36Sopenharmony_ci 0, /* __u16 value */ 7562306a36Sopenharmony_ci 0, /* __u16 index */ 7662306a36Sopenharmony_ci (void *) swocInfo, /* void *data */ 7762306a36Sopenharmony_ci sizeof(struct swoc_info), /* __u16 size */ 7862306a36Sopenharmony_ci USB_CTRL_SET_TIMEOUT); /* int timeout */ 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); 8162306a36Sopenharmony_ci swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); 8262306a36Sopenharmony_ci return result; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic void debug_swoc(const struct device *dev, struct swoc_info *swocInfo) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci dev_dbg(dev, "SWIMS: SWoC Rev: %02d\n", swocInfo->rev); 8862306a36Sopenharmony_ci dev_dbg(dev, "SWIMS: Linux SKU: %04X\n", swocInfo->LinuxSKU); 8962306a36Sopenharmony_ci dev_dbg(dev, "SWIMS: Linux Version: %04X\n", swocInfo->LinuxVer); 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic ssize_t truinst_show(struct device *dev, struct device_attribute *attr, 9462306a36Sopenharmony_ci char *buf) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci struct swoc_info *swocInfo; 9762306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 9862306a36Sopenharmony_ci struct usb_device *udev = interface_to_usbdev(intf); 9962306a36Sopenharmony_ci int result; 10062306a36Sopenharmony_ci if (swi_tru_install == TRU_FORCE_MS) { 10162306a36Sopenharmony_ci result = snprintf(buf, PAGE_SIZE, "Forced Mass Storage\n"); 10262306a36Sopenharmony_ci } else { 10362306a36Sopenharmony_ci swocInfo = kmalloc(sizeof(struct swoc_info), GFP_KERNEL); 10462306a36Sopenharmony_ci if (!swocInfo) { 10562306a36Sopenharmony_ci snprintf(buf, PAGE_SIZE, "Error\n"); 10662306a36Sopenharmony_ci return -ENOMEM; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci result = sierra_get_swoc_info(udev, swocInfo); 10962306a36Sopenharmony_ci if (result < 0) { 11062306a36Sopenharmony_ci dev_dbg(dev, "SWIMS: failed SWoC query\n"); 11162306a36Sopenharmony_ci kfree(swocInfo); 11262306a36Sopenharmony_ci snprintf(buf, PAGE_SIZE, "Error\n"); 11362306a36Sopenharmony_ci return -EIO; 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci debug_swoc(dev, swocInfo); 11662306a36Sopenharmony_ci result = snprintf(buf, PAGE_SIZE, 11762306a36Sopenharmony_ci "REV=%02d SKU=%04X VER=%04X\n", 11862306a36Sopenharmony_ci swocInfo->rev, 11962306a36Sopenharmony_ci swocInfo->LinuxSKU, 12062306a36Sopenharmony_ci swocInfo->LinuxVer); 12162306a36Sopenharmony_ci kfree(swocInfo); 12262306a36Sopenharmony_ci } 12362306a36Sopenharmony_ci return result; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_cistatic DEVICE_ATTR_RO(truinst); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ciint sierra_ms_init(struct us_data *us) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci int result, retries; 13062306a36Sopenharmony_ci struct swoc_info *swocInfo; 13162306a36Sopenharmony_ci struct usb_device *udev; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci udev = us->pusb_dev; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci /* Force Modem mode */ 13662306a36Sopenharmony_ci if (swi_tru_install == TRU_FORCE_MODEM) { 13762306a36Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Forcing Modem Mode\n"); 13862306a36Sopenharmony_ci result = sierra_set_ms_mode(udev, SWIMS_SET_MODE_Modem); 13962306a36Sopenharmony_ci if (result < 0) 14062306a36Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Failed to switch to modem mode\n"); 14162306a36Sopenharmony_ci return -EIO; 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci /* Force Mass Storage mode (keep CD-Rom) */ 14462306a36Sopenharmony_ci else if (swi_tru_install == TRU_FORCE_MS) { 14562306a36Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Forcing Mass Storage Mode\n"); 14662306a36Sopenharmony_ci goto complete; 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci /* Normal TRU-Install Logic */ 14962306a36Sopenharmony_ci else { 15062306a36Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Normal SWoC Logic\n"); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci swocInfo = kmalloc(sizeof(struct swoc_info), 15362306a36Sopenharmony_ci GFP_KERNEL); 15462306a36Sopenharmony_ci if (!swocInfo) 15562306a36Sopenharmony_ci return -ENOMEM; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci retries = 3; 15862306a36Sopenharmony_ci do { 15962306a36Sopenharmony_ci retries--; 16062306a36Sopenharmony_ci result = sierra_get_swoc_info(udev, swocInfo); 16162306a36Sopenharmony_ci if (result < 0) { 16262306a36Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Failed SWoC query\n"); 16362306a36Sopenharmony_ci schedule_timeout_uninterruptible(2*HZ); 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci } while (retries && result < 0); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci if (result < 0) { 16862306a36Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Completely failed SWoC query\n"); 16962306a36Sopenharmony_ci kfree(swocInfo); 17062306a36Sopenharmony_ci return -EIO; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci debug_swoc(&us->pusb_dev->dev, swocInfo); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* 17662306a36Sopenharmony_ci * If there is not Linux software on the TRU-Install device 17762306a36Sopenharmony_ci * then switch to modem mode 17862306a36Sopenharmony_ci */ 17962306a36Sopenharmony_ci if (!containsFullLinuxPackage(swocInfo)) { 18062306a36Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Switching to Modem Mode\n"); 18162306a36Sopenharmony_ci result = sierra_set_ms_mode(udev, 18262306a36Sopenharmony_ci SWIMS_SET_MODE_Modem); 18362306a36Sopenharmony_ci if (result < 0) 18462306a36Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Failed to switch modem\n"); 18562306a36Sopenharmony_ci kfree(swocInfo); 18662306a36Sopenharmony_ci return -EIO; 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci kfree(swocInfo); 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_cicomplete: 19162306a36Sopenharmony_ci return device_create_file(&us->pusb_intf->dev, &dev_attr_truinst); 19262306a36Sopenharmony_ci} 19362306a36Sopenharmony_ci 194