Commit 79968444 authored by Lorenzo Bianconi's avatar Lorenzo Bianconi Committed by David S. Miller
Browse files

net: ethernet: mtk_wed: introduce wed wo support



Introduce WO chip support to mtk wed driver. MTK WED WO is used to
implement RX Wireless Ethernet Dispatch and offload traffic received by
wlan nic to the wired interface.

Tested-by: default avatarDaniel Golle <daniel@makrotopia.org>
Co-developed-by: default avatarSujuan Chen <sujuan.chen@mediatek.com>
Signed-off-by: default avatarSujuan Chen <sujuan.chen@mediatek.com>
Signed-off-by: default avatarLorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent cc514101
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@

obj-$(CONFIG_NET_MEDIATEK_SOC) += mtk_eth.o
mtk_eth-y := mtk_eth_soc.o mtk_sgmii.o mtk_eth_path.o mtk_ppe.o mtk_ppe_debugfs.o mtk_ppe_offload.o
mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed.o mtk_wed_mcu.o
mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed.o mtk_wed_mcu.o mtk_wed_wo.o
ifdef CONFIG_DEBUG_FS
mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_debugfs.o
endif
+6 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include "mtk_wed_regs.h"
#include "mtk_wed.h"
#include "mtk_ppe.h"
#include "mtk_wed_wo.h"

#define MTK_PCIE_BASE(n)		(0x1a143000 + (n) * 0x2000)

@@ -355,6 +356,8 @@ mtk_wed_detach(struct mtk_wed_device *dev)

	mtk_wed_free_buffer(dev);
	mtk_wed_free_tx_rings(dev);
	if (hw->version != 1)
		mtk_wed_wo_deinit(hw);

	if (dev->wlan.bus_type == MTK_WED_BUS_PCIE) {
		struct device_node *wlan_node;
@@ -878,9 +881,11 @@ mtk_wed_attach(struct mtk_wed_device *dev)
	}

	mtk_wed_hw_init_early(dev);
	if (hw->hifsys)
	if (hw->version == 1)
		regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP,
				   BIT(hw->index), 0);
	else
		ret = mtk_wed_wo_init(hw);

out:
	mutex_unlock(&hw_lock);
+2 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
#include <linux/netdevice.h>

struct mtk_eth;
struct mtk_wed_wo;

struct mtk_wed_hw {
	struct device_node *node;
@@ -22,6 +23,7 @@ struct mtk_wed_hw {
	struct regmap *mirror;
	struct dentry *debugfs_dir;
	struct mtk_wed_device *wed_dev;
	struct mtk_wed_wo *wed_wo;
	u32 debugfs_reg;
	u32 num_flows;
	u8 version;
+1 −2
Original line number Diff line number Diff line
@@ -122,8 +122,7 @@ mtk_wed_mcu_skb_send_msg(struct mtk_wed_wo *wo, struct sk_buff *skb,
	if (id == MTK_WED_MODULE_ID_WO)
		hdr->flag |= cpu_to_le16(MTK_WED_WARP_CMD_FLAG_FROM_TO_WO);

	dev_kfree_skb(skb);
	return 0;
	return mtk_wed_wo_queue_tx_skb(wo, &wo->q_tx, skb);
}

static int
+508 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2022 MediaTek Inc.
 *
 * Author: Lorenzo Bianconi <lorenzo@kernel.org>
 *	   Sujuan Chen <sujuan.chen@mediatek.com>
 */

#include <linux/kernel.h>
#include <linux/dma-mapping.h>
#include <linux/of_platform.h>
#include <linux/interrupt.h>
#include <linux/of_address.h>
#include <linux/mfd/syscon.h>
#include <linux/of_irq.h>
#include <linux/bitfield.h>

#include "mtk_wed.h"
#include "mtk_wed_regs.h"
#include "mtk_wed_wo.h"

static u32
mtk_wed_mmio_r32(struct mtk_wed_wo *wo, u32 reg)
{
	u32 val;

	if (regmap_read(wo->mmio.regs, reg, &val))
		val = ~0;

	return val;
}

static void
mtk_wed_mmio_w32(struct mtk_wed_wo *wo, u32 reg, u32 val)
{
	regmap_write(wo->mmio.regs, reg, val);
}

static u32
mtk_wed_wo_get_isr(struct mtk_wed_wo *wo)
{
	u32 val = mtk_wed_mmio_r32(wo, MTK_WED_WO_CCIF_RCHNUM);

	return val & MTK_WED_WO_CCIF_RCHNUM_MASK;
}

static void
mtk_wed_wo_set_isr(struct mtk_wed_wo *wo, u32 mask)
{
	mtk_wed_mmio_w32(wo, MTK_WED_WO_CCIF_IRQ0_MASK, mask);
}

static void
mtk_wed_wo_set_ack(struct mtk_wed_wo *wo, u32 mask)
{
	mtk_wed_mmio_w32(wo, MTK_WED_WO_CCIF_ACK, mask);
}

static void
mtk_wed_wo_set_isr_mask(struct mtk_wed_wo *wo, u32 mask, u32 val, bool set)
{
	unsigned long flags;

	spin_lock_irqsave(&wo->mmio.lock, flags);
	wo->mmio.irq_mask &= ~mask;
	wo->mmio.irq_mask |= val;
	if (set)
		mtk_wed_wo_set_isr(wo, wo->mmio.irq_mask);
	spin_unlock_irqrestore(&wo->mmio.lock, flags);
}

static void
mtk_wed_wo_irq_enable(struct mtk_wed_wo *wo, u32 mask)
{
	mtk_wed_wo_set_isr_mask(wo, 0, mask, false);
	tasklet_schedule(&wo->mmio.irq_tasklet);
}

static void
mtk_wed_wo_irq_disable(struct mtk_wed_wo *wo, u32 mask)
{
	mtk_wed_wo_set_isr_mask(wo, mask, 0, true);
}

static void
mtk_wed_wo_kickout(struct mtk_wed_wo *wo)
{
	mtk_wed_mmio_w32(wo, MTK_WED_WO_CCIF_BUSY, 1 << MTK_WED_WO_TXCH_NUM);
	mtk_wed_mmio_w32(wo, MTK_WED_WO_CCIF_TCHNUM, MTK_WED_WO_TXCH_NUM);
}

static void
mtk_wed_wo_queue_kick(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q,
		      u32 val)
{
	wmb();
	mtk_wed_mmio_w32(wo, q->regs.cpu_idx, val);
}

static void *
mtk_wed_wo_dequeue(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q, u32 *len,
		   bool flush)
{
	int buf_len = SKB_WITH_OVERHEAD(q->buf_size);
	int index = (q->tail + 1) % q->n_desc;
	struct mtk_wed_wo_queue_entry *entry;
	struct mtk_wed_wo_queue_desc *desc;
	void *buf;

	if (!q->queued)
		return NULL;

	if (flush)
		q->desc[index].ctrl |= cpu_to_le32(MTK_WED_WO_CTL_DMA_DONE);
	else if (!(q->desc[index].ctrl & cpu_to_le32(MTK_WED_WO_CTL_DMA_DONE)))
		return NULL;

	q->tail = index;
	q->queued--;

	desc = &q->desc[index];
	entry = &q->entry[index];
	buf = entry->buf;
	if (len)
		*len = FIELD_GET(MTK_WED_WO_CTL_SD_LEN0,
				 le32_to_cpu(READ_ONCE(desc->ctrl)));
	if (buf)
		dma_unmap_single(wo->hw->dev, entry->addr, buf_len,
				 DMA_FROM_DEVICE);
	entry->buf = NULL;

	return buf;
}

static int
mtk_wed_wo_queue_refill(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q,
			gfp_t gfp, bool rx)
{
	enum dma_data_direction dir = rx ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
	int n_buf = 0;

	spin_lock_bh(&q->lock);
	while (q->queued < q->n_desc) {
		void *buf = page_frag_alloc(&q->cache, q->buf_size, gfp);
		struct mtk_wed_wo_queue_entry *entry;
		dma_addr_t addr;

		if (!buf)
			break;

		addr = dma_map_single(wo->hw->dev, buf, q->buf_size, dir);
		if (unlikely(dma_mapping_error(wo->hw->dev, addr))) {
			skb_free_frag(buf);
			break;
		}

		q->head = (q->head + 1) % q->n_desc;
		entry = &q->entry[q->head];
		entry->addr = addr;
		entry->len = q->buf_size;
		q->entry[q->head].buf = buf;

		if (rx) {
			struct mtk_wed_wo_queue_desc *desc = &q->desc[q->head];
			u32 ctrl = MTK_WED_WO_CTL_LAST_SEC0 |
				   FIELD_PREP(MTK_WED_WO_CTL_SD_LEN0,
					      entry->len);

			WRITE_ONCE(desc->buf0, cpu_to_le32(addr));
			WRITE_ONCE(desc->ctrl, cpu_to_le32(ctrl));
		}
		q->queued++;
		n_buf++;
	}
	spin_unlock_bh(&q->lock);

	return n_buf;
}

static void
mtk_wed_wo_rx_complete(struct mtk_wed_wo *wo)
{
	mtk_wed_wo_set_ack(wo, MTK_WED_WO_RXCH_INT_MASK);
	mtk_wed_wo_irq_enable(wo, MTK_WED_WO_RXCH_INT_MASK);
}

static void
mtk_wed_wo_rx_run_queue(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q)
{
	for (;;) {
		struct mtk_wed_mcu_hdr *hdr;
		struct sk_buff *skb;
		void *data;
		u32 len;

		data = mtk_wed_wo_dequeue(wo, q, &len, false);
		if (!data)
			break;

		skb = build_skb(data, q->buf_size);
		if (!skb) {
			skb_free_frag(data);
			continue;
		}

		__skb_put(skb, len);
		if (mtk_wed_mcu_check_msg(wo, skb)) {
			dev_kfree_skb(skb);
			continue;
		}

		hdr = (struct mtk_wed_mcu_hdr *)skb->data;
		if (hdr->flag & cpu_to_le16(MTK_WED_WARP_CMD_FLAG_RSP))
			mtk_wed_mcu_rx_event(wo, skb);
		else
			mtk_wed_mcu_rx_unsolicited_event(wo, skb);
	}

	if (mtk_wed_wo_queue_refill(wo, q, GFP_ATOMIC, true)) {
		u32 index = (q->head - 1) % q->n_desc;

		mtk_wed_wo_queue_kick(wo, q, index);
	}
}

static irqreturn_t
mtk_wed_wo_irq_handler(int irq, void *data)
{
	struct mtk_wed_wo *wo = data;

	mtk_wed_wo_set_isr(wo, 0);
	tasklet_schedule(&wo->mmio.irq_tasklet);

	return IRQ_HANDLED;
}

static void mtk_wed_wo_irq_tasklet(struct tasklet_struct *t)
{
	struct mtk_wed_wo *wo = from_tasklet(wo, t, mmio.irq_tasklet);
	u32 intr, mask;

	/* disable interrupts */
	mtk_wed_wo_set_isr(wo, 0);

	intr = mtk_wed_wo_get_isr(wo);
	intr &= wo->mmio.irq_mask;
	mask = intr & (MTK_WED_WO_RXCH_INT_MASK | MTK_WED_WO_EXCEPTION_INT_MASK);
	mtk_wed_wo_irq_disable(wo, mask);

	if (intr & MTK_WED_WO_RXCH_INT_MASK) {
		mtk_wed_wo_rx_run_queue(wo, &wo->q_rx);
		mtk_wed_wo_rx_complete(wo);
	}
}

/* mtk wed wo hw queues */

static int
mtk_wed_wo_queue_alloc(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q,
		       int n_desc, int buf_size, int index,
		       struct mtk_wed_wo_queue_regs *regs)
{
	spin_lock_init(&q->lock);
	q->regs = *regs;
	q->n_desc = n_desc;
	q->buf_size = buf_size;

	q->desc = dmam_alloc_coherent(wo->hw->dev, n_desc * sizeof(*q->desc),
				      &q->desc_dma, GFP_KERNEL);
	if (!q->desc)
		return -ENOMEM;

	q->entry = devm_kzalloc(wo->hw->dev, n_desc * sizeof(*q->entry),
				GFP_KERNEL);
	if (!q->entry)
		return -ENOMEM;

	return 0;
}

static void
mtk_wed_wo_queue_free(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q)
{
	mtk_wed_mmio_w32(wo, q->regs.cpu_idx, 0);
	dma_free_coherent(wo->hw->dev, q->n_desc * sizeof(*q->desc), q->desc,
			  q->desc_dma);
}

static void
mtk_wed_wo_queue_tx_clean(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q)
{
	struct page *page;
	int i;

	spin_lock_bh(&q->lock);
	for (i = 0; i < q->n_desc; i++) {
		struct mtk_wed_wo_queue_entry *entry = &q->entry[i];

		dma_unmap_single(wo->hw->dev, entry->addr, entry->len,
				 DMA_TO_DEVICE);
		skb_free_frag(entry->buf);
		entry->buf = NULL;
	}
	spin_unlock_bh(&q->lock);

	if (!q->cache.va)
		return;

	page = virt_to_page(q->cache.va);
	__page_frag_cache_drain(page, q->cache.pagecnt_bias);
	memset(&q->cache, 0, sizeof(q->cache));
}

static void
mtk_wed_wo_queue_rx_clean(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q)
{
	struct page *page;

	spin_lock_bh(&q->lock);
	for (;;) {
		void *buf = mtk_wed_wo_dequeue(wo, q, NULL, true);

		if (!buf)
			break;

		skb_free_frag(buf);
	}
	spin_unlock_bh(&q->lock);

	if (!q->cache.va)
		return;

	page = virt_to_page(q->cache.va);
	__page_frag_cache_drain(page, q->cache.pagecnt_bias);
	memset(&q->cache, 0, sizeof(q->cache));
}

static void
mtk_wed_wo_queue_reset(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q)
{
	mtk_wed_mmio_w32(wo, q->regs.cpu_idx, 0);
	mtk_wed_mmio_w32(wo, q->regs.desc_base, q->desc_dma);
	mtk_wed_mmio_w32(wo, q->regs.ring_size, q->n_desc);
}

int mtk_wed_wo_queue_tx_skb(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q,
			    struct sk_buff *skb)
{
	struct mtk_wed_wo_queue_entry *entry;
	struct mtk_wed_wo_queue_desc *desc;
	int ret = 0, index;
	u32 ctrl;

	spin_lock_bh(&q->lock);

	q->tail = mtk_wed_mmio_r32(wo, q->regs.dma_idx);
	index = (q->head + 1) % q->n_desc;
	if (q->tail == index) {
		ret = -ENOMEM;
		goto out;
	}

	entry = &q->entry[index];
	if (skb->len > entry->len) {
		ret = -ENOMEM;
		goto out;
	}

	desc = &q->desc[index];
	q->head = index;

	dma_sync_single_for_cpu(wo->hw->dev, entry->addr, skb->len,
				DMA_TO_DEVICE);
	memcpy(entry->buf, skb->data, skb->len);
	dma_sync_single_for_device(wo->hw->dev, entry->addr, skb->len,
				   DMA_TO_DEVICE);

	ctrl = FIELD_PREP(MTK_WED_WO_CTL_SD_LEN0, skb->len) |
	       MTK_WED_WO_CTL_LAST_SEC0 | MTK_WED_WO_CTL_DMA_DONE;
	WRITE_ONCE(desc->buf0, cpu_to_le32(entry->addr));
	WRITE_ONCE(desc->ctrl, cpu_to_le32(ctrl));

	mtk_wed_wo_queue_kick(wo, q, q->head);
	mtk_wed_wo_kickout(wo);
out:
	spin_unlock_bh(&q->lock);

	dev_kfree_skb(skb);

	return ret;
}

static int
mtk_wed_wo_exception_init(struct mtk_wed_wo *wo)
{
	return 0;
}

static int
mtk_wed_wo_hardware_init(struct mtk_wed_wo *wo)
{
	struct mtk_wed_wo_queue_regs regs;
	struct device_node *np;
	int ret;

	np = of_parse_phandle(wo->hw->node, "mediatek,wo-ccif", 0);
	if (!np)
		return -ENODEV;

	wo->mmio.regs = syscon_regmap_lookup_by_phandle(np, NULL);
	if (IS_ERR_OR_NULL(wo->mmio.regs))
		return PTR_ERR(wo->mmio.regs);

	wo->mmio.irq = irq_of_parse_and_map(np, 0);
	wo->mmio.irq_mask = MTK_WED_WO_ALL_INT_MASK;
	spin_lock_init(&wo->mmio.lock);
	tasklet_setup(&wo->mmio.irq_tasklet, mtk_wed_wo_irq_tasklet);

	ret = devm_request_irq(wo->hw->dev, wo->mmio.irq,
			       mtk_wed_wo_irq_handler, IRQF_TRIGGER_HIGH,
			       KBUILD_MODNAME, wo);
	if (ret)
		goto error;

	regs.desc_base = MTK_WED_WO_CCIF_DUMMY1;
	regs.ring_size = MTK_WED_WO_CCIF_DUMMY2;
	regs.dma_idx = MTK_WED_WO_CCIF_SHADOW4;
	regs.cpu_idx = MTK_WED_WO_CCIF_DUMMY3;

	ret = mtk_wed_wo_queue_alloc(wo, &wo->q_tx, MTK_WED_WO_RING_SIZE,
				     MTK_WED_WO_CMD_LEN, MTK_WED_WO_TXCH_NUM,
				     &regs);
	if (ret)
		goto error;

	mtk_wed_wo_queue_refill(wo, &wo->q_tx, GFP_KERNEL, false);
	mtk_wed_wo_queue_reset(wo, &wo->q_tx);

	regs.desc_base = MTK_WED_WO_CCIF_DUMMY5;
	regs.ring_size = MTK_WED_WO_CCIF_DUMMY6;
	regs.dma_idx = MTK_WED_WO_CCIF_SHADOW8;
	regs.cpu_idx = MTK_WED_WO_CCIF_DUMMY7;

	ret = mtk_wed_wo_queue_alloc(wo, &wo->q_rx, MTK_WED_WO_RING_SIZE,
				     MTK_WED_WO_CMD_LEN, MTK_WED_WO_RXCH_NUM,
				     &regs);
	if (ret)
		goto error;

	mtk_wed_wo_queue_refill(wo, &wo->q_rx, GFP_KERNEL, true);
	mtk_wed_wo_queue_reset(wo, &wo->q_rx);

	/* rx queue irqmask */
	mtk_wed_wo_set_isr(wo, wo->mmio.irq_mask);

	return 0;

error:
	devm_free_irq(wo->hw->dev, wo->mmio.irq, wo);

	return ret;
}

static void
mtk_wed_wo_hw_deinit(struct mtk_wed_wo *wo)
{
	/* disable interrupts */
	mtk_wed_wo_set_isr(wo, 0);

	tasklet_disable(&wo->mmio.irq_tasklet);

	disable_irq(wo->mmio.irq);
	devm_free_irq(wo->hw->dev, wo->mmio.irq, wo);

	mtk_wed_wo_queue_tx_clean(wo, &wo->q_tx);
	mtk_wed_wo_queue_rx_clean(wo, &wo->q_rx);
	mtk_wed_wo_queue_free(wo, &wo->q_tx);
	mtk_wed_wo_queue_free(wo, &wo->q_rx);
}

int mtk_wed_wo_init(struct mtk_wed_hw *hw)
{
	struct mtk_wed_wo *wo;
	int ret;

	wo = devm_kzalloc(hw->dev, sizeof(*wo), GFP_KERNEL);
	if (!wo)
		return -ENOMEM;

	hw->wed_wo = wo;
	wo->hw = hw;

	ret = mtk_wed_wo_hardware_init(wo);
	if (ret)
		return ret;

	ret = mtk_wed_mcu_init(wo);
	if (ret)
		return ret;

	return mtk_wed_wo_exception_init(wo);
}

void mtk_wed_wo_deinit(struct mtk_wed_hw *hw)
{
	struct mtk_wed_wo *wo = hw->wed_wo;

	mtk_wed_wo_hw_deinit(wo);
}
Loading