blob: dcf927de65f39430512a4c2ae64a307b8cb5edc6 [file] [log] [blame]
/*
* 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 "mt76.h"
#include "usb_trace.h"
#include "dma.h"
#define MT_VEND_REQ_MAX_RETRY 10
#define MT_VEND_REQ_TOUT_MS 300
/* 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_interface *intf = to_usb_interface(dev->dev);
struct usb_device *udev = interface_to_usbdev(intf);
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;
}
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, 0,
offset, usb->data, sizeof(__le32));
trace_usb_reg_wr(dev, addr, val);
}
void mt76u_wr(struct mt76_dev *dev, u32 addr, u32 val)
{
mutex_lock(&dev->usb.usb_ctrl_mtx);
__mt76u_wr(dev, addr, val);
mutex_unlock(&dev->usb.usb_ctrl_mtx);
}
static u32 mt76u_rmw(struct mt76_dev *dev, u32 addr,
u32 mask, u32 val)
{
mutex_lock(&dev->usb.usb_ctrl_mtx);
val |= __mt76u_rr(dev, addr) & ~mask;
__mt76u_wr(dev, addr, val);
mutex_unlock(&dev->usb.usb_ctrl_mtx);
return val;
}
static void mt76u_copy(struct mt76_dev *dev, u32 offset,
const void *data, int len)
{
struct mt76_usb *usb = &dev->usb;
const u32 *val = data;
int i, ret;
mutex_lock(&usb->usb_ctrl_mtx);
for (i = 0; i < (len / 4); i++) {
put_unaligned_le32(val[i], usb->data);
ret = __mt76u_vendor_request(dev, MT_VEND_MULTI_WRITE,
USB_DIR_OUT | USB_TYPE_VENDOR,
0, offset + i * 4, usb->data,
sizeof(__le32));
if (ret < 0)
break;
}
mutex_unlock(&usb->usb_ctrl_mtx);
}
void mt76u_single_wr(struct mt76_dev *dev, const u8 req,
const u16 offset, const u32 val)
{
mutex_lock(&dev->usb.usb_ctrl_mtx);
__mt76u_vendor_request(dev, req,
USB_DIR_OUT | USB_TYPE_VENDOR,
val & 0xffff, offset, NULL, 0);
__mt76u_vendor_request(dev, req,
USB_DIR_OUT | USB_TYPE_VENDOR,
val >> 16, offset + 2, NULL, 0);
mutex_unlock(&dev->usb.usb_ctrl_mtx);
}
EXPORT_SYMBOL_GPL(mt76u_single_wr);
static int
mt76u_set_endpoints(struct usb_interface *intf,
struct mt76_usb *usb)
{
struct usb_host_interface *intf_desc = intf->cur_altsetting;
struct usb_endpoint_descriptor *ep_desc;
int i, in_ep = 0, out_ep = 0;
for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) {
ep_desc = &intf_desc->endpoint[i].desc;
if (usb_endpoint_is_bulk_in(ep_desc) &&
in_ep < __MT_EP_IN_MAX) {
usb->in_ep[in_ep] = usb_endpoint_num(ep_desc);
usb->in_max_packet = usb_endpoint_maxp(ep_desc);
in_ep++;
} else if (usb_endpoint_is_bulk_out(ep_desc) &&
out_ep < __MT_EP_OUT_MAX) {
usb->out_ep[out_ep] = usb_endpoint_num(ep_desc);
usb->out_max_packet = usb_endpoint_maxp(ep_desc);
out_ep++;
}
}
if (in_ep != __MT_EP_IN_MAX || out_ep != __MT_EP_OUT_MAX)
return -EINVAL;
return 0;
}
static int
mt76u_fill_rx_sg(struct mt76_dev *dev, struct mt76u_buf *buf,
int nsgs, int len, int sglen)
{
struct urb *urb = buf->urb;
int i;
for (i = 0; i < nsgs; i++) {
struct page *page;
void *data;
int offset;
data = netdev_alloc_frag(len);
if (!data)
break;
page = virt_to_head_page(data);
offset = data - page_address(page);
sg_set_page(&urb->sg[i], page, sglen, offset);
}
if (i < nsgs) {
int j;
for (j = nsgs; j < urb->num_sgs; j++)
skb_free_frag(sg_virt(&urb->sg[j]));
urb->num_sgs = i;
}
urb->num_sgs = max_t(int, i, urb->num_sgs);
buf->len = urb->num_sgs * sglen,
sg_init_marker(urb->sg, urb->num_sgs);
return i ? : -ENOMEM;
}
int mt76u_buf_alloc(struct mt76_dev *dev, struct mt76u_buf *buf,
int nsgs, int len, int sglen, gfp_t gfp)
{
buf->urb = usb_alloc_urb(0, gfp);
if (!buf->urb)
return -ENOMEM;
buf->urb->sg = devm_kcalloc(dev->dev, nsgs, sizeof(*buf->urb->sg),
gfp);
if (!buf->urb->sg)
return -ENOMEM;
sg_init_table(buf->urb->sg, nsgs);
buf->dev = dev;
return mt76u_fill_rx_sg(dev, buf, nsgs, len, sglen);
}
EXPORT_SYMBOL_GPL(mt76u_buf_alloc);
void mt76u_buf_free(struct mt76u_buf *buf)
{
struct urb *urb = buf->urb;
struct scatterlist *sg;
int i;
for (i = 0; i < urb->num_sgs; i++) {
sg = &urb->sg[i];
if (!sg)
continue;
skb_free_frag(sg_virt(sg));
}
usb_free_urb(buf->urb);
}
EXPORT_SYMBOL_GPL(mt76u_buf_free);
int mt76u_submit_buf(struct mt76_dev *dev, int dir, int index,
struct mt76u_buf *buf, gfp_t gfp,
usb_complete_t complete_fn, void *context)
{
struct usb_interface *intf = to_usb_interface(dev->dev);
struct usb_device *udev = interface_to_usbdev(intf);
unsigned int pipe;
if (dir == USB_DIR_IN)
pipe = usb_rcvbulkpipe(udev, dev->usb.in_ep[index]);
else
pipe = usb_sndbulkpipe(udev, dev->usb.out_ep[index]);
usb_fill_bulk_urb(buf->urb, udev, pipe, NULL, buf->len,
complete_fn, context);
return usb_submit_urb(buf->urb, gfp);
}
EXPORT_SYMBOL_GPL(mt76u_submit_buf);
static inline struct mt76u_buf
*mt76u_get_next_rx_entry(struct mt76_queue *q)
{
struct mt76u_buf *buf = NULL;
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
if (q->queued > 0) {
buf = &q->entry[q->head].ubuf;
q->head = (q->head + 1) % q->ndesc;
q->queued--;
}
spin_unlock_irqrestore(&q->lock, flags);
return buf;
}
static int mt76u_get_rx_entry_len(u8 *data, u32 data_len)
{
u16 dma_len, min_len;
dma_len = get_unaligned_le16(data);
min_len = MT_DMA_HDR_LEN + MT_RX_RXWI_LEN +
MT_FCE_INFO_LEN;
if (data_len < min_len || WARN_ON(!dma_len) ||
WARN_ON(dma_len + MT_DMA_HDR_LEN > data_len) ||
WARN_ON(dma_len & 0x3))
return -EINVAL;
return dma_len;
}
static int
mt76u_process_rx_entry(struct mt76_dev *dev, struct urb *urb)
{
struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN];
u8 *data = sg_virt(&urb->sg[0]);
int data_len, len, nsgs = 1;
struct sk_buff *skb;
if (!test_bit(MT76_STATE_INITIALIZED, &dev->state))
return 0;
len = mt76u_get_rx_entry_len(data, urb->actual_length);
if (len < 0)
return 0;
skb = build_skb(data, q->buf_size);
if (!skb)
return 0;
data_len = min_t(int, len, urb->sg[0].length - MT_DMA_HDR_LEN);
skb_reserve(skb, MT_DMA_HDR_LEN);
if (skb->tail + data_len > skb->end) {
dev_kfree_skb(skb);
return 1;
}
__skb_put(skb, data_len);
len -= data_len;
while (len > 0) {
data_len = min_t(int, len, urb->sg[nsgs].length);
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
sg_page(&urb->sg[nsgs]),
urb->sg[nsgs].offset,
data_len, q->buf_size);
len -= data_len;
nsgs++;
}
dev->drv->rx_skb(dev, MT_RXQ_MAIN, skb);
return nsgs;
}
static void mt76u_complete_rx(struct urb *urb)
{
struct mt76_dev *dev = urb->context;
struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN];
unsigned long flags;
switch (urb->status) {
case -ECONNRESET:
case -ESHUTDOWN:
case -ENOENT:
return;
default:
dev_err(dev->dev, "rx urb failed: %d\n", urb->status);
/* fall through */
case 0:
break;
}
spin_lock_irqsave(&q->lock, flags);
if (WARN_ONCE(q->entry[q->tail].ubuf.urb != urb, "rx urb mismatch"))
goto out;
q->tail = (q->tail + 1) % q->ndesc;
q->queued++;
tasklet_schedule(&dev->usb.rx_tasklet);
out:
spin_unlock_irqrestore(&q->lock, flags);
}
static void mt76u_rx_tasklet(unsigned long data)
{
struct mt76_dev *dev = (struct mt76_dev *)data;
struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN];
int err, nsgs, buf_len = q->buf_size;
struct mt76u_buf *buf;
rcu_read_lock();
while (true) {
buf = mt76u_get_next_rx_entry(q);
if (!buf)
break;
nsgs = mt76u_process_rx_entry(dev, buf->urb);
if (nsgs > 0) {
err = mt76u_fill_rx_sg(dev, buf, nsgs,
buf_len,
SKB_WITH_OVERHEAD(buf_len));
if (err < 0)
break;
}
mt76u_submit_buf(dev, USB_DIR_IN, MT_EP_IN_PKT_RX,
buf, GFP_ATOMIC,
mt76u_complete_rx, dev);
}
mt76_rx_poll_complete(dev, MT_RXQ_MAIN, NULL);
rcu_read_unlock();
}
int mt76u_submit_rx_buffers(struct mt76_dev *dev)
{
struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN];
unsigned long flags;
int i, err = 0;
spin_lock_irqsave(&q->lock, flags);
for (i = 0; i < q->ndesc; i++) {
err = mt76u_submit_buf(dev, USB_DIR_IN, MT_EP_IN_PKT_RX,
&q->entry[i].ubuf, GFP_ATOMIC,
mt76u_complete_rx, dev);
if (err < 0)
break;
}
q->head = q->tail = 0;
q->queued = 0;
spin_unlock_irqrestore(&q->lock, flags);
return err;
}
EXPORT_SYMBOL_GPL(mt76u_submit_rx_buffers);
static int mt76u_alloc_rx(struct mt76_dev *dev)
{
struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN];
int i, err, nsgs;
spin_lock_init(&q->lock);
q->entry = devm_kcalloc(dev->dev,
MT_NUM_RX_ENTRIES, sizeof(*q->entry),
GFP_KERNEL);
if (!q->entry)
return -ENOMEM;
if (mt76u_check_sg(dev)) {
q->buf_size = MT_RX_BUF_SIZE;
nsgs = MT_SG_MAX_SIZE;
} else {
q->buf_size = PAGE_SIZE;
nsgs = 1;
}
q->ndesc = MT_NUM_RX_ENTRIES;
for (i = 0; i < q->ndesc; i++) {
err = mt76u_buf_alloc(dev, &q->entry[i].ubuf,
nsgs, q->buf_size,
SKB_WITH_OVERHEAD(q->buf_size),
GFP_KERNEL);
if (err < 0)
return err;
}
return mt76u_submit_rx_buffers(dev);
}
static void mt76u_free_rx(struct mt76_dev *dev)
{
struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN];
int i;
for (i = 0; i < q->ndesc; i++)
mt76u_buf_free(&q->entry[i].ubuf);
}
static void mt76u_stop_rx(struct mt76_dev *dev)
{
struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN];
int i;
for (i = 0; i < q->ndesc; i++)
usb_kill_urb(q->entry[i].ubuf.urb);
}
int mt76u_skb_dma_info(struct sk_buff *skb, int port, u32 flags)
{
struct sk_buff *iter, *last = skb;
u32 info, pad;
/* Buffer layout:
* | 4B | xfer len | pad | 4B |
* | TXINFO | pkt/cmd | zero pad to 4B | zero |
*
* length field of TXINFO should be set to 'xfer len'.
*/
info = FIELD_PREP(MT_TXD_INFO_LEN, round_up(skb->len, 4)) |
FIELD_PREP(MT_TXD_INFO_DPORT, port) | flags;
put_unaligned_le32(info, skb_push(skb, sizeof(info)));
pad = round_up(skb->len, 4) + 4 - skb->len;
skb_walk_frags(skb, iter) {
last = iter;
if (!iter->next) {
skb->data_len += pad;
skb->len += pad;
break;
}
}
if (unlikely(pad)) {
if (__skb_pad(last, pad, true))
return -ENOMEM;
__skb_put(last, pad);
}
return 0;
}
EXPORT_SYMBOL_GPL(mt76u_skb_dma_info);
static void mt76u_tx_tasklet(unsigned long data)
{
struct mt76_dev *dev = (struct mt76_dev *)data;
struct mt76u_buf *buf;
struct mt76_queue *q;
bool wake;
int i;
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
q = &dev->q_tx[i];
spin_lock_bh(&q->lock);
while (true) {
buf = &q->entry[q->head].ubuf;
if (!buf->done || !q->queued)
break;
dev->drv->tx_complete_skb(dev, q,
&q->entry[q->head],
false);
if (q->entry[q->head].schedule) {
q->entry[q->head].schedule = false;
q->swq_queued--;
}
q->head = (q->head + 1) % q->ndesc;
q->queued--;
}
mt76_txq_schedule(dev, q);
wake = i < IEEE80211_NUM_ACS && q->queued < q->ndesc - 8;
if (!q->queued)
wake_up(&dev->tx_wait);
spin_unlock_bh(&q->lock);
if (!test_and_set_bit(MT76_READING_STATS, &dev->state))
ieee80211_queue_delayed_work(dev->hw,
&dev->usb.stat_work,
msecs_to_jiffies(10));
if (wake)
ieee80211_wake_queue(dev->hw, i);
}
}
static void mt76u_tx_status_data(struct work_struct *work)
{
struct mt76_usb *usb;
struct mt76_dev *dev;
u8 update = 1;
u16 count = 0;
usb = container_of(work, struct mt76_usb, stat_work.work);
dev = container_of(usb, struct mt76_dev, usb);
while (true) {
if (test_bit(MT76_REMOVED, &dev->state))
break;
if (!dev->drv->tx_status_data(dev, &update))
break;
count++;
}
if (count && test_bit(MT76_STATE_RUNNING, &dev->state))
ieee80211_queue_delayed_work(dev->hw, &usb->stat_work,
msecs_to_jiffies(10));
else
clear_bit(MT76_READING_STATS, &dev->state);
}
static void mt76u_complete_tx(struct urb *urb)
{
struct mt76u_buf *buf = urb->context;
struct mt76_dev *dev = buf->dev;
if (mt76u_urb_error(urb))
dev_err(dev->dev, "tx urb failed: %d\n", urb->status);
buf->done = true;
tasklet_schedule(&dev->usb.tx_tasklet);
}
static int
mt76u_tx_build_sg(struct sk_buff *skb, struct urb *urb)
{
int nsgs = 1 + skb_shinfo(skb)->nr_frags;
struct sk_buff *iter;
skb_walk_frags(skb, iter)
nsgs += 1 + skb_shinfo(iter)->nr_frags;
memset(urb->sg, 0, sizeof(*urb->sg) * MT_SG_MAX_SIZE);
nsgs = min_t(int, MT_SG_MAX_SIZE, nsgs);
sg_init_marker(urb->sg, nsgs);
urb->num_sgs = nsgs;
return skb_to_sgvec_nomark(skb, urb->sg, 0, skb->len);
}
static int
mt76u_tx_queue_skb(struct mt76_dev *dev, struct mt76_queue *q,
struct sk_buff *skb, struct mt76_wcid *wcid,
struct ieee80211_sta *sta)
{
struct usb_interface *intf = to_usb_interface(dev->dev);
struct usb_device *udev = interface_to_usbdev(intf);
u8 ep = q2ep(q->hw_idx);
struct mt76u_buf *buf;
u16 idx = q->tail;
unsigned int pipe;
int err;
if (q->queued == q->ndesc)
return -ENOSPC;
err = dev->drv->tx_prepare_skb(dev, NULL, skb, q, wcid, sta, NULL);
if (err < 0)
return err;
buf = &q->entry[idx].ubuf;
buf->done = false;
err = mt76u_tx_build_sg(skb, buf->urb);
if (err < 0)
return err;
pipe = usb_sndbulkpipe(udev, dev->usb.out_ep[ep]);
usb_fill_bulk_urb(buf->urb, udev, pipe, NULL, skb->len,
mt76u_complete_tx, buf);
q->tail = (q->tail + 1) % q->ndesc;
q->entry[idx].skb = skb;
q->queued++;
return idx;
}
static void mt76u_tx_kick(struct mt76_dev *dev, struct mt76_queue *q)
{
struct mt76u_buf *buf;
int err;
while (q->first != q->tail) {
buf = &q->entry[q->first].ubuf;
err = usb_submit_urb(buf->urb, GFP_ATOMIC);
if (err < 0) {
if (err == -ENODEV)
set_bit(MT76_REMOVED, &dev->state);
else
dev_err(dev->dev, "tx urb submit failed:%d\n",
err);
break;
}
q->first = (q->first + 1) % q->ndesc;
}
}
static int mt76u_alloc_tx(struct mt76_dev *dev)
{
struct mt76u_buf *buf;
struct mt76_queue *q;
size_t size;
int i, j;
size = MT_SG_MAX_SIZE * sizeof(struct scatterlist);
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
q = &dev->q_tx[i];
spin_lock_init(&q->lock);
INIT_LIST_HEAD(&q->swq);
q->hw_idx = q2hwq(i);
q->entry = devm_kcalloc(dev->dev,
MT_NUM_TX_ENTRIES, sizeof(*q->entry),
GFP_KERNEL);
if (!q->entry)
return -ENOMEM;
q->ndesc = MT_NUM_TX_ENTRIES;
for (j = 0; j < q->ndesc; j++) {
buf = &q->entry[j].ubuf;
buf->dev = dev;
buf->urb = usb_alloc_urb(0, GFP_KERNEL);
if (!buf->urb)
return -ENOMEM;
buf->urb->sg = devm_kzalloc(dev->dev, size, GFP_KERNEL);
if (!buf->urb->sg)
return -ENOMEM;
}
}
return 0;
}
static void mt76u_free_tx(struct mt76_dev *dev)
{
struct mt76_queue *q;
int i, j;
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
q = &dev->q_tx[i];
for (j = 0; j < q->ndesc; j++)
usb_free_urb(q->entry[j].ubuf.urb);
}
}
static void mt76u_stop_tx(struct mt76_dev *dev)
{
struct mt76_queue *q;
int i, j;
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
q = &dev->q_tx[i];
for (j = 0; j < q->ndesc; j++)
usb_kill_urb(q->entry[j].ubuf.urb);
}
}
void mt76u_stop_queues(struct mt76_dev *dev)
{
tasklet_disable(&dev->usb.rx_tasklet);
tasklet_disable(&dev->usb.tx_tasklet);
mt76u_stop_rx(dev);
mt76u_stop_tx(dev);
}
EXPORT_SYMBOL_GPL(mt76u_stop_queues);
void mt76u_stop_stat_wk(struct mt76_dev *dev)
{
cancel_delayed_work_sync(&dev->usb.stat_work);
clear_bit(MT76_READING_STATS, &dev->state);
}
EXPORT_SYMBOL_GPL(mt76u_stop_stat_wk);
void mt76u_queues_deinit(struct mt76_dev *dev)
{
mt76u_stop_queues(dev);
mt76u_free_rx(dev);
mt76u_free_tx(dev);
}
EXPORT_SYMBOL_GPL(mt76u_queues_deinit);
int mt76u_alloc_queues(struct mt76_dev *dev)
{
int err;
err = mt76u_alloc_rx(dev);
if (err < 0)
return err;
return mt76u_alloc_tx(dev);
}
EXPORT_SYMBOL_GPL(mt76u_alloc_queues);
static const struct mt76_queue_ops usb_queue_ops = {
.tx_queue_skb = mt76u_tx_queue_skb,
.kick = mt76u_tx_kick,
};
int mt76u_init(struct mt76_dev *dev,
struct usb_interface *intf)
{
static const struct mt76_bus_ops mt76u_ops = {
.rr = mt76u_rr,
.wr = mt76u_wr,
.rmw = mt76u_rmw,
.copy = mt76u_copy,
};
struct mt76_usb *usb = &dev->usb;
tasklet_init(&usb->rx_tasklet, mt76u_rx_tasklet, (unsigned long)dev);
tasklet_init(&usb->tx_tasklet, mt76u_tx_tasklet, (unsigned long)dev);
INIT_DELAYED_WORK(&usb->stat_work, mt76u_tx_status_data);
skb_queue_head_init(&dev->rx_skb[MT_RXQ_MAIN]);
init_completion(&usb->mcu.cmpl);
mutex_init(&usb->mcu.mutex);
mutex_init(&usb->usb_ctrl_mtx);
dev->bus = &mt76u_ops;
dev->queue_ops = &usb_queue_ops;
return mt76u_set_endpoints(intf, usb);
}
EXPORT_SYMBOL_GPL(mt76u_init);
MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>");
MODULE_LICENSE("Dual BSD/GPL");