diff options
Diffstat (limited to 'drivers/net/wireless/mediatek/mt76')
36 files changed, 9786 insertions, 0 deletions
diff --git a/drivers/net/wireless/mediatek/mt76/Kconfig b/drivers/net/wireless/mediatek/mt76/Kconfig new file mode 100644 index 000000000000..fc05d79c80d0 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/Kconfig @@ -0,0 +1,10 @@ +config MT76_CORE + tristate + +config MT76x2E + tristate "MediaTek MT76x2E (PCIe) support" + select MT76_CORE + depends on MAC80211 + depends on PCI + ---help--- + This adds support for MT7612/MT7602/MT7662-based wireless PCIe devices. diff --git a/drivers/net/wireless/mediatek/mt76/Makefile b/drivers/net/wireless/mediatek/mt76/Makefile new file mode 100644 index 000000000000..a0156bc01dea --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/Makefile @@ -0,0 +1,15 @@ +obj-$(CONFIG_MT76_CORE) += mt76.o +obj-$(CONFIG_MT76x2E) += mt76x2e.o + +mt76-y := \ + mmio.o util.o trace.o dma.o mac80211.o debugfs.o eeprom.o tx.o agg-rx.o + +CFLAGS_trace.o := -I$(src) + +mt76x2e-y := \ + mt76x2_pci.o mt76x2_dma.o \ + mt76x2_main.o mt76x2_init.o mt76x2_debugfs.o mt76x2_tx.o \ + mt76x2_core.o mt76x2_mac.o mt76x2_eeprom.o mt76x2_mcu.o mt76x2_phy.o \ + mt76x2_dfs.o mt76x2_trace.o + +CFLAGS_mt76x2_trace.o := -I$(src) diff --git a/drivers/net/wireless/mediatek/mt76/agg-rx.c b/drivers/net/wireless/mediatek/mt76/agg-rx.c new file mode 100644 index 000000000000..8027bb7c03c2 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/agg-rx.c @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2018 Felix Fietkau <nbd@nbd.name> + * + * 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" + +#define REORDER_TIMEOUT (HZ / 10) + +static void +mt76_aggr_release(struct mt76_rx_tid *tid, struct sk_buff_head *frames, int idx) +{ + struct sk_buff *skb; + + tid->head = ieee80211_sn_inc(tid->head); + + skb = tid->reorder_buf[idx]; + if (!skb) + return; + + tid->reorder_buf[idx] = NULL; + tid->nframes--; + __skb_queue_tail(frames, skb); +} + +static void +mt76_rx_aggr_release_frames(struct mt76_rx_tid *tid, struct sk_buff_head *frames, + u16 head) +{ + int idx; + + while (ieee80211_sn_less(tid->head, head)) { + idx = tid->head % tid->size; + mt76_aggr_release(tid, frames, idx); + } +} + +static void +mt76_rx_aggr_release_head(struct mt76_rx_tid *tid, struct sk_buff_head *frames) +{ + int idx = tid->head % tid->size; + + while (tid->reorder_buf[idx]) { + mt76_aggr_release(tid, frames, idx); + idx = tid->head % tid->size; + } +} + +static void +mt76_rx_aggr_check_release(struct mt76_rx_tid *tid, struct sk_buff_head *frames) +{ + struct mt76_rx_status *status; + struct sk_buff *skb; + int start, idx, nframes; + + if (!tid->nframes) + return; + + mt76_rx_aggr_release_head(tid, frames); + + start = tid->head % tid->size; + nframes = tid->nframes; + + for (idx = (tid->head + 1) % tid->size; + idx != start && nframes; + idx = (idx + 1) % tid->size) { + + skb = tid->reorder_buf[idx]; + if (!skb) + continue; + + nframes--; + status = (struct mt76_rx_status *) skb->cb; + if (!time_after(jiffies, status->reorder_time + + REORDER_TIMEOUT)) + continue; + + mt76_rx_aggr_release_frames(tid, frames, status->seqno); + } + + mt76_rx_aggr_release_head(tid, frames); +} + +static void +mt76_rx_aggr_reorder_work(struct work_struct *work) +{ + struct mt76_rx_tid *tid = container_of(work, struct mt76_rx_tid, + reorder_work.work); + struct mt76_dev *dev = tid->dev; + struct sk_buff_head frames; + + __skb_queue_head_init(&frames); + + local_bh_disable(); + + spin_lock(&tid->lock); + mt76_rx_aggr_check_release(tid, &frames); + spin_unlock(&tid->lock); + + ieee80211_queue_delayed_work(tid->dev->hw, &tid->reorder_work, REORDER_TIMEOUT); + mt76_rx_complete(dev, &frames, -1); + + local_bh_enable(); +} + +void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames) +{ + struct mt76_rx_status *status = (struct mt76_rx_status *) skb->cb; + struct mt76_wcid *wcid = status->wcid; + struct ieee80211_sta *sta; + struct mt76_rx_tid *tid; + bool sn_less; + u16 seqno, head, size; + u8 idx; + + __skb_queue_tail(frames, skb); + + sta = wcid_to_sta(wcid); + if (!sta || !status->aggr) + return; + + tid = rcu_dereference(wcid->aggr[status->tid]); + if (!tid) + return; + + spin_lock_bh(&tid->lock); + + if (tid->stopped) + goto out; + + head = tid->head; + seqno = status->seqno; + size = tid->size; + sn_less = ieee80211_sn_less(seqno, head); + + if (!tid->started) { + if (sn_less) + goto out; + + tid->started = true; + } + + if (sn_less) { + __skb_unlink(skb, frames); + dev_kfree_skb(skb); + goto out; + } + + if (seqno == head) { + tid->head = ieee80211_sn_inc(head); + if (tid->nframes) + mt76_rx_aggr_release_head(tid, frames); + goto out; + } + + __skb_unlink(skb, frames); + + /* + * Frame sequence number exceeds buffering window, free up some space + * by releasing previous frames + */ + if (!ieee80211_sn_less(seqno, head + size)) { + head = ieee80211_sn_inc(ieee80211_sn_sub(seqno, size)); + mt76_rx_aggr_release_frames(tid, frames, head); + } + + idx = seqno % size; + + /* Discard if the current slot is already in use */ + if (tid->reorder_buf[idx]) { + dev_kfree_skb(skb); + goto out; + } + + status->reorder_time = jiffies; + tid->reorder_buf[idx] = skb; + tid->nframes++; + mt76_rx_aggr_release_head(tid, frames); + + ieee80211_queue_delayed_work(tid->dev->hw, &tid->reorder_work, REORDER_TIMEOUT); + +out: + spin_unlock_bh(&tid->lock); +} + +int mt76_rx_aggr_start(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tidno, + u16 ssn, u8 size) +{ + struct mt76_rx_tid *tid; + + mt76_rx_aggr_stop(dev, wcid, tidno); + + tid = kzalloc(sizeof(*tid) + size * sizeof(tid->reorder_buf[0]), + GFP_KERNEL); + if (!tid) + return -ENOMEM; + + tid->dev = dev; + tid->head = ssn; + tid->size = size; + INIT_DELAYED_WORK(&tid->reorder_work, mt76_rx_aggr_reorder_work); + spin_lock_init(&tid->lock); + + rcu_assign_pointer(wcid->aggr[tidno], tid); + + return 0; +} +EXPORT_SYMBOL_GPL(mt76_rx_aggr_start); + +static void mt76_rx_aggr_shutdown(struct mt76_dev *dev, struct mt76_rx_tid *tid) +{ + u8 size = tid->size; + int i; + + spin_lock_bh(&tid->lock); + + tid->stopped = true; + for (i = 0; tid->nframes && i < size; i++) { + struct sk_buff *skb = tid->reorder_buf[i]; + + if (!skb) + continue; + + tid->nframes--; + dev_kfree_skb(skb); + } + + spin_unlock_bh(&tid->lock); + + cancel_delayed_work_sync(&tid->reorder_work); +} + +void mt76_rx_aggr_stop(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tidno) +{ + struct mt76_rx_tid *tid; + + rcu_read_lock(); + + tid = rcu_dereference(wcid->aggr[tidno]); + if (tid) { + rcu_assign_pointer(wcid->aggr[tidno], NULL); + mt76_rx_aggr_shutdown(dev, tid); + kfree_rcu(tid, rcu_head); + } + + rcu_read_unlock(); +} +EXPORT_SYMBOL_GPL(mt76_rx_aggr_stop); diff --git a/drivers/net/wireless/mediatek/mt76/debugfs.c b/drivers/net/wireless/mediatek/mt76/debugfs.c new file mode 100644 index 000000000000..c121b502a462 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/debugfs.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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" + +static int +mt76_reg_set(void *data, u64 val) +{ + struct mt76_dev *dev = data; + + dev->bus->wr(dev, dev->debugfs_reg, val); + return 0; +} + +static int +mt76_reg_get(void *data, u64 *val) +{ + struct mt76_dev *dev = data; + + *val = dev->bus->rr(dev, dev->debugfs_reg); + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(fops_regval, mt76_reg_get, mt76_reg_set, + "0x%08llx\n"); + +static int +mt76_queues_read(struct seq_file *s, void *data) +{ + struct mt76_dev *dev = dev_get_drvdata(s->private); + int i; + + for (i = 0; i < ARRAY_SIZE(dev->q_tx); i++) { + struct mt76_queue *q = &dev->q_tx[i]; + + if (!q->ndesc) + continue; + + seq_printf(s, + "%d: queued=%d head=%d tail=%d swq_queued=%d\n", + i, q->queued, q->head, q->tail, q->swq_queued); + } + + return 0; +} + +struct dentry *mt76_register_debugfs(struct mt76_dev *dev) +{ + struct dentry *dir; + + dir = debugfs_create_dir("mt76", dev->hw->wiphy->debugfsdir); + if (!dir) + return NULL; + + debugfs_create_u8("led_pin", S_IRUSR | S_IWUSR, dir, &dev->led_pin); + debugfs_create_u32("regidx", S_IRUSR | S_IWUSR, dir, &dev->debugfs_reg); + debugfs_create_file_unsafe("regval", S_IRUSR | S_IWUSR, dir, dev, + &fops_regval); + debugfs_create_blob("eeprom", S_IRUSR, dir, &dev->eeprom); + if (dev->otp.data) + debugfs_create_blob("otp", S_IRUSR, dir, &dev->otp); + debugfs_create_devm_seqfile(dev->dev, "queues", dir, mt76_queues_read); + + return dir; +} +EXPORT_SYMBOL_GPL(mt76_register_debugfs); diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c new file mode 100644 index 000000000000..3518703524e7 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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/dma-mapping.h> +#include "mt76.h" +#include "dma.h" + +#define DMA_DUMMY_TXWI ((void *) ~0) + +static int +mt76_dma_alloc_queue(struct mt76_dev *dev, struct mt76_queue *q) +{ + int size; + int i; + + spin_lock_init(&q->lock); + INIT_LIST_HEAD(&q->swq); + + size = q->ndesc * sizeof(struct mt76_desc); + q->desc = dmam_alloc_coherent(dev->dev, size, &q->desc_dma, GFP_KERNEL); + if (!q->desc) + return -ENOMEM; + + size = q->ndesc * sizeof(*q->entry); + q->entry = devm_kzalloc(dev->dev, size, GFP_KERNEL); + if (!q->entry) + return -ENOMEM; + + /* clear descriptors */ + for (i = 0; i < q->ndesc; i++) + q->desc[i].ctrl = cpu_to_le32(MT_DMA_CTL_DMA_DONE); + + iowrite32(q->desc_dma, &q->regs->desc_base); + iowrite32(0, &q->regs->cpu_idx); + iowrite32(0, &q->regs->dma_idx); + iowrite32(q->ndesc, &q->regs->ring_size); + + return 0; +} + +static int +mt76_dma_add_buf(struct mt76_dev *dev, struct mt76_queue *q, + struct mt76_queue_buf *buf, int nbufs, u32 info, + struct sk_buff *skb, void *txwi) +{ + struct mt76_desc *desc; + u32 ctrl; + int i, idx = -1; + + if (txwi) + q->entry[q->head].txwi = DMA_DUMMY_TXWI; + + for (i = 0; i < nbufs; i += 2, buf += 2) { + u32 buf0 = buf[0].addr, buf1 = 0; + + ctrl = FIELD_PREP(MT_DMA_CTL_SD_LEN0, buf[0].len); + if (i < nbufs - 1) { + buf1 = buf[1].addr; + ctrl |= FIELD_PREP(MT_DMA_CTL_SD_LEN1, buf[1].len); + } + + if (i == nbufs - 1) + ctrl |= MT_DMA_CTL_LAST_SEC0; + else if (i == nbufs - 2) + ctrl |= MT_DMA_CTL_LAST_SEC1; + + idx = q->head; + q->head = (q->head + 1) % q->ndesc; + + desc = &q->desc[idx]; + + WRITE_ONCE(desc->buf0, cpu_to_le32(buf0)); + WRITE_ONCE(desc->buf1, cpu_to_le32(buf1)); + WRITE_ONCE(desc->info, cpu_to_le32(info)); + WRITE_ONCE(desc->ctrl, cpu_to_le32(ctrl)); + + q->queued++; + } + + q->entry[idx].txwi = txwi; + q->entry[idx].skb = skb; + + return idx; +} + +static void +mt76_dma_tx_cleanup_idx(struct mt76_dev *dev, struct mt76_queue *q, int idx, + struct mt76_queue_entry *prev_e) +{ + struct mt76_queue_entry *e = &q->entry[idx]; + __le32 __ctrl = READ_ONCE(q->desc[idx].ctrl); + u32 ctrl = le32_to_cpu(__ctrl); + + if (!e->txwi || !e->skb) { + __le32 addr = READ_ONCE(q->desc[idx].buf0); + u32 len = FIELD_GET(MT_DMA_CTL_SD_LEN0, ctrl); + + dma_unmap_single(dev->dev, le32_to_cpu(addr), len, + DMA_TO_DEVICE); + } + + if (!(ctrl & MT_DMA_CTL_LAST_SEC0)) { + __le32 addr = READ_ONCE(q->desc[idx].buf1); + u32 len = FIELD_GET(MT_DMA_CTL_SD_LEN1, ctrl); + + dma_unmap_single(dev->dev, le32_to_cpu(addr), len, + DMA_TO_DEVICE); + } + + if (e->txwi == DMA_DUMMY_TXWI) + e->txwi = NULL; + + *prev_e = *e; + memset(e, 0, sizeof(*e)); +} + +static void +mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q) +{ + q->head = ioread32(&q->regs->dma_idx); + q->tail = q->head; + iowrite32(q->head, &q->regs->cpu_idx); +} + +static void +mt76_dma_tx_cleanup(struct mt76_dev *dev, enum mt76_txq_id qid, bool flush) +{ + struct mt76_queue *q = &dev->q_tx[qid]; + struct mt76_queue_entry entry; + bool wake = false; + int last; + + if (!q->ndesc) + return; + + spin_lock_bh(&q->lock); + if (flush) + last = -1; + else + last = ioread32(&q->regs->dma_idx); + + while (q->queued && q->tail != last) { + mt76_dma_tx_cleanup_idx(dev, q, q->tail, &entry); + if (entry.schedule) + q->swq_queued--; + + if (entry.skb) + dev->drv->tx_complete_skb(dev, q, &entry, flush); + + if (entry.txwi) { + mt76_put_txwi(dev, entry.txwi); + wake = true; + } + + q->tail = (q->tail + 1) % q->ndesc; + q->queued--; + + if (!flush && q->tail == last) + last = ioread32(&q->regs->dma_idx); + } + + if (!flush) + mt76_txq_schedule(dev, q); + else + mt76_dma_sync_idx(dev, q); + + wake = wake && qid < IEEE80211_NUM_ACS && q->queued < q->ndesc - 8; + spin_unlock_bh(&q->lock); + + if (wake) + ieee80211_wake_queue(dev->hw, qid); +} + +static void * +mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx, + int *len, u32 *info, bool *more) +{ + struct mt76_queue_entry *e = &q->entry[idx]; + struct mt76_desc *desc = &q->desc[idx]; + dma_addr_t buf_addr; + void *buf = e->buf; + int buf_len = SKB_WITH_OVERHEAD(q->buf_size); + + buf_addr = le32_to_cpu(READ_ONCE(desc->buf0)); + if (len) { + u32 ctl = le32_to_cpu(READ_ONCE(desc->ctrl)); + *len = FIELD_GET(MT_DMA_CTL_SD_LEN0, ctl); + *more = !(ctl & MT_DMA_CTL_LAST_SEC0); + } + + if (info) + *info = le32_to_cpu(desc->info); + + dma_unmap_single(dev->dev, buf_addr, buf_len, DMA_FROM_DEVICE); + e->buf = NULL; + + return buf; +} + +static void * +mt76_dma_dequeue(struct mt76_dev *dev, struct mt76_queue *q, bool flush, + int *len, u32 *info, bool *more) +{ + int idx = q->tail; + + *more = false; + if (!q->queued) + return NULL; + + if (!flush && !(q->desc[idx].ctrl & cpu_to_le32(MT_DMA_CTL_DMA_DONE))) + return NULL; + + q->tail = (q->tail + 1) % q->ndesc; + q->queued--; + + return mt76_dma_get_buf(dev, q, idx, len, info, more); +} + +static void +mt76_dma_kick_queue(struct mt76_dev *dev, struct mt76_queue *q) +{ + iowrite32(q->head, &q->regs->cpu_idx); +} + +static int +mt76_dma_rx_fill(struct mt76_dev *dev, struct mt76_queue *q, bool napi) +{ + dma_addr_t addr; + void *buf; + int frames = 0; + int len = SKB_WITH_OVERHEAD(q->buf_size); + int offset = q->buf_offset; + int idx; + void *(*alloc)(unsigned int fragsz); + + if (napi) + alloc = napi_alloc_frag; + else + alloc = netdev_alloc_frag; + + spin_lock_bh(&q->lock); + + while (q->queued < q->ndesc - 1) { + struct mt76_queue_buf qbuf; + + buf = alloc(q->buf_size); + if (!buf) + break; + + addr = dma_map_single(dev->dev, buf, len, DMA_FROM_DEVICE); + if (dma_mapping_error(dev->dev, addr)) { + skb_free_frag(buf); + break; + } + + qbuf.addr = addr + offset; + qbuf.len = len - offset; + idx = mt76_dma_add_buf(dev, q, &qbuf, 1, 0, buf, NULL); + frames++; + } + + if (frames) + mt76_dma_kick_queue(dev, q); + + spin_unlock_bh(&q->lock); + + return frames; +} + +static void +mt76_dma_rx_cleanup(struct mt76_dev *dev, struct mt76_queue *q) +{ + void *buf; + bool more; + + spin_lock_bh(&q->lock); + do { + buf = mt76_dma_dequeue(dev, q, true, NULL, NULL, &more); + if (!buf) + break; + + skb_free_frag(buf); + } while (1); + spin_unlock_bh(&q->lock); +} + +static void +mt76_dma_rx_reset(struct mt76_dev *dev, enum mt76_rxq_id qid) +{ + struct mt76_queue *q = &dev->q_rx[qid]; + int i; + + for (i = 0; i < q->ndesc; i++) + q->desc[i].ctrl &= ~cpu_to_le32(MT_DMA_CTL_DMA_DONE); + + mt76_dma_rx_cleanup(dev, q); + mt76_dma_sync_idx(dev, q); + mt76_dma_rx_fill(dev, q, false); +} + +static void +mt76_add_fragment(struct mt76_dev *dev, struct mt76_queue *q, void *data, + int len, bool more) +{ + struct page *page = virt_to_head_page(data); + int offset = data - page_address(page); + struct sk_buff *skb = q->rx_head; + + offset += q->buf_offset; + skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page, offset, len, + q->buf_size); + + if (more) + return; + + q->rx_head = NULL; + dev->drv->rx_skb(dev, q - dev->q_rx, skb); +} + +static int +mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget) +{ + struct sk_buff *skb; + unsigned char *data; + int len; + int done = 0; + bool more; + + while (done < budget) { + u32 info; + + data = mt76_dma_dequeue(dev, q, false, &len, &info, &more); + if (!data) + break; + + if (q->rx_head) { + mt76_add_fragment(dev, q, data, len, more); + continue; + } + + skb = build_skb(data, q->buf_size); + if (!skb) { + skb_free_frag(data); + continue; + } + + skb_reserve(skb, q->buf_offset); + if (skb->tail + len > skb->end) { + dev_kfree_skb(skb); + continue; + } + + if (q == &dev->q_rx[MT_RXQ_MCU]) { + u32 *rxfce = (u32 *) skb->cb; + *rxfce = info; + } + + __skb_put(skb, len); + done++; + + if (more) { + q->rx_head = skb; + continue; + } + + dev->drv->rx_skb(dev, q - dev->q_rx, skb); + } + + mt76_dma_rx_fill(dev, q, true); + return done; +} + +static int +mt76_dma_rx_poll(struct napi_struct *napi, int budget) +{ + struct mt76_dev *dev; + int qid, done = 0, cur; + + dev = container_of(napi->dev, struct mt76_dev, napi_dev); + qid = napi - dev->napi; + + rcu_read_lock(); + + do { + cur = mt76_dma_rx_process(dev, &dev->q_rx[qid], budget - done); + mt76_rx_poll_complete(dev, qid); + done += cur; + } while (cur && done < budget); + + rcu_read_unlock(); + + if (done < budget) { + napi_complete(napi); + dev->drv->rx_poll_complete(dev, qid); + } + + return done; +} + +static int +mt76_dma_init(struct mt76_dev *dev) +{ + int i; + + init_dummy_netdev(&dev->napi_dev); + + for (i = 0; i < ARRAY_SIZE(dev->q_rx); i++) { + netif_napi_add(&dev->napi_dev, &dev->napi[i], mt76_dma_rx_poll, + 64); + mt76_dma_rx_fill(dev, &dev->q_rx[i], false); + skb_queue_head_init(&dev->rx_skb[i]); + napi_enable(&dev->napi[i]); + } + + return 0; +} + +static const struct mt76_queue_ops mt76_dma_ops = { + .init = mt76_dma_init, + .alloc = mt76_dma_alloc_queue, + .add_buf = mt76_dma_add_buf, + .tx_cleanup = mt76_dma_tx_cleanup, + .rx_reset = mt76_dma_rx_reset, + .kick = mt76_dma_kick_queue, +}; + +int mt76_dma_attach(struct mt76_dev *dev) +{ + dev->queue_ops = &mt76_dma_ops; + return 0; +} +EXPORT_SYMBOL_GPL(mt76_dma_attach); + +void mt76_dma_cleanup(struct mt76_dev *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dev->q_tx); i++) + mt76_dma_tx_cleanup(dev, i, true); + + for (i = 0; i < ARRAY_SIZE(dev->q_rx); i++) { + netif_napi_del(&dev->napi[i]); + mt76_dma_rx_cleanup(dev, &dev->q_rx[i]); + } +} +EXPORT_SYMBOL_GPL(mt76_dma_cleanup); diff --git a/drivers/net/wireless/mediatek/mt76/dma.h b/drivers/net/wireless/mediatek/mt76/dma.h new file mode 100644 index 000000000000..1dad39697929 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/dma.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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. + */ +#ifndef __MT76_DMA_H +#define __MT76_DMA_H + +#define MT_RING_SIZE 0x10 + +#define MT_DMA_CTL_SD_LEN1 GENMASK(13, 0) +#define MT_DMA_CTL_LAST_SEC1 BIT(14) +#define MT_DMA_CTL_BURST BIT(15) +#define MT_DMA_CTL_SD_LEN0 GENMASK(29, 16) +#define MT_DMA_CTL_LAST_SEC0 BIT(30) +#define MT_DMA_CTL_DMA_DONE BIT(31) + +struct mt76_desc { + __le32 buf0; + __le32 ctrl; + __le32 buf1; + __le32 info; +} __packed __aligned(4); + +int mt76_dma_attach(struct mt76_dev *dev); +void mt76_dma_cleanup(struct mt76_dev *dev); + +#endif diff --git a/drivers/net/wireless/mediatek/mt76/eeprom.c b/drivers/net/wireless/mediatek/mt76/eeprom.c new file mode 100644 index 000000000000..530e5593765c --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/eeprom.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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/of.h> +#include <linux/of_net.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/etherdevice.h> +#include "mt76.h" + +static int +mt76_get_of_eeprom(struct mt76_dev *dev, int len) +{ +#if defined(CONFIG_OF) && defined(CONFIG_MTD) + struct device_node *np = dev->dev->of_node; + struct mtd_info *mtd; + const __be32 *list; + const char *part; + phandle phandle; + int offset = 0; + int size; + size_t retlen; + int ret; + + if (!np) + return -ENOENT; + + list = of_get_property(np, "mediatek,mtd-eeprom", &size); + if (!list) + return -ENOENT; + + phandle = be32_to_cpup(list++); + if (!phandle) + return -ENOENT; + + np = of_find_node_by_phandle(phandle); + if (!np) + return -EINVAL; + + part = of_get_property(np, "label", NULL); + if (!part) + part = np->name; + + mtd = get_mtd_device_nm(part); + if (IS_ERR(mtd)) + return PTR_ERR(mtd); + + if (size <= sizeof(*list)) + return -EINVAL; + + offset = be32_to_cpup(list); + ret = mtd_read(mtd, offset, len, &retlen, dev->eeprom.data); + put_mtd_device(mtd); + if (ret) + return ret; + + if (retlen < len) + return -EINVAL; + + return 0; +#else + return -ENOENT; +#endif +} + +void +mt76_eeprom_override(struct mt76_dev *dev) +{ +#ifdef CONFIG_OF + struct device_node *np = dev->dev->of_node; + const u8 *mac; + + if (!np) + return; + + mac = of_get_mac_address(np); + if (mac) + memcpy(dev->macaddr, mac, ETH_ALEN); +#endif + + if (!is_valid_ether_addr(dev->macaddr)) { + eth_random_addr(dev->macaddr); + dev_info(dev->dev, + "Invalid MAC address, using random address %pM\n", + dev->macaddr); + } +} +EXPORT_SYMBOL_GPL(mt76_eeprom_override); + +int +mt76_eeprom_init(struct mt76_dev *dev, int len) +{ + dev->eeprom.size = len; + dev->eeprom.data = devm_kzalloc(dev->dev, len, GFP_KERNEL); + if (!dev->eeprom.data) + return -ENOMEM; + + return !mt76_get_of_eeprom(dev, len); +} +EXPORT_SYMBOL_GPL(mt76_eeprom_init); diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c new file mode 100644 index 000000000000..5fcb2deb89a2 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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/of.h> +#include "mt76.h" + +#define CHAN2G(_idx, _freq) { \ + .band = NL80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 30, \ +} + +#define CHAN5G(_idx, _freq) { \ + .band = NL80211_BAND_5GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 30, \ +} + +static const struct ieee80211_channel mt76_channels_2ghz[] = { + CHAN2G(1, 2412), + CHAN2G(2, 2417), + CHAN2G(3, 2422), + CHAN2G(4, 2427), + CHAN2G(5, 2432), + CHAN2G(6, 2437), + CHAN2G(7, 2442), + CHAN2G(8, 2447), + CHAN2G(9, 2452), + CHAN2G(10, 2457), + CHAN2G(11, 2462), + CHAN2G(12, 2467), + CHAN2G(13, 2472), + CHAN2G(14, 2484), +}; + +static const struct ieee80211_channel mt76_channels_5ghz[] = { + CHAN5G(36, 5180), + CHAN5G(40, 5200), + CHAN5G(44, 5220), + CHAN5G(48, 5240), + + CHAN5G(52, 5260), + CHAN5G(56, 5280), + CHAN5G(60, 5300), + CHAN5G(64, 5320), + + CHAN5G(100, 5500), + CHAN5G(104, 5520), + CHAN5G(108, 5540), + CHAN5G(112, 5560), + CHAN5G(116, 5580), + CHAN5G(120, 5600), + CHAN5G(124, 5620), + CHAN5G(128, 5640), + CHAN5G(132, 5660), + CHAN5G(136, 5680), + CHAN5G(140, 5700), + + CHAN5G(149, 5745), + CHAN5G(153, 5765), + CHAN5G(157, 5785), + CHAN5G(161, 5805), + CHAN5G(165, 5825), +}; + +static const struct ieee80211_tpt_blink mt76_tpt_blink[] = { + { .throughput = 0 * 1024, .blink_time = 334 }, + { .throughput = 1 * 1024, .blink_time = 260 }, + { .throughput = 5 * 1024, .blink_time = 220 }, + { .throughput = 10 * 1024, .blink_time = 190 }, + { .throughput = 20 * 1024, .blink_time = 170 }, + { .throughput = 50 * 1024, .blink_time = 150 }, + { .throughput = 70 * 1024, .blink_time = 130 }, + { .throughput = 100 * 1024, .blink_time = 110 }, + { .throughput = 200 * 1024, .blink_time = 80 }, + { .throughput = 300 * 1024, .blink_time = 50 }, +}; + +static int mt76_led_init(struct mt76_dev *dev) +{ + struct device_node *np = dev->dev->of_node; + struct ieee80211_hw *hw = dev->hw; + int led_pin; + + if (!dev->led_cdev.brightness_set && !dev->led_cdev.blink_set) + return 0; + + snprintf(dev->led_name, sizeof(dev->led_name), + "mt76-%s", wiphy_name(hw->wiphy)); + + dev->led_cdev.name = dev->led_name; + dev->led_cdev.default_trigger = + ieee80211_create_tpt_led_trigger(hw, + IEEE80211_TPT_LEDTRIG_FL_RADIO, + mt76_tpt_blink, + ARRAY_SIZE(mt76_tpt_blink)); + + np = of_get_child_by_name(np, "led"); + if (np) { + if (!of_property_read_u32(np, "led-sources", &led_pin)) + dev->led_pin = led_pin; + dev->led_al = of_property_read_bool(np, "led-active-low"); + } + + return devm_led_classdev_register(dev->dev, &dev->led_cdev); +} + +static int +mt76_init_sband(struct mt76_dev *dev, struct mt76_sband *msband, + const struct ieee80211_channel *chan, int n_chan, + struct ieee80211_rate *rates, int n_rates, bool vht) +{ + struct ieee80211_supported_band *sband = &msband->sband; + struct ieee80211_sta_ht_cap *ht_cap; + struct ieee80211_sta_vht_cap *vht_cap; + void *chanlist; + u16 mcs_map; + int size; + + size = n_chan * sizeof(*chan); + chanlist = devm_kmemdup(dev->dev, chan, size, GFP_KERNEL); + if (!chanlist) + return -ENOMEM; + + msband->chan = devm_kzalloc(dev->dev, n_chan * sizeof(*msband->chan), + GFP_KERNEL); + if (!msband->chan) + return -ENOMEM; + + sband->channels = chanlist; + sband->n_channels = n_chan; + sband->bitrates = rates; + sband->n_bitrates = n_rates; + dev->chandef.chan = &sband->channels[0]; + + ht_cap = &sband->ht_cap; + ht_cap->ht_supported = true; + ht_cap->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40 | + IEEE80211_HT_CAP_GRN_FLD | + IEEE80211_HT_CAP_SGI_20 | + IEEE80211_HT_CAP_SGI_40 | + IEEE80211_HT_CAP_TX_STBC | + (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT); + + ht_cap->mcs.rx_mask[0] = 0xff; + ht_cap->mcs.rx_mask[1] = 0xff; + ht_cap->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; + ht_cap->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + ht_cap->ampdu_density = IEEE80211_HT_MPDU_DENSITY_4; + + if (!vht) + return 0; + + vht_cap = &sband->vht_cap; + vht_cap->vht_supported = true; + + mcs_map = (IEEE80211_VHT_MCS_SUPPORT_0_9 << (0 * 2)) | + (IEEE80211_VHT_MCS_SUPPORT_0_9 << (1 * 2)) | + (IEEE80211_VHT_MCS_NOT_SUPPORTED << (2 * 2)) | + (IEEE80211_VHT_MCS_NOT_SUPPORTED << (3 * 2)) | + (IEEE80211_VHT_MCS_NOT_SUPPORTED << (4 * 2)) | + (IEEE80211_VHT_MCS_NOT_SUPPORTED << (5 * 2)) | + (IEEE80211_VHT_MCS_NOT_SUPPORTED << (6 * 2)) | + (IEEE80211_VHT_MCS_NOT_SUPPORTED << (7 * 2)); + + vht_cap->vht_mcs.rx_mcs_map = cpu_to_le16(mcs_map); + vht_cap->vht_mcs.tx_mcs_map = cpu_to_le16(mcs_map); + vht_cap->cap |= IEEE80211_VHT_CAP_RXLDPC | + IEEE80211_VHT_CAP_TXSTBC | + IEEE80211_VHT_CAP_RXSTBC_1 | + IEEE80211_VHT_CAP_SHORT_GI_80; + + return 0; +} + +static int +mt76_init_sband_2g(struct mt76_dev *dev, struct ieee80211_rate *rates, + int n_rates) +{ + dev->hw->wiphy->bands[NL80211_BAND_2GHZ] = &dev->sband_2g.sband; + + return mt76_init_sband(dev, &dev->sband_2g, + mt76_channels_2ghz, + ARRAY_SIZE(mt76_channels_2ghz), + rates, n_rates, false); +} + +static int +mt76_init_sband_5g(struct mt76_dev *dev, struct ieee80211_rate *rates, + int n_rates, bool vht) +{ + dev->hw->wiphy->bands[NL80211_BAND_5GHZ] = &dev->sband_5g.sband; + + return mt76_init_sband(dev, &dev->sband_5g, + mt76_channels_5ghz, + ARRAY_SIZE(mt76_channels_5ghz), + rates, n_rates, vht); +} + +static void +mt76_check_sband(struct mt76_dev *dev, int band) +{ + struct ieee80211_supported_band *sband = dev->hw->wiphy->bands[band]; + bool found = false; + int i; + + if (!sband) + return; + + for (i = 0; i < sband->n_channels; i++) { + if (sband->channels[i].flags & IEEE80211_CHAN_DISABLED) + continue; + + found = true; + break; + } + + if (found) + return; + + sband->n_channels = 0; + dev->hw->wiphy->bands[band] = NULL; +} + +int mt76_register_device(struct mt76_dev *dev, bool vht, + struct ieee80211_rate *rates, int n_rates) +{ + struct ieee80211_hw *hw = dev->hw; + struct wiphy *wiphy = hw->wiphy; + int ret; + + dev_set_drvdata(dev->dev, dev); + + spin_lock_init(&dev->lock); + spin_lock_init(&dev->cc_lock); + INIT_LIST_HEAD(&dev->txwi_cache); + + SET_IEEE80211_DEV(hw, dev->dev); + SET_IEEE80211_PERM_ADDR(hw, dev->macaddr); + + wiphy->interface_modes = + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | +#ifdef CONFIG_MAC80211_MESH + BIT(NL80211_IFTYPE_MESH_POINT) | +#endif + BIT(NL80211_IFTYPE_ADHOC); + + wiphy->features |= NL80211_FEATURE_ACTIVE_MONITOR; + + hw->txq_data_size = sizeof(struct mt76_txq); + hw->max_tx_fragments = 16; + + ieee80211_hw_set(hw, SIGNAL_DBM); + ieee80211_hw_set(hw, PS_NULLFUNC_STACK); + ieee80211_hw_set(hw, HOST_BROADCAST_PS_BUFFERING); + ieee80211_hw_set(hw, AMPDU_AGGREGATION); + ieee80211_hw_set(hw, SUPPORTS_RC_TABLE); + ieee80211_hw_set(hw, SUPPORT_FAST_XMIT); + ieee80211_hw_set(hw, SUPPORTS_CLONED_SKBS); + ieee80211_hw_set(hw, SUPPORTS_AMSDU_IN_AMPDU); + ieee80211_hw_set(hw, TX_AMSDU); + ieee80211_hw_set(hw, TX_FRAG_LIST); + ieee80211_hw_set(hw, MFP_CAPABLE); + + wiphy->flags |= WIPHY_FLAG_IBSS_RSN; + + if (dev->cap.has_2ghz) { + ret = mt76_init_sband_2g(dev, rates, n_rates); + if (ret) + return ret; + } + + if (dev->cap.has_5ghz) { + ret = mt76_init_sband_5g(dev, rates + 4, n_rates - 4, vht); + if (ret) + return ret; + } + + wiphy_read_of_freq_limits(dev->hw->wiphy); + mt76_check_sband(dev, NL80211_BAND_2GHZ); + mt76_check_sband(dev, NL80211_BAND_5GHZ); + + ret = mt76_led_init(dev); + if (ret) + return ret; + + return ieee80211_register_hw(hw); +} +EXPORT_SYMBOL_GPL(mt76_register_device); + +void mt76_unregister_device(struct mt76_dev *dev) +{ + struct ieee80211_hw *hw = dev->hw; + + ieee80211_unregister_hw(hw); + mt76_tx_free(dev); +} +EXPORT_SYMBOL_GPL(mt76_unregister_device); + +void mt76_rx(struct mt76_dev *dev, enum mt76_rxq_id q, struct sk_buff *skb) +{ + if (!test_bit(MT76_STATE_RUNNING, &dev->state)) { + dev_kfree_skb(skb); + return; + } + + __skb_queue_tail(&dev->rx_skb[q], skb); +} +EXPORT_SYMBOL_GPL(mt76_rx); + +void mt76_set_channel(struct mt76_dev *dev) +{ + struct ieee80211_hw *hw = dev->hw; + struct cfg80211_chan_def *chandef = &hw->conf.chandef; + struct mt76_channel_state *state; + bool offchannel = hw->conf.flags & IEEE80211_CONF_OFFCHANNEL; + + if (dev->drv->update_survey) + dev->drv->update_survey(dev); + + dev->chandef = *chandef; + + if (!offchannel) + dev->main_chan = chandef->chan; + + if (chandef->chan != dev->main_chan) { + state = mt76_channel_state(dev, chandef->chan); + memset(state, 0, sizeof(*state)); + } +} +EXPORT_SYMBOL_GPL(mt76_set_channel); + +int mt76_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct mt76_dev *dev = hw->priv; + struct mt76_sband *sband; + struct ieee80211_channel *chan; + struct mt76_channel_state *state; + int ret = 0; + + if (idx == 0 && dev->drv->update_survey) + dev->drv->update_survey(dev); + + sband = &dev->sband_2g; + if (idx >= sband->sband.n_channels) { + idx -= sband->sband.n_channels; + sband = &dev->sband_5g; + } + + if (idx >= sband->sband.n_channels) + return -ENOENT; + + chan = &sband->sband.channels[idx]; + state = mt76_channel_state(dev, chan); + + memset(survey, 0, sizeof(*survey)); + survey->channel = chan; + survey->filled = SURVEY_INFO_TIME | SURVEY_INFO_TIME_BUSY; + if (chan == dev->main_chan) + survey->filled |= SURVEY_INFO_IN_USE; + + spin_lock_bh(&dev->cc_lock); + survey->time = div_u64(state->cc_active, 1000); + survey->time_busy = div_u64(state->cc_busy, 1000); + spin_unlock_bh(&dev->cc_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(mt76_get_survey); + +void mt76_wcid_key_setup(struct mt76_dev *dev, struct mt76_wcid *wcid, + struct ieee80211_key_conf *key) +{ + struct ieee80211_key_seq seq; + int i; + + wcid->rx_check_pn = false; + + if (!key) + return; + + if (key->cipher == WLAN_CIPHER_SUITE_CCMP) + wcid->rx_check_pn = true; + + for (i = 0; i < IEEE80211_NUM_TIDS; i++) { + ieee80211_get_key_rx_seq(key, i, &seq); + memcpy(wcid->rx_key_pn[i], seq.ccmp.pn, sizeof(seq.ccmp.pn)); + } +} +EXPORT_SYMBOL(mt76_wcid_key_setup); + +static struct ieee80211_sta *mt76_rx_convert(struct sk_buff *skb) +{ + struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); + struct mt76_rx_status mstat; + + mstat = *((struct mt76_rx_status *) skb->cb); + memset(status, 0, sizeof(*status)); + + status->flag = mstat.flag; + status->freq = mstat.freq; + status->enc_flags = mstat.enc_flags; + status->encoding = mstat.encoding; + status->bw = mstat.bw; + status->rate_idx = mstat.rate_idx; + status->nss = mstat.nss; + status->band = mstat.band; + status->signal = mstat.signal; + status->chains = mstat.chains; + + BUILD_BUG_ON(sizeof(mstat) > sizeof(skb->cb)); + BUILD_BUG_ON(sizeof(status->chain_signal) != sizeof(mstat.chain_signal)); + memcpy(status->chain_signal, mstat.chain_signal, sizeof(mstat.chain_signal)); + + return wcid_to_sta(mstat.wcid); +} + +static int +mt76_check_ccmp_pn(struct sk_buff *skb) +{ + struct mt76_rx_status *status = (struct mt76_rx_status *) skb->cb; + struct mt76_wcid *wcid = status->wcid; + struct ieee80211_hdr *hdr; + int ret; + + if (!(status->flag & RX_FLAG_DECRYPTED)) + return 0; + + if (!wcid || !wcid->rx_check_pn) + return 0; + + if (!(status->flag & RX_FLAG_IV_STRIPPED)) { + /* + * Validate the first fragment both here and in mac80211 + * All further fragments will be validated by mac80211 only. + */ + hdr = (struct ieee80211_hdr *) skb->data; + if (ieee80211_is_frag(hdr) && + !ieee80211_is_first_frag(hdr->frame_control)) + return 0; + } + + BUILD_BUG_ON(sizeof(status->iv) != sizeof(wcid->rx_key_pn[0])); + ret = memcmp(status->iv, wcid->rx_key_pn[status->tid], + sizeof(status->iv)); + if (ret <= 0) + return -EINVAL; /* replay */ + + memcpy(wcid->rx_key_pn[status->tid], status->iv, sizeof(status->iv)); + + if (status->flag & RX_FLAG_IV_STRIPPED) + status->flag |= RX_FLAG_PN_VALIDATED; + + return 0; +} + +void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames, + int queue) +{ + struct napi_struct *napi = NULL; + struct ieee80211_sta *sta; + struct sk_buff *skb; + + if (queue >= 0) + napi = &dev->napi[queue]; + + while ((skb = __skb_dequeue(frames)) != NULL) { + if (mt76_check_ccmp_pn(skb)) { + dev_kfree_skb(skb); + continue; + } + + sta = mt76_rx_convert(skb); + ieee80211_rx_napi(dev->hw, sta, skb, napi); + } +} + +void mt76_rx_poll_complete(struct mt76_dev *dev, enum mt76_rxq_id q) +{ + struct sk_buff_head frames; + struct sk_buff *skb; + + __skb_queue_head_init(&frames); + + while ((skb = __skb_dequeue(&dev->rx_skb[q])) != NULL) + mt76_rx_aggr_reorder(skb, &frames); + + mt76_rx_complete(dev, &frames, q); +} diff --git a/drivers/net/wireless/mediatek/mt76/mmio.c b/drivers/net/wireless/mediatek/mt76/mmio.c new file mode 100644 index 000000000000..09a14dead6e3 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mmio.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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 "trace.h" + +static u32 mt76_mmio_rr(struct mt76_dev *dev, u32 offset) +{ + u32 val; + + val = ioread32(dev->regs + offset); + trace_reg_rr(dev, offset, val); + + return val; +} + +static void mt76_mmio_wr(struct mt76_dev *dev, u32 offset, u32 val) +{ + trace_reg_wr(dev, offset, val); + iowrite32(val, dev->regs + offset); +} + +static u32 mt76_mmio_rmw(struct mt76_dev *dev, u32 offset, u32 mask, u32 val) +{ + val |= mt76_mmio_rr(dev, offset) & ~mask; + mt76_mmio_wr(dev, offset, val); + return val; +} + +static void mt76_mmio_copy(struct mt76_dev *dev, u32 offset, const void *data, + int len) +{ + __iowrite32_copy(dev->regs + offset, data, len >> 2); +} + +void mt76_mmio_init(struct mt76_dev *dev, void __iomem *regs) +{ + static const struct mt76_bus_ops mt76_mmio_ops = { + .rr = mt76_mmio_rr, + .rmw = mt76_mmio_rmw, + .wr = mt76_mmio_wr, + .copy = mt76_mmio_copy, + }; + + dev->bus = &mt76_mmio_ops; + dev->regs = regs; +} +EXPORT_SYMBOL_GPL(mt76_mmio_init); diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h new file mode 100644 index 000000000000..129015c9d116 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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. + */ + +#ifndef __MT76_H +#define __MT76_H + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <linux/skbuff.h> +#include <linux/leds.h> +#include <net/mac80211.h> +#include "util.h" + +#define MT_TX_RING_SIZE 256 +#define MT_MCU_RING_SIZE 32 +#define MT_RX_BUF_SIZE 2048 + +struct mt76_dev; + +struct mt76_bus_ops { + u32 (*rr)(struct mt76_dev *dev, u32 offset); + void (*wr)(struct mt76_dev *dev, u32 offset, u32 val); + u32 (*rmw)(struct mt76_dev *dev, u32 offset, u32 mask, u32 val); + void (*copy)(struct mt76_dev *dev, u32 offset, const void *data, + int len); +}; + +enum mt76_txq_id { + MT_TXQ_VO = IEEE80211_AC_VO, + MT_TXQ_VI = IEEE80211_AC_VI, + MT_TXQ_BE = IEEE80211_AC_BE, + MT_TXQ_BK = IEEE80211_AC_BK, + MT_TXQ_PSD, + MT_TXQ_MCU, + MT_TXQ_BEACON, + MT_TXQ_CAB, + __MT_TXQ_MAX +}; + +enum mt76_rxq_id { + MT_RXQ_MAIN, + MT_RXQ_MCU, + __MT_RXQ_MAX +}; + +struct mt76_queue_buf { + dma_addr_t addr; + int len; +}; + +struct mt76_queue_entry { + union { + void *buf; + struct sk_buff *skb; + }; + struct mt76_txwi_cache *txwi; + bool schedule; +}; + +struct mt76_queue_regs { + u32 desc_base; + u32 ring_size; + u32 cpu_idx; + u32 dma_idx; +} __packed __aligned(4); + +struct mt76_queue { + struct mt76_queue_regs __iomem *regs; + + spinlock_t lock; + struct mt76_queue_entry *entry; + struct mt76_desc *desc; + + struct list_head swq; + int swq_queued; + + u16 head; + u16 tail; + int ndesc; + int queued; + int buf_size; + + u8 buf_offset; + u8 hw_idx; + + dma_addr_t desc_dma; + struct sk_buff *rx_head; +}; + +struct mt76_queue_ops { + int (*init)(struct mt76_dev *dev); + + int (*alloc)(struct mt76_dev *dev, struct mt76_queue *q); + + int (*add_buf)(struct mt76_dev *dev, struct mt76_queue *q, + struct mt76_queue_buf *buf, int nbufs, u32 info, + struct sk_buff *skb, void *txwi); + + void *(*dequeue)(struct mt76_dev *dev, struct mt76_queue *q, bool flush, + int *len, u32 *info, bool *more); + + void (*rx_reset)(struct mt76_dev *dev, enum mt76_rxq_id qid); + + void (*tx_cleanup)(struct mt76_dev *dev, enum mt76_txq_id qid, + bool flush); + + void (*kick)(struct mt76_dev *dev, struct mt76_queue *q); +}; + +struct mt76_wcid { + struct mt76_rx_tid __rcu *aggr[IEEE80211_NUM_TIDS]; + + struct work_struct aggr_work; + + u8 idx; + u8 hw_key_idx; + + u8 sta:1; + + u8 rx_check_pn; + u8 rx_key_pn[IEEE80211_NUM_TIDS][6]; + + __le16 tx_rate; + bool tx_rate_set; + u8 tx_rate_nss; + s8 max_txpwr_adj; + bool sw_iv; +}; + +struct mt76_txq { + struct list_head list; + struct mt76_queue *hwq; + struct mt76_wcid *wcid; + + struct sk_buff_head retry_q; + + u16 agg_ssn; + bool send_bar; + bool aggr; +}; + +struct mt76_txwi_cache { + u32 txwi[8]; + dma_addr_t dma_addr; + struct list_head list; +}; + + +struct mt76_rx_tid { + struct rcu_head rcu_head; + + struct mt76_dev *dev; + + spinlock_t lock; + struct delayed_work reorder_work; + + u16 head; + u8 size; + u8 nframes; + + u8 started:1, stopped:1, timer_pending:1; + + struct sk_buff *reorder_buf[]; +}; + +enum { + MT76_STATE_INITIALIZED, + MT76_STATE_RUNNING, + MT76_SCANNING, + MT76_RESET, +}; + +struct mt76_hw_cap { + bool has_2ghz; + bool has_5ghz; +}; + +struct mt76_driver_ops { + u16 txwi_size; + + void (*update_survey)(struct mt76_dev *dev); + + int (*tx_prepare_skb)(struct mt76_dev *dev, void *txwi_ptr, + struct sk_buff *skb, struct mt76_queue *q, + struct mt76_wcid *wcid, + struct ieee80211_sta *sta, u32 *tx_info); + + void (*tx_complete_skb)(struct mt76_dev *dev, struct mt76_queue *q, + struct mt76_queue_entry *e, bool flush); + + void (*rx_skb)(struct mt76_dev *dev, enum mt76_rxq_id q, + struct sk_buff *skb); + + void (*rx_poll_complete)(struct mt76_dev *dev, enum mt76_rxq_id q); +}; + +struct mt76_channel_state { + u64 cc_active; + u64 cc_busy; +}; + +struct mt76_sband { + struct ieee80211_supported_band sband; + struct mt76_channel_state *chan; +}; + +struct mt76_dev { + struct ieee80211_hw *hw; + struct cfg80211_chan_def chandef; + struct ieee80211_channel *main_chan; + + spinlock_t lock; + spinlock_t cc_lock; + const struct mt76_bus_ops *bus; + const struct mt76_driver_ops *drv; + void __iomem *regs; + struct device *dev; + + struct net_device napi_dev; + struct napi_struct napi[__MT_RXQ_MAX]; + struct sk_buff_head rx_skb[__MT_RXQ_MAX]; + + struct list_head txwi_cache; + struct mt76_queue q_tx[__MT_TXQ_MAX]; + struct mt76_queue q_rx[__MT_RXQ_MAX]; + const struct mt76_queue_ops *queue_ops; + + u8 macaddr[ETH_ALEN]; + u32 rev; + unsigned long state; + + struct mt76_sband sband_2g; + struct mt76_sband sband_5g; + struct debugfs_blob_wrapper eeprom; + struct debugfs_blob_wrapper otp; + struct mt76_hw_cap cap; + + u32 debugfs_reg; + + struct led_classdev led_cdev; + char led_name[32]; + bool led_al; + u8 led_pin; +}; + +enum mt76_phy_type { + MT_PHY_TYPE_CCK, + MT_PHY_TYPE_OFDM, + MT_PHY_TYPE_HT, + MT_PHY_TYPE_HT_GF, + MT_PHY_TYPE_VHT, +}; + +struct mt76_rate_power { + union { + struct { + s8 cck[4]; + s8 ofdm[8]; + s8 ht[16]; + s8 vht[10]; + }; + s8 all[38]; + }; +}; + +struct mt76_rx_status { + struct mt76_wcid *wcid; + + unsigned long reorder_time; + + u8 iv[6]; + + u8 aggr:1; + u8 tid; + u16 seqno; + + u16 freq; + u32 flag; + u8 enc_flags; + u8 encoding:2, bw:3; + u8 rate_idx; + u8 nss; + u8 band; + u8 signal; + u8 chains; + s8 chain_signal[IEEE80211_MAX_CHAINS]; +}; + +#define mt76_rr(dev, ...) (dev)->mt76.bus->rr(&((dev)->mt76), __VA_ARGS__) +#define mt76_wr(dev, ...) (dev)->mt76.bus->wr(&((dev)->mt76), __VA_ARGS__) +#define mt76_rmw(dev, ...) (dev)->mt76.bus->rmw(&((dev)->mt76), __VA_ARGS__) +#define mt76_wr_copy(dev, ...) (dev)->mt76.bus->copy(&((dev)->mt76), __VA_ARGS__) + +#define mt76_set(dev, offset, val) mt76_rmw(dev, offset, 0, val) +#define mt76_clear(dev, offset, val) mt76_rmw(dev, offset, val, 0) + +#define mt76_get_field(_dev, _reg, _field) \ + FIELD_GET(_field, mt76_rr(dev, _reg)) + +#define mt76_rmw_field(_dev, _reg, _field, _val) \ + mt76_rmw(_dev, _reg, _field, FIELD_PREP(_field, _val)) + +#define mt76_hw(dev) (dev)->mt76.hw + +bool __mt76_poll(struct mt76_dev *dev, u32 offset, u32 mask, u32 val, + int timeout); + +#define mt76_poll(dev, ...) __mt76_poll(&((dev)->mt76), __VA_ARGS__) + +bool __mt76_poll_msec(struct mt76_dev *dev, u32 offset, u32 mask, u32 val, + int timeout); + +#define mt76_poll_msec(dev, ...) __mt76_poll_msec(&((dev)->mt76), __VA_ARGS__) + +void mt76_mmio_init(struct mt76_dev *dev, void __iomem *regs); + +static inline u16 mt76_chip(struct mt76_dev *dev) +{ + return dev->rev >> 16; +} + +static inline u16 mt76_rev(struct mt76_dev *dev) +{ + return dev->rev & 0xffff; +} + +#define mt76xx_chip(dev) mt76_chip(&((dev)->mt76)) +#define mt76xx_rev(dev) mt76_rev(&((dev)->mt76)) + +#define mt76_init_queues(dev) (dev)->mt76.queue_ops->init(&((dev)->mt76)) +#define mt76_queue_alloc(dev, ...) (dev)->mt76.queue_ops->alloc(&((dev)->mt76), __VA_ARGS__) +#define mt76_queue_add_buf(dev, ...) (dev)->mt76.queue_ops->add_buf(&((dev)->mt76), __VA_ARGS__) +#define mt76_queue_rx_reset(dev, ...) (dev)->mt76.queue_ops->rx_reset(&((dev)->mt76), __VA_ARGS__) +#define mt76_queue_tx_cleanup(dev, ...) (dev)->mt76.queue_ops->tx_cleanup(&((dev)->mt76), __VA_ARGS__) +#define mt76_queue_kick(dev, ...) (dev)->mt76.queue_ops->kick(&((dev)->mt76), __VA_ARGS__) + +static inline struct mt76_channel_state * +mt76_channel_state(struct mt76_dev *dev, struct ieee80211_channel *c) +{ + struct mt76_sband *msband; + int idx; + + if (c->band == NL80211_BAND_2GHZ) + msband = &dev->sband_2g; + else + msband = &dev->sband_5g; + + idx = c - &msband->sband.channels[0]; + return &msband->chan[idx]; +} + +int mt76_register_device(struct mt76_dev *dev, bool vht, + struct ieee80211_rate *rates, int n_rates); +void mt76_unregister_device(struct mt76_dev *dev); + +struct dentry *mt76_register_debugfs(struct mt76_dev *dev); + +int mt76_eeprom_init(struct mt76_dev *dev, int len); +void mt76_eeprom_override(struct mt76_dev *dev); + +static inline struct ieee80211_txq * +mtxq_to_txq(struct mt76_txq *mtxq) +{ + void *ptr = mtxq; + + return container_of(ptr, struct ieee80211_txq, drv_priv); +} + +static inline struct ieee80211_sta * +wcid_to_sta(struct mt76_wcid *wcid) +{ + void *ptr = wcid; + + if (!wcid || !wcid->sta) + return NULL; + + return container_of(ptr, struct ieee80211_sta, drv_priv); +} + +int mt76_tx_queue_skb(struct mt76_dev *dev, struct mt76_queue *q, + struct sk_buff *skb, struct mt76_wcid *wcid, + struct ieee80211_sta *sta); + +void mt76_rx(struct mt76_dev *dev, enum mt76_rxq_id q, struct sk_buff *skb); +void mt76_tx(struct mt76_dev *dev, struct ieee80211_sta *sta, + struct mt76_wcid *wcid, struct sk_buff *skb); +void mt76_txq_init(struct mt76_dev *dev, struct ieee80211_txq *txq); +void mt76_txq_remove(struct mt76_dev *dev, struct ieee80211_txq *txq); +void mt76_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *txq); +void mt76_stop_tx_queues(struct mt76_dev *dev, struct ieee80211_sta *sta, + bool send_bar); +void mt76_txq_schedule(struct mt76_dev *dev, struct mt76_queue *hwq); +void mt76_txq_schedule_all(struct mt76_dev *dev); +void mt76_release_buffered_frames(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + u16 tids, int nframes, + enum ieee80211_frame_release_type reason, + bool more_data); +void mt76_set_channel(struct mt76_dev *dev); +int mt76_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey); + +int mt76_rx_aggr_start(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tid, + u16 ssn, u8 size); +void mt76_rx_aggr_stop(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tid); + +void mt76_wcid_key_setup(struct mt76_dev *dev, struct mt76_wcid *wcid, + struct ieee80211_key_conf *key); + +/* internal */ +void mt76_tx_free(struct mt76_dev *dev); +void mt76_put_txwi(struct mt76_dev *dev, struct mt76_txwi_cache *t); +void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames, + int queue); +void mt76_rx_poll_complete(struct mt76_dev *dev, enum mt76_rxq_id q); +void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames); + +#endif diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2.h b/drivers/net/wireless/mediatek/mt76/mt76x2.h new file mode 100644 index 000000000000..17df17afd9bf --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2.h @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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. + */ + +#ifndef __MT76x2_H +#define __MT76x2_H + +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/spinlock.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/bitops.h> +#include <linux/kfifo.h> + +#define MT7662_FIRMWARE "mt7662.bin" +#define MT7662_ROM_PATCH "mt7662_rom_patch.bin" +#define MT7662_EEPROM_SIZE 512 + +#define MT76x2_RX_RING_SIZE 256 +#define MT_RX_HEADROOM 32 + +#define MT_MAX_CHAINS 2 + +#define MT_CALIBRATE_INTERVAL HZ + +#include "mt76.h" +#include "mt76x2_regs.h" +#include "mt76x2_mac.h" +#include "mt76x2_dfs.h" + +struct mt76x2_mcu { + struct mutex mutex; + + wait_queue_head_t wait; + struct sk_buff_head res_q; + + u32 msg_seq; +}; + +struct mt76x2_rx_freq_cal { + s8 high_gain[MT_MAX_CHAINS]; + s8 rssi_offset[MT_MAX_CHAINS]; + s8 lna_gain; + u32 mcu_gain; +}; + +struct mt76x2_calibration { + struct mt76x2_rx_freq_cal rx; + + u8 agc_gain_init[MT_MAX_CHAINS]; + u8 agc_gain_cur[MT_MAX_CHAINS]; + + int avg_rssi[MT_MAX_CHAINS]; + int avg_rssi_all; + + s8 agc_gain_adjust; + s8 low_gain; + + u8 temp; + + bool init_cal_done; + bool tssi_cal_done; + bool tssi_comp_pending; + bool dpd_cal_done; + bool channel_cal_done; +}; + +struct mt76x2_dev { + struct mt76_dev mt76; /* must be first */ + + struct mac_address macaddr_list[8]; + + struct mutex mutex; + + const u16 *beacon_offsets; + unsigned long wcid_mask[128 / BITS_PER_LONG]; + + int txpower_conf; + int txpower_cur; + + u8 txdone_seq; + DECLARE_KFIFO_PTR(txstatus_fifo, struct mt76x2_tx_status); + + struct mt76x2_mcu mcu; + struct sk_buff *rx_head; + + struct tasklet_struct tx_tasklet; + struct tasklet_struct pre_tbtt_tasklet; + struct delayed_work cal_work; + struct delayed_work mac_work; + + u32 aggr_stats[32]; + + struct mt76_wcid global_wcid; + struct mt76_wcid __rcu *wcid[128]; + + spinlock_t irq_lock; + u32 irqmask; + + struct sk_buff *beacons[8]; + u8 beacon_mask; + u8 beacon_data_mask; + + u32 rev; + u32 rxfilter; + + u16 chainmask; + + struct mt76x2_calibration cal; + + s8 target_power; + s8 target_power_delta[2]; + struct mt76_rate_power rate_power; + bool enable_tpc; + + u8 coverage_class; + u8 slottime; + + struct mt76x2_dfs_pattern_detector dfs_pd; +}; + +struct mt76x2_vif { + u8 idx; + + struct mt76_wcid group_wcid; +}; + +struct mt76x2_sta { + struct mt76_wcid wcid; /* must be first */ + + struct mt76x2_vif *vif; + struct mt76x2_tx_status status; + int n_frames; +}; + +static inline bool is_mt7612(struct mt76x2_dev *dev) +{ + return (dev->rev >> 16) == 0x7612; +} + +void mt76x2_set_irq_mask(struct mt76x2_dev *dev, u32 clear, u32 set); + +static inline void mt76x2_irq_enable(struct mt76x2_dev *dev, u32 mask) +{ + mt76x2_set_irq_mask(dev, 0, mask); +} + +static inline void mt76x2_irq_disable(struct mt76x2_dev *dev, u32 mask) +{ + mt76x2_set_irq_mask(dev, mask, 0); +} + +extern const struct ieee80211_ops mt76x2_ops; + +struct mt76x2_dev *mt76x2_alloc_device(struct device *pdev); +int mt76x2_register_device(struct mt76x2_dev *dev); +void mt76x2_init_debugfs(struct mt76x2_dev *dev); + +irqreturn_t mt76x2_irq_handler(int irq, void *dev_instance); +void mt76x2_phy_power_on(struct mt76x2_dev *dev); +int mt76x2_init_hardware(struct mt76x2_dev *dev); +void mt76x2_stop_hardware(struct mt76x2_dev *dev); +int mt76x2_eeprom_init(struct mt76x2_dev *dev); +int mt76x2_apply_calibration_data(struct mt76x2_dev *dev, int channel); +void mt76x2_set_tx_ackto(struct mt76x2_dev *dev); + +int mt76x2_phy_start(struct mt76x2_dev *dev); +int mt76x2_phy_set_channel(struct mt76x2_dev *dev, + struct cfg80211_chan_def *chandef); +int mt76x2_phy_get_rssi(struct mt76x2_dev *dev, s8 rssi, int chain); +void mt76x2_phy_calibrate(struct work_struct *work); +void mt76x2_phy_set_txpower(struct mt76x2_dev *dev); + +int mt76x2_mcu_init(struct mt76x2_dev *dev); +int mt76x2_mcu_set_channel(struct mt76x2_dev *dev, u8 channel, u8 bw, + u8 bw_index, bool scan); +int mt76x2_mcu_set_radio_state(struct mt76x2_dev *dev, bool on); +int mt76x2_mcu_load_cr(struct mt76x2_dev *dev, u8 type, u8 temp_level, + u8 channel); +int mt76x2_mcu_cleanup(struct mt76x2_dev *dev); + +int mt76x2_dma_init(struct mt76x2_dev *dev); +void mt76x2_dma_cleanup(struct mt76x2_dev *dev); + +void mt76x2_cleanup(struct mt76x2_dev *dev); + +int mt76x2_tx_queue_mcu(struct mt76x2_dev *dev, enum mt76_txq_id qid, + struct sk_buff *skb, int cmd, int seq); +void mt76x2_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, + struct sk_buff *skb); +void mt76x2_tx_complete(struct mt76x2_dev *dev, struct sk_buff *skb); +int mt76x2_tx_prepare_skb(struct mt76_dev *mdev, void *txwi, + struct sk_buff *skb, struct mt76_queue *q, + struct mt76_wcid *wcid, struct ieee80211_sta *sta, + u32 *tx_info); +void mt76x2_tx_complete_skb(struct mt76_dev *mdev, struct mt76_queue *q, + struct mt76_queue_entry *e, bool flush); + +void mt76x2_pre_tbtt_tasklet(unsigned long arg); + +void mt76x2_rx_poll_complete(struct mt76_dev *mdev, enum mt76_rxq_id q); +void mt76x2_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q, + struct sk_buff *skb); + +void mt76x2_update_channel(struct mt76_dev *mdev); + +s8 mt76x2_tx_get_max_txpwr_adj(struct mt76x2_dev *dev, + const struct ieee80211_tx_rate *rate); +s8 mt76x2_tx_get_txpwr_adj(struct mt76x2_dev *dev, s8 txpwr, s8 max_txpwr_adj); +void mt76x2_tx_set_txpwr_auto(struct mt76x2_dev *dev, s8 txpwr); + +#endif diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_core.c b/drivers/net/wireless/mediatek/mt76/mt76x2_core.c new file mode 100644 index 000000000000..2629779e8d3e --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_core.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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/delay.h> +#include "mt76x2.h" +#include "mt76x2_trace.h" + +void mt76x2_set_irq_mask(struct mt76x2_dev *dev, u32 clear, u32 set) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->irq_lock, flags); + dev->irqmask &= ~clear; + dev->irqmask |= set; + mt76_wr(dev, MT_INT_MASK_CSR, dev->irqmask); + spin_unlock_irqrestore(&dev->irq_lock, flags); +} + +void mt76x2_rx_poll_complete(struct mt76_dev *mdev, enum mt76_rxq_id q) +{ + struct mt76x2_dev *dev = container_of(mdev, struct mt76x2_dev, mt76); + + mt76x2_irq_enable(dev, MT_INT_RX_DONE(q)); +} + +irqreturn_t mt76x2_irq_handler(int irq, void *dev_instance) +{ + struct mt76x2_dev *dev = dev_instance; + u32 intr; + + intr = mt76_rr(dev, MT_INT_SOURCE_CSR); + mt76_wr(dev, MT_INT_SOURCE_CSR, intr); + + if (!test_bit(MT76_STATE_INITIALIZED, &dev->mt76.state)) + return IRQ_NONE; + + trace_dev_irq(dev, intr, dev->irqmask); + + intr &= dev->irqmask; + + if (intr & MT_INT_TX_DONE_ALL) { + mt76x2_irq_disable(dev, MT_INT_TX_DONE_ALL); + tasklet_schedule(&dev->tx_tasklet); + } + + if (intr & MT_INT_RX_DONE(0)) { + mt76x2_irq_disable(dev, MT_INT_RX_DONE(0)); + napi_schedule(&dev->mt76.napi[0]); + } + + if (intr & MT_INT_RX_DONE(1)) { + mt76x2_irq_disable(dev, MT_INT_RX_DONE(1)); + napi_schedule(&dev->mt76.napi[1]); + } + + if (intr & MT_INT_PRE_TBTT) + tasklet_schedule(&dev->pre_tbtt_tasklet); + + /* send buffered multicast frames now */ + if (intr & MT_INT_TBTT) + mt76_queue_kick(dev, &dev->mt76.q_tx[MT_TXQ_PSD]); + + if (intr & MT_INT_TX_STAT) { + mt76x2_mac_poll_tx_status(dev, true); + tasklet_schedule(&dev->tx_tasklet); + } + + if (intr & MT_INT_GPTIMER) { + mt76x2_irq_disable(dev, MT_INT_GPTIMER); + tasklet_schedule(&dev->dfs_pd.dfs_tasklet); + } + + return IRQ_HANDLED; +} + diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_debugfs.c b/drivers/net/wireless/mediatek/mt76/mt76x2_debugfs.c new file mode 100644 index 000000000000..612feb593d7d --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_debugfs.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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/debugfs.h> +#include "mt76x2.h" + +static int +mt76x2_ampdu_stat_read(struct seq_file *file, void *data) +{ + struct mt76x2_dev *dev = file->private; + int i, j; + + for (i = 0; i < 4; i++) { + seq_puts(file, "Length: "); + for (j = 0; j < 8; j++) + seq_printf(file, "%8d | ", i * 8 + j + 1); + seq_puts(file, "\n"); + seq_puts(file, "Count: "); + for (j = 0; j < 8; j++) + seq_printf(file, "%8d | ", dev->aggr_stats[i * 8 + j]); + seq_puts(file, "\n"); + seq_puts(file, "--------"); + for (j = 0; j < 8; j++) + seq_puts(file, "-----------"); + seq_puts(file, "\n"); + } + + return 0; +} + +static int +mt76x2_ampdu_stat_open(struct inode *inode, struct file *f) +{ + return single_open(f, mt76x2_ampdu_stat_read, inode->i_private); +} + +static void +seq_puts_array(struct seq_file *file, const char *str, s8 *val, int len) +{ + int i; + + seq_printf(file, "%10s:", str); + for (i = 0; i < len; i++) + seq_printf(file, " %2d", val[i]); + seq_puts(file, "\n"); +} + +static int read_txpower(struct seq_file *file, void *data) +{ + struct mt76x2_dev *dev = dev_get_drvdata(file->private); + + seq_printf(file, "Target power: %d\n", dev->target_power); + + seq_puts_array(file, "Delta", dev->target_power_delta, + ARRAY_SIZE(dev->target_power_delta)); + seq_puts_array(file, "CCK", dev->rate_power.cck, + ARRAY_SIZE(dev->rate_power.cck)); + seq_puts_array(file, "OFDM", dev->rate_power.ofdm, + ARRAY_SIZE(dev->rate_power.ofdm)); + seq_puts_array(file, "HT", dev->rate_power.ht, + ARRAY_SIZE(dev->rate_power.ht)); + seq_puts_array(file, "VHT", dev->rate_power.vht, + ARRAY_SIZE(dev->rate_power.vht)); + return 0; +} + +static const struct file_operations fops_ampdu_stat = { + .open = mt76x2_ampdu_stat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int +mt76x2_dfs_stat_read(struct seq_file *file, void *data) +{ + int i; + struct mt76x2_dev *dev = file->private; + struct mt76x2_dfs_pattern_detector *dfs_pd = &dev->dfs_pd; + + for (i = 0; i < MT_DFS_NUM_ENGINES; i++) { + seq_printf(file, "engine: %d\n", i); + seq_printf(file, " hw pattern detected:\t%d\n", + dfs_pd->stats[i].hw_pattern); + seq_printf(file, " hw pulse discarded:\t%d\n", + dfs_pd->stats[i].hw_pulse_discarded); + } + + return 0; +} + +static int +mt76x2_dfs_stat_open(struct inode *inode, struct file *f) +{ + return single_open(f, mt76x2_dfs_stat_read, inode->i_private); +} + +static const struct file_operations fops_dfs_stat = { + .open = mt76x2_dfs_stat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +void mt76x2_init_debugfs(struct mt76x2_dev *dev) +{ + struct dentry *dir; + + dir = mt76_register_debugfs(&dev->mt76); + if (!dir) + return; + + debugfs_create_u8("temperature", S_IRUSR, dir, &dev->cal.temp); + debugfs_create_bool("tpc", S_IRUSR | S_IWUSR, dir, &dev->enable_tpc); + + debugfs_create_file("ampdu_stat", S_IRUSR, dir, dev, &fops_ampdu_stat); + debugfs_create_file("dfs_stats", S_IRUSR, dir, dev, &fops_dfs_stat); + debugfs_create_devm_seqfile(dev->mt76.dev, "txpower", dir, + read_txpower); +} diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_dfs.c b/drivers/net/wireless/mediatek/mt76/mt76x2_dfs.c new file mode 100644 index 000000000000..f936dc9a5476 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_dfs.c @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2016 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 "mt76x2.h" + +#define RADAR_SPEC(m, len, el, eh, wl, wh, \ + w_tolerance, tl, th, t_tolerance, \ + bl, bh, event_exp, power_jmp) \ +{ \ + .mode = m, \ + .avg_len = len, \ + .e_low = el, \ + .e_high = eh, \ + .w_low = wl, \ + .w_high = wh, \ + .w_margin = w_tolerance, \ + .t_low = tl, \ + .t_high = th, \ + .t_margin = t_tolerance, \ + .b_low = bl, \ + .b_high = bh, \ + .event_expiration = event_exp, \ + .pwr_jmp = power_jmp \ +} + +static const struct mt76x2_radar_specs etsi_radar_specs[] = { + /* 20MHz */ + RADAR_SPEC(0, 8, 2, 15, 106, 150, 10, 4900, 100096, 10, 0, + 0x7fffffff, 0x155cc0, 0x19cc), + RADAR_SPEC(0, 40, 4, 59, 96, 380, 150, 4900, 100096, 40, 0, + 0x7fffffff, 0x155cc0, 0x19cc), + RADAR_SPEC(3, 60, 20, 46, 300, 640, 80, 4900, 10100, 80, 0, + 0x7fffffff, 0x155cc0, 0x19dd), + RADAR_SPEC(8, 8, 2, 9, 106, 150, 32, 4900, 296704, 32, 0, + 0x7fffffff, 0x2191c0, 0x15cc), + /* 40MHz */ + RADAR_SPEC(0, 8, 2, 15, 106, 150, 10, 4900, 100096, 10, 0, + 0x7fffffff, 0x155cc0, 0x19cc), + RADAR_SPEC(0, 40, 4, 59, 96, 380, 150, 4900, 100096, 40, 0, + 0x7fffffff, 0x155cc0, 0x19cc), + RADAR_SPEC(3, 60, 20, 46, 300, 640, 80, 4900, 10100, 80, 0, + 0x7fffffff, 0x155cc0, 0x19dd), + RADAR_SPEC(8, 8, 2, 9, 106, 150, 32, 4900, 296704, 32, 0, + 0x7fffffff, 0x2191c0, 0x15cc), + /* 80MHz */ + RADAR_SPEC(0, 8, 2, 15, 106, 150, 10, 4900, 100096, 10, 0, + 0x7fffffff, 0x155cc0, 0x19cc), + RADAR_SPEC(0, 40, 4, 59, 96, 380, 150, 4900, 100096, 40, 0, + 0x7fffffff, 0x155cc0, 0x19cc), + RADAR_SPEC(3, 60, 20, 46, 300, 640, 80, 4900, 10100, 80, 0, + 0x7fffffff, 0x155cc0, 0x19dd), + RADAR_SPEC(8, 8, 2, 9, 106, 150, 32, 4900, 296704, 32, 0, + 0x7fffffff, 0x2191c0, 0x15cc) +}; + +static const struct mt76x2_radar_specs fcc_radar_specs[] = { + /* 20MHz */ + RADAR_SPEC(0, 8, 2, 12, 106, 150, 5, 2900, 80100, 5, 0, + 0x7fffffff, 0xfe808, 0x13dc), + RADAR_SPEC(0, 8, 2, 7, 106, 140, 5, 27600, 27900, 5, 0, + 0x7fffffff, 0xfe808, 0x19dd), + RADAR_SPEC(0, 40, 4, 54, 96, 480, 150, 2900, 80100, 40, 0, + 0x7fffffff, 0xfe808, 0x12cc), + RADAR_SPEC(2, 60, 15, 63, 640, 2080, 32, 19600, 40200, 32, 0, + 0x3938700, 0x57bcf00, 0x1289), + /* 40MHz */ + RADAR_SPEC(0, 8, 2, 12, 106, 150, 5, 2900, 80100, 5, 0, + 0x7fffffff, 0xfe808, 0x13dc), + RADAR_SPEC(0, 8, 2, 7, 106, 140, 5, 27600, 27900, 5, 0, + 0x7fffffff, 0xfe808, 0x19dd), + RADAR_SPEC(0, 40, 4, 54, 96, 480, 150, 2900, 80100, 40, 0, + 0x7fffffff, 0xfe808, 0x12cc), + RADAR_SPEC(2, 60, 15, 63, 640, 2080, 32, 19600, 40200, 32, 0, + 0x3938700, 0x57bcf00, 0x1289), + /* 80MHz */ + RADAR_SPEC(0, 8, 2, 14, 106, 150, 15, 2900, 80100, 15, 0, + 0x7fffffff, 0xfe808, 0x16cc), + RADAR_SPEC(0, 8, 2, 7, 106, 140, 5, 27600, 27900, 5, 0, + 0x7fffffff, 0xfe808, 0x19dd), + RADAR_SPEC(0, 40, 4, 54, 96, 480, 150, 2900, 80100, 40, 0, + 0x7fffffff, 0xfe808, 0x12cc), + RADAR_SPEC(2, 60, 15, 63, 640, 2080, 32, 19600, 40200, 32, 0, + 0x3938700, 0x57bcf00, 0x1289) +}; + +static const struct mt76x2_radar_specs jp_w56_radar_specs[] = { + /* 20MHz */ + RADAR_SPEC(0, 8, 2, 7, 106, 150, 5, 2900, 80100, 5, 0, + 0x7fffffff, 0x14c080, 0x13dc), + RADAR_SPEC(0, 8, 2, 7, 106, 140, 5, 27600, 27900, 5, 0, + 0x7fffffff, 0x14c080, 0x19dd), + RADAR_SPEC(0, 40, 4, 44, 96, 480, 150, 2900, 80100, 40, 0, + 0x7fffffff, 0x14c080, 0x12cc), + RADAR_SPEC(2, 60, 15, 48, 940, 2080, 32, 19600, 40200, 32, 0, + 0x3938700, 0X57bcf00, 0x1289), + /* 40MHz */ + RADAR_SPEC(0, 8, 2, 7, 106, 150, 5, 2900, 80100, 5, 0, + 0x7fffffff, 0x14c080, 0x13dc), + RADAR_SPEC(0, 8, 2, 7, 106, 140, 5, 27600, 27900, 5, 0, + 0x7fffffff, 0x14c080, 0x19dd), + RADAR_SPEC(0, 40, 4, 44, 96, 480, 150, 2900, 80100, 40, 0, + 0x7fffffff, 0x14c080, 0x12cc), + RADAR_SPEC(2, 60, 15, 48, 940, 2080, 32, 19600, 40200, 32, 0, + 0x3938700, 0X57bcf00, 0x1289), + /* 80MHz */ + RADAR_SPEC(0, 8, 2, 9, 106, 150, 15, 2900, 80100, 15, 0, + 0x7fffffff, 0x14c080, 0x16cc), + RADAR_SPEC(0, 8, 2, 7, 106, 140, 5, 27600, 27900, 5, 0, + 0x7fffffff, 0x14c080, 0x19dd), + RADAR_SPEC(0, 40, 4, 44, 96, 480, 150, 2900, 80100, 40, 0, + 0x7fffffff, 0x14c080, 0x12cc), + RADAR_SPEC(2, 60, 15, 48, 940, 2080, 32, 19600, 40200, 32, 0, + 0x3938700, 0X57bcf00, 0x1289) +}; + +static const struct mt76x2_radar_specs jp_w53_radar_specs[] = { + /* 20MHz */ + RADAR_SPEC(0, 8, 2, 9, 106, 150, 20, 28400, 77000, 20, 0, + 0x7fffffff, 0x14c080, 0x16cc), + { 0 }, + RADAR_SPEC(0, 40, 4, 44, 96, 200, 150, 28400, 77000, 60, 0, + 0x7fffffff, 0x14c080, 0x16cc), + { 0 }, + /* 40MHz */ + RADAR_SPEC(0, 8, 2, 9, 106, 150, 20, 28400, 77000, 20, 0, + 0x7fffffff, 0x14c080, 0x16cc), + { 0 }, + RADAR_SPEC(0, 40, 4, 44, 96, 200, 150, 28400, 77000, 60, 0, + 0x7fffffff, 0x14c080, 0x16cc), + { 0 }, + /* 80MHz */ + RADAR_SPEC(0, 8, 2, 9, 106, 150, 20, 28400, 77000, 20, 0, + 0x7fffffff, 0x14c080, 0x16cc), + { 0 }, + RADAR_SPEC(0, 40, 4, 44, 96, 200, 150, 28400, 77000, 60, 0, + 0x7fffffff, 0x14c080, 0x16cc), + { 0 } +}; + +static void mt76x2_dfs_set_capture_mode_ctrl(struct mt76x2_dev *dev, + u8 enable) +{ + u32 data; + + data = (1 << 1) | enable; + mt76_wr(dev, MT_BBP(DFS, 36), data); +} + +static bool mt76x2_dfs_check_chirp(struct mt76x2_dev *dev) +{ + bool ret = false; + u32 current_ts, delta_ts; + struct mt76x2_dfs_pattern_detector *dfs_pd = &dev->dfs_pd; + + current_ts = mt76_rr(dev, MT_PBF_LIFE_TIMER); + delta_ts = current_ts - dfs_pd->chirp_pulse_ts; + dfs_pd->chirp_pulse_ts = current_ts; + + /* 12 sec */ + if (delta_ts <= (12 * (1 << 20))) { + if (++dfs_pd->chirp_pulse_cnt > 8) + ret = true; + } else { + dfs_pd->chirp_pulse_cnt = 1; + } + + return ret; +} + +static void mt76x2_dfs_get_hw_pulse(struct mt76x2_dev *dev, + struct mt76x2_dfs_hw_pulse *pulse) +{ + u32 data; + + /* select channel */ + data = (MT_DFS_CH_EN << 16) | pulse->engine; + mt76_wr(dev, MT_BBP(DFS, 0), data); + + /* reported period */ + pulse->period = mt76_rr(dev, MT_BBP(DFS, 19)); + + /* reported width */ + pulse->w1 = mt76_rr(dev, MT_BBP(DFS, 20)); + pulse->w2 = mt76_rr(dev, MT_BBP(DFS, 23)); + + /* reported burst number */ + pulse->burst = mt76_rr(dev, MT_BBP(DFS, 22)); +} + +static bool mt76x2_dfs_check_hw_pulse(struct mt76x2_dev *dev, + struct mt76x2_dfs_hw_pulse *pulse) +{ + bool ret = false; + + if (!pulse->period || !pulse->w1) + return false; + + switch (dev->dfs_pd.region) { + case NL80211_DFS_FCC: + if (pulse->engine > 3) + break; + + if (pulse->engine == 3) { + ret = mt76x2_dfs_check_chirp(dev); + break; + } + + /* check short pulse*/ + if (pulse->w1 < 120) + ret = (pulse->period >= 2900 && + (pulse->period <= 4700 || + pulse->period >= 6400) && + (pulse->period <= 6800 || + pulse->period >= 10200) && + pulse->period <= 61600); + else if (pulse->w1 < 130) /* 120 - 130 */ + ret = (pulse->period >= 2900 && + pulse->period <= 61600); + else + ret = (pulse->period >= 3500 && + pulse->period <= 10100); + break; + case NL80211_DFS_ETSI: + if (pulse->engine >= 3) + break; + + ret = (pulse->period >= 4900 && + (pulse->period <= 10200 || + pulse->period >= 12400) && + pulse->period <= 100100); + break; + case NL80211_DFS_JP: + if (dev->mt76.chandef.chan->center_freq >= 5250 && + dev->mt76.chandef.chan->center_freq <= 5350) { + /* JPW53 */ + if (pulse->w1 <= 130) + ret = (pulse->period >= 28360 && + (pulse->period <= 28700 || + pulse->period >= 76900) && + pulse->period <= 76940); + break; + } + + if (pulse->engine > 3) + break; + + if (pulse->engine == 3) { + ret = mt76x2_dfs_check_chirp(dev); + break; + } + + /* check short pulse*/ + if (pulse->w1 < 120) + ret = (pulse->period >= 2900 && + (pulse->period <= 4700 || + pulse->period >= 6400) && + (pulse->period <= 6800 || + pulse->period >= 27560) && + (pulse->period <= 27960 || + pulse->period >= 28360) && + (pulse->period <= 28700 || + pulse->period >= 79900) && + pulse->period <= 80100); + else if (pulse->w1 < 130) /* 120 - 130 */ + ret = (pulse->period >= 2900 && + (pulse->period <= 10100 || + pulse->period >= 27560) && + (pulse->period <= 27960 || + pulse->period >= 28360) && + (pulse->period <= 28700 || + pulse->period >= 79900) && + pulse->period <= 80100); + else + ret = (pulse->period >= 3900 && + pulse->period <= 10100); + break; + case NL80211_DFS_UNSET: + default: + return false; + } + + return ret; +} + +static void mt76x2_dfs_tasklet(unsigned long arg) +{ + struct mt76x2_dev *dev = (struct mt76x2_dev *)arg; + struct mt76x2_dfs_pattern_detector *dfs_pd = &dev->dfs_pd; + u32 engine_mask; + int i; + + if (test_bit(MT76_SCANNING, &dev->mt76.state)) + goto out; + + engine_mask = mt76_rr(dev, MT_BBP(DFS, 1)); + if (!(engine_mask & 0xf)) + goto out; + + for (i = 0; i < MT_DFS_NUM_ENGINES; i++) { + struct mt76x2_dfs_hw_pulse pulse; + + if (!(engine_mask & (1 << i))) + continue; + + pulse.engine = i; + mt76x2_dfs_get_hw_pulse(dev, &pulse); + + if (!mt76x2_dfs_check_hw_pulse(dev, &pulse)) { + dfs_pd->stats[i].hw_pulse_discarded++; + continue; + } + + /* hw detector rx radar pattern */ + dfs_pd->stats[i].hw_pattern++; + ieee80211_radar_detected(dev->mt76.hw); + + /* reset hw detector */ + mt76_wr(dev, MT_BBP(DFS, 1), 0xf); + + return; + } + + /* reset hw detector */ + mt76_wr(dev, MT_BBP(DFS, 1), 0xf); + +out: + mt76x2_irq_enable(dev, MT_INT_GPTIMER); +} + +static void mt76x2_dfs_set_bbp_params(struct mt76x2_dev *dev) +{ + u32 data; + u8 i, shift; + const struct mt76x2_radar_specs *radar_specs; + + switch (dev->mt76.chandef.width) { + case NL80211_CHAN_WIDTH_40: + shift = MT_DFS_NUM_ENGINES; + break; + case NL80211_CHAN_WIDTH_80: + shift = 2 * MT_DFS_NUM_ENGINES; + break; + default: + shift = 0; + break; + } + + switch (dev->dfs_pd.region) { + case NL80211_DFS_FCC: + radar_specs = &fcc_radar_specs[shift]; + break; + case NL80211_DFS_ETSI: + radar_specs = &etsi_radar_specs[shift]; + break; + case NL80211_DFS_JP: + if (dev->mt76.chandef.chan->center_freq >= 5250 && + dev->mt76.chandef.chan->center_freq <= 5350) + radar_specs = &jp_w53_radar_specs[shift]; + else + radar_specs = &jp_w56_radar_specs[shift]; + break; + case NL80211_DFS_UNSET: + default: + return; + } + + data = (MT_DFS_VGA_MASK << 16) | + (MT_DFS_PWR_GAIN_OFFSET << 12) | + (MT_DFS_PWR_DOWN_TIME << 8) | + (MT_DFS_SYM_ROUND << 4) | + (MT_DFS_DELTA_DELAY & 0xf); + mt76_wr(dev, MT_BBP(DFS, 2), data); + + data = (MT_DFS_RX_PE_MASK << 16) | MT_DFS_PKT_END_MASK; + mt76_wr(dev, MT_BBP(DFS, 3), data); + + for (i = 0; i < MT_DFS_NUM_ENGINES; i++) { + /* configure engine */ + mt76_wr(dev, MT_BBP(DFS, 0), i); + + /* detection mode + avg_len */ + data = ((radar_specs[i].avg_len & 0x1ff) << 16) | + (radar_specs[i].mode & 0xf); + mt76_wr(dev, MT_BBP(DFS, 4), data); + + /* dfs energy */ + data = ((radar_specs[i].e_high & 0x0fff) << 16) | + (radar_specs[i].e_low & 0x0fff); + mt76_wr(dev, MT_BBP(DFS, 5), data); + + /* dfs period */ + mt76_wr(dev, MT_BBP(DFS, 7), radar_specs[i].t_low); + mt76_wr(dev, MT_BBP(DFS, 9), radar_specs[i].t_high); + + /* dfs burst */ + mt76_wr(dev, MT_BBP(DFS, 11), radar_specs[i].b_low); + mt76_wr(dev, MT_BBP(DFS, 13), radar_specs[i].b_high); + + /* dfs width */ + data = ((radar_specs[i].w_high & 0x0fff) << 16) | + (radar_specs[i].w_low & 0x0fff); + mt76_wr(dev, MT_BBP(DFS, 14), data); + + /* dfs margins */ + data = (radar_specs[i].w_margin << 16) | + radar_specs[i].t_margin; + mt76_wr(dev, MT_BBP(DFS, 15), data); + + /* dfs event expiration */ + mt76_wr(dev, MT_BBP(DFS, 17), radar_specs[i].event_expiration); + + /* dfs pwr adj */ + mt76_wr(dev, MT_BBP(DFS, 30), radar_specs[i].pwr_jmp); + } + + /* reset status */ + mt76_wr(dev, MT_BBP(DFS, 1), 0xf); + mt76_wr(dev, MT_BBP(DFS, 36), 0x3); + + /* enable detection*/ + mt76_wr(dev, MT_BBP(DFS, 0), MT_DFS_CH_EN << 16); + mt76_wr(dev, 0x212c, 0x0c350001); +} + +void mt76x2_dfs_adjust_agc(struct mt76x2_dev *dev) +{ + u32 agc_r8, agc_r4, val_r8, val_r4, dfs_r31; + + agc_r8 = mt76_rr(dev, MT_BBP(AGC, 8)); + agc_r4 = mt76_rr(dev, MT_BBP(AGC, 4)); + + val_r8 = (agc_r8 & 0x00007e00) >> 9; + val_r4 = agc_r4 & ~0x1f000000; + val_r4 += (((val_r8 + 1) >> 1) << 24); + mt76_wr(dev, MT_BBP(AGC, 4), val_r4); + + dfs_r31 = FIELD_GET(MT_BBP_AGC_LNA_HIGH_GAIN, val_r4); + dfs_r31 += val_r8; + dfs_r31 -= (agc_r8 & 0x00000038) >> 3; + dfs_r31 = (dfs_r31 << 16) | 0x00000307; + mt76_wr(dev, MT_BBP(DFS, 31), dfs_r31); + + mt76_wr(dev, MT_BBP(DFS, 32), 0x00040071); +} + +void mt76x2_dfs_init_params(struct mt76x2_dev *dev) +{ + struct cfg80211_chan_def *chandef = &dev->mt76.chandef; + + if ((chandef->chan->flags & IEEE80211_CHAN_RADAR) && + dev->dfs_pd.region != NL80211_DFS_UNSET) { + mt76x2_dfs_set_bbp_params(dev); + /* enable debug mode */ + mt76x2_dfs_set_capture_mode_ctrl(dev, true); + + mt76x2_irq_enable(dev, MT_INT_GPTIMER); + mt76_rmw_field(dev, MT_INT_TIMER_EN, + MT_INT_TIMER_EN_GP_TIMER_EN, 1); + } else { + /* disable hw detector */ + mt76_wr(dev, MT_BBP(DFS, 0), 0); + /* clear detector status */ + mt76_wr(dev, MT_BBP(DFS, 1), 0xf); + mt76_wr(dev, 0x212c, 0); + + mt76x2_irq_disable(dev, MT_INT_GPTIMER); + mt76_rmw_field(dev, MT_INT_TIMER_EN, + MT_INT_TIMER_EN_GP_TIMER_EN, 0); + } +} + +void mt76x2_dfs_init_detector(struct mt76x2_dev *dev) +{ + struct mt76x2_dfs_pattern_detector *dfs_pd = &dev->dfs_pd; + + dfs_pd->region = NL80211_DFS_UNSET; + tasklet_init(&dfs_pd->dfs_tasklet, mt76x2_dfs_tasklet, + (unsigned long)dev); +} + +void mt76x2_dfs_set_domain(struct mt76x2_dev *dev, + enum nl80211_dfs_regions region) +{ + struct mt76x2_dfs_pattern_detector *dfs_pd = &dev->dfs_pd; + + if (dfs_pd->region != region) { + tasklet_disable(&dfs_pd->dfs_tasklet); + dfs_pd->region = region; + mt76x2_dfs_init_params(dev); + tasklet_enable(&dfs_pd->dfs_tasklet); + } +} + diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_dfs.h b/drivers/net/wireless/mediatek/mt76/mt76x2_dfs.h new file mode 100644 index 000000000000..8dbc783cc6bc --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_dfs.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef __MT76x2_DFS_H +#define __MT76x2_DFS_H + +#include <linux/types.h> +#include <linux/nl80211.h> + +#define MT_DFS_GP_INTERVAL (10 << 4) /* 64 us unit */ +#define MT_DFS_NUM_ENGINES 4 + +/* bbp params */ +#define MT_DFS_SYM_ROUND 0 +#define MT_DFS_DELTA_DELAY 2 +#define MT_DFS_VGA_MASK 0 +#define MT_DFS_PWR_GAIN_OFFSET 3 +#define MT_DFS_PWR_DOWN_TIME 0xf +#define MT_DFS_RX_PE_MASK 0xff +#define MT_DFS_PKT_END_MASK 0 +#define MT_DFS_CH_EN 0xf + +struct mt76x2_radar_specs { + u8 mode; + u16 avg_len; + u16 e_low; + u16 e_high; + u16 w_low; + u16 w_high; + u16 w_margin; + u32 t_low; + u32 t_high; + u16 t_margin; + u32 b_low; + u32 b_high; + u32 event_expiration; + u16 pwr_jmp; +}; + +struct mt76x2_dfs_hw_pulse { + u8 engine; + u32 period; + u32 w1; + u32 w2; + u32 burst; +}; + +struct mt76x2_dfs_engine_stats { + u32 hw_pattern; + u32 hw_pulse_discarded; +}; + +struct mt76x2_dfs_pattern_detector { + enum nl80211_dfs_regions region; + + u8 chirp_pulse_cnt; + u32 chirp_pulse_ts; + + struct mt76x2_dfs_engine_stats stats[MT_DFS_NUM_ENGINES]; + struct tasklet_struct dfs_tasklet; +}; + +void mt76x2_dfs_init_params(struct mt76x2_dev *dev); +void mt76x2_dfs_init_detector(struct mt76x2_dev *dev); +void mt76x2_dfs_adjust_agc(struct mt76x2_dev *dev); +void mt76x2_dfs_set_domain(struct mt76x2_dev *dev, + enum nl80211_dfs_regions region); + +#endif /* __MT76x2_DFS_H */ diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_dma.c b/drivers/net/wireless/mediatek/mt76/mt76x2_dma.c new file mode 100644 index 000000000000..fd1ec4743e0b --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_dma.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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 "mt76x2.h" +#include "mt76x2_dma.h" + +int +mt76x2_tx_queue_mcu(struct mt76x2_dev *dev, enum mt76_txq_id qid, + struct sk_buff *skb, int cmd, int seq) +{ + struct mt76_queue *q = &dev->mt76.q_tx[qid]; + struct mt76_queue_buf buf; + dma_addr_t addr; + u32 tx_info; + + tx_info = MT_MCU_MSG_TYPE_CMD | + FIELD_PREP(MT_MCU_MSG_CMD_TYPE, cmd) | + FIELD_PREP(MT_MCU_MSG_CMD_SEQ, seq) | + FIELD_PREP(MT_MCU_MSG_PORT, CPU_TX_PORT) | + FIELD_PREP(MT_MCU_MSG_LEN, skb->len); + + addr = dma_map_single(dev->mt76.dev, skb->data, skb->len, + DMA_TO_DEVICE); + if (dma_mapping_error(dev->mt76.dev, addr)) + return -ENOMEM; + + buf.addr = addr; + buf.len = skb->len; + spin_lock_bh(&q->lock); + mt76_queue_add_buf(dev, q, &buf, 1, tx_info, skb, NULL); + mt76_queue_kick(dev, q); + spin_unlock_bh(&q->lock); + + return 0; +} + +static int +mt76x2_init_tx_queue(struct mt76x2_dev *dev, struct mt76_queue *q, + int idx, int n_desc) +{ + int ret; + + q->regs = dev->mt76.regs + MT_TX_RING_BASE + idx * MT_RING_SIZE; + q->ndesc = n_desc; + q->hw_idx = idx; + + ret = mt76_queue_alloc(dev, q); + if (ret) + return ret; + + mt76x2_irq_enable(dev, MT_INT_TX_DONE(idx)); + + return 0; +} + +void mt76x2_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q, + struct sk_buff *skb) +{ + struct mt76x2_dev *dev = container_of(mdev, struct mt76x2_dev, mt76); + void *rxwi = skb->data; + + if (q == MT_RXQ_MCU) { + skb_queue_tail(&dev->mcu.res_q, skb); + wake_up(&dev->mcu.wait); + return; + } + + skb_pull(skb, sizeof(struct mt76x2_rxwi)); + if (mt76x2_mac_process_rx(dev, skb, rxwi)) { + dev_kfree_skb(skb); + return; + } + + mt76_rx(&dev->mt76, q, skb); +} + +static int +mt76x2_init_rx_queue(struct mt76x2_dev *dev, struct mt76_queue *q, + int idx, int n_desc, int bufsize) +{ + int ret; + + q->regs = dev->mt76.regs + MT_RX_RING_BASE + idx * MT_RING_SIZE; + q->ndesc = n_desc; + q->buf_size = bufsize; + + ret = mt76_queue_alloc(dev, q); + if (ret) + return ret; + + mt76x2_irq_enable(dev, MT_INT_RX_DONE(idx)); + + return 0; +} + +static void +mt76x2_tx_tasklet(unsigned long data) +{ + struct mt76x2_dev *dev = (struct mt76x2_dev *) data; + int i; + + mt76x2_mac_process_tx_status_fifo(dev); + + for (i = MT_TXQ_MCU; i >= 0; i--) + mt76_queue_tx_cleanup(dev, i, false); + + mt76x2_mac_poll_tx_status(dev, false); + mt76x2_irq_enable(dev, MT_INT_TX_DONE_ALL); +} + +int mt76x2_dma_init(struct mt76x2_dev *dev) +{ + static const u8 wmm_queue_map[] = { + [IEEE80211_AC_BE] = 0, + [IEEE80211_AC_BK] = 1, + [IEEE80211_AC_VI] = 2, + [IEEE80211_AC_VO] = 3, + }; + int ret; + int i; + struct mt76_txwi_cache __maybe_unused *t; + struct mt76_queue *q; + + BUILD_BUG_ON(sizeof(t->txwi) < sizeof(struct mt76x2_txwi)); + BUILD_BUG_ON(sizeof(struct mt76x2_rxwi) > MT_RX_HEADROOM); + + mt76_dma_attach(&dev->mt76); + + init_waitqueue_head(&dev->mcu.wait); + skb_queue_head_init(&dev->mcu.res_q); + + tasklet_init(&dev->tx_tasklet, mt76x2_tx_tasklet, (unsigned long) dev); + + mt76_wr(dev, MT_WPDMA_RST_IDX, ~0); + + for (i = 0; i < ARRAY_SIZE(wmm_queue_map); i++) { + ret = mt76x2_init_tx_queue(dev, &dev->mt76.q_tx[i], + wmm_queue_map[i], MT_TX_RING_SIZE); + if (ret) + return ret; + } + + ret = mt76x2_init_tx_queue(dev, &dev->mt76.q_tx[MT_TXQ_PSD], + MT_TX_HW_QUEUE_MGMT, MT_TX_RING_SIZE); + if (ret) + return ret; + + ret = mt76x2_init_tx_queue(dev, &dev->mt76.q_tx[MT_TXQ_MCU], + MT_TX_HW_QUEUE_MCU, MT_MCU_RING_SIZE); + if (ret) + return ret; + + ret = mt76x2_init_rx_queue(dev, &dev->mt76.q_rx[MT_RXQ_MCU], 1, + MT_MCU_RING_SIZE, MT_RX_BUF_SIZE); + if (ret) + return ret; + + q = &dev->mt76.q_rx[MT_RXQ_MAIN]; + q->buf_offset = MT_RX_HEADROOM - sizeof(struct mt76x2_rxwi); + ret = mt76x2_init_rx_queue(dev, q, 0, MT76x2_RX_RING_SIZE, MT_RX_BUF_SIZE); + if (ret) + return ret; + + return mt76_init_queues(dev); +} + +void mt76x2_dma_cleanup(struct mt76x2_dev *dev) +{ + tasklet_kill(&dev->tx_tasklet); + mt76_dma_cleanup(&dev->mt76); +} diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_dma.h b/drivers/net/wireless/mediatek/mt76/mt76x2_dma.h new file mode 100644 index 000000000000..47f79d83fcb4 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_dma.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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. + */ + +#ifndef __MT76x2_DMA_H +#define __MT76x2_DMA_H + +#include "dma.h" + +#define MT_TXD_INFO_LEN GENMASK(13, 0) +#define MT_TXD_INFO_NEXT_VLD BIT(16) +#define MT_TXD_INFO_TX_BURST BIT(17) +#define MT_TXD_INFO_80211 BIT(19) +#define MT_TXD_INFO_TSO BIT(20) +#define MT_TXD_INFO_CSO BIT(21) +#define MT_TXD_INFO_WIV BIT(24) +#define MT_TXD_INFO_QSEL GENMASK(26, 25) +#define MT_TXD_INFO_TCO BIT(29) +#define MT_TXD_INFO_UCO BIT(30) +#define MT_TXD_INFO_ICO BIT(31) + +#define MT_RX_FCE_INFO_LEN GENMASK(13, 0) +#define MT_RX_FCE_INFO_SELF_GEN BIT(15) +#define MT_RX_FCE_INFO_CMD_SEQ GENMASK(19, 16) +#define MT_RX_FCE_INFO_EVT_TYPE GENMASK(23, 20) +#define MT_RX_FCE_INFO_PCIE_INTR BIT(24) +#define MT_RX_FCE_INFO_QSEL GENMASK(26, 25) +#define MT_RX_FCE_INFO_D_PORT GENMASK(29, 27) +#define MT_RX_FCE_INFO_TYPE GENMASK(31, 30) + +/* MCU request message header */ +#define MT_MCU_MSG_LEN GENMASK(15, 0) +#define MT_MCU_MSG_CMD_SEQ GENMASK(19, 16) +#define MT_MCU_MSG_CMD_TYPE GENMASK(26, 20) +#define MT_MCU_MSG_PORT GENMASK(29, 27) +#define MT_MCU_MSG_TYPE GENMASK(31, 30) +#define MT_MCU_MSG_TYPE_CMD BIT(30) + +enum mt76x2_qsel { + MT_QSEL_MGMT, + MT_QSEL_HCCA, + MT_QSEL_EDCA, + MT_QSEL_EDCA_2, +}; + +enum dma_msg_port { + WLAN_PORT, + CPU_RX_PORT, + CPU_TX_PORT, + HOST_PORT, + VIRTUAL_CPU_RX_PORT, + VIRTUAL_CPU_TX_PORT, + DISCARD, +}; + +#endif diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_eeprom.c b/drivers/net/wireless/mediatek/mt76/mt76x2_eeprom.c new file mode 100644 index 000000000000..9c9bf3e785ba --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_eeprom.c @@ -0,0 +1,664 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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 <asm/unaligned.h> +#include "mt76x2.h" +#include "mt76x2_eeprom.h" + +#define EE_FIELD(_name, _value) [MT_EE_##_name] = (_value) | 1 + +static int +mt76x2_eeprom_copy(struct mt76x2_dev *dev, enum mt76x2_eeprom_field field, + void *dest, int len) +{ + if (field + len > dev->mt76.eeprom.size) + return -1; + + memcpy(dest, dev->mt76.eeprom.data + field, len); + return 0; +} + +static int +mt76x2_eeprom_get_macaddr(struct mt76x2_dev *dev) +{ + void *src = dev->mt76.eeprom.data + MT_EE_MAC_ADDR; + + memcpy(dev->mt76.macaddr, src, ETH_ALEN); + return 0; +} + +static void +mt76x2_eeprom_parse_hw_cap(struct mt76x2_dev *dev) +{ + u16 val = mt76x2_eeprom_get(dev, MT_EE_NIC_CONF_0); + + switch (FIELD_GET(MT_EE_NIC_CONF_0_BOARD_TYPE, val)) { + case BOARD_TYPE_5GHZ: + dev->mt76.cap.has_5ghz = true; + break; + case BOARD_TYPE_2GHZ: + dev->mt76.cap.has_2ghz = true; + break; + default: + dev->mt76.cap.has_2ghz = true; + dev->mt76.cap.has_5ghz = true; + break; + } +} + +static int +mt76x2_efuse_read(struct mt76x2_dev *dev, u16 addr, u8 *data) +{ + u32 val; + int i; + + val = mt76_rr(dev, MT_EFUSE_CTRL); + val &= ~(MT_EFUSE_CTRL_AIN | + MT_EFUSE_CTRL_MODE); + val |= FIELD_PREP(MT_EFUSE_CTRL_AIN, addr & ~0xf); + val |= MT_EFUSE_CTRL_KICK; + mt76_wr(dev, MT_EFUSE_CTRL, val); + + if (!mt76_poll(dev, MT_EFUSE_CTRL, MT_EFUSE_CTRL_KICK, 0, 1000)) + return -ETIMEDOUT; + + udelay(2); + + val = mt76_rr(dev, MT_EFUSE_CTRL); + if ((val & MT_EFUSE_CTRL_AOUT) == MT_EFUSE_CTRL_AOUT) { + memset(data, 0xff, 16); + return 0; + } + + for (i = 0; i < 4; i++) { + val = mt76_rr(dev, MT_EFUSE_DATA(i)); + put_unaligned_le32(val, data + 4 * i); + } + + return 0; +} + +static int +mt76x2_get_efuse_data(struct mt76x2_dev *dev, void *buf, int len) +{ + int ret, i; + + for (i = 0; i + 16 <= len; i += 16) { + ret = mt76x2_efuse_read(dev, i, buf + i); + if (ret) + return ret; + } + + return 0; +} + +static bool +mt76x2_has_cal_free_data(struct mt76x2_dev *dev, u8 *efuse) +{ + u16 *efuse_w = (u16 *) efuse; + + if (efuse_w[MT_EE_NIC_CONF_0] != 0) + return false; + + if (efuse_w[MT_EE_XTAL_TRIM_1] == 0xffff) + return false; + + if (efuse_w[MT_EE_TX_POWER_DELTA_BW40] != 0) + return false; + + if (efuse_w[MT_EE_TX_POWER_0_START_2G] == 0xffff) + return false; + + if (efuse_w[MT_EE_TX_POWER_0_GRP3_TX_POWER_DELTA] != 0) + return false; + + if (efuse_w[MT_EE_TX_POWER_0_GRP4_TSSI_SLOPE] == 0xffff) + return false; + + return true; +} + +static void +mt76x2_apply_cal_free_data(struct mt76x2_dev *dev, u8 *efuse) +{ +#define GROUP_5G(_id) \ + MT_EE_TX_POWER_0_START_5G + MT_TX_POWER_GROUP_SIZE_5G * (_id), \ + MT_EE_TX_POWER_0_START_5G + MT_TX_POWER_GROUP_SIZE_5G * (_id) + 1, \ + MT_EE_TX_POWER_1_START_5G + MT_TX_POWER_GROUP_SIZE_5G * (_id), \ + MT_EE_TX_POWER_1_START_5G + MT_TX_POWER_GROUP_SIZE_5G * (_id) + 1 + + static const u8 cal_free_bytes[] = { + MT_EE_XTAL_TRIM_1, + MT_EE_TX_POWER_EXT_PA_5G + 1, + MT_EE_TX_POWER_0_START_2G, + MT_EE_TX_POWER_0_START_2G + 1, + MT_EE_TX_POWER_1_START_2G, + MT_EE_TX_POWER_1_START_2G + 1, + GROUP_5G(0), + GROUP_5G(1), + GROUP_5G(2), + GROUP_5G(3), + GROUP_5G(4), + GROUP_5G(5), + MT_EE_RF_2G_TSSI_OFF_TXPOWER, + MT_EE_RF_2G_RX_HIGH_GAIN + 1, + MT_EE_RF_5G_GRP0_1_RX_HIGH_GAIN, + MT_EE_RF_5G_GRP0_1_RX_HIGH_GAIN + 1, + MT_EE_RF_5G_GRP2_3_RX_HIGH_GAIN, + MT_EE_RF_5G_GRP2_3_RX_HIGH_GAIN + 1, + MT_EE_RF_5G_GRP4_5_RX_HIGH_GAIN, + MT_EE_RF_5G_GRP4_5_RX_HIGH_GAIN + 1, + }; + u8 *eeprom = dev->mt76.eeprom.data; + u8 prev_grp0[4] = { + eeprom[MT_EE_TX_POWER_0_START_5G], + eeprom[MT_EE_TX_POWER_0_START_5G + 1], + eeprom[MT_EE_TX_POWER_1_START_5G], + eeprom[MT_EE_TX_POWER_1_START_5G + 1] + }; + u16 val; + int i; + + if (!mt76x2_has_cal_free_data(dev, efuse)) + return; + + for (i = 0; i < ARRAY_SIZE(cal_free_bytes); i++) { + int offset = cal_free_bytes[i]; + + eeprom[offset] = efuse[offset]; + } + + if (!(efuse[MT_EE_TX_POWER_0_START_5G] | + efuse[MT_EE_TX_POWER_0_START_5G + 1])) + memcpy(eeprom + MT_EE_TX_POWER_0_START_5G, prev_grp0, 2); + if (!(efuse[MT_EE_TX_POWER_1_START_5G] | + efuse[MT_EE_TX_POWER_1_START_5G + 1])) + memcpy(eeprom + MT_EE_TX_POWER_1_START_5G, prev_grp0 + 2, 2); + + val = get_unaligned_le16(efuse + MT_EE_BT_RCAL_RESULT); + if (val != 0xffff) + eeprom[MT_EE_BT_RCAL_RESULT] = val & 0xff; + + val = get_unaligned_le16(efuse + MT_EE_BT_VCDL_CALIBRATION); + if (val != 0xffff) + eeprom[MT_EE_BT_VCDL_CALIBRATION + 1] = val >> 8; + + val = get_unaligned_le16(efuse + MT_EE_BT_PMUCFG); + if (val != 0xffff) + eeprom[MT_EE_BT_PMUCFG] = val & 0xff; +} + +static int mt76x2_check_eeprom(struct mt76x2_dev *dev) +{ + u16 val = get_unaligned_le16(dev->mt76.eeprom.data); + + if (!val) + val = get_unaligned_le16(dev->mt76.eeprom.data + MT_EE_PCI_ID); + + switch (val) { + case 0x7662: + case 0x7612: + return 0; + default: + dev_err(dev->mt76.dev, "EEPROM data check failed: %04x\n", val); + return -EINVAL; + } +} + +static int +mt76x2_eeprom_load(struct mt76x2_dev *dev) +{ + void *efuse; + int len = MT7662_EEPROM_SIZE; + bool found; + int ret; + + ret = mt76_eeprom_init(&dev->mt76, len); + if (ret < 0) + return ret; + + found = ret; + if (found) + found = !mt76x2_check_eeprom(dev); + + dev->mt76.otp.data = devm_kzalloc(dev->mt76.dev, len, GFP_KERNEL); + dev->mt76.otp.size = len; + if (!dev->mt76.otp.data) + return -ENOMEM; + + efuse = dev->mt76.otp.data; + + if (mt76x2_get_efuse_data(dev, efuse, len)) + goto out; + + if (found) { + mt76x2_apply_cal_free_data(dev, efuse); + } else { + /* FIXME: check if efuse data is complete */ + found = true; + memcpy(dev->mt76.eeprom.data, efuse, len); + } + +out: + if (!found) + return -ENOENT; + + return 0; +} + +static inline int +mt76x2_sign_extend(u32 val, unsigned int size) +{ + bool sign = val & BIT(size - 1); + + val &= BIT(size - 1) - 1; + + return sign ? val : -val; +} + +static inline int +mt76x2_sign_extend_optional(u32 val, unsigned int size) +{ + bool enable = val & BIT(size); + + return enable ? mt76x2_sign_extend(val, size) : 0; +} + +static bool +field_valid(u8 val) +{ + return val != 0 && val != 0xff; +} + +static void +mt76x2_set_rx_gain_group(struct mt76x2_dev *dev, u8 val) +{ + s8 *dest = dev->cal.rx.high_gain; + + if (!field_valid(val)) { + dest[0] = 0; + dest[1] = 0; + return; + } + + dest[0] = mt76x2_sign_extend(val, 4); + dest[1] = mt76x2_sign_extend(val >> 4, 4); +} + +static void +mt76x2_set_rssi_offset(struct mt76x2_dev *dev, int chain, u8 val) +{ + s8 *dest = dev->cal.rx.rssi_offset; + + if (!field_valid(val)) { + dest[chain] = 0; + return; + } + + dest[chain] = mt76x2_sign_extend_optional(val, 7); +} + +static enum mt76x2_cal_channel_group +mt76x2_get_cal_channel_group(int channel) +{ + if (channel >= 184 && channel <= 196) + return MT_CH_5G_JAPAN; + if (channel <= 48) + return MT_CH_5G_UNII_1; + if (channel <= 64) + return MT_CH_5G_UNII_2; + if (channel <= 114) + return MT_CH_5G_UNII_2E_1; + if (channel <= 144) + return MT_CH_5G_UNII_2E_2; + return MT_CH_5G_UNII_3; +} + +static u8 +mt76x2_get_5g_rx_gain(struct mt76x2_dev *dev, u8 channel) +{ + enum mt76x2_cal_channel_group group; + + group = mt76x2_get_cal_channel_group(channel); + switch (group) { + case MT_CH_5G_JAPAN: + return mt76x2_eeprom_get(dev, MT_EE_RF_5G_GRP0_1_RX_HIGH_GAIN); + case MT_CH_5G_UNII_1: + return mt76x2_eeprom_get(dev, MT_EE_RF_5G_GRP0_1_RX_HIGH_GAIN) >> 8; + case MT_CH_5G_UNII_2: + return mt76x2_eeprom_get(dev, MT_EE_RF_5G_GRP2_3_RX_HIGH_GAIN); + case MT_CH_5G_UNII_2E_1: + return mt76x2_eeprom_get(dev, MT_EE_RF_5G_GRP2_3_RX_HIGH_GAIN) >> 8; + case MT_CH_5G_UNII_2E_2: + return mt76x2_eeprom_get(dev, MT_EE_RF_5G_GRP4_5_RX_HIGH_GAIN); + default: + return mt76x2_eeprom_get(dev, MT_EE_RF_5G_GRP4_5_RX_HIGH_GAIN) >> 8; + } +} + +void mt76x2_read_rx_gain(struct mt76x2_dev *dev) +{ + struct ieee80211_channel *chan = dev->mt76.chandef.chan; + int channel = chan->hw_value; + s8 lna_5g[3], lna_2g; + u8 lna; + u16 val; + + if (chan->band == NL80211_BAND_2GHZ) + val = mt76x2_eeprom_get(dev, MT_EE_RF_2G_RX_HIGH_GAIN) >> 8; + else + val = mt76x2_get_5g_rx_gain(dev, channel); + + mt76x2_set_rx_gain_group(dev, val); + + if (chan->band == NL80211_BAND_2GHZ) { + val = mt76x2_eeprom_get(dev, MT_EE_RSSI_OFFSET_2G_0); + mt76x2_set_rssi_offset(dev, 0, val); + mt76x2_set_rssi_offset(dev, 1, val >> 8); + } else { + val = mt76x2_eeprom_get(dev, MT_EE_RSSI_OFFSET_5G_0); + mt76x2_set_rssi_offset(dev, 0, val); + mt76x2_set_rssi_offset(dev, 1, val >> 8); + } + + val = mt76x2_eeprom_get(dev, MT_EE_LNA_GAIN); + lna_2g = val & 0xff; + lna_5g[0] = val >> 8; + + val = mt76x2_eeprom_get(dev, MT_EE_RSSI_OFFSET_2G_1); + lna_5g[1] = val >> 8; + + val = mt76x2_eeprom_get(dev, MT_EE_RSSI_OFFSET_5G_1); + lna_5g[2] = val >> 8; + + if (!field_valid(lna_5g[1])) + lna_5g[1] = lna_5g[0]; + + if (!field_valid(lna_5g[2])) + lna_5g[2] = lna_5g[0]; + + dev->cal.rx.mcu_gain = (lna_2g & 0xff); + dev->cal.rx.mcu_gain |= (lna_5g[0] & 0xff) << 8; + dev->cal.rx.mcu_gain |= (lna_5g[1] & 0xff) << 16; + dev->cal.rx.mcu_gain |= (lna_5g[2] & 0xff) << 24; + + val = mt76x2_eeprom_get(dev, MT_EE_NIC_CONF_1); + if (val & MT_EE_NIC_CONF_1_LNA_EXT_2G) + lna_2g = 0; + if (val & MT_EE_NIC_CONF_1_LNA_EXT_5G) + memset(lna_5g, 0, sizeof(lna_5g)); + + if (chan->band == NL80211_BAND_2GHZ) + lna = lna_2g; + else if (channel <= 64) + lna = lna_5g[0]; + else if (channel <= 128) + lna = lna_5g[1]; + else + lna = lna_5g[2]; + + if (lna == 0xff) + lna = 0; + + dev->cal.rx.lna_gain = mt76x2_sign_extend(lna, 8); +} + +static s8 +mt76x2_rate_power_val(u8 val) +{ + if (!field_valid(val)) + return 0; + + return mt76x2_sign_extend_optional(val, 7); +} + +void mt76x2_get_rate_power(struct mt76x2_dev *dev, struct mt76_rate_power *t, + struct ieee80211_channel *chan) +{ + bool is_5ghz; + u16 val; + + is_5ghz = chan->band == NL80211_BAND_5GHZ; + + memset(t, 0, sizeof(*t)); + + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_CCK); + t->cck[0] = t->cck[1] = mt76x2_rate_power_val(val); + t->cck[2] = t->cck[3] = mt76x2_rate_power_val(val >> 8); + + if (is_5ghz) + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_OFDM_5G_6M); + else + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_OFDM_2G_6M); + t->ofdm[0] = t->ofdm[1] = mt76x2_rate_power_val(val); + t->ofdm[2] = t->ofdm[3] = mt76x2_rate_power_val(val >> 8); + + if (is_5ghz) + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_OFDM_5G_24M); + else + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_OFDM_2G_24M); + t->ofdm[4] = t->ofdm[5] = mt76x2_rate_power_val(val); + t->ofdm[6] = t->ofdm[7] = mt76x2_rate_power_val(val >> 8); + + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_HT_MCS0); + t->ht[0] = t->ht[1] = mt76x2_rate_power_val(val); + t->ht[2] = t->ht[3] = mt76x2_rate_power_val(val >> 8); + + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_HT_MCS4); + t->ht[4] = t->ht[5] = mt76x2_rate_power_val(val); + t->ht[6] = t->ht[7] = mt76x2_rate_power_val(val >> 8); + + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_HT_MCS8); + t->ht[8] = t->ht[9] = mt76x2_rate_power_val(val); + t->ht[10] = t->ht[11] = mt76x2_rate_power_val(val >> 8); + + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_HT_MCS12); + t->ht[12] = t->ht[13] = mt76x2_rate_power_val(val); + t->ht[14] = t->ht[15] = mt76x2_rate_power_val(val >> 8); + + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_VHT_MCS0); + t->vht[0] = t->vht[1] = mt76x2_rate_power_val(val); + t->vht[2] = t->vht[3] = mt76x2_rate_power_val(val >> 8); + + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_VHT_MCS4); + t->vht[4] = t->vht[5] = mt76x2_rate_power_val(val); + t->vht[6] = t->vht[7] = mt76x2_rate_power_val(val >> 8); + + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_VHT_MCS8); + if (!is_5ghz) + val >>= 8; + t->vht[8] = t->vht[9] = mt76x2_rate_power_val(val >> 8); +} + +int mt76x2_get_max_rate_power(struct mt76_rate_power *r) +{ + int i; + s8 ret = 0; + + for (i = 0; i < sizeof(r->all); i++) + ret = max(ret, r->all[i]); + + return ret; +} + +static void +mt76x2_get_power_info_2g(struct mt76x2_dev *dev, struct mt76x2_tx_power_info *t, + struct ieee80211_channel *chan, int chain, int offset) +{ + int channel = chan->hw_value; + int delta_idx; + u8 data[6]; + u16 val; + + if (channel < 6) + delta_idx = 3; + else if (channel < 11) + delta_idx = 4; + else + delta_idx = 5; + + mt76x2_eeprom_copy(dev, offset, data, sizeof(data)); + + t->chain[chain].tssi_slope = data[0]; + t->chain[chain].tssi_offset = data[1]; + t->chain[chain].target_power = data[2]; + t->chain[chain].delta = mt76x2_sign_extend_optional(data[delta_idx], 7); + + val = mt76x2_eeprom_get(dev, MT_EE_RF_2G_TSSI_OFF_TXPOWER); + t->target_power = val >> 8; +} + +static void +mt76x2_get_power_info_5g(struct mt76x2_dev *dev, struct mt76x2_tx_power_info *t, + struct ieee80211_channel *chan, int chain, int offset) +{ + int channel = chan->hw_value; + enum mt76x2_cal_channel_group group; + int delta_idx; + u16 val; + u8 data[5]; + + group = mt76x2_get_cal_channel_group(channel); + offset += group * MT_TX_POWER_GROUP_SIZE_5G; + + if (channel >= 192) + delta_idx = 4; + else if (channel >= 184) + delta_idx = 3; + else if (channel < 44) + delta_idx = 3; + else if (channel < 52) + delta_idx = 4; + else if (channel < 58) + delta_idx = 3; + else if (channel < 98) + delta_idx = 4; + else if (channel < 106) + delta_idx = 3; + else if (channel < 116) + delta_idx = 4; + else if (channel < 130) + delta_idx = 3; + else if (channel < 149) + delta_idx = 4; + else if (channel < 157) + delta_idx = 3; + else + delta_idx = 4; + + mt76x2_eeprom_copy(dev, offset, data, sizeof(data)); + + t->chain[chain].tssi_slope = data[0]; + t->chain[chain].tssi_offset = data[1]; + t->chain[chain].target_power = data[2]; + t->chain[chain].delta = mt76x2_sign_extend_optional(data[delta_idx], 7); + + val = mt76x2_eeprom_get(dev, MT_EE_RF_2G_RX_HIGH_GAIN); + t->target_power = val & 0xff; +} + +void mt76x2_get_power_info(struct mt76x2_dev *dev, + struct mt76x2_tx_power_info *t, + struct ieee80211_channel *chan) +{ + u16 bw40, bw80; + + memset(t, 0, sizeof(*t)); + + bw40 = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_DELTA_BW40); + bw80 = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_DELTA_BW80); + + if (chan->band == NL80211_BAND_5GHZ) { + bw40 >>= 8; + mt76x2_get_power_info_5g(dev, t, chan, 0, + MT_EE_TX_POWER_0_START_5G); + mt76x2_get_power_info_5g(dev, t, chan, 1, + MT_EE_TX_POWER_1_START_5G); + } else { + mt76x2_get_power_info_2g(dev, t, chan, 0, + MT_EE_TX_POWER_0_START_2G); + mt76x2_get_power_info_2g(dev, t, chan, 1, + MT_EE_TX_POWER_1_START_2G); + } + + if (mt76x2_tssi_enabled(dev) || !field_valid(t->target_power)) + t->target_power = t->chain[0].target_power; + + t->delta_bw40 = mt76x2_rate_power_val(bw40); + t->delta_bw80 = mt76x2_rate_power_val(bw80); +} + +int mt76x2_get_temp_comp(struct mt76x2_dev *dev, struct mt76x2_temp_comp *t) +{ + enum nl80211_band band = dev->mt76.chandef.chan->band; + u16 val, slope; + u8 bounds; + + memset(t, 0, sizeof(*t)); + + val = mt76x2_eeprom_get(dev, MT_EE_NIC_CONF_1); + if (!(val & MT_EE_NIC_CONF_1_TEMP_TX_ALC)) + return -EINVAL; + + if (!mt76x2_ext_pa_enabled(dev, band)) + return -EINVAL; + + val = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_EXT_PA_5G) >> 8; + if (!(val & BIT(7))) + return -EINVAL; + + t->temp_25_ref = val & 0x7f; + if (band == NL80211_BAND_5GHZ) { + slope = mt76x2_eeprom_get(dev, MT_EE_RF_TEMP_COMP_SLOPE_5G); + bounds = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_EXT_PA_5G); + } else { + slope = mt76x2_eeprom_get(dev, MT_EE_RF_TEMP_COMP_SLOPE_2G); + bounds = mt76x2_eeprom_get(dev, MT_EE_TX_POWER_DELTA_BW80) >> 8; + } + + t->high_slope = slope & 0xff; + t->low_slope = slope >> 8; + t->lower_bound = 0 - (bounds & 0xf); + t->upper_bound = (bounds >> 4) & 0xf; + + return 0; +} + +bool mt76x2_ext_pa_enabled(struct mt76x2_dev *dev, enum nl80211_band band) +{ + u16 conf0 = mt76x2_eeprom_get(dev, MT_EE_NIC_CONF_0); + + if (band == NL80211_BAND_5GHZ) + return !(conf0 & MT_EE_NIC_CONF_0_PA_INT_5G); + else + return !(conf0 & MT_EE_NIC_CONF_0_PA_INT_2G); +} + +int mt76x2_eeprom_init(struct mt76x2_dev *dev) +{ + int ret; + + ret = mt76x2_eeprom_load(dev); + if (ret) + return ret; + + mt76x2_eeprom_parse_hw_cap(dev); + mt76x2_eeprom_get_macaddr(dev); + mt76_eeprom_override(&dev->mt76); + dev->mt76.macaddr[0] &= ~BIT(1); + + return 0; +} diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_eeprom.h b/drivers/net/wireless/mediatek/mt76/mt76x2_eeprom.h new file mode 100644 index 000000000000..d79122728dca --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_eeprom.h @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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. + */ + +#ifndef __MT76x2_EEPROM_H +#define __MT76x2_EEPROM_H + +#include "mt76x2.h" + +enum mt76x2_eeprom_field { + MT_EE_CHIP_ID = 0x000, + MT_EE_VERSION = 0x002, + MT_EE_MAC_ADDR = 0x004, + MT_EE_PCI_ID = 0x00A, + MT_EE_NIC_CONF_0 = 0x034, + MT_EE_NIC_CONF_1 = 0x036, + MT_EE_NIC_CONF_2 = 0x042, + + MT_EE_XTAL_TRIM_1 = 0x03a, + MT_EE_XTAL_TRIM_2 = 0x09e, + + MT_EE_LNA_GAIN = 0x044, + MT_EE_RSSI_OFFSET_2G_0 = 0x046, + MT_EE_RSSI_OFFSET_2G_1 = 0x048, + MT_EE_RSSI_OFFSET_5G_0 = 0x04a, + MT_EE_RSSI_OFFSET_5G_1 = 0x04c, + + MT_EE_TX_POWER_DELTA_BW40 = 0x050, + MT_EE_TX_POWER_DELTA_BW80 = 0x052, + + MT_EE_TX_POWER_EXT_PA_5G = 0x054, + + MT_EE_TX_POWER_0_START_2G = 0x056, + MT_EE_TX_POWER_1_START_2G = 0x05c, + + /* used as byte arrays */ +#define MT_TX_POWER_GROUP_SIZE_5G 5 +#define MT_TX_POWER_GROUPS_5G 6 + MT_EE_TX_POWER_0_START_5G = 0x062, + + MT_EE_TX_POWER_0_GRP3_TX_POWER_DELTA = 0x074, + MT_EE_TX_POWER_0_GRP4_TSSI_SLOPE = 0x076, + + MT_EE_TX_POWER_1_START_5G = 0x080, + + MT_EE_TX_POWER_CCK = 0x0a0, + MT_EE_TX_POWER_OFDM_2G_6M = 0x0a2, + MT_EE_TX_POWER_OFDM_2G_24M = 0x0a4, + MT_EE_TX_POWER_OFDM_5G_6M = 0x0b2, + MT_EE_TX_POWER_OFDM_5G_24M = 0x0b4, + MT_EE_TX_POWER_HT_MCS0 = 0x0a6, + MT_EE_TX_POWER_HT_MCS4 = 0x0a8, + MT_EE_TX_POWER_HT_MCS8 = 0x0aa, + MT_EE_TX_POWER_HT_MCS12 = 0x0ac, + MT_EE_TX_POWER_VHT_MCS0 = 0x0ba, + MT_EE_TX_POWER_VHT_MCS4 = 0x0bc, + MT_EE_TX_POWER_VHT_MCS8 = 0x0be, + + MT_EE_RF_TEMP_COMP_SLOPE_5G = 0x0f2, + MT_EE_RF_TEMP_COMP_SLOPE_2G = 0x0f4, + + MT_EE_RF_2G_TSSI_OFF_TXPOWER = 0x0f6, + MT_EE_RF_2G_RX_HIGH_GAIN = 0x0f8, + MT_EE_RF_5G_GRP0_1_RX_HIGH_GAIN = 0x0fa, + MT_EE_RF_5G_GRP2_3_RX_HIGH_GAIN = 0x0fc, + MT_EE_RF_5G_GRP4_5_RX_HIGH_GAIN = 0x0fe, + + MT_EE_BT_RCAL_RESULT = 0x138, + MT_EE_BT_VCDL_CALIBRATION = 0x13c, + MT_EE_BT_PMUCFG = 0x13e, + + __MT_EE_MAX +}; + +#define MT_EE_NIC_CONF_0_PA_INT_2G BIT(8) +#define MT_EE_NIC_CONF_0_PA_INT_5G BIT(9) +#define MT_EE_NIC_CONF_0_BOARD_TYPE GENMASK(13, 12) + +#define MT_EE_NIC_CONF_1_TEMP_TX_ALC BIT(1) +#define MT_EE_NIC_CONF_1_LNA_EXT_2G BIT(2) +#define MT_EE_NIC_CONF_1_LNA_EXT_5G BIT(3) +#define MT_EE_NIC_CONF_1_TX_ALC_EN BIT(13) + +#define MT_EE_NIC_CONF_2_RX_STREAM GENMASK(3, 0) +#define MT_EE_NIC_CONF_2_TX_STREAM GENMASK(7, 4) +#define MT_EE_NIC_CONF_2_HW_ANTDIV BIT(8) +#define MT_EE_NIC_CONF_2_XTAL_OPTION GENMASK(10, 9) +#define MT_EE_NIC_CONF_2_TEMP_DISABLE BIT(11) +#define MT_EE_NIC_CONF_2_COEX_METHOD GENMASK(15, 13) + +enum mt76x2_board_type { + BOARD_TYPE_2GHZ = 1, + BOARD_TYPE_5GHZ = 2, +}; + +enum mt76x2_cal_channel_group { + MT_CH_5G_JAPAN, + MT_CH_5G_UNII_1, + MT_CH_5G_UNII_2, + MT_CH_5G_UNII_2E_1, + MT_CH_5G_UNII_2E_2, + MT_CH_5G_UNII_3, + __MT_CH_MAX +}; + +struct mt76x2_tx_power_info { + u8 target_power; + + s8 delta_bw40; + s8 delta_bw80; + + struct { + s8 tssi_slope; + s8 tssi_offset; + s8 target_power; + s8 delta; + } chain[MT_MAX_CHAINS]; +}; + +struct mt76x2_temp_comp { + u8 temp_25_ref; + int lower_bound; /* J */ + int upper_bound; /* J */ + unsigned int high_slope; /* J / dB */ + unsigned int low_slope; /* J / dB */ +}; + +static inline int +mt76x2_eeprom_get(struct mt76x2_dev *dev, enum mt76x2_eeprom_field field) +{ + if ((field & 1) || field >= __MT_EE_MAX) + return -1; + + return get_unaligned_le16(dev->mt76.eeprom.data + field); +} + +void mt76x2_get_rate_power(struct mt76x2_dev *dev, struct mt76_rate_power *t, + struct ieee80211_channel *chan); +int mt76x2_get_max_rate_power(struct mt76_rate_power *r); +void mt76x2_get_power_info(struct mt76x2_dev *dev, + struct mt76x2_tx_power_info *t, + struct ieee80211_channel *chan); +int mt76x2_get_temp_comp(struct mt76x2_dev *dev, struct mt76x2_temp_comp *t); +bool mt76x2_ext_pa_enabled(struct mt76x2_dev *dev, enum nl80211_band band); +void mt76x2_read_rx_gain(struct mt76x2_dev *dev); + +static inline bool +mt76x2_temp_tx_alc_enabled(struct mt76x2_dev *dev) +{ + return mt76x2_eeprom_get(dev, MT_EE_NIC_CONF_1) & + MT_EE_NIC_CONF_1_TEMP_TX_ALC; +} + +static inline bool +mt76x2_tssi_enabled(struct mt76x2_dev *dev) +{ + return !mt76x2_temp_tx_alc_enabled(dev) && + (mt76x2_eeprom_get(dev, MT_EE_NIC_CONF_1) & + MT_EE_NIC_CONF_1_TX_ALC_EN); +} + +static inline bool +mt76x2_has_ext_lna(struct mt76x2_dev *dev) +{ + u32 val = mt76x2_eeprom_get(dev, MT_EE_NIC_CONF_1); + + if (dev->mt76.chandef.chan->band == NL80211_BAND_2GHZ) + return val & MT_EE_NIC_CONF_1_LNA_EXT_2G; + else + return val & MT_EE_NIC_CONF_1_LNA_EXT_5G; +} + +#endif diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_init.c b/drivers/net/wireless/mediatek/mt76/mt76x2_init.c new file mode 100644 index 000000000000..1b00ae4465a2 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_init.c @@ -0,0 +1,875 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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/delay.h> +#include "mt76x2.h" +#include "mt76x2_eeprom.h" +#include "mt76x2_mcu.h" + +struct mt76x2_reg_pair { + u32 reg; + u32 value; +}; + +static bool +mt76x2_wait_for_mac(struct mt76x2_dev *dev) +{ + int i; + + for (i = 0; i < 500; i++) { + switch (mt76_rr(dev, MT_MAC_CSR0)) { + case 0: + case ~0: + break; + default: + return true; + } + usleep_range(5000, 10000); + } + + return false; +} + +static bool +wait_for_wpdma(struct mt76x2_dev *dev) +{ + return mt76_poll(dev, MT_WPDMA_GLO_CFG, + MT_WPDMA_GLO_CFG_TX_DMA_BUSY | + MT_WPDMA_GLO_CFG_RX_DMA_BUSY, + 0, 1000); +} + +static void +mt76x2_mac_pbf_init(struct mt76x2_dev *dev) +{ + u32 val; + + val = MT_PBF_SYS_CTRL_MCU_RESET | + MT_PBF_SYS_CTRL_DMA_RESET | + MT_PBF_SYS_CTRL_MAC_RESET | + MT_PBF_SYS_CTRL_PBF_RESET | + MT_PBF_SYS_CTRL_ASY_RESET; + + mt76_set(dev, MT_PBF_SYS_CTRL, val); + mt76_clear(dev, MT_PBF_SYS_CTRL, val); + + mt76_wr(dev, MT_PBF_TX_MAX_PCNT, 0xefef3f1f); + mt76_wr(dev, MT_PBF_RX_MAX_PCNT, 0xfebf); +} + +static void +mt76x2_write_reg_pairs(struct mt76x2_dev *dev, + const struct mt76x2_reg_pair *data, int len) +{ + while (len > 0) { + mt76_wr(dev, data->reg, data->value); + len--; + data++; + } +} + +static void +mt76_write_mac_initvals(struct mt76x2_dev *dev) +{ +#define DEFAULT_PROT_CFG \ + (FIELD_PREP(MT_PROT_CFG_RATE, 0x2004) | \ + FIELD_PREP(MT_PROT_CFG_NAV, 1) | \ + FIELD_PREP(MT_PROT_CFG_TXOP_ALLOW, 0x3f) | \ + MT_PROT_CFG_RTS_THRESH) + +#define DEFAULT_PROT_CFG_20 \ + (FIELD_PREP(MT_PROT_CFG_RATE, 0x2004) | \ + FIELD_PREP(MT_PROT_CFG_CTRL, 1) | \ + FIELD_PREP(MT_PROT_CFG_NAV, 1) | \ + FIELD_PREP(MT_PROT_CFG_TXOP_ALLOW, 0x17)) + +#define DEFAULT_PROT_CFG_40 \ + (FIELD_PREP(MT_PROT_CFG_RATE, 0x2084) | \ + FIELD_PREP(MT_PROT_CFG_CTRL, 1) | \ + FIELD_PREP(MT_PROT_CFG_NAV, 1) | \ + FIELD_PREP(MT_PROT_CFG_TXOP_ALLOW, 0x3f)) + + static const struct mt76x2_reg_pair vals[] = { + /* Copied from MediaTek reference source */ + { MT_PBF_SYS_CTRL, 0x00080c00 }, + { MT_PBF_CFG, 0x1efebcff }, + { MT_FCE_PSE_CTRL, 0x00000001 }, + { MT_MAC_SYS_CTRL, 0x0000000c }, + { MT_MAX_LEN_CFG, 0x003e3f00 }, + { MT_AMPDU_MAX_LEN_20M1S, 0xaaa99887 }, + { MT_AMPDU_MAX_LEN_20M2S, 0x000000aa }, + { MT_XIFS_TIME_CFG, 0x33a40d0a }, + { MT_BKOFF_SLOT_CFG, 0x00000209 }, + { MT_TBTT_SYNC_CFG, 0x00422010 }, + { MT_PWR_PIN_CFG, 0x00000000 }, + { 0x1238, 0x001700c8 }, + { MT_TX_SW_CFG0, 0x00101001 }, + { MT_TX_SW_CFG1, 0x00010000 }, + { MT_TX_SW_CFG2, 0x00000000 }, + { MT_TXOP_CTRL_CFG, 0x0400583f }, + { MT_TX_RTS_CFG, 0x00100020 }, + { MT_TX_TIMEOUT_CFG, 0x000a2290 }, + { MT_TX_RETRY_CFG, 0x47f01f0f }, + { MT_EXP_ACK_TIME, 0x002c00dc }, + { MT_TX_PROT_CFG6, 0xe3f42004 }, + { MT_TX_PROT_CFG7, 0xe3f42084 }, + { MT_TX_PROT_CFG8, 0xe3f42104 }, + { MT_PIFS_TX_CFG, 0x00060fff }, + { MT_RX_FILTR_CFG, 0x00015f97 }, + { MT_LEGACY_BASIC_RATE, 0x0000017f }, + { MT_HT_BASIC_RATE, 0x00004003 }, + { MT_PN_PAD_MODE, 0x00000003 }, + { MT_TXOP_HLDR_ET, 0x00000002 }, + { 0xa44, 0x00000000 }, + { MT_HEADER_TRANS_CTRL_REG, 0x00000000 }, + { MT_TSO_CTRL, 0x00000000 }, + { MT_AUX_CLK_CFG, 0x00000000 }, + { MT_DACCLK_EN_DLY_CFG, 0x00000000 }, + { MT_TX_ALC_CFG_4, 0x00000000 }, + { MT_TX_ALC_VGA3, 0x00000000 }, + { MT_TX_PWR_CFG_0, 0x3a3a3a3a }, + { MT_TX_PWR_CFG_1, 0x3a3a3a3a }, + { MT_TX_PWR_CFG_2, 0x3a3a3a3a }, + { MT_TX_PWR_CFG_3, 0x3a3a3a3a }, + { MT_TX_PWR_CFG_4, 0x3a3a3a3a }, + { MT_TX_PWR_CFG_7, 0x3a3a3a3a }, + { MT_TX_PWR_CFG_8, 0x0000003a }, + { MT_TX_PWR_CFG_9, 0x0000003a }, + { MT_EFUSE_CTRL, 0x0000d000 }, + { MT_PAUSE_ENABLE_CONTROL1, 0x0000000a }, + { MT_FCE_WLAN_FLOW_CONTROL1, 0x60401c18 }, + { MT_WPDMA_DELAY_INT_CFG, 0x94ff0000 }, + { MT_TX_SW_CFG3, 0x00000004 }, + { MT_HT_FBK_TO_LEGACY, 0x00001818 }, + { MT_VHT_HT_FBK_CFG1, 0xedcba980 }, + { MT_PROT_AUTO_TX_CFG, 0x00830083 }, + { MT_HT_CTRL_CFG, 0x000001ff }, + }; + struct mt76x2_reg_pair prot_vals[] = { + { MT_CCK_PROT_CFG, DEFAULT_PROT_CFG }, + { MT_OFDM_PROT_CFG, DEFAULT_PROT_CFG }, + { MT_MM20_PROT_CFG, DEFAULT_PROT_CFG_20 }, + { MT_MM40_PROT_CFG, DEFAULT_PROT_CFG_40 }, + { MT_GF20_PROT_CFG, DEFAULT_PROT_CFG_20 }, + { MT_GF40_PROT_CFG, DEFAULT_PROT_CFG_40 }, + }; + + mt76x2_write_reg_pairs(dev, vals, ARRAY_SIZE(vals)); + mt76x2_write_reg_pairs(dev, prot_vals, ARRAY_SIZE(prot_vals)); +} + +static void +mt76x2_fixup_xtal(struct mt76x2_dev *dev) +{ + u16 eep_val; + s8 offset = 0; + + eep_val = mt76x2_eeprom_get(dev, MT_EE_XTAL_TRIM_2); + + offset = eep_val & 0x7f; + if ((eep_val & 0xff) == 0xff) + offset = 0; + else if (eep_val & 0x80) + offset = 0 - offset; + + eep_val >>= 8; + if (eep_val == 0x00 || eep_val == 0xff) { + eep_val = mt76x2_eeprom_get(dev, MT_EE_XTAL_TRIM_1); + eep_val &= 0xff; + + if (eep_val == 0x00 || eep_val == 0xff) + eep_val = 0x14; + } + + eep_val &= 0x7f; + mt76_rmw_field(dev, MT_XO_CTRL5, MT_XO_CTRL5_C2_VAL, eep_val + offset); + mt76_set(dev, MT_XO_CTRL6, MT_XO_CTRL6_C2_CTRL); + + eep_val = mt76x2_eeprom_get(dev, MT_EE_NIC_CONF_2); + switch (FIELD_GET(MT_EE_NIC_CONF_2_XTAL_OPTION, eep_val)) { + case 0: + mt76_wr(dev, MT_XO_CTRL7, 0x5c1fee80); + break; + case 1: + mt76_wr(dev, MT_XO_CTRL7, 0x5c1feed0); + break; + default: + break; + } +} + +static void +mt76x2_init_beacon_offsets(struct mt76x2_dev *dev) +{ + u16 base = MT_BEACON_BASE; + u32 regs[4] = {}; + int i; + + for (i = 0; i < 16; i++) { + u16 addr = dev->beacon_offsets[i]; + + regs[i / 4] |= ((addr - base) / 64) << (8 * (i % 4)); + } + + for (i = 0; i < 4; i++) + mt76_wr(dev, MT_BCN_OFFSET(i), regs[i]); +} + +int mt76x2_mac_reset(struct mt76x2_dev *dev, bool hard) +{ + static const u8 null_addr[ETH_ALEN] = {}; + const u8 *macaddr = dev->mt76.macaddr; + u32 val; + int i, k; + + if (!mt76x2_wait_for_mac(dev)) + return -ETIMEDOUT; + + val = mt76_rr(dev, MT_WPDMA_GLO_CFG); + + val &= ~(MT_WPDMA_GLO_CFG_TX_DMA_EN | + MT_WPDMA_GLO_CFG_TX_DMA_BUSY | + MT_WPDMA_GLO_CFG_RX_DMA_EN | + MT_WPDMA_GLO_CFG_RX_DMA_BUSY | + MT_WPDMA_GLO_CFG_DMA_BURST_SIZE); + val |= FIELD_PREP(MT_WPDMA_GLO_CFG_DMA_BURST_SIZE, 3); + + mt76_wr(dev, MT_WPDMA_GLO_CFG, val); + + mt76x2_mac_pbf_init(dev); + mt76_write_mac_initvals(dev); + mt76x2_fixup_xtal(dev); + + mt76_clear(dev, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_RESET_CSR | + MT_MAC_SYS_CTRL_RESET_BBP); + + if (is_mt7612(dev)) + mt76_clear(dev, MT_COEXCFG0, MT_COEXCFG0_COEX_EN); + + mt76_set(dev, MT_EXT_CCA_CFG, 0x0000f000); + mt76_clear(dev, MT_TX_ALC_CFG_4, BIT(31)); + + mt76_wr(dev, MT_RF_BYPASS_0, 0x06000000); + mt76_wr(dev, MT_RF_SETTING_0, 0x08800000); + usleep_range(5000, 10000); + mt76_wr(dev, MT_RF_BYPASS_0, 0x00000000); + + mt76_wr(dev, MT_MCU_CLOCK_CTL, 0x1401); + mt76_clear(dev, MT_FCE_L2_STUFF, MT_FCE_L2_STUFF_WR_MPDU_LEN_EN); + + mt76_wr(dev, MT_MAC_ADDR_DW0, get_unaligned_le32(macaddr)); + mt76_wr(dev, MT_MAC_ADDR_DW1, get_unaligned_le16(macaddr + 4)); + + mt76_wr(dev, MT_MAC_BSSID_DW0, get_unaligned_le32(macaddr)); + mt76_wr(dev, MT_MAC_BSSID_DW1, get_unaligned_le16(macaddr + 4) | + FIELD_PREP(MT_MAC_BSSID_DW1_MBSS_MODE, 3) | /* 8 beacons */ + MT_MAC_BSSID_DW1_MBSS_LOCAL_BIT); + + /* Fire a pre-TBTT interrupt 8 ms before TBTT */ + mt76_rmw_field(dev, MT_INT_TIMER_CFG, MT_INT_TIMER_CFG_PRE_TBTT, + 8 << 4); + mt76_rmw_field(dev, MT_INT_TIMER_CFG, MT_INT_TIMER_CFG_GP_TIMER, + MT_DFS_GP_INTERVAL); + mt76_wr(dev, MT_INT_TIMER_EN, 0); + + mt76_wr(dev, MT_BCN_BYPASS_MASK, 0xffff); + if (!hard) + return 0; + + for (i = 0; i < 256 / 32; i++) + mt76_wr(dev, MT_WCID_DROP_BASE + i * 4, 0); + + for (i = 0; i < 256; i++) + mt76x2_mac_wcid_setup(dev, i, 0, NULL); + + for (i = 0; i < 16; i++) + for (k = 0; k < 4; k++) + mt76x2_mac_shared_key_setup(dev, i, k, NULL); + + for (i = 0; i < 8; i++) { + mt76x2_mac_set_bssid(dev, i, null_addr); + mt76x2_mac_set_beacon(dev, i, NULL); + } + + for (i = 0; i < 16; i++) + mt76_rr(dev, MT_TX_STAT_FIFO); + + mt76_wr(dev, MT_CH_TIME_CFG, + MT_CH_TIME_CFG_TIMER_EN | + MT_CH_TIME_CFG_TX_AS_BUSY | + MT_CH_TIME_CFG_RX_AS_BUSY | + MT_CH_TIME_CFG_NAV_AS_BUSY | + MT_CH_TIME_CFG_EIFS_AS_BUSY | + FIELD_PREP(MT_CH_TIME_CFG_CH_TIMER_CLR, 1)); + + mt76x2_init_beacon_offsets(dev); + + mt76x2_set_tx_ackto(dev); + + return 0; +} + +int mt76x2_mac_start(struct mt76x2_dev *dev) +{ + int i; + + for (i = 0; i < 16; i++) + mt76_rr(dev, MT_TX_AGG_CNT(i)); + + for (i = 0; i < 16; i++) + mt76_rr(dev, MT_TX_STAT_FIFO); + + memset(dev->aggr_stats, 0, sizeof(dev->aggr_stats)); + + mt76_wr(dev, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_ENABLE_TX); + wait_for_wpdma(dev); + usleep_range(50, 100); + + mt76_set(dev, MT_WPDMA_GLO_CFG, + MT_WPDMA_GLO_CFG_TX_DMA_EN | + MT_WPDMA_GLO_CFG_RX_DMA_EN); + + mt76_clear(dev, MT_WPDMA_GLO_CFG, MT_WPDMA_GLO_CFG_TX_WRITEBACK_DONE); + + mt76_wr(dev, MT_RX_FILTR_CFG, dev->rxfilter); + + mt76_wr(dev, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_ENABLE_TX | + MT_MAC_SYS_CTRL_ENABLE_RX); + + mt76x2_irq_enable(dev, MT_INT_RX_DONE_ALL | MT_INT_TX_DONE_ALL | + MT_INT_TX_STAT); + + return 0; +} + +void mt76x2_mac_stop(struct mt76x2_dev *dev, bool force) +{ + bool stopped = false; + u32 rts_cfg; + int i; + + mt76_wr(dev, MT_MAC_SYS_CTRL, 0); + + rts_cfg = mt76_rr(dev, MT_TX_RTS_CFG); + mt76_wr(dev, MT_TX_RTS_CFG, rts_cfg & ~MT_TX_RTS_CFG_RETRY_LIMIT); + + /* Wait for MAC to become idle */ + for (i = 0; i < 300; i++) { + if (mt76_rr(dev, MT_MAC_STATUS) & + (MT_MAC_STATUS_RX | MT_MAC_STATUS_TX)) + continue; + + if (mt76_rr(dev, MT_BBP(IBI, 12))) + continue; + + stopped = true; + break; + } + + if (force && !stopped) { + mt76_set(dev, MT_BBP(CORE, 4), BIT(1)); + mt76_clear(dev, MT_BBP(CORE, 4), BIT(1)); + + mt76_set(dev, MT_BBP(CORE, 4), BIT(0)); + mt76_clear(dev, MT_BBP(CORE, 4), BIT(0)); + } + + mt76_wr(dev, MT_TX_RTS_CFG, rts_cfg); +} + +void mt76x2_mac_resume(struct mt76x2_dev *dev) +{ + mt76_wr(dev, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_ENABLE_TX | + MT_MAC_SYS_CTRL_ENABLE_RX); +} + +static void +mt76x2_power_on_rf_patch(struct mt76x2_dev *dev) +{ + mt76_set(dev, 0x10130, BIT(0) | BIT(16)); + udelay(1); + + mt76_clear(dev, 0x1001c, 0xff); + mt76_set(dev, 0x1001c, 0x30); + + mt76_wr(dev, 0x10014, 0x484f); + udelay(1); + + mt76_set(dev, 0x10130, BIT(17)); + udelay(125); + + mt76_clear(dev, 0x10130, BIT(16)); + udelay(50); + + mt76_set(dev, 0x1014c, BIT(19) | BIT(20)); +} + +static void +mt76x2_power_on_rf(struct mt76x2_dev *dev, int unit) +{ + int shift = unit ? 8 : 0; + + /* Enable RF BG */ + mt76_set(dev, 0x10130, BIT(0) << shift); + udelay(10); + + /* Enable RFDIG LDO/AFE/ABB/ADDA */ + mt76_set(dev, 0x10130, (BIT(1) | BIT(3) | BIT(4) | BIT(5)) << shift); + udelay(10); + + /* Switch RFDIG power to internal LDO */ + mt76_clear(dev, 0x10130, BIT(2) << shift); + udelay(10); + + mt76x2_power_on_rf_patch(dev); + + mt76_set(dev, 0x530, 0xf); +} + +static void +mt76x2_power_on(struct mt76x2_dev *dev) +{ + u32 val; + + /* Turn on WL MTCMOS */ + mt76_set(dev, MT_WLAN_MTC_CTRL, MT_WLAN_MTC_CTRL_MTCMOS_PWR_UP); + + val = MT_WLAN_MTC_CTRL_STATE_UP | + MT_WLAN_MTC_CTRL_PWR_ACK | + MT_WLAN_MTC_CTRL_PWR_ACK_S; + + mt76_poll(dev, MT_WLAN_MTC_CTRL, val, val, 1000); + + mt76_clear(dev, MT_WLAN_MTC_CTRL, 0x7f << 16); + udelay(10); + + mt76_clear(dev, MT_WLAN_MTC_CTRL, 0xf << 24); + udelay(10); + + mt76_set(dev, MT_WLAN_MTC_CTRL, 0xf << 24); + mt76_clear(dev, MT_WLAN_MTC_CTRL, 0xfff); + + /* Turn on AD/DA power down */ + mt76_clear(dev, 0x11204, BIT(3)); + + /* WLAN function enable */ + mt76_set(dev, 0x10080, BIT(0)); + + /* Release BBP software reset */ + mt76_clear(dev, 0x10064, BIT(18)); + + mt76x2_power_on_rf(dev, 0); + mt76x2_power_on_rf(dev, 1); +} + +void mt76x2_set_tx_ackto(struct mt76x2_dev *dev) +{ + u8 ackto, sifs, slottime = dev->slottime; + + slottime += 3 * dev->coverage_class; + + sifs = mt76_get_field(dev, MT_XIFS_TIME_CFG, + MT_XIFS_TIME_CFG_OFDM_SIFS); + + ackto = slottime + sifs; + mt76_rmw_field(dev, MT_TX_TIMEOUT_CFG, + MT_TX_TIMEOUT_CFG_ACKTO, ackto); +} + +static void +mt76x2_set_wlan_state(struct mt76x2_dev *dev, bool enable) +{ + u32 val = mt76_rr(dev, MT_WLAN_FUN_CTRL); + + if (enable) + val |= (MT_WLAN_FUN_CTRL_WLAN_EN | + MT_WLAN_FUN_CTRL_WLAN_CLK_EN); + else + val &= ~(MT_WLAN_FUN_CTRL_WLAN_EN | + MT_WLAN_FUN_CTRL_WLAN_CLK_EN); + + mt76_wr(dev, MT_WLAN_FUN_CTRL, val); + udelay(20); +} + +static void +mt76x2_reset_wlan(struct mt76x2_dev *dev, bool enable) +{ + u32 val; + + val = mt76_rr(dev, MT_WLAN_FUN_CTRL); + + val &= ~MT_WLAN_FUN_CTRL_FRC_WL_ANT_SEL; + + if (val & MT_WLAN_FUN_CTRL_WLAN_EN) { + val |= MT_WLAN_FUN_CTRL_WLAN_RESET_RF; + mt76_wr(dev, MT_WLAN_FUN_CTRL, val); + udelay(20); + + val &= ~MT_WLAN_FUN_CTRL_WLAN_RESET_RF; + } + + mt76_wr(dev, MT_WLAN_FUN_CTRL, val); + udelay(20); + + mt76x2_set_wlan_state(dev, enable); +} + +int mt76x2_init_hardware(struct mt76x2_dev *dev) +{ + static const u16 beacon_offsets[16] = { + /* 1024 byte per beacon */ + 0xc000, + 0xc400, + 0xc800, + 0xcc00, + 0xd000, + 0xd400, + 0xd800, + 0xdc00, + + /* BSS idx 8-15 not used for beacons */ + 0xc000, + 0xc000, + 0xc000, + 0xc000, + 0xc000, + 0xc000, + 0xc000, + 0xc000, + }; + u32 val; + int ret; + + dev->beacon_offsets = beacon_offsets; + tasklet_init(&dev->pre_tbtt_tasklet, mt76x2_pre_tbtt_tasklet, + (unsigned long) dev); + + dev->chainmask = 0x202; + dev->global_wcid.idx = 255; + dev->global_wcid.hw_key_idx = -1; + dev->slottime = 9; + + val = mt76_rr(dev, MT_WPDMA_GLO_CFG); + val &= MT_WPDMA_GLO_CFG_DMA_BURST_SIZE | + MT_WPDMA_GLO_CFG_BIG_ENDIAN | + MT_WPDMA_GLO_CFG_HDR_SEG_LEN; + val |= MT_WPDMA_GLO_CFG_TX_WRITEBACK_DONE; + mt76_wr(dev, MT_WPDMA_GLO_CFG, val); + + mt76x2_reset_wlan(dev, true); + mt76x2_power_on(dev); + + ret = mt76x2_eeprom_init(dev); + if (ret) + return ret; + + ret = mt76x2_mac_reset(dev, true); + if (ret) + return ret; + + dev->rxfilter = mt76_rr(dev, MT_RX_FILTR_CFG); + + ret = mt76x2_dma_init(dev); + if (ret) + return ret; + + set_bit(MT76_STATE_INITIALIZED, &dev->mt76.state); + ret = mt76x2_mac_start(dev); + if (ret) + return ret; + + ret = mt76x2_mcu_init(dev); + if (ret) + return ret; + + mt76x2_mac_stop(dev, false); + + return 0; +} + +void mt76x2_stop_hardware(struct mt76x2_dev *dev) +{ + cancel_delayed_work_sync(&dev->cal_work); + cancel_delayed_work_sync(&dev->mac_work); + mt76x2_mcu_set_radio_state(dev, false); + mt76x2_mac_stop(dev, false); +} + +void mt76x2_cleanup(struct mt76x2_dev *dev) +{ + tasklet_disable(&dev->dfs_pd.dfs_tasklet); + tasklet_disable(&dev->pre_tbtt_tasklet); + mt76x2_stop_hardware(dev); + mt76x2_dma_cleanup(dev); + mt76x2_mcu_cleanup(dev); +} + +struct mt76x2_dev *mt76x2_alloc_device(struct device *pdev) +{ + static const struct mt76_driver_ops drv_ops = { + .txwi_size = sizeof(struct mt76x2_txwi), + .update_survey = mt76x2_update_channel, + .tx_prepare_skb = mt76x2_tx_prepare_skb, + .tx_complete_skb = mt76x2_tx_complete_skb, + .rx_skb = mt76x2_queue_rx_skb, + .rx_poll_complete = mt76x2_rx_poll_complete, + }; + struct ieee80211_hw *hw; + struct mt76x2_dev *dev; + + hw = ieee80211_alloc_hw(sizeof(*dev), &mt76x2_ops); + if (!hw) + return NULL; + + dev = hw->priv; + dev->mt76.dev = pdev; + dev->mt76.hw = hw; + dev->mt76.drv = &drv_ops; + mutex_init(&dev->mutex); + spin_lock_init(&dev->irq_lock); + + return dev; +} + +static void mt76x2_regd_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mt76x2_dev *dev = hw->priv; + + mt76x2_dfs_set_domain(dev, request->dfs_region); +} + +#define CCK_RATE(_idx, _rate) { \ + .bitrate = _rate, \ + .flags = IEEE80211_RATE_SHORT_PREAMBLE, \ + .hw_value = (MT_PHY_TYPE_CCK << 8) | _idx, \ + .hw_value_short = (MT_PHY_TYPE_CCK << 8) | (8 + _idx), \ +} + +#define OFDM_RATE(_idx, _rate) { \ + .bitrate = _rate, \ + .hw_value = (MT_PHY_TYPE_OFDM << 8) | _idx, \ + .hw_value_short = (MT_PHY_TYPE_OFDM << 8) | _idx, \ +} + +static struct ieee80211_rate mt76x2_rates[] = { + CCK_RATE(0, 10), + CCK_RATE(1, 20), + CCK_RATE(2, 55), + CCK_RATE(3, 110), + OFDM_RATE(0, 60), + OFDM_RATE(1, 90), + OFDM_RATE(2, 120), + OFDM_RATE(3, 180), + OFDM_RATE(4, 240), + OFDM_RATE(5, 360), + OFDM_RATE(6, 480), + OFDM_RATE(7, 540), +}; + +static const struct ieee80211_iface_limit if_limits[] = { + { + .max = 1, + .types = BIT(NL80211_IFTYPE_ADHOC) + }, { + .max = 8, + .types = BIT(NL80211_IFTYPE_STATION) | +#ifdef CONFIG_MAC80211_MESH + BIT(NL80211_IFTYPE_MESH_POINT) | +#endif + BIT(NL80211_IFTYPE_AP) + }, +}; + +static const struct ieee80211_iface_combination if_comb[] = { + { + .limits = if_limits, + .n_limits = ARRAY_SIZE(if_limits), + .max_interfaces = 8, + .num_different_channels = 1, + .beacon_int_infra_match = true, + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80), + } +}; + +static void mt76x2_led_set_config(struct mt76_dev *mt76, u8 delay_on, + u8 delay_off) +{ + struct mt76x2_dev *dev = container_of(mt76, struct mt76x2_dev, + mt76); + u32 val; + + val = MT_LED_STATUS_DURATION(0xff) | + MT_LED_STATUS_OFF(delay_off) | + MT_LED_STATUS_ON(delay_on); + + mt76_wr(dev, MT_LED_S0(mt76->led_pin), val); + mt76_wr(dev, MT_LED_S1(mt76->led_pin), val); + + val = MT_LED_CTRL_REPLAY(mt76->led_pin) | + MT_LED_CTRL_KICK(mt76->led_pin); + if (mt76->led_al) + val |= MT_LED_CTRL_POLARITY(mt76->led_pin); + mt76_wr(dev, MT_LED_CTRL, val); +} + +static int mt76x2_led_set_blink(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct mt76_dev *mt76 = container_of(led_cdev, struct mt76_dev, + led_cdev); + u8 delta_on, delta_off; + + delta_off = max_t(u8, *delay_off / 10, 1); + delta_on = max_t(u8, *delay_on / 10, 1); + + mt76x2_led_set_config(mt76, delta_on, delta_off); + return 0; +} + +static void mt76x2_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct mt76_dev *mt76 = container_of(led_cdev, struct mt76_dev, + led_cdev); + + if (!brightness) + mt76x2_led_set_config(mt76, 0, 0xff); + else + mt76x2_led_set_config(mt76, 0xff, 0); +} + +static void +mt76x2_init_txpower(struct mt76x2_dev *dev, + struct ieee80211_supported_band *sband) +{ + struct ieee80211_channel *chan; + struct mt76x2_tx_power_info txp; + struct mt76_rate_power t = {}; + int target_power; + int i; + + for (i = 0; i < sband->n_channels; i++) { + chan = &sband->channels[i]; + + mt76x2_get_power_info(dev, &txp, chan); + + target_power = max_t(int, (txp.chain[0].target_power + + txp.chain[0].delta), + (txp.chain[1].target_power + + txp.chain[1].delta)); + + mt76x2_get_rate_power(dev, &t, chan); + + chan->max_power = mt76x2_get_max_rate_power(&t) + + target_power; + chan->max_power /= 2; + + /* convert to combined output power on 2x2 devices */ + chan->max_power += 3; + } +} + +int mt76x2_register_device(struct mt76x2_dev *dev) +{ + struct ieee80211_hw *hw = mt76_hw(dev); + struct wiphy *wiphy = hw->wiphy; + void *status_fifo; + int fifo_size; + int i, ret; + + fifo_size = roundup_pow_of_two(32 * sizeof(struct mt76x2_tx_status)); + status_fifo = devm_kzalloc(dev->mt76.dev, fifo_size, GFP_KERNEL); + if (!status_fifo) + return -ENOMEM; + + kfifo_init(&dev->txstatus_fifo, status_fifo, fifo_size); + + ret = mt76x2_init_hardware(dev); + if (ret) + return ret; + + hw->queues = 4; + hw->max_rates = 1; + hw->max_report_rates = 7; + hw->max_rate_tries = 1; + hw->extra_tx_headroom = 2; + + hw->sta_data_size = sizeof(struct mt76x2_sta); + hw->vif_data_size = sizeof(struct mt76x2_vif); + + for (i = 0; i < ARRAY_SIZE(dev->macaddr_list); i++) { + u8 *addr = dev->macaddr_list[i].addr; + + memcpy(addr, dev->mt76.macaddr, ETH_ALEN); + + if (!i) + continue; + + addr[0] |= BIT(1); + addr[0] ^= ((i - 1) << 2); + } + wiphy->addresses = dev->macaddr_list; + wiphy->n_addresses = ARRAY_SIZE(dev->macaddr_list); + + wiphy->iface_combinations = if_comb; + wiphy->n_iface_combinations = ARRAY_SIZE(if_comb); + + wiphy->reg_notifier = mt76x2_regd_notifier; + + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_VHT_IBSS); + + ieee80211_hw_set(hw, SUPPORTS_HT_CCK_RATES); + ieee80211_hw_set(hw, SUPPORTS_REORDERING_BUFFER); + + INIT_DELAYED_WORK(&dev->cal_work, mt76x2_phy_calibrate); + INIT_DELAYED_WORK(&dev->mac_work, mt76x2_mac_work); + + dev->mt76.sband_2g.sband.ht_cap.cap |= IEEE80211_HT_CAP_LDPC_CODING; + dev->mt76.sband_5g.sband.ht_cap.cap |= IEEE80211_HT_CAP_LDPC_CODING; + + mt76x2_dfs_init_detector(dev); + + /* init led callbacks */ + dev->mt76.led_cdev.brightness_set = mt76x2_led_set_brightness; + dev->mt76.led_cdev.blink_set = mt76x2_led_set_blink; + + ret = mt76_register_device(&dev->mt76, true, mt76x2_rates, + ARRAY_SIZE(mt76x2_rates)); + if (ret) + goto fail; + + mt76x2_init_debugfs(dev); + mt76x2_init_txpower(dev, &dev->mt76.sband_2g.sband); + mt76x2_init_txpower(dev, &dev->mt76.sband_5g.sband); + + return 0; + +fail: + mt76x2_stop_hardware(dev); + return ret; +} + + diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_mac.c b/drivers/net/wireless/mediatek/mt76/mt76x2_mac.c new file mode 100644 index 000000000000..6c30b5eaa9ca --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_mac.c @@ -0,0 +1,839 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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/delay.h> +#include "mt76x2.h" +#include "mt76x2_mcu.h" +#include "mt76x2_eeprom.h" +#include "mt76x2_trace.h" + +void mt76x2_mac_set_bssid(struct mt76x2_dev *dev, u8 idx, const u8 *addr) +{ + idx &= 7; + mt76_wr(dev, MT_MAC_APC_BSSID_L(idx), get_unaligned_le32(addr)); + mt76_rmw_field(dev, MT_MAC_APC_BSSID_H(idx), MT_MAC_APC_BSSID_H_ADDR, + get_unaligned_le16(addr + 4)); +} + +static int +mt76x2_mac_process_rate(struct mt76_rx_status *status, u16 rate) +{ + u8 idx = FIELD_GET(MT_RXWI_RATE_INDEX, rate); + + switch (FIELD_GET(MT_RXWI_RATE_PHY, rate)) { + case MT_PHY_TYPE_OFDM: + if (idx >= 8) + idx = 0; + + if (status->band == NL80211_BAND_2GHZ) + idx += 4; + + status->rate_idx = idx; + return 0; + case MT_PHY_TYPE_CCK: + if (idx >= 8) { + idx -= 8; + status->enc_flags |= RX_ENC_FLAG_SHORTPRE; + } + + if (idx >= 4) + idx = 0; + + status->rate_idx = idx; + return 0; + case MT_PHY_TYPE_HT_GF: + status->enc_flags |= RX_ENC_FLAG_HT_GF; + /* fall through */ + case MT_PHY_TYPE_HT: + status->encoding = RX_ENC_HT; + status->rate_idx = idx; + break; + case MT_PHY_TYPE_VHT: + status->encoding = RX_ENC_VHT; + status->rate_idx = FIELD_GET(MT_RATE_INDEX_VHT_IDX, idx); + status->nss = FIELD_GET(MT_RATE_INDEX_VHT_NSS, idx) + 1; + break; + default: + return -EINVAL; + } + + if (rate & MT_RXWI_RATE_LDPC) + status->enc_flags |= RX_ENC_FLAG_LDPC; + + if (rate & MT_RXWI_RATE_SGI) + status->enc_flags |= RX_ENC_FLAG_SHORT_GI; + + if (rate & MT_RXWI_RATE_STBC) + status->enc_flags |= 1 << RX_ENC_FLAG_STBC_SHIFT; + + switch (FIELD_GET(MT_RXWI_RATE_BW, rate)) { + case MT_PHY_BW_20: + break; + case MT_PHY_BW_40: + status->bw = RATE_INFO_BW_40; + break; + case MT_PHY_BW_80: + status->bw = RATE_INFO_BW_80; + break; + default: + break; + } + + return 0; +} + +static __le16 +mt76x2_mac_tx_rate_val(struct mt76x2_dev *dev, + const struct ieee80211_tx_rate *rate, u8 *nss_val) +{ + u16 rateval; + u8 phy, rate_idx; + u8 nss = 1; + u8 bw = 0; + + if (rate->flags & IEEE80211_TX_RC_VHT_MCS) { + rate_idx = rate->idx; + nss = 1 + (rate->idx >> 4); + phy = MT_PHY_TYPE_VHT; + if (rate->flags & IEEE80211_TX_RC_80_MHZ_WIDTH) + bw = 2; + else if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH) + bw = 1; + } else if (rate->flags & IEEE80211_TX_RC_MCS) { + rate_idx = rate->idx; + nss = 1 + (rate->idx >> 3); + phy = MT_PHY_TYPE_HT; + if (rate->flags & IEEE80211_TX_RC_GREEN_FIELD) + phy = MT_PHY_TYPE_HT_GF; + if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH) + bw = 1; + } else { + const struct ieee80211_rate *r; + int band = dev->mt76.chandef.chan->band; + u16 val; + + r = &mt76_hw(dev)->wiphy->bands[band]->bitrates[rate->idx]; + if (rate->flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) + val = r->hw_value_short; + else + val = r->hw_value; + + phy = val >> 8; + rate_idx = val & 0xff; + bw = 0; + } + + rateval = FIELD_PREP(MT_RXWI_RATE_INDEX, rate_idx); + rateval |= FIELD_PREP(MT_RXWI_RATE_PHY, phy); + rateval |= FIELD_PREP(MT_RXWI_RATE_BW, bw); + if (rate->flags & IEEE80211_TX_RC_SHORT_GI) + rateval |= MT_RXWI_RATE_SGI; + + *nss_val = nss; + return cpu_to_le16(rateval); +} + +void mt76x2_mac_wcid_set_drop(struct mt76x2_dev *dev, u8 idx, bool drop) +{ + u32 val = mt76_rr(dev, MT_WCID_DROP(idx)); + u32 bit = MT_WCID_DROP_MASK(idx); + + /* prevent unnecessary writes */ + if ((val & bit) != (bit * drop)) + mt76_wr(dev, MT_WCID_DROP(idx), (val & ~bit) | (bit * drop)); +} + +void mt76x2_mac_wcid_set_rate(struct mt76x2_dev *dev, struct mt76_wcid *wcid, + const struct ieee80211_tx_rate *rate) +{ + spin_lock_bh(&dev->mt76.lock); + wcid->tx_rate = mt76x2_mac_tx_rate_val(dev, rate, &wcid->tx_rate_nss); + wcid->tx_rate_set = true; + spin_unlock_bh(&dev->mt76.lock); +} + +void mt76x2_mac_write_txwi(struct mt76x2_dev *dev, struct mt76x2_txwi *txwi, + struct sk_buff *skb, struct mt76_wcid *wcid, + struct ieee80211_sta *sta) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_tx_rate *rate = &info->control.rates[0]; + struct ieee80211_key_conf *key = info->control.hw_key; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; + u16 rate_ht_mask = FIELD_PREP(MT_RXWI_RATE_PHY, BIT(1) | BIT(2)); + u16 txwi_flags = 0; + u8 nss; + s8 txpwr_adj, max_txpwr_adj; + u8 ccmp_pn[8]; + + memset(txwi, 0, sizeof(*txwi)); + + if (wcid) + txwi->wcid = wcid->idx; + else + txwi->wcid = 0xff; + + txwi->pktid = 1; + + if (wcid && wcid->sw_iv && key) { + u64 pn = atomic64_inc_return(&key->tx_pn); + ccmp_pn[0] = pn; + ccmp_pn[1] = pn >> 8; + ccmp_pn[2] = 0; + ccmp_pn[3] = 0x20 | (key->keyidx << 6); + ccmp_pn[4] = pn >> 16; + ccmp_pn[5] = pn >> 24; + ccmp_pn[6] = pn >> 32; + ccmp_pn[7] = pn >> 40; + txwi->iv = *((u32 *) &ccmp_pn[0]); + txwi->eiv = *((u32 *) &ccmp_pn[1]); + } + + spin_lock_bh(&dev->mt76.lock); + if (wcid && (rate->idx < 0 || !rate->count)) { + txwi->rate = wcid->tx_rate; + max_txpwr_adj = wcid->max_txpwr_adj; + nss = wcid->tx_rate_nss; + } else { + txwi->rate = mt76x2_mac_tx_rate_val(dev, rate, &nss); + max_txpwr_adj = mt76x2_tx_get_max_txpwr_adj(dev, rate); + } + spin_unlock_bh(&dev->mt76.lock); + + txpwr_adj = mt76x2_tx_get_txpwr_adj(dev, dev->txpower_conf, + max_txpwr_adj); + txwi->ctl2 = FIELD_PREP(MT_TX_PWR_ADJ, txpwr_adj); + + if (mt76xx_rev(dev) >= MT76XX_REV_E4) + txwi->txstream = 0x13; + else if (mt76xx_rev(dev) >= MT76XX_REV_E3 && + !(txwi->rate & cpu_to_le16(rate_ht_mask))) + txwi->txstream = 0x93; + + if (info->flags & IEEE80211_TX_CTL_LDPC) + txwi->rate |= cpu_to_le16(MT_RXWI_RATE_LDPC); + if ((info->flags & IEEE80211_TX_CTL_STBC) && nss == 1) + txwi->rate |= cpu_to_le16(MT_RXWI_RATE_STBC); + if (nss > 1 && sta && sta->smps_mode == IEEE80211_SMPS_DYNAMIC) + txwi_flags |= MT_TXWI_FLAGS_MMPS; + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) + txwi->ack_ctl |= MT_TXWI_ACK_CTL_REQ; + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) + txwi->ack_ctl |= MT_TXWI_ACK_CTL_NSEQ; + if (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE) + txwi->pktid |= MT_TXWI_PKTID_PROBE; + if ((info->flags & IEEE80211_TX_CTL_AMPDU) && sta) { + u8 ba_size = IEEE80211_MIN_AMPDU_BUF; + + ba_size <<= sta->ht_cap.ampdu_factor; + ba_size = min_t(int, 63, ba_size - 1); + if (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE) + ba_size = 0; + txwi->ack_ctl |= FIELD_PREP(MT_TXWI_ACK_CTL_BA_WINDOW, ba_size); + + txwi_flags |= MT_TXWI_FLAGS_AMPDU | + FIELD_PREP(MT_TXWI_FLAGS_MPDU_DENSITY, + sta->ht_cap.ampdu_density); + } + + if (ieee80211_is_probe_resp(hdr->frame_control) || + ieee80211_is_beacon(hdr->frame_control)) + txwi_flags |= MT_TXWI_FLAGS_TS; + + txwi->flags |= cpu_to_le16(txwi_flags); + txwi->len_ctl = cpu_to_le16(skb->len); +} + +static void mt76x2_remove_hdr_pad(struct sk_buff *skb, int len) +{ + int hdrlen; + + if (!len) + return; + + hdrlen = ieee80211_get_hdrlen_from_skb(skb); + memmove(skb->data + len, skb->data, hdrlen); + skb_pull(skb, len); +} + +static struct mt76_wcid * +mt76x2_rx_get_sta_wcid(struct mt76x2_dev *dev, u8 idx, bool unicast) +{ + struct mt76x2_sta *sta; + struct mt76_wcid *wcid; + + if (idx >= ARRAY_SIZE(dev->wcid)) + return NULL; + + wcid = rcu_dereference(dev->wcid[idx]); + if (unicast || !wcid) + return wcid; + + sta = container_of(wcid, struct mt76x2_sta, wcid); + return &sta->vif->group_wcid; +} + +int mt76x2_mac_process_rx(struct mt76x2_dev *dev, struct sk_buff *skb, + void *rxi) +{ + struct mt76_rx_status *status = (struct mt76_rx_status *) skb->cb; + struct mt76x2_rxwi *rxwi = rxi; + u32 rxinfo = le32_to_cpu(rxwi->rxinfo); + u32 ctl = le32_to_cpu(rxwi->ctl); + u16 rate = le16_to_cpu(rxwi->rate); + u16 tid_sn = le16_to_cpu(rxwi->tid_sn); + bool unicast = rxwi->rxinfo & cpu_to_le32(MT_RXINFO_UNICAST); + int pad_len = 0; + u8 pn_len; + u8 wcid; + int len; + + if (rxinfo & MT_RXINFO_L2PAD) + pad_len += 2; + + if (rxinfo & MT_RXINFO_DECRYPT) { + status->flag |= RX_FLAG_DECRYPTED; + status->flag |= RX_FLAG_MMIC_STRIPPED; + status->flag |= RX_FLAG_MIC_STRIPPED; + status->flag |= RX_FLAG_IV_STRIPPED; + } + + wcid = FIELD_GET(MT_RXWI_CTL_WCID, ctl); + status->wcid = mt76x2_rx_get_sta_wcid(dev, wcid, unicast); + + len = FIELD_GET(MT_RXWI_CTL_MPDU_LEN, ctl); + pn_len = FIELD_GET(MT_RXINFO_PN_LEN, rxinfo); + if (pn_len) { + int offset = ieee80211_get_hdrlen_from_skb(skb) + pad_len; + u8 *data = skb->data + offset; + + status->iv[0] = data[7]; + status->iv[1] = data[6]; + status->iv[2] = data[5]; + status->iv[3] = data[4]; + status->iv[4] = data[1]; + status->iv[5] = data[0]; + + /* + * Driver CCMP validation can't deal with fragments. + * Let mac80211 take care of it. + */ + if (rxinfo & MT_RXINFO_FRAG) { + status->flag &= ~RX_FLAG_IV_STRIPPED; + } else { + pad_len += pn_len << 2; + len -= pn_len << 2; + } + } + + mt76x2_remove_hdr_pad(skb, pad_len); + + if (rxinfo & MT_RXINFO_BA) + status->aggr = true; + + if (WARN_ON_ONCE(len > skb->len)) + return -EINVAL; + + pskb_trim(skb, len); + status->chains = BIT(0) | BIT(1); + status->chain_signal[0] = mt76x2_phy_get_rssi(dev, rxwi->rssi[0], 0); + status->chain_signal[1] = mt76x2_phy_get_rssi(dev, rxwi->rssi[1], 1); + status->signal = max(status->chain_signal[0], status->chain_signal[1]); + status->freq = dev->mt76.chandef.chan->center_freq; + status->band = dev->mt76.chandef.chan->band; + + status->tid = FIELD_GET(MT_RXWI_TID, tid_sn); + status->seqno = FIELD_GET(MT_RXWI_SN, tid_sn); + + return mt76x2_mac_process_rate(status, rate); +} + +static int +mt76x2_mac_process_tx_rate(struct ieee80211_tx_rate *txrate, u16 rate, + enum nl80211_band band) +{ + u8 idx = FIELD_GET(MT_RXWI_RATE_INDEX, rate); + + txrate->idx = 0; + txrate->flags = 0; + txrate->count = 1; + + switch (FIELD_GET(MT_RXWI_RATE_PHY, rate)) { + case MT_PHY_TYPE_OFDM: + if (band == NL80211_BAND_2GHZ) + idx += 4; + + txrate->idx = idx; + return 0; + case MT_PHY_TYPE_CCK: + if (idx >= 8) + idx -= 8; + + txrate->idx = idx; + return 0; + case MT_PHY_TYPE_HT_GF: + txrate->flags |= IEEE80211_TX_RC_GREEN_FIELD; + /* fall through */ + case MT_PHY_TYPE_HT: + txrate->flags |= IEEE80211_TX_RC_MCS; + txrate->idx = idx; + break; + case MT_PHY_TYPE_VHT: + txrate->flags |= IEEE80211_TX_RC_VHT_MCS; + txrate->idx = idx; + break; + default: + return -EINVAL; + } + + switch (FIELD_GET(MT_RXWI_RATE_BW, rate)) { + case MT_PHY_BW_20: + break; + case MT_PHY_BW_40: + txrate->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + break; + case MT_PHY_BW_80: + txrate->flags |= IEEE80211_TX_RC_80_MHZ_WIDTH; + break; + default: + return -EINVAL; + break; + } + + if (rate & MT_RXWI_RATE_SGI) + txrate->flags |= IEEE80211_TX_RC_SHORT_GI; + + return 0; +} + +static void +mt76x2_mac_fill_tx_status(struct mt76x2_dev *dev, + struct ieee80211_tx_info *info, + struct mt76x2_tx_status *st, int n_frames) +{ + struct ieee80211_tx_rate *rate = info->status.rates; + int cur_idx, last_rate; + int i; + + if (!n_frames) + return; + + last_rate = min_t(int, st->retry, IEEE80211_TX_MAX_RATES - 1); + mt76x2_mac_process_tx_rate(&rate[last_rate], st->rate, + dev->mt76.chandef.chan->band); + if (last_rate < IEEE80211_TX_MAX_RATES - 1) + rate[last_rate + 1].idx = -1; + + cur_idx = rate[last_rate].idx + st->retry; + for (i = 0; i <= last_rate; i++) { + rate[i].flags = rate[last_rate].flags; + rate[i].idx = max_t(int, 0, cur_idx - i); + rate[i].count = 1; + } + + if (last_rate > 0) + rate[last_rate - 1].count = st->retry + 1 - last_rate; + + info->status.ampdu_len = n_frames; + info->status.ampdu_ack_len = st->success ? n_frames : 0; + + if (st->pktid & MT_TXWI_PKTID_PROBE) + info->flags |= IEEE80211_TX_CTL_RATE_CTRL_PROBE; + + if (st->aggr) + info->flags |= IEEE80211_TX_CTL_AMPDU | + IEEE80211_TX_STAT_AMPDU; + + if (!st->ack_req) + info->flags |= IEEE80211_TX_CTL_NO_ACK; + else if (st->success) + info->flags |= IEEE80211_TX_STAT_ACK; +} + +static void +mt76x2_send_tx_status(struct mt76x2_dev *dev, struct mt76x2_tx_status *stat, + u8 *update) +{ + struct ieee80211_tx_info info = {}; + struct ieee80211_sta *sta = NULL; + struct mt76_wcid *wcid = NULL; + struct mt76x2_sta *msta = NULL; + + rcu_read_lock(); + if (stat->wcid < ARRAY_SIZE(dev->wcid)) + wcid = rcu_dereference(dev->wcid[stat->wcid]); + + if (wcid) { + void *priv; + + priv = msta = container_of(wcid, struct mt76x2_sta, wcid); + sta = container_of(priv, struct ieee80211_sta, + drv_priv); + } + + if (msta && stat->aggr) { + u32 stat_val, stat_cache; + + stat_val = stat->rate; + stat_val |= ((u32) stat->retry) << 16; + stat_cache = msta->status.rate; + stat_cache |= ((u32) msta->status.retry) << 16; + + if (*update == 0 && stat_val == stat_cache && + stat->wcid == msta->status.wcid && msta->n_frames < 32) { + msta->n_frames++; + goto out; + } + + mt76x2_mac_fill_tx_status(dev, &info, &msta->status, + msta->n_frames); + + msta->status = *stat; + msta->n_frames = 1; + *update = 0; + } else { + mt76x2_mac_fill_tx_status(dev, &info, stat, 1); + *update = 1; + } + + ieee80211_tx_status_noskb(mt76_hw(dev), sta, &info); + +out: + rcu_read_unlock(); +} + +void mt76x2_mac_poll_tx_status(struct mt76x2_dev *dev, bool irq) +{ + struct mt76x2_tx_status stat = {}; + unsigned long flags; + u8 update = 1; + + if (!test_bit(MT76_STATE_RUNNING, &dev->mt76.state)) + return; + + trace_mac_txstat_poll(dev); + + while (!irq || !kfifo_is_full(&dev->txstatus_fifo)) { + u32 stat1, stat2; + + spin_lock_irqsave(&dev->irq_lock, flags); + stat2 = mt76_rr(dev, MT_TX_STAT_FIFO_EXT); + stat1 = mt76_rr(dev, MT_TX_STAT_FIFO); + if (!(stat1 & MT_TX_STAT_FIFO_VALID)) { + spin_unlock_irqrestore(&dev->irq_lock, flags); + break; + } + + spin_unlock_irqrestore(&dev->irq_lock, flags); + + stat.valid = 1; + stat.success = !!(stat1 & MT_TX_STAT_FIFO_SUCCESS); + stat.aggr = !!(stat1 & MT_TX_STAT_FIFO_AGGR); + stat.ack_req = !!(stat1 & MT_TX_STAT_FIFO_ACKREQ); + stat.wcid = FIELD_GET(MT_TX_STAT_FIFO_WCID, stat1); + stat.rate = FIELD_GET(MT_TX_STAT_FIFO_RATE, stat1); + stat.retry = FIELD_GET(MT_TX_STAT_FIFO_EXT_RETRY, stat2); + stat.pktid = FIELD_GET(MT_TX_STAT_FIFO_EXT_PKTID, stat2); + trace_mac_txstat_fetch(dev, &stat); + + if (!irq) { + mt76x2_send_tx_status(dev, &stat, &update); + continue; + } + + kfifo_put(&dev->txstatus_fifo, stat); + } +} + +static void +mt76x2_mac_queue_txdone(struct mt76x2_dev *dev, struct sk_buff *skb, + void *txwi_ptr) +{ + struct mt76x2_tx_info *txi = mt76x2_skb_tx_info(skb); + struct mt76x2_txwi *txwi = txwi_ptr; + + mt76x2_mac_poll_tx_status(dev, false); + + txi->tries = 0; + txi->jiffies = jiffies; + txi->wcid = txwi->wcid; + txi->pktid = txwi->pktid; + trace_mac_txdone_add(dev, txwi->wcid, txwi->pktid); + mt76x2_tx_complete(dev, skb); +} + +void mt76x2_mac_process_tx_status_fifo(struct mt76x2_dev *dev) +{ + struct mt76x2_tx_status stat; + u8 update = 1; + + while (kfifo_get(&dev->txstatus_fifo, &stat)) + mt76x2_send_tx_status(dev, &stat, &update); +} + +void mt76x2_tx_complete_skb(struct mt76_dev *mdev, struct mt76_queue *q, + struct mt76_queue_entry *e, bool flush) +{ + struct mt76x2_dev *dev = container_of(mdev, struct mt76x2_dev, mt76); + + if (e->txwi) + mt76x2_mac_queue_txdone(dev, e->skb, &e->txwi->txwi); + else + dev_kfree_skb_any(e->skb); +} + +static enum mt76x2_cipher_type +mt76x2_mac_get_key_info(struct ieee80211_key_conf *key, u8 *key_data) +{ + memset(key_data, 0, 32); + if (!key) + return MT_CIPHER_NONE; + + if (key->keylen > 32) + return MT_CIPHER_NONE; + + memcpy(key_data, key->key, key->keylen); + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + return MT_CIPHER_WEP40; + case WLAN_CIPHER_SUITE_WEP104: + return MT_CIPHER_WEP104; + case WLAN_CIPHER_SUITE_TKIP: + return MT_CIPHER_TKIP; + case WLAN_CIPHER_SUITE_CCMP: + return MT_CIPHER_AES_CCMP; + default: + return MT_CIPHER_NONE; + } +} + +void mt76x2_mac_wcid_setup(struct mt76x2_dev *dev, u8 idx, u8 vif_idx, u8 *mac) +{ + struct mt76_wcid_addr addr = {}; + u32 attr; + + attr = FIELD_PREP(MT_WCID_ATTR_BSS_IDX, vif_idx & 7) | + FIELD_PREP(MT_WCID_ATTR_BSS_IDX_EXT, !!(vif_idx & 8)); + + mt76_wr(dev, MT_WCID_ATTR(idx), attr); + + mt76_wr(dev, MT_WCID_TX_RATE(idx), 0); + mt76_wr(dev, MT_WCID_TX_RATE(idx) + 4, 0); + + if (idx >= 128) + return; + + if (mac) + memcpy(addr.macaddr, mac, ETH_ALEN); + + mt76_wr_copy(dev, MT_WCID_ADDR(idx), &addr, sizeof(addr)); +} + +int mt76x2_mac_wcid_set_key(struct mt76x2_dev *dev, u8 idx, + struct ieee80211_key_conf *key) +{ + enum mt76x2_cipher_type cipher; + u8 key_data[32]; + u8 iv_data[8]; + + cipher = mt76x2_mac_get_key_info(key, key_data); + if (cipher == MT_CIPHER_NONE && key) + return -EOPNOTSUPP; + + mt76_rmw_field(dev, MT_WCID_ATTR(idx), MT_WCID_ATTR_PKEY_MODE, cipher); + mt76_wr_copy(dev, MT_WCID_KEY(idx), key_data, sizeof(key_data)); + + memset(iv_data, 0, sizeof(iv_data)); + if (key) { + mt76_rmw_field(dev, MT_WCID_ATTR(idx), MT_WCID_ATTR_PAIRWISE, + !!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)); + iv_data[3] = key->keyidx << 6; + if (cipher >= MT_CIPHER_TKIP) + iv_data[3] |= 0x20; + } + + mt76_wr_copy(dev, MT_WCID_IV(idx), iv_data, sizeof(iv_data)); + + return 0; +} + +int mt76x2_mac_shared_key_setup(struct mt76x2_dev *dev, u8 vif_idx, u8 key_idx, + struct ieee80211_key_conf *key) +{ + enum mt76x2_cipher_type cipher; + u8 key_data[32]; + u32 val; + + cipher = mt76x2_mac_get_key_info(key, key_data); + if (cipher == MT_CIPHER_NONE && key) + return -EOPNOTSUPP; + + val = mt76_rr(dev, MT_SKEY_MODE(vif_idx)); + val &= ~(MT_SKEY_MODE_MASK << MT_SKEY_MODE_SHIFT(vif_idx, key_idx)); + val |= cipher << MT_SKEY_MODE_SHIFT(vif_idx, key_idx); + mt76_wr(dev, MT_SKEY_MODE(vif_idx), val); + + mt76_wr_copy(dev, MT_SKEY(vif_idx, key_idx), key_data, + sizeof(key_data)); + + return 0; +} + +static int +mt76_write_beacon(struct mt76x2_dev *dev, int offset, struct sk_buff *skb) +{ + int beacon_len = dev->beacon_offsets[1] - dev->beacon_offsets[0]; + struct mt76x2_txwi txwi; + + if (WARN_ON_ONCE(beacon_len < skb->len + sizeof(struct mt76x2_txwi))) + return -ENOSPC; + + mt76x2_mac_write_txwi(dev, &txwi, skb, NULL, NULL); + + mt76_wr_copy(dev, offset, &txwi, sizeof(txwi)); + offset += sizeof(txwi); + + mt76_wr_copy(dev, offset, skb->data, skb->len); + return 0; +} + +static int +__mt76x2_mac_set_beacon(struct mt76x2_dev *dev, u8 bcn_idx, struct sk_buff *skb) +{ + int beacon_len = dev->beacon_offsets[1] - dev->beacon_offsets[0]; + int beacon_addr = dev->beacon_offsets[bcn_idx]; + int ret = 0; + int i; + + /* Prevent corrupt transmissions during update */ + mt76_set(dev, MT_BCN_BYPASS_MASK, BIT(bcn_idx)); + + if (skb) { + ret = mt76_write_beacon(dev, beacon_addr, skb); + if (!ret) + dev->beacon_data_mask |= BIT(bcn_idx) & + dev->beacon_mask; + } else { + dev->beacon_data_mask &= ~BIT(bcn_idx); + for (i = 0; i < beacon_len; i += 4) + mt76_wr(dev, beacon_addr + i, 0); + } + + mt76_wr(dev, MT_BCN_BYPASS_MASK, 0xff00 | ~dev->beacon_data_mask); + + return ret; +} + +int mt76x2_mac_set_beacon(struct mt76x2_dev *dev, u8 vif_idx, + struct sk_buff *skb) +{ + bool force_update = false; + int bcn_idx = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(dev->beacons); i++) { + if (vif_idx == i) { + force_update = !!dev->beacons[i] ^ !!skb; + + if (dev->beacons[i]) + dev_kfree_skb(dev->beacons[i]); + + dev->beacons[i] = skb; + __mt76x2_mac_set_beacon(dev, bcn_idx, skb); + } else if (force_update && dev->beacons[i]) { + __mt76x2_mac_set_beacon(dev, bcn_idx, dev->beacons[i]); + } + + bcn_idx += !!dev->beacons[i]; + } + + for (i = bcn_idx; i < ARRAY_SIZE(dev->beacons); i++) { + if (!(dev->beacon_data_mask & BIT(i))) + break; + + __mt76x2_mac_set_beacon(dev, i, NULL); + } + + mt76_rmw_field(dev, MT_MAC_BSSID_DW1, MT_MAC_BSSID_DW1_MBEACON_N, + bcn_idx - 1); + return 0; +} + +void mt76x2_mac_set_beacon_enable(struct mt76x2_dev *dev, u8 vif_idx, bool val) +{ + u8 old_mask = dev->beacon_mask; + bool en; + u32 reg; + + if (val) { + dev->beacon_mask |= BIT(vif_idx); + } else { + dev->beacon_mask &= ~BIT(vif_idx); + mt76x2_mac_set_beacon(dev, vif_idx, NULL); + } + + if (!!old_mask == !!dev->beacon_mask) + return; + + en = dev->beacon_mask; + + mt76_rmw_field(dev, MT_INT_TIMER_EN, MT_INT_TIMER_EN_PRE_TBTT_EN, en); + reg = MT_BEACON_TIME_CFG_BEACON_TX | + MT_BEACON_TIME_CFG_TBTT_EN | + MT_BEACON_TIME_CFG_TIMER_EN; + mt76_rmw(dev, MT_BEACON_TIME_CFG, reg, reg * en); + + if (en) + mt76x2_irq_enable(dev, MT_INT_PRE_TBTT | MT_INT_TBTT); + else + mt76x2_irq_disable(dev, MT_INT_PRE_TBTT | MT_INT_TBTT); +} + +void mt76x2_update_channel(struct mt76_dev *mdev) +{ + struct mt76x2_dev *dev = container_of(mdev, struct mt76x2_dev, mt76); + struct mt76_channel_state *state; + u32 active, busy; + + state = mt76_channel_state(&dev->mt76, dev->mt76.chandef.chan); + + busy = mt76_rr(dev, MT_CH_BUSY); + active = busy + mt76_rr(dev, MT_CH_IDLE); + + spin_lock_bh(&dev->mt76.cc_lock); + state->cc_busy += busy; + state->cc_active += active; + spin_unlock_bh(&dev->mt76.cc_lock); +} + +void mt76x2_mac_work(struct work_struct *work) +{ + struct mt76x2_dev *dev = container_of(work, struct mt76x2_dev, + mac_work.work); + int i, idx; + + mt76x2_update_channel(&dev->mt76); + for (i = 0, idx = 0; i < 16; i++) { + u32 val = mt76_rr(dev, MT_TX_AGG_CNT(i)); + + dev->aggr_stats[idx++] += val & 0xffff; + dev->aggr_stats[idx++] += val >> 16; + } + + ieee80211_queue_delayed_work(mt76_hw(dev), &dev->mac_work, + MT_CALIBRATE_INTERVAL); +} diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_mac.h b/drivers/net/wireless/mediatek/mt76/mt76x2_mac.h new file mode 100644 index 000000000000..8a8a25e32d5f --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_mac.h @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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. + */ + +#ifndef __MT76x2_MAC_H +#define __MT76x2_MAC_H + +#include "mt76.h" + +struct mt76x2_dev; +struct mt76x2_sta; +struct mt76x2_vif; +struct mt76x2_txwi; + +struct mt76x2_tx_status { + u8 valid:1; + u8 success:1; + u8 aggr:1; + u8 ack_req:1; + u8 wcid; + u8 pktid; + u8 retry; + u16 rate; +} __packed __aligned(2); + +struct mt76x2_tx_info { + unsigned long jiffies; + u8 tries; + + u8 wcid; + u8 pktid; + u8 retry; +}; + +struct mt76x2_rxwi { + __le32 rxinfo; + + __le32 ctl; + + __le16 tid_sn; + __le16 rate; + + u8 rssi[4]; + + __le32 bbp_rxinfo[4]; +}; + +#define MT_RXINFO_BA BIT(0) +#define MT_RXINFO_DATA BIT(1) +#define MT_RXINFO_NULL BIT(2) +#define MT_RXINFO_FRAG BIT(3) +#define MT_RXINFO_UNICAST BIT(4) +#define MT_RXINFO_MULTICAST BIT(5) +#define MT_RXINFO_BROADCAST BIT(6) +#define MT_RXINFO_MYBSS BIT(7) +#define MT_RXINFO_CRCERR BIT(8) +#define MT_RXINFO_ICVERR BIT(9) +#define MT_RXINFO_MICERR BIT(10) +#define MT_RXINFO_AMSDU BIT(11) +#define MT_RXINFO_HTC BIT(12) +#define MT_RXINFO_RSSI BIT(13) +#define MT_RXINFO_L2PAD BIT(14) +#define MT_RXINFO_AMPDU BIT(15) +#define MT_RXINFO_DECRYPT BIT(16) +#define MT_RXINFO_BSSIDX3 BIT(17) +#define MT_RXINFO_WAPI_KEY BIT(18) +#define MT_RXINFO_PN_LEN GENMASK(21, 19) +#define MT_RXINFO_SW_FTYPE0 BIT(22) +#define MT_RXINFO_SW_FTYPE1 BIT(23) +#define MT_RXINFO_PROBE_RESP BIT(24) +#define MT_RXINFO_BEACON BIT(25) +#define MT_RXINFO_DISASSOC BIT(26) +#define MT_RXINFO_DEAUTH BIT(27) +#define MT_RXINFO_ACTION BIT(28) +#define MT_RXINFO_TCP_SUM_ERR BIT(30) +#define MT_RXINFO_IP_SUM_ERR BIT(31) + +#define MT_RXWI_CTL_WCID GENMASK(7, 0) +#define MT_RXWI_CTL_KEY_IDX GENMASK(9, 8) +#define MT_RXWI_CTL_BSS_IDX GENMASK(12, 10) +#define MT_RXWI_CTL_UDF GENMASK(15, 13) +#define MT_RXWI_CTL_MPDU_LEN GENMASK(29, 16) +#define MT_RXWI_CTL_EOF BIT(31) + +#define MT_RXWI_TID GENMASK(3, 0) +#define MT_RXWI_SN GENMASK(15, 4) + +#define MT_RXWI_RATE_INDEX GENMASK(5, 0) +#define MT_RXWI_RATE_LDPC BIT(6) +#define MT_RXWI_RATE_BW GENMASK(8, 7) +#define MT_RXWI_RATE_SGI BIT(9) +#define MT_RXWI_RATE_STBC BIT(10) +#define MT_RXWI_RATE_LDPC_EXSYM BIT(11) +#define MT_RXWI_RATE_PHY GENMASK(15, 13) + +#define MT_RATE_INDEX_VHT_IDX GENMASK(3, 0) +#define MT_RATE_INDEX_VHT_NSS GENMASK(5, 4) + +#define MT_TX_PWR_ADJ GENMASK(3, 0) + +enum mt76x2_phy_bandwidth { + MT_PHY_BW_20, + MT_PHY_BW_40, + MT_PHY_BW_80, +}; + +#define MT_TXWI_FLAGS_FRAG BIT(0) +#define MT_TXWI_FLAGS_MMPS BIT(1) +#define MT_TXWI_FLAGS_CFACK BIT(2) +#define MT_TXWI_FLAGS_TS BIT(3) +#define MT_TXWI_FLAGS_AMPDU BIT(4) +#define MT_TXWI_FLAGS_MPDU_DENSITY GENMASK(7, 5) +#define MT_TXWI_FLAGS_TXOP GENMASK(9, 8) +#define MT_TXWI_FLAGS_NDPS BIT(10) +#define MT_TXWI_FLAGS_RTSBWSIG BIT(11) +#define MT_TXWI_FLAGS_NDP_BW GENMASK(13, 12) +#define MT_TXWI_FLAGS_SOUND BIT(14) +#define MT_TXWI_FLAGS_TX_RATE_LUT BIT(15) + +#define MT_TXWI_ACK_CTL_REQ BIT(0) +#define MT_TXWI_ACK_CTL_NSEQ BIT(1) +#define MT_TXWI_ACK_CTL_BA_WINDOW GENMASK(7, 2) + +#define MT_TXWI_PKTID_PROBE BIT(7) + +struct mt76x2_txwi { + __le16 flags; + __le16 rate; + u8 ack_ctl; + u8 wcid; + __le16 len_ctl; + __le32 iv; + __le32 eiv; + u8 aid; + u8 txstream; + u8 ctl2; + u8 pktid; +} __packed __aligned(4); + +static inline struct mt76x2_tx_info * +mt76x2_skb_tx_info(struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + return (void *) info->status.status_driver_data; +} + +int mt76x2_mac_reset(struct mt76x2_dev *dev, bool hard); +int mt76x2_mac_start(struct mt76x2_dev *dev); +void mt76x2_mac_stop(struct mt76x2_dev *dev, bool force); +void mt76x2_mac_resume(struct mt76x2_dev *dev); +void mt76x2_mac_set_bssid(struct mt76x2_dev *dev, u8 idx, const u8 *addr); + +int mt76x2_mac_process_rx(struct mt76x2_dev *dev, struct sk_buff *skb, + void *rxi); +void mt76x2_mac_write_txwi(struct mt76x2_dev *dev, struct mt76x2_txwi *txwi, + struct sk_buff *skb, struct mt76_wcid *wcid, + struct ieee80211_sta *sta); +void mt76x2_mac_wcid_setup(struct mt76x2_dev *dev, u8 idx, u8 vif_idx, u8 *mac); +int mt76x2_mac_wcid_set_key(struct mt76x2_dev *dev, u8 idx, + struct ieee80211_key_conf *key); +void mt76x2_mac_wcid_set_rate(struct mt76x2_dev *dev, struct mt76_wcid *wcid, + const struct ieee80211_tx_rate *rate); +void mt76x2_mac_wcid_set_drop(struct mt76x2_dev *dev, u8 idx, bool drop); + +int mt76x2_mac_shared_key_setup(struct mt76x2_dev *dev, u8 vif_idx, u8 key_idx, + struct ieee80211_key_conf *key); + +int mt76x2_mac_set_beacon(struct mt76x2_dev *dev, u8 vif_idx, + struct sk_buff *skb); +void mt76x2_mac_set_beacon_enable(struct mt76x2_dev *dev, u8 vif_idx, bool val); + +void mt76x2_mac_poll_tx_status(struct mt76x2_dev *dev, bool irq); +void mt76x2_mac_process_tx_status_fifo(struct mt76x2_dev *dev); + +void mt76x2_mac_work(struct work_struct *work); + +#endif diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_main.c b/drivers/net/wireless/mediatek/mt76/mt76x2_main.c new file mode 100644 index 000000000000..bf26284b9989 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_main.c @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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 "mt76x2.h" + +static int +mt76x2_start(struct ieee80211_hw *hw) +{ + struct mt76x2_dev *dev = hw->priv; + int ret; + + mutex_lock(&dev->mutex); + + ret = mt76x2_mac_start(dev); + if (ret) + goto out; + + ret = mt76x2_phy_start(dev); + if (ret) + goto out; + + ieee80211_queue_delayed_work(mt76_hw(dev), &dev->mac_work, + MT_CALIBRATE_INTERVAL); + + set_bit(MT76_STATE_RUNNING, &dev->mt76.state); + +out: + mutex_unlock(&dev->mutex); + return ret; +} + +static void +mt76x2_stop(struct ieee80211_hw *hw) +{ + struct mt76x2_dev *dev = hw->priv; + + mutex_lock(&dev->mutex); + clear_bit(MT76_STATE_RUNNING, &dev->mt76.state); + mt76x2_stop_hardware(dev); + mutex_unlock(&dev->mutex); +} + +static void +mt76x2_txq_init(struct mt76x2_dev *dev, struct ieee80211_txq *txq) +{ + struct mt76_txq *mtxq; + + if (!txq) + return; + + mtxq = (struct mt76_txq *) txq->drv_priv; + if (txq->sta) { + struct mt76x2_sta *sta; + + sta = (struct mt76x2_sta *) txq->sta->drv_priv; + mtxq->wcid = &sta->wcid; + } else { + struct mt76x2_vif *mvif; + + mvif = (struct mt76x2_vif *) txq->vif->drv_priv; + mtxq->wcid = &mvif->group_wcid; + } + + mt76_txq_init(&dev->mt76, txq); +} + +static int +mt76x2_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct mt76x2_dev *dev = hw->priv; + struct mt76x2_vif *mvif = (struct mt76x2_vif *) vif->drv_priv; + unsigned int idx = 0; + + if (vif->addr[0] & BIT(1)) + idx = 1 + (((dev->mt76.macaddr[0] ^ vif->addr[0]) >> 2) & 7); + + /* + * Client mode typically only has one configurable BSSID register, + * which is used for bssidx=0. This is linked to the MAC address. + * Since mac80211 allows changing interface types, and we cannot + * force the use of the primary MAC address for a station mode + * interface, we need some other way of configuring a per-interface + * remote BSSID. + * The hardware provides an AP-Client feature, where bssidx 0-7 are + * used for AP mode and bssidx 8-15 for client mode. + * We shift the station interface bss index by 8 to force the + * hardware to recognize the BSSID. + * The resulting bssidx mismatch for unicast frames is ignored by hw. + */ + if (vif->type == NL80211_IFTYPE_STATION) + idx += 8; + + mvif->idx = idx; + mvif->group_wcid.idx = 254 - idx; + mvif->group_wcid.hw_key_idx = -1; + mt76x2_txq_init(dev, vif->txq); + + return 0; +} + +static void +mt76x2_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct mt76x2_dev *dev = hw->priv; + + mt76_txq_remove(&dev->mt76, vif->txq); +} + +static int +mt76x2_set_channel(struct mt76x2_dev *dev, struct cfg80211_chan_def *chandef) +{ + int ret; + + mt76_set_channel(&dev->mt76); + + tasklet_disable(&dev->pre_tbtt_tasklet); + tasklet_disable(&dev->dfs_pd.dfs_tasklet); + cancel_delayed_work_sync(&dev->cal_work); + + mt76x2_mac_stop(dev, true); + ret = mt76x2_phy_set_channel(dev, chandef); + + /* channel cycle counters read-and-clear */ + mt76_rr(dev, MT_CH_IDLE); + mt76_rr(dev, MT_CH_BUSY); + + mt76x2_dfs_init_params(dev); + + mt76x2_mac_resume(dev); + tasklet_enable(&dev->dfs_pd.dfs_tasklet); + tasklet_enable(&dev->pre_tbtt_tasklet); + + return ret; +} + +static int +mt76x2_config(struct ieee80211_hw *hw, u32 changed) +{ + struct mt76x2_dev *dev = hw->priv; + int ret = 0; + + mutex_lock(&dev->mutex); + + if (changed & IEEE80211_CONF_CHANGE_MONITOR) { + if (!(hw->conf.flags & IEEE80211_CONF_MONITOR)) + dev->rxfilter |= MT_RX_FILTR_CFG_PROMISC; + else + dev->rxfilter &= ~MT_RX_FILTR_CFG_PROMISC; + + mt76_wr(dev, MT_RX_FILTR_CFG, dev->rxfilter); + } + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + dev->txpower_conf = hw->conf.power_level * 2; + + /* convert to per-chain power for 2x2 devices */ + dev->txpower_conf -= 6; + + if (test_bit(MT76_STATE_RUNNING, &dev->mt76.state)) { + mt76x2_phy_set_txpower(dev); + mt76x2_tx_set_txpwr_auto(dev, dev->txpower_conf); + } + } + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + ieee80211_stop_queues(hw); + ret = mt76x2_set_channel(dev, &hw->conf.chandef); + ieee80211_wake_queues(hw); + } + + mutex_unlock(&dev->mutex); + + return ret; +} + +static void +mt76x2_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags, + unsigned int *total_flags, u64 multicast) +{ + struct mt76x2_dev *dev = hw->priv; + u32 flags = 0; + +#define MT76_FILTER(_flag, _hw) do { \ + flags |= *total_flags & FIF_##_flag; \ + dev->rxfilter &= ~(_hw); \ + dev->rxfilter |= !(flags & FIF_##_flag) * (_hw); \ + } while (0) + + mutex_lock(&dev->mutex); + + dev->rxfilter &= ~MT_RX_FILTR_CFG_OTHER_BSS; + + MT76_FILTER(FCSFAIL, MT_RX_FILTR_CFG_CRC_ERR); + MT76_FILTER(PLCPFAIL, MT_RX_FILTR_CFG_PHY_ERR); + MT76_FILTER(CONTROL, MT_RX_FILTR_CFG_ACK | + MT_RX_FILTR_CFG_CTS | + MT_RX_FILTR_CFG_CFEND | + MT_RX_FILTR_CFG_CFACK | + MT_RX_FILTR_CFG_BA | + MT_RX_FILTR_CFG_CTRL_RSV); + MT76_FILTER(PSPOLL, MT_RX_FILTR_CFG_PSPOLL); + + *total_flags = flags; + mt76_wr(dev, MT_RX_FILTR_CFG, dev->rxfilter); + + mutex_unlock(&dev->mutex); +} + +static void +mt76x2_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, u32 changed) +{ + struct mt76x2_dev *dev = hw->priv; + struct mt76x2_vif *mvif = (struct mt76x2_vif *) vif->drv_priv; + + mutex_lock(&dev->mutex); + + if (changed & BSS_CHANGED_BSSID) + mt76x2_mac_set_bssid(dev, mvif->idx, info->bssid); + + if (changed & BSS_CHANGED_BEACON_INT) + mt76_rmw_field(dev, MT_BEACON_TIME_CFG, + MT_BEACON_TIME_CFG_INTVAL, + info->beacon_int << 4); + + if (changed & BSS_CHANGED_BEACON_ENABLED) { + tasklet_disable(&dev->pre_tbtt_tasklet); + mt76x2_mac_set_beacon_enable(dev, mvif->idx, + info->enable_beacon); + tasklet_enable(&dev->pre_tbtt_tasklet); + } + + if (changed & BSS_CHANGED_ERP_SLOT) { + int slottime = info->use_short_slot ? 9 : 20; + + dev->slottime = slottime; + mt76_rmw_field(dev, MT_BKOFF_SLOT_CFG, + MT_BKOFF_SLOT_CFG_SLOTTIME, slottime); + } + + mutex_unlock(&dev->mutex); +} + +static int +mt76x2_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt76x2_dev *dev = hw->priv; + struct mt76x2_sta *msta = (struct mt76x2_sta *) sta->drv_priv; + struct mt76x2_vif *mvif = (struct mt76x2_vif *) vif->drv_priv; + int ret = 0; + int idx = 0; + int i; + + mutex_lock(&dev->mutex); + + idx = mt76_wcid_alloc(dev->wcid_mask, ARRAY_SIZE(dev->wcid)); + if (idx < 0) { + ret = -ENOSPC; + goto out; + } + + msta->vif = mvif; + msta->wcid.sta = 1; + msta->wcid.idx = idx; + msta->wcid.hw_key_idx = -1; + mt76x2_mac_wcid_setup(dev, idx, mvif->idx, sta->addr); + mt76x2_mac_wcid_set_drop(dev, idx, false); + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) + mt76x2_txq_init(dev, sta->txq[i]); + + rcu_assign_pointer(dev->wcid[idx], &msta->wcid); + +out: + mutex_unlock(&dev->mutex); + + return ret; +} + +static int +mt76x2_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt76x2_dev *dev = hw->priv; + struct mt76x2_sta *msta = (struct mt76x2_sta *) sta->drv_priv; + int idx = msta->wcid.idx; + int i; + + mutex_lock(&dev->mutex); + rcu_assign_pointer(dev->wcid[idx], NULL); + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) + mt76_txq_remove(&dev->mt76, sta->txq[i]); + mt76x2_mac_wcid_set_drop(dev, idx, true); + mt76_wcid_free(dev->wcid_mask, idx); + mt76x2_mac_wcid_setup(dev, idx, 0, NULL); + mutex_unlock(&dev->mutex); + + return 0; +} + +static void +mt76x2_sta_notify(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, struct ieee80211_sta *sta) +{ + struct mt76x2_sta *msta = (struct mt76x2_sta *) sta->drv_priv; + struct mt76x2_dev *dev = hw->priv; + int idx = msta->wcid.idx; + + switch (cmd) { + case STA_NOTIFY_SLEEP: + mt76x2_mac_wcid_set_drop(dev, idx, true); + mt76_stop_tx_queues(&dev->mt76, sta, true); + break; + case STA_NOTIFY_AWAKE: + mt76x2_mac_wcid_set_drop(dev, idx, false); + break; + } +} + +static int +mt76x2_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct mt76x2_dev *dev = hw->priv; + struct mt76x2_vif *mvif = (struct mt76x2_vif *) vif->drv_priv; + struct mt76x2_sta *msta; + struct mt76_wcid *wcid; + int idx = key->keyidx; + int ret; + + /* + * The hardware does not support per-STA RX GTK, fall back + * to software mode for these. + */ + if ((vif->type == NL80211_IFTYPE_ADHOC || + vif->type == NL80211_IFTYPE_MESH_POINT) && + (key->cipher == WLAN_CIPHER_SUITE_TKIP || + key->cipher == WLAN_CIPHER_SUITE_CCMP) && + !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) + return -EOPNOTSUPP; + + msta = sta ? (struct mt76x2_sta *) sta->drv_priv : NULL; + wcid = msta ? &msta->wcid : &mvif->group_wcid; + + if (cmd == SET_KEY) { + key->hw_key_idx = wcid->idx; + wcid->hw_key_idx = idx; + if (key->flags & IEEE80211_KEY_FLAG_RX_MGMT) { + key->flags |= IEEE80211_KEY_FLAG_SW_MGMT_TX; + wcid->sw_iv = true; + } + } else { + if (idx == wcid->hw_key_idx) { + wcid->hw_key_idx = -1; + wcid->sw_iv = true; + } + + key = NULL; + } + mt76_wcid_key_setup(&dev->mt76, wcid, key); + + if (!msta) { + if (key || wcid->hw_key_idx == idx) { + ret = mt76x2_mac_wcid_set_key(dev, wcid->idx, key); + if (ret) + return ret; + } + + return mt76x2_mac_shared_key_setup(dev, mvif->idx, idx, key); + } + + return mt76x2_mac_wcid_set_key(dev, msta->wcid.idx, key); +} + +static int +mt76x2_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct mt76x2_dev *dev = hw->priv; + u8 cw_min = 5, cw_max = 10, qid; + u32 val; + + qid = dev->mt76.q_tx[queue].hw_idx; + + if (params->cw_min) + cw_min = fls(params->cw_min); + if (params->cw_max) + cw_max = fls(params->cw_max); + + val = FIELD_PREP(MT_EDCA_CFG_TXOP, params->txop) | + FIELD_PREP(MT_EDCA_CFG_AIFSN, params->aifs) | + FIELD_PREP(MT_EDCA_CFG_CWMIN, cw_min) | + FIELD_PREP(MT_EDCA_CFG_CWMAX, cw_max); + mt76_wr(dev, MT_EDCA_CFG_AC(qid), val); + + val = mt76_rr(dev, MT_WMM_TXOP(qid)); + val &= ~(MT_WMM_TXOP_MASK << MT_WMM_TXOP_SHIFT(qid)); + val |= params->txop << MT_WMM_TXOP_SHIFT(qid); + mt76_wr(dev, MT_WMM_TXOP(qid), val); + + val = mt76_rr(dev, MT_WMM_AIFSN); + val &= ~(MT_WMM_AIFSN_MASK << MT_WMM_AIFSN_SHIFT(qid)); + val |= params->aifs << MT_WMM_AIFSN_SHIFT(qid); + mt76_wr(dev, MT_WMM_AIFSN, val); + + val = mt76_rr(dev, MT_WMM_CWMIN); + val &= ~(MT_WMM_CWMIN_MASK << MT_WMM_CWMIN_SHIFT(qid)); + val |= cw_min << MT_WMM_CWMIN_SHIFT(qid); + mt76_wr(dev, MT_WMM_CWMIN, val); + + val = mt76_rr(dev, MT_WMM_CWMAX); + val &= ~(MT_WMM_CWMAX_MASK << MT_WMM_CWMAX_SHIFT(qid)); + val |= cw_max << MT_WMM_CWMAX_SHIFT(qid); + mt76_wr(dev, MT_WMM_CWMAX, val); + + return 0; +} + +static void +mt76x2_sw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + const u8 *mac) +{ + struct mt76x2_dev *dev = hw->priv; + + tasklet_disable(&dev->pre_tbtt_tasklet); + set_bit(MT76_SCANNING, &dev->mt76.state); +} + +static void +mt76x2_sw_scan_complete(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct mt76x2_dev *dev = hw->priv; + + clear_bit(MT76_SCANNING, &dev->mt76.state); + tasklet_enable(&dev->pre_tbtt_tasklet); + mt76_txq_schedule_all(&dev->mt76); +} + +static void +mt76x2_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u32 queues, bool drop) +{ +} + +static int +mt76x2_get_txpower(struct ieee80211_hw *hw, struct ieee80211_vif *vif, int *dbm) +{ + struct mt76x2_dev *dev = hw->priv; + + *dbm = dev->txpower_cur / 2; + + /* convert from per-chain power to combined output on 2x2 devices */ + *dbm += 3; + + return 0; +} + +static int +mt76x2_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_ampdu_params *params) +{ + enum ieee80211_ampdu_mlme_action action = params->action; + struct ieee80211_sta *sta = params->sta; + struct mt76x2_dev *dev = hw->priv; + struct mt76x2_sta *msta = (struct mt76x2_sta *) sta->drv_priv; + struct ieee80211_txq *txq = sta->txq[params->tid]; + u16 tid = params->tid; + u16 *ssn = ¶ms->ssn; + struct mt76_txq *mtxq; + + if (!txq) + return -EINVAL; + + mtxq = (struct mt76_txq *)txq->drv_priv; + + switch (action) { + case IEEE80211_AMPDU_RX_START: + mt76_rx_aggr_start(&dev->mt76, &msta->wcid, tid, *ssn, params->buf_size); + mt76_set(dev, MT_WCID_ADDR(msta->wcid.idx) + 4, BIT(16 + tid)); + break; + case IEEE80211_AMPDU_RX_STOP: + mt76_rx_aggr_stop(&dev->mt76, &msta->wcid, tid); + mt76_clear(dev, MT_WCID_ADDR(msta->wcid.idx) + 4, + BIT(16 + tid)); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + mtxq->aggr = true; + mtxq->send_bar = false; + ieee80211_send_bar(vif, sta->addr, tid, mtxq->agg_ssn); + break; + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + mtxq->aggr = false; + ieee80211_send_bar(vif, sta->addr, tid, mtxq->agg_ssn); + break; + case IEEE80211_AMPDU_TX_START: + mtxq->agg_ssn = *ssn << 4; + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + mtxq->aggr = false; + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + } + + return 0; +} + +static void +mt76x2_sta_rate_tbl_update(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt76x2_dev *dev = hw->priv; + struct mt76x2_sta *msta = (struct mt76x2_sta *) sta->drv_priv; + struct ieee80211_sta_rates *rates = rcu_dereference(sta->rates); + struct ieee80211_tx_rate rate = {}; + + if (!rates) + return; + + rate.idx = rates->rate[0].idx; + rate.flags = rates->rate[0].flags; + mt76x2_mac_wcid_set_rate(dev, &msta->wcid, &rate); + msta->wcid.max_txpwr_adj = mt76x2_tx_get_max_txpwr_adj(dev, &rate); +} + +static void mt76x2_set_coverage_class(struct ieee80211_hw *hw, + s16 coverage_class) +{ + struct mt76x2_dev *dev = hw->priv; + + mutex_lock(&dev->mutex); + dev->coverage_class = coverage_class; + mt76x2_set_tx_ackto(dev); + mutex_unlock(&dev->mutex); +} + +const struct ieee80211_ops mt76x2_ops = { + .tx = mt76x2_tx, + .start = mt76x2_start, + .stop = mt76x2_stop, + .add_interface = mt76x2_add_interface, + .remove_interface = mt76x2_remove_interface, + .config = mt76x2_config, + .configure_filter = mt76x2_configure_filter, + .bss_info_changed = mt76x2_bss_info_changed, + .sta_add = mt76x2_sta_add, + .sta_remove = mt76x2_sta_remove, + .sta_notify = mt76x2_sta_notify, + .set_key = mt76x2_set_key, + .conf_tx = mt76x2_conf_tx, + .sw_scan_start = mt76x2_sw_scan, + .sw_scan_complete = mt76x2_sw_scan_complete, + .flush = mt76x2_flush, + .ampdu_action = mt76x2_ampdu_action, + .get_txpower = mt76x2_get_txpower, + .wake_tx_queue = mt76_wake_tx_queue, + .sta_rate_tbl_update = mt76x2_sta_rate_tbl_update, + .release_buffered_frames = mt76_release_buffered_frames, + .set_coverage_class = mt76x2_set_coverage_class, + .get_survey = mt76_get_survey, +}; + diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76x2_mcu.c new file mode 100644 index 000000000000..15820b11f9db --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_mcu.c @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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/kernel.h> +#include <linux/firmware.h> +#include <linux/delay.h> + +#include "mt76x2.h" +#include "mt76x2_mcu.h" +#include "mt76x2_dma.h" +#include "mt76x2_eeprom.h" + +struct mt76x2_fw_header { + __le32 ilm_len; + __le32 dlm_len; + __le16 build_ver; + __le16 fw_ver; + u8 pad[4]; + char build_time[16]; +}; + +struct mt76x2_patch_header { + char build_time[16]; + char platform[4]; + char hw_version[4]; + char patch_version[4]; + u8 pad[2]; +}; + +static struct sk_buff *mt76x2_mcu_msg_alloc(const void *data, int len) +{ + struct sk_buff *skb; + + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) + return NULL; + memcpy(skb_put(skb, len), data, len); + + return skb; +} + +static struct sk_buff * +mt76x2_mcu_get_response(struct mt76x2_dev *dev, unsigned long expires) +{ + unsigned long timeout; + + if (!time_is_after_jiffies(expires)) + return NULL; + + timeout = expires - jiffies; + wait_event_timeout(dev->mcu.wait, !skb_queue_empty(&dev->mcu.res_q), + timeout); + return skb_dequeue(&dev->mcu.res_q); +} + +static int +mt76x2_mcu_msg_send(struct mt76x2_dev *dev, struct sk_buff *skb, + enum mcu_cmd cmd) +{ + unsigned long expires = jiffies + HZ; + int ret; + u8 seq; + + if (!skb) + return -EINVAL; + + mutex_lock(&dev->mcu.mutex); + + seq = ++dev->mcu.msg_seq & 0xf; + if (!seq) + seq = ++dev->mcu.msg_seq & 0xf; + + ret = mt76x2_tx_queue_mcu(dev, MT_TXQ_MCU, skb, cmd, seq); + if (ret) + goto out; + + while (1) { + u32 *rxfce; + bool check_seq = false; + + skb = mt76x2_mcu_get_response(dev, expires); + if (!skb) { + dev_err(dev->mt76.dev, + "MCU message %d (seq %d) timed out\n", cmd, + seq); + ret = -ETIMEDOUT; + break; + } + + rxfce = (u32 *) skb->cb; + + if (seq == FIELD_GET(MT_RX_FCE_INFO_CMD_SEQ, *rxfce)) + check_seq = true; + + dev_kfree_skb(skb); + if (check_seq) + break; + } + +out: + mutex_unlock(&dev->mcu.mutex); + + return ret; +} + +static int +mt76pci_load_rom_patch(struct mt76x2_dev *dev) +{ + const struct firmware *fw = NULL; + struct mt76x2_patch_header *hdr; + bool rom_protect = !is_mt7612(dev); + int len, ret = 0; + __le32 *cur; + u32 patch_mask, patch_reg; + + if (rom_protect && !mt76_poll(dev, MT_MCU_SEMAPHORE_03, 1, 1, 600)) { + dev_err(dev->mt76.dev, + "Could not get hardware semaphore for ROM PATCH\n"); + return -ETIMEDOUT; + } + + if (mt76xx_rev(dev) >= MT76XX_REV_E3) { + patch_mask = BIT(0); + patch_reg = MT_MCU_CLOCK_CTL; + } else { + patch_mask = BIT(1); + patch_reg = MT_MCU_COM_REG0; + } + + if (rom_protect && (mt76_rr(dev, patch_reg) & patch_mask)) { + dev_info(dev->mt76.dev, "ROM patch already applied\n"); + goto out; + } + + ret = request_firmware(&fw, MT7662_ROM_PATCH, dev->mt76.dev); + if (ret) + goto out; + + if (!fw || !fw->data || fw->size <= sizeof(*hdr)) { + ret = -EIO; + dev_err(dev->mt76.dev, "Failed to load firmware\n"); + goto out; + } + + hdr = (struct mt76x2_patch_header *) fw->data; + dev_info(dev->mt76.dev, "ROM patch build: %.15s\n", hdr->build_time); + + mt76_wr(dev, MT_MCU_PCIE_REMAP_BASE4, MT_MCU_ROM_PATCH_OFFSET); + + cur = (__le32 *) (fw->data + sizeof(*hdr)); + len = fw->size - sizeof(*hdr); + mt76_wr_copy(dev, MT_MCU_ROM_PATCH_ADDR, cur, len); + + mt76_wr(dev, MT_MCU_PCIE_REMAP_BASE4, 0); + + /* Trigger ROM */ + mt76_wr(dev, MT_MCU_INT_LEVEL, 4); + + if (!mt76_poll_msec(dev, patch_reg, patch_mask, patch_mask, 2000)) { + dev_err(dev->mt76.dev, "Failed to load ROM patch\n"); + ret = -ETIMEDOUT; + } + +out: + /* release semaphore */ + if (rom_protect) + mt76_wr(dev, MT_MCU_SEMAPHORE_03, 1); + release_firmware(fw); + return ret; +} + +static int +mt76pci_load_firmware(struct mt76x2_dev *dev) +{ + const struct firmware *fw; + const struct mt76x2_fw_header *hdr; + int i, len, ret; + __le32 *cur; + u32 offset, val; + + ret = request_firmware(&fw, MT7662_FIRMWARE, dev->mt76.dev); + if (ret) + return ret; + + if (!fw || !fw->data || fw->size < sizeof(*hdr)) + goto error; + + hdr = (const struct mt76x2_fw_header *) fw->data; + + len = sizeof(*hdr); + len += le32_to_cpu(hdr->ilm_len); + len += le32_to_cpu(hdr->dlm_len); + + if (fw->size != len) + goto error; + + val = le16_to_cpu(hdr->fw_ver); + dev_info(dev->mt76.dev, "Firmware Version: %d.%d.%02d\n", + (val >> 12) & 0xf, (val >> 8) & 0xf, val & 0xf); + + val = le16_to_cpu(hdr->build_ver); + dev_info(dev->mt76.dev, "Build: %x\n", val); + dev_info(dev->mt76.dev, "Build Time: %.16s\n", hdr->build_time); + + cur = (__le32 *) (fw->data + sizeof(*hdr)); + len = le32_to_cpu(hdr->ilm_len); + + mt76_wr(dev, MT_MCU_PCIE_REMAP_BASE4, MT_MCU_ILM_OFFSET); + mt76_wr_copy(dev, MT_MCU_ILM_ADDR, cur, len); + + cur += len / sizeof(*cur); + len = le32_to_cpu(hdr->dlm_len); + + if (mt76xx_rev(dev) >= MT76XX_REV_E3) + offset = MT_MCU_DLM_ADDR_E3; + else + offset = MT_MCU_DLM_ADDR; + + mt76_wr(dev, MT_MCU_PCIE_REMAP_BASE4, MT_MCU_DLM_OFFSET); + mt76_wr_copy(dev, offset, cur, len); + + mt76_wr(dev, MT_MCU_PCIE_REMAP_BASE4, 0); + + val = mt76x2_eeprom_get(dev, MT_EE_NIC_CONF_2); + if (FIELD_GET(MT_EE_NIC_CONF_2_XTAL_OPTION, val) == 1) + mt76_set(dev, MT_MCU_COM_REG0, BIT(30)); + + /* trigger firmware */ + mt76_wr(dev, MT_MCU_INT_LEVEL, 2); + for (i = 200; i > 0; i--) { + val = mt76_rr(dev, MT_MCU_COM_REG0); + + if (val & 1) + break; + + msleep(10); + } + + if (!i) { + dev_err(dev->mt76.dev, "Firmware failed to start\n"); + release_firmware(fw); + return -ETIMEDOUT; + } + + dev_info(dev->mt76.dev, "Firmware running!\n"); + + release_firmware(fw); + + return ret; + +error: + dev_err(dev->mt76.dev, "Invalid firmware\n"); + release_firmware(fw); + return -ENOENT; +} + +static int +mt76x2_mcu_function_select(struct mt76x2_dev *dev, enum mcu_function func, + u32 val) +{ + struct sk_buff *skb; + struct { + __le32 id; + __le32 value; + } __packed __aligned(4) msg = { + .id = cpu_to_le32(func), + .value = cpu_to_le32(val), + }; + + skb = mt76x2_mcu_msg_alloc(&msg, sizeof(msg)); + return mt76x2_mcu_msg_send(dev, skb, CMD_FUN_SET_OP); +} + +int mt76x2_mcu_load_cr(struct mt76x2_dev *dev, u8 type, u8 temp_level, + u8 channel) +{ + struct sk_buff *skb; + struct { + u8 cr_mode; + u8 temp; + u8 ch; + u8 _pad0; + + __le32 cfg; + } __packed __aligned(4) msg = { + .cr_mode = type, + .temp = temp_level, + .ch = channel, + }; + u32 val; + + val = BIT(31); + val |= (mt76x2_eeprom_get(dev, MT_EE_NIC_CONF_0) >> 8) & 0x00ff; + val |= (mt76x2_eeprom_get(dev, MT_EE_NIC_CONF_1) << 8) & 0xff00; + msg.cfg = cpu_to_le32(val); + + /* first set the channel without the extension channel info */ + skb = mt76x2_mcu_msg_alloc(&msg, sizeof(msg)); + return mt76x2_mcu_msg_send(dev, skb, CMD_LOAD_CR); +} + +int mt76x2_mcu_set_channel(struct mt76x2_dev *dev, u8 channel, u8 bw, + u8 bw_index, bool scan) +{ + struct sk_buff *skb; + struct { + u8 idx; + u8 scan; + u8 bw; + u8 _pad0; + + __le16 chainmask; + u8 ext_chan; + u8 _pad1; + + } __packed __aligned(4) msg = { + .idx = channel, + .scan = scan, + .bw = bw, + .chainmask = cpu_to_le16(dev->chainmask), + }; + + /* first set the channel without the extension channel info */ + skb = mt76x2_mcu_msg_alloc(&msg, sizeof(msg)); + mt76x2_mcu_msg_send(dev, skb, CMD_SWITCH_CHANNEL_OP); + + usleep_range(5000, 10000); + + msg.ext_chan = 0xe0 + bw_index; + skb = mt76x2_mcu_msg_alloc(&msg, sizeof(msg)); + return mt76x2_mcu_msg_send(dev, skb, CMD_SWITCH_CHANNEL_OP); +} + +int mt76x2_mcu_set_radio_state(struct mt76x2_dev *dev, bool on) +{ + struct sk_buff *skb; + struct { + __le32 mode; + __le32 level; + } __packed __aligned(4) msg = { + .mode = cpu_to_le32(on ? RADIO_ON : RADIO_OFF), + .level = cpu_to_le32(0), + }; + + skb = mt76x2_mcu_msg_alloc(&msg, sizeof(msg)); + return mt76x2_mcu_msg_send(dev, skb, CMD_POWER_SAVING_OP); +} + +int mt76x2_mcu_calibrate(struct mt76x2_dev *dev, enum mcu_calibration type, + u32 param) +{ + struct sk_buff *skb; + struct { + __le32 id; + __le32 value; + } __packed __aligned(4) msg = { + .id = cpu_to_le32(type), + .value = cpu_to_le32(param), + }; + int ret; + + mt76_clear(dev, MT_MCU_COM_REG0, BIT(31)); + + skb = mt76x2_mcu_msg_alloc(&msg, sizeof(msg)); + ret = mt76x2_mcu_msg_send(dev, skb, CMD_CALIBRATION_OP); + if (ret) + return ret; + + if (WARN_ON(!mt76_poll_msec(dev, MT_MCU_COM_REG0, + BIT(31), BIT(31), 100))) + return -ETIMEDOUT; + + return 0; +} + +int mt76x2_mcu_tssi_comp(struct mt76x2_dev *dev, + struct mt76x2_tssi_comp *tssi_data) +{ + struct sk_buff *skb; + struct { + __le32 id; + struct mt76x2_tssi_comp data; + } __packed __aligned(4) msg = { + .id = cpu_to_le32(MCU_CAL_TSSI_COMP), + .data = *tssi_data, + }; + + skb = mt76x2_mcu_msg_alloc(&msg, sizeof(msg)); + return mt76x2_mcu_msg_send(dev, skb, CMD_CALIBRATION_OP); +} + +int mt76x2_mcu_init_gain(struct mt76x2_dev *dev, u8 channel, u32 gain, + bool force) +{ + struct sk_buff *skb; + struct { + __le32 channel; + __le32 gain_val; + } __packed __aligned(4) msg = { + .channel = cpu_to_le32(channel), + .gain_val = cpu_to_le32(gain), + }; + + if (force) + msg.channel |= cpu_to_le32(BIT(31)); + + skb = mt76x2_mcu_msg_alloc(&msg, sizeof(msg)); + return mt76x2_mcu_msg_send(dev, skb, CMD_INIT_GAIN_OP); +} + +int mt76x2_mcu_init(struct mt76x2_dev *dev) +{ + int ret; + + mutex_init(&dev->mcu.mutex); + + ret = mt76pci_load_rom_patch(dev); + if (ret) + return ret; + + ret = mt76pci_load_firmware(dev); + if (ret) + return ret; + + mt76x2_mcu_function_select(dev, Q_SELECT, 1); + return 0; +} + +int mt76x2_mcu_cleanup(struct mt76x2_dev *dev) +{ + struct sk_buff *skb; + + mt76_wr(dev, MT_MCU_INT_LEVEL, 1); + usleep_range(20000, 30000); + + while ((skb = skb_dequeue(&dev->mcu.res_q)) != NULL) + dev_kfree_skb(skb); + + return 0; +} diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76x2_mcu.h new file mode 100644 index 000000000000..d7a7e83262ce --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_mcu.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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. + */ + +#ifndef __MT76x2_MCU_H +#define __MT76x2_MCU_H + +/* Register definitions */ +#define MT_MCU_CPU_CTL 0x0704 +#define MT_MCU_CLOCK_CTL 0x0708 +#define MT_MCU_RESET_CTL 0x070C +#define MT_MCU_INT_LEVEL 0x0718 +#define MT_MCU_COM_REG0 0x0730 +#define MT_MCU_COM_REG1 0x0734 +#define MT_MCU_COM_REG2 0x0738 +#define MT_MCU_COM_REG3 0x073C +#define MT_MCU_PCIE_REMAP_BASE1 0x0740 +#define MT_MCU_PCIE_REMAP_BASE2 0x0744 +#define MT_MCU_PCIE_REMAP_BASE3 0x0748 +#define MT_MCU_PCIE_REMAP_BASE4 0x074C + +#define MT_LED_CTRL 0x0770 +#define MT_LED_CTRL_REPLAY(_n) BIT(0 + (8 * (_n))) +#define MT_LED_CTRL_POLARITY(_n) BIT(1 + (8 * (_n))) +#define MT_LED_CTRL_TX_BLINK_MODE(_n) BIT(2 + (8 * (_n))) +#define MT_LED_CTRL_KICK(_n) BIT(7 + (8 * (_n))) + +#define MT_LED_TX_BLINK_0 0x0774 +#define MT_LED_TX_BLINK_1 0x0778 + +#define MT_LED_S0_BASE 0x077C +#define MT_LED_S0(_n) (MT_LED_S0_BASE + 8 * (_n)) +#define MT_LED_S1_BASE 0x0780 +#define MT_LED_S1(_n) (MT_LED_S1_BASE + 8 * (_n)) +#define MT_LED_STATUS_OFF_MASK GENMASK(31, 24) +#define MT_LED_STATUS_OFF(_v) (((_v) << __ffs(MT_LED_STATUS_OFF_MASK)) & \ + MT_LED_STATUS_OFF_MASK) +#define MT_LED_STATUS_ON_MASK GENMASK(23, 16) +#define MT_LED_STATUS_ON(_v) (((_v) << __ffs(MT_LED_STATUS_ON_MASK)) & \ + MT_LED_STATUS_ON_MASK) +#define MT_LED_STATUS_DURATION_MASK GENMASK(15, 8) +#define MT_LED_STATUS_DURATION(_v) (((_v) << __ffs(MT_LED_STATUS_DURATION_MASK)) & \ + MT_LED_STATUS_DURATION_MASK) + +#define MT_MCU_SEMAPHORE_00 0x07B0 +#define MT_MCU_SEMAPHORE_01 0x07B4 +#define MT_MCU_SEMAPHORE_02 0x07B8 +#define MT_MCU_SEMAPHORE_03 0x07BC + +#define MT_MCU_ROM_PATCH_OFFSET 0x80000 +#define MT_MCU_ROM_PATCH_ADDR 0x90000 + +#define MT_MCU_ILM_OFFSET 0x80000 +#define MT_MCU_ILM_ADDR 0x80000 + +#define MT_MCU_DLM_OFFSET 0x100000 +#define MT_MCU_DLM_ADDR 0x90000 +#define MT_MCU_DLM_ADDR_E3 0x90800 + +enum mcu_cmd { + CMD_FUN_SET_OP = 1, + CMD_LOAD_CR = 2, + CMD_INIT_GAIN_OP = 3, + CMD_DYNC_VGA_OP = 6, + CMD_TDLS_CH_SW = 7, + CMD_BURST_WRITE = 8, + CMD_READ_MODIFY_WRITE = 9, + CMD_RANDOM_READ = 10, + CMD_BURST_READ = 11, + CMD_RANDOM_WRITE = 12, + CMD_LED_MODE_OP = 16, + CMD_POWER_SAVING_OP = 20, + CMD_WOW_CONFIG = 21, + CMD_WOW_QUERY = 22, + CMD_WOW_FEATURE = 24, + CMD_CARRIER_DETECT_OP = 28, + CMD_RADOR_DETECT_OP = 29, + CMD_SWITCH_CHANNEL_OP = 30, + CMD_CALIBRATION_OP = 31, + CMD_BEACON_OP = 32, + CMD_ANTENNA_OP = 33, +}; + +enum mcu_function { + Q_SELECT = 1, + BW_SETTING = 2, + USB2_SW_DISCONNECT = 2, + USB3_SW_DISCONNECT = 3, + LOG_FW_DEBUG_MSG = 4, + GET_FW_VERSION = 5, +}; + +enum mcu_power_mode { + RADIO_OFF = 0x30, + RADIO_ON = 0x31, + RADIO_OFF_AUTO_WAKEUP = 0x32, + RADIO_OFF_ADVANCE = 0x33, + RADIO_ON_ADVANCE = 0x34, +}; + +enum mcu_calibration { + MCU_CAL_R = 1, + MCU_CAL_TEMP_SENSOR, + MCU_CAL_RXDCOC, + MCU_CAL_RC, + MCU_CAL_SX_LOGEN, + MCU_CAL_LC, + MCU_CAL_TX_LOFT, + MCU_CAL_TXIQ, + MCU_CAL_TSSI, + MCU_CAL_TSSI_COMP, + MCU_CAL_DPD, + MCU_CAL_RXIQC_FI, + MCU_CAL_RXIQC_FD, + MCU_CAL_PWRON, + MCU_CAL_TX_SHAPING, +}; + +enum mt76x2_mcu_cr_mode { + MT_RF_CR, + MT_BBP_CR, + MT_RF_BBP_CR, + MT_HL_TEMP_CR_UPDATE, +}; + +struct mt76x2_tssi_comp { + u8 pa_mode; + u8 cal_mode; + u16 pad; + + u8 slope0; + u8 slope1; + u8 offset0; + u8 offset1; +} __packed __aligned(4); + +int mt76x2_mcu_calibrate(struct mt76x2_dev *dev, enum mcu_calibration type, + u32 param); +int mt76x2_mcu_tssi_comp(struct mt76x2_dev *dev, struct mt76x2_tssi_comp *tssi_data); +int mt76x2_mcu_init_gain(struct mt76x2_dev *dev, u8 channel, u32 gain, + bool force); + +#endif diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_pci.c b/drivers/net/wireless/mediatek/mt76/mt76x2_pci.c new file mode 100644 index 000000000000..e66f047ea448 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_pci.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> + +#include "mt76x2.h" +#include "mt76x2_trace.h" + +static const struct pci_device_id mt76pci_device_table[] = { + { PCI_DEVICE(0x14c3, 0x7662) }, + { PCI_DEVICE(0x14c3, 0x7612) }, + { PCI_DEVICE(0x14c3, 0x7602) }, + { }, +}; + +static int +mt76pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct mt76x2_dev *dev; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + ret = pcim_iomap_regions(pdev, BIT(0), pci_name(pdev)); + if (ret) + return ret; + + pci_set_master(pdev); + + ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + dev = mt76x2_alloc_device(&pdev->dev); + if (!dev) + return -ENOMEM; + + mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]); + + dev->mt76.rev = mt76_rr(dev, MT_ASIC_VERSION); + dev_info(dev->mt76.dev, "ASIC revision: %08x\n", dev->mt76.rev); + + ret = devm_request_irq(dev->mt76.dev, pdev->irq, mt76x2_irq_handler, + IRQF_SHARED, KBUILD_MODNAME, dev); + if (ret) + goto error; + + ret = mt76x2_register_device(dev); + if (ret) + goto error; + + /* Fix up ASPM configuration */ + + /* RG_SSUSB_G1_CDR_BIR_LTR = 0x9 */ + mt76_rmw_field(dev, 0x15a10, 0x1f << 16, 0x9); + + /* RG_SSUSB_G1_CDR_BIC_LTR = 0xf */ + mt76_rmw_field(dev, 0x15a0c, 0xf << 28, 0xf); + + /* RG_SSUSB_CDR_BR_PE1D = 0x3 */ + mt76_rmw_field(dev, 0x15c58, 0x3 << 6, 0x3); + + return 0; + +error: + ieee80211_free_hw(mt76_hw(dev)); + return ret; +} + +static void +mt76pci_remove(struct pci_dev *pdev) +{ + struct mt76_dev *mdev = pci_get_drvdata(pdev); + struct mt76x2_dev *dev = container_of(mdev, struct mt76x2_dev, mt76); + + mt76_unregister_device(mdev); + mt76x2_cleanup(dev); + ieee80211_free_hw(mdev->hw); +} + +MODULE_DEVICE_TABLE(pci, mt76pci_device_table); +MODULE_FIRMWARE(MT7662_FIRMWARE); +MODULE_FIRMWARE(MT7662_ROM_PATCH); +MODULE_LICENSE("Dual BSD/GPL"); + +static struct pci_driver mt76pci_driver = { + .name = KBUILD_MODNAME, + .id_table = mt76pci_device_table, + .probe = mt76pci_probe, + .remove = mt76pci_remove, +}; + +module_pci_driver(mt76pci_driver); diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_phy.c b/drivers/net/wireless/mediatek/mt76/mt76x2_phy.c new file mode 100644 index 000000000000..5b742749d5de --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_phy.c @@ -0,0 +1,740 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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/delay.h> +#include "mt76x2.h" +#include "mt76x2_mcu.h" +#include "mt76x2_eeprom.h" + +static void +mt76x2_adjust_high_lna_gain(struct mt76x2_dev *dev, int reg, s8 offset) +{ + s8 gain; + + gain = FIELD_GET(MT_BBP_AGC_LNA_HIGH_GAIN, mt76_rr(dev, MT_BBP(AGC, reg))); + gain -= offset / 2; + mt76_rmw_field(dev, MT_BBP(AGC, reg), MT_BBP_AGC_LNA_HIGH_GAIN, gain); +} + +static void +mt76x2_adjust_agc_gain(struct mt76x2_dev *dev, int reg, s8 offset) +{ + s8 gain; + + gain = FIELD_GET(MT_BBP_AGC_GAIN, mt76_rr(dev, MT_BBP(AGC, reg))); + gain += offset; + mt76_rmw_field(dev, MT_BBP(AGC, reg), MT_BBP_AGC_GAIN, gain); +} + +static void +mt76x2_apply_gain_adj(struct mt76x2_dev *dev) +{ + s8 *gain_adj = dev->cal.rx.high_gain; + + mt76x2_adjust_high_lna_gain(dev, 4, gain_adj[0]); + mt76x2_adjust_high_lna_gain(dev, 5, gain_adj[1]); + + mt76x2_adjust_agc_gain(dev, 8, gain_adj[0]); + mt76x2_adjust_agc_gain(dev, 9, gain_adj[1]); +} + +static u32 +mt76x2_tx_power_mask(u8 v1, u8 v2, u8 v3, u8 v4) +{ + u32 val = 0; + + val |= (v1 & (BIT(6) - 1)) << 0; + val |= (v2 & (BIT(6) - 1)) << 8; + val |= (v3 & (BIT(6) - 1)) << 16; + val |= (v4 & (BIT(6) - 1)) << 24; + return val; +} + +int mt76x2_phy_get_rssi(struct mt76x2_dev *dev, s8 rssi, int chain) +{ + struct mt76x2_rx_freq_cal *cal = &dev->cal.rx; + + rssi += cal->rssi_offset[chain]; + rssi -= cal->lna_gain; + + return rssi; +} + +static u8 +mt76x2_txpower_check(int value) +{ + if (value < 0) + return 0; + if (value > 0x2f) + return 0x2f; + return value; +} + +static void +mt76x2_add_rate_power_offset(struct mt76_rate_power *r, int offset) +{ + int i; + + for (i = 0; i < sizeof(r->all); i++) + r->all[i] += offset; +} + +static void +mt76x2_limit_rate_power(struct mt76_rate_power *r, int limit) +{ + int i; + + for (i = 0; i < sizeof(r->all); i++) + if (r->all[i] > limit) + r->all[i] = limit; +} + +void mt76x2_phy_set_txpower(struct mt76x2_dev *dev) +{ + enum nl80211_chan_width width = dev->mt76.chandef.width; + struct ieee80211_channel *chan = dev->mt76.chandef.chan; + struct mt76x2_tx_power_info txp; + int txp_0, txp_1, delta = 0; + struct mt76_rate_power t = {}; + + mt76x2_get_power_info(dev, &txp, chan); + + if (width == NL80211_CHAN_WIDTH_40) + delta = txp.delta_bw40; + else if (width == NL80211_CHAN_WIDTH_80) + delta = txp.delta_bw80; + + if (txp.target_power > dev->txpower_conf) + delta -= txp.target_power - dev->txpower_conf; + + mt76x2_get_rate_power(dev, &t, chan); + mt76x2_add_rate_power_offset(&t, txp.chain[0].target_power + + txp.chain[0].delta); + mt76x2_limit_rate_power(&t, dev->txpower_conf); + dev->txpower_cur = mt76x2_get_max_rate_power(&t); + mt76x2_add_rate_power_offset(&t, -(txp.chain[0].target_power + + txp.chain[0].delta + delta)); + dev->target_power = txp.chain[0].target_power; + dev->target_power_delta[0] = txp.chain[0].delta + delta; + dev->target_power_delta[1] = txp.chain[1].delta + delta; + dev->rate_power = t; + + txp_0 = mt76x2_txpower_check(txp.chain[0].target_power + + txp.chain[0].delta + delta); + + txp_1 = mt76x2_txpower_check(txp.chain[1].target_power + + txp.chain[1].delta + delta); + + mt76_rmw_field(dev, MT_TX_ALC_CFG_0, MT_TX_ALC_CFG_0_CH_INIT_0, txp_0); + mt76_rmw_field(dev, MT_TX_ALC_CFG_0, MT_TX_ALC_CFG_0_CH_INIT_1, txp_1); + + mt76_wr(dev, MT_TX_PWR_CFG_0, + mt76x2_tx_power_mask(t.cck[0], t.cck[2], t.ofdm[0], t.ofdm[2])); + mt76_wr(dev, MT_TX_PWR_CFG_1, + mt76x2_tx_power_mask(t.ofdm[4], t.ofdm[6], t.ht[0], t.ht[2])); + mt76_wr(dev, MT_TX_PWR_CFG_2, + mt76x2_tx_power_mask(t.ht[4], t.ht[6], t.ht[8], t.ht[10])); + mt76_wr(dev, MT_TX_PWR_CFG_3, + mt76x2_tx_power_mask(t.ht[12], t.ht[14], t.ht[0], t.ht[2])); + mt76_wr(dev, MT_TX_PWR_CFG_4, + mt76x2_tx_power_mask(t.ht[4], t.ht[6], 0, 0)); + mt76_wr(dev, MT_TX_PWR_CFG_7, + mt76x2_tx_power_mask(t.ofdm[6], t.vht[8], t.ht[6], t.vht[8])); + mt76_wr(dev, MT_TX_PWR_CFG_8, + mt76x2_tx_power_mask(t.ht[14], t.vht[8], t.vht[8], 0)); + mt76_wr(dev, MT_TX_PWR_CFG_9, + mt76x2_tx_power_mask(t.ht[6], t.vht[8], t.vht[8], 0)); +} + +static bool +mt76x2_channel_silent(struct mt76x2_dev *dev) +{ + struct ieee80211_channel *chan = dev->mt76.chandef.chan; + + return ((chan->flags & IEEE80211_CHAN_RADAR) && + chan->dfs_state != NL80211_DFS_AVAILABLE); +} + +static bool +mt76x2_phy_tssi_init_cal(struct mt76x2_dev *dev) +{ + struct ieee80211_channel *chan = dev->mt76.chandef.chan; + u32 flag = 0; + + if (!mt76x2_tssi_enabled(dev)) + return false; + + if (mt76x2_channel_silent(dev)) + return false; + + if (chan->band == NL80211_BAND_2GHZ) + flag |= BIT(0); + + if (mt76x2_ext_pa_enabled(dev, chan->band)) + flag |= BIT(8); + + mt76x2_mcu_calibrate(dev, MCU_CAL_TSSI, flag); + dev->cal.tssi_cal_done = true; + return true; +} + +static void +mt76x2_phy_channel_calibrate(struct mt76x2_dev *dev, bool mac_stopped) +{ + struct ieee80211_channel *chan = dev->mt76.chandef.chan; + bool is_5ghz = chan->band == NL80211_BAND_5GHZ; + + if (dev->cal.channel_cal_done) + return; + + if (mt76x2_channel_silent(dev)) + return; + + if (!dev->cal.tssi_cal_done) + mt76x2_phy_tssi_init_cal(dev); + + if (!mac_stopped) + mt76x2_mac_stop(dev, false); + + if (is_5ghz) + mt76x2_mcu_calibrate(dev, MCU_CAL_LC, 0); + + mt76x2_mcu_calibrate(dev, MCU_CAL_TX_LOFT, is_5ghz); + mt76x2_mcu_calibrate(dev, MCU_CAL_TXIQ, is_5ghz); + mt76x2_mcu_calibrate(dev, MCU_CAL_RXIQC_FI, is_5ghz); + mt76x2_mcu_calibrate(dev, MCU_CAL_TEMP_SENSOR, 0); + mt76x2_mcu_calibrate(dev, MCU_CAL_TX_SHAPING, 0); + + if (!mac_stopped) + mt76x2_mac_resume(dev); + + mt76x2_apply_gain_adj(dev); + + dev->cal.channel_cal_done = true; +} + +static void +mt76x2_phy_set_txpower_regs(struct mt76x2_dev *dev, enum nl80211_band band) +{ + u32 pa_mode[2]; + u32 pa_mode_adj; + + if (band == NL80211_BAND_2GHZ) { + pa_mode[0] = 0x010055ff; + pa_mode[1] = 0x00550055; + + mt76_wr(dev, MT_TX_ALC_CFG_2, 0x35160a00); + mt76_wr(dev, MT_TX_ALC_CFG_3, 0x35160a06); + + if (mt76x2_ext_pa_enabled(dev, band)) { + mt76_wr(dev, MT_RF_PA_MODE_ADJ0, 0x0000ec00); + mt76_wr(dev, MT_RF_PA_MODE_ADJ1, 0x0000ec00); + } else { + mt76_wr(dev, MT_RF_PA_MODE_ADJ0, 0xf4000200); + mt76_wr(dev, MT_RF_PA_MODE_ADJ1, 0xfa000200); + } + } else { + pa_mode[0] = 0x0000ffff; + pa_mode[1] = 0x00ff00ff; + + if (mt76x2_ext_pa_enabled(dev, band)) { + mt76_wr(dev, MT_TX_ALC_CFG_2, 0x2f0f0400); + mt76_wr(dev, MT_TX_ALC_CFG_3, 0x2f0f0476); + } else { + mt76_wr(dev, MT_TX_ALC_CFG_2, 0x1b0f0400); + mt76_wr(dev, MT_TX_ALC_CFG_3, 0x1b0f0476); + } + mt76_wr(dev, MT_TX_ALC_CFG_4, 0); + + if (mt76x2_ext_pa_enabled(dev, band)) + pa_mode_adj = 0x04000000; + else + pa_mode_adj = 0; + + mt76_wr(dev, MT_RF_PA_MODE_ADJ0, pa_mode_adj); + mt76_wr(dev, MT_RF_PA_MODE_ADJ1, pa_mode_adj); + } + + mt76_wr(dev, MT_BB_PA_MODE_CFG0, pa_mode[0]); + mt76_wr(dev, MT_BB_PA_MODE_CFG1, pa_mode[1]); + mt76_wr(dev, MT_RF_PA_MODE_CFG0, pa_mode[0]); + mt76_wr(dev, MT_RF_PA_MODE_CFG1, pa_mode[1]); + + if (mt76x2_ext_pa_enabled(dev, band)) { + u32 val; + + if (band == NL80211_BAND_2GHZ) + val = 0x3c3c023c; + else + val = 0x363c023c; + + mt76_wr(dev, MT_TX0_RF_GAIN_CORR, val); + mt76_wr(dev, MT_TX1_RF_GAIN_CORR, val); + mt76_wr(dev, MT_TX_ALC_CFG_4, 0x00001818); + } else { + if (band == NL80211_BAND_2GHZ) { + u32 val = 0x0f3c3c3c; + + mt76_wr(dev, MT_TX0_RF_GAIN_CORR, val); + mt76_wr(dev, MT_TX1_RF_GAIN_CORR, val); + mt76_wr(dev, MT_TX_ALC_CFG_4, 0x00000606); + } else { + mt76_wr(dev, MT_TX0_RF_GAIN_CORR, 0x383c023c); + mt76_wr(dev, MT_TX1_RF_GAIN_CORR, 0x24282e28); + mt76_wr(dev, MT_TX_ALC_CFG_4, 0); + } + } +} + +static void +mt76x2_configure_tx_delay(struct mt76x2_dev *dev, enum nl80211_band band, u8 bw) +{ + u32 cfg0, cfg1; + + if (mt76x2_ext_pa_enabled(dev, band)) { + cfg0 = bw ? 0x000b0c01 : 0x00101101; + cfg1 = 0x00011414; + } else { + cfg0 = bw ? 0x000b0b01 : 0x00101001; + cfg1 = 0x00021414; + } + mt76_wr(dev, MT_TX_SW_CFG0, cfg0); + mt76_wr(dev, MT_TX_SW_CFG1, cfg1); + + mt76_rmw_field(dev, MT_XIFS_TIME_CFG, MT_XIFS_TIME_CFG_OFDM_SIFS, 15); +} + +static void +mt76x2_phy_set_bw(struct mt76x2_dev *dev, int width, u8 ctrl) +{ + int core_val, agc_val; + + switch (width) { + case NL80211_CHAN_WIDTH_80: + core_val = 3; + agc_val = 7; + break; + case NL80211_CHAN_WIDTH_40: + core_val = 2; + agc_val = 3; + break; + default: + core_val = 0; + agc_val = 1; + break; + } + + mt76_rmw_field(dev, MT_BBP(CORE, 1), MT_BBP_CORE_R1_BW, core_val); + mt76_rmw_field(dev, MT_BBP(AGC, 0), MT_BBP_AGC_R0_BW, agc_val); + mt76_rmw_field(dev, MT_BBP(AGC, 0), MT_BBP_AGC_R0_CTRL_CHAN, ctrl); + mt76_rmw_field(dev, MT_BBP(TXBE, 0), MT_BBP_TXBE_R0_CTRL_CHAN, ctrl); +} + +static void +mt76x2_phy_set_band(struct mt76x2_dev *dev, int band, bool primary_upper) +{ + switch (band) { + case NL80211_BAND_2GHZ: + mt76_set(dev, MT_TX_BAND_CFG, MT_TX_BAND_CFG_2G); + mt76_clear(dev, MT_TX_BAND_CFG, MT_TX_BAND_CFG_5G); + break; + case NL80211_BAND_5GHZ: + mt76_clear(dev, MT_TX_BAND_CFG, MT_TX_BAND_CFG_2G); + mt76_set(dev, MT_TX_BAND_CFG, MT_TX_BAND_CFG_5G); + break; + } + + mt76_rmw_field(dev, MT_TX_BAND_CFG, MT_TX_BAND_CFG_UPPER_40M, + primary_upper); +} + +static void +mt76x2_set_rx_chains(struct mt76x2_dev *dev) +{ + u32 val; + + val = mt76_rr(dev, MT_BBP(AGC, 0)); + val &= ~(BIT(3) | BIT(4)); + + if (dev->chainmask & BIT(1)) + val |= BIT(3); + + mt76_wr(dev, MT_BBP(AGC, 0), val); +} + +static void +mt76x2_set_tx_dac(struct mt76x2_dev *dev) +{ + if (dev->chainmask & BIT(1)) + mt76_set(dev, MT_BBP(TXBE, 5), 3); + else + mt76_clear(dev, MT_BBP(TXBE, 5), 3); +} + +static void +mt76x2_get_agc_gain(struct mt76x2_dev *dev, u8 *dest) +{ + dest[0] = mt76_get_field(dev, MT_BBP(AGC, 8), MT_BBP_AGC_GAIN); + dest[1] = mt76_get_field(dev, MT_BBP(AGC, 9), MT_BBP_AGC_GAIN); +} + +static int +mt76x2_get_rssi_gain_thresh(struct mt76x2_dev *dev) +{ + switch (dev->mt76.chandef.width) { + case NL80211_CHAN_WIDTH_80: + return -62; + case NL80211_CHAN_WIDTH_40: + return -65; + default: + return -68; + } +} + +static int +mt76x2_get_low_rssi_gain_thresh(struct mt76x2_dev *dev) +{ + switch (dev->mt76.chandef.width) { + case NL80211_CHAN_WIDTH_80: + return -76; + case NL80211_CHAN_WIDTH_40: + return -79; + default: + return -82; + } +} + +static void +mt76x2_phy_set_gain_val(struct mt76x2_dev *dev) +{ + u32 val; + u8 gain_val[2]; + + gain_val[0] = dev->cal.agc_gain_cur[0] - dev->cal.agc_gain_adjust; + gain_val[1] = dev->cal.agc_gain_cur[1] - dev->cal.agc_gain_adjust; + + if (dev->mt76.chandef.width >= NL80211_CHAN_WIDTH_40) + val = 0x1e42 << 16; + else + val = 0x1836 << 16; + + val |= 0xf8; + + mt76_wr(dev, MT_BBP(AGC, 8), + val | FIELD_PREP(MT_BBP_AGC_GAIN, gain_val[0])); + mt76_wr(dev, MT_BBP(AGC, 9), + val | FIELD_PREP(MT_BBP_AGC_GAIN, gain_val[1])); + + if (dev->mt76.chandef.chan->flags & IEEE80211_CHAN_RADAR) + mt76x2_dfs_adjust_agc(dev); +} + +static void +mt76x2_phy_adjust_vga_gain(struct mt76x2_dev *dev) +{ + u32 false_cca; + u8 limit = dev->cal.low_gain > 1 ? 4 : 16; + + false_cca = FIELD_GET(MT_RX_STAT_1_CCA_ERRORS, mt76_rr(dev, MT_RX_STAT_1)); + if (false_cca > 800 && dev->cal.agc_gain_adjust < limit) + dev->cal.agc_gain_adjust += 2; + else if (false_cca < 10 && dev->cal.agc_gain_adjust > 0) + dev->cal.agc_gain_adjust -= 2; + else + return; + + mt76x2_phy_set_gain_val(dev); +} + +static void +mt76x2_phy_update_channel_gain(struct mt76x2_dev *dev) +{ + u32 val = mt76_rr(dev, MT_BBP(AGC, 20)); + int rssi0 = (s8) FIELD_GET(MT_BBP_AGC20_RSSI0, val); + int rssi1 = (s8) FIELD_GET(MT_BBP_AGC20_RSSI1, val); + u8 *gain = dev->cal.agc_gain_init; + u8 gain_delta; + int low_gain; + + dev->cal.avg_rssi[0] = (dev->cal.avg_rssi[0] * 15) / 16 + (rssi0 << 8); + dev->cal.avg_rssi[1] = (dev->cal.avg_rssi[1] * 15) / 16 + (rssi1 << 8); + dev->cal.avg_rssi_all = (dev->cal.avg_rssi[0] + + dev->cal.avg_rssi[1]) / 512; + + low_gain = (dev->cal.avg_rssi_all > mt76x2_get_rssi_gain_thresh(dev)) + + (dev->cal.avg_rssi_all > mt76x2_get_low_rssi_gain_thresh(dev)); + + if (dev->cal.low_gain == low_gain) { + mt76x2_phy_adjust_vga_gain(dev); + return; + } + + dev->cal.low_gain = low_gain; + + if (dev->mt76.chandef.width == NL80211_CHAN_WIDTH_80) + mt76_wr(dev, MT_BBP(RXO, 14), 0x00560211); + else + mt76_wr(dev, MT_BBP(RXO, 14), 0x00560423); + + if (low_gain) { + mt76_wr(dev, MT_BBP(RXO, 18), 0xf000a991); + mt76_wr(dev, MT_BBP(AGC, 35), 0x08080808); + mt76_wr(dev, MT_BBP(AGC, 37), 0x08080808); + if (mt76x2_has_ext_lna(dev)) + gain_delta = 10; + else + gain_delta = 14; + } else { + mt76_wr(dev, MT_BBP(RXO, 18), 0xf000a990); + if (dev->mt76.chandef.width == NL80211_CHAN_WIDTH_80) + mt76_wr(dev, MT_BBP(AGC, 35), 0x10101014); + else + mt76_wr(dev, MT_BBP(AGC, 35), 0x11111116); + mt76_wr(dev, MT_BBP(AGC, 37), 0x2121262C); + gain_delta = 0; + } + + dev->cal.agc_gain_cur[0] = gain[0] - gain_delta; + dev->cal.agc_gain_cur[1] = gain[1] - gain_delta; + dev->cal.agc_gain_adjust = 0; + mt76x2_phy_set_gain_val(dev); +} + +int mt76x2_phy_set_channel(struct mt76x2_dev *dev, + struct cfg80211_chan_def *chandef) +{ + struct ieee80211_channel *chan = chandef->chan; + bool scan = test_bit(MT76_SCANNING, &dev->mt76.state); + enum nl80211_band band = chan->band; + u8 channel; + + u32 ext_cca_chan[4] = { + [0] = FIELD_PREP(MT_EXT_CCA_CFG_CCA0, 0) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA1, 1) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA2, 2) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA3, 3) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA_MASK, BIT(0)), + [1] = FIELD_PREP(MT_EXT_CCA_CFG_CCA0, 1) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA1, 0) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA2, 2) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA3, 3) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA_MASK, BIT(1)), + [2] = FIELD_PREP(MT_EXT_CCA_CFG_CCA0, 2) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA1, 3) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA2, 1) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA3, 0) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA_MASK, BIT(2)), + [3] = FIELD_PREP(MT_EXT_CCA_CFG_CCA0, 3) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA1, 2) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA2, 1) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA3, 0) | + FIELD_PREP(MT_EXT_CCA_CFG_CCA_MASK, BIT(3)), + }; + int ch_group_index; + u8 bw, bw_index; + int freq, freq1; + int ret; + + dev->cal.channel_cal_done = false; + freq = chandef->chan->center_freq; + freq1 = chandef->center_freq1; + channel = chan->hw_value; + + switch (chandef->width) { + case NL80211_CHAN_WIDTH_40: + bw = 1; + if (freq1 > freq) { + bw_index = 1; + ch_group_index = 0; + } else { + bw_index = 3; + ch_group_index = 1; + } + channel += 2 - ch_group_index * 4; + break; + case NL80211_CHAN_WIDTH_80: + ch_group_index = (freq - freq1 + 30) / 20; + if (WARN_ON(ch_group_index < 0 || ch_group_index > 3)) + ch_group_index = 0; + bw = 2; + bw_index = ch_group_index; + channel += 6 - ch_group_index * 4; + break; + default: + bw = 0; + bw_index = 0; + ch_group_index = 0; + break; + } + + mt76x2_read_rx_gain(dev); + mt76x2_phy_set_txpower_regs(dev, band); + mt76x2_configure_tx_delay(dev, band, bw); + mt76x2_phy_set_txpower(dev); + + mt76x2_set_rx_chains(dev); + mt76x2_phy_set_band(dev, chan->band, ch_group_index & 1); + mt76x2_phy_set_bw(dev, chandef->width, ch_group_index); + mt76x2_set_tx_dac(dev); + + mt76_rmw(dev, MT_EXT_CCA_CFG, + (MT_EXT_CCA_CFG_CCA0 | + MT_EXT_CCA_CFG_CCA1 | + MT_EXT_CCA_CFG_CCA2 | + MT_EXT_CCA_CFG_CCA3 | + MT_EXT_CCA_CFG_CCA_MASK), + ext_cca_chan[ch_group_index]); + + ret = mt76x2_mcu_set_channel(dev, channel, bw, bw_index, scan); + if (ret) + return ret; + + mt76x2_mcu_init_gain(dev, channel, dev->cal.rx.mcu_gain, true); + + /* Enable LDPC Rx */ + if (mt76xx_rev(dev) >= MT76XX_REV_E3) + mt76_set(dev, MT_BBP(RXO, 13), BIT(10)); + + if (!dev->cal.init_cal_done) { + u8 val = mt76x2_eeprom_get(dev, MT_EE_BT_RCAL_RESULT); + + if (val != 0xff) + mt76x2_mcu_calibrate(dev, MCU_CAL_R, 0); + } + + mt76x2_mcu_calibrate(dev, MCU_CAL_RXDCOC, channel); + + /* Rx LPF calibration */ + if (!dev->cal.init_cal_done) + mt76x2_mcu_calibrate(dev, MCU_CAL_RC, 0); + + dev->cal.init_cal_done = true; + + mt76_wr(dev, MT_BBP(AGC, 61), 0xFF64A4E2); + mt76_wr(dev, MT_BBP(AGC, 7), 0x08081010); + mt76_wr(dev, MT_BBP(AGC, 11), 0x00000404); + mt76_wr(dev, MT_BBP(AGC, 2), 0x00007070); + mt76_wr(dev, MT_TXOP_CTRL_CFG, 0x04101B3F); + + if (scan) + return 0; + + dev->cal.low_gain = -1; + mt76x2_phy_channel_calibrate(dev, true); + mt76x2_get_agc_gain(dev, dev->cal.agc_gain_init); + memcpy(dev->cal.agc_gain_cur, dev->cal.agc_gain_init, + sizeof(dev->cal.agc_gain_cur)); + + ieee80211_queue_delayed_work(mt76_hw(dev), &dev->cal_work, + MT_CALIBRATE_INTERVAL); + + return 0; +} + +static void +mt76x2_phy_tssi_compensate(struct mt76x2_dev *dev) +{ + struct ieee80211_channel *chan = dev->mt76.chandef.chan; + struct mt76x2_tx_power_info txp; + struct mt76x2_tssi_comp t = {}; + + if (!dev->cal.tssi_cal_done) + return; + + if (!dev->cal.tssi_comp_pending) { + /* TSSI trigger */ + t.cal_mode = BIT(0); + mt76x2_mcu_tssi_comp(dev, &t); + dev->cal.tssi_comp_pending = true; + } else { + if (mt76_rr(dev, MT_BBP(CORE, 34)) & BIT(4)) + return; + + dev->cal.tssi_comp_pending = false; + mt76x2_get_power_info(dev, &txp, chan); + + if (mt76x2_ext_pa_enabled(dev, chan->band)) + t.pa_mode = 1; + + t.cal_mode = BIT(1); + t.slope0 = txp.chain[0].tssi_slope; + t.offset0 = txp.chain[0].tssi_offset; + t.slope1 = txp.chain[1].tssi_slope; + t.offset1 = txp.chain[1].tssi_offset; + mt76x2_mcu_tssi_comp(dev, &t); + + if (t.pa_mode || dev->cal.dpd_cal_done) + return; + + usleep_range(10000, 20000); + mt76x2_mcu_calibrate(dev, MCU_CAL_DPD, chan->hw_value); + dev->cal.dpd_cal_done = true; + } +} + +static void +mt76x2_phy_temp_compensate(struct mt76x2_dev *dev) +{ + struct mt76x2_temp_comp t; + int temp, db_diff; + + if (mt76x2_get_temp_comp(dev, &t)) + return; + + temp = mt76_get_field(dev, MT_TEMP_SENSOR, MT_TEMP_SENSOR_VAL); + temp -= t.temp_25_ref; + temp = (temp * 1789) / 1000 + 25; + dev->cal.temp = temp; + + if (temp > 25) + db_diff = (temp - 25) / t.high_slope; + else + db_diff = (25 - temp) / t.low_slope; + + db_diff = min(db_diff, t.upper_bound); + db_diff = max(db_diff, t.lower_bound); + + mt76_rmw_field(dev, MT_TX_ALC_CFG_1, MT_TX_ALC_CFG_1_TEMP_COMP, + db_diff * 2); + mt76_rmw_field(dev, MT_TX_ALC_CFG_2, MT_TX_ALC_CFG_2_TEMP_COMP, + db_diff * 2); +} + +void mt76x2_phy_calibrate(struct work_struct *work) +{ + struct mt76x2_dev *dev; + + dev = container_of(work, struct mt76x2_dev, cal_work.work); + mt76x2_phy_channel_calibrate(dev, false); + mt76x2_phy_tssi_compensate(dev); + mt76x2_phy_temp_compensate(dev); + mt76x2_phy_update_channel_gain(dev); + ieee80211_queue_delayed_work(mt76_hw(dev), &dev->cal_work, + MT_CALIBRATE_INTERVAL); +} + +int mt76x2_phy_start(struct mt76x2_dev *dev) +{ + int ret; + + ret = mt76x2_mcu_set_radio_state(dev, true); + if (ret) + return ret; + + mt76x2_mcu_load_cr(dev, MT_RF_BBP_CR, 0, 0); + + return ret; +} diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_regs.h b/drivers/net/wireless/mediatek/mt76/mt76x2_regs.h new file mode 100644 index 000000000000..ce3ab85c8b0f --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_regs.h @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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. + */ + +#ifndef __MT76x2_REGS_H +#define __MT76x2_REGS_H + +#define MT_ASIC_VERSION 0x0000 + +#define MT76XX_REV_E3 0x22 +#define MT76XX_REV_E4 0x33 + +#define MT_CMB_CTRL 0x0020 +#define MT_CMB_CTRL_XTAL_RDY BIT(22) +#define MT_CMB_CTRL_PLL_LD BIT(23) + +#define MT_EFUSE_CTRL 0x0024 +#define MT_EFUSE_CTRL_AOUT GENMASK(5, 0) +#define MT_EFUSE_CTRL_MODE GENMASK(7, 6) +#define MT_EFUSE_CTRL_LDO_OFF_TIME GENMASK(13, 8) +#define MT_EFUSE_CTRL_LDO_ON_TIME GENMASK(15, 14) +#define MT_EFUSE_CTRL_AIN GENMASK(25, 16) +#define MT_EFUSE_CTRL_KICK BIT(30) +#define MT_EFUSE_CTRL_SEL BIT(31) + +#define MT_EFUSE_DATA_BASE 0x0028 +#define MT_EFUSE_DATA(_n) (MT_EFUSE_DATA_BASE + ((_n) << 2)) + +#define MT_COEXCFG0 0x0040 +#define MT_COEXCFG0_COEX_EN BIT(0) + +#define MT_WLAN_FUN_CTRL 0x0080 +#define MT_WLAN_FUN_CTRL_WLAN_EN BIT(0) +#define MT_WLAN_FUN_CTRL_WLAN_CLK_EN BIT(1) +#define MT_WLAN_FUN_CTRL_WLAN_RESET_RF BIT(2) + +#define MT_WLAN_FUN_CTRL_WLAN_RESET BIT(3) /* MT76x0 */ +#define MT_WLAN_FUN_CTRL_CSR_F20M_CKEN BIT(3) /* MT76x2 */ + +#define MT_WLAN_FUN_CTRL_PCIE_CLK_REQ BIT(4) +#define MT_WLAN_FUN_CTRL_FRC_WL_ANT_SEL BIT(5) +#define MT_WLAN_FUN_CTRL_INV_ANT_SEL BIT(6) +#define MT_WLAN_FUN_CTRL_WAKE_HOST BIT(7) + +#define MT_WLAN_FUN_CTRL_THERM_RST BIT(8) /* MT76x2 */ +#define MT_WLAN_FUN_CTRL_THERM_CKEN BIT(9) /* MT76x2 */ + +#define MT_WLAN_FUN_CTRL_GPIO_IN GENMASK(15, 8) /* MT76x0 */ +#define MT_WLAN_FUN_CTRL_GPIO_OUT GENMASK(23, 16) /* MT76x0 */ +#define MT_WLAN_FUN_CTRL_GPIO_OUT_EN GENMASK(31, 24) /* MT76x0 */ + +#define MT_XO_CTRL0 0x0100 +#define MT_XO_CTRL1 0x0104 +#define MT_XO_CTRL2 0x0108 +#define MT_XO_CTRL3 0x010c +#define MT_XO_CTRL4 0x0110 + +#define MT_XO_CTRL5 0x0114 +#define MT_XO_CTRL5_C2_VAL GENMASK(14, 8) + +#define MT_XO_CTRL6 0x0118 +#define MT_XO_CTRL6_C2_CTRL GENMASK(14, 8) + +#define MT_XO_CTRL7 0x011c + +#define MT_WLAN_MTC_CTRL 0x10148 +#define MT_WLAN_MTC_CTRL_MTCMOS_PWR_UP BIT(0) +#define MT_WLAN_MTC_CTRL_PWR_ACK BIT(12) +#define MT_WLAN_MTC_CTRL_PWR_ACK_S BIT(13) +#define MT_WLAN_MTC_CTRL_BBP_MEM_PD GENMASK(19, 16) +#define MT_WLAN_MTC_CTRL_PBF_MEM_PD BIT(20) +#define MT_WLAN_MTC_CTRL_FCE_MEM_PD BIT(21) +#define MT_WLAN_MTC_CTRL_TSO_MEM_PD BIT(22) +#define MT_WLAN_MTC_CTRL_BBP_MEM_RB BIT(24) +#define MT_WLAN_MTC_CTRL_PBF_MEM_RB BIT(25) +#define MT_WLAN_MTC_CTRL_FCE_MEM_RB BIT(26) +#define MT_WLAN_MTC_CTRL_TSO_MEM_RB BIT(27) +#define MT_WLAN_MTC_CTRL_STATE_UP BIT(28) + +#define MT_INT_SOURCE_CSR 0x0200 +#define MT_INT_MASK_CSR 0x0204 + +#define MT_INT_RX_DONE(_n) BIT(_n) +#define MT_INT_RX_DONE_ALL GENMASK(1, 0) +#define MT_INT_TX_DONE_ALL GENMASK(13, 4) +#define MT_INT_TX_DONE(_n) BIT(_n + 4) +#define MT_INT_RX_COHERENT BIT(16) +#define MT_INT_TX_COHERENT BIT(17) +#define MT_INT_ANY_COHERENT BIT(18) +#define MT_INT_MCU_CMD BIT(19) +#define MT_INT_TBTT BIT(20) +#define MT_INT_PRE_TBTT BIT(21) +#define MT_INT_TX_STAT BIT(22) +#define MT_INT_AUTO_WAKEUP BIT(23) +#define MT_INT_GPTIMER BIT(24) +#define MT_INT_RXDELAYINT BIT(26) +#define MT_INT_TXDELAYINT BIT(27) + +#define MT_WPDMA_GLO_CFG 0x0208 +#define MT_WPDMA_GLO_CFG_TX_DMA_EN BIT(0) +#define MT_WPDMA_GLO_CFG_TX_DMA_BUSY BIT(1) +#define MT_WPDMA_GLO_CFG_RX_DMA_EN BIT(2) +#define MT_WPDMA_GLO_CFG_RX_DMA_BUSY BIT(3) +#define MT_WPDMA_GLO_CFG_DMA_BURST_SIZE GENMASK(5, 4) +#define MT_WPDMA_GLO_CFG_TX_WRITEBACK_DONE BIT(6) +#define MT_WPDMA_GLO_CFG_BIG_ENDIAN BIT(7) +#define MT_WPDMA_GLO_CFG_HDR_SEG_LEN GENMASK(15, 8) +#define MT_WPDMA_GLO_CFG_CLK_GATE_DIS BIT(30) +#define MT_WPDMA_GLO_CFG_RX_2B_OFFSET BIT(31) + +#define MT_WPDMA_RST_IDX 0x020c + +#define MT_WPDMA_DELAY_INT_CFG 0x0210 + +#define MT_WMM_AIFSN 0x0214 +#define MT_WMM_AIFSN_MASK GENMASK(3, 0) +#define MT_WMM_AIFSN_SHIFT(_n) ((_n) * 4) + +#define MT_WMM_CWMIN 0x0218 +#define MT_WMM_CWMIN_MASK GENMASK(3, 0) +#define MT_WMM_CWMIN_SHIFT(_n) ((_n) * 4) + +#define MT_WMM_CWMAX 0x021c +#define MT_WMM_CWMAX_MASK GENMASK(3, 0) +#define MT_WMM_CWMAX_SHIFT(_n) ((_n) * 4) + +#define MT_WMM_TXOP_BASE 0x0220 +#define MT_WMM_TXOP(_n) (MT_WMM_TXOP_BASE + (((_n) / 2) << 2)) +#define MT_WMM_TXOP_SHIFT(_n) ((_n & 1) * 16) +#define MT_WMM_TXOP_MASK GENMASK(15, 0) + +#define MT_TSO_CTRL 0x0250 +#define MT_HEADER_TRANS_CTRL_REG 0x0260 + +#define MT_TX_RING_BASE 0x0300 +#define MT_RX_RING_BASE 0x03c0 + +#define MT_TX_HW_QUEUE_MCU 8 +#define MT_TX_HW_QUEUE_MGMT 9 + +#define MT_PBF_SYS_CTRL 0x0400 +#define MT_PBF_SYS_CTRL_MCU_RESET BIT(0) +#define MT_PBF_SYS_CTRL_DMA_RESET BIT(1) +#define MT_PBF_SYS_CTRL_MAC_RESET BIT(2) +#define MT_PBF_SYS_CTRL_PBF_RESET BIT(3) +#define MT_PBF_SYS_CTRL_ASY_RESET BIT(4) + +#define MT_PBF_CFG 0x0404 +#define MT_PBF_CFG_TX0Q_EN BIT(0) +#define MT_PBF_CFG_TX1Q_EN BIT(1) +#define MT_PBF_CFG_TX2Q_EN BIT(2) +#define MT_PBF_CFG_TX3Q_EN BIT(3) +#define MT_PBF_CFG_RX0Q_EN BIT(4) +#define MT_PBF_CFG_RX_DROP_EN BIT(8) + +#define MT_PBF_TX_MAX_PCNT 0x0408 +#define MT_PBF_RX_MAX_PCNT 0x040c + +#define MT_BCN_OFFSET_BASE 0x041c +#define MT_BCN_OFFSET(_n) (MT_BCN_OFFSET_BASE + ((_n) << 2)) + +#define MT_RF_BYPASS_0 0x0504 +#define MT_RF_BYPASS_1 0x0508 +#define MT_RF_SETTING_0 0x050c + +#define MT_RF_DATA_WRITE 0x0524 + +#define MT_RF_CTRL 0x0528 +#define MT_RF_CTRL_ADDR GENMASK(11, 0) +#define MT_RF_CTRL_WRITE BIT(12) +#define MT_RF_CTRL_BUSY BIT(13) +#define MT_RF_CTRL_IDX BIT(16) + +#define MT_RF_DATA_READ 0x052c + +#define MT_FCE_PSE_CTRL 0x0800 +#define MT_FCE_PARAMETERS 0x0804 +#define MT_FCE_CSO 0x0808 + +#define MT_FCE_L2_STUFF 0x080c +#define MT_FCE_L2_STUFF_HT_L2_EN BIT(0) +#define MT_FCE_L2_STUFF_QOS_L2_EN BIT(1) +#define MT_FCE_L2_STUFF_RX_STUFF_EN BIT(2) +#define MT_FCE_L2_STUFF_TX_STUFF_EN BIT(3) +#define MT_FCE_L2_STUFF_WR_MPDU_LEN_EN BIT(4) +#define MT_FCE_L2_STUFF_MVINV_BSWAP BIT(5) +#define MT_FCE_L2_STUFF_TS_CMD_QSEL_EN GENMASK(15, 8) +#define MT_FCE_L2_STUFF_TS_LEN_EN GENMASK(23, 16) +#define MT_FCE_L2_STUFF_OTHER_PORT GENMASK(25, 24) + +#define MT_FCE_WLAN_FLOW_CONTROL1 0x0824 + +#define MT_PAUSE_ENABLE_CONTROL1 0x0a38 + +#define MT_MAC_CSR0 0x1000 + +#define MT_MAC_SYS_CTRL 0x1004 +#define MT_MAC_SYS_CTRL_RESET_CSR BIT(0) +#define MT_MAC_SYS_CTRL_RESET_BBP BIT(1) +#define MT_MAC_SYS_CTRL_ENABLE_TX BIT(2) +#define MT_MAC_SYS_CTRL_ENABLE_RX BIT(3) + +#define MT_MAC_ADDR_DW0 0x1008 +#define MT_MAC_ADDR_DW1 0x100c + +#define MT_MAC_BSSID_DW0 0x1010 +#define MT_MAC_BSSID_DW1 0x1014 +#define MT_MAC_BSSID_DW1_ADDR GENMASK(15, 0) +#define MT_MAC_BSSID_DW1_MBSS_MODE GENMASK(17, 16) +#define MT_MAC_BSSID_DW1_MBEACON_N GENMASK(20, 18) +#define MT_MAC_BSSID_DW1_MBSS_LOCAL_BIT BIT(21) +#define MT_MAC_BSSID_DW1_MBSS_MODE_B2 BIT(22) +#define MT_MAC_BSSID_DW1_MBEACON_N_B3 BIT(23) +#define MT_MAC_BSSID_DW1_MBSS_IDX_BYTE GENMASK(26, 24) + +#define MT_MAX_LEN_CFG 0x1018 + +#define MT_AMPDU_MAX_LEN_20M1S 0x1030 +#define MT_AMPDU_MAX_LEN_20M2S 0x1034 +#define MT_AMPDU_MAX_LEN_40M1S 0x1038 +#define MT_AMPDU_MAX_LEN_40M2S 0x103c +#define MT_AMPDU_MAX_LEN 0x1040 + +#define MT_WCID_DROP_BASE 0x106c +#define MT_WCID_DROP(_n) (MT_WCID_DROP_BASE + ((_n) >> 5) * 4) +#define MT_WCID_DROP_MASK(_n) BIT((_n) % 32) + +#define MT_BCN_BYPASS_MASK 0x108c + +#define MT_MAC_APC_BSSID_BASE 0x1090 +#define MT_MAC_APC_BSSID_L(_n) (MT_MAC_APC_BSSID_BASE + ((_n) * 8)) +#define MT_MAC_APC_BSSID_H(_n) (MT_MAC_APC_BSSID_BASE + ((_n) * 8 + 4)) +#define MT_MAC_APC_BSSID_H_ADDR GENMASK(15, 0) +#define MT_MAC_APC_BSSID0_H_EN BIT(16) + +#define MT_XIFS_TIME_CFG 0x1100 +#define MT_XIFS_TIME_CFG_CCK_SIFS GENMASK(7, 0) +#define MT_XIFS_TIME_CFG_OFDM_SIFS GENMASK(15, 8) +#define MT_XIFS_TIME_CFG_OFDM_XIFS GENMASK(19, 16) +#define MT_XIFS_TIME_CFG_EIFS GENMASK(28, 20) +#define MT_XIFS_TIME_CFG_BB_RXEND_EN BIT(29) + +#define MT_BKOFF_SLOT_CFG 0x1104 +#define MT_BKOFF_SLOT_CFG_SLOTTIME GENMASK(7, 0) +#define MT_BKOFF_SLOT_CFG_CC_DELAY GENMASK(11, 8) + +#define MT_CH_TIME_CFG 0x110c +#define MT_CH_TIME_CFG_TIMER_EN BIT(0) +#define MT_CH_TIME_CFG_TX_AS_BUSY BIT(1) +#define MT_CH_TIME_CFG_RX_AS_BUSY BIT(2) +#define MT_CH_TIME_CFG_NAV_AS_BUSY BIT(3) +#define MT_CH_TIME_CFG_EIFS_AS_BUSY BIT(4) +#define MT_CH_TIME_CFG_MDRDY_CNT_EN BIT(5) +#define MT_CH_TIME_CFG_CH_TIMER_CLR GENMASK(9, 8) +#define MT_CH_TIME_CFG_MDRDY_CLR GENMASK(11, 10) + +#define MT_PBF_LIFE_TIMER 0x1110 + +#define MT_BEACON_TIME_CFG 0x1114 +#define MT_BEACON_TIME_CFG_INTVAL GENMASK(15, 0) +#define MT_BEACON_TIME_CFG_TIMER_EN BIT(16) +#define MT_BEACON_TIME_CFG_SYNC_MODE GENMASK(18, 17) +#define MT_BEACON_TIME_CFG_TBTT_EN BIT(19) +#define MT_BEACON_TIME_CFG_BEACON_TX BIT(20) +#define MT_BEACON_TIME_CFG_TSF_COMP GENMASK(31, 24) + +#define MT_TBTT_SYNC_CFG 0x1118 +#define MT_TBTT_TIMER_CFG 0x1124 + +#define MT_INT_TIMER_CFG 0x1128 +#define MT_INT_TIMER_CFG_PRE_TBTT GENMASK(15, 0) +#define MT_INT_TIMER_CFG_GP_TIMER GENMASK(31, 16) + +#define MT_INT_TIMER_EN 0x112c +#define MT_INT_TIMER_EN_PRE_TBTT_EN BIT(0) +#define MT_INT_TIMER_EN_GP_TIMER_EN BIT(1) + +#define MT_CH_IDLE 0x1130 +#define MT_CH_BUSY 0x1134 +#define MT_EXT_CH_BUSY 0x1138 +#define MT_ED_CCA_TIMER 0x1140 + +#define MT_MAC_STATUS 0x1200 +#define MT_MAC_STATUS_TX BIT(0) +#define MT_MAC_STATUS_RX BIT(1) + +#define MT_PWR_PIN_CFG 0x1204 +#define MT_AUX_CLK_CFG 0x120c + +#define MT_BB_PA_MODE_CFG0 0x1214 +#define MT_BB_PA_MODE_CFG1 0x1218 +#define MT_RF_PA_MODE_CFG0 0x121c +#define MT_RF_PA_MODE_CFG1 0x1220 + +#define MT_RF_PA_MODE_ADJ0 0x1228 +#define MT_RF_PA_MODE_ADJ1 0x122c + +#define MT_DACCLK_EN_DLY_CFG 0x1264 + +#define MT_EDCA_CFG_BASE 0x1300 +#define MT_EDCA_CFG_AC(_n) (MT_EDCA_CFG_BASE + ((_n) << 2)) +#define MT_EDCA_CFG_TXOP GENMASK(7, 0) +#define MT_EDCA_CFG_AIFSN GENMASK(11, 8) +#define MT_EDCA_CFG_CWMIN GENMASK(15, 12) +#define MT_EDCA_CFG_CWMAX GENMASK(19, 16) + +#define MT_TX_PWR_CFG_0 0x1314 +#define MT_TX_PWR_CFG_1 0x1318 +#define MT_TX_PWR_CFG_2 0x131c +#define MT_TX_PWR_CFG_3 0x1320 +#define MT_TX_PWR_CFG_4 0x1324 + +#define MT_TX_BAND_CFG 0x132c +#define MT_TX_BAND_CFG_UPPER_40M BIT(0) +#define MT_TX_BAND_CFG_5G BIT(1) +#define MT_TX_BAND_CFG_2G BIT(2) + +#define MT_HT_FBK_TO_LEGACY 0x1384 +#define MT_TX_MPDU_ADJ_INT 0x1388 + +#define MT_TX_PWR_CFG_7 0x13d4 +#define MT_TX_PWR_CFG_8 0x13d8 +#define MT_TX_PWR_CFG_9 0x13dc + +#define MT_TX_SW_CFG0 0x1330 +#define MT_TX_SW_CFG1 0x1334 +#define MT_TX_SW_CFG2 0x1338 + +#define MT_TXOP_CTRL_CFG 0x1340 + +#define MT_TX_RTS_CFG 0x1344 +#define MT_TX_RTS_CFG_RETRY_LIMIT GENMASK(7, 0) +#define MT_TX_RTS_CFG_THRESH GENMASK(23, 8) +#define MT_TX_RTS_FALLBACK BIT(24) + +#define MT_TX_TIMEOUT_CFG 0x1348 +#define MT_TX_TIMEOUT_CFG_ACKTO GENMASK(15, 8) + +#define MT_TX_RETRY_CFG 0x134c +#define MT_VHT_HT_FBK_CFG1 0x1358 + +#define MT_PROT_CFG_RATE GENMASK(15, 0) +#define MT_PROT_CFG_CTRL GENMASK(17, 16) +#define MT_PROT_CFG_NAV GENMASK(19, 18) +#define MT_PROT_CFG_TXOP_ALLOW GENMASK(25, 20) +#define MT_PROT_CFG_RTS_THRESH BIT(26) + +#define MT_CCK_PROT_CFG 0x1364 +#define MT_OFDM_PROT_CFG 0x1368 +#define MT_MM20_PROT_CFG 0x136c +#define MT_MM40_PROT_CFG 0x1370 +#define MT_GF20_PROT_CFG 0x1374 +#define MT_GF40_PROT_CFG 0x1378 + +#define MT_EXP_ACK_TIME 0x1380 + +#define MT_TX_PWR_CFG_0_EXT 0x1390 +#define MT_TX_PWR_CFG_1_EXT 0x1394 + +#define MT_TX_FBK_LIMIT 0x1398 +#define MT_TX_FBK_LIMIT_MPDU_FBK GENMASK(7, 0) +#define MT_TX_FBK_LIMIT_AMPDU_FBK GENMASK(15, 8) +#define MT_TX_FBK_LIMIT_MPDU_UP_CLEAR BIT(16) +#define MT_TX_FBK_LIMIT_AMPDU_UP_CLEAR BIT(17) +#define MT_TX_FBK_LIMIT_RATE_LUT BIT(18) + +#define MT_TX0_RF_GAIN_CORR 0x13a0 +#define MT_TX1_RF_GAIN_CORR 0x13a4 + +#define MT_TX_ALC_CFG_0 0x13b0 +#define MT_TX_ALC_CFG_0_CH_INIT_0 GENMASK(5, 0) +#define MT_TX_ALC_CFG_0_CH_INIT_1 GENMASK(13, 8) +#define MT_TX_ALC_CFG_0_LIMIT_0 GENMASK(21, 16) +#define MT_TX_ALC_CFG_0_LIMIT_1 GENMASK(29, 24) + +#define MT_TX_ALC_CFG_1 0x13b4 +#define MT_TX_ALC_CFG_1_TEMP_COMP GENMASK(5, 0) + +#define MT_TX_ALC_CFG_2 0x13a8 +#define MT_TX_ALC_CFG_2_TEMP_COMP GENMASK(5, 0) + +#define MT_TX_ALC_CFG_3 0x13ac +#define MT_TX_ALC_CFG_4 0x13c0 +#define MT_TX_ALC_CFG_4_LOWGAIN_CH_EN BIT(31) + +#define MT_TX_ALC_VGA3 0x13c8 + +#define MT_TX_PROT_CFG6 0x13e0 +#define MT_TX_PROT_CFG7 0x13e4 +#define MT_TX_PROT_CFG8 0x13e8 + +#define MT_PIFS_TX_CFG 0x13ec + +#define MT_RX_FILTR_CFG 0x1400 + +#define MT_RX_FILTR_CFG_CRC_ERR BIT(0) +#define MT_RX_FILTR_CFG_PHY_ERR BIT(1) +#define MT_RX_FILTR_CFG_PROMISC BIT(2) +#define MT_RX_FILTR_CFG_OTHER_BSS BIT(3) +#define MT_RX_FILTR_CFG_VER_ERR BIT(4) +#define MT_RX_FILTR_CFG_MCAST BIT(5) +#define MT_RX_FILTR_CFG_BCAST BIT(6) +#define MT_RX_FILTR_CFG_DUP BIT(7) +#define MT_RX_FILTR_CFG_CFACK BIT(8) +#define MT_RX_FILTR_CFG_CFEND BIT(9) +#define MT_RX_FILTR_CFG_ACK BIT(10) +#define MT_RX_FILTR_CFG_CTS BIT(11) +#define MT_RX_FILTR_CFG_RTS BIT(12) +#define MT_RX_FILTR_CFG_PSPOLL BIT(13) +#define MT_RX_FILTR_CFG_BA BIT(14) +#define MT_RX_FILTR_CFG_BAR BIT(15) +#define MT_RX_FILTR_CFG_CTRL_RSV BIT(16) + +#define MT_LEGACY_BASIC_RATE 0x1408 +#define MT_HT_BASIC_RATE 0x140c + +#define MT_HT_CTRL_CFG 0x1410 + +#define MT_EXT_CCA_CFG 0x141c +#define MT_EXT_CCA_CFG_CCA0 GENMASK(1, 0) +#define MT_EXT_CCA_CFG_CCA1 GENMASK(3, 2) +#define MT_EXT_CCA_CFG_CCA2 GENMASK(5, 4) +#define MT_EXT_CCA_CFG_CCA3 GENMASK(7, 6) +#define MT_EXT_CCA_CFG_CCA_MASK GENMASK(11, 8) +#define MT_EXT_CCA_CFG_ED_CCA_MASK GENMASK(15, 12) + +#define MT_TX_SW_CFG3 0x1478 + +#define MT_PN_PAD_MODE 0x150c + +#define MT_TXOP_HLDR_ET 0x1608 + +#define MT_PROT_AUTO_TX_CFG 0x1648 +#define MT_PROT_AUTO_TX_CFG_PROT_PADJ GENMASK(11, 8) +#define MT_PROT_AUTO_TX_CFG_AUTO_PADJ GENMASK(27, 24) + +#define MT_RX_STAT_0 0x1700 +#define MT_RX_STAT_0_CRC_ERRORS GENMASK(15, 0) +#define MT_RX_STAT_0_PHY_ERRORS GENMASK(31, 16) + +#define MT_RX_STAT_1 0x1704 +#define MT_RX_STAT_1_CCA_ERRORS GENMASK(15, 0) +#define MT_RX_STAT_1_PLCP_ERRORS GENMASK(31, 16) + +#define MT_RX_STAT_2 0x1708 +#define MT_RX_STAT_2_DUP_ERRORS GENMASK(15, 0) +#define MT_RX_STAT_2_OVERFLOW_ERRORS GENMASK(31, 16) + +#define MT_TX_STAT_FIFO 0x1718 +#define MT_TX_STAT_FIFO_VALID BIT(0) +#define MT_TX_STAT_FIFO_SUCCESS BIT(5) +#define MT_TX_STAT_FIFO_AGGR BIT(6) +#define MT_TX_STAT_FIFO_ACKREQ BIT(7) +#define MT_TX_STAT_FIFO_WCID GENMASK(15, 8) +#define MT_TX_STAT_FIFO_RATE GENMASK(31, 16) + +#define MT_TX_AGG_CNT_BASE0 0x1720 +#define MT_TX_AGG_CNT_BASE1 0x174c + +#define MT_TX_AGG_CNT(_id) ((_id) < 8 ? \ + MT_TX_AGG_CNT_BASE0 + ((_id) << 2) : \ + MT_TX_AGG_CNT_BASE1 + ((_id - 8) << 2)) + +#define MT_TX_STAT_FIFO_EXT 0x1798 +#define MT_TX_STAT_FIFO_EXT_RETRY GENMASK(7, 0) +#define MT_TX_STAT_FIFO_EXT_PKTID GENMASK(15, 8) + +#define MT_WCID_TX_RATE_BASE 0x1c00 +#define MT_WCID_TX_RATE(_i) (MT_WCID_TX_RATE_BASE + ((_i) << 3)) + +#define MT_BBP_CORE_BASE 0x2000 +#define MT_BBP_IBI_BASE 0x2100 +#define MT_BBP_AGC_BASE 0x2300 +#define MT_BBP_TXC_BASE 0x2400 +#define MT_BBP_RXC_BASE 0x2500 +#define MT_BBP_TXO_BASE 0x2600 +#define MT_BBP_TXBE_BASE 0x2700 +#define MT_BBP_RXFE_BASE 0x2800 +#define MT_BBP_RXO_BASE 0x2900 +#define MT_BBP_DFS_BASE 0x2a00 +#define MT_BBP_TR_BASE 0x2b00 +#define MT_BBP_CAL_BASE 0x2c00 +#define MT_BBP_DSC_BASE 0x2e00 +#define MT_BBP_PFMU_BASE 0x2f00 + +#define MT_BBP(_type, _n) (MT_BBP_##_type##_BASE + ((_n) << 2)) + +#define MT_BBP_CORE_R1_BW GENMASK(4, 3) + +#define MT_BBP_AGC_R0_CTRL_CHAN GENMASK(9, 8) +#define MT_BBP_AGC_R0_BW GENMASK(14, 12) + +/* AGC, R4/R5 */ +#define MT_BBP_AGC_LNA_HIGH_GAIN GENMASK(21, 16) +#define MT_BBP_AGC_LNA_MID_GAIN GENMASK(13, 8) +#define MT_BBP_AGC_LNA_LOW_GAIN GENMASK(5, 0) + +/* AGC, R6/R7 */ +#define MT_BBP_AGC_LNA_ULOW_GAIN GENMASK(5, 0) + +/* AGC, R8/R9 */ +#define MT_BBP_AGC_LNA_GAIN_MODE GENMASK(7, 6) +#define MT_BBP_AGC_GAIN GENMASK(14, 8) + +#define MT_BBP_AGC20_RSSI0 GENMASK(7, 0) +#define MT_BBP_AGC20_RSSI1 GENMASK(15, 8) + +#define MT_BBP_TXBE_R0_CTRL_CHAN GENMASK(1, 0) + +#define MT_WCID_ADDR_BASE 0x1800 +#define MT_WCID_ADDR(_n) (MT_WCID_ADDR_BASE + (_n) * 8) + +#define MT_SRAM_BASE 0x4000 + +#define MT_WCID_KEY_BASE 0x8000 +#define MT_WCID_KEY(_n) (MT_WCID_KEY_BASE + (_n) * 32) + +#define MT_WCID_IV_BASE 0xa000 +#define MT_WCID_IV(_n) (MT_WCID_IV_BASE + (_n) * 8) + +#define MT_WCID_ATTR_BASE 0xa800 +#define MT_WCID_ATTR(_n) (MT_WCID_ATTR_BASE + (_n) * 4) + +#define MT_WCID_ATTR_PAIRWISE BIT(0) +#define MT_WCID_ATTR_PKEY_MODE GENMASK(3, 1) +#define MT_WCID_ATTR_BSS_IDX GENMASK(6, 4) +#define MT_WCID_ATTR_RXWI_UDF GENMASK(9, 7) +#define MT_WCID_ATTR_PKEY_MODE_EXT BIT(10) +#define MT_WCID_ATTR_BSS_IDX_EXT BIT(11) +#define MT_WCID_ATTR_WAPI_MCBC BIT(15) +#define MT_WCID_ATTR_WAPI_KEYID GENMASK(31, 24) + +#define MT_SKEY_BASE_0 0xac00 +#define MT_SKEY_BASE_1 0xb400 +#define MT_SKEY_0(_bss, _idx) (MT_SKEY_BASE_0 + (4 * (_bss) + _idx) * 32) +#define MT_SKEY_1(_bss, _idx) (MT_SKEY_BASE_1 + (4 * ((_bss) & 7) + _idx) * 32) +#define MT_SKEY(_bss, _idx) ((_bss & 8) ? MT_SKEY_1(_bss, _idx) : MT_SKEY_0(_bss, _idx)) + +#define MT_SKEY_MODE_BASE_0 0xb000 +#define MT_SKEY_MODE_BASE_1 0xb3f0 +#define MT_SKEY_MODE_0(_bss) (MT_SKEY_MODE_BASE_0 + ((_bss / 2) << 2)) +#define MT_SKEY_MODE_1(_bss) (MT_SKEY_MODE_BASE_1 + ((((_bss) & 7) / 2) << 2)) +#define MT_SKEY_MODE(_bss) ((_bss & 8) ? MT_SKEY_MODE_1(_bss) : MT_SKEY_MODE_0(_bss)) +#define MT_SKEY_MODE_MASK GENMASK(3, 0) +#define MT_SKEY_MODE_SHIFT(_bss, _idx) (4 * ((_idx) + 4 * (_bss & 1))) + +#define MT_BEACON_BASE 0xc000 + +#define MT_TEMP_SENSOR 0x1d000 +#define MT_TEMP_SENSOR_VAL GENMASK(6, 0) + +struct mt76_wcid_addr { + u8 macaddr[6]; + __le16 ba_mask; +} __packed __aligned(4); + +struct mt76_wcid_key { + u8 key[16]; + u8 tx_mic[8]; + u8 rx_mic[8]; +} __packed __aligned(4); + +enum mt76x2_cipher_type { + MT_CIPHER_NONE, + MT_CIPHER_WEP40, + MT_CIPHER_WEP104, + MT_CIPHER_TKIP, + MT_CIPHER_AES_CCMP, + MT_CIPHER_CKIP40, + MT_CIPHER_CKIP104, + MT_CIPHER_CKIP128, + MT_CIPHER_WAPI, +}; + +#endif diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_trace.c b/drivers/net/wireless/mediatek/mt76/mt76x2_trace.c new file mode 100644 index 000000000000..a09f117848d6 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_trace.c @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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> + +#ifndef __CHECKER__ +#define CREATE_TRACE_POINTS +#include "mt76x2_trace.h" + +#endif diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_trace.h b/drivers/net/wireless/mediatek/mt76/mt76x2_trace.h new file mode 100644 index 000000000000..4cd424148d4b --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_trace.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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. + */ + +#if !defined(__MT76x2_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __MT76x2_TRACE_H + +#include <linux/tracepoint.h> +#include "mt76x2.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM mt76x2 + +#define MAXNAME 32 +#define DEV_ENTRY __array(char, wiphy_name, 32) +#define DEV_ASSIGN strlcpy(__entry->wiphy_name, wiphy_name(mt76_hw(dev)->wiphy), MAXNAME) +#define DEV_PR_FMT "%s" +#define DEV_PR_ARG __entry->wiphy_name + +#define TXID_ENTRY __field(u8, wcid) __field(u8, pktid) +#define TXID_ASSIGN __entry->wcid = wcid; __entry->pktid = pktid +#define TXID_PR_FMT " [%d:%d]" +#define TXID_PR_ARG __entry->wcid, __entry->pktid + +DECLARE_EVENT_CLASS(dev_evt, + TP_PROTO(struct mt76x2_dev *dev), + TP_ARGS(dev), + TP_STRUCT__entry( + DEV_ENTRY + ), + TP_fast_assign( + DEV_ASSIGN; + ), + TP_printk(DEV_PR_FMT, DEV_PR_ARG) +); + +DECLARE_EVENT_CLASS(dev_txid_evt, + TP_PROTO(struct mt76x2_dev *dev, u8 wcid, u8 pktid), + TP_ARGS(dev, wcid, pktid), + TP_STRUCT__entry( + DEV_ENTRY + TXID_ENTRY + ), + TP_fast_assign( + DEV_ASSIGN; + TXID_ASSIGN; + ), + TP_printk( + DEV_PR_FMT TXID_PR_FMT, + DEV_PR_ARG, TXID_PR_ARG + ) +); + +DEFINE_EVENT(dev_evt, mac_txstat_poll, + TP_PROTO(struct mt76x2_dev *dev), + TP_ARGS(dev) +); + +DEFINE_EVENT(dev_txid_evt, mac_txdone_add, + TP_PROTO(struct mt76x2_dev *dev, u8 wcid, u8 pktid), + TP_ARGS(dev, wcid, pktid) +); + +TRACE_EVENT(mac_txstat_fetch, + TP_PROTO(struct mt76x2_dev *dev, + struct mt76x2_tx_status *stat), + + TP_ARGS(dev, stat), + + TP_STRUCT__entry( + DEV_ENTRY + TXID_ENTRY + __field(bool, success) + __field(bool, aggr) + __field(bool, ack_req) + __field(u16, rate) + __field(u8, retry) + ), + + TP_fast_assign( + DEV_ASSIGN; + __entry->success = stat->success; + __entry->aggr = stat->aggr; + __entry->ack_req = stat->ack_req; + __entry->wcid = stat->wcid; + __entry->pktid = stat->pktid; + __entry->rate = stat->rate; + __entry->retry = stat->retry; + ), + + TP_printk( + DEV_PR_FMT TXID_PR_FMT + " success:%d aggr:%d ack_req:%d" + " rate:%04x retry:%d", + DEV_PR_ARG, TXID_PR_ARG, + __entry->success, __entry->aggr, __entry->ack_req, + __entry->rate, __entry->retry + ) +); + + +TRACE_EVENT(dev_irq, + TP_PROTO(struct mt76x2_dev *dev, u32 val, u32 mask), + + TP_ARGS(dev, val, mask), + + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, val) + __field(u32, mask) + ), + + TP_fast_assign( + DEV_ASSIGN; + __entry->val = val; + __entry->mask = mask; + ), + + TP_printk( + DEV_PR_FMT " %08x & %08x", + DEV_PR_ARG, __entry->val, __entry->mask + ) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE mt76x2_trace + +#include <trace/define_trace.h> diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2_tx.c b/drivers/net/wireless/mediatek/mt76/mt76x2_tx.c new file mode 100644 index 000000000000..534e4bf9a34c --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x2_tx.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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 "mt76x2.h" +#include "mt76x2_dma.h" + +struct beacon_bc_data { + struct mt76x2_dev *dev; + struct sk_buff_head q; + struct sk_buff *tail[8]; +}; + +void mt76x2_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct mt76x2_dev *dev = hw->priv; + struct ieee80211_vif *vif = info->control.vif; + struct mt76_wcid *wcid = &dev->global_wcid; + + if (control->sta) { + struct mt76x2_sta *msta; + + msta = (struct mt76x2_sta *) control->sta->drv_priv; + wcid = &msta->wcid; + } + + if (vif || (!info->control.hw_key && wcid->hw_key_idx != -1)) { + struct mt76x2_vif *mvif; + + mvif = (struct mt76x2_vif *) vif->drv_priv; + wcid = &mvif->group_wcid; + } + + mt76_tx(&dev->mt76, control->sta, wcid, skb); +} + +void mt76x2_tx_complete(struct mt76x2_dev *dev, struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + if (info->flags & IEEE80211_TX_CTL_AMPDU) { + ieee80211_free_txskb(mt76_hw(dev), skb); + } else { + ieee80211_tx_info_clear_status(info); + info->status.rates[0].idx = -1; + info->flags |= IEEE80211_TX_STAT_ACK; + ieee80211_tx_status(mt76_hw(dev), skb); + } +} + +s8 mt76x2_tx_get_max_txpwr_adj(struct mt76x2_dev *dev, + const struct ieee80211_tx_rate *rate) +{ + s8 max_txpwr; + + if (rate->flags & IEEE80211_TX_RC_VHT_MCS) { + u8 mcs = ieee80211_rate_get_vht_mcs(rate); + + if (mcs == 8 || mcs == 9) { + max_txpwr = dev->rate_power.vht[8]; + } else { + u8 nss, idx; + + nss = ieee80211_rate_get_vht_nss(rate); + idx = ((nss - 1) << 3) + mcs; + max_txpwr = dev->rate_power.ht[idx & 0xf]; + } + } else if (rate->flags & IEEE80211_TX_RC_MCS) { + max_txpwr = dev->rate_power.ht[rate->idx & 0xf]; + } else { + enum nl80211_band band = dev->mt76.chandef.chan->band; + + if (band == NL80211_BAND_2GHZ) { + const struct ieee80211_rate *r; + struct wiphy *wiphy = mt76_hw(dev)->wiphy; + struct mt76_rate_power *rp = &dev->rate_power; + + r = &wiphy->bands[band]->bitrates[rate->idx]; + if (r->flags & IEEE80211_RATE_SHORT_PREAMBLE) + max_txpwr = rp->cck[r->hw_value & 0x3]; + else + max_txpwr = rp->ofdm[r->hw_value & 0x7]; + } else { + max_txpwr = dev->rate_power.ofdm[rate->idx & 0x7]; + } + } + + return max_txpwr; +} + +s8 mt76x2_tx_get_txpwr_adj(struct mt76x2_dev *dev, s8 txpwr, s8 max_txpwr_adj) +{ + txpwr = min_t(s8, txpwr, dev->txpower_conf); + txpwr -= (dev->target_power + dev->target_power_delta[0]); + txpwr = min_t(s8, txpwr, max_txpwr_adj); + + if (!dev->enable_tpc) + return 0; + else if (txpwr >= 0) + return min_t(s8, txpwr, 7); + else + return (txpwr < -16) ? 8 : (txpwr + 32) / 2; +} + +void mt76x2_tx_set_txpwr_auto(struct mt76x2_dev *dev, s8 txpwr) +{ + s8 txpwr_adj; + + txpwr_adj = mt76x2_tx_get_txpwr_adj(dev, txpwr, + dev->rate_power.ofdm[4]); + mt76_rmw_field(dev, MT_PROT_AUTO_TX_CFG, + MT_PROT_AUTO_TX_CFG_PROT_PADJ, txpwr_adj); + mt76_rmw_field(dev, MT_PROT_AUTO_TX_CFG, + MT_PROT_AUTO_TX_CFG_AUTO_PADJ, txpwr_adj); +} + +static int mt76x2_insert_hdr_pad(struct sk_buff *skb) +{ + int len = ieee80211_get_hdrlen_from_skb(skb); + + if (len % 4 == 0) + return 0; + + skb_push(skb, 2); + memmove(skb->data, skb->data + 2, len); + + skb->data[len] = 0; + skb->data[len + 1] = 0; + return 2; +} + +int mt76x2_tx_prepare_skb(struct mt76_dev *mdev, void *txwi, + struct sk_buff *skb, struct mt76_queue *q, + struct mt76_wcid *wcid, struct ieee80211_sta *sta, + u32 *tx_info) +{ + struct mt76x2_dev *dev = container_of(mdev, struct mt76x2_dev, mt76); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + int qsel = MT_QSEL_EDCA; + int ret; + + if (q == &dev->mt76.q_tx[MT_TXQ_PSD] && wcid && wcid->idx < 128) + mt76x2_mac_wcid_set_drop(dev, wcid->idx, false); + + mt76x2_mac_write_txwi(dev, txwi, skb, wcid, sta); + + ret = mt76x2_insert_hdr_pad(skb); + if (ret < 0) + return ret; + + if (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE) + qsel = MT_QSEL_MGMT; + + *tx_info = FIELD_PREP(MT_TXD_INFO_QSEL, qsel) | + MT_TXD_INFO_80211; + + if (!wcid || wcid->hw_key_idx == 0xff || wcid->sw_iv) + *tx_info |= MT_TXD_INFO_WIV; + + return 0; +} + +static void +mt76x2_update_beacon_iter(void *priv, u8 *mac, struct ieee80211_vif *vif) +{ + struct mt76x2_dev *dev = (struct mt76x2_dev *) priv; + struct mt76x2_vif *mvif = (struct mt76x2_vif *) vif->drv_priv; + struct sk_buff *skb = NULL; + + if (!(dev->beacon_mask & BIT(mvif->idx))) + return; + + skb = ieee80211_beacon_get(mt76_hw(dev), vif); + if (!skb) + return; + + mt76x2_mac_set_beacon(dev, mvif->idx, skb); +} + +static void +mt76x2_add_buffered_bc(void *priv, u8 *mac, struct ieee80211_vif *vif) +{ + struct beacon_bc_data *data = priv; + struct mt76x2_dev *dev = data->dev; + struct mt76x2_vif *mvif = (struct mt76x2_vif *) vif->drv_priv; + struct ieee80211_tx_info *info; + struct sk_buff *skb; + + if (!(dev->beacon_mask & BIT(mvif->idx))) + return; + + skb = ieee80211_get_buffered_bc(mt76_hw(dev), vif); + if (!skb) + return; + + info = IEEE80211_SKB_CB(skb); + info->control.vif = vif; + info->flags |= IEEE80211_TX_CTL_ASSIGN_SEQ; + mt76_skb_set_moredata(skb, true); + __skb_queue_tail(&data->q, skb); + data->tail[mvif->idx] = skb; +} + +void mt76x2_pre_tbtt_tasklet(unsigned long arg) +{ + struct mt76x2_dev *dev = (struct mt76x2_dev *) arg; + struct mt76_queue *q = &dev->mt76.q_tx[MT_TXQ_PSD]; + struct beacon_bc_data data = {}; + struct sk_buff *skb; + int i, nframes; + + data.dev = dev; + __skb_queue_head_init(&data.q); + + ieee80211_iterate_active_interfaces_atomic(mt76_hw(dev), + IEEE80211_IFACE_ITER_RESUME_ALL, + mt76x2_update_beacon_iter, dev); + + do { + nframes = skb_queue_len(&data.q); + ieee80211_iterate_active_interfaces_atomic(mt76_hw(dev), + IEEE80211_IFACE_ITER_RESUME_ALL, + mt76x2_add_buffered_bc, &data); + } while (nframes != skb_queue_len(&data.q)); + + if (!nframes) + return; + + for (i = 0; i < ARRAY_SIZE(data.tail); i++) { + if (!data.tail[i]) + continue; + + mt76_skb_set_moredata(data.tail[i], false); + } + + spin_lock_bh(&q->lock); + while ((skb = __skb_dequeue(&data.q)) != NULL) { + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_vif *vif = info->control.vif; + struct mt76x2_vif *mvif = (struct mt76x2_vif *) vif->drv_priv; + + mt76_tx_queue_skb(&dev->mt76, q, skb, &mvif->group_wcid, NULL); + } + spin_unlock_bh(&q->lock); +} + diff --git a/drivers/net/wireless/mediatek/mt76/trace.c b/drivers/net/wireless/mediatek/mt76/trace.c new file mode 100644 index 000000000000..ea4ab8729ae4 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/trace.c @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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> + +#ifndef __CHECKER__ +#define CREATE_TRACE_POINTS +#include "trace.h" + +#endif diff --git a/drivers/net/wireless/mediatek/mt76/trace.h b/drivers/net/wireless/mediatek/mt76/trace.h new file mode 100644 index 000000000000..ea30895933c5 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/trace.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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. + */ + +#if !defined(__MT76_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __MT76_TRACE_H + +#include <linux/tracepoint.h> +#include "mt76.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM mt76 + +#define MAXNAME 32 +#define DEV_ENTRY __array(char, wiphy_name, 32) +#define DEV_ASSIGN strlcpy(__entry->wiphy_name, wiphy_name(dev->hw->wiphy), MAXNAME) +#define DEV_PR_FMT "%s" +#define DEV_PR_ARG __entry->wiphy_name + +#define REG_ENTRY __field(u32, reg) __field(u32, val) +#define REG_ASSIGN __entry->reg = reg; __entry->val = val +#define REG_PR_FMT " %04x=%08x" +#define REG_PR_ARG __entry->reg, __entry->val + +DECLARE_EVENT_CLASS(dev_reg_evt, + TP_PROTO(struct mt76_dev *dev, u32 reg, u32 val), + TP_ARGS(dev, reg, val), + TP_STRUCT__entry( + DEV_ENTRY + REG_ENTRY + ), + TP_fast_assign( + DEV_ASSIGN; + REG_ASSIGN; + ), + TP_printk( + DEV_PR_FMT REG_PR_FMT, + DEV_PR_ARG, REG_PR_ARG + ) +); + +DEFINE_EVENT(dev_reg_evt, reg_rr, + TP_PROTO(struct mt76_dev *dev, u32 reg, u32 val), + TP_ARGS(dev, reg, val) +); + +DEFINE_EVENT(dev_reg_evt, reg_wr, + TP_PROTO(struct mt76_dev *dev, u32 reg, u32 val), + TP_ARGS(dev, reg, val) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include <trace/define_trace.h> diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c new file mode 100644 index 000000000000..4eef69bd8a9e --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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" + +static struct mt76_txwi_cache * +mt76_alloc_txwi(struct mt76_dev *dev) +{ + struct mt76_txwi_cache *t; + dma_addr_t addr; + int size; + + size = (sizeof(*t) + L1_CACHE_BYTES - 1) & ~(L1_CACHE_BYTES - 1); + t = devm_kzalloc(dev->dev, size, GFP_ATOMIC); + if (!t) + return NULL; + + addr = dma_map_single(dev->dev, &t->txwi, sizeof(t->txwi), + DMA_TO_DEVICE); + t->dma_addr = addr; + + return t; +} + +static struct mt76_txwi_cache * +__mt76_get_txwi(struct mt76_dev *dev) +{ + struct mt76_txwi_cache *t = NULL; + + spin_lock_bh(&dev->lock); + if (!list_empty(&dev->txwi_cache)) { + t = list_first_entry(&dev->txwi_cache, struct mt76_txwi_cache, + list); + list_del(&t->list); + } + spin_unlock_bh(&dev->lock); + + return t; +} + +static struct mt76_txwi_cache * +mt76_get_txwi(struct mt76_dev *dev) +{ + struct mt76_txwi_cache *t = __mt76_get_txwi(dev); + + if (t) + return t; + + return mt76_alloc_txwi(dev); +} + +void +mt76_put_txwi(struct mt76_dev *dev, struct mt76_txwi_cache *t) +{ + if (!t) + return; + + spin_lock_bh(&dev->lock); + list_add(&t->list, &dev->txwi_cache); + spin_unlock_bh(&dev->lock); +} + +void mt76_tx_free(struct mt76_dev *dev) +{ + struct mt76_txwi_cache *t; + + while ((t = __mt76_get_txwi(dev)) != NULL) + dma_unmap_single(dev->dev, t->dma_addr, sizeof(t->txwi), + DMA_TO_DEVICE); +} + +static int +mt76_txq_get_qid(struct ieee80211_txq *txq) +{ + if (!txq->sta) + return MT_TXQ_BE; + + return txq->ac; +} + +int mt76_tx_queue_skb(struct mt76_dev *dev, struct mt76_queue *q, + struct sk_buff *skb, struct mt76_wcid *wcid, + struct ieee80211_sta *sta) +{ + struct mt76_queue_entry e; + struct mt76_txwi_cache *t; + struct mt76_queue_buf buf[32]; + struct sk_buff *iter; + dma_addr_t addr; + int len; + u32 tx_info = 0; + int n, ret; + + t = mt76_get_txwi(dev); + if (!t) { + ieee80211_free_txskb(dev->hw, skb); + return -ENOMEM; + } + + dma_sync_single_for_cpu(dev->dev, t->dma_addr, sizeof(t->txwi), + DMA_TO_DEVICE); + ret = dev->drv->tx_prepare_skb(dev, &t->txwi, skb, q, wcid, sta, + &tx_info); + dma_sync_single_for_device(dev->dev, t->dma_addr, sizeof(t->txwi), + DMA_TO_DEVICE); + if (ret < 0) + goto free; + + len = skb->len - skb->data_len; + addr = dma_map_single(dev->dev, skb->data, len, DMA_TO_DEVICE); + if (dma_mapping_error(dev->dev, addr)) { + ret = -ENOMEM; + goto free; + } + + n = 0; + buf[n].addr = t->dma_addr; + buf[n++].len = dev->drv->txwi_size; + buf[n].addr = addr; + buf[n++].len = len; + + skb_walk_frags(skb, iter) { + if (n == ARRAY_SIZE(buf)) + goto unmap; + + addr = dma_map_single(dev->dev, iter->data, iter->len, + DMA_TO_DEVICE); + if (dma_mapping_error(dev->dev, addr)) + goto unmap; + + buf[n].addr = addr; + buf[n++].len = iter->len; + } + + if (q->queued + (n + 1) / 2 >= q->ndesc - 1) + goto unmap; + + return dev->queue_ops->add_buf(dev, q, buf, n, tx_info, skb, t); + +unmap: + ret = -ENOMEM; + for (n--; n > 0; n--) + dma_unmap_single(dev->dev, buf[n].addr, buf[n].len, + DMA_TO_DEVICE); + +free: + e.skb = skb; + e.txwi = t; + dev->drv->tx_complete_skb(dev, q, &e, true); + mt76_put_txwi(dev, t); + return ret; +} +EXPORT_SYMBOL_GPL(mt76_tx_queue_skb); + +void +mt76_tx(struct mt76_dev *dev, struct ieee80211_sta *sta, + struct mt76_wcid *wcid, struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct mt76_queue *q; + int qid = skb_get_queue_mapping(skb); + + if (WARN_ON(qid >= MT_TXQ_PSD)) { + qid = MT_TXQ_BE; + skb_set_queue_mapping(skb, qid); + } + + if (!wcid->tx_rate_set) + ieee80211_get_tx_rates(info->control.vif, sta, skb, + info->control.rates, 1); + + q = &dev->q_tx[qid]; + + spin_lock_bh(&q->lock); + mt76_tx_queue_skb(dev, q, skb, wcid, sta); + dev->queue_ops->kick(dev, q); + + if (q->queued > q->ndesc - 8) + ieee80211_stop_queue(dev->hw, skb_get_queue_mapping(skb)); + spin_unlock_bh(&q->lock); +} +EXPORT_SYMBOL_GPL(mt76_tx); + +static struct sk_buff * +mt76_txq_dequeue(struct mt76_dev *dev, struct mt76_txq *mtxq, bool ps) +{ + struct ieee80211_txq *txq = mtxq_to_txq(mtxq); + struct sk_buff *skb; + + skb = skb_dequeue(&mtxq->retry_q); + if (skb) { + u8 tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK; + + if (ps && skb_queue_empty(&mtxq->retry_q)) + ieee80211_sta_set_buffered(txq->sta, tid, false); + + return skb; + } + + skb = ieee80211_tx_dequeue(dev->hw, txq); + if (!skb) + return NULL; + + return skb; +} + +static void +mt76_check_agg_ssn(struct mt76_txq *mtxq, struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; + + if (!ieee80211_is_data_qos(hdr->frame_control)) + return; + + mtxq->agg_ssn = le16_to_cpu(hdr->seq_ctrl) + 0x10; +} + +static void +mt76_queue_ps_skb(struct mt76_dev *dev, struct ieee80211_sta *sta, + struct sk_buff *skb, bool last) +{ + struct mt76_wcid *wcid = (struct mt76_wcid *) sta->drv_priv; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct mt76_queue *hwq = &dev->q_tx[MT_TXQ_PSD]; + + info->control.flags |= IEEE80211_TX_CTRL_PS_RESPONSE; + if (last) + info->flags |= IEEE80211_TX_STATUS_EOSP; + + mt76_skb_set_moredata(skb, !last); + mt76_tx_queue_skb(dev, hwq, skb, wcid, sta); +} + +void +mt76_release_buffered_frames(struct ieee80211_hw *hw, struct ieee80211_sta *sta, + u16 tids, int nframes, + enum ieee80211_frame_release_type reason, + bool more_data) +{ + struct mt76_dev *dev = hw->priv; + struct sk_buff *last_skb = NULL; + struct mt76_queue *hwq = &dev->q_tx[MT_TXQ_PSD]; + int i; + + spin_lock_bh(&hwq->lock); + for (i = 0; tids && nframes; i++, tids >>= 1) { + struct ieee80211_txq *txq = sta->txq[i]; + struct mt76_txq *mtxq = (struct mt76_txq *) txq->drv_priv; + struct sk_buff *skb; + + if (!(tids & 1)) + continue; + + do { + skb = mt76_txq_dequeue(dev, mtxq, true); + if (!skb) + break; + + if (mtxq->aggr) + mt76_check_agg_ssn(mtxq, skb); + + nframes--; + if (last_skb) + mt76_queue_ps_skb(dev, sta, last_skb, false); + + last_skb = skb; + } while (nframes); + } + + if (last_skb) { + mt76_queue_ps_skb(dev, sta, last_skb, true); + dev->queue_ops->kick(dev, hwq); + } + spin_unlock_bh(&hwq->lock); +} +EXPORT_SYMBOL_GPL(mt76_release_buffered_frames); + +static int +mt76_txq_send_burst(struct mt76_dev *dev, struct mt76_queue *hwq, + struct mt76_txq *mtxq, bool *empty) +{ + struct ieee80211_txq *txq = mtxq_to_txq(mtxq); + struct ieee80211_tx_info *info; + struct mt76_wcid *wcid = mtxq->wcid; + struct sk_buff *skb; + int n_frames = 1, limit; + struct ieee80211_tx_rate tx_rate; + bool ampdu; + bool probe; + int idx; + + skb = mt76_txq_dequeue(dev, mtxq, false); + if (!skb) { + *empty = true; + return 0; + } + + info = IEEE80211_SKB_CB(skb); + if (!wcid->tx_rate_set) + ieee80211_get_tx_rates(txq->vif, txq->sta, skb, + info->control.rates, 1); + tx_rate = info->control.rates[0]; + + probe = (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE); + ampdu = IEEE80211_SKB_CB(skb)->flags & IEEE80211_TX_CTL_AMPDU; + limit = ampdu ? 16 : 3; + + if (ampdu) + mt76_check_agg_ssn(mtxq, skb); + + idx = mt76_tx_queue_skb(dev, hwq, skb, wcid, txq->sta); + + if (idx < 0) + return idx; + + do { + bool cur_ampdu; + + if (probe) + break; + + if (test_bit(MT76_SCANNING, &dev->state) || + test_bit(MT76_RESET, &dev->state)) + return -EBUSY; + + skb = mt76_txq_dequeue(dev, mtxq, false); + if (!skb) { + *empty = true; + break; + } + + info = IEEE80211_SKB_CB(skb); + cur_ampdu = info->flags & IEEE80211_TX_CTL_AMPDU; + + if (ampdu != cur_ampdu || + (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE)) { + skb_queue_tail(&mtxq->retry_q, skb); + break; + } + + info->control.rates[0] = tx_rate; + + if (cur_ampdu) + mt76_check_agg_ssn(mtxq, skb); + + idx = mt76_tx_queue_skb(dev, hwq, skb, wcid, txq->sta); + if (idx < 0) + return idx; + + n_frames++; + } while (n_frames < limit); + + if (!probe) { + hwq->swq_queued++; + hwq->entry[idx].schedule = true; + } + + dev->queue_ops->kick(dev, hwq); + + return n_frames; +} + +static int +mt76_txq_schedule_list(struct mt76_dev *dev, struct mt76_queue *hwq) +{ + struct mt76_txq *mtxq, *mtxq_last; + int len = 0; + +restart: + mtxq_last = list_last_entry(&hwq->swq, struct mt76_txq, list); + while (!list_empty(&hwq->swq)) { + bool empty = false; + int cur; + + mtxq = list_first_entry(&hwq->swq, struct mt76_txq, list); + if (mtxq->send_bar && mtxq->aggr) { + struct ieee80211_txq *txq = mtxq_to_txq(mtxq); + struct ieee80211_sta *sta = txq->sta; + struct ieee80211_vif *vif = txq->vif; + u16 agg_ssn = mtxq->agg_ssn; + u8 tid = txq->tid; + + mtxq->send_bar = false; + spin_unlock_bh(&hwq->lock); + ieee80211_send_bar(vif, sta->addr, tid, agg_ssn); + spin_lock_bh(&hwq->lock); + goto restart; + } + + list_del_init(&mtxq->list); + + cur = mt76_txq_send_burst(dev, hwq, mtxq, &empty); + if (!empty) + list_add_tail(&mtxq->list, &hwq->swq); + + if (cur < 0) + return cur; + + len += cur; + + if (mtxq == mtxq_last) + break; + } + + return len; +} + +void mt76_txq_schedule(struct mt76_dev *dev, struct mt76_queue *hwq) +{ + int len; + + do { + if (hwq->swq_queued >= 4 || list_empty(&hwq->swq)) + break; + + len = mt76_txq_schedule_list(dev, hwq); + } while (len > 0); +} +EXPORT_SYMBOL_GPL(mt76_txq_schedule); + +void mt76_txq_schedule_all(struct mt76_dev *dev) +{ + int i; + + for (i = 0; i <= MT_TXQ_BK; i++) { + struct mt76_queue *q = &dev->q_tx[i]; + + spin_lock_bh(&q->lock); + mt76_txq_schedule(dev, q); + spin_unlock_bh(&q->lock); + } +} +EXPORT_SYMBOL_GPL(mt76_txq_schedule_all); + +void mt76_stop_tx_queues(struct mt76_dev *dev, struct ieee80211_sta *sta, + bool send_bar) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) { + struct ieee80211_txq *txq = sta->txq[i]; + struct mt76_txq *mtxq = (struct mt76_txq *) txq->drv_priv; + + spin_lock_bh(&mtxq->hwq->lock); + mtxq->send_bar = mtxq->aggr && send_bar; + if (!list_empty(&mtxq->list)) + list_del_init(&mtxq->list); + spin_unlock_bh(&mtxq->hwq->lock); + } +} +EXPORT_SYMBOL_GPL(mt76_stop_tx_queues); + +void mt76_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *txq) +{ + struct mt76_dev *dev = hw->priv; + struct mt76_txq *mtxq = (struct mt76_txq *) txq->drv_priv; + struct mt76_queue *hwq = mtxq->hwq; + + spin_lock_bh(&hwq->lock); + if (list_empty(&mtxq->list)) + list_add_tail(&mtxq->list, &hwq->swq); + mt76_txq_schedule(dev, hwq); + spin_unlock_bh(&hwq->lock); +} +EXPORT_SYMBOL_GPL(mt76_wake_tx_queue); + +void mt76_txq_remove(struct mt76_dev *dev, struct ieee80211_txq *txq) +{ + struct mt76_txq *mtxq; + struct mt76_queue *hwq; + struct sk_buff *skb; + + if (!txq) + return; + + mtxq = (struct mt76_txq *) txq->drv_priv; + hwq = mtxq->hwq; + + spin_lock_bh(&hwq->lock); + if (!list_empty(&mtxq->list)) + list_del(&mtxq->list); + spin_unlock_bh(&hwq->lock); + + while ((skb = skb_dequeue(&mtxq->retry_q)) != NULL) + ieee80211_free_txskb(dev->hw, skb); +} +EXPORT_SYMBOL_GPL(mt76_txq_remove); + +void mt76_txq_init(struct mt76_dev *dev, struct ieee80211_txq *txq) +{ + struct mt76_txq *mtxq = (struct mt76_txq *) txq->drv_priv; + + INIT_LIST_HEAD(&mtxq->list); + skb_queue_head_init(&mtxq->retry_q); + + mtxq->hwq = &dev->q_tx[mt76_txq_get_qid(txq)]; +} +EXPORT_SYMBOL_GPL(mt76_txq_init); diff --git a/drivers/net/wireless/mediatek/mt76/util.c b/drivers/net/wireless/mediatek/mt76/util.c new file mode 100644 index 000000000000..0c35b8db58cd --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/util.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * 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" + +bool __mt76_poll(struct mt76_dev *dev, u32 offset, u32 mask, u32 val, + int timeout) +{ + u32 cur; + + timeout /= 10; + do { + cur = dev->bus->rr(dev, offset) & mask; + if (cur == val) + return true; + + udelay(10); + } while (timeout-- > 0); + + return false; +} +EXPORT_SYMBOL_GPL(__mt76_poll); + +bool __mt76_poll_msec(struct mt76_dev *dev, u32 offset, u32 mask, u32 val, + int timeout) +{ + u32 cur; + + timeout /= 10; + do { + cur = dev->bus->rr(dev, offset) & mask; + if (cur == val) + return true; + + usleep_range(10000, 20000); + } while (timeout-- > 0); + + return false; +} +EXPORT_SYMBOL_GPL(__mt76_poll_msec); + +int mt76_wcid_alloc(unsigned long *mask, int size) +{ + int i, idx = 0, cur; + + for (i = 0; i < size / BITS_PER_LONG; i++) { + idx = ffs(~mask[i]); + if (!idx) + continue; + + idx--; + cur = i * BITS_PER_LONG + idx; + if (cur >= size) + break; + + mask[i] |= BIT(idx); + return cur; + } + + return -1; +} +EXPORT_SYMBOL_GPL(mt76_wcid_alloc); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/wireless/mediatek/mt76/util.h b/drivers/net/wireless/mediatek/mt76/util.h new file mode 100644 index 000000000000..018d475504a2 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/util.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MT76_UTIL_H +#define __MT76_UTIL_H + +#include <linux/skbuff.h> +#include <linux/bitops.h> +#include <linux/bitfield.h> + +#define MT76_INCR(_var, _size) \ + _var = (((_var) + 1) % _size) + +int mt76_wcid_alloc(unsigned long *mask, int size); + +static inline void +mt76_wcid_free(unsigned long *mask, int idx) +{ + mask[idx / BITS_PER_LONG] &= ~BIT(idx % BITS_PER_LONG); +} + +static inline void +mt76_skb_set_moredata(struct sk_buff *skb, bool enable) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; + + if (enable) + hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_MOREDATA); + else + hdr->frame_control &= ~cpu_to_le16(IEEE80211_FCTL_MOREDATA); +} + +#endif |