[PATCH v10 1/6] net: wwan: t9xx: Add PCIe core

Loic Poulain loic.poulain at oss.qualcomm.com
Wed Oct 14 05:49:19 PDT 2026


On Wed, Oct 14, 2026 at 4:52 AM Jack Wu via B4 Relay
<devnull+jackbb_wu.compal.com at kernel.org> wrote:
>
> From: Jack Wu <jackbb_wu at compal.com>
>
> Registers the T900 device driver with the kernel. Set up all
> the fundamental configurations for the device: PCIe layer,
> Modem Host Cross Core Interface (MHCCIF), modem common control
> operations, build infrastructure and MAINTAINERS entry.
> Besides the T900 itself (14c3:0900) the driver binds its
> HP-branded variant, the HP RMM-G4 (03f0:01ca); both IDs are
> exported via MODULE_DEVICE_TABLE for module autoloading.
>
> * PCIe layer code implements driver probe and removal, MSI-X
>   interrupt initialization and de-initialization, and the way
>   of resetting the device.
> * MHCCIF provides the interrupt channels used for the boot
>   handshake and for the host-to-device reset doorbell.
> * Modem common control operations provide the basic read/write
>   functions of the device's hardware registers,
>   mask/unmask/get/clear functions of the device's interrupt
>   registers and inquiry functions of the device's status.
> * Add MAINTAINERS entry for the MediaTek T9XX 5G WWAN modem
>   device driver.
>
> Removal resets the modem, because the next driver load has to
> redo the device handshake and status sync and cannot do that
> against firmware left running by the previous load.
> mtk_pci_remove() evaluates MRST._RST on the device's own ACPI
> node: that is a platform-level reset and does not depend on the
> firmware's state.  Where the platform has no MRST._RST, removal
> falls back to the MHCCIF DEV_EVT_H2D_DEVICE_RESET doorbell if
> the link is still up.  The doorbell is a request to the
> firmware, so it is best effort and cannot recover firmware that
> has stopped responding - the case the reset exists for.  Probe
> therefore does not require MRST._RST; without it the driver
> still binds and only removal is degraded.
>
> Signed-off-by: Jack Wu <jackbb_wu at compal.com>
> ---
>  MAINTAINERS                              |   11 +
>  drivers/net/wwan/Kconfig                 |   11 +
>  drivers/net/wwan/Makefile                |    1 +
>  drivers/net/wwan/t9xx/Makefile           |    9 +
>  drivers/net/wwan/t9xx/mtk_dev.h          |   42 ++
>  drivers/net/wwan/t9xx/pcie/mtk_pci.c     | 1042 ++++++++++++++++++++++++++++++
>  drivers/net/wwan/t9xx/pcie/mtk_pci.h     |  140 ++++
>  drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h |   34 +
>  8 files changed, 1290 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 461a3eed6129..8f13ef7440ce 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16494,6 +16494,17 @@ L:     netdev at vger.kernel.org
>  S:     Supported
>  F:     drivers/net/wwan/t7xx/
>
> +MEDIATEK T9XX 5G WWAN MODEM DRIVER
> +M:     Jack Wu <jackbb_wu at compal.com>
> +M:     Robert Yu <robert_yu at compal.com>
> +M:     Jeff Chang <Jeff_Chang at compal.com>
> +R:     Wen-Zhi Huang <wen-zhi.huang at mediatek.com>
> +R:     Shi-Wei Yeh <shi-wei.yeh at mediatek.com>
> +R:     Minano Tseng <Minano.tseng at mediatek.com>
> +L:     netdev at vger.kernel.org
> +S:     Supported
> +F:     drivers/net/wwan/t9xx/
> +
>  MEDIATEK USB3 DRD IP DRIVER
>  M:     Chunfeng Yun <chunfeng.yun at mediatek.com>
>  L:     linux-usb at vger.kernel.org
> diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig
> index 88df55d78d90..f510cc8ab4e4 100644
> --- a/drivers/net/wwan/Kconfig
> +++ b/drivers/net/wwan/Kconfig
> @@ -121,6 +121,17 @@ config MTK_T7XX
>
>           If unsure, say N.
>
> +config MTK_T9XX
> +       tristate "MediaTek PCIe 5G WWAN modem T9xx device"
> +       depends on PCI_MSI && ACPI
> +       help
> +         Enables MediaTek PCIe based 5G WWAN modem (T9xx series) device.
> +
> +         To compile this driver as a module, choose M here: the module will be
> +         called mtk_t9xx.
> +
> +         If unsure, say N.
> +
>  endif # WWAN
>
>  endmenu
> diff --git a/drivers/net/wwan/Makefile b/drivers/net/wwan/Makefile
> index 3960c0ae2445..7361eef4c472 100644
> --- a/drivers/net/wwan/Makefile
> +++ b/drivers/net/wwan/Makefile
> @@ -14,3 +14,4 @@ obj-$(CONFIG_QCOM_BAM_DMUX) += qcom_bam_dmux.o
>  obj-$(CONFIG_RPMSG_WWAN_CTRL) += rpmsg_wwan_ctrl.o
>  obj-$(CONFIG_IOSM) += iosm/
>  obj-$(CONFIG_MTK_T7XX) += t7xx/
> +obj-$(CONFIG_MTK_T9XX) += t9xx/
> diff --git a/drivers/net/wwan/t9xx/Makefile b/drivers/net/wwan/t9xx/Makefile
> new file mode 100644
> index 000000000000..bd6063c85c2b
> --- /dev/null
> +++ b/drivers/net/wwan/t9xx/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +ccflags-y += -I$(src)/pcie
> +ccflags-y += -I$(src)
> +
> +obj-$(CONFIG_MTK_T9XX) += mtk_t9xx.o
> +
> +mtk_t9xx-y := \
> +       pcie/mtk_pci.o
> diff --git a/drivers/net/wwan/t9xx/mtk_dev.h b/drivers/net/wwan/t9xx/mtk_dev.h
> new file mode 100644
> index 000000000000..329e5e6695df
> --- /dev/null
> +++ b/drivers/net/wwan/t9xx/mtk_dev.h
> @@ -0,0 +1,42 @@
> +/* SPDX-License-Identifier: GPL-2.0-only
> + *
> + * Copyright (c) 2022, MediaTek Inc.
> + */
> +
> +#ifndef __MTK_DEV_H__
> +#define __MTK_DEV_H__
> +
> +#include <linux/dma-mapping.h>
> +#include <linux/dmapool.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/sched.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>

Looks like a lot of headers for such a small header file. Could you
trim the unnecessary includes? For example, spinlock.h doesn't appear
to be used.

> +
> +#define MTK_DEV_STR_LEN 16
> +
> +enum mtk_user_id {
> +       MTK_USER_CTRL,
> +       MTK_USER_MAX
> +};
> +
> +enum mtk_dev_evt_h2d {
> +       DEV_EVT_H2D_DEVICE_RESET        = BIT(2),
> +};
> +
> +enum mtk_dev_evt_d2h {
> +       DEV_EVT_D2H_BOOT_FLOW_SYNC      = BIT(4),
> +       DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP = BIT(5),
> +       DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD  = BIT(6),
> +};
> +
> +/* mtk_md_dev defines the structure of MTK modem device */
> +struct mtk_md_dev {
> +       struct device *dev;
> +       void *hw_priv;
> +       u32 hw_ver;
> +       char dev_str[MTK_DEV_STR_LEN];
> +};
> +
> +#endif /* __MTK_DEV_H__ */
> diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
> new file mode 100644
> index 000000000000..a3bd1caf470b
> --- /dev/null
> +++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
> @@ -0,0 +1,1042 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2022, MediaTek Inc.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/aer.h>
> +#include <linux/bitfield.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include "mtk_dev.h"
> +#include "mtk_pci.h"
> +#include "mtk_pci_reg.h"
> +
> +#define MTK_MHCCIF_RC_BASE_ADDR                0x1000A000
> +
> +#define MTK_MHCCIF_RESET_DELAY_MS      2000
> +
> +static const int mtk_pci_irq_tbl[MTK_IRQ_SRC_MAX] = {
> +       [MTK_IRQ_SRC_MHCCIF] = 28,
> +       [MTK_IRQ_SRC_CLDMA0] = 27,
> +       [MTK_IRQ_SRC_CLDMA1] = 26,
> +};
> +
> +#define MTK_PCI_TRANSPARENT_ATR_SIZE   (0x3F)
> +#define MTK_PCI_MINIMUM_ATR_SIZE       (0x1000)
> +#define ATR_SIZE_LO32_MASK             GENMASK_ULL(31, 0)
> +#define ATR_SIZE_HI32_MASK             GENMASK_ULL(63, 32)
> +#define ATR_SIZE_BIAS_FROM_LO32                2
> +#define ATR_ADDR_ALIGN_MASK            0xFFFFF000
> +#define ATR_EN                         BIT(0)
> +#define ATR_PARAM_OFFSET               16
> +#define SET_HW_BITS(dest, chs, mhccif, dev)            \
> +       ({                                              \
> +               if ((chs) & (dev))                                      \
> +                       (dest) |= FIELD_PREP(mhccif, 1);                \
> +       })

A static inline helper would be cleaner.

> +
> +struct mtk_mhccif_cb {
> +       struct list_head entry;
> +       int (*evt_cb)(u32 status, void *data);
> +       void *data;
> +       u32 chs;
> +};
> +
> +/**
> + * mtk_pci_setup_atr() - Configure a PCIe address translation rule
> + * @mdev: MTK MD device
> + * @cfg: ATR configuration parameters
> + *
> + * Return: 0 on success, negative error code on failure.
> + */
> +int mtk_pci_setup_atr(struct mtk_md_dev *mdev, struct mtk_atr_cfg *cfg)
> +{
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +       u32 addr, val, size_h, size_l;
> +       int atr_size, pos, offset;
> +
> +       if (cfg->transparent) {
> +               /* No address conversion is performed */
> +               atr_size = MTK_PCI_TRANSPARENT_ATR_SIZE;
> +       } else {
> +               if (cfg->size < MTK_PCI_MINIMUM_ATR_SIZE)
> +                       cfg->size = MTK_PCI_MINIMUM_ATR_SIZE;
> +
> +               if (cfg->src_addr & (cfg->size - 1)) {
> +                       dev_err(mdev->dev, "Invalid atr src addr is not aligned to size\n");
> +                       return -EFAULT;
> +               }
> +
> +               if (cfg->trsl_addr & (cfg->size - 1)) {
> +                       dev_err(mdev->dev,
> +                               "Invalid atr trsl addr is not aligned to size, %llx, %llx\n",
> +                               cfg->trsl_addr, cfg->size - 1);
> +                       return -EFAULT;
> +               }
> +
> +               size_l = FIELD_GET(ATR_SIZE_LO32_MASK, cfg->size);
> +               size_h = FIELD_GET(ATR_SIZE_HI32_MASK, cfg->size);
> +               pos = ffs(size_l);
> +               if (pos) {
> +                       atr_size = pos - ATR_SIZE_BIAS_FROM_LO32;
> +               } else {
> +                       pos = ffs(size_h);
> +                       atr_size = pos + 32 - ATR_SIZE_BIAS_FROM_LO32;
> +               }
> +       }
> +
> +       /* Calculate table offset */
> +       offset = ATR_PORT_OFFSET * cfg->port + ATR_TABLE_OFFSET * cfg->table;
> +
> +       addr = REG_ATR_PCIE_WIN0_T0_SRC_ADDR_MSB + offset;
> +       val = (u32)(cfg->src_addr >> 32);
> +       mtk_pci_mac_write32(priv, addr, val);
> +
> +       addr = REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_MSB + offset;
> +       val = (u32)(cfg->trsl_addr >> 32);
> +       mtk_pci_mac_write32(priv, addr, val);
> +
> +       addr = REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_LSB + offset;
> +       val = (u32)(cfg->trsl_addr & ATR_ADDR_ALIGN_MASK);
> +       mtk_pci_mac_write32(priv, addr, val);
> +
> +       /* TRSL_PARAM */
> +       addr = REG_ATR_PCIE_WIN0_T0_TRSL_PARAM + offset;
> +       val = (cfg->trsl_param << ATR_PARAM_OFFSET) | cfg->trsl_id;
> +       mtk_pci_mac_write32(priv, addr, val);
> +
> +       /* Enable ATR last, after translation target is fully programmed */
> +       addr = REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset;
> +       val = (u32)(cfg->src_addr & ATR_ADDR_ALIGN_MASK) | (atr_size << 1) | ATR_EN;
> +       mtk_pci_mac_write32(priv, addr, val);
> +
> +       /* Ensure ATR is set */
> +       mtk_pci_mac_read32(priv, addr);
> +
> +       return 0;
> +}
> +
> +/**
> + * mtk_pci_atr_disable() - Disable all PCIe address translation rules
> + * @priv: MTK PCI private data
> + */
> +void mtk_pci_atr_disable(struct mtk_pci_priv *priv)
> +{
> +       int port, tbl, offset;
> +       u32 val;
> +
> +       /* Disable all ATR table for all ports */
> +       for (port = ATR_SRC_PCI_WIN0; port <= ATR_SRC_AXIS_3; port++)
> +               for (tbl = 0; tbl < ATR_TABLE_NUM_PER_ATR; tbl++) {
> +                       /* Calculate table offset */
> +                       offset = ATR_PORT_OFFSET * port + ATR_TABLE_OFFSET * tbl;
> +                       val = mtk_pci_mac_read32(priv, REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset);
> +                       val = val & (~BIT(0));
> +                       /* Disable table by SRC_ADDR_L */
> +                       mtk_pci_mac_write32(priv, REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset, val);
> +               }
> +}
> +
> +/**
> + * mtk_pci_get_dev_state() - Read the device state from the modem
> + * @mdev: MTK MD device
> + *
> + * Return: Device state value.
> + */
> +u32 mtk_pci_get_dev_state(struct mtk_md_dev *mdev)
> +{
> +       return mtk_pci_mac_read32(mdev->hw_priv, REG_PCIE_DEBUG_DUMMY_7);
> +}
> +
> +/**
> + * mtk_pci_ack_dev_state() - Acknowledge the device state to the modem
> + * @mdev: MTK MD device
> + * @state: State value to acknowledge
> + */
> +void mtk_pci_ack_dev_state(struct mtk_md_dev *mdev, u32 state)
> +{
> +       mtk_pci_mac_write32(mdev->hw_priv, REG_PCIE_DEBUG_DUMMY_7, state);
> +}
> +
> +/**
> + * mtk_pci_get_irq_id() - Map an IRQ source to its hardware IRQ ID
> + * @mdev: MTK MD device
> + * @irq_src: IRQ source enum
> + *
> + * Return: IRQ ID on success, -EINVAL on failure.
> + */
> +int mtk_pci_get_irq_id(struct mtk_md_dev *mdev, enum mtk_irq_src irq_src)
> +{
> +       int irq_id = -EINVAL;
> +
> +       if (irq_src > MTK_IRQ_SRC_MIN && irq_src < MTK_IRQ_SRC_MAX) {
> +               irq_id = mtk_pci_irq_tbl[irq_src];
> +               if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX)
> +                       irq_id = -EINVAL;
> +       }
> +
> +       return irq_id;
> +}
> +
> +/**
> + * mtk_pci_get_virq_id() - Get the Linux virtual IRQ for a hardware IRQ ID
> + * @mdev: MTK MD device
> + * @irq_id: Hardware IRQ ID
> + *
> + * Return: Virtual IRQ number on success, negative error code on failure.
> + */
> +int mtk_pci_get_virq_id(struct mtk_md_dev *mdev, int irq_id)
> +{
> +       struct pci_dev *pdev = to_pci_dev(mdev->dev);
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +
> +       if (irq_id < 0 || irq_id >= priv->irq_cnt)
> +               return -EINVAL;
> +
> +       return pci_irq_vector(pdev, irq_id);
> +}
> +
> +/**
> + * mtk_pci_register_irq() - Register a callback for a hardware IRQ
> + * @mdev: MTK MD device
> + * @irq_id: Hardware IRQ ID
> + * @irq_cb: Callback function
> + * @data: Private data passed to callback
> + *
> + * Return: 0 on success, negative error code on failure.
> + */
> +int mtk_pci_register_irq(struct mtk_md_dev *mdev, int irq_id,
> +                        int (*irq_cb)(int irq_id, void *data), void *data)
> +{
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +
> +       if ((irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX) || !irq_cb)
> +               return -EINVAL;
> +
> +       spin_lock(&priv->irq_cb_lock);
> +       if (priv->irq_cb_list[irq_id]) {
> +               spin_unlock(&priv->irq_cb_lock);
> +               dev_err(mdev->dev,
> +                       "Unable to register irq, irq_id=%d, it's already been register by %ps.\n",
> +                       irq_id, priv->irq_cb_list[irq_id]);
> +               return -EFAULT;
> +       }
> +       priv->irq_cb_data[irq_id] = data;
> +       smp_wmb(); /* Ensure data is visible before callback */
> +       WRITE_ONCE(priv->irq_cb_list[irq_id], irq_cb);
> +       spin_unlock(&priv->irq_cb_lock);
> +
> +       return 0;
> +}
> +
> +/**
> + * mtk_pci_unregister_irq() - Unregister a hardware IRQ callback
> + * @mdev: MTK MD device
> + * @irq_id: Hardware IRQ ID
> + *
> + * Return: 0 on success, negative error code on failure.
> + */
> +int mtk_pci_unregister_irq(struct mtk_md_dev *mdev, int irq_id)
> +{
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +       int virq_id;
> +
> +       if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX)
> +               return -EINVAL;
> +
> +       if (!READ_ONCE(priv->irq_cb_list[irq_id])) {
> +               dev_err(mdev->dev, "irq_id=%d has not been registered\n", irq_id);
> +               return -EFAULT;
> +       }
> +
> +       /* mtk_pci_irq_handler() loads the callback and its data separately, so
> +        * the data may only be cleared once no handler can still hold a live
> +        * callback pointer.
> +        */
> +       mtk_pci_mask_irq(mdev, irq_id);
> +
> +       spin_lock(&priv->irq_cb_lock);
> +       WRITE_ONCE(priv->irq_cb_list[irq_id], NULL);
> +       spin_unlock(&priv->irq_cb_lock);
> +
> +       virq_id = mtk_pci_get_virq_id(mdev, irq_id);
> +       if (virq_id >= 0)
> +               synchronize_irq(virq_id);
> +
> +       priv->irq_cb_data[irq_id] = NULL;
> +
> +       return 0;
> +}
> +
> +/**
> + * mtk_pci_mask_irq() - Mask (disable) a hardware IRQ
> + * @mdev: MTK MD device
> + * @irq_id: Hardware IRQ ID
> + *
> + * Return: 0 on success, negative error code on failure.
> + */
> +int mtk_pci_mask_irq(struct mtk_md_dev *mdev, int irq_id)
> +{
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +
> +       if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX) {
> +               dev_err(mdev->dev, "Failed to mask irq: input irq_id=%d\n", irq_id);
> +               return -EINVAL;
> +       }
> +
> +       mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, BIT(irq_id));
> +
> +       return 0;
> +}
> +
> +/**
> + * mtk_pci_unmask_irq() - Unmask (enable) a hardware IRQ
> + * @mdev: MTK MD device
> + * @irq_id: Hardware IRQ ID
> + *
> + * Return: 0 on success, negative error code on failure.
> + */
> +int mtk_pci_unmask_irq(struct mtk_md_dev *mdev, int irq_id)
> +{
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +
> +       if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX) {
> +               dev_err(mdev->dev, "Failed to unmask irq: input irq_id=%d\n", irq_id);
> +               return -EINVAL;
> +       }
> +
> +       mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_SET_GRP0_0, BIT(irq_id));
> +
> +       return 0;
> +}
> +
> +/**
> + * mtk_pci_clear_irq() - Clear (acknowledge) a hardware IRQ
> + * @mdev: MTK MD device
> + * @irq_id: Hardware IRQ ID
> + *
> + * Return: 0 on success, negative error code on failure.
> + */
> +int mtk_pci_clear_irq(struct mtk_md_dev *mdev, int irq_id)
> +{
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +
> +       if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX) {
> +               dev_err(mdev->dev, "Failed to clear irq: input irq_id=%d\n", irq_id);
> +               return -EINVAL;
> +       }
> +
> +       mtk_pci_mac_write32(priv, REG_MSIX_ISTATUS_HOST_GRP0_0, BIT(irq_id));
> +
> +       return 0;
> +}
> +
> +static u32 mtk_pci_ext_d2h_evt_hw_bits(u32 chs)
> +{
> +       u32 hw_bits = 0;
> +
> +       SET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC,
> +                   DEV_EVT_D2H_BOOT_FLOW_SYNC);
> +       SET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP,
> +                   DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP);
> +       SET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD,
> +                   DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD);
> +
> +       return hw_bits;
> +}
> +
> +static u32 mtk_pci_ext_d2h_evt_chs(u32 hw_bits)
> +{
> +       u32 chs = 0;
> +
> +       if (!hw_bits)
> +               return chs;
> +
> +       chs = FIELD_PREP(DEV_EVT_D2H_BOOT_FLOW_SYNC,
> +                        FIELD_GET(MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC, hw_bits)) |
> +             FIELD_PREP(DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP,
> +                        FIELD_GET(MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP, hw_bits)) |
> +             FIELD_PREP(DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD,
> +                        FIELD_GET(MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD, hw_bits));
> +
> +       return chs;
> +}
> +
> +/**
> + * mtk_pci_register_ext_evt() - Register a callback for MHCCIF device events
> + * @mdev: MTK MD device
> + * @chs: Bitmask of event channels to register
> + * @evt_cb: Callback function
> + * @data: Private data passed to callback
> + *
> + * Return: 0 on success, negative error code on failure.
> + */
> +int mtk_pci_register_ext_evt(struct mtk_md_dev *mdev, u32 chs,
> +                            int (*evt_cb)(u32 status, void *data), void *data)
> +{
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +       struct mtk_mhccif_cb *cb;
> +       int ret = 0;
> +
> +       if (!chs || !evt_cb)
> +               return -EINVAL;
> +
> +       spin_lock_bh(&priv->mhccif_lock);
> +       list_for_each_entry(cb, &priv->mhccif_cb_list, entry) {
> +               if (cb->chs & chs) {
> +                       ret = -EFAULT;
> +                       dev_err(mdev->dev,
> +                               "Unable to register evt, intersection: chs=0x%08x&0x%08x cb=%ps\n",
> +                               chs, cb->chs, cb->evt_cb);
> +                       goto err_spin_unlock;
> +               }
> +       }
> +       cb = kzalloc(sizeof(*cb), GFP_ATOMIC);
> +       if (!cb) {
> +               ret = -ENOMEM;
> +               goto err_spin_unlock;
> +       }
> +       cb->evt_cb = evt_cb;
> +       cb->data = data;
> +       cb->chs = chs;
> +       list_add_tail(&cb->entry, &priv->mhccif_cb_list);
> +err_spin_unlock:
> +       spin_unlock_bh(&priv->mhccif_lock);
> +
> +       return ret;
> +}
> +
> +/**
> + * mtk_pci_unregister_ext_evt() - Unregister an MHCCIF device event callback
> + * @mdev: MTK MD device
> + * @chs: Bitmask of event channels to unregister
> + */
> +void mtk_pci_unregister_ext_evt(struct mtk_md_dev *mdev, u32 chs)
> +{
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +       struct mtk_mhccif_cb *cb, *next;
> +
> +       if (!chs)
> +               return;
> +
> +       spin_lock_bh(&priv->mhccif_lock);
> +       list_for_each_entry_safe(cb, next, &priv->mhccif_cb_list, entry) {
> +               if (cb->chs == chs) {
> +                       list_del(&cb->entry);
> +                       kfree(cb);
> +                       goto out;
> +               }
> +       }
> +       dev_warn(mdev->dev,
> +                "Unable to unregister evt, no chs=0x%08x has been registered.\n", chs);
> +out:
> +       spin_unlock_bh(&priv->mhccif_lock);
> +}
> +
> +/**
> + * mtk_pci_mask_ext_evt() - Mask (disable) MHCCIF device events
> + * @mdev: MTK MD device
> + * @chs: Bitmask of event channels to mask
> + */
> +void mtk_pci_mask_ext_evt(struct mtk_md_dev *mdev, u32 chs)
> +{
> +       u32 hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);
> +
> +       mtk_pci_write32(mdev, MTK_MHCCIF_RC_BASE_ADDR +
> +                       MHCCIF_EP2RC_SW_INT_EAP_MASK_SET, hw_bits);
> +}
> +
> +/**
> + * mtk_pci_unmask_ext_evt() - Unmask (enable) MHCCIF device events
> + * @mdev: MTK MD device
> + * @chs: Bitmask of event channels to unmask
> + */
> +void mtk_pci_unmask_ext_evt(struct mtk_md_dev *mdev, u32 chs)
> +{
> +       u32 hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);
> +
> +       mtk_pci_write32(mdev, MTK_MHCCIF_RC_BASE_ADDR +
> +                       MHCCIF_EP2RC_SW_INT_EAP_MASK_CLR, hw_bits);
> +}
> +
> +static u32 mtk_pci_ext_h2d_evt_hw_bits(u32 chs)
> +{
> +       u32 hw_bits = 0;
> +
> +       SET_HW_BITS(hw_bits, chs, MHCCIF_RC2EP_EVT_DEVICE_RESET,
> +                   DEV_EVT_H2D_DEVICE_RESET);
> +       return hw_bits;
> +}
> +
> +/**
> + * mtk_pci_send_ext_evt() - Send an MHCCIF event to the modem
> + * @mdev: MTK MD device
> + * @ch: Event channel to trigger (must be a single bit)
> + *
> + * Return: 0 on success, negative error code on failure.
> + */
> +int mtk_pci_send_ext_evt(struct mtk_md_dev *mdev, u32 ch)
> +{
> +       u32 rc_base = MTK_MHCCIF_RC_BASE_ADDR;
> +       u32 hw_bits;
> +
> +       /* Only allow one ch to be triggered at a time */
> +       if (!is_power_of_2(ch)) {
> +               dev_err(mdev->dev, "Unsupported ext evt ch=0x%08x\n", ch);
> +               return -EINVAL;
> +       }
> +
> +       hw_bits = mtk_pci_ext_h2d_evt_hw_bits(ch);
> +       if (!hw_bits) {
> +               dev_err(mdev->dev, "Unmapped ext evt ch=0x%08x\n", ch);
> +               return -EINVAL;
> +       }
> +
> +       mtk_pci_write32(mdev, rc_base + MHCCIF_RC2EP_SW_BSY, hw_bits);
> +       mtk_pci_write32(mdev, rc_base + MHCCIF_RC2EP_SW_TCHNUM, ffs(hw_bits) - 1);
> +       return 0;
> +}
> +
> +static u32 mtk_pci_get_ext_evt_hw_status(struct mtk_md_dev *mdev)
> +{
> +       return mtk_pci_read32(mdev, MTK_MHCCIF_RC_BASE_ADDR +
> +                             MHCCIF_EP2RC_SW_INT_STS);
> +}
> +
> +static void mtk_pci_ack_ext_evt_hw(struct mtk_md_dev *mdev, u32 hw_bits)
> +{
> +       mtk_pci_write32(mdev, MTK_MHCCIF_RC_BASE_ADDR +
> +                       MHCCIF_EP2RC_SW_INT_ACK, hw_bits);
> +       /* Ensure the ack lands before level 1 is cleared */
> +       mtk_pci_read32(mdev, MTK_MHCCIF_RC_BASE_ADDR +
> +                      MHCCIF_EP2RC_SW_INT_STS);
> +}
> +
> +/**
> + * mtk_pci_pldr() - Reset the modem via its ACPI MRST._RST method
> + * @mdev: MTK MD device
> + *
> + * Return:
> + * * 0       - Success, device was reset.
> + * * -ENODEV - No ACPI, no handle or no MRST._RST method.  Device
> + *             untouched.
> + * * -EIO    - MRST._RST failed.  Device untouched, still running;
> + *             a different reset may be attempted.
> + */
> +int mtk_pci_pldr(struct mtk_md_dev *mdev)
> +{
> +       acpi_status acpi_ret;
> +       acpi_handle handle;
> +
> +       if (acpi_disabled) {
> +               dev_dbg(mdev->dev, "Unsupported, acpi function isn't enable\n");
> +               return -ENODEV;
> +       }
> +
> +       handle = ACPI_HANDLE(mdev->dev);
> +       if (!handle) {
> +               dev_dbg(mdev->dev, "Unsupported, acpi handle isn't found\n");
> +               return -ENODEV;
> +       }
> +       if (!acpi_has_method(handle, "MRST._RST")) {
> +               dev_dbg(mdev->dev, "Unsupported, pldr method isn't supported\n");
> +               return -ENODEV;
> +       }
> +       acpi_ret = acpi_evaluate_object(handle, "MRST._RST", NULL, NULL);
> +       if (ACPI_FAILURE(acpi_ret)) {
> +               dev_err(mdev->dev, "Failed to execute MRST._RST method: %s\n",
> +                       acpi_format_exception(acpi_ret));
> +               return -EIO;
> +       }
> +
> +       return 0;
> +}
> +
> +/**
> + * mtk_pci_link_check() - Check if the PCIe link to the modem is active
> + * @mdev: MTK MD device
> + *
> + * Return: true if the device is present, false otherwise.
> + */
> +bool mtk_pci_link_check(struct mtk_md_dev *mdev)
> +{
> +       return pci_device_is_present(to_pci_dev(mdev->dev));
> +}
> +
> +static void mtk_mhccif_isr_work(struct work_struct *work)
> +{
> +       struct mtk_pci_priv *priv =
> +               container_of(work, struct mtk_pci_priv, mhccif_work);
> +       struct mtk_md_dev *mdev = priv->irq_desc->mdev;
> +       struct mtk_mhccif_cb *cb;
> +       u32 stat, mask, chs;
> +
> +       stat = mtk_pci_get_ext_evt_hw_status(mdev);
> +       mask = mtk_pci_read32(mdev, MTK_MHCCIF_RC_BASE_ADDR
> +               + MHCCIF_EP2RC_SW_INT_EAP_MASK);
> +       if (unlikely(stat == U32_MAX && !(mtk_pci_link_check(mdev)))) {
> +               /* When link failed, we don't need to unmask/clear. */
> +               dev_err(mdev->dev, "Failed to check link in MHCCIF handler.\n");
> +               return;
> +       }
> +
> +       stat &= ~mask;
> +       /* Acknowledge level 2 before dispatch: an event that re-asserts
> +        * while a callback runs must survive as a new status bit.
> +        */
> +       if (stat)
> +               mtk_pci_ack_ext_evt_hw(mdev, stat);
> +
> +       chs = mtk_pci_ext_d2h_evt_chs(stat);
> +       /* Callbacks must not sleep or modify mhccif_cb_list */
> +       spin_lock_bh(&priv->mhccif_lock);
> +       list_for_each_entry(cb, &priv->mhccif_cb_list, entry) {
> +               if (cb->chs & chs)
> +                       cb->evt_cb(cb->chs & chs, cb->data);
> +       }
> +       spin_unlock_bh(&priv->mhccif_lock);
> +
> +       mtk_pci_clear_irq(mdev, priv->mhccif_irq_id);
> +       mtk_pci_unmask_irq(mdev, priv->mhccif_irq_id);
> +}
> +
> +static const struct pci_device_id t9xx_pci_table[] = {
> +       { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0900),
> +         MTK_PCI_CLASS, PCI_ANY_ID },
> +       { PCI_DEVICE(0x03f0, 0x01ca),  /* HP RMM-G4 */
> +         MTK_PCI_CLASS, PCI_ANY_ID },
> +       {/* end: all zeroes */}
> +};
> +
> +MODULE_DEVICE_TABLE(pci, t9xx_pci_table);
> +
> +static int mtk_pci_atr_init(struct mtk_md_dev *mdev)
> +{
> +       struct pci_dev *pdev = to_pci_dev(mdev->dev);
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +       struct mtk_atr_cfg cfg;
> +       int port, ret;
> +
> +       mtk_pci_atr_disable(priv);
> +
> +       /* Config ATR for RC to access device's register */
> +       cfg.src_addr = pci_bus_address(pdev, MTK_BAR_2_3_IDX);
> +       cfg.size = ATR_PCIE_REG_SIZE;
> +       cfg.trsl_addr = ATR_PCIE_REG_TRSL_ADDR;
> +       cfg.port = ATR_PCIE_REG_PORT;
> +       cfg.table = ATR_PCIE_REG_TABLE_NUM;
> +       cfg.trsl_id = ATR_PCIE_REG_TRSL_PORT;
> +       cfg.trsl_param = 0x0;
> +       cfg.transparent = 0x0;
> +       ret = mtk_pci_setup_atr(mdev, &cfg);
> +       if (ret)
> +               return ret;
> +
> +       /* Config ATR for EP to access RC's memory */
> +       for (port = ATR_SRC_AXIS_0; port <= ATR_SRC_AXIS_3; port++) {
> +               cfg.src_addr = ATR_PCIE_DEV_DMA_SRC_ADDR;
> +               cfg.size = ATR_PCIE_DEV_DMA_SIZE;
> +               cfg.trsl_addr = ATR_PCIE_DEV_DMA_TRSL_ADDR;
> +               cfg.port = port;
> +               cfg.table = ATR_PCIE_DEV_DMA_TABLE_NUM;
> +               cfg.trsl_id = ATR_DST_PCI_TRX;
> +               cfg.trsl_param = 0x0;
> +               /* Enable transparent translation */
> +               cfg.transparent = ATR_PCIE_DEV_DMA_TRANSPARENT;
> +               ret = mtk_pci_setup_atr(mdev, &cfg);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static int mtk_pci_bar_init(struct mtk_md_dev *mdev)
> +{
> +       struct pci_dev *pdev = to_pci_dev(mdev->dev);
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +
> +       /* Fixed offsets used on these mappings (MSI-X registers up to
> +        * 0x3103 on BAR0/1, the ATR-biased MHCCIF window on BAR2/3) are
> +        * guaranteed by the only two devices this driver binds (0x0900,
> +        * 0x01CA), whose BAR sizes are fixed by hardware; an undersized
> +        * BAR here is impossible, so no pci_resource_len() checks.
> +        */
> +       priv->mac_reg_base = pcim_iomap_region(pdev, MTK_BAR_0_1_IDX,
> +                                              mdev->dev_str);
> +       if (IS_ERR(priv->mac_reg_base)) {
> +               dev_err(mdev->dev, "Failed to map BAR0/1\n");
> +               return PTR_ERR(priv->mac_reg_base);
> +       }
> +
> +       priv->bar23_addr = pcim_iomap_region(pdev, MTK_BAR_2_3_IDX,
> +                                            mdev->dev_str);
> +       if (IS_ERR(priv->bar23_addr)) {
> +               dev_err(mdev->dev, "Failed to map BAR2/3\n");
> +               return PTR_ERR(priv->bar23_addr);
> +       }
> +
> +       /* We use MD view base address "0" to observe registers */
> +       priv->ext_reg_base = priv->bar23_addr - ATR_PCIE_REG_TRSL_ADDR;
> +
> +       return 0;
> +}
> +
> +static int mtk_mhccif_irq_cb(int irq_id, void *data)
> +{
> +       struct mtk_md_dev *mdev = data;
> +       struct mtk_pci_priv *priv;
> +
> +       priv = mdev->hw_priv;
> +       queue_work(system_highpri_wq, &priv->mhccif_work);
> +
> +       return 0;
> +}
> +
> +static int mtk_mhccif_init(struct mtk_md_dev *mdev)
> +{
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +       int ret;
> +
> +       INIT_LIST_HEAD(&priv->mhccif_cb_list);
> +       spin_lock_init(&priv->mhccif_lock);
> +       INIT_WORK(&priv->mhccif_work, mtk_mhccif_isr_work);
> +
> +       /* The write below goes through the BAR 2/3 ATR window, so this
> +        * function must run after mtk_pci_atr_init().
> +        * Mask every channel; consumers unmask the channels they need.
> +        * Do NOT ack here: EP2RC status bits are latched one-shot
> +        * notifications (e.g. BOOT_FLOW_SYNC raised before the driver
> +        * loads) that the device never re-sends.  Masking blocks
> +        * delivery but preserves the bit for delivery on unmask; an
> +        * ack would erase it and the boot flow would hang silently.
> +        */
> +       mtk_pci_write32(mdev, MTK_MHCCIF_RC_BASE_ADDR +
> +                       MHCCIF_EP2RC_SW_INT_EAP_MASK_SET, U32_MAX);
> +
> +       ret = mtk_pci_get_irq_id(mdev, MTK_IRQ_SRC_MHCCIF);
> +       if (ret < 0) {
> +               dev_err(mdev->dev, "Failed to get mhccif_irq_id. ret=%d\n", ret);
> +               return ret;
> +       }
> +       priv->mhccif_irq_id = ret;
> +
> +       ret = mtk_pci_register_irq(mdev, priv->mhccif_irq_id, mtk_mhccif_irq_cb, mdev);
> +       if (ret) {
> +               dev_err(mdev->dev, "Failed to register mhccif_irq callback\n");
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static void mtk_mhccif_exit(struct mtk_md_dev *mdev)
> +{
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +       struct mtk_mhccif_cb *cb, *tmp;
> +
> +       mtk_pci_unregister_irq(mdev, priv->mhccif_irq_id);
> +       cancel_work_sync(&priv->mhccif_work);
> +       /* The work unmasks the source on its way out, so leave it masked. */
> +       mtk_pci_mask_irq(mdev, priv->mhccif_irq_id);
> +
> +       list_for_each_entry_safe(cb, tmp, &priv->mhccif_cb_list, entry) {
> +               list_del(&cb->entry);
> +               kfree(cb);
> +       }
> +}
> +
> +static irqreturn_t mtk_pci_irq_handler(struct mtk_md_dev *mdev, u32 irq_state)
> +{
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +       int irq_id;
> +
> +       /* Check whether each set bit has a callback, if has, call it */
> +       do {
> +               int (*cb)(int irq_id, void *data);
> +
> +               irq_id = fls(irq_state) - 1;
> +               irq_state &= ~BIT(irq_id);
> +               cb = READ_ONCE(priv->irq_cb_list[irq_id]);
> +               if (likely(cb)) {
> +                       smp_rmb(); /* Ensure data is read after callback */
> +                       cb(irq_id, priv->irq_cb_data[irq_id]);
> +               } else {
> +                       dev_err_ratelimited(mdev->dev,
> +                                           "Unhandled irq_id=%d, no callback for it.\n",
> +                                           irq_id);
> +                       mtk_pci_clear_irq(mdev, irq_id);
> +               }
> +       } while (irq_state);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t mtk_pci_irq_msix(int irq, void *data)
> +{
> +       struct mtk_pci_irq_desc *irq_desc = data;
> +       struct mtk_md_dev *mdev = irq_desc->mdev;
> +       struct mtk_pci_priv *priv;
> +       u32 irq_state, irq_enable;
> +
> +       priv = mdev->hw_priv;
> +       irq_state = mtk_pci_mac_read32(priv, REG_MSIX_ISTATUS_HOST_GRP0_0);
> +       irq_enable = mtk_pci_mac_read32(priv, REG_IMASK_HOST_MSIX_GRP0_0);
> +       irq_state &= irq_enable;
> +
> +       if (unlikely(irq_state == U32_MAX && irq_enable == U32_MAX))
> +               return IRQ_NONE; /* device gone */
> +
> +       irq_state &= irq_desc->msix_bits; /* scope to this vector */
> +       if (unlikely(!irq_state))
> +               return IRQ_NONE;
> +
> +       /* Mask the bit; the consumer unmasks it when it is done */
> +       mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, irq_state);
> +
> +       return mtk_pci_irq_handler(mdev, irq_state);
> +}
> +
> +static int mtk_pci_request_irq_msix(struct mtk_md_dev *mdev)
> +{
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +       struct mtk_pci_irq_desc *irq_desc;
> +       struct pci_dev *pdev;
> +       int ret, i;
> +
> +       pdev = to_pci_dev(mdev->dev);
> +       irq_desc = priv->irq_desc;
> +
> +       priv->irq_cnt = MTK_IRQ_CNT_MAX;
> +
> +       for (i = 0; i < MTK_IRQ_CNT_MAX; i++) {
> +               irq_desc[i].mdev = mdev;
> +               irq_desc[i].msix_bits = BIT(i);
> +               snprintf(irq_desc[i].name, MTK_IRQ_NAME_LEN, "msix%d-%s", i, mdev->dev_str);
> +               ret = pci_request_irq(pdev, i, mtk_pci_irq_msix, NULL,
> +                                     &irq_desc[i], "%s", irq_desc[i].name);
> +               if (ret) {
> +                       dev_err(mdev->dev, "Failed to request %s: ret=%d\n",
> +                               irq_desc[i].name, ret);
> +                       for (i--; i >= 0; i--)
> +                               pci_free_irq(pdev, i, &irq_desc[i]);
> +                       priv->irq_cnt = 0;
> +                       return ret;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static int mtk_pci_request_irq(struct mtk_md_dev *mdev)
> +{
> +       struct pci_dev *pdev = to_pci_dev(mdev->dev);
> +       int ret;
> +
> +       ret = pci_alloc_irq_vectors(pdev, MTK_IRQ_CNT_MAX,
> +                                   MTK_IRQ_CNT_MAX, PCI_IRQ_MSIX);
> +       if (ret < 0) {
> +               dev_err(mdev->dev,
> +                       "Unable to alloc %d MSI-X vectors: ret=%d\n",
> +                       MTK_IRQ_CNT_MAX, ret);
> +               return ret;
> +       }
> +
> +       ret = mtk_pci_request_irq_msix(mdev);
> +       if (ret)
> +               pci_free_irq_vectors(pdev);
> +
> +       return ret;
> +}
> +
> +static void mtk_pci_free_irq(struct mtk_md_dev *mdev)
> +{
> +       struct pci_dev *pdev = to_pci_dev(mdev->dev);
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +       int i;
> +
> +       for (i = 0; i < priv->irq_cnt; i++)
> +               pci_free_irq(pdev, i, &priv->irq_desc[i]);
> +
> +       pci_free_irq_vectors(pdev);
> +       priv->irq_cnt = 0;
> +}
> +
> +static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct mtk_pci_priv *priv;
> +       struct mtk_md_dev *mdev;
> +       int ret;
> +
> +       mdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL);
> +       if (!mdev) {
> +               ret = -ENOMEM;
> +               goto log_err;
> +       }
> +       mdev->dev = dev;
> +
> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +       if (!priv) {
> +               ret = -ENOMEM;
> +               goto log_err;
> +       }
> +
> +       pci_set_drvdata(pdev, mdev);
> +       priv->mdev = mdev;
> +       mdev->hw_ver  = pdev->device;
> +       mdev->hw_priv = priv;
> +       mdev->dev     = dev;
> +       snprintf(mdev->dev_str, MTK_DEV_STR_LEN, "%02x%02x%d",
> +                pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));
> +       if (pdev->state_saved)
> +               pci_restore_state(pdev);
> +
> +       ret = pci_enable_device(pdev);
> +       if (ret) {
> +               dev_err(mdev->dev, "Failed to enable pci device.\n");
> +               goto log_err;
> +       }
> +
> +       ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
> +       if (ret) {
> +               dev_err(mdev->dev, "Failed to set DMA Mask and Coherent. (ret=%d)\n", ret);
> +               goto disable_dev;
> +       }
> +
> +       ret = mtk_pci_bar_init(mdev);
> +       if (ret)
> +               goto disable_dev;
> +
> +       ret = mtk_pci_atr_init(mdev);
> +       if (ret)
> +               goto disable_dev;
> +
> +       spin_lock_init(&priv->irq_cb_lock);
> +
> +       ret = mtk_mhccif_init(mdev);
> +       if (ret)
> +               goto disable_dev;
> +
> +       /* Mask every source before the handlers go in, so a source
> +        * left enabled by firmware or a previous bind cannot fire
> +        * into a half-initialised driver.
> +        */
> +       mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, U32_MAX);
> +
> +       ret = mtk_pci_request_irq(mdev);
> +       if (ret)
> +               goto free_mhccif;
> +
> +       pci_set_master(pdev);
> +       mtk_pci_unmask_irq(mdev, priv->mhccif_irq_id);
> +
> +       if (!mtk_pci_link_check(mdev)) {
> +               ret = -ENOLINK;
> +               goto clear_master;
> +       }
> +
> +       ret = pci_save_state(pdev);
> +       if (ret) {
> +               dev_err(mdev->dev, "Failed to save PCI state: %d\n", ret);
> +               goto clear_master;
> +       }
> +
> +       priv->saved_state = pci_store_saved_state(pdev);
> +       if (!priv->saved_state) {
> +               ret = -ENOMEM;
> +               goto clear_master;
> +       }
> +
> +       return 0;
> +
> +clear_master:
> +       pci_clear_master(pdev);
> +       mtk_pci_free_irq(mdev);
> +free_mhccif:
> +       mtk_mhccif_exit(mdev);
> +disable_dev:
> +       pci_disable_device(pdev);
> +log_err:
> +       dev_err(dev, "Failed to probe device, ret=%d\n", ret);
> +
> +       return ret;
> +}
> +
> +static void mtk_pci_remove(struct pci_dev *pdev)
> +{
> +       struct mtk_md_dev *mdev = pci_get_drvdata(pdev);
> +       struct mtk_pci_priv *priv = mdev->hw_priv;
> +       struct device *dev = &pdev->dev;
> +       int ret;
> +
> +       /* Silence every source before tearing anything down. */
> +       mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, U32_MAX);
> +
> +       /* Free the vectors first: once no handler can run, the unmask at the
> +        * tail of mtk_mhccif_isr_work() cannot re-open the source behind
> +        * mtk_mhccif_exit()'s back.
> +        */
> +       mtk_pci_free_irq(mdev);
> +       mtk_mhccif_exit(mdev);
> +       pci_clear_master(pdev);
> +       pci_disable_device(pdev);
> +
> +       /* Reset last: the endpoint comes back at power-on defaults, so no MMIO
> +        * and no MSI-X teardown may follow it.
> +        */
> +       ret = mtk_pci_pldr(mdev);
> +       if (ret && mtk_pci_link_check(mdev)) {
> +               dev_warn(dev, "PLDR failed (%d), trying MHCCIF reset\n", ret);
> +               if (mtk_pci_send_ext_evt(mdev, DEV_EVT_H2D_DEVICE_RESET))
> +                       dev_err(dev, "MHCCIF reset failed\n");
> +               /* Unlike MRST._RST the doorbell returns before the device
> +                * has reset, so let it settle before the restore below.
> +                */
> +               msleep(MTK_MHCCIF_RESET_DELAY_MS);
> +       }
> +
> +       /* Without this a runtime-PM suspend after unbind would snapshot the
> +        * reset state over our copy, and whoever binds next finds BARs the
> +        * device does not decode.
> +        */
> +       pci_load_and_free_saved_state(pdev, &priv->saved_state);
> +       pci_restore_state(pdev);
> +       /* The state was saved after pci_set_master(), so the restore just
> +        * re-enabled bus mastering on an unbound device - drop it again.
> +        */
> +       pci_clear_master(pdev);
> +}
> +
> +static pci_ers_result_t mtk_pci_error_detected(struct pci_dev *pdev,
> +                                              pci_channel_state_t state)
> +{
> +       struct mtk_md_dev *mdev = pci_get_drvdata(pdev);
> +
> +       dev_err(mdev->dev, "AER detected: pci_channel_state_t=%u\n", state);
> +
> +       /* AER recovery not supported, disconnect the device */
> +       return PCI_ERS_RESULT_DISCONNECT;
> +}
> +
> +static const struct pci_error_handlers mtk_pci_err_handler = {
> +       .error_detected = mtk_pci_error_detected,
> +};
> +
> +static struct pci_driver mtk_pci_drv = {
> +       .name = "mtk_pci_drv",
> +       .id_table = t9xx_pci_table,
> +       .probe = mtk_pci_probe,
> +       .remove = mtk_pci_remove,
> +       .err_handler = &mtk_pci_err_handler
> +};
> +
> +module_pci_driver(mtk_pci_drv);
> +
> +MODULE_DESCRIPTION("MediaTek T9xx PCIe WWAN driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.h b/drivers/net/wwan/t9xx/pcie/mtk_pci.h
> new file mode 100644
> index 000000000000..80c1a40f98d5
> --- /dev/null
> +++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.h
> @@ -0,0 +1,140 @@
> +/* SPDX-License-Identifier: GPL-2.0-only
> + *
> + * Copyright (c) 2022, MediaTek Inc.
> + */
> +
> +#ifndef __MTK_PCI_H__
> +#define __MTK_PCI_H__
> +
> +#include <linux/pci.h>
> +
> +#include "../mtk_dev.h"
> +
> +enum mtk_irq_src {
> +       MTK_IRQ_SRC_MIN,
> +       MTK_IRQ_SRC_MHCCIF,
> +       MTK_IRQ_SRC_CLDMA0,
> +       MTK_IRQ_SRC_CLDMA1,
> +       MTK_IRQ_SRC_MAX
> +};
> +
> +enum mtk_atr_src_port {
> +       ATR_SRC_PCI_WIN0 = 0,
> +       ATR_SRC_PCI_WIN1,
> +       ATR_SRC_AXIS_0,
> +       ATR_SRC_AXIS_1,
> +       ATR_SRC_AXIS_2,
> +       ATR_SRC_AXIS_3,
> +};
> +
> +enum mtk_atr_dst_port {
> +       ATR_DST_PCI_TRX = 0,
> +       ATR_DST_AXIM_0 = 4,
> +};
> +
> +#define MTK_PCI_CLASS                 0x0D4000
> +
> +#define MTK_BAR_0_1_IDX                 0
> +#define MTK_BAR_2_3_IDX                 2
> +
> +#define MTK_IRQ_CNT_MAX                                32
> +#define MTK_IRQ_NAME_LEN                       32
> +
> +#define ATR_PORT_OFFSET                                0x100
> +#define ATR_TABLE_OFFSET                       0x20
> +#define ATR_TABLE_NUM_PER_ATR                  8
> +#define ATR_PCIE_REG_TRSL_ADDR                 0x10000000
> +#define ATR_PCIE_REG_SIZE                      0x00400000
> +#define ATR_PCIE_REG_PORT                      ATR_SRC_PCI_WIN0
> +#define ATR_PCIE_REG_TABLE_NUM                 1
> +#define ATR_PCIE_REG_TRSL_PORT                 ATR_DST_AXIM_0
> +#define ATR_PCIE_DEV_DMA_SRC_ADDR              0x00000000
> +#define ATR_PCIE_DEV_DMA_TRANSPARENT           1
> +#define ATR_PCIE_DEV_DMA_SIZE                  0
> +#define ATR_PCIE_DEV_DMA_TABLE_NUM             0
> +#define ATR_PCIE_DEV_DMA_TRSL_ADDR             0x00000000
> +
> +struct mtk_pci_irq_desc {
> +       struct mtk_md_dev *mdev;
> +       u32 msix_bits;
> +       char name[MTK_IRQ_NAME_LEN];
> +};
> +
> +struct mtk_pci_priv {
> +       struct mtk_md_dev *mdev;
> +       void __iomem *bar23_addr;
> +       void __iomem *mac_reg_base;
> +       void __iomem *ext_reg_base;
> +       int irq_cnt;
> +       /* irq_cb_lock: protects irq_cb_list[] and irq_cb_data[] */
> +       spinlock_t irq_cb_lock;
> +       void *irq_cb_data[MTK_IRQ_CNT_MAX];
> +
> +       int (*irq_cb_list[MTK_IRQ_CNT_MAX])(int irq_id, void *data);
> +       struct mtk_pci_irq_desc irq_desc[MTK_IRQ_CNT_MAX];
> +       struct list_head mhccif_cb_list;
> +       /* mhccif_lock: lock to protect mhccif_cb_list */
> +       spinlock_t mhccif_lock;
> +       struct work_struct mhccif_work;
> +       int mhccif_irq_id;
> +       struct pci_saved_state *saved_state;
> +};
> +
> +struct mtk_atr_cfg {
> +       u64 src_addr;
> +       u64 trsl_addr;
> +       u64 size;
> +       u32 port;      /* Port number */
> +       u32 table;     /* Table number (8 tables for each port) */
> +       u32 trsl_id;
> +       u32 trsl_param;
> +       u32 transparent;
> +};
> +
> +/* BAR 0/1 MMIO access */
> +static inline u32 mtk_pci_mac_read32(struct mtk_pci_priv *priv, u64 addr)
> +{
> +       return ioread32(priv->mac_reg_base + addr);
> +}
> +
> +static inline void mtk_pci_mac_write32(struct mtk_pci_priv *priv, u64 addr, u32 val)
> +{
> +       iowrite32(val, priv->mac_reg_base + addr);
> +}
> +
> +/* BAR 2/3 MMIO access */
> +static inline u32 mtk_pci_read32(struct mtk_md_dev *mdev, u64 addr)
> +{
> +       return ioread32(((struct mtk_pci_priv *)mdev->hw_priv)->ext_reg_base + addr);
> +}
> +
> +static inline void mtk_pci_write32(struct mtk_md_dev *mdev, u64 addr, u32 val)
> +{
> +       iowrite32(val, ((struct mtk_pci_priv *)mdev->hw_priv)->ext_reg_base + addr);
> +}
> +
> +/* Device operations */
> +u32 mtk_pci_get_dev_state(struct mtk_md_dev *mdev);
> +void mtk_pci_ack_dev_state(struct mtk_md_dev *mdev, u32 state);
> +/* IRQ Related operations */
> +int mtk_pci_get_irq_id(struct mtk_md_dev *mdev, enum mtk_irq_src irq_src);
> +int mtk_pci_get_virq_id(struct mtk_md_dev *mdev, int irq_id);
> +int mtk_pci_register_irq(struct mtk_md_dev *mdev, int irq_id,
> +                        int (*irq_cb)(int irq_id, void *data), void *data);
> +int mtk_pci_unregister_irq(struct mtk_md_dev *mdev, int irq_id);
> +int mtk_pci_mask_irq(struct mtk_md_dev *mdev, int irq_id);
> +int mtk_pci_unmask_irq(struct mtk_md_dev *mdev, int irq_id);
> +int mtk_pci_clear_irq(struct mtk_md_dev *mdev, int irq_id);
> +/* External event related */
> +int mtk_pci_register_ext_evt(struct mtk_md_dev *mdev, u32 chs,
> +                            int (*evt_cb)(u32 status, void *data), void *data);
> +void mtk_pci_unregister_ext_evt(struct mtk_md_dev *mdev, u32 chs);
> +void mtk_pci_mask_ext_evt(struct mtk_md_dev *mdev, u32 chs);
> +void mtk_pci_unmask_ext_evt(struct mtk_md_dev *mdev, u32 chs);
> +int mtk_pci_send_ext_evt(struct mtk_md_dev *mdev, u32 ch);
> +int mtk_pci_pldr(struct mtk_md_dev *mdev);
> +bool mtk_pci_link_check(struct mtk_md_dev *mdev);
> +int mtk_pci_setup_atr(struct mtk_md_dev *mdev, struct mtk_atr_cfg *cfg);
> +void mtk_pci_atr_disable(struct mtk_pci_priv *priv);
> +
> +#endif /* __MTK_PCI_H__ */
> diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h b/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h
> new file mode 100644
> index 000000000000..a97ad6630dcf
> --- /dev/null
> +++ b/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h
> @@ -0,0 +1,34 @@
> +/* SPDX-License-Identifier: GPL-2.0-only
> + *
> + * Copyright (c) 2022, MediaTek Inc.
> + */
> +
> +#ifndef __MTK_PCI_REG_H__
> +#define __MTK_PCI_REG_H__
> +
> +#define REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB      0x0600
> +#define REG_ATR_PCIE_WIN0_T0_SRC_ADDR_MSB      0x0604
> +#define REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_LSB     0x0608
> +#define REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_MSB     0x060C
> +#define REG_ATR_PCIE_WIN0_T0_TRSL_PARAM                0x0610
> +#define REG_PCIE_DEBUG_DUMMY_7                 0x0D1C
> +#define REG_MSIX_ISTATUS_HOST_GRP0_0           0x0F00
> +#define REG_IMASK_HOST_MSIX_SET_GRP0_0         0x3000
> +#define REG_IMASK_HOST_MSIX_CLR_GRP0_0         0x3080
> +#define REG_IMASK_HOST_MSIX_GRP0_0             0x3100
> +
> +/* mhccif registers */
> +#define MHCCIF_RC2EP_SW_BSY                    0x4
> +#define MHCCIF_RC2EP_SW_TCHNUM                 0xC
> +#define MHCCIF_RC2EP_EVT_DEVICE_RESET          BIT(13)
> +
> +#define MHCCIF_EP2RC_SW_INT_STS                        0x10
> +#define MHCCIF_EP2RC_SW_INT_ACK                        0x14
> +#define MHCCIF_EP2RC_SW_INT_EAP_MASK           0x20
> +#define MHCCIF_EP2RC_SW_INT_EAP_MASK_SET       0x30
> +#define MHCCIF_EP2RC_SW_INT_EAP_MASK_CLR       0x40
> +#define MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC                BIT(5)
> +#define MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP   BIT(15)
> +#define MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD    BIT(16)
> +
> +#endif /* __MTK_PCI_REG_H__ */
>
> --
> 2.34.1
>
>



More information about the Linux-mediatek mailing list