From 9e7049b1d11c67b77450b0ac748496905ac40b33 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Thu, 20 Nov 2014 21:21:02 +0100 Subject: [PATCH] Initial import Signed-off-by: Felix Fietkau --- Makefile | 9 + core.c | 132 +++ debugfs.c | 89 ++ dma.c | 589 ++++++++++++++ dma.h | 59 ++ eeprom.c | 578 +++++++++++++ eeprom.h | 180 +++++ firmware/LICENSE | 39 + firmware/mt7662_firmware_e3_v1.5.bin | Bin 0 -> 91412 bytes firmware/mt7662_patch_e3_hdr_v0.0.2_P48.bin | Bin 0 -> 20686 bytes init.c | 849 ++++++++++++++++++++ mac.c | 666 +++++++++++++++ mac.h | 199 +++++ main.c | 459 +++++++++++ mcu.c | 452 +++++++++++ mcu.h | 148 ++++ mt76.h | 339 ++++++++ pci.c | 135 ++++ phy.c | 690 ++++++++++++++++ regs.h | 540 +++++++++++++ trace.c | 20 + trace.h | 173 ++++ tx.c | 361 +++++++++ util.c | 42 + util.h | 75 ++ 25 files changed, 6823 insertions(+) create mode 100644 Makefile create mode 100644 core.c create mode 100644 debugfs.c create mode 100644 dma.c create mode 100644 dma.h create mode 100644 eeprom.c create mode 100644 eeprom.h create mode 100644 firmware/LICENSE create mode 100755 firmware/mt7662_firmware_e3_v1.5.bin create mode 100755 firmware/mt7662_patch_e3_hdr_v0.0.2_P48.bin create mode 100644 init.c create mode 100644 mac.c create mode 100644 mac.h create mode 100644 main.c create mode 100644 mcu.c create mode 100644 mcu.h create mode 100644 mt76.h create mode 100644 pci.c create mode 100644 phy.c create mode 100644 regs.h create mode 100644 trace.c create mode 100644 trace.h create mode 100644 tx.c create mode 100644 util.c create mode 100644 util.h diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..cccd558bc --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +EXTRA_CFLAGS += -Werror + +obj-m := mt76pci.o + +mt76pci-y := \ + pci.o dma.o \ + main.o init.o debugfs.o tx.o util.o \ + core.o mac.o eeprom.o mcu.o phy.o \ + trace.o diff --git a/core.c b/core.c new file mode 100644 index 000000000..8e68a9347 --- /dev/null +++ b/core.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include +#include "mt76.h" +#include "trace.h" + +bool mt76_poll(struct mt76_dev *dev, u32 offset, u32 mask, u32 val, + int timeout) +{ + u32 cur; + + timeout /= 10; + do { + cur = mt76_rr(dev, offset) & mask; + if (cur == val) + return true; + + udelay(10); + } while (timeout-- > 0); + + return false; +} + +bool mt76_poll_msec(struct mt76_dev *dev, u32 offset, u32 mask, u32 val, + int timeout) +{ + u32 cur; + + timeout /= 10; + do { + cur = mt76_rr(dev, offset) & mask; + if (cur == val) + return true; + + msleep(10); + } while (timeout-- > 0); + + return false; +} + +void mt76_write_reg_pairs(struct mt76_dev *dev, + const struct mt76_reg_pair *data, int len) +{ + while (len > 0) { + mt76_wr(dev, data->reg, data->value); + len--; + data++; + } +} + +void mt76_set_irq_mask(struct mt76_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); +} + +irqreturn_t mt76_irq_handler(int irq, void *dev_instance) +{ + struct mt76_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_INITAILIZED, &dev->state)) + return IRQ_NONE; + + trace_dev_irq(dev, intr, dev->irqmask); + + intr &= dev->irqmask; + + if (intr & MT_INT_TX_DONE_ALL) { + mt76_irq_disable(dev, MT_INT_TX_DONE_ALL); + tasklet_schedule(&dev->tx_tasklet); + } + + if (intr & MT_INT_RX_DONE(0)) { + mt76_irq_disable(dev, MT_INT_RX_DONE(0)); + napi_schedule(&dev->napi); + } + + if (intr & MT_INT_RX_DONE(1)) { + mt76_irq_disable(dev, MT_INT_RX_DONE(1)); + tasklet_schedule(&dev->rx_tasklet); + } + + if (intr & MT_INT_PRE_TBTT) + tasklet_schedule(&dev->pre_tbtt_tasklet); + + /* send buffered multicast frames now */ + if (intr & MT_INT_TBTT) + mt76_kick_queue(dev, &dev->q_tx[MT_TXQ_PSD]); + + if (intr & MT_INT_TX_STAT) { + mt76_mac_poll_tx_status(dev, true); + tasklet_schedule(&dev->tx_tasklet); + } + + return IRQ_HANDLED; +} + +int mt76_set_channel(struct mt76_dev *dev, struct cfg80211_chan_def *chandef) +{ + int ret; + + tasklet_disable(&dev->pre_tbtt_tasklet); + cancel_delayed_work_sync(&dev->cal_work); + + mt76_mac_stop(dev, true); + ret = mt76_phy_set_channel(dev, chandef); + mt76_mac_resume(dev); + tasklet_enable(&dev->pre_tbtt_tasklet); + + return ret; +} + diff --git a/debugfs.c b/debugfs.c new file mode 100644 index 000000000..98c67068e --- /dev/null +++ b/debugfs.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include +#include "mt76.h" + +static int +mt76_reg_set(void *data, u64 val) +{ + struct mt76_dev *dev = data; + + mt76_wr(dev, dev->debugfs_reg, val); + return 0; +} + +static int +mt76_reg_get(void *data, u64 *val) +{ + struct mt76_dev *dev = data; + + *val = mt76_rr(dev, dev->debugfs_reg); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(fops_regval, mt76_reg_get, mt76_reg_set, "0x%08llx\n"); + + +static int +mt76_ampdu_stat_read(struct seq_file *file, void *data) +{ + struct mt76_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 +mt76_ampdu_stat_open(struct inode *inode, struct file *f) +{ + return single_open(f, mt76_ampdu_stat_read, inode->i_private); +} + +static const struct file_operations fops_ampdu_stat = { + .open = mt76_ampdu_stat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +void mt76_init_debugfs(struct mt76_dev *dev) +{ + struct dentry *dir; + + dir = debugfs_create_dir("mt76", dev->hw->wiphy->debugfsdir); + if (!dir) + return; + + debugfs_create_blob("eeprom", S_IRUSR, dir, &dev->eeprom); + debugfs_create_u8("temperature", S_IRUSR, dir, &dev->cal.temp); + + debugfs_create_u32("regidx", S_IRUSR | S_IWUSR, dir, &dev->debugfs_reg); + debugfs_create_file("regval", S_IRUSR | S_IWUSR, dir, dev, &fops_regval); + debugfs_create_file("ampdu_stat", S_IRUSR, dir, dev, &fops_ampdu_stat); +} diff --git a/dma.c b/dma.c new file mode 100644 index 000000000..102854bb1 --- /dev/null +++ b/dma.c @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include "mt76.h" +#include "dma.h" + +struct mt76_txwi_cache { + struct mt76_txwi txwi; + dma_addr_t dma_addr; + struct list_head list; +}; + +static inline int +mt76_rx_buf_offset(struct mt76_dev *dev) +{ + BUILD_BUG_ON(MT_RX_HEADROOM < sizeof(struct mt76_rxwi)); + + return MT_RX_HEADROOM - sizeof(struct mt76_rxwi); +} + +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); +} + +static 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); +} + +static int +mt76_alloc_queue(struct mt76_dev *dev, struct mt76_queue *q) +{ + int size; + int i; + + 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; +} + +void mt76_kick_queue(struct mt76_dev *dev, struct mt76_queue *q) +{ + iowrite32(q->head, &q->regs->cpu_idx); +} + +static int +mt76_queue_add_buf(struct mt76_dev *dev, struct mt76_queue *q, + u32 buf0, int len0, u32 buf1, int len1, u32 info) +{ + struct mt76_desc *desc; + u32 ctrl; + int idx; + + ctrl = MT76_SET(MT_DMA_CTL_SD_LEN0, len0) | + MT76_SET(MT_DMA_CTL_SD_LEN1, len1) | + (len1 ? MT_DMA_CTL_LAST_SEC1 : MT_DMA_CTL_LAST_SEC0); + + idx = q->head; + q->head = (q->head + 1) % q->ndesc; + + desc = &q->desc[idx]; + + ACCESS_ONCE(desc->buf0) = cpu_to_le32(buf0); + ACCESS_ONCE(desc->buf1) = cpu_to_le32(buf1); + ACCESS_ONCE(desc->info) = cpu_to_le32(info); + ACCESS_ONCE(desc->ctrl) = cpu_to_le32(ctrl); + + q->queued++; + + return idx; +} + +static int +mt76_dma_rx_fill(struct mt76_dev *dev, struct mt76_queue *q) +{ + dma_addr_t addr; + void *buf; + int frames = 0; + int len = SKB_WITH_OVERHEAD(q->buf_size); + int idx; + + spin_lock_bh(&q->lock); + + while (q->queued < q->ndesc - 1) { + int offset = mt76_rx_buf_offset(dev); + + buf = kzalloc(len, GFP_ATOMIC); + if (!buf) + break; + + addr = dma_map_single(dev->dev, buf, len, DMA_FROM_DEVICE); + + if (dma_mapping_error(dev->dev, addr)) { + kfree(buf); + break; + } + + idx = mt76_queue_add_buf(dev, q, addr + offset, len - offset, + 0, 0, 0); + + q->entry[idx].buf = buf; + frames++; + } + + if (frames) + mt76_kick_queue(dev, q); + + spin_unlock_bh(&q->lock); + + return frames; +} + +int __mt76_tx_queue_skb(struct mt76_dev *dev, enum mt76_txq_id qid, + struct sk_buff *skb, u32 tx_info) +{ + struct mt76_queue *q = &dev->q_tx[qid]; + dma_addr_t addr; + int idx; + + addr = dma_map_single(dev->dev, skb->data, skb->len, DMA_TO_DEVICE); + if (dma_mapping_error(dev->dev, addr)) + return -ENOMEM; + + spin_lock_bh(&q->lock); + idx = mt76_queue_add_buf(dev, q, addr, skb->len, 0, 0, tx_info); + q->entry[idx].skb = skb; + mt76_kick_queue(dev, q); + spin_unlock_bh(&q->lock); + + return 0; +} + +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 ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct mt76_txwi_cache *t; + dma_addr_t addr; + u32 tx_info = 0; + int idx, ret, len; + int qsel = MT_QSEL_EDCA; + + ret = -ENOMEM; + t = mt76_get_txwi(dev); + if (!t) + goto free; + + dma_sync_single_for_cpu(dev->dev, t->dma_addr, sizeof(t->txwi), + DMA_TO_DEVICE); + mt76_mac_write_txwi(dev, &t->txwi, skb, wcid, sta); + dma_sync_single_for_device(dev->dev, t->dma_addr, sizeof(t->txwi), + DMA_TO_DEVICE); + + ret = mt76_insert_hdr_pad(skb); + if (ret) + goto put_txwi; + + if (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE) + qsel = 0; + + len = skb->len + sizeof(t->txwi); + len += mt76_mac_skb_tx_overhead(dev, skb); + tx_info = MT76_SET(MT_TXD_INFO_LEN, len) | + MT76_SET(MT_TXD_INFO_QSEL, qsel) | + MT_TXD_INFO_80211; + + if (!wcid || wcid->hw_key_idx < 0) + tx_info |= MT_TXD_INFO_WIV; + + addr = dma_map_single(dev->dev, skb->data, skb->len, DMA_TO_DEVICE); + if (dma_mapping_error(dev->dev, addr)) + goto put_txwi; + + idx = mt76_queue_add_buf(dev, q, t->dma_addr, sizeof(t->txwi), + addr, skb->len, tx_info); + q->entry[idx].skb = skb; + q->entry[idx].txwi = t; + + return idx; + +put_txwi: + mt76_put_txwi(dev, t); +free: + ieee80211_free_txskb(dev->hw, skb); + return ret; +} + +static int +mt76_dma_dequeue(struct mt76_dev *dev, struct mt76_queue *q, bool flush) +{ + int ret = -1; + + spin_lock_bh(&q->lock); + + if (!q->queued) + goto out; + + if (!flush && !(q->desc[q->tail].ctrl & cpu_to_le32(MT_DMA_CTL_DMA_DONE))) + goto out; + + ret = q->tail; + q->tail = (q->tail + 1) % q->ndesc; + q->queued--; + +out: + spin_unlock_bh(&q->lock); + return ret; +} + +static void +mt76_tx_cleanup_entry(struct mt76_dev *dev, struct mt76_queue *q, int idx, + bool flush) +{ + struct mt76_queue_entry *e = &q->entry[idx]; + struct sk_buff *skb = e->skb; + struct mt76_txwi_cache *txwi = e->txwi; + dma_addr_t skb_addr; + bool schedule = e->schedule; + + if (txwi) + skb_addr = ACCESS_ONCE(q->desc[idx].buf1); + else + skb_addr = ACCESS_ONCE(q->desc[idx].buf0); + + dma_unmap_single(dev->dev, skb_addr, skb->len, DMA_TO_DEVICE); + e->skb = NULL; + e->txwi = NULL; + e->schedule = false; + + if (txwi) { + skb_orphan(skb); + mt76_mac_queue_txdone(dev, skb, &txwi->txwi); + mt76_put_txwi(dev, txwi); + + if (schedule) { + spin_lock_bh(&q->lock); + q->swq_queued--; + if (!flush) + mt76_txq_schedule(dev, q); + spin_unlock_bh(&q->lock); + } + } else { + dev_kfree_skb_any(skb); + } +} + +static void +mt76_tx_cleanup_queue(struct mt76_dev *dev, struct mt76_queue *q, bool flush) +{ + int idx; + + do { + idx = mt76_dma_dequeue(dev, q, flush); + if (idx < 0) + break; + + mt76_tx_cleanup_entry(dev, q, idx, flush); + } while (1); +} + +static void * +mt76_rx_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx, int *len) +{ + struct mt76_queue_entry *e; + dma_addr_t buf_addr; + void *buf; + + e = &q->entry[idx]; + buf = e->buf; + buf_addr = ACCESS_ONCE(q->desc[idx].buf0); + if (len) { + u32 ctl = ACCESS_ONCE(q->desc[idx].ctrl); + *len = MT76_GET(MT_DMA_CTL_SD_LEN0, ctl); + } + dma_unmap_single(dev->dev, buf_addr, q->buf_size, DMA_FROM_DEVICE); + e->buf = NULL; + + return buf; +} + +static void +mt76_rx_cleanup(struct mt76_dev *dev, struct mt76_queue *q) +{ + void *buf; + int idx; + + do { + idx = mt76_dma_dequeue(dev, q, true); + if (idx < 0) + break; + + buf = mt76_rx_get_buf(dev, q, idx, NULL); + kfree(buf); + } while (1); +} + +static int +mt76_init_tx_queue(struct mt76_dev *dev, struct mt76_queue *q, + int idx, int n_desc, bool mcu) +{ + int ret; + + q->regs = dev->regs + MT_TX_RING_BASE + idx * MT_RING_SIZE; + q->ndesc = n_desc; + + ret = mt76_alloc_queue(dev, q); + if (ret) + return ret; + + mt76_irq_enable(dev, MT_INT_TX_DONE(idx)); + + return 0; +} + +static void +mt76_process_rx_skb(struct mt76_dev *dev, struct mt76_queue *q, + struct sk_buff *skb, u32 info) +{ + void *rxwi = skb->data - sizeof(struct mt76_rxwi); + + if (q == &dev->mcu.q_rx) { + u32 *rxfce; + + /* No RXWI header, data starts with payload */ + skb_push(skb, sizeof(struct mt76_rxwi)); + + rxfce = (u32 *) skb->cb; + *rxfce = info; + + skb_queue_tail(&dev->mcu.res_q, skb); + wake_up(&dev->mcu.wait); + return; + } + + memset(skb->cb, 0, sizeof(skb->cb)); + if (mt76_mac_process_rx(dev, skb, rxwi)) { + dev_kfree_skb(skb); + return; + } + + mt76_rx(dev, skb); +} + +static int +mt76_process_rx_queue(struct mt76_dev *dev, struct mt76_queue *q, int budget) +{ + struct mt76_desc *desc; + struct sk_buff *skb; + unsigned char *data; + int idx, len; + int done = 0; + + while (done < budget) { + idx = mt76_dma_dequeue(dev, q, false); + if (idx < 0) + break; + + data = mt76_rx_get_buf(dev, q, idx, &len); + skb = build_skb(data, 0); + if (!skb) { + kfree(data); + continue; + } + + skb_reserve(skb, MT_RX_HEADROOM); + skb_put(skb, len); + + desc = &q->desc[idx]; + mt76_process_rx_skb(dev, q, skb, le32_to_cpu(desc->info)); + done++; + } + + mt76_dma_rx_fill(dev, q); + return done; +} + +static int +mt76_init_rx_queue(struct mt76_dev *dev, struct mt76_queue *q, + int idx, int n_desc, int bufsize) +{ + int ret; + + q->regs = dev->regs + MT_RX_RING_BASE + idx * MT_RING_SIZE; + q->ndesc = n_desc; + q->buf_size = bufsize; + + ret = mt76_alloc_queue(dev, q); + if (ret) + return ret; + + mt76_irq_enable(dev, MT_INT_RX_DONE(idx)); + + return 0; +} + +static void +mt76_tx_tasklet(unsigned long data) +{ + struct mt76_dev *dev = (struct mt76_dev *) data; + int i; + + mt76_mac_process_tx_status_fifo(dev); + + for (i = ARRAY_SIZE(dev->q_tx) - 1; i >= 0; i--) + mt76_tx_cleanup_queue(dev, &dev->q_tx[i], false); + + mt76_mac_poll_tx_status(dev, false); + mt76_irq_enable(dev, MT_INT_TX_DONE_ALL); +} + +static int +mt76_dma_rx_poll(struct napi_struct *napi, int budget) +{ + struct mt76_dev *dev = container_of(napi, struct mt76_dev, napi); + int done; + + done = mt76_process_rx_queue(dev, &dev->q_rx, budget); + + if (done < budget) { + napi_complete(napi); + mt76_irq_enable(dev, MT_INT_RX_DONE(0)); + } + + return done; +} + +static void +mt76_rx_tasklet(unsigned long data) +{ + struct mt76_dev *dev = (struct mt76_dev *) data; + + mt76_process_rx_queue(dev, &dev->mcu.q_rx, dev->mcu.q_rx.ndesc); + + mt76_irq_enable(dev, MT_INT_RX_DONE(1)); +} + +int mt76_dma_init(struct mt76_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; + + init_waitqueue_head(&dev->mcu.wait); + skb_queue_head_init(&dev->mcu.res_q); + + spin_lock_init(&dev->q_rx.lock); + spin_lock_init(&dev->mcu.q_rx.lock); + for (i = 0; i < ARRAY_SIZE(dev->q_tx); i++) { + spin_lock_init(&dev->q_tx[i].lock); + INIT_LIST_HEAD(&dev->q_tx[i].swq); + } + + init_dummy_netdev(&dev->napi_dev); + netif_napi_add(&dev->napi_dev, &dev->napi, mt76_dma_rx_poll, 64); + + tasklet_init(&dev->tx_tasklet, mt76_tx_tasklet, (unsigned long) dev); + tasklet_init(&dev->rx_tasklet, mt76_rx_tasklet, (unsigned long) dev); + + mt76_wr(dev, MT_WPDMA_RST_IDX, ~0); + + for (i = 0; i < ARRAY_SIZE(wmm_queue_map); i++) { + ret = mt76_init_tx_queue(dev, &dev->q_tx[i], wmm_queue_map[i], + MT_TX_RING_SIZE, false); + if (ret) + return ret; + } + + ret = mt76_init_tx_queue(dev, &dev->q_tx[MT_TXQ_PSD], + MT_TX_HW_QUEUE_MGMT, MT_TX_RING_SIZE, false); + if (ret) + return ret; + + ret = mt76_init_tx_queue(dev, &dev->q_tx[MT_TXQ_MCU], + MT_TX_HW_QUEUE_MCU, MT_MCU_RING_SIZE, true); + if (ret) + return ret; + + ret = mt76_init_rx_queue(dev, &dev->mcu.q_rx, 1, MT_MCU_RING_SIZE, + MT_RX_BUF_SIZE); + if (ret) + return ret; + + ret = mt76_init_rx_queue(dev, &dev->q_rx, 0, + MT_RX_RING_SIZE, MT_RX_BUF_SIZE); + if (ret) + return ret; + + mt76_dma_rx_fill(dev, &dev->q_rx); + mt76_dma_rx_fill(dev, &dev->mcu.q_rx); + + return 0; +} + +void mt76_dma_cleanup(struct mt76_dev *dev) +{ + struct mt76_txwi_cache *t; + int i; + + tasklet_kill(&dev->tx_tasklet); + tasklet_kill(&dev->rx_tasklet); + for (i = 0; i < ARRAY_SIZE(dev->q_tx); i++) + mt76_tx_cleanup_queue(dev, &dev->q_tx[i], true); + mt76_rx_cleanup(dev, &dev->q_rx); + mt76_rx_cleanup(dev, &dev->mcu.q_rx); + + while ((t = __mt76_get_txwi(dev)) != NULL) + dma_unmap_single(dev->dev, t->dma_addr, sizeof(t->txwi), + DMA_TO_DEVICE); +} diff --git a/dma.h b/dma.h new file mode 100644 index 000000000..5f07773ab --- /dev/null +++ b/dma.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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_DMA_H +#define __MT76_DMA_H + +#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) + +#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) + +enum mt76_qsel { + MT_QSEL_MGMT, + MT_QSEL_HCCA, + MT_QSEL_EDCA, + MT_QSEL_EDCA_2, +}; + +struct mt76_desc { + __le32 buf0; + __le32 ctrl; + __le32 buf1; + __le32 info; +} __packed __aligned(4); + +#endif diff --git a/eeprom.c b/eeprom.c new file mode 100644 index 000000000..37f3f6fd8 --- /dev/null +++ b/eeprom.c @@ -0,0 +1,578 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include +#include +#include +#include +#include +#include "mt76.h" +#include "eeprom.h" + +#define EE_FIELD(_name, _value) [MT_EE_##_name] = (_value) | 1 + +static int +mt76_eeprom_copy(struct mt76_dev *dev, enum mt76_eeprom_field field, + void *dest, int len) +{ + if (field + len > dev->eeprom.size) + return -1; + + memcpy(dest, dev->eeprom.data + field, len); + return 0; +} + +static int +mt76_eeprom_get_macaddr(struct mt76_dev *dev) +{ + void *src = dev->eeprom.data + MT_EE_MAC_ADDR; + + memcpy(dev->macaddr, src, ETH_ALEN); + return 0; +} + +static int mt76_get_of_eeprom(struct mt76_dev *dev, int len) +{ + int ret = -ENOENT; +#ifdef CONFIG_OF + 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, retlen; + + 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; +#endif + return ret; +} + +static void +mt76_eeprom_parse_hw_cap(struct mt76_dev *dev) +{ + u16 val = MT_EE_NIC_CONF_0; + + switch (MT76_GET(MT_EE_NIC_CONF_0_BOARD_TYPE, val)) { + case BOARD_TYPE_5GHZ: + dev->cap.has_5ghz = true; + break; + case BOARD_TYPE_2GHZ: + dev->cap.has_2ghz = true; + break; + default: + dev->cap.has_2ghz = true; + dev->cap.has_5ghz = true; + break; + } +} + +static void +mt76_get_of_overrides(struct mt76_dev *dev) +{ +#ifdef CONFIG_OF + struct device_node *np = dev->dev->of_node; + const __be32 *val; + int size; + + if (!np) + return; + + val = of_get_property(np, "mediatek,2ghz", &size); + if (val) + dev->cap.has_2ghz = be32_to_cpup(val); + + val = of_get_property(np, "mediatek,5ghz", &size); + if (val) + dev->cap.has_5ghz = be32_to_cpup(val); +#endif +} + +static int +mt76_efuse_read(struct mt76_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 |= MT76_SET(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 < 3; i++) { + val = mt76_rr(dev, MT_EFUSE_DATA(i)); + put_unaligned_le32(val, data + 4 * i); + } + + return 0; +} + +static int +mt76_get_efuse_data(struct mt76_dev *dev, int len) +{ + int ret, i; + + for (i = 0; i + 16 <= len; i += 16) { + ret = mt76_efuse_read(dev, i, dev->eeprom.data + i); + if (ret) + return ret; + } + + return 0; +} + +static int +mt76_eeprom_load(struct mt76_dev *dev) +{ + int len = MT7662_EEPROM_SIZE; + + dev->eeprom.size = len; + dev->eeprom.data = devm_kzalloc(dev->dev, len, GFP_KERNEL); + if (!dev->eeprom.data) + return -ENOMEM; + + if (!mt76_get_of_eeprom(dev, len)) + return 0; + + if (!mt76_get_efuse_data(dev, len)) + return 0; + + return -ENOENT; +} + +static inline int +mt76_sign_extend(u32 val, unsigned size) +{ + bool sign = val & BIT(size - 1); + val &= BIT(size - 1) - 1; + return sign ? val : -val; +} + +static inline int +mt76_sign_extend_optional(u32 val, unsigned size) +{ + bool enable = val & BIT(size); + return enable ? mt76_sign_extend(val, size) : 0; +} + +static bool +field_valid(u8 val) +{ + return val != 0 && val != 0xff; +} + +static void +mt76_set_rx_gain_group(struct mt76_dev *dev, u8 val) +{ + s8 *dest = dev->cal.rx.high_gain; + + if (!field_valid(val)) { + dest[0] = 0; + dest[1] = 0; + return; + } + + dest[0] = mt76_sign_extend(val, 4); + dest[1] = mt76_sign_extend(val >> 4, 4); +} + +static void +mt76_set_rssi_offset(struct mt76_dev *dev, int chain, u8 val) +{ + s8 *dest = dev->cal.rx.rssi_offset; + + if (!field_valid(val)) { + dest[chain] = 0; + return; + } + + dest[chain] = mt76_sign_extend(val, 6); +} + +static enum mt76_cal_channel_group +mt76_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 +mt76_get_5g_rx_gain(struct mt76_dev *dev, u8 channel) +{ + enum mt76_cal_channel_group group = mt76_get_cal_channel_group(channel); + + switch (group) { + case MT_CH_5G_JAPAN: + return mt76_eeprom_get(dev, MT_EE_RF_5G_GRP0_1_RX_HIGH_GAIN); + case MT_CH_5G_UNII_1: + return mt76_eeprom_get(dev, MT_EE_RF_5G_GRP0_1_RX_HIGH_GAIN) >> 8; + case MT_CH_5G_UNII_2: + return mt76_eeprom_get(dev, MT_EE_RF_5G_GRP2_3_RX_HIGH_GAIN); + case MT_CH_5G_UNII_2E_1: + return mt76_eeprom_get(dev, MT_EE_RF_5G_GRP2_3_RX_HIGH_GAIN) >> 8; + case MT_CH_5G_UNII_2E_2: + return mt76_eeprom_get(dev, MT_EE_RF_5G_GRP4_5_RX_HIGH_GAIN); + default: + return mt76_eeprom_get(dev, MT_EE_RF_5G_GRP4_5_RX_HIGH_GAIN) >> 8; + } +} + +void mt76_read_rx_gain(struct mt76_dev *dev) +{ + struct ieee80211_channel *chan = dev->chandef.chan; + int channel = chan->hw_value; + s8 lna_5g[3], lna_2g; + u16 val; + + if (chan->band == IEEE80211_BAND_2GHZ) + val = mt76_eeprom_get(dev, MT_EE_RF_2G_RX_HIGH_GAIN) >> 8; + else + val = mt76_get_5g_rx_gain(dev, channel); + + mt76_set_rx_gain_group(dev, val); + + if (chan->band == IEEE80211_BAND_2GHZ) { + val = mt76_eeprom_get(dev, MT_EE_RSSI_OFFSET_2G_0); + mt76_set_rssi_offset(dev, 0, val); + mt76_set_rssi_offset(dev, 1, val >> 8); + } else { + val = mt76_eeprom_get(dev, MT_EE_RSSI_OFFSET_5G_0); + mt76_set_rssi_offset(dev, 0, val); + mt76_set_rssi_offset(dev, 1, val >> 8); + } + + val = mt76_eeprom_get(dev, MT_EE_LNA_GAIN); + lna_2g = val & 0xff; + lna_5g[0] = val >> 8; + + val = mt76_eeprom_get(dev, MT_EE_RSSI_OFFSET_2G_1); + lna_5g[1] = val >> 8; + + val = mt76_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 = mt76_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 == IEEE80211_BAND_2GHZ) + dev->cal.rx.lna_gain = lna_2g; + else if (channel <= 64) + dev->cal.rx.lna_gain = lna_5g[0]; + else if (channel <= 128) + dev->cal.rx.lna_gain = lna_5g[1]; + else + dev->cal.rx.lna_gain = lna_5g[2]; + +} + +static s8 +mt76_rate_power_val(u8 val) +{ + if (!field_valid(val)) + return 0; + + return mt76_sign_extend_optional(val, 7); +} + +void mt76_get_rate_power(struct mt76_dev *dev, struct mt76_rate_power *t) +{ + bool is_5ghz = false; + u16 val; + + is_5ghz = dev->chandef.chan->band == IEEE80211_BAND_5GHZ; + + memset(t, 0, sizeof(*t)); + + val = mt76_eeprom_get(dev, MT_EE_TX_POWER_CCK); + t->cck[0] = mt76_rate_power_val(val); + t->cck[1] = mt76_rate_power_val(val >> 8); + + if (is_5ghz) + val = mt76_eeprom_get(dev, MT_EE_TX_POWER_OFDM_5G_6M); + else + val = mt76_eeprom_get(dev, MT_EE_TX_POWER_OFDM_2G_6M); + t->ofdm[0] = mt76_rate_power_val(val); + t->ofdm[1] = mt76_rate_power_val(val >> 8); + + if (is_5ghz) + val = mt76_eeprom_get(dev, MT_EE_TX_POWER_OFDM_2G_24M); + else + val = mt76_eeprom_get(dev, MT_EE_TX_POWER_OFDM_5G_24M); + t->ofdm[2] = mt76_rate_power_val(val); + t->ofdm[3] = mt76_rate_power_val(val >> 8); + + val = mt76_eeprom_get(dev, MT_EE_TX_POWER_HT_MCS0); + t->ht[0] = mt76_rate_power_val(val); + t->ht[1] = mt76_rate_power_val(val >> 8); + + val = mt76_eeprom_get(dev, MT_EE_TX_POWER_HT_MCS4); + t->ht[2] = mt76_rate_power_val(val); + t->ht[3] = mt76_rate_power_val(val >> 8); + + val = mt76_eeprom_get(dev, MT_EE_TX_POWER_HT_MCS8); + t->ht[4] = mt76_rate_power_val(val); + t->ht[5] = mt76_rate_power_val(val >> 8); + + val = mt76_eeprom_get(dev, MT_EE_TX_POWER_HT_MCS12); + t->ht[6] = mt76_rate_power_val(val); + t->ht[7] = mt76_rate_power_val(val >> 8); + + val = mt76_eeprom_get(dev, MT_EE_TX_POWER_VHT_MCS0); + t->vht[0] = mt76_rate_power_val(val); + t->vht[1] = mt76_rate_power_val(val >> 8); + + val = mt76_eeprom_get(dev, MT_EE_TX_POWER_VHT_MCS4); + t->vht[2] = mt76_rate_power_val(val); + t->vht[3] = mt76_rate_power_val(val >> 8); + + val = mt76_eeprom_get(dev, MT_EE_TX_POWER_VHT_MCS8); + if (!is_5ghz) + val >>= 8; + t->vht[4] = mt76_rate_power_val(val); +} + +static void +mt76_get_power_info_2g(struct mt76_dev *dev, struct mt76_tx_power_info *t, + int chain, int offset) +{ + int channel = dev->chandef.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; + + mt76_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 = data[delta_idx]; + + val = mt76_eeprom_get(dev, MT_EE_RF_2G_TSSI_OFF_TXPOWER); + t->target_power = val >> 8; +} + +static void +mt76_get_power_info_5g(struct mt76_dev *dev, struct mt76_tx_power_info *t, + int chain, int offset) +{ + int channel = dev->chandef.chan->hw_value; + enum mt76_cal_channel_group group = mt76_get_cal_channel_group(channel); + int delta_idx; + u16 val; + u8 data[5]; + + offset += group * MT_TX_POWER_GROUP_SIZE_5G; + + if (channel >= 192) + delta_idx = 4; + else if (channel >= 484) + 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; + + mt76_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 = data[delta_idx]; + + val = mt76_eeprom_get(dev, MT_EE_RF_2G_RX_HIGH_GAIN); + t->target_power = val & 0xff; +} + +void mt76_get_power_info(struct mt76_dev *dev, struct mt76_tx_power_info *t) +{ + u16 bw40, bw80; + memset(t, 0, sizeof(*t)); + + bw40 = mt76_eeprom_get(dev, MT_EE_TX_POWER_DELTA_BW40); + bw80 = mt76_eeprom_get(dev, MT_EE_TX_POWER_DELTA_BW80); + + if (dev->chandef.chan->band == IEEE80211_BAND_5GHZ) { + bw40 >>= 8; + mt76_get_power_info_5g(dev, t, 0, MT_EE_TX_POWER_0_START_5G); + mt76_get_power_info_5g(dev, t, 1, MT_EE_TX_POWER_1_START_5G); + } else { + mt76_get_power_info_2g(dev, t, 0, MT_EE_TX_POWER_0_START_2G); + mt76_get_power_info_2g(dev, t, 1, MT_EE_TX_POWER_1_START_2G); + } + + if (mt76_tssi_enabled(dev) || !field_valid(t->target_power)) + t->target_power = t->chain[0].target_power; + + t->delta_bw40 = mt76_rate_power_val(bw40); + t->delta_bw80 = mt76_rate_power_val(bw80); +} + +int mt76_get_temp_comp(struct mt76_dev *dev, struct mt76_temp_comp *t) +{ + enum ieee80211_band band = dev->chandef.chan->band; + u16 val, slope; + u8 bounds; + + memset(t, 0, sizeof(*t)); + + val = mt76_eeprom_get(dev, MT_EE_NIC_CONF_1); + if (!(val & MT_EE_NIC_CONF_1_TEMP_TX_ALC)) + return -EINVAL; + + if (!mt76_ext_pa_enabled(dev, band)) + return -EINVAL; + + val = mt76_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 == IEEE80211_BAND_5GHZ) { + slope = mt76_eeprom_get(dev, MT_EE_RF_TEMP_COMP_SLOPE_5G); + bounds = mt76_eeprom_get(dev, MT_EE_TX_POWER_EXT_PA_5G); + } else { + slope = mt76_eeprom_get(dev, MT_EE_RF_TEMP_COMP_SLOPE_2G); + bounds = mt76_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 mt76_ext_pa_enabled(struct mt76_dev *dev, enum ieee80211_band band) +{ + u16 conf1 = mt76_eeprom_get(dev, MT_EE_NIC_CONF_0); + + if (band == IEEE80211_BAND_5GHZ) + return !(conf1 & MT_EE_NIC_CONF_0_PA_INT_5G); + else + return !(conf1 & MT_EE_NIC_CONF_0_PA_INT_2G); +} + +int mt76_eeprom_init(struct mt76_dev *dev) +{ + int ret; + + ret = mt76_eeprom_load(dev); + if (ret) + return ret; + + mt76_eeprom_parse_hw_cap(dev); + mt76_get_of_overrides(dev); + + mt76_eeprom_get_macaddr(dev); + if (!is_valid_ether_addr(dev->macaddr)) { + eth_random_addr(dev->macaddr); + dev_printk(KERN_INFO, dev->dev, + "Invalid MAC address, using random address %pM\n", + dev->macaddr); + } + + return 0; +} diff --git a/eeprom.h b/eeprom.h new file mode 100644 index 000000000..e5c121e15 --- /dev/null +++ b/eeprom.h @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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_EEPROM_H +#define __MT76_EEPROM_H + +#include "mt76.h" + +enum mt76_eeprom_field { + MT_EE_CHIP_ID = 0x000, + MT_EE_VERSION = 0x002, + MT_EE_MAC_ADDR = 0x004, + 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_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_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 mt76_board_type { + BOARD_TYPE_2GHZ = 1, + BOARD_TYPE_5GHZ = 2, +}; + +enum mt76_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 mt76_rate_power { + s8 cck[2]; + s8 ofdm[4]; + s8 ht[8]; + s8 vht[5]; +}; + +struct mt76_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 mt76_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 +mt76_eeprom_get(struct mt76_dev *dev, enum mt76_eeprom_field field) +{ + if ((field & 1) || field >= __MT_EE_MAX) + return -1; + + return get_unaligned_le16(dev->eeprom.data + field); +} + +void mt76_get_rate_power(struct mt76_dev *dev, struct mt76_rate_power *t); +void mt76_get_power_info(struct mt76_dev *dev, struct mt76_tx_power_info *t); +int mt76_get_temp_comp(struct mt76_dev *dev, struct mt76_temp_comp *t); +bool mt76_ext_pa_enabled(struct mt76_dev *dev, enum ieee80211_band band); +void mt76_read_rx_gain(struct mt76_dev *dev); + +static inline bool +mt76_temp_tx_alc_enabled(struct mt76_dev *dev) +{ + return mt76_eeprom_get(dev, MT_EE_NIC_CONF_1) & + MT_EE_NIC_CONF_1_TEMP_TX_ALC; +} + +static inline bool +mt76_tssi_enabled(struct mt76_dev *dev) +{ + return !mt76_temp_tx_alc_enabled(dev) && + (mt76_eeprom_get(dev, MT_EE_NIC_CONF_1) & + MT_EE_NIC_CONF_1_TX_ALC_EN); +} + + +static inline bool +mt76_has_ext_lna(struct mt76_dev *dev) +{ + u32 val = mt76_eeprom_get(dev, MT_EE_NIC_CONF_1); + + if (dev->chandef.chan->band == IEEE80211_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/firmware/LICENSE b/firmware/LICENSE new file mode 100644 index 000000000..be614c0b4 --- /dev/null +++ b/firmware/LICENSE @@ -0,0 +1,39 @@ +Copyright (c) 2014, Ralink, A MediaTek Company +All rights reserved. + +Redistribution. Redistribution and use in binary form, without +modification, are permitted provided that the following conditions are +met: + +* Redistributions must reproduce the above copyright notice and the + following disclaimer in the documentation and/or other materials + provided with the distribution. +* Neither the name of Ralink Technology Corporation nor the names of its + suppliers may be used to endorse or promote products derived from this + software without specific prior written permission. +* No reverse engineering, decompilation, or disassembly of this software + is permitted. + +Limited patent license. Ralink Technology Corporation grants a world-wide, +royalty-free, non-exclusive license under patents it now or hereafter +owns or controls to make, have made, use, import, offer to sell and +sell ("Utilize") this software, but solely to the extent that any +such patent is necessary to Utilize the software alone, or in +combination with an operating system licensed under an approved Open +Source license as listed by the Open Source Initiative at +http://opensource.org/licenses. The patent license shall not apply to +any other combinations which include this software. No hardware per +se is licensed hereunder. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/firmware/mt7662_firmware_e3_v1.5.bin b/firmware/mt7662_firmware_e3_v1.5.bin new file mode 100755 index 0000000000000000000000000000000000000000..93c8a7967c8b225f7be3cb2b24f717a79d18bda5 GIT binary patch literal 91412 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®&QtM!QeI5;KSAw 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 literal 0 HcmV?d00001 diff --git a/firmware/mt7662_patch_e3_hdr_v0.0.2_P48.bin b/firmware/mt7662_patch_e3_hdr_v0.0.2_P48.bin new file mode 100755 index 0000000000000000000000000000000000000000..2213b2a831e8050705e2374aef16872aacb08c66 GIT binary patch literal 20686 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 ASO5S3 literal 0 HcmV?d00001 diff --git a/init.c b/init.c new file mode 100644 index 000000000..d995fc8a0 --- /dev/null +++ b/init.c @@ -0,0 +1,849 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include +#include "mt76.h" +#include "eeprom.h" +#include "mcu.h" + +static bool +mt76_wait_for_mac(struct mt76_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; + } + msleep(5); + } + + return false; +} + +static bool +wait_for_wpdma(struct mt76_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 +mt76_mac_pbf_init(struct mt76_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 +mt76_write_mac_initvals(struct mt76_dev *dev) +{ + static const struct mt76_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, 0x003e3fff }, + { 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, 0x0000583f }, + { MT_TX_RTS_CFG, 0x00092b20 }, + { 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, 0x00008003 }, + { MT_PN_PAD_MODE, 0x00000002 }, + { 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 }, + }; + + mt76_write_reg_pairs(dev, vals, ARRAY_SIZE(vals)); +} + +static void +mt76_fixup_xtal(struct mt76_dev *dev) +{ + u16 eep_val; + s8 offset = 0; + + eep_val = mt76_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 = mt76_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 = mt76_eeprom_get(dev, MT_EE_NIC_CONF_2); + switch (MT76_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 +mt76_init_beacon_offsets(struct mt76_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 mt76_mac_reset(struct mt76_dev *dev, bool hard) +{ + static const u8 null_addr[ETH_ALEN] = {}; + u32 val; + int i, k; + + if (!mt76_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 |= MT76_SET(MT_WPDMA_GLO_CFG_DMA_BURST_SIZE, 3); + + mt76_wr(dev, MT_WPDMA_GLO_CFG, val); + + mt76_mac_pbf_init(dev); + mt76_write_mac_initvals(dev); + mt76_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); + msleep(5); + 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(dev->macaddr)); + mt76_wr(dev, MT_MAC_ADDR_DW1, get_unaligned_le16(dev->macaddr + 4)); + + mt76_wr(dev, MT_MAC_BSSID_DW0, get_unaligned_le32(dev->macaddr)); + mt76_wr(dev, MT_MAC_BSSID_DW1, get_unaligned_le16(dev->macaddr + 4) | + MT76_SET(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_wr(dev, MT_INT_TIMER_EN, 0); + + mt76_wr(dev, MT_BCN_BYPASS_MASK, 0xffff); + if (!hard) + return 0; + + for (i = 0; i < 256; i++) + mt76_mac_wcid_setup(dev, i, 0, NULL); + + for (i = 0; i < 16; i++) + for (k = 0; k < 4; k++) + mt76_mac_shared_key_setup(dev, i, k, NULL); + + for (i = 0; i < 8; i++) { + mt76_mac_set_bssid(dev, i, null_addr); + mt76_mac_set_beacon(dev, i, NULL); + } + + for (i = 0; i < 16; i++) + mt76_rr(dev, MT_TX_STAT_FIFO); + + mt76_set(dev, MT_MAC_APC_BSSID_H(0), MT_MAC_APC_BSSID0_H_EN); + mt76_init_beacon_offsets(dev); + + return 0; +} + +int mt76_mac_start(struct mt76_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); + udelay(50); + + mt76_set(dev, MT_WPDMA_GLO_CFG, + MT_WPDMA_GLO_CFG_TX_DMA_EN | + MT_WPDMA_GLO_CFG_RX_DMA_EN | + 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); + + mt76_irq_enable(dev, MT_INT_RX_DONE_ALL | MT_INT_TX_DONE_ALL | MT_INT_TX_STAT); + + return 0; +} + +void mt76_mac_stop(struct mt76_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(2)); + mt76_clear(dev, MT_BBP(CORE, 4), BIT(2)); + + mt76_set(dev, MT_BBP(CORE, 4), BIT(1)); + mt76_clear(dev, MT_BBP(CORE, 4), BIT(1)); + } + + mt76_wr(dev, MT_TX_RTS_CFG, rts_cfg); +} + +void mt76_mac_resume(struct mt76_dev *dev) +{ + mt76_wr(dev, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_ENABLE_TX | + MT_MAC_SYS_CTRL_ENABLE_RX); +} + + +static void +mt76_power_on_rf_patch(struct mt76_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 +mt76_power_on_rf(struct mt76_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); + + mt76_power_on_rf_patch(dev); + + mt76_set(dev, 0x530, 0xf); +} + +static void +mt76_power_on(struct mt76_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)); + + mt76_power_on_rf(dev, 0); + mt76_power_on_rf(dev, 1); +} + +static void +mt76_set_wlan_state(struct mt76_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 +mt76_reset_wlan(struct mt76_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); + + mt76_set_wlan_state(dev, enable); +} + +int mt76_init_hardware(struct mt76_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, mt76_pre_tbtt_tasklet, + (unsigned long) dev); + + dev->chainmask = 0x202; + + 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); + + mt76_reset_wlan(dev, true); + mt76_power_on(dev); + + ret = mt76_eeprom_init(dev); + if (ret) + return ret; + + ret = mt76_mac_reset(dev, true); + if (ret) + return ret; + + ret = mt76_dma_init(dev); + if (ret) + return ret; + + set_bit(MT76_STATE_INITAILIZED, &dev->state); + ret = mt76_mac_start(dev); + if (ret) + return ret; + + ret = mt76_mcu_init(dev); + if (ret) + return ret; + + mt76_mac_stop(dev, false); + dev->rxfilter = mt76_rr(dev, MT_RX_FILTR_CFG); + + return 0; +} + +void mt76_stop_hardware(struct mt76_dev *dev) +{ + cancel_delayed_work_sync(&dev->cal_work); + cancel_delayed_work_sync(&dev->mac_work); + mt76_mcu_set_radio_state(dev, false); + mt76_mac_stop(dev, false); +} + +void mt76_cleanup(struct mt76_dev *dev) +{ + mt76_stop_hardware(dev); + mt76_dma_cleanup(dev); + mt76_mcu_cleanup(dev); +} + +struct mt76_dev *mt76_alloc_device(struct device *pdev) +{ + struct ieee80211_hw *hw; + struct mt76_dev *dev; + + hw = ieee80211_alloc_hw(sizeof(*dev), &mt76_ops); + if (!hw) + return NULL; + + dev = hw->priv; + dev->dev = pdev; + dev->hw = hw; + mutex_init(&dev->mutex); + spin_lock_init(&dev->lock); + spin_lock_init(&dev->irq_lock); + + return dev; +} + +#define CHAN2G(_idx, _freq) { \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 30, \ +} + +#define CHAN5G(_idx, _freq) { \ + .band = IEEE80211_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), +}; + +#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 mt76_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 int +mt76_init_sband(struct mt76_dev *dev, struct ieee80211_supported_band *sband, + const struct ieee80211_channel *chan, int n_chan, + struct ieee80211_rate *rates, int n_rates) +{ + 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; + + sband->channels = chanlist; + sband->n_channels = n_chan; + sband->bitrates = rates; + sband->n_bitrates = n_rates; + + 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_LDPC_CODING | + 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; + + 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; + + dev->chandef.chan = &sband->channels[0]; + + return 0; +} + +static int +mt76_init_sband_2g(struct mt76_dev *dev) +{ + if (!dev->cap.has_2ghz) + return 0; + + dev->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &dev->sband_2g; + return mt76_init_sband(dev, &dev->sband_2g, + mt76_channels_2ghz, + ARRAY_SIZE(mt76_channels_2ghz), + mt76_rates, ARRAY_SIZE(mt76_rates)); +} + +static int +mt76_init_sband_5g(struct mt76_dev *dev) +{ + if (!dev->cap.has_5ghz) + return 0; + + dev->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &dev->sband_5g; + return mt76_init_sband(dev, &dev->sband_5g, + mt76_channels_5ghz, + ARRAY_SIZE(mt76_channels_5ghz), + mt76_rates + 4, ARRAY_SIZE(mt76_rates) - 4); +} + +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, + } +}; + +int mt76_register_device(struct mt76_dev *dev) +{ + struct ieee80211_hw *hw = dev->hw; + struct wiphy *wiphy = hw->wiphy; + void *status_fifo; + int fifo_size; + int i, ret; + + fifo_size = roundup_pow_of_two(32 * sizeof(struct mt76_tx_status)); + status_fifo = devm_kzalloc(dev->dev, fifo_size, GFP_KERNEL); + if (!status_fifo) + return -ENOMEM; + + kfifo_init(&dev->txstatus_fifo, status_fifo, fifo_size); + + ret = mt76_init_hardware(dev); + if (ret) + return ret; + + SET_IEEE80211_DEV(hw, dev->dev); + + hw->queues = 4; + hw->flags = IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_PS_NULLFUNC_STACK | + IEEE80211_HW_SUPPORTS_HT_CCK_RATES | + IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING | + IEEE80211_HW_AMPDU_AGGREGATION | + IEEE80211_HW_SUPPORTS_RC_TABLE; + hw->max_rates = 1; + hw->max_report_rates = 7; + hw->max_rate_tries = 1; + + hw->sta_data_size = sizeof(struct mt76_sta); + hw->vif_data_size = sizeof(struct mt76_vif); + hw->txq_data_size = sizeof(struct mt76_txq); + + dev->macaddr[0] &= ~BIT(1); + SET_IEEE80211_PERM_ADDR(hw, dev->macaddr); + + for (i = 0; i < ARRAY_SIZE(dev->macaddr_list); i++) { + u8 *addr = dev->macaddr_list[i].addr; + memcpy(addr, dev->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->features |= NL80211_FEATURE_ACTIVE_MONITOR; + + 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->iface_combinations = if_comb; + wiphy->n_iface_combinations = ARRAY_SIZE(if_comb); + + ret = mt76_init_sband_2g(dev); + if (ret) + goto fail; + + ret = mt76_init_sband_5g(dev); + if (ret) + goto fail; + + INIT_LIST_HEAD(&dev->txwi_cache); + INIT_DELAYED_WORK(&dev->cal_work, mt76_phy_calibrate); + INIT_DELAYED_WORK(&dev->mac_work, mt76_mac_work); + + ret = ieee80211_register_hw(hw); + if (ret) + goto fail; + + mt76_init_debugfs(dev); + + return 0; + +fail: + mt76_stop_hardware(dev); + return ret; +} + + diff --git a/mac.c b/mac.c new file mode 100644 index 000000000..f9c254de2 --- /dev/null +++ b/mac.c @@ -0,0 +1,666 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include +#include "mt76.h" +#include "mcu.h" +#include "eeprom.h" +#include "trace.h" + +void mt76_mac_set_bssid(struct mt76_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 void +mt76_mac_process_rate(struct ieee80211_rx_status *status, u16 rate) +{ + u8 idx = MT76_GET(MT_RXWI_RATE_INDEX, rate); + + switch (MT76_GET(MT_RXWI_RATE_PHY, rate)) { + case MT_PHY_TYPE_OFDM: + if (idx >= 8) + idx = 0; + + if (status->band == IEEE80211_BAND_2GHZ) + idx += 4; + + status->rate_idx = idx; + return; + case MT_PHY_TYPE_CCK: + if (idx >= 8) { + idx -= 8; + status->flag |= RX_FLAG_SHORTPRE; + } + + if (idx >= 4) + idx = 0; + + status->rate_idx = idx; + return; + case MT_PHY_TYPE_HT_GF: + status->flag |= RX_FLAG_HT_GF; + /* fall through */ + case MT_PHY_TYPE_HT: + status->flag |= RX_FLAG_HT; + status->rate_idx = idx; + break; + case MT_PHY_TYPE_VHT: + status->flag |= RX_FLAG_VHT; + status->rate_idx = MT76_GET(MT_RATE_INDEX_VHT_IDX, idx); + status->vht_nss = MT76_GET(MT_RATE_INDEX_VHT_NSS, idx) + 1; + break; + default: + WARN_ON(1); + return; + } + + if (rate & MT_RXWI_RATE_LDPC) + status->flag |= RX_FLAG_LDPC; + + if (rate & MT_RXWI_RATE_SGI) + status->flag |= RX_FLAG_SHORT_GI; + + if (rate & MT_RXWI_RATE_STBC) + status->flag |= 1 << RX_FLAG_STBC_SHIFT; + + switch (MT76_GET(MT_RXWI_RATE_BW, rate)) { + case MT_PHY_BW_20: + break; + case MT_PHY_BW_40: + status->flag |= RX_FLAG_40MHZ; + break; + case MT_PHY_BW_80: + status->vht_flag |= RX_VHT_FLAG_80MHZ; + break; + default: + break; + } +} + +static __le16 +mt76_mac_tx_rate_val(struct mt76_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->chandef.chan->band; + u16 val; + + r = &dev->hw->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 = MT76_SET(MT_RXWI_RATE_INDEX, rate_idx); + rateval |= MT76_SET(MT_RXWI_RATE_PHY, phy); + rateval |= MT76_SET(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 mt76_mac_wcid_set_rate(struct mt76_dev *dev, struct mt76_wcid *wcid, + const struct ieee80211_tx_rate *rate) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + wcid->tx_rate = mt76_mac_tx_rate_val(dev, rate, &wcid->tx_rate_nss); + wcid->tx_rate_set = true; + spin_unlock_irqrestore(&dev->lock, flags); +} + +void mt76_mac_write_txwi(struct mt76_dev *dev, struct mt76_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]; + unsigned long flags; + u16 rate_ht_mask = MT76_SET(MT_RXWI_RATE_PHY, BIT(1) | BIT(2)); + u16 txwi_flags = 0; + u8 nss; + + memset(txwi, 0, sizeof(*txwi)); + + if (wcid) + txwi->wcid = wcid->idx; + else + txwi->wcid = 0xff; + + txwi->pktid = 1; + + spin_lock_irqsave(&dev->lock, flags); + if (rate->idx < 0 || !rate->count) { + txwi->rate = wcid->tx_rate; + nss = wcid->tx_rate_nss; + } else { + txwi->rate = mt76_mac_tx_rate_val(dev, rate, &nss); + } + spin_unlock_irqrestore(&dev->lock, flags); + + if (mt76xx_rev(dev) >= MT76XX_REV_E4) + txwi->txstream = 0x03; + else if (mt76xx_rev(dev) >= MT76XX_REV_E3 && + !(txwi->rate & cpu_to_le16(rate_ht_mask))) + txwi->txstream = 0x83; + + 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 (!(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); + if (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE) + ba_size = 0; + txwi->ack_ctl |= MT76_SET(MT_TXWI_ACK_CTL_BA_WINDOW, ba_size); + + txwi_flags |= MT_TXWI_FLAGS_AMPDU | + MT76_SET(MT_TXWI_FLAGS_MPDU_DENSITY, + sta->ht_cap.ampdu_density); + } + + txwi->flags |= cpu_to_le16(txwi_flags); + txwi->len_ctl = cpu_to_le16(skb->len); +} + +int mt76_mac_process_rx(struct mt76_dev *dev, struct sk_buff *skb, void *rxi) +{ + struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); + struct mt76_rxwi *rxwi = rxi; + u32 ctl = le32_to_cpu(rxwi->ctl); + u16 rate = le16_to_cpu(rxwi->rate); + int len; + + if (rxwi->rxinfo & cpu_to_le32(MT_RXINFO_L2PAD)) + mt76_remove_hdr_pad(skb); + + if (rxwi->rxinfo & cpu_to_le32(MT_RXINFO_DECRYPT)) { + status->flag |= RX_FLAG_DECRYPTED; + status->flag |= RX_FLAG_IV_STRIPPED | RX_FLAG_MMIC_STRIPPED; + } + + len = MT76_GET(MT_RXWI_CTL_MPDU_LEN, ctl); + skb_trim(skb, len); + + status->chains = BIT(0) | BIT(1); + status->chain_signal[0] = mt76_phy_get_rssi(dev, rxwi->rssi[0], 0); + status->chain_signal[1] = mt76_phy_get_rssi(dev, rxwi->rssi[1], 1); + status->signal = max(status->chain_signal[0], status->chain_signal[1]); + status->freq = dev->chandef.chan->center_freq; + status->band = dev->chandef.chan->band; + + mt76_mac_process_rate(status, rate); + + return 0; +} + +static void +mt76_mac_process_tx_rate(struct ieee80211_tx_rate *txrate, u16 rate, + enum ieee80211_band band) +{ + u8 idx = MT76_GET(MT_RXWI_RATE_INDEX, rate); + + txrate->idx = 0; + txrate->flags = 0; + txrate->count = 1; + + switch (MT76_GET(MT_RXWI_RATE_PHY, rate)) { + case MT_PHY_TYPE_OFDM: + if (band == IEEE80211_BAND_2GHZ) + idx += 4; + + txrate->idx = idx; + return; + case MT_PHY_TYPE_CCK: + if (idx >= 8) + idx -= 8; + + txrate->idx = idx; + return; + 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: + WARN_ON(1); + return; + } + + switch (MT76_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: + WARN_ON(1); + break; + } + + if (rate & MT_RXWI_RATE_SGI) + txrate->flags |= IEEE80211_TX_RC_SHORT_GI; +} + +static void +mt76_mac_fill_tx_status(struct mt76_dev *dev, struct ieee80211_tx_info *info, + struct mt76_tx_status *st) +{ + struct ieee80211_tx_rate *rate = info->status.rates; + int cur_idx, last_rate; + int i; + + last_rate = min_t(int, st->retry, IEEE80211_TX_MAX_RATES - 1); + mt76_mac_process_tx_rate(&rate[last_rate], st->rate, + dev->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 = 1; + info->status.ampdu_ack_len = st->success; + + 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 +mt76_send_tx_status(struct mt76_dev *dev, struct mt76_tx_status *stat) +{ + struct ieee80211_tx_info info = {}; + struct ieee80211_sta *sta = NULL; + struct mt76_wcid *wcid = NULL; + void *msta; + + rcu_read_lock(); + if (stat->wcid < ARRAY_SIZE(dev->wcid)) + wcid = rcu_dereference(dev->wcid[stat->wcid]); + + if (wcid) { + msta = container_of(wcid, struct mt76_sta, wcid); + sta = container_of(msta, struct ieee80211_sta, + drv_priv); + } + + mt76_mac_fill_tx_status(dev, &info, stat); + ieee80211_tx_status_noskb(dev->hw, sta, &info); + rcu_read_unlock(); +} + +void mt76_mac_poll_tx_status(struct mt76_dev *dev, bool irq) +{ + struct mt76_tx_status stat = {}; + unsigned long flags; + + if (!test_bit(MT76_STATE_RUNNING, &dev->state)) + return; + + trace_mac_txstat_poll(dev); + + spin_lock_irqsave(&dev->irq_lock, flags); + while (!irq || !kfifo_is_full(&dev->txstatus_fifo)) { + u32 stat1, stat2; + + stat1 = mt76_rr(dev, MT_TX_STAT_FIFO); + if (!(stat1 & MT_TX_STAT_FIFO_VALID)) + break; + + stat2 = mt76_rr(dev, MT_TX_STAT_FIFO_EXT); + + 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 = MT76_GET(MT_TX_STAT_FIFO_WCID, stat1); + stat.rate = MT76_GET(MT_TX_STAT_FIFO_RATE, stat1); + stat.retry = MT76_GET(MT_TX_STAT_FIFO_EXT_RETRY, stat2); + stat.pktid = MT76_GET(MT_TX_STAT_FIFO_EXT_PKTID, stat2); + trace_mac_txstat_fetch(dev, &stat); + + if (!irq) { + mt76_send_tx_status(dev, &stat); + continue; + } + + kfifo_put(&dev->txstatus_fifo, stat); + } + spin_unlock_irqrestore(&dev->irq_lock, flags); +} + +void mt76_mac_queue_txdone(struct mt76_dev *dev, struct sk_buff *skb, + struct mt76_txwi *txwi) +{ + struct mt76_tx_info *txi = mt76_skb_tx_info(skb); + + mt76_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); + mt76_tx_complete(dev, skb); +} + +void mt76_mac_process_tx_status_fifo(struct mt76_dev *dev) +{ + struct mt76_tx_status stat; + + while (kfifo_get(&dev->txstatus_fifo, &stat)) + mt76_send_tx_status(dev, &stat); +} + +static enum mt76_cipher_type +mt76_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 mt76_mac_wcid_setup(struct mt76_dev *dev, u8 idx, u8 vif_idx, u8 *mac) +{ + struct mt76_wcid_addr addr = {}; + u32 attr; + + attr = MT76_SET(MT_WCID_ATTR_BSS_IDX, vif_idx & 7) | + MT76_SET(MT_WCID_ATTR_BSS_IDX_EXT, !!(vif_idx & 8)); + + mt76_wr(dev, MT_WCID_ATTR(idx), attr); + + if (mac) + memcpy(addr.macaddr, mac, ETH_ALEN); + + mt76_wr_copy(dev, MT_WCID_ADDR(idx), &addr, sizeof(addr)); +} + +int mt76_mac_wcid_set_key(struct mt76_dev *dev, u8 idx, + struct ieee80211_key_conf *key) +{ + enum mt76_cipher_type cipher; + u8 key_data[32]; + u8 iv_data[8]; + + cipher = mt76_mac_get_key_info(key, key_data); + if (cipher == MT_CIPHER_NONE && key) + return -EINVAL; + + 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 >= WLAN_CIPHER_SUITE_TKIP) + iv_data[3] |= 0x20; + } + + mt76_wr_copy(dev, MT_WCID_IV(idx), iv_data, sizeof(iv_data)); + + return 0; +} + +int mt76_mac_shared_key_setup(struct mt76_dev *dev, u8 vif_idx, u8 key_idx, + struct ieee80211_key_conf *key) +{ + enum mt76_cipher_type cipher; + u8 key_data[32]; + u32 val; + + cipher = mt76_mac_get_key_info(key, key_data); + if (cipher == MT_CIPHER_NONE && key) + return -EINVAL; + + 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; +} + +int mt76_mac_skb_tx_overhead(struct mt76_dev *dev, struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_key_conf *key = info->control.hw_key; + int overhead; + + if (!key) + return 0; + + overhead = key->icv_len; + if (!(key->flags & IEEE80211_KEY_FLAG_GENERATE_IV)) + overhead += key->iv_len; + + if (!(key->flags & IEEE80211_KEY_FLAG_GENERATE_MMIC) && + key->cipher == WLAN_CIPHER_SUITE_TKIP) + overhead += 8; + + return overhead; +} + +static int +mt76_write_beacon(struct mt76_dev *dev, int offset, struct sk_buff *skb) +{ + int beacon_len = dev->beacon_offsets[1] - dev->beacon_offsets[0]; + struct mt76_txwi txwi; + + if (WARN_ON_ONCE(beacon_len < skb->len + sizeof(struct mt76_txwi))) + return -ENOSPC; + + mt76_mac_write_txwi(dev, &txwi, skb, NULL, NULL); + txwi.flags |= cpu_to_le16(MT_TXWI_FLAGS_TS); + + mt76_wr_copy(dev, offset, &txwi, sizeof(txwi)); + offset += sizeof(txwi); + + mt76_wr_copy(dev, offset, skb->data, skb->len); + return 0; +} + +static int +__mt76_mac_set_beacon(struct mt76_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 mt76_mac_set_beacon(struct mt76_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; + __mt76_mac_set_beacon(dev, bcn_idx, skb); + } else if (force_update && dev->beacons[i]) { + __mt76_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; + + __mt76_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 mt76_mac_set_beacon_enable(struct mt76_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); + mt76_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) + mt76_irq_enable(dev, MT_INT_PRE_TBTT | MT_INT_TBTT); + else + mt76_irq_disable(dev, MT_INT_PRE_TBTT | MT_INT_TBTT); +} + +void mt76_mac_work(struct work_struct *work) +{ + struct mt76_dev *dev = container_of(work, struct mt76_dev, + mac_work.work); + int i, idx; + + 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(dev->hw, &dev->mac_work, + MT_CALIBRATE_INTERVAL); + +} diff --git a/mac.h b/mac.h new file mode 100644 index 000000000..3f59bdc7e --- /dev/null +++ b/mac.h @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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_MAC_H +#define __MT76_MAC_H + +struct mt76_dev; +struct mt76_sta; +struct mt76_vif; +struct mt76_wcid; + +struct mt76_txwi; +struct mt76_queue; +struct mt76_txwi_cache; + +struct mt76_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 mt76_tx_info { + unsigned long jiffies; + u8 tries; + + u8 wcid; + u8 pktid; + u8 retry; +}; + +struct mt76_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) + +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, +}; + +enum mt76_phy_bandwith { + 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 mt76_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 mt76_tx_info * +mt76_skb_tx_info(struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + return (void *) info->status.status_driver_data; +} + +int mt76_mac_reset(struct mt76_dev *dev, bool hard); +int mt76_mac_start(struct mt76_dev *dev); +void mt76_mac_stop(struct mt76_dev *dev, bool force); +void mt76_mac_resume(struct mt76_dev *dev); +void mt76_mac_set_bssid(struct mt76_dev *dev, u8 idx, const u8 *addr); + +int mt76_mac_process_rx(struct mt76_dev *dev, struct sk_buff *skb, void *rxwi); +void mt76_mac_write_txwi(struct mt76_dev *dev, struct mt76_txwi *txwi, + struct sk_buff *skb, struct mt76_wcid *wcid, + struct ieee80211_sta *sta); +void mt76_mac_wcid_setup(struct mt76_dev *dev, u8 idx, u8 vif_idx, u8 *mac); +int mt76_mac_wcid_set_key(struct mt76_dev *dev, u8 idx, + struct ieee80211_key_conf *key); +void mt76_mac_wcid_set_rate(struct mt76_dev *dev, struct mt76_wcid *wcid, + const struct ieee80211_tx_rate *rate); + +int mt76_mac_shared_key_setup(struct mt76_dev *dev, u8 vif_idx, u8 key_idx, + struct ieee80211_key_conf *key); + +int mt76_insert_hdr_pad(struct sk_buff *skb); +void mt76_remove_hdr_pad(struct sk_buff *skb); +int mt76_mac_skb_tx_overhead(struct mt76_dev *dev, struct sk_buff *skb); + +int mt76_mac_set_beacon(struct mt76_dev *dev, u8 vif_idx, struct sk_buff *skb); +void mt76_mac_set_beacon_enable(struct mt76_dev *dev, u8 vif_idx, bool val); + +void mt76_mac_queue_txdone(struct mt76_dev *dev, struct sk_buff *skb, + struct mt76_txwi *txwi); + +void mt76_mac_poll_tx_status(struct mt76_dev *dev, bool irq); +void mt76_mac_process_tx_status_fifo(struct mt76_dev *dev); + +void mt76_mac_work(struct work_struct *work); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 000000000..2f9aee8a1 --- /dev/null +++ b/main.c @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include "mt76.h" + +static int +mt76_start(struct ieee80211_hw *hw) +{ + struct mt76_dev *dev = hw->priv; + int ret; + + mutex_lock(&dev->mutex); + + ret = mt76_mac_start(dev); + if (ret) + goto out; + + ret = mt76_phy_start(dev); + if (ret) + goto out; + + ieee80211_queue_delayed_work(dev->hw, &dev->mac_work, + MT_CALIBRATE_INTERVAL); + napi_enable(&dev->napi); + + set_bit(MT76_STATE_RUNNING, &dev->state); + +out: + mutex_unlock(&dev->mutex); + return ret; +} + +static void +mt76_stop(struct ieee80211_hw *hw) +{ + struct mt76_dev *dev = hw->priv; + + mutex_lock(&dev->mutex); + napi_disable(&dev->napi); + clear_bit(MT76_STATE_RUNNING, &dev->state); + mt76_stop_hardware(dev); + mutex_unlock(&dev->mutex); +} + +static int +mt76_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct mt76_dev *dev = hw->priv; + struct mt76_vif *mvif = (struct mt76_vif *) vif->drv_priv; + unsigned int idx = 0; + int ret = 0; + + if (vif->addr[0] & BIT(1)) + idx = 1 + (((dev->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; + mt76_txq_init(dev, vif->txq); + + return ret; +} + +static void +mt76_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct mt76_dev *dev = hw->priv; + + mt76_txq_remove(dev, vif->txq); +} + +static int +mt76_config(struct ieee80211_hw *hw, u32 changed) +{ + struct mt76_dev *dev = hw->priv; + int ret = 0; + + mutex_lock(&dev->mutex); + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + dev->txpower_conf = hw->conf.power_level; + + if (test_bit(MT76_STATE_RUNNING, &dev->state)) + mt76_phy_set_txpower(dev); + } + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + ieee80211_stop_queues(hw); + ret = mt76_set_channel(dev, &hw->conf.chandef); + ieee80211_wake_queues(hw); + } + + mutex_unlock(&dev->mutex); + + return ret; +} + +static void +mt76_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags, + unsigned int *total_flags, u64 multicast) +{ + struct mt76_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(PROMISC_IN_BSS, MT_RX_FILTR_CFG_PROMISC); + 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 +mt76_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, u32 changed) +{ + struct mt76_dev *dev = hw->priv; + struct mt76_vif *mvif = (struct mt76_vif *) vif->drv_priv; + + mutex_lock(&dev->mutex); + + if (changed & BSS_CHANGED_BSSID) + mt76_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); + mt76_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; + + mt76_rmw_field(dev, MT_BKOFF_SLOT_CFG, + MT_BKOFF_SLOT_CFG_SLOTTIME, slottime); + } + + mutex_unlock(&dev->mutex); +} + +static int +mt76_wcid_alloc(struct mt76_dev *dev) +{ + int i, idx = 0; + + for (i = 0; i < ARRAY_SIZE(dev->wcid_mask); i++) { + idx = ffs(~dev->wcid_mask[i]); + if (!idx) + continue; + + if (idx > 247) + return -1; + + idx--; + dev->wcid_mask[i] |= BIT(idx); + return i * BITS_PER_LONG + idx; + } + + return -1; +} + +static int +mt76_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt76_dev *dev = hw->priv; + struct mt76_sta *msta = (struct mt76_sta *) sta->drv_priv; + struct mt76_vif *mvif = (struct mt76_vif *) vif->drv_priv; + int ret = 0; + int idx = 0; + int i; + + mutex_lock(&dev->mutex); + + idx = mt76_wcid_alloc(dev); + if (idx < 0) { + ret = -ENOSPC; + goto out; + } + + msta->wcid.idx = idx; + msta->wcid.hw_key_idx = -1; + mt76_mac_wcid_setup(dev, idx, mvif->idx, sta->addr); + mt76_clear(dev, MT_WCID_DROP(idx), MT_WCID_DROP_MASK(idx)); + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) + mt76_txq_init(dev, sta->txq[i]); + + rcu_assign_pointer(dev->wcid[idx], &msta->wcid); + +out: + mutex_unlock(&dev->mutex); + + return ret; +} + +static int +mt76_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt76_dev *dev = hw->priv; + struct mt76_sta *msta = (struct mt76_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, sta->txq[i]); + mt76_set(dev, MT_WCID_DROP(idx), MT_WCID_DROP_MASK(idx)); + dev->wcid_mask[idx / BITS_PER_LONG] &= ~BIT(idx % BITS_PER_LONG); + mt76_mac_wcid_setup(dev, idx, 0, NULL); + mutex_unlock(&dev->mutex); + + return 0; +} + +static void +mt76_sta_notify(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, struct ieee80211_sta *sta) +{ +} + +static int +mt76_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 mt76_dev *dev = hw->priv; + struct mt76_vif *mvif = (struct mt76_vif *) vif->drv_priv; + struct mt76_sta *msta = sta ? (struct mt76_sta *) sta->drv_priv : NULL; + struct mt76_wcid *wcid = msta ? &msta->wcid : &mvif->group_wcid; + int idx = key->keyidx; + int ret; + + if (cmd == SET_KEY) { + key->hw_key_idx = wcid->idx; + wcid->hw_key_idx = idx; + } else { + if (idx == wcid->hw_key_idx) + wcid->hw_key_idx = -1; + + key = NULL; + } + + if (!msta) { + if (key || wcid->hw_key_idx == idx) { + ret = mt76_mac_wcid_set_key(dev, wcid->idx, key); + if (ret) + return ret; + } + + return mt76_mac_shared_key_setup(dev, mvif->idx, idx, key); + } + + return mt76_mac_wcid_set_key(dev, msta->wcid.idx, key); +} + +static int +mt76_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct mt76_dev *dev = hw->priv; + u32 val; + + val = MT76_SET(MT_EDCA_CFG_TXOP, params->txop) | + MT76_SET(MT_EDCA_CFG_AIFSN, params->aifs) | + MT76_SET(MT_EDCA_CFG_CWMIN, params->cw_min) | + MT76_SET(MT_EDCA_CFG_CWMAX, params->cw_max); + mt76_wr(dev, MT_EDCA_CFG_AC(queue), val); + + val = mt76_rr(dev, MT_WMM_TXOP(queue)); + val &= ~(MT_WMM_TXOP_MASK << MT_WMM_TXOP_SHIFT(queue)); + val |= params->txop << MT_WMM_TXOP_SHIFT(queue); + mt76_wr(dev, MT_WMM_TXOP(queue), val); + + val = mt76_rr(dev, MT_WMM_AIFSN); + val &= ~(MT_WMM_AIFSN_MASK << MT_WMM_AIFSN_SHIFT(queue)); + val |= params->aifs << MT_WMM_AIFSN_SHIFT(queue); + mt76_wr(dev, MT_WMM_AIFSN, val); + + val = mt76_rr(dev, MT_WMM_CWMIN); + val &= ~(MT_WMM_CWMIN_MASK << MT_WMM_CWMIN_SHIFT(queue)); + val |= params->cw_min << MT_WMM_CWMIN_SHIFT(queue); + mt76_wr(dev, MT_WMM_CWMIN, val); + + val = mt76_rr(dev, MT_WMM_CWMAX); + val &= ~(MT_WMM_CWMAX_MASK << MT_WMM_CWMAX_SHIFT(queue)); + val |= params->cw_max << MT_WMM_CWMAX_SHIFT(queue); + mt76_wr(dev, MT_WMM_CWMAX, val); + + return 0; +} + +static void +mt76_sw_scan(struct ieee80211_hw *hw) +{ + struct mt76_dev *dev = hw->priv; + + tasklet_disable(&dev->pre_tbtt_tasklet); + set_bit(MT76_SCANNING, &dev->state); +} + +static void +mt76_sw_scan_complete(struct ieee80211_hw *hw) +{ + struct mt76_dev *dev = hw->priv; + + clear_bit(MT76_SCANNING, &dev->state); + tasklet_enable(&dev->pre_tbtt_tasklet); +} + +static void +mt76_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u32 queues, bool drop) +{ +} + +static int +mt76_get_txpower(struct ieee80211_hw *hw, struct ieee80211_vif *vif, int *dbm) +{ + struct mt76_dev *dev = hw->priv; + + *dbm = dev->txpower_cur; + return 0; +} + +static int +mt76_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta,u16 tid, u16 *ssn, u8 buf_size) +{ + struct mt76_dev *dev = hw->priv; + struct mt76_sta *msta = (struct mt76_sta *) sta->drv_priv; + + switch (action) { + case IEEE80211_AMPDU_RX_START: + mt76_set(dev, MT_WCID_ADDR(msta->wcid.idx)+4, BIT(16 + tid)); + break; + case IEEE80211_AMPDU_RX_STOP: + mt76_clear(dev, MT_WCID_ADDR(msta->wcid.idx)+4, BIT(16 + tid)); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + ieee80211_send_bar(vif, sta->addr, tid, msta->agg_ssn[tid]); + break; + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + break; + case IEEE80211_AMPDU_TX_START: + msta->agg_ssn[tid] = *ssn << 4; + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + } + + return 0; +} + +static void +mt76_sta_rate_tbl_update(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt76_dev *dev = hw->priv; + struct mt76_sta *msta = (struct mt76_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; + mt76_mac_wcid_set_rate(dev, &msta->wcid, &rate); +} + +const struct ieee80211_ops mt76_ops = { + .tx = mt76_tx, + .start = mt76_start, + .stop = mt76_stop, + .add_interface = mt76_add_interface, + .remove_interface = mt76_remove_interface, + .config = mt76_config, + .configure_filter = mt76_configure_filter, + .bss_info_changed = mt76_bss_info_changed, + .sta_add = mt76_sta_add, + .sta_remove = mt76_sta_remove, + .sta_notify = mt76_sta_notify, + .set_key = mt76_set_key, + .conf_tx = mt76_conf_tx, + .sw_scan_start = mt76_sw_scan, + .sw_scan_complete = mt76_sw_scan_complete, + .flush = mt76_flush, + .ampdu_action = mt76_ampdu_action, + .get_txpower = mt76_get_txpower, + .wake_tx_queue = mt76_wake_tx_queue, + .sta_rate_tbl_update = mt76_sta_rate_tbl_update, +}; + +void mt76_rx(struct mt76_dev *dev, struct sk_buff *skb) +{ + if (!test_bit(MT76_STATE_RUNNING, &dev->state)) { + dev_kfree_skb(skb); + return; + } + + ieee80211_rx(dev->hw, skb); +} + diff --git a/mcu.c b/mcu.c new file mode 100644 index 000000000..e8ceae253 --- /dev/null +++ b/mcu.c @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include +#include +#include + +#include "mt76.h" +#include "mcu.h" +#include "dma.h" +#include "eeprom.h" + +struct mt76_fw_header { + __le32 ilm_len; + __le32 dlm_len; + __le16 build_ver; + __le16 fw_ver; + u8 pad[4]; + char build_time[16]; +}; + +struct mt76_patch_header { + char build_time[16]; + char platform[4]; + char hw_version[4]; + char patch_version[4]; + u8 pad[2]; +}; + +static struct sk_buff * +mt76_mcu_msg_alloc(struct mt76_dev *dev, const void *data, int len) +{ + struct sk_buff *skb; + + skb = alloc_skb(len, GFP_KERNEL); + memcpy(skb_put(skb, len), data, len); + + return skb; +} + +static struct sk_buff * +mt76_mcu_get_response(struct mt76_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 +mt76_mcu_msg_send(struct mt76_dev *dev, struct sk_buff *skb, enum mcu_cmd cmd) +{ + unsigned long expires = jiffies + HZ; + u32 info; + 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; + + info = MT_MCU_MSG_TYPE_CMD | + MT76_SET(MT_MCU_MSG_CMD_TYPE, cmd) | + MT76_SET(MT_MCU_MSG_CMD_SEQ, seq) | + MT76_SET(MT_MCU_MSG_PORT, CPU_TX_PORT) | + MT76_SET(MT_MCU_MSG_LEN, skb->len); + + ret = __mt76_tx_queue_skb(dev, MT_TXQ_MCU, skb, info); + if (ret) + goto out; + + while (1) { + u32 *rxfce; + bool check_seq = false; + + skb = mt76_mcu_get_response(dev, expires); + if (!skb) { + printk("MCU message %d (seq %d) timed out\n", cmd, + MT76_GET(MT_MCU_MSG_CMD_SEQ, info)); + ret = -ETIMEDOUT; + break; + } + + rxfce = (u32 *) skb->cb; + + if (seq == MT76_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 const char * +get_string(const char *data, int len) +{ + static char buf[17]; + + BUG_ON(len >= sizeof(buf)); + memcpy(buf, data, len); + buf[len] = 0; + + return buf; +} + +static void +write_data(struct mt76_dev *dev, u32 offset, __le32 *data, int len) +{ + __iowrite32_copy(dev->regs + offset, data, len / 4); +} + +static int +mt76pci_load_rom_patch(struct mt76_dev *dev) +{ + const struct firmware *fw = NULL; + struct mt76_patch_header *hdr; + int len, ret = 0; + __le32 *cur; + u32 patch_mask, patch_reg; + + if (!mt76_poll(dev, MT_MCU_SEMAPHORE_03, 1, 1, 600)) { + printk("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 (mt76_rr(dev, patch_reg) & patch_mask) { + printk("ROM patch already applied\n"); + goto out; + } + + ret = request_firmware(&fw, MT7662_ROM_PATCH, dev->dev); + if (ret) + goto out; + + if (!fw || !fw->data || fw->size <= sizeof(*hdr)) { + ret = -EIO; + printk("Failed to load firmware\n"); + goto out; + } + + hdr = (struct mt76_patch_header *) fw->data; + printk("ROM patch build: %s\n", get_string(hdr->build_time, 15)); + + mt76_wr(dev, MT_MCU_PCIE_REMAP_BASE4, MT_MCU_ROM_PATCH_OFFSET); + + cur = (__le32 *) (fw->data + sizeof(*hdr)); + len = fw->size - sizeof(*hdr); + write_data(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)) { + printk("Failed to load ROM patch\n"); + ret = -ETIMEDOUT; + } + +out: + /* release semaphore */ + mt76_wr(dev, MT_MCU_SEMAPHORE_03, 1); + release_firmware(fw); + return ret; +} + +static int +mt76pci_load_firmware(struct mt76_dev *dev) +{ + const struct firmware *fw; + const struct mt76_fw_header *hdr; + int i, len, ret; + __le32 *cur; + u32 offset, val; + + ret = request_firmware(&fw, MT7662_FIRMWARE, dev->dev); + if (ret) + return ret; + + if (!fw || !fw->data || fw->size < sizeof(*hdr)) + goto error; + + hdr = (const struct mt76_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); + printk("Firmware Version: %d.%d.%02d\n", + (val >> 12) & 0xf, (val >> 8) & 0xf, val & 0xf); + + val = le16_to_cpu(hdr->build_ver); + printk("Build: %x\n", val); + printk("Build Time: %16s\n", get_string(hdr->build_time, 16)); + + 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); + write_data(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); + write_data(dev, offset, cur, len); + + mt76_wr(dev, MT_MCU_PCIE_REMAP_BASE4, 0); + + /* 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) { + printk("Firmware failed to start\n"); + return -ETIMEDOUT; + } + + printk("Firmware running!\n"); + + return ret; + +error: + printk("Invalid firmware\n"); + release_firmware(fw); + return -ENOENT; +} + +int mt76_mcu_function_select(struct mt76_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 = mt76_mcu_msg_alloc(dev, &msg, sizeof(msg)); + return mt76_mcu_msg_send(dev, skb, CMD_FUN_SET_OP); +} + +int mt76_mcu_load_cr(struct mt76_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 |= (mt76_eeprom_get(dev, MT_EE_NIC_CONF_0) >> 8) & 0x00ff; + val |= (mt76_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 = mt76_mcu_msg_alloc(dev, &msg, sizeof(msg)); + return mt76_mcu_msg_send(dev, skb, CMD_LOAD_CR); +} + +int mt76_mcu_set_channel(struct mt76_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 = mt76_mcu_msg_alloc(dev, &msg, sizeof(msg)); + mt76_mcu_msg_send(dev, skb, CMD_SWITCH_CHANNEL_OP); + + msleep(5); + + msg.ext_chan = 0xe0 + bw_index; + skb = mt76_mcu_msg_alloc(dev, &msg, sizeof(msg)); + return mt76_mcu_msg_send(dev, skb, CMD_SWITCH_CHANNEL_OP); +} + +int mt76_mcu_set_radio_state(struct mt76_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 = mt76_mcu_msg_alloc(dev, &msg, sizeof(msg)); + return mt76_mcu_msg_send(dev, skb, CMD_POWER_SAVING_OP); +} + +int mt76_mcu_calibrate(struct mt76_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 = mt76_mcu_msg_alloc(dev, &msg, sizeof(msg)); + ret = mt76_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 mt76_mcu_tssi_comp(struct mt76_dev *dev, struct mt76_tssi_comp *tssi_data) +{ + struct sk_buff *skb; + struct { + __le32 id; + struct mt76_tssi_comp data; + } __packed __aligned(4) msg = { + .id = cpu_to_le32(MCU_CAL_TSSI_COMP), + .data = *tssi_data, + }; + + skb = mt76_mcu_msg_alloc(dev, &msg, sizeof(msg)); + return mt76_mcu_msg_send(dev, skb, CMD_CALIBRATION_OP); +} + +int mt76_mcu_init_gain(struct mt76_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 = mt76_mcu_msg_alloc(dev, &msg, sizeof(msg)); + return mt76_mcu_msg_send(dev, skb, CMD_INIT_GAIN_OP); +} + +int mt76_mcu_init(struct mt76_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; + + mt76_mcu_function_select(dev, Q_SELECT, 1); + return 0; +} + +int mt76_mcu_cleanup(struct mt76_dev *dev) +{ + mt76_wr(dev, MT_MCU_INT_LEVEL, 1); + msleep(20); + + return 0; +} diff --git a/mcu.h b/mcu.h new file mode 100644 index 000000000..2cbea8b9b --- /dev/null +++ b/mcu.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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_MCU_H +#define __MT76_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_MCU_LED_CTRL 0x0770 +#define MT_MCU_LED_TX_BLINK_0 0x0774 +#define MT_MCU_LED_TX_BLINK_1 0x0778 +#define MT_MCU_LED0_S0 0x077C +#define MT_MCU_LED0_S1 0x0780 +#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 + +/* 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 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_msg_port { + WLAN_PORT, + CPU_RX_PORT, + CPU_TX_PORT, + HOST_PORT, + VIRTUAL_CPU_RX_PORT, + VIRTUAL_CPU_TX_PORT, + DISCARD, +}; + +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, +}; + +enum mt76_mcu_cr_mode { + MT_RF_CR, + MT_BBP_CR, + MT_RF_BBP_CR, + MT_HL_TEMP_CR_UPDATE, +}; + +struct mt76_tssi_comp { + u8 pa_mode; + u8 cal_mode; + u16 pad; + + u8 slope0; + u8 slope1; + u8 offset0; + u8 offset1; +} __packed __aligned(4); + +int mt76_mcu_calibrate(struct mt76_dev *dev, enum mcu_calibration type, + u32 param); +int mt76_mcu_tssi_comp(struct mt76_dev *dev, struct mt76_tssi_comp *data); +int mt76_mcu_init_gain(struct mt76_dev *dev, u8 channel, u32 gain, bool force); + +#endif diff --git a/mt76.h b/mt76.h new file mode 100644 index 000000000..260b329eb --- /dev/null +++ b/mt76.h @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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_H +#define __MT76_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MT7662_FIRMWARE "mt7662.bin" +#define MT7662_ROM_PATCH "mt7662_rom_patch.bin" +#define MT7662_EEPROM_SIZE 512 + +#define MT_RX_BUF_SIZE 2048 +#define MT_RX_HEADROOM 32 +#define MT_RX_RING_SIZE 128 + +#define MT_TX_RING_SIZE 128 + +#define MT_MCU_RING_SIZE 32 + +#define MT_MAX_CHAINS 2 + +#define MT_CALIBRATE_INTERVAL HZ + +#include "regs.h" +#include "util.h" +#include "mac.h" + +struct mt76_queue_entry { + struct sk_buff *skb; + union { + void *buf; + struct mt76_txwi_cache *txwi; + }; + bool schedule; +}; + +enum { + MT76_STATE_INITAILIZED, + MT76_STATE_RUNNING, + MT76_SCANNING, +}; + +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_MAX +}; + +struct mt76_queue { + struct mt76_queue_regs *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; + + dma_addr_t desc_dma; +}; + +struct mt76_mcu { + struct mutex mutex; + + wait_queue_head_t wait; + struct sk_buff_head res_q; + + struct mt76_queue q_rx; + u32 msg_seq; +}; + +struct mt76_rx_freq_cal { + s8 high_gain[MT_MAX_CHAINS]; + s8 rssi_offset[MT_MAX_CHAINS]; + s8 lna_gain; + u32 mcu_gain; +}; + +struct mt76_calibration { + struct mt76_rx_freq_cal rx; + + u8 agc_gain_init[MT_MAX_CHAINS]; + int avg_rssi[MT_MAX_CHAINS]; + int avg_rssi_all; + + s8 low_gain; + + u8 temp; + + bool init_cal_done; + bool tssi_cal_done; + bool tssi_comp_done; + bool dpd_cal_done; + bool channel_cal_done; +}; + +struct mt76_wcid { + u8 idx; + u8 hw_key_idx; + + __le16 tx_rate; + bool tx_rate_set; + u8 tx_rate_nss; +}; + +struct mt76_hw_cap { + bool has_2ghz; + bool has_5ghz; +}; + +struct mt76_dev { + struct ieee80211_hw *hw; + struct device *dev; + u8 macaddr[ETH_ALEN]; + struct mac_address macaddr_list[8]; + + void __iomem *regs; + + struct mutex mutex; + + const u16 *beacon_offsets; + unsigned long wcid_mask[256 / BITS_PER_LONG]; + + struct cfg80211_chan_def chandef; + struct ieee80211_supported_band sband_2g; + struct ieee80211_supported_band sband_5g; + int txpower_conf; + int txpower_cur; + + u8 txdone_seq; + DECLARE_KFIFO_PTR(txstatus_fifo, struct mt76_tx_status); + + struct list_head txwi_cache; + struct mt76_mcu mcu; + struct mt76_queue q_rx; + struct mt76_queue q_tx[__MT_TXQ_MAX]; + + struct net_device napi_dev; + struct napi_struct napi; + + struct tasklet_struct tx_tasklet; + struct tasklet_struct rx_tasklet; + struct tasklet_struct pre_tbtt_tasklet; + struct delayed_work cal_work; + struct delayed_work mac_work; + + u32 aggr_stats[32]; + + struct mt76_wcid __rcu *wcid[254 - 8]; + + spinlock_t lock; + spinlock_t irq_lock; + u32 irqmask; + unsigned long state; + + struct sk_buff *beacons[8]; + u8 beacon_mask; + u8 beacon_data_mask; + + u32 rev; + u32 rxfilter; + + u16 chainmask; + + struct mt76_calibration cal; + struct debugfs_blob_wrapper eeprom; + struct mt76_hw_cap cap; + + u32 debugfs_reg; +}; + +struct mt76_vif { + u8 idx; + + struct mt76_wcid group_wcid; +}; + +struct mt76_sta { + struct mt76_wcid wcid; + u16 agg_ssn[IEEE80211_NUM_TIDS]; +}; + +struct mt76_txq { + struct list_head list; + struct mt76_queue *hwq; + + struct sk_buff_head retry_q; +}; + +struct mt76_reg_pair { + u32 reg; + u32 value; +}; + +u32 mt76_rr(struct mt76_dev *dev, u32 offset); +void mt76_wr(struct mt76_dev *dev, u32 offset, u32 val); +u32 mt76_rmw(struct mt76_dev *dev, u32 offset, u32 mask, u32 val); +void mt76_wr_copy(struct mt76_dev *dev, u32 offset, const void *data, int len); + +bool mt76_poll(struct mt76_dev *dev, u32 offset, u32 mask, u32 val, + int timeout); +bool mt76_poll_msec(struct mt76_dev *dev, u32 offset, u32 mask, u32 val, + int timeout); +void mt76_write_reg_pairs(struct mt76_dev *dev, + const struct mt76_reg_pair *data, int len); + +#define mt76_get_field(_dev, _reg, _field) \ + MT76_GET(_field, mt76_rr(dev, _reg)) + +#define mt76_rmw_field(_dev, _reg, _field, _val) \ + mt76_rmw(_dev, _reg, _field, MT76_SET(_field, _val)) + +static inline u32 mt76_set(struct mt76_dev *dev, u32 offset, u32 val) +{ + return mt76_rmw(dev, offset, 0, val); +} + +static inline u32 mt76_clear(struct mt76_dev *dev, u32 offset, u32 val) +{ + return mt76_rmw(dev, offset, val, 0); +} + +static inline bool is_mt7612(struct mt76_dev *dev) +{ + return (dev->rev >> 16) == 0x7612; +} + +static inline u16 mt76xx_rev(struct mt76_dev *dev) +{ + return dev->rev & 0xffff; +} + +void mt76_set_irq_mask(struct mt76_dev *dev, u32 clear, u32 set); + +static inline void mt76_irq_enable(struct mt76_dev *dev, u32 mask) +{ + mt76_set_irq_mask(dev, 0, mask); +} + +static inline void mt76_irq_disable(struct mt76_dev *dev, u32 mask) +{ + mt76_set_irq_mask(dev, mask, 0); +} + +static inline struct ieee80211_txq * +mtxq_to_txq(struct mt76_txq *mtxq) +{ + void *ptr = mtxq; + return container_of(ptr, struct ieee80211_txq, drv_priv); +} + +extern const struct ieee80211_ops mt76_ops; + +struct mt76_dev *mt76_alloc_device(struct device *pdev); +int mt76_register_device(struct mt76_dev *dev); +void mt76_init_debugfs(struct mt76_dev *dev); + +irqreturn_t mt76_irq_handler(int irq, void *dev_instance); +void mt76_phy_power_on(struct mt76_dev *dev); +int mt76_init_hardware(struct mt76_dev *dev); +void mt76_stop_hardware(struct mt76_dev *dev); +int mt76_eeprom_init(struct mt76_dev *dev); +int mt76_apply_calibration_data(struct mt76_dev *dev, int channel); + +int mt76_phy_start(struct mt76_dev *dev); +int mt76_set_channel(struct mt76_dev *dev, struct cfg80211_chan_def *chandef); +int mt76_phy_set_channel(struct mt76_dev *dev, + struct cfg80211_chan_def *chandef); +int mt76_phy_get_rssi(struct mt76_dev *dev, s8 rssi, int chain); +void mt76_phy_calibrate(struct work_struct *work); +void mt76_phy_set_txpower(struct mt76_dev *dev); + +int mt76_mcu_init(struct mt76_dev *dev); +int mt76_mcu_set_channel(struct mt76_dev *dev, u8 channel, u8 bw, u8 bw_index, + bool scan); +int mt76_mcu_set_radio_state(struct mt76_dev *dev, bool on); +int mt76_mcu_load_cr(struct mt76_dev *dev, u8 type, u8 temp_level, u8 channel); +int mt76_mcu_cleanup(struct mt76_dev *dev); + +int mt76_dma_init(struct mt76_dev *dev); +void mt76_dma_cleanup(struct mt76_dev *dev); + +void mt76_cleanup(struct mt76_dev *dev); +void mt76_rx(struct mt76_dev *dev, struct sk_buff *skb); + +int __mt76_tx_queue_skb(struct mt76_dev *dev, enum mt76_txq_id qid, + struct sk_buff *skb, u32 tx_info); + +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_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, + struct sk_buff *skb); +void mt76_tx_complete(struct mt76_dev *dev, struct sk_buff *skb); + +void mt76_kick_queue(struct mt76_dev *dev, struct mt76_queue *q); + +void mt76_pre_tbtt_tasklet(unsigned long data); + +void mt76_txq_init(struct mt76_dev *dev, struct ieee80211_txq *txq); +void mt76_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *txq); +void mt76_txq_remove(struct mt76_dev *dev, struct ieee80211_txq *txq); +void mt76_txq_schedule(struct mt76_dev *dev, struct mt76_queue *hwq); + +#endif diff --git a/pci.c b/pci.c new file mode 100644 index 000000000..79cfeb009 --- /dev/null +++ b/pci.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include +#include +#include + +#include "mt76.h" +#include "trace.h" + +static const struct pci_device_id mt76pci_device_table[] = { + { PCI_DEVICE(0x14c3, 0x7662) }, +}; + +u32 mt76_rr(struct mt76_dev *dev, u32 offset) +{ + u32 val; + + val = ioread32(dev->regs + offset); + trace_reg_read(dev, offset, val); + + return val; +} + +void mt76_wr(struct mt76_dev *dev, u32 offset, u32 val) +{ + trace_reg_write(dev, offset, val); + iowrite32(val, dev->regs + offset); +} + +u32 mt76_rmw(struct mt76_dev *dev, u32 offset, u32 mask, u32 val) +{ + val |= mt76_rr(dev, offset) & ~mask; + mt76_wr(dev, offset, val); + return val; +} + +void mt76_wr_copy(struct mt76_dev *dev, u32 offset, const void *data, int len) +{ + __iowrite32_copy(dev->regs + offset, data, len >> 2); +} + +static int +mt76pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct mt76_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 = mt76_alloc_device(&pdev->dev); + if (!dev) + return -ENOMEM; + + dev->regs = pcim_iomap_table(pdev)[0]; + + pci_set_drvdata(pdev, dev); + + dev->rev = mt76_rr(dev, MT_ASIC_VERSION); + printk("ASIC revision: %08x\n", dev->rev); + + ret = devm_request_irq(dev->dev, pdev->irq, mt76_irq_handler, + IRQF_SHARED, KBUILD_MODNAME, dev); + if (ret) + goto error; + + ret = mt76_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); + + printk("pci device driver attached\n"); + return 0; + +error: + ieee80211_free_hw(dev->hw); + return ret; +} + +static void +mt76pci_remove(struct pci_dev *pdev) +{ + struct mt76_dev *dev = pci_get_drvdata(pdev); + + ieee80211_unregister_hw(dev->hw); + mt76_cleanup(dev); + ieee80211_free_hw(dev->hw); + printk("pci device driver detached\n"); +} + +MODULE_DEVICE_TABLE(pci, mt76pci_device_table); +MODULE_FIRMWARE(MT7662_FIRMWARE); +MODULE_FIRMWARE(MT7662_ROM_PATCH); +MODULE_LICENSE("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/phy.c b/phy.c new file mode 100644 index 000000000..17b511cd7 --- /dev/null +++ b/phy.c @@ -0,0 +1,690 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include +#include "mt76.h" +#include "mcu.h" +#include "eeprom.h" + +static bool +mt76_phy_rf_op(struct mt76_dev *dev, bool idx, u16 offset, bool write) +{ + u32 val = MT76_SET(MT_RF_CTRL_ADDR, offset); + + if (idx) + val |= MT_RF_CTRL_IDX; + + if (write) + val |= MT_RF_CTRL_WRITE; + + mt76_wr(dev, MT_RF_CTRL, val); + + return mt76_poll(dev, MT_RF_CTRL, MT_RF_CTRL_BUSY, 0, 2000); +} + +static int __maybe_unused +mt76_phy_rf_read(struct mt76_dev *dev, bool idx, u16 offset, u32 *val) +{ + if (!mt76_phy_rf_op(dev, idx, offset, false)) + return -ETIMEDOUT; + + *val = mt76_rr(dev, MT_RF_DATA_READ); + return 0; +} + +static int __maybe_unused +mt76_phy_rf_write(struct mt76_dev *dev, bool idx, u16 offset, u32 val) +{ + mt76_wr(dev, MT_RF_DATA_WRITE, val); + + if (!mt76_phy_rf_op(dev, idx, offset, true)) + return -ETIMEDOUT; + + return 0; +} + +static void +mt76_adjust_lna_gain(struct mt76_dev *dev, int reg, s8 offset) +{ + s8 gain; + + gain = MT76_GET(MT_BBP_AGC_LNA_GAIN, mt76_rr(dev, MT_BBP(AGC, reg))); + gain -= offset / 2; + mt76_rmw_field(dev, MT_BBP(AGC, reg), MT_BBP_AGC_LNA_GAIN, gain); +} + +static void +mt76_adjust_agc_gain(struct mt76_dev *dev, int reg, s8 offset) +{ + s8 gain; + + gain = MT76_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 +mt76_apply_gain_adj(struct mt76_dev *dev) +{ + s8 *gain_adj = dev->cal.rx.high_gain; + + mt76_adjust_lna_gain(dev, 4, gain_adj[0]); + mt76_adjust_lna_gain(dev, 5, gain_adj[1]); + + mt76_adjust_agc_gain(dev, 8, gain_adj[0]); + mt76_adjust_agc_gain(dev, 9, gain_adj[1]); +} + +static u32 +mt76_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; +} + +static void mt76_apply_rate_power_table(struct mt76_dev *dev) +{ + struct mt76_rate_power t; + + mt76_get_rate_power(dev, &t); + + mt76_wr(dev, MT_TX_PWR_CFG_0, + mt76_tx_power_mask(t.cck[0], t.cck[1], t.ofdm[0], t.ofdm[1])); + mt76_wr(dev, MT_TX_PWR_CFG_1, + mt76_tx_power_mask(t.ofdm[2], t.ofdm[3], t.ht[0], t.ht[1])); + mt76_wr(dev, MT_TX_PWR_CFG_2, + mt76_tx_power_mask(t.ht[2], t.ht[3], t.ht[4], t.ht[5])); + mt76_wr(dev, MT_TX_PWR_CFG_3, + mt76_tx_power_mask(t.ht[6], t.ht[7], t.ht[0], t.ht[1])); + mt76_wr(dev, MT_TX_PWR_CFG_4, + mt76_tx_power_mask(t.ht[2], t.ht[3], 0, 0)); + mt76_wr(dev, MT_TX_PWR_CFG_7, + mt76_tx_power_mask(t.ofdm[2], t.vht[4], t.ht[3], t.vht[4])); + mt76_wr(dev, MT_TX_PWR_CFG_8, + mt76_tx_power_mask(t.ht[7], t.vht[4], t.vht[4], 0)); + mt76_wr(dev, MT_TX_PWR_CFG_9, + mt76_tx_power_mask(t.ht[3], t.vht[4], t.vht[4], 0)); +} + +int mt76_phy_get_rssi(struct mt76_dev *dev, s8 rssi, int chain) +{ + struct mt76_rx_freq_cal *cal = &dev->cal.rx; + + rssi += cal->rssi_offset[chain]; + rssi -= cal->lna_gain; + + return rssi; +} + +static u8 +mt76_txpower_check(int value) +{ + if (value < 0) + return 0; + if (value > 0x2f) + return 0x2f; + return value; +} + +void mt76_phy_set_txpower(struct mt76_dev *dev) +{ + enum nl80211_chan_width width = dev->chandef.width; + struct mt76_tx_power_info txp; + int txp_0, txp_1, delta = 0; + + mt76_get_power_info(dev, &txp); + + 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) { + dev->txpower_cur = dev->txpower_conf; + delta -= txp.target_power - dev->txpower_conf; + } else { + dev->txpower_cur = txp.target_power; + } + + txp_0 = mt76_txpower_check(txp.chain[0].target_power + + txp.chain[0].delta + delta); + + txp_1 = mt76_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); +} + +static bool +mt76_channel_silent(struct mt76_dev *dev) +{ + struct ieee80211_channel *chan = dev->chandef.chan; + + return ((chan->flags & IEEE80211_CHAN_RADAR) && + chan->dfs_state != NL80211_DFS_AVAILABLE); +} + +static bool +mt76_phy_tssi_init_cal(struct mt76_dev *dev) +{ + struct ieee80211_channel *chan = dev->chandef.chan; + u16 flag = 0; + + if (!mt76_tssi_enabled(dev)) + return false; + + if (mt76_channel_silent(dev)) + return false; + + if (chan->band == IEEE80211_BAND_2GHZ) + flag |= BIT(0); + + if (mt76_ext_pa_enabled(dev, chan->band)) + flag |= BIT(16); + + mt76_mcu_calibrate(dev, MCU_CAL_TSSI, flag); + dev->cal.tssi_cal_done = true; + return true; +} + +static void +mt76_phy_channel_calibrate(struct mt76_dev *dev, bool mac_stopped) +{ + struct ieee80211_channel *chan = dev->chandef.chan; + bool is_5ghz = chan->band == IEEE80211_BAND_5GHZ; + + if (dev->cal.channel_cal_done) + return; + + if (mt76_channel_silent(dev)) + return; + + if (!dev->cal.tssi_cal_done) + mt76_phy_tssi_init_cal(dev); + + if (!mac_stopped) + mt76_mac_stop(dev, false); + + if (is_5ghz) + mt76_mcu_calibrate(dev, MCU_CAL_LC, 0); + + mt76_mcu_calibrate(dev, MCU_CAL_TX_LOFT, is_5ghz); + mt76_mcu_calibrate(dev, MCU_CAL_TXIQ, is_5ghz); + mt76_mcu_calibrate(dev, MCU_CAL_RXIQC_FI, is_5ghz); + mt76_mcu_calibrate(dev, MCU_CAL_TEMP_SENSOR, 0); + + if (!mac_stopped) + mt76_mac_resume(dev); + + mt76_apply_gain_adj(dev); + + dev->cal.channel_cal_done = true; +} + +static void +mt76_phy_set_txpower_regs(struct mt76_dev *dev, enum ieee80211_band band) +{ + u32 pa_mode[2]; + u32 pa_mode_adj; + u32 tx_cfg = 0; + + if (band == IEEE80211_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 (mt76_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; + + 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 (mt76_ext_pa_enabled(dev, band)) { + tx_cfg = 0x00830083; + 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]); + mt76_wr(dev, MT_PROT_AUTO_TX_CFG, tx_cfg); + + if (mt76_ext_pa_enabled(dev, band)) { + u32 val = 0x3c3c023c; + 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 == IEEE80211_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 +mt76_configure_tx_delay(struct mt76_dev *dev, enum ieee80211_band band, u8 bw) +{ + u32 cfg0, cfg1; + + if (mt76_ext_pa_enabled(dev, band)) { + cfg0 = bw ? 0x000b0c01 : 0x00101101; + cfg1 = 0x00010200; + } else { + cfg0 = bw ? 0x000b0b01 : 0x00101001; + cfg1 = 0x00020000; + } + 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_CCK_SIFS, + 13 + (bw ? 1 : 0)); +} + +static void +mt76_phy_set_bw(struct mt76_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, core_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 +mt76_phy_set_band(struct mt76_dev *dev, int band, bool primary_upper) +{ + switch (band) { + case IEEE80211_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 IEEE80211_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 +mt76_set_rx_chains(struct mt76_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 +mt76_set_tx_dac(struct mt76_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 +mt76_get_agc_gain(struct mt76_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 +mt76_get_rssi_gain_thresh(struct mt76_dev *dev) +{ + switch (dev->chandef.width) { + case NL80211_CHAN_WIDTH_80: + return -62; + case NL80211_CHAN_WIDTH_40: + return -65; + default: + return -68; + } +} + +static void +mt76_phy_update_channel_gain(struct mt76_dev *dev) +{ + u32 val = mt76_rr(dev, MT_BBP(AGC, 20)); + int rssi0 = (s8) MT76_GET(MT_BBP_AGC20_RSSI0, val); + int rssi1 = (s8) MT76_GET(MT_BBP_AGC20_RSSI1, val); + bool low_gain; + u8 gain[2], gain_delta; + + dev->cal.avg_rssi[0] = (dev->cal.avg_rssi[0] * 15) / 16 + (rssi0 << 8); + dev->cal.avg_rssi[1] = (dev->cal.avg_rssi[0] * 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 > mt76_get_rssi_gain_thresh(dev); + if (dev->cal.low_gain == low_gain) + return; + + dev->cal.low_gain = low_gain; + + if (dev->chandef.width >= NL80211_CHAN_WIDTH_40) + val = 0x1e42 << 16; + else + val = 0x1836 << 16; + + mt76_get_agc_gain(dev, gain); + val |= 0xf8; + + if (dev->chandef.width == NL80211_CHAN_WIDTH_80) + mt76_wr(dev, MT_BBP(RXO, 14), 0x00560411); + else + mt76_wr(dev, MT_BBP(RXO, 14), 0x00560423); + + if (low_gain) { + mt76_wr(dev, MT_BBP(AGC, 35), 0x08080808); + mt76_wr(dev, MT_BBP(AGC, 37), 0x08080808); + if (mt76_has_ext_lna(dev)) + gain_delta = 10; + else + gain_delta = 14; + } else { + mt76_wr(dev, MT_BBP(AGC, 35), 0x11111116); + mt76_wr(dev, MT_BBP(AGC, 37), 0x1010161C); + gain_delta = 0; + } + + mt76_wr(dev, MT_BBP(AGC, 8), + val | MT76_SET(MT_BBP_AGC_GAIN, gain[0] - gain_delta)); + mt76_wr(dev, MT_BBP(AGC, 9), + val | MT76_SET(MT_BBP_AGC_GAIN, gain[1] - gain_delta)); +} + +int mt76_phy_set_channel(struct mt76_dev *dev, + struct cfg80211_chan_def *chandef) +{ + struct ieee80211_channel *chan = chandef->chan; + bool scan = test_bit(MT76_SCANNING, &dev->state); + enum ieee80211_band band = chan->band; + u8 channel; + + u32 ext_cca_chan[4] = { + [0] = MT76_SET(MT_EXT_CCA_CFG_CCA0, 0) | + MT76_SET(MT_EXT_CCA_CFG_CCA1, 1) | + MT76_SET(MT_EXT_CCA_CFG_CCA2, 2) | + MT76_SET(MT_EXT_CCA_CFG_CCA3, 3) | + MT76_SET(MT_EXT_CCA_CFG_CCA_MASK, BIT(0)), + [1] = MT76_SET(MT_EXT_CCA_CFG_CCA0, 1) | + MT76_SET(MT_EXT_CCA_CFG_CCA1, 0) | + MT76_SET(MT_EXT_CCA_CFG_CCA2, 2) | + MT76_SET(MT_EXT_CCA_CFG_CCA3, 3) | + MT76_SET(MT_EXT_CCA_CFG_CCA_MASK, BIT(1)), + [2] = MT76_SET(MT_EXT_CCA_CFG_CCA0, 2) | + MT76_SET(MT_EXT_CCA_CFG_CCA1, 3) | + MT76_SET(MT_EXT_CCA_CFG_CCA2, 1) | + MT76_SET(MT_EXT_CCA_CFG_CCA3, 0) | + MT76_SET(MT_EXT_CCA_CFG_CCA_MASK, BIT(2)), + [3] = MT76_SET(MT_EXT_CCA_CFG_CCA0, 3) | + MT76_SET(MT_EXT_CCA_CFG_CCA1, 2) | + MT76_SET(MT_EXT_CCA_CFG_CCA2, 1) | + MT76_SET(MT_EXT_CCA_CFG_CCA3, 0) | + MT76_SET(MT_EXT_CCA_CFG_CCA_MASK, BIT(3)), + }; + int ch_group_index; + u8 bw, bw_index; + int freq, freq1; + int ret; + u8 sifs = 13; + + dev->chandef = *chandef; + 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; + } + + mt76_read_rx_gain(dev); + mt76_phy_set_txpower_regs(dev, band); + mt76_configure_tx_delay(dev, band, bw); + mt76_phy_set_txpower(dev); + mt76_apply_rate_power_table(dev); + + mt76_set_rx_chains(dev); + mt76_phy_set_band(dev, chan->band, ch_group_index & 1); + mt76_phy_set_bw(dev, chandef->width, ch_group_index); + mt76_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]); + + if (chandef->width >= NL80211_CHAN_WIDTH_40) + sifs++; + + mt76_rmw_field(dev, MT_XIFS_TIME_CFG, MT_XIFS_TIME_CFG_OFDM_SIFS, sifs); + + ret = mt76_mcu_set_channel(dev, channel, bw, bw_index, scan); + if (ret) + return ret; + + mt76_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 = mt76_eeprom_get(dev, MT_EE_BT_RCAL_RESULT); + + if (val != 0xff) + mt76_mcu_calibrate(dev, MCU_CAL_R, 0); + } + + mt76_mcu_calibrate(dev, MCU_CAL_RXDCOC, channel); + + /* Rx LPF calibration */ + if (!dev->cal.init_cal_done) + mt76_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; + mt76_phy_channel_calibrate(dev, true); + mt76_get_agc_gain(dev, dev->cal.agc_gain_init); + + ieee80211_queue_delayed_work(dev->hw, &dev->cal_work, + MT_CALIBRATE_INTERVAL); + + return 0; +} + +static void +mt76_phy_tssi_compensate(struct mt76_dev *dev) +{ + struct ieee80211_channel *chan = dev->chandef.chan; + struct mt76_tx_power_info txp; + struct mt76_tssi_comp t = {}; + + if (!dev->cal.tssi_cal_done) + return; + + if (dev->cal.tssi_comp_done) { + /* TSSI trigger */ + t.cal_mode = BIT(0); + mt76_mcu_tssi_comp(dev, &t); + } else { + if (!(mt76_rr(dev, MT_BBP(CORE, 34)) & BIT(4))) + return; + + mt76_get_power_info(dev, &txp); + + if (mt76_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; + dev->cal.tssi_comp_done = true; + mt76_mcu_tssi_comp(dev, &t); + + if (t.pa_mode || dev->cal.dpd_cal_done) + return; + + msleep(10); + mt76_mcu_calibrate(dev, MCU_CAL_DPD, chan->hw_value); + dev->cal.dpd_cal_done = true; + } +} + +static void +mt76_phy_temp_compensate(struct mt76_dev *dev) +{ + struct mt76_temp_comp t; + int temp, db_diff; + + if (mt76_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 mt76_phy_calibrate(struct work_struct *work) +{ + struct mt76_dev *dev; + + dev = container_of(work, struct mt76_dev, cal_work.work); + mt76_phy_channel_calibrate(dev, false); + mt76_phy_tssi_compensate(dev); + mt76_phy_temp_compensate(dev); + mt76_phy_update_channel_gain(dev); + ieee80211_queue_delayed_work(dev->hw, &dev->cal_work, + MT_CALIBRATE_INTERVAL); +} + +int mt76_phy_start(struct mt76_dev *dev) +{ + int ret; + + ret = mt76_mcu_set_radio_state(dev, true); + if (ret) + return ret; + + mt76_mcu_load_cr(dev, MT_RF_BBP_CR, 0, 0); + + return ret; +} diff --git a/regs.h b/regs.h new file mode 100644 index 000000000..efeb61e73 --- /dev/null +++ b/regs.h @@ -0,0 +1,540 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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_REGS_H +#define __MT76_REGS_H + +#include + +#ifndef GENMASK +#define GENMASK(h, l) (((U32_C(1) << ((h) - (l) + 1)) - 1) << (l)) +#endif + +#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_RING_SIZE 0x10 + +#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_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_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_RETRY_CFG 0x134c +#define MT_VHT_HT_FBK_CFG1 0x1358 + +#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_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_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) > 7 ? MT_TX_AGG_CNT_BASE1 : \ + MT_TX_AGG_CNT_BASE0) + ((_id) << 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_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_GAIN GENMASK(21, 16) + +/* AGC, R8/R9 */ +#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); + +struct mt76_queue_regs { + u32 desc_base; + u32 ring_size; + u32 cpu_idx; + u32 dma_idx; +} __packed __aligned(4); + +enum mt76_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/trace.c b/trace.c new file mode 100644 index 000000000..48c4ca2de --- /dev/null +++ b/trace.c @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include + +#ifndef __CHECKER__ +#define CREATE_TRACE_POINTS +#include "trace.h" + +#endif diff --git a/trace.h b/trace.h new file mode 100644 index 000000000..7c21f9c48 --- /dev/null +++ b/trace.h @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#if !defined(__MT76_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __MT76_TRACE_H + +#include +#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 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 + +#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_evt, + TP_PROTO(struct mt76_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 mt76_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 + ) +); + +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_read, + TP_PROTO(struct mt76_dev *dev, u32 reg, u32 val), + TP_ARGS(dev, reg, val) +); + +DEFINE_EVENT(dev_reg_evt, reg_write, + TP_PROTO(struct mt76_dev *dev, u32 reg, u32 val), + TP_ARGS(dev, reg, val) +); + +DEFINE_EVENT(dev_evt, mac_txstat_poll, + TP_PROTO(struct mt76_dev *dev), + TP_ARGS(dev) +); + +DEFINE_EVENT(dev_txid_evt, mac_txdone_add, + TP_PROTO(struct mt76_dev *dev, u8 wcid, u8 pktid), + TP_ARGS(dev, wcid, pktid) +); + +TRACE_EVENT(mac_txstat_fetch, + TP_PROTO(struct mt76_dev *dev, + struct mt76_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 mt76_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 trace + +#include diff --git a/tx.c b/tx.c new file mode 100644 index 000000000..cfb0f4c5e --- /dev/null +++ b/tx.c @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include "mt76.h" + +struct beacon_bc_data { + struct mt76_dev *dev; + struct sk_buff_head q; + struct sk_buff *tail[8]; +}; + +void mt76_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct mt76_dev *dev = hw->priv; + struct ieee80211_vif *vif = info->control.vif; + struct mt76_vif *mvif = (struct mt76_vif *) vif->drv_priv; + struct mt76_sta *msta = NULL; + struct mt76_wcid *wcid = &mvif->group_wcid; + 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 (control->sta) { + msta = (struct mt76_sta *) control->sta->drv_priv; + wcid = &msta->wcid; + } + + if (!wcid->tx_rate_set) + ieee80211_get_tx_rates(info->control.vif, control->sta, skb, + info->control.rates, 1); + + q = &dev->q_tx[qid]; + + spin_lock_bh(&q->lock); + mt76_tx_queue_skb(dev, q, skb, wcid, control->sta); + mt76_kick_queue(dev, q); + + if (q->queued > q->ndesc - 8) + ieee80211_stop_queue(hw, skb_get_queue_mapping(skb)); + spin_unlock_bh(&q->lock); +} + +void mt76_tx_complete(struct mt76_dev *dev, struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct mt76_queue *q; + int qid = skb_get_queue_mapping(skb); + + ieee80211_tx_info_clear_status(info); + info->status.rates[0].idx = -1; + info->flags |= IEEE80211_TX_STAT_ACK; + ieee80211_tx_status(dev->hw, skb); + + q = &dev->q_tx[qid]; + if (q->queued < q->ndesc - 8) + ieee80211_wake_queue(dev->hw, qid); +} + +static void +mt76_update_beacon_iter(void *priv, u8 *mac, struct ieee80211_vif *vif) +{ + struct mt76_dev *dev = (struct mt76_dev *) priv; + struct mt76_vif *mvif = (struct mt76_vif *) vif->drv_priv; + struct ieee80211_tx_info *info; + struct sk_buff *skb = NULL; + + if (!(dev->beacon_mask & BIT(mvif->idx))) + return; + + skb = ieee80211_beacon_get(dev->hw, vif); + if (!skb) + return; + + info = IEEE80211_SKB_CB(skb); + info->flags |= IEEE80211_TX_CTL_ASSIGN_SEQ; + mt76_mac_set_beacon(dev, mvif->idx, skb); +} + +static 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); +} + +static void +mt76_add_buffered_bc(void *priv, u8 *mac, struct ieee80211_vif *vif) +{ + struct beacon_bc_data *data = priv; + struct mt76_dev *dev = data->dev; + struct mt76_vif *mvif = (struct mt76_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(dev->hw, 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 mt76_pre_tbtt_tasklet(unsigned long arg) +{ + struct mt76_dev *dev = (struct mt76_dev *) arg; + struct mt76_queue *q = &dev->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(dev->hw, + IEEE80211_IFACE_ITER_RESUME_ALL, + mt76_update_beacon_iter, dev); + + do { + nframes = skb_queue_len(&data.q); + ieee80211_iterate_active_interfaces_atomic(dev->hw, + IEEE80211_IFACE_ITER_RESUME_ALL, + mt76_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 mt76_vif *mvif = (struct mt76_vif *) vif->drv_priv; + + mt76_tx_queue_skb(dev, q, skb, &mvif->group_wcid, NULL); + } + spin_unlock_bh(&q->lock); +} + +static int +mt76_txq_get_qid(struct ieee80211_txq *txq) +{ + if (!txq->sta) + return MT_TXQ_BE; + + switch (txq->tid) { + case 0: + case 3: + return MT_TXQ_BE; + case 1: + case 2: + return MT_TXQ_BK; + case 4: + case 5: + return MT_TXQ_VI; + default: + return MT_TXQ_VO; + } +} + +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; + struct sk_buff *skb = NULL; + int n_frames = 1, limit; + struct ieee80211_tx_rate tx_rate; + bool ampdu; + bool probe; + int idx; + + if (txq->sta) { + struct mt76_sta *sta = (struct mt76_sta *) txq->sta->drv_priv; + wcid = &sta->wcid; + } else { + struct mt76_vif *mvif = (struct mt76_vif *) txq->vif->drv_priv; + wcid = &mvif->group_wcid; + } + + if (ieee80211_tx_dequeue(dev->hw, txq, &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; + idx = mt76_tx_queue_skb(dev, hwq, skb, wcid, txq->sta); + + if (idx < 0) + return idx; + + do { + bool cur_ampdu; + + if (probe) + break; + + if (ieee80211_tx_dequeue(dev->hw, txq, &skb)) { + *empty = true; + break; + } + + 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 = IEEE80211_SKB_CB(skb); + info->control.rates[0] = tx_rate; + + 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; + } + + mt76_kick_queue(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; + + mtxq_last = list_last_entry(&hwq->swq, struct mt76_txq, list); + while (1) { + bool empty = false; + int cur, len; + + if (hwq->swq_queued >= 4) + break; + + if (list_empty(&hwq->swq)) + break; + + mtxq = list_first_entry(&hwq->swq, struct mt76_txq, list); + 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 { + len = mt76_txq_schedule_list(dev, hwq); + } while (len > 0); +} + +void mt76_txq_init(struct mt76_dev *dev, struct ieee80211_txq *txq) +{ + struct mt76_txq *mtxq; + + if (!txq) + return; + + 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)]; +} + +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); +} + +void mt76_txq_remove(struct mt76_dev *dev, struct ieee80211_txq *txq) +{ + struct mt76_txq *mtxq; + struct mt76_queue *hwq; + + 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); +} diff --git a/util.c b/util.c new file mode 100644 index 000000000..b4496f7ec --- /dev/null +++ b/util.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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. + */ + +#include "mt76.h" + +void mt76_remove_hdr_pad(struct sk_buff *skb) +{ + int len = ieee80211_get_hdrlen_from_skb(skb); + memmove(skb->data + 2, skb->data, len); + skb_pull(skb, 2); +} + +int mt76_insert_hdr_pad(struct sk_buff *skb) +{ + int len = ieee80211_get_hdrlen_from_skb(skb); + int ret; + + if (len % 4 == 0) + return 0; + + ret = skb_cow(skb, 2); + if (ret) + return ret; + + skb_push(skb, 2); + memmove(skb->data, skb->data + 2, len); + + skb->data[len] = 0; + skb->data[len + 1] = 0; + return 0; +} + diff --git a/util.h b/util.h new file mode 100644 index 000000000..a1ad47369 --- /dev/null +++ b/util.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2004 - 2009 Ivo van Doorn + * + * 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 + +/* + * Power of two check, this will check + * if the mask that has been given contains and contiguous set of bits. + * Note that we cannot use the is_power_of_2() function since this + * check must be done at compile-time. + */ +#define is_power_of_two(x) ( !((x) & ((x)-1)) ) +#define low_bit_mask(x) ( ((x)-1) & ~(x) ) +#define is_valid_mask(x) is_power_of_two(1LU + (x) + low_bit_mask(x)) + +/* + * Macros to find first set bit in a variable. + * These macros behave the same as the __ffs() functions but + * the most important difference that this is done during + * compile-time rather then run-time. + */ +#define compile_ffs2(__x) \ + __builtin_choose_expr(((__x) & 0x1), 0, 1) + +#define compile_ffs4(__x) \ + __builtin_choose_expr(((__x) & 0x3), \ + (compile_ffs2((__x))), \ + (compile_ffs2((__x) >> 2) + 2)) + +#define compile_ffs8(__x) \ + __builtin_choose_expr(((__x) & 0xf), \ + (compile_ffs4((__x))), \ + (compile_ffs4((__x) >> 4) + 4)) + +#define compile_ffs16(__x) \ + __builtin_choose_expr(((__x) & 0xff), \ + (compile_ffs8((__x))), \ + (compile_ffs8((__x) >> 8) + 8)) + +#define compile_ffs32(__x) \ + __builtin_choose_expr(((__x) & 0xffff), \ + (compile_ffs16((__x))), \ + (compile_ffs16((__x) >> 16) + 16)) + + +/* + * This macro will check the requirements for the FIELD{8,16,32} macros + * The mask should be a constant non-zero contiguous set of bits which + * does not exceed the given typelimit. + */ +#define FIELD_CHECK(__mask) \ + BUILD_BUG_ON(!(__mask) || !is_valid_mask(__mask)) + +#define MT76_SET(_mask, _val) \ + ({ FIELD_CHECK(_mask); ((_val) << compile_ffs32(_mask)) & _mask; }) + +#define MT76_GET(_mask, _val) \ + ({ FIELD_CHECK(_mask); ((_val) & _mask) >> compile_ffs32(_mask); }) + +#define MT76_INCR(_var, _size) \ + _var = (((_var) + 1) % _size) + +#endif