[][openwrt][mt7988][tops][TOPS Alpha release]

[Description]
Add alpha version of TOPS(tunnel offload processor system) and tops-tool
package.

TOPS package supports tunnel protocol HW offload. The support offload
tunnel protocols for Alpha version are L2oGRE and L2TPv2.
Notice that, TOPS only guarantees that inner packets are TCP. It is still
unstable for UDP inner packet flow.

tops-tool package provides several debug features such as logger, coredump
for TOPS.

[Release-log]
N/A

Change-Id: Iab6e4a89bebbe42c967f28e0c9e9c0611673f354
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/7852683
diff --git a/package-21.02/kernel/tops/src/firmware.c b/package-21.02/kernel/tops/src/firmware.c
new file mode 100644
index 0000000..20a7db1
--- /dev/null
+++ b/package-21.02/kernel/tops/src/firmware.c
@@ -0,0 +1,796 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+#include "firmware.h"
+#include "internal.h"
+#include "mcu.h"
+
+#define TOPS_MGMT_IMG				"mediatek/tops-mgmt.img"
+#define TOPS_OFFLOAD_IMG			"mediatek/tops-offload.img"
+
+#define MTK_SIP_TOPS_LOAD			0xC2000560
+
+#define PAYLOAD_ALIGNMENT			(32)
+
+#define TOPS_ITCM_BOOT_ADDR			(0x40020000)
+#define TOPS_DTCM_BOOT_ADDR			(0x40000000)
+#define TOPS_L2SRAM_BOOT_ADDR			(0x4E100000)
+#define TOPS_DEFAULT_BOOT_ADDR			(TOPS_ITCM_BOOT_ADDR)
+
+#define TOPS_FW_MAGIC				(0x53504f54)
+#define TOPS_FW_HDR_VER				(1)
+#define FW_HLEN					(sizeof(struct tops_fw_header))
+#define FW_DATA(fw)				((fw)->data)
+#define FW_ROLE(fw)				((fw)->hdr.role)
+#define FW_PART_HLEN(fw)			((fw)->hdr.part_hdr_len)
+#define FW_PART_HDR(fw, idx)			(FW_DATA(fw) + FW_PART_HLEN(fw) * (idx))
+#define FW_NUM_PARTS(fw)			((fw)->hdr.num_parts)
+#define FW_GIT_ID(fw)				((fw)->hdr.git_commit_id)
+#define FW_BUILD_TS(fw)				((fw)->hdr.build_ts)
+
+#define FW_PART_LOAD_ADDR_OVERRIDE		(BIT(0))
+#define FW_PART_BOOT_OVERRIDE			(BIT(1))
+
+enum tops_part_type {
+	TOPS_PART_TYPE_IRAM0,
+	TOPS_PART_TYPE_DRAM0,
+	TOPS_PART_TYPE_L2SRAM,
+	TOPS_PART_TYPE_METADATA,
+
+	__TOPS_PART_TYPE_MAX,
+};
+
+enum tops_plat_id {
+	TOPS_PLAT_MT7988,
+
+	__TOPS_PLAT_MAX,
+};
+
+struct tops_boot_config {
+	enum tops_part_type boot_type;
+	u32 boot_addr;
+};
+
+struct tops_fw_plat {
+	enum tops_plat_id plat;
+	u16 id;
+};
+
+struct tops_fw_header {
+	u32 magic;
+	u8 hdr_ver;
+	u8 api_ver;
+	u16 hdr_len;
+	u32 hdr_crc;
+	u16 plat_id;
+	u16 flags;
+	u64 git_commit_id;
+	u32 build_ts;
+	u8 role;
+	u8 signing_type;
+	u8 num_parts;
+	u8 part_hdr_len;
+	u32 part_hdr_crc;
+	u32 payload_len;
+	u32 payload_crc;
+	u32 sign_body_len;
+} __aligned(4);
+
+struct tops_fw_part_hdr {
+	u8 part_type;
+	u8 resv;
+	u16 flags;
+	u32 size;
+	u32 value[2];
+} __aligned(4);
+
+struct tops_fw_part {
+	const struct tops_fw_part_hdr *hdr[__TOPS_PART_TYPE_MAX];
+	const void *payload[__TOPS_PART_TYPE_MAX];
+};
+
+struct tops_fw {
+	struct tops_fw_header hdr;
+	u8 data[0];
+};
+
+struct tops_fw_attr {
+	char *property;
+	char *value;
+};
+
+struct tops_fw_info {
+	struct tops_fw_attr *attrs;
+	u64 git_commit_id;
+	u32 build_ts;
+	u32 nattr;
+};
+
+struct npu {
+	void __iomem *base;
+	struct device *dev;
+	struct tops_fw_info fw_info[__TOPS_ROLE_TYPE_MAX];
+};
+
+#if !defined(CONFIG_MTK_TOPS_SECURE_FW)
+static struct tops_boot_config tops_boot_configs[] = {
+	{ .boot_type = TOPS_PART_TYPE_IRAM0, .boot_addr = TOPS_ITCM_BOOT_ADDR },
+	{ .boot_type = TOPS_PART_TYPE_DRAM0, .boot_addr = TOPS_DTCM_BOOT_ADDR },
+	{ .boot_type = TOPS_PART_TYPE_L2SRAM, .boot_addr = TOPS_L2SRAM_BOOT_ADDR},
+};
+
+static struct tops_fw_plat tops_plats[] = {
+	{ .plat = TOPS_PLAT_MT7988, .id = 0x7988 },
+};
+#endif /* !defined(CONFIG_MTK_TOPS_SECURE_FW) */
+
+static struct npu npu;
+
+static inline void npu_write(u32 reg, u32 val)
+{
+	writel(val, npu.base + reg);
+}
+
+static inline void npu_set(u32 reg, u32 mask)
+{
+	setbits(npu.base + reg, mask);
+}
+
+static inline void npu_clr(u32 reg, u32 mask)
+{
+	clrbits(npu.base + reg, mask);
+}
+
+static inline void npu_rmw(u32 reg, u32 mask, u32 val)
+{
+	clrsetbits(npu.base + reg, mask, val);
+}
+
+static inline u32 npu_read(u32 reg)
+{
+	return readl(npu.base + reg);
+}
+
+u64 mtk_tops_fw_get_git_commit_id(enum tops_role_type rtype)
+{
+	if (rtype >= __TOPS_ROLE_TYPE_MAX)
+		return 0;
+
+	return npu.fw_info[rtype].git_commit_id;
+}
+
+void mtk_tops_fw_get_built_date(enum tops_role_type rtype, struct tm *tm)
+{
+	if (rtype >= __TOPS_ROLE_TYPE_MAX)
+		return;
+
+	time64_to_tm(npu.fw_info[rtype].build_ts, 0, tm);
+}
+
+u32 mtk_tops_fw_attr_get_num(enum tops_role_type rtype)
+{
+	if (rtype >= __TOPS_ROLE_TYPE_MAX)
+		return 0;
+
+	return npu.fw_info[rtype].nattr;
+}
+
+const char *mtk_tops_fw_attr_get_property(enum tops_role_type rtype, u32 idx)
+{
+	if (rtype >= __TOPS_ROLE_TYPE_MAX || idx >= npu.fw_info[rtype].nattr)
+		return NULL;
+
+	return npu.fw_info[rtype].attrs[idx].property;
+}
+
+const char *mtk_tops_fw_attr_get_value(enum tops_role_type rtype,
+				       const char *property)
+{
+	u32 plen = strlen(property);
+	u32 nattr;
+	u32 i;
+
+	if (rtype >= __TOPS_ROLE_TYPE_MAX)
+		return NULL;
+
+	nattr = npu.fw_info[rtype].nattr;
+	for (i = 0; i < nattr; i++) {
+		if (!strncmp(property, npu.fw_info[rtype].attrs[i].property, plen))
+			return npu.fw_info[rtype].attrs[i].value;
+	}
+
+	return NULL;
+}
+
+static bool mtk_tops_fw_support_plat(const struct tops_fw_header *fw_hdr)
+{
+	u32 i;
+
+	for (i = 0; i < __TOPS_PLAT_MAX; i++)
+		if (le16_to_cpu(fw_hdr->plat_id) == tops_plats[i].plat)
+			return true;
+
+	return false;
+}
+
+static int mtk_tops_fw_valid_hdr(const struct tops_fw *tfw, uint32_t fw_size)
+{
+	const struct tops_fw_header *fw_hdr = &tfw->hdr;
+	u32 total_size;
+	u32 ph_len;
+
+	if (fw_size < FW_HLEN) {
+		TOPS_ERR("requested fw hlen is less than minimal TOPS fw hlen\n");
+		return -EINVAL;
+	}
+
+	if (le32_to_cpu(fw_hdr->magic) != TOPS_FW_MAGIC) {
+		TOPS_ERR("not a tops fw!\n");
+		return -EBADF;
+	}
+
+	if (le16_to_cpu(fw_hdr->hdr_ver) != TOPS_FW_HDR_VER) {
+		TOPS_ERR("unsupport tops fw header: %u\n",
+			      le16_to_cpu(fw_hdr->hdr_ver));
+		return -EBADF;
+	}
+
+	if (le16_to_cpu(fw_hdr->hdr_len) != sizeof(struct tops_fw_header)) {
+		TOPS_ERR("tops fw header length mismatch\n");
+		return -EBADF;
+	}
+
+	if (fw_hdr->part_hdr_len != sizeof(struct tops_fw_part_hdr)) {
+		TOPS_ERR("unsupport tops fw header len: %u\n",
+			      fw_hdr->part_hdr_len);
+		return -EBADF;
+	}
+
+	if (!mtk_tops_fw_support_plat(fw_hdr)) {
+		TOPS_ERR("unsupport tops platform fw: %u\n",
+			le16_to_cpu(fw_hdr->plat_id));
+		return -EBADF;
+	}
+
+	if (fw_hdr->role >= __TOPS_ROLE_TYPE_MAX) {
+		TOPS_ERR("unsupport tops role: %u\n", fw_hdr->role);
+		return -EBADF;
+	}
+
+	if (fw_hdr->num_parts > __TOPS_PART_TYPE_MAX) {
+		TOPS_ERR("number of parts exceeds tops' support: %u\n",
+			fw_hdr->num_parts);
+		return -EBADF;
+	}
+
+	ph_len = fw_hdr->part_hdr_len * fw_hdr->num_parts;
+	total_size = fw_hdr->hdr_len + ph_len + fw_hdr->payload_len;
+
+	if (total_size > fw_size) {
+		TOPS_ERR("firmware incomplete\n");
+		return -EBADF;
+	}
+
+	return 0;
+}
+
+static int mtk_tops_fw_init_part_data(const struct tops_fw *fw,
+				      struct tops_fw_part *part)
+{
+	const struct tops_fw_part_hdr *phdr;
+	uint32_t part_off = FW_PART_HLEN(fw) * FW_NUM_PARTS(fw);
+	int ret = 0;
+	u8 i;
+
+	for (i = 0; i < FW_NUM_PARTS(fw); i++) {
+		/* get part hdr */
+		phdr = (struct tops_fw_part_hdr *)FW_PART_HDR(fw, i);
+		if (phdr->part_type >= __TOPS_PART_TYPE_MAX) {
+			TOPS_ERR("unknown part type: %u\n", phdr->part_type);
+			return -EBADF;
+		}
+
+		part->hdr[phdr->part_type] = phdr;
+
+		/* get part payload */
+		part->payload[phdr->part_type] = FW_DATA(fw) + part_off;
+
+		part_off += ALIGN(le32_to_cpu(phdr->size), PAYLOAD_ALIGNMENT);
+	}
+
+	return ret;
+}
+
+#if defined(CONFIG_MTK_TOPS_SECURE_FW)
+static int mtk_tops_fw_smc(u32 smc_id,
+			   u64 x1,
+			   u64 x2,
+			   u64 x3,
+			   u64 x4,
+			   struct arm_smccc_res *res)
+{
+	if (!res)
+		return -EINVAL;
+
+	arm_smccc_smc(smc_id, x1, x2, x3, x4, 0, 0, 0, res);
+
+	return res->a0;
+}
+
+static int __mtk_tops_fw_bring_up_core(const void *fw, u32 fw_size)
+{
+	struct arm_smccc_res res = {0};
+	dma_addr_t fw_paddr;
+	void *fw_vaddr;
+	u32 order = 0;
+	u32 psize;
+	int ret;
+
+	psize = (fw_size / PAGE_SIZE) + 1;
+	while ((1 << order) < psize)
+		order++;
+
+	fw_vaddr = __get_free_pages(GFP_KERNEL, order);
+	if (!fw_vaddr)
+		return -ENOMEM;
+
+	memcpy(fw_vaddr, fw, fw_size);
+
+	fw_paddr = dma_map_single(tops_dev, fw_vaddr, PAGE_SIZE, DMA_TO_DEVICE);
+	if (dma_mapping_error(tops_dev, fw_paddr)) {
+		ret = -ENOMEM;
+		goto dma_map_err;
+	}
+	/* make sure firmware data is written and mapped to buffer */
+	wmb();
+
+	ret = mtk_tops_fw_smc(MTK_SIP_TOPS_LOAD, 0, fw_paddr, fw_size, 0, &res);
+	if (ret)
+		TOPS_ERR("tops secure firmware load failed: %d\n", ret);
+
+	dma_unmap_single(tops_dev, fw_paddr, fw_size, DMA_TO_DEVICE);
+
+dma_map_err:
+	free_pages(fw_vaddr, order);
+
+	return ret;
+}
+#else /* !defined(CONFIG_MTK_TOPS_SECURE_FW) */
+static u32 mtk_tops_fw_get_boot_addr(struct tops_fw_part *part)
+{
+	const struct tops_fw_part_hdr *hdr = NULL;
+	u32 boot_addr = TOPS_DEFAULT_BOOT_ADDR;
+	u32 i;
+
+	for (i = TOPS_PART_TYPE_IRAM0; i < __TOPS_PART_TYPE_MAX; i++) {
+		hdr = part->hdr[i];
+
+		if (le16_to_cpu(hdr->flags) & FW_PART_BOOT_OVERRIDE) {
+			boot_addr = tops_boot_configs[i].boot_addr;
+
+			if (le16_to_cpu(hdr->flags) & FW_PART_LOAD_ADDR_OVERRIDE)
+				boot_addr = le32_to_cpu(hdr->value[0]);
+		}
+	}
+
+	return boot_addr;
+}
+
+static void __mtk_tops_fw_load_data(const struct tops_fw_part_hdr *phdr,
+				     const void *payload,
+				     u32 addr)
+{
+	int ofs;
+
+	for (ofs = 0; ofs < le32_to_cpu(phdr->size); ofs += 0x4)
+		npu_write(addr + ofs, *(u32 *)(payload + ofs));
+}
+
+static int mtk_tops_fw_load_core_mgmt(struct tops_fw_part *part)
+{
+	if (!part)
+		return -ENODEV;
+
+	__mtk_tops_fw_load_data(part->hdr[TOPS_PART_TYPE_IRAM0],
+				part->payload[TOPS_PART_TYPE_IRAM0],
+				TOP_CORE_M_ITCM);
+
+	__mtk_tops_fw_load_data(part->hdr[TOPS_PART_TYPE_DRAM0],
+				part->payload[TOPS_PART_TYPE_DRAM0],
+				TOP_CORE_M_DTCM);
+
+	__mtk_tops_fw_load_data(part->hdr[TOPS_PART_TYPE_L2SRAM],
+				part->payload[TOPS_PART_TYPE_L2SRAM],
+				TOP_L2SRAM);
+
+	return 0;
+}
+
+static int mtk_tops_fw_bring_up_core_mgmt(struct tops_fw_part *part)
+{
+	int ret = 0;
+
+	/* setup boot address */
+	npu_write(TOP_CORE_M_RESET_VECTOR, mtk_tops_fw_get_boot_addr(part));
+
+	/* de-assert core reset */
+	npu_write(TOP_CORE_NPU_SW_RST, 0);
+
+	/* enable run stall */
+	npu_write(TOP_CORE_NPU_CTRL, 0x1);
+
+	/* enable ext bootup sel */
+	npu_write(TOP_CORE_M_STAT_VECTOR_SEL, 0x1);
+
+	/* toggle reset */
+	npu_write(TOP_CORE_NPU_SW_RST, 0x1);
+	npu_write(TOP_CORE_NPU_SW_RST, 0x0);
+
+	/* load firmware */
+	ret = mtk_tops_fw_load_core_mgmt(part);
+	if (ret) {
+		TOPS_ERR("load core mgmt fw failed: %d\n", ret);
+		return ret;
+	}
+
+	/* release run stall */
+	npu_write(TOP_CORE_NPU_CTRL, 0);
+
+	return ret;
+}
+
+static int mtk_tops_fw_load_core_offload(struct tops_fw_part *part,
+					 enum core_id core)
+{
+	if (!part)
+		return -ENODEV;
+
+	if (core >= CORE_OFFLOAD_NUM)
+		return -EPERM;
+
+	__mtk_tops_fw_load_data(part->hdr[TOPS_PART_TYPE_IRAM0],
+				part->payload[TOPS_PART_TYPE_IRAM0],
+				CLUST_CORE_X_ITCM(core));
+
+	__mtk_tops_fw_load_data(part->hdr[TOPS_PART_TYPE_DRAM0],
+				part->payload[TOPS_PART_TYPE_DRAM0],
+				CLUST_CORE_X_DTCM(core));
+
+	return 0;
+}
+
+static int __mtk_tops_fw_bring_up_core_offload(struct tops_fw_part *part,
+					       enum core_id core)
+{
+	int ret = 0;
+
+	/* setup boot address */
+	npu_write(CLUST_CORE_X_RESET_VECTOR(core),
+		mtk_tops_fw_get_boot_addr(part));
+
+	/* de-assert core reset */
+	npu_write(CLUST_CORE_NPU_SW_RST(core), 0);
+
+	/* enable run stall */
+	npu_write(CLUST_CORE_NPU_CTRL(core), 0x1);
+
+	/* enable ext bootup sel */
+	npu_write(CLUST_CORE_X_STAT_VECTOR_SEL(core), 0x1);
+
+	/* toggle reset */
+	npu_write(CLUST_CORE_NPU_SW_RST(core), 0x1);
+	npu_write(CLUST_CORE_NPU_SW_RST(core), 0x0);
+
+	/* load firmware */
+	ret = mtk_tops_fw_load_core_offload(part, core);
+	if (ret) {
+		TOPS_ERR("load core offload fw failed: %d\n", ret);
+		return ret;
+	}
+
+	/* release run stall */
+	npu_write(CLUST_CORE_NPU_CTRL(core), 0);
+
+	return ret;
+}
+
+static int mtk_tops_fw_bring_up_core_offload(struct tops_fw_part *part)
+{
+	int ret = 0;
+	u32 i = 0;
+
+	__mtk_tops_fw_load_data(part->hdr[TOPS_PART_TYPE_L2SRAM],
+				part->payload[TOPS_PART_TYPE_L2SRAM],
+				CLUST_L2SRAM);
+
+	for (i = 0; i < CORE_OFFLOAD_NUM; i++) {
+		ret = __mtk_tops_fw_bring_up_core_offload(part, i);
+		if (ret)
+			return ret;
+	}
+
+	return ret;
+}
+
+static int __mtk_tops_fw_bring_up_core(const struct tops_fw *tfw,
+				       struct tops_fw_part *part)
+{
+	int ret = 0;
+
+	if (!tfw || !part)
+		return -EINVAL;
+
+	/* bring up core by role */
+	switch (FW_ROLE(tfw)) {
+	case TOPS_ROLE_TYPE_MGMT:
+		ret = mtk_tops_fw_bring_up_core_mgmt(part);
+
+		break;
+	case TOPS_ROLE_TYPE_CLUSTER:
+		ret = mtk_tops_fw_bring_up_core_offload(part);
+
+		break;
+	default:
+		TOPS_ERR("unsupport tops fw role\n");
+
+		return -EBADF;
+	}
+
+	return ret;
+}
+#endif /* defined(CONFIG_MTK_TOPS_SECURE_FW) */
+
+static int mtk_tops_fw_get_info(const struct tops_fw *tfw, struct tops_fw_part *part)
+{
+	const struct tops_fw_part_hdr *phdr;
+	const u8 *payload;
+	struct tops_fw_info *fw_info;
+	struct tops_fw_attr *attrs;
+	u32 kofs, klen, vofs, vlen;
+	u32 meta_len;
+	u32 ofs = 0;
+	u32 nattr;
+	u32 i;
+
+	if (!tfw || !part)
+		return -EINVAL;
+
+	if (FW_ROLE(tfw) > __TOPS_ROLE_TYPE_MAX)
+		return -EINVAL;
+
+	phdr = part->hdr[TOPS_PART_TYPE_METADATA];
+	payload = part->payload[TOPS_PART_TYPE_METADATA];
+	meta_len = le32_to_cpu(phdr->size);
+
+	if (!phdr || !payload || !meta_len)
+		return 0;
+
+	fw_info = &npu.fw_info[FW_ROLE(tfw)];
+	fw_info->nattr = nattr = le32_to_cpu(*((u32 *)payload));
+	ofs += 0x4;
+
+	fw_info->attrs = devm_kcalloc(tops_dev,
+				      nattr * 2,
+				      sizeof(char *),
+				      GFP_KERNEL);
+	if (!fw_info->attrs) {
+		fw_info->nattr = 0;
+		return -ENOMEM;
+	}
+	attrs = fw_info->attrs;
+
+	for (i = 0; i < nattr; i++) {
+		struct tops_fw_attr *attr = &attrs[i];
+
+		/* get property offset */
+		if (ofs + (i * 2) * 0x4 >= meta_len)
+			break;
+		kofs = le32_to_cpu(*((u32 *)(payload + ofs + (i * 2) * 0x4)));
+
+		/* get value offset */
+		if (ofs + (i * 2 + 1) * 0x4 >= meta_len)
+			break;
+		vofs = le32_to_cpu(*((u32 *)(payload + ofs + (i * 2 + 1) * 0x4)));
+
+		klen = strlen(payload + kofs);
+		vlen = strlen(payload + vofs);
+		if (!kofs || !vofs || !klen || !vlen) {
+			TOPS_ERR("invalid attribute property value pair, kofs: %u, klen: %u, vofs: %u, vlen: %u\n",
+				 kofs, klen, vofs, vlen);
+			break;
+		}
+
+		attr->property = devm_kzalloc(tops_dev,
+					 sizeof(char) * klen + 1,
+					 GFP_KERNEL);
+		if (!attr->property)
+			goto err_out;
+
+		attr->value = devm_kzalloc(tops_dev,
+					 sizeof(char) * vlen + 1,
+					 GFP_KERNEL);
+		if (!attr->value) {
+			devm_kfree(tops_dev, attr->property);
+			goto err_out;
+		}
+
+		strncpy(attr->property, payload + kofs, klen);
+		strncpy(attr->value, payload + vofs, vlen);
+	}
+
+	fw_info->git_commit_id = le64_to_cpu(FW_GIT_ID(tfw));
+	fw_info->build_ts = le32_to_cpu(FW_BUILD_TS(tfw));
+
+	return 0;
+
+err_out:
+	fw_info->git_commit_id = 0;
+	fw_info->build_ts = 0;
+
+	for (i = i - 1; i >= 0; i--) {
+		devm_kfree(tops_dev, attrs[i].property);
+		devm_kfree(tops_dev, attrs[i].value);
+	}
+
+	devm_kfree(tops_dev, attrs);
+
+	return -ENOMEM;
+}
+
+static void mtk_tops_fw_put_info(void)
+{
+	enum tops_role_type rtype;
+	struct tops_fw_attr *attrs;
+	u32 nattr;
+	u32 i;
+
+	for (rtype = TOPS_ROLE_TYPE_MGMT; rtype < __TOPS_ROLE_TYPE_MAX; rtype++) {
+		nattr = npu.fw_info[rtype].nattr;
+		attrs = npu.fw_info[rtype].attrs;
+
+		npu.fw_info[rtype].git_commit_id = 0;
+		npu.fw_info[rtype].build_ts = 0;
+
+		if (!nattr)
+			continue;
+
+		for (i = 0; i < nattr; i++) {
+			devm_kfree(tops_dev, attrs[i].property);
+			devm_kfree(tops_dev, attrs[i].value);
+		}
+
+		devm_kfree(tops_dev, attrs);
+
+		npu.fw_info[rtype].nattr = 0;
+		npu.fw_info[rtype].attrs = NULL;
+	}
+}
+
+int mtk_tops_fw_bring_up_core(const char *fw_path)
+{
+	const struct firmware *fw;
+	const struct tops_fw *tfw;
+	struct tops_fw_part part;
+	struct tm tm = {0};
+	int ret;
+
+	ret = request_firmware(&fw, fw_path, tops_dev);
+	if (ret) {
+		TOPS_ERR("request %s firmware failed\n", fw_path);
+		return ret;
+	}
+
+	tfw = (const void *)fw->data;
+
+	ret = mtk_tops_fw_valid_hdr(tfw, fw->size);
+	if (ret) {
+		TOPS_ERR("valid fw: %s image failed: %d\n", fw_path, ret);
+		goto err_out;
+	}
+
+	ret = mtk_tops_fw_init_part_data(tfw, &part);
+	if (ret) {
+		TOPS_ERR("init fw part data failed: %d\n", ret);
+		goto err_out;
+	}
+
+	ret = mtk_tops_fw_get_info(tfw, &part);
+	if (ret) {
+		TOPS_ERR("meta data initialize failed: %d\n", ret);
+		goto err_out;
+	}
+
+	ret = __mtk_tops_fw_bring_up_core(tfw, &part);
+	if (ret) {
+		TOPS_ERR("bring up core %s failed\n", fw_path);
+		mtk_tops_fw_put_info();
+		goto err_out;
+	}
+
+	mtk_tops_fw_get_built_date(FW_ROLE(tfw), &tm);
+
+	TOPS_NOTICE("TOPS Load Firmware: %s\n", fw_path);
+	TOPS_NOTICE("\tFirmware version:\t%s\n",
+		    mtk_tops_fw_attr_get_value(FW_ROLE(tfw), "version"));
+	TOPS_NOTICE("\tGit revision:\t\t%llx\n",
+		    mtk_tops_fw_get_git_commit_id(FW_ROLE(tfw)));
+	TOPS_NOTICE("\tBuilt date:\t\t%04ld/%02d/%02d %02d:%02d:%02d\n",
+		    tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+		    tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+err_out:
+	release_firmware(fw);
+
+	return ret;
+}
+#if defined(CONFIG_MTK_TOPS_EVALUATION)
+EXPORT_SYMBOL(mtk_tops_fw_bring_up_core);
+#endif /* defined(CONFIG_MTK_TOPS_EVALUATION) */
+
+int mtk_tops_fw_bring_up_default_cores(void)
+{
+	int ret;
+
+	ret = mtk_tops_fw_bring_up_core(TOPS_MGMT_IMG);
+	if (ret)
+		return ret;
+
+	ret = mtk_tops_fw_bring_up_core(TOPS_OFFLOAD_IMG);
+
+	return ret;
+}
+
+#if defined(CONFIG_MTK_TOPS_CORE_DEBUG)
+static void mtk_tops_fw_enable_core_debug(void)
+{
+	u32 i;
+
+	npu_write(TOP_CORE_DBG_CTRL, 0x3F);
+	npu_write(CLUST_CORE_DBG_CTRL, 0x1F);
+
+	npu_write(TOP_CORE_OCD_CTRL, 0x1);
+
+	for (i = 0; i < CORE_OFFLOAD_NUM; i++)
+		npu_write(CLUST_CORE_OCD_CTRL(i), 0x1);
+}
+#endif /* defined(CONFIG_MTK_TOPS_CORE_DEBUG) */
+
+void mtk_tops_fw_clean_up(void)
+{
+	mtk_tops_fw_put_info();
+}
+
+int mtk_tops_fw_init(struct platform_device *pdev)
+{
+	struct resource *res;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tops-base");
+	if (!res)
+		return -ENXIO;
+
+	npu.base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+	if (!npu.base)
+		return -ENOMEM;
+
+/* TODO: move to somewhere else */
+#if defined(CONFIG_MTK_TOPS_CORE_DEBUG)
+	mtk_tops_enable_core_debug();
+#endif /* defined(CONFIG_MTK_TOPS_CORE_DEBUG) */
+
+	return 0;
+}