-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
net: ethernet: mtk_wed: introduce wed mcu support
Introduce WED mcu support used to configure WED WO chip. This is a preliminary patch in order to add RX Wireless Ethernet Dispatch available on MT7986 SoC. Tested-by: Daniel Golle <daniel@makrotopia.org> Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org> Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com> Signed-off-by: David S. Miller <davem@davemloft.net>
- Loading branch information
Sujuan Chen
authored and
David S. Miller
committed
Nov 11, 2022
1 parent
ceb82ac
commit cc51410
Showing
5 changed files
with
540 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,359 @@ | ||
// 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/firmware.h> | ||
#include <linux/of_address.h> | ||
#include <linux/of_reserved_mem.h> | ||
#include <linux/mfd/syscon.h> | ||
#include <linux/soc/mediatek/mtk_wed.h> | ||
|
||
#include "mtk_wed_regs.h" | ||
#include "mtk_wed_wo.h" | ||
#include "mtk_wed.h" | ||
|
||
static u32 wo_r32(struct mtk_wed_wo *wo, u32 reg) | ||
{ | ||
return readl(wo->boot.addr + reg); | ||
} | ||
|
||
static void wo_w32(struct mtk_wed_wo *wo, u32 reg, u32 val) | ||
{ | ||
writel(val, wo->boot.addr + reg); | ||
} | ||
|
||
static struct sk_buff * | ||
mtk_wed_mcu_msg_alloc(const void *data, int data_len) | ||
{ | ||
int length = sizeof(struct mtk_wed_mcu_hdr) + data_len; | ||
struct sk_buff *skb; | ||
|
||
skb = alloc_skb(length, GFP_KERNEL); | ||
if (!skb) | ||
return NULL; | ||
|
||
memset(skb->head, 0, length); | ||
skb_reserve(skb, sizeof(struct mtk_wed_mcu_hdr)); | ||
if (data && data_len) | ||
skb_put_data(skb, data, data_len); | ||
|
||
return skb; | ||
} | ||
|
||
static struct sk_buff * | ||
mtk_wed_mcu_get_response(struct mtk_wed_wo *wo, unsigned long expires) | ||
{ | ||
if (!time_is_after_jiffies(expires)) | ||
return NULL; | ||
|
||
wait_event_timeout(wo->mcu.wait, !skb_queue_empty(&wo->mcu.res_q), | ||
expires - jiffies); | ||
return skb_dequeue(&wo->mcu.res_q); | ||
} | ||
|
||
void mtk_wed_mcu_rx_event(struct mtk_wed_wo *wo, struct sk_buff *skb) | ||
{ | ||
skb_queue_tail(&wo->mcu.res_q, skb); | ||
wake_up(&wo->mcu.wait); | ||
} | ||
|
||
void mtk_wed_mcu_rx_unsolicited_event(struct mtk_wed_wo *wo, | ||
struct sk_buff *skb) | ||
{ | ||
struct mtk_wed_mcu_hdr *hdr = (struct mtk_wed_mcu_hdr *)skb->data; | ||
|
||
switch (hdr->cmd) { | ||
case MTK_WED_WO_EVT_LOG_DUMP: { | ||
const char *msg = (const char *)(skb->data + sizeof(*hdr)); | ||
|
||
dev_notice(wo->hw->dev, "%s\n", msg); | ||
break; | ||
} | ||
case MTK_WED_WO_EVT_PROFILING: { | ||
struct mtk_wed_wo_log_info *info; | ||
u32 count = (skb->len - sizeof(*hdr)) / sizeof(*info); | ||
int i; | ||
|
||
info = (struct mtk_wed_wo_log_info *)(skb->data + sizeof(*hdr)); | ||
for (i = 0 ; i < count ; i++) | ||
dev_notice(wo->hw->dev, | ||
"SN:%u latency: total=%u, rro:%u, mod:%u\n", | ||
le32_to_cpu(info[i].sn), | ||
le32_to_cpu(info[i].total), | ||
le32_to_cpu(info[i].rro), | ||
le32_to_cpu(info[i].mod)); | ||
break; | ||
} | ||
case MTK_WED_WO_EVT_RXCNT_INFO: | ||
break; | ||
default: | ||
break; | ||
} | ||
|
||
dev_kfree_skb(skb); | ||
} | ||
|
||
static int | ||
mtk_wed_mcu_skb_send_msg(struct mtk_wed_wo *wo, struct sk_buff *skb, | ||
int id, int cmd, u16 *wait_seq, bool wait_resp) | ||
{ | ||
struct mtk_wed_mcu_hdr *hdr; | ||
|
||
/* TODO: make it dynamic based on cmd */ | ||
wo->mcu.timeout = 20 * HZ; | ||
|
||
hdr = (struct mtk_wed_mcu_hdr *)skb_push(skb, sizeof(*hdr)); | ||
hdr->cmd = cmd; | ||
hdr->length = cpu_to_le16(skb->len); | ||
|
||
if (wait_resp && wait_seq) { | ||
u16 seq = ++wo->mcu.seq; | ||
|
||
if (!seq) | ||
seq = ++wo->mcu.seq; | ||
*wait_seq = seq; | ||
|
||
hdr->flag |= cpu_to_le16(MTK_WED_WARP_CMD_FLAG_NEED_RSP); | ||
hdr->seq = cpu_to_le16(seq); | ||
} | ||
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; | ||
} | ||
|
||
static int | ||
mtk_wed_mcu_parse_response(struct mtk_wed_wo *wo, struct sk_buff *skb, | ||
int cmd, int seq) | ||
{ | ||
struct mtk_wed_mcu_hdr *hdr; | ||
|
||
if (!skb) { | ||
dev_err(wo->hw->dev, "Message %08x (seq %d) timeout\n", | ||
cmd, seq); | ||
return -ETIMEDOUT; | ||
} | ||
|
||
hdr = (struct mtk_wed_mcu_hdr *)skb->data; | ||
if (le16_to_cpu(hdr->seq) != seq) | ||
return -EAGAIN; | ||
|
||
skb_pull(skb, sizeof(*hdr)); | ||
switch (cmd) { | ||
case MTK_WED_WO_CMD_RXCNT_INFO: | ||
default: | ||
break; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
int mtk_wed_mcu_send_msg(struct mtk_wed_wo *wo, int id, int cmd, | ||
const void *data, int len, bool wait_resp) | ||
{ | ||
unsigned long expires; | ||
struct sk_buff *skb; | ||
u16 seq; | ||
int ret; | ||
|
||
skb = mtk_wed_mcu_msg_alloc(data, len); | ||
if (!skb) | ||
return -ENOMEM; | ||
|
||
mutex_lock(&wo->mcu.mutex); | ||
|
||
ret = mtk_wed_mcu_skb_send_msg(wo, skb, id, cmd, &seq, wait_resp); | ||
if (ret || !wait_resp) | ||
goto unlock; | ||
|
||
expires = jiffies + wo->mcu.timeout; | ||
do { | ||
skb = mtk_wed_mcu_get_response(wo, expires); | ||
ret = mtk_wed_mcu_parse_response(wo, skb, cmd, seq); | ||
dev_kfree_skb(skb); | ||
} while (ret == -EAGAIN); | ||
|
||
unlock: | ||
mutex_unlock(&wo->mcu.mutex); | ||
|
||
return ret; | ||
} | ||
|
||
static int | ||
mtk_wed_get_memory_region(struct mtk_wed_wo *wo, | ||
struct mtk_wed_wo_memory_region *region) | ||
{ | ||
struct reserved_mem *rmem; | ||
struct device_node *np; | ||
int index; | ||
|
||
index = of_property_match_string(wo->hw->node, "memory-region-names", | ||
region->name); | ||
if (index < 0) | ||
return index; | ||
|
||
np = of_parse_phandle(wo->hw->node, "memory-region", index); | ||
if (!np) | ||
return -ENODEV; | ||
|
||
rmem = of_reserved_mem_lookup(np); | ||
of_node_put(np); | ||
|
||
if (!rmem) | ||
return -ENODEV; | ||
|
||
region->phy_addr = rmem->base; | ||
region->size = rmem->size; | ||
region->addr = devm_ioremap(wo->hw->dev, region->phy_addr, region->size); | ||
|
||
return !region->addr ? -EINVAL : 0; | ||
} | ||
|
||
static int | ||
mtk_wed_mcu_run_firmware(struct mtk_wed_wo *wo, const struct firmware *fw, | ||
struct mtk_wed_wo_memory_region *region) | ||
{ | ||
const u8 *first_region_ptr, *region_ptr, *trailer_ptr, *ptr = fw->data; | ||
const struct mtk_wed_fw_trailer *trailer; | ||
const struct mtk_wed_fw_region *fw_region; | ||
|
||
trailer_ptr = fw->data + fw->size - sizeof(*trailer); | ||
trailer = (const struct mtk_wed_fw_trailer *)trailer_ptr; | ||
region_ptr = trailer_ptr - trailer->num_region * sizeof(*fw_region); | ||
first_region_ptr = region_ptr; | ||
|
||
while (region_ptr < trailer_ptr) { | ||
u32 length; | ||
|
||
fw_region = (const struct mtk_wed_fw_region *)region_ptr; | ||
length = le32_to_cpu(fw_region->len); | ||
|
||
if (region->phy_addr != le32_to_cpu(fw_region->addr)) | ||
goto next; | ||
|
||
if (region->size < length) | ||
goto next; | ||
|
||
if (first_region_ptr < ptr + length) | ||
goto next; | ||
|
||
if (region->shared && region->consumed) | ||
return 0; | ||
|
||
if (!region->shared || !region->consumed) { | ||
memcpy_toio(region->addr, ptr, length); | ||
region->consumed = true; | ||
return 0; | ||
} | ||
next: | ||
region_ptr += sizeof(*fw_region); | ||
ptr += length; | ||
} | ||
|
||
return -EINVAL; | ||
} | ||
|
||
static int | ||
mtk_wed_mcu_load_firmware(struct mtk_wed_wo *wo) | ||
{ | ||
static struct mtk_wed_wo_memory_region mem_region[] = { | ||
[MTK_WED_WO_REGION_EMI] = { | ||
.name = "wo-emi", | ||
}, | ||
[MTK_WED_WO_REGION_ILM] = { | ||
.name = "wo-ilm", | ||
}, | ||
[MTK_WED_WO_REGION_DATA] = { | ||
.name = "wo-data", | ||
.shared = true, | ||
}, | ||
}; | ||
const struct mtk_wed_fw_trailer *trailer; | ||
const struct firmware *fw; | ||
const char *fw_name; | ||
u32 val, boot_cr; | ||
int ret, i; | ||
|
||
/* load firmware region metadata */ | ||
for (i = 0; i < ARRAY_SIZE(mem_region); i++) { | ||
ret = mtk_wed_get_memory_region(wo, &mem_region[i]); | ||
if (ret) | ||
return ret; | ||
} | ||
|
||
wo->boot.name = "wo-boot"; | ||
ret = mtk_wed_get_memory_region(wo, &wo->boot); | ||
if (ret) | ||
return ret; | ||
|
||
/* set dummy cr */ | ||
wed_w32(wo->hw->wed_dev, MTK_WED_SCR0 + 4 * MTK_WED_DUMMY_CR_FWDL, | ||
wo->hw->index + 1); | ||
|
||
/* load firmware */ | ||
fw_name = wo->hw->index ? MT7986_FIRMWARE_WO1 : MT7986_FIRMWARE_WO0; | ||
ret = request_firmware(&fw, fw_name, wo->hw->dev); | ||
if (ret) | ||
return ret; | ||
|
||
trailer = (void *)(fw->data + fw->size - | ||
sizeof(struct mtk_wed_fw_trailer)); | ||
dev_info(wo->hw->dev, | ||
"MTK WED WO Firmware Version: %.10s, Build Time: %.15s\n", | ||
trailer->fw_ver, trailer->build_date); | ||
dev_info(wo->hw->dev, "MTK WED WO Chip ID %02x Region %d\n", | ||
trailer->chip_id, trailer->num_region); | ||
|
||
for (i = 0; i < ARRAY_SIZE(mem_region); i++) { | ||
ret = mtk_wed_mcu_run_firmware(wo, fw, &mem_region[i]); | ||
if (ret) | ||
goto out; | ||
} | ||
|
||
/* set the start address */ | ||
boot_cr = wo->hw->index ? MTK_WO_MCU_CFG_LS_WA_BOOT_ADDR_ADDR | ||
: MTK_WO_MCU_CFG_LS_WM_BOOT_ADDR_ADDR; | ||
wo_w32(wo, boot_cr, mem_region[MTK_WED_WO_REGION_EMI].phy_addr >> 16); | ||
/* wo firmware reset */ | ||
wo_w32(wo, MTK_WO_MCU_CFG_LS_WF_MCCR_CLR_ADDR, 0xc00); | ||
|
||
val = wo_r32(wo, MTK_WO_MCU_CFG_LS_WF_MCU_CFG_WM_WA_ADDR); | ||
val |= wo->hw->index ? MTK_WO_MCU_CFG_LS_WF_WM_WA_WA_CPU_RSTB_MASK | ||
: MTK_WO_MCU_CFG_LS_WF_WM_WA_WM_CPU_RSTB_MASK; | ||
wo_w32(wo, MTK_WO_MCU_CFG_LS_WF_MCU_CFG_WM_WA_ADDR, val); | ||
out: | ||
release_firmware(fw); | ||
|
||
return ret; | ||
} | ||
|
||
static u32 | ||
mtk_wed_mcu_read_fw_dl(struct mtk_wed_wo *wo) | ||
{ | ||
return wed_r32(wo->hw->wed_dev, | ||
MTK_WED_SCR0 + 4 * MTK_WED_DUMMY_CR_FWDL); | ||
} | ||
|
||
int mtk_wed_mcu_init(struct mtk_wed_wo *wo) | ||
{ | ||
u32 val; | ||
int ret; | ||
|
||
skb_queue_head_init(&wo->mcu.res_q); | ||
init_waitqueue_head(&wo->mcu.wait); | ||
mutex_init(&wo->mcu.mutex); | ||
|
||
ret = mtk_wed_mcu_load_firmware(wo); | ||
if (ret) | ||
return ret; | ||
|
||
return readx_poll_timeout(mtk_wed_mcu_read_fw_dl, wo, val, !val, | ||
100, MTK_FW_DL_TIMEOUT); | ||
} | ||
|
||
MODULE_FIRMWARE(MT7986_FIRMWARE_WO0); | ||
MODULE_FIRMWARE(MT7986_FIRMWARE_WO1); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.