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@xxxxxxxxxxxx> Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@xxxxxxxxx> Signed-off-by: Ricardo Martinez <ricardo.martinez@xxxxxxxxxxxxxxx> --- drivers/net/wwan/Kconfig | 14 + drivers/net/wwan/Makefile | 1 + drivers/net/wwan/t7xx/Makefile | 13 + drivers/net/wwan/t7xx/t7xx_common.h | 69 +++ drivers/net/wwan/t7xx/t7xx_mhccif.c | 99 ++++ drivers/net/wwan/t7xx/t7xx_mhccif.h | 29 + drivers/net/wwan/t7xx/t7xx_modem_ops.c | 480 +++++++++++++++++ drivers/net/wwan/t7xx/t7xx_modem_ops.h | 82 +++ drivers/net/wwan/t7xx/t7xx_monitor.h | 137 +++++ drivers/net/wwan/t7xx/t7xx_pci.c | 228 ++++++++ drivers/net/wwan/t7xx/t7xx_pci.h | 59 ++ drivers/net/wwan/t7xx/t7xx_pcie_mac.c | 270 ++++++++++ drivers/net/wwan/t7xx/t7xx_pcie_mac.h | 29 + drivers/net/wwan/t7xx/t7xx_reg.h | 389 ++++++++++++++ drivers/net/wwan/t7xx/t7xx_skb_util.c | 354 ++++++++++++ drivers/net/wwan/t7xx/t7xx_skb_util.h | 102 ++++ drivers/net/wwan/t7xx/t7xx_state_monitor.c | 591 +++++++++++++++++++++ 17 files changed, 2946 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 diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig index 77dbfc418bce..244cb522e2c5 100644 --- a/drivers/net/wwan/Kconfig +++ b/drivers/net/wwan/Kconfig @@ -79,6 +79,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..2b0340236c2e --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_common.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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..c68d98a3d765 --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_mhccif.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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..ad25945a74e5 --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_mhccif.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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..b47ce6e55758 --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_modem_ops.c @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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; + + md = mtk_dev->md; + + 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..1d2fee18b559 --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_modem_ops.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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..42dec0e19035 --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_monitor.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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..c16b3a2557f1 --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_pci.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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", pdev->driver->name, 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..fc27c8c9bf12 --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_pci.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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..9e512010245d --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_pcie_mac.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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..bba0998179ae --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_pcie_mac.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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..8e4b5507a3c8 --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_reg.h @@ -0,0 +1,389 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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..0194a7c65934 --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_skb_util.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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..0d6860a4eac9 --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_skb_util.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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 + * @FREE: free the skb + * @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..e4d7fd9fa8b1 --- /dev/null +++ b/drivers/net/wwan/t7xx/t7xx_state_monitor.c @@ -0,0 +1,591 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, MediaTek Inc. + * Copyright (c) 2021, Intel Corporation. + */ + +#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(¬ifier->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(¬ifier->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); +} -- 2.17.1