/*
* Copyright (C) 2018 Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/module.h>
#include "mt76.h"
#include "usb_trace.h"
#include "dma.h"
#define MT_VEND_REQ_MAX_RETRY 10
#define MT_VEND_REQ_TOUT_MS 300
static bool disable_usb_sg;
module_param_named(disable_usb_sg, disable_usb_sg, bool, 0644);
MODULE_PARM_DESC(disable_usb_sg, "Disable usb scatter-gather support");
/* should be called with usb_ctrl_mtx locked */
static int __mt76u_vendor_request(struct mt76_dev *dev, u8 req,
u8 req_type, u16 val, u16 offset,
void *buf, size_t len)
{
struct usb_device *udev = to_usb_device(dev->dev);
unsigned int pipe;
int i, ret;
pipe = (req_type & USB_DIR_IN) ? usb_rcvctrlpipe(udev, 0)
: usb_sndctrlpipe(udev, 0);
for (i = 0; i < MT_VEND_REQ_MAX_RETRY; i++) {
if (test_bit(MT76_REMOVED, &dev->state))
return -EIO;
ret = usb_control_msg(udev, pipe, req, req_type, val,
offset, buf, len, MT_VEND_REQ_TOUT_MS);
if (ret == -ENODEV)
set_bit(MT76_REMOVED, &dev->state);
if (ret >= 0 || ret == -ENODEV)
return ret;
usleep_range(5000, 10000);
}
dev_err(dev->dev, "vendor request req:%02x off:%04x failed:%d\n",
req, offset, ret);
return ret;
}
int mt76u_vendor_request(struct mt76_dev *dev, u8 req,
u8 req_type, u16 val, u16 offset,
void *buf, size_t len)
{
int ret;
mutex_lock(&dev->usb.usb_ctrl_mtx);
ret = __mt76u_vendor_request(dev, req, req_type,
val, offset, buf, len);
trace_usb_reg_wr(dev, offset, val);
mutex_unlock(&dev->usb.usb_ctrl_mtx);
return ret;
}
EXPORT_SYMBOL_GPL(mt76u_vendor_request);
/* should be called with usb_ctrl_mtx locked */
static u32 __mt76u_rr(struct mt76_dev *dev, u32 addr)
{
struct mt76_usb *usb = &dev->usb;
u32 data = ~0;
u16 offset;
int ret;
u8 req;
switch (addr & MT_VEND_TYPE_MASK) {
case MT_VEND_TYPE_EEPROM:
req = MT_VEND_READ_EEPROM;
break;
case MT_VEND_TYPE_CFG:
req = MT_VEND_READ_CFG;
break;
default:
req = MT_VEND_MULTI_READ;
break;
}
offset = addr & ~MT_VEND_TYPE_MASK;
ret = __mt76u_vendor_request(dev, req,
USB_DIR_IN | USB_TYPE_VENDOR,
0, offset, usb->data, sizeof(__le32));
if (ret == sizeof(__le32))
data = get_unaligned_le32(usb->data);
trace_usb_reg_rr(dev, addr, data);
return data;
}
static u32 mt76u_rr(struct mt76_dev *dev, u32 addr)
{
u32 ret;
mutex_lock(&dev->usb.usb_ctrl_mtx);
ret = __mt76u_rr(dev, addr);
mutex_unlock(&dev->usb.usb_ctrl_mtx);
return ret;
}
/* should be called with usb_ctrl_mtx locked */
static void __mt76u_wr(struct mt76_dev *dev, u32 addr, u32 val)
{
struct mt76_usb *usb = &dev->usb;
u16 offset;
u8 req;
switch (addr & MT_VEND_TYPE_MASK) {
case MT_VEND_TYPE_CFG:
req = MT_VEND_WRITE_CFG;
break;
default:
req = MT_VEND_MULTI_WRITE;
break;
}
offset = addr & ~MT_VEND_TYPE_MASK;
put_unaligned_le32(val, usb->data);
__mt76u_vendor_request(dev, req,
USB_DIR_OUT | USB_TYPE_VENDOR