AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/10 06:33 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "MTK_T9XX"
  ],
  "FocusSymbols": [
    "mtk_pci_probe",
    "mtk_pci_remove",
    "mtk_port_wwan_open",
    "mtk_port_wwan_close",
    "mtk_port_wwan_write",
    "mtk_port_wwan_write_blocking",
    "mtk_port_wwan_poll",
    "mtk_cldma_open",
    "mtk_cldma_tx",
    "mtk_cldma_close",
    "mtk_fsm_startup_act",
    "mtk_fsm_dev_rm_act",
    "mtk_fsm_dev_add_act",
    "mtk_cldma_isr",
    "mtk_pci_irq_msix",
    "mtk_cldma_tx_done_work",
    "mtk_cldma_rx_done_work"
  ],
  "Reasoning": "The patch introduces a new WWAN driver for MediaTek T9xx PCIe 5G modems. It implements a PCI driver, CLDMA (DMA controller) handling, state machine, and WWAN port operations. The driver is reachable via the PCI subsystem and WWAN core, making it suitable for fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/07/10 06:33 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 915eb8adbd2d1f10cf4afa0b12fc2ffbad2f925f\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Jul 10 06:33:38 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex 461a3eed612958..8155d26bff03ea 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -16494,6 +16494,15 @@ L:\tnetdev@vger.kernel.org\n S:\tSupported\n F:\tdrivers/net/wwan/t7xx/\n \n+MEDIATEK T9XX 5G WWAN MODEM DRIVER\n+M:\tJack Wu \u003cjackbb_wu@compal.com\u003e\n+R:\tWen-Zhi Huang \u003cwen-zhi.huang@mediatek.com\u003e\n+R:\tShi-Wei Yeh \u003cshi-wei.yeh@mediatek.com\u003e\n+R:\tMinano Tseng \u003cMinano.tseng@mediatek.com\u003e\n+L:\tnetdev@vger.kernel.org\n+S:\tSupported\n+F:\tdrivers/net/wwan/t9xx/\n+\n MEDIATEK USB3 DRD IP DRIVER\n M:\tChunfeng Yun \u003cchunfeng.yun@mediatek.com\u003e\n L:\tlinux-usb@vger.kernel.org\ndiff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig\nindex 88df55d78d902f..a2ffed346acc99 100644\n--- a/drivers/net/wwan/Kconfig\n+++ b/drivers/net/wwan/Kconfig\n@@ -121,6 +121,23 @@ config MTK_T7XX\n \n \t  If unsure, say N.\n \n+config MTK_T9XX\n+\ttristate \"MediaTek PCIe 5G WWAN modem T9xx device\"\n+\tdepends on PCI \u0026\u0026 ACPI\n+\tselect MTK_T9XX_PCI\n+\tselect NET_DEVLINK\n+\thelp\n+\t  Enables MediaTek PCIe based 5G WWAN modem (T9xx series) device.\n+\n+\t  To compile this driver as a module, choose M here: the module will be\n+\t  called mtk_t9xx.\n+\n+\t  If unsure, say N.\n+\n+config MTK_T9XX_PCI\n+\ttristate\n+\tdepends on PCI\n+\n endif # WWAN\n \n endmenu\ndiff --git a/drivers/net/wwan/Makefile b/drivers/net/wwan/Makefile\nindex 3960c0ae2445cd..7361eef4c472c4 100644\n--- a/drivers/net/wwan/Makefile\n+++ b/drivers/net/wwan/Makefile\n@@ -14,3 +14,4 @@ obj-$(CONFIG_QCOM_BAM_DMUX) += qcom_bam_dmux.o\n obj-$(CONFIG_RPMSG_WWAN_CTRL) += rpmsg_wwan_ctrl.o\n obj-$(CONFIG_IOSM) += iosm/\n obj-$(CONFIG_MTK_T7XX) += t7xx/\n+obj-$(CONFIG_MTK_T9XX) += t9xx/\ndiff --git a/drivers/net/wwan/t9xx/Makefile b/drivers/net/wwan/t9xx/Makefile\nnew file mode 100644\nindex 00000000000000..75760b2039dc54\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/Makefile\n@@ -0,0 +1,14 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+\n+ccflags-y += -I$(src)/pcie\n+ccflags-y += -I$(src)\n+\n+obj-$(CONFIG_MTK_T9XX) += mtk_t9xx.o\n+obj-$(CONFIG_MTK_T9XX_PCI) += pcie/\n+\n+mtk_t9xx-y := \\\n+\tmtk_dev.o \\\n+\tmtk_ctrl_plane.o \\\n+\tmtk_port.o \\\n+\tmtk_port_io.o \\\n+\tmtk_fsm.o\ndiff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c\nnew file mode 100644\nindex 00000000000000..1d9dc534824053\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c\n@@ -0,0 +1,108 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2022, MediaTek Inc.\n+ * Copyright (c) 2022-2023, Intel Corporation.\n+ */\n+\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/freezer.h\u003e\n+#include \u003clinux/kthread.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/pm_runtime.h\u003e\n+#include \u003clinux/sched.h\u003e\n+#include \u003clinux/wait.h\u003e\n+\n+#include \"mtk_ctrl_plane.h\"\n+#include \"mtk_port.h\"\n+\n+#define TAG \"CTRL\"\n+\n+static void mtk_ctrl_trans_fsm_state_handler(struct mtk_fsm_param *param,\n+\t\t\t\t\t     struct mtk_ctrl_blk *ctrl_blk)\n+{\n+\tstruct mtk_md_dev *mdev = ctrl_blk-\u003emdev;\n+\n+\tswitch (param-\u003eto) {\n+\tcase FSM_STATE_OFF:\n+\t\tctrl_blk-\u003eops-\u003efsm_indication(mdev, param);\n+\t\tctrl_blk-\u003eops-\u003eexit(mdev);\n+\t\tbreak;\n+\tcase FSM_STATE_ON:\n+\t\tctrl_blk-\u003eops-\u003einit(mdev);\n+\t\tfallthrough;\n+\tdefault:\n+\t\tctrl_blk-\u003eops-\u003efsm_indication(mdev, param);\n+\t\tbreak;\n+\t}\n+}\n+\n+static void mtk_ctrl_fsm_state_listener(struct mtk_fsm_param *param, void *data)\n+{\n+\tstruct mtk_ctrl_blk *ctrl_blk = data;\n+\n+\tmtk_port_mngr_fsm_state_handler(param, ctrl_blk-\u003eport_mngr);\n+\tmtk_ctrl_trans_fsm_state_handler(param, ctrl_blk);\n+\tmtk_port_mngr_fsm_state_handler_late(param, ctrl_blk-\u003eport_mngr);\n+}\n+\n+/**\n+ * mtk_ctrl_init() - Initialize the control plane block.\n+ * @mdev: Pointer to the MTK modem device.\n+ * @ops: HIF operations for the control plane.\n+ * @cfg: Control plane configuration.\n+ *\n+ * Allocates and initializes the control plane block\n+ * associated with @mdev.\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops, struct mtk_ctrl_cfg *cfg)\n+{\n+\tstruct mtk_ctrl_blk *ctrl_blk;\n+\tint err;\n+\n+\tctrl_blk = devm_kzalloc(mdev-\u003edev, sizeof(*ctrl_blk), GFP_KERNEL);\n+\tif (!ctrl_blk)\n+\t\treturn -ENOMEM;\n+\n+\tctrl_blk-\u003emdev = mdev;\n+\tmdev-\u003ectrl_blk = ctrl_blk;\n+\tctrl_blk-\u003eops = ops;\n+\tctrl_blk-\u003ecfg = cfg;\n+\n+\terr = mtk_port_mngr_init(ctrl_blk, cfg-\u003eport_layer_cfg-\u003eport_cfg,\n+\t\t\t\t cfg-\u003eport_layer_cfg-\u003eport_cnt);\n+\tif (err)\n+\t\tgoto err_free_mem;\n+\n+\terr = mtk_fsm_notifier_register(mdev, MTK_USER_CTRL, mtk_ctrl_fsm_state_listener,\n+\t\t\t\t\tctrl_blk, FSM_PRIO_1, false);\n+\tif (err) {\n+\t\tdev_err(mdev-\u003edev, \"Fail to register fsm notification(ret = %d)\\n\", err);\n+\t\tgoto err_port_exit;\n+\t}\n+\n+\treturn 0;\n+\n+err_port_exit:\n+\tmtk_port_mngr_exit(ctrl_blk);\n+err_free_mem:\n+\treturn err;\n+}\n+EXPORT_SYMBOL(mtk_ctrl_init);\n+\n+/**\n+ * mtk_ctrl_exit() - Clean up the control plane block.\n+ * @mdev: Pointer to the MTK modem device.\n+ *\n+ * Frees the control plane block associated with @mdev.\n+ */\n+void mtk_ctrl_exit(struct mtk_md_dev *mdev)\n+{\n+\tstruct mtk_ctrl_blk *ctrl_blk = mdev-\u003ectrl_blk;\n+\n+\tmtk_fsm_notifier_unregister(mdev, MTK_USER_CTRL);\n+\tmtk_port_mngr_exit(ctrl_blk);\n+\tmdev-\u003ectrl_blk = NULL;\n+}\n+EXPORT_SYMBOL(mtk_ctrl_exit);\ndiff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h\nnew file mode 100644\nindex 00000000000000..92817e92a2e46d\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h\n@@ -0,0 +1,88 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#ifndef __MTK_CTRL_PLANE_H__\n+#define __MTK_CTRL_PLANE_H__\n+\n+#include \u003clinux/kref.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+\n+#include \"mtk_dev.h\"\n+#include \"mtk_fsm.h\"\n+\n+#define Q_MTU_2K\t\t\t(0x800)\n+#define Q_MTU_3_5K\t\t\t(0xE00)\n+#define Q_MTU_7K\t\t\t(0x1C00)\n+#define Q_MTU_32K\t\t\t(0x8000)\n+#define Q_MTU_63K\t\t\t(0xFC00)\n+#define Q_FRAG_2K\t\t\t(0x800)\n+#define Q_FRAG_3_5K\t\t\t(0xE00)\n+#define Q_FRAG_7K\t\t\t(0x1C00)\n+#define Q_FRAG_32K\t\t\t(0x8000)\n+#define Q_FRAG_63K\t\t\t(0xFC00)\n+\n+enum mtk_trb_cmd_type {\n+\tTRB_CMD_MIN,\n+\tTRB_CMD_ENABLE,\n+\tTRB_CMD_TX,\n+\tTRB_CMD_DISABLE,\n+\tTRB_CMD_STOP,\n+\tTRB_CMD_RECOVER,\n+\tTRB_CMD_MAX,\n+};\n+\n+enum mtk_hif_dev_ctrl_cmd {\n+\tHIF_CTRL_CMD_CHECK_TX_FULL,\n+};\n+\n+struct trb_open_priv {\n+\tu8 log_rg_offset;\n+\tu32 tx_mtu;\n+\tu32 rx_mtu;\n+\tu32 tx_frag_size;\n+\tu32 rx_frag_size;\n+\tint (*rx_done)(struct sk_buff *skb, void *priv, bool force_recv);\n+};\n+\n+struct trb {\n+\tu32 channel_id;\n+\tenum mtk_trb_cmd_type cmd;\n+\tint status;\n+\tstruct kref kref;\n+\tvoid *priv;\n+\tint (*trb_complete)(struct sk_buff *skb);\n+};\n+\n+union ctrl_hif_cmd_data {\n+\tu32 rx_ch;\n+};\n+\n+struct mtk_ctrl_hif_ops {\n+\tint (*init)(struct mtk_md_dev *mdev);\n+\tint (*exit)(struct mtk_md_dev *mdev);\n+\tint (*submit_skb)(struct mtk_md_dev *mdev, struct sk_buff *skb, bool force_send);\n+\tvoid (*fsm_indication)(struct mtk_md_dev *mdev, struct mtk_fsm_param *param);\n+\tint (*send_cmd)(struct mtk_md_dev *mdev, int cmd, void *data);\n+};\n+\n+struct mtk_ctrl_cfg {\n+\tstruct mtk_port_layer_cfg *port_layer_cfg;\n+};\n+\n+struct mtk_ctrl_trans;\n+\n+struct mtk_ctrl_blk {\n+\tstruct mtk_md_dev *mdev;\n+\tstruct mtk_port_mngr *port_mngr;\n+\tstruct mtk_ctrl_hif_ops *ops;\n+\tvoid *ctrl_hw_priv;\n+\tstruct mtk_ctrl_cfg *cfg;\n+};\n+\n+int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops,\n+\t\t  struct mtk_ctrl_cfg *cfg);\n+void mtk_ctrl_exit(struct mtk_md_dev *mdev);\n+\n+#endif /* __MTK_CTRL_PLANE_H__ */\ndiff --git a/drivers/net/wwan/t9xx/mtk_dev.c b/drivers/net/wwan/t9xx/mtk_dev.c\nnew file mode 100644\nindex 00000000000000..7c449030a35ff5\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/mtk_dev.c\n@@ -0,0 +1,33 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#include \u003clinux/module.h\u003e\n+\n+#include \"mtk_dev.h\"\n+#include \"mtk_port.h\"\n+#include \"mtk_port_io.h\"\n+\n+static int __init mtk_common_drv_init(void)\n+{\n+\tint ret;\n+\n+\tret = mtk_port_io_init();\n+\tif (ret)\n+\t\tgoto err_init_devid;\n+\n+err_init_devid:\n+\treturn ret;\n+}\n+module_init(mtk_common_drv_init);\n+\n+static void __exit mtk_common_drv_exit(void)\n+{\n+\tmtk_port_io_exit();\n+\tmtk_port_stale_list_grp_cleanup();\n+}\n+module_exit(mtk_common_drv_exit);\n+\n+MODULE_DESCRIPTION(\"MediaTek T9xx PCIe WWAN driver\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/net/wwan/t9xx/mtk_dev.h b/drivers/net/wwan/t9xx/mtk_dev.h\nnew file mode 100644\nindex 00000000000000..b4777b7c5d49fd\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/mtk_dev.h\n@@ -0,0 +1,111 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#ifndef __MTK_DEV_H__\n+#define __MTK_DEV_H__\n+\n+#include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/dmapool.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/sched.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/spinlock.h\u003e\n+\n+#define MTK_DEV_STR_LEN 16\n+\n+enum mtk_user_id {\n+\tMTK_USER_MIN,\n+\tMTK_USER_CTRL,\n+\tMTK_USER_DATA,\n+\tMTK_USER_MAX\n+};\n+\n+enum mtk_dev_evt_h2d {\n+\tDEV_EVT_H2D_DEVICE_RESET\t= BIT(2),\n+\tDEV_EVT_H2D_MAX\t\t\t= BIT(5)\n+};\n+\n+enum mtk_dev_evt_d2h {\n+\tDEV_EVT_D2H_BOOT_FLOW_SYNC\t= BIT(4),\n+\tDEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP = BIT(5),\n+\tDEV_EVT_D2H_ASYNC_HS_NOTIFY_MD\t= BIT(6),\n+\tDEV_EVT_D2H_MAX\t\t\t= BIT(11)\n+};\n+\n+struct mtk_md_dev;\n+struct mtk_ctrl_blk;\n+\n+struct mtk_dev_ops {\n+\tu32 (*get_dev_state)(struct mtk_md_dev *mdev);\n+\tvoid (*ack_dev_state)(struct mtk_md_dev *mdev, u32 state);\n+\tu32 (*get_dev_cfg)(struct mtk_md_dev *mdev);\n+\tint (*register_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt,\n+\t\t\t\tint (*evt_cb)(u32 status, void *data), void *data);\n+\tvoid (*unregister_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);\n+\tvoid (*mask_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);\n+\tvoid (*unmask_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);\n+\tvoid (*clear_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);\n+\tint (*send_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);\n+};\n+\n+/* mtk_md_dev defines the structure of MTK modem device */\n+struct mtk_md_dev {\n+\tstruct device *dev;\n+\tconst struct mtk_dev_ops *dev_ops;\n+\tvoid *hw_priv;\n+\tu32 hw_ver;\n+\tchar dev_str[MTK_DEV_STR_LEN];\n+\tstruct mtk_ctrl_blk *ctrl_blk;\n+\tstruct mtk_md_fsm *fsm;\n+};\n+\n+static inline u32 mtk_dev_get_dev_state(struct mtk_md_dev *mdev)\n+{\n+\treturn mdev-\u003edev_ops-\u003eget_dev_state(mdev);\n+}\n+\n+static inline void mtk_dev_ack_dev_state(struct mtk_md_dev *mdev, u32 state)\n+{\n+\treturn mdev-\u003edev_ops-\u003eack_dev_state(mdev, state);\n+}\n+\n+static inline u32 mtk_dev_get_dev_cfg(struct mtk_md_dev *mdev)\n+{\n+\treturn mdev-\u003edev_ops-\u003eget_dev_cfg(mdev);\n+}\n+\n+static inline int mtk_dev_register_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt,\n+\t\t\t\t\t   int (*evt_cb)(u32 status, void *data), void *data)\n+{\n+\treturn mdev-\u003edev_ops-\u003eregister_dev_evt(mdev, dev_evt, evt_cb, data);\n+}\n+\n+static inline void mtk_dev_unregister_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)\n+{\n+\tmdev-\u003edev_ops-\u003eunregister_dev_evt(mdev, dev_evt);\n+}\n+\n+static inline void mtk_dev_mask_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)\n+{\n+\tmdev-\u003edev_ops-\u003emask_dev_evt(mdev, dev_evt);\n+}\n+\n+static inline void mtk_dev_unmask_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)\n+{\n+\tmdev-\u003edev_ops-\u003eunmask_dev_evt(mdev, dev_evt);\n+}\n+\n+static inline void mtk_dev_clear_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)\n+{\n+\tmdev-\u003edev_ops-\u003eclear_dev_evt(mdev, dev_evt);\n+}\n+\n+static inline int mtk_dev_send_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)\n+{\n+\treturn mdev-\u003edev_ops-\u003esend_dev_evt(mdev, dev_evt);\n+}\n+\n+#endif /* __MTK_DEV_H__ */\ndiff --git a/drivers/net/wwan/t9xx/mtk_fsm.c b/drivers/net/wwan/t9xx/mtk_fsm.c\nnew file mode 100644\nindex 00000000000000..daefb7581ce0fd\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/mtk_fsm.c\n@@ -0,0 +1,946 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/kref.h\u003e\n+#include \u003clinux/kthread.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/pci.h\u003e\n+#include \u003clinux/sched/signal.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/wait.h\u003e\n+\n+#include \"mtk_fsm.h\"\n+#include \"mtk_port.h\"\n+#include \"mtk_port_io.h\"\n+#include \"mtk_utility.h\"\n+\n+#define EVT_TF_GATECLOSED (1)\n+#define MTK_FSM_INFO_LEN\t(64)\n+\n+#define FSM_HS_START_MASK\t(FSM_F_SAP_HS_START | FSM_F_MD_HS_START)\n+#define FSM_HS2_DONE_MASK\t(FSM_F_SAP_HS2_DONE | FSM_F_MD_HS2_DONE)\n+\n+#define RTFT_DATA_SIZE\t\t(3 * 1024)\n+#define EVT_HANDLER_TIMEOUT\t(HZ * 30)\n+#define BLOCKING_EVT_TIMEOUT\t(2 * EVT_HANDLER_TIMEOUT)\n+\n+#define REGION_BITMASK\t\t0xF\n+#define DEVICE_CFG_SHIFT\t24\n+#define DEVICE_CFG_REGION_MASK\t0x3\n+\n+enum device_stage {\n+\tDEV_STAGE_IDLE = 4,\n+\tDEV_STAGE_MAX\n+};\n+\n+enum device_cfg {\n+\tDEV_CFG_NORMAL = 0,\n+\tDEV_CFG_MD_ONLY,\n+};\n+\n+enum runtime_feature_support_type {\n+\tRTFT_TYPE_NOT_EXIST = 0,\n+\tRTFT_TYPE_NOT_SUPPORT = 1,\n+\tRTFT_TYPE_MUST_SUPPORT = 2,\n+\tRTFT_TYPE_OPTIONAL_SUPPORT = 3,\n+\tRTFT_TYPE_SUPPORT_BACKWARD_COMPAT = 4,\n+};\n+\n+enum query_runtime_feature_id {\n+\tQUERY_RTFT_ID_MD_PORT_ENUM = 0,\n+\tQUERY_RTFT_ID_SAP_PORT_ENUM = 1,\n+\tQUERY_RTFT_ID_MD_PORT_CFG = 2,\n+\tQUERY_RTFT_ID_MAX\n+};\n+\n+enum ctrl_msg_id {\n+\tCTRL_MSG_HS1 = 0,\n+\tCTRL_MSG_HS2 = 1,\n+\tCTRL_MSG_HS3 = 2,\n+};\n+\n+struct ctrl_msg_header {\n+\t__le32 id;\n+\t__le32 ex_msg;\n+\t__le32 data_len;\n+\tu8 reserved[];\n+} __packed;\n+\n+struct runtime_feature_entry {\n+\tu8 feature_id;\n+\tstruct runtime_feature_info support_info;\n+\tu8 reserved[2];\n+\t__le32 data_len;\n+\tu8 data[];\n+};\n+\n+struct feature_query {\n+\t__le32 head_pattern;\n+\tstruct runtime_feature_info ft_set[FEATURE_CNT];\n+\t__le32 tail_pattern;\n+};\n+\n+static int mtk_fsm_send_hs1_msg(struct fsm_hs_info *hs_info)\n+{\n+\tstruct ctrl_msg_header *ctrl_msg_h;\n+\tstruct feature_query *ft_query;\n+\tstruct sk_buff *skb;\n+\tint ret, msg_size;\n+\n+\tmsg_size = sizeof(*ctrl_msg_h) + sizeof(*ft_query);\n+\tskb = __dev_alloc_skb(msg_size, GFP_KERNEL);\n+\tif (!skb)\n+\t\treturn -ENOMEM;\n+\n+\tskb_put(skb, msg_size);\n+\tctrl_msg_h = (struct ctrl_msg_header *)skb-\u003edata;\n+\tctrl_msg_h-\u003eid = cpu_to_le32(CTRL_MSG_HS1);\n+\tctrl_msg_h-\u003eex_msg = 0;\n+\tctrl_msg_h-\u003edata_len = cpu_to_le32(sizeof(*ft_query));\n+\n+\tft_query = (struct feature_query *)(skb-\u003edata + sizeof(*ctrl_msg_h));\n+\tft_query-\u003ehead_pattern = cpu_to_le32(FEATURE_QUERY_PATTERN);\n+\tmemcpy(ft_query-\u003eft_set, hs_info-\u003equery_ft_set, sizeof(hs_info-\u003equery_ft_set));\n+\tft_query-\u003etail_pattern = cpu_to_le32(FEATURE_QUERY_PATTERN);\n+\n+\t/* send handshake1 message to device */\n+\tret = mtk_port_internal_write(hs_info-\u003ectrl_port, skb);\n+\tif (ret \u003c= 0)\n+\t\treturn ret;\n+\n+\treturn 0;\n+}\n+\n+static int mtk_fsm_feature_set_match(enum runtime_feature_support_type *cur_ft_spt,\n+\t\t\t\t     struct runtime_feature_info rtft_info_st,\n+\t\t\t\t     struct runtime_feature_info rtft_info_cfg)\n+{\n+\tint ret = 0;\n+\n+\tswitch (FIELD_GET(FEATURE_TYPE, rtft_info_st.feature)) {\n+\tcase RTFT_TYPE_NOT_EXIST:\n+\t\tfallthrough;\n+\tcase RTFT_TYPE_NOT_SUPPORT:\n+\t\t*cur_ft_spt = RTFT_TYPE_NOT_EXIST;\n+\t\tbreak;\n+\tcase RTFT_TYPE_MUST_SUPPORT:\n+\t\tif (FIELD_GET(FEATURE_TYPE, rtft_info_cfg.feature) == RTFT_TYPE_NOT_EXIST ||\n+\t\t    FIELD_GET(FEATURE_TYPE, rtft_info_cfg.feature) == RTFT_TYPE_NOT_SUPPORT)\n+\t\t\tret = -EPROTO;\n+\t\telse\n+\t\t\t*cur_ft_spt = RTFT_TYPE_MUST_SUPPORT;\n+\t\tbreak;\n+\tcase RTFT_TYPE_OPTIONAL_SUPPORT:\n+\t\tif (FIELD_GET(FEATURE_TYPE, rtft_info_cfg.feature) == RTFT_TYPE_NOT_EXIST ||\n+\t\t    FIELD_GET(FEATURE_TYPE, rtft_info_cfg.feature) == RTFT_TYPE_NOT_SUPPORT) {\n+\t\t\t*cur_ft_spt = RTFT_TYPE_NOT_SUPPORT;\n+\t\t} else {\n+\t\t\tif (FIELD_GET(FEATURE_VER, rtft_info_st.feature) ==\n+\t\t\t    FIELD_GET(FEATURE_VER, rtft_info_cfg.feature))\n+\t\t\t\t*cur_ft_spt = RTFT_TYPE_MUST_SUPPORT;\n+\t\t\telse\n+\t\t\t\t*cur_ft_spt = RTFT_TYPE_NOT_SUPPORT;\n+\t\t}\n+\t\tbreak;\n+\tcase RTFT_TYPE_SUPPORT_BACKWARD_COMPAT:\n+\t\tif (FIELD_GET(FEATURE_VER, rtft_info_st.feature) \u003e=\n+\t\t    FIELD_GET(FEATURE_VER, rtft_info_cfg.feature))\n+\t\t\t*cur_ft_spt = RTFT_TYPE_MUST_SUPPORT;\n+\t\telse\n+\t\t\t*cur_ft_spt = RTFT_TYPE_NOT_EXIST;\n+\t\tbreak;\n+\tdefault:\n+\t\tret = -EPROTO;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int (*query_rtft_action[FEATURE_CNT])(struct mtk_md_dev *mdev, void *rt_data) = {\n+\t[QUERY_RTFT_ID_MD_PORT_ENUM] = mtk_port_status_update,\n+\t[QUERY_RTFT_ID_SAP_PORT_ENUM] = mtk_port_status_update,\n+};\n+\n+static int mtk_fsm_parse_hs2_msg(struct fsm_hs_info *hs_info)\n+{\n+\tstruct mtk_md_fsm *fsm = container_of(hs_info, struct mtk_md_fsm, hs_info[hs_info-\u003eid]);\n+\tchar *rt_data = ((struct sk_buff *)hs_info-\u003ert_data)-\u003edata;\n+\tenum runtime_feature_support_type cur_ft_spt;\n+\tstruct runtime_feature_entry *rtft_entry;\n+\tunsigned int ft_id, offset, data_len;\n+\tint ret = 0;\n+\n+\toffset = sizeof(struct feature_query);\n+\tfor (ft_id = 0; ft_id \u003c FEATURE_CNT; ft_id++) {\n+\t\tif (offset + sizeof(*rtft_entry) \u003e hs_info-\u003ert_data_len)\n+\t\t\tbreak;\n+\n+\t\trtft_entry = (struct runtime_feature_entry *)(rt_data + offset);\n+\t\tret = mtk_fsm_feature_set_match(\u0026cur_ft_spt,\n+\t\t\t\t\t\trtft_entry-\u003esupport_info,\n+\t\t\t\t\t\ths_info-\u003equery_ft_set[ft_id]);\n+\t\tif (ret \u003c 0)\n+\t\t\tbreak;\n+\n+\t\tif (cur_ft_spt == RTFT_TYPE_MUST_SUPPORT)\n+\t\t\tif (query_rtft_action[ft_id])\n+\t\t\t\tret = query_rtft_action[ft_id](fsm-\u003emdev, rtft_entry-\u003edata);\n+\t\tif (ret \u003c 0)\n+\t\t\tbreak;\n+\n+\t\tdata_len = le32_to_cpu(rtft_entry-\u003edata_len);\n+\t\tif (data_len \u003e hs_info-\u003ert_data_len - offset - sizeof(*rtft_entry))\n+\t\t\tbreak;\n+\n+\t\toffset += sizeof(*rtft_entry) + data_len;\n+\t}\n+\n+\tif (ft_id != FEATURE_CNT) {\n+\t\tdev_err((fsm-\u003emdev)-\u003edev, \"Unable to handle mistake hs2 msg, ft_id=%d\\n\", ft_id);\n+\t\tret = -EPROTO;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int mtk_fsm_append_rtft_entries(struct mtk_md_dev *mdev, void *feature_data,\n+\t\t\t\t       unsigned int *len, struct fsm_hs_info *hs_info)\n+{\n+\tchar *rt_data = ((struct sk_buff *)hs_info-\u003ert_data)-\u003edata;\n+\tstruct runtime_feature_entry *rtft_entry;\n+\tint ft_id, ret = 0, rtdata_len = 0;\n+\tstruct feature_query *ft_query;\n+\n+\tft_query = (struct feature_query *)rt_data;\n+\tif (le32_to_cpu(ft_query-\u003ehead_pattern) != FEATURE_QUERY_PATTERN ||\n+\t    le32_to_cpu(ft_query-\u003etail_pattern) != FEATURE_QUERY_PATTERN) {\n+\t\tret = -EPROTO;\n+\t\tgoto hs_err;\n+\t}\n+\n+\t/* parse runtime feature query and fill runtime feature entry */\n+\trtft_entry = feature_data;\n+\tfor (ft_id = 0; ft_id \u003c FEATURE_CNT \u0026\u0026 rtdata_len \u003c RTFT_DATA_SIZE; ft_id++) {\n+\t\trtft_entry-\u003efeature_id = ft_id;\n+\t\trtft_entry-\u003edata_len = 0;\n+\n+\t\tswitch (FIELD_GET(FEATURE_TYPE, ft_query-\u003eft_set[ft_id].feature)) {\n+\t\tcase RTFT_TYPE_NOT_EXIST:\n+\t\t\tfallthrough;\n+\t\tcase RTFT_TYPE_NOT_SUPPORT:\n+\t\t\tfallthrough;\n+\t\tcase RTFT_TYPE_MUST_SUPPORT:\n+\t\t\trtft_entry-\u003esupport_info = ft_query-\u003eft_set[ft_id];\n+\t\t\tbreak;\n+\t\tcase RTFT_TYPE_OPTIONAL_SUPPORT:\n+\t\t\tfallthrough;\n+\t\tcase RTFT_TYPE_SUPPORT_BACKWARD_COMPAT:\n+\t\t\trtft_entry-\u003esupport_info.feature = FEATURE_TYPE_NOT;\n+\t\t\trtft_entry-\u003esupport_info.feature |= FEATURE_VER_0;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\trtdata_len += sizeof(*rtft_entry) + le32_to_cpu(rtft_entry-\u003edata_len);\n+\t\trtft_entry = (struct runtime_feature_entry *)(feature_data + rtdata_len);\n+\t}\n+\t*len = rtdata_len;\n+\treturn 0;\n+\n+hs_err:\n+\t*len = 0;\n+\treturn ret;\n+}\n+\n+static int mtk_fsm_send_hs3_msg(struct fsm_hs_info *hs_info)\n+{\n+\tstruct mtk_md_fsm *fsm = container_of(hs_info, struct mtk_md_fsm, hs_info[hs_info-\u003eid]);\n+\tunsigned int data_len, msg_size = 0;\n+\tstruct ctrl_msg_header *ctrl_msg_h;\n+\tstruct sk_buff *skb;\n+\tint ret;\n+\n+\tskb = __dev_alloc_skb(RTFT_DATA_SIZE, GFP_KERNEL);\n+\tif (!skb)\n+\t\treturn -ENOMEM;\n+\n+\tmsg_size += sizeof(*ctrl_msg_h);\n+\tctrl_msg_h = (struct ctrl_msg_header *)skb-\u003edata;\n+\tctrl_msg_h-\u003eid = cpu_to_le32(CTRL_MSG_HS3);\n+\tctrl_msg_h-\u003eex_msg = 0;\n+\tret = mtk_fsm_append_rtft_entries(fsm-\u003emdev,\n+\t\t\t\t\t  skb-\u003edata + sizeof(*ctrl_msg_h),\n+\t\t\t\t\t  \u0026data_len, hs_info);\n+\tif (ret) {\n+\t\tdev_kfree_skb(skb);\n+\t\treturn ret;\n+\t}\n+\n+\tctrl_msg_h-\u003edata_len = cpu_to_le32(data_len);\n+\tmsg_size += data_len;\n+\tskb_put(skb, msg_size);\n+\tret = mtk_port_internal_write(hs_info-\u003ectrl_port, skb);\n+\tif (ret \u003c= 0)\n+\t\treturn ret;\n+\n+\treturn 0;\n+}\n+\n+static int mtk_fsm_sap_ctrl_msg_handler(void *__fsm, struct sk_buff *skb)\n+{\n+\tstruct ctrl_msg_header *ctrl_msg_h;\n+\tstruct mtk_md_fsm *fsm = __fsm;\n+\tstruct fsm_hs_info *hs_info;\n+\tint ret;\n+\n+\tif (skb-\u003elen \u003c sizeof(*ctrl_msg_h)) {\n+\t\tdev_kfree_skb(skb);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tctrl_msg_h = (struct ctrl_msg_header *)skb-\u003edata;\n+\tskb_pull(skb, sizeof(*ctrl_msg_h));\n+\n+\ths_info = \u0026fsm-\u003ehs_info[HS_ID_SAP];\n+\tif (le32_to_cpu(ctrl_msg_h-\u003eid) != CTRL_MSG_HS2) {\n+\t\tdev_kfree_skb(skb);\n+\t\treturn -EPROTO;\n+\t}\n+\n+\ths_info-\u003ert_data = skb;\n+\ths_info-\u003ert_data_len = skb-\u003elen;\n+\tret = mtk_fsm_evt_submit(fsm-\u003emdev, FSM_EVT_STARTUP,\n+\t\t\t\t hs_info-\u003efsm_flag_hs2, hs_info, sizeof(*hs_info), 0);\n+\tif (ret == FSM_EVT_RET_FAIL)\n+\t\tdev_kfree_skb(skb);\n+\n+\treturn 0;\n+}\n+\n+static int mtk_fsm_md_ctrl_msg_handler(void *__fsm, struct sk_buff *skb)\n+{\n+\tstruct ctrl_msg_header *ctrl_msg_h;\n+\tstruct mtk_md_fsm *fsm = __fsm;\n+\tstruct fsm_hs_info *hs_info;\n+\tbool consumed_skb = false;\n+\tint ret;\n+\n+\tif (skb-\u003elen \u003c sizeof(*ctrl_msg_h)) {\n+\t\tdev_kfree_skb(skb);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tctrl_msg_h = (struct ctrl_msg_header *)skb-\u003edata;\n+\ths_info = \u0026fsm-\u003ehs_info[HS_ID_MD];\n+\tswitch (le32_to_cpu(ctrl_msg_h-\u003eid)) {\n+\tcase CTRL_MSG_HS2:\n+\t\tskb_pull(skb, sizeof(*ctrl_msg_h));\n+\t\ths_info-\u003ert_data = skb;\n+\t\ths_info-\u003ert_data_len = skb-\u003elen;\n+\t\tret = mtk_fsm_evt_submit(fsm-\u003emdev, FSM_EVT_STARTUP,\n+\t\t\t\t\t hs_info-\u003efsm_flag_hs2, hs_info, sizeof(*hs_info), 0);\n+\t\tif (ret != FSM_EVT_RET_FAIL)\n+\t\t\tconsumed_skb = true;\n+\t\tbreak;\n+\tdefault:\n+\t\tdev_err(fsm-\u003emdev-\u003edev, \"Invalid ctrl msg id\\n\");\n+\t}\n+\n+\tif (!consumed_skb)\n+\t\tdev_kfree_skb(skb);\n+\n+\treturn 0;\n+}\n+\n+static int (*ctrl_msg_handler[HS_ID_MAX])(void *__fsm, struct sk_buff *skb) = {\n+\t[HS_ID_MD] = mtk_fsm_md_ctrl_msg_handler,\n+\t[HS_ID_SAP] = mtk_fsm_sap_ctrl_msg_handler,\n+};\n+\n+static void mtk_fsm_idle_evt_handler(struct mtk_md_dev *mdev,\n+\t\t\t\t     u32 dev_state, struct mtk_md_fsm *fsm)\n+{\n+\tu32 dev_cfg = dev_state \u003e\u003e DEVICE_CFG_SHIFT \u0026 DEVICE_CFG_REGION_MASK;\n+\tint hs_id;\n+\n+\tif (dev_cfg == DEV_CFG_MD_ONLY)\n+\t\tfsm-\u003ehs_done_flag = FSM_F_MD_HS_START | FSM_F_MD_HS2_DONE;\n+\telse\n+\t\tfsm-\u003ehs_done_flag = FSM_HS_START_MASK | FSM_HS2_DONE_MASK;\n+\n+\tmtk_fsm_evt_submit(mdev, FSM_EVT_STARTUP, FSM_F_DFLT, NULL, 0, 0);\n+\n+\tfor (hs_id = 0; hs_id \u003c HS_ID_MAX; hs_id++)\n+\t\tmtk_dev_unmask_dev_evt(mdev, fsm-\u003ehs_info[hs_id].mhccif_ch);\n+}\n+\n+static int mtk_fsm_early_bootup_handler(u32 status, void *__fsm)\n+{\n+\tstruct mtk_md_fsm *fsm = __fsm;\n+\tstruct mtk_md_dev *mdev;\n+\tu32 dev_state, dev_stage;\n+\n+\tmdev = fsm-\u003emdev;\n+\tmtk_dev_mask_dev_evt(mdev, status);\n+\tmtk_dev_clear_dev_evt(mdev, status);\n+\n+\tdev_state = mtk_dev_get_dev_state(mdev);\n+\tdev_stage = dev_state \u0026 REGION_BITMASK;\n+\tif (dev_stage \u003e= DEV_STAGE_MAX) {\n+\t\tdev_err(mdev-\u003edev, \"Invalid dev state 0x%x\\n\", dev_state);\n+\t\treturn -ENXIO;\n+\t}\n+\n+\tif (dev_state == fsm-\u003elast_dev_state)\n+\t\tgoto exit;\n+\tfsm-\u003elast_dev_state = dev_state;\n+\n+\tif (dev_stage == DEV_STAGE_IDLE)\n+\t\tmtk_fsm_idle_evt_handler(mdev, dev_state, fsm);\n+\n+exit:\n+\treturn 0;\n+}\n+\n+static int mtk_fsm_ctrl_ch_start(struct mtk_md_fsm *fsm, struct fsm_hs_info *hs_info, int flag)\n+{\n+\tif (!hs_info-\u003ectrl_port) {\n+\t\ths_info-\u003ectrl_port = mtk_port_internal_open(fsm-\u003emdev, hs_info-\u003eport_name, flag);\n+\t\tif (!hs_info-\u003ectrl_port) {\n+\t\t\tdev_err(fsm-\u003emdev-\u003edev, \"Failed to open ctrl port(%s)\\n\",\n+\t\t\t\ths_info-\u003eport_name);\n+\t\t\treturn -ENODEV;\n+\t\t}\n+\n+\t\tmtk_port_internal_recv_register(hs_info-\u003ectrl_port,\n+\t\t\t\t\t\tctrl_msg_handler[hs_info-\u003eid], fsm);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void mtk_fsm_ctrl_ch_stop(struct mtk_md_fsm *fsm)\n+{\n+\tstruct fsm_hs_info *hs_info;\n+\tint hs_id;\n+\n+\tfor (hs_id = 0; hs_id \u003c HS_ID_MAX; hs_id++) {\n+\t\ths_info = \u0026fsm-\u003ehs_info[hs_id];\n+\t\tif (hs_info-\u003ectrl_port) {\n+\t\t\tmtk_port_internal_close(hs_info-\u003ectrl_port);\n+\t\t\ths_info-\u003ectrl_port = NULL;\n+\t\t}\n+\t}\n+}\n+\n+static void mtk_fsm_switch_state(struct mtk_md_fsm *fsm,\n+\t\t\t\t enum mtk_fsm_state to_state, struct mtk_fsm_evt *event)\n+{\n+\tchar fsm_info[MTK_FSM_INFO_LEN];\n+\tstruct mtk_fsm_notifier *nt;\n+\tstruct mtk_fsm_param param;\n+\n+\tparam.from = fsm-\u003estate;\n+\tparam.to = to_state;\n+\tparam.evt_id = event ? event-\u003eid : FSM_EVT_MAX;\n+\tparam.fsm_flag = event ? event-\u003efsm_flag : FSM_F_DFLT;\n+\n+\tlist_for_each_entry(nt, \u0026fsm-\u003epre_notifiers, entry)\n+\t\tnt-\u003ecb(\u0026param, nt-\u003edata);\n+\n+\tfsm-\u003estate = to_state;\n+\tfsm-\u003efsm_flag |= event ? event-\u003efsm_flag : FSM_F_DFLT;\n+\n+\tsnprintf(fsm_info, MTK_FSM_INFO_LEN,\n+\t\t \"state=%d, fsm_flag=0x%x\", to_state, fsm-\u003efsm_flag);\n+\tmtk_uevent_notify(fsm-\u003emdev-\u003edev, MTK_UEVENT_FSM, fsm_info);\n+\n+\tlist_for_each_entry(nt, \u0026fsm-\u003epost_notifiers, entry)\n+\t\tnt-\u003ecb(\u0026param, nt-\u003edata);\n+}\n+\n+static int mtk_fsm_startup_act(struct mtk_md_fsm *fsm, struct mtk_fsm_evt *event)\n+{\n+\tenum mtk_fsm_state to_state = FSM_STATE_BOOTUP;\n+\tstruct fsm_hs_info *hs_info = event-\u003edata;\n+\tstruct mtk_md_dev *mdev = fsm-\u003emdev;\n+\tint ret = 0;\n+\n+\tif (fsm-\u003estate != FSM_STATE_ON \u0026\u0026 fsm-\u003estate != FSM_STATE_BOOTUP) {\n+\t\tret = -EPROTO;\n+\t\tgoto free_rt_data;\n+\t}\n+\n+\tif (fsm-\u003estate != FSM_STATE_BOOTUP) {\n+\t\tmtk_fsm_switch_state(fsm, to_state, event);\n+\t\treturn 0;\n+\t}\n+\n+\tif (event-\u003efsm_flag \u0026 FSM_HS_START_MASK) {\n+\t\tmtk_fsm_switch_state(fsm, to_state, event);\n+\n+\t\tret = mtk_fsm_ctrl_ch_start(fsm, hs_info, O_NONBLOCK);\n+\t\tif (!ret)\n+\t\t\tret = mtk_fsm_send_hs1_msg(hs_info);\n+\t\tif (ret)\n+\t\t\tgoto hs_err;\n+\t} else if (event-\u003efsm_flag \u0026 FSM_HS2_DONE_MASK) {\n+\t\tret = mtk_fsm_parse_hs2_msg(hs_info);\n+\t\tif (!ret) {\n+\t\t\tmtk_fsm_switch_state(fsm, to_state, event);\n+\t\t\tret = mtk_fsm_send_hs3_msg(hs_info);\n+\t\t}\n+\t\tdev_kfree_skb(hs_info-\u003ert_data);\n+\t\ths_info-\u003ert_data = NULL;\n+\t\tif (ret)\n+\t\t\tgoto hs_err;\n+\t}\n+\n+\tif (((fsm-\u003efsm_flag | event-\u003efsm_flag) \u0026 fsm-\u003ehs_done_flag) == fsm-\u003ehs_done_flag) {\n+\t\tto_state = FSM_STATE_READY;\n+\t\tmtk_fsm_switch_state(fsm, to_state, NULL);\n+\t}\n+\n+\treturn 0;\n+\n+free_rt_data:\n+\tif (hs_info \u0026\u0026 hs_info-\u003ert_data) {\n+\t\tdev_kfree_skb(hs_info-\u003ert_data);\n+\t\ths_info-\u003ert_data = NULL;\n+\t}\n+hs_err:\n+\tdev_err(mdev-\u003edev, \"Failed to hs with device %d:0x%x, ret=%d\",\n+\t\tfsm-\u003estate, fsm-\u003efsm_flag, ret);\n+\treturn ret;\n+}\n+\n+static void mtk_fsm_evt_release(struct kref *kref)\n+{\n+\tstruct mtk_fsm_evt *event = container_of(kref, struct mtk_fsm_evt, kref);\n+\n+\tkfree(event);\n+}\n+\n+static void mtk_fsm_evt_put(struct mtk_fsm_evt *event)\n+{\n+\tkref_put(\u0026event-\u003ekref, mtk_fsm_evt_release);\n+}\n+\n+static void mtk_fsm_evt_finish(struct mtk_md_fsm *fsm,\n+\t\t\t       struct mtk_fsm_evt *event, int retval)\n+{\n+\tif (event-\u003emode \u0026 EVT_MODE_BLOCKING) {\n+\t\tevent-\u003estatus = retval;\n+\t\twake_up(\u0026fsm-\u003eevt_waitq);\n+\t}\n+\tmtk_fsm_evt_put(event);\n+}\n+\n+static void mtk_fsm_evt_cleanup(struct mtk_md_fsm *fsm, struct list_head *evtq)\n+{\n+\tstruct mtk_fsm_evt *event, *tmp;\n+\n+\tlist_for_each_entry_safe(event, tmp, evtq, entry) {\n+\t\tlist_del(\u0026event-\u003eentry);\n+\t\tmtk_fsm_evt_finish(fsm, event, FSM_EVT_RET_FAIL);\n+\t}\n+}\n+\n+static int mtk_fsm_enter_off_state(struct mtk_md_fsm *fsm, struct mtk_fsm_evt *event)\n+{\n+\tstruct mtk_md_dev *mdev = fsm-\u003emdev;\n+\tint hs_id;\n+\n+\tif (fsm-\u003estate == FSM_STATE_OFF || fsm-\u003estate == FSM_STATE_INVALID)\n+\t\treturn -EPROTO;\n+\n+\tmtk_dev_mask_dev_evt(mdev, DEV_EVT_D2H_BOOT_FLOW_SYNC);\n+\tfor (hs_id = 0; hs_id \u003c HS_ID_MAX; hs_id++)\n+\t\tmtk_dev_mask_dev_evt(mdev, fsm-\u003ehs_info[hs_id].mhccif_ch);\n+\n+\tmtk_fsm_ctrl_ch_stop(fsm);\n+\tmtk_fsm_switch_state(fsm, FSM_STATE_OFF, event);\n+\n+\treturn 0;\n+}\n+\n+static int mtk_fsm_dev_rm_act(struct mtk_md_fsm *fsm, struct mtk_fsm_evt *event)\n+{\n+\tunsigned long flags;\n+\n+\tspin_lock_irqsave(\u0026fsm-\u003eevtq_lock, flags);\n+\tset_bit(EVT_TF_GATECLOSED, \u0026fsm-\u003et_flag);\n+\tmtk_fsm_evt_cleanup(fsm, \u0026fsm-\u003eevtq);\n+\tspin_unlock_irqrestore(\u0026fsm-\u003eevtq_lock, flags);\n+\n+\treturn mtk_fsm_enter_off_state(fsm, event);\n+}\n+\n+static int mtk_fsm_hs1_handler(u32 status, void *__hs_info)\n+{\n+\tstruct fsm_hs_info *hs_info = __hs_info;\n+\tstruct mtk_md_dev *mdev;\n+\tstruct mtk_md_fsm *fsm;\n+\n+\tfsm = container_of(hs_info, struct mtk_md_fsm, hs_info[hs_info-\u003eid]);\n+\tmdev = fsm-\u003emdev;\n+\tmtk_fsm_evt_submit(mdev, FSM_EVT_STARTUP,\n+\t\t\t   hs_info-\u003efsm_flag_hs1, hs_info, sizeof(*hs_info), 0);\n+\tmtk_dev_mask_dev_evt(mdev, hs_info-\u003emhccif_ch);\n+\tmtk_dev_clear_dev_evt(mdev, hs_info-\u003emhccif_ch);\n+\n+\treturn 0;\n+}\n+\n+static void mtk_fsm_hs_info_init_by_hsid(struct mtk_md_fsm *fsm, int hs_id)\n+{\n+\tstruct fsm_hs_info *hs_info;\n+\n+\tif (hs_id \u003c 0 || hs_id \u003e= HS_ID_MAX) {\n+\t\tdev_warn((fsm-\u003emdev)-\u003edev, \"hs_id = %d, invalid.\\n\", hs_id);\n+\t\treturn;\n+\t}\n+\n+\ths_info = \u0026fsm-\u003ehs_info[hs_id];\n+\ths_info-\u003eid = hs_id;\n+\ths_info-\u003ectrl_port = NULL;\n+\ths_info-\u003ert_data = NULL;\n+\tswitch (hs_id) {\n+\tcase HS_ID_MD:\n+\t\tsnprintf(hs_info-\u003eport_name, PORT_NAME_LEN, \"MDCTRL\");\n+\t\ths_info-\u003emhccif_ch = DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD;\n+\t\ths_info-\u003efsm_flag_hs1 = FSM_F_MD_HS_START;\n+\t\ths_info-\u003efsm_flag_hs2 = FSM_F_MD_HS2_DONE;\n+\t\ths_info-\u003equery_ft_set[QUERY_RTFT_ID_MD_PORT_ENUM].feature =\n+\t\t\tFIELD_PREP(FEATURE_TYPE, RTFT_TYPE_MUST_SUPPORT);\n+\t\ths_info-\u003equery_ft_set[QUERY_RTFT_ID_MD_PORT_ENUM].feature |=\n+\t\t\tFIELD_PREP(FEATURE_VER, 0);\n+\t\ths_info-\u003equery_ft_set[QUERY_RTFT_ID_MD_PORT_CFG].feature =\n+\t\t\tFIELD_PREP(FEATURE_TYPE, RTFT_TYPE_NOT_SUPPORT);\n+\t\tbreak;\n+\tcase HS_ID_SAP:\n+\t\tsnprintf(hs_info-\u003eport_name, PORT_NAME_LEN, \"SAPCTRL\");\n+\t\ths_info-\u003emhccif_ch = DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP;\n+\t\ths_info-\u003efsm_flag_hs1 = FSM_F_SAP_HS_START;\n+\t\ths_info-\u003efsm_flag_hs2 = FSM_F_SAP_HS2_DONE;\n+\t\ths_info-\u003equery_ft_set[QUERY_RTFT_ID_SAP_PORT_ENUM].feature =\n+\t\t\tFIELD_PREP(FEATURE_TYPE, RTFT_TYPE_MUST_SUPPORT);\n+\t\ths_info-\u003equery_ft_set[QUERY_RTFT_ID_SAP_PORT_ENUM].feature |=\n+\t\t\tFIELD_PREP(FEATURE_VER, 0);\n+\t\tbreak;\n+\t}\n+}\n+\n+static void mtk_fsm_hs_info_init(struct mtk_md_fsm *fsm)\n+{\n+\tstruct mtk_md_dev *mdev = fsm-\u003emdev;\n+\tstruct fsm_hs_info *hs_info;\n+\tint hs_id;\n+\n+\tfor (hs_id = 0; hs_id \u003c HS_ID_MAX; hs_id++) {\n+\t\tmtk_fsm_hs_info_init_by_hsid(fsm, hs_id);\n+\t\ths_info = \u0026fsm-\u003ehs_info[hs_id];\n+\t\tmtk_dev_register_dev_evt(mdev, hs_info-\u003emhccif_ch,\n+\t\t\t\t\t mtk_fsm_hs1_handler, hs_info);\n+\t}\n+}\n+\n+static void mtk_fsm_hs_info_exit(struct mtk_md_fsm *fsm)\n+{\n+\tstruct mtk_md_dev *mdev = fsm-\u003emdev;\n+\tstruct fsm_hs_info *hs_info;\n+\tint hs_id;\n+\n+\tfor (hs_id = 0; hs_id \u003c HS_ID_MAX; hs_id++) {\n+\t\ths_info = \u0026fsm-\u003ehs_info[hs_id];\n+\t\tmtk_dev_unregister_dev_evt(mdev, hs_info-\u003emhccif_ch);\n+\t}\n+}\n+\n+static int mtk_fsm_dev_add_act(struct mtk_md_fsm *fsm, struct mtk_fsm_evt *event)\n+{\n+\tif (fsm-\u003estate != FSM_STATE_OFF \u0026\u0026 fsm-\u003estate != FSM_STATE_INVALID)\n+\t\treturn -EPROTO;\n+\n+\tmtk_fsm_switch_state(fsm, FSM_STATE_ON, event);\n+\tmtk_dev_unmask_dev_evt(fsm-\u003emdev, DEV_EVT_D2H_BOOT_FLOW_SYNC);\n+\n+\treturn 0;\n+}\n+\n+static int (*evts_act_tbl[FSM_EVT_MAX])(struct mtk_md_fsm *__fsm, struct mtk_fsm_evt *event) = {\n+\t[FSM_EVT_STARTUP] = mtk_fsm_startup_act,\n+\t[FSM_EVT_DEV_RM] = mtk_fsm_dev_rm_act,\n+\t[FSM_EVT_DEV_ADD] = mtk_fsm_dev_add_act,\n+};\n+\n+int mtk_fsm_start(struct mtk_md_dev *mdev)\n+{\n+\tstruct mtk_md_fsm *fsm = mdev-\u003efsm;\n+\n+\tif (!fsm)\n+\t\treturn -EINVAL;\n+\n+\tif (!fsm-\u003efsm_handler)\n+\t\treturn -EFAULT;\n+\n+\twake_up_process(fsm-\u003efsm_handler);\n+\treturn 0;\n+}\n+EXPORT_SYMBOL(mtk_fsm_start);\n+\n+static void mkt_fsm_notifier_cleanup(struct mtk_md_dev *mdev, struct list_head *ntq)\n+{\n+\tstruct mtk_fsm_notifier *nt, *tmp;\n+\n+\tlist_for_each_entry_safe(nt, tmp, ntq, entry) {\n+\t\tlist_del(\u0026nt-\u003eentry);\n+\t\tdev_warn(mdev-\u003edev, \"Having to free notifier(%d) by FSM!\\n\", nt-\u003eid);\n+\t\tkfree(nt);\n+\t}\n+}\n+\n+static void mtk_fsm_notifier_insert(struct mtk_fsm_notifier *notifier, struct list_head *head)\n+{\n+\tstruct mtk_fsm_notifier *nt;\n+\n+\tlist_for_each_entry(nt, head, entry) {\n+\t\tif (notifier-\u003eprio \u003e nt-\u003eprio) {\n+\t\t\tlist_add(\u0026notifier-\u003eentry, nt-\u003eentry.prev);\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\tlist_add_tail(\u0026notifier-\u003eentry, head);\n+}\n+\n+int mtk_fsm_notifier_register(struct mtk_md_dev *mdev, enum mtk_user_id id,\n+\t\t\t      void (*cb)(struct mtk_fsm_param *, void *data),\n+\t\t\t      void *data, enum mtk_fsm_prio prio, bool is_pre)\n+{\n+\tstruct mtk_md_fsm *fsm = mdev-\u003efsm;\n+\tstruct mtk_fsm_notifier *notifier;\n+\n+\tif (!fsm)\n+\t\treturn -EINVAL;\n+\n+\tif (id \u003e= MTK_USER_MAX || !cb || prio \u003e= FSM_PRIO_MAX)\n+\t\treturn -EINVAL;\n+\n+\tnotifier = kzalloc_obj(*notifier);\n+\tif (!notifier)\n+\t\treturn -ENOMEM;\n+\n+\tINIT_LIST_HEAD(\u0026notifier-\u003eentry);\n+\tnotifier-\u003eid = id;\n+\tnotifier-\u003ecb = cb;\n+\tnotifier-\u003edata = data;\n+\tnotifier-\u003eprio = prio;\n+\n+\tif (is_pre)\n+\t\tmtk_fsm_notifier_insert(notifier, \u0026fsm-\u003epre_notifiers);\n+\telse\n+\t\tmtk_fsm_notifier_insert(notifier, \u0026fsm-\u003epost_notifiers);\n+\n+\treturn 0;\n+}\n+EXPORT_SYMBOL(mtk_fsm_notifier_register);\n+\n+int mtk_fsm_notifier_unregister(struct mtk_md_dev *mdev, enum mtk_user_id id)\n+{\n+\tstruct mtk_md_fsm *fsm = mdev-\u003efsm;\n+\tstruct mtk_fsm_notifier *nt, *tmp;\n+\n+\tif (!fsm)\n+\t\treturn -EINVAL;\n+\n+\tlist_for_each_entry_safe(nt, tmp, \u0026fsm-\u003epre_notifiers, entry) {\n+\t\tif (nt-\u003eid == id) {\n+\t\t\tlist_del(\u0026nt-\u003eentry);\n+\t\t\tkfree(nt);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tlist_for_each_entry_safe(nt, tmp, \u0026fsm-\u003epost_notifiers, entry) {\n+\t\tif (nt-\u003eid == id) {\n+\t\t\tlist_del(\u0026nt-\u003eentry);\n+\t\t\tkfree(nt);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+EXPORT_SYMBOL(mtk_fsm_notifier_unregister);\n+\n+int mtk_fsm_evt_submit(struct mtk_md_dev *mdev,\n+\t\t       enum mtk_fsm_evt_id id, enum mtk_fsm_flag flag,\n+\t\t       void *data, unsigned int len, unsigned char mode)\n+{\n+\tstruct mtk_md_fsm *fsm = mdev-\u003efsm;\n+\tstruct mtk_fsm_evt *event;\n+\tunsigned long flags;\n+\tint ret = 0;\n+\n+\tif (!fsm || id \u003e= FSM_EVT_MAX) {\n+\t\tdev_err(mdev-\u003edev, \"Invalid param!\\n\");\n+\t\treturn FSM_EVT_RET_FAIL;\n+\t}\n+\n+\tif (test_bit(EVT_TF_GATECLOSED, \u0026fsm-\u003et_flag)) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to submit evt, fsm has been removed!\\n\");\n+\t\treturn FSM_EVT_RET_FAIL;\n+\t}\n+\n+\tevent = kzalloc(sizeof(*event),\n+\t\t\t(in_hardirq() || in_softirq() || irqs_disabled()) ?\n+\t\t\tGFP_ATOMIC : GFP_KERNEL);\n+\tif (!event)\n+\t\treturn FSM_EVT_RET_FAIL;\n+\n+\tkref_init(\u0026event-\u003ekref);\n+\tevent-\u003emdev = mdev;\n+\tevent-\u003eid = id;\n+\tevent-\u003efsm_flag = flag;\n+\tevent-\u003estatus = FSM_EVT_RET_ONGOING;\n+\tevent-\u003edata = data;\n+\tevent-\u003elen = len;\n+\tevent-\u003emode = mode;\n+\n+\tspin_lock_irqsave(\u0026fsm-\u003eevtq_lock, flags);\n+\tif (test_bit(EVT_TF_GATECLOSED, \u0026fsm-\u003et_flag)) {\n+\t\tspin_unlock_irqrestore(\u0026fsm-\u003eevtq_lock, flags);\n+\t\tmtk_fsm_evt_put(event);\n+\t\tdev_err(mdev-\u003edev, \"Failed to add event, fsm dev has been removed!\\n\");\n+\t\treturn FSM_EVT_RET_FAIL;\n+\t}\n+\n+\tkref_get(\u0026event-\u003ekref);\n+\tif (mode \u0026 EVT_MODE_TOHEAD)\n+\t\tlist_add(\u0026event-\u003eentry, \u0026fsm-\u003eevtq);\n+\telse\n+\t\tlist_add_tail(\u0026event-\u003eentry, \u0026fsm-\u003eevtq);\n+\tspin_unlock_irqrestore(\u0026fsm-\u003eevtq_lock, flags);\n+\n+\twake_up_process(fsm-\u003efsm_handler);\n+\tif (mode \u0026 EVT_MODE_BLOCKING) {\n+\t\tret = wait_event_timeout(fsm-\u003eevt_waitq,\n+\t\t\t\t\t (event-\u003estatus != 0), BLOCKING_EVT_TIMEOUT);\n+\t\tif (!ret \u0026\u0026 event-\u003estatus != FSM_EVT_RET_DONE) {\n+\t\t\tdev_err(mdev-\u003edev, \"Handling fsm blocking event timeout!\\n\");\n+\t\t\tret = -ETIMEDOUT;\n+\t\t} else {\n+\t\t\tret = event-\u003estatus;\n+\t\t}\n+\t}\n+\tmtk_fsm_evt_put(event);\n+\n+\treturn ret;\n+}\n+EXPORT_SYMBOL(mtk_fsm_evt_submit);\n+\n+static int mtk_fsm_evt_handler(void *__fsm)\n+{\n+\tstruct mtk_md_fsm *fsm = __fsm;\n+\tstruct mtk_fsm_evt *event;\n+\tunsigned long flags;\n+\tint ret;\n+\n+wake_up:\n+\tset_current_state(TASK_UNINTERRUPTIBLE);\n+\twhile (!kthread_should_stop() \u0026\u0026 !list_empty(\u0026fsm-\u003eevtq)) {\n+\t\tset_current_state(TASK_RUNNING);\n+\t\tspin_lock_irqsave(\u0026fsm-\u003eevtq_lock, flags);\n+\t\tevent = list_first_entry(\u0026fsm-\u003eevtq, struct mtk_fsm_evt, entry);\n+\t\tlist_del(\u0026event-\u003eentry);\n+\t\tspin_unlock_irqrestore(\u0026fsm-\u003eevtq_lock, flags);\n+\n+\t\tif (event-\u003eid \u003c FSM_EVT_MAX) {\n+\t\t\tret = evts_act_tbl[event-\u003eid](fsm, event);\n+\t\t\tif (ret) {\n+\t\t\t\tdev_err((fsm-\u003emdev)-\u003edev,\n+\t\t\t\t\t\"Failed to handle evt, fsm state = %d, ret = %d\\n\",\n+\t\t\t\t\tfsm-\u003estate, ret);\n+\t\t\t\tmtk_fsm_evt_finish(fsm, event, FSM_EVT_RET_FAIL);\n+\t\t\t} else {\n+\t\t\t\tmtk_fsm_evt_finish(fsm, event, FSM_EVT_RET_DONE);\n+\t\t\t}\n+\t\t} else {\n+\t\t\tmtk_fsm_evt_finish(fsm, event, FSM_EVT_RET_DONE);\n+\t\t}\n+\t}\n+\n+\tif (kthread_should_stop()) {\n+\t\tset_current_state(TASK_RUNNING);\n+\t\treturn 0;\n+\t}\n+\n+\tschedule();\n+\tgoto wake_up;\n+}\n+\n+int mtk_fsm_init(struct mtk_md_dev *mdev)\n+{\n+\tstruct mtk_md_fsm *fsm;\n+\tint ret;\n+\n+\tfsm = devm_kzalloc(mdev-\u003edev, sizeof(*fsm), GFP_KERNEL);\n+\tif (!fsm)\n+\t\treturn -ENOMEM;\n+\n+\tfsm-\u003efsm_handler = kthread_create(mtk_fsm_evt_handler, fsm, \"fsm_evt_thread%d_%s\",\n+\t\t\t\t\t  mdev-\u003ehw_ver, mdev-\u003edev_str);\n+\tif (IS_ERR(fsm-\u003efsm_handler)) {\n+\t\tret = PTR_ERR(fsm-\u003efsm_handler);\n+\t\tgoto exit;\n+\t}\n+\n+\tfsm-\u003emdev = mdev;\n+\tfsm-\u003estate = FSM_STATE_INVALID;\n+\tfsm-\u003efsm_flag = FSM_F_DFLT;\n+\n+\tINIT_LIST_HEAD(\u0026fsm-\u003eevtq);\n+\tspin_lock_init(\u0026fsm-\u003eevtq_lock);\n+\tinit_waitqueue_head(\u0026fsm-\u003eevt_waitq);\n+\n+\tINIT_LIST_HEAD(\u0026fsm-\u003epre_notifiers);\n+\tINIT_LIST_HEAD(\u0026fsm-\u003epost_notifiers);\n+\n+\tmtk_fsm_hs_info_init(fsm);\n+\tmtk_dev_register_dev_evt(mdev, DEV_EVT_D2H_BOOT_FLOW_SYNC,\n+\t\t\t\t mtk_fsm_early_bootup_handler, fsm);\n+\tmdev-\u003efsm = fsm;\n+\treturn 0;\n+exit:\n+\treturn ret;\n+}\n+EXPORT_SYMBOL(mtk_fsm_init);\n+\n+int mtk_fsm_exit(struct mtk_md_dev *mdev)\n+{\n+\tstruct mtk_md_fsm *fsm = mdev-\u003efsm;\n+\tunsigned long flags;\n+\n+\tif (!fsm)\n+\t\treturn -EINVAL;\n+\n+\tif (fsm-\u003efsm_handler) {\n+\t\tkthread_stop(fsm-\u003efsm_handler);\n+\t\tfsm-\u003efsm_handler = NULL;\n+\t}\n+\n+\tspin_lock_irqsave(\u0026fsm-\u003eevtq_lock, flags);\n+\tif (WARN_ON(!list_empty(\u0026fsm-\u003eevtq)))\n+\t\tmtk_fsm_evt_cleanup(fsm, \u0026fsm-\u003eevtq);\n+\tspin_unlock_irqrestore(\u0026fsm-\u003eevtq_lock, flags);\n+\n+\tmkt_fsm_notifier_cleanup(mdev, \u0026fsm-\u003epre_notifiers);\n+\tmkt_fsm_notifier_cleanup(mdev, \u0026fsm-\u003epost_notifiers);\n+\n+\tmtk_dev_unregister_dev_evt(mdev, DEV_EVT_D2H_BOOT_FLOW_SYNC);\n+\tmtk_fsm_hs_info_exit(fsm);\n+\n+\treturn 0;\n+}\n+EXPORT_SYMBOL(mtk_fsm_exit);\ndiff --git a/drivers/net/wwan/t9xx/mtk_fsm.h b/drivers/net/wwan/t9xx/mtk_fsm.h\nnew file mode 100644\nindex 00000000000000..f2fc66bcef614a\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/mtk_fsm.h\n@@ -0,0 +1,140 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#ifndef __MTK_FSM_H__\n+#define __MTK_FSM_H__\n+\n+#include \"mtk_dev.h\"\n+\n+#define FEATURE_CNT\t\t(64)\n+#define FEATURE_QUERY_PATTERN\t(0x49434343)\n+\n+#define FEATURE_TYPE\t\tGENMASK(3, 0)\n+#define FEATURE_VER\t\tGENMASK(7, 4)\n+\n+#define FEATURE_TYPE_NOT\tFIELD_PREP(FEATURE_TYPE, RTFT_TYPE_NOT_SUPPORT)\n+#define FEATURE_TYPE_MUST\tFIELD_PREP(FEATURE_TYPE, RTFT_TYPE_MUST_SUPPORT)\n+#define FEATURE_TYPE_OPTIONAL\tFIELD_PREP(FEATURE_TYPE, RTFT_TYPE_OPTIONAL_SUPPORT)\n+#define FEATURE_VER_0\t\tFIELD_PREP(FEATURE_VER, 0)\n+\n+#define EVT_MODE_BLOCKING\t(0x01)\n+#define EVT_MODE_TOHEAD\t\t(0x02)\n+\n+#define FSM_EVT_RET_FAIL\t(-1)\n+#define FSM_EVT_RET_ONGOING\t(0)\n+#define FSM_EVT_RET_DONE\t(1)\n+\n+enum mtk_fsm_flag {\n+\tFSM_F_DFLT = 0,\n+\tFSM_F_SAP_HS_START\t= BIT(0),\n+\tFSM_F_SAP_HS2_DONE\t= BIT(1),\n+\tFSM_F_MD_HS_START\t= BIT(2),\n+\tFSM_F_MD_HS2_DONE\t= BIT(3),\n+};\n+\n+enum mtk_fsm_state {\n+\tFSM_STATE_INVALID = 0,\n+\tFSM_STATE_OFF,\n+\tFSM_STATE_ON,\n+\tFSM_STATE_BOOTUP,\n+\tFSM_STATE_READY,\n+};\n+\n+enum mtk_fsm_evt_id {\n+\tFSM_EVT_STARTUP = 0,\n+\tFSM_EVT_DEV_RM,\n+\tFSM_EVT_DEV_ADD,\n+\tFSM_EVT_MAX\n+};\n+\n+enum mtk_fsm_prio {\n+\tFSM_PRIO_0 = 0,\n+\tFSM_PRIO_1 = 1,\n+\tFSM_PRIO_MAX\n+};\n+\n+struct mtk_fsm_param {\n+\tenum mtk_fsm_state from;\n+\tenum mtk_fsm_state to;\n+\tenum mtk_fsm_evt_id evt_id;\n+\tenum mtk_fsm_flag fsm_flag;\n+};\n+\n+#define PORT_NAME_LEN 20\n+\n+enum handshake_info_id {\n+\tHS_ID_MD = 0,\n+\tHS_ID_SAP,\n+\tHS_ID_MAX\n+};\n+\n+struct runtime_feature_info {\n+\tu8 feature;\n+};\n+\n+struct fsm_hs_info {\n+\tunsigned char id;\n+\tvoid *ctrl_port;\n+\tchar port_name[PORT_NAME_LEN];\n+\tunsigned int mhccif_ch;\n+\tunsigned int fsm_flag_hs1;\n+\tunsigned int fsm_flag_hs2;\n+\t/* the feature that the device should support */\n+\tstruct runtime_feature_info query_ft_set[FEATURE_CNT];\n+\t/* runtime data from device need to be parsed by host */\n+\tvoid *rt_data;\n+\tunsigned int rt_data_len;\n+};\n+\n+struct mtk_md_fsm {\n+\tstruct mtk_md_dev *mdev;\n+\tstruct task_struct *fsm_handler;\n+\tstruct fsm_hs_info hs_info[HS_ID_MAX];\n+\tunsigned int hs_done_flag;\n+\tunsigned long t_flag;\n+\tu32 last_dev_state;\n+\tenum mtk_fsm_state state;\n+\tunsigned int fsm_flag;\n+\tstruct list_head evtq;\n+\t/* protect evtq */\n+\tspinlock_t evtq_lock;\n+\t/* waitq for fsm blocking submit */\n+\twait_queue_head_t evt_waitq;\n+\tstruct list_head pre_notifiers;\n+\tstruct list_head post_notifiers;\n+};\n+\n+struct mtk_fsm_evt {\n+\tstruct list_head entry;\n+\tstruct kref kref;\n+\tstruct mtk_md_dev *mdev;\n+\tenum mtk_fsm_evt_id id;\n+\tunsigned int fsm_flag;\n+\tint status;\n+\tunsigned char mode;\n+\tunsigned int len;\n+\tvoid *data;\n+};\n+\n+struct mtk_fsm_notifier {\n+\tstruct list_head entry;\n+\tenum mtk_user_id id;\n+\tvoid (*cb)(struct mtk_fsm_param *param, void *data);\n+\tvoid *data;\n+\tenum mtk_fsm_prio prio;\n+};\n+\n+int mtk_fsm_init(struct mtk_md_dev *mdev);\n+int mtk_fsm_exit(struct mtk_md_dev *mdev);\n+int mtk_fsm_start(struct mtk_md_dev *mdev);\n+int mtk_fsm_notifier_register(struct mtk_md_dev *mdev, enum mtk_user_id id,\n+\t\t\t      void (*cb)(struct mtk_fsm_param *, void *data),\n+\t\t\t      void *data, enum mtk_fsm_prio prio, bool is_pre);\n+int mtk_fsm_notifier_unregister(struct mtk_md_dev *mdev, enum mtk_user_id id);\n+int mtk_fsm_evt_submit(struct mtk_md_dev *mdev,\n+\t\t       enum mtk_fsm_evt_id id, enum mtk_fsm_flag flag,\n+\t\t       void *data, unsigned int len, unsigned char mode);\n+\n+#endif /* __MTK_FSM_H__ */\ndiff --git a/drivers/net/wwan/t9xx/mtk_port.c b/drivers/net/wwan/t9xx/mtk_port.c\nnew file mode 100644\nindex 00000000000000..0f37137f127a61\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/mtk_port.c\n@@ -0,0 +1,966 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/err.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/wait.h\u003e\n+\n+#include \"mtk_port.h\"\n+#include \"mtk_port_io.h\"\n+\n+#define MTK_DFLT_TRB_TIMEOUT\t\t(5 * HZ)\n+#define MTK_DFLT_TRB_STATUS\t\t(0x1)\n+#define MTK_TRB_HEADER_ADDED\t\t(0xADDED)\n+#define MTK_CHECK_RX_SEQ_MASK\t\t(0x7fff)\n+\n+#define MTK_PORT_ENUM_VER\t\t(0)\n+#define MTK_PORT_ENUM_HEAD_PATTERN\t(0x5a5a5a5a)\n+#define MTK_PORT_ENUM_TAIL_PATTERN\t(0xa5a5a5a5)\n+\n+#define MTK_PORT_SEARCH_FROM_RADIX_TREE(p, s) ({\\\n+\tstruct mtk_port *_p;\t\t\t\\\n+\t_p = rcu_dereference_raw(*(s));\t\t\\\n+\tif (!_p)\t\t\t\t\\\n+\t\tcontinue;\t\t\t\\\n+\tp = _p;\t\t\t\t\t\\\n+})\n+\n+#define MTK_PORT_INTERNAL_NODE_CHECK(p, s, i) ({\\\n+\tif (radix_tree_is_internal_node(p)) {\t\\\n+\t\ts = radix_tree_iter_retry(\u0026(i));\\\n+\t\tcontinue;\t\t\t\\\n+\t}\t\t\t\t\t\\\n+})\n+\n+struct mtk_port_info {\n+\t__le16 channel;\n+\t__le16 reserved;\n+} __packed;\n+\n+struct mtk_port_enum_msg {\n+\t__le32 head_pattern;\n+\t__le16 port_cnt;\n+\t__le16 version;\n+\t__le32 tail_pattern;\n+\tu8 data[];\n+} __packed;\n+\n+/* global group for stale ports */\n+static LIST_HEAD(stale_list_grp);\n+/* mutex lock for stale_list_group */\n+DEFINE_MUTEX(port_mngr_grp_mtx);\n+\n+static DEFINE_IDA(ccci_dev_ids);\n+\n+/* This function working always under mutex lock port_mngr_grp_mtx */\n+void mtk_port_release(struct kref *port_kref)\n+{\n+\tstruct mtk_stale_list *s_list;\n+\tstruct mtk_port *port;\n+\n+\tport = container_of(port_kref, struct mtk_port, kref);\n+\tif (!test_bit(PORT_S_ON_STALE_LIST, \u0026port-\u003estatus))\n+\t\tgoto port_exit;\n+\n+\tlist_del(\u0026port-\u003estale_entry);\n+\tlist_for_each_entry(s_list, \u0026stale_list_grp, entry) {\n+\t\tif (!strncmp(s_list-\u003edev_str, port-\u003edev_str, MTK_DEV_STR_LEN) \u0026\u0026\n+\t\t    list_empty(\u0026s_list-\u003eports) \u0026\u0026 s_list-\u003edev_id \u003e= 0) {\n+\t\t\tida_free(\u0026ccci_dev_ids, s_list-\u003edev_id);\n+\t\t\ts_list-\u003edev_id = -1;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+port_exit:\n+\tports_ops[port-\u003einfo.type]-\u003eexit(port);\n+\tkfree(port);\n+}\n+\n+static int mtk_port_tbl_add(struct mtk_port_mngr *port_mngr, struct mtk_port *port)\n+{\n+\tint ret;\n+\n+\tret = radix_tree_insert(\u0026port_mngr-\u003eport_tbl[MTK_PORT_TBL_TYPE(port-\u003einfo.rx_ch)],\n+\t\t\t\tport-\u003einfo.rx_ch \u0026 0xFFF, port);\n+\tif (ret)\n+\t\tdev_err(port_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t\"port(%s) add to port_tbl failed, return %d\\n\",\n+\t\t\tport-\u003einfo.name, ret);\n+\telse\n+\t\tport_mngr-\u003eport_cnt++;\n+\n+\treturn ret;\n+}\n+\n+static void mtk_port_tbl_del(struct mtk_port_mngr *port_mngr, struct mtk_port *port)\n+{\n+\tradix_tree_delete(\u0026port_mngr-\u003eport_tbl[MTK_PORT_TBL_TYPE(port-\u003einfo.rx_ch)],\n+\t\t\t  port-\u003einfo.rx_ch \u0026 0xFFF);\n+\tport_mngr-\u003eport_cnt--;\n+}\n+\n+static struct mtk_port *mtk_port_restore_from_stale_list(struct mtk_port_mngr *port_mngr,\n+\t\t\t\t\t\t\t struct mtk_stale_list *s_list)\n+{\n+\tstruct mtk_port *port, *next_port;\n+\tint ret;\n+\n+\tmutex_lock(\u0026port_mngr_grp_mtx);\n+\tlist_for_each_entry_safe(port, next_port, \u0026s_list-\u003eports, stale_entry) {\n+\t\tkref_get(\u0026port-\u003ekref);\n+\t\tlist_del(\u0026port-\u003estale_entry);\n+\t\tret = mtk_port_tbl_add(port_mngr, port);\n+\t\tif (ret) {\n+\t\t\tlist_add_tail(\u0026port-\u003estale_entry, \u0026s_list-\u003eports);\n+\t\t\tkref_put(\u0026port-\u003ekref, mtk_port_release);\n+\t\t\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+\t\t\tdev_err(port_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t\t\"Failed when adding (%s) to port mngr\\n\",\n+\t\t\t\tport-\u003einfo.name);\n+\t\t\treturn ERR_PTR(ret);\n+\t\t}\n+\n+\t\tport-\u003eport_mngr = port_mngr;\n+\t\tclear_bit(PORT_S_ON_STALE_LIST, \u0026port-\u003estatus);\n+\t\tports_ops[port-\u003einfo.type]-\u003ereset(port);\n+\t}\n+\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+\n+\treturn NULL;\n+}\n+\n+static struct mtk_port *mtk_port_alloc_and_add(struct mtk_port_mngr *port_mngr,\n+\t\t\t\t\t       struct mtk_port_cfg *dflt_info)\n+{\n+\tstruct mtk_port *port;\n+\tint ret;\n+\n+\tport = kzalloc_obj(*port, GFP_KERNEL);\n+\tif (!port) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_alloc_port;\n+\t}\n+\tmemcpy(\u0026port-\u003einfo, dflt_info, sizeof(*dflt_info));\n+\n+\tret = mtk_port_tbl_add(port_mngr, port);\n+\tif (ret \u003c 0) {\n+\t\tdev_err(port_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t\"Failed to add port(%s) to port tbl\\n\", dflt_info-\u003ename);\n+\t\tgoto err_free_port;\n+\t}\n+\n+\tport-\u003eport_mngr = port_mngr;\n+\tret = ports_ops[port-\u003einfo.type]-\u003einit(port);\n+\tif (ret \u003c 0) {\n+\t\tmtk_port_tbl_del(port_mngr, port);\n+\t\tgoto err_free_port;\n+\t}\n+\n+\tmemcpy(port-\u003edev_str, port_mngr-\u003ectrl_blk-\u003emdev-\u003edev_str, MTK_DEV_STR_LEN);\n+\treturn port;\n+\n+err_free_port:\n+\tkfree(port);\n+err_alloc_port:\n+\treturn ERR_PTR(ret);\n+}\n+\n+static void mtk_port_free_or_backup(struct mtk_port_mngr *port_mngr,\n+\t\t\t\t    struct mtk_port *port, struct mtk_stale_list *s_list)\n+{\n+\tmutex_lock(\u0026port_mngr_grp_mtx);\n+\tmtk_port_tbl_del(port_mngr, port);\n+\tif (port-\u003einfo.type != PORT_TYPE_INTERNAL) {\n+\t\tif (test_bit(PORT_S_OPEN, \u0026port-\u003estatus)) {\n+\t\t\tlist_add_tail(\u0026port-\u003estale_entry, \u0026s_list-\u003eports);\n+\t\t\tset_bit(PORT_S_ON_STALE_LIST, \u0026port-\u003estatus);\n+\t\t\tmemcpy(port-\u003edev_str, port_mngr-\u003ectrl_blk-\u003emdev-\u003edev_str,\n+\t\t\t       MTK_DEV_STR_LEN);\n+\t\t\tport-\u003eport_mngr = NULL;\n+\t\t}\n+\t\tkref_put(\u0026port-\u003ekref, mtk_port_release);\n+\t} else {\n+\t\tmtk_port_release(\u0026port-\u003ekref);\n+\t}\n+\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+}\n+\n+static struct mtk_port *mtk_port_search_by_id(struct mtk_port_mngr *port_mngr, int rx_ch)\n+{\n+\tint tbl_type = MTK_PORT_TBL_TYPE(rx_ch);\n+\n+\tif (tbl_type \u003c PORT_TBL_SAP || tbl_type \u003e= PORT_TBL_MAX)\n+\t\treturn NULL;\n+\n+\treturn radix_tree_lookup(\u0026port_mngr-\u003eport_tbl[tbl_type], MTK_CH_ID(rx_ch));\n+}\n+\n+struct mtk_port *mtk_port_search_by_name(struct mtk_port_mngr *port_mngr, char *name)\n+{\n+\tint tbl_type = PORT_TBL_SAP;\n+\tstruct radix_tree_iter iter;\n+\tstruct mtk_port *port;\n+\tvoid __rcu **slot;\n+\n+\tdo {\n+\t\tradix_tree_for_each_slot(slot, \u0026port_mngr-\u003eport_tbl[tbl_type], \u0026iter, 0) {\n+\t\t\tMTK_PORT_SEARCH_FROM_RADIX_TREE(port, slot);\n+\t\t\tMTK_PORT_INTERNAL_NODE_CHECK(port, slot, iter);\n+\t\t\tif (!strncmp(port-\u003einfo.name, name, MTK_DFLT_PORT_NAME_LEN))\n+\t\t\t\treturn port;\n+\t\t}\n+\t\ttbl_type++;\n+\t} while (tbl_type \u003c PORT_TBL_MAX);\n+\n+\treturn NULL;\n+}\n+\n+static int mtk_port_tbl_create(struct mtk_port_mngr *port_mngr, struct mtk_port_cfg *cfg,\n+\t\t\t       const int port_cnt, struct mtk_stale_list *s_list)\n+{\n+\tstruct mtk_port_cfg *dflt_port;\n+\tstruct mtk_port *port;\n+\tint i;\n+\n+\tINIT_RADIX_TREE(\u0026port_mngr-\u003eport_tbl[PORT_TBL_SAP], GFP_KERNEL);\n+\tINIT_RADIX_TREE(\u0026port_mngr-\u003eport_tbl[PORT_TBL_MD], GFP_KERNEL);\n+\n+\tmtk_port_restore_from_stale_list(port_mngr, s_list);\n+\n+\t/* copy ports from static port cfg table */\n+\tfor (i = 0; i \u003c port_cnt; i++) {\n+\t\tdflt_port = cfg + i;\n+\t\tif (!mtk_port_search_by_id(port_mngr, dflt_port-\u003erx_ch)) {\n+\t\t\tport = mtk_port_alloc_and_add(port_mngr, dflt_port);\n+\t\t\tif (IS_ERR(port))\n+\t\t\t\treturn PTR_ERR(port);\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void mtk_port_tbl_destroy(struct mtk_port_mngr *port_mngr, struct mtk_stale_list *s_list)\n+{\n+\tstruct radix_tree_iter iter;\n+\tstruct mtk_port *port;\n+\tvoid __rcu **slot;\n+\tint tbl_type;\n+\n+\ttbl_type = PORT_TBL_SAP;\n+\tdo {\n+\t\tradix_tree_for_each_slot(slot, \u0026port_mngr-\u003eport_tbl[tbl_type], \u0026iter, 0) {\n+\t\t\tport = radix_tree_deref_slot(slot);\n+\t\t\tif (!port)\n+\t\t\t\tcontinue;\n+\t\t\tports_ops[port-\u003einfo.type]-\u003edisable(port);\n+\t\t}\n+\n+\t\twhile (radix_tree_gang_lookup(\u0026port_mngr-\u003eport_tbl[tbl_type],\n+\t\t\t\t\t      (void **)\u0026port, 0, 1))\n+\t\t\tmtk_port_free_or_backup(port_mngr, port, s_list);\n+\t} while (++tbl_type \u003c PORT_TBL_MAX);\n+}\n+\n+static struct mtk_stale_list *mtk_port_stale_list_create(struct mtk_ctrl_blk *ctrl_blk)\n+{\n+\tstruct mtk_stale_list *s_list;\n+\n+\ts_list = kzalloc_obj(*s_list, GFP_KERNEL);\n+\tif (!s_list)\n+\t\treturn NULL;\n+\n+\tmemcpy(s_list-\u003edev_str, ctrl_blk-\u003emdev-\u003edev_str, MTK_DEV_STR_LEN);\n+\ts_list-\u003edev_id = -1;\n+\tINIT_LIST_HEAD(\u0026s_list-\u003eports);\n+\trwlock_init(\u0026s_list-\u003eport_mngr_lock);\n+\n+\tmutex_lock(\u0026port_mngr_grp_mtx);\n+\tlist_add_tail(\u0026s_list-\u003eentry, \u0026stale_list_grp);\n+\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+\n+\treturn s_list;\n+}\n+\n+static void mtk_port_stale_list_destroy(struct mtk_stale_list *s_list)\n+{\n+\tmutex_lock(\u0026port_mngr_grp_mtx);\n+\tlist_del(\u0026s_list-\u003eentry);\n+\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+\tkfree(s_list);\n+}\n+\n+static struct mtk_stale_list *mtk_port_stale_list_search(const char *dev_str)\n+{\n+\tstruct mtk_stale_list *tmp, *s_list = NULL;\n+\n+\tmutex_lock(\u0026port_mngr_grp_mtx);\n+\tlist_for_each_entry(tmp, \u0026stale_list_grp, entry) {\n+\t\tif (!strncmp(tmp-\u003edev_str, dev_str, MTK_DEV_STR_LEN)) {\n+\t\t\ts_list = tmp;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+\n+\treturn s_list;\n+}\n+\n+void mtk_port_stale_list_grp_cleanup(void)\n+{\n+\tstruct mtk_stale_list *s_list, *next_s_list;\n+\tstruct mtk_port *port, *next_port;\n+\n+\tmutex_lock(\u0026port_mngr_grp_mtx);\n+\tlist_for_each_entry_safe(s_list, next_s_list, \u0026stale_list_grp, entry) {\n+\t\tlist_del(\u0026s_list-\u003eentry);\n+\n+\t\tlist_for_each_entry_safe(port, next_port, \u0026s_list-\u003eports, stale_entry) {\n+\t\t\tclear_bit(PORT_S_ON_STALE_LIST, \u0026port-\u003estatus);\n+\t\t\tmtk_port_release(\u0026port-\u003ekref);\n+\t\t}\n+\n+\t\tkfree(s_list);\n+\t}\n+\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+}\n+\n+static struct mtk_stale_list *mtk_port_stale_list_init(struct mtk_ctrl_blk *ctrl_blk, int *dev_id)\n+{\n+\tstruct mtk_stale_list *s_list;\n+\n+\ts_list = mtk_port_stale_list_search(ctrl_blk-\u003emdev-\u003edev_str);\n+\tif (!s_list) {\n+\t\ts_list = mtk_port_stale_list_create(ctrl_blk);\n+\t\tif (unlikely(!s_list))\n+\t\t\treturn NULL;\n+\t}\n+\n+\tmutex_lock(\u0026port_mngr_grp_mtx);\n+\tif (s_list-\u003edev_id \u003c 0) {\n+\t\t*dev_id = ida_alloc_range(\u0026ccci_dev_ids, 0, MTK_DFLT_MAX_DEV_CNT - 1, GFP_KERNEL);\n+\t} else {\n+\t\t*dev_id = s_list-\u003edev_id;\n+\t\ts_list-\u003edev_id = -1;\n+\t}\n+\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+\n+\treturn s_list;\n+}\n+\n+static void mtk_port_stale_list_exit(struct mtk_ctrl_blk *ctrl_blk,\n+\t\t\t\t     struct mtk_stale_list *s_list, int dev_id)\n+{\n+\tif (!s_list)\n+\t\treturn;\n+\tmutex_lock(\u0026port_mngr_grp_mtx);\n+\tif (list_empty(\u0026s_list-\u003eports)) {\n+\t\tida_free(\u0026ccci_dev_ids, dev_id);\n+\t\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+\t\tmtk_port_stale_list_destroy(s_list);\n+\t} else {\n+\t\ts_list-\u003edev_id = dev_id;\n+\t\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+\t}\n+}\n+\n+void mtk_port_trb_init(struct mtk_port *port, struct trb *trb, enum mtk_trb_cmd_type cmd,\n+\t\t       int (*trb_complete)(struct sk_buff *skb))\n+{\n+\tkref_init(\u0026trb-\u003ekref);\n+\ttrb-\u003echannel_id = port-\u003einfo.rx_ch;\n+\ttrb-\u003estatus = MTK_DFLT_TRB_STATUS;\n+\ttrb-\u003epriv = port;\n+\ttrb-\u003ecmd = cmd;\n+\ttrb-\u003etrb_complete = trb_complete;\n+}\n+\n+void mtk_port_trb_free(struct kref *trb_kref)\n+{\n+\tstruct trb *trb = container_of(trb_kref, struct trb, kref);\n+\tstruct sk_buff *skb, *frag_skb, *next_skb;\n+\n+\tskb = container_of((char *)trb, struct sk_buff, cb[0]);\n+\t/* Free frag_list for scatter gather TX */\n+\tif (trb-\u003ecmd == TRB_CMD_TX \u0026\u0026 skb_has_frag_list(skb)) {\n+\t\tfrag_skb = skb_shinfo(skb)-\u003efrag_list;\n+\t\twhile (frag_skb) {\n+\t\t\tnext_skb = frag_skb-\u003enext;\n+\t\t\tfrag_skb-\u003enext = NULL;\n+\t\t\tdev_kfree_skb_any(frag_skb);\n+\t\t\tfrag_skb = next_skb;\n+\t\t}\n+\t\tskb_shinfo(skb)-\u003efrag_list = NULL;\n+\t\tskb-\u003edata_len = 0;\n+\t}\n+\tdev_kfree_skb_any(skb);\n+}\n+EXPORT_SYMBOL(mtk_port_trb_free);\n+\n+static int mtk_port_open_trb_complete(struct sk_buff *skb)\n+{\n+\tstruct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb-\u003edata;\n+\tstruct trb *trb = (struct trb *)skb-\u003ecb;\n+\tstruct mtk_port *port = trb-\u003epriv;\n+\n+\tif (!trb-\u003estatus) {\n+\t\tport-\u003etx_mtu = trb_open_priv-\u003etx_mtu;\n+\t\tport-\u003erx_mtu = trb_open_priv-\u003erx_mtu;\n+\t\tport-\u003etx_frag_size = trb_open_priv-\u003etx_frag_size;\n+\t\tport-\u003erx_frag_size = trb_open_priv-\u003erx_frag_size;\n+\t\tport-\u003etx_mtu -= MTK_CCCI_H_ELEN;\n+\t\tport-\u003erx_mtu -= MTK_CCCI_H_ELEN;\n+\t}\n+\n+\twake_up_interruptible_all(\u0026port-\u003etrb_wq);\n+\n+\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\treturn 0;\n+}\n+\n+static int mtk_port_close_trb_complete(struct sk_buff *skb)\n+{\n+\tstruct trb *trb = (struct trb *)skb-\u003ecb;\n+\tstruct mtk_port *port = trb-\u003epriv;\n+\n+\twake_up_interruptible_all(\u0026port-\u003etrb_wq);\n+\twake_up_interruptible_all(\u0026port-\u003erx_wq);\n+\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\n+\treturn 0;\n+}\n+\n+static int mtk_port_tx_complete(struct sk_buff *skb)\n+{\n+\tstruct trb *trb = (struct trb *)skb-\u003ecb;\n+\tstruct mtk_port *port = trb-\u003epriv;\n+\n+\tif (trb-\u003estatus \u003c 0)\n+\t\tdev_warn(port-\u003eport_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t \"Failed to send data: status:%d, port:%s\\n\",\n+\t\t\t trb-\u003estatus, port-\u003einfo.name);\n+\n+\twake_up_interruptible_all(\u0026port-\u003etrb_wq);\n+\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\n+\treturn 0;\n+}\n+\n+int mtk_port_status_check(struct mtk_port *port)\n+{\n+\tif (!test_bit(PORT_S_ENABLE, \u0026port-\u003estatus))\n+\t\treturn -ENODEV;\n+\n+\tif (!test_bit(PORT_S_OPEN, \u0026port-\u003estatus) || test_bit(PORT_S_FLUSH, \u0026port-\u003estatus) ||\n+\t    !test_bit(PORT_S_WR, \u0026port-\u003estatus))\n+\t\treturn -EBADF;\n+\n+\treturn 0;\n+}\n+\n+int mtk_port_send_data(struct mtk_port *port, void *data)\n+{\n+\tstruct mtk_port_mngr *port_mngr;\n+\tstruct sk_buff *skb = data;\n+\tbool force_send;\n+\tstruct trb *trb;\n+\tint ret, len;\n+\n+\tport_mngr = port-\u003eport_mngr;\n+\n+\tforce_send = !!(port-\u003einfo.flags \u0026 (PORT_F_BLOCKING | PORT_F_FORCE_SEND));\n+\ttrb = (struct trb *)skb-\u003ecb;\n+\tmtk_port_trb_init(port, trb, TRB_CMD_TX, mtk_port_tx_complete);\n+\tlen = skb-\u003elen;\n+\tkref_get(\u0026trb-\u003ekref); /* kref count 1-\u003e2 */\n+\n+\t/* add ccci header */\n+\tmtk_port_add_header(skb);\n+\tret = mtk_port_status_check(port);\n+\tif (!ret)\n+\t\tret = port_mngr-\u003ectrl_blk-\u003eops-\u003esubmit_skb(port_mngr-\u003ectrl_blk-\u003emdev,\n+\t\t\t\t\t\t\t   skb, force_send);\n+\n+\tif (ret \u003c 0) {\n+\t\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free); /* kref count 2-\u003e1 */\n+\t\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free); /* kref count 1-\u003e0 */\n+\t\tport-\u003etx_seq--;\n+\t\tgoto out;\n+\t}\n+\n+\tif (!(port-\u003einfo.flags \u0026 PORT_F_BLOCKING)) {\n+\t\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\t\tret = len;\n+\t\tgoto out;\n+\t}\n+start_wait:\n+\n+\t/* wait trb done, and no timeout in tx blocking mode */\n+\tret = wait_event_interruptible_timeout(port-\u003etrb_wq,\n+\t\t\t\t\t       trb-\u003estatus \u003c= 0 ||\n+\t\t\t\t\t       test_bit(PORT_S_FLUSH, \u0026port-\u003estatus) ||\n+\t\t\t\t\t       !test_bit(PORT_S_WR, \u0026port-\u003estatus),\n+\t\t\t\t\t       MTK_DFLT_TRB_TIMEOUT);\n+\tif (!ret) {\n+\t\tgoto start_wait;\n+\t} else if (ret == -ERESTARTSYS) {\n+\t\tret = -EINTR;\n+\t} else if (ret \u003e 0) {\n+\t\tif (test_bit(PORT_S_FLUSH, \u0026port-\u003estatus))\n+\t\t\tret = len;\n+\t\telse\n+\t\t\tret = (!trb-\u003estatus) ? len : trb-\u003estatus;\n+\t}\n+\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\n+out:\n+\treturn ret;\n+}\n+\n+static int mtk_port_check_rx_seq(struct mtk_port *port, struct mtk_ccci_header *ccci_h)\n+{\n+\tu16 seq_num, assert_bit, channel;\n+\tstruct mtk_md_dev *mdev;\n+\n+\tseq_num = FIELD_GET(MTK_HDR_FLD_SEQ, le32_to_cpu(ccci_h-\u003estatus));\n+\tassert_bit = FIELD_GET(MTK_HDR_FLD_AST, le32_to_cpu(ccci_h-\u003estatus));\n+\tif (assert_bit \u0026\u0026 port-\u003erx_seq \u0026\u0026\n+\t    ((seq_num - port-\u003erx_seq) \u0026 MTK_CHECK_RX_SEQ_MASK) != 1) {\n+\t\tmdev = port-\u003eport_mngr-\u003ectrl_blk-\u003emdev;\n+\t\tchannel = FIELD_GET(MTK_HDR_FLD_CHN, le32_to_cpu(ccci_h-\u003estatus));\n+\t\tdev_warn(mdev-\u003edev,\n+\t\t\t \"\u003cch: %04x\u003e seq num out-of-order %d-\u003e%d, len(%u)\\n\",\n+\t\t\t channel, seq_num, port-\u003erx_seq,\n+\t\t\t le32_to_cpu(ccci_h-\u003epacket_len));\n+\n+\t\tport-\u003erx_seq = seq_num;\n+\t\treturn -EPROTO;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int mtk_port_rx_dispatch_frag_skb(struct mtk_port *port, struct sk_buff *skb)\n+{\n+\tstruct sk_buff *frag_skb, *frag_next;\n+\tint ret;\n+\n+\tfrag_skb = skb_shinfo(skb)-\u003efrag_list;\n+\tskb-\u003elen -= skb-\u003edata_len;\n+\tskb-\u003edata_len = 0;\n+\tskb_shinfo(skb)-\u003efrag_list = NULL;\n+\n+\tret = ports_ops[port-\u003einfo.type]-\u003erecv(port, skb);\n+\tif (ret \u003c 0) {\n+\t\tskb_shinfo(skb)-\u003efrag_list = frag_skb;\n+\t\treturn ret;\n+\t}\n+\n+\twhile (frag_skb) {\n+\t\tfrag_next = frag_skb-\u003enext;\n+\t\tif (!frag_skb-\u003elen) {\n+\t\t\tfrag_skb-\u003enext = NULL;\n+\t\t\tdev_kfree_skb_any(frag_skb);\n+\t\t\tfrag_skb = frag_next;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tfrag_skb-\u003enext = NULL;\n+\t\tret = ports_ops[port-\u003einfo.type]-\u003erecv(port, frag_skb);\n+\t\tif (ret \u003c 0) {\n+\t\t\tfrag_skb-\u003enext = frag_next;\n+\t\t\twhile (frag_skb) {\n+\t\t\t\tfrag_next = frag_skb-\u003enext;\n+\t\t\t\tfrag_skb-\u003enext = NULL;\n+\t\t\t\tdev_kfree_skb_any(frag_skb);\n+\t\t\t\tfrag_skb = frag_next;\n+\t\t\t}\n+\t\t\treturn -EIO;\n+\t\t}\n+\t\tfrag_skb = frag_next;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int mtk_port_rx_dispatch(struct sk_buff *skb, void *priv, bool force_recv)\n+{\n+\tstruct mtk_port_mngr *port_mngr;\n+\tstruct mtk_ccci_header *ccci_h;\n+\tstruct mtk_port *port = priv;\n+\tint ret = -EPROTO;\n+\tu16 channel;\n+\n+\tif (!skb || !priv) {\n+\t\tpr_err(\"Invalid input value in rx dispatch\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tport_mngr = port-\u003eport_mngr;\n+\n+\tccci_h = mtk_port_strip_header(skb);\n+\tif (unlikely(!ccci_h)) {\n+\t\tdev_warn(port_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t \"Unsupported: skb length(%d) is less than ccci header\\n\",\n+\t\t\t skb-\u003elen);\n+\t\tgoto drop_data;\n+\t}\n+\n+\tchannel = FIELD_GET(MTK_HDR_FLD_CHN, le32_to_cpu(ccci_h-\u003estatus));\n+\tport = mtk_port_search_by_id(port_mngr, channel);\n+\tif (unlikely(!port)) {\n+\t\tdev_warn(port_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t \"Failed to find port by channel:%d\\n\", channel);\n+\t\tgoto drop_data;\n+\t}\n+\n+\tret = mtk_port_check_rx_seq(port, ccci_h);\n+\tif (unlikely(ret))\n+\t\tgoto drop_data;\n+\n+\tport-\u003erx_seq = FIELD_GET(MTK_HDR_FLD_SEQ, le32_to_cpu(ccci_h-\u003estatus));\n+\tskb_pull(skb, sizeof(*ccci_h));\n+\n+\t/* Support scatter gather transmission */\n+\tif (port-\u003erx_mtu \u003e port-\u003erx_frag_size) {\n+\t\tret = mtk_port_rx_dispatch_frag_skb(port, skb);\n+\t\t/* -EIO means partial data dispatch complete, does not goto drop flow */\n+\t\tif (ret \u003c 0 \u0026\u0026 ret != -EIO)\n+\t\t\tgoto drop_frag_skb;\n+\t} else {\n+\t\tret = ports_ops[port-\u003einfo.type]-\u003erecv(port, skb);\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto drop_data;\n+\t}\n+\n+\treturn ret;\n+\n+drop_frag_skb:\n+\t{\n+\t\tstruct sk_buff *frag_skb, *tmp;\n+\n+\t\tfrag_skb = skb_shinfo(skb)-\u003efrag_list;\n+\t\twhile (frag_skb) {\n+\t\t\ttmp = frag_skb-\u003enext;\n+\t\t\tfrag_skb-\u003enext = NULL;\n+\t\t\tdev_kfree_skb_any(frag_skb);\n+\t\t\tfrag_skb = tmp;\n+\t\t}\n+\t\tskb_shinfo(skb)-\u003efrag_list = NULL;\n+\t}\n+drop_data:\n+\tdev_kfree_skb_any(skb);\n+\treturn ret;\n+}\n+\n+int mtk_port_add_header(struct sk_buff *skb)\n+{\n+\tstruct mtk_ccci_header *ccci_h;\n+\tstruct mtk_port *port;\n+\tstruct trb *trb;\n+\n+\ttrb = (struct trb *)skb-\u003ecb;\n+\tif (trb-\u003estatus == MTK_TRB_HEADER_ADDED)\n+\t\treturn 0;\n+\n+\tport = trb-\u003epriv;\n+\tif (!port)\n+\t\treturn -EINVAL;\n+\n+\tccci_h = skb_push(skb, sizeof(*ccci_h));\n+\n+\tccci_h-\u003epacket_header = cpu_to_le32(0);\n+\tccci_h-\u003epacket_len = cpu_to_le32(skb-\u003elen);\n+\tccci_h-\u003eex_msg = cpu_to_le32(0);\n+\tccci_h-\u003estatus = cpu_to_le32(FIELD_PREP(MTK_HDR_FLD_CHN, port-\u003einfo.tx_ch) |\n+\t\t\t\t     FIELD_PREP(MTK_HDR_FLD_SEQ, port-\u003etx_seq++) |\n+\t\t\t\t     FIELD_PREP(MTK_HDR_FLD_AST, 1));\n+\n+\ttrb-\u003estatus = MTK_TRB_HEADER_ADDED;\n+\n+\treturn 0;\n+}\n+\n+struct mtk_ccci_header *mtk_port_strip_header(struct sk_buff *skb)\n+{\n+\tstruct mtk_ccci_header *ccci_h;\n+\n+\tif (skb-\u003elen \u003c sizeof(*ccci_h)) {\n+\t\tpr_err(\"Invalid input value\\n\");\n+\t\treturn NULL;\n+\t}\n+\n+\tccci_h = (struct mtk_ccci_header *)skb-\u003edata;\n+\n+\treturn ccci_h;\n+}\n+\n+int mtk_port_status_update(struct mtk_md_dev *mdev, void *data)\n+{\n+\tstruct mtk_port_enum_msg *msg = data;\n+\tstruct mtk_port_info *port_info;\n+\tstruct mtk_port_mngr *port_mngr;\n+\tstruct mtk_ctrl_blk *ctrl_blk;\n+\tstruct mtk_port *port;\n+\tint port_id;\n+\tu16 ch_id;\n+\n+\tif (unlikely(!mdev || !msg))\n+\t\treturn -EINVAL;\n+\n+\tctrl_blk = mdev-\u003ectrl_blk;\n+\tport_mngr = ctrl_blk-\u003eport_mngr;\n+\tif (le16_to_cpu(msg-\u003eversion) != MTK_PORT_ENUM_VER ||\n+\t    le32_to_cpu(msg-\u003ehead_pattern) != MTK_PORT_ENUM_HEAD_PATTERN ||\n+\t    le32_to_cpu(msg-\u003etail_pattern) != MTK_PORT_ENUM_TAIL_PATTERN)\n+\t\treturn -EPROTO;\n+\n+\tfor (port_id = 0; port_id \u003c le16_to_cpu(msg-\u003eport_cnt); port_id++) {\n+\t\tport_info = (struct mtk_port_info *)(msg-\u003edata +\n+\t\t\t\t\t\t   (sizeof(*port_info) * port_id));\n+\t\tch_id = FIELD_GET(MTK_INFO_FLD_CHID, le16_to_cpu(port_info-\u003echannel));\n+\t\tport = mtk_port_search_by_id(port_mngr, ch_id);\n+\t\tif (!port)\n+\t\t\tcontinue;\n+\t\tport-\u003eenable = FIELD_GET(MTK_INFO_FLD_EN, le16_to_cpu(port_info-\u003echannel));\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int mtk_port_ch_enable(struct mtk_port *port)\n+{\n+\tstruct mtk_port_mngr *port_mngr = port-\u003eport_mngr;\n+\tstruct trb_open_priv *trb_open_priv;\n+\tstruct sk_buff *skb;\n+\tstruct trb *trb;\n+\tint ret;\n+\n+\tskb = __dev_alloc_skb(Q_MTU_3_5K, GFP_KERNEL);\n+\tif (!skb)\n+\t\treturn -ENOMEM;\n+\n+\ttrb_open_priv = (struct trb_open_priv *)skb-\u003edata;\n+\ttrb_open_priv-\u003erx_done = mtk_port_rx_dispatch;\n+\n+\tskb_put(skb, sizeof(struct trb_open_priv));\n+\ttrb = (struct trb *)skb-\u003ecb;\n+\tmtk_port_trb_init(port, trb, TRB_CMD_ENABLE, mtk_port_open_trb_complete);\n+\tkref_get(\u0026trb-\u003ekref);\n+\n+\tret = port_mngr-\u003ectrl_blk-\u003eops-\u003esubmit_skb(port_mngr-\u003ectrl_blk-\u003emdev, skb, true);\n+\tif (ret) {\n+\t\tdev_err(port_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t\"Failed to submit trb for port(%s), ret=%d\\n\",\n+\t\t\tport-\u003einfo.name, ret);\n+\t\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\t\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\t\treturn ret;\n+\t}\n+\n+start_wait:\n+\tret = wait_event_interruptible_timeout(port-\u003etrb_wq, trb-\u003estatus \u003c= 0,\n+\t\t\t\t\t       MTK_DFLT_TRB_TIMEOUT);\n+\tif (ret == -ERESTARTSYS)\n+\t\tgoto start_wait;\n+\telse if (!ret)\n+\t\tret = -ETIMEDOUT;\n+\telse\n+\t\tret = trb-\u003estatus;\n+\n+\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\n+\treturn ret;\n+}\n+\n+int mtk_port_ch_disable(struct mtk_port *port)\n+{\n+\tstruct mtk_port_mngr *port_mngr = port-\u003eport_mngr;\n+\tstruct sk_buff *skb;\n+\tstruct trb *trb;\n+\tint ret;\n+\n+\tskb = __dev_alloc_skb(Q_MTU_3_5K, GFP_KERNEL);\n+\tif (!skb)\n+\t\treturn -ENOMEM;\n+\n+\ttrb = (struct trb *)skb-\u003ecb;\n+\tmtk_port_trb_init(port, trb, TRB_CMD_DISABLE, mtk_port_close_trb_complete);\n+\tkref_get(\u0026trb-\u003ekref);\n+\n+\tret = port_mngr-\u003ectrl_blk-\u003eops-\u003esubmit_skb(port_mngr-\u003ectrl_blk-\u003emdev, skb, true);\n+\tif (ret) {\n+\t\tdev_warn(port_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t \"Failed to submit trb for port(%s), ret=%d\\n\",\n+\t\t\t port-\u003einfo.name, ret);\n+\t\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\t\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\t\treturn ret;\n+\t}\n+\n+start_wait:\n+\tret = wait_event_interruptible_timeout(port-\u003etrb_wq, trb-\u003estatus \u003c= 0,\n+\t\t\t\t\t       MTK_DFLT_TRB_TIMEOUT);\n+\tif (ret == -ERESTARTSYS)\n+\t\tgoto start_wait;\n+\telse if (!ret)\n+\t\tret = -ETIMEDOUT;\n+\telse\n+\t\tret = trb-\u003estatus;\n+\n+\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\n+\treturn ret;\n+}\n+\n+static int mtk_port_enable_by_type(struct mtk_port_mngr *port_mngr, int tbl_type)\n+{\n+\tstruct mtk_port **ports;\n+\tint ret, idx;\n+\n+\tif (tbl_type \u003c 0 || tbl_type \u003e= PORT_TBL_MAX)\n+\t\treturn -EINVAL;\n+\n+\tports = kcalloc(port_mngr-\u003eport_cnt, sizeof(struct mtk_port *), GFP_KERNEL);\n+\tif (!ports)\n+\t\treturn -ENOMEM;\n+\n+\tret = radix_tree_gang_lookup(\u0026port_mngr-\u003eport_tbl[tbl_type],\n+\t\t\t\t     (void **)ports, 0, port_mngr-\u003eport_cnt);\n+\tfor (idx = 0; idx \u003c ret; idx++) {\n+\t\tif (ports[idx]-\u003eenable)\n+\t\t\tports_ops[ports[idx]-\u003einfo.type]-\u003eenable(ports[idx]);\n+\t}\n+\n+\tkfree(ports);\n+\treturn 0;\n+}\n+\n+static void mtk_port_disable(struct mtk_port_mngr *port_mngr)\n+{\n+\tstruct mtk_port **ports;\n+\tint tbl_type;\n+\tint ret, idx;\n+\n+\tports = kcalloc(port_mngr-\u003eport_cnt, sizeof(struct mtk_port *), GFP_KERNEL);\n+\tif (!ports)\n+\t\treturn;\n+\n+\ttbl_type = PORT_TBL_SAP;\n+\tdo {\n+\t\tret = radix_tree_gang_lookup(\u0026port_mngr-\u003eport_tbl[tbl_type],\n+\t\t\t\t\t     (void **)ports, 0, port_mngr-\u003eport_cnt);\n+\t\tfor (idx = 0; idx \u003c ret; idx++)\n+\t\t\tports_ops[ports[idx]-\u003einfo.type]-\u003edisable(ports[idx]);\n+\t} while (++tbl_type \u003c PORT_TBL_MAX);\n+\tkfree(ports);\n+}\n+\n+void mtk_port_mngr_fsm_state_handler(struct mtk_fsm_param *fsm_param, void *arg)\n+{\n+\tstruct mtk_port_mngr *port_mngr;\n+\n+\tif (!fsm_param || !arg)\n+\t\treturn;\n+\n+\tport_mngr = arg;\n+\n+\tswitch (fsm_param-\u003eto) {\n+\tcase FSM_STATE_OFF:\n+\t\tmtk_port_disable(port_mngr);\n+\t\tbreak;\n+\tcase FSM_STATE_READY:\n+\t\tmtk_port_enable_by_type(port_mngr, PORT_TBL_MD);\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+}\n+\n+void mtk_port_mngr_fsm_state_handler_late(struct mtk_fsm_param *fsm_param, void *arg)\n+{\n+\tstruct mtk_port_mngr *port_mngr;\n+\tstruct mtk_port *port;\n+\n+\tif (!fsm_param || !arg)\n+\t\treturn;\n+\n+\tport_mngr = arg;\n+\n+\tswitch (fsm_param-\u003eto) {\n+\tcase FSM_STATE_BOOTUP:\n+\t\tif (fsm_param-\u003efsm_flag \u0026 FSM_F_MD_HS_START) {\n+\t\t\tport = mtk_port_search_by_id(port_mngr, CCCI_CONTROL_RX);\n+\t\t\tif (port)\n+\t\t\t\tports_ops[port-\u003einfo.type]-\u003eenable(port);\n+\t\t} else if (fsm_param-\u003efsm_flag \u0026 FSM_F_SAP_HS_START) {\n+\t\t\tport = mtk_port_search_by_id(port_mngr, CCCI_SAP_CONTROL_RX);\n+\t\t\tif (port)\n+\t\t\t\tports_ops[port-\u003einfo.type]-\u003eenable(port);\n+\t\t}\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+}\n+\n+int mtk_port_mngr_init(struct mtk_ctrl_blk *ctrl_blk, struct mtk_port_cfg *port_cfg, int port_cnt)\n+{\n+\tstruct mtk_port_mngr *port_mngr;\n+\tstruct mtk_stale_list *s_list;\n+\tint ret = -ENOMEM;\n+\tint dev_id;\n+\n+\ts_list = mtk_port_stale_list_init(ctrl_blk, \u0026dev_id);\n+\tif (!s_list) {\n+\t\tdev_err((ctrl_blk-\u003emdev)-\u003edev, \"Failed to init mtk_stale_list\\n\");\n+\t\tgoto err_out;\n+\t}\n+\n+\tport_mngr = devm_kzalloc(ctrl_blk-\u003emdev-\u003edev, sizeof(*port_mngr), GFP_KERNEL);\n+\tif (unlikely(!port_mngr)) {\n+\t\tdev_err((ctrl_blk-\u003emdev)-\u003edev, \"Failed to alloc memory for port_mngr\\n\");\n+\t\tgoto err_exit_stale_list;\n+\t}\n+\n+\tport_mngr-\u003ectrl_blk = ctrl_blk;\n+\tport_mngr-\u003edev_id = dev_id;\n+\n+\tret = mtk_port_tbl_create(port_mngr, port_cfg, port_cnt, s_list);\n+\tif (unlikely(ret)) {\n+\t\tdev_err((ctrl_blk-\u003emdev)-\u003edev, \"Failed to create port_tbl\\n\");\n+\t\tgoto err_free_port_mngr;\n+\t}\n+\n+\tctrl_blk-\u003eport_mngr = port_mngr;\n+\n+\treturn ret;\n+\n+err_free_port_mngr:\n+\tmtk_port_tbl_destroy(port_mngr, s_list);\n+err_exit_stale_list:\n+\tmtk_port_stale_list_exit(ctrl_blk, s_list, dev_id);\n+err_out:\n+\treturn ret;\n+}\n+\n+void mtk_port_mngr_exit(struct mtk_ctrl_blk *ctrl_blk)\n+{\n+\tstruct mtk_port_mngr *port_mngr = ctrl_blk-\u003eport_mngr;\n+\tstruct mtk_stale_list *s_list;\n+\tint dev_id;\n+\n+\ts_list = mtk_port_stale_list_search(port_mngr-\u003ectrl_blk-\u003emdev-\u003edev_str);\n+\tdev_id = port_mngr-\u003edev_id;\n+\n+\tmtk_port_tbl_destroy(port_mngr, s_list);\n+\n+\tctrl_blk-\u003eport_mngr = NULL;\n+\tmtk_port_stale_list_exit(ctrl_blk, s_list, dev_id);\n+}\ndiff --git a/drivers/net/wwan/t9xx/mtk_port.h b/drivers/net/wwan/t9xx/mtk_port.h\nnew file mode 100644\nindex 00000000000000..4846354981d7f1\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/mtk_port.h\n@@ -0,0 +1,174 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#ifndef __MTK_PORT_H__\n+#define __MTK_PORT_H__\n+\n+#include \u003clinux/bits.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/radix-tree.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+#include \"mtk_ctrl_plane.h\"\n+#include \"mtk_dev.h\"\n+\n+#define MTK_PEER_ID_MASK\t\t\t(0xF000)\n+#define MTK_PEER_ID_SHIFT\t\t\t(12)\n+#define MTK_PEER_ID(ch)\t\t\t\t(((ch) \u0026 MTK_PEER_ID_MASK) \u003e\u003e MTK_PEER_ID_SHIFT)\n+#define MTK_PEER_ID_SAP\t\t\t\t(0x1)\n+#define MTK_PEER_ID_MD\t\t\t\t(0x2)\n+#define MTK_CH_ID_MASK\t\t\t\t(0x0FFF)\n+#define MTK_CH_ID(ch)\t\t\t\t((ch) \u0026 MTK_CH_ID_MASK)\n+#define MTK_DFLT_MAX_DEV_CNT\t\t\t(10)\n+#define MTK_DFLT_PORT_NAME_LEN\t\t\t(20)\n+\n+/* Mapping MTK_PEER_ID and mtk_port_tbl index */\n+#define MTK_PORT_TBL_TYPE(ch)\t\t\t(MTK_PEER_ID(ch) - 1)\n+\n+/* ccci header length + reserved space that is used in exception flow */\n+#define MTK_CCCI_H_ELEN\t\t(128)\n+\n+#define MTK_HDR_FLD_AST\t\t((u32)BIT(31))\n+#define MTK_HDR_FLD_SEQ\t\tGENMASK(30, 16)\n+#define MTK_HDR_FLD_CHN\t\tGENMASK(15, 0)\n+\n+#define MTK_INFO_FLD_EN\t\t((u16)BIT(15))\n+#define MTK_INFO_FLD_CHID\tGENMASK(14, 0)\n+\n+enum mtk_port_status {\n+\tPORT_S_DFLT = 0,\n+\tPORT_S_ENABLE,\n+\tPORT_S_OPEN,\n+\tPORT_S_RD,\n+\tPORT_S_WR,\n+\tPORT_S_FLUSH,\n+\tPORT_S_ON_STALE_LIST,\n+\tPORT_S_STOP,\n+};\n+\n+enum mtk_ccci_ch {\n+\t/* to sAP */\n+\tCCCI_SAP_CONTROL_RX\t\t\t= 0x1000,\n+\tCCCI_SAP_CONTROL_TX\t\t\t= 0x1001,\n+\t/* to MD */\n+\tCCCI_CONTROL_RX\t\t\t\t= 0x2000,\n+\tCCCI_CONTROL_TX\t\t\t\t= 0x2001,\n+\tCCCI_UART2_RX\t\t\t\t= 0x200A,\n+\tCCCI_UART2_TX\t\t\t\t= 0x200C,\n+\tCCCI_MBIM_RX\t\t\t\t= 0x20D0,\n+\tCCCI_MBIM_TX\t\t\t\t= 0x20D1,\n+};\n+\n+enum mtk_port_flag {\n+\tPORT_F_DFLT = 0,\n+\tPORT_F_BLOCKING = BIT(1),\n+\tPORT_F_ALLOW_DROP = BIT(2),\n+\tPORT_F_FORCE_SEND = BIT(6),\n+};\n+\n+enum mtk_port_tbl {\n+\tPORT_TBL_SAP,\n+\tPORT_TBL_MD,\n+\tPORT_TBL_MAX\n+};\n+\n+enum mtk_port_type {\n+\tPORT_TYPE_INTERNAL,\n+\tPORT_TYPE_WWAN,\n+\tPORT_TYPE_MAX\n+};\n+\n+struct mtk_internal_port {\n+\tvoid *arg;\n+\tint (*recv_cb)(void *arg, struct sk_buff *skb);\n+};\n+\n+struct mtk_wwan_port {\n+\t/* w_lock protects wwan_port when recv data and disable port at the same time */\n+\tstruct mutex w_lock;\n+\tint w_type;\n+\tvoid *w_port;\n+};\n+\n+struct mtk_port_cfg {\n+\tenum mtk_ccci_ch tx_ch;\n+\tenum mtk_ccci_ch rx_ch;\n+\tenum mtk_port_type type;\n+\tchar name[MTK_DFLT_PORT_NAME_LEN];\n+\tunsigned char flags;\n+};\n+\n+struct mtk_port {\n+\tstruct mtk_port_cfg info;\n+\tstruct kref kref;\n+\tbool enable;\n+\tunsigned long status;\n+\tunsigned int minor;\n+\tunsigned short tx_seq;\n+\tunsigned short rx_seq;\n+\tunsigned int tx_mtu;\n+\tunsigned int rx_mtu;\n+\tu32 tx_frag_size;\n+\tu32 rx_frag_size;\n+\tstruct sk_buff_head rx_skb_list;\n+\tunsigned int rx_data_len;\n+\tunsigned int rx_buf_size;\n+\twait_queue_head_t trb_wq;\n+\twait_queue_head_t rx_wq;\n+\tstruct list_head stale_entry;\n+\tchar dev_str[MTK_DEV_STR_LEN];\n+\tstruct mtk_port_mngr *port_mngr;\n+\tstruct mtk_internal_port i_priv;\n+\tstruct mtk_wwan_port w_priv;\n+};\n+\n+struct mtk_port_mngr {\n+\tstruct mtk_ctrl_blk *ctrl_blk;\n+\tstruct radix_tree_root port_tbl[PORT_TBL_MAX];\n+\tunsigned int port_cnt;\n+\tint dev_id;\n+};\n+\n+struct mtk_stale_list {\n+\tstruct list_head entry;\n+\tstruct list_head ports;\n+\tchar dev_str[MTK_DEV_STR_LEN];\n+\tint dev_id;\n+\trwlock_t port_mngr_lock;\n+};\n+\n+struct mtk_ccci_header {\n+\t__le32 packet_header;\n+\t__le32 packet_len;\n+\t__le32 status;\n+\t__le32 ex_msg;\n+};\n+\n+struct mtk_port_layer_cfg {\n+\tstruct mtk_port_cfg *port_cfg;\n+\tint port_cnt;\n+};\n+\n+extern const struct port_ops *ports_ops[PORT_TYPE_MAX];\n+\n+void mtk_port_release(struct kref *port_kref);\n+void mtk_port_trb_free(struct kref *trb_kref);\n+struct mtk_port *mtk_port_search_by_name(struct mtk_port_mngr *port_mngr, char *name);\n+void mtk_port_stale_list_grp_cleanup(void);\n+int mtk_port_add_header(struct sk_buff *skb);\n+struct mtk_ccci_header *mtk_port_strip_header(struct sk_buff *skb);\n+int mtk_port_status_check(struct mtk_port *port);\n+int mtk_port_send_data(struct mtk_port *port, void *data);\n+int mtk_port_status_update(struct mtk_md_dev *mdev, void *data);\n+int mtk_port_ch_enable(struct mtk_port *port);\n+int mtk_port_ch_disable(struct mtk_port *port);\n+void mtk_port_mngr_fsm_state_handler(struct mtk_fsm_param *fsm_param, void *arg);\n+void mtk_port_mngr_fsm_state_handler_late(struct mtk_fsm_param *fsm_param, void *arg);\n+int mtk_port_mngr_init(struct mtk_ctrl_blk *ctrl_blk, struct mtk_port_cfg *port_cfg, int port_cnt);\n+void mtk_port_mngr_exit(struct mtk_ctrl_blk *ctrl_blk);\n+void mtk_port_trb_init(struct mtk_port *port, struct trb *trb, enum mtk_trb_cmd_type cmd,\n+\t\t       int (*trb_complete)(struct sk_buff *skb));\n+#endif /* __MTK_PORT_H__ */\ndiff --git a/drivers/net/wwan/t9xx/mtk_port_io.c b/drivers/net/wwan/t9xx/mtk_port_io.c\nnew file mode 100644\nindex 00000000000000..882254b74026f5\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/mtk_port_io.c\n@@ -0,0 +1,573 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/poll.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/wait.h\u003e\n+#include \u003clinux/wwan.h\u003e\n+\n+#include \"mtk_port_io.h\"\n+\n+static int mtk_port_get_locked(struct mtk_port *port)\n+{\n+\tint ret = 0;\n+\n+\tmutex_lock(\u0026port_mngr_grp_mtx);\n+\tif (!port) {\n+\t\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+\t\tpr_err(\"Port does not exist\\n\");\n+\t\treturn -ENODEV;\n+\t}\n+\tkref_get(\u0026port-\u003ekref);\n+\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+\n+\treturn ret;\n+}\n+\n+static void mtk_port_put_locked(struct mtk_port *port)\n+{\n+\tmutex_lock(\u0026port_mngr_grp_mtx);\n+\tkref_put(\u0026port-\u003ekref, mtk_port_release);\n+\tmutex_unlock(\u0026port_mngr_grp_mtx);\n+}\n+\n+static void mtk_port_struct_init(struct mtk_port *port)\n+{\n+\tport-\u003etx_seq = 0;\n+\tport-\u003erx_seq = -1;\n+\tclear_bit(PORT_S_ENABLE, \u0026port-\u003estatus);\n+\tkref_init(\u0026port-\u003ekref);\n+\tskb_queue_head_init(\u0026port-\u003erx_skb_list);\n+\tport-\u003erx_buf_size = MTK_RX_BUF_SIZE;\n+\tinit_waitqueue_head(\u0026port-\u003etrb_wq);\n+\tinit_waitqueue_head(\u0026port-\u003erx_wq);\n+}\n+\n+static int mtk_port_copy_data_from(void *to, union user_buf from, unsigned int len,\n+\t\t\t\t   unsigned int offset, bool from_user_space)\n+{\n+\tif (from_user_space) {\n+\t\tif (copy_from_user(to, from.ubuf + offset, len))\n+\t\t\treturn -EINVAL;\n+\t} else {\n+\t\tmemcpy(to, from.kbuf + offset, len);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int mtk_port_common_write_frag_skb(struct mtk_port *port, struct sk_buff *skb,\n+\t\t\t\t\t  union user_buf buf, u32 packet_size,\n+\t\t\t\t\t  u32 cur_pos, bool from_user_space)\n+{\n+\tstruct sk_buff *frag_skb, *tmp = NULL;\n+\tu32 frag_size;\n+\tint ret;\n+\n+\tfrag_size = min(packet_size, port-\u003etx_frag_size);\n+\tret = mtk_port_copy_data_from(skb_put(skb, frag_size),\n+\t\t\t\t      buf, frag_size,\n+\t\t\t\t      cur_pos, from_user_space);\n+\tif (ret) {\n+\t\tdev_err(port-\u003eport_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t\"Failed to copy skb for port(%s)\\n\", port-\u003einfo.name);\n+\t\tgoto err_reset_skb;\n+\t}\n+\tcur_pos += frag_size;\n+\tpacket_size -= frag_size;\n+\tif (!packet_size)\n+\t\treturn cur_pos;\n+\n+\twhile (packet_size \u003e 0) {\n+\t\tfrag_skb = __dev_alloc_skb(port-\u003etx_mtu, GFP_KERNEL);\n+\t\tif (!frag_skb) {\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto err_free_frag_list;\n+\t\t}\n+\n+\t\tfrag_size = min(packet_size, port-\u003etx_frag_size);\n+\t\tret = mtk_port_copy_data_from(skb_put(frag_skb, frag_size),\n+\t\t\t\t\t      buf, frag_size,\n+\t\t\t\t\t      cur_pos, from_user_space);\n+\t\tif (ret) {\n+\t\t\tdev_err(port-\u003eport_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t\t\"Failed to copy frag_skb for port(%s)\\n\", port-\u003einfo.name);\n+\t\t\tdev_kfree_skb_any(frag_skb);\n+\t\t\tgoto err_free_frag_list;\n+\t\t}\n+\t\tskb-\u003edata_len += frag_size;\n+\t\tskb-\u003elen += frag_size;\n+\t\tcur_pos += frag_size;\n+\t\tpacket_size -= frag_size;\n+\t\tif (!tmp)\n+\t\t\tskb_shinfo(skb)-\u003efrag_list = frag_skb;\n+\t\telse\n+\t\t\ttmp-\u003enext = frag_skb;\n+\t\ttmp = frag_skb;\n+\t}\n+\treturn cur_pos;\n+\n+err_free_frag_list:\n+\tfrag_skb = skb_shinfo(skb)-\u003efrag_list;\n+\twhile (frag_skb) {\n+\t\ttmp = frag_skb-\u003enext;\n+\t\tfrag_skb-\u003enext = NULL;\n+\t\tdev_kfree_skb_any(frag_skb);\n+\t\tfrag_skb = tmp;\n+\t}\n+\tskb_shinfo(skb)-\u003efrag_list = NULL;\n+err_reset_skb:\n+\tskb-\u003edata_len = 0;\n+\treturn ret;\n+}\n+\n+static int mtk_port_common_write(struct mtk_port *port, union user_buf buf, unsigned int len,\n+\t\t\t\t bool from_user_space)\n+{\n+\tu32 packet_size, left_cnt = len, cur_pos;\n+\tstruct sk_buff *skb;\n+\tint ret;\n+\n+\tif (len == 0)\n+\t\treturn -EINVAL;\n+\n+start_write:\n+\tret = mtk_port_status_check(port);\n+\tif (ret)\n+\t\tgoto end_write;\n+\n+\tskb = __dev_alloc_skb(port-\u003etx_mtu, GFP_KERNEL);\n+\tif (!skb) {\n+\t\tret = -ENOMEM;\n+\t\tgoto end_write;\n+\t}\n+\n+\tskb_reserve(skb, sizeof(struct mtk_ccci_header));\n+\n+\tpacket_size = min_t(u32, left_cnt, port-\u003etx_mtu - sizeof(struct mtk_ccci_header));\n+\tcur_pos = len - left_cnt;\n+\t/* Support scatter gather transmission */\n+\tif (port-\u003etx_mtu \u003e port-\u003etx_frag_size) {\n+\t\tret = mtk_port_common_write_frag_skb(port, skb, buf, packet_size,\n+\t\t\t\t\t\t     cur_pos, from_user_space);\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto err_free_skb;\n+\t} else {\n+\t\tret = mtk_port_copy_data_from(skb_put(skb, packet_size),\n+\t\t\t\t\t      buf, packet_size,\n+\t\t\t\t\t      cur_pos, from_user_space);\n+\t\tif (ret) {\n+\t\t\tdev_err(port-\u003eport_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t\t\"Failed to copy data for port(%s)\\n\", port-\u003einfo.name);\n+\t\t\tgoto err_free_skb;\n+\t\t}\n+\t}\n+\n+\tret = mtk_port_send_data(port, skb);\n+\tif (ret \u003c 0) {\n+\t\tif (ret == -EINTR)\n+\t\t\tleft_cnt -= packet_size;\n+\t\tgoto end_write;\n+\t}\n+\n+\tleft_cnt -= ret;\n+\tif (left_cnt)\n+\t\tgoto start_write;\n+\telse\n+\t\tgoto end_write;\n+\n+err_free_skb:\n+\tdev_kfree_skb_any(skb);\n+end_write:\n+\treturn (len \u003e left_cnt) ? (len - left_cnt) : ret;\n+}\n+\n+static int mtk_port_internal_init(struct mtk_port *port)\n+{\n+\tmtk_port_struct_init(port);\n+\tport-\u003eenable = false;\n+\n+\treturn 0;\n+}\n+\n+static void mtk_port_internal_exit(struct mtk_port *port)\n+{\n+\tif (test_bit(PORT_S_ENABLE, \u0026port-\u003estatus))\n+\t\tports_ops[port-\u003einfo.type]-\u003edisable(port);\n+}\n+\n+static void mtk_port_reset(struct mtk_port *port)\n+{\n+\tport-\u003etx_seq = 0;\n+\tport-\u003erx_seq = -1;\n+}\n+\n+static void mtk_port_internal_enable(struct mtk_port *port)\n+{\n+\tint ret;\n+\n+\tif (test_bit(PORT_S_ENABLE, \u0026port-\u003estatus))\n+\t\treturn;\n+\n+\tret = mtk_port_ch_enable(port);\n+\tif (ret \u0026\u0026 ret != -EBUSY)\n+\t\treturn;\n+\n+\tset_bit(PORT_S_WR, \u0026port-\u003estatus);\n+\tset_bit(PORT_S_ENABLE, \u0026port-\u003estatus);\n+}\n+\n+static void mtk_port_internal_disable(struct mtk_port *port)\n+{\n+\tif (!test_and_clear_bit(PORT_S_ENABLE, \u0026port-\u003estatus))\n+\t\treturn;\n+\n+\tclear_bit(PORT_S_WR, \u0026port-\u003estatus);\n+\tmtk_port_ch_disable(port);\n+}\n+\n+static int mtk_port_internal_recv(struct mtk_port *port, struct sk_buff *skb)\n+{\n+\tstruct mtk_internal_port *priv;\n+\tint ret = -ENXIO;\n+\n+\tif (!test_bit(PORT_S_OPEN, \u0026port-\u003estatus))\n+\t\tgoto drop_data;\n+\n+\tpriv = \u0026port-\u003ei_priv;\n+\tif (!priv-\u003erecv_cb || !priv-\u003earg)\n+\t\tgoto drop_data;\n+\n+\tret = priv-\u003erecv_cb(priv-\u003earg, skb);\n+\treturn ret;\n+\n+drop_data:\n+\treturn ret;\n+}\n+\n+static int mtk_port_common_open(struct mtk_port *port)\n+{\n+\tint ret = 0;\n+\n+\tif (!test_bit(PORT_S_ENABLE, \u0026port-\u003estatus))\n+\t\treturn -ENODEV;\n+\n+\tif (test_bit(PORT_S_OPEN, \u0026port-\u003estatus))\n+\t\treturn -EBUSY;\n+\n+\tskb_queue_purge(\u0026port-\u003erx_skb_list);\n+\tset_bit(PORT_S_OPEN, \u0026port-\u003estatus);\n+\tclear_bit(PORT_S_FLUSH, \u0026port-\u003estatus);\n+\n+\treturn ret;\n+}\n+\n+static void mtk_port_common_close(struct mtk_port *port)\n+{\n+\tclear_bit(PORT_S_OPEN, \u0026port-\u003estatus);\n+\n+\tskb_queue_purge(\u0026port-\u003erx_skb_list);\n+\tport-\u003erx_data_len = 0;\n+\n+\tset_bit(PORT_S_FLUSH, \u0026port-\u003estatus);\n+\twake_up_interruptible_all(\u0026port-\u003etrb_wq);\n+\twake_up_interruptible_all(\u0026port-\u003erx_wq);\n+}\n+\n+void *mtk_port_internal_open(struct mtk_md_dev *mdev, char *name, int flag)\n+{\n+\tstruct mtk_port_mngr *port_mngr;\n+\tstruct mtk_ctrl_blk *ctrl_blk;\n+\tstruct mtk_port *port;\n+\tint ret;\n+\n+\tctrl_blk = mdev-\u003ectrl_blk;\n+\tport_mngr = ctrl_blk-\u003eport_mngr;\n+\n+\tport = mtk_port_search_by_name(port_mngr, name);\n+\tif (port \u0026\u0026 port-\u003einfo.type != PORT_TYPE_INTERNAL) {\n+\t\tport = NULL;\n+\t\tgoto out;\n+\t}\n+\n+\tret = mtk_port_get_locked(port);\n+\tif (ret)\n+\t\tgoto out;\n+\n+\tret = mtk_port_common_open(port);\n+\tif (ret) {\n+\t\tmtk_port_put_locked(port);\n+\t\tport = NULL;\n+\t\tgoto out;\n+\t}\n+\n+\tif (flag \u0026 O_NONBLOCK)\n+\t\tport-\u003einfo.flags \u0026= ~PORT_F_BLOCKING;\n+\telse\n+\t\tport-\u003einfo.flags |= PORT_F_BLOCKING;\n+out:\n+\treturn port;\n+}\n+\n+int mtk_port_internal_close(void *i_port)\n+{\n+\tstruct mtk_port *port = i_port;\n+\tint ret = 0;\n+\n+\tif (!port) {\n+\t\tret = -EINVAL;\n+\t\tgoto end;\n+\t}\n+\n+\tif (!test_bit(PORT_S_OPEN, \u0026port-\u003estatus)) {\n+\t\tpr_err(\"Port(%s) has been closed\\n\", port-\u003einfo.name);\n+\t\tret = -EBADF;\n+\t\tgoto end;\n+\t}\n+\n+\tmtk_port_common_close(port);\n+\tmtk_port_put_locked(port);\n+end:\n+\treturn ret;\n+}\n+\n+int mtk_port_internal_write(void *i_port, struct sk_buff *skb)\n+{\n+\tstruct mtk_port *port = i_port;\n+\n+\tif (!port || !skb) {\n+\t\tif (skb)\n+\t\t\tdev_kfree_skb_any(skb);\n+\t\tpr_err_ratelimited(\"Internal write: invalid input\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\treturn mtk_port_send_data(port, skb);\n+}\n+\n+void mtk_port_internal_recv_register(void *i_port,\n+\t\t\t\t     int (*cb)(void *priv, struct sk_buff *skb),\n+\t\t\t\t     void *arg)\n+{\n+\tstruct mtk_port *port = i_port;\n+\tstruct mtk_internal_port *priv;\n+\n+\tpriv = \u0026port-\u003ei_priv;\n+\tpriv-\u003earg = arg;\n+\tpriv-\u003erecv_cb = cb;\n+}\n+\n+int mtk_port_io_init(void)\n+{\n+\treturn 0;\n+}\n+\n+void mtk_port_io_exit(void)\n+{\n+}\n+\n+static const struct port_ops port_internal_ops = {\n+\t.init = mtk_port_internal_init,\n+\t.exit = mtk_port_internal_exit,\n+\t.reset = mtk_port_reset,\n+\t.enable = mtk_port_internal_enable,\n+\t.disable = mtk_port_internal_disable,\n+\t.recv = mtk_port_internal_recv,\n+};\n+\n+static int mtk_port_wwan_open(struct wwan_port *w_port)\n+{\n+\tstruct mtk_port *port;\n+\tint ret;\n+\n+\tport = wwan_port_get_drvdata(w_port);\n+\tret = mtk_port_get_locked(port);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = mtk_port_common_open(port);\n+\tif (ret)\n+\t\tmtk_port_put_locked(port);\n+\n+\treturn ret;\n+}\n+\n+static void mtk_port_wwan_close(struct wwan_port *w_port)\n+{\n+\tstruct mtk_port *port = wwan_port_get_drvdata(w_port);\n+\n+\tmtk_port_common_close(port);\n+\tmtk_port_put_locked(port);\n+}\n+\n+static int mtk_port_wwan_write(struct wwan_port *w_port, struct sk_buff *skb)\n+{\n+\tstruct mtk_port *port = wwan_port_get_drvdata(w_port);\n+\tunion user_buf user_buf;\n+\tint ret;\n+\n+\tif (unlikely(!skb-\u003elen)) {\n+\t\tconsume_skb(skb);\n+\t\treturn 0;\n+\t}\n+\n+\tport-\u003einfo.flags \u0026= ~PORT_F_BLOCKING;\n+\tuser_buf.kbuf = (void *)skb-\u003edata;\n+\tret = mtk_port_common_write(port, user_buf, skb-\u003elen, false);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tconsume_skb(skb);\n+\treturn 0;\n+}\n+\n+static int mtk_port_wwan_write_blocking(struct wwan_port *w_port, struct sk_buff *skb)\n+{\n+\tstruct mtk_port *port = wwan_port_get_drvdata(w_port);\n+\tunion user_buf user_buf;\n+\tint ret;\n+\n+\tif (unlikely(!skb-\u003elen)) {\n+\t\tconsume_skb(skb);\n+\t\treturn 0;\n+\t}\n+\n+\tport-\u003einfo.flags |= PORT_F_BLOCKING;\n+\tuser_buf.kbuf = (void *)skb-\u003edata;\n+\tret = mtk_port_common_write(port, user_buf, skb-\u003elen, false);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tconsume_skb(skb);\n+\treturn 0;\n+}\n+\n+static __poll_t mtk_port_wwan_poll(struct wwan_port *w_port, struct file *file,\n+\t\t\t\t   struct poll_table_struct *poll)\n+{\n+\tstruct mtk_port *port = wwan_port_get_drvdata(w_port);\n+\tunion ctrl_hif_cmd_data hif_cmd;\n+\tstruct mtk_ctrl_blk *ctrl_blk;\n+\t__poll_t mask = 0;\n+\n+\tpoll_wait(file, \u0026port-\u003etrb_wq, poll);\n+\tif (mtk_port_status_check(port))\n+\t\treturn EPOLLERR | EPOLLHUP;\n+\n+\tctrl_blk = port-\u003eport_mngr-\u003ectrl_blk;\n+\thif_cmd.rx_ch = port-\u003einfo.rx_ch;\n+\tif (!ctrl_blk-\u003eops-\u003esend_cmd(ctrl_blk-\u003emdev, HIF_CTRL_CMD_CHECK_TX_FULL, \u0026hif_cmd))\n+\t\tmask |= EPOLLOUT | EPOLLWRNORM;\n+\n+\treturn mask;\n+}\n+\n+static const struct wwan_port_ops wwan_ops = {\n+\t.start = mtk_port_wwan_open,\n+\t.stop = mtk_port_wwan_close,\n+\t.tx = mtk_port_wwan_write,\n+\t.tx_blocking = mtk_port_wwan_write_blocking,\n+\t.tx_poll = mtk_port_wwan_poll,\n+};\n+\n+static int mtk_port_wwan_init(struct mtk_port *port)\n+{\n+\tmtk_port_struct_init(port);\n+\tport-\u003eenable = false;\n+\n+\tmutex_init(\u0026port-\u003ew_priv.w_lock);\n+\n+\tswitch (port-\u003einfo.rx_ch) {\n+\tcase CCCI_MBIM_RX:\n+\t\tport-\u003ew_priv.w_type = WWAN_PORT_MBIM;\n+\t\tbreak;\n+\tcase CCCI_UART2_RX:\n+\t\tport-\u003ew_priv.w_type = WWAN_PORT_AT;\n+\t\tbreak;\n+\tdefault:\n+\t\tport-\u003ew_priv.w_type = WWAN_PORT_UNKNOWN;\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void mtk_port_wwan_exit(struct mtk_port *port)\n+{\n+\tif (test_bit(PORT_S_ENABLE, \u0026port-\u003estatus))\n+\t\tports_ops[port-\u003einfo.type]-\u003edisable(port);\n+}\n+\n+static void mtk_port_wwan_enable(struct mtk_port *port)\n+{\n+\tstruct mtk_port_mngr *port_mngr;\n+\tint ret;\n+\n+\tport_mngr = port-\u003eport_mngr;\n+\n+\tif (test_bit(PORT_S_ENABLE, \u0026port-\u003estatus))\n+\t\treturn;\n+\n+\tret = mtk_port_ch_enable(port);\n+\tif (ret \u0026\u0026 ret != -EBUSY)\n+\t\treturn;\n+\n+\tport-\u003ew_priv.w_port = wwan_create_port(port_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t\t\t       port-\u003ew_priv.w_type,\n+\t\t\t\t\t       \u0026wwan_ops, NULL, port);\n+\tif (IS_ERR(port-\u003ew_priv.w_port)) {\n+\t\tdev_warn(port_mngr-\u003ectrl_blk-\u003emdev-\u003edev,\n+\t\t\t \"Failed to create wwan port for (%s)\\n\", port-\u003einfo.name);\n+\t\tport-\u003ew_priv.w_port = NULL;\n+\t\tmtk_port_ch_disable(port);\n+\t\treturn;\n+\t}\n+\n+\tset_bit(PORT_S_WR, \u0026port-\u003estatus);\n+\tset_bit(PORT_S_ENABLE, \u0026port-\u003estatus);\n+}\n+\n+static void mtk_port_wwan_disable(struct mtk_port *port)\n+{\n+\tstruct wwan_port *w_port;\n+\n+\tif (!test_and_clear_bit(PORT_S_ENABLE, \u0026port-\u003estatus))\n+\t\treturn;\n+\n+\tclear_bit(PORT_S_WR, \u0026port-\u003estatus);\n+\tw_port = port-\u003ew_priv.w_port;\n+\tmutex_lock(\u0026port-\u003ew_priv.w_lock);\n+\tport-\u003ew_priv.w_port = NULL;\n+\tmutex_unlock(\u0026port-\u003ew_priv.w_lock);\n+\n+\tmtk_port_ch_disable(port);\n+\twwan_remove_port(w_port);\n+}\n+\n+static int mtk_port_wwan_recv(struct mtk_port *port, struct sk_buff *skb)\n+{\n+\tmutex_lock(\u0026port-\u003ew_priv.w_lock);\n+\tif (!port-\u003ew_priv.w_port) {\n+\t\tmutex_unlock(\u0026port-\u003ew_priv.w_lock);\n+\t\treturn -ENXIO;\n+\t}\n+\n+\twwan_port_rx(port-\u003ew_priv.w_port, skb);\n+\tmutex_unlock(\u0026port-\u003ew_priv.w_lock);\n+\treturn 0;\n+}\n+\n+static const struct port_ops port_wwan_ops = {\n+\t.init = mtk_port_wwan_init,\n+\t.exit = mtk_port_wwan_exit,\n+\t.reset = mtk_port_reset,\n+\t.enable = mtk_port_wwan_enable,\n+\t.disable = mtk_port_wwan_disable,\n+\t.recv = mtk_port_wwan_recv,\n+};\n+\n+const struct port_ops *ports_ops[PORT_TYPE_MAX] = {\n+\t\u0026port_internal_ops,\n+\t\u0026port_wwan_ops,\n+};\ndiff --git a/drivers/net/wwan/t9xx/mtk_port_io.h b/drivers/net/wwan/t9xx/mtk_port_io.h\nnew file mode 100644\nindex 00000000000000..ea92cd22dba0ef\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/mtk_port_io.h\n@@ -0,0 +1,41 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#ifndef __MTK_PORT_IO_H__\n+#define __MTK_PORT_IO_H__\n+\n+#include \u003clinux/skbuff.h\u003e\n+\n+#include \"mtk_port.h\"\n+\n+#define MTK_RX_BUF_SIZE\t\t\t(1024 * 1024)\n+\n+extern struct mutex port_mngr_grp_mtx;\n+\n+struct port_ops {\n+\tint (*init)(struct mtk_port *port);\n+\tvoid (*exit)(struct mtk_port *port);\n+\tvoid (*reset)(struct mtk_port *port);\n+\tvoid (*enable)(struct mtk_port *port);\n+\tvoid (*disable)(struct mtk_port *port);\n+\tint (*recv)(struct mtk_port *port, struct sk_buff *skb);\n+};\n+\n+union user_buf {\n+\tvoid __user *ubuf;\n+\tvoid *kbuf;\n+};\n+\n+void *mtk_port_internal_open(struct mtk_md_dev *mdev, char *name, int flag);\n+int mtk_port_internal_close(void *i_port);\n+int mtk_port_internal_write(void *i_port, struct sk_buff *skb);\n+void mtk_port_internal_recv_register(void *i_port,\n+\t\t\t\t     int (*cb)(void *priv, struct sk_buff *skb),\n+\t\t\t\t     void *arg);\n+\n+int mtk_port_io_init(void);\n+void mtk_port_io_exit(void);\n+\n+#endif /* __MTK_PORT_IO_H__ */\ndiff --git a/drivers/net/wwan/t9xx/mtk_utility.h b/drivers/net/wwan/t9xx/mtk_utility.h\nnew file mode 100644\nindex 00000000000000..b72db3842d2ddb\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/mtk_utility.h\n@@ -0,0 +1,33 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#ifndef __MTK_UTILITY_H__\n+#define __MTK_UTILITY_H__\n+\n+#include \u003clinux/device.h\u003e\n+#include \"mtk_dev.h\"\n+\n+#define MTK_UEVENT_INFO_LEN 128\n+\n+/* MTK uevent */\n+enum mtk_uevent_id {\n+\tMTK_UEVENT_UNDEF = 0,\n+\tMTK_UEVENT_FSM = 1,\n+\tMTK_UEVENT_MINIDUMP = 2,\n+\tMTK_UEVENT_LOWPOWER = 3,\n+\tMTK_UEVENT_MAX\n+};\n+\n+static inline void mtk_uevent_notify(struct device *dev, enum mtk_uevent_id id, const char *info)\n+{\n+\tchar buf[MTK_UEVENT_INFO_LEN];\n+\tchar *ext[2] = {NULL, NULL};\n+\n+\tsnprintf(buf, MTK_UEVENT_INFO_LEN, \"%s:event_id=%d, info=%s\",\n+\t\t dev-\u003ekobj.name, id, info);\n+\text[0] = buf;\n+\tkobject_uevent_env(\u0026dev-\u003ekobj, KOBJ_CHANGE, ext);\n+}\n+#endif /* __MTK_UTILITY_H__ */\ndiff --git a/drivers/net/wwan/t9xx/pcie/Makefile b/drivers/net/wwan/t9xx/pcie/Makefile\nnew file mode 100644\nindex 00000000000000..5252f158b0582c\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/Makefile\n@@ -0,0 +1,15 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+\n+ccflags-y += -I$(src)\n+ccflags-y += -I$(src)/..\n+\n+obj-$(CONFIG_MTK_T9XX_PCI) += mtk_t9xx_pcie.o\n+\n+mtk_t9xx_pcie-y := \\\n+\tmtk_pci_drv_m9xx.o \\\n+\tmtk_cldma_drv_m9xx.o \\\n+\tmtk_ctrl_cfg_m9xx.o \\\n+\tmtk_pci.o \\\n+\tmtk_trans_ctrl.o \\\n+\tmtk_cldma.o \\\n+\tmtk_cldma_drv.o\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma.c\nnew file mode 100644\nindex 00000000000000..3ac553b99255e0\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma.c\n@@ -0,0 +1,1418 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/dmapool.h\u003e\n+#include \u003clinux/err.h\u003e\n+#include \u003clinux/interrupt.h\u003e\n+#include \u003clinux/kdev_t.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/kthread.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/sched.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/timer.h\u003e\n+#include \u003clinux/wait.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+#include \"mtk_pci.h\"\n+#include \"mtk_cldma.h\"\n+#include \"mtk_cldma_drv.h\"\n+#include \"mtk_dev.h\"\n+\n+#define cldma_drv_ops_null\tNULL\n+#define DMA_POOL_NAME_LEN\t(64)\n+#define WAIT_HWO_ROUND\t\t(10)\n+#define WAIT_HWO_TIME\t\t(5)\n+#define CLDMA_RETRY_DELAY_MS\t(100)\n+#define NO_BUDGET\t\t(0)\n+\n+static struct cldma_drv_info_desc cldma_drv_info_tbl[] = {\n+\t{0x01CA, \u0026drv_ops_name(m9xx), \u0026cldma_regs_name(m9xx)},\n+\t{0, NULL},\n+};\n+\n+static void mtk_cldma_get_drv_info(struct cldma_drv_info *drv_info, u32 hw_ver)\n+{\n+\tstruct cldma_drv_info_desc *p_drv_info;\n+\tu8 i;\n+\n+\tfor (i = 0; (p_drv_info = \u0026cldma_drv_info_tbl[i]) \u0026\u0026 p_drv_info \u0026\u0026\n+\t     p_drv_info-\u003edrv_ops \u0026\u0026 p_drv_info-\u003ehw_regs; i++)\n+\t\tif (p_drv_info-\u003ehw_ver == hw_ver) {\n+\t\t\tdrv_info-\u003edrv_ops = p_drv_info-\u003edrv_ops;\n+\t\t\tdrv_info-\u003ehw_regs = p_drv_info-\u003ehw_regs;\n+\t\t}\n+}\n+\n+static int mtk_cldma_isr(int irq_id, void *param)\n+{\n+\tstruct cldma_drv_info *drv_info = param;\n+\tstruct mtk_md_dev *mdev;\n+\tu32 tx_done, rx_done;\n+\tu32 tx_sta, rx_sta;\n+\tstruct txq *txq;\n+\tstruct rxq *rxq;\n+\tint i;\n+\n+\tmdev = drv_info-\u003emdev;\n+\tdrv_info-\u003edrv_ops-\u003ecldma_get_intr_status(drv_info, \u0026tx_sta, \u0026rx_sta);\n+\ttx_done = (tx_sta \u003e\u003e QUEUE_XFER_DONE) \u0026 0xFF;\n+\trx_done = (rx_sta \u003e\u003e QUEUE_XFER_DONE) \u0026 0xFF;\n+\n+\tif (tx_done) {\n+\t\tfor (i = 0; i \u003c HW_QUEUE_NUM; i++) {\n+\t\t\ttxq = drv_info-\u003etxq[i];\n+\t\t\tif (!(tx_done \u0026 BIT(i)) || !txq)\n+\t\t\t\tcontinue;\n+\t\t\tqueue_work(drv_info-\u003ewq, \u0026txq-\u003etx_done_work);\n+\t\t}\n+\t}\n+\tif (rx_done) {\n+\t\tfor (i = 0; i \u003c HW_QUEUE_NUM; i++) {\n+\t\t\trxq = drv_info-\u003erxq[i];\n+\t\t\tif (!(rx_done \u0026 BIT(i)) || !rxq)\n+\t\t\t\tcontinue;\n+\t\t\tqueue_work(drv_info-\u003ewq, \u0026rxq-\u003erx_done_work);\n+\t\t}\n+\t}\n+\n+\tmtk_pci_clear_irq(mdev, drv_info-\u003epci_ext_irq_id);\n+\tmtk_pci_unmask_irq(mdev, drv_info-\u003epci_ext_irq_id);\n+\n+\treturn IRQ_HANDLED;\n+}\n+\n+static const int mtk_cldma_hw_id_tbl[NR_CLDMA] = {\n+\t[CLDMA0] = CLDMA0_HW_ID,\n+\t[CLDMA1] = CLDMA1_HW_ID,\n+};\n+\n+static int mtk_cldma_dev_init(struct cldma_dev *cd, int hif_id)\n+{\n+\tchar gpd_pool_name[DMA_POOL_NAME_LEN];\n+\tchar bd_pool_name[DMA_POOL_NAME_LEN];\n+\tstruct cldma_drv_info *drv_info;\n+\tstruct cldma_hw_regs *hw_regs;\n+\tstruct mtk_md_dev *mdev;\n+\tunsigned int flag;\n+\tint hw_id, ret;\n+\n+\tif (!cd || hif_id \u003e= NR_CLDMA)\n+\t\treturn -EINVAL;\n+\n+\tif (cd-\u003ecldma_drv_info[hif_id])\n+\t\treturn 0;\n+\n+\thw_id = mtk_cldma_hw_id_tbl[hif_id];\n+\tmdev = cd-\u003etrans-\u003emdev;\n+\tdrv_info = kzalloc_obj(*drv_info);\n+\tif (!drv_info)\n+\t\treturn -ENOMEM;\n+\n+\tdrv_info-\u003ecd = cd;\n+\tdrv_info-\u003emdev = mdev;\n+\tdrv_info-\u003ehif_id = hif_id;\n+\tdrv_info-\u003ehw_id = hw_id;\n+\tmtk_cldma_get_drv_info(drv_info, mdev-\u003ehw_ver);\n+\n+\tif (!drv_info-\u003edrv_ops || !drv_info-\u003ehw_regs) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to find CLDMA Driver for PCI %x\\n\", mdev-\u003ehw_ver);\n+\t\tret = -EIO;\n+\t\tgoto err_free_drv_info;\n+\t}\n+\n+\thw_regs = drv_info-\u003ehw_regs;\n+\tsnprintf(gpd_pool_name, DMA_POOL_NAME_LEN, \"cldma%d_gpd_pool_%s\",\n+\t\t hw_id, mdev-\u003edev_str);\n+\tsnprintf(bd_pool_name, DMA_POOL_NAME_LEN, \"cldma%d_bd_pool_%s\",\n+\t\t hw_id, mdev-\u003edev_str);\n+\tdrv_info-\u003egpd_dma_pool = dma_pool_create(gpd_pool_name, mdev-\u003edev,\n+\t\t\t\t\t\t sizeof(union gpd), 4, 0);\n+\tif (!drv_info-\u003egpd_dma_pool) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to alloc gpd dma pool for cldma%d\\n\", hw_id);\n+\t\tret = -ENOMEM;\n+\t\tgoto err_free_drv_info;\n+\t}\n+\tdrv_info-\u003ebd_dma_pool = dma_pool_create(bd_pool_name, mdev-\u003edev,\n+\t\t\t\t\t\tsizeof(union bd), 4, 0);\n+\tif (!drv_info-\u003ebd_dma_pool) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to alloc bd dma pool for cldma%d\\n\", hw_id);\n+\t\tret = -ENOMEM;\n+\t\tgoto err_destroy_gpd_pool;\n+\t}\n+\n+\tswitch (hif_id) {\n+\tcase CLDMA0:\n+\t\tdrv_info-\u003epci_ext_irq_id = mtk_pci_get_irq_id(mdev, MTK_IRQ_SRC_CLDMA0);\n+\t\tdrv_info-\u003ebase_addr = hw_regs-\u003ecldma0_base_addr;\n+\t\tbreak;\n+\tcase CLDMA1:\n+\t\tdrv_info-\u003epci_ext_irq_id = mtk_pci_get_irq_id(mdev, MTK_IRQ_SRC_CLDMA1);\n+\t\tdrv_info-\u003ebase_addr = hw_regs-\u003ecldma1_base_addr;\n+\t\tbreak;\n+\tdefault:\n+\t\tret = -EINVAL;\n+\t\tgoto err_destroy_dma_pool;\n+\t}\n+\n+\tflag = WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI;\n+\tdrv_info-\u003ewq = alloc_workqueue(\"cldma%d_workq_%s\", flag, 0, hw_id, mdev-\u003edev_str);\n+\tif (!drv_info-\u003ewq) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to alloc work queue for cldma%d\\n\", hw_id);\n+\t\tret = -ENOMEM;\n+\t\tgoto err_destroy_dma_pool;\n+\t}\n+\n+\tdrv_info-\u003edrv_ops-\u003ecldma_drv_init(drv_info);\n+\n+\t/* mask/clear PCI CLDMA L1 interrupt */\n+\tmtk_pci_mask_irq(mdev, drv_info-\u003epci_ext_irq_id);\n+\tmtk_pci_clear_irq(mdev, drv_info-\u003epci_ext_irq_id);\n+\n+\t/* register CLDMA interrupt handler */\n+\tret = mtk_pci_register_irq(mdev, drv_info-\u003epci_ext_irq_id, mtk_cldma_isr, drv_info);\n+\tif (ret)\n+\t\tgoto err_destroy_wq;\n+\n+\t/* unmask PCI CLDMA L1 interrupt */\n+\tmtk_pci_unmask_irq(mdev, drv_info-\u003epci_ext_irq_id);\n+\n+\tcd-\u003ecldma_drv_info[hif_id] = drv_info;\n+\treturn 0;\n+\n+err_destroy_wq:\n+\tdestroy_workqueue(drv_info-\u003ewq);\n+err_destroy_dma_pool:\n+\tdma_pool_destroy(drv_info-\u003ebd_dma_pool);\n+err_destroy_gpd_pool:\n+\tdma_pool_destroy(drv_info-\u003egpd_dma_pool);\n+err_free_drv_info:\n+\tkfree(drv_info);\n+\n+\treturn ret;\n+}\n+\n+static void mtk_cldma_clr_bd_dsc(struct cldma_drv_info *drv_info,\n+\t\t\t\t struct bd_dsc *bd_dsc_pool, int nr_bds)\n+{\n+\tstruct bd_dsc *bd_dsc;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c nr_bds; i++) {\n+\t\tbd_dsc = bd_dsc_pool + i;\n+\t\tdma_unmap_single(drv_info-\u003emdev-\u003edev, bd_dsc-\u003edata_dma_addr,\n+\t\t\t\t bd_dsc-\u003edata_len, DMA_TO_DEVICE);\n+\t\tbd_dsc-\u003edata_dma_addr = 0;\n+\t\tbd_dsc-\u003edata_len = 0;\n+\t\tif (bd_dsc-\u003ebd-\u003etx_bd.bd_flags \u0026 CLDMA_BD_FLAG_EOL) {\n+\t\t\tbd_dsc-\u003ebd-\u003etx_bd.bd_flags \u0026= ~CLDMA_BD_FLAG_EOL;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+}\n+\n+static void mtk_cldma_tx_done_work(struct work_struct *work)\n+{\n+\tstruct txq *txq = container_of(work, struct txq, tx_done_work);\n+\tstruct cldma_drv_info *drv_info;\n+\tstruct cldma_drv_ops *drv_ops;\n+\tstruct mtk_ctrl_trans *trans;\n+\tstruct mtk_md_dev *mdev;\n+\tstruct tx_req *req;\n+\tunsigned int state;\n+\tstruct trb *trb;\n+\tint i, hif_id;\n+\tu32 txqno;\n+\n+\tdrv_info = txq-\u003edrv_info;\n+\thif_id = drv_info-\u003ehif_id;\n+\ttxqno = txq-\u003etxqno;\n+\tmdev = drv_info-\u003emdev;\n+\tdrv_ops = drv_info-\u003edrv_ops;\n+\ttrans = drv_info-\u003ecd-\u003etrans;\n+\n+again:\n+\tfor (i = 0; i \u003c txq-\u003enr_gpds; i++) {\n+\t\treq = txq-\u003ereq_pool + txq-\u003efree_idx;\n+\n+\t\trmb(); /* ensure HWO setup done before HWO read */\n+\n+\t\tif (!req-\u003edata_vm_addr || (req-\u003egpd-\u003etx_gpd.gpd_flags \u0026 CLDMA_GPD_FLAG_HWO))\n+\t\t\tbreak;\n+\n+\t\tif (txq-\u003enr_bds)\n+\t\t\tmtk_cldma_clr_bd_dsc(drv_info, req-\u003ebd_dsc_pool, txq-\u003enr_bds);\n+\t\telse\n+\t\t\tdma_unmap_single(mdev-\u003edev, req-\u003edata_dma_addr,\n+\t\t\t\t\t req-\u003edata_len, DMA_TO_DEVICE);\n+\n+\t\ttrb = (struct trb *)req-\u003eskb-\u003ecb;\n+\t\ttrb-\u003estatus = 0;\n+\t\ttrb-\u003etrb_complete(req-\u003eskb);\n+\n+\t\treq-\u003edata_vm_addr = NULL;\n+\t\treq-\u003edata_dma_addr = 0;\n+\t\treq-\u003edata_len = 0;\n+\t\treq-\u003eskb = NULL;\n+\n+\t\ttxq-\u003efree_idx = (txq-\u003efree_idx + 1) % txq-\u003enr_gpds;\n+\t\tif (atomic_fetch_inc(\u0026txq-\u003ereq_budget) == NO_BUDGET)\n+\t\t\twake_up(\u0026trans-\u003etrb_srv[trans-\u003esrv_cfg[hif_id][txqno]]-\u003etrb_waitq);\n+\t}\n+\n+\tstate = drv_ops-\u003ecldma_check_intr_status(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);\n+\tif (state) {\n+\t\tif (unlikely(state == LINK_ERROR_VAL))\n+\t\t\tgoto out;\n+\n+\t\tdrv_ops-\u003ecldma_clr_intr_status(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);\n+\n+\t\tcond_resched();\n+\n+\t\tgoto again;\n+\t}\n+\n+out:\n+\tdrv_ops-\u003ecldma_unmask_intr(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);\n+}\n+\n+static void mtk_cldma_rx_skb_adjust(struct mtk_md_dev *mdev, struct rxq *rxq,\n+\t\t\t\t    struct rx_req *req)\n+{\n+\tstruct bd_dsc *bd_dsc;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c rxq-\u003enr_bds; i++) {\n+\t\tbd_dsc = req-\u003ebd_dsc_pool + i;\n+\t\tif (bd_dsc-\u003edata_dma_addr) {\n+\t\t\tdma_unmap_single(mdev-\u003edev, bd_dsc-\u003edata_dma_addr,\n+\t\t\t\t\t req-\u003efrag_size, DMA_FROM_DEVICE);\n+\t\t\tbd_dsc-\u003edata_dma_addr = 0;\n+\t\t}\n+\t\tbd_dsc-\u003eskb-\u003elen = 0;\n+\t\tskb_reset_tail_pointer(bd_dsc-\u003eskb);\n+\t\tskb_put(bd_dsc-\u003eskb,\n+\t\t\tmin_t(u16, le16_to_cpu(bd_dsc-\u003ebd-\u003erx_bd.data_recv_len),\n+\t\t\t      req-\u003efrag_size));\n+\t\tif (req-\u003eskb != bd_dsc-\u003eskb) {\n+\t\t\treq-\u003eskb-\u003elen += bd_dsc-\u003eskb-\u003elen;\n+\t\t\treq-\u003eskb-\u003edata_len += bd_dsc-\u003eskb-\u003elen;\n+\t\t}\n+\t\tbd_dsc-\u003ebd-\u003erx_bd.data_recv_len = 0;\n+\t\tbd_dsc-\u003eskb = NULL;\n+\t}\n+\tif (!rxq-\u003enr_bds) {\n+\t\tif (req-\u003edata_dma_addr) {\n+\t\t\tdma_unmap_single(mdev-\u003edev, req-\u003edata_dma_addr,\n+\t\t\t\t\t req-\u003emtu, DMA_FROM_DEVICE);\n+\t\t\treq-\u003edata_dma_addr = 0;\n+\t\t}\n+\t\treq-\u003eskb-\u003elen = 0;\n+\t\tskb_reset_tail_pointer(req-\u003eskb);\n+\t\tskb_put(req-\u003eskb,\n+\t\t\tmin_t(u16, le16_to_cpu(req-\u003egpd-\u003erx_gpd.data_recv_len),\n+\t\t\t      req-\u003emtu));\n+\t}\n+\n+\treq-\u003egpd-\u003erx_gpd.data_recv_len = 0;\n+}\n+\n+static int mtk_cldma_reload_rx_skb(struct mtk_md_dev *mdev, struct rxq *rxq,\n+\t\t\t\t   struct rx_req *req)\n+{\n+\tstruct sk_buff *tail = NULL;\n+\tstruct bd_dsc *bd_dsc;\n+\tint nr_bds;\n+\tint i, ret;\n+\n+\tnr_bds = rxq-\u003enr_bds;\n+\n+\tfor (i = 0; i \u003c nr_bds; i++) {\n+\t\tbd_dsc = req-\u003ebd_dsc_pool + i;\n+\t\tbd_dsc-\u003eskb = __dev_alloc_skb(req-\u003efrag_size, GFP_KERNEL);\n+\t\tif (!bd_dsc-\u003eskb) {\n+\t\t\tdev_warn_ratelimited(mdev-\u003edev, \"Failed to alloc SKB\\n\");\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto err_free_skb;\n+\t\t}\n+\t\tbd_dsc-\u003eskb-\u003enext = NULL;\n+\t\tbd_dsc-\u003edata_dma_addr = dma_map_single(mdev-\u003edev, bd_dsc-\u003eskb-\u003edata,\n+\t\t\t\t\t\t       req-\u003efrag_size, DMA_FROM_DEVICE);\n+\t\tret = dma_mapping_error(mdev-\u003edev, bd_dsc-\u003edata_dma_addr);\n+\t\tif (unlikely(ret)) {\n+\t\t\tdev_warn_ratelimited(mdev-\u003edev, \"Failed to map SKB data\\n\");\n+\t\t\tret = -EFAULT;\n+\t\t\tgoto err_free_skb;\n+\t\t}\n+\t\tbd_dsc-\u003ebd-\u003erx_bd.data_buff_ptr_h =\n+\t\t\tcpu_to_le32((u64)(bd_dsc-\u003edata_dma_addr) \u003e\u003e 32);\n+\t\tbd_dsc-\u003ebd-\u003erx_bd.data_buff_ptr_l =\n+\t\t\tcpu_to_le32(bd_dsc-\u003edata_dma_addr);\n+\t\tif (tail) {\n+\t\t\ttail-\u003enext = bd_dsc-\u003eskb;\n+\t\t\ttail = bd_dsc-\u003eskb;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!req-\u003eskb) {\n+\t\t\treq-\u003eskb = bd_dsc-\u003eskb;\n+\t\t} else {\n+\t\t\tskb_shinfo(req-\u003eskb)-\u003efrag_list = bd_dsc-\u003eskb;\n+\t\t\ttail = bd_dsc-\u003eskb;\n+\t\t}\n+\t}\n+\tif (!nr_bds) {\n+\t\treq-\u003eskb = __dev_alloc_skb(req-\u003emtu, GFP_KERNEL);\n+\t\tif (!req-\u003eskb) {\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto err_free_skb;\n+\t\t}\n+\n+\t\treq-\u003edata_dma_addr = dma_map_single(mdev-\u003edev, req-\u003eskb-\u003edata,\n+\t\t\t\t\t\t    req-\u003emtu, DMA_FROM_DEVICE);\n+\t\tret = dma_mapping_error(mdev-\u003edev, req-\u003edata_dma_addr);\n+\t\tif (unlikely(ret)) {\n+\t\t\tdev_warn_ratelimited(mdev-\u003edev, \"Failed to map SKB data\\n\");\n+\t\t\tret = -EFAULT;\n+\t\t\tgoto err_free_skb;\n+\t\t}\n+\t\treq-\u003egpd-\u003erx_gpd.data_buff_ptr_h = cpu_to_le32((u64)req-\u003edata_dma_addr \u003e\u003e 32);\n+\t\treq-\u003egpd-\u003erx_gpd.data_buff_ptr_l = cpu_to_le32(req-\u003edata_dma_addr);\n+\t}\n+\treturn 0;\n+\n+err_free_skb:\n+\tif (nr_bds) {\n+\t\tif (req-\u003eskb)\n+\t\t\tskb_shinfo(req-\u003eskb)-\u003efrag_list = NULL;\n+\t\tfor (i = 0; i \u003c nr_bds; i++) {\n+\t\t\tbd_dsc = req-\u003ebd_dsc_pool + i;\n+\t\t\tif (!bd_dsc-\u003eskb)\n+\t\t\t\tbreak;\n+\t\t\tif (!dma_mapping_error(mdev-\u003edev, bd_dsc-\u003edata_dma_addr))\n+\t\t\t\tdma_unmap_single(mdev-\u003edev, bd_dsc-\u003edata_dma_addr,\n+\t\t\t\t\t\t req-\u003efrag_size, DMA_FROM_DEVICE);\n+\t\t\tbd_dsc-\u003edata_dma_addr = 0;\n+\t\t\tbd_dsc-\u003eskb-\u003enext = NULL;\n+\t\t\tdev_kfree_skb_any(bd_dsc-\u003eskb);\n+\t\t}\n+\t} else {\n+\t\treq-\u003edata_dma_addr = 0;\n+\t\tif (req-\u003eskb)\n+\t\t\tdev_kfree_skb_any(req-\u003eskb);\n+\t}\n+\treq-\u003eskb = NULL;\n+\n+\treturn ret;\n+}\n+\n+static int mtk_cldma_check_rx_req(struct cldma_drv_info *drv_info, struct rxq *rxq)\n+{\n+\tstruct rx_req *req = rxq-\u003ereq_pool + rxq-\u003efree_idx;\n+\tu64 curr_addr;\n+\tint i;\n+\n+\tcurr_addr = drv_info-\u003edrv_ops-\u003ecldma_get_rx_curr_addr(drv_info, rxq-\u003erxqno);\n+\tif (unlikely(!curr_addr))\n+\t\treturn -ENXIO;\n+\n+\tif (req-\u003egpd_dma_addr == curr_addr)\n+\t\treturn -EAGAIN;\n+\tfor (i = 0; i \u003c WAIT_HWO_ROUND; i++) {\n+\t\tudelay(WAIT_HWO_TIME);\n+\t\tif (!(READ_ONCE(req-\u003egpd-\u003erx_gpd.gpd_flags) \u0026 CLDMA_GPD_FLAG_HWO))\n+\t\t\tbreak;\n+\t}\n+\tif (i == WAIT_HWO_ROUND) {\n+\t\tdev_err((drv_info-\u003emdev)-\u003edev, \"Failed to check HWO=0\\n\");\n+\t\treturn -EAGAIN;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static bool mtk_cldma_rx_check_again(struct rxq *rxq)\n+{\n+\tstruct cldma_drv_info *drv_info;\n+\tstruct cldma_drv_ops *drv_ops;\n+\tbool need_check_again = false;\n+\tu32 state;\n+\tint rxqno;\n+\n+\tdrv_info = rxq-\u003edrv_info;\n+\tdrv_ops = drv_info-\u003edrv_ops;\n+\trxqno = rxq-\u003erxqno;\n+\n+\tdo {\n+\t\tstate = drv_ops-\u003ecldma_check_intr_status(drv_info, DIR_RX,\n+\t\t\t\t\t\t\t rxqno, QUEUE_XFER_DONE);\n+\t\tif (state) {\n+\t\t\tif (unlikely(state == LINK_ERROR_VAL))\n+\t\t\t\tbreak;\n+\n+\t\t\tdrv_ops-\u003ecldma_clr_intr_status(drv_info, DIR_RX,\n+\t\t\t\t\t\t       rxqno, QUEUE_XFER_DONE);\n+\t\t\tcond_resched();\n+\t\t\treturn true;\n+\t\t}\n+\t} while (need_check_again);\n+\n+\treturn false;\n+}\n+\n+static void mtk_cldma_rx_done_work(struct work_struct *work)\n+{\n+\tstruct rx_req *req = NULL, *pre_req = NULL;\n+\tstruct rxq *rxq = container_of(work, struct rxq, rx_done_work);\n+\tstruct cldma_drv_info *drv_info;\n+\tstruct cldma_drv_ops *drv_ops;\n+\tstruct mtk_md_dev *mdev;\n+\tint i, ret, idx;\n+\n+\tdrv_info = rxq-\u003edrv_info;\n+\tmdev = drv_info-\u003emdev;\n+\tdrv_ops = drv_info-\u003edrv_ops;\n+\n+again:\n+\tfor (i = 0; i \u003c rxq-\u003enr_gpds; i++) {\n+\t\treq = rxq-\u003ereq_pool + rxq-\u003efree_idx;\n+\t\tif (!req-\u003eskb) {\n+\t\t\tdev_err(mdev-\u003edev,\n+\t\t\t\t\"Failed to get valid req cldma%d rxq%d req%d\\n\",\n+\t\t\t\tdrv_info-\u003ehw_id, rxq-\u003erxqno, rxq-\u003efree_idx);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (req-\u003egpd-\u003erx_gpd.gpd_flags \u0026 CLDMA_GPD_FLAG_HWO)\n+\t\t\tbreak;\n+\n+\t\tmtk_cldma_rx_skb_adjust(mdev, rxq, req);\n+\t\tdo {\n+\t\t\tret = rxq-\u003erx_done(req-\u003eskb, rxq-\u003earg,\n+\t\t\t\t\t   atomic_read(\u0026rxq-\u003eneed_exit) ? true : false);\n+\t\t\tif (ret == -EAGAIN)\n+\t\t\t\tusleep_range(1000, 2000);\n+\t\t\telse\n+\t\t\t\treq-\u003eskb = NULL;\n+\t\t} while (ret == -EAGAIN);\n+\n+\t\tret = mtk_cldma_reload_rx_skb(mdev, rxq, req);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\n+\t\twmb(); /* ensure addr set done before HWO setup done  */\n+\n+\t\tidx = rxq-\u003efree_idx == 0 ? rxq-\u003enr_gpds - 1 : rxq-\u003efree_idx - 1;\n+\t\tpre_req = rxq-\u003ereq_pool + idx;\n+\t\tpre_req-\u003egpd-\u003erx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;\n+\t\trxq-\u003efree_idx = (rxq-\u003efree_idx + 1) % rxq-\u003enr_gpds;\n+\t}\n+\n+\tret = mtk_cldma_check_rx_req(drv_info, rxq);\n+\tif (!ret)\n+\t\tgoto again;\n+\telse if (ret == -ENXIO)\n+\t\tgoto out;\n+\n+\tif (!atomic_read(\u0026rxq-\u003eneed_exit))\n+\t\tdrv_ops-\u003ecldma_resume_queue(drv_info, DIR_RX, rxq-\u003erxqno);\n+\n+\tif (mtk_cldma_rx_check_again(rxq))\n+\t\tgoto again;\n+\n+out:\n+\tdrv_ops-\u003ecldma_unmask_intr(drv_info, DIR_RX, rxq-\u003erxqno, QUEUE_XFER_DONE);\n+\tdrv_ops-\u003ecldma_clear_ip_busy(drv_info);\n+}\n+\n+static int mtk_cldma_alloc_tx_bd(struct cldma_drv_info *drv_info, struct txq *txq,\n+\t\t\t\t struct tx_req *req)\n+{\n+\tstruct bd_dsc *bd_dsc, *last_bd_dsc = NULL;\n+\tint i;\n+\n+\treq-\u003ebd_dsc_pool = devm_kcalloc(drv_info-\u003emdev-\u003edev, txq-\u003enr_bds,\n+\t\t\t\t\tsizeof(*bd_dsc), GFP_KERNEL);\n+\tif (!req-\u003ebd_dsc_pool)\n+\t\treturn -ENOMEM;\n+\n+\tfor (i = 0; i \u003c txq-\u003enr_bds; i++) {\n+\t\tbd_dsc = req-\u003ebd_dsc_pool + i;\n+\t\tbd_dsc-\u003ebd = dma_pool_zalloc(drv_info-\u003ebd_dma_pool, GFP_KERNEL,\n+\t\t\t\t\t     \u0026bd_dsc-\u003ebd_dma_addr);\n+\t\tif (!bd_dsc-\u003ebd)\n+\t\t\treturn -ENOMEM;\n+\t\tif (!last_bd_dsc) {\n+\t\t\treq-\u003egpd-\u003etx_gpd.data_buff_ptr_h =\n+\t\t\t\tcpu_to_le32((u64)(bd_dsc-\u003ebd_dma_addr) \u003e\u003e 32);\n+\t\t\treq-\u003egpd-\u003etx_gpd.data_buff_ptr_l =\n+\t\t\t\tcpu_to_le32(bd_dsc-\u003ebd_dma_addr);\n+\t\t} else {\n+\t\t\tlast_bd_dsc-\u003ebd-\u003etx_bd.next_bd_ptr_h =\n+\t\t\t\tcpu_to_le32((u64)(bd_dsc-\u003ebd_dma_addr) \u003e\u003e 32);\n+\t\t\tlast_bd_dsc-\u003ebd-\u003etx_bd.next_bd_ptr_l =\n+\t\t\t\tcpu_to_le32(bd_dsc-\u003ebd_dma_addr);\n+\t\t}\n+\t\tlast_bd_dsc = bd_dsc;\n+\t}\n+\treturn 0;\n+}\n+\n+static struct txq *mtk_cldma_txq_alloc(struct cldma_drv_info *drv_info, struct sk_buff *skb)\n+{\n+\tstruct trb *trb = (struct trb *)skb-\u003ecb;\n+\tstruct cldma_drv_ops *drv_ops;\n+\tstruct mtk_ctrl_trans *trans;\n+\tstruct mtk_ctrl_blk *ctrl_blk;\n+\tstruct mtk_md_dev *mdev;\n+\tstruct bd_dsc *bd_dsc;\n+\tstruct tx_req *next;\n+\tstruct tx_req *req;\n+\tu16 tx_frag_size;\n+\tstruct txq *txq;\n+\tint i, j, ret;\n+\n+\tmdev = drv_info-\u003emdev;\n+\tctrl_blk = mdev-\u003ectrl_blk;\n+\ttrans = ctrl_blk-\u003ectrl_hw_priv;\n+\tdrv_ops = drv_info-\u003edrv_ops;\n+\n+\ttxq = kzalloc_obj(*txq);\n+\tif (!txq)\n+\t\treturn NULL;\n+\n+\ttxq-\u003eque = radix_tree_lookup(\u0026trans-\u003equeue_tbl, trb-\u003echannel_id \u0026 0xFFFF);\n+\ttxq-\u003edrv_info = drv_info;\n+\ttxq-\u003etxqno = txq-\u003eque-\u003etxqno;\n+\ttxq-\u003enr_gpds = txq-\u003eque-\u003etx_nr_gpds;\n+\tatomic_set(\u0026txq-\u003ereq_budget, txq-\u003eque-\u003etx_nr_gpds);\n+\ttxq-\u003eis_stopping = false;\n+\ttx_frag_size = txq-\u003eque-\u003etx_frag_size;\n+\tif (txq-\u003eque-\u003etx_mtu \u003e tx_frag_size \u0026\u0026 tx_frag_size)\n+\t\ttxq-\u003enr_bds = (txq-\u003eque-\u003etx_mtu + tx_frag_size - 1) / tx_frag_size;\n+\n+\ttxq-\u003ereq_pool = devm_kcalloc(mdev-\u003edev, txq-\u003enr_gpds, sizeof(*req), GFP_KERNEL);\n+\tif (!txq-\u003ereq_pool)\n+\t\tgoto err_free_txq;\n+\n+\tfor (i = 0; i \u003c txq-\u003enr_gpds; i++) {\n+\t\treq = txq-\u003ereq_pool + i;\n+\t\treq-\u003emtu = txq-\u003eque-\u003etx_mtu;\n+\t\treq-\u003efrag_size = tx_frag_size;\n+\t\treq-\u003egpd = dma_pool_zalloc(drv_info-\u003egpd_dma_pool, GFP_KERNEL, \u0026req-\u003egpd_dma_addr);\n+\t\tif (!req-\u003egpd)\n+\t\t\tgoto err_free_req;\n+\t\tif (txq-\u003enr_bds) {\n+\t\t\tret = mtk_cldma_alloc_tx_bd(drv_info, txq, req);\n+\t\t\tif (ret)\n+\t\t\t\tgoto err_free_req;\n+\t\t\treq-\u003egpd-\u003etx_gpd.gpd_flags |= CLDMA_GPD_FLAG_BDP;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i \u003c txq-\u003enr_gpds; i++) {\n+\t\treq = txq-\u003ereq_pool + i;\n+\t\tnext = txq-\u003ereq_pool + ((i + 1) % txq-\u003enr_gpds);\n+\t\treq-\u003egpd-\u003etx_gpd.gpd_flags |= CLDMA_GPD_FLAG_IOC;\n+\t\treq-\u003egpd-\u003etx_gpd.next_gpd_ptr_h = cpu_to_le32((u64)(next-\u003egpd_dma_addr) \u003e\u003e 32);\n+\t\treq-\u003egpd-\u003etx_gpd.next_gpd_ptr_l = cpu_to_le32(next-\u003egpd_dma_addr);\n+\t}\n+\n+\tINIT_WORK(\u0026txq-\u003etx_done_work, mtk_cldma_tx_done_work);\n+\n+\tdrv_ops-\u003ecldma_stop_queue(drv_info, DIR_TX, txq-\u003etxqno);\n+\ttxq-\u003etx_started = false;\n+\tdrv_ops-\u003ecldma_setup_start_addr(drv_info, DIR_TX, txq-\u003etxqno,\n+\t\t\t\t\ttxq-\u003ereq_pool[0].gpd_dma_addr);\n+\tdrv_ops-\u003ecldma_unmask_intr(drv_info, DIR_TX, txq-\u003etxqno, QUEUE_ERROR);\n+\tdrv_ops-\u003ecldma_unmask_intr(drv_info, DIR_TX, txq-\u003etxqno, QUEUE_XFER_DONE);\n+\n+\tdrv_info-\u003etxq[txq-\u003etxqno] = txq;\n+\treturn txq;\n+\n+err_free_req:\n+\tfor (i = 0; i \u003c txq-\u003enr_gpds; i++) {\n+\t\treq = txq-\u003ereq_pool + i;\n+\t\tif (!req-\u003egpd)\n+\t\t\tbreak;\n+\t\tif (req-\u003ebd_dsc_pool) {\n+\t\t\tfor (j = 0; j \u003c txq-\u003enr_bds; j++) {\n+\t\t\t\tbd_dsc = req-\u003ebd_dsc_pool + j;\n+\t\t\t\tif (!bd_dsc-\u003ebd)\n+\t\t\t\t\tbreak;\n+\t\t\t\tdma_pool_free(drv_info-\u003ebd_dma_pool, bd_dsc-\u003ebd,\n+\t\t\t\t\t      bd_dsc-\u003ebd_dma_addr);\n+\t\t\t}\n+\t\t\tkfree(req-\u003ebd_dsc_pool);\n+\t\t}\n+\t\tdma_pool_free(drv_info-\u003egpd_dma_pool, req-\u003egpd, req-\u003egpd_dma_addr);\n+\t}\n+\tkfree(txq-\u003ereq_pool);\n+err_free_txq:\n+\tkfree(txq);\n+\treturn NULL;\n+}\n+\n+static void mtk_cldma_txq_free(struct cldma_drv_info *drv_info, u32 txqno)\n+{\n+\tstruct cldma_drv_ops *drv_ops;\n+\tstruct mtk_md_dev *mdev;\n+\tstruct bd_dsc *bd_dsc;\n+\tstruct tx_req *req;\n+\tstruct txq *txq;\n+\tstruct trb *trb;\n+\tint irq_id;\n+\tint i, j;\n+\n+\tmdev = drv_info-\u003emdev;\n+\tdrv_ops = drv_info-\u003edrv_ops;\n+\n+\ttxq = drv_info-\u003etxq[txqno];\n+\tdrv_info-\u003etxq[txqno] = NULL;\n+\t/* stop HW tx transaction */\n+\tdrv_ops-\u003ecldma_stop_queue(drv_info, DIR_TX, txqno);\n+\ttxq-\u003etx_started = false;\n+\n+\tirq_id = mtk_pci_get_virq_id(mdev, drv_info-\u003epci_ext_irq_id);\n+\tsynchronize_irq(irq_id);\n+\t/* flush on-going work */\n+\tflush_work(\u0026txq-\u003etx_done_work);\n+\tdrv_ops-\u003ecldma_mask_intr(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);\n+\tdrv_ops-\u003ecldma_mask_intr(drv_info, DIR_TX, txqno, QUEUE_ERROR);\n+\n+\t/* free tx req resource */\n+\tfor (i = 0; i \u003c txq-\u003enr_gpds; i++) {\n+\t\treq = txq-\u003ereq_pool + txq-\u003efree_idx;\n+\t\tif (req-\u003eskb \u0026\u0026 req-\u003edata_len) {\n+\t\t\tif (!txq-\u003enr_bds)\n+\t\t\t\tdma_unmap_single(mdev-\u003edev, req-\u003edata_dma_addr,\n+\t\t\t\t\t\t req-\u003edata_len, DMA_TO_DEVICE);\n+\t\t\tfor (j = 0; j \u003c txq-\u003enr_bds; j++) {\n+\t\t\t\tbd_dsc = req-\u003ebd_dsc_pool + j;\n+\t\t\t\tif (!bd_dsc-\u003edata_dma_addr)\n+\t\t\t\t\tcontinue;\n+\t\t\t\tdma_unmap_single(mdev-\u003edev, bd_dsc-\u003edata_dma_addr,\n+\t\t\t\t\t\t bd_dsc-\u003edata_len, DMA_TO_DEVICE);\n+\t\t\t}\n+\t\t\ttrb = (struct trb *)req-\u003eskb-\u003ecb;\n+\t\t\ttrb-\u003estatus = -EPIPE;\n+\t\t\ttrb-\u003etrb_complete(req-\u003eskb);\n+\t\t}\n+\t\tfor (j = 0; j \u003c txq-\u003enr_bds; j++) {\n+\t\t\tbd_dsc = req-\u003ebd_dsc_pool + j;\n+\t\t\tdma_pool_free(drv_info-\u003ebd_dma_pool, bd_dsc-\u003ebd,\n+\t\t\t\t      bd_dsc-\u003ebd_dma_addr);\n+\t\t}\n+\t\tkfree(req-\u003ebd_dsc_pool);\n+\t\tdma_pool_free(drv_info-\u003egpd_dma_pool, req-\u003egpd, req-\u003egpd_dma_addr);\n+\t\ttxq-\u003efree_idx = (txq-\u003efree_idx + 1) % txq-\u003enr_gpds;\n+\t}\n+\n+\tkfree(txq-\u003ereq_pool);\n+\tkfree(txq);\n+}\n+\n+static int mtk_cldma_alloc_rx_bd(struct cldma_drv_info *drv_info, struct rx_req *req,\n+\t\t\t\t int nr_bds)\n+{\n+\tstruct bd_dsc *bd_dsc, *last_bd_dsc = NULL;\n+\tstruct sk_buff *tail = NULL;\n+\tstruct mtk_md_dev *mdev;\n+\tu32 left_size;\n+\tint ret;\n+\tint i;\n+\n+\tmdev = drv_info-\u003emdev;\n+\tleft_size = req-\u003emtu;\n+\n+\treq-\u003ebd_dsc_pool = devm_kcalloc(mdev-\u003edev, nr_bds,\n+\t\t\t\t\tsizeof(*bd_dsc), GFP_KERNEL);\n+\tif (!req-\u003ebd_dsc_pool)\n+\t\treturn -ENOMEM;\n+\tfor (i = 0; i \u003c nr_bds; i++) {\n+\t\tbd_dsc = req-\u003ebd_dsc_pool + i;\n+\t\tbd_dsc-\u003ebd = dma_pool_zalloc(drv_info-\u003ebd_dma_pool, GFP_KERNEL,\n+\t\t\t\t\t     \u0026bd_dsc-\u003ebd_dma_addr);\n+\t\tif (!bd_dsc-\u003ebd)\n+\t\t\treturn -ENOMEM;\n+\n+\t\tbd_dsc-\u003eskb = __dev_alloc_skb(req-\u003efrag_size, GFP_KERNEL);\n+\t\tif (!bd_dsc-\u003eskb)\n+\t\t\treturn -ENOMEM;\n+\t\tbd_dsc-\u003eskb-\u003enext = NULL;\n+\t\tbd_dsc-\u003edata_dma_addr =\n+\t\t\tdma_map_single(mdev-\u003edev, bd_dsc-\u003eskb-\u003edata,\n+\t\t\t\t       req-\u003efrag_size, DMA_FROM_DEVICE);\n+\t\tret = dma_mapping_error(mdev-\u003edev, bd_dsc-\u003edata_dma_addr);\n+\t\tif (unlikely(ret))\n+\t\t\treturn -ENOMEM;\n+\n+\t\tbd_dsc-\u003ebd-\u003erx_bd.data_buff_ptr_h =\n+\t\t\tcpu_to_le32((u64)(bd_dsc-\u003edata_dma_addr) \u003e\u003e 32);\n+\t\tbd_dsc-\u003ebd-\u003erx_bd.data_buff_ptr_l =\n+\t\t\tcpu_to_le32(bd_dsc-\u003edata_dma_addr);\n+\t\tbd_dsc-\u003ebd-\u003erx_bd.data_allow_len =\n+\t\t\tcpu_to_le16(min(req-\u003efrag_size, left_size));\n+\t\tleft_size -= min(req-\u003efrag_size, left_size);\n+\t\tif (!last_bd_dsc) {\n+\t\t\treq-\u003egpd-\u003erx_gpd.data_buff_ptr_h =\n+\t\t\t\tcpu_to_le32((u64)(bd_dsc-\u003ebd_dma_addr) \u003e\u003e 32);\n+\t\t\treq-\u003egpd-\u003erx_gpd.data_buff_ptr_l =\n+\t\t\t\tcpu_to_le32(bd_dsc-\u003ebd_dma_addr);\n+\t\t} else {\n+\t\t\tlast_bd_dsc-\u003ebd-\u003erx_bd.next_bd_ptr_h =\n+\t\t\t\tcpu_to_le32((u64)(bd_dsc-\u003ebd_dma_addr) \u003e\u003e 32);\n+\t\t\tlast_bd_dsc-\u003ebd-\u003erx_bd.next_bd_ptr_l =\n+\t\t\t\tcpu_to_le32(bd_dsc-\u003ebd_dma_addr);\n+\t\t}\n+\t\tlast_bd_dsc = bd_dsc;\n+\t\tif (tail) {\n+\t\t\ttail-\u003enext = bd_dsc-\u003eskb;\n+\t\t\ttail = bd_dsc-\u003eskb;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!req-\u003eskb) {\n+\t\t\treq-\u003eskb = bd_dsc-\u003eskb;\n+\t\t} else {\n+\t\t\tskb_shinfo(req-\u003eskb)-\u003efrag_list = bd_dsc-\u003eskb;\n+\t\t\ttail = bd_dsc-\u003eskb;\n+\t\t}\n+\t}\n+\tlast_bd_dsc-\u003ebd-\u003erx_bd.bd_flags |= CLDMA_BD_FLAG_EOL;\n+\treturn 0;\n+}\n+\n+static void mtk_cldma_rxq_alloc_cancel(struct cldma_drv_info *drv_info, struct rx_req *req,\n+\t\t\t\t       int nr_bds)\n+{\n+\tstruct mtk_md_dev *mdev;\n+\tstruct bd_dsc *bd_dsc;\n+\tint i;\n+\n+\tmdev = drv_info-\u003emdev;\n+\n+\tif (nr_bds) {\n+\t\tif (req-\u003eskb)\n+\t\t\tskb_shinfo(req-\u003eskb)-\u003efrag_list = NULL;\n+\t\tif (req-\u003ebd_dsc_pool) {\n+\t\t\tfor (i = 0; i \u003c nr_bds; i++) {\n+\t\t\t\tbd_dsc = req-\u003ebd_dsc_pool + i;\n+\t\t\t\tif (!bd_dsc-\u003ebd)\n+\t\t\t\t\tbreak;\n+\t\t\t\tif (bd_dsc-\u003eskb) {\n+\t\t\t\t\tif (!dma_mapping_error(mdev-\u003edev, bd_dsc-\u003edata_dma_addr))\n+\t\t\t\t\t\tdma_unmap_single(mdev-\u003edev, bd_dsc-\u003edata_dma_addr,\n+\t\t\t\t\t\t\t\t req-\u003efrag_size, DMA_FROM_DEVICE);\n+\t\t\t\t\tbd_dsc-\u003edata_dma_addr = 0;\n+\t\t\t\t\tbd_dsc-\u003eskb-\u003enext = NULL;\n+\t\t\t\t\tdev_kfree_skb_any(bd_dsc-\u003eskb);\n+\t\t\t\t}\n+\t\t\t\tdma_pool_free(drv_info-\u003ebd_dma_pool, bd_dsc-\u003ebd,\n+\t\t\t\t\t      bd_dsc-\u003ebd_dma_addr);\n+\t\t\t}\n+\t\t\tkfree(req-\u003ebd_dsc_pool);\n+\t\t}\n+\t} else {\n+\t\tif (req-\u003eskb) {\n+\t\t\tif (!dma_mapping_error(mdev-\u003edev, req-\u003edata_dma_addr))\n+\t\t\t\tdma_unmap_single(mdev-\u003edev, req-\u003edata_dma_addr,\n+\t\t\t\t\t\t req-\u003emtu, DMA_FROM_DEVICE);\n+\t\t\treq-\u003edata_dma_addr = 0;\n+\t\t\tdev_kfree_skb_any(req-\u003eskb);\n+\t\t}\n+\t}\n+\tdma_pool_free(drv_info-\u003egpd_dma_pool, req-\u003egpd, req-\u003egpd_dma_addr);\n+}\n+\n+static struct rxq *mtk_cldma_rxq_alloc(struct cldma_drv_info *drv_info, struct sk_buff *skb)\n+{\n+\tstruct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb-\u003edata;\n+\tstruct trb *trb = (struct trb *)skb-\u003ecb;\n+\tstruct cldma_drv_ops *drv_ops;\n+\tstruct mtk_ctrl_trans *trans;\n+\tstruct mtk_ctrl_blk *ctrl_blk;\n+\tstruct mtk_md_dev *mdev;\n+\tstruct rx_req *next;\n+\tstruct rx_req *req;\n+\tu16 rx_frag_size;\n+\tstruct rxq *rxq;\n+\tint ret;\n+\tint i;\n+\n+\tmdev = drv_info-\u003emdev;\n+\tctrl_blk = mdev-\u003ectrl_blk;\n+\ttrans = ctrl_blk-\u003ectrl_hw_priv;\n+\tdrv_ops = drv_info-\u003edrv_ops;\n+\n+\trxq = kzalloc_obj(*rxq);\n+\tif (!rxq)\n+\t\treturn NULL;\n+\n+\trxq-\u003eque = radix_tree_lookup(\u0026trans-\u003equeue_tbl, trb-\u003echannel_id \u0026 0xFFFF);\n+\tif (rxq-\u003eque-\u003erx_nr_gpds \u003c MIN_GPD_NUM) {\n+\t\tdev_err(mdev-\u003edev,\n+\t\t\t\"Failed to alloc cldma%d rxq%d due to gpd number \u003c 2\\n\",\n+\t\t\tdrv_info-\u003ehw_id, rxq-\u003erxqno);\n+\t\tgoto err_free_rxq;\n+\t}\n+\trxq-\u003edrv_info = drv_info;\n+\trxq-\u003erxqno = rxq-\u003eque-\u003erxqno;\n+\trxq-\u003enr_gpds = rxq-\u003eque-\u003erx_nr_gpds;\n+\trxq-\u003earg = trb-\u003epriv;\n+\trxq-\u003erx_done = trb_open_priv-\u003erx_done;\n+\tatomic_set(\u0026rxq-\u003eneed_exit, 0);\n+\trx_frag_size = rxq-\u003eque-\u003erx_frag_size;\n+\tif (rxq-\u003eque-\u003erx_mtu \u003e rx_frag_size \u0026\u0026 rx_frag_size)\n+\t\trxq-\u003enr_bds = (rxq-\u003eque-\u003erx_mtu + rx_frag_size - 1) / rx_frag_size;\n+\n+\trxq-\u003ereq_pool = devm_kcalloc(mdev-\u003edev, rxq-\u003enr_gpds, sizeof(*req), GFP_KERNEL);\n+\tif (!rxq-\u003ereq_pool)\n+\t\tgoto err_free_rxq;\n+\n+\t/* setup rx request */\n+\tfor (i = 0; i \u003c rxq-\u003enr_gpds; i++) {\n+\t\treq = rxq-\u003ereq_pool + i;\n+\t\treq-\u003emtu = rxq-\u003eque-\u003erx_mtu;\n+\t\treq-\u003efrag_size = rx_frag_size;\n+\t\treq-\u003egpd = dma_pool_zalloc(drv_info-\u003egpd_dma_pool, GFP_KERNEL, \u0026req-\u003egpd_dma_addr);\n+\t\tif (!req-\u003egpd)\n+\t\t\tgoto err_free_req;\n+\t\tif (rxq-\u003enr_bds) {\n+\t\t\tret = mtk_cldma_alloc_rx_bd(drv_info, req, rxq-\u003enr_bds);\n+\t\t\tif (ret)\n+\t\t\t\tgoto err_free_req;\n+\t\t\treq-\u003egpd-\u003erx_gpd.gpd_flags |= CLDMA_GPD_FLAG_BDP;\n+\t\t} else {\n+\t\t\treq-\u003eskb = __dev_alloc_skb(req-\u003emtu, GFP_KERNEL);\n+\t\t\tif (!req-\u003eskb)\n+\t\t\t\tgoto err_free_req;\n+\t\t\treq-\u003edata_dma_addr = dma_map_single(mdev-\u003edev, req-\u003eskb-\u003edata,\n+\t\t\t\t\t\t\t    req-\u003emtu, DMA_FROM_DEVICE);\n+\t\t\tret = dma_mapping_error(mdev-\u003edev, req-\u003edata_dma_addr);\n+\t\t\tif (unlikely(ret))\n+\t\t\t\tgoto err_free_req;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i \u003c rxq-\u003enr_gpds; i++) {\n+\t\treq = rxq-\u003ereq_pool + i;\n+\t\tnext = rxq-\u003ereq_pool + ((i + 1) % rxq-\u003enr_gpds);\n+\t\treq-\u003egpd-\u003erx_gpd.gpd_flags |= CLDMA_GPD_FLAG_IOC;\n+\t\treq-\u003egpd-\u003erx_gpd.data_allow_len = cpu_to_le16(req-\u003emtu);\n+\t\treq-\u003egpd-\u003erx_gpd.next_gpd_ptr_h = cpu_to_le32((u64)(next-\u003egpd_dma_addr) \u003e\u003e 32);\n+\t\treq-\u003egpd-\u003erx_gpd.next_gpd_ptr_l = cpu_to_le32(next-\u003egpd_dma_addr);\n+\t\tif (!rxq-\u003enr_bds) {\n+\t\t\treq-\u003egpd-\u003erx_gpd.data_buff_ptr_h =\n+\t\t\t\tcpu_to_le32((u64)(req-\u003edata_dma_addr) \u003e\u003e 32);\n+\t\t\treq-\u003egpd-\u003erx_gpd.data_buff_ptr_l = cpu_to_le32(req-\u003edata_dma_addr);\n+\t\t}\n+\t\tif (i != rxq-\u003enr_gpds - 1)\n+\t\t\treq-\u003egpd-\u003erx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;\n+\t}\n+\n+\tINIT_WORK(\u0026rxq-\u003erx_done_work, mtk_cldma_rx_done_work);\n+\n+\tdrv_info-\u003erxq[rxq-\u003erxqno] = rxq;\n+\tdrv_ops-\u003ecldma_stop_queue(drv_info, DIR_RX, rxq-\u003erxqno);\n+\tdrv_ops-\u003ecldma_setup_start_addr(drv_info, DIR_RX,\n+\t\t\t\t\trxq-\u003erxqno, rxq-\u003ereq_pool[0].gpd_dma_addr);\n+\tdrv_ops-\u003ecldma_start_queue(drv_info, DIR_RX, rxq-\u003erxqno);\n+\tdrv_ops-\u003ecldma_unmask_intr(drv_info, DIR_RX, rxq-\u003erxqno, QUEUE_ERROR);\n+\tdrv_ops-\u003ecldma_unmask_intr(drv_info, DIR_RX, rxq-\u003erxqno, QUEUE_XFER_DONE);\n+\n+\treturn rxq;\n+\n+err_free_req:\n+\tfor (i = 0; i \u003c rxq-\u003enr_gpds; i++) {\n+\t\treq = rxq-\u003ereq_pool + i;\n+\t\tif (!req-\u003egpd)\n+\t\t\tbreak;\n+\t\tmtk_cldma_rxq_alloc_cancel(drv_info, req, rxq-\u003enr_bds);\n+\t}\n+\n+\tkfree(rxq-\u003ereq_pool);\n+err_free_rxq:\n+\tkfree(rxq);\n+\treturn NULL;\n+}\n+\n+static void mtk_cldma_rxq_free(struct cldma_drv_info *drv_info, u32 rxqno)\n+{\n+\tstruct cldma_drv_ops *drv_ops;\n+\tstruct mtk_md_dev *mdev;\n+\tstruct bd_dsc *bd_dsc;\n+\tstruct rx_req *req;\n+\tstruct rxq *rxq;\n+\tint irq_id;\n+\tint i, j;\n+\n+\tmdev = drv_info-\u003emdev;\n+\tdrv_ops = drv_info-\u003edrv_ops;\n+\n+\trxq = drv_info-\u003erxq[rxqno];\n+\tdrv_info-\u003erxq[rxqno] = NULL;\n+\n+\t/* stop HW rx transaction */\n+\tatomic_set(\u0026rxq-\u003eneed_exit, 1);\n+\tdrv_ops-\u003ecldma_stop_queue(drv_info, DIR_RX, rxqno);\n+\n+\tirq_id = mtk_pci_get_virq_id(mdev, drv_info-\u003epci_ext_irq_id);\n+\tsynchronize_irq(irq_id);\n+\t/* flush on-going work */\n+\tflush_work(\u0026rxq-\u003erx_done_work);\n+\t/* mask L2 RX interrupt again to avoid race condition causing use-after-free issue */\n+\tdrv_ops-\u003ecldma_mask_intr(drv_info, DIR_RX, rxqno, QUEUE_XFER_DONE);\n+\tdrv_ops-\u003ecldma_mask_intr(drv_info, DIR_RX, rxqno, QUEUE_ERROR);\n+\n+\t/* free rx req resource */\n+\tfor (i = 0; i \u003c rxq-\u003enr_gpds; i++) {\n+\t\treq = rxq-\u003ereq_pool + rxq-\u003efree_idx;\n+\t\tif (!(req-\u003egpd-\u003erx_gpd.gpd_flags \u0026 CLDMA_GPD_FLAG_HWO) \u0026\u0026\n+\t\t    le16_to_cpu(req-\u003egpd-\u003erx_gpd.data_recv_len)) {\n+\t\t\tmtk_cldma_rx_skb_adjust(mdev, rxq, req);\n+\t\t\trxq-\u003erx_done(req-\u003eskb, rxq-\u003earg, true);\n+\t\t\treq-\u003eskb = NULL;\n+\t\t}\n+\t\tif (req-\u003eskb) {\n+\t\t\tif (rxq-\u003enr_bds) {\n+\t\t\t\tskb_shinfo(req-\u003eskb)-\u003efrag_list = NULL;\n+\t\t\t\tdev_kfree_skb_any(req-\u003eskb);\n+\t\t\t} else {\n+\t\t\t\tif (req-\u003edata_dma_addr)\n+\t\t\t\t\tdma_unmap_single(mdev-\u003edev, req-\u003edata_dma_addr,\n+\t\t\t\t\t\t\t req-\u003emtu, DMA_FROM_DEVICE);\n+\t\t\t\tdev_kfree_skb_any(req-\u003eskb);\n+\t\t\t}\n+\t\t}\n+\t\tfor (j = 0; j \u003c rxq-\u003enr_bds; j++) {\n+\t\t\tbd_dsc = req-\u003ebd_dsc_pool + j;\n+\t\t\tif (bd_dsc-\u003eskb) {\n+\t\t\t\tif (bd_dsc-\u003edata_dma_addr)\n+\t\t\t\t\tdma_unmap_single(mdev-\u003edev, bd_dsc-\u003edata_dma_addr,\n+\t\t\t\t\t\t\t req-\u003efrag_size, DMA_FROM_DEVICE);\n+\t\t\t\tbd_dsc-\u003eskb-\u003enext = NULL;\n+\t\t\t\tdev_kfree_skb_any(bd_dsc-\u003eskb);\n+\t\t\t}\n+\t\t\tdma_pool_free(drv_info-\u003ebd_dma_pool,\n+\t\t\t\t      bd_dsc-\u003ebd, bd_dsc-\u003ebd_dma_addr);\n+\t\t}\n+\t\tkfree(req-\u003ebd_dsc_pool);\n+\t\tdma_pool_free(drv_info-\u003egpd_dma_pool, req-\u003egpd, req-\u003egpd_dma_addr);\n+\t\trxq-\u003efree_idx = (rxq-\u003efree_idx + 1) % rxq-\u003enr_gpds;\n+\t}\n+\n+\tkfree(rxq-\u003ereq_pool);\n+\tkfree(rxq);\n+}\n+\n+static int mtk_cldma_dev_exit(struct cldma_dev *cd, int hif_id)\n+{\n+\tstruct cldma_drv_info *drv_info;\n+\tstruct mtk_md_dev *mdev;\n+\tint virq_id;\n+\tint i;\n+\n+\tif (!cd || hif_id \u003e= NR_CLDMA)\n+\t\treturn -EINVAL;\n+\n+\tif (!cd-\u003ecldma_drv_info[hif_id])\n+\t\treturn 0;\n+\n+\t/* free cldma descriptor */\n+\tdrv_info = cd-\u003ecldma_drv_info[hif_id];\n+\tmdev = cd-\u003etrans-\u003emdev;\n+\tvirq_id = mtk_pci_get_virq_id(mdev, drv_info-\u003epci_ext_irq_id);\n+\tmtk_pci_mask_irq(mdev, drv_info-\u003epci_ext_irq_id);\n+\tsynchronize_irq(virq_id);\n+\tfor (i = 0; i \u003c HW_QUEUE_NUM; i++) {\n+\t\tif (drv_info-\u003etxq[i])\n+\t\t\tmtk_cldma_txq_free(drv_info, drv_info-\u003etxq[i]-\u003etxqno);\n+\t\tif (drv_info-\u003erxq[i])\n+\t\t\tmtk_cldma_rxq_free(drv_info, drv_info-\u003erxq[i]-\u003erxqno);\n+\t}\n+\n+\tflush_workqueue(drv_info-\u003ewq);\n+\tdestroy_workqueue(drv_info-\u003ewq);\n+\tdma_pool_destroy(drv_info-\u003ebd_dma_pool);\n+\tdma_pool_destroy(drv_info-\u003egpd_dma_pool);\n+\tmtk_pci_unregister_irq(mdev, drv_info-\u003epci_ext_irq_id);\n+\n+\tkfree(drv_info);\n+\tcd-\u003ecldma_drv_info[hif_id] = NULL;\n+\n+\treturn 0;\n+}\n+\n+static int mtk_cldma_start_xfer(struct cldma_drv_info *drv_info, u32 qno)\n+{\n+\tstruct cldma_drv_ops *drv_ops;\n+\tstruct txq *txq;\n+\tu32 val;\n+\n+\ttxq = drv_info-\u003etxq[qno];\n+\tdrv_ops = drv_info-\u003edrv_ops;\n+\n+\tval = drv_ops-\u003ecldma_get_tx_start_addr(drv_info, qno);\n+\tif (unlikely(val == LINK_ERROR_VAL))\n+\t\treturn -EIO;\n+\n+\tif (unlikely(!val)) {\n+\t\tdrv_ops-\u003ecldma_drv_init(drv_info);\n+\t\ttxq = drv_info-\u003etxq[qno];\n+\t\tdrv_ops-\u003ecldma_setup_start_addr(drv_info, DIR_TX, qno,\n+\t\t\t\t\t\ttxq-\u003ereq_pool[txq-\u003efree_idx].gpd_dma_addr);\n+\t\tdrv_ops-\u003ecldma_start_queue(drv_info, DIR_TX, qno);\n+\t\ttxq-\u003etx_started = true;\n+\t} else if (unlikely(!txq-\u003etx_started)) {\n+\t\tdrv_ops-\u003ecldma_start_queue(drv_info, DIR_TX, qno);\n+\t\ttxq-\u003etx_started = true;\n+\t} else {\n+\t\tdrv_ops-\u003ecldma_resume_queue(drv_info, DIR_TX, qno);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int mtk_cldma_init(struct mtk_ctrl_trans *trans)\n+{\n+\tstruct cldma_dev *cd;\n+\n+\tcd = kzalloc_obj(*cd);\n+\tif (!cd)\n+\t\treturn -ENOMEM;\n+\n+\tcd-\u003etrans = trans;\n+\ttrans-\u003edev = cd;\n+\n+\treturn 0;\n+}\n+\n+void mtk_cldma_exit(struct mtk_ctrl_trans *trans)\n+{\n+\tif (!trans-\u003edev)\n+\t\treturn;\n+\n+\tkfree(trans-\u003edev);\n+\ttrans-\u003edev = NULL;\n+}\n+\n+static int mtk_cldma_open(struct cldma_dev *cd, struct sk_buff *skb)\n+{\n+\tstruct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb-\u003edata;\n+\tstruct trb *trb = (struct trb *)skb-\u003ecb;\n+\tstruct cldma_drv_info *drv_info;\n+\tstruct queue_info *que;\n+\tstruct txq *txq;\n+\tstruct rxq *rxq;\n+\tint ret = 0;\n+\n+\tque = radix_tree_lookup(\u0026cd-\u003etrans-\u003equeue_tbl, trb-\u003echannel_id \u0026 0xFFFF);\n+\tdrv_info = cd-\u003ecldma_drv_info[que-\u003ehif_id];\n+\tif (!drv_info) {\n+\t\tret = -EIO;\n+\t\tgoto out;\n+\t}\n+\n+\tif (que-\u003etx_mtu == 0 || que-\u003erx_mtu == 0) {\n+\t\tdev_err((cd-\u003etrans-\u003emdev)-\u003edev,\n+\t\t\t\"Failed to enable cldma%d txq%d rxq%d due to wrong mtu\\n\",\n+\t\t\tdrv_info-\u003ehw_id, que-\u003etxqno, que-\u003erxqno);\n+\t\tret = -EINVAL;\n+\t\tgoto out;\n+\t}\n+\n+\ttrb_open_priv-\u003etx_mtu = que-\u003etx_mtu;\n+\ttrb_open_priv-\u003erx_mtu = que-\u003erx_mtu;\n+\ttrb_open_priv-\u003etx_frag_size = que-\u003etx_frag_size;\n+\ttrb_open_priv-\u003erx_frag_size = que-\u003erx_frag_size;\n+\n+\tif (drv_info-\u003etxq[que-\u003etxqno] || drv_info-\u003erxq[que-\u003erxqno]) {\n+\t\tret = -EBUSY;\n+\t\tgoto out;\n+\t}\n+\n+\ttxq = mtk_cldma_txq_alloc(drv_info, skb);\n+\tif (!txq) {\n+\t\tret = -ENOMEM;\n+\t\tgoto out;\n+\t}\n+\n+\trxq = mtk_cldma_rxq_alloc(drv_info, skb);\n+\tif (!rxq) {\n+\t\tret = -ENOMEM;\n+\t\tmtk_cldma_txq_free(drv_info, txq-\u003etxqno);\n+\t\tgoto out;\n+\t}\n+\n+out:\n+\ttrb-\u003estatus = ret;\n+\ttrb-\u003etrb_complete(skb);\n+\n+\treturn ret;\n+}\n+\n+static int mtk_cldma_tx(struct cldma_dev *cd, struct sk_buff *skb)\n+{\n+\tstruct trb *trb = (struct trb *)skb-\u003ecb;\n+\tstruct cldma_drv_info *drv_info;\n+\tstruct mtk_md_dev *mdev;\n+\tstruct queue_info *que;\n+\tstruct txq *txq;\n+\tint ret;\n+\n+\tque = radix_tree_lookup(\u0026cd-\u003etrans-\u003equeue_tbl, trb-\u003echannel_id \u0026 0xFFFF);\n+\tdrv_info = cd-\u003ecldma_drv_info[que-\u003ehif_id];\n+\tif (unlikely(!drv_info))\n+\t\treturn -EPIPE;\n+\ttxq = drv_info-\u003etxq[que-\u003etxqno];\n+\tif (unlikely(!txq) || txq-\u003eis_stopping)\n+\t\treturn -EPIPE;\n+\n+\tmdev = drv_info-\u003emdev;\n+\n+\tret = mtk_cldma_start_xfer(drv_info, que-\u003etxqno);\n+\tif (unlikely(ret))\n+\t\tdev_err(mdev-\u003edev, \"Failed to trigger cldma tx\\n\");\n+\n+\treturn ret;\n+}\n+\n+static int mtk_cldma_close(struct cldma_dev *cd, struct sk_buff *skb)\n+{\n+\tstruct trb *trb = (struct trb *)skb-\u003ecb;\n+\tstruct cldma_drv_info *drv_info;\n+\tstruct queue_info *que;\n+\n+\tque = radix_tree_lookup(\u0026cd-\u003etrans-\u003equeue_tbl, trb-\u003echannel_id \u0026 0xFFFF);\n+\tdrv_info = cd-\u003ecldma_drv_info[que-\u003ehif_id];\n+\tif (unlikely(!drv_info))\n+\t\treturn -EPIPE;\n+\n+\tif (drv_info-\u003etxq[que-\u003etxqno])\n+\t\tmtk_cldma_txq_free(drv_info, que-\u003etxqno);\n+\tif (drv_info-\u003erxq[que-\u003erxqno])\n+\t\tmtk_cldma_rxq_free(drv_info, que-\u003erxqno);\n+\n+\ttrb-\u003estatus = 0;\n+\ttrb-\u003etrb_complete(skb);\n+\n+\treturn 0;\n+}\n+\n+static int mtk_cldma_txbuf_set(struct cldma_drv_info *drv_info, struct sk_buff *skb,\n+\t\t\t       struct tx_req *req, int nr_bds)\n+{\n+\tstruct sk_buff *curr_skb, *next_skb;\n+\tstruct mtk_md_dev *mdev;\n+\tstruct bd_dsc *bd_dsc;\n+\tint ret;\n+\tint i;\n+\n+\tmdev = drv_info-\u003emdev;\n+\n+\tif (nr_bds) {\n+\t\tbd_dsc = req-\u003ebd_dsc_pool;\n+\t\tcurr_skb = skb;\n+\t\tfor (i = 0; i \u003c nr_bds \u0026\u0026 curr_skb; i++) {\n+\t\t\tbd_dsc = req-\u003ebd_dsc_pool + i;\n+\t\t\tif (req-\u003ebd_dsc_pool == bd_dsc) {\n+\t\t\t\tbd_dsc-\u003edata_len = skb-\u003elen - skb-\u003edata_len;\n+\t\t\t\tnext_skb = skb_shinfo(skb)-\u003efrag_list;\n+\t\t\t} else {\n+\t\t\t\tbd_dsc-\u003edata_len = curr_skb-\u003elen;\n+\t\t\t\tnext_skb = curr_skb-\u003enext;\n+\t\t\t}\n+\t\t\tbd_dsc-\u003edata_dma_addr = dma_map_single(mdev-\u003edev, curr_skb-\u003edata,\n+\t\t\t\t\t\t\t       bd_dsc-\u003edata_len, DMA_TO_DEVICE);\n+\t\t\tret = dma_mapping_error(mdev-\u003edev, bd_dsc-\u003edata_dma_addr);\n+\t\t\tif (unlikely(ret))\n+\t\t\t\tgoto err_unmap_buffer;\n+\n+\t\t\tbd_dsc-\u003ebd-\u003etx_bd.data_buff_ptr_h =\n+\t\t\t\tcpu_to_le32((u64)(bd_dsc-\u003edata_dma_addr) \u003e\u003e 32);\n+\t\t\tbd_dsc-\u003ebd-\u003etx_bd.data_buff_ptr_l = cpu_to_le32(bd_dsc-\u003edata_dma_addr);\n+\t\t\tbd_dsc-\u003ebd-\u003etx_bd.data_buffer_len = cpu_to_le16(bd_dsc-\u003edata_len);\n+\t\t\tcurr_skb = next_skb;\n+\t\t}\n+\t\tbd_dsc-\u003ebd-\u003etx_bd.bd_flags = CLDMA_BD_FLAG_EOL;\n+\t} else {\n+\t\treq-\u003edata_dma_addr = dma_map_single(mdev-\u003edev, skb-\u003edata,\n+\t\t\t\t\t\t    skb-\u003elen, DMA_TO_DEVICE);\n+\t\tret = dma_mapping_error(mdev-\u003edev, req-\u003edata_dma_addr);\n+\t\tif (unlikely(ret)) {\n+\t\t\treq-\u003edata_dma_addr = 0;\n+\t\t\tgoto err_exit;\n+\t\t}\n+\n+\t\treq-\u003egpd-\u003etx_gpd.data_buff_ptr_h = cpu_to_le32((u64)(req-\u003edata_dma_addr) \u003e\u003e 32);\n+\t\treq-\u003egpd-\u003etx_gpd.data_buff_ptr_l = cpu_to_le32(req-\u003edata_dma_addr);\n+\t}\n+\n+\treturn 0;\n+\n+err_unmap_buffer:\n+\tfor (i = 0; i \u003c nr_bds; i++) {\n+\t\tbd_dsc = req-\u003ebd_dsc_pool + i;\n+\t\tif (dma_mapping_error(mdev-\u003edev, bd_dsc-\u003edata_dma_addr)) {\n+\t\t\tbd_dsc-\u003edata_dma_addr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tdma_unmap_single(mdev-\u003edev, bd_dsc-\u003edata_dma_addr,\n+\t\t\t\t bd_dsc-\u003edata_len, DMA_TO_DEVICE);\n+\t\tbd_dsc-\u003edata_dma_addr = 0;\n+\t}\n+err_exit:\n+\tdev_err(mdev-\u003edev, \"Failed to map dma! error:%d\\n\", ret);\n+\treturn -EAGAIN;\n+}\n+\n+int mtk_cldma_submit_tx(void *dev, struct sk_buff *skb)\n+{\n+\tstruct trb *trb = (struct trb *)skb-\u003ecb;\n+\tstruct cldma_drv_info *drv_info;\n+\tstruct cldma_dev *cd = dev;\n+\tstruct queue_info *que;\n+\tstruct tx_req *req;\n+\tstruct txq *txq;\n+\tint ret;\n+\n+\tque = radix_tree_lookup(\u0026cd-\u003etrans-\u003equeue_tbl, trb-\u003echannel_id \u0026 0xFFFF);\n+\tdrv_info = cd-\u003ecldma_drv_info[que-\u003ehif_id];\n+\tif (unlikely(!drv_info))\n+\t\treturn -EINVAL;\n+\n+\ttxq = drv_info-\u003etxq[que-\u003etxqno];\n+\tif (unlikely(!txq))\n+\t\treturn -EINVAL;\n+\n+\tif (!atomic_read(\u0026txq-\u003ereq_budget))\n+\t\treturn -EAGAIN;\n+\n+\treq = txq-\u003ereq_pool + txq-\u003ewr_idx;\n+\treq-\u003egpd-\u003etx_gpd.debug_id = 0x01;\n+\tret = mtk_cldma_txbuf_set(drv_info, skb, req, txq-\u003enr_bds);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treq-\u003egpd-\u003etx_gpd.data_buff_len = cpu_to_le16(skb-\u003elen);\n+\n+\treq-\u003edata_len = skb-\u003elen;\n+\treq-\u003eskb = skb;\n+\treq-\u003edata_vm_addr = skb-\u003edata;\n+\n+\twmb(); /* ensure req and data msg set done before HWO setup */\n+\n+\treq-\u003egpd-\u003etx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;\n+\n+\twmb(); /* ensure HWO setup done before index update */\n+\n+\ttxq-\u003ewr_idx = (txq-\u003ewr_idx + 1) % txq-\u003enr_gpds;\n+\tatomic_dec(\u0026txq-\u003ereq_budget);\n+\n+\treturn 0;\n+}\n+\n+int mtk_cldma_get_tx_budget(void *dev, enum mtk_hif_id hif_id, u32 qno)\n+{\n+\tstruct cldma_drv_info *drv_info;\n+\tstruct cldma_dev *cd = dev;\n+\tstruct txq *txq;\n+\n+\tif (unlikely(hif_id \u003e= NR_CLDMA || qno \u003e= HW_QUE_NUM || !cd))\n+\t\treturn -EINVAL;\n+\n+\tdrv_info = cd-\u003ecldma_drv_info[hif_id];\n+\tif (!drv_info)\n+\t\treturn -EINVAL;\n+\ttxq = drv_info-\u003etxq[qno];\n+\tif (!txq)\n+\t\treturn -EINVAL;\n+\treturn atomic_read(\u0026txq-\u003ereq_budget);\n+}\n+\n+static int (*trb_act_tbl[TRB_CMD_MAX])(struct cldma_dev *cd, struct sk_buff *skb) = {\n+\t[TRB_CMD_ENABLE] = mtk_cldma_open,\n+\t[TRB_CMD_TX] = mtk_cldma_tx,\n+\t[TRB_CMD_DISABLE] = mtk_cldma_close,\n+};\n+\n+int mtk_cldma_trb_process(void *dev, struct sk_buff *skb)\n+{\n+\tstruct cldma_dev *cd;\n+\tstruct trb *trb;\n+\n+\tif (!dev || !skb)\n+\t\treturn -EINVAL;\n+\n+\tcd = (struct cldma_dev *)dev;\n+\ttrb = (struct trb *)skb-\u003ecb;\n+\n+\tif (!(trb-\u003ecmd \u003e TRB_CMD_MIN \u0026\u0026 trb-\u003ecmd \u003c TRB_CMD_STOP))\n+\t\treturn -EINVAL;\n+\n+\treturn trb_act_tbl[trb-\u003ecmd](cd, skb);\n+}\n+\n+void mtk_cldma_fsm_state_listener(struct mtk_fsm_param *param, struct mtk_ctrl_trans *trans)\n+{\n+\tstruct cldma_dev *cd = trans-\u003edev;\n+\tint i;\n+\n+\tswitch (param-\u003eto) {\n+\tcase FSM_STATE_BOOTUP:\n+\t\tif (param-\u003efsm_flag \u0026 FSM_F_SAP_HS_START)\n+\t\t\tmtk_cldma_dev_init(cd, CLDMA0);\n+\t\telse if (param-\u003efsm_flag \u0026 FSM_F_MD_HS_START)\n+\t\t\tmtk_cldma_dev_init(cd, CLDMA1);\n+\t\tbreak;\n+\tcase FSM_STATE_OFF:\n+\t\tfor (i = 0; i \u003c NR_CLDMA; i++)\n+\t\t\tmtk_cldma_dev_exit(cd, i);\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+}\n+\n+int mtk_cldma_check_ch_cfg(void *dev, struct queue_info *que)\n+{\n+\tstruct cldma_drv_info *drv_info;\n+\tstruct cldma_dev *cd = dev;\n+\tstruct mtk_md_dev *mdev;\n+\tstruct txq *txq;\n+\tstruct rxq *rxq;\n+\n+\tmdev = cd-\u003etrans-\u003emdev;\n+\tdrv_info = cd-\u003ecldma_drv_info[que-\u003ehif_id];\n+\n+\tif (!drv_info) {\n+\t\tdev_err(mdev-\u003edev, \"CLDMA%d has not been initialized\\n\",\n+\t\t\tmtk_cldma_hw_id_tbl[que-\u003ehif_id]);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\ttxq = drv_info-\u003etxq[que-\u003etxqno];\n+\trxq = drv_info-\u003erxq[que-\u003erxqno];\n+\tif (!txq || !rxq) {\n+\t\tdev_err(mdev-\u003edev,\n+\t\t\t\"CLDMA%d txq%d rxq%d has not been enabled\\n\",\n+\t\t\tmtk_cldma_hw_id_tbl[que-\u003ehif_id], que-\u003etxqno, que-\u003erxqno);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (que-\u003etx_mtu != txq-\u003eque-\u003etx_mtu || que-\u003erx_mtu != rxq-\u003eque-\u003erx_mtu) {\n+\t\tdev_err(mdev-\u003edev,\n+\t\t\t\"Channel:%08x tx_mtu:%08x rx_mtu:%08x do not match ch cfg\\n\",\n+\t\t\tque-\u003etx_chl, que-\u003etx_mtu, que-\u003erx_mtu);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma.h\nnew file mode 100644\nindex 00000000000000..4686f7b178e5bd\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma.h\n@@ -0,0 +1,173 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#ifndef __MTK_CLDMA_H__\n+#define __MTK_CLDMA_H__\n+\n+#include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/dmapool.h\u003e\n+#include \u003clinux/interrupt.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/spinlock.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+#include \"mtk_ctrl_plane.h\"\n+#include \"mtk_trans_ctrl.h\"\n+\n+struct mtk_fsm_param;\n+\n+#define TXQ(N)\t\t\t\t\t(N)\n+#define RXQ(N)\t\t\t\t\t(N)\n+\n+#define CLDMA_GPD_FLAG_HWO\t\t\tBIT(0)\n+#define CLDMA_GPD_FLAG_BDP\t\t\tBIT(1)\n+#define CLDMA_GPD_FLAG_BPS\t\t\tBIT(2)\n+#define CLDMA_GPD_FLAG_IOC\t\t\tBIT(7)\n+#define CLDMA_BD_FLAG_EOL\t\t\tBIT(0)\n+\n+union gpd {\n+\tstruct {\n+\t\tu8 gpd_flags;\n+\t\tu8 non_used1;\n+\t\t__le16 data_allow_len;\n+\t\t__le32 next_gpd_ptr_h;\n+\t\t__le32 next_gpd_ptr_l;\n+\t\t__le32 data_buff_ptr_h;\n+\t\t__le32 data_buff_ptr_l;\n+\t\t__le16 data_recv_len;\n+\t\tu8 non_used2;\n+\t\tu8 debug_id;\n+\t} rx_gpd;\n+\n+\tstruct {\n+\t\tu8 gpd_flags;\n+\t\tu8 non_used1;\n+\t\tu8 non_used2;\n+\t\tu8 debug_id;\n+\t\t__le32 next_gpd_ptr_h;\n+\t\t__le32 next_gpd_ptr_l;\n+\t\t__le32 data_buff_ptr_h;\n+\t\t__le32 data_buff_ptr_l;\n+\t\t__le16 data_buff_len;\n+\t\t__le16 non_used3;\n+\t} tx_gpd;\n+} __packed;\n+\n+union bd {\n+\tstruct {\n+\t\tu8 bd_flags;\n+\t\tu8 non_used1;\n+\t\t__le16 data_allow_len;\n+\t\t__le32 next_bd_ptr_h;\n+\t\t__le32 next_bd_ptr_l;\n+\t\t__le32 data_buff_ptr_h;\n+\t\t__le32 data_buff_ptr_l;\n+\t\t__le16 data_recv_len;\n+\t\t__le16 non_used2;\n+\t} rx_bd;\n+\n+\tstruct {\n+\t\tu8 bd_flags;\n+\t\tu8 non_used1;\n+\t\t__le16 non_used2;\n+\t\t__le32 next_bd_ptr_h;\n+\t\t__le32 next_bd_ptr_l;\n+\t\t__le32 data_buff_ptr_h;\n+\t\t__le32 data_buff_ptr_l;\n+\t\t__le16 data_buffer_len;\n+\t\tu8 extension_len;\n+\t\tu8 non_used3;\n+\t} tx_bd;\n+} __packed;\n+\n+struct bd_dsc {\n+\tunion bd *bd;\n+\tstruct sk_buff *skb;\n+\tdma_addr_t bd_dma_addr;\n+\tdma_addr_t data_dma_addr;\n+\tsize_t data_len;\n+};\n+\n+struct rx_req {\n+\tunion gpd *gpd;\n+\tu32 mtu;\n+\tstruct sk_buff *skb;\n+\tsize_t data_len;\n+\tdma_addr_t gpd_dma_addr;\n+\tdma_addr_t data_dma_addr;\n+\tu32 frag_size;\n+\tstruct bd_dsc *bd_dsc_pool;\n+};\n+\n+struct rxq {\n+\tstruct cldma_drv_info *drv_info;\n+\tu32 rxqno;\n+\tstruct queue_info *que;\n+\tstruct work_struct rx_done_work;\n+\tstruct rx_req *req_pool;\n+\tu32 nr_gpds;\n+\tu32 free_idx;\n+\tunsigned short rx_done_cnt;\n+\tvoid *arg;\n+\tint (*rx_done)(struct sk_buff *skb, void *priv, bool force_recv);\n+\tu32 nr_bds;\n+\tatomic_t need_exit;\n+};\n+\n+struct tx_req {\n+\tunion gpd *gpd;\n+\tu32 mtu;\n+\tvoid *data_vm_addr;\n+\tsize_t data_len;\n+\tdma_addr_t data_dma_addr;\n+\tdma_addr_t gpd_dma_addr;\n+\tstruct sk_buff *skb;\n+\tint (*trb_complete)(struct sk_buff *skb);\n+\tu32 frag_size;\n+\tstruct bd_dsc *bd_dsc_pool;\n+};\n+\n+struct txq {\n+\tstruct cldma_drv_info *drv_info;\n+\tu32 txqno;\n+\tstruct queue_info *que;\n+\tstruct work_struct tx_done_work;\n+\tstruct tx_req *req_pool;\n+\tu32 nr_gpds;\n+\tatomic_t req_budget;\n+\tu32 wr_idx;\n+\tu32 free_idx;\n+\tbool tx_started;\n+\tbool is_stopping;\n+\tunsigned short tx_done_cnt;\n+\tu32 nr_bds;\n+};\n+\n+struct cldma_dev {\n+\tstruct cldma_drv_info *cldma_drv_info[NR_CLDMA];\n+\tstruct mtk_ctrl_trans *trans;\n+};\n+\n+struct cldma_drv_info_desc {\n+\tu32 hw_ver;\n+\tstruct cldma_drv_ops *drv_ops;\n+\tstruct cldma_hw_regs *hw_regs;\n+};\n+\n+int mtk_cldma_init(struct mtk_ctrl_trans *trans);\n+void mtk_cldma_exit(struct mtk_ctrl_trans *trans);\n+int mtk_cldma_submit_tx(void *dev, struct sk_buff *skb);\n+int mtk_cldma_get_tx_budget(void *dev, enum mtk_hif_id hif_id, u32 qno);\n+int mtk_cldma_trb_process(void *dev, struct sk_buff *skb);\n+void mtk_cldma_fsm_state_listener(struct mtk_fsm_param *param, struct mtk_ctrl_trans *trans);\n+int mtk_cldma_check_ch_cfg(void *dev, struct queue_info *que);\n+\n+#define drv_ops_name(NAME) cldma_drv_ops_##NAME\n+#define cldma_regs_name(NAME) mtk_cldma_regs_##NAME\n+\n+extern struct cldma_drv_ops cldma_drv_ops_m9xx;\n+extern struct cldma_hw_regs mtk_cldma_regs_m9xx;\n+\n+#endif\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c\nnew file mode 100644\nindex 00000000000000..8745c147089194\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c\n@@ -0,0 +1,369 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2023, MediaTek Inc.\n+ */\n+\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/iopoll.h\u003e\n+#include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/dmapool.h\u003e\n+#include \u003clinux/err.h\u003e\n+#include \u003clinux/interrupt.h\u003e\n+#include \u003clinux/kdev_t.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/kthread.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/sched.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/timer.h\u003e\n+#include \u003clinux/wait.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+\n+#include \"mtk_cldma_drv.h\"\n+#include \"mtk_dev.h\"\n+#include \"mtk_pci.h\"\n+#include \"mtk_pci_reg.h\"\n+\n+#define WAIT_QUEUE_STOP\t\t(70)\n+\n+void mtk_cldma_drv_init(struct cldma_drv_info *drv_info)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tstruct mtk_md_dev *mdev;\n+\tint base;\n+\tu32 val;\n+\n+\tmdev = drv_info-\u003emdev;\n+\tbase = drv_info-\u003ebase_addr;\n+\thw_regs = drv_info-\u003ehw_regs;\n+\n+\t/* set CLDMA to 64 bit mode GPD */\n+\tval = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_ul_cfg);\n+\tval = (val \u0026 (~(0x7 \u003c\u003c 5))) | ((0x4) \u003c\u003c 5);\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_ul_cfg, val);\n+\n+\tval = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_so_cfg);\n+\tval = (val \u0026 (~(0x7 \u003c\u003c 10))) | ((0x4) \u003c\u003c 10) | (1 \u003c\u003c 2);\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_so_cfg, val);\n+\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_rx_work_to_reg_mask_set, ALLQ);\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_ip_busy_to_pcie_mask_set,\n+\t\t\tALLQ \u003c\u003c 16);\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_ip_busy_to_pcie_mask_clr,\n+\t\t\tALLQ \u003c\u003c 24);\n+\n+\t/* enable interrupt to PCIe */\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_int_mask, 0);\n+\n+\t/* disable illegal memory check */\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_ul_dummy_0, 1);\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_so_dummy_0, 1);\n+}\n+\n+void mtk_cldma_setup_start_addr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t\tu32 qno, dma_addr_t addr)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tunsigned int addr_l;\n+\tunsigned int addr_h;\n+\tint base;\n+\n+\thw_regs = drv_info-\u003ehw_regs;\n+\tbase = drv_info-\u003ebase_addr;\n+\n+\tif (dir == DIR_TX) {\n+\t\taddr_l = base + hw_regs-\u003ereg_cldma_ul_start_addrl_0 + qno * HW_QUEUE_NUM;\n+\t\taddr_h = base + hw_regs-\u003ereg_cldma_ul_start_addrh_0 + qno * HW_QUEUE_NUM;\n+\t} else {\n+\t\taddr_l = base + hw_regs-\u003ereg_cldma_so_start_addrl_0 + qno * HW_QUEUE_NUM;\n+\t\taddr_h = base + hw_regs-\u003ereg_cldma_so_start_addrh_0 + qno * HW_QUEUE_NUM;\n+\t}\n+\n+\tmtk_pci_write32(drv_info-\u003emdev, addr_l, (u32)addr);\n+\tmtk_pci_write32(drv_info-\u003emdev, addr_h, (u32)((u64)addr \u003e\u003e 32));\n+}\n+\n+void mtk_cldma_mask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t u32 qno, enum mtk_intr_type type)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tint base;\n+\tu32 addr;\n+\tu32 val;\n+\n+\thw_regs = drv_info-\u003ehw_regs;\n+\tbase = drv_info-\u003ebase_addr;\n+\n+\tif (dir == DIR_TX)\n+\t\taddr = base + hw_regs-\u003ereg_cldma_l2timsr0;\n+\telse\n+\t\taddr = base + hw_regs-\u003ereg_cldma_l2rimsr0;\n+\n+\tif (qno == ALLQ)\n+\t\tval = qno \u003c\u003c type;\n+\telse\n+\t\tval = BIT(qno) \u003c\u003c type;\n+\n+\tmtk_pci_write32(drv_info-\u003emdev, addr, val);\n+}\n+\n+void mtk_cldma_unmask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t   u32 qno, enum mtk_intr_type type)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tint base;\n+\tu32 addr;\n+\tu32 val;\n+\n+\thw_regs = drv_info-\u003ehw_regs;\n+\tbase = drv_info-\u003ebase_addr;\n+\n+\tif (dir == DIR_TX)\n+\t\taddr = base + hw_regs-\u003ereg_cldma_l2timcr0;\n+\telse\n+\t\taddr = base + hw_regs-\u003ereg_cldma_l2rimcr0;\n+\n+\tif (qno == ALLQ)\n+\t\tval = qno \u003c\u003c type;\n+\telse\n+\t\tval = BIT(qno) \u003c\u003c type;\n+\n+\tmtk_pci_write32(drv_info-\u003emdev, addr, val);\n+}\n+\n+void mtk_cldma_clr_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t       u32 qno, enum mtk_intr_type type)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tstruct mtk_md_dev *mdev;\n+\tint base;\n+\tu32 addr;\n+\tu32 val;\n+\n+\thw_regs = drv_info-\u003ehw_regs;\n+\tbase = drv_info-\u003ebase_addr;\n+\tmdev = drv_info-\u003emdev;\n+\n+\tif (type == QUEUE_ERROR) {\n+\t\tif (dir == DIR_TX) {\n+\t\t\tval = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_l3tisar0);\n+\t\t\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_l3tisar0, val);\n+\t\t\tval = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_l3tisar1);\n+\t\t\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_l3tisar1, val);\n+\t\t\tval = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_l3tisar2);\n+\t\t\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_l3tisar2, val);\n+\t\t} else {\n+\t\t\tval = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_l3risar0);\n+\t\t\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_l3risar0, val);\n+\t\t\tval = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_l3risar1);\n+\t\t\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_l3risar1, val);\n+\t\t}\n+\t}\n+\n+\tif (dir == DIR_TX)\n+\t\taddr = base + hw_regs-\u003ereg_cldma_l2tisar0;\n+\telse\n+\t\taddr = base + hw_regs-\u003ereg_cldma_l2risar0;\n+\n+\tif (qno == ALLQ)\n+\t\tval = qno \u003c\u003c type;\n+\telse\n+\t\tval = BIT(qno) \u003c\u003c type;\n+\n+\tmtk_pci_write32(mdev, addr, val);\n+\tval = mtk_pci_read32(mdev, addr);\n+}\n+\n+u32 mtk_cldma_check_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t\tu32 qno, enum mtk_intr_type type)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tu32 addr, val, sta;\n+\tint base;\n+\n+\thw_regs = drv_info-\u003ehw_regs;\n+\tbase = drv_info-\u003ebase_addr;\n+\n+\tif (dir == DIR_TX)\n+\t\taddr = base + hw_regs-\u003ereg_cldma_l2tisar0;\n+\telse\n+\t\taddr = base + hw_regs-\u003ereg_cldma_l2risar0;\n+\n+\tval = mtk_pci_read32(drv_info-\u003emdev, addr);\n+\tif (val == LINK_ERROR_VAL)\n+\t\tsta = val;\n+\telse if (qno == ALLQ)\n+\t\tsta = (val \u003e\u003e type) \u0026 0xFF;\n+\telse\n+\t\tsta = (val \u003e\u003e type) \u0026 BIT(qno);\n+\n+\treturn sta;\n+}\n+\n+void mtk_cldma_start_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tu32 val = BIT(qno);\n+\tint base;\n+\tu32 addr;\n+\n+\thw_regs = drv_info-\u003ehw_regs;\n+\tbase = drv_info-\u003ebase_addr;\n+\n+\tif (dir == DIR_TX)\n+\t\taddr = base + hw_regs-\u003ereg_cldma_ul_start_cmd;\n+\telse\n+\t\taddr = base + hw_regs-\u003ereg_cldma_so_start_cmd;\n+\n+\tmtk_pci_write32(drv_info-\u003emdev, addr, val);\n+}\n+\n+void mtk_cldma_resume_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tu32 val = BIT(qno);\n+\tint base;\n+\tu32 addr;\n+\n+\thw_regs = drv_info-\u003ehw_regs;\n+\tbase = drv_info-\u003ebase_addr;\n+\n+\tif (dir == DIR_TX)\n+\t\taddr = base + hw_regs-\u003ereg_cldma_ul_resume_cmd;\n+\telse\n+\t\taddr = base + hw_regs-\u003ereg_cldma_so_resume_cmd;\n+\n+\tmtk_pci_write32(drv_info-\u003emdev, addr, val);\n+}\n+\n+u32 mtk_cldma_queue_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tint base;\n+\tu32 addr;\n+\tu32 val;\n+\n+\thw_regs = drv_info-\u003ehw_regs;\n+\tbase = drv_info-\u003ebase_addr;\n+\n+\tif (dir == DIR_TX)\n+\t\taddr = base + hw_regs-\u003ereg_cldma_ul_status;\n+\telse\n+\t\taddr = base + hw_regs-\u003ereg_cldma_so_status;\n+\n+\tval = mtk_pci_read32(drv_info-\u003emdev, addr);\n+\n+\tif (qno == ALLQ || val == LINK_ERROR_VAL)\n+\t\treturn val;\n+\n+\treturn val \u0026 BIT(qno);\n+}\n+\n+u32 mtk_cldma_stop_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)\n+{\n+\tu32 val = (qno == ALLQ) ? qno : BIT(qno);\n+\tstruct cldma_hw_regs *hw_regs;\n+\tunsigned int active;\n+\tint base;\n+\tu32 addr;\n+\n+\thw_regs = drv_info-\u003ehw_regs;\n+\tbase = drv_info-\u003ebase_addr;\n+\n+\tif (dir == DIR_TX)\n+\t\taddr = base + hw_regs-\u003ereg_cldma_ul_stop_cmd;\n+\telse\n+\t\taddr = base + hw_regs-\u003ereg_cldma_so_stop_cmd;\n+\n+\tmtk_pci_write32(drv_info-\u003emdev, addr, val);\n+\n+\tread_poll_timeout(mtk_cldma_queue_status, active,\n+\t\t\t  active == LINK_ERROR_VAL || !active,\n+\t\t\t  WAIT_QUEUE_STOP, WAIT_QUEUE_STOP * 10, false,\n+\t\t\t  drv_info, dir, qno);\n+\n+\treturn active;\n+}\n+\n+void mtk_cldma_clear_ip_busy(struct cldma_drv_info *drv_info)\n+{\n+\tmtk_pci_write32(drv_info-\u003emdev, drv_info-\u003ebase_addr +\n+\t\t\tdrv_info-\u003ehw_regs-\u003ereg_cldma_ip_busy, 0x01);\n+}\n+\n+void mtk_cldma_get_intr_status(struct cldma_drv_info *drv_info, u32 *tx_sta, u32 *rx_sta)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tstruct mtk_md_dev *mdev;\n+\tu32 tx_mask, rx_mask;\n+\tint base;\n+\n+\tmdev = drv_info-\u003emdev;\n+\tbase = drv_info-\u003ebase_addr;\n+\thw_regs = drv_info-\u003ehw_regs;\n+\n+\t*tx_sta = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_l2tisar0);\n+\ttx_mask = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_l2timr0);\n+\t*rx_sta = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_l2risar0);\n+\trx_mask = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_l2rimr0);\n+\n+\t*tx_sta = (*tx_sta) \u0026 (~tx_mask);\n+\t*rx_sta = (*rx_sta) \u0026 (~rx_mask);\n+\n+\tif (*tx_sta) {\n+\t\t/* TX XFER_DONE and QUEUE_ERROR mask */\n+\t\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_l2timsr0, *tx_sta);\n+\t\t/* TX XFER_DONE clear */\n+\t\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_l2tisar0,\n+\t\t\t\t(*tx_sta) \u0026 (0xFF \u003c\u003c QUEUE_XFER_DONE));\n+\t}\n+\n+\tif (*rx_sta) {\n+\t\t/* RX XFER_DONE and QUEUE_ERROR mask */\n+\t\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_l2rimsr0, *rx_sta);\n+\t\t/* RX XFER_DONE clear */\n+\t\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_l2risar0,\n+\t\t\t\t(*rx_sta) \u0026 (0xFF \u003c\u003c QUEUE_XFER_DONE));\n+\t}\n+}\n+\n+u32 mtk_cldma_get_tx_start_addr(struct cldma_drv_info *drv_info, u32 qno)\n+{\n+\tu32 addr, val;\n+\n+\taddr = drv_info-\u003ebase_addr + drv_info-\u003ehw_regs-\u003ereg_cldma_ul_start_addrl_0 +\n+\t       qno * HW_QUEUE_NUM;\n+\tval = mtk_pci_read32(drv_info-\u003emdev, addr);\n+\n+\treturn val;\n+}\n+\n+u64 mtk_cldma_get_rx_curr_addr(struct cldma_drv_info *drv_info, u32 qno)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tu32 curr_addr_h, curr_addr_l;\n+\tstruct mtk_md_dev *mdev;\n+\tu64 curr_addr;\n+\tint base;\n+\tu64 addr;\n+\n+\thw_regs = drv_info-\u003ehw_regs;\n+\tbase = drv_info-\u003ebase_addr;\n+\tmdev = drv_info-\u003emdev;\n+\n+\taddr = base + hw_regs-\u003ereg_cldma_so_current_addrh_0 +\n+\t       (u64)qno * HW_QUEUE_NUM;\n+\tcurr_addr_h = mtk_pci_read32(mdev, addr);\n+\taddr = base + hw_regs-\u003ereg_cldma_so_current_addrl_0 +\n+\t       (u64)qno * HW_QUEUE_NUM;\n+\tcurr_addr_l = mtk_pci_read32(mdev, addr);\n+\tcurr_addr = ((u64)curr_addr_h \u003c\u003c 32) | curr_addr_l;\n+\tif (curr_addr_h == LINK_ERROR_VAL \u0026\u0026 curr_addr_l == LINK_ERROR_VAL)\n+\t\tcurr_addr = 0;\n+\treturn curr_addr;\n+}\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h\nnew file mode 100644\nindex 00000000000000..6de87b7ffd4593\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h\n@@ -0,0 +1,174 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Copyright (c) 2023, MediaTek Inc.\n+ */\n+\n+#ifndef __MTK_CLDMA_DRV_H__\n+#define __MTK_CLDMA_DRV_H__\n+\n+#define HW_QUEUE_NUM\t\t(8)\n+#define ALLQ\t\t\t(0xFF)\n+#define LINK_ERROR_VAL\t\t(0xFFFFFFFF)\n+#define CLDMA0_HW_ID\t\t(0)\n+#define CLDMA1_HW_ID\t\t(1)\n+\n+struct cldma_hw_regs {\n+\tu8 cldma_rx_skb_pool_max_size;\n+\tu8 cldma_rx_skb_reload_threshold;\n+\tu8 tq_err_int_offset;\n+\tu8 tq_active_start_err_int_offset;\n+\tu8 rq_err_int_offset;\n+\tu8 rq_active_start_err_int_offset;\n+\tu16 reg_cldma_so_cfg;\n+\tu16 reg_cldma_so_start_addrl_0;\n+\tu16 reg_cldma_so_start_addrh_0;\n+\tu16 reg_cldma_so_current_addrl_0;\n+\tu16 reg_cldma_so_current_addrh_0;\n+\tu16 reg_cldma_so_status;\n+\tu16 reg_cldma_debug_id_en;\n+\tu16 reg_cldma_so_last_update_addrl_0;\n+\tu16 reg_cldma_so_last_update_addrh_0;\n+\tu16 reg_cldma_l2rimr0;\n+\tu16 reg_cldma_l2rimr1;\n+\tu16 reg_cldma_l2rimcr0;\n+\tu16 reg_cldma_l2rimcr1;\n+\tu16 reg_cldma_l2rimsr0;\n+\tu16 reg_cldma_l2rimsr1;\n+\tu16 reg_cldma_int_mask;\n+\tu16 reg_cldma_slp_mem_ctl;\n+\tu16 reg_cldma_busy_mask;\n+\tu16 reg_cldma_ip_busy_to_pcie_mask;\n+\tu16 reg_cldma_ip_busy_to_pcie_mask_set;\n+\tu16 reg_cldma_ip_busy_to_pcie_mask_clr;\n+\tu16 reg_cldma_ip_busy_to_ap_mask;\n+\tu16 reg_cldma_ip_busy_to_ap_mask_set;\n+\tu16 reg_cldma_ip_busy_to_ap_mask_clr;\n+\tu16 reg_cldma_ip_busy_to_md_mask_set;\n+\tu16 reg_cldma_rx_work_to_reg_mask_set;\n+\tu16 reg_infra_rst4_set;\n+\tu16 reg_infra_rst4_clr;\n+\tu16 reg_infra_rst2_set;\n+\tu16 reg_infra_rst2_clr;\n+\tu16 reg_infra_rst0_set;\n+\tu16 reg_infra_rst0_clr;\n+\tu32 tq_err_int_bitmask;\n+\tu32 tq_active_start_err_int_bitmask;\n+\tu32 rq_err_int_bitmask;\n+\tu32 cldma0_base_addr;\n+\tu32 cldma1_base_addr;\n+\tu32 rq_active_start_err_int_bitmask;\n+\tu32 reg_cldma_ul_start_addrl_0;\n+\tu32 reg_cldma_ul_start_addrh_0;\n+\tu32 reg_cldma_ul_current_addrl_0;\n+\tu32 reg_cldma_ul_current_addrh_0;\n+\tu32 reg_cldma_ul_status;\n+\tu32 reg_cldma_ul_start_cmd;\n+\tu32 reg_cldma_ul_resume_cmd;\n+\tu32 reg_cldma_ul_stop_cmd;\n+\tu32 reg_cldma_ul_error;\n+\tu32 reg_cldma_ul_cfg;\n+\tu32 reg_cldma_ul_dummy_0;\n+\tu32 reg_cldma_so_error;\n+\tu32 reg_cldma_so_start_cmd;\n+\tu32 reg_cldma_so_resume_cmd;\n+\tu32 reg_cldma_so_stop_cmd;\n+\tu32 reg_cldma_so_dummy_0;\n+\tu32 reg_cldma_l2tisar0;\n+\tu32 reg_cldma_l2tisar1;\n+\tu32 reg_cldma_l2timr0;\n+\tu32 reg_cldma_l2timr1;\n+\tu32 reg_cldma_l2timcr0;\n+\tu32 reg_cldma_l2timcr1;\n+\tu32 reg_cldma_l2timsr0;\n+\tu32 reg_cldma_l2timsr1;\n+\tu32 reg_cldma_l2risar0;\n+\tu32 reg_cldma_l2risar1;\n+\tu32 reg_cldma_l3tisar0;\n+\tu32 reg_cldma_l3tisar1;\n+\tu32 reg_cldma_l3tisar2;\n+\tu32 reg_cldma_l3risar0;\n+\tu32 reg_cldma_l3risar1;\n+\tu32 reg_cldma_ip_busy;\n+};\n+\n+enum mtk_ip_busy_src {\n+\tIP_BUSY_TXDONE = 0,\n+\tIP_BUSY_TXEMPTY = 8,\n+\tIP_BUSY_TXACTIVE = 16,\n+\tIP_BUSY_RXDONE = 24\n+};\n+\n+enum mtk_intr_type {\n+\tQUEUE_XFER_DONE = 0,\n+\tQUEUE_EMPTY = 8,\n+\tQUEUE_ERROR = 16,\n+\tQUEUE_ACTIVE_START = 24,\n+\tINVALID_TYPE\n+};\n+\n+enum mtk_tx_rx {\n+\tDIR_TX,\n+\tDIR_RX,\n+\tDIR_MAX\n+};\n+\n+struct cldma_drv_info {\n+\tint hif_id;\n+\tint hw_id;\n+\tint base_addr;\n+\tint pci_ext_irq_id;\n+\tstruct mtk_md_dev *mdev;\n+\tstruct cldma_dev *cd;\n+\tstruct txq *txq[HW_QUEUE_NUM];\n+\tstruct rxq *rxq[HW_QUEUE_NUM];\n+\tstruct dma_pool *gpd_dma_pool;\n+\tstruct dma_pool *bd_dma_pool;\n+\tstruct workqueue_struct *wq;\n+\tstruct cldma_hw_regs *hw_regs;\n+\tstruct cldma_drv_ops *drv_ops;\n+};\n+\n+struct cldma_drv_ops {\n+\tvoid (*cldma_drv_init)(struct cldma_drv_info *drv_info);\n+\tvoid (*cldma_drv_reset)(struct cldma_drv_info *drv_info);\n+\tvoid (*cldma_setup_start_addr)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t\t       u32 qno, dma_addr_t addr);\n+\tvoid (*cldma_mask_intr)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t\tu32 qno, enum mtk_intr_type type);\n+\tvoid (*cldma_unmask_intr)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t\t  u32 qno, enum mtk_intr_type type);\n+\tvoid (*cldma_clr_intr_status)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t\t      u32 qno, enum mtk_intr_type type);\n+\tu32 (*cldma_check_intr_status)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t\t       u32 qno, enum mtk_intr_type type);\n+\tvoid (*cldma_start_queue)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);\n+\tvoid (*cldma_resume_queue)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);\n+\tu32 (*cldma_queue_status)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);\n+\tu32 (*cldma_stop_queue)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);\n+\tvoid (*cldma_clear_ip_busy)(struct cldma_drv_info *drv_info);\n+\tvoid (*cldma_get_intr_status)(struct cldma_drv_info *drv_info, u32 *tx_sta, u32 *rx_sta);\n+\tu32 (*cldma_get_tx_start_addr)(struct cldma_drv_info *drv_info, u32 qno);\n+\tu64 (*cldma_get_rx_curr_addr)(struct cldma_drv_info *drv_info, u32 qno);\n+};\n+\n+void mtk_cldma_drv_init(struct cldma_drv_info *drv_info);\n+void mtk_cldma_setup_start_addr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t\tu32 qno, dma_addr_t addr);\n+void mtk_cldma_mask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t u32 qno, enum mtk_intr_type type);\n+void mtk_cldma_unmask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t   u32 qno, enum mtk_intr_type type);\n+void mtk_cldma_clr_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t       u32 qno, enum mtk_intr_type type);\n+u32 mtk_cldma_check_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,\n+\t\t\t\tu32 qno, enum mtk_intr_type type);\n+void mtk_cldma_start_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);\n+void mtk_cldma_resume_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);\n+u32 mtk_cldma_queue_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);\n+u32 mtk_cldma_stop_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);\n+void mtk_cldma_clear_ip_busy(struct cldma_drv_info *drv_info);\n+void mtk_cldma_get_intr_status(struct cldma_drv_info *drv_info, u32 *tx_sta, u32 *rx_sta);\n+u32 mtk_cldma_get_tx_start_addr(struct cldma_drv_info *drv_info, u32 qno);\n+u64 mtk_cldma_get_rx_curr_addr(struct cldma_drv_info *drv_info, u32 qno);\n+\n+#endif\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c\nnew file mode 100644\nindex 00000000000000..a59c35fc15773e\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c\n@@ -0,0 +1,178 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2023, MediaTek Inc.\n+ */\n+\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/dmapool.h\u003e\n+#include \u003clinux/err.h\u003e\n+#include \u003clinux/interrupt.h\u003e\n+#include \u003clinux/kdev_t.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/kthread.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/sched.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/timer.h\u003e\n+#include \u003clinux/wait.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+\n+#include \"mtk_cldma.h\"\n+#include \"mtk_cldma_drv.h\"\n+#include \"mtk_cldma_drv_m9xx.h\"\n+#include \"mtk_dev.h\"\n+#include \"mtk_pci.h\"\n+#include \"mtk_pci_reg.h\"\n+#include \"mtk_trans_ctrl.h\"\n+\n+struct cldma_hw_regs mtk_cldma_regs_m9xx = {\n+\t.cldma0_base_addr = CLDMA0_BASE_ADDR,\n+\t.cldma1_base_addr = CLDMA1_BASE_ADDR,\n+\t.cldma_rx_skb_pool_max_size = CLDMA_RX_SKB_POOL_MAX_SIZE,\n+\t.cldma_rx_skb_reload_threshold = CLDMA_RX_SKB_RELOAD_THRESHOLD,\n+\t.tq_err_int_offset = TQ_ERR_INT_OFFSET,\n+\t.tq_err_int_bitmask = TQ_ERR_INT_BITMASK,\n+\t.tq_active_start_err_int_offset = TQ_ACTIVE_START_ERR_INT_OFFSET,\n+\t.tq_active_start_err_int_bitmask = TQ_ACTIVE_START_ERR_INT_BITMASK,\n+\t.rq_err_int_offset = RQ_ERR_INT_OFFSET,\n+\t.rq_err_int_bitmask = RQ_ERR_INT_BITMASK,\n+\t.rq_active_start_err_int_offset = RQ_ACTIVE_START_ERR_INT_OFFSET,\n+\t.rq_active_start_err_int_bitmask = RQ_ACTIVE_START_ERR_INT_BITMASK,\n+\t.reg_cldma_ul_start_addrl_0 = REG_CLDMA_UL_START_ADDRL_0,\n+\t.reg_cldma_ul_start_addrh_0 = REG_CLDMA_UL_START_ADDRH_0,\n+\t.reg_cldma_ul_current_addrl_0 = REG_CLDMA_UL_CURRENT_ADDRL_0,\n+\t.reg_cldma_ul_current_addrh_0 = REG_CLDMA_UL_CURRENT_ADDRH_0,\n+\t.reg_cldma_ul_status = REG_CLDMA_UL_STATUS,\n+\t.reg_cldma_ul_start_cmd = REG_CLDMA_UL_START_CMD,\n+\t.reg_cldma_ul_resume_cmd = REG_CLDMA_UL_RESUME_CMD,\n+\t.reg_cldma_ul_stop_cmd = REG_CLDMA_UL_STOP_CMD,\n+\t.reg_cldma_ul_error = REG_CLDMA_UL_ERROR,\n+\t.reg_cldma_ul_cfg = REG_CLDMA_UL_CFG,\n+\t.reg_cldma_ul_dummy_0 = REG_CLDMA_UL_DUMMY_0,\n+\t.reg_cldma_so_error = REG_CLDMA_SO_ERROR,\n+\t.reg_cldma_so_start_cmd = REG_CLDMA_SO_START_CMD,\n+\t.reg_cldma_so_resume_cmd = REG_CLDMA_SO_RESUME_CMD,\n+\t.reg_cldma_so_stop_cmd = REG_CLDMA_SO_STOP_CMD,\n+\t.reg_cldma_so_dummy_0 = REG_CLDMA_SO_DUMMY_0,\n+\t.reg_cldma_so_cfg = REG_CLDMA_SO_CFG,\n+\t.reg_cldma_so_start_addrl_0 = REG_CLDMA_SO_START_ADDRL_0,\n+\t.reg_cldma_so_start_addrh_0 = REG_CLDMA_SO_START_ADDRH_0,\n+\t.reg_cldma_so_current_addrl_0 = REG_CLDMA_SO_CUR_ADDRL_0,\n+\t.reg_cldma_so_current_addrh_0 = REG_CLDMA_SO_CUR_ADDRH_0,\n+\t.reg_cldma_so_status = REG_CLDMA_SO_STATUS,\n+\t.reg_cldma_debug_id_en = REG_CLDMA_DEBUG_ID_EN,\n+\t.reg_cldma_so_last_update_addrl_0 = REG_CLDMA_SO_LAST_UPDATE_ADDRL_0,\n+\t.reg_cldma_so_last_update_addrh_0 = REG_CLDMA_SO_LAST_UPDATE_ADDRH_0,\n+\t.reg_cldma_l2tisar0 = REG_CLDMA_L2TISAR0,\n+\t.reg_cldma_l2tisar1 = REG_CLDMA_L2TISAR1,\n+\t.reg_cldma_l2timr0 = REG_CLDMA_L2TIMR0,\n+\t.reg_cldma_l2timr1 = REG_CLDMA_L2TIMR1,\n+\t.reg_cldma_l2timcr0 = REG_CLDMA_L2TIMCR0,\n+\t.reg_cldma_l2timcr1 = REG_CLDMA_L2TIMCR1,\n+\t.reg_cldma_l2timsr0 = REG_CLDMA_L2TIMSR0,\n+\t.reg_cldma_l2timsr1 = REG_CLDMA_L2TIMSR1,\n+\t.reg_cldma_l3tisar0 = REG_CLDMA_L3TISAR0,\n+\t.reg_cldma_l3tisar1 = REG_CLDMA_L3TISAR1,\n+\t.reg_cldma_l3tisar2 = REG_CLDMA_L3TISAR2,\n+\t.reg_cldma_l2risar0 = REG_CLDMA_L2RISAR0,\n+\t.reg_cldma_l2risar1 = REG_CLDMA_L2RISAR1,\n+\t.reg_cldma_l2rimr0 = REG_CLDMA_L2RIMR0,\n+\t.reg_cldma_l2rimr1 = REG_CLDMA_L2RIMR1,\n+\t.reg_cldma_l2rimcr0 = REG_CLDMA_L2RIMCR0,\n+\t.reg_cldma_l2rimcr1 = REG_CLDMA_L2RIMCR1,\n+\t.reg_cldma_l2rimsr0 = REG_CLDMA_L2RIMSR0,\n+\t.reg_cldma_l2rimsr1 = REG_CLDMA_L2RIMSR1,\n+\t.reg_cldma_l3risar0 = REG_CLDMA_L3RISAR0,\n+\t.reg_cldma_l3risar1 = REG_CLDMA_L3RISAR1,\n+\t.reg_cldma_ip_busy = REG_CLDMA_IP_BUSY,\n+\t.reg_cldma_int_mask = REG_CLDMA_INT_EAP_USIP_MASK,\n+\t.reg_cldma_ip_busy_to_pcie_mask = REG_CLDMA_IP_BUSY_TO_PCIE_MASK,\n+\t.reg_cldma_ip_busy_to_pcie_mask_set = REG_CLDMA_IP_BUSY_TO_PCIE_MASK_SET,\n+\t.reg_cldma_ip_busy_to_pcie_mask_clr = REG_CLDMA_IP_BUSY_TO_PCIE_MASK_CLR,\n+\t.reg_cldma_ip_busy_to_ap_mask = REG_CLDMA_IP_BUSY_TO_AP_MASK,\n+\t.reg_cldma_ip_busy_to_ap_mask_set = REG_CLDMA_IP_BUSY_TO_AP_MASK_SET,\n+\t.reg_cldma_ip_busy_to_ap_mask_clr = REG_CLDMA_IP_BUSY_TO_AP_MASK_CLR,\n+\t.reg_cldma_ip_busy_to_md_mask_set = REG_CLDMA_IP_BUSY_TO_MD_MASK_SET,\n+\t.reg_cldma_rx_work_to_reg_mask_set = REG_CLDMA_RX_WORK_TO_REG_MASK_SET,\n+\t.reg_infra_rst0_set = REG_INFRA_RST0_SET,\n+\t.reg_infra_rst0_clr = REG_INFRA_RST0_CLR,\n+};\n+\n+static void mtk_cldma_drv_init_m9xx(struct cldma_drv_info *drv_info)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tstruct mtk_md_dev *mdev;\n+\tint base;\n+\tu32 val;\n+\n+\tmdev = drv_info-\u003emdev;\n+\tbase = drv_info-\u003ebase_addr;\n+\thw_regs = drv_info-\u003ehw_regs;\n+\n+\t/* set CLDMA to 64 bit mode GPD */\n+\tval = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_ul_cfg);\n+\n+\tval = (val \u0026 (~(0x7 \u003c\u003c 5))) | ((0x4) \u003c\u003c 5);\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_ul_cfg, val);\n+\n+\tval = mtk_pci_read32(mdev, base + hw_regs-\u003ereg_cldma_so_cfg);\n+\tval = (val \u0026 (~(0x7 \u003c\u003c 10))) | ((0x4) \u003c\u003c 10) | (1 \u003c\u003c 2);\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_so_cfg, val);\n+\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_rx_work_to_reg_mask_set, ALLQ);\n+\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_ip_busy_to_pcie_mask_set,\n+\t\t\tALLQ \u003c\u003c 16);\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_ip_busy_to_pcie_mask_clr,\n+\t\t\tALLQ \u003c\u003c 24);\n+\n+\t/* enable interrupt to PCIe */\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_int_mask, 0);\n+\n+\t/* disable illegal memory check */\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_ul_dummy_0, 1);\n+\tmtk_pci_write32(mdev, base + hw_regs-\u003ereg_cldma_so_dummy_0, 1);\n+}\n+\n+static void mtk_cldma_drv_reset_m9xx(struct cldma_drv_info *drv_info)\n+{\n+\tstruct cldma_hw_regs *hw_regs;\n+\tstruct mtk_md_dev *mdev;\n+\tu32 val;\n+\n+\tmdev = drv_info-\u003emdev;\n+\thw_regs = drv_info-\u003ehw_regs;\n+\n+\tval = mtk_pci_read32(mdev, REG_DEV_INFRA_BASE + hw_regs-\u003ereg_infra_rst0_set);\n+\n+\tval |= 1 \u003c\u003c (REG_CLDMA0_RST_SET_BIT + drv_info-\u003ehw_id);\n+\tmtk_pci_write32(mdev, REG_DEV_INFRA_BASE + hw_regs-\u003ereg_infra_rst0_set, val);\n+\tudelay(1);\n+\tval = mtk_pci_read32(mdev, REG_DEV_INFRA_BASE + hw_regs-\u003ereg_infra_rst0_clr);\n+\tval |= 1 \u003c\u003c (REG_CLDMA0_RST_CLR_BIT + drv_info-\u003ehw_id);\n+\tmtk_pci_write32(mdev, REG_DEV_INFRA_BASE + hw_regs-\u003ereg_infra_rst0_clr, val);\n+}\n+\n+struct cldma_drv_ops cldma_drv_ops_m9xx = {\n+\t.cldma_drv_init = mtk_cldma_drv_init_m9xx,\n+\t.cldma_drv_reset = mtk_cldma_drv_reset_m9xx,\n+\t.cldma_setup_start_addr = mtk_cldma_setup_start_addr,\n+\t.cldma_mask_intr = mtk_cldma_mask_intr,\n+\t.cldma_unmask_intr = mtk_cldma_unmask_intr,\n+\t.cldma_clr_intr_status = mtk_cldma_clr_intr_status,\n+\t.cldma_check_intr_status = mtk_cldma_check_intr_status,\n+\t.cldma_start_queue = mtk_cldma_start_queue,\n+\t.cldma_resume_queue = mtk_cldma_resume_queue,\n+\t.cldma_queue_status = mtk_cldma_queue_status,\n+\t.cldma_stop_queue = mtk_cldma_stop_queue,\n+\t.cldma_clear_ip_busy = mtk_cldma_clear_ip_busy,\n+\t.cldma_get_intr_status = mtk_cldma_get_intr_status,\n+\t.cldma_get_tx_start_addr = mtk_cldma_get_tx_start_addr,\n+\t.cldma_get_rx_curr_addr = mtk_cldma_get_rx_curr_addr,\n+};\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h\nnew file mode 100644\nindex 00000000000000..f113c4c1068a2c\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h\n@@ -0,0 +1,101 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Copyright (c) 2023, MediaTek Inc.\n+ */\n+\n+#ifndef __MTK_CLDMA_DRV_M9XX_H__\n+#define __MTK_CLDMA_DRV_M9XX_H__\n+\n+#define CLDMA0_BASE_ADDR\t\t\t\t(0x1021C000)\n+#define CLDMA1_BASE_ADDR\t\t\t\t(0x1021E000)\n+\n+#define CLDMA_RX_SKB_POOL_MAX_SIZE\t\t\t(64)\n+#define CLDMA_RX_SKB_RELOAD_THRESHOLD\t\t\t(16)\n+\n+/* L2TISAR0 */\n+#define TQ_ERR_INT_OFFSET\t\t\t\t(16)\n+#define TQ_ERR_INT_BITMASK\t\t\t\t(0x00FF0000)\n+#define TQ_ACTIVE_START_ERR_INT_OFFSET\t\t\t(24)\n+#define TQ_ACTIVE_START_ERR_INT_BITMASK\t\t\t(0xFF000000)\n+\n+/* L2RISAR0 */\n+#define RQ_ERR_INT_OFFSET\t\t\t\t(16)\n+#define RQ_ERR_INT_BITMASK\t\t\t\t(0x00FF0000)\n+#define RQ_ACTIVE_START_ERR_INT_OFFSET\t\t\t(24)\n+#define RQ_ACTIVE_START_ERR_INT_BITMASK\t\t\t(0xFF000000)\n+\n+/* CLDMA IN(Tx) */\n+#define REG_CLDMA_UL_START_ADDRL_0\t\t\t(0x0004)\n+#define REG_CLDMA_UL_START_ADDRH_0\t\t\t(0x0008)\n+#define REG_CLDMA_UL_CURRENT_ADDRL_0\t\t\t(0x0044)\n+#define REG_CLDMA_UL_CURRENT_ADDRH_0\t\t\t(0x0048)\n+#define REG_CLDMA_UL_STATUS\t\t\t\t(0x0084)\n+#define REG_CLDMA_UL_START_CMD\t\t\t\t(0x0088)\n+#define REG_CLDMA_UL_RESUME_CMD\t\t\t\t(0x008C)\n+#define REG_CLDMA_UL_STOP_CMD\t\t\t\t(0x0090)\n+#define REG_CLDMA_UL_ERROR\t\t\t\t(0x0094)\n+#define REG_CLDMA_UL_CFG\t\t\t\t(0x0098)\n+#define REG_CLDMA_UL_DUMMY_0\t\t\t\t(0x009C)\n+\n+/* CLDMA OUT(Rx) */\n+#define REG_CLDMA_SO_ERROR\t\t\t\t(0x0400 + 0x0100)\n+#define REG_CLDMA_SO_START_CMD\t\t\t\t(0x0400 + 0x01BC)\n+#define REG_CLDMA_SO_RESUME_CMD\t\t\t\t(0x0400 + 0x01C0)\n+#define REG_CLDMA_SO_STOP_CMD\t\t\t\t(0x0400 + 0x01C4)\n+#define REG_CLDMA_SO_DUMMY_0\t\t\t\t(0x0400 + 0x0108)\n+#define REG_CLDMA_SO_CFG\t\t\t\t(0x0400 + 0x0004)\n+#define REG_CLDMA_SO_START_ADDRL_0\t\t\t(0x0400 + 0x0078)\n+#define REG_CLDMA_SO_START_ADDRH_0\t\t\t(0x0400 + 0x007C)\n+#define REG_CLDMA_SO_CUR_ADDRL_0\t\t\t(0x0400 + 0x00B8)\n+#define REG_CLDMA_SO_CUR_ADDRH_0\t\t\t(0x0400 + 0x00BC)\n+#define REG_CLDMA_SO_STATUS\t\t\t\t(0x0400 + 0x00F8)\n+#define REG_CLDMA_DEBUG_ID_EN\t\t\t\t(0x0400 + 0x00FC)\n+#define REG_CLDMA_SO_LAST_UPDATE_ADDRL_0\t\t(0x0400 + 0x01C8)\n+#define REG_CLDMA_SO_LAST_UPDATE_ADDRH_0\t\t(0x0400 + 0x01CC)\n+\n+/* CLDMA MISC */\n+#define REG_CLDMA_L2TISAR0\t\t\t\t(0x0800 + 0x0010)\n+#define REG_CLDMA_L2TISAR1\t\t\t\t(0x0800 + 0x0014)\n+#define REG_CLDMA_L2TIMR0\t\t\t\t(0x0800 + 0x0018)\n+#define REG_CLDMA_L2TIMR1\t\t\t\t(0x0800 + 0x001C)\n+#define REG_CLDMA_L2TIMCR0\t\t\t\t(0x0800 + 0x0020)\n+#define REG_CLDMA_L2TIMCR1\t\t\t\t(0x0800 + 0x0024)\n+#define REG_CLDMA_L2TIMSR0\t\t\t\t(0x0800 + 0x0028)\n+#define REG_CLDMA_L2TIMSR1\t\t\t\t(0x0800 + 0x002C)\n+#define REG_CLDMA_L3TISAR0\t\t\t\t(0x0800 + 0x0030)\n+#define REG_CLDMA_L3TISAR1\t\t\t\t(0x0800 + 0x0034)\n+#define REG_CLDMA_L2RISAR0\t\t\t\t(0x0800 + 0x0050)\n+#define REG_CLDMA_L2RISAR1\t\t\t\t(0x0800 + 0x0054)\n+#define REG_CLDMA_L3RISAR0\t\t\t\t(0x0800 + 0x0070)\n+#define REG_CLDMA_L3RISAR1\t\t\t\t(0x0800 + 0x0074)\n+#define REG_CLDMA_IP_BUSY\t\t\t\t(0x0800 + 0x00B4)\n+#define REG_CLDMA_L3TISAR2\t\t\t\t(0x0800 + 0x00C0)\n+\n+#define REG_CLDMA_L2RIMR0\t\t\t\t(0x0800 + 0x00E8)\n+#define REG_CLDMA_L2RIMR1\t\t\t\t(0x0800 + 0x00EC)\n+#define REG_CLDMA_L2RIMCR0\t\t\t\t(0x0800 + 0x00F0)\n+#define REG_CLDMA_L2RIMCR1\t\t\t\t(0x0800 + 0x00F4)\n+#define REG_CLDMA_L2RIMSR0\t\t\t\t(0x0800 + 0x00F8)\n+#define REG_CLDMA_L2RIMSR1\t\t\t\t(0x0800 + 0x00FC)\n+\n+#define REG_CLDMA_INT_EAP_USIP_MASK\t\t\t(0x0800 + 0x011C)\n+#define REG_CLDMA_RQ1_GPD_DONE_CNT\t\t\t(0x0800 + 0x0174)\n+#define REG_CLDMA_TQ1_GPD_DONE_CNT\t\t\t(0x0800 + 0x0184)\n+\n+#define REG_CLDMA_IP_BUSY_TO_PCIE_MASK\t\t\t(0x0800 + 0x0194)\n+#define REG_CLDMA_IP_BUSY_TO_PCIE_MASK_SET\t\t(0x0800 + 0x0198)\n+#define REG_CLDMA_IP_BUSY_TO_PCIE_MASK_CLR\t\t(0x0800 + 0x019C)\n+\n+#define REG_CLDMA_IP_BUSY_TO_AP_MASK\t\t\t(0x0800 + 0x0200)\n+#define REG_CLDMA_IP_BUSY_TO_AP_MASK_SET\t\t(0x0800 + 0x0204)\n+#define REG_CLDMA_IP_BUSY_TO_AP_MASK_CLR\t\t(0x0800 + 0x0208)\n+#define REG_CLDMA_IP_BUSY_TO_MD_MASK_SET\t\t(0x0800 + 0x0210)\n+#define REG_CLDMA_RX_WORK_TO_REG_MASK_SET\t\t(0x0800 + 0x021C)\n+\n+/* CLDMA RESET */\n+#define REG_INFRA_RST0_SET\t\t\t\t(0x120)\n+#define REG_INFRA_RST0_CLR\t\t\t\t(0x124)\n+#define REG_CLDMA0_RST_SET_BIT\t\t\t\t(8)\n+#define REG_CLDMA0_RST_CLR_BIT\t\t\t\t(8)\n+\n+#endif\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c\nnew file mode 100644\nindex 00000000000000..aab09cab360c37\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c\n@@ -0,0 +1,55 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#include \"mtk_cldma.h\"\n+#include \"mtk_port.h\"\n+#include \"mtk_trans_ctrl.h\"\n+\n+#define TRB_SRV_NUM\t(1)\n+\n+static const int mtk_srv_cfg_m9xx[NR_CLDMA][HW_QUE_NUM] = {\n+\t{0},\n+\t{0},\n+};\n+\n+/* the number of RX GPDs should be at last two */\n+static const struct queue_info mtk_queue_info_m9xx[] = {\n+\t{CCCI_UART2_TX, CCCI_UART2_RX, CLDMA1, TXQ(5), RXQ(5),\n+\t Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},\n+\t{CCCI_MBIM_TX, CCCI_MBIM_RX, CLDMA1, TXQ(2), RXQ(2),\n+\t Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},\n+\t{CCCI_CONTROL_TX, CCCI_CONTROL_RX, CLDMA1, TXQ(0), RXQ(0),\n+\t Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},\n+\t{CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, CLDMA0, TXQ(0), RXQ(0),\n+\t Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},\n+};\n+\n+static const struct mtk_port_cfg port_cfg_m9xx[] = {\n+\t{CCCI_UART2_TX, CCCI_UART2_RX, PORT_TYPE_WWAN, \"AT\",\n+\t\tPORT_F_ALLOW_DROP},\n+\t{CCCI_MBIM_TX, CCCI_MBIM_RX, PORT_TYPE_WWAN, \"MBIM\",\n+\t\tPORT_F_ALLOW_DROP},\n+\t{CCCI_CONTROL_TX, CCCI_CONTROL_RX, PORT_TYPE_INTERNAL, \"MDCTRL\",\n+\t\tPORT_F_ALLOW_DROP},\n+\t{CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, PORT_TYPE_INTERNAL, \"SAPCTRL\",\n+\t\tPORT_F_ALLOW_DROP},\n+};\n+\n+static struct mtk_port_layer_cfg port_layer_cfg_m9xx = {\n+\t.port_cfg = (struct mtk_port_cfg *)port_cfg_m9xx,\n+\t.port_cnt = ARRAY_SIZE(port_cfg_m9xx),\n+};\n+\n+static struct mtk_ctrl_cfg mtk_ctrl_cfg_m9xx = {\n+\t.port_layer_cfg = \u0026port_layer_cfg_m9xx,\n+};\n+\n+struct mtk_ctrl_info mtk_ctrl_info_m9xx = {\n+\t.ctrl_cfg = \u0026mtk_ctrl_cfg_m9xx,\n+\t.srv_cfg = (int **)mtk_srv_cfg_m9xx,\n+\t.queue_info = (struct queue_info *)mtk_queue_info_m9xx,\n+\t.queue_info_num = ARRAY_SIZE(mtk_queue_info_m9xx),\n+\t.trb_srv_num = TRB_SRV_NUM,\n+};\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c\nnew file mode 100644\nindex 00000000000000..13cebe23fb4ab3\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c\n@@ -0,0 +1,1085 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#include \u003clinux/acpi.h\u003e\n+#include \u003clinux/aer.h\u003e\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/debugfs.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/module.h\u003e\n+\n+#include \"mtk_dev.h\"\n+#include \"mtk_trans_ctrl.h\"\n+#include \"mtk_pci.h\"\n+#include \"mtk_pci_reg.h\"\n+#include \"mtk_port.h\"\n+#include \"mtk_port_io.h\"\n+\n+#define MTK_PCI_BAR_NUM\t\t6\n+#define MTK_PCI_TRANSPARENT_ATR_SIZE\t(0x3F)\n+#define MTK_PCI_MINIMUM_ATR_SIZE\t(0x1000)\n+#define ATR_SIZE_LO32_MASK\t\tGENMASK_ULL(31, 0)\n+#define ATR_SIZE_HI32_MASK\t\tGENMASK_ULL(63, 32)\n+#define ATR_SIZE_BIAS_FROM_LO32\t\t2\n+#define ATR_ADDR_ALIGN_MASK\t\t0xFFFFF000\n+#define ATR_EN\t\t\t\tBIT(0)\n+#define ATR_PARAM_OFFSET\t\t16\n+/* Delay between ACPI PXP._OFF and _ON for modem power cycle stabilization */\n+#define MTK_PLDR_POWER_OFF_DELAY_MS\t500\n+#define LE32_TO_U32(x) ((__force u32)(__le32)(x))\n+#define SET_HW_BITS(dest, chs, mhccif, dev)\t\t\\\n+\t({\t\t\t\t\t\t\\\n+\t\tif ((chs) \u0026 (dev))\t\t\t\t\t\\\n+\t\t\t(dest) |= FIELD_PREP(mhccif, 1);\t\t\\\n+\t})\n+\n+struct mtk_mhccif_cb {\n+\tstruct list_head entry;\n+\tint (*evt_cb)(u32 status, void *data);\n+\tvoid *data;\n+\tu32 chs;\n+};\n+\n+/**\n+ * mtk_pci_setup_atr() - Configure a PCIe address translation rule\n+ * @mdev: MTK MD device\n+ * @cfg: ATR configuration parameters\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int mtk_pci_setup_atr(struct mtk_md_dev *mdev, struct mtk_atr_cfg *cfg)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tu32 addr, val, size_h, size_l;\n+\tint atr_size, pos, offset;\n+\n+\tif (cfg-\u003etransparent) {\n+\t\t/* No address conversion is performed */\n+\t\tatr_size = MTK_PCI_TRANSPARENT_ATR_SIZE;\n+\t} else {\n+\t\tif (cfg-\u003esize \u003c MTK_PCI_MINIMUM_ATR_SIZE)\n+\t\t\tcfg-\u003esize = MTK_PCI_MINIMUM_ATR_SIZE;\n+\n+\t\tif (cfg-\u003esrc_addr \u0026 (cfg-\u003esize - 1)) {\n+\t\t\tdev_err(mdev-\u003edev, \"Invalid atr src addr is not aligned to size\\n\");\n+\t\t\treturn -EFAULT;\n+\t\t}\n+\n+\t\tif (cfg-\u003etrsl_addr \u0026 (cfg-\u003esize - 1)) {\n+\t\t\tdev_err(mdev-\u003edev,\n+\t\t\t\t\"Invalid atr trsl addr is not aligned to size, %llx, %llx\\n\",\n+\t\t\t\tcfg-\u003etrsl_addr, cfg-\u003esize - 1);\n+\t\t\treturn -EFAULT;\n+\t\t}\n+\n+\t\tsize_l = FIELD_GET(ATR_SIZE_LO32_MASK, cfg-\u003esize);\n+\t\tsize_h = FIELD_GET(ATR_SIZE_HI32_MASK, cfg-\u003esize);\n+\t\tpos = ffs(size_l);\n+\t\tif (pos) {\n+\t\t\tatr_size = pos - ATR_SIZE_BIAS_FROM_LO32;\n+\t\t} else {\n+\t\t\tpos = ffs(size_h);\n+\t\t\tatr_size = pos + 32 - ATR_SIZE_BIAS_FROM_LO32;\n+\t\t}\n+\t}\n+\n+\t/* Calculate table offset */\n+\toffset = ATR_PORT_OFFSET * cfg-\u003eport + ATR_TABLE_OFFSET * cfg-\u003etable;\n+\taddr = REG_ATR_PCIE_WIN0_T0_SRC_ADDR_MSB + offset;\n+\tval = (u32)(cfg-\u003esrc_addr \u003e\u003e 32);\n+\tmtk_pci_mac_write32(priv, addr, val);\n+\n+\taddr = REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset;\n+\tval = (u32)(cfg-\u003esrc_addr \u0026 ATR_ADDR_ALIGN_MASK) | (atr_size \u003c\u003c 1) | ATR_EN;\n+\tmtk_pci_mac_write32(priv, addr, val);\n+\n+\taddr = REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_MSB + offset;\n+\tval = (u32)(cfg-\u003etrsl_addr \u003e\u003e 32);\n+\tmtk_pci_mac_write32(priv, addr, val);\n+\n+\taddr = REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_LSB + offset;\n+\tval = (u32)(cfg-\u003etrsl_addr \u0026 ATR_ADDR_ALIGN_MASK);\n+\tmtk_pci_mac_write32(priv, addr, val);\n+\n+\t/* TRSL_PARAM */\n+\taddr = REG_ATR_PCIE_WIN0_T0_TRSL_PARAM + offset;\n+\tval = (cfg-\u003etrsl_param \u003c\u003c ATR_PARAM_OFFSET) | cfg-\u003etrsl_id;\n+\tmtk_pci_mac_write32(priv, addr, val);\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * mtk_pci_atr_disable() - Disable all PCIe address translation rules\n+ * @priv: MTK PCI private data\n+ */\n+void mtk_pci_atr_disable(struct mtk_pci_priv *priv)\n+{\n+\tint port, tbl, offset;\n+\tu32 val;\n+\n+\t/* Disable all ATR table for all ports */\n+\tfor (port = ATR_SRC_PCI_WIN0; port \u003c= ATR_SRC_AXIS_3; port++)\n+\t\tfor (tbl = 0; tbl \u003c ATR_TABLE_NUM_PER_ATR; tbl++) {\n+\t\t\t/* Calculate table offset */\n+\t\t\toffset = ATR_PORT_OFFSET * port + ATR_TABLE_OFFSET * tbl;\n+\t\t\tval = mtk_pci_mac_read32(priv, REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset);\n+\t\t\tval = val \u0026 (~BIT(0));\n+\t\t\t/* Disable table by SRC_ADDR_L */\n+\t\t\tmtk_pci_mac_write32(priv, REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset, val);\n+\t\t}\n+}\n+\n+static void mtk_pci_set_msix_merged(struct mtk_pci_priv *priv, int irq_cnt)\n+{\n+\tmtk_pci_mac_write32(priv, REG_PCIE_CFG_MSIX, ffs(irq_cnt) * 2 - 1);\n+}\n+\n+/**\n+ * mtk_pci_get_dev_state() - Read the device state from the modem\n+ * @mdev: MTK MD device\n+ *\n+ * Return: Device state value.\n+ */\n+u32 mtk_pci_get_dev_state(struct mtk_md_dev *mdev)\n+{\n+\treturn mtk_pci_mac_read32(mdev-\u003ehw_priv, REG_PCIE_DEBUG_DUMMY_7);\n+}\n+\n+/**\n+ * mtk_pci_ack_dev_state() - Acknowledge the device state to the modem\n+ * @mdev: MTK MD device\n+ * @state: State value to acknowledge\n+ */\n+void mtk_pci_ack_dev_state(struct mtk_md_dev *mdev, u32 state)\n+{\n+\tmtk_pci_mac_write32(mdev-\u003ehw_priv, REG_PCIE_DEBUG_DUMMY_7, state);\n+}\n+\n+/**\n+ * mtk_pci_get_irq_id() - Map an IRQ source to its hardware IRQ ID\n+ * @mdev: MTK MD device\n+ * @irq_src: IRQ source enum\n+ *\n+ * Return: IRQ ID on success, -EINVAL on failure.\n+ */\n+int mtk_pci_get_irq_id(struct mtk_md_dev *mdev, enum mtk_irq_src irq_src)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tconst int *irq_tbl = priv-\u003ecfg-\u003eirq_tbl;\n+\tint irq_id = -EINVAL;\n+\n+\tif (irq_src \u003e MTK_IRQ_SRC_MIN \u0026\u0026 irq_src \u003c MTK_IRQ_SRC_MAX) {\n+\t\tirq_id = irq_tbl[irq_src];\n+\t\tif (irq_id \u003c 0 || irq_id \u003e= MTK_IRQ_CNT_MAX)\n+\t\t\tirq_id = -EINVAL;\n+\t}\n+\n+\treturn irq_id;\n+}\n+\n+/**\n+ * mtk_pci_get_virq_id() - Get the Linux virtual IRQ for a hardware IRQ ID\n+ * @mdev: MTK MD device\n+ * @irq_id: Hardware IRQ ID\n+ *\n+ * Return: Virtual IRQ number on success, negative error code on failure.\n+ */\n+int mtk_pci_get_virq_id(struct mtk_md_dev *mdev, int irq_id)\n+{\n+\tstruct pci_dev *pdev = to_pci_dev(mdev-\u003edev);\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\n+\tif (!priv-\u003eirq_cnt || irq_id \u003c 0)\n+\t\treturn -EINVAL;\n+\n+\treturn pci_irq_vector(pdev, irq_id % priv-\u003eirq_cnt);\n+}\n+\n+/**\n+ * mtk_pci_register_irq() - Register a callback for a hardware IRQ\n+ * @mdev: MTK MD device\n+ * @irq_id: Hardware IRQ ID\n+ * @irq_cb: Callback function\n+ * @data: Private data passed to callback\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int mtk_pci_register_irq(struct mtk_md_dev *mdev, int irq_id,\n+\t\t\t int (*irq_cb)(int irq_id, void *data), void *data)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\n+\tif ((irq_id \u003c 0 || irq_id \u003e= MTK_IRQ_CNT_MAX) || !irq_cb)\n+\t\treturn -EINVAL;\n+\n+\tif (priv-\u003eirq_cb_list[irq_id]) {\n+\t\tdev_err(mdev-\u003edev,\n+\t\t\t\"Unable to register irq, irq_id=%d, it's already been register by %ps.\\n\",\n+\t\t\tirq_id, priv-\u003eirq_cb_list[irq_id]);\n+\t\treturn -EFAULT;\n+\t}\n+\tpriv-\u003eirq_cb_list[irq_id] = irq_cb;\n+\tpriv-\u003eirq_cb_data[irq_id] = data;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * mtk_pci_unregister_irq() - Unregister a hardware IRQ callback\n+ * @mdev: MTK MD device\n+ * @irq_id: Hardware IRQ ID\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int mtk_pci_unregister_irq(struct mtk_md_dev *mdev, int irq_id)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\n+\tif (irq_id \u003c 0 || irq_id \u003e= MTK_IRQ_CNT_MAX)\n+\t\treturn -EINVAL;\n+\n+\tif (!priv-\u003eirq_cb_list[irq_id]) {\n+\t\tdev_err(mdev-\u003edev, \"irq_id=%d has not been registered\\n\", irq_id);\n+\t\treturn -EFAULT;\n+\t}\n+\tpriv-\u003eirq_cb_list[irq_id] = NULL;\n+\tpriv-\u003eirq_cb_data[irq_id] = NULL;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * mtk_pci_mask_irq() - Mask (disable) a hardware IRQ\n+ * @mdev: MTK MD device\n+ * @irq_id: Hardware IRQ ID\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int mtk_pci_mask_irq(struct mtk_md_dev *mdev, int irq_id)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\n+\tif (irq_id \u003c 0 || irq_id \u003e= MTK_IRQ_CNT_MAX ||\n+\t    priv-\u003eirq_type != PCI_IRQ_MSIX) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to mask irq: input irq_id=%d\\n\", irq_id);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tmtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, BIT(irq_id));\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * mtk_pci_unmask_irq() - Unmask (enable) a hardware IRQ\n+ * @mdev: MTK MD device\n+ * @irq_id: Hardware IRQ ID\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int mtk_pci_unmask_irq(struct mtk_md_dev *mdev, int irq_id)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\n+\tif (irq_id \u003c 0 || irq_id \u003e= MTK_IRQ_CNT_MAX ||\n+\t    priv-\u003eirq_type != PCI_IRQ_MSIX) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to unmask irq: input irq_id=%d\\n\", irq_id);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tmtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_SET_GRP0_0, BIT(irq_id));\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * mtk_pci_clear_irq() - Clear (acknowledge) a hardware IRQ\n+ * @mdev: MTK MD device\n+ * @irq_id: Hardware IRQ ID\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int mtk_pci_clear_irq(struct mtk_md_dev *mdev, int irq_id)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\n+\tif (irq_id \u003c 0 || irq_id \u003e= MTK_IRQ_CNT_MAX ||\n+\t    priv-\u003eirq_type != PCI_IRQ_MSIX) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to clear irq: input irq_id=%d\\n\", irq_id);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tmtk_pci_mac_write32(priv, REG_MSIX_ISTATUS_HOST_GRP0_0, BIT(irq_id));\n+\n+\treturn 0;\n+}\n+\n+static u32 mtk_pci_ext_d2h_evt_hw_bits(u32 chs)\n+{\n+\tu32 hw_bits = 0;\n+\n+\tSET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC,\n+\t\t    DEV_EVT_D2H_BOOT_FLOW_SYNC);\n+\tSET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP,\n+\t\t    DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP);\n+\tSET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD,\n+\t\t    DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD);\n+\n+\treturn LE32_TO_U32(cpu_to_le32(hw_bits));\n+}\n+\n+static u32 mtk_pci_ext_d2h_evt_chs(u32 hw_bits)\n+{\n+\tu32 chs = 0;\n+\n+\tif (!hw_bits)\n+\t\treturn chs;\n+\n+\tchs = FIELD_PREP(DEV_EVT_D2H_BOOT_FLOW_SYNC,\n+\t\t\t FIELD_GET(MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC, hw_bits)) |\n+\t      FIELD_PREP(DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP,\n+\t\t\t FIELD_GET(MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP, hw_bits)) |\n+\t      FIELD_PREP(DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD,\n+\t\t\t FIELD_GET(MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD, hw_bits));\n+\n+\treturn chs;\n+}\n+\n+/**\n+ * mtk_pci_register_ext_evt() - Register a callback for MHCCIF device events\n+ * @mdev: MTK MD device\n+ * @chs: Bitmask of event channels to register\n+ * @evt_cb: Callback function\n+ * @data: Private data passed to callback\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int mtk_pci_register_ext_evt(struct mtk_md_dev *mdev, u32 chs,\n+\t\t\t     int (*evt_cb)(u32 status, void *data), void *data)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tstruct mtk_mhccif_cb *cb;\n+\tint ret = 0;\n+\n+\tif (!chs || !evt_cb)\n+\t\treturn -EINVAL;\n+\n+\tspin_lock_bh(\u0026priv-\u003emhccif_lock);\n+\tlist_for_each_entry(cb, \u0026priv-\u003emhccif_cb_list, entry) {\n+\t\tif (cb-\u003echs \u0026 chs) {\n+\t\t\tret = -EFAULT;\n+\t\t\tdev_err(mdev-\u003edev,\n+\t\t\t\t\"Unable to register evt, intersection: chs=0x%08x\u00260x%08x cb=%ps\\n\",\n+\t\t\t\tchs, cb-\u003echs, cb-\u003eevt_cb);\n+\t\t\tgoto err_spin_unlock;\n+\t\t}\n+\t}\n+\tcb = kzalloc(sizeof(*cb), GFP_ATOMIC);\n+\tif (!cb) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_spin_unlock;\n+\t}\n+\tcb-\u003eevt_cb = evt_cb;\n+\tcb-\u003edata = data;\n+\tcb-\u003echs = chs;\n+\tlist_add_tail(\u0026cb-\u003eentry, \u0026priv-\u003emhccif_cb_list);\n+err_spin_unlock:\n+\tspin_unlock_bh(\u0026priv-\u003emhccif_lock);\n+\n+\treturn ret;\n+}\n+\n+/**\n+ * mtk_pci_unregister_ext_evt() - Unregister an MHCCIF device event callback\n+ * @mdev: MTK MD device\n+ * @chs: Bitmask of event channels to unregister\n+ */\n+void mtk_pci_unregister_ext_evt(struct mtk_md_dev *mdev, u32 chs)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tstruct mtk_mhccif_cb *cb, *next;\n+\n+\tif (!chs)\n+\t\treturn;\n+\n+\tspin_lock_bh(\u0026priv-\u003emhccif_lock);\n+\tlist_for_each_entry_safe(cb, next, \u0026priv-\u003emhccif_cb_list, entry) {\n+\t\tif (cb-\u003echs == chs) {\n+\t\t\tlist_del(\u0026cb-\u003eentry);\n+\t\t\tkfree(cb);\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\tdev_warn(mdev-\u003edev,\n+\t\t \"Unable to unregister evt, no chs=0x%08x has been registered.\\n\", chs);\n+out:\n+\tspin_unlock_bh(\u0026priv-\u003emhccif_lock);\n+}\n+\n+/**\n+ * mtk_pci_mask_ext_evt() - Mask (disable) MHCCIF device events\n+ * @mdev: MTK MD device\n+ * @chs: Bitmask of event channels to mask\n+ */\n+void mtk_pci_mask_ext_evt(struct mtk_md_dev *mdev, u32 chs)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tu32 hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);\n+\n+\tmtk_pci_write32(mdev, priv-\u003ecfg-\u003emhccif_rc_base_addr +\n+\t\t\tMHCCIF_EP2RC_SW_INT_EAP_MASK_SET, hw_bits);\n+}\n+\n+/**\n+ * mtk_pci_unmask_ext_evt() - Unmask (enable) MHCCIF device events\n+ * @mdev: MTK MD device\n+ * @chs: Bitmask of event channels to unmask\n+ */\n+void mtk_pci_unmask_ext_evt(struct mtk_md_dev *mdev, u32 chs)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tu32 hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);\n+\n+\tmtk_pci_write32(mdev, priv-\u003ecfg-\u003emhccif_rc_base_addr +\n+\t\t\tMHCCIF_EP2RC_SW_INT_EAP_MASK_CLR, hw_bits);\n+}\n+\n+/**\n+ * mtk_pci_clear_ext_evt() - Clear (acknowledge) MHCCIF device events\n+ * @mdev: MTK MD device\n+ * @chs: Bitmask of event channels to clear\n+ */\n+void mtk_pci_clear_ext_evt(struct mtk_md_dev *mdev, u32 chs)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tu32 hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);\n+\n+\tmtk_pci_write32(mdev, priv-\u003ecfg-\u003emhccif_rc_base_addr +\n+\t\t\tMHCCIF_EP2RC_SW_INT_ACK, hw_bits);\n+}\n+\n+static u32 mtk_pci_ext_h2d_evt_hw_bits(u32 chs)\n+{\n+\tu32 hw_bits = 0;\n+\n+\tSET_HW_BITS(hw_bits, chs, MHCCIF_RC2EP_EVT_DEVICE_RESET,\n+\t\t    DEV_EVT_H2D_DEVICE_RESET);\n+\n+\treturn LE32_TO_U32(cpu_to_le32(hw_bits));\n+}\n+\n+/**\n+ * mtk_pci_send_ext_evt() - Send an MHCCIF event to the modem\n+ * @mdev: MTK MD device\n+ * @ch: Event channel to trigger (must be a single bit)\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int mtk_pci_send_ext_evt(struct mtk_md_dev *mdev, u32 ch)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tu32 rc_base, hw_bits;\n+\n+\trc_base = priv-\u003ecfg-\u003emhccif_rc_base_addr;\n+\n+\t/* Only allow one ch to be triggered at a time */\n+\tif (!is_power_of_2(ch)) {\n+\t\tdev_err(mdev-\u003edev, \"Unsupported ext evt ch=0x%08x\\n\", ch);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\thw_bits = mtk_pci_ext_h2d_evt_hw_bits(ch);\n+\tmtk_pci_write32(mdev, rc_base + MHCCIF_RC2EP_SW_BSY, hw_bits);\n+\tmtk_pci_write32(mdev, rc_base + MHCCIF_RC2EP_SW_TCHNUM, ffs(hw_bits) - 1);\n+\treturn 0;\n+}\n+\n+static u32 mtk_pci_get_ext_evt_hw_status(struct mtk_md_dev *mdev)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\n+\treturn mtk_pci_read32(mdev, priv-\u003ecfg-\u003emhccif_rc_base_addr +\n+\t\t\t      MHCCIF_EP2RC_SW_INT_STS);\n+}\n+\n+/**\n+ * mtk_pci_fldr() - Perform a Function Level Device Reset via ACPI _RST\n+ * @mdev: MTK MD device\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int mtk_pci_fldr(struct mtk_md_dev *mdev)\n+{\n+\tstruct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };\n+\tacpi_status acpi_ret;\n+\tacpi_handle handle;\n+\n+\tif (acpi_disabled) {\n+\t\tdev_err(mdev-\u003edev, \"Unsupported, acpi function isn't enable\\n\");\n+\t\treturn -ENODEV;\n+\t}\n+\n+\thandle = ACPI_HANDLE(mdev-\u003edev);\n+\n+\tif (!handle) {\n+\t\tdev_err(mdev-\u003edev, \"Unsupported, acpi handle isn't found\\n\");\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tif (!acpi_has_method(handle, \"_RST\")) {\n+\t\tdev_err(mdev-\u003edev, \"Unsupported, _RST method isn't found\\n\");\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tacpi_ret = acpi_evaluate_object(handle, \"_RST\", NULL, \u0026buffer);\n+\tif (ACPI_FAILURE(acpi_ret)) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to execute _RST method: %s\\n\",\n+\t\t\tacpi_format_exception(acpi_ret));\n+\t\treturn -EFAULT;\n+\t}\n+\n+\tacpi_os_free(buffer.pointer);\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * mtk_pci_pldr() - Perform a PCIe Link Down Reset via ACPI PXP._OFF/_ON\n+ * @mdev: MTK MD device\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int mtk_pci_pldr(struct mtk_md_dev *mdev)\n+{\n+\tstruct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };\n+\tstruct pci_dev *bridge;\n+\tacpi_status acpi_ret;\n+\tacpi_handle handle;\n+\n+\tif (acpi_disabled) {\n+\t\tdev_err(mdev-\u003edev, \"Unsupported, acpi function isn't enable\\n\");\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tbridge = pci_upstream_bridge(to_pci_dev(mdev-\u003edev));\n+\tif (!bridge) {\n+\t\tdev_err(mdev-\u003edev, \"Unable to find bridge\\n\");\n+\t\treturn -ENODEV;\n+\t}\n+\n+\thandle = ACPI_HANDLE(\u0026bridge-\u003edev);\n+\tif (!handle) {\n+\t\tdev_err(mdev-\u003edev, \"Unsupported, acpi handle isn't found\\n\");\n+\t\treturn -ENODEV;\n+\t}\n+\tif (!acpi_has_method(handle, \"PXP._OFF\") ||\n+\t    !acpi_has_method(handle, \"PXP._ON\")) {\n+\t\tdev_err(mdev-\u003edev, \"Unsupported, pldr method isn't supported\\n\");\n+\t\treturn -ENODEV;\n+\t}\n+\tacpi_ret = acpi_evaluate_object(handle, \"PXP._OFF\", NULL, \u0026buffer);\n+\tif (ACPI_FAILURE(acpi_ret)) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to execute _OFF method: %s\\n\",\n+\t\t\tacpi_format_exception(acpi_ret));\n+\t\treturn -EFAULT;\n+\t}\n+\tacpi_os_free(buffer.pointer);\n+\n+\tmsleep(MTK_PLDR_POWER_OFF_DELAY_MS);\n+\n+\tbuffer.length = ACPI_ALLOCATE_BUFFER;\n+\tbuffer.pointer = NULL;\n+\tacpi_ret = acpi_evaluate_object(handle, \"PXP._ON\", NULL, \u0026buffer);\n+\tif (ACPI_FAILURE(acpi_ret)) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to execute _ON method: %s\\n\",\n+\t\t\tacpi_format_exception(acpi_ret));\n+\t\treturn -EFAULT;\n+\t}\n+\tacpi_os_free(buffer.pointer);\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * mtk_pci_get_dev_cfg() - Read the device configuration from the modem\n+ * @mdev: MTK MD device\n+ *\n+ * Return: Device configuration value.\n+ */\n+u32 mtk_pci_get_dev_cfg(struct mtk_md_dev *mdev)\n+{\n+\tu32 val;\n+\n+\tval = mtk_pci_mac_read32(mdev-\u003ehw_priv, REG_PCIE_DEBUG_DUMMY_4);\n+\treturn (val \u003e\u003e MTK_CFG_INFO_BIT_SHIFT);\n+}\n+\n+static int mtk_pci_dev_reset(struct mtk_md_dev *mdev, enum mtk_reset_type type)\n+{\n+\tswitch (type) {\n+\tcase RESET_MHCCIF:\n+\t\treturn mtk_pci_send_ext_evt(mdev, DEV_EVT_H2D_DEVICE_RESET);\n+\tcase RESET_FLDR:\n+\t\treturn mtk_pci_fldr(mdev);\n+\tcase RESET_PLDR:\n+\t\treturn mtk_pci_pldr(mdev);\n+\tdefault:\n+\t\treturn -EINVAL;\n+\t}\n+}\n+\n+/**\n+ * mtk_pci_reset() - Reset the modem device\n+ * @mdev: MTK MD device\n+ * @type: Reset type (MHCCIF, FLDR, or PLDR)\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int mtk_pci_reset(struct mtk_md_dev *mdev, enum mtk_reset_type type)\n+{\n+\treturn mtk_pci_dev_reset(mdev, type);\n+}\n+\n+/**\n+ * mtk_pci_link_check() - Check if the PCIe link to the modem is active\n+ * @mdev: MTK MD device\n+ *\n+ * Return: true if the device is present, false otherwise.\n+ */\n+bool mtk_pci_link_check(struct mtk_md_dev *mdev)\n+{\n+\treturn pci_device_is_present(to_pci_dev(mdev-\u003edev));\n+}\n+\n+static void mtk_mhccif_isr_work(struct work_struct *work)\n+{\n+\tstruct mtk_pci_priv *priv =\n+\t\tcontainer_of(work, struct mtk_pci_priv, mhccif_work);\n+\tstruct mtk_md_dev *mdev = priv-\u003eirq_desc-\u003emdev;\n+\tstruct mtk_mhccif_cb *cb;\n+\tu32 stat, mask, chs;\n+\n+\tstat = mtk_pci_get_ext_evt_hw_status(mdev);\n+\tmask = mtk_pci_read32(mdev, priv-\u003ecfg-\u003emhccif_rc_base_addr\n+\t\t+ MHCCIF_EP2RC_SW_INT_EAP_MASK);\n+\tif (unlikely(stat == U32_MAX \u0026\u0026 !(mtk_pci_link_check(mdev)))) {\n+\t\t/* When link failed, we don't need to unmask/clear. */\n+\t\tdev_err(mdev-\u003edev, \"Failed to check link in MHCCIF handler.\\n\");\n+\t\treturn;\n+\t}\n+\n+\tstat \u0026= ~mask;\n+\tchs = mtk_pci_ext_d2h_evt_chs(stat);\n+\tspin_lock_bh(\u0026priv-\u003emhccif_lock);\n+\tlist_for_each_entry(cb, \u0026priv-\u003emhccif_cb_list, entry) {\n+\t\tif (cb-\u003echs \u0026 chs)\n+\t\t\tcb-\u003eevt_cb(cb-\u003echs \u0026 chs, cb-\u003edata);\n+\t}\n+\tspin_unlock_bh(\u0026priv-\u003emhccif_lock);\n+\n+\tmtk_pci_clear_irq(mdev, priv-\u003emhccif_irq_id);\n+\tmtk_pci_unmask_irq(mdev, priv-\u003emhccif_irq_id);\n+}\n+\n+static const struct  pci_device_id t9xx_pci_table[] = {\n+\tMTK_PCI_DEV_CFG(0x0900, mtk_dev_cfg_0900),\n+\tCEI_PCI_DEV_CFG(0x01CA, mtk_dev_cfg_0900),\n+\t{/* end: all zeroes */}\n+};\n+\n+MODULE_DEVICE_TABLE(pci, t9xx_pci_table);\n+\n+static int mtk_pci_bar_init(struct mtk_md_dev *mdev)\n+{\n+\tstruct pci_dev *pdev = to_pci_dev(mdev-\u003edev);\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tu32 bar[MTK_PCI_BAR_NUM];\n+\tint i, ret;\n+\n+\tfor (i = 0; i \u003c MTK_PCI_BAR_NUM; i++)\n+\t\tpci_read_config_dword(to_pci_dev(mdev-\u003edev),\n+\t\t\t\t      PCI_BASE_ADDRESS_0 + (i \u003c\u003c 2), bar + i);\n+\n+\tret = pcim_iomap_regions(pdev, MTK_REQUESTED_BARS, mdev-\u003edev_str);\n+\tif (ret) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to init MMIO. ret=%d\\n\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\t/* get ioremapped memory */\n+\tpriv-\u003emac_reg_base = pcim_iomap_table(pdev)[MTK_BAR_0_1_IDX];\n+\tpriv-\u003ebar23_addr = pcim_iomap_table(pdev)[MTK_BAR_2_3_IDX];\n+\tif (!priv-\u003emac_reg_base || !priv-\u003ebar23_addr) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to init BAR.\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\t/* We use MD view base address \"0\" to observe registers */\n+\tpriv-\u003eext_reg_base = priv-\u003ebar23_addr - ATR_PCIE_REG_TRSL_ADDR;\n+\n+\treturn 0;\n+}\n+\n+static int mtk_mhccif_irq_cb(int irq_id, void *data)\n+{\n+\tstruct mtk_md_dev *mdev = data;\n+\tstruct mtk_pci_priv *priv;\n+\n+\tpriv = mdev-\u003ehw_priv;\n+\tqueue_work(system_highpri_wq, \u0026priv-\u003emhccif_work);\n+\n+\treturn 0;\n+}\n+\n+static int mtk_mhccif_init(struct mtk_md_dev *mdev)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tint ret;\n+\n+\tINIT_LIST_HEAD(\u0026priv-\u003emhccif_cb_list);\n+\tspin_lock_init(\u0026priv-\u003emhccif_lock);\n+\tINIT_WORK(\u0026priv-\u003emhccif_work, mtk_mhccif_isr_work);\n+\n+\tret = mtk_pci_get_irq_id(mdev, MTK_IRQ_SRC_MHCCIF);\n+\tif (ret \u003c 0) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to get mhccif_irq_id. ret=%d\\n\", ret);\n+\t\treturn ret;\n+\t}\n+\tpriv-\u003emhccif_irq_id = ret;\n+\n+\tret = mtk_pci_register_irq(mdev, priv-\u003emhccif_irq_id, mtk_mhccif_irq_cb, mdev);\n+\tif (ret) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to register mhccif_irq callback\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void mtk_mhccif_exit(struct mtk_md_dev *mdev)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\n+\tmtk_pci_unregister_irq(mdev, priv-\u003emhccif_irq_id);\n+\tcancel_work_sync(\u0026priv-\u003emhccif_work);\n+}\n+\n+static irqreturn_t mtk_pci_irq_handler(struct mtk_md_dev *mdev, u32 irq_state)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tint irq_id;\n+\n+\t/* Check whether each set bit has a callback, if has, call it */\n+\tdo {\n+\t\tirq_id = fls(irq_state) - 1;\n+\t\tirq_state \u0026= ~BIT(irq_id);\n+\t\tif (likely(priv-\u003eirq_cb_list[irq_id]))\n+\t\t\tpriv-\u003eirq_cb_list[irq_id](irq_id, priv-\u003eirq_cb_data[irq_id]);\n+\t\telse\n+\t\t\tdev_err(mdev-\u003edev, \"Unhandled irq_id=%d, no callback for it.\\n\", irq_id);\n+\t} while (irq_state);\n+\n+\treturn IRQ_HANDLED;\n+}\n+\n+static irqreturn_t mtk_pci_irq_msix(int irq, void *data)\n+{\n+\tstruct mtk_pci_irq_desc *irq_desc = data;\n+\tstruct mtk_md_dev *mdev = irq_desc-\u003emdev;\n+\tstruct mtk_pci_priv *priv;\n+\tu32 irq_state, irq_enable;\n+\n+\tpriv = mdev-\u003ehw_priv;\n+\tirq_state = mtk_pci_mac_read32(priv, REG_MSIX_ISTATUS_HOST_GRP0_0);\n+\tirq_enable = mtk_pci_mac_read32(priv, REG_IMASK_HOST_MSIX_GRP0_0);\n+\tirq_state \u0026= irq_enable;\n+\n+\tif (unlikely(!irq_state) ||\n+\t    unlikely(!((irq_state \u0026 GENMASK(priv-\u003eirq_cnt - 1, 0)) \u0026\n+\t\t      irq_desc-\u003emsix_bits)))\n+\t\treturn IRQ_NONE;\n+\n+\t/* Mask the bit and user needs to unmask by itself */\n+\tmtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0,\n+\t\t\t    irq_state \u0026 ~BIT(30));\n+\n+\treturn mtk_pci_irq_handler(mdev, irq_state);\n+}\n+\n+static int mtk_pci_request_irq_msix(struct mtk_md_dev *mdev,\n+\t\t\t\t    int irq_cnt_allocated)\n+{\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tstruct mtk_pci_irq_desc *irq_desc;\n+\tstruct pci_dev *pdev;\n+\tint irq_cnt;\n+\tint ret, i;\n+\n+\t/* calculate the nearest 2's power number */\n+\tirq_cnt = BIT(fls(irq_cnt_allocated) - 1);\n+\tpdev = to_pci_dev(mdev-\u003edev);\n+\tirq_desc = priv-\u003eirq_desc;\n+\tfor (i = 0; i \u003c irq_cnt; i++) {\n+\t\tirq_desc[i].mdev = mdev;\n+\t\tirq_desc[i].msix_bits = BIT(i);\n+\t\tsnprintf(irq_desc[i].name, MTK_IRQ_NAME_LEN, \"msix%d-%s\", i, mdev-\u003edev_str);\n+\t\tret = pci_request_irq(pdev, i, mtk_pci_irq_msix, NULL,\n+\t\t\t\t      \u0026irq_desc[i], irq_desc[i].name);\n+\t\tif (ret) {\n+\t\t\tdev_err(mdev-\u003edev, \"Failed to request %s: ret=%d\\n\",\n+\t\t\t\tirq_desc[i].name, ret);\n+\t\t\tfor (i--; i \u003e= 0; i--)\n+\t\t\t\tpci_free_irq(pdev, i, \u0026irq_desc[i]);\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\tpriv-\u003eirq_cnt = irq_cnt;\n+\tpriv-\u003eirq_type = PCI_IRQ_MSIX;\n+\n+\tif (irq_cnt != MTK_IRQ_CNT_MAX)\n+\t\tmtk_pci_set_msix_merged(priv, irq_cnt);\n+\n+\treturn 0;\n+}\n+\n+static int mtk_pci_request_irq(struct mtk_md_dev *mdev)\n+{\n+\tstruct pci_dev *pdev = to_pci_dev(mdev-\u003edev);\n+\tint irq_cnt, ret;\n+\n+\tirq_cnt = pci_alloc_irq_vectors(pdev, MTK_IRQ_CNT_MIN,\n+\t\t\t\t\tMTK_IRQ_CNT_MAX, PCI_IRQ_MSIX);\n+\n+\tif (irq_cnt \u003c MTK_IRQ_CNT_MIN) {\n+\t\tdev_err(mdev-\u003edev,\n+\t\t\t\"Unable to alloc pci irq vectors. ret=%d maxirqcnt=%d irqtype=0x%x\\n\",\n+\t\t\tirq_cnt, MTK_IRQ_CNT_MAX, PCI_IRQ_MSIX);\n+\t\treturn -EFAULT;\n+\t}\n+\n+\tret = mtk_pci_request_irq_msix(mdev, irq_cnt);\n+\tif (ret)\n+\t\tpci_free_irq_vectors(pdev);\n+\n+\treturn ret;\n+}\n+\n+static void mtk_pci_free_irq(struct mtk_md_dev *mdev)\n+{\n+\tstruct pci_dev *pdev = to_pci_dev(mdev-\u003edev);\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c priv-\u003eirq_cnt; i++)\n+\t\tpci_free_irq(pdev, i, \u0026priv-\u003eirq_desc[i]);\n+\n+\tpci_free_irq_vectors(pdev);\n+}\n+\n+static int mtk_pci_dev_init(struct mtk_md_dev *mdev)\n+{\n+\tint ret;\n+\n+\tret = mtk_fsm_init(mdev);\n+\tif (ret) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to initialize FSM: %d\\n\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\tret = mtk_trans_ctrl_init(mdev);\n+\tif (ret)\n+\t\tgoto free_fsm;\n+\n+\treturn 0;\n+free_fsm:\n+\tmtk_fsm_exit(mdev);\n+\treturn ret;\n+}\n+\n+static void mtk_pci_dev_exit(struct mtk_md_dev *mdev)\n+{\n+\tmtk_fsm_evt_submit(mdev, FSM_EVT_DEV_RM, 0, NULL, 0,\n+\t\t\t   EVT_MODE_BLOCKING | EVT_MODE_TOHEAD);\n+\tmtk_trans_ctrl_exit(mdev);\n+\tmtk_fsm_exit(mdev);\n+}\n+\n+static int mtk_pci_dev_start(struct mtk_md_dev *mdev)\n+{\n+\tmtk_fsm_evt_submit(mdev, FSM_EVT_DEV_ADD, 0, NULL, 0, 0);\n+\tmtk_fsm_start(mdev);\n+\treturn 0;\n+}\n+static const struct mtk_dev_ops pci_hw_ops = {\n+\t.get_dev_state = mtk_pci_get_dev_state,\n+\t.ack_dev_state = mtk_pci_ack_dev_state,\n+\t.get_dev_cfg = mtk_pci_get_dev_cfg,\n+\t.register_dev_evt = mtk_pci_register_ext_evt,\n+\t.unregister_dev_evt = mtk_pci_unregister_ext_evt,\n+\t.mask_dev_evt = mtk_pci_mask_ext_evt,\n+\t.unmask_dev_evt = mtk_pci_unmask_ext_evt,\n+\t.clear_dev_evt = mtk_pci_clear_ext_evt,\n+\t.send_dev_evt = mtk_pci_send_ext_evt,\n+};\n+\n+static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)\n+{\n+\tstruct device *dev = \u0026pdev-\u003edev;\n+\tstruct mtk_pci_priv *priv;\n+\tstruct mtk_md_dev *mdev;\n+\tint ret;\n+\n+\tmdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL);\n+\tif (!mdev) {\n+\t\tret = -ENOMEM;\n+\t\tgoto log_err;\n+\t}\n+\tmdev-\u003edev_ops = \u0026pci_hw_ops;\n+\tmdev-\u003edev = dev;\n+\n+\tpriv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);\n+\tif (!priv) {\n+\t\tret = -ENOMEM;\n+\t\tgoto log_err;\n+\t}\n+\n+\tpci_set_drvdata(pdev, mdev);\n+\tpriv-\u003ecfg = (void *)id-\u003edriver_data;\n+\tpriv-\u003emdev = mdev;\n+\tmdev-\u003ehw_ver  = pdev-\u003edevice;\n+\tmdev-\u003ehw_priv = priv;\n+\tmdev-\u003edev     = dev;\n+\tsnprintf(mdev-\u003edev_str, MTK_DEV_STR_LEN, \"%02x%02x%d\",\n+\t\t pdev-\u003ebus-\u003enumber, PCI_SLOT(pdev-\u003edevfn), PCI_FUNC(pdev-\u003edevfn));\n+\tif (pdev-\u003estate_saved)\n+\t\tpci_restore_state(pdev);\n+\n+\tret = pcim_enable_device(pdev);\n+\tif (ret) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to enable pci device.\\n\");\n+\t\tgoto log_err;\n+\t}\n+\n+\tret = dma_set_mask_and_coherent(\u0026pdev-\u003edev, DMA_BIT_MASK(64));\n+\tif (ret) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to set DMA Mask and Coherent. (ret=%d)\\n\", ret);\n+\t\tgoto log_err;\n+\t}\n+\n+\tret = mtk_pci_bar_init(mdev);\n+\tif (ret)\n+\t\tgoto log_err;\n+\n+\tret = priv-\u003ecfg-\u003eatr_init(mdev);\n+\tif (ret)\n+\t\tgoto log_err;\n+\n+\tret = mtk_mhccif_init(mdev);\n+\tif (ret)\n+\t\tgoto log_err;\n+\n+\t/* mask all irqs */\n+\tif (priv-\u003ecfg-\u003eflag \u0026 MTK_CFG_IRQ_DFLT_MASK)\n+\t\tmtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, U32_MAX);\n+\n+\tret = mtk_pci_request_irq(mdev);\n+\tif (ret)\n+\t\tgoto free_mhccif;\n+\n+\tret = mtk_pci_dev_init(mdev);\n+\tif (ret) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to init dev.\\n\");\n+\t\tgoto free_irq;\n+\t}\n+\n+\tpci_set_master(pdev);\n+\tmtk_pci_unmask_irq(mdev, priv-\u003emhccif_irq_id);\n+\n+\tif (mtk_pci_link_check(mdev)) {\n+\t\tpci_save_state(pdev);\n+\t} else {\n+\t\tret = -ENOLINK;\n+\t\tgoto clear_master;\n+\t}\n+\n+\tpriv-\u003esaved_state = pci_store_saved_state(pdev);\n+\tif (!priv-\u003esaved_state) {\n+\t\tret = -EFAULT;\n+\t\tgoto clear_master;\n+\t}\n+\n+\tret = mtk_pci_dev_start(mdev);\n+\tif (ret) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to start dev.\\n\");\n+\t\tgoto free_saved_state;\n+\t}\n+\n+\treturn 0;\n+\n+free_saved_state:\n+\tpci_load_and_free_saved_state(pdev, \u0026priv-\u003esaved_state);\n+clear_master:\n+\tpci_clear_master(pdev);\n+\tmtk_pci_dev_exit(mdev);\n+free_irq:\n+\tmtk_pci_free_irq(mdev);\n+free_mhccif:\n+\tmtk_mhccif_exit(mdev);\n+log_err:\n+\tdev_err(dev, \"Failed to probe device, ret=%d\\n\", ret);\n+\n+\treturn ret;\n+}\n+\n+static void mtk_pci_remove(struct pci_dev *pdev)\n+{\n+\tstruct mtk_md_dev *mdev = pci_get_drvdata(pdev);\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tstruct device *dev = \u0026pdev-\u003edev;\n+\n+\tmtk_pci_mask_irq(mdev, priv-\u003emhccif_irq_id);\n+\n+\tif (mtk_pci_pldr(mdev)) {\n+\t\tdev_warn(dev, \"Failed to execute PLDR, try external event\\n\");\n+\t\tmtk_pci_reset(mdev, RESET_MHCCIF);\n+\t}\n+\n+\tpci_clear_master(pdev);\n+\tmtk_pci_dev_exit(mdev);\n+\tmtk_pci_free_irq(mdev);\n+\tmtk_mhccif_exit(mdev);\n+\tpci_load_and_free_saved_state(pdev, \u0026priv-\u003esaved_state);\n+}\n+\n+static pci_ers_result_t mtk_pci_error_detected(struct pci_dev *pdev,\n+\t\t\t\t\t       pci_channel_state_t state)\n+{\n+\tstruct mtk_md_dev *mdev = pci_get_drvdata(pdev);\n+\n+\tdev_err(mdev-\u003edev, \"AER detected: pci_channel_state_t=%d\\n\", state);\n+\n+\t/* Request a slot reset. */\n+\treturn PCI_ERS_RESULT_CAN_RECOVER;\n+}\n+\n+static const struct pci_error_handlers mtk_pci_err_handler = {\n+\t.error_detected = mtk_pci_error_detected,\n+};\n+\n+static struct pci_driver mtk_pci_drv = {\n+\t.name = \"mtk_pci_drv\",\n+\t.id_table = t9xx_pci_table,\n+\t.probe = mtk_pci_probe,\n+\t.remove = mtk_pci_remove,\n+\t.err_handler = \u0026mtk_pci_err_handler\n+};\n+\n+module_pci_driver(mtk_pci_drv);\n+\n+MODULE_DESCRIPTION(\"MediaTek T9xx PCIe WWAN driver pcie layer\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.h b/drivers/net/wwan/t9xx/pcie/mtk_pci.h\nnew file mode 100644\nindex 00000000000000..9819a1b07c1b3d\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.h\n@@ -0,0 +1,234 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#ifndef __MTK_PCI_H__\n+#define __MTK_PCI_H__\n+\n+#include \u003clinux/pci.h\u003e\n+\n+#include \"../mtk_dev.h\"\n+\n+enum mtk_irq_src {\n+\tMTK_IRQ_SRC_MIN,\n+\tMTK_IRQ_SRC_MHCCIF,\n+\tMTK_IRQ_SRC_DPMAIF,\n+\tMTK_IRQ_SRC_DPMAIF2,\n+\tMTK_IRQ_SRC_CLDMA0,\n+\tMTK_IRQ_SRC_CLDMA1,\n+\tMTK_IRQ_SRC_CLDMA2,\n+\tMTK_IRQ_SRC_CLDMA3,\n+\tMTK_IRQ_SRC_PM_LOCK,\n+\tMTK_IRQ_SRC_DPMAIF3,\n+\tMTK_IRQ_SRC_DPMAIF6,\n+\tMTK_IRQ_SRC_MAX\n+};\n+\n+enum mtk_reset_type {\n+\tRESET_FLDR,\n+\tRESET_PLDR,\n+\tRESET_MHCCIF,\n+};\n+\n+enum mtk_atr_type {\n+\tATR_PCI2AXI = 0,\n+\tATR_AXI2PCI,\n+};\n+\n+enum mtk_atr_src_port {\n+\tATR_SRC_PCI_WIN0 = 0,\n+\tATR_SRC_PCI_WIN1,\n+\tATR_SRC_AXIS_0,\n+\tATR_SRC_AXIS_1,\n+\tATR_SRC_AXIS_2,\n+\tATR_SRC_AXIS_3,\n+};\n+\n+enum mtk_atr_dst_port {\n+\tATR_DST_PCI_TRX = 0,\n+\tATR_DST_AXIM_0 = 4,\n+\tATR_DST_AXIM_1,\n+\tATR_DST_AXIM_2,\n+\tATR_DST_AXIM_3,\n+};\n+\n+enum mtk_pci_evt_h2d {\n+\tDEV_EVT_H2D_EXTEND_BASE            = DEV_EVT_H2D_MAX,\n+\tEXT_EVT_H2D_RESERVED_FOR_CLDMA0    = DEV_EVT_H2D_EXTEND_BASE \u003c\u003c 1,\n+\tEXT_EVT_H2D_RESERVED_FOR_CLDMA1    = DEV_EVT_H2D_EXTEND_BASE \u003c\u003c 2,\n+\tEXT_EVT_H2D_RESERVED_FOR_CLDMA3    = DEV_EVT_H2D_EXTEND_BASE \u003c\u003c 3,\n+\tEXT_EVT_H2D_RESERVED_FOR_CLDMA2    = DEV_EVT_H2D_EXTEND_BASE \u003c\u003c 4,\n+\tEXT_EVT_H2D_RESERVED_FOR_DPMAIF    = DEV_EVT_H2D_EXTEND_BASE \u003c\u003c 5,\n+\tEXT_EVT_H2D_PCIE_PM_SUSPEND_REQ    = DEV_EVT_H2D_EXTEND_BASE \u003c\u003c 6,\n+\tEXT_EVT_H2D_PCIE_PM_RESUME_REQ     = DEV_EVT_H2D_EXTEND_BASE \u003c\u003c 7,\n+\tEXT_EVT_H2D_PCIE_PM_SUSPEND_REQ_AP = DEV_EVT_H2D_EXTEND_BASE \u003c\u003c 8,\n+\tEXT_EVT_H2D_PCIE_PM_RESUME_REQ_AP  = DEV_EVT_H2D_EXTEND_BASE \u003c\u003c 9,\n+\tEXT_EVT_H2D_RESERVED_FOR_TEST      = DEV_EVT_H2D_EXTEND_BASE \u003c\u003c 11,\n+};\n+\n+enum mtk_pci_evt_d2h {\n+\tDEV_EVT_D2H_EXTEND_BASE            = DEV_EVT_D2H_MAX,\n+\tEXT_EVT_D2H_RESERVED_FOR_CLDMA0    = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 1,\n+\tEXT_EVT_D2H_RESERVED_FOR_CLDMA1    = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 2,\n+\tEXT_EVT_D2H_RESERVED_FOR_CLDMA3    = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 3,\n+\tEXT_EVT_D2H_RESERVED_FOR_CLDMA2    = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 4,\n+\tEXT_EVT_D2H_RESERVED_FOR_DPMAIF    = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 5,\n+\tEXT_EVT_D2H_PCIE_PM_SUSPEND_ACK    = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 6,\n+\tEXT_EVT_D2H_PCIE_PM_RESUME_ACK     = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 7,\n+\tEXT_EVT_D2H_PCIE_PM_SUSPEND_ACK_AP = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 8,\n+\tEXT_EVT_D2H_PCIE_PM_RESUME_ACK_AP  = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 9,\n+\tEXT_EVT_D2H_SOFT_OFF_NOTIFY        = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 10,\n+\tEXT_EVT_D2H_FRC_DONE_NOTIFY        = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 11,\n+\tEXT_EVT_D2H_RESERVED_FOR_TEST1\t   = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 12,\n+\tEXT_EVT_D2H_RESERVED_FOR_TEST2\t   = DEV_EVT_D2H_EXTEND_BASE \u003c\u003c 13,\n+};\n+\n+#define MTK_PCI_CLASS                 0x0D4000\n+#define MTK_PCI_VENDOR_ID             0x14C3\n+#define CEI_PCI_VENDOR_ID             0x03F0\n+\n+#define MTK_CFG_INFO_BIT_SHIFT        4\n+\n+#define MTK_PCI_DEV_CFG(id, cfg) \\\n+{ \\\n+\tPCI_DEVICE(MTK_PCI_VENDOR_ID, id), \\\n+\tMTK_PCI_CLASS, PCI_ANY_ID, \\\n+\t.driver_data = (kernel_ulong_t)\u0026(cfg), \\\n+}\n+\n+#define CEI_PCI_DEV_CFG(id, cfg) \\\n+{ \\\n+\tPCI_DEVICE(CEI_PCI_VENDOR_ID, id), \\\n+\tMTK_PCI_CLASS, PCI_ANY_ID, \\\n+\t.driver_data = (kernel_ulong_t)\u0026(cfg), \\\n+}\n+\n+#define MTK_CFG_IRQ_DFLT_MASK\t\tBIT(0)\n+#define MTK_CFG_DISABLE_AP_DRM\t\tBIT(2)\n+#define MTK_CFG_PM_SW_IRQ\t\tBIT(6)\n+\n+#define MTK_BAR_0_1_IDX                 0\n+#define MTK_BAR_2_3_IDX                 2\n+\n+#define MTK_REQUESTED_BARS \\\n+\t((1 \u003c\u003c MTK_BAR_0_1_IDX) | \\\n+\t (1 \u003c\u003c MTK_BAR_2_3_IDX))\n+\n+#define MTK_IRQ_CNT_MIN\t\t\t\t1\n+#define MTK_IRQ_CNT_MAX\t\t\t\t32\n+#define MTK_IRQ_NAME_LEN\t\t\t32\n+\n+#define ATR_PORT_OFFSET\t\t\t\t0x100\n+#define ATR_TABLE_OFFSET\t\t\t0x20\n+#define ATR_TABLE_NUM_PER_ATR\t\t\t8\n+#define ATR_PCIE_REG_TRSL_ADDR\t\t\t0x10000000\n+#define ATR_PCIE_REG_SIZE\t\t\t0x00400000\n+#define ATR_PCIE_REG_PORT\t\t\tATR_SRC_PCI_WIN0\n+#define ATR_PCIE_REG_TABLE_NUM\t\t\t1\n+#define ATR_PCIE_REG_TRSL_PORT\t\t\tATR_DST_AXIM_0\n+#define ATR_PCIE_DEV_DMA_SRC_ADDR\t\t0x00000000\n+#define ATR_PCIE_DEV_DMA_TRANSPARENT\t\t1\n+#define ATR_PCIE_DEV_DMA_SIZE\t\t\t0\n+#define ATR_PCIE_DEV_DMA_TABLE_NUM\t\t0\n+#define ATR_PCIE_DEV_DMA_TRSL_ADDR\t\t0x00000000\n+\n+struct mtk_pci_irq_desc {\n+\tstruct mtk_md_dev *mdev;\n+\tu32 msix_bits;\n+\tchar name[MTK_IRQ_NAME_LEN];\n+};\n+\n+struct mtk_pci_dev_cfg {\n+\tu32 flag;\n+\tu32 mhccif_rc_base_addr;\n+\tu32 istatus_host_ctrl_addr;\n+\tint irq_tbl[MTK_IRQ_SRC_MAX];\n+\tint (*atr_init)(struct mtk_md_dev *mdev);\n+};\n+\n+extern const struct mtk_pci_dev_cfg mtk_dev_cfg_0900;\n+\n+struct mtk_pci_priv {\n+\tstruct mtk_md_dev *mdev;\n+\tconst struct mtk_pci_dev_cfg *cfg;\n+\tvoid __iomem *bar23_addr;\n+\tvoid __iomem *mac_reg_base;\n+\tvoid __iomem *ext_reg_base;\n+\tint irq_cnt;\n+\tint irq_type;\n+\tvoid *irq_cb_data[MTK_IRQ_CNT_MAX];\n+\n+\tint (*irq_cb_list[MTK_IRQ_CNT_MAX])(int irq_id, void *data);\n+\tstruct mtk_pci_irq_desc irq_desc[MTK_IRQ_CNT_MAX];\n+\tstruct list_head mhccif_cb_list;\n+\t/* mhccif_lock: lock to protect mhccif_cb_list */\n+\tspinlock_t mhccif_lock;\n+\tstruct work_struct mhccif_work;\n+\tint mhccif_irq_id;\n+\tstruct pci_saved_state *saved_state;\n+};\n+\n+struct mtk_atr_cfg {\n+\tu64 src_addr;\n+\tu64 trsl_addr;\n+\tu64 size;\n+\tu32 type;      /* Port type */\n+\tu32 port;      /* Port number */\n+\tu32 table;     /* Table number (8 tables for each port) */\n+\tu32 trsl_id;\n+\tu32 trsl_param;\n+\tu32 transparent;\n+};\n+\n+/* BAR 0/1 MMIO access */\n+static inline u32 mtk_pci_mac_read32(struct mtk_pci_priv *priv, u64 addr)\n+{\n+\treturn ioread32(priv-\u003emac_reg_base + addr);\n+}\n+\n+static inline void mtk_pci_mac_write32(struct mtk_pci_priv *priv, u64 addr, u32 val)\n+{\n+\tiowrite32(val, priv-\u003emac_reg_base + addr);\n+}\n+\n+/* BAR 2/3 MMIO access */\n+static inline u32 mtk_pci_read32(struct mtk_md_dev *mdev, u64 addr)\n+{\n+\treturn ioread32(((struct mtk_pci_priv *)mdev-\u003ehw_priv)-\u003eext_reg_base + addr);\n+}\n+\n+static inline void mtk_pci_write32(struct mtk_md_dev *mdev, u64 addr, u32 val)\n+{\n+\tiowrite32(val, ((struct mtk_pci_priv *)mdev-\u003ehw_priv)-\u003eext_reg_base + addr);\n+}\n+\n+/* Device operations */\n+u32 mtk_pci_get_dev_state(struct mtk_md_dev *mdev);\n+void mtk_pci_ack_dev_state(struct mtk_md_dev *mdev, u32 state);\n+u32 mtk_pci_get_dev_cfg(struct mtk_md_dev *mdev);\n+/* IRQ Related operations */\n+int mtk_pci_get_irq_id(struct mtk_md_dev *mdev, enum mtk_irq_src irq_src);\n+int mtk_pci_get_virq_id(struct mtk_md_dev *mdev, int irq_id);\n+int mtk_pci_register_irq(struct mtk_md_dev *mdev, int irq_id,\n+\t\t\t int (*irq_cb)(int irq_id, void *data), void *data);\n+int mtk_pci_unregister_irq(struct mtk_md_dev *mdev, int irq_id);\n+int mtk_pci_mask_irq(struct mtk_md_dev *mdev, int irq_id);\n+int mtk_pci_unmask_irq(struct mtk_md_dev *mdev, int irq_id);\n+int mtk_pci_clear_irq(struct mtk_md_dev *mdev, int irq_id);\n+/* External event related */\n+int mtk_pci_register_ext_evt(struct mtk_md_dev *mdev, u32 chs,\n+\t\t\t     int (*evt_cb)(u32 status, void *data), void *data);\n+void mtk_pci_unregister_ext_evt(struct mtk_md_dev *mdev, u32 chs);\n+void mtk_pci_mask_ext_evt(struct mtk_md_dev *mdev, u32 chs);\n+void mtk_pci_unmask_ext_evt(struct mtk_md_dev *mdev, u32 chs);\n+void mtk_pci_clear_ext_evt(struct mtk_md_dev *mdev, u32 chs);\n+int mtk_pci_send_ext_evt(struct mtk_md_dev *mdev, u32 ch);\n+int mtk_pci_fldr(struct mtk_md_dev *mdev);\n+int mtk_pci_pldr(struct mtk_md_dev *mdev);\n+int mtk_pci_reset(struct mtk_md_dev *mdev, enum mtk_reset_type type);\n+bool mtk_pci_link_check(struct mtk_md_dev *mdev);\n+int mtk_pci_setup_atr(struct mtk_md_dev *mdev, struct mtk_atr_cfg *cfg);\n+void mtk_pci_atr_disable(struct mtk_pci_priv *priv);\n+\n+#endif /* __MTK_PCI_H__ */\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c\nnew file mode 100644\nindex 00000000000000..88b44142afb79c\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c\n@@ -0,0 +1,69 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+#include \u003clinux/types.h\u003e\n+#include \"mtk_pci.h\"\n+#include \"mtk_pci_reg.h\"\n+\n+static int mtk_pci_atr_init_m9xx(struct mtk_md_dev *mdev)\n+{\n+\tstruct pci_dev *pdev = to_pci_dev(mdev-\u003edev);\n+\tstruct mtk_pci_priv *priv = mdev-\u003ehw_priv;\n+\tstruct mtk_atr_cfg cfg;\n+\tint port, ret;\n+\n+\tmtk_pci_atr_disable(priv);\n+\n+\t/* Config ATR for RC to access device's register */\n+\tcfg.src_addr = pci_resource_start(pdev, MTK_BAR_2_3_IDX);\n+\tcfg.size = ATR_PCIE_REG_SIZE;\n+\tcfg.trsl_addr = ATR_PCIE_REG_TRSL_ADDR;\n+\tcfg.type = ATR_PCI2AXI;\n+\tcfg.port = ATR_PCIE_REG_PORT;\n+\tcfg.table = ATR_PCIE_REG_TABLE_NUM;\n+\tcfg.trsl_id = ATR_PCIE_REG_TRSL_PORT;\n+\tcfg.trsl_param = 0x0;\n+\tcfg.transparent = 0x0;\n+\tret = mtk_pci_setup_atr(mdev, \u0026cfg);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Config ATR for EP to access RC's memory */\n+\tfor (port = ATR_SRC_AXIS_0; port \u003c= ATR_SRC_AXIS_3; port++) {\n+\t\tcfg.src_addr = ATR_PCIE_DEV_DMA_SRC_ADDR;\n+\t\tcfg.size = ATR_PCIE_DEV_DMA_SIZE;\n+\t\tcfg.trsl_addr = ATR_PCIE_DEV_DMA_TRSL_ADDR;\n+\t\tcfg.type = ATR_AXI2PCI;\n+\t\tcfg.port = port;\n+\t\tcfg.table = ATR_PCIE_DEV_DMA_TABLE_NUM;\n+\t\tcfg.trsl_id = ATR_DST_PCI_TRX;\n+\t\tcfg.trsl_param = 0x0;\n+\t\t/* Enable transparent translation */\n+\t\tcfg.transparent = ATR_PCIE_DEV_DMA_TRANSPARENT;\n+\t\tret = mtk_pci_setup_atr(mdev, \u0026cfg);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+const struct mtk_pci_dev_cfg mtk_dev_cfg_0900 = {\n+\t.flag = MTK_CFG_PM_SW_IRQ,\n+\t.mhccif_rc_base_addr = 0x1000A000,\n+\t.istatus_host_ctrl_addr = REG_ISTATUS_HOST_CTRL_NEW,\n+\t.irq_tbl = {\n+\t\t[MTK_IRQ_SRC_DPMAIF]  = 24,\n+\t\t[MTK_IRQ_SRC_CLDMA0]  = 27,\n+\t\t[MTK_IRQ_SRC_CLDMA1]  = 26,\n+\t\t[MTK_IRQ_SRC_CLDMA2]  = 25,\n+\t\t[MTK_IRQ_SRC_MHCCIF]  = 28,\n+\t\t[MTK_IRQ_SRC_DPMAIF2] = 29,\n+\t\t[MTK_IRQ_SRC_CLDMA3]  = 31,\n+\t\t[MTK_IRQ_SRC_PM_LOCK] = 0,\n+\t\t[MTK_IRQ_SRC_DPMAIF3] = 7,\n+\t\t[MTK_IRQ_SRC_DPMAIF6]  = 10,\n+\t},\n+\t.atr_init = mtk_pci_atr_init_m9xx,\n+};\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h b/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h\nnew file mode 100644\nindex 00000000000000..73299ae03f897a\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h\n@@ -0,0 +1,71 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#ifndef __MTK_PCI_REG_H__\n+#define __MTK_PCI_REG_H__\n+\n+#define REG_ISTATUS_HOST_CTRL_NEW\t\t0x031C\n+#define REG_PCIE_MISC_CTRL\t\t\t0x0348\n+#define REG_PCIE_CFG_MSIX\t\t\t0x03EC\n+#define REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB\t0x0600\n+#define REG_ATR_PCIE_WIN0_T0_SRC_ADDR_MSB\t0x0604\n+#define REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_LSB\t0x0608\n+#define REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_MSB\t0x060C\n+#define REG_ATR_PCIE_WIN0_T0_TRSL_PARAM\t\t0x0610\n+#define REG_PCIE_DEBUG_DUMMY_3\t\t\t0x0D0C\n+#define REG_PCIE_DEBUG_DUMMY_4\t\t\t0x0D10\n+#define REG_PCIE_DEBUG_DUMMY_7\t\t\t0x0D1C\n+#define REG_MSIX_ISTATUS_HOST_GRP0_0\t\t0x0F00\n+#define REG_IMASK_HOST_MSIX_SET_GRP0_0\t\t0x3000\n+#define REG_IMASK_HOST_MSIX_CLR_GRP0_0\t\t0x3080\n+#define REG_IMASK_HOST_MSIX_GRP0_0\t\t0x3100\n+#define REG_DEV_INFRA_BASE\t\t\t0x10001000\n+\n+/* mhccif registers */\n+#define MHCCIF_RC2EP_SW_BSY\t\t\t0x4\n+#define MHCCIF_RC2EP_SW_TCHNUM\t\t\t0xC\n+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_CLDMA0\tBIT(4)\n+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_CLDMA1\tBIT(5)\n+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_CLDMA3\tBIT(6)\n+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_CLDMA2\tBIT(7)\n+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_DPMAIF\tBIT(8)\n+#define MHCCIF_RC2EP_EVT_PCIE_PM_SUSPEND_REQ\tBIT(9)\n+#define MHCCIF_RC2EP_EVT_PCIE_PM_RESUME_REQ\tBIT(10)\n+#define MHCCIF_RC2EP_EVT_PCIE_PM_SUSPEND_REQ_AP\tBIT(11)\n+#define MHCCIF_RC2EP_EVT_PCIE_PM_RESUME_REQ_AP\tBIT(12)\n+#define MHCCIF_RC2EP_EVT_DEVICE_RESET\t\tBIT(13)\n+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_TEST\tBIT(31)\n+\n+#define MHCCIF_EP2RC_SW_INT_STS\t\t\t0x10\n+#define MHCCIF_EP2RC_SW_INT_ACK\t\t\t0x14\n+#define MHCCIF_EP2RC_SW_INT_EAP_MASK\t\t0x20\n+#define MHCCIF_EP2RC_SW_INT_EAP_MASK_SET\t0x30\n+#define MHCCIF_EP2RC_SW_INT_EAP_MASK_CLR\t0x40\n+#define MHCCIF_EP2RC_SPARE_REG_1\t\t0x0104\n+#define MHCCIF_EP2RC_SPARE_REG_5\t\t0x0114\n+#define MHCCIF_EP2RC_SPARE_REG_13\t\t0x0134\n+#define MHCCIF_EP2RC_SPARE_REG_14\t\t0x0138\n+#define MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC\t\tBIT(5)\n+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_CLDMA0\tBIT(6)\n+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_CLDMA1\tBIT(7)\n+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_CLDMA3\tBIT(8)\n+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_CLDMA2\tBIT(9)\n+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_DPMAIF\tBIT(10)\n+#define MHCCIF_EP2RC_EVT_PCIE_PM_SUSPEND_ACK\tBIT(11)\n+#define MHCCIF_EP2RC_EVT_PCIE_PM_RESUME_ACK\tBIT(12)\n+#define MHCCIF_EP2RC_EVT_PCIE_PM_SUSPEND_ACK_AP\tBIT(13)\n+#define MHCCIF_EP2RC_EVT_PCIE_PM_RESUME_ACK_AP\tBIT(14)\n+#define MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP\tBIT(15)\n+#define MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD\tBIT(16)\n+#define MHCCIF_EP2RC_EVT_SOFT_OFF_NOTIFY\tBIT(17)\n+#define MHCCIF_EP2RC_EVT_MD_REBOOT\t\tBIT(19)\n+#define MHCCIF_EP2RC_EVT_MD_POWEROFF\t\tBIT(20)\n+#define MHCCIF_EP2RC_EVT_GNSS_ENABLE\t\tBIT(21)\n+#define MHCCIF_EP2RC_EVT_GNSS_DISABLE\t\tBIT(22)\n+#define MHCCIF_EP2RC_EVT_FRC_DONE_NOTIFY\tBIT(24)\n+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_TEST1\tBIT(30)\n+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_TEST2\tBIT(31)\n+\n+#endif /* __MTK_PCI_REG_H__ */\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c\nnew file mode 100644\nindex 00000000000000..fa36a94014ea42\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c\n@@ -0,0 +1,563 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/freezer.h\u003e\n+#include \u003clinux/hashtable.h\u003e\n+#include \u003clinux/kthread.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/nospec.h\u003e\n+#include \u003clinux/sched.h\u003e\n+#include \u003clinux/wait.h\u003e\n+\n+#include \"mtk_cldma.h\"\n+#include \"mtk_ctrl_plane.h\"\n+#include \"mtk_dev.h\"\n+#include \"mtk_pci.h\"\n+#include \"mtk_port.h\"\n+#include \"mtk_trans_ctrl.h\"\n+\n+#define MTK_DFLT_PORT_NAME_LEN\t\t\t(20)\n+\n+static struct mtk_ctrl_info_desc mtk_ctrl_info_tbl[] = {\n+\t{0x01CA, \u0026ctrl_info_name(m9xx)},\n+\t{0, NULL},\n+};\n+\n+#define QUEUE_CHL_MASK\t0xFFFF\n+\n+static bool mtk_queue_list_is_full(struct mtk_ctrl_trans *trans, struct queue_info *que)\n+{\n+\treturn trans-\u003etrans_list[que-\u003ehif_id].skb_list[que-\u003etxqno].qlen \u003e= SKB_LIST_MAX_LEN;\n+}\n+\n+static bool mtk_ctrl_chs_is_busy_or_empty(struct trb_srv *srv)\n+{\n+\tstruct srv_que *srv_que;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c NR_CLDMA; i++)\n+\t\tlist_for_each_entry(srv_que, \u0026srv-\u003esrv_q_list[i], list)\n+\t\t\tif (!skb_queue_empty(\u0026srv-\u003etrans-\u003etrans_list[i].skb_list[srv_que-\u003eqno]) \u0026\u0026\n+\t\t\t    mtk_cldma_get_tx_budget(srv-\u003etrans-\u003edev, i, srv_que-\u003eqno))\n+\t\t\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+static void mtk_ctrl_ch_flush(struct sk_buff_head *skb_list)\n+{\n+\tstruct sk_buff *skb;\n+\tstruct trb *trb;\n+\n+\twhile (!skb_queue_empty(skb_list)) {\n+\t\tskb = skb_dequeue(skb_list);\n+\t\ttrb = (struct trb *)skb-\u003ecb;\n+\t\ttrb-\u003estatus = -EIO;\n+\t\ttrb-\u003etrb_complete(skb);\n+\t}\n+}\n+\n+static void mtk_ctrl_chs_flush(struct trb_srv *srv)\n+{\n+\tstruct srv_que *srv_que;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c NR_CLDMA; i++)\n+\t\tlist_for_each_entry(srv_que, \u0026srv-\u003esrv_q_list[i], list)\n+\t\t\tmtk_ctrl_ch_flush(\u0026srv-\u003etrans-\u003etrans_list[i].skb_list[srv_que-\u003eqno]);\n+}\n+\n+static int mtk_ch_status_check(struct mtk_ctrl_trans *trans, struct sk_buff *skb)\n+{\n+\tstruct trb *trb = (struct trb *)skb-\u003ecb;\n+\tstruct trb_open_priv *trb_open_priv;\n+\tstruct queue_info *que;\n+\tint ret = 0;\n+\n+\tque = radix_tree_lookup(\u0026trans-\u003equeue_tbl, trb-\u003echannel_id \u0026 QUEUE_CHL_MASK);\n+\n+\tswitch (trb-\u003ecmd) {\n+\tcase TRB_CMD_ENABLE:\n+\t\ttrb_open_priv = (struct trb_open_priv *)skb-\u003edata;\n+\t\ttrb_open_priv-\u003elog_rg_offset = que-\u003elog_rg_offset;\n+\t\ttrans-\u003eusr_cnt[que-\u003ehif_id][que-\u003etxqno]++;\n+\t\tif (trans-\u003eusr_cnt[que-\u003ehif_id][que-\u003etxqno] == 1)\n+\t\t\tbreak;\n+\t\ttrb_open_priv-\u003etx_mtu = que-\u003etx_mtu;\n+\t\ttrb_open_priv-\u003erx_mtu = que-\u003erx_mtu;\n+\t\ttrb_open_priv-\u003etx_frag_size = que-\u003etx_frag_size;\n+\t\ttrb_open_priv-\u003erx_frag_size = que-\u003erx_frag_size;\n+\t\tif (mtk_cldma_check_ch_cfg(trans-\u003edev, que)) {\n+\t\t\ttrb-\u003estatus = -EINVAL;\n+\t\t\tret = -EINVAL;\n+\t\t} else {\n+\t\t\ttrb-\u003estatus = -EBUSY;\n+\t\t\tret = -EBUSY;\n+\t\t}\n+\t\ttrb-\u003etrb_complete(skb);\n+\t\tbreak;\n+\tcase TRB_CMD_DISABLE:\n+\t\tif (trans-\u003eusr_cnt[que-\u003ehif_id][que-\u003etxqno] \u003e 0) {\n+\t\t\ttrans-\u003eusr_cnt[que-\u003ehif_id][que-\u003etxqno]--;\n+\t\t\tif (!trans-\u003eusr_cnt[que-\u003ehif_id][que-\u003etxqno])\n+\t\t\t\tbreak;\n+\t\t}\n+\t\ttrb-\u003estatus = -EBUSY;\n+\t\ttrb-\u003etrb_complete(skb);\n+\t\tret = -EBUSY;\n+\t\tbreak;\n+\tdefault:\n+\t\tdev_err((trans-\u003emdev)-\u003edev, \"Invalid trb command(%d)\\n\", trb-\u003ecmd);\n+\t\tret = -EINVAL;\n+\t\tbreak;\n+\t}\n+\treturn ret;\n+}\n+\n+static void mtk_ctrl_trb_handler(struct trb_srv *srv, struct trans_list *trans_list, u32 qno)\n+{\n+\tstruct sk_buff_head *skb_list = \u0026trans_list-\u003eskb_list[qno];\n+\tstruct mtk_ctrl_trans *trans = srv-\u003etrans;\n+\tstruct sk_buff *skb, *skb_next;\n+\tstruct trb *trb, *trb_next;\n+\tbool kick = false;\n+\tint loop = 0;\n+\tint err;\n+\n+\tdo {\n+\t\tskb = skb_peek(skb_list);\n+\t\tif (!skb)\n+\t\t\tbreak;\n+\t\ttrb = (struct trb *)skb-\u003ecb;\n+\t\tkref_get(\u0026trb-\u003ekref);\n+\n+\t\tswitch (trb-\u003ecmd) {\n+\t\tcase TRB_CMD_ENABLE:\n+\t\tcase TRB_CMD_DISABLE:\n+\t\t\tskb_unlink(skb, skb_list);\n+\t\t\terr = mtk_ch_status_check(trans, skb);\n+\t\t\tif (!err) {\n+\t\t\t\tkick = true;\n+\t\t\t\tif (trb-\u003ecmd == TRB_CMD_DISABLE)\n+\t\t\t\t\tmtk_ctrl_ch_flush(skb_list);\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase TRB_CMD_TX:\n+\t\t\terr = mtk_cldma_submit_tx(trans-\u003edev, skb);\n+\t\t\tif (err) {\n+\t\t\t\tif (trans_list-\u003etx_burst_cnt[qno]) {\n+\t\t\t\t\tkick = true;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t\tif (err == -EAGAIN) {\n+\t\t\t\t\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\t\t\t\t\treturn;\n+\t\t\t\t}\n+\n+\t\t\t\tskb_unlink(skb, skb_list);\n+\t\t\t\ttrb-\u003estatus = err;\n+\t\t\t\ttrb-\u003etrb_complete(skb);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\ttrans_list-\u003etx_burst_cnt[qno]++;\n+\t\t\tif (trans_list-\u003etx_burst_cnt[qno] \u003e= TX_BURST_MAX_CNT ||\n+\t\t\t    skb_queue_is_last(skb_list, skb)) {\n+\t\t\t\tkick = true;\n+\t\t\t} else {\n+\t\t\t\tskb_next = skb_peek_next(skb, skb_list);\n+\t\t\t\ttrb_next = (struct trb *)skb_next-\u003ecb;\n+\t\t\t\tif (trb_next-\u003ecmd != TRB_CMD_TX)\n+\t\t\t\t\tkick = true;\n+\t\t\t}\n+\n+\t\t\tskb_unlink(skb, skb_list);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tskb_unlink(skb, skb_list);\n+\t\t}\n+\n+\t\tif (kick) {\n+\t\t\tmtk_cldma_trb_process(trans-\u003edev, skb);\n+\t\t\ttrans_list-\u003etx_burst_cnt[qno] = 0;\n+\t\t\tkick = false;\n+\t\t}\n+\n+\t\tkref_put(\u0026trb-\u003ekref, mtk_port_trb_free);\n+\n+\t\tloop++;\n+\t} while (loop \u003c TRB_NUM_PER_ROUND);\n+}\n+\n+static void mtk_ctrl_trb_process(struct trb_srv *srv)\n+{\n+\tstruct mtk_ctrl_trans *trans = srv-\u003etrans;\n+\tstruct srv_que *srv_que;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c NR_CLDMA; i++)\n+\t\tlist_for_each_entry(srv_que, \u0026srv-\u003esrv_q_list[i], list)\n+\t\t\tmtk_ctrl_trb_handler(srv, \u0026trans-\u003etrans_list[i], srv_que-\u003eqno);\n+}\n+\n+static int mtk_ctrl_trb_thread(void *args)\n+{\n+\tstruct trb_srv *srv = args;\n+\n+\tfor (;;) {\n+\t\twait_event_interruptible(srv-\u003etrb_waitq,\n+\t\t\t\t\t !mtk_ctrl_chs_is_busy_or_empty(srv) ||\n+\t\t\t\t\t kthread_should_stop() || kthread_should_park());\n+\t\tif (kthread_should_stop())\n+\t\t\tbreak;\n+\n+\t\tif (kthread_should_park())\n+\t\t\tkthread_parkme();\n+\n+\t\tdo {\n+\t\t\tmtk_ctrl_trb_process(srv);\n+\t\t\tcond_resched();\n+\t\t} while (!mtk_ctrl_chs_is_busy_or_empty(srv) \u0026\u0026 !kthread_should_stop() \u0026\u0026\n+\t\t\t !kthread_should_park());\n+\t}\n+\tmtk_ctrl_chs_flush(srv);\n+\treturn 0;\n+}\n+\n+static int mtk_ctrl_trb_srv_init(struct mtk_ctrl_trans *trans)\n+{\n+\tstruct srv_que *srv_que;\n+\tstruct trb_srv *srv;\n+\tint i, j;\n+\tint ret;\n+\n+\tfor (i = 0; i \u003c trans-\u003etrb_srv_num; i++) {\n+\t\tsrv = kzalloc_obj(*srv);\n+\t\tif (!srv) {\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto err_free_srv;\n+\t\t}\n+\n+\t\tsrv-\u003etrans = trans;\n+\t\tsrv-\u003esrv_id = i;\n+\t\ttrans-\u003etrb_srv[i] = srv;\n+\n+\t\tinit_waitqueue_head(\u0026srv-\u003etrb_waitq);\n+\t\tfor (j = 0; j \u003c NR_CLDMA; j++)\n+\t\t\tINIT_LIST_HEAD(\u0026srv-\u003esrv_q_list[j]);\n+\t}\n+\n+\tfor (i = 0; i \u003c NR_CLDMA; i++)\n+\t\tfor (j = 0; j \u003c HW_QUE_NUM; j++) {\n+\t\t\tif (trans-\u003esrv_cfg[i][j] \u003c 0 ||\n+\t\t\t    trans-\u003esrv_cfg[i][j] \u003e= trans-\u003etrb_srv_num)\n+\t\t\t\ttrans-\u003esrv_cfg[i][j] = 0;\n+\t\t\tsrv_que = kzalloc_obj(*srv_que);\n+\t\t\tif (!srv_que) {\n+\t\t\t\tret = -ENOMEM;\n+\t\t\t\tgoto err_free_srv_que;\n+\t\t\t}\n+\t\t\tsrv_que-\u003ehif_id = i;\n+\t\t\tsrv_que-\u003eqno = j;\n+\t\t\tlist_add_tail(\u0026srv_que-\u003elist,\n+\t\t\t\t      \u0026trans-\u003etrb_srv[trans-\u003esrv_cfg[i][j]]-\u003esrv_q_list[i]);\n+\t\t}\n+\n+\tfor (i = 0; i \u003c trans-\u003etrb_srv_num; i++) {\n+\t\ttrans-\u003etrb_srv[i]-\u003etrb_thread = kthread_run(mtk_ctrl_trb_thread, trans-\u003etrb_srv[i],\n+\t\t\t\t\t\t\t    \"mtk_trb_srv%d_%s\", i,\n+\t\t\t\t\t\t\t    trans-\u003emdev-\u003edev_str);\n+\t\tif (IS_ERR(trans-\u003etrb_srv[i]-\u003etrb_thread)) {\n+\t\t\tret = PTR_ERR(trans-\u003etrb_srv[i]-\u003etrb_thread);\n+\t\t\ttrans-\u003etrb_srv[i]-\u003etrb_thread = NULL;\n+\t\t\tgoto err_stop_kthread;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+err_stop_kthread:\n+\twhile (--i \u003e= 0)\n+\t\tkthread_stop(trans-\u003etrb_srv[i]-\u003etrb_thread);\n+err_free_srv_que:\n+\tfor (i = 0; i \u003c trans-\u003etrb_srv_num; i++) {\n+\t\tfor (j = 0; j \u003c NR_CLDMA; j++) {\n+\t\t\tstruct srv_que *next_srv_que;\n+\n+\t\t\tlist_for_each_entry_safe(srv_que, next_srv_que,\n+\t\t\t\t\t\t \u0026trans-\u003etrb_srv[i]-\u003esrv_q_list[j], list) {\n+\t\t\t\tlist_del(\u0026srv_que-\u003elist);\n+\t\t\t\tkfree(srv_que);\n+\t\t\t}\n+\t\t}\n+\t}\n+err_free_srv:\n+\tfor (i = 0; i \u003c trans-\u003etrb_srv_num; i++) {\n+\t\tif (!trans-\u003etrb_srv[i])\n+\t\t\tbreak;\n+\t\tkfree(trans-\u003etrb_srv[i]);\n+\t\ttrans-\u003etrb_srv[i] = NULL;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static void mtk_ctrl_trb_srv_exit(struct mtk_ctrl_trans *trans)\n+{\n+\tstruct srv_que *srv_que, *next_srv_que;\n+\tstruct trb_srv *srv;\n+\tint i, j;\n+\n+\tfor (i = 0; i \u003c trans-\u003etrb_srv_num; i++) {\n+\t\tsrv = trans-\u003etrb_srv[i];\n+\t\tkthread_stop(srv-\u003etrb_thread);\n+\t\tfor (j = 0; j \u003c NR_CLDMA; j++) {\n+\t\t\tlist_for_each_entry_safe(srv_que, next_srv_que,\n+\t\t\t\t\t\t \u0026trans-\u003etrb_srv[i]-\u003esrv_q_list[j], list) {\n+\t\t\t\tlist_del(\u0026srv_que-\u003elist);\n+\t\t\t\tkfree(srv_que);\n+\t\t\t}\n+\t\t}\n+\t\tkfree(srv);\n+\t\ttrans-\u003etrb_srv[i] = NULL;\n+\t}\n+}\n+\n+static void mtk_ctrl_remove_radix_tree(struct mtk_ctrl_trans *trans)\n+{\n+\tstruct radix_tree_iter iter;\n+\tstruct queue_info *queue;\n+\tvoid __rcu **slot;\n+\n+\tradix_tree_for_each_slot(slot, \u0026trans-\u003equeue_tbl, \u0026iter, 0) {\n+\t\tqueue = radix_tree_deref_slot(slot);\n+\t\tif (!queue)\n+\t\t\tcontinue;\n+\t\tradix_tree_delete(\u0026trans-\u003equeue_tbl, iter.index);\n+\t\tkfree(queue);\n+\t}\n+}\n+\n+static int mtk_pcie_hif_init(struct mtk_md_dev *mdev)\n+{\n+\tstruct mtk_ctrl_blk *ctrl_blk = mdev-\u003ectrl_blk;\n+\tstruct queue_info *queue, *queue_info;\n+\tstruct mtk_ctrl_trans *trans;\n+\tint i, j;\n+\tint ret;\n+\n+\ttrans = ctrl_blk-\u003ectrl_hw_priv;\n+\ttrans-\u003ectrl_blk = ctrl_blk;\n+\tqueue_info = trans-\u003equeue_info;\n+\n+\tINIT_RADIX_TREE(\u0026trans-\u003equeue_tbl, GFP_KERNEL);\n+\tfor (i = 0; i \u003c trans-\u003equeue_info_num; i++) {\n+\t\tqueue = kmemdup(queue_info + i, sizeof(*queue), GFP_KERNEL);\n+\t\tif (!queue) {\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto err_free_radix_tree;\n+\t\t}\n+\t\tif (queue-\u003etxqno \u003e= HW_QUE_NUM || queue-\u003erxqno \u003e= HW_QUE_NUM ||\n+\t\t    queue-\u003ehif_id \u003e= NR_CLDMA) {\n+\t\t\tdev_err(mdev-\u003edev, \"Failed to get correct queue info %x\\n\",\n+\t\t\t\tqueue-\u003erx_chl);\n+\t\t\tkfree(queue);\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto err_free_radix_tree;\n+\t\t}\n+\t\tret = radix_tree_insert(\u0026trans-\u003equeue_tbl, queue-\u003erx_chl \u0026 QUEUE_CHL_MASK, queue);\n+\t\tif (ret) {\n+\t\t\tdev_err(mdev-\u003edev, \"Insert %x fail, ret: %d\", queue-\u003erx_chl, ret);\n+\t\t\tkfree(queue);\n+\t\t\tgoto err_free_radix_tree;\n+\t\t}\n+\t\ttrans-\u003equeues_cnt++;\n+\t}\n+\n+\tfor (i = 0; i \u003c NR_CLDMA; i++) {\n+\t\tfor (j = 0; j \u003c HW_QUE_NUM; j++) {\n+\t\t\tskb_queue_head_init(\u0026trans-\u003etrans_list[i].skb_list[j]);\n+\t\t\ttrans-\u003etrans_list[i].tx_burst_cnt[j] = 0;\n+\t\t}\n+\t}\n+\tret = mtk_cldma_init(trans);\n+\tif (ret)\n+\t\tgoto err_free_radix_tree;\n+\n+\tret = mtk_ctrl_trb_srv_init(trans);\n+\tif (ret)\n+\t\tgoto err_cldma_exit;\n+\n+\tatomic_set(\u0026trans-\u003eavailable, 1);\n+\n+\treturn 0;\n+\n+err_cldma_exit:\n+\tmtk_cldma_exit(trans);\n+err_free_radix_tree:\n+\tmtk_ctrl_remove_radix_tree(trans);\n+\n+\treturn ret;\n+}\n+\n+static int mtk_pcie_hif_exit(struct mtk_md_dev *mdev)\n+{\n+\tstruct mtk_ctrl_blk *ctrl_blk = mdev-\u003ectrl_blk;\n+\tstruct mtk_ctrl_trans *trans;\n+\n+\ttrans = ctrl_blk-\u003ectrl_hw_priv;\n+\n+\tatomic_set(\u0026trans-\u003eavailable, 0);\n+\tmtk_ctrl_trb_srv_exit(trans);\n+\tmtk_ctrl_remove_radix_tree(trans);\n+\tmtk_cldma_exit(trans);\n+\n+\treturn 0;\n+}\n+\n+static int mtk_pcie_hif_submit_skb(struct mtk_md_dev *mdev, struct sk_buff *skb, bool force_send)\n+{\n+\tstruct mtk_ctrl_blk *ctrl_blk = mdev-\u003ectrl_blk;\n+\tstruct mtk_ctrl_trans *trans;\n+\tstruct queue_info *que;\n+\tstruct trb *trb;\n+\n+\ttrans = ctrl_blk-\u003ectrl_hw_priv;\n+\ttrb = (struct trb *)skb-\u003ecb;\n+\n+\tif (trb-\u003ecmd == TRB_CMD_STOP || trb-\u003ecmd == TRB_CMD_RECOVER) {\n+\t\ttrb-\u003etrb_complete(skb);\n+\t\treturn 0;\n+\t}\n+\n+\tque = radix_tree_lookup(\u0026trans-\u003equeue_tbl, trb-\u003echannel_id \u0026 QUEUE_CHL_MASK);\n+\tif (!que) {\n+\t\tdev_warn(mdev-\u003edev, \"lookup que fail, ch_id: %x, que: 0x%p\\n\",\n+\t\t\t trb-\u003echannel_id, que);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (!atomic_read(\u0026trans-\u003eavailable))\n+\t\treturn -EIO;\n+\n+\tif (mtk_queue_list_is_full(trans, que) \u0026\u0026 !force_send)\n+\t\treturn -EAGAIN;\n+\n+\tif (trb-\u003ecmd == TRB_CMD_DISABLE)\n+\t\tskb_queue_head(\u0026trans-\u003etrans_list[que-\u003ehif_id].skb_list[que-\u003etxqno], skb);\n+\telse\n+\t\tskb_queue_tail(\u0026trans-\u003etrans_list[que-\u003ehif_id].skb_list[que-\u003etxqno], skb);\n+\n+\twake_up(\u0026trans-\u003etrb_srv[trans-\u003esrv_cfg[que-\u003ehif_id][que-\u003etxqno]]-\u003etrb_waitq);\n+\n+\treturn 0;\n+}\n+\n+static void mtk_pcie_hif_fsm_indication(struct mtk_md_dev *mdev, struct mtk_fsm_param *param)\n+{\n+\tstruct mtk_ctrl_blk *ctrl_blk = mdev-\u003ectrl_blk;\n+\tstruct mtk_ctrl_trans *trans;\n+\n+\ttrans = ctrl_blk-\u003ectrl_hw_priv;\n+\tmtk_cldma_fsm_state_listener(param, trans);\n+}\n+\n+static int mtk_pcie_hif_cmd_func(struct mtk_md_dev *mdev, int cmd, void *data)\n+{\n+\tstruct mtk_ctrl_blk *ctrl_blk = mdev-\u003ectrl_blk;\n+\tstruct mtk_ctrl_trans *trans;\n+\tstruct queue_info *que;\n+\n+\tswitch (cmd) {\n+\tcase HIF_CTRL_CMD_CHECK_TX_FULL:\n+\t\ttrans = ctrl_blk-\u003ectrl_hw_priv;\n+\t\tque = radix_tree_lookup(\u0026trans-\u003equeue_tbl,\n+\t\t\t\t\t((union ctrl_hif_cmd_data *)data)-\u003erx_ch \u0026 QUEUE_CHL_MASK);\n+\t\tif (!que) {\n+\t\t\tdev_warn(mdev-\u003edev, \"Failed to find que to check tx full\\n\");\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t\treturn mtk_queue_list_is_full(trans, que);\n+\tdefault:\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static struct mtk_ctrl_hif_ops pcie_ctrl_ops = {\n+\t.init = mtk_pcie_hif_init,\n+\t.exit = mtk_pcie_hif_exit,\n+\t.submit_skb = mtk_pcie_hif_submit_skb,\n+\t.fsm_indication = mtk_pcie_hif_fsm_indication,\n+\t.send_cmd = mtk_pcie_hif_cmd_func,\n+};\n+\n+static void mtk_trans_get_ctrl_info(struct mtk_ctrl_cfg *cfg,\n+\t\t\t\t    struct mtk_ctrl_trans *trans, u32 hw_ver)\n+{\n+\tstruct mtk_ctrl_info_desc *ctrl_info_desc;\n+\tstruct mtk_ctrl_info *ctrl_info;\n+\tu8 i;\n+\n+\tfor (i = 0; (ctrl_info_desc = \u0026mtk_ctrl_info_tbl[i]) \u0026\u0026 ctrl_info_desc \u0026\u0026\n+\t     ctrl_info_desc-\u003ectrl_info; i++) {\n+\t\tif (ctrl_info_desc-\u003ehw_ver != hw_ver)\n+\t\t\tcontinue;\n+\n+\t\tctrl_info = ctrl_info_desc-\u003ectrl_info;\n+\t\tcfg-\u003eport_layer_cfg = ctrl_info-\u003ectrl_cfg-\u003eport_layer_cfg;\n+\t\tmemcpy(trans-\u003esrv_cfg, ctrl_info-\u003esrv_cfg,\n+\t\t       sizeof(int) * NR_CLDMA * HW_QUE_NUM);\n+\t\ttrans-\u003equeue_info = ctrl_info-\u003equeue_info;\n+\t\ttrans-\u003equeue_info_num = ctrl_info-\u003equeue_info_num;\n+\t\ttrans-\u003etrb_srv_num = ctrl_info-\u003etrb_srv_num;\n+\t}\n+}\n+\n+int mtk_trans_ctrl_init(struct mtk_md_dev *mdev)\n+{\n+\tstruct mtk_ctrl_trans *trans;\n+\tstruct mtk_ctrl_blk *ctrl_blk;\n+\tstruct mtk_ctrl_cfg *cfg;\n+\tint err;\n+\n+\ttrans = devm_kzalloc(mdev-\u003edev, sizeof(*trans), GFP_KERNEL);\n+\tif (!trans)\n+\t\treturn -ENOMEM;\n+\ttrans-\u003emdev = mdev;\n+\ttrans-\u003equeues_cnt = 0;\n+\n+\tcfg = devm_kzalloc(mdev-\u003edev, sizeof(*cfg), GFP_KERNEL);\n+\tif (!cfg)\n+\t\treturn -ENOMEM;\n+\n+\tmtk_trans_get_ctrl_info(cfg, trans, mdev-\u003ehw_ver);\n+\tif (!cfg-\u003eport_layer_cfg || !trans-\u003equeue_info ||\n+\t    trans-\u003etrb_srv_num \u003c= 0 || trans-\u003etrb_srv_num \u003e TRB_SRV_MAX_NUM ||\n+\t    trans-\u003equeue_info_num \u003c= 0) {\n+\t\tdev_err(mdev-\u003edev, \"Failed to get ctrl info!\\n\");\n+\t\tgoto err_free_cfg;\n+\t}\n+\n+\terr = mtk_ctrl_init(mdev, \u0026pcie_ctrl_ops, cfg);\n+\tif (err)\n+\t\tgoto err_free_cfg;\n+\n+\tctrl_blk = mdev-\u003ectrl_blk;\n+\tctrl_blk-\u003ectrl_hw_priv = trans;\n+\n+\treturn 0;\n+\n+err_free_cfg:\n+\treturn -ENOMEM;\n+}\n+\n+int mtk_trans_ctrl_exit(struct mtk_md_dev *mdev)\n+{\n+\tmtk_ctrl_exit(mdev);\n+\n+\treturn 0;\n+}\ndiff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h\nnew file mode 100644\nindex 00000000000000..4b9c9db6ad7152\n--- /dev/null\n+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h\n@@ -0,0 +1,107 @@\n+/* SPDX-License-Identifier: GPL-2.0-only\n+ *\n+ * Copyright (c) 2022, MediaTek Inc.\n+ */\n+\n+#ifndef __MTK_TRANS_CTRL_H__\n+#define __MTK_TRANS_CTRL_H__\n+\n+#include \u003clinux/kref.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+#include \"mtk_dev.h\"\n+#include \"mtk_port.h\"\n+\n+#define TRB_SRV_MAX_NUM\t\t\t(1)\n+#define HW_QUE_NUM\t\t\t(8)\n+#define TX_GPD_NUM\t\t\t(16)\n+#define RX_GPD_NUM\t\t\t(TX_GPD_NUM)\n+#define MIN_GPD_NUM\t\t\t(2)\n+#define SKB_LIST_MAX_LEN\t\t(16)\n+#define MTU_RSV_ROOM\t\t\t(0x100)\n+#define TRB_NUM_PER_ROUND\t\t(TX_GPD_NUM)\n+#define TX_BURST_MAX_CNT\t\t(TX_GPD_NUM / 4 + 1)\n+\n+#define HIF_ID(peer_id)\t\t\t((peer_id) - 1)\n+\n+enum mtk_hif_id {\n+\tCLDMA0,\n+\tCLDMA1,\n+\tNR_CLDMA\n+};\n+\n+struct queue_info {\n+\tu32 tx_chl;\n+\tu32 rx_chl;\n+\tenum mtk_hif_id hif_id;\n+\tu32 txqno;\n+\tu32 rxqno;\n+\tu32 tx_mtu;\n+\tu32 rx_mtu;\n+\tu32 tx_nr_gpds;\n+\tu32 rx_nr_gpds;\n+\tu32 tx_frag_size;\n+\tu32 rx_frag_size;\n+\tu8 log_rg_offset;\n+};\n+\n+struct trans_list {\n+\tstruct sk_buff_head skb_list[HW_QUE_NUM];\n+\tu8 tx_burst_cnt[HW_QUE_NUM];\n+};\n+\n+struct mtk_ctrl_trans {\n+\tstruct mtk_ctrl_blk *ctrl_blk;\n+\tstruct trb_srv *trb_srv[TRB_SRV_MAX_NUM];\n+\tstruct trans_list trans_list[NR_CLDMA];\n+\tvoid *dev;\n+\tstruct radix_tree_root queue_tbl;\n+\tstruct mtk_md_dev *mdev;\n+\tint usr_cnt[NR_CLDMA][HW_QUE_NUM];\n+\tu32 tx_mtu_cfg[NR_CLDMA][HW_QUE_NUM];\n+\tu32 rx_mtu_cfg[NR_CLDMA][HW_QUE_NUM];\n+\tatomic_t available;\n+\tint queues_cnt;\n+\tint srv_cfg[NR_CLDMA][HW_QUE_NUM];\n+\tstruct queue_info *queue_info;\n+\tint queue_info_num;\n+\tint trb_srv_num;\n+};\n+\n+struct srv_que {\n+\tu32 hif_id;\n+\tu32 qno;\n+\tstruct list_head list;\n+};\n+\n+struct trb_srv {\n+\tu32 srv_id;\n+\tstruct list_head srv_q_list[NR_CLDMA];\n+\tstruct mtk_ctrl_trans *trans;\n+\twait_queue_head_t trb_waitq;\n+\tstruct task_struct *trb_thread;\n+};\n+\n+struct mtk_ctrl_info {\n+\tstruct mtk_ctrl_cfg *ctrl_cfg;\n+\tint **srv_cfg;\n+\tstruct queue_info *queue_info;\n+\tu32 queue_info_num;\n+\tu32 trb_srv_num;\n+};\n+\n+struct mtk_ctrl_info_desc {\n+\tu32 hw_ver;\n+\tstruct mtk_ctrl_info *ctrl_info;\n+};\n+\n+#define ctrl_info_name(NAME)\tmtk_ctrl_info_##NAME\n+\n+extern struct mtk_ctrl_info mtk_ctrl_info_m9xx;\n+\n+int mtk_trans_ctrl_init(struct mtk_md_dev *mdev);\n+int mtk_trans_ctrl_exit(struct mtk_md_dev *mdev);\n+\n+#endif\n"
}

2/1 2026/07/10 06:33 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "MTK_T9XX"
  ],
  "FocusSymbols": [
    "mtk_pci_probe",
    "mtk_pci_remove",
    "mtk_port_wwan_open",
    "mtk_port_wwan_close",
    "mtk_port_wwan_write",
    "mtk_port_wwan_write_blocking",
    "mtk_port_wwan_poll",
    "mtk_cldma_open",
    "mtk_cldma_tx",
    "mtk_cldma_close",
    "mtk_fsm_startup_act",
    "mtk_fsm_dev_rm_act",
    "mtk_fsm_dev_add_act",
    "mtk_cldma_isr",
    "mtk_pci_irq_msix",
    "mtk_cldma_tx_done_work",
    "mtk_cldma_rx_done_work"
  ],
  "Reasoning": "The patch introduces a new WWAN driver for MediaTek T9xx PCIe 5G modems. It implements a PCI driver, CLDMA (DMA controller) handling, state machine, and WWAN port operations. The driver is reachable via the PCI subsystem and WWAN core, making it suitable for fuzzing.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 915eb8adbd2d1f10cf4afa0b12fc2ffbad2f925f
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Jul 10 06:33:38 2026 +0000

    syz-cluster: applied patch under review

diff --git a/MAINTAINERS b/MAINTAINERS
index 461a3eed612958..8155d26bff03ea 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16494,6 +16494,15 @@ L:	netdev@vger.kernel.org
 S:	Supported
 F:	drivers/net/wwan/t7xx/
 
+MEDIATEK T9XX 5G WWAN MODEM DRIVER
+M:	Jack Wu <jackbb_wu@compal.com>
+R:	Wen-Zhi Huang <wen-zhi.huang@mediatek.com>
+R:	Shi-Wei Yeh <shi-wei.yeh@mediatek.com>
+R:	Minano Tseng <Minano.tseng@mediatek.com>
+L:	netdev@vger.kernel.org
+S:	Supported
+F:	drivers/net/wwan/t9xx/
+
 MEDIATEK USB3 DRD IP DRIVER
 M:	Chunfeng Yun <chunfeng.yun@mediatek.com>
 L:	linux-usb@vger.kernel.org
diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig
index 88df55d78d902f..a2ffed346acc99 100644
--- a/drivers/net/wwan/Kconfig
+++ b/drivers/net/wwan/Kconfig
@@ -121,6 +121,23 @@ config MTK_T7XX
 
 	  If unsure, say N.
 
+config MTK_T9XX
+	tristate "MediaTek PCIe 5G WWAN modem T9xx device"
+	depends on PCI && ACPI
+	select MTK_T9XX_PCI
+	select NET_DEVLINK
+	help
+	  Enables MediaTek PCIe based 5G WWAN modem (T9xx series) device.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called mtk_t9xx.
+
+	  If unsure, say N.
+
+config MTK_T9XX_PCI
+	tristate
+	depends on PCI
+
 endif # WWAN
 
 endmenu
diff --git a/drivers/net/wwan/Makefile b/drivers/net/wwan/Makefile
index 3960c0ae2445cd..7361eef4c472c4 100644
--- a/drivers/net/wwan/Makefile
+++ b/drivers/net/wwan/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_QCOM_BAM_DMUX) += qcom_bam_dmux.o
 obj-$(CONFIG_RPMSG_WWAN_CTRL) += rpmsg_wwan_ctrl.o
 obj-$(CONFIG_IOSM) += iosm/
 obj-$(CONFIG_MTK_T7XX) += t7xx/
+obj-$(CONFIG_MTK_T9XX) += t9xx/
diff --git a/drivers/net/wwan/t9xx/Makefile b/drivers/net/wwan/t9xx/Makefile
new file mode 100644
index 00000000000000..75760b2039dc54
--- /dev/null
+++ b/drivers/net/wwan/t9xx/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+ccflags-y += -I$(src)/pcie
+ccflags-y += -I$(src)
+
+obj-$(CONFIG_MTK_T9XX) += mtk_t9xx.o
+obj-$(CONFIG_MTK_T9XX_PCI) += pcie/
+
+mtk_t9xx-y := \
+	mtk_dev.o \
+	mtk_ctrl_plane.o \
+	mtk_port.o \
+	mtk_port_io.o \
+	mtk_fsm.o
diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
new file mode 100644
index 00000000000000..1d9dc534824053
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ * Copyright (c) 2022-2023, Intel Corporation.
+ */
+
+#include <linux/device.h>
+#include <linux/freezer.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/pm_runtime.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+
+#include "mtk_ctrl_plane.h"
+#include "mtk_port.h"
+
+#define TAG "CTRL"
+
+static void mtk_ctrl_trans_fsm_state_handler(struct mtk_fsm_param *param,
+					     struct mtk_ctrl_blk *ctrl_blk)
+{
+	struct mtk_md_dev *mdev = ctrl_blk->mdev;
+
+	switch (param->to) {
+	case FSM_STATE_OFF:
+		ctrl_blk->ops->fsm_indication(mdev, param);
+		ctrl_blk->ops->exit(mdev);
+		break;
+	case FSM_STATE_ON:
+		ctrl_blk->ops->init(mdev);
+		fallthrough;
+	default:
+		ctrl_blk->ops->fsm_indication(mdev, param);
+		break;
+	}
+}
+
+static void mtk_ctrl_fsm_state_listener(struct mtk_fsm_param *param, void *data)
+{
+	struct mtk_ctrl_blk *ctrl_blk = data;
+
+	mtk_port_mngr_fsm_state_handler(param, ctrl_blk->port_mngr);
+	mtk_ctrl_trans_fsm_state_handler(param, ctrl_blk);
+	mtk_port_mngr_fsm_state_handler_late(param, ctrl_blk->port_mngr);
+}
+
+/**
+ * mtk_ctrl_init() - Initialize the control plane block.
+ * @mdev: Pointer to the MTK modem device.
+ * @ops: HIF operations for the control plane.
+ * @cfg: Control plane configuration.
+ *
+ * Allocates and initializes the control plane block
+ * associated with @mdev.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops, struct mtk_ctrl_cfg *cfg)
+{
+	struct mtk_ctrl_blk *ctrl_blk;
+	int err;
+
+	ctrl_blk = devm_kzalloc(mdev->dev, sizeof(*ctrl_blk), GFP_KERNEL);
+	if (!ctrl_blk)
+		return -ENOMEM;
+
+	ctrl_blk->mdev = mdev;
+	mdev->ctrl_blk = ctrl_blk;
+	ctrl_blk->ops = ops;
+	ctrl_blk->cfg = cfg;
+
+	err = mtk_port_mngr_init(ctrl_blk, cfg->port_layer_cfg->port_cfg,
+				 cfg->port_layer_cfg->port_cnt);
+	if (err)
+		goto err_free_mem;
+
+	err = mtk_fsm_notifier_register(mdev, MTK_USER_CTRL, mtk_ctrl_fsm_state_listener,
+					ctrl_blk, FSM_PRIO_1, false);
+	if (err) {
+		dev_err(mdev->dev, "Fail to register fsm notification(ret = %d)\n", err);
+		goto err_port_exit;
+	}
+
+	return 0;
+
+err_port_exit:
+	mtk_port_mngr_exit(ctrl_blk);
+err_free_mem:
+	return err;
+}
+EXPORT_SYMBOL(mtk_ctrl_init);
+
+/**
+ * mtk_ctrl_exit() - Clean up the control plane block.
+ * @mdev: Pointer to the MTK modem device.
+ *
+ * Frees the control plane block associated with @mdev.
+ */
+void mtk_ctrl_exit(struct mtk_md_dev *mdev)
+{
+	struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+
+	mtk_fsm_notifier_unregister(mdev, MTK_USER_CTRL);
+	mtk_port_mngr_exit(ctrl_blk);
+	mdev->ctrl_blk = NULL;
+}
+EXPORT_SYMBOL(mtk_ctrl_exit);
diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
new file mode 100644
index 00000000000000..92817e92a2e46d
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_CTRL_PLANE_H__
+#define __MTK_CTRL_PLANE_H__
+
+#include <linux/kref.h>
+#include <linux/skbuff.h>
+
+#include "mtk_dev.h"
+#include "mtk_fsm.h"
+
+#define Q_MTU_2K			(0x800)
+#define Q_MTU_3_5K			(0xE00)
+#define Q_MTU_7K			(0x1C00)
+#define Q_MTU_32K			(0x8000)
+#define Q_MTU_63K			(0xFC00)
+#define Q_FRAG_2K			(0x800)
+#define Q_FRAG_3_5K			(0xE00)
+#define Q_FRAG_7K			(0x1C00)
+#define Q_FRAG_32K			(0x8000)
+#define Q_FRAG_63K			(0xFC00)
+
+enum mtk_trb_cmd_type {
+	TRB_CMD_MIN,
+	TRB_CMD_ENABLE,
+	TRB_CMD_TX,
+	TRB_CMD_DISABLE,
+	TRB_CMD_STOP,
+	TRB_CMD_RECOVER,
+	TRB_CMD_MAX,
+};
+
+enum mtk_hif_dev_ctrl_cmd {
+	HIF_CTRL_CMD_CHECK_TX_FULL,
+};
+
+struct trb_open_priv {
+	u8 log_rg_offset;
+	u32 tx_mtu;
+	u32 rx_mtu;
+	u32 tx_frag_size;
+	u32 rx_frag_size;
+	int (*rx_done)(struct sk_buff *skb, void *priv, bool force_recv);
+};
+
+struct trb {
+	u32 channel_id;
+	enum mtk_trb_cmd_type cmd;
+	int status;
+	struct kref kref;
+	void *priv;
+	int (*trb_complete)(struct sk_buff *skb);
+};
+
+union ctrl_hif_cmd_data {
+	u32 rx_ch;
+};
+
+struct mtk_ctrl_hif_ops {
+	int (*init)(struct mtk_md_dev *mdev);
+	int (*exit)(struct mtk_md_dev *mdev);
+	int (*submit_skb)(struct mtk_md_dev *mdev, struct sk_buff *skb, bool force_send);
+	void (*fsm_indication)(struct mtk_md_dev *mdev, struct mtk_fsm_param *param);
+	int (*send_cmd)(struct mtk_md_dev *mdev, int cmd, void *data);
+};
+
+struct mtk_ctrl_cfg {
+	struct mtk_port_layer_cfg *port_layer_cfg;
+};
+
+struct mtk_ctrl_trans;
+
+struct mtk_ctrl_blk {
+	struct mtk_md_dev *mdev;
+	struct mtk_port_mngr *port_mngr;
+	struct mtk_ctrl_hif_ops *ops;
+	void *ctrl_hw_priv;
+	struct mtk_ctrl_cfg *cfg;
+};
+
+int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops,
+		  struct mtk_ctrl_cfg *cfg);
+void mtk_ctrl_exit(struct mtk_md_dev *mdev);
+
+#endif /* __MTK_CTRL_PLANE_H__ */
diff --git a/drivers/net/wwan/t9xx/mtk_dev.c b/drivers/net/wwan/t9xx/mtk_dev.c
new file mode 100644
index 00000000000000..7c449030a35ff5
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_dev.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/module.h>
+
+#include "mtk_dev.h"
+#include "mtk_port.h"
+#include "mtk_port_io.h"
+
+static int __init mtk_common_drv_init(void)
+{
+	int ret;
+
+	ret = mtk_port_io_init();
+	if (ret)
+		goto err_init_devid;
+
+err_init_devid:
+	return ret;
+}
+module_init(mtk_common_drv_init);
+
+static void __exit mtk_common_drv_exit(void)
+{
+	mtk_port_io_exit();
+	mtk_port_stale_list_grp_cleanup();
+}
+module_exit(mtk_common_drv_exit);
+
+MODULE_DESCRIPTION("MediaTek T9xx PCIe WWAN driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wwan/t9xx/mtk_dev.h b/drivers/net/wwan/t9xx/mtk_dev.h
new file mode 100644
index 00000000000000..b4777b7c5d49fd
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_dev.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_DEV_H__
+#define __MTK_DEV_H__
+
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define MTK_DEV_STR_LEN 16
+
+enum mtk_user_id {
+	MTK_USER_MIN,
+	MTK_USER_CTRL,
+	MTK_USER_DATA,
+	MTK_USER_MAX
+};
+
+enum mtk_dev_evt_h2d {
+	DEV_EVT_H2D_DEVICE_RESET	= BIT(2),
+	DEV_EVT_H2D_MAX			= BIT(5)
+};
+
+enum mtk_dev_evt_d2h {
+	DEV_EVT_D2H_BOOT_FLOW_SYNC	= BIT(4),
+	DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP = BIT(5),
+	DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD	= BIT(6),
+	DEV_EVT_D2H_MAX			= BIT(11)
+};
+
+struct mtk_md_dev;
+struct mtk_ctrl_blk;
+
+struct mtk_dev_ops {
+	u32 (*get_dev_state)(struct mtk_md_dev *mdev);
+	void (*ack_dev_state)(struct mtk_md_dev *mdev, u32 state);
+	u32 (*get_dev_cfg)(struct mtk_md_dev *mdev);
+	int (*register_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt,
+				int (*evt_cb)(u32 status, void *data), void *data);
+	void (*unregister_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
+	void (*mask_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
+	void (*unmask_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
+	void (*clear_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
+	int (*send_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
+};
+
+/* mtk_md_dev defines the structure of MTK modem device */
+struct mtk_md_dev {
+	struct device *dev;
+	const struct mtk_dev_ops *dev_ops;
+	void *hw_priv;
+	u32 hw_ver;
+	char dev_str[MTK_DEV_STR_LEN];
+	struct mtk_ctrl_blk *ctrl_blk;
+	struct mtk_md_fsm *fsm;
+};
+
+static inline u32 mtk_dev_get_dev_state(struct mtk_md_dev *mdev)
+{
+	return mdev->dev_ops->get_dev_state(mdev);
+}
+
+static inline void mtk_dev_ack_dev_state(struct mtk_md_dev *mdev, u32 state)
+{
+	return mdev->dev_ops->ack_dev_state(mdev, state);
+}
+
+static inline u32 mtk_dev_get_dev_cfg(struct mtk_md_dev *mdev)
+{
+	return mdev->dev_ops->get_dev_cfg(mdev);
+}
+
+static inline int mtk_dev_register_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt,
+					   int (*evt_cb)(u32 status, void *data), void *data)
+{
+	return mdev->dev_ops->register_dev_evt(mdev, dev_evt, evt_cb, data);
+}
+
+static inline void mtk_dev_unregister_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
+{
+	mdev->dev_ops->unregister_dev_evt(mdev, dev_evt);
+}
+
+static inline void mtk_dev_mask_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
+{
+	mdev->dev_ops->mask_dev_evt(mdev, dev_evt);
+}
+
+static inline void mtk_dev_unmask_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
+{
+	mdev->dev_ops->unmask_dev_evt(mdev, dev_evt);
+}
+
+static inline void mtk_dev_clear_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
+{
+	mdev->dev_ops->clear_dev_evt(mdev, dev_evt);
+}
+
+static inline int mtk_dev_send_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
+{
+	return mdev->dev_ops->send_dev_evt(mdev, dev_evt);
+}
+
+#endif /* __MTK_DEV_H__ */
diff --git a/drivers/net/wwan/t9xx/mtk_fsm.c b/drivers/net/wwan/t9xx/mtk_fsm.c
new file mode 100644
index 00000000000000..daefb7581ce0fd
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_fsm.c
@@ -0,0 +1,946 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/kref.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <linux/sched/signal.h>
+#include <linux/skbuff.h>
+#include <linux/wait.h>
+
+#include "mtk_fsm.h"
+#include "mtk_port.h"
+#include "mtk_port_io.h"
+#include "mtk_utility.h"
+
+#define EVT_TF_GATECLOSED (1)
+#define MTK_FSM_INFO_LEN	(64)
+
+#define FSM_HS_START_MASK	(FSM_F_SAP_HS_START | FSM_F_MD_HS_START)
+#define FSM_HS2_DONE_MASK	(FSM_F_SAP_HS2_DONE | FSM_F_MD_HS2_DONE)
+
+#define RTFT_DATA_SIZE		(3 * 1024)
+#define EVT_HANDLER_TIMEOUT	(HZ * 30)
+#define BLOCKING_EVT_TIMEOUT	(2 * EVT_HANDLER_TIMEOUT)
+
+#define REGION_BITMASK		0xF
+#define DEVICE_CFG_SHIFT	24
+#define DEVICE_CFG_REGION_MASK	0x3
+
+enum device_stage {
+	DEV_STAGE_IDLE = 4,
+	DEV_STAGE_MAX
+};
+
+enum device_cfg {
+	DEV_CFG_NORMAL = 0,
+	DEV_CFG_MD_ONLY,
+};
+
+enum runtime_feature_support_type {
+	RTFT_TYPE_NOT_EXIST = 0,
+	RTFT_TYPE_NOT_SUPPORT = 1,
+	RTFT_TYPE_MUST_SUPPORT = 2,
+	RTFT_TYPE_OPTIONAL_SUPPORT = 3,
+	RTFT_TYPE_SUPPORT_BACKWARD_COMPAT = 4,
+};
+
+enum query_runtime_feature_id {
+	QUERY_RTFT_ID_MD_PORT_ENUM = 0,
+	QUERY_RTFT_ID_SAP_PORT_ENUM = 1,
+	QUERY_RTFT_ID_MD_PORT_CFG = 2,
+	QUERY_RTFT_ID_MAX
+};
+
+enum ctrl_msg_id {
+	CTRL_MSG_HS1 = 0,
+	CTRL_MSG_HS2 = 1,
+	CTRL_MSG_HS3 = 2,
+};
+
+struct ctrl_msg_header {
+	__le32 id;
+	__le32 ex_msg;
+	__le32 data_len;
+	u8 reserved[];
+} __packed;
+
+struct runtime_feature_entry {
+	u8 feature_id;
+	struct runtime_feature_info support_info;
+	u8 reserved[2];
+	__le32 data_len;
+	u8 data[];
+};
+
+struct feature_query {
+	__le32 head_pattern;
+	struct runtime_feature_info ft_set[FEATURE_CNT];
+	__le32 tail_pattern;
+};
+
+static int mtk_fsm_send_hs1_msg(struct fsm_hs_info *hs_info)
+{
+	struct ctrl_msg_header *ctrl_msg_h;
+	struct feature_query *ft_query;
+	struct sk_buff *skb;
+	int ret, msg_size;
+
+	msg_size = sizeof(*ctrl_msg_h) + sizeof(*ft_query);
+	skb = __dev_alloc_skb(msg_size, GFP_KERNEL);
+	if (!skb)
+		return -ENOMEM;
+
+	skb_put(skb, msg_size);
+	ctrl_msg_h = (struct ctrl_msg_header *)skb->data;
+	ctrl_msg_h->id = cpu_to_le32(CTRL_MSG_HS1);
+	ctrl_msg_h->ex_msg = 0;
+	ctrl_msg_h->data_len = cpu_to_le32(sizeof(*ft_query));
+
+	ft_query = (struct feature_query *)(skb->data + sizeof(*ctrl_msg_h));
+	ft_query->head_pattern = cpu_to_le32(FEATURE_QUERY_PATTERN);
+	memcpy(ft_query->ft_set, hs_info->query_ft_set, sizeof(hs_info->query_ft_set));
+	ft_query->tail_pattern = cpu_to_le32(FEATURE_QUERY_PATTERN);
+
+	/* send handshake1 message to device */
+	ret = mtk_port_internal_write(hs_info->ctrl_port, skb);
+	if (ret <= 0)
+		return ret;
+
+	return 0;
+}
+
+static int mtk_fsm_feature_set_match(enum runtime_feature_support_type *cur_ft_spt,
+				     struct runtime_feature_info rtft_info_st,
+				     struct runtime_feature_info rtft_info_cfg)
+{
+	int ret = 0;
+
+	switch (FIELD_GET(FEATURE_TYPE, rtft_info_st.feature)) {
+	case RTFT_TYPE_NOT_EXIST:
+		fallthrough;
+	case RTFT_TYPE_NOT_SUPPORT:
+		*cur_ft_spt = RTFT_TYPE_NOT_EXIST;
+		break;
+	case RTFT_TYPE_MUST_SUPPORT:
+		if (FIELD_GET(FEATURE_TYPE, rtft_info_cfg.feature) == RTFT_TYPE_NOT_EXIST ||
+		    FIELD_GET(FEATURE_TYPE, rtft_info_cfg.feature) == RTFT_TYPE_NOT_SUPPORT)
+			ret = -EPROTO;
+		else
+			*cur_ft_spt = RTFT_TYPE_MUST_SUPPORT;
+		break;
+	case RTFT_TYPE_OPTIONAL_SUPPORT:
+		if (FIELD_GET(FEATURE_TYPE, rtft_info_cfg.feature) == RTFT_TYPE_NOT_EXIST ||
+		    FIELD_GET(FEATURE_TYPE, rtft_info_cfg.feature) == RTFT_TYPE_NOT_SUPPORT) {
+			*cur_ft_spt = RTFT_TYPE_NOT_SUPPORT;
+		} else {
+			if (FIELD_GET(FEATURE_VER, rtft_info_st.feature) ==
+			    FIELD_GET(FEATURE_VER, rtft_info_cfg.feature))
+				*cur_ft_spt = RTFT_TYPE_MUST_SUPPORT;
+			else
+				*cur_ft_spt = RTFT_TYPE_NOT_SUPPORT;
+		}
+		break;
+	case RTFT_TYPE_SUPPORT_BACKWARD_COMPAT:
+		if (FIELD_GET(FEATURE_VER, rtft_info_st.feature) >=
+		    FIELD_GET(FEATURE_VER, rtft_info_cfg.feature))
+			*cur_ft_spt = RTFT_TYPE_MUST_SUPPORT;
+		else
+			*cur_ft_spt = RTFT_TYPE_NOT_EXIST;
+		break;
+	default:
+		ret = -EPROTO;
+	}
+
+	return ret;
+}
+
+static int (*query_rtft_action[FEATURE_CNT])(struct mtk_md_dev *mdev, void *rt_data) = {
+	[QUERY_RTFT_ID_MD_PORT_ENUM] = mtk_port_status_update,
+	[QUERY_RTFT_ID_SAP_PORT_ENUM] = mtk_port_status_update,
+};
+
+static int mtk_fsm_parse_hs2_msg(struct fsm_hs_info *hs_info)
+{
+	struct mtk_md_fsm *fsm = container_of(hs_info, struct mtk_md_fsm, hs_info[hs_info->id]);
+	char *rt_data = ((struct sk_buff *)hs_info->rt_data)->data;
+	enum runtime_feature_support_type cur_ft_spt;
+	struct runtime_feature_entry *rtft_entry;
+	unsigned int ft_id, offset, data_len;
+	int ret = 0;
+
+	offset = sizeof(struct feature_query);
+	for (ft_id = 0; ft_id < FEATURE_CNT; ft_id++) {
+		if (offset + sizeof(*rtft_entry) > hs_info->rt_data_len)
+			break;
+
+		rtft_entry = (struct runtime_feature_entry *)(rt_data + offset);
+		ret = mtk_fsm_feature_set_match(&cur_ft_spt,
+						rtft_entry->support_info,
+						hs_info->query_ft_set[ft_id]);
+		if (ret < 0)
+			break;
+
+		if (cur_ft_spt == RTFT_TYPE_MUST_SUPPORT)
+			if (query_rtft_action[ft_id])
+				ret = query_rtft_action[ft_id](fsm->mdev, rtft_entry->data);
+		if (ret < 0)
+			break;
+
+		data_len = le32_to_cpu(rtft_entry->data_len);
+		if (data_len > hs_info->rt_data_len - offset - sizeof(*rtft_entry))
+			break;
+
+		offset += sizeof(*rtft_entry) + data_len;
+	}
+
+	if (ft_id != FEATURE_CNT) {
+		dev_err((fsm->mdev)->dev, "Unable to handle mistake hs2 msg, ft_id=%d\n", ft_id);
+		ret = -EPROTO;
+	}
+
+	return ret;
+}
+
+static int mtk_fsm_append_rtft_entries(struct mtk_md_dev *mdev, void *feature_data,
+				       unsigned int *len, struct fsm_hs_info *hs_info)
+{
+	char *rt_data = ((struct sk_buff *)hs_info->rt_data)->data;
+	struct runtime_feature_entry *rtft_entry;
+	int ft_id, ret = 0, rtdata_len = 0;
+	struct feature_query *ft_query;
+
+	ft_query = (struct feature_query *)rt_data;
+	if (le32_to_cpu(ft_query->head_pattern) != FEATURE_QUERY_PATTERN ||
+	    le32_to_cpu(ft_query->tail_pattern) != FEATURE_QUERY_PATTERN) {
+		ret = -EPROTO;
+		goto hs_err;
+	}
+
+	/* parse runtime feature query and fill runtime feature entry */
+	rtft_entry = feature_data;
+	for (ft_id = 0; ft_id < FEATURE_CNT && rtdata_len < RTFT_DATA_SIZE; ft_id++) {
+		rtft_entry->feature_id = ft_id;
+		rtft_entry->data_len = 0;
+
+		switch (FIELD_GET(FEATURE_TYPE, ft_query->ft_set[ft_id].feature)) {
+		case RTFT_TYPE_NOT_EXIST:
+			fallthrough;
+		case RTFT_TYPE_NOT_SUPPORT:
+			fallthrough;
+		case RTFT_TYPE_MUST_SUPPORT:
+			rtft_entry->support_info = ft_query->ft_set[ft_id];
+			break;
+		case RTFT_TYPE_OPTIONAL_SUPPORT:
+			fallthrough;
+		case RTFT_TYPE_SUPPORT_BACKWARD_COMPAT:
+			rtft_entry->support_info.feature = FEATURE_TYPE_NOT;
+			rtft_entry->support_info.feature |= FEATURE_VER_0;
+			break;
+		}
+
+		rtdata_len += sizeof(*rtft_entry) + le32_to_cpu(rtft_entry->data_len);
+		rtft_entry = (struct runtime_feature_entry *)(feature_data + rtdata_len);
+	}
+	*len = rtdata_len;
+	return 0;
+
+hs_err:
+	*len = 0;
+	return ret;
+}
+
+static int mtk_fsm_send_hs3_msg(struct fsm_hs_info *hs_info)
+{
+	struct mtk_md_fsm *fsm = container_of(hs_info, struct mtk_md_fsm, hs_info[hs_info->id]);
+	unsigned int data_len, msg_size = 0;
+	struct ctrl_msg_header *ctrl_msg_h;
+	struct sk_buff *skb;
+	int ret;
+
+	skb = __dev_alloc_skb(RTFT_DATA_SIZE, GFP_KERNEL);
+	if (!skb)
+		return -ENOMEM;
+
+	msg_size += sizeof(*ctrl_msg_h);
+	ctrl_msg_h = (struct ctrl_msg_header *)skb->data;
+	ctrl_msg_h->id = cpu_to_le32(CTRL_MSG_HS3);
+	ctrl_msg_h->ex_msg = 0;
+	ret = mtk_fsm_append_rtft_entries(fsm->mdev,
+					  skb->data + sizeof(*ctrl_msg_h),
+					  &data_len, hs_info);
+	if (ret) {
+		dev_kfree_skb(skb);
+		return ret;
+	}
+
+	ctrl_msg_h->data_len = cpu_to_le32(data_len);
+	msg_size += data_len;
+	skb_put(skb, msg_size);
+	ret = mtk_port_internal_write(hs_info->ctrl_port, skb);
+	if (ret <= 0)
+		return ret;
+
+	return 0;
+}
+
+static int mtk_fsm_sap_ctrl_msg_handler(void *__fsm, struct sk_buff *skb)
+{
+	struct ctrl_msg_header *ctrl_msg_h;
+	struct mtk_md_fsm *fsm = __fsm;
+	struct fsm_hs_info *hs_info;
+	int ret;
+
+	if (skb->len < sizeof(*ctrl_msg_h)) {
+		dev_kfree_skb(skb);
+		return -EINVAL;
+	}
+
+	ctrl_msg_h = (struct ctrl_msg_header *)skb->data;
+	skb_pull(skb, sizeof(*ctrl_msg_h));
+
+	hs_info = &fsm->hs_info[HS_ID_SAP];
+	if (le32_to_cpu(ctrl_msg_h->id) != CTRL_MSG_HS2) {
+		dev_kfree_skb(skb);
+		return -EPROTO;
+	}
+
+	hs_info->rt_data = skb;
+	hs_info->rt_data_len = skb->len;
+	ret = mtk_fsm_evt_submit(fsm->mdev, FSM_EVT_STARTUP,
+				 hs_info->fsm_flag_hs2, hs_info, sizeof(*hs_info), 0);
+	if (ret == FSM_EVT_RET_FAIL)
+		dev_kfree_skb(skb);
+
+	return 0;
+}
+
+static int mtk_fsm_md_ctrl_msg_handler(void *__fsm, struct sk_buff *skb)
+{
+	struct ctrl_msg_header *ctrl_msg_h;
+	struct mtk_md_fsm *fsm = __fsm;
+	struct fsm_hs_info *hs_info;
+	bool consumed_skb = false;
+	int ret;
+
+	if (skb->len < sizeof(*ctrl_msg_h)) {
+		dev_kfree_skb(skb);
+		return -EINVAL;
+	}
+
+	ctrl_msg_h = (struct ctrl_msg_header *)skb->data;
+	hs_info = &fsm->hs_info[HS_ID_MD];
+	switch (le32_to_cpu(ctrl_msg_h->id)) {
+	case CTRL_MSG_HS2:
+		skb_pull(skb, sizeof(*ctrl_msg_h));
+		hs_info->rt_data = skb;
+		hs_info->rt_data_len = skb->len;
+		ret = mtk_fsm_evt_submit(fsm->mdev, FSM_EVT_STARTUP,
+					 hs_info->fsm_flag_hs2, hs_info, sizeof(*hs_info), 0);
+		if (ret != FSM_EVT_RET_FAIL)
+			consumed_skb = true;
+		break;
+	default:
+		dev_err(fsm->mdev->dev, "Invalid ctrl msg id\n");
+	}
+
+	if (!consumed_skb)
+		dev_kfree_skb(skb);
+
+	return 0;
+}
+
+static int (*ctrl_msg_handler[HS_ID_MAX])(void *__fsm, struct sk_buff *skb) = {
+	[HS_ID_MD] = mtk_fsm_md_ctrl_msg_handler,
+	[HS_ID_SAP] = mtk_fsm_sap_ctrl_msg_handler,
+};
+
+static void mtk_fsm_idle_evt_handler(struct mtk_md_dev *mdev,
+				     u32 dev_state, struct mtk_md_fsm *fsm)
+{
+	u32 dev_cfg = dev_state >> DEVICE_CFG_SHIFT & DEVICE_CFG_REGION_MASK;
+	int hs_id;
+
+	if (dev_cfg == DEV_CFG_MD_ONLY)
+		fsm->hs_done_flag = FSM_F_MD_HS_START | FSM_F_MD_HS2_DONE;
+	else
+		fsm->hs_done_flag = FSM_HS_START_MASK | FSM_HS2_DONE_MASK;
+
+	mtk_fsm_evt_submit(mdev, FSM_EVT_STARTUP, FSM_F_DFLT, NULL, 0, 0);
+
+	for (hs_id = 0; hs_id < HS_ID_MAX; hs_id++)
+		mtk_dev_unmask_dev_evt(mdev, fsm->hs_info[hs_id].mhccif_ch);
+}
+
+static int mtk_fsm_early_bootup_handler(u32 status, void *__fsm)
+{
+	struct mtk_md_fsm *fsm = __fsm;
+	struct mtk_md_dev *mdev;
+	u32 dev_state, dev_stage;
+
+	mdev = fsm->mdev;
+	mtk_dev_mask_dev_evt(mdev, status);
+	mtk_dev_clear_dev_evt(mdev, status);
+
+	dev_state = mtk_dev_get_dev_state(mdev);
+	dev_stage = dev_state & REGION_BITMASK;
+	if (dev_stage >= DEV_STAGE_MAX) {
+		dev_err(mdev->dev, "Invalid dev state 0x%x\n", dev_state);
+		return -ENXIO;
+	}
+
+	if (dev_state == fsm->last_dev_state)
+		goto exit;
+	fsm->last_dev_state = dev_state;
+
+	if (dev_stage == DEV_STAGE_IDLE)
+		mtk_fsm_idle_evt_handler(mdev, dev_state, fsm);
+
+exit:
+	return 0;
+}
+
+static int mtk_fsm_ctrl_ch_start(struct mtk_md_fsm *fsm, struct fsm_hs_info *hs_info, int flag)
+{
+	if (!hs_info->ctrl_port) {
+		hs_info->ctrl_port = mtk_port_internal_open(fsm->mdev, hs_info->port_name, flag);
+		if (!hs_info->ctrl_port) {
+			dev_err(fsm->mdev->dev, "Failed to open ctrl port(%s)\n",
+				hs_info->port_name);
+			return -ENODEV;
+		}
+
+		mtk_port_internal_recv_register(hs_info->ctrl_port,
+						ctrl_msg_handler[hs_info->id], fsm);
+	}
+
+	return 0;
+}
+
+static void mtk_fsm_ctrl_ch_stop(struct mtk_md_fsm *fsm)
+{
+	struct fsm_hs_info *hs_info;
+	int hs_id;
+
+	for (hs_id = 0; hs_id < HS_ID_MAX; hs_id++) {
+		hs_info = &fsm->hs_info[hs_id];
+		if (hs_info->ctrl_port) {
+			mtk_port_internal_close(hs_info->ctrl_port);
+			hs_info->ctrl_port = NULL;
+		}
+	}
+}
+
+static void mtk_fsm_switch_state(struct mtk_md_fsm *fsm,
+				 enum mtk_fsm_state to_state, struct mtk_fsm_evt *event)
+{
+	char fsm_info[MTK_FSM_INFO_LEN];
+	struct mtk_fsm_notifier *nt;
+	struct mtk_fsm_param param;
+
+	param.from = fsm->state;
+	param.to = to_state;
+	param.evt_id = event ? event->id : FSM_EVT_MAX;
+	param.fsm_flag = event ? event->fsm_flag : FSM_F_DFLT;
+
+	list_for_each_entry(nt, &fsm->pre_notifiers, entry)
+		nt->cb(&param, nt->data);
+
+	fsm->state = to_state;
+	fsm->fsm_flag |= event ? event->fsm_flag : FSM_F_DFLT;
+
+	snprintf(fsm_info, MTK_FSM_INFO_LEN,
+		 "state=%d, fsm_flag=0x%x", to_state, fsm->fsm_flag);
+	mtk_uevent_notify(fsm->mdev->dev, MTK_UEVENT_FSM, fsm_info);
+
+	list_for_each_entry(nt, &fsm->post_notifiers, entry)
+		nt->cb(&param, nt->data);
+}
+
+static int mtk_fsm_startup_act(struct mtk_md_fsm *fsm, struct mtk_fsm_evt *event)
+{
+	enum mtk_fsm_state to_state = FSM_STATE_BOOTUP;
+	struct fsm_hs_info *hs_info = event->data;
+	struct mtk_md_dev *mdev = fsm->mdev;
+	int ret = 0;
+
+	if (fsm->state != FSM_STATE_ON && fsm->state != FSM_STATE_BOOTUP) {
+		ret = -EPROTO;
+		goto free_rt_data;
+	}
+
+	if (fsm->state != FSM_STATE_BOOTUP) {
+		mtk_fsm_switch_state(fsm, to_state, event);
+		return 0;
+	}
+
+	if (event->fsm_flag & FSM_HS_START_MASK) {
+		mtk_fsm_switch_state(fsm, to_state, event);
+
+		ret = mtk_fsm_ctrl_ch_start(fsm, hs_info, O_NONBLOCK);
+		if (!ret)
+			ret = mtk_fsm_send_hs1_msg(hs_info);
+		if (ret)
+			goto hs_err;
+	} else if (event->fsm_flag & FSM_HS2_DONE_MASK) {
+		ret = mtk_fsm_parse_hs2_msg(hs_info);
+		if (!ret) {
+			mtk_fsm_switch_state(fsm, to_state, event);
+			ret = mtk_fsm_send_hs3_msg(hs_info);
+		}
+		dev_kfree_skb(hs_info->rt_data);
+		hs_info->rt_data = NULL;
+		if (ret)
+			goto hs_err;
+	}
+
+	if (((fsm->fsm_flag | event->fsm_flag) & fsm->hs_done_flag) == fsm->hs_done_flag) {
+		to_state = FSM_STATE_READY;
+		mtk_fsm_switch_state(fsm, to_state, NULL);
+	}
+
+	return 0;
+
+free_rt_data:
+	if (hs_info && hs_info->rt_data) {
+		dev_kfree_skb(hs_info->rt_data);
+		hs_info->rt_data = NULL;
+	}
+hs_err:
+	dev_err(mdev->dev, "Failed to hs with device %d:0x%x, ret=%d",
+		fsm->state, fsm->fsm_flag, ret);
+	return ret;
+}
+
+static void mtk_fsm_evt_release(struct kref *kref)
+{
+	struct mtk_fsm_evt *event = container_of(kref, struct mtk_fsm_evt, kref);
+
+	kfree(event);
+}
+
+static void mtk_fsm_evt_put(struct mtk_fsm_evt *event)
+{
+	kref_put(&event->kref, mtk_fsm_evt_release);
+}
+
+static void mtk_fsm_evt_finish(struct mtk_md_fsm *fsm,
+			       struct mtk_fsm_evt *event, int retval)
+{
+	if (event->mode & EVT_MODE_BLOCKING) {
+		event->status = retval;
+		wake_up(&fsm->evt_waitq);
+	}
+	mtk_fsm_evt_put(event);
+}
+
+static void mtk_fsm_evt_cleanup(struct mtk_md_fsm *fsm, struct list_head *evtq)
+{
+	struct mtk_fsm_evt *event, *tmp;
+
+	list_for_each_entry_safe(event, tmp, evtq, entry) {
+		list_del(&event->entry);
+		mtk_fsm_evt_finish(fsm, event, FSM_EVT_RET_FAIL);
+	}
+}
+
+static int mtk_fsm_enter_off_state(struct mtk_md_fsm *fsm, struct mtk_fsm_evt *event)
+{
+	struct mtk_md_dev *mdev = fsm->mdev;
+	int hs_id;
+
+	if (fsm->state == FSM_STATE_OFF || fsm->state == FSM_STATE_INVALID)
+		return -EPROTO;
+
+	mtk_dev_mask_dev_evt(mdev, DEV_EVT_D2H_BOOT_FLOW_SYNC);
+	for (hs_id = 0; hs_id < HS_ID_MAX; hs_id++)
+		mtk_dev_mask_dev_evt(mdev, fsm->hs_info[hs_id].mhccif_ch);
+
+	mtk_fsm_ctrl_ch_stop(fsm);
+	mtk_fsm_switch_state(fsm, FSM_STATE_OFF, event);
+
+	return 0;
+}
+
+static int mtk_fsm_dev_rm_act(struct mtk_md_fsm *fsm, struct mtk_fsm_evt *event)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&fsm->evtq_lock, flags);
+	set_bit(EVT_TF_GATECLOSED, &fsm->t_flag);
+	mtk_fsm_evt_cleanup(fsm, &fsm->evtq);
+	spin_unlock_irqrestore(&fsm->evtq_lock, flags);
+
+	return mtk_fsm_enter_off_state(fsm, event);
+}
+
+static int mtk_fsm_hs1_handler(u32 status, void *__hs_info)
+{
+	struct fsm_hs_info *hs_info = __hs_info;
+	struct mtk_md_dev *mdev;
+	struct mtk_md_fsm *fsm;
+
+	fsm = container_of(hs_info, struct mtk_md_fsm, hs_info[hs_info->id]);
+	mdev = fsm->mdev;
+	mtk_fsm_evt_submit(mdev, FSM_EVT_STARTUP,
+			   hs_info->fsm_flag_hs1, hs_info, sizeof(*hs_info), 0);
+	mtk_dev_mask_dev_evt(mdev, hs_info->mhccif_ch);
+	mtk_dev_clear_dev_evt(mdev, hs_info->mhccif_ch);
+
+	return 0;
+}
+
+static void mtk_fsm_hs_info_init_by_hsid(struct mtk_md_fsm *fsm, int hs_id)
+{
+	struct fsm_hs_info *hs_info;
+
+	if (hs_id < 0 || hs_id >= HS_ID_MAX) {
+		dev_warn((fsm->mdev)->dev, "hs_id = %d, invalid.\n", hs_id);
+		return;
+	}
+
+	hs_info = &fsm->hs_info[hs_id];
+	hs_info->id = hs_id;
+	hs_info->ctrl_port = NULL;
+	hs_info->rt_data = NULL;
+	switch (hs_id) {
+	case HS_ID_MD:
+		snprintf(hs_info->port_name, PORT_NAME_LEN, "MDCTRL");
+		hs_info->mhccif_ch = DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD;
+		hs_info->fsm_flag_hs1 = FSM_F_MD_HS_START;
+		hs_info->fsm_flag_hs2 = FSM_F_MD_HS2_DONE;
+		hs_info->query_ft_set[QUERY_RTFT_ID_MD_PORT_ENUM].feature =
+			FIELD_PREP(FEATURE_TYPE, RTFT_TYPE_MUST_SUPPORT);
+		hs_info->query_ft_set[QUERY_RTFT_ID_MD_PORT_ENUM].feature |=
+			FIELD_PREP(FEATURE_VER, 0);
+		hs_info->query_ft_set[QUERY_RTFT_ID_MD_PORT_CFG].feature =
+			FIELD_PREP(FEATURE_TYPE, RTFT_TYPE_NOT_SUPPORT);
+		break;
+	case HS_ID_SAP:
+		snprintf(hs_info->port_name, PORT_NAME_LEN, "SAPCTRL");
+		hs_info->mhccif_ch = DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP;
+		hs_info->fsm_flag_hs1 = FSM_F_SAP_HS_START;
+		hs_info->fsm_flag_hs2 = FSM_F_SAP_HS2_DONE;
+		hs_info->query_ft_set[QUERY_RTFT_ID_SAP_PORT_ENUM].feature =
+			FIELD_PREP(FEATURE_TYPE, RTFT_TYPE_MUST_SUPPORT);
+		hs_info->query_ft_set[QUERY_RTFT_ID_SAP_PORT_ENUM].feature |=
+			FIELD_PREP(FEATURE_VER, 0);
+		break;
+	}
+}
+
+static void mtk_fsm_hs_info_init(struct mtk_md_fsm *fsm)
+{
+	struct mtk_md_dev *mdev = fsm->mdev;
+	struct fsm_hs_info *hs_info;
+	int hs_id;
+
+	for (hs_id = 0; hs_id < HS_ID_MAX; hs_id++) {
+		mtk_fsm_hs_info_init_by_hsid(fsm, hs_id);
+		hs_info = &fsm->hs_info[hs_id];
+		mtk_dev_register_dev_evt(mdev, hs_info->mhccif_ch,
+					 mtk_fsm_hs1_handler, hs_info);
+	}
+}
+
+static void mtk_fsm_hs_info_exit(struct mtk_md_fsm *fsm)
+{
+	struct mtk_md_dev *mdev = fsm->mdev;
+	struct fsm_hs_info *hs_info;
+	int hs_id;
+
+	for (hs_id = 0; hs_id < HS_ID_MAX; hs_id++) {
+		hs_info = &fsm->hs_info[hs_id];
+		mtk_dev_unregister_dev_evt(mdev, hs_info->mhccif_ch);
+	}
+}
+
+static int mtk_fsm_dev_add_act(struct mtk_md_fsm *fsm, struct mtk_fsm_evt *event)
+{
+	if (fsm->state != FSM_STATE_OFF && fsm->state != FSM_STATE_INVALID)
+		return -EPROTO;
+
+	mtk_fsm_switch_state(fsm, FSM_STATE_ON, event);
+	mtk_dev_unmask_dev_evt(fsm->mdev, DEV_EVT_D2H_BOOT_FLOW_SYNC);
+
+	return 0;
+}
+
+static int (*evts_act_tbl[FSM_EVT_MAX])(struct mtk_md_fsm *__fsm, struct mtk_fsm_evt *event) = {
+	[FSM_EVT_STARTUP] = mtk_fsm_startup_act,
+	[FSM_EVT_DEV_RM] = mtk_fsm_dev_rm_act,
+	[FSM_EVT_DEV_ADD] = mtk_fsm_dev_add_act,
+};
+
+int mtk_fsm_start(struct mtk_md_dev *mdev)
+{
+	struct mtk_md_fsm *fsm = mdev->fsm;
+
+	if (!fsm)
+		return -EINVAL;
+
+	if (!fsm->fsm_handler)
+		return -EFAULT;
+
+	wake_up_process(fsm->fsm_handler);
+	return 0;
+}
+EXPORT_SYMBOL(mtk_fsm_start);
+
+static void mkt_fsm_notifier_cleanup(struct mtk_md_dev *mdev, struct list_head *ntq)
+{
+	struct mtk_fsm_notifier *nt, *tmp;
+
+	list_for_each_entry_safe(nt, tmp, ntq, entry) {
+		list_del(&nt->entry);
+		dev_warn(mdev->dev, "Having to free notifier(%d) by FSM!\n", nt->id);
+		kfree(nt);
+	}
+}
+
+static void mtk_fsm_notifier_insert(struct mtk_fsm_notifier *notifier, struct list_head *head)
+{
+	struct mtk_fsm_notifier *nt;
+
+	list_for_each_entry(nt, head, entry) {
+		if (notifier->prio > nt->prio) {
+			list_add(&notifier->entry, nt->entry.prev);
+			return;
+		}
+	}
+	list_add_tail(&notifier->entry, head);
+}
+
+int mtk_fsm_notifier_register(struct mtk_md_dev *mdev, enum mtk_user_id id,
+			      void (*cb)(struct mtk_fsm_param *, void *data),
+			      void *data, enum mtk_fsm_prio prio, bool is_pre)
+{
+	struct mtk_md_fsm *fsm = mdev->fsm;
+	struct mtk_fsm_notifier *notifier;
+
+	if (!fsm)
+		return -EINVAL;
+
+	if (id >= MTK_USER_MAX || !cb || prio >= FSM_PRIO_MAX)
+		return -EINVAL;
+
+	notifier = kzalloc_obj(*notifier);
+	if (!notifier)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&notifier->entry);
+	notifier->id = id;
+	notifier->cb = cb;
+	notifier->data = data;
+	notifier->prio = prio;
+
+	if (is_pre)
+		mtk_fsm_notifier_insert(notifier, &fsm->pre_notifiers);
+	else
+		mtk_fsm_notifier_insert(notifier, &fsm->post_notifiers);
+
+	return 0;
+}
+EXPORT_SYMBOL(mtk_fsm_notifier_register);
+
+int mtk_fsm_notifier_unregister(struct mtk_md_dev *mdev, enum mtk_user_id id)
+{
+	struct mtk_md_fsm *fsm = mdev->fsm;
+	struct mtk_fsm_notifier *nt, *tmp;
+
+	if (!fsm)
+		return -EINVAL;
+
+	list_for_each_entry_safe(nt, tmp, &fsm->pre_notifiers, entry) {
+		if (nt->id == id) {
+			list_del(&nt->entry);
+			kfree(nt);
+			break;
+		}
+	}
+	list_for_each_entry_safe(nt, tmp, &fsm->post_notifiers, entry) {
+		if (nt->id == id) {
+			list_del(&nt->entry);
+			kfree(nt);
+			break;
+		}
+	}
+	return 0;
+}
+EXPORT_SYMBOL(mtk_fsm_notifier_unregister);
+
+int mtk_fsm_evt_submit(struct mtk_md_dev *mdev,
+		       enum mtk_fsm_evt_id id, enum mtk_fsm_flag flag,
+		       void *data, unsigned int len, unsigned char mode)
+{
+	struct mtk_md_fsm *fsm = mdev->fsm;
+	struct mtk_fsm_evt *event;
+	unsigned long flags;
+	int ret = 0;
+
+	if (!fsm || id >= FSM_EVT_MAX) {
+		dev_err(mdev->dev, "Invalid param!\n");
+		return FSM_EVT_RET_FAIL;
+	}
+
+	if (test_bit(EVT_TF_GATECLOSED, &fsm->t_flag)) {
+		dev_err(mdev->dev, "Failed to submit evt, fsm has been removed!\n");
+		return FSM_EVT_RET_FAIL;
+	}
+
+	event = kzalloc(sizeof(*event),
+			(in_hardirq() || in_softirq() || irqs_disabled()) ?
+			GFP_ATOMIC : GFP_KERNEL);
+	if (!event)
+		return FSM_EVT_RET_FAIL;
+
+	kref_init(&event->kref);
+	event->mdev = mdev;
+	event->id = id;
+	event->fsm_flag = flag;
+	event->status = FSM_EVT_RET_ONGOING;
+	event->data = data;
+	event->len = len;
+	event->mode = mode;
+
+	spin_lock_irqsave(&fsm->evtq_lock, flags);
+	if (test_bit(EVT_TF_GATECLOSED, &fsm->t_flag)) {
+		spin_unlock_irqrestore(&fsm->evtq_lock, flags);
+		mtk_fsm_evt_put(event);
+		dev_err(mdev->dev, "Failed to add event, fsm dev has been removed!\n");
+		return FSM_EVT_RET_FAIL;
+	}
+
+	kref_get(&event->kref);
+	if (mode & EVT_MODE_TOHEAD)
+		list_add(&event->entry, &fsm->evtq);
+	else
+		list_add_tail(&event->entry, &fsm->evtq);
+	spin_unlock_irqrestore(&fsm->evtq_lock, flags);
+
+	wake_up_process(fsm->fsm_handler);
+	if (mode & EVT_MODE_BLOCKING) {
+		ret = wait_event_timeout(fsm->evt_waitq,
+					 (event->status != 0), BLOCKING_EVT_TIMEOUT);
+		if (!ret && event->status != FSM_EVT_RET_DONE) {
+			dev_err(mdev->dev, "Handling fsm blocking event timeout!\n");
+			ret = -ETIMEDOUT;
+		} else {
+			ret = event->status;
+		}
+	}
+	mtk_fsm_evt_put(event);
+
+	return ret;
+}
+EXPORT_SYMBOL(mtk_fsm_evt_submit);
+
+static int mtk_fsm_evt_handler(void *__fsm)
+{
+	struct mtk_md_fsm *fsm = __fsm;
+	struct mtk_fsm_evt *event;
+	unsigned long flags;
+	int ret;
+
+wake_up:
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	while (!kthread_should_stop() && !list_empty(&fsm->evtq)) {
+		set_current_state(TASK_RUNNING);
+		spin_lock_irqsave(&fsm->evtq_lock, flags);
+		event = list_first_entry(&fsm->evtq, struct mtk_fsm_evt, entry);
+		list_del(&event->entry);
+		spin_unlock_irqrestore(&fsm->evtq_lock, flags);
+
+		if (event->id < FSM_EVT_MAX) {
+			ret = evts_act_tbl[event->id](fsm, event);
+			if (ret) {
+				dev_err((fsm->mdev)->dev,
+					"Failed to handle evt, fsm state = %d, ret = %d\n",
+					fsm->state, ret);
+				mtk_fsm_evt_finish(fsm, event, FSM_EVT_RET_FAIL);
+			} else {
+				mtk_fsm_evt_finish(fsm, event, FSM_EVT_RET_DONE);
+			}
+		} else {
+			mtk_fsm_evt_finish(fsm, event, FSM_EVT_RET_DONE);
+		}
+	}
+
+	if (kthread_should_stop()) {
+		set_current_state(TASK_RUNNING);
+		return 0;
+	}
+
+	schedule();
+	goto wake_up;
+}
+
+int mtk_fsm_init(struct mtk_md_dev *mdev)
+{
+	struct mtk_md_fsm *fsm;
+	int ret;
+
+	fsm = devm_kzalloc(mdev->dev, sizeof(*fsm), GFP_KERNEL);
+	if (!fsm)
+		return -ENOMEM;
+
+	fsm->fsm_handler = kthread_create(mtk_fsm_evt_handler, fsm, "fsm_evt_thread%d_%s",
+					  mdev->hw_ver, mdev->dev_str);
+	if (IS_ERR(fsm->fsm_handler)) {
+		ret = PTR_ERR(fsm->fsm_handler);
+		goto exit;
+	}
+
+	fsm->mdev = mdev;
+	fsm->state = FSM_STATE_INVALID;
+	fsm->fsm_flag = FSM_F_DFLT;
+
+	INIT_LIST_HEAD(&fsm->evtq);
+	spin_lock_init(&fsm->evtq_lock);
+	init_waitqueue_head(&fsm->evt_waitq);
+
+	INIT_LIST_HEAD(&fsm->pre_notifiers);
+	INIT_LIST_HEAD(&fsm->post_notifiers);
+
+	mtk_fsm_hs_info_init(fsm);
+	mtk_dev_register_dev_evt(mdev, DEV_EVT_D2H_BOOT_FLOW_SYNC,
+				 mtk_fsm_early_bootup_handler, fsm);
+	mdev->fsm = fsm;
+	return 0;
+exit:
+	return ret;
+}
+EXPORT_SYMBOL(mtk_fsm_init);
+
+int mtk_fsm_exit(struct mtk_md_dev *mdev)
+{
+	struct mtk_md_fsm *fsm = mdev->fsm;
+	unsigned long flags;
+
+	if (!fsm)
+		return -EINVAL;
+
+	if (fsm->fsm_handler) {
+		kthread_stop(fsm->fsm_handler);
+		fsm->fsm_handler = NULL;
+	}
+
+	spin_lock_irqsave(&fsm->evtq_lock, flags);
+	if (WARN_ON(!list_empty(&fsm->evtq)))
+		mtk_fsm_evt_cleanup(fsm, &fsm->evtq);
+	spin_unlock_irqrestore(&fsm->evtq_lock, flags);
+
+	mkt_fsm_notifier_cleanup(mdev, &fsm->pre_notifiers);
+	mkt_fsm_notifier_cleanup(mdev, &fsm->post_notifiers);
+
+	mtk_dev_unregister_dev_evt(mdev, DEV_EVT_D2H_BOOT_FLOW_SYNC);
+	mtk_fsm_hs_info_exit(fsm);
+
+	return 0;
+}
+EXPORT_SYMBOL(mtk_fsm_exit);
diff --git a/drivers/net/wwan/t9xx/mtk_fsm.h b/drivers/net/wwan/t9xx/mtk_fsm.h
new file mode 100644
index 00000000000000..f2fc66bcef614a
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_fsm.h
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_FSM_H__
+#define __MTK_FSM_H__
+
+#include "mtk_dev.h"
+
+#define FEATURE_CNT		(64)
+#define FEATURE_QUERY_PATTERN	(0x49434343)
+
+#define FEATURE_TYPE		GENMASK(3, 0)
+#define FEATURE_VER		GENMASK(7, 4)
+
+#define FEATURE_TYPE_NOT	FIELD_PREP(FEATURE_TYPE, RTFT_TYPE_NOT_SUPPORT)
+#define FEATURE_TYPE_MUST	FIELD_PREP(FEATURE_TYPE, RTFT_TYPE_MUST_SUPPORT)
+#define FEATURE_TYPE_OPTIONAL	FIELD_PREP(FEATURE_TYPE, RTFT_TYPE_OPTIONAL_SUPPORT)
+#define FEATURE_VER_0		FIELD_PREP(FEATURE_VER, 0)
+
+#define EVT_MODE_BLOCKING	(0x01)
+#define EVT_MODE_TOHEAD		(0x02)
+
+#define FSM_EVT_RET_FAIL	(-1)
+#define FSM_EVT_RET_ONGOING	(0)
+#define FSM_EVT_RET_DONE	(1)
+
+enum mtk_fsm_flag {
+	FSM_F_DFLT = 0,
+	FSM_F_SAP_HS_START	= BIT(0),
+	FSM_F_SAP_HS2_DONE	= BIT(1),
+	FSM_F_MD_HS_START	= BIT(2),
+	FSM_F_MD_HS2_DONE	= BIT(3),
+};
+
+enum mtk_fsm_state {
+	FSM_STATE_INVALID = 0,
+	FSM_STATE_OFF,
+	FSM_STATE_ON,
+	FSM_STATE_BOOTUP,
+	FSM_STATE_READY,
+};
+
+enum mtk_fsm_evt_id {
+	FSM_EVT_STARTUP = 0,
+	FSM_EVT_DEV_RM,
+	FSM_EVT_DEV_ADD,
+	FSM_EVT_MAX
+};
+
+enum mtk_fsm_prio {
+	FSM_PRIO_0 = 0,
+	FSM_PRIO_1 = 1,
+	FSM_PRIO_MAX
+};
+
+struct mtk_fsm_param {
+	enum mtk_fsm_state from;
+	enum mtk_fsm_state to;
+	enum mtk_fsm_evt_id evt_id;
+	enum mtk_fsm_flag fsm_flag;
+};
+
+#define PORT_NAME_LEN 20
+
+enum handshake_info_id {
+	HS_ID_MD = 0,
+	HS_ID_SAP,
+	HS_ID_MAX
+};
+
+struct runtime_feature_info {
+	u8 feature;
+};
+
+struct fsm_hs_info {
+	unsigned char id;
+	void *ctrl_port;
+	char port_name[PORT_NAME_LEN];
+	unsigned int mhccif_ch;
+	unsigned int fsm_flag_hs1;
+	unsigned int fsm_flag_hs2;
+	/* the feature that the device should support */
+	struct runtime_feature_info query_ft_set[FEATURE_CNT];
+	/* runtime data from device need to be parsed by host */
+	void *rt_data;
+	unsigned int rt_data_len;
+};
+
+struct mtk_md_fsm {
+	struct mtk_md_dev *mdev;
+	struct task_struct *fsm_handler;
+	struct fsm_hs_info hs_info[HS_ID_MAX];
+	unsigned int hs_done_flag;
+	unsigned long t_flag;
+	u32 last_dev_state;
+	enum mtk_fsm_state state;
+	unsigned int fsm_flag;
+	struct list_head evtq;
+	/* protect evtq */
+	spinlock_t evtq_lock;
+	/* waitq for fsm blocking submit */
+	wait_queue_head_t evt_waitq;
+	struct list_head pre_notifiers;
+	struct list_head post_notifiers;
+};
+
+struct mtk_fsm_evt {
+	struct list_head entry;
+	struct kref kref;
+	struct mtk_md_dev *mdev;
+	enum mtk_fsm_evt_id id;
+	unsigned int fsm_flag;
+	int status;
+	unsigned char mode;
+	unsigned int len;
+	void *data;
+};
+
+struct mtk_fsm_notifier {
+	struct list_head entry;
+	enum mtk_user_id id;
+	void (*cb)(struct mtk_fsm_param *param, void *data);
+	void *data;
+	enum mtk_fsm_prio prio;
+};
+
+int mtk_fsm_init(struct mtk_md_dev *mdev);
+int mtk_fsm_exit(struct mtk_md_dev *mdev);
+int mtk_fsm_start(struct mtk_md_dev *mdev);
+int mtk_fsm_notifier_register(struct mtk_md_dev *mdev, enum mtk_user_id id,
+			      void (*cb)(struct mtk_fsm_param *, void *data),
+			      void *data, enum mtk_fsm_prio prio, bool is_pre);
+int mtk_fsm_notifier_unregister(struct mtk_md_dev *mdev, enum mtk_user_id id);
+int mtk_fsm_evt_submit(struct mtk_md_dev *mdev,
+		       enum mtk_fsm_evt_id id, enum mtk_fsm_flag flag,
+		       void *data, unsigned int len, unsigned char mode);
+
+#endif /* __MTK_FSM_H__ */
diff --git a/drivers/net/wwan/t9xx/mtk_port.c b/drivers/net/wwan/t9xx/mtk_port.c
new file mode 100644
index 00000000000000..0f37137f127a61
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_port.c
@@ -0,0 +1,966 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include "mtk_port.h"
+#include "mtk_port_io.h"
+
+#define MTK_DFLT_TRB_TIMEOUT		(5 * HZ)
+#define MTK_DFLT_TRB_STATUS		(0x1)
+#define MTK_TRB_HEADER_ADDED		(0xADDED)
+#define MTK_CHECK_RX_SEQ_MASK		(0x7fff)
+
+#define MTK_PORT_ENUM_VER		(0)
+#define MTK_PORT_ENUM_HEAD_PATTERN	(0x5a5a5a5a)
+#define MTK_PORT_ENUM_TAIL_PATTERN	(0xa5a5a5a5)
+
+#define MTK_PORT_SEARCH_FROM_RADIX_TREE(p, s) ({\
+	struct mtk_port *_p;			\
+	_p = rcu_dereference_raw(*(s));		\
+	if (!_p)				\
+		continue;			\
+	p = _p;					\
+})
+
+#define MTK_PORT_INTERNAL_NODE_CHECK(p, s, i) ({\
+	if (radix_tree_is_internal_node(p)) {	\
+		s = radix_tree_iter_retry(&(i));\
+		continue;			\
+	}					\
+})
+
+struct mtk_port_info {
+	__le16 channel;
+	__le16 reserved;
+} __packed;
+
+struct mtk_port_enum_msg {
+	__le32 head_pattern;
+	__le16 port_cnt;
+	__le16 version;
+	__le32 tail_pattern;
+	u8 data[];
+} __packed;
+
+/* global group for stale ports */
+static LIST_HEAD(stale_list_grp);
+/* mutex lock for stale_list_group */
+DEFINE_MUTEX(port_mngr_grp_mtx);
+
+static DEFINE_IDA(ccci_dev_ids);
+
+/* This function working always under mutex lock port_mngr_grp_mtx */
+void mtk_port_release(struct kref *port_kref)
+{
+	struct mtk_stale_list *s_list;
+	struct mtk_port *port;
+
+	port = container_of(port_kref, struct mtk_port, kref);
+	if (!test_bit(PORT_S_ON_STALE_LIST, &port->status))
+		goto port_exit;
+
+	list_del(&port->stale_entry);
+	list_for_each_entry(s_list, &stale_list_grp, entry) {
+		if (!strncmp(s_list->dev_str, port->dev_str, MTK_DEV_STR_LEN) &&
+		    list_empty(&s_list->ports) && s_list->dev_id >= 0) {
+			ida_free(&ccci_dev_ids, s_list->dev_id);
+			s_list->dev_id = -1;
+			break;
+		}
+	}
+port_exit:
+	ports_ops[port->info.type]->exit(port);
+	kfree(port);
+}
+
+static int mtk_port_tbl_add(struct mtk_port_mngr *port_mngr, struct mtk_port *port)
+{
+	int ret;
+
+	ret = radix_tree_insert(&port_mngr->port_tbl[MTK_PORT_TBL_TYPE(port->info.rx_ch)],
+				port->info.rx_ch & 0xFFF, port);
+	if (ret)
+		dev_err(port_mngr->ctrl_blk->mdev->dev,
+			"port(%s) add to port_tbl failed, return %d\n",
+			port->info.name, ret);
+	else
+		port_mngr->port_cnt++;
+
+	return ret;
+}
+
+static void mtk_port_tbl_del(struct mtk_port_mngr *port_mngr, struct mtk_port *port)
+{
+	radix_tree_delete(&port_mngr->port_tbl[MTK_PORT_TBL_TYPE(port->info.rx_ch)],
+			  port->info.rx_ch & 0xFFF);
+	port_mngr->port_cnt--;
+}
+
+static struct mtk_port *mtk_port_restore_from_stale_list(struct mtk_port_mngr *port_mngr,
+							 struct mtk_stale_list *s_list)
+{
+	struct mtk_port *port, *next_port;
+	int ret;
+
+	mutex_lock(&port_mngr_grp_mtx);
+	list_for_each_entry_safe(port, next_port, &s_list->ports, stale_entry) {
+		kref_get(&port->kref);
+		list_del(&port->stale_entry);
+		ret = mtk_port_tbl_add(port_mngr, port);
+		if (ret) {
+			list_add_tail(&port->stale_entry, &s_list->ports);
+			kref_put(&port->kref, mtk_port_release);
+			mutex_unlock(&port_mngr_grp_mtx);
+			dev_err(port_mngr->ctrl_blk->mdev->dev,
+				"Failed when adding (%s) to port mngr\n",
+				port->info.name);
+			return ERR_PTR(ret);
+		}
+
+		port->port_mngr = port_mngr;
+		clear_bit(PORT_S_ON_STALE_LIST, &port->status);
+		ports_ops[port->info.type]->reset(port);
+	}
+	mutex_unlock(&port_mngr_grp_mtx);
+
+	return NULL;
+}
+
+static struct mtk_port *mtk_port_alloc_and_add(struct mtk_port_mngr *port_mngr,
+					       struct mtk_port_cfg *dflt_info)
+{
+	struct mtk_port *port;
+	int ret;
+
+	port = kzalloc_obj(*port, GFP_KERNEL);
+	if (!port) {
+		ret = -ENOMEM;
+		goto err_alloc_port;
+	}
+	memcpy(&port->info, dflt_info, sizeof(*dflt_info));
+
+	ret = mtk_port_tbl_add(port_mngr, port);
+	if (ret < 0) {
+		dev_err(port_mngr->ctrl_blk->mdev->dev,
+			"Failed to add port(%s) to port tbl\n", dflt_info->name);
+		goto err_free_port;
+	}
+
+	port->port_mngr = port_mngr;
+	ret = ports_ops[port->info.type]->init(port);
+	if (ret < 0) {
+		mtk_port_tbl_del(port_mngr, port);
+		goto err_free_port;
+	}
+
+	memcpy(port->dev_str, port_mngr->ctrl_blk->mdev->dev_str, MTK_DEV_STR_LEN);
+	return port;
+
+err_free_port:
+	kfree(port);
+err_alloc_port:
+	return ERR_PTR(ret);
+}
+
+static void mtk_port_free_or_backup(struct mtk_port_mngr *port_mngr,
+				    struct mtk_port *port, struct mtk_stale_list *s_list)
+{
+	mutex_lock(&port_mngr_grp_mtx);
+	mtk_port_tbl_del(port_mngr, port);
+	if (port->info.type != PORT_TYPE_INTERNAL) {
+		if (test_bit(PORT_S_OPEN, &port->status)) {
+			list_add_tail(&port->stale_entry, &s_list->ports);
+			set_bit(PORT_S_ON_STALE_LIST, &port->status);
+			memcpy(port->dev_str, port_mngr->ctrl_blk->mdev->dev_str,
+			       MTK_DEV_STR_LEN);
+			port->port_mngr = NULL;
+		}
+		kref_put(&port->kref, mtk_port_release);
+	} else {
+		mtk_port_release(&port->kref);
+	}
+	mutex_unlock(&port_mngr_grp_mtx);
+}
+
+static struct mtk_port *mtk_port_search_by_id(struct mtk_port_mngr *port_mngr, int rx_ch)
+{
+	int tbl_type = MTK_PORT_TBL_TYPE(rx_ch);
+
+	if (tbl_type < PORT_TBL_SAP || tbl_type >= PORT_TBL_MAX)
+		return NULL;
+
+	return radix_tree_lookup(&port_mngr->port_tbl[tbl_type], MTK_CH_ID(rx_ch));
+}
+
+struct mtk_port *mtk_port_search_by_name(struct mtk_port_mngr *port_mngr, char *name)
+{
+	int tbl_type = PORT_TBL_SAP;
+	struct radix_tree_iter iter;
+	struct mtk_port *port;
+	void __rcu **slot;
+
+	do {
+		radix_tree_for_each_slot(slot, &port_mngr->port_tbl[tbl_type], &iter, 0) {
+			MTK_PORT_SEARCH_FROM_RADIX_TREE(port, slot);
+			MTK_PORT_INTERNAL_NODE_CHECK(port, slot, iter);
+			if (!strncmp(port->info.name, name, MTK_DFLT_PORT_NAME_LEN))
+				return port;
+		}
+		tbl_type++;
+	} while (tbl_type < PORT_TBL_MAX);
+
+	return NULL;
+}
+
+static int mtk_port_tbl_create(struct mtk_port_mngr *port_mngr, struct mtk_port_cfg *cfg,
+			       const int port_cnt, struct mtk_stale_list *s_list)
+{
+	struct mtk_port_cfg *dflt_port;
+	struct mtk_port *port;
+	int i;
+
+	INIT_RADIX_TREE(&port_mngr->port_tbl[PORT_TBL_SAP], GFP_KERNEL);
+	INIT_RADIX_TREE(&port_mngr->port_tbl[PORT_TBL_MD], GFP_KERNEL);
+
+	mtk_port_restore_from_stale_list(port_mngr, s_list);
+
+	/* copy ports from static port cfg table */
+	for (i = 0; i < port_cnt; i++) {
+		dflt_port = cfg + i;
+		if (!mtk_port_search_by_id(port_mngr, dflt_port->rx_ch)) {
+			port = mtk_port_alloc_and_add(port_mngr, dflt_port);
+			if (IS_ERR(port))
+				return PTR_ERR(port);
+		}
+	}
+
+	return 0;
+}
+
+static void mtk_port_tbl_destroy(struct mtk_port_mngr *port_mngr, struct mtk_stale_list *s_list)
+{
+	struct radix_tree_iter iter;
+	struct mtk_port *port;
+	void __rcu **slot;
+	int tbl_type;
+
+	tbl_type = PORT_TBL_SAP;
+	do {
+		radix_tree_for_each_slot(slot, &port_mngr->port_tbl[tbl_type], &iter, 0) {
+			port = radix_tree_deref_slot(slot);
+			if (!port)
+				continue;
+			ports_ops[port->info.type]->disable(port);
+		}
+
+		while (radix_tree_gang_lookup(&port_mngr->port_tbl[tbl_type],
+					      (void **)&port, 0, 1))
+			mtk_port_free_or_backup(port_mngr, port, s_list);
+	} while (++tbl_type < PORT_TBL_MAX);
+}
+
+static struct mtk_stale_list *mtk_port_stale_list_create(struct mtk_ctrl_blk *ctrl_blk)
+{
+	struct mtk_stale_list *s_list;
+
+	s_list = kzalloc_obj(*s_list, GFP_KERNEL);
+	if (!s_list)
+		return NULL;
+
+	memcpy(s_list->dev_str, ctrl_blk->mdev->dev_str, MTK_DEV_STR_LEN);
+	s_list->dev_id = -1;
+	INIT_LIST_HEAD(&s_list->ports);
+	rwlock_init(&s_list->port_mngr_lock);
+
+	mutex_lock(&port_mngr_grp_mtx);
+	list_add_tail(&s_list->entry, &stale_list_grp);
+	mutex_unlock(&port_mngr_grp_mtx);
+
+	return s_list;
+}
+
+static void mtk_port_stale_list_destroy(struct mtk_stale_list *s_list)
+{
+	mutex_lock(&port_mngr_grp_mtx);
+	list_del(&s_list->entry);
+	mutex_unlock(&port_mngr_grp_mtx);
+	kfree(s_list);
+}
+
+static struct mtk_stale_list *mtk_port_stale_list_search(const char *dev_str)
+{
+	struct mtk_stale_list *tmp, *s_list = NULL;
+
+	mutex_lock(&port_mngr_grp_mtx);
+	list_for_each_entry(tmp, &stale_list_grp, entry) {
+		if (!strncmp(tmp->dev_str, dev_str, MTK_DEV_STR_LEN)) {
+			s_list = tmp;
+			break;
+		}
+	}
+	mutex_unlock(&port_mngr_grp_mtx);
+
+	return s_list;
+}
+
+void mtk_port_stale_list_grp_cleanup(void)
+{
+	struct mtk_stale_list *s_list, *next_s_list;
+	struct mtk_port *port, *next_port;
+
+	mutex_lock(&port_mngr_grp_mtx);
+	list_for_each_entry_safe(s_list, next_s_list, &stale_list_grp, entry) {
+		list_del(&s_list->entry);
+
+		list_for_each_entry_safe(port, next_port, &s_list->ports, stale_entry) {
+			clear_bit(PORT_S_ON_STALE_LIST, &port->status);
+			mtk_port_release(&port->kref);
+		}
+
+		kfree(s_list);
+	}
+	mutex_unlock(&port_mngr_grp_mtx);
+}
+
+static struct mtk_stale_list *mtk_port_stale_list_init(struct mtk_ctrl_blk *ctrl_blk, int *dev_id)
+{
+	struct mtk_stale_list *s_list;
+
+	s_list = mtk_port_stale_list_search(ctrl_blk->mdev->dev_str);
+	if (!s_list) {
+		s_list = mtk_port_stale_list_create(ctrl_blk);
+		if (unlikely(!s_list))
+			return NULL;
+	}
+
+	mutex_lock(&port_mngr_grp_mtx);
+	if (s_list->dev_id < 0) {
+		*dev_id = ida_alloc_range(&ccci_dev_ids, 0, MTK_DFLT_MAX_DEV_CNT - 1, GFP_KERNEL);
+	} else {
+		*dev_id = s_list->dev_id;
+		s_list->dev_id = -1;
+	}
+	mutex_unlock(&port_mngr_grp_mtx);
+
+	return s_list;
+}
+
+static void mtk_port_stale_list_exit(struct mtk_ctrl_blk *ctrl_blk,
+				     struct mtk_stale_list *s_list, int dev_id)
+{
+	if (!s_list)
+		return;
+	mutex_lock(&port_mngr_grp_mtx);
+	if (list_empty(&s_list->ports)) {
+		ida_free(&ccci_dev_ids, dev_id);
+		mutex_unlock(&port_mngr_grp_mtx);
+		mtk_port_stale_list_destroy(s_list);
+	} else {
+		s_list->dev_id = dev_id;
+		mutex_unlock(&port_mngr_grp_mtx);
+	}
+}
+
+void mtk_port_trb_init(struct mtk_port *port, struct trb *trb, enum mtk_trb_cmd_type cmd,
+		       int (*trb_complete)(struct sk_buff *skb))
+{
+	kref_init(&trb->kref);
+	trb->channel_id = port->info.rx_ch;
+	trb->status = MTK_DFLT_TRB_STATUS;
+	trb->priv = port;
+	trb->cmd = cmd;
+	trb->trb_complete = trb_complete;
+}
+
+void mtk_port_trb_free(struct kref *trb_kref)
+{
+	struct trb *trb = container_of(trb_kref, struct trb, kref);
+	struct sk_buff *skb, *frag_skb, *next_skb;
+
+	skb = container_of((char *)trb, struct sk_buff, cb[0]);
+	/* Free frag_list for scatter gather TX */
+	if (trb->cmd == TRB_CMD_TX && skb_has_frag_list(skb)) {
+		frag_skb = skb_shinfo(skb)->frag_list;
+		while (frag_skb) {
+			next_skb = frag_skb->next;
+			frag_skb->next = NULL;
+			dev_kfree_skb_any(frag_skb);
+			frag_skb = next_skb;
+		}
+		skb_shinfo(skb)->frag_list = NULL;
+		skb->data_len = 0;
+	}
+	dev_kfree_skb_any(skb);
+}
+EXPORT_SYMBOL(mtk_port_trb_free);
+
+static int mtk_port_open_trb_complete(struct sk_buff *skb)
+{
+	struct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb->data;
+	struct trb *trb = (struct trb *)skb->cb;
+	struct mtk_port *port = trb->priv;
+
+	if (!trb->status) {
+		port->tx_mtu = trb_open_priv->tx_mtu;
+		port->rx_mtu = trb_open_priv->rx_mtu;
+		port->tx_frag_size = trb_open_priv->tx_frag_size;
+		port->rx_frag_size = trb_open_priv->rx_frag_size;
+		port->tx_mtu -= MTK_CCCI_H_ELEN;
+		port->rx_mtu -= MTK_CCCI_H_ELEN;
+	}
+
+	wake_up_interruptible_all(&port->trb_wq);
+
+	kref_put(&trb->kref, mtk_port_trb_free);
+	return 0;
+}
+
+static int mtk_port_close_trb_complete(struct sk_buff *skb)
+{
+	struct trb *trb = (struct trb *)skb->cb;
+	struct mtk_port *port = trb->priv;
+
+	wake_up_interruptible_all(&port->trb_wq);
+	wake_up_interruptible_all(&port->rx_wq);
+	kref_put(&trb->kref, mtk_port_trb_free);
+
+	return 0;
+}
+
+static int mtk_port_tx_complete(struct sk_buff *skb)
+{
+	struct trb *trb = (struct trb *)skb->cb;
+	struct mtk_port *port = trb->priv;
+
+	if (trb->status < 0)
+		dev_warn(port->port_mngr->ctrl_blk->mdev->dev,
+			 "Failed to send data: status:%d, port:%s\n",
+			 trb->status, port->info.name);
+
+	wake_up_interruptible_all(&port->trb_wq);
+	kref_put(&trb->kref, mtk_port_trb_free);
+
+	return 0;
+}
+
+int mtk_port_status_check(struct mtk_port *port)
+{
+	if (!test_bit(PORT_S_ENABLE, &port->status))
+		return -ENODEV;
+
+	if (!test_bit(PORT_S_OPEN, &port->status) || test_bit(PORT_S_FLUSH, &port->status) ||
+	    !test_bit(PORT_S_WR, &port->status))
+		return -EBADF;
+
+	return 0;
+}
+
+int mtk_port_send_data(struct mtk_port *port, void *data)
+{
+	struct mtk_port_mngr *port_mngr;
+	struct sk_buff *skb = data;
+	bool force_send;
+	struct trb *trb;
+	int ret, len;
+
+	port_mngr = port->port_mngr;
+
+	force_send = !!(port->info.flags & (PORT_F_BLOCKING | PORT_F_FORCE_SEND));
+	trb = (struct trb *)skb->cb;
+	mtk_port_trb_init(port, trb, TRB_CMD_TX, mtk_port_tx_complete);
+	len = skb->len;
+	kref_get(&trb->kref); /* kref count 1->2 */
+
+	/* add ccci header */
+	mtk_port_add_header(skb);
+	ret = mtk_port_status_check(port);
+	if (!ret)
+		ret = port_mngr->ctrl_blk->ops->submit_skb(port_mngr->ctrl_blk->mdev,
+							   skb, force_send);
+
+	if (ret < 0) {
+		kref_put(&trb->kref, mtk_port_trb_free); /* kref count 2->1 */
+		kref_put(&trb->kref, mtk_port_trb_free); /* kref count 1->0 */
+		port->tx_seq--;
+		goto out;
+	}
+
+	if (!(port->info.flags & PORT_F_BLOCKING)) {
+		kref_put(&trb->kref, mtk_port_trb_free);
+		ret = len;
+		goto out;
+	}
+start_wait:
+
+	/* wait trb done, and no timeout in tx blocking mode */
+	ret = wait_event_interruptible_timeout(port->trb_wq,
+					       trb->status <= 0 ||
+					       test_bit(PORT_S_FLUSH, &port->status) ||
+					       !test_bit(PORT_S_WR, &port->status),
+					       MTK_DFLT_TRB_TIMEOUT);
+	if (!ret) {
+		goto start_wait;
+	} else if (ret == -ERESTARTSYS) {
+		ret = -EINTR;
+	} else if (ret > 0) {
+		if (test_bit(PORT_S_FLUSH, &port->status))
+			ret = len;
+		else
+			ret = (!trb->status) ? len : trb->status;
+	}
+	kref_put(&trb->kref, mtk_port_trb_free);
+
+out:
+	return ret;
+}
+
+static int mtk_port_check_rx_seq(struct mtk_port *port, struct mtk_ccci_header *ccci_h)
+{
+	u16 seq_num, assert_bit, channel;
+	struct mtk_md_dev *mdev;
+
+	seq_num = FIELD_GET(MTK_HDR_FLD_SEQ, le32_to_cpu(ccci_h->status));
+	assert_bit = FIELD_GET(MTK_HDR_FLD_AST, le32_to_cpu(ccci_h->status));
+	if (assert_bit && port->rx_seq &&
+	    ((seq_num - port->rx_seq) & MTK_CHECK_RX_SEQ_MASK) != 1) {
+		mdev = port->port_mngr->ctrl_blk->mdev;
+		channel = FIELD_GET(MTK_HDR_FLD_CHN, le32_to_cpu(ccci_h->status));
+		dev_warn(mdev->dev,
+			 "<ch: %04x> seq num out-of-order %d->%d, len(%u)\n",
+			 channel, seq_num, port->rx_seq,
+			 le32_to_cpu(ccci_h->packet_len));
+
+		port->rx_seq = seq_num;
+		return -EPROTO;
+	}
+
+	return 0;
+}
+
+static int mtk_port_rx_dispatch_frag_skb(struct mtk_port *port, struct sk_buff *skb)
+{
+	struct sk_buff *frag_skb, *frag_next;
+	int ret;
+
+	frag_skb = skb_shinfo(skb)->frag_list;
+	skb->len -= skb->data_len;
+	skb->data_len = 0;
+	skb_shinfo(skb)->frag_list = NULL;
+
+	ret = ports_ops[port->info.type]->recv(port, skb);
+	if (ret < 0) {
+		skb_shinfo(skb)->frag_list = frag_skb;
+		return ret;
+	}
+
+	while (frag_skb) {
+		frag_next = frag_skb->next;
+		if (!frag_skb->len) {
+			frag_skb->next = NULL;
+			dev_kfree_skb_any(frag_skb);
+			frag_skb = frag_next;
+			continue;
+		}
+		frag_skb->next = NULL;
+		ret = ports_ops[port->info.type]->recv(port, frag_skb);
+		if (ret < 0) {
+			frag_skb->next = frag_next;
+			while (frag_skb) {
+				frag_next = frag_skb->next;
+				frag_skb->next = NULL;
+				dev_kfree_skb_any(frag_skb);
+				frag_skb = frag_next;
+			}
+			return -EIO;
+		}
+		frag_skb = frag_next;
+	}
+
+	return 0;
+}
+
+static int mtk_port_rx_dispatch(struct sk_buff *skb, void *priv, bool force_recv)
+{
+	struct mtk_port_mngr *port_mngr;
+	struct mtk_ccci_header *ccci_h;
+	struct mtk_port *port = priv;
+	int ret = -EPROTO;
+	u16 channel;
+
+	if (!skb || !priv) {
+		pr_err("Invalid input value in rx dispatch\n");
+		return -EINVAL;
+	}
+
+	port_mngr = port->port_mngr;
+
+	ccci_h = mtk_port_strip_header(skb);
+	if (unlikely(!ccci_h)) {
+		dev_warn(port_mngr->ctrl_blk->mdev->dev,
+			 "Unsupported: skb length(%d) is less than ccci header\n",
+			 skb->len);
+		goto drop_data;
+	}
+
+	channel = FIELD_GET(MTK_HDR_FLD_CHN, le32_to_cpu(ccci_h->status));
+	port = mtk_port_search_by_id(port_mngr, channel);
+	if (unlikely(!port)) {
+		dev_warn(port_mngr->ctrl_blk->mdev->dev,
+			 "Failed to find port by channel:%d\n", channel);
+		goto drop_data;
+	}
+
+	ret = mtk_port_check_rx_seq(port, ccci_h);
+	if (unlikely(ret))
+		goto drop_data;
+
+	port->rx_seq = FIELD_GET(MTK_HDR_FLD_SEQ, le32_to_cpu(ccci_h->status));
+	skb_pull(skb, sizeof(*ccci_h));
+
+	/* Support scatter gather transmission */
+	if (port->rx_mtu > port->rx_frag_size) {
+		ret = mtk_port_rx_dispatch_frag_skb(port, skb);
+		/* -EIO means partial data dispatch complete, does not goto drop flow */
+		if (ret < 0 && ret != -EIO)
+			goto drop_frag_skb;
+	} else {
+		ret = ports_ops[port->info.type]->recv(port, skb);
+		if (ret < 0)
+			goto drop_data;
+	}
+
+	return ret;
+
+drop_frag_skb:
+	{
+		struct sk_buff *frag_skb, *tmp;
+
+		frag_skb = skb_shinfo(skb)->frag_list;
+		while (frag_skb) {
+			tmp = frag_skb->next;
+			frag_skb->next = NULL;
+			dev_kfree_skb_any(frag_skb);
+			frag_skb = tmp;
+		}
+		skb_shinfo(skb)->frag_list = NULL;
+	}
+drop_data:
+	dev_kfree_skb_any(skb);
+	return ret;
+}
+
+int mtk_port_add_header(struct sk_buff *skb)
+{
+	struct mtk_ccci_header *ccci_h;
+	struct mtk_port *port;
+	struct trb *trb;
+
+	trb = (struct trb *)skb->cb;
+	if (trb->status == MTK_TRB_HEADER_ADDED)
+		return 0;
+
+	port = trb->priv;
+	if (!port)
+		return -EINVAL;
+
+	ccci_h = skb_push(skb, sizeof(*ccci_h));
+
+	ccci_h->packet_header = cpu_to_le32(0);
+	ccci_h->packet_len = cpu_to_le32(skb->len);
+	ccci_h->ex_msg = cpu_to_le32(0);
+	ccci_h->status = cpu_to_le32(FIELD_PREP(MTK_HDR_FLD_CHN, port->info.tx_ch) |
+				     FIELD_PREP(MTK_HDR_FLD_SEQ, port->tx_seq++) |
+				     FIELD_PREP(MTK_HDR_FLD_AST, 1));
+
+	trb->status = MTK_TRB_HEADER_ADDED;
+
+	return 0;
+}
+
+struct mtk_ccci_header *mtk_port_strip_header(struct sk_buff *skb)
+{
+	struct mtk_ccci_header *ccci_h;
+
+	if (skb->len < sizeof(*ccci_h)) {
+		pr_err("Invalid input value\n");
+		return NULL;
+	}
+
+	ccci_h = (struct mtk_ccci_header *)skb->data;
+
+	return ccci_h;
+}
+
+int mtk_port_status_update(struct mtk_md_dev *mdev, void *data)
+{
+	struct mtk_port_enum_msg *msg = data;
+	struct mtk_port_info *port_info;
+	struct mtk_port_mngr *port_mngr;
+	struct mtk_ctrl_blk *ctrl_blk;
+	struct mtk_port *port;
+	int port_id;
+	u16 ch_id;
+
+	if (unlikely(!mdev || !msg))
+		return -EINVAL;
+
+	ctrl_blk = mdev->ctrl_blk;
+	port_mngr = ctrl_blk->port_mngr;
+	if (le16_to_cpu(msg->version) != MTK_PORT_ENUM_VER ||
+	    le32_to_cpu(msg->head_pattern) != MTK_PORT_ENUM_HEAD_PATTERN ||
+	    le32_to_cpu(msg->tail_pattern) != MTK_PORT_ENUM_TAIL_PATTERN)
+		return -EPROTO;
+
+	for (port_id = 0; port_id < le16_to_cpu(msg->port_cnt); port_id++) {
+		port_info = (struct mtk_port_info *)(msg->data +
+						   (sizeof(*port_info) * port_id));
+		ch_id = FIELD_GET(MTK_INFO_FLD_CHID, le16_to_cpu(port_info->channel));
+		port = mtk_port_search_by_id(port_mngr, ch_id);
+		if (!port)
+			continue;
+		port->enable = FIELD_GET(MTK_INFO_FLD_EN, le16_to_cpu(port_info->channel));
+	}
+
+	return 0;
+}
+
+int mtk_port_ch_enable(struct mtk_port *port)
+{
+	struct mtk_port_mngr *port_mngr = port->port_mngr;
+	struct trb_open_priv *trb_open_priv;
+	struct sk_buff *skb;
+	struct trb *trb;
+	int ret;
+
+	skb = __dev_alloc_skb(Q_MTU_3_5K, GFP_KERNEL);
+	if (!skb)
+		return -ENOMEM;
+
+	trb_open_priv = (struct trb_open_priv *)skb->data;
+	trb_open_priv->rx_done = mtk_port_rx_dispatch;
+
+	skb_put(skb, sizeof(struct trb_open_priv));
+	trb = (struct trb *)skb->cb;
+	mtk_port_trb_init(port, trb, TRB_CMD_ENABLE, mtk_port_open_trb_complete);
+	kref_get(&trb->kref);
+
+	ret = port_mngr->ctrl_blk->ops->submit_skb(port_mngr->ctrl_blk->mdev, skb, true);
+	if (ret) {
+		dev_err(port_mngr->ctrl_blk->mdev->dev,
+			"Failed to submit trb for port(%s), ret=%d\n",
+			port->info.name, ret);
+		kref_put(&trb->kref, mtk_port_trb_free);
+		kref_put(&trb->kref, mtk_port_trb_free);
+		return ret;
+	}
+
+start_wait:
+	ret = wait_event_interruptible_timeout(port->trb_wq, trb->status <= 0,
+					       MTK_DFLT_TRB_TIMEOUT);
+	if (ret == -ERESTARTSYS)
+		goto start_wait;
+	else if (!ret)
+		ret = -ETIMEDOUT;
+	else
+		ret = trb->status;
+
+	kref_put(&trb->kref, mtk_port_trb_free);
+
+	return ret;
+}
+
+int mtk_port_ch_disable(struct mtk_port *port)
+{
+	struct mtk_port_mngr *port_mngr = port->port_mngr;
+	struct sk_buff *skb;
+	struct trb *trb;
+	int ret;
+
+	skb = __dev_alloc_skb(Q_MTU_3_5K, GFP_KERNEL);
+	if (!skb)
+		return -ENOMEM;
+
+	trb = (struct trb *)skb->cb;
+	mtk_port_trb_init(port, trb, TRB_CMD_DISABLE, mtk_port_close_trb_complete);
+	kref_get(&trb->kref);
+
+	ret = port_mngr->ctrl_blk->ops->submit_skb(port_mngr->ctrl_blk->mdev, skb, true);
+	if (ret) {
+		dev_warn(port_mngr->ctrl_blk->mdev->dev,
+			 "Failed to submit trb for port(%s), ret=%d\n",
+			 port->info.name, ret);
+		kref_put(&trb->kref, mtk_port_trb_free);
+		kref_put(&trb->kref, mtk_port_trb_free);
+		return ret;
+	}
+
+start_wait:
+	ret = wait_event_interruptible_timeout(port->trb_wq, trb->status <= 0,
+					       MTK_DFLT_TRB_TIMEOUT);
+	if (ret == -ERESTARTSYS)
+		goto start_wait;
+	else if (!ret)
+		ret = -ETIMEDOUT;
+	else
+		ret = trb->status;
+
+	kref_put(&trb->kref, mtk_port_trb_free);
+
+	return ret;
+}
+
+static int mtk_port_enable_by_type(struct mtk_port_mngr *port_mngr, int tbl_type)
+{
+	struct mtk_port **ports;
+	int ret, idx;
+
+	if (tbl_type < 0 || tbl_type >= PORT_TBL_MAX)
+		return -EINVAL;
+
+	ports = kcalloc(port_mngr->port_cnt, sizeof(struct mtk_port *), GFP_KERNEL);
+	if (!ports)
+		return -ENOMEM;
+
+	ret = radix_tree_gang_lookup(&port_mngr->port_tbl[tbl_type],
+				     (void **)ports, 0, port_mngr->port_cnt);
+	for (idx = 0; idx < ret; idx++) {
+		if (ports[idx]->enable)
+			ports_ops[ports[idx]->info.type]->enable(ports[idx]);
+	}
+
+	kfree(ports);
+	return 0;
+}
+
+static void mtk_port_disable(struct mtk_port_mngr *port_mngr)
+{
+	struct mtk_port **ports;
+	int tbl_type;
+	int ret, idx;
+
+	ports = kcalloc(port_mngr->port_cnt, sizeof(struct mtk_port *), GFP_KERNEL);
+	if (!ports)
+		return;
+
+	tbl_type = PORT_TBL_SAP;
+	do {
+		ret = radix_tree_gang_lookup(&port_mngr->port_tbl[tbl_type],
+					     (void **)ports, 0, port_mngr->port_cnt);
+		for (idx = 0; idx < ret; idx++)
+			ports_ops[ports[idx]->info.type]->disable(ports[idx]);
+	} while (++tbl_type < PORT_TBL_MAX);
+	kfree(ports);
+}
+
+void mtk_port_mngr_fsm_state_handler(struct mtk_fsm_param *fsm_param, void *arg)
+{
+	struct mtk_port_mngr *port_mngr;
+
+	if (!fsm_param || !arg)
+		return;
+
+	port_mngr = arg;
+
+	switch (fsm_param->to) {
+	case FSM_STATE_OFF:
+		mtk_port_disable(port_mngr);
+		break;
+	case FSM_STATE_READY:
+		mtk_port_enable_by_type(port_mngr, PORT_TBL_MD);
+		break;
+	default:
+		break;
+	}
+}
+
+void mtk_port_mngr_fsm_state_handler_late(struct mtk_fsm_param *fsm_param, void *arg)
+{
+	struct mtk_port_mngr *port_mngr;
+	struct mtk_port *port;
+
+	if (!fsm_param || !arg)
+		return;
+
+	port_mngr = arg;
+
+	switch (fsm_param->to) {
+	case FSM_STATE_BOOTUP:
+		if (fsm_param->fsm_flag & FSM_F_MD_HS_START) {
+			port = mtk_port_search_by_id(port_mngr, CCCI_CONTROL_RX);
+			if (port)
+				ports_ops[port->info.type]->enable(port);
+		} else if (fsm_param->fsm_flag & FSM_F_SAP_HS_START) {
+			port = mtk_port_search_by_id(port_mngr, CCCI_SAP_CONTROL_RX);
+			if (port)
+				ports_ops[port->info.type]->enable(port);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+int mtk_port_mngr_init(struct mtk_ctrl_blk *ctrl_blk, struct mtk_port_cfg *port_cfg, int port_cnt)
+{
+	struct mtk_port_mngr *port_mngr;
+	struct mtk_stale_list *s_list;
+	int ret = -ENOMEM;
+	int dev_id;
+
+	s_list = mtk_port_stale_list_init(ctrl_blk, &dev_id);
+	if (!s_list) {
+		dev_err((ctrl_blk->mdev)->dev, "Failed to init mtk_stale_list\n");
+		goto err_out;
+	}
+
+	port_mngr = devm_kzalloc(ctrl_blk->mdev->dev, sizeof(*port_mngr), GFP_KERNEL);
+	if (unlikely(!port_mngr)) {
+		dev_err((ctrl_blk->mdev)->dev, "Failed to alloc memory for port_mngr\n");
+		goto err_exit_stale_list;
+	}
+
+	port_mngr->ctrl_blk = ctrl_blk;
+	port_mngr->dev_id = dev_id;
+
+	ret = mtk_port_tbl_create(port_mngr, port_cfg, port_cnt, s_list);
+	if (unlikely(ret)) {
+		dev_err((ctrl_blk->mdev)->dev, "Failed to create port_tbl\n");
+		goto err_free_port_mngr;
+	}
+
+	ctrl_blk->port_mngr = port_mngr;
+
+	return ret;
+
+err_free_port_mngr:
+	mtk_port_tbl_destroy(port_mngr, s_list);
+err_exit_stale_list:
+	mtk_port_stale_list_exit(ctrl_blk, s_list, dev_id);
+err_out:
+	return ret;
+}
+
+void mtk_port_mngr_exit(struct mtk_ctrl_blk *ctrl_blk)
+{
+	struct mtk_port_mngr *port_mngr = ctrl_blk->port_mngr;
+	struct mtk_stale_list *s_list;
+	int dev_id;
+
+	s_list = mtk_port_stale_list_search(port_mngr->ctrl_blk->mdev->dev_str);
+	dev_id = port_mngr->dev_id;
+
+	mtk_port_tbl_destroy(port_mngr, s_list);
+
+	ctrl_blk->port_mngr = NULL;
+	mtk_port_stale_list_exit(ctrl_blk, s_list, dev_id);
+}
diff --git a/drivers/net/wwan/t9xx/mtk_port.h b/drivers/net/wwan/t9xx/mtk_port.h
new file mode 100644
index 00000000000000..4846354981d7f1
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_port.h
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_PORT_H__
+#define __MTK_PORT_H__
+
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/radix-tree.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+
+#include "mtk_ctrl_plane.h"
+#include "mtk_dev.h"
+
+#define MTK_PEER_ID_MASK			(0xF000)
+#define MTK_PEER_ID_SHIFT			(12)
+#define MTK_PEER_ID(ch)				(((ch) & MTK_PEER_ID_MASK) >> MTK_PEER_ID_SHIFT)
+#define MTK_PEER_ID_SAP				(0x1)
+#define MTK_PEER_ID_MD				(0x2)
+#define MTK_CH_ID_MASK				(0x0FFF)
+#define MTK_CH_ID(ch)				((ch) & MTK_CH_ID_MASK)
+#define MTK_DFLT_MAX_DEV_CNT			(10)
+#define MTK_DFLT_PORT_NAME_LEN			(20)
+
+/* Mapping MTK_PEER_ID and mtk_port_tbl index */
+#define MTK_PORT_TBL_TYPE(ch)			(MTK_PEER_ID(ch) - 1)
+
+/* ccci header length + reserved space that is used in exception flow */
+#define MTK_CCCI_H_ELEN		(128)
+
+#define MTK_HDR_FLD_AST		((u32)BIT(31))
+#define MTK_HDR_FLD_SEQ		GENMASK(30, 16)
+#define MTK_HDR_FLD_CHN		GENMASK(15, 0)
+
+#define MTK_INFO_FLD_EN		((u16)BIT(15))
+#define MTK_INFO_FLD_CHID	GENMASK(14, 0)
+
+enum mtk_port_status {
+	PORT_S_DFLT = 0,
+	PORT_S_ENABLE,
+	PORT_S_OPEN,
+	PORT_S_RD,
+	PORT_S_WR,
+	PORT_S_FLUSH,
+	PORT_S_ON_STALE_LIST,
+	PORT_S_STOP,
+};
+
+enum mtk_ccci_ch {
+	/* to sAP */
+	CCCI_SAP_CONTROL_RX			= 0x1000,
+	CCCI_SAP_CONTROL_TX			= 0x1001,
+	/* to MD */
+	CCCI_CONTROL_RX				= 0x2000,
+	CCCI_CONTROL_TX				= 0x2001,
+	CCCI_UART2_RX				= 0x200A,
+	CCCI_UART2_TX				= 0x200C,
+	CCCI_MBIM_RX				= 0x20D0,
+	CCCI_MBIM_TX				= 0x20D1,
+};
+
+enum mtk_port_flag {
+	PORT_F_DFLT = 0,
+	PORT_F_BLOCKING = BIT(1),
+	PORT_F_ALLOW_DROP = BIT(2),
+	PORT_F_FORCE_SEND = BIT(6),
+};
+
+enum mtk_port_tbl {
+	PORT_TBL_SAP,
+	PORT_TBL_MD,
+	PORT_TBL_MAX
+};
+
+enum mtk_port_type {
+	PORT_TYPE_INTERNAL,
+	PORT_TYPE_WWAN,
+	PORT_TYPE_MAX
+};
+
+struct mtk_internal_port {
+	void *arg;
+	int (*recv_cb)(void *arg, struct sk_buff *skb);
+};
+
+struct mtk_wwan_port {
+	/* w_lock protects wwan_port when recv data and disable port at the same time */
+	struct mutex w_lock;
+	int w_type;
+	void *w_port;
+};
+
+struct mtk_port_cfg {
+	enum mtk_ccci_ch tx_ch;
+	enum mtk_ccci_ch rx_ch;
+	enum mtk_port_type type;
+	char name[MTK_DFLT_PORT_NAME_LEN];
+	unsigned char flags;
+};
+
+struct mtk_port {
+	struct mtk_port_cfg info;
+	struct kref kref;
+	bool enable;
+	unsigned long status;
+	unsigned int minor;
+	unsigned short tx_seq;
+	unsigned short rx_seq;
+	unsigned int tx_mtu;
+	unsigned int rx_mtu;
+	u32 tx_frag_size;
+	u32 rx_frag_size;
+	struct sk_buff_head rx_skb_list;
+	unsigned int rx_data_len;
+	unsigned int rx_buf_size;
+	wait_queue_head_t trb_wq;
+	wait_queue_head_t rx_wq;
+	struct list_head stale_entry;
+	char dev_str[MTK_DEV_STR_LEN];
+	struct mtk_port_mngr *port_mngr;
+	struct mtk_internal_port i_priv;
+	struct mtk_wwan_port w_priv;
+};
+
+struct mtk_port_mngr {
+	struct mtk_ctrl_blk *ctrl_blk;
+	struct radix_tree_root port_tbl[PORT_TBL_MAX];
+	unsigned int port_cnt;
+	int dev_id;
+};
+
+struct mtk_stale_list {
+	struct list_head entry;
+	struct list_head ports;
+	char dev_str[MTK_DEV_STR_LEN];
+	int dev_id;
+	rwlock_t port_mngr_lock;
+};
+
+struct mtk_ccci_header {
+	__le32 packet_header;
+	__le32 packet_len;
+	__le32 status;
+	__le32 ex_msg;
+};
+
+struct mtk_port_layer_cfg {
+	struct mtk_port_cfg *port_cfg;
+	int port_cnt;
+};
+
+extern const struct port_ops *ports_ops[PORT_TYPE_MAX];
+
+void mtk_port_release(struct kref *port_kref);
+void mtk_port_trb_free(struct kref *trb_kref);
+struct mtk_port *mtk_port_search_by_name(struct mtk_port_mngr *port_mngr, char *name);
+void mtk_port_stale_list_grp_cleanup(void);
+int mtk_port_add_header(struct sk_buff *skb);
+struct mtk_ccci_header *mtk_port_strip_header(struct sk_buff *skb);
+int mtk_port_status_check(struct mtk_port *port);
+int mtk_port_send_data(struct mtk_port *port, void *data);
+int mtk_port_status_update(struct mtk_md_dev *mdev, void *data);
+int mtk_port_ch_enable(struct mtk_port *port);
+int mtk_port_ch_disable(struct mtk_port *port);
+void mtk_port_mngr_fsm_state_handler(struct mtk_fsm_param *fsm_param, void *arg);
+void mtk_port_mngr_fsm_state_handler_late(struct mtk_fsm_param *fsm_param, void *arg);
+int mtk_port_mngr_init(struct mtk_ctrl_blk *ctrl_blk, struct mtk_port_cfg *port_cfg, int port_cnt);
+void mtk_port_mngr_exit(struct mtk_ctrl_blk *ctrl_blk);
+void mtk_port_trb_init(struct mtk_port *port, struct trb *trb, enum mtk_trb_cmd_type cmd,
+		       int (*trb_complete)(struct sk_buff *skb));
+#endif /* __MTK_PORT_H__ */
diff --git a/drivers/net/wwan/t9xx/mtk_port_io.c b/drivers/net/wwan/t9xx/mtk_port_io.c
new file mode 100644
index 00000000000000..882254b74026f5
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_port_io.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+#include <linux/netdevice.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/wwan.h>
+
+#include "mtk_port_io.h"
+
+static int mtk_port_get_locked(struct mtk_port *port)
+{
+	int ret = 0;
+
+	mutex_lock(&port_mngr_grp_mtx);
+	if (!port) {
+		mutex_unlock(&port_mngr_grp_mtx);
+		pr_err("Port does not exist\n");
+		return -ENODEV;
+	}
+	kref_get(&port->kref);
+	mutex_unlock(&port_mngr_grp_mtx);
+
+	return ret;
+}
+
+static void mtk_port_put_locked(struct mtk_port *port)
+{
+	mutex_lock(&port_mngr_grp_mtx);
+	kref_put(&port->kref, mtk_port_release);
+	mutex_unlock(&port_mngr_grp_mtx);
+}
+
+static void mtk_port_struct_init(struct mtk_port *port)
+{
+	port->tx_seq = 0;
+	port->rx_seq = -1;
+	clear_bit(PORT_S_ENABLE, &port->status);
+	kref_init(&port->kref);
+	skb_queue_head_init(&port->rx_skb_list);
+	port->rx_buf_size = MTK_RX_BUF_SIZE;
+	init_waitqueue_head(&port->trb_wq);
+	init_waitqueue_head(&port->rx_wq);
+}
+
+static int mtk_port_copy_data_from(void *to, union user_buf from, unsigned int len,
+				   unsigned int offset, bool from_user_space)
+{
+	if (from_user_space) {
+		if (copy_from_user(to, from.ubuf + offset, len))
+			return -EINVAL;
+	} else {
+		memcpy(to, from.kbuf + offset, len);
+	}
+
+	return 0;
+}
+
+static int mtk_port_common_write_frag_skb(struct mtk_port *port, struct sk_buff *skb,
+					  union user_buf buf, u32 packet_size,
+					  u32 cur_pos, bool from_user_space)
+{
+	struct sk_buff *frag_skb, *tmp = NULL;
+	u32 frag_size;
+	int ret;
+
+	frag_size = min(packet_size, port->tx_frag_size);
+	ret = mtk_port_copy_data_from(skb_put(skb, frag_size),
+				      buf, frag_size,
+				      cur_pos, from_user_space);
+	if (ret) {
+		dev_err(port->port_mngr->ctrl_blk->mdev->dev,
+			"Failed to copy skb for port(%s)\n", port->info.name);
+		goto err_reset_skb;
+	}
+	cur_pos += frag_size;
+	packet_size -= frag_size;
+	if (!packet_size)
+		return cur_pos;
+
+	while (packet_size > 0) {
+		frag_skb = __dev_alloc_skb(port->tx_mtu, GFP_KERNEL);
+		if (!frag_skb) {
+			ret = -ENOMEM;
+			goto err_free_frag_list;
+		}
+
+		frag_size = min(packet_size, port->tx_frag_size);
+		ret = mtk_port_copy_data_from(skb_put(frag_skb, frag_size),
+					      buf, frag_size,
+					      cur_pos, from_user_space);
+		if (ret) {
+			dev_err(port->port_mngr->ctrl_blk->mdev->dev,
+				"Failed to copy frag_skb for port(%s)\n", port->info.name);
+			dev_kfree_skb_any(frag_skb);
+			goto err_free_frag_list;
+		}
+		skb->data_len += frag_size;
+		skb->len += frag_size;
+		cur_pos += frag_size;
+		packet_size -= frag_size;
+		if (!tmp)
+			skb_shinfo(skb)->frag_list = frag_skb;
+		else
+			tmp->next = frag_skb;
+		tmp = frag_skb;
+	}
+	return cur_pos;
+
+err_free_frag_list:
+	frag_skb = skb_shinfo(skb)->frag_list;
+	while (frag_skb) {
+		tmp = frag_skb->next;
+		frag_skb->next = NULL;
+		dev_kfree_skb_any(frag_skb);
+		frag_skb = tmp;
+	}
+	skb_shinfo(skb)->frag_list = NULL;
+err_reset_skb:
+	skb->data_len = 0;
+	return ret;
+}
+
+static int mtk_port_common_write(struct mtk_port *port, union user_buf buf, unsigned int len,
+				 bool from_user_space)
+{
+	u32 packet_size, left_cnt = len, cur_pos;
+	struct sk_buff *skb;
+	int ret;
+
+	if (len == 0)
+		return -EINVAL;
+
+start_write:
+	ret = mtk_port_status_check(port);
+	if (ret)
+		goto end_write;
+
+	skb = __dev_alloc_skb(port->tx_mtu, GFP_KERNEL);
+	if (!skb) {
+		ret = -ENOMEM;
+		goto end_write;
+	}
+
+	skb_reserve(skb, sizeof(struct mtk_ccci_header));
+
+	packet_size = min_t(u32, left_cnt, port->tx_mtu - sizeof(struct mtk_ccci_header));
+	cur_pos = len - left_cnt;
+	/* Support scatter gather transmission */
+	if (port->tx_mtu > port->tx_frag_size) {
+		ret = mtk_port_common_write_frag_skb(port, skb, buf, packet_size,
+						     cur_pos, from_user_space);
+		if (ret < 0)
+			goto err_free_skb;
+	} else {
+		ret = mtk_port_copy_data_from(skb_put(skb, packet_size),
+					      buf, packet_size,
+					      cur_pos, from_user_space);
+		if (ret) {
+			dev_err(port->port_mngr->ctrl_blk->mdev->dev,
+				"Failed to copy data for port(%s)\n", port->info.name);
+			goto err_free_skb;
+		}
+	}
+
+	ret = mtk_port_send_data(port, skb);
+	if (ret < 0) {
+		if (ret == -EINTR)
+			left_cnt -= packet_size;
+		goto end_write;
+	}
+
+	left_cnt -= ret;
+	if (left_cnt)
+		goto start_write;
+	else
+		goto end_write;
+
+err_free_skb:
+	dev_kfree_skb_any(skb);
+end_write:
+	return (len > left_cnt) ? (len - left_cnt) : ret;
+}
+
+static int mtk_port_internal_init(struct mtk_port *port)
+{
+	mtk_port_struct_init(port);
+	port->enable = false;
+
+	return 0;
+}
+
+static void mtk_port_internal_exit(struct mtk_port *port)
+{
+	if (test_bit(PORT_S_ENABLE, &port->status))
+		ports_ops[port->info.type]->disable(port);
+}
+
+static void mtk_port_reset(struct mtk_port *port)
+{
+	port->tx_seq = 0;
+	port->rx_seq = -1;
+}
+
+static void mtk_port_internal_enable(struct mtk_port *port)
+{
+	int ret;
+
+	if (test_bit(PORT_S_ENABLE, &port->status))
+		return;
+
+	ret = mtk_port_ch_enable(port);
+	if (ret && ret != -EBUSY)
+		return;
+
+	set_bit(PORT_S_WR, &port->status);
+	set_bit(PORT_S_ENABLE, &port->status);
+}
+
+static void mtk_port_internal_disable(struct mtk_port *port)
+{
+	if (!test_and_clear_bit(PORT_S_ENABLE, &port->status))
+		return;
+
+	clear_bit(PORT_S_WR, &port->status);
+	mtk_port_ch_disable(port);
+}
+
+static int mtk_port_internal_recv(struct mtk_port *port, struct sk_buff *skb)
+{
+	struct mtk_internal_port *priv;
+	int ret = -ENXIO;
+
+	if (!test_bit(PORT_S_OPEN, &port->status))
+		goto drop_data;
+
+	priv = &port->i_priv;
+	if (!priv->recv_cb || !priv->arg)
+		goto drop_data;
+
+	ret = priv->recv_cb(priv->arg, skb);
+	return ret;
+
+drop_data:
+	return ret;
+}
+
+static int mtk_port_common_open(struct mtk_port *port)
+{
+	int ret = 0;
+
+	if (!test_bit(PORT_S_ENABLE, &port->status))
+		return -ENODEV;
+
+	if (test_bit(PORT_S_OPEN, &port->status))
+		return -EBUSY;
+
+	skb_queue_purge(&port->rx_skb_list);
+	set_bit(PORT_S_OPEN, &port->status);
+	clear_bit(PORT_S_FLUSH, &port->status);
+
+	return ret;
+}
+
+static void mtk_port_common_close(struct mtk_port *port)
+{
+	clear_bit(PORT_S_OPEN, &port->status);
+
+	skb_queue_purge(&port->rx_skb_list);
+	port->rx_data_len = 0;
+
+	set_bit(PORT_S_FLUSH, &port->status);
+	wake_up_interruptible_all(&port->trb_wq);
+	wake_up_interruptible_all(&port->rx_wq);
+}
+
+void *mtk_port_internal_open(struct mtk_md_dev *mdev, char *name, int flag)
+{
+	struct mtk_port_mngr *port_mngr;
+	struct mtk_ctrl_blk *ctrl_blk;
+	struct mtk_port *port;
+	int ret;
+
+	ctrl_blk = mdev->ctrl_blk;
+	port_mngr = ctrl_blk->port_mngr;
+
+	port = mtk_port_search_by_name(port_mngr, name);
+	if (port && port->info.type != PORT_TYPE_INTERNAL) {
+		port = NULL;
+		goto out;
+	}
+
+	ret = mtk_port_get_locked(port);
+	if (ret)
+		goto out;
+
+	ret = mtk_port_common_open(port);
+	if (ret) {
+		mtk_port_put_locked(port);
+		port = NULL;
+		goto out;
+	}
+
+	if (flag & O_NONBLOCK)
+		port->info.flags &= ~PORT_F_BLOCKING;
+	else
+		port->info.flags |= PORT_F_BLOCKING;
+out:
+	return port;
+}
+
+int mtk_port_internal_close(void *i_port)
+{
+	struct mtk_port *port = i_port;
+	int ret = 0;
+
+	if (!port) {
+		ret = -EINVAL;
+		goto end;
+	}
+
+	if (!test_bit(PORT_S_OPEN, &port->status)) {
+		pr_err("Port(%s) has been closed\n", port->info.name);
+		ret = -EBADF;
+		goto end;
+	}
+
+	mtk_port_common_close(port);
+	mtk_port_put_locked(port);
+end:
+	return ret;
+}
+
+int mtk_port_internal_write(void *i_port, struct sk_buff *skb)
+{
+	struct mtk_port *port = i_port;
+
+	if (!port || !skb) {
+		if (skb)
+			dev_kfree_skb_any(skb);
+		pr_err_ratelimited("Internal write: invalid input\n");
+		return -EINVAL;
+	}
+	return mtk_port_send_data(port, skb);
+}
+
+void mtk_port_internal_recv_register(void *i_port,
+				     int (*cb)(void *priv, struct sk_buff *skb),
+				     void *arg)
+{
+	struct mtk_port *port = i_port;
+	struct mtk_internal_port *priv;
+
+	priv = &port->i_priv;
+	priv->arg = arg;
+	priv->recv_cb = cb;
+}
+
+int mtk_port_io_init(void)
+{
+	return 0;
+}
+
+void mtk_port_io_exit(void)
+{
+}
+
+static const struct port_ops port_internal_ops = {
+	.init = mtk_port_internal_init,
+	.exit = mtk_port_internal_exit,
+	.reset = mtk_port_reset,
+	.enable = mtk_port_internal_enable,
+	.disable = mtk_port_internal_disable,
+	.recv = mtk_port_internal_recv,
+};
+
+static int mtk_port_wwan_open(struct wwan_port *w_port)
+{
+	struct mtk_port *port;
+	int ret;
+
+	port = wwan_port_get_drvdata(w_port);
+	ret = mtk_port_get_locked(port);
+	if (ret)
+		return ret;
+
+	ret = mtk_port_common_open(port);
+	if (ret)
+		mtk_port_put_locked(port);
+
+	return ret;
+}
+
+static void mtk_port_wwan_close(struct wwan_port *w_port)
+{
+	struct mtk_port *port = wwan_port_get_drvdata(w_port);
+
+	mtk_port_common_close(port);
+	mtk_port_put_locked(port);
+}
+
+static int mtk_port_wwan_write(struct wwan_port *w_port, struct sk_buff *skb)
+{
+	struct mtk_port *port = wwan_port_get_drvdata(w_port);
+	union user_buf user_buf;
+	int ret;
+
+	if (unlikely(!skb->len)) {
+		consume_skb(skb);
+		return 0;
+	}
+
+	port->info.flags &= ~PORT_F_BLOCKING;
+	user_buf.kbuf = (void *)skb->data;
+	ret = mtk_port_common_write(port, user_buf, skb->len, false);
+	if (ret < 0)
+		return ret;
+
+	consume_skb(skb);
+	return 0;
+}
+
+static int mtk_port_wwan_write_blocking(struct wwan_port *w_port, struct sk_buff *skb)
+{
+	struct mtk_port *port = wwan_port_get_drvdata(w_port);
+	union user_buf user_buf;
+	int ret;
+
+	if (unlikely(!skb->len)) {
+		consume_skb(skb);
+		return 0;
+	}
+
+	port->info.flags |= PORT_F_BLOCKING;
+	user_buf.kbuf = (void *)skb->data;
+	ret = mtk_port_common_write(port, user_buf, skb->len, false);
+	if (ret < 0)
+		return ret;
+
+	consume_skb(skb);
+	return 0;
+}
+
+static __poll_t mtk_port_wwan_poll(struct wwan_port *w_port, struct file *file,
+				   struct poll_table_struct *poll)
+{
+	struct mtk_port *port = wwan_port_get_drvdata(w_port);
+	union ctrl_hif_cmd_data hif_cmd;
+	struct mtk_ctrl_blk *ctrl_blk;
+	__poll_t mask = 0;
+
+	poll_wait(file, &port->trb_wq, poll);
+	if (mtk_port_status_check(port))
+		return EPOLLERR | EPOLLHUP;
+
+	ctrl_blk = port->port_mngr->ctrl_blk;
+	hif_cmd.rx_ch = port->info.rx_ch;
+	if (!ctrl_blk->ops->send_cmd(ctrl_blk->mdev, HIF_CTRL_CMD_CHECK_TX_FULL, &hif_cmd))
+		mask |= EPOLLOUT | EPOLLWRNORM;
+
+	return mask;
+}
+
+static const struct wwan_port_ops wwan_ops = {
+	.start = mtk_port_wwan_open,
+	.stop = mtk_port_wwan_close,
+	.tx = mtk_port_wwan_write,
+	.tx_blocking = mtk_port_wwan_write_blocking,
+	.tx_poll = mtk_port_wwan_poll,
+};
+
+static int mtk_port_wwan_init(struct mtk_port *port)
+{
+	mtk_port_struct_init(port);
+	port->enable = false;
+
+	mutex_init(&port->w_priv.w_lock);
+
+	switch (port->info.rx_ch) {
+	case CCCI_MBIM_RX:
+		port->w_priv.w_type = WWAN_PORT_MBIM;
+		break;
+	case CCCI_UART2_RX:
+		port->w_priv.w_type = WWAN_PORT_AT;
+		break;
+	default:
+		port->w_priv.w_type = WWAN_PORT_UNKNOWN;
+		break;
+	}
+
+	return 0;
+}
+
+static void mtk_port_wwan_exit(struct mtk_port *port)
+{
+	if (test_bit(PORT_S_ENABLE, &port->status))
+		ports_ops[port->info.type]->disable(port);
+}
+
+static void mtk_port_wwan_enable(struct mtk_port *port)
+{
+	struct mtk_port_mngr *port_mngr;
+	int ret;
+
+	port_mngr = port->port_mngr;
+
+	if (test_bit(PORT_S_ENABLE, &port->status))
+		return;
+
+	ret = mtk_port_ch_enable(port);
+	if (ret && ret != -EBUSY)
+		return;
+
+	port->w_priv.w_port = wwan_create_port(port_mngr->ctrl_blk->mdev->dev,
+					       port->w_priv.w_type,
+					       &wwan_ops, NULL, port);
+	if (IS_ERR(port->w_priv.w_port)) {
+		dev_warn(port_mngr->ctrl_blk->mdev->dev,
+			 "Failed to create wwan port for (%s)\n", port->info.name);
+		port->w_priv.w_port = NULL;
+		mtk_port_ch_disable(port);
+		return;
+	}
+
+	set_bit(PORT_S_WR, &port->status);
+	set_bit(PORT_S_ENABLE, &port->status);
+}
+
+static void mtk_port_wwan_disable(struct mtk_port *port)
+{
+	struct wwan_port *w_port;
+
+	if (!test_and_clear_bit(PORT_S_ENABLE, &port->status))
+		return;
+
+	clear_bit(PORT_S_WR, &port->status);
+	w_port = port->w_priv.w_port;
+	mutex_lock(&port->w_priv.w_lock);
+	port->w_priv.w_port = NULL;
+	mutex_unlock(&port->w_priv.w_lock);
+
+	mtk_port_ch_disable(port);
+	wwan_remove_port(w_port);
+}
+
+static int mtk_port_wwan_recv(struct mtk_port *port, struct sk_buff *skb)
+{
+	mutex_lock(&port->w_priv.w_lock);
+	if (!port->w_priv.w_port) {
+		mutex_unlock(&port->w_priv.w_lock);
+		return -ENXIO;
+	}
+
+	wwan_port_rx(port->w_priv.w_port, skb);
+	mutex_unlock(&port->w_priv.w_lock);
+	return 0;
+}
+
+static const struct port_ops port_wwan_ops = {
+	.init = mtk_port_wwan_init,
+	.exit = mtk_port_wwan_exit,
+	.reset = mtk_port_reset,
+	.enable = mtk_port_wwan_enable,
+	.disable = mtk_port_wwan_disable,
+	.recv = mtk_port_wwan_recv,
+};
+
+const struct port_ops *ports_ops[PORT_TYPE_MAX] = {
+	&port_internal_ops,
+	&port_wwan_ops,
+};
diff --git a/drivers/net/wwan/t9xx/mtk_port_io.h b/drivers/net/wwan/t9xx/mtk_port_io.h
new file mode 100644
index 00000000000000..ea92cd22dba0ef
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_port_io.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_PORT_IO_H__
+#define __MTK_PORT_IO_H__
+
+#include <linux/skbuff.h>
+
+#include "mtk_port.h"
+
+#define MTK_RX_BUF_SIZE			(1024 * 1024)
+
+extern struct mutex port_mngr_grp_mtx;
+
+struct port_ops {
+	int (*init)(struct mtk_port *port);
+	void (*exit)(struct mtk_port *port);
+	void (*reset)(struct mtk_port *port);
+	void (*enable)(struct mtk_port *port);
+	void (*disable)(struct mtk_port *port);
+	int (*recv)(struct mtk_port *port, struct sk_buff *skb);
+};
+
+union user_buf {
+	void __user *ubuf;
+	void *kbuf;
+};
+
+void *mtk_port_internal_open(struct mtk_md_dev *mdev, char *name, int flag);
+int mtk_port_internal_close(void *i_port);
+int mtk_port_internal_write(void *i_port, struct sk_buff *skb);
+void mtk_port_internal_recv_register(void *i_port,
+				     int (*cb)(void *priv, struct sk_buff *skb),
+				     void *arg);
+
+int mtk_port_io_init(void);
+void mtk_port_io_exit(void);
+
+#endif /* __MTK_PORT_IO_H__ */
diff --git a/drivers/net/wwan/t9xx/mtk_utility.h b/drivers/net/wwan/t9xx/mtk_utility.h
new file mode 100644
index 00000000000000..b72db3842d2ddb
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_utility.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_UTILITY_H__
+#define __MTK_UTILITY_H__
+
+#include <linux/device.h>
+#include "mtk_dev.h"
+
+#define MTK_UEVENT_INFO_LEN 128
+
+/* MTK uevent */
+enum mtk_uevent_id {
+	MTK_UEVENT_UNDEF = 0,
+	MTK_UEVENT_FSM = 1,
+	MTK_UEVENT_MINIDUMP = 2,
+	MTK_UEVENT_LOWPOWER = 3,
+	MTK_UEVENT_MAX
+};
+
+static inline void mtk_uevent_notify(struct device *dev, enum mtk_uevent_id id, const char *info)
+{
+	char buf[MTK_UEVENT_INFO_LEN];
+	char *ext[2] = {NULL, NULL};
+
+	snprintf(buf, MTK_UEVENT_INFO_LEN, "%s:event_id=%d, info=%s",
+		 dev->kobj.name, id, info);
+	ext[0] = buf;
+	kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, ext);
+}
+#endif /* __MTK_UTILITY_H__ */
diff --git a/drivers/net/wwan/t9xx/pcie/Makefile b/drivers/net/wwan/t9xx/pcie/Makefile
new file mode 100644
index 00000000000000..5252f158b0582c
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+ccflags-y += -I$(src)
+ccflags-y += -I$(src)/..
+
+obj-$(CONFIG_MTK_T9XX_PCI) += mtk_t9xx_pcie.o
+
+mtk_t9xx_pcie-y := \
+	mtk_pci_drv_m9xx.o \
+	mtk_cldma_drv_m9xx.o \
+	mtk_ctrl_cfg_m9xx.o \
+	mtk_pci.o \
+	mtk_trans_ctrl.o \
+	mtk_cldma.o \
+	mtk_cldma_drv.o
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma.c
new file mode 100644
index 00000000000000..3ac553b99255e0
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma.c
@@ -0,0 +1,1418 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include "mtk_pci.h"
+#include "mtk_cldma.h"
+#include "mtk_cldma_drv.h"
+#include "mtk_dev.h"
+
+#define cldma_drv_ops_null	NULL
+#define DMA_POOL_NAME_LEN	(64)
+#define WAIT_HWO_ROUND		(10)
+#define WAIT_HWO_TIME		(5)
+#define CLDMA_RETRY_DELAY_MS	(100)
+#define NO_BUDGET		(0)
+
+static struct cldma_drv_info_desc cldma_drv_info_tbl[] = {
+	{0x01CA, &drv_ops_name(m9xx), &cldma_regs_name(m9xx)},
+	{0, NULL},
+};
+
+static void mtk_cldma_get_drv_info(struct cldma_drv_info *drv_info, u32 hw_ver)
+{
+	struct cldma_drv_info_desc *p_drv_info;
+	u8 i;
+
+	for (i = 0; (p_drv_info = &cldma_drv_info_tbl[i]) && p_drv_info &&
+	     p_drv_info->drv_ops && p_drv_info->hw_regs; i++)
+		if (p_drv_info->hw_ver == hw_ver) {
+			drv_info->drv_ops = p_drv_info->drv_ops;
+			drv_info->hw_regs = p_drv_info->hw_regs;
+		}
+}
+
+static int mtk_cldma_isr(int irq_id, void *param)
+{
+	struct cldma_drv_info *drv_info = param;
+	struct mtk_md_dev *mdev;
+	u32 tx_done, rx_done;
+	u32 tx_sta, rx_sta;
+	struct txq *txq;
+	struct rxq *rxq;
+	int i;
+
+	mdev = drv_info->mdev;
+	drv_info->drv_ops->cldma_get_intr_status(drv_info, &tx_sta, &rx_sta);
+	tx_done = (tx_sta >> QUEUE_XFER_DONE) & 0xFF;
+	rx_done = (rx_sta >> QUEUE_XFER_DONE) & 0xFF;
+
+	if (tx_done) {
+		for (i = 0; i < HW_QUEUE_NUM; i++) {
+			txq = drv_info->txq[i];
+			if (!(tx_done & BIT(i)) || !txq)
+				continue;
+			queue_work(drv_info->wq, &txq->tx_done_work);
+		}
+	}
+	if (rx_done) {
+		for (i = 0; i < HW_QUEUE_NUM; i++) {
+			rxq = drv_info->rxq[i];
+			if (!(rx_done & BIT(i)) || !rxq)
+				continue;
+			queue_work(drv_info->wq, &rxq->rx_done_work);
+		}
+	}
+
+	mtk_pci_clear_irq(mdev, drv_info->pci_ext_irq_id);
+	mtk_pci_unmask_irq(mdev, drv_info->pci_ext_irq_id);
+
+	return IRQ_HANDLED;
+}
+
+static const int mtk_cldma_hw_id_tbl[NR_CLDMA] = {
+	[CLDMA0] = CLDMA0_HW_ID,
+	[CLDMA1] = CLDMA1_HW_ID,
+};
+
+static int mtk_cldma_dev_init(struct cldma_dev *cd, int hif_id)
+{
+	char gpd_pool_name[DMA_POOL_NAME_LEN];
+	char bd_pool_name[DMA_POOL_NAME_LEN];
+	struct cldma_drv_info *drv_info;
+	struct cldma_hw_regs *hw_regs;
+	struct mtk_md_dev *mdev;
+	unsigned int flag;
+	int hw_id, ret;
+
+	if (!cd || hif_id >= NR_CLDMA)
+		return -EINVAL;
+
+	if (cd->cldma_drv_info[hif_id])
+		return 0;
+
+	hw_id = mtk_cldma_hw_id_tbl[hif_id];
+	mdev = cd->trans->mdev;
+	drv_info = kzalloc_obj(*drv_info);
+	if (!drv_info)
+		return -ENOMEM;
+
+	drv_info->cd = cd;
+	drv_info->mdev = mdev;
+	drv_info->hif_id = hif_id;
+	drv_info->hw_id = hw_id;
+	mtk_cldma_get_drv_info(drv_info, mdev->hw_ver);
+
+	if (!drv_info->drv_ops || !drv_info->hw_regs) {
+		dev_err(mdev->dev, "Failed to find CLDMA Driver for PCI %x\n", mdev->hw_ver);
+		ret = -EIO;
+		goto err_free_drv_info;
+	}
+
+	hw_regs = drv_info->hw_regs;
+	snprintf(gpd_pool_name, DMA_POOL_NAME_LEN, "cldma%d_gpd_pool_%s",
+		 hw_id, mdev->dev_str);
+	snprintf(bd_pool_name, DMA_POOL_NAME_LEN, "cldma%d_bd_pool_%s",
+		 hw_id, mdev->dev_str);
+	drv_info->gpd_dma_pool = dma_pool_create(gpd_pool_name, mdev->dev,
+						 sizeof(union gpd), 4, 0);
+	if (!drv_info->gpd_dma_pool) {
+		dev_err(mdev->dev, "Failed to alloc gpd dma pool for cldma%d\n", hw_id);
+		ret = -ENOMEM;
+		goto err_free_drv_info;
+	}
+	drv_info->bd_dma_pool = dma_pool_create(bd_pool_name, mdev->dev,
+						sizeof(union bd), 4, 0);
+	if (!drv_info->bd_dma_pool) {
+		dev_err(mdev->dev, "Failed to alloc bd dma pool for cldma%d\n", hw_id);
+		ret = -ENOMEM;
+		goto err_destroy_gpd_pool;
+	}
+
+	switch (hif_id) {
+	case CLDMA0:
+		drv_info->pci_ext_irq_id = mtk_pci_get_irq_id(mdev, MTK_IRQ_SRC_CLDMA0);
+		drv_info->base_addr = hw_regs->cldma0_base_addr;
+		break;
+	case CLDMA1:
+		drv_info->pci_ext_irq_id = mtk_pci_get_irq_id(mdev, MTK_IRQ_SRC_CLDMA1);
+		drv_info->base_addr = hw_regs->cldma1_base_addr;
+		break;
+	default:
+		ret = -EINVAL;
+		goto err_destroy_dma_pool;
+	}
+
+	flag = WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI;
+	drv_info->wq = alloc_workqueue("cldma%d_workq_%s", flag, 0, hw_id, mdev->dev_str);
+	if (!drv_info->wq) {
+		dev_err(mdev->dev, "Failed to alloc work queue for cldma%d\n", hw_id);
+		ret = -ENOMEM;
+		goto err_destroy_dma_pool;
+	}
+
+	drv_info->drv_ops->cldma_drv_init(drv_info);
+
+	/* mask/clear PCI CLDMA L1 interrupt */
+	mtk_pci_mask_irq(mdev, drv_info->pci_ext_irq_id);
+	mtk_pci_clear_irq(mdev, drv_info->pci_ext_irq_id);
+
+	/* register CLDMA interrupt handler */
+	ret = mtk_pci_register_irq(mdev, drv_info->pci_ext_irq_id, mtk_cldma_isr, drv_info);
+	if (ret)
+		goto err_destroy_wq;
+
+	/* unmask PCI CLDMA L1 interrupt */
+	mtk_pci_unmask_irq(mdev, drv_info->pci_ext_irq_id);
+
+	cd->cldma_drv_info[hif_id] = drv_info;
+	return 0;
+
+err_destroy_wq:
+	destroy_workqueue(drv_info->wq);
+err_destroy_dma_pool:
+	dma_pool_destroy(drv_info->bd_dma_pool);
+err_destroy_gpd_pool:
+	dma_pool_destroy(drv_info->gpd_dma_pool);
+err_free_drv_info:
+	kfree(drv_info);
+
+	return ret;
+}
+
+static void mtk_cldma_clr_bd_dsc(struct cldma_drv_info *drv_info,
+				 struct bd_dsc *bd_dsc_pool, int nr_bds)
+{
+	struct bd_dsc *bd_dsc;
+	int i;
+
+	for (i = 0; i < nr_bds; i++) {
+		bd_dsc = bd_dsc_pool + i;
+		dma_unmap_single(drv_info->mdev->dev, bd_dsc->data_dma_addr,
+				 bd_dsc->data_len, DMA_TO_DEVICE);
+		bd_dsc->data_dma_addr = 0;
+		bd_dsc->data_len = 0;
+		if (bd_dsc->bd->tx_bd.bd_flags & CLDMA_BD_FLAG_EOL) {
+			bd_dsc->bd->tx_bd.bd_flags &= ~CLDMA_BD_FLAG_EOL;
+			break;
+		}
+	}
+}
+
+static void mtk_cldma_tx_done_work(struct work_struct *work)
+{
+	struct txq *txq = container_of(work, struct txq, tx_done_work);
+	struct cldma_drv_info *drv_info;
+	struct cldma_drv_ops *drv_ops;
+	struct mtk_ctrl_trans *trans;
+	struct mtk_md_dev *mdev;
+	struct tx_req *req;
+	unsigned int state;
+	struct trb *trb;
+	int i, hif_id;
+	u32 txqno;
+
+	drv_info = txq->drv_info;
+	hif_id = drv_info->hif_id;
+	txqno = txq->txqno;
+	mdev = drv_info->mdev;
+	drv_ops = drv_info->drv_ops;
+	trans = drv_info->cd->trans;
+
+again:
+	for (i = 0; i < txq->nr_gpds; i++) {
+		req = txq->req_pool + txq->free_idx;
+
+		rmb(); /* ensure HWO setup done before HWO read */
+
+		if (!req->data_vm_addr || (req->gpd->tx_gpd.gpd_flags & CLDMA_GPD_FLAG_HWO))
+			break;
+
+		if (txq->nr_bds)
+			mtk_cldma_clr_bd_dsc(drv_info, req->bd_dsc_pool, txq->nr_bds);
+		else
+			dma_unmap_single(mdev->dev, req->data_dma_addr,
+					 req->data_len, DMA_TO_DEVICE);
+
+		trb = (struct trb *)req->skb->cb;
+		trb->status = 0;
+		trb->trb_complete(req->skb);
+
+		req->data_vm_addr = NULL;
+		req->data_dma_addr = 0;
+		req->data_len = 0;
+		req->skb = NULL;
+
+		txq->free_idx = (txq->free_idx + 1) % txq->nr_gpds;
+		if (atomic_fetch_inc(&txq->req_budget) == NO_BUDGET)
+			wake_up(&trans->trb_srv[trans->srv_cfg[hif_id][txqno]]->trb_waitq);
+	}
+
+	state = drv_ops->cldma_check_intr_status(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);
+	if (state) {
+		if (unlikely(state == LINK_ERROR_VAL))
+			goto out;
+
+		drv_ops->cldma_clr_intr_status(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);
+
+		cond_resched();
+
+		goto again;
+	}
+
+out:
+	drv_ops->cldma_unmask_intr(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);
+}
+
+static void mtk_cldma_rx_skb_adjust(struct mtk_md_dev *mdev, struct rxq *rxq,
+				    struct rx_req *req)
+{
+	struct bd_dsc *bd_dsc;
+	int i;
+
+	for (i = 0; i < rxq->nr_bds; i++) {
+		bd_dsc = req->bd_dsc_pool + i;
+		if (bd_dsc->data_dma_addr) {
+			dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
+					 req->frag_size, DMA_FROM_DEVICE);
+			bd_dsc->data_dma_addr = 0;
+		}
+		bd_dsc->skb->len = 0;
+		skb_reset_tail_pointer(bd_dsc->skb);
+		skb_put(bd_dsc->skb,
+			min_t(u16, le16_to_cpu(bd_dsc->bd->rx_bd.data_recv_len),
+			      req->frag_size));
+		if (req->skb != bd_dsc->skb) {
+			req->skb->len += bd_dsc->skb->len;
+			req->skb->data_len += bd_dsc->skb->len;
+		}
+		bd_dsc->bd->rx_bd.data_recv_len = 0;
+		bd_dsc->skb = NULL;
+	}
+	if (!rxq->nr_bds) {
+		if (req->data_dma_addr) {
+			dma_unmap_single(mdev->dev, req->data_dma_addr,
+					 req->mtu, DMA_FROM_DEVICE);
+			req->data_dma_addr = 0;
+		}
+		req->skb->len = 0;
+		skb_reset_tail_pointer(req->skb);
+		skb_put(req->skb,
+			min_t(u16, le16_to_cpu(req->gpd->rx_gpd.data_recv_len),
+			      req->mtu));
+	}
+
+	req->gpd->rx_gpd.data_recv_len = 0;
+}
+
+static int mtk_cldma_reload_rx_skb(struct mtk_md_dev *mdev, struct rxq *rxq,
+				   struct rx_req *req)
+{
+	struct sk_buff *tail = NULL;
+	struct bd_dsc *bd_dsc;
+	int nr_bds;
+	int i, ret;
+
+	nr_bds = rxq->nr_bds;
+
+	for (i = 0; i < nr_bds; i++) {
+		bd_dsc = req->bd_dsc_pool + i;
+		bd_dsc->skb = __dev_alloc_skb(req->frag_size, GFP_KERNEL);
+		if (!bd_dsc->skb) {
+			dev_warn_ratelimited(mdev->dev, "Failed to alloc SKB\n");
+			ret = -ENOMEM;
+			goto err_free_skb;
+		}
+		bd_dsc->skb->next = NULL;
+		bd_dsc->data_dma_addr = dma_map_single(mdev->dev, bd_dsc->skb->data,
+						       req->frag_size, DMA_FROM_DEVICE);
+		ret = dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr);
+		if (unlikely(ret)) {
+			dev_warn_ratelimited(mdev->dev, "Failed to map SKB data\n");
+			ret = -EFAULT;
+			goto err_free_skb;
+		}
+		bd_dsc->bd->rx_bd.data_buff_ptr_h =
+			cpu_to_le32((u64)(bd_dsc->data_dma_addr) >> 32);
+		bd_dsc->bd->rx_bd.data_buff_ptr_l =
+			cpu_to_le32(bd_dsc->data_dma_addr);
+		if (tail) {
+			tail->next = bd_dsc->skb;
+			tail = bd_dsc->skb;
+			continue;
+		}
+		if (!req->skb) {
+			req->skb = bd_dsc->skb;
+		} else {
+			skb_shinfo(req->skb)->frag_list = bd_dsc->skb;
+			tail = bd_dsc->skb;
+		}
+	}
+	if (!nr_bds) {
+		req->skb = __dev_alloc_skb(req->mtu, GFP_KERNEL);
+		if (!req->skb) {
+			ret = -ENOMEM;
+			goto err_free_skb;
+		}
+
+		req->data_dma_addr = dma_map_single(mdev->dev, req->skb->data,
+						    req->mtu, DMA_FROM_DEVICE);
+		ret = dma_mapping_error(mdev->dev, req->data_dma_addr);
+		if (unlikely(ret)) {
+			dev_warn_ratelimited(mdev->dev, "Failed to map SKB data\n");
+			ret = -EFAULT;
+			goto err_free_skb;
+		}
+		req->gpd->rx_gpd.data_buff_ptr_h = cpu_to_le32((u64)req->data_dma_addr >> 32);
+		req->gpd->rx_gpd.data_buff_ptr_l = cpu_to_le32(req->data_dma_addr);
+	}
+	return 0;
+
+err_free_skb:
+	if (nr_bds) {
+		if (req->skb)
+			skb_shinfo(req->skb)->frag_list = NULL;
+		for (i = 0; i < nr_bds; i++) {
+			bd_dsc = req->bd_dsc_pool + i;
+			if (!bd_dsc->skb)
+				break;
+			if (!dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr))
+				dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
+						 req->frag_size, DMA_FROM_DEVICE);
+			bd_dsc->data_dma_addr = 0;
+			bd_dsc->skb->next = NULL;
+			dev_kfree_skb_any(bd_dsc->skb);
+		}
+	} else {
+		req->data_dma_addr = 0;
+		if (req->skb)
+			dev_kfree_skb_any(req->skb);
+	}
+	req->skb = NULL;
+
+	return ret;
+}
+
+static int mtk_cldma_check_rx_req(struct cldma_drv_info *drv_info, struct rxq *rxq)
+{
+	struct rx_req *req = rxq->req_pool + rxq->free_idx;
+	u64 curr_addr;
+	int i;
+
+	curr_addr = drv_info->drv_ops->cldma_get_rx_curr_addr(drv_info, rxq->rxqno);
+	if (unlikely(!curr_addr))
+		return -ENXIO;
+
+	if (req->gpd_dma_addr == curr_addr)
+		return -EAGAIN;
+	for (i = 0; i < WAIT_HWO_ROUND; i++) {
+		udelay(WAIT_HWO_TIME);
+		if (!(READ_ONCE(req->gpd->rx_gpd.gpd_flags) & CLDMA_GPD_FLAG_HWO))
+			break;
+	}
+	if (i == WAIT_HWO_ROUND) {
+		dev_err((drv_info->mdev)->dev, "Failed to check HWO=0\n");
+		return -EAGAIN;
+	}
+
+	return 0;
+}
+
+static bool mtk_cldma_rx_check_again(struct rxq *rxq)
+{
+	struct cldma_drv_info *drv_info;
+	struct cldma_drv_ops *drv_ops;
+	bool need_check_again = false;
+	u32 state;
+	int rxqno;
+
+	drv_info = rxq->drv_info;
+	drv_ops = drv_info->drv_ops;
+	rxqno = rxq->rxqno;
+
+	do {
+		state = drv_ops->cldma_check_intr_status(drv_info, DIR_RX,
+							 rxqno, QUEUE_XFER_DONE);
+		if (state) {
+			if (unlikely(state == LINK_ERROR_VAL))
+				break;
+
+			drv_ops->cldma_clr_intr_status(drv_info, DIR_RX,
+						       rxqno, QUEUE_XFER_DONE);
+			cond_resched();
+			return true;
+		}
+	} while (need_check_again);
+
+	return false;
+}
+
+static void mtk_cldma_rx_done_work(struct work_struct *work)
+{
+	struct rx_req *req = NULL, *pre_req = NULL;
+	struct rxq *rxq = container_of(work, struct rxq, rx_done_work);
+	struct cldma_drv_info *drv_info;
+	struct cldma_drv_ops *drv_ops;
+	struct mtk_md_dev *mdev;
+	int i, ret, idx;
+
+	drv_info = rxq->drv_info;
+	mdev = drv_info->mdev;
+	drv_ops = drv_info->drv_ops;
+
+again:
+	for (i = 0; i < rxq->nr_gpds; i++) {
+		req = rxq->req_pool + rxq->free_idx;
+		if (!req->skb) {
+			dev_err(mdev->dev,
+				"Failed to get valid req cldma%d rxq%d req%d\n",
+				drv_info->hw_id, rxq->rxqno, rxq->free_idx);
+			goto out;
+		}
+
+		if (req->gpd->rx_gpd.gpd_flags & CLDMA_GPD_FLAG_HWO)
+			break;
+
+		mtk_cldma_rx_skb_adjust(mdev, rxq, req);
+		do {
+			ret = rxq->rx_done(req->skb, rxq->arg,
+					   atomic_read(&rxq->need_exit) ? true : false);
+			if (ret == -EAGAIN)
+				usleep_range(1000, 2000);
+			else
+				req->skb = NULL;
+		} while (ret == -EAGAIN);
+
+		ret = mtk_cldma_reload_rx_skb(mdev, rxq, req);
+		if (ret)
+			goto out;
+
+		wmb(); /* ensure addr set done before HWO setup done  */
+
+		idx = rxq->free_idx == 0 ? rxq->nr_gpds - 1 : rxq->free_idx - 1;
+		pre_req = rxq->req_pool + idx;
+		pre_req->gpd->rx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;
+		rxq->free_idx = (rxq->free_idx + 1) % rxq->nr_gpds;
+	}
+
+	ret = mtk_cldma_check_rx_req(drv_info, rxq);
+	if (!ret)
+		goto again;
+	else if (ret == -ENXIO)
+		goto out;
+
+	if (!atomic_read(&rxq->need_exit))
+		drv_ops->cldma_resume_queue(drv_info, DIR_RX, rxq->rxqno);
+
+	if (mtk_cldma_rx_check_again(rxq))
+		goto again;
+
+out:
+	drv_ops->cldma_unmask_intr(drv_info, DIR_RX, rxq->rxqno, QUEUE_XFER_DONE);
+	drv_ops->cldma_clear_ip_busy(drv_info);
+}
+
+static int mtk_cldma_alloc_tx_bd(struct cldma_drv_info *drv_info, struct txq *txq,
+				 struct tx_req *req)
+{
+	struct bd_dsc *bd_dsc, *last_bd_dsc = NULL;
+	int i;
+
+	req->bd_dsc_pool = devm_kcalloc(drv_info->mdev->dev, txq->nr_bds,
+					sizeof(*bd_dsc), GFP_KERNEL);
+	if (!req->bd_dsc_pool)
+		return -ENOMEM;
+
+	for (i = 0; i < txq->nr_bds; i++) {
+		bd_dsc = req->bd_dsc_pool + i;
+		bd_dsc->bd = dma_pool_zalloc(drv_info->bd_dma_pool, GFP_KERNEL,
+					     &bd_dsc->bd_dma_addr);
+		if (!bd_dsc->bd)
+			return -ENOMEM;
+		if (!last_bd_dsc) {
+			req->gpd->tx_gpd.data_buff_ptr_h =
+				cpu_to_le32((u64)(bd_dsc->bd_dma_addr) >> 32);
+			req->gpd->tx_gpd.data_buff_ptr_l =
+				cpu_to_le32(bd_dsc->bd_dma_addr);
+		} else {
+			last_bd_dsc->bd->tx_bd.next_bd_ptr_h =
+				cpu_to_le32((u64)(bd_dsc->bd_dma_addr) >> 32);
+			last_bd_dsc->bd->tx_bd.next_bd_ptr_l =
+				cpu_to_le32(bd_dsc->bd_dma_addr);
+		}
+		last_bd_dsc = bd_dsc;
+	}
+	return 0;
+}
+
+static struct txq *mtk_cldma_txq_alloc(struct cldma_drv_info *drv_info, struct sk_buff *skb)
+{
+	struct trb *trb = (struct trb *)skb->cb;
+	struct cldma_drv_ops *drv_ops;
+	struct mtk_ctrl_trans *trans;
+	struct mtk_ctrl_blk *ctrl_blk;
+	struct mtk_md_dev *mdev;
+	struct bd_dsc *bd_dsc;
+	struct tx_req *next;
+	struct tx_req *req;
+	u16 tx_frag_size;
+	struct txq *txq;
+	int i, j, ret;
+
+	mdev = drv_info->mdev;
+	ctrl_blk = mdev->ctrl_blk;
+	trans = ctrl_blk->ctrl_hw_priv;
+	drv_ops = drv_info->drv_ops;
+
+	txq = kzalloc_obj(*txq);
+	if (!txq)
+		return NULL;
+
+	txq->que = radix_tree_lookup(&trans->queue_tbl, trb->channel_id & 0xFFFF);
+	txq->drv_info = drv_info;
+	txq->txqno = txq->que->txqno;
+	txq->nr_gpds = txq->que->tx_nr_gpds;
+	atomic_set(&txq->req_budget, txq->que->tx_nr_gpds);
+	txq->is_stopping = false;
+	tx_frag_size = txq->que->tx_frag_size;
+	if (txq->que->tx_mtu > tx_frag_size && tx_frag_size)
+		txq->nr_bds = (txq->que->tx_mtu + tx_frag_size - 1) / tx_frag_size;
+
+	txq->req_pool = devm_kcalloc(mdev->dev, txq->nr_gpds, sizeof(*req), GFP_KERNEL);
+	if (!txq->req_pool)
+		goto err_free_txq;
+
+	for (i = 0; i < txq->nr_gpds; i++) {
+		req = txq->req_pool + i;
+		req->mtu = txq->que->tx_mtu;
+		req->frag_size = tx_frag_size;
+		req->gpd = dma_pool_zalloc(drv_info->gpd_dma_pool, GFP_KERNEL, &req->gpd_dma_addr);
+		if (!req->gpd)
+			goto err_free_req;
+		if (txq->nr_bds) {
+			ret = mtk_cldma_alloc_tx_bd(drv_info, txq, req);
+			if (ret)
+				goto err_free_req;
+			req->gpd->tx_gpd.gpd_flags |= CLDMA_GPD_FLAG_BDP;
+		}
+	}
+
+	for (i = 0; i < txq->nr_gpds; i++) {
+		req = txq->req_pool + i;
+		next = txq->req_pool + ((i + 1) % txq->nr_gpds);
+		req->gpd->tx_gpd.gpd_flags |= CLDMA_GPD_FLAG_IOC;
+		req->gpd->tx_gpd.next_gpd_ptr_h = cpu_to_le32((u64)(next->gpd_dma_addr) >> 32);
+		req->gpd->tx_gpd.next_gpd_ptr_l = cpu_to_le32(next->gpd_dma_addr);
+	}
+
+	INIT_WORK(&txq->tx_done_work, mtk_cldma_tx_done_work);
+
+	drv_ops->cldma_stop_queue(drv_info, DIR_TX, txq->txqno);
+	txq->tx_started = false;
+	drv_ops->cldma_setup_start_addr(drv_info, DIR_TX, txq->txqno,
+					txq->req_pool[0].gpd_dma_addr);
+	drv_ops->cldma_unmask_intr(drv_info, DIR_TX, txq->txqno, QUEUE_ERROR);
+	drv_ops->cldma_unmask_intr(drv_info, DIR_TX, txq->txqno, QUEUE_XFER_DONE);
+
+	drv_info->txq[txq->txqno] = txq;
+	return txq;
+
+err_free_req:
+	for (i = 0; i < txq->nr_gpds; i++) {
+		req = txq->req_pool + i;
+		if (!req->gpd)
+			break;
+		if (req->bd_dsc_pool) {
+			for (j = 0; j < txq->nr_bds; j++) {
+				bd_dsc = req->bd_dsc_pool + j;
+				if (!bd_dsc->bd)
+					break;
+				dma_pool_free(drv_info->bd_dma_pool, bd_dsc->bd,
+					      bd_dsc->bd_dma_addr);
+			}
+			kfree(req->bd_dsc_pool);
+		}
+		dma_pool_free(drv_info->gpd_dma_pool, req->gpd, req->gpd_dma_addr);
+	}
+	kfree(txq->req_pool);
+err_free_txq:
+	kfree(txq);
+	return NULL;
+}
+
+static void mtk_cldma_txq_free(struct cldma_drv_info *drv_info, u32 txqno)
+{
+	struct cldma_drv_ops *drv_ops;
+	struct mtk_md_dev *mdev;
+	struct bd_dsc *bd_dsc;
+	struct tx_req *req;
+	struct txq *txq;
+	struct trb *trb;
+	int irq_id;
+	int i, j;
+
+	mdev = drv_info->mdev;
+	drv_ops = drv_info->drv_ops;
+
+	txq = drv_info->txq[txqno];
+	drv_info->txq[txqno] = NULL;
+	/* stop HW tx transaction */
+	drv_ops->cldma_stop_queue(drv_info, DIR_TX, txqno);
+	txq->tx_started = false;
+
+	irq_id = mtk_pci_get_virq_id(mdev, drv_info->pci_ext_irq_id);
+	synchronize_irq(irq_id);
+	/* flush on-going work */
+	flush_work(&txq->tx_done_work);
+	drv_ops->cldma_mask_intr(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);
+	drv_ops->cldma_mask_intr(drv_info, DIR_TX, txqno, QUEUE_ERROR);
+
+	/* free tx req resource */
+	for (i = 0; i < txq->nr_gpds; i++) {
+		req = txq->req_pool + txq->free_idx;
+		if (req->skb && req->data_len) {
+			if (!txq->nr_bds)
+				dma_unmap_single(mdev->dev, req->data_dma_addr,
+						 req->data_len, DMA_TO_DEVICE);
+			for (j = 0; j < txq->nr_bds; j++) {
+				bd_dsc = req->bd_dsc_pool + j;
+				if (!bd_dsc->data_dma_addr)
+					continue;
+				dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
+						 bd_dsc->data_len, DMA_TO_DEVICE);
+			}
+			trb = (struct trb *)req->skb->cb;
+			trb->status = -EPIPE;
+			trb->trb_complete(req->skb);
+		}
+		for (j = 0; j < txq->nr_bds; j++) {
+			bd_dsc = req->bd_dsc_pool + j;
+			dma_pool_free(drv_info->bd_dma_pool, bd_dsc->bd,
+				      bd_dsc->bd_dma_addr);
+		}
+		kfree(req->bd_dsc_pool);
+		dma_pool_free(drv_info->gpd_dma_pool, req->gpd, req->gpd_dma_addr);
+		txq->free_idx = (txq->free_idx + 1) % txq->nr_gpds;
+	}
+
+	kfree(txq->req_pool);
+	kfree(txq);
+}
+
+static int mtk_cldma_alloc_rx_bd(struct cldma_drv_info *drv_info, struct rx_req *req,
+				 int nr_bds)
+{
+	struct bd_dsc *bd_dsc, *last_bd_dsc = NULL;
+	struct sk_buff *tail = NULL;
+	struct mtk_md_dev *mdev;
+	u32 left_size;
+	int ret;
+	int i;
+
+	mdev = drv_info->mdev;
+	left_size = req->mtu;
+
+	req->bd_dsc_pool = devm_kcalloc(mdev->dev, nr_bds,
+					sizeof(*bd_dsc), GFP_KERNEL);
+	if (!req->bd_dsc_pool)
+		return -ENOMEM;
+	for (i = 0; i < nr_bds; i++) {
+		bd_dsc = req->bd_dsc_pool + i;
+		bd_dsc->bd = dma_pool_zalloc(drv_info->bd_dma_pool, GFP_KERNEL,
+					     &bd_dsc->bd_dma_addr);
+		if (!bd_dsc->bd)
+			return -ENOMEM;
+
+		bd_dsc->skb = __dev_alloc_skb(req->frag_size, GFP_KERNEL);
+		if (!bd_dsc->skb)
+			return -ENOMEM;
+		bd_dsc->skb->next = NULL;
+		bd_dsc->data_dma_addr =
+			dma_map_single(mdev->dev, bd_dsc->skb->data,
+				       req->frag_size, DMA_FROM_DEVICE);
+		ret = dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr);
+		if (unlikely(ret))
+			return -ENOMEM;
+
+		bd_dsc->bd->rx_bd.data_buff_ptr_h =
+			cpu_to_le32((u64)(bd_dsc->data_dma_addr) >> 32);
+		bd_dsc->bd->rx_bd.data_buff_ptr_l =
+			cpu_to_le32(bd_dsc->data_dma_addr);
+		bd_dsc->bd->rx_bd.data_allow_len =
+			cpu_to_le16(min(req->frag_size, left_size));
+		left_size -= min(req->frag_size, left_size);
+		if (!last_bd_dsc) {
+			req->gpd->rx_gpd.data_buff_ptr_h =
+				cpu_to_le32((u64)(bd_dsc->bd_dma_addr) >> 32);
+			req->gpd->rx_gpd.data_buff_ptr_l =
+				cpu_to_le32(bd_dsc->bd_dma_addr);
+		} else {
+			last_bd_dsc->bd->rx_bd.next_bd_ptr_h =
+				cpu_to_le32((u64)(bd_dsc->bd_dma_addr) >> 32);
+			last_bd_dsc->bd->rx_bd.next_bd_ptr_l =
+				cpu_to_le32(bd_dsc->bd_dma_addr);
+		}
+		last_bd_dsc = bd_dsc;
+		if (tail) {
+			tail->next = bd_dsc->skb;
+			tail = bd_dsc->skb;
+			continue;
+		}
+		if (!req->skb) {
+			req->skb = bd_dsc->skb;
+		} else {
+			skb_shinfo(req->skb)->frag_list = bd_dsc->skb;
+			tail = bd_dsc->skb;
+		}
+	}
+	last_bd_dsc->bd->rx_bd.bd_flags |= CLDMA_BD_FLAG_EOL;
+	return 0;
+}
+
+static void mtk_cldma_rxq_alloc_cancel(struct cldma_drv_info *drv_info, struct rx_req *req,
+				       int nr_bds)
+{
+	struct mtk_md_dev *mdev;
+	struct bd_dsc *bd_dsc;
+	int i;
+
+	mdev = drv_info->mdev;
+
+	if (nr_bds) {
+		if (req->skb)
+			skb_shinfo(req->skb)->frag_list = NULL;
+		if (req->bd_dsc_pool) {
+			for (i = 0; i < nr_bds; i++) {
+				bd_dsc = req->bd_dsc_pool + i;
+				if (!bd_dsc->bd)
+					break;
+				if (bd_dsc->skb) {
+					if (!dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr))
+						dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
+								 req->frag_size, DMA_FROM_DEVICE);
+					bd_dsc->data_dma_addr = 0;
+					bd_dsc->skb->next = NULL;
+					dev_kfree_skb_any(bd_dsc->skb);
+				}
+				dma_pool_free(drv_info->bd_dma_pool, bd_dsc->bd,
+					      bd_dsc->bd_dma_addr);
+			}
+			kfree(req->bd_dsc_pool);
+		}
+	} else {
+		if (req->skb) {
+			if (!dma_mapping_error(mdev->dev, req->data_dma_addr))
+				dma_unmap_single(mdev->dev, req->data_dma_addr,
+						 req->mtu, DMA_FROM_DEVICE);
+			req->data_dma_addr = 0;
+			dev_kfree_skb_any(req->skb);
+		}
+	}
+	dma_pool_free(drv_info->gpd_dma_pool, req->gpd, req->gpd_dma_addr);
+}
+
+static struct rxq *mtk_cldma_rxq_alloc(struct cldma_drv_info *drv_info, struct sk_buff *skb)
+{
+	struct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb->data;
+	struct trb *trb = (struct trb *)skb->cb;
+	struct cldma_drv_ops *drv_ops;
+	struct mtk_ctrl_trans *trans;
+	struct mtk_ctrl_blk *ctrl_blk;
+	struct mtk_md_dev *mdev;
+	struct rx_req *next;
+	struct rx_req *req;
+	u16 rx_frag_size;
+	struct rxq *rxq;
+	int ret;
+	int i;
+
+	mdev = drv_info->mdev;
+	ctrl_blk = mdev->ctrl_blk;
+	trans = ctrl_blk->ctrl_hw_priv;
+	drv_ops = drv_info->drv_ops;
+
+	rxq = kzalloc_obj(*rxq);
+	if (!rxq)
+		return NULL;
+
+	rxq->que = radix_tree_lookup(&trans->queue_tbl, trb->channel_id & 0xFFFF);
+	if (rxq->que->rx_nr_gpds < MIN_GPD_NUM) {
+		dev_err(mdev->dev,
+			"Failed to alloc cldma%d rxq%d due to gpd number < 2\n",
+			drv_info->hw_id, rxq->rxqno);
+		goto err_free_rxq;
+	}
+	rxq->drv_info = drv_info;
+	rxq->rxqno = rxq->que->rxqno;
+	rxq->nr_gpds = rxq->que->rx_nr_gpds;
+	rxq->arg = trb->priv;
+	rxq->rx_done = trb_open_priv->rx_done;
+	atomic_set(&rxq->need_exit, 0);
+	rx_frag_size = rxq->que->rx_frag_size;
+	if (rxq->que->rx_mtu > rx_frag_size && rx_frag_size)
+		rxq->nr_bds = (rxq->que->rx_mtu + rx_frag_size - 1) / rx_frag_size;
+
+	rxq->req_pool = devm_kcalloc(mdev->dev, rxq->nr_gpds, sizeof(*req), GFP_KERNEL);
+	if (!rxq->req_pool)
+		goto err_free_rxq;
+
+	/* setup rx request */
+	for (i = 0; i < rxq->nr_gpds; i++) {
+		req = rxq->req_pool + i;
+		req->mtu = rxq->que->rx_mtu;
+		req->frag_size = rx_frag_size;
+		req->gpd = dma_pool_zalloc(drv_info->gpd_dma_pool, GFP_KERNEL, &req->gpd_dma_addr);
+		if (!req->gpd)
+			goto err_free_req;
+		if (rxq->nr_bds) {
+			ret = mtk_cldma_alloc_rx_bd(drv_info, req, rxq->nr_bds);
+			if (ret)
+				goto err_free_req;
+			req->gpd->rx_gpd.gpd_flags |= CLDMA_GPD_FLAG_BDP;
+		} else {
+			req->skb = __dev_alloc_skb(req->mtu, GFP_KERNEL);
+			if (!req->skb)
+				goto err_free_req;
+			req->data_dma_addr = dma_map_single(mdev->dev, req->skb->data,
+							    req->mtu, DMA_FROM_DEVICE);
+			ret = dma_mapping_error(mdev->dev, req->data_dma_addr);
+			if (unlikely(ret))
+				goto err_free_req;
+		}
+	}
+
+	for (i = 0; i < rxq->nr_gpds; i++) {
+		req = rxq->req_pool + i;
+		next = rxq->req_pool + ((i + 1) % rxq->nr_gpds);
+		req->gpd->rx_gpd.gpd_flags |= CLDMA_GPD_FLAG_IOC;
+		req->gpd->rx_gpd.data_allow_len = cpu_to_le16(req->mtu);
+		req->gpd->rx_gpd.next_gpd_ptr_h = cpu_to_le32((u64)(next->gpd_dma_addr) >> 32);
+		req->gpd->rx_gpd.next_gpd_ptr_l = cpu_to_le32(next->gpd_dma_addr);
+		if (!rxq->nr_bds) {
+			req->gpd->rx_gpd.data_buff_ptr_h =
+				cpu_to_le32((u64)(req->data_dma_addr) >> 32);
+			req->gpd->rx_gpd.data_buff_ptr_l = cpu_to_le32(req->data_dma_addr);
+		}
+		if (i != rxq->nr_gpds - 1)
+			req->gpd->rx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;
+	}
+
+	INIT_WORK(&rxq->rx_done_work, mtk_cldma_rx_done_work);
+
+	drv_info->rxq[rxq->rxqno] = rxq;
+	drv_ops->cldma_stop_queue(drv_info, DIR_RX, rxq->rxqno);
+	drv_ops->cldma_setup_start_addr(drv_info, DIR_RX,
+					rxq->rxqno, rxq->req_pool[0].gpd_dma_addr);
+	drv_ops->cldma_start_queue(drv_info, DIR_RX, rxq->rxqno);
+	drv_ops->cldma_unmask_intr(drv_info, DIR_RX, rxq->rxqno, QUEUE_ERROR);
+	drv_ops->cldma_unmask_intr(drv_info, DIR_RX, rxq->rxqno, QUEUE_XFER_DONE);
+
+	return rxq;
+
+err_free_req:
+	for (i = 0; i < rxq->nr_gpds; i++) {
+		req = rxq->req_pool + i;
+		if (!req->gpd)
+			break;
+		mtk_cldma_rxq_alloc_cancel(drv_info, req, rxq->nr_bds);
+	}
+
+	kfree(rxq->req_pool);
+err_free_rxq:
+	kfree(rxq);
+	return NULL;
+}
+
+static void mtk_cldma_rxq_free(struct cldma_drv_info *drv_info, u32 rxqno)
+{
+	struct cldma_drv_ops *drv_ops;
+	struct mtk_md_dev *mdev;
+	struct bd_dsc *bd_dsc;
+	struct rx_req *req;
+	struct rxq *rxq;
+	int irq_id;
+	int i, j;
+
+	mdev = drv_info->mdev;
+	drv_ops = drv_info->drv_ops;
+
+	rxq = drv_info->rxq[rxqno];
+	drv_info->rxq[rxqno] = NULL;
+
+	/* stop HW rx transaction */
+	atomic_set(&rxq->need_exit, 1);
+	drv_ops->cldma_stop_queue(drv_info, DIR_RX, rxqno);
+
+	irq_id = mtk_pci_get_virq_id(mdev, drv_info->pci_ext_irq_id);
+	synchronize_irq(irq_id);
+	/* flush on-going work */
+	flush_work(&rxq->rx_done_work);
+	/* mask L2 RX interrupt again to avoid race condition causing use-after-free issue */
+	drv_ops->cldma_mask_intr(drv_info, DIR_RX, rxqno, QUEUE_XFER_DONE);
+	drv_ops->cldma_mask_intr(drv_info, DIR_RX, rxqno, QUEUE_ERROR);
+
+	/* free rx req resource */
+	for (i = 0; i < rxq->nr_gpds; i++) {
+		req = rxq->req_pool + rxq->free_idx;
+		if (!(req->gpd->rx_gpd.gpd_flags & CLDMA_GPD_FLAG_HWO) &&
+		    le16_to_cpu(req->gpd->rx_gpd.data_recv_len)) {
+			mtk_cldma_rx_skb_adjust(mdev, rxq, req);
+			rxq->rx_done(req->skb, rxq->arg, true);
+			req->skb = NULL;
+		}
+		if (req->skb) {
+			if (rxq->nr_bds) {
+				skb_shinfo(req->skb)->frag_list = NULL;
+				dev_kfree_skb_any(req->skb);
+			} else {
+				if (req->data_dma_addr)
+					dma_unmap_single(mdev->dev, req->data_dma_addr,
+							 req->mtu, DMA_FROM_DEVICE);
+				dev_kfree_skb_any(req->skb);
+			}
+		}
+		for (j = 0; j < rxq->nr_bds; j++) {
+			bd_dsc = req->bd_dsc_pool + j;
+			if (bd_dsc->skb) {
+				if (bd_dsc->data_dma_addr)
+					dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
+							 req->frag_size, DMA_FROM_DEVICE);
+				bd_dsc->skb->next = NULL;
+				dev_kfree_skb_any(bd_dsc->skb);
+			}
+			dma_pool_free(drv_info->bd_dma_pool,
+				      bd_dsc->bd, bd_dsc->bd_dma_addr);
+		}
+		kfree(req->bd_dsc_pool);
+		dma_pool_free(drv_info->gpd_dma_pool, req->gpd, req->gpd_dma_addr);
+		rxq->free_idx = (rxq->free_idx + 1) % rxq->nr_gpds;
+	}
+
+	kfree(rxq->req_pool);
+	kfree(rxq);
+}
+
+static int mtk_cldma_dev_exit(struct cldma_dev *cd, int hif_id)
+{
+	struct cldma_drv_info *drv_info;
+	struct mtk_md_dev *mdev;
+	int virq_id;
+	int i;
+
+	if (!cd || hif_id >= NR_CLDMA)
+		return -EINVAL;
+
+	if (!cd->cldma_drv_info[hif_id])
+		return 0;
+
+	/* free cldma descriptor */
+	drv_info = cd->cldma_drv_info[hif_id];
+	mdev = cd->trans->mdev;
+	virq_id = mtk_pci_get_virq_id(mdev, drv_info->pci_ext_irq_id);
+	mtk_pci_mask_irq(mdev, drv_info->pci_ext_irq_id);
+	synchronize_irq(virq_id);
+	for (i = 0; i < HW_QUEUE_NUM; i++) {
+		if (drv_info->txq[i])
+			mtk_cldma_txq_free(drv_info, drv_info->txq[i]->txqno);
+		if (drv_info->rxq[i])
+			mtk_cldma_rxq_free(drv_info, drv_info->rxq[i]->rxqno);
+	}
+
+	flush_workqueue(drv_info->wq);
+	destroy_workqueue(drv_info->wq);
+	dma_pool_destroy(drv_info->bd_dma_pool);
+	dma_pool_destroy(drv_info->gpd_dma_pool);
+	mtk_pci_unregister_irq(mdev, drv_info->pci_ext_irq_id);
+
+	kfree(drv_info);
+	cd->cldma_drv_info[hif_id] = NULL;
+
+	return 0;
+}
+
+static int mtk_cldma_start_xfer(struct cldma_drv_info *drv_info, u32 qno)
+{
+	struct cldma_drv_ops *drv_ops;
+	struct txq *txq;
+	u32 val;
+
+	txq = drv_info->txq[qno];
+	drv_ops = drv_info->drv_ops;
+
+	val = drv_ops->cldma_get_tx_start_addr(drv_info, qno);
+	if (unlikely(val == LINK_ERROR_VAL))
+		return -EIO;
+
+	if (unlikely(!val)) {
+		drv_ops->cldma_drv_init(drv_info);
+		txq = drv_info->txq[qno];
+		drv_ops->cldma_setup_start_addr(drv_info, DIR_TX, qno,
+						txq->req_pool[txq->free_idx].gpd_dma_addr);
+		drv_ops->cldma_start_queue(drv_info, DIR_TX, qno);
+		txq->tx_started = true;
+	} else if (unlikely(!txq->tx_started)) {
+		drv_ops->cldma_start_queue(drv_info, DIR_TX, qno);
+		txq->tx_started = true;
+	} else {
+		drv_ops->cldma_resume_queue(drv_info, DIR_TX, qno);
+	}
+
+	return 0;
+}
+
+int mtk_cldma_init(struct mtk_ctrl_trans *trans)
+{
+	struct cldma_dev *cd;
+
+	cd = kzalloc_obj(*cd);
+	if (!cd)
+		return -ENOMEM;
+
+	cd->trans = trans;
+	trans->dev = cd;
+
+	return 0;
+}
+
+void mtk_cldma_exit(struct mtk_ctrl_trans *trans)
+{
+	if (!trans->dev)
+		return;
+
+	kfree(trans->dev);
+	trans->dev = NULL;
+}
+
+static int mtk_cldma_open(struct cldma_dev *cd, struct sk_buff *skb)
+{
+	struct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb->data;
+	struct trb *trb = (struct trb *)skb->cb;
+	struct cldma_drv_info *drv_info;
+	struct queue_info *que;
+	struct txq *txq;
+	struct rxq *rxq;
+	int ret = 0;
+
+	que = radix_tree_lookup(&cd->trans->queue_tbl, trb->channel_id & 0xFFFF);
+	drv_info = cd->cldma_drv_info[que->hif_id];
+	if (!drv_info) {
+		ret = -EIO;
+		goto out;
+	}
+
+	if (que->tx_mtu == 0 || que->rx_mtu == 0) {
+		dev_err((cd->trans->mdev)->dev,
+			"Failed to enable cldma%d txq%d rxq%d due to wrong mtu\n",
+			drv_info->hw_id, que->txqno, que->rxqno);
+		ret = -EINVAL;
+		goto out;
+	}
+
+	trb_open_priv->tx_mtu = que->tx_mtu;
+	trb_open_priv->rx_mtu = que->rx_mtu;
+	trb_open_priv->tx_frag_size = que->tx_frag_size;
+	trb_open_priv->rx_frag_size = que->rx_frag_size;
+
+	if (drv_info->txq[que->txqno] || drv_info->rxq[que->rxqno]) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	txq = mtk_cldma_txq_alloc(drv_info, skb);
+	if (!txq) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	rxq = mtk_cldma_rxq_alloc(drv_info, skb);
+	if (!rxq) {
+		ret = -ENOMEM;
+		mtk_cldma_txq_free(drv_info, txq->txqno);
+		goto out;
+	}
+
+out:
+	trb->status = ret;
+	trb->trb_complete(skb);
+
+	return ret;
+}
+
+static int mtk_cldma_tx(struct cldma_dev *cd, struct sk_buff *skb)
+{
+	struct trb *trb = (struct trb *)skb->cb;
+	struct cldma_drv_info *drv_info;
+	struct mtk_md_dev *mdev;
+	struct queue_info *que;
+	struct txq *txq;
+	int ret;
+
+	que = radix_tree_lookup(&cd->trans->queue_tbl, trb->channel_id & 0xFFFF);
+	drv_info = cd->cldma_drv_info[que->hif_id];
+	if (unlikely(!drv_info))
+		return -EPIPE;
+	txq = drv_info->txq[que->txqno];
+	if (unlikely(!txq) || txq->is_stopping)
+		return -EPIPE;
+
+	mdev = drv_info->mdev;
+
+	ret = mtk_cldma_start_xfer(drv_info, que->txqno);
+	if (unlikely(ret))
+		dev_err(mdev->dev, "Failed to trigger cldma tx\n");
+
+	return ret;
+}
+
+static int mtk_cldma_close(struct cldma_dev *cd, struct sk_buff *skb)
+{
+	struct trb *trb = (struct trb *)skb->cb;
+	struct cldma_drv_info *drv_info;
+	struct queue_info *que;
+
+	que = radix_tree_lookup(&cd->trans->queue_tbl, trb->channel_id & 0xFFFF);
+	drv_info = cd->cldma_drv_info[que->hif_id];
+	if (unlikely(!drv_info))
+		return -EPIPE;
+
+	if (drv_info->txq[que->txqno])
+		mtk_cldma_txq_free(drv_info, que->txqno);
+	if (drv_info->rxq[que->rxqno])
+		mtk_cldma_rxq_free(drv_info, que->rxqno);
+
+	trb->status = 0;
+	trb->trb_complete(skb);
+
+	return 0;
+}
+
+static int mtk_cldma_txbuf_set(struct cldma_drv_info *drv_info, struct sk_buff *skb,
+			       struct tx_req *req, int nr_bds)
+{
+	struct sk_buff *curr_skb, *next_skb;
+	struct mtk_md_dev *mdev;
+	struct bd_dsc *bd_dsc;
+	int ret;
+	int i;
+
+	mdev = drv_info->mdev;
+
+	if (nr_bds) {
+		bd_dsc = req->bd_dsc_pool;
+		curr_skb = skb;
+		for (i = 0; i < nr_bds && curr_skb; i++) {
+			bd_dsc = req->bd_dsc_pool + i;
+			if (req->bd_dsc_pool == bd_dsc) {
+				bd_dsc->data_len = skb->len - skb->data_len;
+				next_skb = skb_shinfo(skb)->frag_list;
+			} else {
+				bd_dsc->data_len = curr_skb->len;
+				next_skb = curr_skb->next;
+			}
+			bd_dsc->data_dma_addr = dma_map_single(mdev->dev, curr_skb->data,
+							       bd_dsc->data_len, DMA_TO_DEVICE);
+			ret = dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr);
+			if (unlikely(ret))
+				goto err_unmap_buffer;
+
+			bd_dsc->bd->tx_bd.data_buff_ptr_h =
+				cpu_to_le32((u64)(bd_dsc->data_dma_addr) >> 32);
+			bd_dsc->bd->tx_bd.data_buff_ptr_l = cpu_to_le32(bd_dsc->data_dma_addr);
+			bd_dsc->bd->tx_bd.data_buffer_len = cpu_to_le16(bd_dsc->data_len);
+			curr_skb = next_skb;
+		}
+		bd_dsc->bd->tx_bd.bd_flags = CLDMA_BD_FLAG_EOL;
+	} else {
+		req->data_dma_addr = dma_map_single(mdev->dev, skb->data,
+						    skb->len, DMA_TO_DEVICE);
+		ret = dma_mapping_error(mdev->dev, req->data_dma_addr);
+		if (unlikely(ret)) {
+			req->data_dma_addr = 0;
+			goto err_exit;
+		}
+
+		req->gpd->tx_gpd.data_buff_ptr_h = cpu_to_le32((u64)(req->data_dma_addr) >> 32);
+		req->gpd->tx_gpd.data_buff_ptr_l = cpu_to_le32(req->data_dma_addr);
+	}
+
+	return 0;
+
+err_unmap_buffer:
+	for (i = 0; i < nr_bds; i++) {
+		bd_dsc = req->bd_dsc_pool + i;
+		if (dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr)) {
+			bd_dsc->data_dma_addr = 0;
+			break;
+		}
+		dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
+				 bd_dsc->data_len, DMA_TO_DEVICE);
+		bd_dsc->data_dma_addr = 0;
+	}
+err_exit:
+	dev_err(mdev->dev, "Failed to map dma! error:%d\n", ret);
+	return -EAGAIN;
+}
+
+int mtk_cldma_submit_tx(void *dev, struct sk_buff *skb)
+{
+	struct trb *trb = (struct trb *)skb->cb;
+	struct cldma_drv_info *drv_info;
+	struct cldma_dev *cd = dev;
+	struct queue_info *que;
+	struct tx_req *req;
+	struct txq *txq;
+	int ret;
+
+	que = radix_tree_lookup(&cd->trans->queue_tbl, trb->channel_id & 0xFFFF);
+	drv_info = cd->cldma_drv_info[que->hif_id];
+	if (unlikely(!drv_info))
+		return -EINVAL;
+
+	txq = drv_info->txq[que->txqno];
+	if (unlikely(!txq))
+		return -EINVAL;
+
+	if (!atomic_read(&txq->req_budget))
+		return -EAGAIN;
+
+	req = txq->req_pool + txq->wr_idx;
+	req->gpd->tx_gpd.debug_id = 0x01;
+	ret = mtk_cldma_txbuf_set(drv_info, skb, req, txq->nr_bds);
+	if (ret)
+		return ret;
+
+	req->gpd->tx_gpd.data_buff_len = cpu_to_le16(skb->len);
+
+	req->data_len = skb->len;
+	req->skb = skb;
+	req->data_vm_addr = skb->data;
+
+	wmb(); /* ensure req and data msg set done before HWO setup */
+
+	req->gpd->tx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;
+
+	wmb(); /* ensure HWO setup done before index update */
+
+	txq->wr_idx = (txq->wr_idx + 1) % txq->nr_gpds;
+	atomic_dec(&txq->req_budget);
+
+	return 0;
+}
+
+int mtk_cldma_get_tx_budget(void *dev, enum mtk_hif_id hif_id, u32 qno)
+{
+	struct cldma_drv_info *drv_info;
+	struct cldma_dev *cd = dev;
+	struct txq *txq;
+
+	if (unlikely(hif_id >= NR_CLDMA || qno >= HW_QUE_NUM || !cd))
+		return -EINVAL;
+
+	drv_info = cd->cldma_drv_info[hif_id];
+	if (!drv_info)
+		return -EINVAL;
+	txq = drv_info->txq[qno];
+	if (!txq)
+		return -EINVAL;
+	return atomic_read(&txq->req_budget);
+}
+
+static int (*trb_act_tbl[TRB_CMD_MAX])(struct cldma_dev *cd, struct sk_buff *skb) = {
+	[TRB_CMD_ENABLE] = mtk_cldma_open,
+	[TRB_CMD_TX] = mtk_cldma_tx,
+	[TRB_CMD_DISABLE] = mtk_cldma_close,
+};
+
+int mtk_cldma_trb_process(void *dev, struct sk_buff *skb)
+{
+	struct cldma_dev *cd;
+	struct trb *trb;
+
+	if (!dev || !skb)
+		return -EINVAL;
+
+	cd = (struct cldma_dev *)dev;
+	trb = (struct trb *)skb->cb;
+
+	if (!(trb->cmd > TRB_CMD_MIN && trb->cmd < TRB_CMD_STOP))
+		return -EINVAL;
+
+	return trb_act_tbl[trb->cmd](cd, skb);
+}
+
+void mtk_cldma_fsm_state_listener(struct mtk_fsm_param *param, struct mtk_ctrl_trans *trans)
+{
+	struct cldma_dev *cd = trans->dev;
+	int i;
+
+	switch (param->to) {
+	case FSM_STATE_BOOTUP:
+		if (param->fsm_flag & FSM_F_SAP_HS_START)
+			mtk_cldma_dev_init(cd, CLDMA0);
+		else if (param->fsm_flag & FSM_F_MD_HS_START)
+			mtk_cldma_dev_init(cd, CLDMA1);
+		break;
+	case FSM_STATE_OFF:
+		for (i = 0; i < NR_CLDMA; i++)
+			mtk_cldma_dev_exit(cd, i);
+		break;
+	default:
+		break;
+	}
+}
+
+int mtk_cldma_check_ch_cfg(void *dev, struct queue_info *que)
+{
+	struct cldma_drv_info *drv_info;
+	struct cldma_dev *cd = dev;
+	struct mtk_md_dev *mdev;
+	struct txq *txq;
+	struct rxq *rxq;
+
+	mdev = cd->trans->mdev;
+	drv_info = cd->cldma_drv_info[que->hif_id];
+
+	if (!drv_info) {
+		dev_err(mdev->dev, "CLDMA%d has not been initialized\n",
+			mtk_cldma_hw_id_tbl[que->hif_id]);
+		return -EINVAL;
+	}
+
+	txq = drv_info->txq[que->txqno];
+	rxq = drv_info->rxq[que->rxqno];
+	if (!txq || !rxq) {
+		dev_err(mdev->dev,
+			"CLDMA%d txq%d rxq%d has not been enabled\n",
+			mtk_cldma_hw_id_tbl[que->hif_id], que->txqno, que->rxqno);
+		return -EINVAL;
+	}
+
+	if (que->tx_mtu != txq->que->tx_mtu || que->rx_mtu != rxq->que->rx_mtu) {
+		dev_err(mdev->dev,
+			"Channel:%08x tx_mtu:%08x rx_mtu:%08x do not match ch cfg\n",
+			que->tx_chl, que->tx_mtu, que->rx_mtu);
+		return -EINVAL;
+	}
+
+	return 0;
+}
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma.h
new file mode 100644
index 00000000000000..4686f7b178e5bd
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma.h
@@ -0,0 +1,173 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_CLDMA_H__
+#define __MTK_CLDMA_H__
+
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include "mtk_ctrl_plane.h"
+#include "mtk_trans_ctrl.h"
+
+struct mtk_fsm_param;
+
+#define TXQ(N)					(N)
+#define RXQ(N)					(N)
+
+#define CLDMA_GPD_FLAG_HWO			BIT(0)
+#define CLDMA_GPD_FLAG_BDP			BIT(1)
+#define CLDMA_GPD_FLAG_BPS			BIT(2)
+#define CLDMA_GPD_FLAG_IOC			BIT(7)
+#define CLDMA_BD_FLAG_EOL			BIT(0)
+
+union gpd {
+	struct {
+		u8 gpd_flags;
+		u8 non_used1;
+		__le16 data_allow_len;
+		__le32 next_gpd_ptr_h;
+		__le32 next_gpd_ptr_l;
+		__le32 data_buff_ptr_h;
+		__le32 data_buff_ptr_l;
+		__le16 data_recv_len;
+		u8 non_used2;
+		u8 debug_id;
+	} rx_gpd;
+
+	struct {
+		u8 gpd_flags;
+		u8 non_used1;
+		u8 non_used2;
+		u8 debug_id;
+		__le32 next_gpd_ptr_h;
+		__le32 next_gpd_ptr_l;
+		__le32 data_buff_ptr_h;
+		__le32 data_buff_ptr_l;
+		__le16 data_buff_len;
+		__le16 non_used3;
+	} tx_gpd;
+} __packed;
+
+union bd {
+	struct {
+		u8 bd_flags;
+		u8 non_used1;
+		__le16 data_allow_len;
+		__le32 next_bd_ptr_h;
+		__le32 next_bd_ptr_l;
+		__le32 data_buff_ptr_h;
+		__le32 data_buff_ptr_l;
+		__le16 data_recv_len;
+		__le16 non_used2;
+	} rx_bd;
+
+	struct {
+		u8 bd_flags;
+		u8 non_used1;
+		__le16 non_used2;
+		__le32 next_bd_ptr_h;
+		__le32 next_bd_ptr_l;
+		__le32 data_buff_ptr_h;
+		__le32 data_buff_ptr_l;
+		__le16 data_buffer_len;
+		u8 extension_len;
+		u8 non_used3;
+	} tx_bd;
+} __packed;
+
+struct bd_dsc {
+	union bd *bd;
+	struct sk_buff *skb;
+	dma_addr_t bd_dma_addr;
+	dma_addr_t data_dma_addr;
+	size_t data_len;
+};
+
+struct rx_req {
+	union gpd *gpd;
+	u32 mtu;
+	struct sk_buff *skb;
+	size_t data_len;
+	dma_addr_t gpd_dma_addr;
+	dma_addr_t data_dma_addr;
+	u32 frag_size;
+	struct bd_dsc *bd_dsc_pool;
+};
+
+struct rxq {
+	struct cldma_drv_info *drv_info;
+	u32 rxqno;
+	struct queue_info *que;
+	struct work_struct rx_done_work;
+	struct rx_req *req_pool;
+	u32 nr_gpds;
+	u32 free_idx;
+	unsigned short rx_done_cnt;
+	void *arg;
+	int (*rx_done)(struct sk_buff *skb, void *priv, bool force_recv);
+	u32 nr_bds;
+	atomic_t need_exit;
+};
+
+struct tx_req {
+	union gpd *gpd;
+	u32 mtu;
+	void *data_vm_addr;
+	size_t data_len;
+	dma_addr_t data_dma_addr;
+	dma_addr_t gpd_dma_addr;
+	struct sk_buff *skb;
+	int (*trb_complete)(struct sk_buff *skb);
+	u32 frag_size;
+	struct bd_dsc *bd_dsc_pool;
+};
+
+struct txq {
+	struct cldma_drv_info *drv_info;
+	u32 txqno;
+	struct queue_info *que;
+	struct work_struct tx_done_work;
+	struct tx_req *req_pool;
+	u32 nr_gpds;
+	atomic_t req_budget;
+	u32 wr_idx;
+	u32 free_idx;
+	bool tx_started;
+	bool is_stopping;
+	unsigned short tx_done_cnt;
+	u32 nr_bds;
+};
+
+struct cldma_dev {
+	struct cldma_drv_info *cldma_drv_info[NR_CLDMA];
+	struct mtk_ctrl_trans *trans;
+};
+
+struct cldma_drv_info_desc {
+	u32 hw_ver;
+	struct cldma_drv_ops *drv_ops;
+	struct cldma_hw_regs *hw_regs;
+};
+
+int mtk_cldma_init(struct mtk_ctrl_trans *trans);
+void mtk_cldma_exit(struct mtk_ctrl_trans *trans);
+int mtk_cldma_submit_tx(void *dev, struct sk_buff *skb);
+int mtk_cldma_get_tx_budget(void *dev, enum mtk_hif_id hif_id, u32 qno);
+int mtk_cldma_trb_process(void *dev, struct sk_buff *skb);
+void mtk_cldma_fsm_state_listener(struct mtk_fsm_param *param, struct mtk_ctrl_trans *trans);
+int mtk_cldma_check_ch_cfg(void *dev, struct queue_info *que);
+
+#define drv_ops_name(NAME) cldma_drv_ops_##NAME
+#define cldma_regs_name(NAME) mtk_cldma_regs_##NAME
+
+extern struct cldma_drv_ops cldma_drv_ops_m9xx;
+extern struct cldma_hw_regs mtk_cldma_regs_m9xx;
+
+#endif
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c
new file mode 100644
index 00000000000000..8745c147089194
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023, MediaTek Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/iopoll.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include "mtk_cldma_drv.h"
+#include "mtk_dev.h"
+#include "mtk_pci.h"
+#include "mtk_pci_reg.h"
+
+#define WAIT_QUEUE_STOP		(70)
+
+void mtk_cldma_drv_init(struct cldma_drv_info *drv_info)
+{
+	struct cldma_hw_regs *hw_regs;
+	struct mtk_md_dev *mdev;
+	int base;
+	u32 val;
+
+	mdev = drv_info->mdev;
+	base = drv_info->base_addr;
+	hw_regs = drv_info->hw_regs;
+
+	/* set CLDMA to 64 bit mode GPD */
+	val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_ul_cfg);
+	val = (val & (~(0x7 << 5))) | ((0x4) << 5);
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_cfg, val);
+
+	val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_so_cfg);
+	val = (val & (~(0x7 << 10))) | ((0x4) << 10) | (1 << 2);
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_so_cfg, val);
+
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_rx_work_to_reg_mask_set, ALLQ);
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ip_busy_to_pcie_mask_set,
+			ALLQ << 16);
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ip_busy_to_pcie_mask_clr,
+			ALLQ << 24);
+
+	/* enable interrupt to PCIe */
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_int_mask, 0);
+
+	/* disable illegal memory check */
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_dummy_0, 1);
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_so_dummy_0, 1);
+}
+
+void mtk_cldma_setup_start_addr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+				u32 qno, dma_addr_t addr)
+{
+	struct cldma_hw_regs *hw_regs;
+	unsigned int addr_l;
+	unsigned int addr_h;
+	int base;
+
+	hw_regs = drv_info->hw_regs;
+	base = drv_info->base_addr;
+
+	if (dir == DIR_TX) {
+		addr_l = base + hw_regs->reg_cldma_ul_start_addrl_0 + qno * HW_QUEUE_NUM;
+		addr_h = base + hw_regs->reg_cldma_ul_start_addrh_0 + qno * HW_QUEUE_NUM;
+	} else {
+		addr_l = base + hw_regs->reg_cldma_so_start_addrl_0 + qno * HW_QUEUE_NUM;
+		addr_h = base + hw_regs->reg_cldma_so_start_addrh_0 + qno * HW_QUEUE_NUM;
+	}
+
+	mtk_pci_write32(drv_info->mdev, addr_l, (u32)addr);
+	mtk_pci_write32(drv_info->mdev, addr_h, (u32)((u64)addr >> 32));
+}
+
+void mtk_cldma_mask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+			 u32 qno, enum mtk_intr_type type)
+{
+	struct cldma_hw_regs *hw_regs;
+	int base;
+	u32 addr;
+	u32 val;
+
+	hw_regs = drv_info->hw_regs;
+	base = drv_info->base_addr;
+
+	if (dir == DIR_TX)
+		addr = base + hw_regs->reg_cldma_l2timsr0;
+	else
+		addr = base + hw_regs->reg_cldma_l2rimsr0;
+
+	if (qno == ALLQ)
+		val = qno << type;
+	else
+		val = BIT(qno) << type;
+
+	mtk_pci_write32(drv_info->mdev, addr, val);
+}
+
+void mtk_cldma_unmask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+			   u32 qno, enum mtk_intr_type type)
+{
+	struct cldma_hw_regs *hw_regs;
+	int base;
+	u32 addr;
+	u32 val;
+
+	hw_regs = drv_info->hw_regs;
+	base = drv_info->base_addr;
+
+	if (dir == DIR_TX)
+		addr = base + hw_regs->reg_cldma_l2timcr0;
+	else
+		addr = base + hw_regs->reg_cldma_l2rimcr0;
+
+	if (qno == ALLQ)
+		val = qno << type;
+	else
+		val = BIT(qno) << type;
+
+	mtk_pci_write32(drv_info->mdev, addr, val);
+}
+
+void mtk_cldma_clr_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+			       u32 qno, enum mtk_intr_type type)
+{
+	struct cldma_hw_regs *hw_regs;
+	struct mtk_md_dev *mdev;
+	int base;
+	u32 addr;
+	u32 val;
+
+	hw_regs = drv_info->hw_regs;
+	base = drv_info->base_addr;
+	mdev = drv_info->mdev;
+
+	if (type == QUEUE_ERROR) {
+		if (dir == DIR_TX) {
+			val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3tisar0);
+			mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3tisar0, val);
+			val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3tisar1);
+			mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3tisar1, val);
+			val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3tisar2);
+			mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3tisar2, val);
+		} else {
+			val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3risar0);
+			mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3risar0, val);
+			val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3risar1);
+			mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3risar1, val);
+		}
+	}
+
+	if (dir == DIR_TX)
+		addr = base + hw_regs->reg_cldma_l2tisar0;
+	else
+		addr = base + hw_regs->reg_cldma_l2risar0;
+
+	if (qno == ALLQ)
+		val = qno << type;
+	else
+		val = BIT(qno) << type;
+
+	mtk_pci_write32(mdev, addr, val);
+	val = mtk_pci_read32(mdev, addr);
+}
+
+u32 mtk_cldma_check_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+				u32 qno, enum mtk_intr_type type)
+{
+	struct cldma_hw_regs *hw_regs;
+	u32 addr, val, sta;
+	int base;
+
+	hw_regs = drv_info->hw_regs;
+	base = drv_info->base_addr;
+
+	if (dir == DIR_TX)
+		addr = base + hw_regs->reg_cldma_l2tisar0;
+	else
+		addr = base + hw_regs->reg_cldma_l2risar0;
+
+	val = mtk_pci_read32(drv_info->mdev, addr);
+	if (val == LINK_ERROR_VAL)
+		sta = val;
+	else if (qno == ALLQ)
+		sta = (val >> type) & 0xFF;
+	else
+		sta = (val >> type) & BIT(qno);
+
+	return sta;
+}
+
+void mtk_cldma_start_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)
+{
+	struct cldma_hw_regs *hw_regs;
+	u32 val = BIT(qno);
+	int base;
+	u32 addr;
+
+	hw_regs = drv_info->hw_regs;
+	base = drv_info->base_addr;
+
+	if (dir == DIR_TX)
+		addr = base + hw_regs->reg_cldma_ul_start_cmd;
+	else
+		addr = base + hw_regs->reg_cldma_so_start_cmd;
+
+	mtk_pci_write32(drv_info->mdev, addr, val);
+}
+
+void mtk_cldma_resume_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)
+{
+	struct cldma_hw_regs *hw_regs;
+	u32 val = BIT(qno);
+	int base;
+	u32 addr;
+
+	hw_regs = drv_info->hw_regs;
+	base = drv_info->base_addr;
+
+	if (dir == DIR_TX)
+		addr = base + hw_regs->reg_cldma_ul_resume_cmd;
+	else
+		addr = base + hw_regs->reg_cldma_so_resume_cmd;
+
+	mtk_pci_write32(drv_info->mdev, addr, val);
+}
+
+u32 mtk_cldma_queue_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)
+{
+	struct cldma_hw_regs *hw_regs;
+	int base;
+	u32 addr;
+	u32 val;
+
+	hw_regs = drv_info->hw_regs;
+	base = drv_info->base_addr;
+
+	if (dir == DIR_TX)
+		addr = base + hw_regs->reg_cldma_ul_status;
+	else
+		addr = base + hw_regs->reg_cldma_so_status;
+
+	val = mtk_pci_read32(drv_info->mdev, addr);
+
+	if (qno == ALLQ || val == LINK_ERROR_VAL)
+		return val;
+
+	return val & BIT(qno);
+}
+
+u32 mtk_cldma_stop_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)
+{
+	u32 val = (qno == ALLQ) ? qno : BIT(qno);
+	struct cldma_hw_regs *hw_regs;
+	unsigned int active;
+	int base;
+	u32 addr;
+
+	hw_regs = drv_info->hw_regs;
+	base = drv_info->base_addr;
+
+	if (dir == DIR_TX)
+		addr = base + hw_regs->reg_cldma_ul_stop_cmd;
+	else
+		addr = base + hw_regs->reg_cldma_so_stop_cmd;
+
+	mtk_pci_write32(drv_info->mdev, addr, val);
+
+	read_poll_timeout(mtk_cldma_queue_status, active,
+			  active == LINK_ERROR_VAL || !active,
+			  WAIT_QUEUE_STOP, WAIT_QUEUE_STOP * 10, false,
+			  drv_info, dir, qno);
+
+	return active;
+}
+
+void mtk_cldma_clear_ip_busy(struct cldma_drv_info *drv_info)
+{
+	mtk_pci_write32(drv_info->mdev, drv_info->base_addr +
+			drv_info->hw_regs->reg_cldma_ip_busy, 0x01);
+}
+
+void mtk_cldma_get_intr_status(struct cldma_drv_info *drv_info, u32 *tx_sta, u32 *rx_sta)
+{
+	struct cldma_hw_regs *hw_regs;
+	struct mtk_md_dev *mdev;
+	u32 tx_mask, rx_mask;
+	int base;
+
+	mdev = drv_info->mdev;
+	base = drv_info->base_addr;
+	hw_regs = drv_info->hw_regs;
+
+	*tx_sta = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l2tisar0);
+	tx_mask = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l2timr0);
+	*rx_sta = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l2risar0);
+	rx_mask = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l2rimr0);
+
+	*tx_sta = (*tx_sta) & (~tx_mask);
+	*rx_sta = (*rx_sta) & (~rx_mask);
+
+	if (*tx_sta) {
+		/* TX XFER_DONE and QUEUE_ERROR mask */
+		mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l2timsr0, *tx_sta);
+		/* TX XFER_DONE clear */
+		mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l2tisar0,
+				(*tx_sta) & (0xFF << QUEUE_XFER_DONE));
+	}
+
+	if (*rx_sta) {
+		/* RX XFER_DONE and QUEUE_ERROR mask */
+		mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l2rimsr0, *rx_sta);
+		/* RX XFER_DONE clear */
+		mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l2risar0,
+				(*rx_sta) & (0xFF << QUEUE_XFER_DONE));
+	}
+}
+
+u32 mtk_cldma_get_tx_start_addr(struct cldma_drv_info *drv_info, u32 qno)
+{
+	u32 addr, val;
+
+	addr = drv_info->base_addr + drv_info->hw_regs->reg_cldma_ul_start_addrl_0 +
+	       qno * HW_QUEUE_NUM;
+	val = mtk_pci_read32(drv_info->mdev, addr);
+
+	return val;
+}
+
+u64 mtk_cldma_get_rx_curr_addr(struct cldma_drv_info *drv_info, u32 qno)
+{
+	struct cldma_hw_regs *hw_regs;
+	u32 curr_addr_h, curr_addr_l;
+	struct mtk_md_dev *mdev;
+	u64 curr_addr;
+	int base;
+	u64 addr;
+
+	hw_regs = drv_info->hw_regs;
+	base = drv_info->base_addr;
+	mdev = drv_info->mdev;
+
+	addr = base + hw_regs->reg_cldma_so_current_addrh_0 +
+	       (u64)qno * HW_QUEUE_NUM;
+	curr_addr_h = mtk_pci_read32(mdev, addr);
+	addr = base + hw_regs->reg_cldma_so_current_addrl_0 +
+	       (u64)qno * HW_QUEUE_NUM;
+	curr_addr_l = mtk_pci_read32(mdev, addr);
+	curr_addr = ((u64)curr_addr_h << 32) | curr_addr_l;
+	if (curr_addr_h == LINK_ERROR_VAL && curr_addr_l == LINK_ERROR_VAL)
+		curr_addr = 0;
+	return curr_addr;
+}
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h
new file mode 100644
index 00000000000000..6de87b7ffd4593
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2023, MediaTek Inc.
+ */
+
+#ifndef __MTK_CLDMA_DRV_H__
+#define __MTK_CLDMA_DRV_H__
+
+#define HW_QUEUE_NUM		(8)
+#define ALLQ			(0xFF)
+#define LINK_ERROR_VAL		(0xFFFFFFFF)
+#define CLDMA0_HW_ID		(0)
+#define CLDMA1_HW_ID		(1)
+
+struct cldma_hw_regs {
+	u8 cldma_rx_skb_pool_max_size;
+	u8 cldma_rx_skb_reload_threshold;
+	u8 tq_err_int_offset;
+	u8 tq_active_start_err_int_offset;
+	u8 rq_err_int_offset;
+	u8 rq_active_start_err_int_offset;
+	u16 reg_cldma_so_cfg;
+	u16 reg_cldma_so_start_addrl_0;
+	u16 reg_cldma_so_start_addrh_0;
+	u16 reg_cldma_so_current_addrl_0;
+	u16 reg_cldma_so_current_addrh_0;
+	u16 reg_cldma_so_status;
+	u16 reg_cldma_debug_id_en;
+	u16 reg_cldma_so_last_update_addrl_0;
+	u16 reg_cldma_so_last_update_addrh_0;
+	u16 reg_cldma_l2rimr0;
+	u16 reg_cldma_l2rimr1;
+	u16 reg_cldma_l2rimcr0;
+	u16 reg_cldma_l2rimcr1;
+	u16 reg_cldma_l2rimsr0;
+	u16 reg_cldma_l2rimsr1;
+	u16 reg_cldma_int_mask;
+	u16 reg_cldma_slp_mem_ctl;
+	u16 reg_cldma_busy_mask;
+	u16 reg_cldma_ip_busy_to_pcie_mask;
+	u16 reg_cldma_ip_busy_to_pcie_mask_set;
+	u16 reg_cldma_ip_busy_to_pcie_mask_clr;
+	u16 reg_cldma_ip_busy_to_ap_mask;
+	u16 reg_cldma_ip_busy_to_ap_mask_set;
+	u16 reg_cldma_ip_busy_to_ap_mask_clr;
+	u16 reg_cldma_ip_busy_to_md_mask_set;
+	u16 reg_cldma_rx_work_to_reg_mask_set;
+	u16 reg_infra_rst4_set;
+	u16 reg_infra_rst4_clr;
+	u16 reg_infra_rst2_set;
+	u16 reg_infra_rst2_clr;
+	u16 reg_infra_rst0_set;
+	u16 reg_infra_rst0_clr;
+	u32 tq_err_int_bitmask;
+	u32 tq_active_start_err_int_bitmask;
+	u32 rq_err_int_bitmask;
+	u32 cldma0_base_addr;
+	u32 cldma1_base_addr;
+	u32 rq_active_start_err_int_bitmask;
+	u32 reg_cldma_ul_start_addrl_0;
+	u32 reg_cldma_ul_start_addrh_0;
+	u32 reg_cldma_ul_current_addrl_0;
+	u32 reg_cldma_ul_current_addrh_0;
+	u32 reg_cldma_ul_status;
+	u32 reg_cldma_ul_start_cmd;
+	u32 reg_cldma_ul_resume_cmd;
+	u32 reg_cldma_ul_stop_cmd;
+	u32 reg_cldma_ul_error;
+	u32 reg_cldma_ul_cfg;
+	u32 reg_cldma_ul_dummy_0;
+	u32 reg_cldma_so_error;
+	u32 reg_cldma_so_start_cmd;
+	u32 reg_cldma_so_resume_cmd;
+	u32 reg_cldma_so_stop_cmd;
+	u32 reg_cldma_so_dummy_0;
+	u32 reg_cldma_l2tisar0;
+	u32 reg_cldma_l2tisar1;
+	u32 reg_cldma_l2timr0;
+	u32 reg_cldma_l2timr1;
+	u32 reg_cldma_l2timcr0;
+	u32 reg_cldma_l2timcr1;
+	u32 reg_cldma_l2timsr0;
+	u32 reg_cldma_l2timsr1;
+	u32 reg_cldma_l2risar0;
+	u32 reg_cldma_l2risar1;
+	u32 reg_cldma_l3tisar0;
+	u32 reg_cldma_l3tisar1;
+	u32 reg_cldma_l3tisar2;
+	u32 reg_cldma_l3risar0;
+	u32 reg_cldma_l3risar1;
+	u32 reg_cldma_ip_busy;
+};
+
+enum mtk_ip_busy_src {
+	IP_BUSY_TXDONE = 0,
+	IP_BUSY_TXEMPTY = 8,
+	IP_BUSY_TXACTIVE = 16,
+	IP_BUSY_RXDONE = 24
+};
+
+enum mtk_intr_type {
+	QUEUE_XFER_DONE = 0,
+	QUEUE_EMPTY = 8,
+	QUEUE_ERROR = 16,
+	QUEUE_ACTIVE_START = 24,
+	INVALID_TYPE
+};
+
+enum mtk_tx_rx {
+	DIR_TX,
+	DIR_RX,
+	DIR_MAX
+};
+
+struct cldma_drv_info {
+	int hif_id;
+	int hw_id;
+	int base_addr;
+	int pci_ext_irq_id;
+	struct mtk_md_dev *mdev;
+	struct cldma_dev *cd;
+	struct txq *txq[HW_QUEUE_NUM];
+	struct rxq *rxq[HW_QUEUE_NUM];
+	struct dma_pool *gpd_dma_pool;
+	struct dma_pool *bd_dma_pool;
+	struct workqueue_struct *wq;
+	struct cldma_hw_regs *hw_regs;
+	struct cldma_drv_ops *drv_ops;
+};
+
+struct cldma_drv_ops {
+	void (*cldma_drv_init)(struct cldma_drv_info *drv_info);
+	void (*cldma_drv_reset)(struct cldma_drv_info *drv_info);
+	void (*cldma_setup_start_addr)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+				       u32 qno, dma_addr_t addr);
+	void (*cldma_mask_intr)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+				u32 qno, enum mtk_intr_type type);
+	void (*cldma_unmask_intr)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+				  u32 qno, enum mtk_intr_type type);
+	void (*cldma_clr_intr_status)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+				      u32 qno, enum mtk_intr_type type);
+	u32 (*cldma_check_intr_status)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+				       u32 qno, enum mtk_intr_type type);
+	void (*cldma_start_queue)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+	void (*cldma_resume_queue)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+	u32 (*cldma_queue_status)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+	u32 (*cldma_stop_queue)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+	void (*cldma_clear_ip_busy)(struct cldma_drv_info *drv_info);
+	void (*cldma_get_intr_status)(struct cldma_drv_info *drv_info, u32 *tx_sta, u32 *rx_sta);
+	u32 (*cldma_get_tx_start_addr)(struct cldma_drv_info *drv_info, u32 qno);
+	u64 (*cldma_get_rx_curr_addr)(struct cldma_drv_info *drv_info, u32 qno);
+};
+
+void mtk_cldma_drv_init(struct cldma_drv_info *drv_info);
+void mtk_cldma_setup_start_addr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+				u32 qno, dma_addr_t addr);
+void mtk_cldma_mask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+			 u32 qno, enum mtk_intr_type type);
+void mtk_cldma_unmask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+			   u32 qno, enum mtk_intr_type type);
+void mtk_cldma_clr_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+			       u32 qno, enum mtk_intr_type type);
+u32 mtk_cldma_check_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+				u32 qno, enum mtk_intr_type type);
+void mtk_cldma_start_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+void mtk_cldma_resume_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+u32 mtk_cldma_queue_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+u32 mtk_cldma_stop_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+void mtk_cldma_clear_ip_busy(struct cldma_drv_info *drv_info);
+void mtk_cldma_get_intr_status(struct cldma_drv_info *drv_info, u32 *tx_sta, u32 *rx_sta);
+u32 mtk_cldma_get_tx_start_addr(struct cldma_drv_info *drv_info, u32 qno);
+u64 mtk_cldma_get_rx_curr_addr(struct cldma_drv_info *drv_info, u32 qno);
+
+#endif
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c
new file mode 100644
index 00000000000000..a59c35fc15773e
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023, MediaTek Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include "mtk_cldma.h"
+#include "mtk_cldma_drv.h"
+#include "mtk_cldma_drv_m9xx.h"
+#include "mtk_dev.h"
+#include "mtk_pci.h"
+#include "mtk_pci_reg.h"
+#include "mtk_trans_ctrl.h"
+
+struct cldma_hw_regs mtk_cldma_regs_m9xx = {
+	.cldma0_base_addr = CLDMA0_BASE_ADDR,
+	.cldma1_base_addr = CLDMA1_BASE_ADDR,
+	.cldma_rx_skb_pool_max_size = CLDMA_RX_SKB_POOL_MAX_SIZE,
+	.cldma_rx_skb_reload_threshold = CLDMA_RX_SKB_RELOAD_THRESHOLD,
+	.tq_err_int_offset = TQ_ERR_INT_OFFSET,
+	.tq_err_int_bitmask = TQ_ERR_INT_BITMASK,
+	.tq_active_start_err_int_offset = TQ_ACTIVE_START_ERR_INT_OFFSET,
+	.tq_active_start_err_int_bitmask = TQ_ACTIVE_START_ERR_INT_BITMASK,
+	.rq_err_int_offset = RQ_ERR_INT_OFFSET,
+	.rq_err_int_bitmask = RQ_ERR_INT_BITMASK,
+	.rq_active_start_err_int_offset = RQ_ACTIVE_START_ERR_INT_OFFSET,
+	.rq_active_start_err_int_bitmask = RQ_ACTIVE_START_ERR_INT_BITMASK,
+	.reg_cldma_ul_start_addrl_0 = REG_CLDMA_UL_START_ADDRL_0,
+	.reg_cldma_ul_start_addrh_0 = REG_CLDMA_UL_START_ADDRH_0,
+	.reg_cldma_ul_current_addrl_0 = REG_CLDMA_UL_CURRENT_ADDRL_0,
+	.reg_cldma_ul_current_addrh_0 = REG_CLDMA_UL_CURRENT_ADDRH_0,
+	.reg_cldma_ul_status = REG_CLDMA_UL_STATUS,
+	.reg_cldma_ul_start_cmd = REG_CLDMA_UL_START_CMD,
+	.reg_cldma_ul_resume_cmd = REG_CLDMA_UL_RESUME_CMD,
+	.reg_cldma_ul_stop_cmd = REG_CLDMA_UL_STOP_CMD,
+	.reg_cldma_ul_error = REG_CLDMA_UL_ERROR,
+	.reg_cldma_ul_cfg = REG_CLDMA_UL_CFG,
+	.reg_cldma_ul_dummy_0 = REG_CLDMA_UL_DUMMY_0,
+	.reg_cldma_so_error = REG_CLDMA_SO_ERROR,
+	.reg_cldma_so_start_cmd = REG_CLDMA_SO_START_CMD,
+	.reg_cldma_so_resume_cmd = REG_CLDMA_SO_RESUME_CMD,
+	.reg_cldma_so_stop_cmd = REG_CLDMA_SO_STOP_CMD,
+	.reg_cldma_so_dummy_0 = REG_CLDMA_SO_DUMMY_0,
+	.reg_cldma_so_cfg = REG_CLDMA_SO_CFG,
+	.reg_cldma_so_start_addrl_0 = REG_CLDMA_SO_START_ADDRL_0,
+	.reg_cldma_so_start_addrh_0 = REG_CLDMA_SO_START_ADDRH_0,
+	.reg_cldma_so_current_addrl_0 = REG_CLDMA_SO_CUR_ADDRL_0,
+	.reg_cldma_so_current_addrh_0 = REG_CLDMA_SO_CUR_ADDRH_0,
+	.reg_cldma_so_status = REG_CLDMA_SO_STATUS,
+	.reg_cldma_debug_id_en = REG_CLDMA_DEBUG_ID_EN,
+	.reg_cldma_so_last_update_addrl_0 = REG_CLDMA_SO_LAST_UPDATE_ADDRL_0,
+	.reg_cldma_so_last_update_addrh_0 = REG_CLDMA_SO_LAST_UPDATE_ADDRH_0,
+	.reg_cldma_l2tisar0 = REG_CLDMA_L2TISAR0,
+	.reg_cldma_l2tisar1 = REG_CLDMA_L2TISAR1,
+	.reg_cldma_l2timr0 = REG_CLDMA_L2TIMR0,
+	.reg_cldma_l2timr1 = REG_CLDMA_L2TIMR1,
+	.reg_cldma_l2timcr0 = REG_CLDMA_L2TIMCR0,
+	.reg_cldma_l2timcr1 = REG_CLDMA_L2TIMCR1,
+	.reg_cldma_l2timsr0 = REG_CLDMA_L2TIMSR0,
+	.reg_cldma_l2timsr1 = REG_CLDMA_L2TIMSR1,
+	.reg_cldma_l3tisar0 = REG_CLDMA_L3TISAR0,
+	.reg_cldma_l3tisar1 = REG_CLDMA_L3TISAR1,
+	.reg_cldma_l3tisar2 = REG_CLDMA_L3TISAR2,
+	.reg_cldma_l2risar0 = REG_CLDMA_L2RISAR0,
+	.reg_cldma_l2risar1 = REG_CLDMA_L2RISAR1,
+	.reg_cldma_l2rimr0 = REG_CLDMA_L2RIMR0,
+	.reg_cldma_l2rimr1 = REG_CLDMA_L2RIMR1,
+	.reg_cldma_l2rimcr0 = REG_CLDMA_L2RIMCR0,
+	.reg_cldma_l2rimcr1 = REG_CLDMA_L2RIMCR1,
+	.reg_cldma_l2rimsr0 = REG_CLDMA_L2RIMSR0,
+	.reg_cldma_l2rimsr1 = REG_CLDMA_L2RIMSR1,
+	.reg_cldma_l3risar0 = REG_CLDMA_L3RISAR0,
+	.reg_cldma_l3risar1 = REG_CLDMA_L3RISAR1,
+	.reg_cldma_ip_busy = REG_CLDMA_IP_BUSY,
+	.reg_cldma_int_mask = REG_CLDMA_INT_EAP_USIP_MASK,
+	.reg_cldma_ip_busy_to_pcie_mask = REG_CLDMA_IP_BUSY_TO_PCIE_MASK,
+	.reg_cldma_ip_busy_to_pcie_mask_set = REG_CLDMA_IP_BUSY_TO_PCIE_MASK_SET,
+	.reg_cldma_ip_busy_to_pcie_mask_clr = REG_CLDMA_IP_BUSY_TO_PCIE_MASK_CLR,
+	.reg_cldma_ip_busy_to_ap_mask = REG_CLDMA_IP_BUSY_TO_AP_MASK,
+	.reg_cldma_ip_busy_to_ap_mask_set = REG_CLDMA_IP_BUSY_TO_AP_MASK_SET,
+	.reg_cldma_ip_busy_to_ap_mask_clr = REG_CLDMA_IP_BUSY_TO_AP_MASK_CLR,
+	.reg_cldma_ip_busy_to_md_mask_set = REG_CLDMA_IP_BUSY_TO_MD_MASK_SET,
+	.reg_cldma_rx_work_to_reg_mask_set = REG_CLDMA_RX_WORK_TO_REG_MASK_SET,
+	.reg_infra_rst0_set = REG_INFRA_RST0_SET,
+	.reg_infra_rst0_clr = REG_INFRA_RST0_CLR,
+};
+
+static void mtk_cldma_drv_init_m9xx(struct cldma_drv_info *drv_info)
+{
+	struct cldma_hw_regs *hw_regs;
+	struct mtk_md_dev *mdev;
+	int base;
+	u32 val;
+
+	mdev = drv_info->mdev;
+	base = drv_info->base_addr;
+	hw_regs = drv_info->hw_regs;
+
+	/* set CLDMA to 64 bit mode GPD */
+	val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_ul_cfg);
+
+	val = (val & (~(0x7 << 5))) | ((0x4) << 5);
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_cfg, val);
+
+	val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_so_cfg);
+	val = (val & (~(0x7 << 10))) | ((0x4) << 10) | (1 << 2);
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_so_cfg, val);
+
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_rx_work_to_reg_mask_set, ALLQ);
+
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ip_busy_to_pcie_mask_set,
+			ALLQ << 16);
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ip_busy_to_pcie_mask_clr,
+			ALLQ << 24);
+
+	/* enable interrupt to PCIe */
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_int_mask, 0);
+
+	/* disable illegal memory check */
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_dummy_0, 1);
+	mtk_pci_write32(mdev, base + hw_regs->reg_cldma_so_dummy_0, 1);
+}
+
+static void mtk_cldma_drv_reset_m9xx(struct cldma_drv_info *drv_info)
+{
+	struct cldma_hw_regs *hw_regs;
+	struct mtk_md_dev *mdev;
+	u32 val;
+
+	mdev = drv_info->mdev;
+	hw_regs = drv_info->hw_regs;
+
+	val = mtk_pci_read32(mdev, REG_DEV_INFRA_BASE + hw_regs->reg_infra_rst0_set);
+
+	val |= 1 << (REG_CLDMA0_RST_SET_BIT + drv_info->hw_id);
+	mtk_pci_write32(mdev, REG_DEV_INFRA_BASE + hw_regs->reg_infra_rst0_set, val);
+	udelay(1);
+	val = mtk_pci_read32(mdev, REG_DEV_INFRA_BASE + hw_regs->reg_infra_rst0_clr);
+	val |= 1 << (REG_CLDMA0_RST_CLR_BIT + drv_info->hw_id);
+	mtk_pci_write32(mdev, REG_DEV_INFRA_BASE + hw_regs->reg_infra_rst0_clr, val);
+}
+
+struct cldma_drv_ops cldma_drv_ops_m9xx = {
+	.cldma_drv_init = mtk_cldma_drv_init_m9xx,
+	.cldma_drv_reset = mtk_cldma_drv_reset_m9xx,
+	.cldma_setup_start_addr = mtk_cldma_setup_start_addr,
+	.cldma_mask_intr = mtk_cldma_mask_intr,
+	.cldma_unmask_intr = mtk_cldma_unmask_intr,
+	.cldma_clr_intr_status = mtk_cldma_clr_intr_status,
+	.cldma_check_intr_status = mtk_cldma_check_intr_status,
+	.cldma_start_queue = mtk_cldma_start_queue,
+	.cldma_resume_queue = mtk_cldma_resume_queue,
+	.cldma_queue_status = mtk_cldma_queue_status,
+	.cldma_stop_queue = mtk_cldma_stop_queue,
+	.cldma_clear_ip_busy = mtk_cldma_clear_ip_busy,
+	.cldma_get_intr_status = mtk_cldma_get_intr_status,
+	.cldma_get_tx_start_addr = mtk_cldma_get_tx_start_addr,
+	.cldma_get_rx_curr_addr = mtk_cldma_get_rx_curr_addr,
+};
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h
new file mode 100644
index 00000000000000..f113c4c1068a2c
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2023, MediaTek Inc.
+ */
+
+#ifndef __MTK_CLDMA_DRV_M9XX_H__
+#define __MTK_CLDMA_DRV_M9XX_H__
+
+#define CLDMA0_BASE_ADDR				(0x1021C000)
+#define CLDMA1_BASE_ADDR				(0x1021E000)
+
+#define CLDMA_RX_SKB_POOL_MAX_SIZE			(64)
+#define CLDMA_RX_SKB_RELOAD_THRESHOLD			(16)
+
+/* L2TISAR0 */
+#define TQ_ERR_INT_OFFSET				(16)
+#define TQ_ERR_INT_BITMASK				(0x00FF0000)
+#define TQ_ACTIVE_START_ERR_INT_OFFSET			(24)
+#define TQ_ACTIVE_START_ERR_INT_BITMASK			(0xFF000000)
+
+/* L2RISAR0 */
+#define RQ_ERR_INT_OFFSET				(16)
+#define RQ_ERR_INT_BITMASK				(0x00FF0000)
+#define RQ_ACTIVE_START_ERR_INT_OFFSET			(24)
+#define RQ_ACTIVE_START_ERR_INT_BITMASK			(0xFF000000)
+
+/* CLDMA IN(Tx) */
+#define REG_CLDMA_UL_START_ADDRL_0			(0x0004)
+#define REG_CLDMA_UL_START_ADDRH_0			(0x0008)
+#define REG_CLDMA_UL_CURRENT_ADDRL_0			(0x0044)
+#define REG_CLDMA_UL_CURRENT_ADDRH_0			(0x0048)
+#define REG_CLDMA_UL_STATUS				(0x0084)
+#define REG_CLDMA_UL_START_CMD				(0x0088)
+#define REG_CLDMA_UL_RESUME_CMD				(0x008C)
+#define REG_CLDMA_UL_STOP_CMD				(0x0090)
+#define REG_CLDMA_UL_ERROR				(0x0094)
+#define REG_CLDMA_UL_CFG				(0x0098)
+#define REG_CLDMA_UL_DUMMY_0				(0x009C)
+
+/* CLDMA OUT(Rx) */
+#define REG_CLDMA_SO_ERROR				(0x0400 + 0x0100)
+#define REG_CLDMA_SO_START_CMD				(0x0400 + 0x01BC)
+#define REG_CLDMA_SO_RESUME_CMD				(0x0400 + 0x01C0)
+#define REG_CLDMA_SO_STOP_CMD				(0x0400 + 0x01C4)
+#define REG_CLDMA_SO_DUMMY_0				(0x0400 + 0x0108)
+#define REG_CLDMA_SO_CFG				(0x0400 + 0x0004)
+#define REG_CLDMA_SO_START_ADDRL_0			(0x0400 + 0x0078)
+#define REG_CLDMA_SO_START_ADDRH_0			(0x0400 + 0x007C)
+#define REG_CLDMA_SO_CUR_ADDRL_0			(0x0400 + 0x00B8)
+#define REG_CLDMA_SO_CUR_ADDRH_0			(0x0400 + 0x00BC)
+#define REG_CLDMA_SO_STATUS				(0x0400 + 0x00F8)
+#define REG_CLDMA_DEBUG_ID_EN				(0x0400 + 0x00FC)
+#define REG_CLDMA_SO_LAST_UPDATE_ADDRL_0		(0x0400 + 0x01C8)
+#define REG_CLDMA_SO_LAST_UPDATE_ADDRH_0		(0x0400 + 0x01CC)
+
+/* CLDMA MISC */
+#define REG_CLDMA_L2TISAR0				(0x0800 + 0x0010)
+#define REG_CLDMA_L2TISAR1				(0x0800 + 0x0014)
+#define REG_CLDMA_L2TIMR0				(0x0800 + 0x0018)
+#define REG_CLDMA_L2TIMR1				(0x0800 + 0x001C)
+#define REG_CLDMA_L2TIMCR0				(0x0800 + 0x0020)
+#define REG_CLDMA_L2TIMCR1				(0x0800 + 0x0024)
+#define REG_CLDMA_L2TIMSR0				(0x0800 + 0x0028)
+#define REG_CLDMA_L2TIMSR1				(0x0800 + 0x002C)
+#define REG_CLDMA_L3TISAR0				(0x0800 + 0x0030)
+#define REG_CLDMA_L3TISAR1				(0x0800 + 0x0034)
+#define REG_CLDMA_L2RISAR0				(0x0800 + 0x0050)
+#define REG_CLDMA_L2RISAR1				(0x0800 + 0x0054)
+#define REG_CLDMA_L3RISAR0				(0x0800 + 0x0070)
+#define REG_CLDMA_L3RISAR1				(0x0800 + 0x0074)
+#define REG_CLDMA_IP_BUSY				(0x0800 + 0x00B4)
+#define REG_CLDMA_L3TISAR2				(0x0800 + 0x00C0)
+
+#define REG_CLDMA_L2RIMR0				(0x0800 + 0x00E8)
+#define REG_CLDMA_L2RIMR1				(0x0800 + 0x00EC)
+#define REG_CLDMA_L2RIMCR0				(0x0800 + 0x00F0)
+#define REG_CLDMA_L2RIMCR1				(0x0800 + 0x00F4)
+#define REG_CLDMA_L2RIMSR0				(0x0800 + 0x00F8)
+#define REG_CLDMA_L2RIMSR1				(0x0800 + 0x00FC)
+
+#define REG_CLDMA_INT_EAP_USIP_MASK			(0x0800 + 0x011C)
+#define REG_CLDMA_RQ1_GPD_DONE_CNT			(0x0800 + 0x0174)
+#define REG_CLDMA_TQ1_GPD_DONE_CNT			(0x0800 + 0x0184)
+
+#define REG_CLDMA_IP_BUSY_TO_PCIE_MASK			(0x0800 + 0x0194)
+#define REG_CLDMA_IP_BUSY_TO_PCIE_MASK_SET		(0x0800 + 0x0198)
+#define REG_CLDMA_IP_BUSY_TO_PCIE_MASK_CLR		(0x0800 + 0x019C)
+
+#define REG_CLDMA_IP_BUSY_TO_AP_MASK			(0x0800 + 0x0200)
+#define REG_CLDMA_IP_BUSY_TO_AP_MASK_SET		(0x0800 + 0x0204)
+#define REG_CLDMA_IP_BUSY_TO_AP_MASK_CLR		(0x0800 + 0x0208)
+#define REG_CLDMA_IP_BUSY_TO_MD_MASK_SET		(0x0800 + 0x0210)
+#define REG_CLDMA_RX_WORK_TO_REG_MASK_SET		(0x0800 + 0x021C)
+
+/* CLDMA RESET */
+#define REG_INFRA_RST0_SET				(0x120)
+#define REG_INFRA_RST0_CLR				(0x124)
+#define REG_CLDMA0_RST_SET_BIT				(8)
+#define REG_CLDMA0_RST_CLR_BIT				(8)
+
+#endif
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
new file mode 100644
index 00000000000000..aab09cab360c37
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include "mtk_cldma.h"
+#include "mtk_port.h"
+#include "mtk_trans_ctrl.h"
+
+#define TRB_SRV_NUM	(1)
+
+static const int mtk_srv_cfg_m9xx[NR_CLDMA][HW_QUE_NUM] = {
+	{0},
+	{0},
+};
+
+/* the number of RX GPDs should be at last two */
+static const struct queue_info mtk_queue_info_m9xx[] = {
+	{CCCI_UART2_TX, CCCI_UART2_RX, CLDMA1, TXQ(5), RXQ(5),
+	 Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
+	{CCCI_MBIM_TX, CCCI_MBIM_RX, CLDMA1, TXQ(2), RXQ(2),
+	 Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
+	{CCCI_CONTROL_TX, CCCI_CONTROL_RX, CLDMA1, TXQ(0), RXQ(0),
+	 Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
+	{CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, CLDMA0, TXQ(0), RXQ(0),
+	 Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
+};
+
+static const struct mtk_port_cfg port_cfg_m9xx[] = {
+	{CCCI_UART2_TX, CCCI_UART2_RX, PORT_TYPE_WWAN, "AT",
+		PORT_F_ALLOW_DROP},
+	{CCCI_MBIM_TX, CCCI_MBIM_RX, PORT_TYPE_WWAN, "MBIM",
+		PORT_F_ALLOW_DROP},
+	{CCCI_CONTROL_TX, CCCI_CONTROL_RX, PORT_TYPE_INTERNAL, "MDCTRL",
+		PORT_F_ALLOW_DROP},
+	{CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, PORT_TYPE_INTERNAL, "SAPCTRL",
+		PORT_F_ALLOW_DROP},
+};
+
+static struct mtk_port_layer_cfg port_layer_cfg_m9xx = {
+	.port_cfg = (struct mtk_port_cfg *)port_cfg_m9xx,
+	.port_cnt = ARRAY_SIZE(port_cfg_m9xx),
+};
+
+static struct mtk_ctrl_cfg mtk_ctrl_cfg_m9xx = {
+	.port_layer_cfg = &port_layer_cfg_m9xx,
+};
+
+struct mtk_ctrl_info mtk_ctrl_info_m9xx = {
+	.ctrl_cfg = &mtk_ctrl_cfg_m9xx,
+	.srv_cfg = (int **)mtk_srv_cfg_m9xx,
+	.queue_info = (struct queue_info *)mtk_queue_info_m9xx,
+	.queue_info_num = ARRAY_SIZE(mtk_queue_info_m9xx),
+	.trb_srv_num = TRB_SRV_NUM,
+};
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
new file mode 100644
index 00000000000000..13cebe23fb4ab3
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
@@ -0,0 +1,1085 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/acpi.h>
+#include <linux/aer.h>
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "mtk_dev.h"
+#include "mtk_trans_ctrl.h"
+#include "mtk_pci.h"
+#include "mtk_pci_reg.h"
+#include "mtk_port.h"
+#include "mtk_port_io.h"
+
+#define MTK_PCI_BAR_NUM		6
+#define MTK_PCI_TRANSPARENT_ATR_SIZE	(0x3F)
+#define MTK_PCI_MINIMUM_ATR_SIZE	(0x1000)
+#define ATR_SIZE_LO32_MASK		GENMASK_ULL(31, 0)
+#define ATR_SIZE_HI32_MASK		GENMASK_ULL(63, 32)
+#define ATR_SIZE_BIAS_FROM_LO32		2
+#define ATR_ADDR_ALIGN_MASK		0xFFFFF000
+#define ATR_EN				BIT(0)
+#define ATR_PARAM_OFFSET		16
+/* Delay between ACPI PXP._OFF and _ON for modem power cycle stabilization */
+#define MTK_PLDR_POWER_OFF_DELAY_MS	500
+#define LE32_TO_U32(x) ((__force u32)(__le32)(x))
+#define SET_HW_BITS(dest, chs, mhccif, dev)		\
+	({						\
+		if ((chs) & (dev))					\
+			(dest) |= FIELD_PREP(mhccif, 1);		\
+	})
+
+struct mtk_mhccif_cb {
+	struct list_head entry;
+	int (*evt_cb)(u32 status, void *data);
+	void *data;
+	u32 chs;
+};
+
+/**
+ * mtk_pci_setup_atr() - Configure a PCIe address translation rule
+ * @mdev: MTK MD device
+ * @cfg: ATR configuration parameters
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_setup_atr(struct mtk_md_dev *mdev, struct mtk_atr_cfg *cfg)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	u32 addr, val, size_h, size_l;
+	int atr_size, pos, offset;
+
+	if (cfg->transparent) {
+		/* No address conversion is performed */
+		atr_size = MTK_PCI_TRANSPARENT_ATR_SIZE;
+	} else {
+		if (cfg->size < MTK_PCI_MINIMUM_ATR_SIZE)
+			cfg->size = MTK_PCI_MINIMUM_ATR_SIZE;
+
+		if (cfg->src_addr & (cfg->size - 1)) {
+			dev_err(mdev->dev, "Invalid atr src addr is not aligned to size\n");
+			return -EFAULT;
+		}
+
+		if (cfg->trsl_addr & (cfg->size - 1)) {
+			dev_err(mdev->dev,
+				"Invalid atr trsl addr is not aligned to size, %llx, %llx\n",
+				cfg->trsl_addr, cfg->size - 1);
+			return -EFAULT;
+		}
+
+		size_l = FIELD_GET(ATR_SIZE_LO32_MASK, cfg->size);
+		size_h = FIELD_GET(ATR_SIZE_HI32_MASK, cfg->size);
+		pos = ffs(size_l);
+		if (pos) {
+			atr_size = pos - ATR_SIZE_BIAS_FROM_LO32;
+		} else {
+			pos = ffs(size_h);
+			atr_size = pos + 32 - ATR_SIZE_BIAS_FROM_LO32;
+		}
+	}
+
+	/* Calculate table offset */
+	offset = ATR_PORT_OFFSET * cfg->port + ATR_TABLE_OFFSET * cfg->table;
+	addr = REG_ATR_PCIE_WIN0_T0_SRC_ADDR_MSB + offset;
+	val = (u32)(cfg->src_addr >> 32);
+	mtk_pci_mac_write32(priv, addr, val);
+
+	addr = REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset;
+	val = (u32)(cfg->src_addr & ATR_ADDR_ALIGN_MASK) | (atr_size << 1) | ATR_EN;
+	mtk_pci_mac_write32(priv, addr, val);
+
+	addr = REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_MSB + offset;
+	val = (u32)(cfg->trsl_addr >> 32);
+	mtk_pci_mac_write32(priv, addr, val);
+
+	addr = REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_LSB + offset;
+	val = (u32)(cfg->trsl_addr & ATR_ADDR_ALIGN_MASK);
+	mtk_pci_mac_write32(priv, addr, val);
+
+	/* TRSL_PARAM */
+	addr = REG_ATR_PCIE_WIN0_T0_TRSL_PARAM + offset;
+	val = (cfg->trsl_param << ATR_PARAM_OFFSET) | cfg->trsl_id;
+	mtk_pci_mac_write32(priv, addr, val);
+
+	return 0;
+}
+
+/**
+ * mtk_pci_atr_disable() - Disable all PCIe address translation rules
+ * @priv: MTK PCI private data
+ */
+void mtk_pci_atr_disable(struct mtk_pci_priv *priv)
+{
+	int port, tbl, offset;
+	u32 val;
+
+	/* Disable all ATR table for all ports */
+	for (port = ATR_SRC_PCI_WIN0; port <= ATR_SRC_AXIS_3; port++)
+		for (tbl = 0; tbl < ATR_TABLE_NUM_PER_ATR; tbl++) {
+			/* Calculate table offset */
+			offset = ATR_PORT_OFFSET * port + ATR_TABLE_OFFSET * tbl;
+			val = mtk_pci_mac_read32(priv, REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset);
+			val = val & (~BIT(0));
+			/* Disable table by SRC_ADDR_L */
+			mtk_pci_mac_write32(priv, REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset, val);
+		}
+}
+
+static void mtk_pci_set_msix_merged(struct mtk_pci_priv *priv, int irq_cnt)
+{
+	mtk_pci_mac_write32(priv, REG_PCIE_CFG_MSIX, ffs(irq_cnt) * 2 - 1);
+}
+
+/**
+ * mtk_pci_get_dev_state() - Read the device state from the modem
+ * @mdev: MTK MD device
+ *
+ * Return: Device state value.
+ */
+u32 mtk_pci_get_dev_state(struct mtk_md_dev *mdev)
+{
+	return mtk_pci_mac_read32(mdev->hw_priv, REG_PCIE_DEBUG_DUMMY_7);
+}
+
+/**
+ * mtk_pci_ack_dev_state() - Acknowledge the device state to the modem
+ * @mdev: MTK MD device
+ * @state: State value to acknowledge
+ */
+void mtk_pci_ack_dev_state(struct mtk_md_dev *mdev, u32 state)
+{
+	mtk_pci_mac_write32(mdev->hw_priv, REG_PCIE_DEBUG_DUMMY_7, state);
+}
+
+/**
+ * mtk_pci_get_irq_id() - Map an IRQ source to its hardware IRQ ID
+ * @mdev: MTK MD device
+ * @irq_src: IRQ source enum
+ *
+ * Return: IRQ ID on success, -EINVAL on failure.
+ */
+int mtk_pci_get_irq_id(struct mtk_md_dev *mdev, enum mtk_irq_src irq_src)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	const int *irq_tbl = priv->cfg->irq_tbl;
+	int irq_id = -EINVAL;
+
+	if (irq_src > MTK_IRQ_SRC_MIN && irq_src < MTK_IRQ_SRC_MAX) {
+		irq_id = irq_tbl[irq_src];
+		if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX)
+			irq_id = -EINVAL;
+	}
+
+	return irq_id;
+}
+
+/**
+ * mtk_pci_get_virq_id() - Get the Linux virtual IRQ for a hardware IRQ ID
+ * @mdev: MTK MD device
+ * @irq_id: Hardware IRQ ID
+ *
+ * Return: Virtual IRQ number on success, negative error code on failure.
+ */
+int mtk_pci_get_virq_id(struct mtk_md_dev *mdev, int irq_id)
+{
+	struct pci_dev *pdev = to_pci_dev(mdev->dev);
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+
+	if (!priv->irq_cnt || irq_id < 0)
+		return -EINVAL;
+
+	return pci_irq_vector(pdev, irq_id % priv->irq_cnt);
+}
+
+/**
+ * mtk_pci_register_irq() - Register a callback for a hardware IRQ
+ * @mdev: MTK MD device
+ * @irq_id: Hardware IRQ ID
+ * @irq_cb: Callback function
+ * @data: Private data passed to callback
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_register_irq(struct mtk_md_dev *mdev, int irq_id,
+			 int (*irq_cb)(int irq_id, void *data), void *data)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+
+	if ((irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX) || !irq_cb)
+		return -EINVAL;
+
+	if (priv->irq_cb_list[irq_id]) {
+		dev_err(mdev->dev,
+			"Unable to register irq, irq_id=%d, it's already been register by %ps.\n",
+			irq_id, priv->irq_cb_list[irq_id]);
+		return -EFAULT;
+	}
+	priv->irq_cb_list[irq_id] = irq_cb;
+	priv->irq_cb_data[irq_id] = data;
+
+	return 0;
+}
+
+/**
+ * mtk_pci_unregister_irq() - Unregister a hardware IRQ callback
+ * @mdev: MTK MD device
+ * @irq_id: Hardware IRQ ID
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_unregister_irq(struct mtk_md_dev *mdev, int irq_id)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+
+	if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX)
+		return -EINVAL;
+
+	if (!priv->irq_cb_list[irq_id]) {
+		dev_err(mdev->dev, "irq_id=%d has not been registered\n", irq_id);
+		return -EFAULT;
+	}
+	priv->irq_cb_list[irq_id] = NULL;
+	priv->irq_cb_data[irq_id] = NULL;
+
+	return 0;
+}
+
+/**
+ * mtk_pci_mask_irq() - Mask (disable) a hardware IRQ
+ * @mdev: MTK MD device
+ * @irq_id: Hardware IRQ ID
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_mask_irq(struct mtk_md_dev *mdev, int irq_id)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+
+	if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX ||
+	    priv->irq_type != PCI_IRQ_MSIX) {
+		dev_err(mdev->dev, "Failed to mask irq: input irq_id=%d\n", irq_id);
+		return -EINVAL;
+	}
+
+	mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, BIT(irq_id));
+
+	return 0;
+}
+
+/**
+ * mtk_pci_unmask_irq() - Unmask (enable) a hardware IRQ
+ * @mdev: MTK MD device
+ * @irq_id: Hardware IRQ ID
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_unmask_irq(struct mtk_md_dev *mdev, int irq_id)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+
+	if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX ||
+	    priv->irq_type != PCI_IRQ_MSIX) {
+		dev_err(mdev->dev, "Failed to unmask irq: input irq_id=%d\n", irq_id);
+		return -EINVAL;
+	}
+
+	mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_SET_GRP0_0, BIT(irq_id));
+
+	return 0;
+}
+
+/**
+ * mtk_pci_clear_irq() - Clear (acknowledge) a hardware IRQ
+ * @mdev: MTK MD device
+ * @irq_id: Hardware IRQ ID
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_clear_irq(struct mtk_md_dev *mdev, int irq_id)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+
+	if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX ||
+	    priv->irq_type != PCI_IRQ_MSIX) {
+		dev_err(mdev->dev, "Failed to clear irq: input irq_id=%d\n", irq_id);
+		return -EINVAL;
+	}
+
+	mtk_pci_mac_write32(priv, REG_MSIX_ISTATUS_HOST_GRP0_0, BIT(irq_id));
+
+	return 0;
+}
+
+static u32 mtk_pci_ext_d2h_evt_hw_bits(u32 chs)
+{
+	u32 hw_bits = 0;
+
+	SET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC,
+		    DEV_EVT_D2H_BOOT_FLOW_SYNC);
+	SET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP,
+		    DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP);
+	SET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD,
+		    DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD);
+
+	return LE32_TO_U32(cpu_to_le32(hw_bits));
+}
+
+static u32 mtk_pci_ext_d2h_evt_chs(u32 hw_bits)
+{
+	u32 chs = 0;
+
+	if (!hw_bits)
+		return chs;
+
+	chs = FIELD_PREP(DEV_EVT_D2H_BOOT_FLOW_SYNC,
+			 FIELD_GET(MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC, hw_bits)) |
+	      FIELD_PREP(DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP,
+			 FIELD_GET(MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP, hw_bits)) |
+	      FIELD_PREP(DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD,
+			 FIELD_GET(MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD, hw_bits));
+
+	return chs;
+}
+
+/**
+ * mtk_pci_register_ext_evt() - Register a callback for MHCCIF device events
+ * @mdev: MTK MD device
+ * @chs: Bitmask of event channels to register
+ * @evt_cb: Callback function
+ * @data: Private data passed to callback
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_register_ext_evt(struct mtk_md_dev *mdev, u32 chs,
+			     int (*evt_cb)(u32 status, void *data), void *data)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	struct mtk_mhccif_cb *cb;
+	int ret = 0;
+
+	if (!chs || !evt_cb)
+		return -EINVAL;
+
+	spin_lock_bh(&priv->mhccif_lock);
+	list_for_each_entry(cb, &priv->mhccif_cb_list, entry) {
+		if (cb->chs & chs) {
+			ret = -EFAULT;
+			dev_err(mdev->dev,
+				"Unable to register evt, intersection: chs=0x%08x&0x%08x cb=%ps\n",
+				chs, cb->chs, cb->evt_cb);
+			goto err_spin_unlock;
+		}
+	}
+	cb = kzalloc(sizeof(*cb), GFP_ATOMIC);
+	if (!cb) {
+		ret = -ENOMEM;
+		goto err_spin_unlock;
+	}
+	cb->evt_cb = evt_cb;
+	cb->data = data;
+	cb->chs = chs;
+	list_add_tail(&cb->entry, &priv->mhccif_cb_list);
+err_spin_unlock:
+	spin_unlock_bh(&priv->mhccif_lock);
+
+	return ret;
+}
+
+/**
+ * mtk_pci_unregister_ext_evt() - Unregister an MHCCIF device event callback
+ * @mdev: MTK MD device
+ * @chs: Bitmask of event channels to unregister
+ */
+void mtk_pci_unregister_ext_evt(struct mtk_md_dev *mdev, u32 chs)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	struct mtk_mhccif_cb *cb, *next;
+
+	if (!chs)
+		return;
+
+	spin_lock_bh(&priv->mhccif_lock);
+	list_for_each_entry_safe(cb, next, &priv->mhccif_cb_list, entry) {
+		if (cb->chs == chs) {
+			list_del(&cb->entry);
+			kfree(cb);
+			goto out;
+		}
+	}
+	dev_warn(mdev->dev,
+		 "Unable to unregister evt, no chs=0x%08x has been registered.\n", chs);
+out:
+	spin_unlock_bh(&priv->mhccif_lock);
+}
+
+/**
+ * mtk_pci_mask_ext_evt() - Mask (disable) MHCCIF device events
+ * @mdev: MTK MD device
+ * @chs: Bitmask of event channels to mask
+ */
+void mtk_pci_mask_ext_evt(struct mtk_md_dev *mdev, u32 chs)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	u32 hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);
+
+	mtk_pci_write32(mdev, priv->cfg->mhccif_rc_base_addr +
+			MHCCIF_EP2RC_SW_INT_EAP_MASK_SET, hw_bits);
+}
+
+/**
+ * mtk_pci_unmask_ext_evt() - Unmask (enable) MHCCIF device events
+ * @mdev: MTK MD device
+ * @chs: Bitmask of event channels to unmask
+ */
+void mtk_pci_unmask_ext_evt(struct mtk_md_dev *mdev, u32 chs)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	u32 hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);
+
+	mtk_pci_write32(mdev, priv->cfg->mhccif_rc_base_addr +
+			MHCCIF_EP2RC_SW_INT_EAP_MASK_CLR, hw_bits);
+}
+
+/**
+ * mtk_pci_clear_ext_evt() - Clear (acknowledge) MHCCIF device events
+ * @mdev: MTK MD device
+ * @chs: Bitmask of event channels to clear
+ */
+void mtk_pci_clear_ext_evt(struct mtk_md_dev *mdev, u32 chs)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	u32 hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);
+
+	mtk_pci_write32(mdev, priv->cfg->mhccif_rc_base_addr +
+			MHCCIF_EP2RC_SW_INT_ACK, hw_bits);
+}
+
+static u32 mtk_pci_ext_h2d_evt_hw_bits(u32 chs)
+{
+	u32 hw_bits = 0;
+
+	SET_HW_BITS(hw_bits, chs, MHCCIF_RC2EP_EVT_DEVICE_RESET,
+		    DEV_EVT_H2D_DEVICE_RESET);
+
+	return LE32_TO_U32(cpu_to_le32(hw_bits));
+}
+
+/**
+ * mtk_pci_send_ext_evt() - Send an MHCCIF event to the modem
+ * @mdev: MTK MD device
+ * @ch: Event channel to trigger (must be a single bit)
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_send_ext_evt(struct mtk_md_dev *mdev, u32 ch)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	u32 rc_base, hw_bits;
+
+	rc_base = priv->cfg->mhccif_rc_base_addr;
+
+	/* Only allow one ch to be triggered at a time */
+	if (!is_power_of_2(ch)) {
+		dev_err(mdev->dev, "Unsupported ext evt ch=0x%08x\n", ch);
+		return -EINVAL;
+	}
+
+	hw_bits = mtk_pci_ext_h2d_evt_hw_bits(ch);
+	mtk_pci_write32(mdev, rc_base + MHCCIF_RC2EP_SW_BSY, hw_bits);
+	mtk_pci_write32(mdev, rc_base + MHCCIF_RC2EP_SW_TCHNUM, ffs(hw_bits) - 1);
+	return 0;
+}
+
+static u32 mtk_pci_get_ext_evt_hw_status(struct mtk_md_dev *mdev)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+
+	return mtk_pci_read32(mdev, priv->cfg->mhccif_rc_base_addr +
+			      MHCCIF_EP2RC_SW_INT_STS);
+}
+
+/**
+ * mtk_pci_fldr() - Perform a Function Level Device Reset via ACPI _RST
+ * @mdev: MTK MD device
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_fldr(struct mtk_md_dev *mdev)
+{
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	acpi_status acpi_ret;
+	acpi_handle handle;
+
+	if (acpi_disabled) {
+		dev_err(mdev->dev, "Unsupported, acpi function isn't enable\n");
+		return -ENODEV;
+	}
+
+	handle = ACPI_HANDLE(mdev->dev);
+
+	if (!handle) {
+		dev_err(mdev->dev, "Unsupported, acpi handle isn't found\n");
+		return -ENODEV;
+	}
+
+	if (!acpi_has_method(handle, "_RST")) {
+		dev_err(mdev->dev, "Unsupported, _RST method isn't found\n");
+		return -ENODEV;
+	}
+
+	acpi_ret = acpi_evaluate_object(handle, "_RST", NULL, &buffer);
+	if (ACPI_FAILURE(acpi_ret)) {
+		dev_err(mdev->dev, "Failed to execute _RST method: %s\n",
+			acpi_format_exception(acpi_ret));
+		return -EFAULT;
+	}
+
+	acpi_os_free(buffer.pointer);
+
+	return 0;
+}
+
+/**
+ * mtk_pci_pldr() - Perform a PCIe Link Down Reset via ACPI PXP._OFF/_ON
+ * @mdev: MTK MD device
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_pldr(struct mtk_md_dev *mdev)
+{
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct pci_dev *bridge;
+	acpi_status acpi_ret;
+	acpi_handle handle;
+
+	if (acpi_disabled) {
+		dev_err(mdev->dev, "Unsupported, acpi function isn't enable\n");
+		return -ENODEV;
+	}
+
+	bridge = pci_upstream_bridge(to_pci_dev(mdev->dev));
+	if (!bridge) {
+		dev_err(mdev->dev, "Unable to find bridge\n");
+		return -ENODEV;
+	}
+
+	handle = ACPI_HANDLE(&bridge->dev);
+	if (!handle) {
+		dev_err(mdev->dev, "Unsupported, acpi handle isn't found\n");
+		return -ENODEV;
+	}
+	if (!acpi_has_method(handle, "PXP._OFF") ||
+	    !acpi_has_method(handle, "PXP._ON")) {
+		dev_err(mdev->dev, "Unsupported, pldr method isn't supported\n");
+		return -ENODEV;
+	}
+	acpi_ret = acpi_evaluate_object(handle, "PXP._OFF", NULL, &buffer);
+	if (ACPI_FAILURE(acpi_ret)) {
+		dev_err(mdev->dev, "Failed to execute _OFF method: %s\n",
+			acpi_format_exception(acpi_ret));
+		return -EFAULT;
+	}
+	acpi_os_free(buffer.pointer);
+
+	msleep(MTK_PLDR_POWER_OFF_DELAY_MS);
+
+	buffer.length = ACPI_ALLOCATE_BUFFER;
+	buffer.pointer = NULL;
+	acpi_ret = acpi_evaluate_object(handle, "PXP._ON", NULL, &buffer);
+	if (ACPI_FAILURE(acpi_ret)) {
+		dev_err(mdev->dev, "Failed to execute _ON method: %s\n",
+			acpi_format_exception(acpi_ret));
+		return -EFAULT;
+	}
+	acpi_os_free(buffer.pointer);
+
+	return 0;
+}
+
+/**
+ * mtk_pci_get_dev_cfg() - Read the device configuration from the modem
+ * @mdev: MTK MD device
+ *
+ * Return: Device configuration value.
+ */
+u32 mtk_pci_get_dev_cfg(struct mtk_md_dev *mdev)
+{
+	u32 val;
+
+	val = mtk_pci_mac_read32(mdev->hw_priv, REG_PCIE_DEBUG_DUMMY_4);
+	return (val >> MTK_CFG_INFO_BIT_SHIFT);
+}
+
+static int mtk_pci_dev_reset(struct mtk_md_dev *mdev, enum mtk_reset_type type)
+{
+	switch (type) {
+	case RESET_MHCCIF:
+		return mtk_pci_send_ext_evt(mdev, DEV_EVT_H2D_DEVICE_RESET);
+	case RESET_FLDR:
+		return mtk_pci_fldr(mdev);
+	case RESET_PLDR:
+		return mtk_pci_pldr(mdev);
+	default:
+		return -EINVAL;
+	}
+}
+
+/**
+ * mtk_pci_reset() - Reset the modem device
+ * @mdev: MTK MD device
+ * @type: Reset type (MHCCIF, FLDR, or PLDR)
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_reset(struct mtk_md_dev *mdev, enum mtk_reset_type type)
+{
+	return mtk_pci_dev_reset(mdev, type);
+}
+
+/**
+ * mtk_pci_link_check() - Check if the PCIe link to the modem is active
+ * @mdev: MTK MD device
+ *
+ * Return: true if the device is present, false otherwise.
+ */
+bool mtk_pci_link_check(struct mtk_md_dev *mdev)
+{
+	return pci_device_is_present(to_pci_dev(mdev->dev));
+}
+
+static void mtk_mhccif_isr_work(struct work_struct *work)
+{
+	struct mtk_pci_priv *priv =
+		container_of(work, struct mtk_pci_priv, mhccif_work);
+	struct mtk_md_dev *mdev = priv->irq_desc->mdev;
+	struct mtk_mhccif_cb *cb;
+	u32 stat, mask, chs;
+
+	stat = mtk_pci_get_ext_evt_hw_status(mdev);
+	mask = mtk_pci_read32(mdev, priv->cfg->mhccif_rc_base_addr
+		+ MHCCIF_EP2RC_SW_INT_EAP_MASK);
+	if (unlikely(stat == U32_MAX && !(mtk_pci_link_check(mdev)))) {
+		/* When link failed, we don't need to unmask/clear. */
+		dev_err(mdev->dev, "Failed to check link in MHCCIF handler.\n");
+		return;
+	}
+
+	stat &= ~mask;
+	chs = mtk_pci_ext_d2h_evt_chs(stat);
+	spin_lock_bh(&priv->mhccif_lock);
+	list_for_each_entry(cb, &priv->mhccif_cb_list, entry) {
+		if (cb->chs & chs)
+			cb->evt_cb(cb->chs & chs, cb->data);
+	}
+	spin_unlock_bh(&priv->mhccif_lock);
+
+	mtk_pci_clear_irq(mdev, priv->mhccif_irq_id);
+	mtk_pci_unmask_irq(mdev, priv->mhccif_irq_id);
+}
+
+static const struct  pci_device_id t9xx_pci_table[] = {
+	MTK_PCI_DEV_CFG(0x0900, mtk_dev_cfg_0900),
+	CEI_PCI_DEV_CFG(0x01CA, mtk_dev_cfg_0900),
+	{/* end: all zeroes */}
+};
+
+MODULE_DEVICE_TABLE(pci, t9xx_pci_table);
+
+static int mtk_pci_bar_init(struct mtk_md_dev *mdev)
+{
+	struct pci_dev *pdev = to_pci_dev(mdev->dev);
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	u32 bar[MTK_PCI_BAR_NUM];
+	int i, ret;
+
+	for (i = 0; i < MTK_PCI_BAR_NUM; i++)
+		pci_read_config_dword(to_pci_dev(mdev->dev),
+				      PCI_BASE_ADDRESS_0 + (i << 2), bar + i);
+
+	ret = pcim_iomap_regions(pdev, MTK_REQUESTED_BARS, mdev->dev_str);
+	if (ret) {
+		dev_err(mdev->dev, "Failed to init MMIO. ret=%d\n", ret);
+		return ret;
+	}
+
+	/* get ioremapped memory */
+	priv->mac_reg_base = pcim_iomap_table(pdev)[MTK_BAR_0_1_IDX];
+	priv->bar23_addr = pcim_iomap_table(pdev)[MTK_BAR_2_3_IDX];
+	if (!priv->mac_reg_base || !priv->bar23_addr) {
+		dev_err(mdev->dev, "Failed to init BAR.\n");
+		return -EINVAL;
+	}
+	/* We use MD view base address "0" to observe registers */
+	priv->ext_reg_base = priv->bar23_addr - ATR_PCIE_REG_TRSL_ADDR;
+
+	return 0;
+}
+
+static int mtk_mhccif_irq_cb(int irq_id, void *data)
+{
+	struct mtk_md_dev *mdev = data;
+	struct mtk_pci_priv *priv;
+
+	priv = mdev->hw_priv;
+	queue_work(system_highpri_wq, &priv->mhccif_work);
+
+	return 0;
+}
+
+static int mtk_mhccif_init(struct mtk_md_dev *mdev)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	int ret;
+
+	INIT_LIST_HEAD(&priv->mhccif_cb_list);
+	spin_lock_init(&priv->mhccif_lock);
+	INIT_WORK(&priv->mhccif_work, mtk_mhccif_isr_work);
+
+	ret = mtk_pci_get_irq_id(mdev, MTK_IRQ_SRC_MHCCIF);
+	if (ret < 0) {
+		dev_err(mdev->dev, "Failed to get mhccif_irq_id. ret=%d\n", ret);
+		return ret;
+	}
+	priv->mhccif_irq_id = ret;
+
+	ret = mtk_pci_register_irq(mdev, priv->mhccif_irq_id, mtk_mhccif_irq_cb, mdev);
+	if (ret) {
+		dev_err(mdev->dev, "Failed to register mhccif_irq callback\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static void mtk_mhccif_exit(struct mtk_md_dev *mdev)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+
+	mtk_pci_unregister_irq(mdev, priv->mhccif_irq_id);
+	cancel_work_sync(&priv->mhccif_work);
+}
+
+static irqreturn_t mtk_pci_irq_handler(struct mtk_md_dev *mdev, u32 irq_state)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	int irq_id;
+
+	/* Check whether each set bit has a callback, if has, call it */
+	do {
+		irq_id = fls(irq_state) - 1;
+		irq_state &= ~BIT(irq_id);
+		if (likely(priv->irq_cb_list[irq_id]))
+			priv->irq_cb_list[irq_id](irq_id, priv->irq_cb_data[irq_id]);
+		else
+			dev_err(mdev->dev, "Unhandled irq_id=%d, no callback for it.\n", irq_id);
+	} while (irq_state);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t mtk_pci_irq_msix(int irq, void *data)
+{
+	struct mtk_pci_irq_desc *irq_desc = data;
+	struct mtk_md_dev *mdev = irq_desc->mdev;
+	struct mtk_pci_priv *priv;
+	u32 irq_state, irq_enable;
+
+	priv = mdev->hw_priv;
+	irq_state = mtk_pci_mac_read32(priv, REG_MSIX_ISTATUS_HOST_GRP0_0);
+	irq_enable = mtk_pci_mac_read32(priv, REG_IMASK_HOST_MSIX_GRP0_0);
+	irq_state &= irq_enable;
+
+	if (unlikely(!irq_state) ||
+	    unlikely(!((irq_state & GENMASK(priv->irq_cnt - 1, 0)) &
+		      irq_desc->msix_bits)))
+		return IRQ_NONE;
+
+	/* Mask the bit and user needs to unmask by itself */
+	mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0,
+			    irq_state & ~BIT(30));
+
+	return mtk_pci_irq_handler(mdev, irq_state);
+}
+
+static int mtk_pci_request_irq_msix(struct mtk_md_dev *mdev,
+				    int irq_cnt_allocated)
+{
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	struct mtk_pci_irq_desc *irq_desc;
+	struct pci_dev *pdev;
+	int irq_cnt;
+	int ret, i;
+
+	/* calculate the nearest 2's power number */
+	irq_cnt = BIT(fls(irq_cnt_allocated) - 1);
+	pdev = to_pci_dev(mdev->dev);
+	irq_desc = priv->irq_desc;
+	for (i = 0; i < irq_cnt; i++) {
+		irq_desc[i].mdev = mdev;
+		irq_desc[i].msix_bits = BIT(i);
+		snprintf(irq_desc[i].name, MTK_IRQ_NAME_LEN, "msix%d-%s", i, mdev->dev_str);
+		ret = pci_request_irq(pdev, i, mtk_pci_irq_msix, NULL,
+				      &irq_desc[i], irq_desc[i].name);
+		if (ret) {
+			dev_err(mdev->dev, "Failed to request %s: ret=%d\n",
+				irq_desc[i].name, ret);
+			for (i--; i >= 0; i--)
+				pci_free_irq(pdev, i, &irq_desc[i]);
+			return ret;
+		}
+	}
+	priv->irq_cnt = irq_cnt;
+	priv->irq_type = PCI_IRQ_MSIX;
+
+	if (irq_cnt != MTK_IRQ_CNT_MAX)
+		mtk_pci_set_msix_merged(priv, irq_cnt);
+
+	return 0;
+}
+
+static int mtk_pci_request_irq(struct mtk_md_dev *mdev)
+{
+	struct pci_dev *pdev = to_pci_dev(mdev->dev);
+	int irq_cnt, ret;
+
+	irq_cnt = pci_alloc_irq_vectors(pdev, MTK_IRQ_CNT_MIN,
+					MTK_IRQ_CNT_MAX, PCI_IRQ_MSIX);
+
+	if (irq_cnt < MTK_IRQ_CNT_MIN) {
+		dev_err(mdev->dev,
+			"Unable to alloc pci irq vectors. ret=%d maxirqcnt=%d irqtype=0x%x\n",
+			irq_cnt, MTK_IRQ_CNT_MAX, PCI_IRQ_MSIX);
+		return -EFAULT;
+	}
+
+	ret = mtk_pci_request_irq_msix(mdev, irq_cnt);
+	if (ret)
+		pci_free_irq_vectors(pdev);
+
+	return ret;
+}
+
+static void mtk_pci_free_irq(struct mtk_md_dev *mdev)
+{
+	struct pci_dev *pdev = to_pci_dev(mdev->dev);
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	int i;
+
+	for (i = 0; i < priv->irq_cnt; i++)
+		pci_free_irq(pdev, i, &priv->irq_desc[i]);
+
+	pci_free_irq_vectors(pdev);
+}
+
+static int mtk_pci_dev_init(struct mtk_md_dev *mdev)
+{
+	int ret;
+
+	ret = mtk_fsm_init(mdev);
+	if (ret) {
+		dev_err(mdev->dev, "Failed to initialize FSM: %d\n", ret);
+		return ret;
+	}
+
+	ret = mtk_trans_ctrl_init(mdev);
+	if (ret)
+		goto free_fsm;
+
+	return 0;
+free_fsm:
+	mtk_fsm_exit(mdev);
+	return ret;
+}
+
+static void mtk_pci_dev_exit(struct mtk_md_dev *mdev)
+{
+	mtk_fsm_evt_submit(mdev, FSM_EVT_DEV_RM, 0, NULL, 0,
+			   EVT_MODE_BLOCKING | EVT_MODE_TOHEAD);
+	mtk_trans_ctrl_exit(mdev);
+	mtk_fsm_exit(mdev);
+}
+
+static int mtk_pci_dev_start(struct mtk_md_dev *mdev)
+{
+	mtk_fsm_evt_submit(mdev, FSM_EVT_DEV_ADD, 0, NULL, 0, 0);
+	mtk_fsm_start(mdev);
+	return 0;
+}
+static const struct mtk_dev_ops pci_hw_ops = {
+	.get_dev_state = mtk_pci_get_dev_state,
+	.ack_dev_state = mtk_pci_ack_dev_state,
+	.get_dev_cfg = mtk_pci_get_dev_cfg,
+	.register_dev_evt = mtk_pci_register_ext_evt,
+	.unregister_dev_evt = mtk_pci_unregister_ext_evt,
+	.mask_dev_evt = mtk_pci_mask_ext_evt,
+	.unmask_dev_evt = mtk_pci_unmask_ext_evt,
+	.clear_dev_evt = mtk_pci_clear_ext_evt,
+	.send_dev_evt = mtk_pci_send_ext_evt,
+};
+
+static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_pci_priv *priv;
+	struct mtk_md_dev *mdev;
+	int ret;
+
+	mdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL);
+	if (!mdev) {
+		ret = -ENOMEM;
+		goto log_err;
+	}
+	mdev->dev_ops = &pci_hw_ops;
+	mdev->dev = dev;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		ret = -ENOMEM;
+		goto log_err;
+	}
+
+	pci_set_drvdata(pdev, mdev);
+	priv->cfg = (void *)id->driver_data;
+	priv->mdev = mdev;
+	mdev->hw_ver  = pdev->device;
+	mdev->hw_priv = priv;
+	mdev->dev     = dev;
+	snprintf(mdev->dev_str, MTK_DEV_STR_LEN, "%02x%02x%d",
+		 pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));
+	if (pdev->state_saved)
+		pci_restore_state(pdev);
+
+	ret = pcim_enable_device(pdev);
+	if (ret) {
+		dev_err(mdev->dev, "Failed to enable pci device.\n");
+		goto log_err;
+	}
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+	if (ret) {
+		dev_err(mdev->dev, "Failed to set DMA Mask and Coherent. (ret=%d)\n", ret);
+		goto log_err;
+	}
+
+	ret = mtk_pci_bar_init(mdev);
+	if (ret)
+		goto log_err;
+
+	ret = priv->cfg->atr_init(mdev);
+	if (ret)
+		goto log_err;
+
+	ret = mtk_mhccif_init(mdev);
+	if (ret)
+		goto log_err;
+
+	/* mask all irqs */
+	if (priv->cfg->flag & MTK_CFG_IRQ_DFLT_MASK)
+		mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, U32_MAX);
+
+	ret = mtk_pci_request_irq(mdev);
+	if (ret)
+		goto free_mhccif;
+
+	ret = mtk_pci_dev_init(mdev);
+	if (ret) {
+		dev_err(mdev->dev, "Failed to init dev.\n");
+		goto free_irq;
+	}
+
+	pci_set_master(pdev);
+	mtk_pci_unmask_irq(mdev, priv->mhccif_irq_id);
+
+	if (mtk_pci_link_check(mdev)) {
+		pci_save_state(pdev);
+	} else {
+		ret = -ENOLINK;
+		goto clear_master;
+	}
+
+	priv->saved_state = pci_store_saved_state(pdev);
+	if (!priv->saved_state) {
+		ret = -EFAULT;
+		goto clear_master;
+	}
+
+	ret = mtk_pci_dev_start(mdev);
+	if (ret) {
+		dev_err(mdev->dev, "Failed to start dev.\n");
+		goto free_saved_state;
+	}
+
+	return 0;
+
+free_saved_state:
+	pci_load_and_free_saved_state(pdev, &priv->saved_state);
+clear_master:
+	pci_clear_master(pdev);
+	mtk_pci_dev_exit(mdev);
+free_irq:
+	mtk_pci_free_irq(mdev);
+free_mhccif:
+	mtk_mhccif_exit(mdev);
+log_err:
+	dev_err(dev, "Failed to probe device, ret=%d\n", ret);
+
+	return ret;
+}
+
+static void mtk_pci_remove(struct pci_dev *pdev)
+{
+	struct mtk_md_dev *mdev = pci_get_drvdata(pdev);
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	struct device *dev = &pdev->dev;
+
+	mtk_pci_mask_irq(mdev, priv->mhccif_irq_id);
+
+	if (mtk_pci_pldr(mdev)) {
+		dev_warn(dev, "Failed to execute PLDR, try external event\n");
+		mtk_pci_reset(mdev, RESET_MHCCIF);
+	}
+
+	pci_clear_master(pdev);
+	mtk_pci_dev_exit(mdev);
+	mtk_pci_free_irq(mdev);
+	mtk_mhccif_exit(mdev);
+	pci_load_and_free_saved_state(pdev, &priv->saved_state);
+}
+
+static pci_ers_result_t mtk_pci_error_detected(struct pci_dev *pdev,
+					       pci_channel_state_t state)
+{
+	struct mtk_md_dev *mdev = pci_get_drvdata(pdev);
+
+	dev_err(mdev->dev, "AER detected: pci_channel_state_t=%d\n", state);
+
+	/* Request a slot reset. */
+	return PCI_ERS_RESULT_CAN_RECOVER;
+}
+
+static const struct pci_error_handlers mtk_pci_err_handler = {
+	.error_detected = mtk_pci_error_detected,
+};
+
+static struct pci_driver mtk_pci_drv = {
+	.name = "mtk_pci_drv",
+	.id_table = t9xx_pci_table,
+	.probe = mtk_pci_probe,
+	.remove = mtk_pci_remove,
+	.err_handler = &mtk_pci_err_handler
+};
+
+module_pci_driver(mtk_pci_drv);
+
+MODULE_DESCRIPTION("MediaTek T9xx PCIe WWAN driver pcie layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.h b/drivers/net/wwan/t9xx/pcie/mtk_pci.h
new file mode 100644
index 00000000000000..9819a1b07c1b3d
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.h
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_PCI_H__
+#define __MTK_PCI_H__
+
+#include <linux/pci.h>
+
+#include "../mtk_dev.h"
+
+enum mtk_irq_src {
+	MTK_IRQ_SRC_MIN,
+	MTK_IRQ_SRC_MHCCIF,
+	MTK_IRQ_SRC_DPMAIF,
+	MTK_IRQ_SRC_DPMAIF2,
+	MTK_IRQ_SRC_CLDMA0,
+	MTK_IRQ_SRC_CLDMA1,
+	MTK_IRQ_SRC_CLDMA2,
+	MTK_IRQ_SRC_CLDMA3,
+	MTK_IRQ_SRC_PM_LOCK,
+	MTK_IRQ_SRC_DPMAIF3,
+	MTK_IRQ_SRC_DPMAIF6,
+	MTK_IRQ_SRC_MAX
+};
+
+enum mtk_reset_type {
+	RESET_FLDR,
+	RESET_PLDR,
+	RESET_MHCCIF,
+};
+
+enum mtk_atr_type {
+	ATR_PCI2AXI = 0,
+	ATR_AXI2PCI,
+};
+
+enum mtk_atr_src_port {
+	ATR_SRC_PCI_WIN0 = 0,
+	ATR_SRC_PCI_WIN1,
+	ATR_SRC_AXIS_0,
+	ATR_SRC_AXIS_1,
+	ATR_SRC_AXIS_2,
+	ATR_SRC_AXIS_3,
+};
+
+enum mtk_atr_dst_port {
+	ATR_DST_PCI_TRX = 0,
+	ATR_DST_AXIM_0 = 4,
+	ATR_DST_AXIM_1,
+	ATR_DST_AXIM_2,
+	ATR_DST_AXIM_3,
+};
+
+enum mtk_pci_evt_h2d {
+	DEV_EVT_H2D_EXTEND_BASE            = DEV_EVT_H2D_MAX,
+	EXT_EVT_H2D_RESERVED_FOR_CLDMA0    = DEV_EVT_H2D_EXTEND_BASE << 1,
+	EXT_EVT_H2D_RESERVED_FOR_CLDMA1    = DEV_EVT_H2D_EXTEND_BASE << 2,
+	EXT_EVT_H2D_RESERVED_FOR_CLDMA3    = DEV_EVT_H2D_EXTEND_BASE << 3,
+	EXT_EVT_H2D_RESERVED_FOR_CLDMA2    = DEV_EVT_H2D_EXTEND_BASE << 4,
+	EXT_EVT_H2D_RESERVED_FOR_DPMAIF    = DEV_EVT_H2D_EXTEND_BASE << 5,
+	EXT_EVT_H2D_PCIE_PM_SUSPEND_REQ    = DEV_EVT_H2D_EXTEND_BASE << 6,
+	EXT_EVT_H2D_PCIE_PM_RESUME_REQ     = DEV_EVT_H2D_EXTEND_BASE << 7,
+	EXT_EVT_H2D_PCIE_PM_SUSPEND_REQ_AP = DEV_EVT_H2D_EXTEND_BASE << 8,
+	EXT_EVT_H2D_PCIE_PM_RESUME_REQ_AP  = DEV_EVT_H2D_EXTEND_BASE << 9,
+	EXT_EVT_H2D_RESERVED_FOR_TEST      = DEV_EVT_H2D_EXTEND_BASE << 11,
+};
+
+enum mtk_pci_evt_d2h {
+	DEV_EVT_D2H_EXTEND_BASE            = DEV_EVT_D2H_MAX,
+	EXT_EVT_D2H_RESERVED_FOR_CLDMA0    = DEV_EVT_D2H_EXTEND_BASE << 1,
+	EXT_EVT_D2H_RESERVED_FOR_CLDMA1    = DEV_EVT_D2H_EXTEND_BASE << 2,
+	EXT_EVT_D2H_RESERVED_FOR_CLDMA3    = DEV_EVT_D2H_EXTEND_BASE << 3,
+	EXT_EVT_D2H_RESERVED_FOR_CLDMA2    = DEV_EVT_D2H_EXTEND_BASE << 4,
+	EXT_EVT_D2H_RESERVED_FOR_DPMAIF    = DEV_EVT_D2H_EXTEND_BASE << 5,
+	EXT_EVT_D2H_PCIE_PM_SUSPEND_ACK    = DEV_EVT_D2H_EXTEND_BASE << 6,
+	EXT_EVT_D2H_PCIE_PM_RESUME_ACK     = DEV_EVT_D2H_EXTEND_BASE << 7,
+	EXT_EVT_D2H_PCIE_PM_SUSPEND_ACK_AP = DEV_EVT_D2H_EXTEND_BASE << 8,
+	EXT_EVT_D2H_PCIE_PM_RESUME_ACK_AP  = DEV_EVT_D2H_EXTEND_BASE << 9,
+	EXT_EVT_D2H_SOFT_OFF_NOTIFY        = DEV_EVT_D2H_EXTEND_BASE << 10,
+	EXT_EVT_D2H_FRC_DONE_NOTIFY        = DEV_EVT_D2H_EXTEND_BASE << 11,
+	EXT_EVT_D2H_RESERVED_FOR_TEST1	   = DEV_EVT_D2H_EXTEND_BASE << 12,
+	EXT_EVT_D2H_RESERVED_FOR_TEST2	   = DEV_EVT_D2H_EXTEND_BASE << 13,
+};
+
+#define MTK_PCI_CLASS                 0x0D4000
+#define MTK_PCI_VENDOR_ID             0x14C3
+#define CEI_PCI_VENDOR_ID             0x03F0
+
+#define MTK_CFG_INFO_BIT_SHIFT        4
+
+#define MTK_PCI_DEV_CFG(id, cfg) \
+{ \
+	PCI_DEVICE(MTK_PCI_VENDOR_ID, id), \
+	MTK_PCI_CLASS, PCI_ANY_ID, \
+	.driver_data = (kernel_ulong_t)&(cfg), \
+}
+
+#define CEI_PCI_DEV_CFG(id, cfg) \
+{ \
+	PCI_DEVICE(CEI_PCI_VENDOR_ID, id), \
+	MTK_PCI_CLASS, PCI_ANY_ID, \
+	.driver_data = (kernel_ulong_t)&(cfg), \
+}
+
+#define MTK_CFG_IRQ_DFLT_MASK		BIT(0)
+#define MTK_CFG_DISABLE_AP_DRM		BIT(2)
+#define MTK_CFG_PM_SW_IRQ		BIT(6)
+
+#define MTK_BAR_0_1_IDX                 0
+#define MTK_BAR_2_3_IDX                 2
+
+#define MTK_REQUESTED_BARS \
+	((1 << MTK_BAR_0_1_IDX) | \
+	 (1 << MTK_BAR_2_3_IDX))
+
+#define MTK_IRQ_CNT_MIN				1
+#define MTK_IRQ_CNT_MAX				32
+#define MTK_IRQ_NAME_LEN			32
+
+#define ATR_PORT_OFFSET				0x100
+#define ATR_TABLE_OFFSET			0x20
+#define ATR_TABLE_NUM_PER_ATR			8
+#define ATR_PCIE_REG_TRSL_ADDR			0x10000000
+#define ATR_PCIE_REG_SIZE			0x00400000
+#define ATR_PCIE_REG_PORT			ATR_SRC_PCI_WIN0
+#define ATR_PCIE_REG_TABLE_NUM			1
+#define ATR_PCIE_REG_TRSL_PORT			ATR_DST_AXIM_0
+#define ATR_PCIE_DEV_DMA_SRC_ADDR		0x00000000
+#define ATR_PCIE_DEV_DMA_TRANSPARENT		1
+#define ATR_PCIE_DEV_DMA_SIZE			0
+#define ATR_PCIE_DEV_DMA_TABLE_NUM		0
+#define ATR_PCIE_DEV_DMA_TRSL_ADDR		0x00000000
+
+struct mtk_pci_irq_desc {
+	struct mtk_md_dev *mdev;
+	u32 msix_bits;
+	char name[MTK_IRQ_NAME_LEN];
+};
+
+struct mtk_pci_dev_cfg {
+	u32 flag;
+	u32 mhccif_rc_base_addr;
+	u32 istatus_host_ctrl_addr;
+	int irq_tbl[MTK_IRQ_SRC_MAX];
+	int (*atr_init)(struct mtk_md_dev *mdev);
+};
+
+extern const struct mtk_pci_dev_cfg mtk_dev_cfg_0900;
+
+struct mtk_pci_priv {
+	struct mtk_md_dev *mdev;
+	const struct mtk_pci_dev_cfg *cfg;
+	void __iomem *bar23_addr;
+	void __iomem *mac_reg_base;
+	void __iomem *ext_reg_base;
+	int irq_cnt;
+	int irq_type;
+	void *irq_cb_data[MTK_IRQ_CNT_MAX];
+
+	int (*irq_cb_list[MTK_IRQ_CNT_MAX])(int irq_id, void *data);
+	struct mtk_pci_irq_desc irq_desc[MTK_IRQ_CNT_MAX];
+	struct list_head mhccif_cb_list;
+	/* mhccif_lock: lock to protect mhccif_cb_list */
+	spinlock_t mhccif_lock;
+	struct work_struct mhccif_work;
+	int mhccif_irq_id;
+	struct pci_saved_state *saved_state;
+};
+
+struct mtk_atr_cfg {
+	u64 src_addr;
+	u64 trsl_addr;
+	u64 size;
+	u32 type;      /* Port type */
+	u32 port;      /* Port number */
+	u32 table;     /* Table number (8 tables for each port) */
+	u32 trsl_id;
+	u32 trsl_param;
+	u32 transparent;
+};
+
+/* BAR 0/1 MMIO access */
+static inline u32 mtk_pci_mac_read32(struct mtk_pci_priv *priv, u64 addr)
+{
+	return ioread32(priv->mac_reg_base + addr);
+}
+
+static inline void mtk_pci_mac_write32(struct mtk_pci_priv *priv, u64 addr, u32 val)
+{
+	iowrite32(val, priv->mac_reg_base + addr);
+}
+
+/* BAR 2/3 MMIO access */
+static inline u32 mtk_pci_read32(struct mtk_md_dev *mdev, u64 addr)
+{
+	return ioread32(((struct mtk_pci_priv *)mdev->hw_priv)->ext_reg_base + addr);
+}
+
+static inline void mtk_pci_write32(struct mtk_md_dev *mdev, u64 addr, u32 val)
+{
+	iowrite32(val, ((struct mtk_pci_priv *)mdev->hw_priv)->ext_reg_base + addr);
+}
+
+/* Device operations */
+u32 mtk_pci_get_dev_state(struct mtk_md_dev *mdev);
+void mtk_pci_ack_dev_state(struct mtk_md_dev *mdev, u32 state);
+u32 mtk_pci_get_dev_cfg(struct mtk_md_dev *mdev);
+/* IRQ Related operations */
+int mtk_pci_get_irq_id(struct mtk_md_dev *mdev, enum mtk_irq_src irq_src);
+int mtk_pci_get_virq_id(struct mtk_md_dev *mdev, int irq_id);
+int mtk_pci_register_irq(struct mtk_md_dev *mdev, int irq_id,
+			 int (*irq_cb)(int irq_id, void *data), void *data);
+int mtk_pci_unregister_irq(struct mtk_md_dev *mdev, int irq_id);
+int mtk_pci_mask_irq(struct mtk_md_dev *mdev, int irq_id);
+int mtk_pci_unmask_irq(struct mtk_md_dev *mdev, int irq_id);
+int mtk_pci_clear_irq(struct mtk_md_dev *mdev, int irq_id);
+/* External event related */
+int mtk_pci_register_ext_evt(struct mtk_md_dev *mdev, u32 chs,
+			     int (*evt_cb)(u32 status, void *data), void *data);
+void mtk_pci_unregister_ext_evt(struct mtk_md_dev *mdev, u32 chs);
+void mtk_pci_mask_ext_evt(struct mtk_md_dev *mdev, u32 chs);
+void mtk_pci_unmask_ext_evt(struct mtk_md_dev *mdev, u32 chs);
+void mtk_pci_clear_ext_evt(struct mtk_md_dev *mdev, u32 chs);
+int mtk_pci_send_ext_evt(struct mtk_md_dev *mdev, u32 ch);
+int mtk_pci_fldr(struct mtk_md_dev *mdev);
+int mtk_pci_pldr(struct mtk_md_dev *mdev);
+int mtk_pci_reset(struct mtk_md_dev *mdev, enum mtk_reset_type type);
+bool mtk_pci_link_check(struct mtk_md_dev *mdev);
+int mtk_pci_setup_atr(struct mtk_md_dev *mdev, struct mtk_atr_cfg *cfg);
+void mtk_pci_atr_disable(struct mtk_pci_priv *priv);
+
+#endif /* __MTK_PCI_H__ */
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c
new file mode 100644
index 00000000000000..88b44142afb79c
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+#include <linux/types.h>
+#include "mtk_pci.h"
+#include "mtk_pci_reg.h"
+
+static int mtk_pci_atr_init_m9xx(struct mtk_md_dev *mdev)
+{
+	struct pci_dev *pdev = to_pci_dev(mdev->dev);
+	struct mtk_pci_priv *priv = mdev->hw_priv;
+	struct mtk_atr_cfg cfg;
+	int port, ret;
+
+	mtk_pci_atr_disable(priv);
+
+	/* Config ATR for RC to access device's register */
+	cfg.src_addr = pci_resource_start(pdev, MTK_BAR_2_3_IDX);
+	cfg.size = ATR_PCIE_REG_SIZE;
+	cfg.trsl_addr = ATR_PCIE_REG_TRSL_ADDR;
+	cfg.type = ATR_PCI2AXI;
+	cfg.port = ATR_PCIE_REG_PORT;
+	cfg.table = ATR_PCIE_REG_TABLE_NUM;
+	cfg.trsl_id = ATR_PCIE_REG_TRSL_PORT;
+	cfg.trsl_param = 0x0;
+	cfg.transparent = 0x0;
+	ret = mtk_pci_setup_atr(mdev, &cfg);
+	if (ret)
+		return ret;
+
+	/* Config ATR for EP to access RC's memory */
+	for (port = ATR_SRC_AXIS_0; port <= ATR_SRC_AXIS_3; port++) {
+		cfg.src_addr = ATR_PCIE_DEV_DMA_SRC_ADDR;
+		cfg.size = ATR_PCIE_DEV_DMA_SIZE;
+		cfg.trsl_addr = ATR_PCIE_DEV_DMA_TRSL_ADDR;
+		cfg.type = ATR_AXI2PCI;
+		cfg.port = port;
+		cfg.table = ATR_PCIE_DEV_DMA_TABLE_NUM;
+		cfg.trsl_id = ATR_DST_PCI_TRX;
+		cfg.trsl_param = 0x0;
+		/* Enable transparent translation */
+		cfg.transparent = ATR_PCIE_DEV_DMA_TRANSPARENT;
+		ret = mtk_pci_setup_atr(mdev, &cfg);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+const struct mtk_pci_dev_cfg mtk_dev_cfg_0900 = {
+	.flag = MTK_CFG_PM_SW_IRQ,
+	.mhccif_rc_base_addr = 0x1000A000,
+	.istatus_host_ctrl_addr = REG_ISTATUS_HOST_CTRL_NEW,
+	.irq_tbl = {
+		[MTK_IRQ_SRC_DPMAIF]  = 24,
+		[MTK_IRQ_SRC_CLDMA0]  = 27,
+		[MTK_IRQ_SRC_CLDMA1]  = 26,
+		[MTK_IRQ_SRC_CLDMA2]  = 25,
+		[MTK_IRQ_SRC_MHCCIF]  = 28,
+		[MTK_IRQ_SRC_DPMAIF2] = 29,
+		[MTK_IRQ_SRC_CLDMA3]  = 31,
+		[MTK_IRQ_SRC_PM_LOCK] = 0,
+		[MTK_IRQ_SRC_DPMAIF3] = 7,
+		[MTK_IRQ_SRC_DPMAIF6]  = 10,
+	},
+	.atr_init = mtk_pci_atr_init_m9xx,
+};
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h b/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h
new file mode 100644
index 00000000000000..73299ae03f897a
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_PCI_REG_H__
+#define __MTK_PCI_REG_H__
+
+#define REG_ISTATUS_HOST_CTRL_NEW		0x031C
+#define REG_PCIE_MISC_CTRL			0x0348
+#define REG_PCIE_CFG_MSIX			0x03EC
+#define REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB	0x0600
+#define REG_ATR_PCIE_WIN0_T0_SRC_ADDR_MSB	0x0604
+#define REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_LSB	0x0608
+#define REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_MSB	0x060C
+#define REG_ATR_PCIE_WIN0_T0_TRSL_PARAM		0x0610
+#define REG_PCIE_DEBUG_DUMMY_3			0x0D0C
+#define REG_PCIE_DEBUG_DUMMY_4			0x0D10
+#define REG_PCIE_DEBUG_DUMMY_7			0x0D1C
+#define REG_MSIX_ISTATUS_HOST_GRP0_0		0x0F00
+#define REG_IMASK_HOST_MSIX_SET_GRP0_0		0x3000
+#define REG_IMASK_HOST_MSIX_CLR_GRP0_0		0x3080
+#define REG_IMASK_HOST_MSIX_GRP0_0		0x3100
+#define REG_DEV_INFRA_BASE			0x10001000
+
+/* mhccif registers */
+#define MHCCIF_RC2EP_SW_BSY			0x4
+#define MHCCIF_RC2EP_SW_TCHNUM			0xC
+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_CLDMA0	BIT(4)
+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_CLDMA1	BIT(5)
+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_CLDMA3	BIT(6)
+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_CLDMA2	BIT(7)
+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_DPMAIF	BIT(8)
+#define MHCCIF_RC2EP_EVT_PCIE_PM_SUSPEND_REQ	BIT(9)
+#define MHCCIF_RC2EP_EVT_PCIE_PM_RESUME_REQ	BIT(10)
+#define MHCCIF_RC2EP_EVT_PCIE_PM_SUSPEND_REQ_AP	BIT(11)
+#define MHCCIF_RC2EP_EVT_PCIE_PM_RESUME_REQ_AP	BIT(12)
+#define MHCCIF_RC2EP_EVT_DEVICE_RESET		BIT(13)
+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_TEST	BIT(31)
+
+#define MHCCIF_EP2RC_SW_INT_STS			0x10
+#define MHCCIF_EP2RC_SW_INT_ACK			0x14
+#define MHCCIF_EP2RC_SW_INT_EAP_MASK		0x20
+#define MHCCIF_EP2RC_SW_INT_EAP_MASK_SET	0x30
+#define MHCCIF_EP2RC_SW_INT_EAP_MASK_CLR	0x40
+#define MHCCIF_EP2RC_SPARE_REG_1		0x0104
+#define MHCCIF_EP2RC_SPARE_REG_5		0x0114
+#define MHCCIF_EP2RC_SPARE_REG_13		0x0134
+#define MHCCIF_EP2RC_SPARE_REG_14		0x0138
+#define MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC		BIT(5)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_CLDMA0	BIT(6)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_CLDMA1	BIT(7)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_CLDMA3	BIT(8)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_CLDMA2	BIT(9)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_DPMAIF	BIT(10)
+#define MHCCIF_EP2RC_EVT_PCIE_PM_SUSPEND_ACK	BIT(11)
+#define MHCCIF_EP2RC_EVT_PCIE_PM_RESUME_ACK	BIT(12)
+#define MHCCIF_EP2RC_EVT_PCIE_PM_SUSPEND_ACK_AP	BIT(13)
+#define MHCCIF_EP2RC_EVT_PCIE_PM_RESUME_ACK_AP	BIT(14)
+#define MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP	BIT(15)
+#define MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD	BIT(16)
+#define MHCCIF_EP2RC_EVT_SOFT_OFF_NOTIFY	BIT(17)
+#define MHCCIF_EP2RC_EVT_MD_REBOOT		BIT(19)
+#define MHCCIF_EP2RC_EVT_MD_POWEROFF		BIT(20)
+#define MHCCIF_EP2RC_EVT_GNSS_ENABLE		BIT(21)
+#define MHCCIF_EP2RC_EVT_GNSS_DISABLE		BIT(22)
+#define MHCCIF_EP2RC_EVT_FRC_DONE_NOTIFY	BIT(24)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_TEST1	BIT(30)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_TEST2	BIT(31)
+
+#endif /* __MTK_PCI_REG_H__ */
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
new file mode 100644
index 00000000000000..fa36a94014ea42
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/freezer.h>
+#include <linux/hashtable.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/nospec.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+
+#include "mtk_cldma.h"
+#include "mtk_ctrl_plane.h"
+#include "mtk_dev.h"
+#include "mtk_pci.h"
+#include "mtk_port.h"
+#include "mtk_trans_ctrl.h"
+
+#define MTK_DFLT_PORT_NAME_LEN			(20)
+
+static struct mtk_ctrl_info_desc mtk_ctrl_info_tbl[] = {
+	{0x01CA, &ctrl_info_name(m9xx)},
+	{0, NULL},
+};
+
+#define QUEUE_CHL_MASK	0xFFFF
+
+static bool mtk_queue_list_is_full(struct mtk_ctrl_trans *trans, struct queue_info *que)
+{
+	return trans->trans_list[que->hif_id].skb_list[que->txqno].qlen >= SKB_LIST_MAX_LEN;
+}
+
+static bool mtk_ctrl_chs_is_busy_or_empty(struct trb_srv *srv)
+{
+	struct srv_que *srv_que;
+	int i;
+
+	for (i = 0; i < NR_CLDMA; i++)
+		list_for_each_entry(srv_que, &srv->srv_q_list[i], list)
+			if (!skb_queue_empty(&srv->trans->trans_list[i].skb_list[srv_que->qno]) &&
+			    mtk_cldma_get_tx_budget(srv->trans->dev, i, srv_que->qno))
+				return false;
+
+	return true;
+}
+
+static void mtk_ctrl_ch_flush(struct sk_buff_head *skb_list)
+{
+	struct sk_buff *skb;
+	struct trb *trb;
+
+	while (!skb_queue_empty(skb_list)) {
+		skb = skb_dequeue(skb_list);
+		trb = (struct trb *)skb->cb;
+		trb->status = -EIO;
+		trb->trb_complete(skb);
+	}
+}
+
+static void mtk_ctrl_chs_flush(struct trb_srv *srv)
+{
+	struct srv_que *srv_que;
+	int i;
+
+	for (i = 0; i < NR_CLDMA; i++)
+		list_for_each_entry(srv_que, &srv->srv_q_list[i], list)
+			mtk_ctrl_ch_flush(&srv->trans->trans_list[i].skb_list[srv_que->qno]);
+}
+
+static int mtk_ch_status_check(struct mtk_ctrl_trans *trans, struct sk_buff *skb)
+{
+	struct trb *trb = (struct trb *)skb->cb;
+	struct trb_open_priv *trb_open_priv;
+	struct queue_info *que;
+	int ret = 0;
+
+	que = radix_tree_lookup(&trans->queue_tbl, trb->channel_id & QUEUE_CHL_MASK);
+
+	switch (trb->cmd) {
+	case TRB_CMD_ENABLE:
+		trb_open_priv = (struct trb_open_priv *)skb->data;
+		trb_open_priv->log_rg_offset = que->log_rg_offset;
+		trans->usr_cnt[que->hif_id][que->txqno]++;
+		if (trans->usr_cnt[que->hif_id][que->txqno] == 1)
+			break;
+		trb_open_priv->tx_mtu = que->tx_mtu;
+		trb_open_priv->rx_mtu = que->rx_mtu;
+		trb_open_priv->tx_frag_size = que->tx_frag_size;
+		trb_open_priv->rx_frag_size = que->rx_frag_size;
+		if (mtk_cldma_check_ch_cfg(trans->dev, que)) {
+			trb->status = -EINVAL;
+			ret = -EINVAL;
+		} else {
+			trb->status = -EBUSY;
+			ret = -EBUSY;
+		}
+		trb->trb_complete(skb);
+		break;
+	case TRB_CMD_DISABLE:
+		if (trans->usr_cnt[que->hif_id][que->txqno] > 0) {
+			trans->usr_cnt[que->hif_id][que->txqno]--;
+			if (!trans->usr_cnt[que->hif_id][que->txqno])
+				break;
+		}
+		trb->status = -EBUSY;
+		trb->trb_complete(skb);
+		ret = -EBUSY;
+		break;
+	default:
+		dev_err((trans->mdev)->dev, "Invalid trb command(%d)\n", trb->cmd);
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static void mtk_ctrl_trb_handler(struct trb_srv *srv, struct trans_list *trans_list, u32 qno)
+{
+	struct sk_buff_head *skb_list = &trans_list->skb_list[qno];
+	struct mtk_ctrl_trans *trans = srv->trans;
+	struct sk_buff *skb, *skb_next;
+	struct trb *trb, *trb_next;
+	bool kick = false;
+	int loop = 0;
+	int err;
+
+	do {
+		skb = skb_peek(skb_list);
+		if (!skb)
+			break;
+		trb = (struct trb *)skb->cb;
+		kref_get(&trb->kref);
+
+		switch (trb->cmd) {
+		case TRB_CMD_ENABLE:
+		case TRB_CMD_DISABLE:
+			skb_unlink(skb, skb_list);
+			err = mtk_ch_status_check(trans, skb);
+			if (!err) {
+				kick = true;
+				if (trb->cmd == TRB_CMD_DISABLE)
+					mtk_ctrl_ch_flush(skb_list);
+			}
+			break;
+		case TRB_CMD_TX:
+			err = mtk_cldma_submit_tx(trans->dev, skb);
+			if (err) {
+				if (trans_list->tx_burst_cnt[qno]) {
+					kick = true;
+					break;
+				}
+				if (err == -EAGAIN) {
+					kref_put(&trb->kref, mtk_port_trb_free);
+					return;
+				}
+
+				skb_unlink(skb, skb_list);
+				trb->status = err;
+				trb->trb_complete(skb);
+				break;
+			}
+
+			trans_list->tx_burst_cnt[qno]++;
+			if (trans_list->tx_burst_cnt[qno] >= TX_BURST_MAX_CNT ||
+			    skb_queue_is_last(skb_list, skb)) {
+				kick = true;
+			} else {
+				skb_next = skb_peek_next(skb, skb_list);
+				trb_next = (struct trb *)skb_next->cb;
+				if (trb_next->cmd != TRB_CMD_TX)
+					kick = true;
+			}
+
+			skb_unlink(skb, skb_list);
+			break;
+		default:
+			skb_unlink(skb, skb_list);
+		}
+
+		if (kick) {
+			mtk_cldma_trb_process(trans->dev, skb);
+			trans_list->tx_burst_cnt[qno] = 0;
+			kick = false;
+		}
+
+		kref_put(&trb->kref, mtk_port_trb_free);
+
+		loop++;
+	} while (loop < TRB_NUM_PER_ROUND);
+}
+
+static void mtk_ctrl_trb_process(struct trb_srv *srv)
+{
+	struct mtk_ctrl_trans *trans = srv->trans;
+	struct srv_que *srv_que;
+	int i;
+
+	for (i = 0; i < NR_CLDMA; i++)
+		list_for_each_entry(srv_que, &srv->srv_q_list[i], list)
+			mtk_ctrl_trb_handler(srv, &trans->trans_list[i], srv_que->qno);
+}
+
+static int mtk_ctrl_trb_thread(void *args)
+{
+	struct trb_srv *srv = args;
+
+	for (;;) {
+		wait_event_interruptible(srv->trb_waitq,
+					 !mtk_ctrl_chs_is_busy_or_empty(srv) ||
+					 kthread_should_stop() || kthread_should_park());
+		if (kthread_should_stop())
+			break;
+
+		if (kthread_should_park())
+			kthread_parkme();
+
+		do {
+			mtk_ctrl_trb_process(srv);
+			cond_resched();
+		} while (!mtk_ctrl_chs_is_busy_or_empty(srv) && !kthread_should_stop() &&
+			 !kthread_should_park());
+	}
+	mtk_ctrl_chs_flush(srv);
+	return 0;
+}
+
+static int mtk_ctrl_trb_srv_init(struct mtk_ctrl_trans *trans)
+{
+	struct srv_que *srv_que;
+	struct trb_srv *srv;
+	int i, j;
+	int ret;
+
+	for (i = 0; i < trans->trb_srv_num; i++) {
+		srv = kzalloc_obj(*srv);
+		if (!srv) {
+			ret = -ENOMEM;
+			goto err_free_srv;
+		}
+
+		srv->trans = trans;
+		srv->srv_id = i;
+		trans->trb_srv[i] = srv;
+
+		init_waitqueue_head(&srv->trb_waitq);
+		for (j = 0; j < NR_CLDMA; j++)
+			INIT_LIST_HEAD(&srv->srv_q_list[j]);
+	}
+
+	for (i = 0; i < NR_CLDMA; i++)
+		for (j = 0; j < HW_QUE_NUM; j++) {
+			if (trans->srv_cfg[i][j] < 0 ||
+			    trans->srv_cfg[i][j] >= trans->trb_srv_num)
+				trans->srv_cfg[i][j] = 0;
+			srv_que = kzalloc_obj(*srv_que);
+			if (!srv_que) {
+				ret = -ENOMEM;
+				goto err_free_srv_que;
+			}
+			srv_que->hif_id = i;
+			srv_que->qno = j;
+			list_add_tail(&srv_que->list,
+				      &trans->trb_srv[trans->srv_cfg[i][j]]->srv_q_list[i]);
+		}
+
+	for (i = 0; i < trans->trb_srv_num; i++) {
+		trans->trb_srv[i]->trb_thread = kthread_run(mtk_ctrl_trb_thread, trans->trb_srv[i],
+							    "mtk_trb_srv%d_%s", i,
+							    trans->mdev->dev_str);
+		if (IS_ERR(trans->trb_srv[i]->trb_thread)) {
+			ret = PTR_ERR(trans->trb_srv[i]->trb_thread);
+			trans->trb_srv[i]->trb_thread = NULL;
+			goto err_stop_kthread;
+		}
+	}
+
+	return 0;
+err_stop_kthread:
+	while (--i >= 0)
+		kthread_stop(trans->trb_srv[i]->trb_thread);
+err_free_srv_que:
+	for (i = 0; i < trans->trb_srv_num; i++) {
+		for (j = 0; j < NR_CLDMA; j++) {
+			struct srv_que *next_srv_que;
+
+			list_for_each_entry_safe(srv_que, next_srv_que,
+						 &trans->trb_srv[i]->srv_q_list[j], list) {
+				list_del(&srv_que->list);
+				kfree(srv_que);
+			}
+		}
+	}
+err_free_srv:
+	for (i = 0; i < trans->trb_srv_num; i++) {
+		if (!trans->trb_srv[i])
+			break;
+		kfree(trans->trb_srv[i]);
+		trans->trb_srv[i] = NULL;
+	}
+
+	return ret;
+}
+
+static void mtk_ctrl_trb_srv_exit(struct mtk_ctrl_trans *trans)
+{
+	struct srv_que *srv_que, *next_srv_que;
+	struct trb_srv *srv;
+	int i, j;
+
+	for (i = 0; i < trans->trb_srv_num; i++) {
+		srv = trans->trb_srv[i];
+		kthread_stop(srv->trb_thread);
+		for (j = 0; j < NR_CLDMA; j++) {
+			list_for_each_entry_safe(srv_que, next_srv_que,
+						 &trans->trb_srv[i]->srv_q_list[j], list) {
+				list_del(&srv_que->list);
+				kfree(srv_que);
+			}
+		}
+		kfree(srv);
+		trans->trb_srv[i] = NULL;
+	}
+}
+
+static void mtk_ctrl_remove_radix_tree(struct mtk_ctrl_trans *trans)
+{
+	struct radix_tree_iter iter;
+	struct queue_info *queue;
+	void __rcu **slot;
+
+	radix_tree_for_each_slot(slot, &trans->queue_tbl, &iter, 0) {
+		queue = radix_tree_deref_slot(slot);
+		if (!queue)
+			continue;
+		radix_tree_delete(&trans->queue_tbl, iter.index);
+		kfree(queue);
+	}
+}
+
+static int mtk_pcie_hif_init(struct mtk_md_dev *mdev)
+{
+	struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+	struct queue_info *queue, *queue_info;
+	struct mtk_ctrl_trans *trans;
+	int i, j;
+	int ret;
+
+	trans = ctrl_blk->ctrl_hw_priv;
+	trans->ctrl_blk = ctrl_blk;
+	queue_info = trans->queue_info;
+
+	INIT_RADIX_TREE(&trans->queue_tbl, GFP_KERNEL);
+	for (i = 0; i < trans->queue_info_num; i++) {
+		queue = kmemdup(queue_info + i, sizeof(*queue), GFP_KERNEL);
+		if (!queue) {
+			ret = -ENOMEM;
+			goto err_free_radix_tree;
+		}
+		if (queue->txqno >= HW_QUE_NUM || queue->rxqno >= HW_QUE_NUM ||
+		    queue->hif_id >= NR_CLDMA) {
+			dev_err(mdev->dev, "Failed to get correct queue info %x\n",
+				queue->rx_chl);
+			kfree(queue);
+			ret = -EINVAL;
+			goto err_free_radix_tree;
+		}
+		ret = radix_tree_insert(&trans->queue_tbl, queue->rx_chl & QUEUE_CHL_MASK, queue);
+		if (ret) {
+			dev_err(mdev->dev, "Insert %x fail, ret: %d", queue->rx_chl, ret);
+			kfree(queue);
+			goto err_free_radix_tree;
+		}
+		trans->queues_cnt++;
+	}
+
+	for (i = 0; i < NR_CLDMA; i++) {
+		for (j = 0; j < HW_QUE_NUM; j++) {
+			skb_queue_head_init(&trans->trans_list[i].skb_list[j]);
+			trans->trans_list[i].tx_burst_cnt[j] = 0;
+		}
+	}
+	ret = mtk_cldma_init(trans);
+	if (ret)
+		goto err_free_radix_tree;
+
+	ret = mtk_ctrl_trb_srv_init(trans);
+	if (ret)
+		goto err_cldma_exit;
+
+	atomic_set(&trans->available, 1);
+
+	return 0;
+
+err_cldma_exit:
+	mtk_cldma_exit(trans);
+err_free_radix_tree:
+	mtk_ctrl_remove_radix_tree(trans);
+
+	return ret;
+}
+
+static int mtk_pcie_hif_exit(struct mtk_md_dev *mdev)
+{
+	struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+	struct mtk_ctrl_trans *trans;
+
+	trans = ctrl_blk->ctrl_hw_priv;
+
+	atomic_set(&trans->available, 0);
+	mtk_ctrl_trb_srv_exit(trans);
+	mtk_ctrl_remove_radix_tree(trans);
+	mtk_cldma_exit(trans);
+
+	return 0;
+}
+
+static int mtk_pcie_hif_submit_skb(struct mtk_md_dev *mdev, struct sk_buff *skb, bool force_send)
+{
+	struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+	struct mtk_ctrl_trans *trans;
+	struct queue_info *que;
+	struct trb *trb;
+
+	trans = ctrl_blk->ctrl_hw_priv;
+	trb = (struct trb *)skb->cb;
+
+	if (trb->cmd == TRB_CMD_STOP || trb->cmd == TRB_CMD_RECOVER) {
+		trb->trb_complete(skb);
+		return 0;
+	}
+
+	que = radix_tree_lookup(&trans->queue_tbl, trb->channel_id & QUEUE_CHL_MASK);
+	if (!que) {
+		dev_warn(mdev->dev, "lookup que fail, ch_id: %x, que: 0x%p\n",
+			 trb->channel_id, que);
+		return -EINVAL;
+	}
+
+	if (!atomic_read(&trans->available))
+		return -EIO;
+
+	if (mtk_queue_list_is_full(trans, que) && !force_send)
+		return -EAGAIN;
+
+	if (trb->cmd == TRB_CMD_DISABLE)
+		skb_queue_head(&trans->trans_list[que->hif_id].skb_list[que->txqno], skb);
+	else
+		skb_queue_tail(&trans->trans_list[que->hif_id].skb_list[que->txqno], skb);
+
+	wake_up(&trans->trb_srv[trans->srv_cfg[que->hif_id][que->txqno]]->trb_waitq);
+
+	return 0;
+}
+
+static void mtk_pcie_hif_fsm_indication(struct mtk_md_dev *mdev, struct mtk_fsm_param *param)
+{
+	struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+	struct mtk_ctrl_trans *trans;
+
+	trans = ctrl_blk->ctrl_hw_priv;
+	mtk_cldma_fsm_state_listener(param, trans);
+}
+
+static int mtk_pcie_hif_cmd_func(struct mtk_md_dev *mdev, int cmd, void *data)
+{
+	struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+	struct mtk_ctrl_trans *trans;
+	struct queue_info *que;
+
+	switch (cmd) {
+	case HIF_CTRL_CMD_CHECK_TX_FULL:
+		trans = ctrl_blk->ctrl_hw_priv;
+		que = radix_tree_lookup(&trans->queue_tbl,
+					((union ctrl_hif_cmd_data *)data)->rx_ch & QUEUE_CHL_MASK);
+		if (!que) {
+			dev_warn(mdev->dev, "Failed to find que to check tx full\n");
+			return -EINVAL;
+		}
+		return mtk_queue_list_is_full(trans, que);
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static struct mtk_ctrl_hif_ops pcie_ctrl_ops = {
+	.init = mtk_pcie_hif_init,
+	.exit = mtk_pcie_hif_exit,
+	.submit_skb = mtk_pcie_hif_submit_skb,
+	.fsm_indication = mtk_pcie_hif_fsm_indication,
+	.send_cmd = mtk_pcie_hif_cmd_func,
+};
+
+static void mtk_trans_get_ctrl_info(struct mtk_ctrl_cfg *cfg,
+				    struct mtk_ctrl_trans *trans, u32 hw_ver)
+{
+	struct mtk_ctrl_info_desc *ctrl_info_desc;
+	struct mtk_ctrl_info *ctrl_info;
+	u8 i;
+
+	for (i = 0; (ctrl_info_desc = &mtk_ctrl_info_tbl[i]) && ctrl_info_desc &&
+	     ctrl_info_desc->ctrl_info; i++) {
+		if (ctrl_info_desc->hw_ver != hw_ver)
+			continue;
+
+		ctrl_info = ctrl_info_desc->ctrl_info;
+		cfg->port_layer_cfg = ctrl_info->ctrl_cfg->port_layer_cfg;
+		memcpy(trans->srv_cfg, ctrl_info->srv_cfg,
+		       sizeof(int) * NR_CLDMA * HW_QUE_NUM);
+		trans->queue_info = ctrl_info->queue_info;
+		trans->queue_info_num = ctrl_info->queue_info_num;
+		trans->trb_srv_num = ctrl_info->trb_srv_num;
+	}
+}
+
+int mtk_trans_ctrl_init(struct mtk_md_dev *mdev)
+{
+	struct mtk_ctrl_trans *trans;
+	struct mtk_ctrl_blk *ctrl_blk;
+	struct mtk_ctrl_cfg *cfg;
+	int err;
+
+	trans = devm_kzalloc(mdev->dev, sizeof(*trans), GFP_KERNEL);
+	if (!trans)
+		return -ENOMEM;
+	trans->mdev = mdev;
+	trans->queues_cnt = 0;
+
+	cfg = devm_kzalloc(mdev->dev, sizeof(*cfg), GFP_KERNEL);
+	if (!cfg)
+		return -ENOMEM;
+
+	mtk_trans_get_ctrl_info(cfg, trans, mdev->hw_ver);
+	if (!cfg->port_layer_cfg || !trans->queue_info ||
+	    trans->trb_srv_num <= 0 || trans->trb_srv_num > TRB_SRV_MAX_NUM ||
+	    trans->queue_info_num <= 0) {
+		dev_err(mdev->dev, "Failed to get ctrl info!\n");
+		goto err_free_cfg;
+	}
+
+	err = mtk_ctrl_init(mdev, &pcie_ctrl_ops, cfg);
+	if (err)
+		goto err_free_cfg;
+
+	ctrl_blk = mdev->ctrl_blk;
+	ctrl_blk->ctrl_hw_priv = trans;
+
+	return 0;
+
+err_free_cfg:
+	return -ENOMEM;
+}
+
+int mtk_trans_ctrl_exit(struct mtk_md_dev *mdev)
+{
+	mtk_ctrl_exit(mdev);
+
+	return 0;
+}
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
new file mode 100644
index 00000000000000..4b9c9db6ad7152
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_TRANS_CTRL_H__
+#define __MTK_TRANS_CTRL_H__
+
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+
+#include "mtk_dev.h"
+#include "mtk_port.h"
+
+#define TRB_SRV_MAX_NUM			(1)
+#define HW_QUE_NUM			(8)
+#define TX_GPD_NUM			(16)
+#define RX_GPD_NUM			(TX_GPD_NUM)
+#define MIN_GPD_NUM			(2)
+#define SKB_LIST_MAX_LEN		(16)
+#define MTU_RSV_ROOM			(0x100)
+#define TRB_NUM_PER_ROUND		(TX_GPD_NUM)
+#define TX_BURST_MAX_CNT		(TX_GPD_NUM / 4 + 1)
+
+#define HIF_ID(peer_id)			((peer_id) - 1)
+
+enum mtk_hif_id {
+	CLDMA0,
+	CLDMA1,
+	NR_CLDMA
+};
+
+struct queue_info {
+	u32 tx_chl;
+	u32 rx_chl;
+	enum mtk_hif_id hif_id;
+	u32 txqno;
+	u32 rxqno;
+	u32 tx_mtu;
+	u32 rx_mtu;
+	u32 tx_nr_gpds;
+	u32 rx_nr_gpds;
+	u32 tx_frag_size;
+	u32 rx_frag_size;
+	u8 log_rg_offset;
+};
+
+struct trans_list {
+	struct sk_buff_head skb_list[HW_QUE_NUM];
+	u8 tx_burst_cnt[HW_QUE_NUM];
+};
+
+struct mtk_ctrl_trans {
+	struct mtk_ctrl_blk *ctrl_blk;
+	struct trb_srv *trb_srv[TRB_SRV_MAX_NUM];
+	struct trans_list trans_list[NR_CLDMA];
+	void *dev;
+	struct radix_tree_root queue_tbl;
+	struct mtk_md_dev *mdev;
+	int usr_cnt[NR_CLDMA][HW_QUE_NUM];
+	u32 tx_mtu_cfg[NR_CLDMA][HW_QUE_NUM];
+	u32 rx_mtu_cfg[NR_CLDMA][HW_QUE_NUM];
+	atomic_t available;
+	int queues_cnt;
+	int srv_cfg[NR_CLDMA][HW_QUE_NUM];
+	struct queue_info *queue_info;
+	int queue_info_num;
+	int trb_srv_num;
+};
+
+struct srv_que {
+	u32 hif_id;
+	u32 qno;
+	struct list_head list;
+};
+
+struct trb_srv {
+	u32 srv_id;
+	struct list_head srv_q_list[NR_CLDMA];
+	struct mtk_ctrl_trans *trans;
+	wait_queue_head_t trb_waitq;
+	struct task_struct *trb_thread;
+};
+
+struct mtk_ctrl_info {
+	struct mtk_ctrl_cfg *ctrl_cfg;
+	int **srv_cfg;
+	struct queue_info *queue_info;
+	u32 queue_info_num;
+	u32 trb_srv_num;
+};
+
+struct mtk_ctrl_info_desc {
+	u32 hw_ver;
+	struct mtk_ctrl_info *ctrl_info;
+};
+
+#define ctrl_info_name(NAME)	mtk_ctrl_info_##NAME
+
+extern struct mtk_ctrl_info mtk_ctrl_info_m9xx;
+
+int mtk_trans_ctrl_init(struct mtk_md_dev *mdev);
+int mtk_trans_ctrl_exit(struct mtk_md_dev *mdev);
+
+#endif

3/2 2026/07/10 06:33 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 109655
							output: 312
							thoughts: 2348
						

Thoughts:
**Analysis of the mtk_t9xx Driver for Fuzzing**

Alright, let's break this down. We have a new driver, `mtk_t9xx`, for a MediaTek PCIe 5G WWAN modem – T9xx specifically. It's neatly organized in `drivers/net/wwan/t9xx/` and `drivers/net/wwan/t9xx/pcie/`, which is a good sign. This driver registers a PCI driver, `mtk_pci_drv`, and that’s a direct entry point for us, given the PCI context. The driver provides WWAN ports (AT, MBIM) and internal ports, indicating good attack surface. Crucially, it has state machine (`mtk_fsm`), port management (`mtk_port`), and CLDMA driver (`mtk_cldma`) implementations, which suggests complexity and, potentially, vulnerabilities.

The config options are straightforward: `CONFIG_MTK_T9XX` and `CONFIG_MTK_T9XX_PCI`. However, `MTK_T9XX` selects `MTK_T9XX_PCI` and `NET_DEVLINK`, simplifying things. We just need to ensure `MTK_T9XX` is enabled.

Looking at the PCI device IDs, we see support for MediaTek's Vendor ID (0x14C3, Device ID 0x0900) and CEI (0x03F0, 0x01CA). This means we're dealing with standard PCI devices, and can be tested using vhci/pci fuzzing via syzkaller, which is ideal.

Now, let's identify the key focus symbols. Since it registers a PCI driver, `mtk_pci_drv`, we need to look into its probe, remove and error handling functions. I'll need to focus on those, and also understand the CLDMA and WWAN port interactions, as this is all new code:

*   `mtk_pci_probe`
*   `mtk_pci_remove`
*   `mtk_cldma_submit_tx`
*   `mtk_cldma_trb_process`
*   `mtk_port_wwan_write`
*   `mtk_port_wwan_open`
*   `mtk_port_wwan_close`
*   `mtk_port_wwan_poll`
*   `mtk_fsm_evt_submit`
*   `mtk_fsm_evt_handler`
*   `mtk_cldma_tx_done_work`
*   `mtk_cldma_rx_done_work`
*   `mtk_port_rx_dispatch`

Furthermore, since `wwan_ops` includes `start`, `stop`, `tx`, and `tx_poll` functions, those are all targets. I will also need to review how the DMA controller is being used (`mtk_cldma_open`, `mtk_cldma_tx`, `mtk_cldma_close`) and the FSM events (`mtk_fsm_startup_act`, `mtk_fsm_dev_rm_act`, `mtk_fsm_dev_add_act`). Interrupt handling is always critical: `mtk_cldma_isr` and `mtk_pci_irq_msix` will also be key targets.

So, in summary, here are my focus symbols:

*   `mtk_pci_probe`
*   `mtk_pci_remove`
*   `mtk_port_wwan_open`
*   `mtk_port_wwan_close`
*   `mtk_port_wwan_write`
*   `mtk_port_wwan_write_blocking`
*   `mtk_port_wwan_poll`
*   `mtk_cldma_open`
*   `mtk_cldma_tx`
*   `mtk_cldma_close`
*   `mtk_fsm_startup_act`
*   `mtk_fsm_dev_rm_act`
*   `mtk_fsm_dev_add_act`
*   `mtk_cldma_isr`
*   `mtk_pci_irq_msix`
*   `mtk_cldma_tx_done_work`
*   `mtk_cldma_rx_done_


4/2 2026/07/10 06:34 tool set-results
0m Args:
{
  "EnableConfigs": [
    "MTK_T9XX"
  ],
  "FocusSymbols": [
    "mtk_pci_probe",
    "mtk_pci_remove",
    "mtk_port_wwan_open",
    "mtk_port_wwan_close",
    "mtk_port_wwan_write",
    "mtk_port_wwan_write_blocking",
    "mtk_port_wwan_poll",
    "mtk_cldma_open",
    "mtk_cldma_tx",
    "mtk_cldma_close",
    "mtk_fsm_startup_act",
    "mtk_fsm_dev_rm_act",
    "mtk_fsm_dev_add_act",
    "mtk_cldma_isr",
    "mtk_pci_irq_msix",
    "mtk_cldma_tx_done_work",
    "mtk_cldma_rx_done_work"
  ],
  "Reasoning": "The patch introduces a new WWAN driver for MediaTek T9xx PCIe 5G modems. It implements a PCI driver, CLDMA (DMA controller) handling, state machine, and WWAN port operations. The driver is reachable via the PCI subsystem and WWAN core, making it suitable for fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "MTK_T9XX"
  ],
  "FocusSymbols": [
    "mtk_pci_probe",
    "mtk_pci_remove",
    "mtk_port_wwan_open",
    "mtk_port_wwan_close",
    "mtk_port_wwan_write",
    "mtk_port_wwan_write_blocking",
    "mtk_port_wwan_poll",
    "mtk_cldma_open",
    "mtk_cldma_tx",
    "mtk_cldma_close",
    "mtk_fsm_startup_act",
    "mtk_fsm_dev_rm_act",
    "mtk_fsm_dev_add_act",
    "mtk_cldma_isr",
    "mtk_pci_irq_msix",
    "mtk_cldma_tx_done_work",
    "mtk_cldma_rx_done_work"
  ],
  "Reasoning": "The patch introduces a new WWAN driver for MediaTek T9xx PCIe 5G modems. It implements a PCI driver, CLDMA (DMA controller) handling, state machine, and WWAN port operations. The driver is reachable via the PCI subsystem and WWAN core, making it suitable for fuzzing.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)