diff mbox series

[v2,03/14] net: wwan: t7xx: Add core components

Message ID 20211101035635.26999-4-ricardo.martinez@linux.intel.com (mailing list archive)
State Not Applicable
Delegated to: Johannes Berg
Headers show
Series net: wwan: t7xx: PCIe driver for MediaTek M.2 modem | expand

Commit Message

Ricardo Martinez Nov. 1, 2021, 3:56 a.m. UTC
From: Haijun Lio <haijun.liu@mediatek.com>

Registers the t7xx device driver with the kernel. Setup all the core
components: PCIe layer, Modem Host Cross Core Interface (MHCCIF),
modem control operations, modem state machine, and build
infrastructure.

* PCIe layer code implements driver probe and removal.
* MHCCIF provides interrupt channels to communicate events
  such as handshake, PM and port enumeration.
* Modem control implements the entry point for modem init,
  reset and exit.
* The modem status monitor is a state machine used by modem control
  to complete initialization and stop. It is used also to propagate
  exception events reported by other components.

Signed-off-by: Haijun Lio <haijun.liu@mediatek.com>
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
Co-developed-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
Signed-off-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
---
 drivers/net/wwan/Kconfig                   |  14 +
 drivers/net/wwan/Makefile                  |   1 +
 drivers/net/wwan/t7xx/Makefile             |  13 +
 drivers/net/wwan/t7xx/t7xx_common.h        |  76 +++
 drivers/net/wwan/t7xx/t7xx_mhccif.c        | 105 ++++
 drivers/net/wwan/t7xx/t7xx_mhccif.h        |  35 ++
 drivers/net/wwan/t7xx/t7xx_modem_ops.c     | 486 +++++++++++++++++
 drivers/net/wwan/t7xx/t7xx_modem_ops.h     |  90 ++++
 drivers/net/wwan/t7xx/t7xx_monitor.h       | 144 +++++
 drivers/net/wwan/t7xx/t7xx_pci.c           | 238 ++++++++
 drivers/net/wwan/t7xx/t7xx_pci.h           |  66 +++
 drivers/net/wwan/t7xx/t7xx_pcie_mac.c      | 277 ++++++++++
 drivers/net/wwan/t7xx/t7xx_pcie_mac.h      |  36 ++
 drivers/net/wwan/t7xx/t7xx_reg.h           | 398 ++++++++++++++
 drivers/net/wwan/t7xx/t7xx_skb_util.c      | 362 +++++++++++++
 drivers/net/wwan/t7xx/t7xx_skb_util.h      | 110 ++++
 drivers/net/wwan/t7xx/t7xx_state_monitor.c | 598 +++++++++++++++++++++
 17 files changed, 3049 insertions(+)
 create mode 100644 drivers/net/wwan/t7xx/Makefile
 create mode 100644 drivers/net/wwan/t7xx/t7xx_common.h
 create mode 100644 drivers/net/wwan/t7xx/t7xx_mhccif.c
 create mode 100644 drivers/net/wwan/t7xx/t7xx_mhccif.h
 create mode 100644 drivers/net/wwan/t7xx/t7xx_modem_ops.c
 create mode 100644 drivers/net/wwan/t7xx/t7xx_modem_ops.h
 create mode 100644 drivers/net/wwan/t7xx/t7xx_monitor.h
 create mode 100644 drivers/net/wwan/t7xx/t7xx_pci.c
 create mode 100644 drivers/net/wwan/t7xx/t7xx_pci.h
 create mode 100644 drivers/net/wwan/t7xx/t7xx_pcie_mac.c
 create mode 100644 drivers/net/wwan/t7xx/t7xx_pcie_mac.h
 create mode 100644 drivers/net/wwan/t7xx/t7xx_reg.h
 create mode 100644 drivers/net/wwan/t7xx/t7xx_skb_util.c
 create mode 100644 drivers/net/wwan/t7xx/t7xx_skb_util.h
 create mode 100644 drivers/net/wwan/t7xx/t7xx_state_monitor.c

Comments

Andy Shevchenko Nov. 2, 2021, 3:46 p.m. UTC | #1
On Sun, Oct 31, 2021 at 08:56:24PM -0700, Ricardo Martinez wrote:
> From: Haijun Lio <haijun.liu@mediatek.com>
> 
> Registers the t7xx device driver with the kernel. Setup all the core
> components: PCIe layer, Modem Host Cross Core Interface (MHCCIF),
> modem control operations, modem state machine, and build
> infrastructure.
> 
> * PCIe layer code implements driver probe and removal.
> * MHCCIF provides interrupt channels to communicate events
>   such as handshake, PM and port enumeration.
> * Modem control implements the entry point for modem init,
>   reset and exit.
> * The modem status monitor is a state machine used by modem control
>   to complete initialization and stop. It is used also to propagate
>   exception events reported by other components.

I will assume that the comments given against previous patch will be applied
to this and the rest of the patches where it makes sense or appropriate.

Below only new comments.

...

> +config MTK_T7XX
> +	tristate "MediaTek PCIe 5G WWAN modem T7XX device"

T77xx is easier to read by human beings.

> +	depends on PCI

...

> +struct ccci_header {
> +	/* do not assume data[1] is data length in rx */

To understand this comment you need to elaborate the content of the header.

> +	u32 data[2];
> +	u32 status;
> +	u32 reserved;
> +};

...

> +#define CCCI_HEADER_NO_DATA	0xffffffff

Is this internal value to Linux or something which is given by hardware?

...

> +/* Modem exception check identification number */
> +#define MD_EX_CHK_ID		0x45584350
> +/* Modem exception check acknowledge identification number */
> +#define MD_EX_CHK_ACK_ID	0x45524543

To me both looks like fourcc. Can you add their ASCII values into comments?

...

> +	/* Use 1*4 bits to avoid low power bits*/

What does "1*4 bits" mean?

> +	iowrite32(L1_1_DISABLE_BIT(1) | L1_2_DISABLE_BIT(1),
> +		  IREG_BASE(mtk_dev) + DIS_ASPM_LOWPWR_SET_0);

...

> +int mtk_pci_mhccif_isr(struct mtk_pci_dev *mtk_dev)
> +{
> +	struct md_sys_info *md_info;
> +	struct ccci_fsm_ctl *ctl;
> +	struct mtk_modem *md;
> +	unsigned int int_sta;
> +	unsigned long flags;
> +	u32 mask;
> +
> +	md = mtk_dev->md;
> +	ctl = fsm_get_entry();
> +	if (!ctl) {

> +		dev_err(&mtk_dev->pdev->dev,
> +			"process MHCCIF interrupt before modem monitor was initialized\n");

Can this potentially flood the logs? If so, needs to be rate limited.

> +		return -EINVAL;
> +	}

> +	md_info = md->md_info;

> +	spin_lock_irqsave(&md_info->exp_spinlock, flags);

Can it be called outside of IRQ context?

> +	int_sta = get_interrupt_status(mtk_dev);
> +	md_info->exp_id |= int_sta;
> +
> +	if (md_info->exp_id & D2H_INT_PORT_ENUM) {
> +		md_info->exp_id &= ~D2H_INT_PORT_ENUM;
> +		if (ctl->curr_state == CCCI_FSM_INIT ||
> +		    ctl->curr_state == CCCI_FSM_PRE_START ||
> +		    ctl->curr_state == CCCI_FSM_STOPPED)
> +			ccci_fsm_recv_md_interrupt(MD_IRQ_PORT_ENUM);
> +	}
> +
> +	if (md_info->exp_id & D2H_INT_EXCEPTION_INIT) {
> +		if (ctl->md_state == MD_STATE_INVALID ||
> +		    ctl->md_state == MD_STATE_WAITING_FOR_HS1 ||
> +		    ctl->md_state == MD_STATE_WAITING_FOR_HS2 ||
> +		    ctl->md_state == MD_STATE_READY) {
> +			md_info->exp_id &= ~D2H_INT_EXCEPTION_INIT;
> +			ccci_fsm_recv_md_interrupt(MD_IRQ_CCIF_EX);
> +		}
> +	} else if (ctl->md_state == MD_STATE_WAITING_FOR_HS1) {
> +		/* start handshake if MD not assert */
> +		mask = mhccif_mask_get(mtk_dev);
> +		if ((md_info->exp_id & D2H_INT_ASYNC_MD_HK) && !(mask & D2H_INT_ASYNC_MD_HK)) {
> +			md_info->exp_id &= ~D2H_INT_ASYNC_MD_HK;
> +			queue_work(md->handshake_wq, &md->handshake_work);
> +		}
> +	}
> +
> +	spin_unlock_irqrestore(&md_info->exp_spinlock, flags);
> +
> +	return 0;
> +}

...

> +static int mtk_acpi_reset(struct mtk_pci_dev *mtk_dev, char *fn_name)
> +{
> +#ifdef CONFIG_ACPI
> +	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
> +	acpi_status acpi_ret;
> +	struct device *dev;
> +	acpi_handle handle;
> +
> +	dev = &mtk_dev->pdev->dev;

> +	if (acpi_disabled) {
> +		dev_err(dev, "acpi function isn't enabled\n");
> +		return -EFAULT;
> +	}

Why this check?

> +	handle = ACPI_HANDLE(dev);
> +	if (!handle) {
> +		dev_err(dev, "acpi handle isn't found\n");

acpi --> ACPI

> +		return -EFAULT;
> +	}
> +
> +	if (!acpi_has_method(handle, fn_name)) {
> +		dev_err(dev, "%s method isn't found\n", fn_name);
> +		return -EFAULT;
> +	}
> +
> +	acpi_ret = acpi_evaluate_object(handle, fn_name, NULL, &buffer);
> +	if (ACPI_FAILURE(acpi_ret)) {
> +		dev_err(dev, "%s method fail: %s\n", fn_name, acpi_format_exception(acpi_ret));
> +		return -EFAULT;
> +	}
> +#endif
> +	return 0;
> +}

...

> +	msleep(RGU_RESET_DELAY_US);

DELAY in microseconds while msleep() takes milliseconds.
Something is wrong here.

Also, delays such as 10ms+ should be explained. Esp. when they are
in the threaded IRQ handler.

...

> +void mtk_md_exception_handshake(struct mtk_modem *md)
> +{
> +	struct mtk_pci_dev *mtk_dev;

	struct device *dev = &mtk_dev->pdev->dev;

will help a lot to make below code cleaner.

> +	int ret;

> +	mtk_dev = md->mtk_dev;
> +	md_exception(md, HIF_EX_INIT);
> +	ret = wait_hif_ex_hk_event(md, D2H_INT_EXCEPTION_INIT_DONE);
> +
> +	if (ret)
> +		dev_err(&mtk_dev->pdev->dev, "EX CCIF HS timeout, RCH 0x%lx\n",
> +			D2H_INT_EXCEPTION_INIT_DONE);
> +
> +	md_exception(md, HIF_EX_INIT_DONE);
> +	ret = wait_hif_ex_hk_event(md, D2H_INT_EXCEPTION_CLEARQ_DONE);
> +	if (ret)
> +		dev_err(&mtk_dev->pdev->dev, "EX CCIF HS timeout, RCH 0x%lx\n",
> +			D2H_INT_EXCEPTION_CLEARQ_DONE);
> +
> +	md_exception(md, HIF_EX_CLEARQ_DONE);
> +	ret = wait_hif_ex_hk_event(md, D2H_INT_EXCEPTION_ALLQ_RESET);
> +	if (ret)
> +		dev_err(&mtk_dev->pdev->dev, "EX CCIF HS timeout, RCH 0x%lx\n",
> +			D2H_INT_EXCEPTION_ALLQ_RESET);
> +
> +	md_exception(md, HIF_EX_ALLQ_RESET);
> +}

...

> +err_fsm_init:
> +	ccci_fsm_uninit();
> +err_alloc:
> +	destroy_workqueue(md->handshake_wq);

Labels should explain what will be done when goto, and not what was done.

...

> +/* Modem feature query identification number */
> +#define MD_FEATURE_QUERY_ID	0x49434343

All fourcc:s should be represented as ASCII in the comments.

...

> +#ifndef __T7XX_MONITOR_H__
> +#define __T7XX_MONITOR_H__

> +#include <linux/sched.h>

Who is the user of this?

...

> +static int mtk_request_irq(struct pci_dev *pdev)
> +{
> +	struct mtk_pci_dev *mtk_dev;
> +	int ret, i;
> +
> +	mtk_dev = pci_get_drvdata(pdev);
> +
> +	for (i = 0; i < EXT_INT_NUM; i++) {
> +		const char *irq_descr;
> +		int irq_vec;
> +
> +		if (!mtk_dev->intr_handler[i])
> +			continue;
> +
> +		irq_descr = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s_%d",
> +					   dev_driver_string(&pdev->dev), i);
> +		if (!irq_descr)

Resource leak is here.

> +			return -ENOMEM;
> +
> +		irq_vec = pci_irq_vector(pdev, i);
> +		ret = request_threaded_irq(irq_vec, mtk_dev->intr_handler[i],
> +					   mtk_dev->intr_thread[i], 0, irq_descr,
> +					   mtk_dev->callback_param[i]);
> +		if (ret) {
> +			dev_err(&pdev->dev, "Failed to request_irq: %d, int: %d, ret: %d\n",
> +				irq_vec, i, ret);
> +			while (i--) {
> +				if (!mtk_dev->intr_handler[i])
> +					continue;
> +
> +				free_irq(pci_irq_vector(pdev, i), mtk_dev->callback_param[i]);
> +			}
> +
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}

...

> +	ret = pci_alloc_irq_vectors(mtk_dev->pdev, EXT_INT_NUM, EXT_INT_NUM, PCI_IRQ_MSIX);
> +	if (ret < 0) {
> +		dev_err(&mtk_dev->pdev->dev, "Failed to allocate MSI-X entry, errno: %d\n", ret);

', errno' is redundant.

> +		return ret;
> +	}
> +
> +	ret = mtk_request_irq(mtk_dev->pdev);
> +	if (ret) {
> +		pci_free_irq_vectors(mtk_dev->pdev);
> +		return ret;
> +	}
> +
> +	/* Set MSIX merge config */
> +	mtk_pcie_mac_msix_cfg(mtk_dev, EXT_INT_NUM);
> +	return 0;
> +}

...

> +	ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));

This API is absoleted, use corresponding DMA API directly.
On top of that, 64-bit setting never fail...

> +	if (ret) {

> +		ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
> +		if (ret) {
> +			dev_err(&pdev->dev, "Could not set PCI DMA mask, err: %d\n", ret);
> +			return ret;
> +		}

...so this attempt is almost a dead code.

> +	}

...

> +	ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
> +	if (ret) {
> +		ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
> +		if (ret) {
> +			dev_err(&pdev->dev, "Could not set consistent PCI DMA mask, err: %d\n",
> +				ret);
> +			return ret;
> +		}
> +	}

Ditto.

...

> +	mtk_pcie_mac_set_int(mtk_dev, MHCCIF_INT);
> +	mtk_pcie_mac_interrupts_en(mtk_dev);

> +	pci_set_master(pdev);

It's too late for this call. Are you sure it's needed here? Why?

> +
> +	return 0;

...

> +err:

Meaningless label name. Try your best to make it better.

> +	ccci_skb_pool_free(&mtk_dev->pools);

Does it free IRQ handlers? If so, the function naming is not good enough.

> +	return ret;

...

> +static int __init mtk_pci_init(void)
> +{
> +	return pci_register_driver(&mtk_pci_driver);
> +}
> +module_init(mtk_pci_init);
> +
> +static void __exit mtk_pci_cleanup(void)
> +{
> +	pci_unregister_driver(&mtk_pci_driver);
> +}
> +module_exit(mtk_pci_cleanup);

NIH module_pci_driver().

...

> + * @pdev: pci device

pci --> PCI

...

> +#include <linux/io-64-nonatomic-lo-hi.h>

> +#include <linux/msi.h>

Wondering what the APIs you are using from there.

...

> +	for (i = 0; i < ATR_TABLE_NUM_PER_ATR; i++) {
> +		offset = (ATR_PORT_OFFSET * port) + (ATR_TABLE_OFFSET * i);

Too many parentheses.

> +		/* Disable table by SRC_ADDR */
> +		reg = pbase + ATR_PCIE_WIN0_T0_ATR_PARAM_SRC_ADDR + offset;
> +		iowrite64(0, reg);
> +	}

...

> +		pos = ffs(lower_32_bits(cfg->size));
> +		if (!pos)
> +			pos = ffs(upper_32_bits(cfg->size)) + 32;

NIH __ffs64() ?

...

> +static void mtk_pcie_mac_enable_disable_int(struct mtk_pci_dev *mtk_dev, bool enable)
> +{
> +	u32 value;
> +
> +	value = ioread32(IREG_BASE(mtk_dev) + ISTAT_HST_CTRL);

Either add...

> +	if (enable)
> +		value &= ~ISTAT_HST_CTRL_DIS;
> +	else
> +		value |= ISTAT_HST_CTRL_DIS;

> +

...or remove blank line for the sake of consistency.

> +	iowrite32(value, IREG_BASE(mtk_dev) + ISTAT_HST_CTRL);
> +}

...

> +#include <linux/bitops.h>

Who is the user of this?

...

> +#ifndef __T7XX_REG_H__
> +#define __T7XX_REG_H__
> +
> +#include <linux/bits.h>

...

> +#define EXP_BAR0				0x0c
> +#define EXP_BAR2				0x04
> +#define EXP_BAR4				0x0c

BAR0 and BAR4 have the same value. Either explain or fix accordingly.

...

> +#define MSIX_MSK_SET_ALL			GENMASK(31, 24)

Missed blank line ?

> +enum pcie_int {
> +	DPMAIF_INT = 0,
> +	CLDMA0_INT,
> +	CLDMA1_INT,
> +	CLDMA2_INT,
> +	MHCCIF_INT,
> +	DPMAIF2_INT,
> +	SAP_RGU_INT,
> +	CLDMA3_INT,
> +};

...

> +static struct sk_buff *alloc_skb_from_pool(struct skb_pools *pools, size_t size)
> +{
> +	if (size > MTK_SKB_4K)
> +		return ccci_skb_dequeue(pools->reload_work_queue, &pools->skb_pool_64k);
> +	else if (size > MTK_SKB_16)
> +		return ccci_skb_dequeue(pools->reload_work_queue, &pools->skb_pool_4k);
> +	else if (size > 0)
> +		return ccci_skb_dequeue(pools->reload_work_queue, &pools->skb_pool_16);

Redundant 'else'. Recommend to read again our internal Wiki about typical
issues with the code.

> +	return NULL;
> +}

...

> +static struct sk_buff *alloc_skb_from_kernel(size_t size, gfp_t gfp_mask)
> +{
> +	if (size > MTK_SKB_4K)
> +		return __dev_alloc_skb(MTK_SKB_64K, gfp_mask);
> +	else if (size > MTK_SKB_1_5K)
> +		return __dev_alloc_skb(MTK_SKB_4K, gfp_mask);
> +	else if (size > MTK_SKB_16)
> +		return __dev_alloc_skb(MTK_SKB_1_5K, gfp_mask);
> +	else if (size > 0)
> +		return __dev_alloc_skb(MTK_SKB_16, gfp_mask);

Ditto.

> +	return NULL;
> +}

...

> +		for (i = 0; i < queue->max_len; i++) {
> +			struct sk_buff *skb;
> +
> +			skb = alloc_skb_from_kernel(skb_size, GFP_KERNEL);

> +

Redundant.

> +			if (!skb) {
> +				while ((skb = skb_dequeue(&queue->skb_list)))
> +					dev_kfree_skb_any(skb);
> +				return -ENOMEM;
> +			}
> +
> +			skb_queue_tail(&queue->skb_list, skb);
> +		}

...

> +/**
> + * ccci_alloc_skb_from_pool() - allocate memory for skb from pre-allocated pools
> + * @pools: skb pools
> + * @size: allocation size
> + * @blocking : true for blocking operation

Extra white space.

Again, revisit _all_ comments in your series and make them consistent in _all_
possible aspects (style, grammar, ...).

> + *
> + * Returns pointer to skb on success, NULL on failure.
> + */

...

> +	if (blocking) {

> +		might_sleep();

might_sleep_if() at the top of the function?

> +		skb = alloc_skb_from_kernel(size, GFP_KERNEL);
> +	} else {
> +		for (count = 0; count < ALLOC_SKB_RETRY; count++) {
> +			skb = alloc_skb_from_kernel(size, GFP_ATOMIC);
> +			if (skb)
> +				return skb;
> +		}
> +	}

...

> +	while (queue->skb_list.qlen < SKB_64K_POOL_SIZE) {
> +		skb = alloc_skb_from_kernel(MTK_SKB_64K, GFP_KERNEL);
> +		if (skb)
> +			skb_queue_tail(&queue->skb_list, skb);
> +	}

May it become an infinite loop?

...

> +	while (queue->skb_list.qlen < SKB_4K_POOL_SIZE) {
> +		skb = alloc_skb_from_kernel(MTK_SKB_4K, GFP_KERNEL);
> +		if (skb)
> +			skb_queue_tail(&queue->skb_list, skb);
> +	}

Ditto.

...

> +	while (queue->skb_list.qlen < SKB_16_POOL_SIZE) {
> +		skb = alloc_skb_from_kernel(MTK_SKB_16, GFP_KERNEL);
> +		if (skb)
> +			skb_queue_tail(&queue->skb_list, skb);
> +	}

Ditto.

...

> +	pools->reload_work_queue = alloc_workqueue("pool_reload_work",
> +						   WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI,
> +						   1);

	... wqflags = WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI;

	pools->reload_work_queue = alloc_workqueue("pool_reload_work", wqflags, 1);

> +	if (!pools->reload_work_queue) {
> +		ret = -ENOMEM;
> +		goto err_wq;
> +	}

...

> +	list_for_each_entry_safe(notifier_cur, notifier_next,
> +				 &ctl->notifier_list, entry) {

Out of a sudden this is two lines...

> +		if (notifier_cur == notifier)
> +			list_del(&notifier->entry);
> +	}

...

> +			if (!list_empty(&ctl->event_queue)) {
> +				event = list_first_entry(&ctl->event_queue,
> +							 struct ccci_fsm_event, entry);

	event = list_first_entry_or_null();
	if (event) {

> +				if (event->event_id == CCCI_EVENT_MD_EX) {
> +					fsm_finish_event(ctl, event);
> +				} else if (event->event_id == CCCI_EVENT_MD_EX_REC_OK) {
> +					rec_ok = true;
> +					fsm_finish_event(ctl, event);
> +				}
> +			}

...

> +			if (!list_empty(&ctl->event_queue)) {
> +				event = list_first_entry(&ctl->event_queue,
> +							 struct ccci_fsm_event, entry);
> +				if (event->event_id == CCCI_EVENT_MD_EX_PASS)
> +					fsm_finish_event(ctl, event);
> +			}

Ditto

...

> +	if (!atomic_read(&ctl->md->rgu_irq_asserted)) {

It may be set exactly here, what's the point in atomicity of the above check?

> +		/* disable DRM before FLDR */
> +		mhccif_h2d_swint_trigger(mtk_dev, H2D_CH_DRM_DISABLE_AP);
> +		msleep(FSM_DRM_DISABLE_DELAY_MS);
> +		/* try FLDR first */
> +		err = mtk_acpi_fldr_func(mtk_dev);
> +		if (err)
> +			mhccif_h2d_swint_trigger(mtk_dev, H2D_CH_DEVICE_RESET);
> +	}

...

> +	wait_event_interruptible_timeout(ctl->async_hk_wq,
> +					 atomic_read(&md->core_md.ready) ||
> +					 atomic_read(&ctl->exp_flg), HZ * 60);

Are you sure you understand what you are doing with the atomics?

> +	if (atomic_read(&ctl->exp_flg))
> +		dev_err(dev, "MD exception is captured during handshake\n");
> +
> +	if (!atomic_read(&md->core_md.ready)) {
> +		dev_err(dev, "MD handshake timeout\n");
> +		fsm_routine_exception(ctl, NULL, EXCEPTION_HS_TIMEOUT);
> +	} else {
> +		fsm_routine_ready(ctl);
> +	}

...

> +	read_poll_timeout(ioread32, dev_status, (dev_status & MISC_STAGE_MASK) == LINUX_STAGE,
> +			  20000, 2000000, false, IREG_BASE(md->mtk_dev) + PCIE_MISC_DEV_STATUS);

Why ignoring an error is fine here?

...

> +	cmd = kmalloc(sizeof(*cmd),
> +		      (in_irq() || in_softirq() || irqs_disabled()) ? GFP_ATOMIC : GFP_KERNEL);

Hmm...

> +	if (!cmd)
> +		return -ENOMEM;

> +	if (in_irq() || irqs_disabled())
> +		flag &= ~FSM_CMD_FLAG_WAITING_TO_COMPLETE;

Even more hmm...

> +	if (flag & FSM_CMD_FLAG_WAITING_TO_COMPLETE) {
> +		wait_event(cmd->complete_wq, cmd->result != FSM_CMD_RESULT_PENDING);

Is it okay in IRQ context?

> +		if (cmd->result != FSM_CMD_RESULT_OK)
> +			result = -EINVAL;

> +		spin_lock_irqsave(&ctl->cmd_complete_lock, flags);
> +		kfree(cmd);
> +		spin_unlock_irqrestore(&ctl->cmd_complete_lock, flags);

While this is under spin lock?

> +	}

...

> +enum md_state ccci_fsm_get_md_state(void)
> +{
> +	struct ccci_fsm_ctl *ctl;
> +
> +	ctl = ccci_fsm_entry;
> +	if (ctl)
> +		return ctl->md_state;
> +	else
> +		return MD_STATE_INVALID;
> +}
> +
> +unsigned int ccci_fsm_get_current_state(void)
> +{
> +	struct ccci_fsm_ctl *ctl;
> +
> +	ctl = ccci_fsm_entry;
> +	if (ctl)
> +		return ctl->curr_state;
> +	else
> +		return CCCI_FSM_STOPPED;
> +}

Redundant 'else' everywhere.

...

> +int ccci_fsm_init(struct mtk_modem *md)
> +{
> +	struct ccci_fsm_ctl *ctl;

	struct device *dev = &md->mtk_dev->pdev->dev;

...

> +	ctl->fsm_thread = kthread_run(fsm_main_thread, ctl, "ccci_fsm");
> +	if (IS_ERR(ctl->fsm_thread)) {
> +		dev_err(&md->mtk_dev->pdev->dev, "failed to start monitor thread\n");

> +		return PTR_ERR(ctl->fsm_thread);
> +	}
> +
> +	return 0;

	return PTR_ERR_OR_ZERO(...);

> +}
Sergey Ryazanov Nov. 6, 2021, 6:05 p.m. UTC | #2
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
<ricardo.martinez@linux.intel.com> wrote:
> Registers the t7xx device driver with the kernel. Setup all the core
> components: PCIe layer, Modem Host Cross Core Interface (MHCCIF),
> modem control operations, modem state machine, and build
> infrastructure.
>
> * PCIe layer code implements driver probe and removal.
> * MHCCIF provides interrupt channels to communicate events
>   such as handshake, PM and port enumeration.
> * Modem control implements the entry point for modem init,
>   reset and exit.
> * The modem status monitor is a state machine used by modem control
>   to complete initialization and stop. It is used also to propagate
>   exception events reported by other components.

[skipped]

>  drivers/net/wwan/t7xx/t7xx_monitor.h       | 144 +++++
> ...
>  drivers/net/wwan/t7xx/t7xx_state_monitor.c | 598 +++++++++++++++++++++

Out of curiosity, why is this file called t7xx_state_monitor.c, while
the corresponding header file is called simply t7xx_monitor.h? Are any
other monitors planed?

[skipped]

> diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig
> ...
> +config MTK_T7XX
> +       tristate "MediaTek PCIe 5G WWAN modem T7XX device"

As already suggested by Andy, using "T7xx" (lowercase x) in the title
to make it more readable.

> +       depends on PCI
> +       help
> +         Enables MediaTek PCIe based 5G WWAN modem (T700 series) device.

Maybe use the term "T7xx series" here too? Otherwise, it sounds like a
driver for the smartphone chipset only.

> +         Adapts WWAN framework and provides network interface like wwan0
> +         and tty interfaces like wwan0at0 (AT protocol), wwan0mbim0
> +         (MBIM protocol), etc.

[skipped]

> diff --git a/drivers/net/wwan/t7xx/t7xx_common.h b/drivers/net/wwan/t7xx/t7xx_common.h
> ...
> +struct ccci_header {
> +       /* do not assume data[1] is data length in rx */
> +       u32 data[2];

If these fields have different meaning on Tx and Rx you could define
them using a union. E.g.

        union {
                struct {
                       __le32 idx;
                       __le32 type;
                } rx;
                struct {
                       __le32 idx;
                      __le32 len;
                } tx;
        };
        __le32 status;

or even like this:

        __le32 idx;
        union {
                __le32 rx_type;
                __le32 tx_len;
        };
        __le32 status;

Such definition better documents code then the comment above the field.

> +       u32 status;
> +       u32 reserved;
> +};

All these fields should have a type of __le32 since the structure
passed to the modem as is.

[skipped]

> diff --git a/drivers/net/wwan/t7xx/t7xx_pci.c b/drivers/net/wwan/t7xx/t7xx_pci.c
> ...
> +static int __init mtk_pci_init(void)
> +{
> +       return pci_register_driver(&mtk_pci_driver);
> +}
> +module_init(mtk_pci_init);
> +
> +static void __exit mtk_pci_cleanup(void)
> +{
> +       pci_unregister_driver(&mtk_pci_driver);
> +}
> +module_exit(mtk_pci_cleanup);

Since the module does not do anything specific on (de-)initialization
use the module_pci_driver() macro instead of this boilerplate code.

[skipped]

> diff --git a/drivers/net/wwan/t7xx/t7xx_skb_util.c b/drivers/net/wwan/t7xx/t7xx_skb_util.c
> ...
> +static struct sk_buff *alloc_skb_from_pool(struct skb_pools *pools, size_t size)
> +{
> +       if (size > MTK_SKB_4K)
> +               return ccci_skb_dequeue(pools->reload_work_queue, &pools->skb_pool_64k);
> +       else if (size > MTK_SKB_16)
> +               return ccci_skb_dequeue(pools->reload_work_queue, &pools->skb_pool_4k);
> +       else if (size > 0)
> +               return ccci_skb_dequeue(pools->reload_work_queue, &pools->skb_pool_16);
> +
> +       return NULL;
> +}
> +
> +static struct sk_buff *alloc_skb_from_kernel(size_t size, gfp_t gfp_mask)
> +{
> +       if (size > MTK_SKB_4K)
> +               return __dev_alloc_skb(MTK_SKB_64K, gfp_mask);
> +       else if (size > MTK_SKB_1_5K)
> +               return __dev_alloc_skb(MTK_SKB_4K, gfp_mask);
> +       else if (size > MTK_SKB_16)
> +               return __dev_alloc_skb(MTK_SKB_1_5K, gfp_mask);
> +       else if (size > 0)
> +               return __dev_alloc_skb(MTK_SKB_16, gfp_mask);
> +
> +       return NULL;
> +}

I am wondering what performance gains have you achieved with these skb
pools? Can we see any numbers?

I do not think the control path performance is worth the complexity of
the multilayer skb allocation. In the data packet Rx path, you need to
allocate skb anyway as soon as the driver passes them to the stack. So
what is the gain?

[skipped]

> diff --git a/drivers/net/wwan/t7xx/t7xx_state_monitor.c b/drivers/net/wwan/t7xx/t7xx_state_monitor.c
> ...
> +static struct ccci_fsm_ctl *ccci_fsm_entry;

Place this pointer at least to the mtk_modem structure. Otherwise,
with this global pointer, the driver will break as soon as a second
modem will be connected to the host.

Also all functions in this file also should be reworked to be able to
accept modem state container pointer, e.g. mtk_modem or some other
related structure.

--
Sergey
Ricardo Martinez Dec. 2, 2021, 10:42 p.m. UTC | #3
On 11/6/2021 11:05 AM, Sergey Ryazanov wrote:
> On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
> <ricardo.martinez@linux.intel.com> wrote:
>> Registers the t7xx device driver with the kernel. Setup all the core
>> components: PCIe layer, Modem Host Cross Core Interface (MHCCIF),
>> modem control operations, modem state machine, and build
>> infrastructure.
>>
>> * PCIe layer code implements driver probe and removal.
>> * MHCCIF provides interrupt channels to communicate events
>>    such as handshake, PM and port enumeration.
>> * Modem control implements the entry point for modem init,
>>    reset and exit.
>> * The modem status monitor is a state machine used by modem control
>>    to complete initialization and stop. It is used also to propagate
>>    exception events reported by other components.
> [skipped]
>
>>   drivers/net/wwan/t7xx/t7xx_monitor.h       | 144 +++++
>> ...
>>   drivers/net/wwan/t7xx/t7xx_state_monitor.c | 598 +++++++++++++++++++++
> Out of curiosity, why is this file called t7xx_state_monitor.c, while
> the corresponding header file is called simply t7xx_monitor.h? Are any
> other monitors planed?
>
> [skipped]

No other monitors, I'll rename it to make it consistent.

[skipped]

>
>> diff --git a/drivers/net/wwan/t7xx/t7xx_skb_util.c b/drivers/net/wwan/t7xx/t7xx_skb_util.c
>> ...
>> +static struct sk_buff *alloc_skb_from_pool(struct skb_pools *pools, size_t size)
>> +{
>> +       if (size > MTK_SKB_4K)
>> +               return ccci_skb_dequeue(pools->reload_work_queue, &pools->skb_pool_64k);
>> +       else if (size > MTK_SKB_16)
>> +               return ccci_skb_dequeue(pools->reload_work_queue, &pools->skb_pool_4k);
>> +       else if (size > 0)
>> +               return ccci_skb_dequeue(pools->reload_work_queue, &pools->skb_pool_16);
>> +
>> +       return NULL;
>> +}
>> +
>> +static struct sk_buff *alloc_skb_from_kernel(size_t size, gfp_t gfp_mask)
>> +{
>> +       if (size > MTK_SKB_4K)
>> +               return __dev_alloc_skb(MTK_SKB_64K, gfp_mask);
>> +       else if (size > MTK_SKB_1_5K)
>> +               return __dev_alloc_skb(MTK_SKB_4K, gfp_mask);
>> +       else if (size > MTK_SKB_16)
>> +               return __dev_alloc_skb(MTK_SKB_1_5K, gfp_mask);
>> +       else if (size > 0)
>> +               return __dev_alloc_skb(MTK_SKB_16, gfp_mask);
>> +
>> +       return NULL;
>> +}
> I am wondering what performance gains have you achieved with these skb
> pools? Can we see any numbers?
>
> I do not think the control path performance is worth the complexity of
> the multilayer skb allocation. In the data packet Rx path, you need to
> allocate skb anyway as soon as the driver passes them to the stack. So
> what is the gain?
>
> [skipped]

Agree, we are removing the skb pools for the control path.

Regarding Rx data path, we'll get some numbers to see if the pool is 
worth it,

otherwise remove it too.

[skipped]
diff mbox series

Patch

diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig
index 17543be14665..98e29616c025 100644
--- a/drivers/net/wwan/Kconfig
+++ b/drivers/net/wwan/Kconfig
@@ -80,6 +80,20 @@  config IOSM
 
 	  If unsure, say N.
 
+config MTK_T7XX
+	tristate "MediaTek PCIe 5G WWAN modem T7XX device"
+	depends on PCI
+	help
+	  Enables MediaTek PCIe based 5G WWAN modem (T700 series) device.
+	  Adapts WWAN framework and provides network interface like wwan0
+	  and tty interfaces like wwan0at0 (AT protocol), wwan0mbim0
+	  (MBIM protocol), etc.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called mtk_t7xx.
+
+	  If unsure, say N.
+
 endif # WWAN
 
 endmenu
diff --git a/drivers/net/wwan/Makefile b/drivers/net/wwan/Makefile
index fe51feedac21..ae913797efa4 100644
--- a/drivers/net/wwan/Makefile
+++ b/drivers/net/wwan/Makefile
@@ -12,3 +12,4 @@  obj-$(CONFIG_MHI_WWAN_CTRL) += mhi_wwan_ctrl.o
 obj-$(CONFIG_MHI_WWAN_MBIM) += mhi_wwan_mbim.o
 obj-$(CONFIG_RPMSG_WWAN_CTRL) += rpmsg_wwan_ctrl.o
 obj-$(CONFIG_IOSM) += iosm/
+obj-$(CONFIG_MTK_T7XX) += t7xx/
diff --git a/drivers/net/wwan/t7xx/Makefile b/drivers/net/wwan/t7xx/Makefile
new file mode 100644
index 000000000000..dc0e6e025d55
--- /dev/null
+++ b/drivers/net/wwan/t7xx/Makefile
@@ -0,0 +1,13 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+
+ccflags-y += -Werror
+
+obj-${CONFIG_MTK_T7XX} := mtk_t7xx.o
+mtk_t7xx-y:=	t7xx_pci.o \
+		t7xx_pcie_mac.o \
+		t7xx_mhccif.o \
+		t7xx_state_monitor.o  \
+		t7xx_modem_ops.o \
+		t7xx_skb_util.o \
+		t7xx_cldma.o \
+		t7xx_hif_cldma.o  \
diff --git a/drivers/net/wwan/t7xx/t7xx_common.h b/drivers/net/wwan/t7xx/t7xx_common.h
new file mode 100644
index 000000000000..816acddb1481
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_common.h
@@ -0,0 +1,76 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
+ *               Eliot Lee <eliot.lee@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#ifndef __T7XX_COMMON_H__
+#define __T7XX_COMMON_H__
+
+#include <linux/bits.h>
+#include <linux/types.h>
+
+struct ccci_header {
+	/* do not assume data[1] is data length in rx */
+	u32 data[2];
+	u32 status;
+	u32 reserved;
+};
+
+enum txq_type {
+	TXQ_NORMAL,
+	TXQ_FAST,
+	TXQ_TYPE_CNT
+};
+
+enum direction {
+	MTK_IN,
+	MTK_OUT,
+	MTK_INOUT,
+};
+
+#define HDR_FLD_AST		BIT(31)
+#define HDR_FLD_SEQ		GENMASK(30, 16)
+#define HDR_FLD_CHN		GENMASK(15, 0)
+
+#define CCCI_H_LEN		16
+/* CCCI_H_LEN + reserved space that is used in exception flow */
+#define CCCI_H_ELEN		128
+
+#define CCCI_HEADER_NO_DATA	0xffffffff
+
+/* Control identification numbers for AP<->MD messages  */
+#define CTL_ID_HS1_MSG		0x0
+#define CTL_ID_HS2_MSG		0x1
+#define CTL_ID_HS3_MSG		0x2
+#define CTL_ID_MD_EX		0x4
+#define CTL_ID_DRV_VER_ERROR	0x5
+#define CTL_ID_MD_EX_ACK	0x6
+#define CTL_ID_MD_EX_PASS	0x8
+#define CTL_ID_PORT_ENUM	0x9
+
+/* Modem exception check identification number */
+#define MD_EX_CHK_ID		0x45584350
+/* Modem exception check acknowledge identification number */
+#define MD_EX_CHK_ACK_ID	0x45524543
+
+enum md_state {
+	MD_STATE_INVALID, /* no traffic */
+	MD_STATE_GATED, /* no traffic */
+	MD_STATE_WAITING_FOR_HS1,
+	MD_STATE_WAITING_FOR_HS2,
+	MD_STATE_READY,
+	MD_STATE_EXCEPTION,
+	MD_STATE_RESET, /* no traffic */
+	MD_STATE_WAITING_TO_STOP,
+	MD_STATE_STOPPED,
+};
+
+#endif
diff --git a/drivers/net/wwan/t7xx/t7xx_mhccif.c b/drivers/net/wwan/t7xx/t7xx_mhccif.c
new file mode 100644
index 000000000000..927aeb39e313
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_mhccif.c
@@ -0,0 +1,105 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#include <linux/completion.h>
+#include <linux/pci.h>
+
+#include "t7xx_mhccif.h"
+#include "t7xx_modem_ops.h"
+#include "t7xx_pci.h"
+#include "t7xx_pcie_mac.h"
+
+static void mhccif_clear_interrupts(struct mtk_pci_dev *mtk_dev, u32 mask)
+{
+	void __iomem *mhccif_pbase;
+
+	mhccif_pbase = mtk_dev->base_addr.mhccif_rc_base;
+
+	/* Clear level 2 interrupt */
+	iowrite32(mask, mhccif_pbase + REG_EP2RC_SW_INT_ACK);
+
+	/* Read back to ensure write is done */
+	mhccif_read_sw_int_sts(mtk_dev);
+
+	/* Clear level 1 interrupt */
+	mtk_pcie_mac_clear_int_status(mtk_dev, MHCCIF_INT);
+}
+
+static irqreturn_t mhccif_isr_thread(int irq, void *data)
+{
+	struct mtk_pci_dev *mtk_dev;
+	u32 int_sts;
+
+	mtk_dev = data;
+
+	/* Use 1*4 bits to avoid low power bits*/
+	iowrite32(L1_1_DISABLE_BIT(1) | L1_2_DISABLE_BIT(1),
+		  IREG_BASE(mtk_dev) + DIS_ASPM_LOWPWR_SET_0);
+
+	int_sts = mhccif_read_sw_int_sts(mtk_dev);
+	if (int_sts & mtk_dev->mhccif_bitmask)
+		mtk_pci_mhccif_isr(mtk_dev);
+
+	/* Clear 2 & 1 level interrupts */
+	mhccif_clear_interrupts(mtk_dev, int_sts);
+
+	/* Enable corresponding interrupt */
+	mtk_pcie_mac_set_int(mtk_dev, MHCCIF_INT);
+	return IRQ_HANDLED;
+}
+
+u32 mhccif_read_sw_int_sts(struct mtk_pci_dev *mtk_dev)
+{
+	return ioread32(mtk_dev->base_addr.mhccif_rc_base + REG_EP2RC_SW_INT_STS);
+}
+
+void mhccif_mask_set(struct mtk_pci_dev *mtk_dev, u32 val)
+{
+	iowrite32(val, mtk_dev->base_addr.mhccif_rc_base + REG_EP2RC_SW_INT_EAP_MASK_SET);
+}
+
+void mhccif_mask_clr(struct mtk_pci_dev *mtk_dev, u32 val)
+{
+	iowrite32(val, mtk_dev->base_addr.mhccif_rc_base + REG_EP2RC_SW_INT_EAP_MASK_CLR);
+}
+
+u32 mhccif_mask_get(struct mtk_pci_dev *mtk_dev)
+{
+	/* mtk_dev is validated in calling function */
+	return ioread32(mtk_dev->base_addr.mhccif_rc_base + REG_EP2RC_SW_INT_EAP_MASK);
+}
+
+static irqreturn_t mhccif_isr_handler(int irq, void *data)
+{
+	return IRQ_WAKE_THREAD;
+}
+
+void mhccif_init(struct mtk_pci_dev *mtk_dev)
+{
+	mtk_dev->base_addr.mhccif_rc_base = mtk_dev->base_addr.pcie_ext_reg_base +
+					    MHCCIF_RC_DEV_BASE -
+					    mtk_dev->base_addr.pcie_dev_reg_trsl_addr;
+
+	/* Register MHCCHIF int handler to handle */
+	mtk_dev->intr_handler[MHCCIF_INT] = mhccif_isr_handler;
+	mtk_dev->intr_thread[MHCCIF_INT] = mhccif_isr_thread;
+	mtk_dev->callback_param[MHCCIF_INT] = mtk_dev;
+}
+
+void mhccif_h2d_swint_trigger(struct mtk_pci_dev *mtk_dev, u32 channel)
+{
+	void __iomem *mhccif_pbase;
+
+	mhccif_pbase = mtk_dev->base_addr.mhccif_rc_base;
+	iowrite32(BIT(channel), mhccif_pbase + REG_RC2EP_SW_BSY);
+	iowrite32(channel, mhccif_pbase + REG_RC2EP_SW_TCHNUM);
+}
diff --git a/drivers/net/wwan/t7xx/t7xx_mhccif.h b/drivers/net/wwan/t7xx/t7xx_mhccif.h
new file mode 100644
index 000000000000..930c20f1ec78
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_mhccif.h
@@ -0,0 +1,35 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#ifndef __T7XX_MHCCIF_H__
+#define __T7XX_MHCCIF_H__
+
+#include <linux/types.h>
+
+#include "t7xx_pci.h"
+#include "t7xx_reg.h"
+
+#define D2H_SW_INT_MASK (D2H_INT_EXCEPTION_INIT |		\
+			 D2H_INT_EXCEPTION_INIT_DONE |		\
+			 D2H_INT_EXCEPTION_CLEARQ_DONE |	\
+			 D2H_INT_EXCEPTION_ALLQ_RESET |		\
+			 D2H_INT_PORT_ENUM |			\
+			 D2H_INT_ASYNC_MD_HK)
+
+void mhccif_mask_set(struct mtk_pci_dev *mtk_dev, u32 val);
+void mhccif_mask_clr(struct mtk_pci_dev *mtk_dev, u32 val);
+u32 mhccif_mask_get(struct mtk_pci_dev *mtk_dev);
+void mhccif_init(struct mtk_pci_dev *mtk_dev);
+u32 mhccif_read_sw_int_sts(struct mtk_pci_dev *mtk_dev);
+void mhccif_h2d_swint_trigger(struct mtk_pci_dev *mtk_dev, u32 channel);
+
+#endif /*__T7XX_MHCCIF_H__ */
diff --git a/drivers/net/wwan/t7xx/t7xx_modem_ops.c b/drivers/net/wwan/t7xx/t7xx_modem_ops.c
new file mode 100644
index 000000000000..b80a36e09523
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_modem_ops.c
@@ -0,0 +1,486 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
+ *               Eliot Lee <eliot.lee@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+
+#include "t7xx_hif_cldma.h"
+#include "t7xx_mhccif.h"
+#include "t7xx_modem_ops.h"
+#include "t7xx_monitor.h"
+#include "t7xx_pci.h"
+#include "t7xx_pcie_mac.h"
+
+#define RGU_RESET_DELAY_US	20
+#define PORT_RESET_DELAY_US	2000
+
+enum mtk_feature_support_type {
+	MTK_FEATURE_DOES_NOT_EXIST,
+	MTK_FEATURE_NOT_SUPPORTED,
+	MTK_FEATURE_MUST_BE_SUPPORTED,
+};
+
+static inline unsigned int get_interrupt_status(struct mtk_pci_dev *mtk_dev)
+{
+	return mhccif_read_sw_int_sts(mtk_dev) & D2H_SW_INT_MASK;
+}
+
+/**
+ * mtk_pci_mhccif_isr() - Process MHCCIF interrupts
+ * @mtk_dev: MTK device
+ *
+ * Check the interrupt status, and queue commands accordingly
+ *
+ * Returns: 0 on success or -EINVAL on failure
+ */
+int mtk_pci_mhccif_isr(struct mtk_pci_dev *mtk_dev)
+{
+	struct md_sys_info *md_info;
+	struct ccci_fsm_ctl *ctl;
+	struct mtk_modem *md;
+	unsigned int int_sta;
+	unsigned long flags;
+	u32 mask;
+
+	md = mtk_dev->md;
+	ctl = fsm_get_entry();
+	if (!ctl) {
+		dev_err(&mtk_dev->pdev->dev,
+			"process MHCCIF interrupt before modem monitor was initialized\n");
+		return -EINVAL;
+	}
+
+	md_info = md->md_info;
+	spin_lock_irqsave(&md_info->exp_spinlock, flags);
+	int_sta = get_interrupt_status(mtk_dev);
+	md_info->exp_id |= int_sta;
+
+	if (md_info->exp_id & D2H_INT_PORT_ENUM) {
+		md_info->exp_id &= ~D2H_INT_PORT_ENUM;
+		if (ctl->curr_state == CCCI_FSM_INIT ||
+		    ctl->curr_state == CCCI_FSM_PRE_START ||
+		    ctl->curr_state == CCCI_FSM_STOPPED)
+			ccci_fsm_recv_md_interrupt(MD_IRQ_PORT_ENUM);
+	}
+
+	if (md_info->exp_id & D2H_INT_EXCEPTION_INIT) {
+		if (ctl->md_state == MD_STATE_INVALID ||
+		    ctl->md_state == MD_STATE_WAITING_FOR_HS1 ||
+		    ctl->md_state == MD_STATE_WAITING_FOR_HS2 ||
+		    ctl->md_state == MD_STATE_READY) {
+			md_info->exp_id &= ~D2H_INT_EXCEPTION_INIT;
+			ccci_fsm_recv_md_interrupt(MD_IRQ_CCIF_EX);
+		}
+	} else if (ctl->md_state == MD_STATE_WAITING_FOR_HS1) {
+		/* start handshake if MD not assert */
+		mask = mhccif_mask_get(mtk_dev);
+		if ((md_info->exp_id & D2H_INT_ASYNC_MD_HK) && !(mask & D2H_INT_ASYNC_MD_HK)) {
+			md_info->exp_id &= ~D2H_INT_ASYNC_MD_HK;
+			queue_work(md->handshake_wq, &md->handshake_work);
+		}
+	}
+
+	spin_unlock_irqrestore(&md_info->exp_spinlock, flags);
+
+	return 0;
+}
+
+static void clr_device_irq_via_pcie(struct mtk_pci_dev *mtk_dev)
+{
+	struct mtk_addr_base *pbase_addr;
+	void __iomem *rgu_pciesta_reg;
+
+	pbase_addr = &mtk_dev->base_addr;
+	rgu_pciesta_reg = pbase_addr->pcie_ext_reg_base + TOPRGU_CH_PCIE_IRQ_STA -
+			  pbase_addr->pcie_dev_reg_trsl_addr;
+
+	/* clear RGU PCIe IRQ state */
+	iowrite32(ioread32(rgu_pciesta_reg), rgu_pciesta_reg);
+}
+
+void mtk_clear_rgu_irq(struct mtk_pci_dev *mtk_dev)
+{
+	/* clear L2 */
+	clr_device_irq_via_pcie(mtk_dev);
+	/* clear L1 */
+	mtk_pcie_mac_clear_int_status(mtk_dev, SAP_RGU_INT);
+}
+
+static int mtk_acpi_reset(struct mtk_pci_dev *mtk_dev, char *fn_name)
+{
+#ifdef CONFIG_ACPI
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	acpi_status acpi_ret;
+	struct device *dev;
+	acpi_handle handle;
+
+	dev = &mtk_dev->pdev->dev;
+
+	if (acpi_disabled) {
+		dev_err(dev, "acpi function isn't enabled\n");
+		return -EFAULT;
+	}
+
+	handle = ACPI_HANDLE(dev);
+	if (!handle) {
+		dev_err(dev, "acpi handle isn't found\n");
+		return -EFAULT;
+	}
+
+	if (!acpi_has_method(handle, fn_name)) {
+		dev_err(dev, "%s method isn't found\n", fn_name);
+		return -EFAULT;
+	}
+
+	acpi_ret = acpi_evaluate_object(handle, fn_name, NULL, &buffer);
+	if (ACPI_FAILURE(acpi_ret)) {
+		dev_err(dev, "%s method fail: %s\n", fn_name, acpi_format_exception(acpi_ret));
+		return -EFAULT;
+	}
+#endif
+	return 0;
+}
+
+int mtk_acpi_fldr_func(struct mtk_pci_dev *mtk_dev)
+{
+	return mtk_acpi_reset(mtk_dev, "_RST");
+}
+
+static void reset_device_via_pmic(struct mtk_pci_dev *mtk_dev)
+{
+	unsigned int val;
+
+	val = ioread32(IREG_BASE(mtk_dev) + PCIE_MISC_DEV_STATUS);
+
+	if (val & MISC_RESET_TYPE_PLDR)
+		mtk_acpi_reset(mtk_dev, "MRST._RST");
+	else if (val & MISC_RESET_TYPE_FLDR)
+		mtk_acpi_fldr_func(mtk_dev);
+}
+
+static irqreturn_t rgu_isr_thread(int irq, void *data)
+{
+	struct mtk_pci_dev *mtk_dev;
+	struct mtk_modem *modem;
+
+	mtk_dev = data;
+	modem = mtk_dev->md;
+
+	msleep(RGU_RESET_DELAY_US);
+	reset_device_via_pmic(modem->mtk_dev);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t rgu_isr_handler(int irq, void *data)
+{
+	struct mtk_pci_dev *mtk_dev;
+	struct mtk_modem *modem;
+
+	mtk_dev = data;
+	modem = mtk_dev->md;
+
+	mtk_clear_rgu_irq(mtk_dev);
+
+	if (!mtk_dev->rgu_pci_irq_en)
+		return IRQ_HANDLED;
+
+	atomic_set(&modem->rgu_irq_asserted, 1);
+	mtk_pcie_mac_clear_int(mtk_dev, SAP_RGU_INT);
+	return IRQ_WAKE_THREAD;
+}
+
+static void mtk_pcie_register_rgu_isr(struct mtk_pci_dev *mtk_dev)
+{
+	/* registers RGU callback isr with PCIe driver */
+	mtk_pcie_mac_clear_int(mtk_dev, SAP_RGU_INT);
+	mtk_pcie_mac_clear_int_status(mtk_dev, SAP_RGU_INT);
+
+	mtk_dev->intr_handler[SAP_RGU_INT] = rgu_isr_handler;
+	mtk_dev->intr_thread[SAP_RGU_INT] = rgu_isr_thread;
+	mtk_dev->callback_param[SAP_RGU_INT] = mtk_dev;
+	mtk_pcie_mac_set_int(mtk_dev, SAP_RGU_INT);
+}
+
+static void md_exception(struct mtk_modem *md, enum hif_ex_stage stage)
+{
+	struct mtk_pci_dev *mtk_dev;
+
+	mtk_dev = md->mtk_dev;
+
+	if (stage == HIF_EX_CLEARQ_DONE)
+		/* give DHL time to flush data.
+		 * this is an empirical value that assure
+		 * that DHL have enough time to flush all the date.
+		 */
+		msleep(PORT_RESET_DELAY_US);
+
+	cldma_exception(ID_CLDMA1, stage);
+
+	if (stage == HIF_EX_INIT)
+		mhccif_h2d_swint_trigger(mtk_dev, H2D_CH_EXCEPTION_ACK);
+	else if (stage == HIF_EX_CLEARQ_DONE)
+		mhccif_h2d_swint_trigger(mtk_dev, H2D_CH_EXCEPTION_CLEARQ_ACK);
+}
+
+static int wait_hif_ex_hk_event(struct mtk_modem *md, int event_id)
+{
+	struct md_sys_info *md_info;
+	int sleep_time = 10;
+	int retries = 500; /* MD timeout is 5s */
+
+	md_info = md->md_info;
+	do {
+		if (md_info->exp_id & event_id)
+			return 0;
+
+		msleep(sleep_time);
+	} while (--retries);
+
+	return -EFAULT;
+}
+
+static void md_sys_sw_init(struct mtk_pci_dev *mtk_dev)
+{
+	/* Register the MHCCIF isr for MD exception, port enum and
+	 * async handshake notifications.
+	 */
+	mhccif_mask_set(mtk_dev, D2H_SW_INT_MASK);
+	mtk_dev->mhccif_bitmask = D2H_SW_INT_MASK;
+	mhccif_mask_clr(mtk_dev, D2H_INT_PORT_ENUM);
+
+	/* register RGU irq handler for sAP exception notification */
+	mtk_dev->rgu_pci_irq_en = true;
+	mtk_pcie_register_rgu_isr(mtk_dev);
+}
+
+static void md_hk_wq(struct work_struct *work)
+{
+	struct ccci_fsm_ctl *ctl;
+	struct mtk_modem *md;
+
+	ctl = fsm_get_entry();
+
+	cldma_switch_cfg(ID_CLDMA1);
+	cldma_start(ID_CLDMA1);
+	fsm_broadcast_state(ctl, MD_STATE_WAITING_FOR_HS2);
+	md = container_of(work, struct mtk_modem, handshake_work);
+	atomic_set(&md->core_md.ready, 1);
+	wake_up(&ctl->async_hk_wq);
+}
+
+void mtk_md_event_notify(struct mtk_modem *md, enum md_event_id evt_id)
+{
+	struct md_sys_info *md_info;
+	void __iomem *mhccif_base;
+	struct ccci_fsm_ctl *ctl;
+	unsigned int int_sta;
+	unsigned long flags;
+
+	ctl = fsm_get_entry();
+	md_info = md->md_info;
+
+	switch (evt_id) {
+	case FSM_PRE_START:
+		mhccif_mask_clr(md->mtk_dev, D2H_INT_PORT_ENUM);
+		break;
+
+	case FSM_START:
+		mhccif_mask_set(md->mtk_dev, D2H_INT_PORT_ENUM);
+		spin_lock_irqsave(&md_info->exp_spinlock, flags);
+		int_sta = get_interrupt_status(md->mtk_dev);
+		md_info->exp_id |= int_sta;
+		if (md_info->exp_id & D2H_INT_EXCEPTION_INIT) {
+			atomic_set(&ctl->exp_flg, 1);
+			md_info->exp_id &= ~D2H_INT_EXCEPTION_INIT;
+			md_info->exp_id &= ~D2H_INT_ASYNC_MD_HK;
+		} else if (atomic_read(&ctl->exp_flg)) {
+			md_info->exp_id &= ~D2H_INT_ASYNC_MD_HK;
+		} else if (md_info->exp_id & D2H_INT_ASYNC_MD_HK) {
+			queue_work(md->handshake_wq, &md->handshake_work);
+			md_info->exp_id &= ~D2H_INT_ASYNC_MD_HK;
+			mhccif_base = md->mtk_dev->base_addr.mhccif_rc_base;
+			iowrite32(D2H_INT_ASYNC_MD_HK, mhccif_base + REG_EP2RC_SW_INT_ACK);
+			mhccif_mask_set(md->mtk_dev, D2H_INT_ASYNC_MD_HK);
+		} else {
+			/* unmask async handshake interrupt */
+			mhccif_mask_clr(md->mtk_dev, D2H_INT_ASYNC_MD_HK);
+		}
+
+		spin_unlock_irqrestore(&md_info->exp_spinlock, flags);
+		/* unmask exception interrupt */
+		mhccif_mask_clr(md->mtk_dev,
+				D2H_INT_EXCEPTION_INIT |
+				D2H_INT_EXCEPTION_INIT_DONE |
+				D2H_INT_EXCEPTION_CLEARQ_DONE |
+				D2H_INT_EXCEPTION_ALLQ_RESET);
+		break;
+
+	case FSM_READY:
+		/* mask async handshake interrupt */
+		mhccif_mask_set(md->mtk_dev, D2H_INT_ASYNC_MD_HK);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static void md_structure_reset(struct mtk_modem *md)
+{
+	struct md_sys_info *md_info;
+
+	md_info = md->md_info;
+	md_info->exp_id = 0;
+	spin_lock_init(&md_info->exp_spinlock);
+}
+
+void mtk_md_exception_handshake(struct mtk_modem *md)
+{
+	struct mtk_pci_dev *mtk_dev;
+	int ret;
+
+	mtk_dev = md->mtk_dev;
+	md_exception(md, HIF_EX_INIT);
+	ret = wait_hif_ex_hk_event(md, D2H_INT_EXCEPTION_INIT_DONE);
+
+	if (ret)
+		dev_err(&mtk_dev->pdev->dev, "EX CCIF HS timeout, RCH 0x%lx\n",
+			D2H_INT_EXCEPTION_INIT_DONE);
+
+	md_exception(md, HIF_EX_INIT_DONE);
+	ret = wait_hif_ex_hk_event(md, D2H_INT_EXCEPTION_CLEARQ_DONE);
+	if (ret)
+		dev_err(&mtk_dev->pdev->dev, "EX CCIF HS timeout, RCH 0x%lx\n",
+			D2H_INT_EXCEPTION_CLEARQ_DONE);
+
+	md_exception(md, HIF_EX_CLEARQ_DONE);
+	ret = wait_hif_ex_hk_event(md, D2H_INT_EXCEPTION_ALLQ_RESET);
+	if (ret)
+		dev_err(&mtk_dev->pdev->dev, "EX CCIF HS timeout, RCH 0x%lx\n",
+			D2H_INT_EXCEPTION_ALLQ_RESET);
+
+	md_exception(md, HIF_EX_ALLQ_RESET);
+}
+
+static struct mtk_modem *ccci_md_alloc(struct mtk_pci_dev *mtk_dev)
+{
+	struct mtk_modem *md;
+
+	md = devm_kzalloc(&mtk_dev->pdev->dev, sizeof(*md), GFP_KERNEL);
+	if (!md)
+		return NULL;
+
+	md->md_info = devm_kzalloc(&mtk_dev->pdev->dev, sizeof(*md->md_info), GFP_KERNEL);
+	if (!md->md_info)
+		return NULL;
+
+	md->mtk_dev = mtk_dev;
+	mtk_dev->md = md;
+	atomic_set(&md->core_md.ready, 0);
+	md->handshake_wq = alloc_workqueue("%s",
+					   WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI,
+					   0, "md_hk_wq");
+	if (!md->handshake_wq)
+		return NULL;
+
+	INIT_WORK(&md->handshake_work, md_hk_wq);
+	md->core_md.feature_set[RT_ID_MD_PORT_ENUM] &= ~FEATURE_MSK;
+	md->core_md.feature_set[RT_ID_MD_PORT_ENUM] |=
+		FIELD_PREP(FEATURE_MSK, MTK_FEATURE_MUST_BE_SUPPORTED);
+	return md;
+}
+
+void mtk_md_reset(struct mtk_pci_dev *mtk_dev)
+{
+	struct mtk_modem *md;
+
+	md = mtk_dev->md;
+	md->md_init_finish = false;
+	md_structure_reset(md);
+	ccci_fsm_reset();
+	cldma_reset(ID_CLDMA1);
+	md->md_init_finish = true;
+}
+
+/**
+ * mtk_md_init() - Initialize modem
+ * @mtk_dev: MTK device
+ *
+ * Allocate and initialize MD ctrl block, and initialize data path
+ * Register MHCCIF ISR and RGU ISR, and start the state machine
+ *
+ * Return: 0 on success or -ENOMEM on allocation failure
+ */
+int mtk_md_init(struct mtk_pci_dev *mtk_dev)
+{
+	struct ccci_fsm_ctl *fsm_ctl;
+	struct mtk_modem *md;
+	int ret;
+
+	/* allocate and initialize md ctrl memory */
+	md = ccci_md_alloc(mtk_dev);
+	if (!md)
+		return -ENOMEM;
+
+	ret = cldma_alloc(ID_CLDMA1, mtk_dev);
+	if (ret)
+		goto err_alloc;
+
+	/* initialize md ctrl block */
+	md_structure_reset(md);
+
+	ret = ccci_fsm_init(md);
+	if (ret)
+		goto err_alloc;
+
+	ret = cldma_init(ID_CLDMA1);
+	if (ret)
+		goto err_fsm_init;
+
+	fsm_ctl = fsm_get_entry();
+	fsm_append_command(fsm_ctl, CCCI_COMMAND_START, 0);
+
+	md_sys_sw_init(mtk_dev);
+
+	md->md_init_finish = true;
+	return 0;
+
+err_fsm_init:
+	ccci_fsm_uninit();
+err_alloc:
+	destroy_workqueue(md->handshake_wq);
+
+	dev_err(&mtk_dev->pdev->dev, "modem init failed\n");
+	return ret;
+}
+
+void mtk_md_exit(struct mtk_pci_dev *mtk_dev)
+{
+	struct mtk_modem *md = mtk_dev->md;
+	struct ccci_fsm_ctl *fsm_ctl;
+
+	mtk_pcie_mac_clear_int(mtk_dev, SAP_RGU_INT);
+
+	if (!md->md_init_finish)
+		return;
+
+	fsm_ctl = fsm_get_entry();
+	/* change FSM state, will auto jump to stopped */
+	fsm_append_command(fsm_ctl, CCCI_COMMAND_PRE_STOP, 1);
+	cldma_exit(ID_CLDMA1);
+	ccci_fsm_uninit();
+	destroy_workqueue(md->handshake_wq);
+}
diff --git a/drivers/net/wwan/t7xx/t7xx_modem_ops.h b/drivers/net/wwan/t7xx/t7xx_modem_ops.h
new file mode 100644
index 000000000000..11aa29ad023f
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_modem_ops.h
@@ -0,0 +1,90 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
+ *               Eliot Lee <eliot.lee@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#ifndef __T7XX_MODEM_OPS_H__
+#define __T7XX_MODEM_OPS_H__
+
+#include <linux/bits.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include "t7xx_pci.h"
+
+#define RT_ID_MD_PORT_ENUM 0
+#define FEATURE_COUNT 64
+/* Modem feature query identification number */
+#define MD_FEATURE_QUERY_ID	0x49434343
+
+#define FEATURE_VER GENMASK(7, 4)
+#define FEATURE_MSK GENMASK(3, 0)
+
+/**
+ * enum hif_ex_stage -	HIF exception handshake stages with the HW
+ * @HIF_EX_INIT:        disable and clear TXQ
+ * @HIF_EX_INIT_DONE:   polling for init to be done
+ * @HIF_EX_CLEARQ_DONE: disable RX, flush TX/RX workqueues and clear RX
+ * @HIF_EX_ALLQ_RESET:  HW is back in safe mode for reinitialization and restart
+ */
+enum hif_ex_stage {
+	HIF_EX_INIT = 0,
+	HIF_EX_INIT_DONE,
+	HIF_EX_CLEARQ_DONE,
+	HIF_EX_ALLQ_RESET,
+};
+
+struct mtk_runtime_feature {
+	u8 feature_id;
+	u8 support_info;
+	u8 reserved[2];
+	u32 data_len;
+	u8 data[];
+};
+
+enum md_event_id {
+	FSM_PRE_START,
+	FSM_START,
+	FSM_READY,
+};
+
+struct core_sys_info {
+	atomic_t ready;
+	u8 feature_set[FEATURE_COUNT];
+};
+
+struct mtk_modem {
+	struct md_sys_info *md_info;
+	struct mtk_pci_dev *mtk_dev;
+	struct core_sys_info core_md;
+	bool md_init_finish;
+	atomic_t rgu_irq_asserted;
+	struct workqueue_struct *handshake_wq;
+	struct work_struct handshake_work;
+};
+
+struct md_sys_info {
+	unsigned int	exp_id;
+	spinlock_t	exp_spinlock; /* protect exp_id containing EX events */
+};
+
+void mtk_md_exception_handshake(struct mtk_modem *md);
+void mtk_md_event_notify(struct mtk_modem *md, enum md_event_id evt_id);
+void mtk_md_reset(struct mtk_pci_dev *mtk_dev);
+int mtk_md_init(struct mtk_pci_dev *mtk_dev);
+void mtk_md_exit(struct mtk_pci_dev *mtk_dev);
+void mtk_clear_rgu_irq(struct mtk_pci_dev *mtk_dev);
+int mtk_acpi_fldr_func(struct mtk_pci_dev *mtk_dev);
+int mtk_pci_mhccif_isr(struct mtk_pci_dev *mtk_dev);
+
+#endif	/* __T7XX_MODEM_OPS_H__ */
diff --git a/drivers/net/wwan/t7xx/t7xx_monitor.h b/drivers/net/wwan/t7xx/t7xx_monitor.h
new file mode 100644
index 000000000000..e03e044e685a
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_monitor.h
@@ -0,0 +1,144 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Eliot Lee <eliot.lee@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#ifndef __T7XX_MONITOR_H__
+#define __T7XX_MONITOR_H__
+
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include "t7xx_common.h"
+#include "t7xx_modem_ops.h"
+#include "t7xx_monitor.h"
+#include "t7xx_skb_util.h"
+
+enum ccci_fsm_state {
+	CCCI_FSM_INIT,
+	CCCI_FSM_PRE_START,
+	CCCI_FSM_STARTING,
+	CCCI_FSM_READY,
+	CCCI_FSM_EXCEPTION,
+	CCCI_FSM_STOPPING,
+	CCCI_FSM_STOPPED,
+};
+
+enum ccci_fsm_event_state {
+	CCCI_EVENT_INVALID,
+	CCCI_EVENT_MD_EX,
+	CCCI_EVENT_MD_EX_REC_OK,
+	CCCI_EVENT_MD_EX_PASS,
+	CCCI_EVENT_MAX
+};
+
+enum ccci_fsm_cmd_state {
+	CCCI_COMMAND_INVALID,
+	CCCI_COMMAND_START,
+	CCCI_COMMAND_EXCEPTION,
+	CCCI_COMMAND_PRE_STOP,
+	CCCI_COMMAND_STOP,
+};
+
+enum ccci_ex_reason {
+	EXCEPTION_HS_TIMEOUT,
+	EXCEPTION_EE,
+	EXCEPTION_EVENT,
+};
+
+enum md_irq_type {
+	MD_IRQ_WDT,
+	MD_IRQ_CCIF_EX,
+	MD_IRQ_PORT_ENUM,
+};
+
+enum fsm_cmd_result {
+	FSM_CMD_RESULT_PENDING,
+	FSM_CMD_RESULT_OK,
+	FSM_CMD_RESULT_FAIL,
+};
+
+#define FSM_CMD_FLAG_WAITING_TO_COMPLETE	BIT(0)
+#define FSM_CMD_FLAG_FLIGHT_MODE		BIT(1)
+
+#define EVENT_POLL_INTERVAL_MS			20
+#define MD_EX_REC_OK_TIMEOUT_MS			10000
+#define MD_EX_PASS_TIMEOUT_MS			(45 * 1000)
+
+struct ccci_fsm_monitor {
+	dev_t dev_n;
+	wait_queue_head_t rx_wq;
+	struct ccci_skb_queue rx_skb_list;
+};
+
+struct ccci_fsm_ctl {
+	struct mtk_modem *md;
+	enum md_state md_state;
+	unsigned int curr_state;
+	unsigned int last_state;
+	struct list_head command_queue;
+	struct list_head event_queue;
+	wait_queue_head_t command_wq;
+	wait_queue_head_t event_wq;
+	wait_queue_head_t async_hk_wq;
+	spinlock_t event_lock;		/* protects event_queue */
+	spinlock_t command_lock;	/* protects command_queue */
+	spinlock_t cmd_complete_lock;	/* protects fsm_command */
+	struct task_struct *fsm_thread;
+	atomic_t exp_flg;
+	struct ccci_fsm_monitor monitor_ctl;
+	spinlock_t notifier_lock;	/* protects notifier_list */
+	struct list_head notifier_list;
+};
+
+struct ccci_fsm_event {
+	struct list_head entry;
+	enum ccci_fsm_event_state event_id;
+	unsigned int length;
+	unsigned char data[];
+};
+
+struct ccci_fsm_command {
+	struct list_head entry;
+	enum ccci_fsm_cmd_state cmd_id;
+	unsigned int flag;
+	enum fsm_cmd_result result;
+	wait_queue_head_t complete_wq;
+};
+
+struct fsm_notifier_block {
+	struct list_head entry;
+	int (*notifier_fn)(enum md_state state, void *data);
+	void *data;
+};
+
+int fsm_append_command(struct ccci_fsm_ctl *ctl, enum ccci_fsm_cmd_state cmd_id,
+		       unsigned int flag);
+int fsm_append_event(struct ccci_fsm_ctl *ctl, enum ccci_fsm_event_state event_id,
+		     unsigned char *data, unsigned int length);
+void fsm_clear_event(struct ccci_fsm_ctl *ctl, enum ccci_fsm_event_state event_id);
+
+struct ccci_fsm_ctl *fsm_get_entity_by_device_number(dev_t dev_n);
+struct ccci_fsm_ctl *fsm_get_entry(void);
+
+void fsm_broadcast_state(struct ccci_fsm_ctl *ctl, enum md_state state);
+void ccci_fsm_reset(void);
+int ccci_fsm_init(struct mtk_modem *md);
+void ccci_fsm_uninit(void);
+void ccci_fsm_recv_md_interrupt(enum md_irq_type type);
+enum md_state ccci_fsm_get_md_state(void);
+unsigned int ccci_fsm_get_current_state(void);
+void fsm_notifier_register(struct fsm_notifier_block *notifier);
+void fsm_notifier_unregister(struct fsm_notifier_block *notifier);
+
+#endif /* __T7XX_MONITOR_H__ */
diff --git a/drivers/net/wwan/t7xx/t7xx_pci.c b/drivers/net/wwan/t7xx/t7xx_pci.c
new file mode 100644
index 000000000000..4b624b36e584
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_pci.c
@@ -0,0 +1,238 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+ *               Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
+ *               Eliot Lee <eliot.lee@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+
+#include "t7xx_mhccif.h"
+#include "t7xx_modem_ops.h"
+#include "t7xx_pci.h"
+#include "t7xx_pcie_mac.h"
+#include "t7xx_reg.h"
+#include "t7xx_skb_util.h"
+
+#define PCI_IREG_BASE			0
+#define PCI_EREG_BASE			2
+
+static int mtk_request_irq(struct pci_dev *pdev)
+{
+	struct mtk_pci_dev *mtk_dev;
+	int ret, i;
+
+	mtk_dev = pci_get_drvdata(pdev);
+
+	for (i = 0; i < EXT_INT_NUM; i++) {
+		const char *irq_descr;
+		int irq_vec;
+
+		if (!mtk_dev->intr_handler[i])
+			continue;
+
+		irq_descr = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s_%d",
+					   dev_driver_string(&pdev->dev), i);
+		if (!irq_descr)
+			return -ENOMEM;
+
+		irq_vec = pci_irq_vector(pdev, i);
+		ret = request_threaded_irq(irq_vec, mtk_dev->intr_handler[i],
+					   mtk_dev->intr_thread[i], 0, irq_descr,
+					   mtk_dev->callback_param[i]);
+		if (ret) {
+			dev_err(&pdev->dev, "Failed to request_irq: %d, int: %d, ret: %d\n",
+				irq_vec, i, ret);
+			while (i--) {
+				if (!mtk_dev->intr_handler[i])
+					continue;
+
+				free_irq(pci_irq_vector(pdev, i), mtk_dev->callback_param[i]);
+			}
+
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int mtk_setup_msix(struct mtk_pci_dev *mtk_dev)
+{
+	int ret;
+
+	/* We are interested only in 6 interrupts, but HW-design requires power-of-2
+	 * IRQs allocation.
+	 */
+	ret = pci_alloc_irq_vectors(mtk_dev->pdev, EXT_INT_NUM, EXT_INT_NUM, PCI_IRQ_MSIX);
+	if (ret < 0) {
+		dev_err(&mtk_dev->pdev->dev, "Failed to allocate MSI-X entry, errno: %d\n", ret);
+		return ret;
+	}
+
+	ret = mtk_request_irq(mtk_dev->pdev);
+	if (ret) {
+		pci_free_irq_vectors(mtk_dev->pdev);
+		return ret;
+	}
+
+	/* Set MSIX merge config */
+	mtk_pcie_mac_msix_cfg(mtk_dev, EXT_INT_NUM);
+	return 0;
+}
+
+static int mtk_interrupt_init(struct mtk_pci_dev *mtk_dev)
+{
+	int ret, i;
+
+	if (!mtk_dev->pdev->msix_cap)
+		return -EINVAL;
+
+	ret = mtk_setup_msix(mtk_dev);
+	if (ret)
+		return ret;
+
+	/* let the IPs enable interrupts when they are ready */
+	for (i = EXT_INT_START; i < EXT_INT_START + EXT_INT_NUM; i++)
+		PCIE_MAC_MSIX_MSK_SET(mtk_dev, i);
+
+	return 0;
+}
+
+static inline void mtk_pci_infracfg_ao_calc(struct mtk_pci_dev *mtk_dev)
+{
+	mtk_dev->base_addr.infracfg_ao_base = mtk_dev->base_addr.pcie_ext_reg_base +
+					      INFRACFG_AO_DEV_CHIP -
+					      mtk_dev->base_addr.pcie_dev_reg_trsl_addr;
+}
+
+static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	struct mtk_pci_dev *mtk_dev;
+	int ret;
+
+	mtk_dev = devm_kzalloc(&pdev->dev, sizeof(*mtk_dev), GFP_KERNEL);
+	if (!mtk_dev)
+		return -ENOMEM;
+
+	pci_set_drvdata(pdev, mtk_dev);
+	mtk_dev->pdev = pdev;
+
+	ret = pcim_enable_device(pdev);
+	if (ret)
+		return ret;
+
+	ret = pcim_iomap_regions(pdev, BIT(PCI_IREG_BASE) | BIT(PCI_EREG_BASE), pci_name(pdev));
+	if (ret) {
+		dev_err(&pdev->dev, "PCIm iomap regions fail %d\n", ret);
+		return -ENOMEM;
+	}
+
+	ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
+	if (ret) {
+		ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
+		if (ret) {
+			dev_err(&pdev->dev, "Could not set PCI DMA mask, err: %d\n", ret);
+			return ret;
+		}
+	}
+
+	ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
+	if (ret) {
+		ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
+		if (ret) {
+			dev_err(&pdev->dev, "Could not set consistent PCI DMA mask, err: %d\n",
+				ret);
+			return ret;
+		}
+	}
+
+	IREG_BASE(mtk_dev) = pcim_iomap_table(pdev)[PCI_IREG_BASE];
+	mtk_dev->base_addr.pcie_ext_reg_base = pcim_iomap_table(pdev)[PCI_EREG_BASE];
+
+	ret = ccci_skb_pool_alloc(&mtk_dev->pools);
+	if (ret)
+		return ret;
+
+	mtk_pcie_mac_atr_init(mtk_dev);
+	mtk_pci_infracfg_ao_calc(mtk_dev);
+	mhccif_init(mtk_dev);
+
+	ret = mtk_md_init(mtk_dev);
+	if (ret)
+		goto err;
+
+	mtk_pcie_mac_interrupts_dis(mtk_dev);
+	ret = mtk_interrupt_init(mtk_dev);
+	if (ret)
+		goto err;
+
+	mtk_pcie_mac_set_int(mtk_dev, MHCCIF_INT);
+	mtk_pcie_mac_interrupts_en(mtk_dev);
+	pci_set_master(pdev);
+
+	return 0;
+
+err:
+	ccci_skb_pool_free(&mtk_dev->pools);
+	return ret;
+}
+
+static void mtk_pci_remove(struct pci_dev *pdev)
+{
+	struct mtk_pci_dev *mtk_dev;
+	int i;
+
+	mtk_dev = pci_get_drvdata(pdev);
+	mtk_md_exit(mtk_dev);
+
+	for (i = 0; i < EXT_INT_NUM; i++) {
+		if (!mtk_dev->intr_handler[i])
+			continue;
+
+		free_irq(pci_irq_vector(pdev, i), mtk_dev->callback_param[i]);
+	}
+
+	pci_free_irq_vectors(mtk_dev->pdev);
+	ccci_skb_pool_free(&mtk_dev->pools);
+}
+
+static const struct pci_device_id t7xx_pci_table[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x4d75) },
+	{ }
+};
+MODULE_DEVICE_TABLE(pci, t7xx_pci_table);
+
+static struct pci_driver mtk_pci_driver = {
+	.name = "mtk_t7xx",
+	.id_table = t7xx_pci_table,
+	.probe = mtk_pci_probe,
+	.remove = mtk_pci_remove,
+};
+
+static int __init mtk_pci_init(void)
+{
+	return pci_register_driver(&mtk_pci_driver);
+}
+module_init(mtk_pci_init);
+
+static void __exit mtk_pci_cleanup(void)
+{
+	pci_unregister_driver(&mtk_pci_driver);
+}
+module_exit(mtk_pci_cleanup);
+
+MODULE_AUTHOR("MediaTek Inc");
+MODULE_DESCRIPTION("MediaTek PCIe 5G WWAN modem t7xx driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wwan/t7xx/t7xx_pci.h b/drivers/net/wwan/t7xx/t7xx_pci.h
new file mode 100644
index 000000000000..bc6f0a83f546
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_pci.h
@@ -0,0 +1,66 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#ifndef __T7XX_PCI_H__
+#define __T7XX_PCI_H__
+
+#include <linux/pci.h>
+#include <linux/types.h>
+
+#include "t7xx_reg.h"
+#include "t7xx_skb_util.h"
+
+/* struct mtk_addr_base - holds base addresses
+ * @pcie_mac_ireg_base: PCIe MAC register base
+ * @pcie_ext_reg_base: used to calculate base addresses for CLDMA, DPMA and MHCCIF registers
+ * @pcie_dev_reg_trsl_addr: used to calculate the register base address
+ * @infracfg_ao_base: base address used in CLDMA reset operations
+ * @mhccif_rc_base: host view of MHCCIF rc base addr
+ */
+struct mtk_addr_base {
+	void __iomem *pcie_mac_ireg_base;
+	void __iomem *pcie_ext_reg_base;
+	u32 pcie_dev_reg_trsl_addr;
+	void __iomem *infracfg_ao_base;
+	void __iomem *mhccif_rc_base;
+};
+
+typedef irqreturn_t (*mtk_intr_callback)(int irq, void *param);
+
+/* struct mtk_pci_dev - MTK device context structure
+ * @intr_handler: array of handler function for request_threaded_irq
+ * @intr_thread: array of thread_fn for request_threaded_irq
+ * @callback_param: array of cookie passed back to interrupt functions
+ * @mhccif_bitmask: device to host interrupt mask
+ * @pdev: pci device
+ * @base_addr: memory base addresses of HW components
+ * @md: modem interface
+ * @ccmni_ctlb: context structure used to control the network data path
+ * @rgu_pci_irq_en: RGU callback isr registered and active
+ * @pools: pre allocated skb pools
+ */
+struct mtk_pci_dev {
+	mtk_intr_callback	intr_handler[EXT_INT_NUM];
+	mtk_intr_callback	intr_thread[EXT_INT_NUM];
+	void			*callback_param[EXT_INT_NUM];
+	u32			mhccif_bitmask;
+	struct pci_dev		*pdev;
+	struct mtk_addr_base	base_addr;
+	struct mtk_modem	*md;
+
+	struct ccmni_ctl_block	*ccmni_ctlb;
+	bool			rgu_pci_irq_en;
+	struct skb_pools	pools;
+};
+
+#endif /* __T7XX_PCI_H__ */
diff --git a/drivers/net/wwan/t7xx/t7xx_pcie_mac.c b/drivers/net/wwan/t7xx/t7xx_pcie_mac.c
new file mode 100644
index 000000000000..c26ab0a9a47d
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_pcie_mac.c
@@ -0,0 +1,277 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/msi.h>
+
+#include "t7xx_pci.h"
+#include "t7xx_pcie_mac.h"
+#include "t7xx_reg.h"
+
+#define PCIE_REG_BAR			2
+#define PCIE_REG_PORT			ATR_SRC_PCI_WIN0
+#define PCIE_REG_TABLE_NUM		0
+#define PCIE_REG_TRSL_PORT		ATR_DST_AXIM_0
+
+#define PCIE_DEV_DMA_PORT_START		ATR_SRC_AXIS_0
+#define PCIE_DEV_DMA_PORT_END		ATR_SRC_AXIS_2
+#define PCIE_DEV_DMA_TABLE_NUM		0
+#define PCIE_DEV_DMA_TRSL_ADDR		0x00000000
+#define PCIE_DEV_DMA_SRC_ADDR		0x00000000
+#define PCIE_DEV_DMA_TRANSPARENT	1
+#define PCIE_DEV_DMA_SIZE		0
+
+enum mtk_atr_src_port {
+	ATR_SRC_PCI_WIN0,
+	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,
+	ATR_DST_PCI_CONFIG,
+	ATR_DST_AXIM_0 = 4,
+	ATR_DST_AXIM_1,
+	ATR_DST_AXIM_2,
+	ATR_DST_AXIM_3,
+};
+
+struct mtk_atr_config {
+	u64			src_addr;
+	u64			trsl_addr;
+	u64			size;
+	u32			port;
+	u32			table;
+	enum mtk_atr_dst_port	trsl_id;
+	u32			transparent;
+};
+
+static void mtk_pcie_mac_atr_tables_dis(void __iomem *pbase, enum mtk_atr_src_port port)
+{
+	void __iomem *reg;
+	int i, offset;
+
+	for (i = 0; i < ATR_TABLE_NUM_PER_ATR; i++) {
+		offset = (ATR_PORT_OFFSET * port) + (ATR_TABLE_OFFSET * i);
+
+		/* Disable table by SRC_ADDR */
+		reg = pbase + ATR_PCIE_WIN0_T0_ATR_PARAM_SRC_ADDR + offset;
+		iowrite64(0, reg);
+	}
+}
+
+static int mtk_pcie_mac_atr_cfg(struct mtk_pci_dev *mtk_dev, struct mtk_atr_config *cfg)
+{
+	int atr_size, pos, offset;
+	void __iomem *pbase;
+	struct device *dev;
+	void __iomem *reg;
+	u64 value;
+
+	dev = &mtk_dev->pdev->dev;
+	pbase = IREG_BASE(mtk_dev);
+
+	if (cfg->transparent) {
+		/* No address conversion is performed */
+		atr_size = ATR_TRANSPARENT_SIZE;
+	} else {
+		if (cfg->src_addr & (cfg->size - 1)) {
+			dev_err(dev, "src addr is not aligned to size\n");
+			return -EINVAL;
+		}
+
+		if (cfg->trsl_addr & (cfg->size - 1)) {
+			dev_err(dev, "trsl addr %llx not aligned to size %llx\n",
+				cfg->trsl_addr, cfg->size - 1);
+			return -EINVAL;
+		}
+
+		pos = ffs(lower_32_bits(cfg->size));
+		if (!pos)
+			pos = ffs(upper_32_bits(cfg->size)) + 32;
+
+		/* The HW calculates the address translation space as 2^(atr_size + 1)
+		 * but we also need to consider that ffs() starts counting from 1.
+		 */
+		atr_size = pos - 2;
+	}
+
+	/* Calculate table offset */
+	offset = ATR_PORT_OFFSET * cfg->port + ATR_TABLE_OFFSET * cfg->table;
+
+	reg = pbase + ATR_PCIE_WIN0_T0_TRSL_ADDR + offset;
+	value = cfg->trsl_addr & ATR_PCIE_WIN0_ADDR_ALGMT;
+	iowrite64(value, reg);
+
+	reg = pbase + ATR_PCIE_WIN0_T0_TRSL_PARAM + offset;
+	iowrite32(cfg->trsl_id, reg);
+
+	reg = pbase + ATR_PCIE_WIN0_T0_ATR_PARAM_SRC_ADDR + offset;
+	value = (cfg->src_addr & ATR_PCIE_WIN0_ADDR_ALGMT) | (atr_size << 1) | BIT(0);
+	iowrite64(value, reg);
+
+	/* Read back to ensure ATR is set */
+	ioread64(reg);
+	return 0;
+}
+
+/**
+ * mtk_pcie_mac_atr_init() - initialize address translation
+ * @mtk_dev: MTK device
+ *
+ * Setup ATR for ports & device.
+ *
+ */
+void mtk_pcie_mac_atr_init(struct mtk_pci_dev *mtk_dev)
+{
+	struct mtk_atr_config cfg;
+	u32 i;
+
+	/* Disable all ATR table for all ports */
+	for (i = ATR_SRC_PCI_WIN0; i <= ATR_SRC_AXIS_3; i++)
+		mtk_pcie_mac_atr_tables_dis(IREG_BASE(mtk_dev), i);
+
+	memset(&cfg, 0, sizeof(cfg));
+	/* Config ATR for RC to access device's register */
+	cfg.src_addr = pci_resource_start(mtk_dev->pdev, PCIE_REG_BAR);
+	cfg.size = PCIE_REG_SIZE_CHIP;
+	cfg.trsl_addr = PCIE_REG_TRSL_ADDR_CHIP;
+	cfg.port = PCIE_REG_PORT;
+	cfg.table = PCIE_REG_TABLE_NUM;
+	cfg.trsl_id = PCIE_REG_TRSL_PORT;
+	mtk_pcie_mac_atr_tables_dis(IREG_BASE(mtk_dev), cfg.port);
+	mtk_pcie_mac_atr_cfg(mtk_dev, &cfg);
+
+	/* Update translation base */
+	mtk_dev->base_addr.pcie_dev_reg_trsl_addr = PCIE_REG_TRSL_ADDR_CHIP;
+
+	/* Config ATR for EP to access RC's memory */
+	for (i = PCIE_DEV_DMA_PORT_START; i <= PCIE_DEV_DMA_PORT_END; i++) {
+		cfg.src_addr = PCIE_DEV_DMA_SRC_ADDR;
+		cfg.size = PCIE_DEV_DMA_SIZE;
+		cfg.trsl_addr = PCIE_DEV_DMA_TRSL_ADDR;
+		cfg.port = i;
+		cfg.table = PCIE_DEV_DMA_TABLE_NUM;
+		cfg.trsl_id = ATR_DST_PCI_TRX;
+		/* Enable transparent translation */
+		cfg.transparent = PCIE_DEV_DMA_TRANSPARENT;
+		mtk_pcie_mac_atr_tables_dis(IREG_BASE(mtk_dev), cfg.port);
+		mtk_pcie_mac_atr_cfg(mtk_dev, &cfg);
+	}
+}
+
+/**
+ * mtk_pcie_mac_enable_disable_int() - enable/disable interrupts
+ * @mtk_dev: MTK device
+ * @enable: enable/disable
+ *
+ * Enable or disable device interrupts.
+ *
+ */
+static void mtk_pcie_mac_enable_disable_int(struct mtk_pci_dev *mtk_dev, bool enable)
+{
+	u32 value;
+
+	value = ioread32(IREG_BASE(mtk_dev) + ISTAT_HST_CTRL);
+	if (enable)
+		value &= ~ISTAT_HST_CTRL_DIS;
+	else
+		value |= ISTAT_HST_CTRL_DIS;
+
+	iowrite32(value, IREG_BASE(mtk_dev) + ISTAT_HST_CTRL);
+}
+
+void mtk_pcie_mac_interrupts_en(struct mtk_pci_dev *mtk_dev)
+{
+	mtk_pcie_mac_enable_disable_int(mtk_dev, true);
+}
+
+void mtk_pcie_mac_interrupts_dis(struct mtk_pci_dev *mtk_dev)
+{
+	mtk_pcie_mac_enable_disable_int(mtk_dev, false);
+}
+
+/**
+ * mtk_pcie_mac_clear_set_int() - clear/set interrupt by type
+ * @mtk_dev: MTK device
+ * @int_type: interrupt type
+ * @clear: clear/set
+ *
+ * Clear or set device interrupt by type.
+ *
+ */
+static void mtk_pcie_mac_clear_set_int(struct mtk_pci_dev *mtk_dev,
+				       enum pcie_int int_type, bool clear)
+{
+	void __iomem *reg;
+
+	if (mtk_dev->pdev->msix_enabled) {
+		if (clear)
+			reg = IREG_BASE(mtk_dev) + IMASK_HOST_MSIX_CLR_GRP0_0;
+		else
+			reg = IREG_BASE(mtk_dev) + IMASK_HOST_MSIX_SET_GRP0_0;
+	} else {
+		if (clear)
+			reg = IREG_BASE(mtk_dev) + INT_EN_HST_CLR;
+		else
+			reg = IREG_BASE(mtk_dev) + INT_EN_HST_SET;
+	}
+
+	iowrite32(BIT(EXT_INT_START + int_type), reg);
+}
+
+void mtk_pcie_mac_clear_int(struct mtk_pci_dev *mtk_dev, enum pcie_int int_type)
+{
+	mtk_pcie_mac_clear_set_int(mtk_dev, int_type, true);
+}
+
+void mtk_pcie_mac_set_int(struct mtk_pci_dev *mtk_dev, enum pcie_int int_type)
+{
+	mtk_pcie_mac_clear_set_int(mtk_dev, int_type, false);
+}
+
+/**
+ * mtk_pcie_mac_clear_int_status() - clear interrupt status by type
+ * @mtk_dev: MTK device
+ * @int_type: interrupt type
+ *
+ * Enable or disable device interrupts' status by type.
+ *
+ */
+void mtk_pcie_mac_clear_int_status(struct mtk_pci_dev *mtk_dev, enum pcie_int int_type)
+{
+	void __iomem *reg;
+
+	if (mtk_dev->pdev->msix_enabled)
+		reg = IREG_BASE(mtk_dev) + MSIX_ISTAT_HST_GRP0_0;
+	else
+		reg = IREG_BASE(mtk_dev) + ISTAT_HST;
+
+	iowrite32(BIT(EXT_INT_START + int_type), reg);
+}
+
+/**
+ * mtk_pcie_mac_msix_cfg() - write MSIX control configuration
+ * @mtk_dev: MTK device
+ * @irq_count: number of MSIX IRQ vectors
+ *
+ * Write IRQ count to device.
+ *
+ */
+void mtk_pcie_mac_msix_cfg(struct mtk_pci_dev *mtk_dev, unsigned int irq_count)
+{
+	iowrite32(ffs(irq_count) * 2 - 1, IREG_BASE(mtk_dev) + PCIE_CFG_MSIX);
+}
diff --git a/drivers/net/wwan/t7xx/t7xx_pcie_mac.h b/drivers/net/wwan/t7xx/t7xx_pcie_mac.h
new file mode 100644
index 000000000000..ff078f1a92f4
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_pcie_mac.h
@@ -0,0 +1,36 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#ifndef __T7XX_PCIE_MAC_H__
+#define __T7XX_PCIE_MAC_H__
+
+#include <linux/bitops.h>
+#include <linux/io.h>
+
+#include "t7xx_pci.h"
+#include "t7xx_reg.h"
+
+#define IREG_BASE(mtk_dev)	((mtk_dev)->base_addr.pcie_mac_ireg_base)
+
+#define PCIE_MAC_MSIX_MSK_SET(mtk_dev, ext_id)	\
+	iowrite32(BIT(ext_id), IREG_BASE(mtk_dev) + IMASK_HOST_MSIX_SET_GRP0_0)
+
+void mtk_pcie_mac_interrupts_en(struct mtk_pci_dev *mtk_dev);
+void mtk_pcie_mac_interrupts_dis(struct mtk_pci_dev *mtk_dev);
+void mtk_pcie_mac_atr_init(struct mtk_pci_dev *mtk_dev);
+void mtk_pcie_mac_clear_int(struct mtk_pci_dev *mtk_dev, enum pcie_int int_type);
+void mtk_pcie_mac_set_int(struct mtk_pci_dev *mtk_dev, enum pcie_int int_type);
+void mtk_pcie_mac_clear_int_status(struct mtk_pci_dev *mtk_dev, enum pcie_int int_type);
+void mtk_pcie_mac_msix_cfg(struct mtk_pci_dev *mtk_dev, unsigned int irq_count);
+
+#endif /* __T7XX_PCIE_MAC_H__ */
diff --git a/drivers/net/wwan/t7xx/t7xx_reg.h b/drivers/net/wwan/t7xx/t7xx_reg.h
new file mode 100644
index 000000000000..07ce66d4844b
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_reg.h
@@ -0,0 +1,398 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+ *               Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
+ *               Eliot Lee <eliot.lee@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#ifndef __T7XX_REG_H__
+#define __T7XX_REG_H__
+
+#include <linux/bits.h>
+
+/* RC part */
+
+/* Device base address offset - update if reg BAR base is changed */
+#define MHCCIF_RC_DEV_BASE			0x10024000
+
+#define REG_RC2EP_SW_BSY			0x04
+#define REG_RC2EP_SW_INT_START			0x08
+
+#define REG_RC2EP_SW_TCHNUM			0x0c
+#define H2D_CH_EXCEPTION_ACK			1
+#define H2D_CH_EXCEPTION_CLEARQ_ACK		2
+#define H2D_CH_DS_LOCK				3
+/* Channels 4-8 are reserved */
+#define H2D_CH_SUSPEND_REQ			9
+#define H2D_CH_RESUME_REQ			10
+#define H2D_CH_SUSPEND_REQ_AP			11
+#define H2D_CH_RESUME_REQ_AP			12
+#define H2D_CH_DEVICE_RESET			13
+#define H2D_CH_DRM_DISABLE_AP			14
+
+#define REG_EP2RC_SW_INT_STS			0x10
+#define REG_EP2RC_SW_INT_ACK			0x14
+#define REG_EP2RC_SW_INT_EAP_MASK		0x20
+#define REG_EP2RC_SW_INT_EAP_MASK_SET		0x30
+#define REG_EP2RC_SW_INT_EAP_MASK_CLR		0x40
+
+#define D2H_INT_DS_LOCK_ACK			BIT(0)
+#define D2H_INT_EXCEPTION_INIT			BIT(1)
+#define D2H_INT_EXCEPTION_INIT_DONE		BIT(2)
+#define D2H_INT_EXCEPTION_CLEARQ_DONE		BIT(3)
+#define D2H_INT_EXCEPTION_ALLQ_RESET		BIT(4)
+#define D2H_INT_PORT_ENUM			BIT(5)
+/* bits 6-10 are reserved */
+#define D2H_INT_SUSPEND_ACK			BIT(11)
+#define D2H_INT_RESUME_ACK			BIT(12)
+#define D2H_INT_SUSPEND_ACK_AP			BIT(13)
+#define D2H_INT_RESUME_ACK_AP			BIT(14)
+#define D2H_INT_ASYNC_SAP_HK			BIT(15)
+#define D2H_INT_ASYNC_MD_HK			BIT(16)
+
+/* EP part */
+
+/* Device base address offset */
+#define MHCCIF_EP_DEV_BASE			0x10025000
+
+/* Reg base */
+#define INFRACFG_AO_DEV_CHIP			0x10001000
+
+/* ATR setting */
+#define PCIE_REG_TRSL_ADDR_CHIP			0x10000000
+#define PCIE_REG_SIZE_CHIP			0x00400000
+
+/* RGU */
+#define TOPRGU_CH_PCIE_IRQ_STA			0x1000790c
+
+#define EXP_BAR0				0x0c
+#define EXP_BAR2				0x04
+#define EXP_BAR4				0x0c
+#define EXP_BAR0_SIZE				0x00008000
+#define EXP_BAR2_SIZE				0x00800000
+#define EXP_BAR4_SIZE				0x00800000
+
+#define ATR_PORT_OFFSET				0x100
+#define ATR_TABLE_OFFSET			0x20
+#define ATR_TABLE_NUM_PER_ATR			8
+#define ATR_TRANSPARENT_SIZE			0x3f
+
+/* PCIE_MAC_IREG Register Definition */
+
+#define INT_EN_HST				0x0188
+#define ISTAT_HST				0x018c
+
+#define ISTAT_HST_CTRL				0x01ac
+#define ISTAT_HST_CTRL_DIS			BIT(0)
+
+#define INT_EN_HST_SET				0x01f0
+#define INT_EN_HST_CLR				0x01f4
+
+#define PCIE_MISC_CTRL				0x0348
+#define PCIE_MISC_MAC_SLEEP_DIS			BIT(7)
+
+#define PCIE_CFG_MSIX				0x03ec
+#define ATR_PCIE_WIN0_T0_ATR_PARAM_SRC_ADDR	0x0600
+#define ATR_PCIE_WIN0_T0_TRSL_ADDR		0x0608
+#define ATR_PCIE_WIN0_T0_TRSL_PARAM		0x0610
+#define ATR_PCIE_WIN0_ADDR_ALGMT		GENMASK_ULL(63, 12)
+
+#define ATR_SRC_ADDR_INVALID			0x007f
+
+#define PCIE_PM_RESUME_STATE			0x0d0c
+enum mtk_pm_resume_state {
+	PM_RESUME_REG_STATE_L3,
+	PM_RESUME_REG_STATE_L1,
+	PM_RESUME_REG_STATE_INIT,
+	PM_RESUME_REG_STATE_EXP,
+	PM_RESUME_REG_STATE_L2,
+	PM_RESUME_REG_STATE_L2_EXP,
+};
+
+#define PCIE_MISC_DEV_STATUS			0x0d1c
+#define MISC_STAGE_MASK				GENMASK(2, 0)
+#define MISC_RESET_TYPE_PLDR			BIT(26)
+#define MISC_RESET_TYPE_FLDR			BIT(27)
+#define LINUX_STAGE				4
+
+#define PCIE_RESOURCE_STATUS			0x0d28
+#define PCIE_RESOURCE_STATUS_MSK		GENMASK(4, 0)
+
+#define DIS_ASPM_LOWPWR_SET_0			0x0e50
+#define DIS_ASPM_LOWPWR_CLR_0			0x0e54
+#define DIS_ASPM_LOWPWR_SET_1			0x0e58
+#define DIS_ASPM_LOWPWR_CLR_1			0x0e5c
+#define L1_DISABLE_BIT(i)			BIT((i) * 4 + 1)
+#define L1_1_DISABLE_BIT(i)			BIT((i) * 4 + 2)
+#define L1_2_DISABLE_BIT(i)			BIT((i) * 4 + 3)
+
+#define MSIX_SW_TRIG_SET_GRP0_0			0x0e80
+#define MSIX_ISTAT_HST_GRP0_0			0x0f00
+#define IMASK_HOST_MSIX_SET_GRP0_0		0x3000
+#define IMASK_HOST_MSIX_CLR_GRP0_0		0x3080
+#define IMASK_HOST_MSIX_GRP0_0			0x3100
+#define EXT_INT_START				24
+#define EXT_INT_NUM				8
+#define MSIX_MSK_SET_ALL			GENMASK(31, 24)
+enum pcie_int {
+	DPMAIF_INT = 0,
+	CLDMA0_INT,
+	CLDMA1_INT,
+	CLDMA2_INT,
+	MHCCIF_INT,
+	DPMAIF2_INT,
+	SAP_RGU_INT,
+	CLDMA3_INT,
+};
+
+/* DPMA definitions */
+
+#define DPMAIF_PD_BASE				0x1022d000
+#define BASE_DPMAIF_UL				DPMAIF_PD_BASE
+#define BASE_DPMAIF_DL				(DPMAIF_PD_BASE + 0x100)
+#define BASE_DPMAIF_AP_MISC			(DPMAIF_PD_BASE + 0x400)
+#define BASE_DPMAIF_MMW_HPC			(DPMAIF_PD_BASE + 0x600)
+#define BASE_DPMAIF_DL_DLQ_REMOVEAO_IDX		(DPMAIF_PD_BASE + 0x900)
+#define BASE_DPMAIF_PD_SRAM_DL			(DPMAIF_PD_BASE + 0xc00)
+#define BASE_DPMAIF_PD_SRAM_UL			(DPMAIF_PD_BASE + 0xd00)
+
+#define DPMAIF_AO_BASE				0x10014000
+#define BASE_DPMAIF_AO_UL			DPMAIF_AO_BASE
+#define BASE_DPMAIF_AO_DL			(DPMAIF_AO_BASE + 0x400)
+
+/* dpmaif_ul */
+#define DPMAIF_UL_ADD_DESC			(BASE_DPMAIF_UL + 0x00)
+#define DPMAIF_UL_CHK_BUSY			(BASE_DPMAIF_UL + 0x88)
+#define DPMAIF_UL_RESERVE_AO_RW			(BASE_DPMAIF_UL + 0xac)
+#define DPMAIF_UL_ADD_DESC_CH0			(BASE_DPMAIF_UL + 0xb0)
+
+/* dpmaif_dl */
+#define DPMAIF_DL_BAT_INIT			(BASE_DPMAIF_DL + 0x00)
+#define DPMAIF_DL_BAT_ADD			(BASE_DPMAIF_DL + 0x04)
+#define DPMAIF_DL_BAT_INIT_CON0			(BASE_DPMAIF_DL + 0x08)
+#define DPMAIF_DL_BAT_INIT_CON1			(BASE_DPMAIF_DL + 0x0c)
+#define DPMAIF_DL_BAT_INIT_CON2			(BASE_DPMAIF_DL + 0x10)
+#define DPMAIF_DL_BAT_INIT_CON3			(BASE_DPMAIF_DL + 0x50)
+#define DPMAIF_DL_CHK_BUSY			(BASE_DPMAIF_DL + 0xb4)
+
+/* dpmaif_ap_misc */
+#define DPMAIF_AP_L2TISAR0			(BASE_DPMAIF_AP_MISC + 0x00)
+#define DPMAIF_AP_APDL_L2TISAR0			(BASE_DPMAIF_AP_MISC + 0x50)
+#define DPMAIF_AP_IP_BUSY			(BASE_DPMAIF_AP_MISC + 0x60)
+#define DPMAIF_AP_CG_EN				(BASE_DPMAIF_AP_MISC + 0x68)
+#define DPMAIF_AP_OVERWRITE_CFG			(BASE_DPMAIF_AP_MISC + 0x90)
+#define DPMAIF_AP_MEM_CLR			(BASE_DPMAIF_AP_MISC + 0x94)
+#define DPMAIF_AP_ALL_L2TISAR0_MASK		GENMASK(31, 0)
+#define DPMAIF_AP_APDL_ALL_L2TISAR0_MASK	GENMASK(31, 0)
+#define DPMAIF_AP_IP_BUSY_MASK			GENMASK(31, 0)
+
+/* dpmaif_ao_ul */
+#define DPMAIF_AO_UL_INIT_SET			(BASE_DPMAIF_AO_UL + 0x0)
+#define DPMAIF_AO_UL_CHNL_ARB0			(BASE_DPMAIF_AO_UL + 0x1c)
+#define DPMAIF_AO_UL_AP_L2TIMR0			(BASE_DPMAIF_AO_UL + 0x80)
+#define DPMAIF_AO_UL_AP_L2TIMCR0		(BASE_DPMAIF_AO_UL + 0x84)
+#define DPMAIF_AO_UL_AP_L2TIMSR0		(BASE_DPMAIF_AO_UL + 0x88)
+#define DPMAIF_AO_UL_AP_L1TIMR0			(BASE_DPMAIF_AO_UL + 0x8c)
+#define DPMAIF_AO_UL_APDL_L2TIMR0		(BASE_DPMAIF_AO_UL + 0x90)
+#define DPMAIF_AO_UL_APDL_L2TIMCR0		(BASE_DPMAIF_AO_UL + 0x94)
+#define DPMAIF_AO_UL_APDL_L2TIMSR0		(BASE_DPMAIF_AO_UL + 0x98)
+#define DPMAIF_AO_AP_DLUL_IP_BUSY_MASK		(BASE_DPMAIF_AO_UL + 0x9c)
+
+/* dpmaif_pd_sram_ul */
+#define DPMAIF_AO_UL_CHNL0_CON0			(BASE_DPMAIF_PD_SRAM_UL + 0x10)
+#define DPMAIF_AO_UL_CHNL0_CON1			(BASE_DPMAIF_PD_SRAM_UL + 0x14)
+#define DPMAIF_AO_UL_CHNL0_CON2			(BASE_DPMAIF_PD_SRAM_UL + 0x18)
+#define DPMAIF_AO_UL_CH0_STA			(BASE_DPMAIF_PD_SRAM_UL + 0x70)
+
+/* dpmaif_ao_dl */
+#define DPMAIF_AO_DL_INIT_SET			(BASE_DPMAIF_AO_DL + 0x00)
+#define DPMAIF_AO_DL_IRQ_MASK			(BASE_DPMAIF_AO_DL + 0x0c)
+#define DPMAIF_AO_DL_DLQPIT_INIT_CON5		(BASE_DPMAIF_AO_DL + 0x28)
+#define DPMAIF_AO_DL_DLQPIT_TRIG_THRES		(BASE_DPMAIF_AO_DL + 0x34)
+
+/* dpmaif_pd_sram_dl */
+#define DPMAIF_AO_DL_PKTINFO_CON0		(BASE_DPMAIF_PD_SRAM_DL + 0x00)
+#define DPMAIF_AO_DL_PKTINFO_CON1		(BASE_DPMAIF_PD_SRAM_DL + 0x04)
+#define DPMAIF_AO_DL_PKTINFO_CON2		(BASE_DPMAIF_PD_SRAM_DL + 0x08)
+#define DPMAIF_AO_DL_RDY_CHK_THRES		(BASE_DPMAIF_PD_SRAM_DL + 0x0c)
+#define DPMAIF_AO_DL_RDY_CHK_FRG_THRES		(BASE_DPMAIF_PD_SRAM_DL + 0x10)
+
+#define DPMAIF_AO_DL_DLQ_AGG_CFG		(BASE_DPMAIF_PD_SRAM_DL + 0x20)
+#define DPMAIF_AO_DL_DLQPIT_TIMEOUT0		(BASE_DPMAIF_PD_SRAM_DL + 0x24)
+#define DPMAIF_AO_DL_DLQPIT_TIMEOUT1		(BASE_DPMAIF_PD_SRAM_DL + 0x28)
+#define DPMAIF_AO_DL_HPC_CNTL			(BASE_DPMAIF_PD_SRAM_DL + 0x38)
+#define DPMAIF_AO_DL_PIT_SEQ_END		(BASE_DPMAIF_PD_SRAM_DL + 0x40)
+
+#define DPMAIF_AO_DL_BAT_RIDX			(BASE_DPMAIF_PD_SRAM_DL + 0xd8)
+#define DPMAIF_AO_DL_BAT_WRIDX			(BASE_DPMAIF_PD_SRAM_DL + 0xdc)
+#define DPMAIF_AO_DL_PIT_RIDX			(BASE_DPMAIF_PD_SRAM_DL + 0xec)
+#define DPMAIF_AO_DL_PIT_WRIDX			(BASE_DPMAIF_PD_SRAM_DL + 0x60)
+#define DPMAIF_AO_DL_FRGBAT_WRIDX		(BASE_DPMAIF_PD_SRAM_DL + 0x78)
+#define DPMAIF_AO_DL_DLQ_WRIDX			(BASE_DPMAIF_PD_SRAM_DL + 0xa4)
+
+/* dpmaif_hpc */
+#define DPMAIF_HPC_INTR_MASK			(BASE_DPMAIF_MMW_HPC + 0x0f4)
+#define DPMA_HPC_ALL_INT_MASK			GENMASK(15, 0)
+
+#define DPMAIF_HPC_DLQ_PATH_MODE		3
+#define DPMAIF_HPC_ADD_MODE_DF			0
+#define DPMAIF_HPC_TOTAL_NUM			8
+#define DPMAIF_HPC_MAX_TOTAL_NUM		8
+
+/* dpmaif_dlq */
+#define DPMAIF_DL_DLQPIT_INIT			(BASE_DPMAIF_DL_DLQ_REMOVEAO_IDX + 0x00)
+#define DPMAIF_DL_DLQPIT_ADD			(BASE_DPMAIF_DL_DLQ_REMOVEAO_IDX + 0x10)
+#define DPMAIF_DL_DLQPIT_INIT_CON0		(BASE_DPMAIF_DL_DLQ_REMOVEAO_IDX + 0x14)
+#define DPMAIF_DL_DLQPIT_INIT_CON1		(BASE_DPMAIF_DL_DLQ_REMOVEAO_IDX + 0x18)
+#define DPMAIF_DL_DLQPIT_INIT_CON2		(BASE_DPMAIF_DL_DLQ_REMOVEAO_IDX + 0x1c)
+#define DPMAIF_DL_DLQPIT_INIT_CON3		(BASE_DPMAIF_DL_DLQ_REMOVEAO_IDX + 0x20)
+#define DPMAIF_DL_DLQPIT_INIT_CON4		(BASE_DPMAIF_DL_DLQ_REMOVEAO_IDX + 0x24)
+#define DPMAIF_DL_DLQPIT_INIT_CON5		(BASE_DPMAIF_DL_DLQ_REMOVEAO_IDX + 0x28)
+#define DPMAIF_DL_DLQPIT_INIT_CON6		(BASE_DPMAIF_DL_DLQ_REMOVEAO_IDX + 0x2c)
+
+/* common include function */
+#define DPMAIF_ULQSAR_n(q)			(DPMAIF_AO_UL_CHNL0_CON0 + 0x10 * (q))
+#define DPMAIF_UL_DRBSIZE_ADDRH_n(q)		(DPMAIF_AO_UL_CHNL0_CON1 + 0x10 * (q))
+#define DPMAIF_UL_DRB_ADDRH_n(q)		(DPMAIF_AO_UL_CHNL0_CON2 + 0x10 * (q))
+#define DPMAIF_ULQ_STA0_n(q)			(DPMAIF_AO_UL_CH0_STA + 0x04 * (q))
+#define DPMAIF_ULQ_ADD_DESC_CH_n(q)		(DPMAIF_UL_ADD_DESC_CH0 + 0x04 * (q))
+
+#define DPMAIF_UL_DRB_RIDX_OFFSET		16
+
+#define DPMAIF_AP_RGU_ASSERT			0x10001150
+#define DPMAIF_AP_RGU_DEASSERT			0x10001154
+#define DPMAIF_AP_RST_BIT			BIT(2)
+
+#define DPMAIF_AP_AO_RGU_ASSERT			0x10001140
+#define DPMAIF_AP_AO_RGU_DEASSERT		0x10001144
+#define DPMAIF_AP_AO_RST_BIT			BIT(6)
+
+/* DPMAIF init/restore */
+#define DPMAIF_UL_ADD_NOT_READY			BIT(31)
+#define DPMAIF_UL_ADD_UPDATE			BIT(31)
+#define DPMAIF_UL_ADD_COUNT_MASK		GENMASK(15, 0)
+#define DPMAIF_UL_ALL_QUE_ARB_EN		GENMASK(11, 8)
+
+#define DPMAIF_DL_ADD_UPDATE			BIT(31)
+#define DPMAIF_DL_ADD_NOT_READY			BIT(31)
+#define DPMAIF_DL_FRG_ADD_UPDATE		BIT(16)
+#define DPMAIF_DL_ADD_COUNT_MASK		GENMASK(15, 0)
+
+#define DPMAIF_DL_BAT_INIT_ALLSET		BIT(0)
+#define DPMAIF_DL_BAT_FRG_INIT			BIT(16)
+#define DPMAIF_DL_BAT_INIT_EN			BIT(31)
+#define DPMAIF_DL_BAT_INIT_NOT_READY		BIT(31)
+#define DPMAIF_DL_BAT_INIT_ONLY_ENABLE_BIT	0
+
+#define DPMAIF_DL_PIT_INIT_ALLSET		BIT(0)
+#define DPMAIF_DL_PIT_INIT_EN			BIT(31)
+#define DPMAIF_DL_PIT_INIT_NOT_READY		BIT(31)
+
+#define DPMAIF_PKT_ALIGN64_MODE			0
+#define DPMAIF_PKT_ALIGN128_MODE		1
+
+#define DPMAIF_BAT_REMAIN_SZ_BASE		16
+#define DPMAIF_BAT_BUFFER_SZ_BASE		128
+#define DPMAIF_FRG_BUFFER_SZ_BASE		128
+
+#define DLQ_PIT_IDX_SIZE			0x20
+
+#define DPMAIF_PIT_SIZE_MSK			GENMASK(17, 0)
+
+#define DPMAIF_PIT_REM_CNT_MSK			GENMASK(17, 0)
+
+#define DPMAIF_BAT_EN_MSK			BIT(16)
+#define DPMAIF_FRG_EN_MSK			BIT(28)
+#define DPMAIF_BAT_SIZE_MSK			GENMASK(15, 0)
+
+#define DPMAIF_BAT_BID_MAXCNT_MSK		GENMASK(31, 16)
+#define DPMAIF_BAT_REMAIN_MINSZ_MSK		GENMASK(15, 8)
+#define DPMAIF_PIT_CHK_NUM_MSK			GENMASK(31, 24)
+#define DPMAIF_BAT_BUF_SZ_MSK			GENMASK(16, 8)
+#define DPMAIF_FRG_BUF_SZ_MSK			GENMASK(16, 8)
+#define DPMAIF_BAT_RSV_LEN_MSK			GENMASK(7, 0)
+#define DPMAIF_PKT_ALIGN_MSK			GENMASK(23, 22)
+
+#define DPMAIF_BAT_CHECK_THRES_MSK		GENMASK(21, 16)
+#define DPMAIF_FRG_CHECK_THRES_MSK		GENMASK(7, 0)
+
+#define DPMAIF_PKT_ALIGN_EN			BIT(23)
+
+#define DPMAIF_DRB_SIZE_MSK			GENMASK(15, 0)
+
+#define DPMAIF_DL_PIT_WRIDX_MSK			GENMASK(17, 0)
+#define DPMAIF_DL_BAT_WRIDX_MSK			GENMASK(17, 0)
+#define DPMAIF_DL_FRG_WRIDX_MSK			GENMASK(17, 0)
+
+/* Bit fields of registers */
+/* DPMAIF_UL_CHK_BUSY */
+#define DPMAIF_UL_IDLE_STS			BIT(11)
+/* DPMAIF_DL_CHK_BUSY */
+#define DPMAIF_DL_IDLE_STS			BIT(23)
+/* DPMAIF_AO_DL_RDY_CHK_THRES */
+#define DPMAIF_DL_PKT_CHECKSUM_EN		BIT(31)
+#define DPMAIF_PORT_MODE_PCIE			BIT(30)
+#define DPMAIF_DL_BURST_PIT_EN			BIT(13)
+/* DPMAIF_DL_BAT_INIT_CON1 */
+#define DPMAIF_DL_BAT_CACHE_PRI			BIT(22)
+/* DPMAIF_AP_MEM_CLR */
+#define DPMAIF_MEM_CLR				BIT(0)
+/* DPMAIF_AP_OVERWRITE_CFG */
+#define DPMAIF_SRAM_SYNC			BIT(0)
+/* DPMAIF_AO_UL_INIT_SET */
+#define DPMAIF_UL_INIT_DONE			BIT(0)
+/* DPMAIF_AO_DL_INIT_SET */
+#define DPMAIF_DL_INIT_DONE			BIT(0)
+/* DPMAIF_AO_DL_PIT_SEQ_END */
+#define DPMAIF_DL_PIT_SEQ_MSK			GENMASK(7, 0)
+/* DPMAIF_UL_RESERVE_AO_RW */
+#define DPMAIF_PCIE_MODE_SET_VALUE		0x55
+/* DPMAIF_AP_CG_EN */
+#define DPMAIF_CG_EN				0x7f
+
+/* DPMAIF interrupt */
+#define _UL_INT_DONE_OFFSET			0
+#define _UL_INT_EMPTY_OFFSET			4
+#define _UL_INT_MD_NOTRDY_OFFSET		8
+#define _UL_INT_PWR_NOTRDY_OFFSET		12
+#define _UL_INT_LEN_ERR_OFFSET			16
+
+#define DPMAIF_DL_INT_BATCNT_LEN_ERR		BIT(2)
+#define DPMAIF_DL_INT_PITCNT_LEN_ERR		BIT(3)
+
+#define DPMAIF_UL_INT_QDONE_MSK			GENMASK(3, 0)
+#define DPMAIF_UL_INT_ERR_MSK			GENMASK(19, 16)
+
+#define DPMAIF_UDL_IP_BUSY			BIT(0)
+#define DPMAIF_DL_INT_DLQ0_QDONE		BIT(8)
+#define DPMAIF_DL_INT_DLQ1_QDONE		BIT(9)
+#define DPMAIF_DL_INT_DLQ0_PITCNT_LEN		BIT(10)
+#define DPMAIF_DL_INT_DLQ1_PITCNT_LEN		BIT(11)
+#define DPMAIF_DL_INT_Q2TOQ1			BIT(24)
+#define DPMAIF_DL_INT_Q2APTOP			BIT(25)
+
+#define DPMAIF_DLQ_LOW_TIMEOUT_THRES_MKS	GENMASK(15, 0)
+#define DPMAIF_DLQ_HIGH_TIMEOUT_THRES_MSK	GENMASK(31, 16)
+
+/* DPMAIF DLQ HW configure */
+#define DPMAIF_AGG_MAX_LEN_DF			65535
+#define DPMAIF_AGG_TBL_ENT_NUM_DF		50
+#define DPMAIF_HASH_PRIME_DF			13
+#define DPMAIF_MID_TIMEOUT_THRES_DF		100
+#define DPMAIF_DLQ_TIMEOUT_THRES_DF		100
+#define DPMAIF_DLQ_PRS_THRES_DF			10
+#define DPMAIF_DLQ_HASH_BIT_CHOOSE_DF		0
+
+#define DPMAIF_DLQPIT_EN_MSK			BIT(20)
+#define DPMAIF_DLQPIT_CHAN_OFS			16
+#define DPMAIF_ADD_DLQ_PIT_CHAN_OFS		20
+
+#endif /* __T7XX_REG_H__ */
diff --git a/drivers/net/wwan/t7xx/t7xx_skb_util.c b/drivers/net/wwan/t7xx/t7xx_skb_util.c
new file mode 100644
index 000000000000..872dc82176dc
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_skb_util.c
@@ -0,0 +1,362 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
+ *               Eliot Lee <eliot.lee@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/skbuff.h>
+
+#include "t7xx_pci.h"
+#include "t7xx_skb_util.h"
+
+/* define the pool size of each different skb length */
+#define SKB_64K_POOL_SIZE	50
+#define SKB_4K_POOL_SIZE	256
+#define SKB_16_POOL_SIZE	64
+
+/* reload pool if pool size dropped below 1/RELOAD_TH */
+#define RELOAD_TH		3
+
+#define ALLOC_SKB_RETRY		20
+
+static struct sk_buff *alloc_skb_from_pool(struct skb_pools *pools, size_t size)
+{
+	if (size > MTK_SKB_4K)
+		return ccci_skb_dequeue(pools->reload_work_queue, &pools->skb_pool_64k);
+	else if (size > MTK_SKB_16)
+		return ccci_skb_dequeue(pools->reload_work_queue, &pools->skb_pool_4k);
+	else if (size > 0)
+		return ccci_skb_dequeue(pools->reload_work_queue, &pools->skb_pool_16);
+
+	return NULL;
+}
+
+static struct sk_buff *alloc_skb_from_kernel(size_t size, gfp_t gfp_mask)
+{
+	if (size > MTK_SKB_4K)
+		return __dev_alloc_skb(MTK_SKB_64K, gfp_mask);
+	else if (size > MTK_SKB_1_5K)
+		return __dev_alloc_skb(MTK_SKB_4K, gfp_mask);
+	else if (size > MTK_SKB_16)
+		return __dev_alloc_skb(MTK_SKB_1_5K, gfp_mask);
+	else if (size > 0)
+		return __dev_alloc_skb(MTK_SKB_16, gfp_mask);
+
+	return NULL;
+}
+
+struct sk_buff *ccci_skb_dequeue(struct workqueue_struct *reload_work_queue,
+				 struct ccci_skb_queue *queue)
+{
+	struct sk_buff *skb;
+	unsigned long flags;
+
+	spin_lock_irqsave(&queue->skb_list.lock, flags);
+	skb = __skb_dequeue(&queue->skb_list);
+	if (queue->max_occupied < queue->max_len - queue->skb_list.qlen)
+		queue->max_occupied = queue->max_len - queue->skb_list.qlen;
+
+	queue->deq_count++;
+	if (queue->pre_filled && queue->skb_list.qlen < queue->max_len / RELOAD_TH)
+		queue_work(reload_work_queue, &queue->reload_work);
+
+	spin_unlock_irqrestore(&queue->skb_list.lock, flags);
+
+	return skb;
+}
+
+void ccci_skb_enqueue(struct ccci_skb_queue *queue, struct sk_buff *skb)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&queue->skb_list.lock, flags);
+	if (queue->skb_list.qlen < queue->max_len) {
+		queue->enq_count++;
+		__skb_queue_tail(&queue->skb_list, skb);
+		if (queue->skb_list.qlen > queue->max_history)
+			queue->max_history = queue->skb_list.qlen;
+	} else {
+		dev_kfree_skb_any(skb);
+	}
+
+	spin_unlock_irqrestore(&queue->skb_list.lock, flags);
+}
+
+int ccci_skb_queue_alloc(struct ccci_skb_queue *queue, size_t skb_size, size_t max_len, bool fill)
+{
+	skb_queue_head_init(&queue->skb_list);
+	queue->max_len = max_len;
+	if (fill) {
+		unsigned int i;
+
+		for (i = 0; i < queue->max_len; i++) {
+			struct sk_buff *skb;
+
+			skb = alloc_skb_from_kernel(skb_size, GFP_KERNEL);
+
+			if (!skb) {
+				while ((skb = skb_dequeue(&queue->skb_list)))
+					dev_kfree_skb_any(skb);
+				return -ENOMEM;
+			}
+
+			skb_queue_tail(&queue->skb_list, skb);
+		}
+
+		queue->pre_filled = true;
+	} else {
+		queue->pre_filled = false;
+	}
+
+	queue->max_history = 0;
+
+	return 0;
+}
+
+/**
+ * ccci_alloc_skb_from_pool() - allocate memory for skb from pre-allocated pools
+ * @pools: skb pools
+ * @size: allocation size
+ * @blocking : true for blocking operation
+ *
+ * Returns pointer to skb on success, NULL on failure.
+ */
+struct sk_buff *ccci_alloc_skb_from_pool(struct skb_pools *pools, size_t size, bool blocking)
+{
+	struct ccci_buffer_ctrl *buf_ctrl;
+	struct sk_buff *skb;
+	int count;
+
+	if (!size || size > MTK_SKB_64K)
+		return NULL;
+
+	for (count = 0; count < ALLOC_SKB_RETRY; count++) {
+		skb = alloc_skb_from_pool(pools, size);
+		if (skb || !blocking)
+			break;
+
+		/* no memory at the pool.
+		 * waiting for pools reload_work which will allocate new memory from kernel
+		 */
+		might_sleep();
+		msleep(MTK_SKB_WAIT_FOR_POOLS_RELOAD_MS);
+	}
+
+	if (skb && skb_headroom(skb) == NET_SKB_PAD) {
+		buf_ctrl = skb_push(skb, sizeof(*buf_ctrl));
+		buf_ctrl->head_magic = CCCI_BUF_MAGIC;
+		buf_ctrl->policy = MTK_SKB_POLICY_RECYCLE;
+		buf_ctrl->ioc_override = 0;
+		skb_pull(skb, sizeof(struct ccci_buffer_ctrl));
+	}
+
+	return skb;
+}
+
+/**
+ * ccci_alloc_skb() - allocate memory for skb from the kernel
+ * @size: allocation size
+ * @blocking : true for blocking operation
+ *
+ * Returns pointer to skb on success, NULL on failure.
+ */
+struct sk_buff *ccci_alloc_skb(size_t size, bool blocking)
+{
+	struct sk_buff *skb;
+	int count;
+
+	if (!size || size > MTK_SKB_64K)
+		return NULL;
+
+	if (blocking) {
+		might_sleep();
+		skb = alloc_skb_from_kernel(size, GFP_KERNEL);
+	} else {
+		for (count = 0; count < ALLOC_SKB_RETRY; count++) {
+			skb = alloc_skb_from_kernel(size, GFP_ATOMIC);
+			if (skb)
+				return skb;
+		}
+	}
+
+	return skb;
+}
+
+static void free_skb_from_pool(struct skb_pools *pools, struct sk_buff *skb)
+{
+	if (skb_size(skb) < MTK_SKB_4K)
+		ccci_skb_enqueue(&pools->skb_pool_16, skb);
+	else if (skb_size(skb) < MTK_SKB_64K)
+		ccci_skb_enqueue(&pools->skb_pool_4k, skb);
+	else
+		ccci_skb_enqueue(&pools->skb_pool_64k, skb);
+}
+
+void ccci_free_skb(struct skb_pools *pools, struct sk_buff *skb)
+{
+	struct ccci_buffer_ctrl *buf_ctrl;
+	enum data_policy policy;
+	int offset;
+
+	if (!skb)
+		return;
+
+	offset = NET_SKB_PAD - sizeof(*buf_ctrl);
+	buf_ctrl = (struct ccci_buffer_ctrl *)(skb->head + offset);
+	policy = MTK_SKB_POLICY_FREE;
+
+	if (buf_ctrl->head_magic == CCCI_BUF_MAGIC) {
+		policy = buf_ctrl->policy;
+		memset(buf_ctrl, 0, sizeof(*buf_ctrl));
+	}
+
+	if (policy != MTK_SKB_POLICY_RECYCLE || skb->dev ||
+	    skb_size(skb) < NET_SKB_PAD + MTK_SKB_16)
+		policy = MTK_SKB_POLICY_FREE;
+
+	switch (policy) {
+	case MTK_SKB_POLICY_RECYCLE:
+		skb->data = skb->head;
+		skb->len = 0;
+		skb_reset_tail_pointer(skb);
+		/* reserve memory as netdev_alloc_skb */
+		skb_reserve(skb, NET_SKB_PAD);
+		/* enqueue */
+		free_skb_from_pool(pools, skb);
+		break;
+
+	case MTK_SKB_POLICY_FREE:
+		dev_kfree_skb_any(skb);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static void reload_work_64k(struct work_struct *work)
+{
+	struct ccci_skb_queue *queue;
+	struct sk_buff *skb;
+
+	queue = container_of(work, struct ccci_skb_queue, reload_work);
+
+	while (queue->skb_list.qlen < SKB_64K_POOL_SIZE) {
+		skb = alloc_skb_from_kernel(MTK_SKB_64K, GFP_KERNEL);
+		if (skb)
+			skb_queue_tail(&queue->skb_list, skb);
+	}
+}
+
+static void reload_work_4k(struct work_struct *work)
+{
+	struct ccci_skb_queue *queue;
+	struct sk_buff *skb;
+
+	queue = container_of(work, struct ccci_skb_queue, reload_work);
+
+	while (queue->skb_list.qlen < SKB_4K_POOL_SIZE) {
+		skb = alloc_skb_from_kernel(MTK_SKB_4K, GFP_KERNEL);
+		if (skb)
+			skb_queue_tail(&queue->skb_list, skb);
+	}
+}
+
+static void reload_work_16(struct work_struct *work)
+{
+	struct ccci_skb_queue *queue;
+	struct sk_buff *skb;
+
+	queue = container_of(work, struct ccci_skb_queue, reload_work);
+
+	while (queue->skb_list.qlen < SKB_16_POOL_SIZE) {
+		skb = alloc_skb_from_kernel(MTK_SKB_16, GFP_KERNEL);
+		if (skb)
+			skb_queue_tail(&queue->skb_list, skb);
+	}
+}
+
+int ccci_skb_pool_alloc(struct skb_pools *pools)
+{
+	struct sk_buff *skb;
+	int ret;
+
+	ret = ccci_skb_queue_alloc(&pools->skb_pool_64k,
+				   MTK_SKB_64K, SKB_64K_POOL_SIZE, true);
+	if (ret)
+		return ret;
+
+	ret = ccci_skb_queue_alloc(&pools->skb_pool_4k,
+				   MTK_SKB_4K, SKB_4K_POOL_SIZE, true);
+	if (ret)
+		goto err_pool_4k;
+
+	ret = ccci_skb_queue_alloc(&pools->skb_pool_16,
+				   MTK_SKB_16, SKB_16_POOL_SIZE, true);
+	if (ret)
+		goto err_pool_16k;
+
+	pools->reload_work_queue = alloc_workqueue("pool_reload_work",
+						   WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI,
+						   1);
+	if (!pools->reload_work_queue) {
+		ret = -ENOMEM;
+		goto err_wq;
+	}
+
+	INIT_WORK(&pools->skb_pool_64k.reload_work, reload_work_64k);
+	INIT_WORK(&pools->skb_pool_4k.reload_work, reload_work_4k);
+	INIT_WORK(&pools->skb_pool_16.reload_work, reload_work_16);
+
+	return ret;
+
+err_wq:
+	while ((skb = skb_dequeue(&pools->skb_pool_16.skb_list)))
+		dev_kfree_skb_any(skb);
+err_pool_16k:
+	while ((skb = skb_dequeue(&pools->skb_pool_4k.skb_list)))
+		dev_kfree_skb_any(skb);
+err_pool_4k:
+	while ((skb = skb_dequeue(&pools->skb_pool_64k.skb_list)))
+		dev_kfree_skb_any(skb);
+
+	return ret;
+}
+
+void ccci_skb_pool_free(struct skb_pools *pools)
+{
+	struct ccci_skb_queue *queue;
+	struct sk_buff *newsk;
+
+	flush_work(&pools->skb_pool_64k.reload_work);
+	flush_work(&pools->skb_pool_4k.reload_work);
+	flush_work(&pools->skb_pool_16.reload_work);
+
+	if (pools->reload_work_queue)
+		destroy_workqueue(pools->reload_work_queue);
+
+	queue = &pools->skb_pool_64k;
+	while ((newsk = skb_dequeue(&queue->skb_list)) != NULL)
+		dev_kfree_skb_any(newsk);
+
+	queue = &pools->skb_pool_4k;
+	while ((newsk = skb_dequeue(&queue->skb_list)) != NULL)
+		dev_kfree_skb_any(newsk);
+
+	queue = &pools->skb_pool_16;
+	while ((newsk = skb_dequeue(&queue->skb_list)) != NULL)
+		dev_kfree_skb_any(newsk);
+}
diff --git a/drivers/net/wwan/t7xx/t7xx_skb_util.h b/drivers/net/wwan/t7xx/t7xx_skb_util.h
new file mode 100644
index 000000000000..390e312d5eed
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_skb_util.h
@@ -0,0 +1,110 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
+ *               Eliot Lee <eliot.lee@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#ifndef __T7XX_SKB_UTIL_H__
+#define __T7XX_SKB_UTIL_H__
+
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+#include <linux/wwan.h>
+
+#define MTK_SKB_64K	64528		/* 63kB + CCCI header */
+#define MTK_SKB_4K	3584		/* 3.5kB */
+#define MTK_SKB_1_5K	(WWAN_DEFAULT_MTU + 16)	/* net MTU + CCCI_H, for network packet */
+#define MTK_SKB_16	16		/* for struct ccci_header */
+#define NET_RX_BUF	MTK_SKB_4K
+
+#define CCCI_BUF_MAGIC	0xFECDBA89
+
+#define MTK_SKB_WAIT_FOR_POOLS_RELOAD_MS	20
+
+struct ccci_skb_queue {
+	struct sk_buff_head	skb_list;
+	unsigned int		max_len;
+	struct work_struct	reload_work;
+	bool			pre_filled;
+	unsigned int		max_history;
+	unsigned int		max_occupied;
+	unsigned int		enq_count;
+	unsigned int		deq_count;
+};
+
+struct skb_pools {
+	struct ccci_skb_queue skb_pool_64k;
+	struct ccci_skb_queue skb_pool_4k;
+	struct ccci_skb_queue skb_pool_16;
+	struct workqueue_struct *reload_work_queue;
+};
+
+/**
+ * enum data_policy - tells the request free routine how to handle the skb
+ * @MTK_SKB_POLICY_FREE: free the skb
+ * @MTK_SKB_POLICY_RECYCLE: put the skb back into our pool
+ *
+ * The driver request structure will always be recycled, but its skb
+ * can have a different policy. The driver request can work as a wrapper
+ * because the network subsys will handle the skb itself.
+ * TX: policy is determined by sender
+ * RX: policy is determined by receiver
+ */
+enum data_policy {
+	MTK_SKB_POLICY_FREE = 0,
+	MTK_SKB_POLICY_RECYCLE,
+};
+
+/* Get free skb flags */
+#define	GFS_NONE_BLOCKING	0
+#define	GFS_BLOCKING		1
+
+/* CCCI buffer control structure. Must be less than NET_SKB_PAD */
+struct ccci_buffer_ctrl {
+	unsigned int		head_magic;
+	enum data_policy	policy;
+	unsigned char		ioc_override;
+	unsigned char		blocking;
+};
+
+#ifdef NET_SKBUFF_DATA_USES_OFFSET
+static inline unsigned int skb_size(struct sk_buff *skb)
+{
+	return skb->end;
+}
+
+static inline unsigned int skb_data_size(struct sk_buff *skb)
+{
+	return skb->head + skb->end - skb->data;
+}
+#else
+static inline unsigned int skb_size(struct sk_buff *skb)
+{
+	return skb->end - skb->head;
+}
+
+static inline unsigned int skb_data_size(struct sk_buff *skb)
+{
+	return skb->end - skb->data;
+}
+#endif
+
+int ccci_skb_pool_alloc(struct skb_pools *pools);
+void ccci_skb_pool_free(struct skb_pools *pools);
+struct sk_buff *ccci_alloc_skb_from_pool(struct skb_pools *pools, size_t size, bool blocking);
+struct sk_buff *ccci_alloc_skb(size_t size, bool blocking);
+void ccci_free_skb(struct skb_pools *pools, struct sk_buff *skb);
+struct sk_buff *ccci_skb_dequeue(struct workqueue_struct *reload_work_queue,
+				 struct ccci_skb_queue *queue);
+void ccci_skb_enqueue(struct ccci_skb_queue *queue, struct sk_buff *skb);
+int ccci_skb_queue_alloc(struct ccci_skb_queue *queue, size_t skb_size, size_t max_len, bool fill);
+
+#endif /* __T7XX_SKB_UTIL_H__ */
diff --git a/drivers/net/wwan/t7xx/t7xx_state_monitor.c b/drivers/net/wwan/t7xx/t7xx_state_monitor.c
new file mode 100644
index 000000000000..fb81e894d5f9
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_state_monitor.c
@@ -0,0 +1,598 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Haijun Lio <haijun.liu@mediatek.com>
+ * Contributors: Amir Hanania <amir.hanania@intel.com>
+ *               Eliot Lee <eliot.lee@intel.com>
+ *               Moises Veleta <moises.veleta@intel.com>
+ *               Ricardo Martinez<ricardo.martinez@linux.intel.com>
+ *               Sreehari Kancharla <sreehari.kancharla@intel.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+
+#include "t7xx_hif_cldma.h"
+#include "t7xx_mhccif.h"
+#include "t7xx_modem_ops.h"
+#include "t7xx_monitor.h"
+#include "t7xx_pci.h"
+#include "t7xx_pcie_mac.h"
+
+#define FSM_DRM_DISABLE_DELAY_MS 200
+#define FSM_EX_REASON GENMASK(23, 16)
+
+static struct ccci_fsm_ctl *ccci_fsm_entry;
+
+void fsm_notifier_register(struct fsm_notifier_block *notifier)
+{
+	struct ccci_fsm_ctl *ctl;
+	unsigned long flags;
+
+	ctl = ccci_fsm_entry;
+	spin_lock_irqsave(&ctl->notifier_lock, flags);
+	list_add_tail(&notifier->entry, &ctl->notifier_list);
+	spin_unlock_irqrestore(&ctl->notifier_lock, flags);
+}
+
+void fsm_notifier_unregister(struct fsm_notifier_block *notifier)
+{
+	struct fsm_notifier_block *notifier_cur, *notifier_next;
+	struct ccci_fsm_ctl *ctl;
+	unsigned long flags;
+
+	ctl = ccci_fsm_entry;
+	spin_lock_irqsave(&ctl->notifier_lock, flags);
+	list_for_each_entry_safe(notifier_cur, notifier_next,
+				 &ctl->notifier_list, entry) {
+		if (notifier_cur == notifier)
+			list_del(&notifier->entry);
+	}
+
+	spin_unlock_irqrestore(&ctl->notifier_lock, flags);
+}
+
+static void fsm_state_notify(enum md_state state)
+{
+	struct fsm_notifier_block *notifier;
+	struct ccci_fsm_ctl *ctl;
+	unsigned long flags;
+
+	ctl = ccci_fsm_entry;
+	spin_lock_irqsave(&ctl->notifier_lock, flags);
+	list_for_each_entry(notifier, &ctl->notifier_list, entry) {
+		spin_unlock_irqrestore(&ctl->notifier_lock, flags);
+		if (notifier->notifier_fn)
+			notifier->notifier_fn(state, notifier->data);
+
+		spin_lock_irqsave(&ctl->notifier_lock, flags);
+	}
+
+	spin_unlock_irqrestore(&ctl->notifier_lock, flags);
+}
+
+void fsm_broadcast_state(struct ccci_fsm_ctl *ctl, enum md_state state)
+{
+	if (ctl->md_state != MD_STATE_WAITING_FOR_HS2 && state == MD_STATE_READY)
+		return;
+
+	ctl->md_state = state;
+
+	fsm_state_notify(state);
+}
+
+static void fsm_finish_command(struct ccci_fsm_ctl *ctl, struct ccci_fsm_command *cmd, int result)
+{
+	unsigned long flags;
+
+	if (cmd->flag & FSM_CMD_FLAG_WAITING_TO_COMPLETE) {
+		/* The processing thread may see the list, after a command is added,
+		 * without being woken up. Hence a spinlock is needed.
+		 */
+		spin_lock_irqsave(&ctl->cmd_complete_lock, flags);
+		cmd->result = result;
+		wake_up_all(&cmd->complete_wq);
+		spin_unlock_irqrestore(&ctl->cmd_complete_lock, flags);
+	} else {
+		/* no one is waiting for this command, free to kfree */
+		kfree(cmd);
+	}
+}
+
+/* call only with protection of event_lock */
+static void fsm_finish_event(struct ccci_fsm_ctl *ctl, struct ccci_fsm_event *event)
+{
+	list_del(&event->entry);
+	kfree(event);
+}
+
+static void fsm_flush_queue(struct ccci_fsm_ctl *ctl)
+{
+	struct ccci_fsm_event *event, *evt_next;
+	struct ccci_fsm_command *cmd, *cmd_next;
+	unsigned long flags;
+	struct device *dev;
+
+	dev = &ctl->md->mtk_dev->pdev->dev;
+	spin_lock_irqsave(&ctl->command_lock, flags);
+	list_for_each_entry_safe(cmd, cmd_next, &ctl->command_queue, entry) {
+		dev_warn(dev, "unhandled command %d\n", cmd->cmd_id);
+		list_del(&cmd->entry);
+		fsm_finish_command(ctl, cmd, FSM_CMD_RESULT_FAIL);
+	}
+
+	spin_unlock_irqrestore(&ctl->command_lock, flags);
+	spin_lock_irqsave(&ctl->event_lock, flags);
+	list_for_each_entry_safe(event, evt_next, &ctl->event_queue, entry) {
+		dev_warn(dev, "unhandled event %d\n", event->event_id);
+		fsm_finish_event(ctl, event);
+	}
+
+	spin_unlock_irqrestore(&ctl->event_lock, flags);
+}
+
+/* cmd is not NULL only when reason is ordinary exception */
+static void fsm_routine_exception(struct ccci_fsm_ctl *ctl, struct ccci_fsm_command *cmd,
+				  enum ccci_ex_reason reason)
+{
+	bool rec_ok = false;
+	struct ccci_fsm_event *event;
+	unsigned long flags;
+	struct device *dev;
+	int cnt;
+
+	dev = &ctl->md->mtk_dev->pdev->dev;
+	dev_err(dev, "exception %d, from %ps\n", reason, __builtin_return_address(0));
+	/* state sanity check */
+	if (ctl->curr_state != CCCI_FSM_READY && ctl->curr_state != CCCI_FSM_STARTING) {
+		if (cmd)
+			fsm_finish_command(ctl, cmd, FSM_CMD_RESULT_FAIL);
+		return;
+	}
+
+	ctl->last_state = ctl->curr_state;
+	ctl->curr_state = CCCI_FSM_EXCEPTION;
+
+	/* check exception reason */
+	switch (reason) {
+	case EXCEPTION_HS_TIMEOUT:
+		dev_err(dev, "BOOT_HS_FAIL\n");
+		break;
+
+	case EXCEPTION_EVENT:
+		fsm_broadcast_state(ctl, MD_STATE_EXCEPTION);
+		mtk_md_exception_handshake(ctl->md);
+		cnt = 0;
+		while (cnt < MD_EX_REC_OK_TIMEOUT_MS / EVENT_POLL_INTERVAL_MS) {
+			if (kthread_should_stop())
+				return;
+
+			spin_lock_irqsave(&ctl->event_lock, flags);
+			if (!list_empty(&ctl->event_queue)) {
+				event = list_first_entry(&ctl->event_queue,
+							 struct ccci_fsm_event, entry);
+				if (event->event_id == CCCI_EVENT_MD_EX) {
+					fsm_finish_event(ctl, event);
+				} else if (event->event_id == CCCI_EVENT_MD_EX_REC_OK) {
+					rec_ok = true;
+					fsm_finish_event(ctl, event);
+				}
+			}
+
+			spin_unlock_irqrestore(&ctl->event_lock, flags);
+			if (rec_ok)
+				break;
+
+			cnt++;
+			msleep(EVENT_POLL_INTERVAL_MS);
+		}
+
+		cnt = 0;
+		while (cnt < MD_EX_PASS_TIMEOUT_MS / EVENT_POLL_INTERVAL_MS) {
+			if (kthread_should_stop())
+				return;
+
+			spin_lock_irqsave(&ctl->event_lock, flags);
+			if (!list_empty(&ctl->event_queue)) {
+				event = list_first_entry(&ctl->event_queue,
+							 struct ccci_fsm_event, entry);
+				if (event->event_id == CCCI_EVENT_MD_EX_PASS)
+					fsm_finish_event(ctl, event);
+			}
+
+			spin_unlock_irqrestore(&ctl->event_lock, flags);
+			cnt++;
+			msleep(EVENT_POLL_INTERVAL_MS);
+		}
+
+		break;
+
+	default:
+		break;
+	}
+
+	if (cmd)
+		fsm_finish_command(ctl, cmd, FSM_CMD_RESULT_OK);
+}
+
+static void fsm_stopped_handler(struct ccci_fsm_ctl *ctl)
+{
+	ctl->last_state = ctl->curr_state;
+	ctl->curr_state = CCCI_FSM_STOPPED;
+
+	fsm_broadcast_state(ctl, MD_STATE_STOPPED);
+	mtk_md_reset(ctl->md->mtk_dev);
+}
+
+static void fsm_routine_stopped(struct ccci_fsm_ctl *ctl, struct ccci_fsm_command *cmd)
+{
+	/* state sanity check */
+	if (ctl->curr_state == CCCI_FSM_STOPPED) {
+		fsm_finish_command(ctl, cmd, FSM_CMD_RESULT_FAIL);
+		return;
+	}
+
+	fsm_stopped_handler(ctl);
+	fsm_finish_command(ctl, cmd, FSM_CMD_RESULT_OK);
+}
+
+static void fsm_routine_stopping(struct ccci_fsm_ctl *ctl, struct ccci_fsm_command *cmd)
+{
+	struct mtk_pci_dev *mtk_dev;
+	int err;
+
+	/* state sanity check */
+	if (ctl->curr_state == CCCI_FSM_STOPPED || ctl->curr_state == CCCI_FSM_STOPPING) {
+		fsm_finish_command(ctl, cmd, FSM_CMD_RESULT_FAIL);
+		return;
+	}
+
+	ctl->last_state = ctl->curr_state;
+	ctl->curr_state = CCCI_FSM_STOPPING;
+
+	fsm_broadcast_state(ctl, MD_STATE_WAITING_TO_STOP);
+	/* stop HW */
+	cldma_stop(ID_CLDMA1);
+
+	mtk_dev = ctl->md->mtk_dev;
+	if (!atomic_read(&ctl->md->rgu_irq_asserted)) {
+		/* disable DRM before FLDR */
+		mhccif_h2d_swint_trigger(mtk_dev, H2D_CH_DRM_DISABLE_AP);
+		msleep(FSM_DRM_DISABLE_DELAY_MS);
+		/* try FLDR first */
+		err = mtk_acpi_fldr_func(mtk_dev);
+		if (err)
+			mhccif_h2d_swint_trigger(mtk_dev, H2D_CH_DEVICE_RESET);
+	}
+
+	/* auto jump to stopped state handler */
+	fsm_stopped_handler(ctl);
+
+	fsm_finish_command(ctl, cmd, FSM_CMD_RESULT_OK);
+}
+
+static void fsm_routine_ready(struct ccci_fsm_ctl *ctl)
+{
+	struct mtk_modem *md;
+
+	ctl->last_state = ctl->curr_state;
+	ctl->curr_state = CCCI_FSM_READY;
+	fsm_broadcast_state(ctl, MD_STATE_READY);
+	md = ctl->md;
+	mtk_md_event_notify(md, FSM_READY);
+}
+
+static void fsm_routine_starting(struct ccci_fsm_ctl *ctl)
+{
+	struct mtk_modem *md;
+	struct device *dev;
+
+	ctl->last_state = ctl->curr_state;
+	ctl->curr_state = CCCI_FSM_STARTING;
+
+	fsm_broadcast_state(ctl, MD_STATE_WAITING_FOR_HS1);
+	md = ctl->md;
+	dev = &md->mtk_dev->pdev->dev;
+	mtk_md_event_notify(md, FSM_START);
+
+	wait_event_interruptible_timeout(ctl->async_hk_wq,
+					 atomic_read(&md->core_md.ready) ||
+					 atomic_read(&ctl->exp_flg), HZ * 60);
+
+	if (atomic_read(&ctl->exp_flg))
+		dev_err(dev, "MD exception is captured during handshake\n");
+
+	if (!atomic_read(&md->core_md.ready)) {
+		dev_err(dev, "MD handshake timeout\n");
+		fsm_routine_exception(ctl, NULL, EXCEPTION_HS_TIMEOUT);
+	} else {
+		fsm_routine_ready(ctl);
+	}
+}
+
+static void fsm_routine_start(struct ccci_fsm_ctl *ctl, struct ccci_fsm_command *cmd)
+{
+	struct mtk_modem *md;
+	struct device *dev;
+	u32 dev_status;
+
+	md = ctl->md;
+	if (!md)
+		return;
+
+	dev = &md->mtk_dev->pdev->dev;
+	/* state sanity check */
+	if (ctl->curr_state != CCCI_FSM_INIT &&
+	    ctl->curr_state != CCCI_FSM_PRE_START &&
+	    ctl->curr_state != CCCI_FSM_STOPPED) {
+		fsm_finish_command(ctl, cmd, FSM_CMD_RESULT_FAIL);
+		return;
+	}
+
+	ctl->last_state = ctl->curr_state;
+	ctl->curr_state = CCCI_FSM_PRE_START;
+	mtk_md_event_notify(md, FSM_PRE_START);
+
+	read_poll_timeout(ioread32, dev_status, (dev_status & MISC_STAGE_MASK) == LINUX_STAGE,
+			  20000, 2000000, false, IREG_BASE(md->mtk_dev) + PCIE_MISC_DEV_STATUS);
+	if ((dev_status & MISC_STAGE_MASK) != LINUX_STAGE) {
+		dev_err(dev, "invalid device status 0x%lx\n", dev_status & MISC_STAGE_MASK);
+		fsm_finish_command(ctl, cmd, FSM_CMD_RESULT_FAIL);
+		return;
+	}
+	cldma_hif_hw_init(ID_CLDMA1);
+	fsm_routine_starting(ctl);
+	fsm_finish_command(ctl, cmd, FSM_CMD_RESULT_OK);
+}
+
+static int fsm_main_thread(void *data)
+{
+	struct ccci_fsm_command *cmd;
+	struct ccci_fsm_ctl *ctl;
+	unsigned long flags;
+
+	ctl = data;
+
+	while (!kthread_should_stop()) {
+		if (wait_event_interruptible(ctl->command_wq, !list_empty(&ctl->command_queue) ||
+					     kthread_should_stop()))
+			continue;
+		if (kthread_should_stop())
+			break;
+
+		spin_lock_irqsave(&ctl->command_lock, flags);
+		cmd = list_first_entry(&ctl->command_queue, struct ccci_fsm_command, entry);
+		list_del(&cmd->entry);
+		spin_unlock_irqrestore(&ctl->command_lock, flags);
+
+		switch (cmd->cmd_id) {
+		case CCCI_COMMAND_START:
+			fsm_routine_start(ctl, cmd);
+			break;
+
+		case CCCI_COMMAND_EXCEPTION:
+			fsm_routine_exception(ctl, cmd, FIELD_GET(FSM_EX_REASON, cmd->flag));
+			break;
+
+		case CCCI_COMMAND_PRE_STOP:
+			fsm_routine_stopping(ctl, cmd);
+			break;
+
+		case CCCI_COMMAND_STOP:
+			fsm_routine_stopped(ctl, cmd);
+			break;
+
+		default:
+			fsm_finish_command(ctl, cmd, FSM_CMD_RESULT_FAIL);
+			fsm_flush_queue(ctl);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+int fsm_append_command(struct ccci_fsm_ctl *ctl, enum ccci_fsm_cmd_state cmd_id, unsigned int flag)
+{
+	struct ccci_fsm_command *cmd;
+	unsigned long flags;
+	int result = 0;
+
+	cmd = kmalloc(sizeof(*cmd),
+		      (in_irq() || in_softirq() || irqs_disabled()) ? GFP_ATOMIC : GFP_KERNEL);
+	if (!cmd)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&cmd->entry);
+	init_waitqueue_head(&cmd->complete_wq);
+	cmd->cmd_id = cmd_id;
+	cmd->result = FSM_CMD_RESULT_PENDING;
+	if (in_irq() || irqs_disabled())
+		flag &= ~FSM_CMD_FLAG_WAITING_TO_COMPLETE;
+
+	cmd->flag = flag;
+
+	spin_lock_irqsave(&ctl->command_lock, flags);
+	list_add_tail(&cmd->entry, &ctl->command_queue);
+	spin_unlock_irqrestore(&ctl->command_lock, flags);
+	/* after this line, only dereference command when "waiting-to-complete" */
+	wake_up(&ctl->command_wq);
+	if (flag & FSM_CMD_FLAG_WAITING_TO_COMPLETE) {
+		wait_event(cmd->complete_wq, cmd->result != FSM_CMD_RESULT_PENDING);
+		if (cmd->result != FSM_CMD_RESULT_OK)
+			result = -EINVAL;
+
+		spin_lock_irqsave(&ctl->cmd_complete_lock, flags);
+		kfree(cmd);
+		spin_unlock_irqrestore(&ctl->cmd_complete_lock, flags);
+	}
+
+	return result;
+}
+
+int fsm_append_event(struct ccci_fsm_ctl *ctl, enum ccci_fsm_event_state event_id,
+		     unsigned char *data, unsigned int length)
+{
+	struct ccci_fsm_event *event;
+	unsigned long flags;
+	struct device *dev;
+
+	dev = &ctl->md->mtk_dev->pdev->dev;
+
+	if (event_id <= CCCI_EVENT_INVALID || event_id >= CCCI_EVENT_MAX) {
+		dev_err(dev, "invalid event %d\n", event_id);
+		return -EINVAL;
+	}
+
+	event = kmalloc(struct_size(event, data, length),
+			in_interrupt() ? GFP_ATOMIC : GFP_KERNEL);
+	if (!event)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&event->entry);
+	event->event_id = event_id;
+	event->length = length;
+	if (data && length)
+		memcpy(event->data, data, flex_array_size(event, data, event->length));
+
+	spin_lock_irqsave(&ctl->event_lock, flags);
+	list_add_tail(&event->entry, &ctl->event_queue);
+	spin_unlock_irqrestore(&ctl->event_lock, flags);
+	wake_up_all(&ctl->event_wq);
+	return 0;
+}
+
+void fsm_clear_event(struct ccci_fsm_ctl *ctl, enum ccci_fsm_event_state event_id)
+{
+	struct ccci_fsm_event *event, *evt_next;
+	unsigned long flags;
+	struct device *dev;
+
+	dev = &ctl->md->mtk_dev->pdev->dev;
+
+	spin_lock_irqsave(&ctl->event_lock, flags);
+	list_for_each_entry_safe(event, evt_next, &ctl->event_queue, entry) {
+		dev_err(dev, "unhandled event %d\n", event->event_id);
+		if (event->event_id == event_id)
+			fsm_finish_event(ctl, event);
+	}
+
+	spin_unlock_irqrestore(&ctl->event_lock, flags);
+}
+
+struct ccci_fsm_ctl *fsm_get_entity_by_device_number(dev_t dev_n)
+{
+	if (ccci_fsm_entry && ccci_fsm_entry->monitor_ctl.dev_n == dev_n)
+		return ccci_fsm_entry;
+
+	return NULL;
+}
+
+struct ccci_fsm_ctl *fsm_get_entry(void)
+{
+	return ccci_fsm_entry;
+}
+
+enum md_state ccci_fsm_get_md_state(void)
+{
+	struct ccci_fsm_ctl *ctl;
+
+	ctl = ccci_fsm_entry;
+	if (ctl)
+		return ctl->md_state;
+	else
+		return MD_STATE_INVALID;
+}
+
+unsigned int ccci_fsm_get_current_state(void)
+{
+	struct ccci_fsm_ctl *ctl;
+
+	ctl = ccci_fsm_entry;
+	if (ctl)
+		return ctl->curr_state;
+	else
+		return CCCI_FSM_STOPPED;
+}
+
+void ccci_fsm_recv_md_interrupt(enum md_irq_type type)
+{
+	struct ccci_fsm_ctl *ctl;
+
+	ctl = ccci_fsm_entry;
+	if (type == MD_IRQ_PORT_ENUM) {
+		fsm_append_command(ctl, CCCI_COMMAND_START, 0);
+	} else if (type == MD_IRQ_CCIF_EX) {
+		/* interrupt handshake flow */
+		atomic_set(&ctl->exp_flg, 1);
+		wake_up(&ctl->async_hk_wq);
+		fsm_append_command(ctl, CCCI_COMMAND_EXCEPTION,
+				   FIELD_PREP(FSM_EX_REASON, EXCEPTION_EE));
+	}
+}
+
+void ccci_fsm_reset(void)
+{
+	struct ccci_fsm_ctl *ctl;
+
+	ctl = ccci_fsm_entry;
+	/* Clear event and command queues */
+	fsm_flush_queue(ctl);
+
+	ctl->last_state = CCCI_FSM_INIT;
+	ctl->curr_state = CCCI_FSM_STOPPED;
+	atomic_set(&ctl->exp_flg, 0);
+}
+
+int ccci_fsm_init(struct mtk_modem *md)
+{
+	struct ccci_fsm_ctl *ctl;
+
+	ctl = devm_kzalloc(&md->mtk_dev->pdev->dev, sizeof(*ctl), GFP_KERNEL);
+	if (!ctl)
+		return -ENOMEM;
+
+	ccci_fsm_entry = ctl;
+	ctl->md = md;
+	ctl->last_state = CCCI_FSM_INIT;
+	ctl->curr_state = CCCI_FSM_INIT;
+	INIT_LIST_HEAD(&ctl->command_queue);
+	INIT_LIST_HEAD(&ctl->event_queue);
+	init_waitqueue_head(&ctl->async_hk_wq);
+	init_waitqueue_head(&ctl->event_wq);
+	INIT_LIST_HEAD(&ctl->notifier_list);
+	init_waitqueue_head(&ctl->command_wq);
+	spin_lock_init(&ctl->event_lock);
+	spin_lock_init(&ctl->command_lock);
+	spin_lock_init(&ctl->cmd_complete_lock);
+	atomic_set(&ctl->exp_flg, 0);
+	spin_lock_init(&ctl->notifier_lock);
+
+	ctl->fsm_thread = kthread_run(fsm_main_thread, ctl, "ccci_fsm");
+	if (IS_ERR(ctl->fsm_thread)) {
+		dev_err(&md->mtk_dev->pdev->dev, "failed to start monitor thread\n");
+		return PTR_ERR(ctl->fsm_thread);
+	}
+
+	return 0;
+}
+
+void ccci_fsm_uninit(void)
+{
+	struct ccci_fsm_ctl *ctl;
+
+	ctl = ccci_fsm_entry;
+	if (!ctl)
+		return;
+
+	if (ctl->fsm_thread)
+		kthread_stop(ctl->fsm_thread);
+
+	fsm_flush_queue(ctl);
+}