net: wwan: t7xx: Add core components
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 Liu <haijun.liu@mediatek.com> Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com> Co-developed-by: Ricardo Martinez <ricardo.martinez@linux.intel.com> Signed-off-by: Ricardo Martinez <ricardo.martinez@linux.intel.com> Reviewed-by: Loic Poulain <loic.poulain@linaro.org> Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> Reviewed-by: Sergey Ryazanov <ryazanov.s.a@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
39d439047f
commit
13e920d93e
@ -105,6 +105,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 (T7xx 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
|
||||
|
@ -13,3 +13,4 @@ obj-$(CONFIG_MHI_WWAN_MBIM) += mhi_wwan_mbim.o
|
||||
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/
|
||||
|
12
drivers/net/wwan/t7xx/Makefile
Normal file
12
drivers/net/wwan/t7xx/Makefile
Normal file
@ -0,0 +1,12 @@
|
||||
# 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_cldma.o \
|
||||
t7xx_hif_cldma.o \
|
102
drivers/net/wwan/t7xx/t7xx_mhccif.c
Normal file
102
drivers/net/wwan/t7xx/t7xx_mhccif.c
Normal file
@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2021, MediaTek Inc.
|
||||
* Copyright (c) 2021-2022, Intel Corporation.
|
||||
*
|
||||
* Authors:
|
||||
* Haijun Liu <haijun.liu@mediatek.com>
|
||||
* Sreehari Kancharla <sreehari.kancharla@intel.com>
|
||||
*
|
||||
* Contributors:
|
||||
* Amir Hanania <amir.hanania@intel.com>
|
||||
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irqreturn.h>
|
||||
|
||||
#include "t7xx_mhccif.h"
|
||||
#include "t7xx_modem_ops.h"
|
||||
#include "t7xx_pci.h"
|
||||
#include "t7xx_pcie_mac.h"
|
||||
#include "t7xx_reg.h"
|
||||
|
||||
static void t7xx_mhccif_clear_interrupts(struct t7xx_pci_dev *t7xx_dev, u32 mask)
|
||||
{
|
||||
void __iomem *mhccif_pbase = t7xx_dev->base_addr.mhccif_rc_base;
|
||||
|
||||
/* Clear level 2 interrupt */
|
||||
iowrite32(mask, mhccif_pbase + REG_EP2RC_SW_INT_ACK);
|
||||
/* Ensure write is complete */
|
||||
t7xx_mhccif_read_sw_int_sts(t7xx_dev);
|
||||
/* Clear level 1 interrupt */
|
||||
t7xx_pcie_mac_clear_int_status(t7xx_dev, MHCCIF_INT);
|
||||
}
|
||||
|
||||
static irqreturn_t t7xx_mhccif_isr_thread(int irq, void *data)
|
||||
{
|
||||
struct t7xx_pci_dev *t7xx_dev = data;
|
||||
u32 int_status, val;
|
||||
|
||||
val = T7XX_L1_1_BIT(1) | T7XX_L1_2_BIT(1);
|
||||
iowrite32(val, IREG_BASE(t7xx_dev) + DISABLE_ASPM_LOWPWR);
|
||||
|
||||
int_status = t7xx_mhccif_read_sw_int_sts(t7xx_dev);
|
||||
if (int_status & D2H_SW_INT_MASK) {
|
||||
int ret = t7xx_pci_mhccif_isr(t7xx_dev);
|
||||
|
||||
if (ret)
|
||||
dev_err(&t7xx_dev->pdev->dev, "PCI MHCCIF ISR failure: %d", ret);
|
||||
}
|
||||
|
||||
t7xx_mhccif_clear_interrupts(t7xx_dev, int_status);
|
||||
t7xx_pcie_mac_set_int(t7xx_dev, MHCCIF_INT);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
u32 t7xx_mhccif_read_sw_int_sts(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
return ioread32(t7xx_dev->base_addr.mhccif_rc_base + REG_EP2RC_SW_INT_STS);
|
||||
}
|
||||
|
||||
void t7xx_mhccif_mask_set(struct t7xx_pci_dev *t7xx_dev, u32 val)
|
||||
{
|
||||
iowrite32(val, t7xx_dev->base_addr.mhccif_rc_base + REG_EP2RC_SW_INT_EAP_MASK_SET);
|
||||
}
|
||||
|
||||
void t7xx_mhccif_mask_clr(struct t7xx_pci_dev *t7xx_dev, u32 val)
|
||||
{
|
||||
iowrite32(val, t7xx_dev->base_addr.mhccif_rc_base + REG_EP2RC_SW_INT_EAP_MASK_CLR);
|
||||
}
|
||||
|
||||
u32 t7xx_mhccif_mask_get(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
return ioread32(t7xx_dev->base_addr.mhccif_rc_base + REG_EP2RC_SW_INT_EAP_MASK);
|
||||
}
|
||||
|
||||
static irqreturn_t t7xx_mhccif_isr_handler(int irq, void *data)
|
||||
{
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
void t7xx_mhccif_init(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
t7xx_dev->base_addr.mhccif_rc_base = t7xx_dev->base_addr.pcie_ext_reg_base +
|
||||
MHCCIF_RC_DEV_BASE -
|
||||
t7xx_dev->base_addr.pcie_dev_reg_trsl_addr;
|
||||
|
||||
t7xx_dev->intr_handler[MHCCIF_INT] = t7xx_mhccif_isr_handler;
|
||||
t7xx_dev->intr_thread[MHCCIF_INT] = t7xx_mhccif_isr_thread;
|
||||
t7xx_dev->callback_param[MHCCIF_INT] = t7xx_dev;
|
||||
}
|
||||
|
||||
void t7xx_mhccif_h2d_swint_trigger(struct t7xx_pci_dev *t7xx_dev, u32 channel)
|
||||
{
|
||||
void __iomem *mhccif_pbase = t7xx_dev->base_addr.mhccif_rc_base;
|
||||
|
||||
iowrite32(BIT(channel), mhccif_pbase + REG_RC2EP_SW_BSY);
|
||||
iowrite32(channel, mhccif_pbase + REG_RC2EP_SW_TCHNUM);
|
||||
}
|
37
drivers/net/wwan/t7xx/t7xx_mhccif.h
Normal file
37
drivers/net/wwan/t7xx/t7xx_mhccif.h
Normal file
@ -0,0 +1,37 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* Copyright (c) 2021, MediaTek Inc.
|
||||
* Copyright (c) 2021-2022, Intel Corporation.
|
||||
*
|
||||
* Authors:
|
||||
* Haijun Liu <haijun.liu@mediatek.com>
|
||||
* Sreehari Kancharla <sreehari.kancharla@intel.com>
|
||||
*
|
||||
* Contributors:
|
||||
* Amir Hanania <amir.hanania@intel.com>
|
||||
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
||||
*/
|
||||
|
||||
#ifndef __T7XX_MHCCIF_H__
|
||||
#define __T7XX_MHCCIF_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "t7xx_pci.h"
|
||||
#include "t7xx_reg.h"
|
||||
|
||||
#define D2H_SW_INT_MASK (D2H_INT_EXCEPTION_INIT | \
|
||||
D2H_INT_EXCEPTION_INIT_DONE | \
|
||||
D2H_INT_EXCEPTION_CLEARQ_DONE | \
|
||||
D2H_INT_EXCEPTION_ALLQ_RESET | \
|
||||
D2H_INT_PORT_ENUM | \
|
||||
D2H_INT_ASYNC_MD_HK)
|
||||
|
||||
void t7xx_mhccif_mask_set(struct t7xx_pci_dev *t7xx_dev, u32 val);
|
||||
void t7xx_mhccif_mask_clr(struct t7xx_pci_dev *t7xx_dev, u32 val);
|
||||
u32 t7xx_mhccif_mask_get(struct t7xx_pci_dev *t7xx_dev);
|
||||
void t7xx_mhccif_init(struct t7xx_pci_dev *t7xx_dev);
|
||||
u32 t7xx_mhccif_read_sw_int_sts(struct t7xx_pci_dev *t7xx_dev);
|
||||
void t7xx_mhccif_h2d_swint_trigger(struct t7xx_pci_dev *t7xx_dev, u32 channel);
|
||||
|
||||
#endif /*__T7XX_MHCCIF_H__ */
|
498
drivers/net/wwan/t7xx/t7xx_modem_ops.c
Normal file
498
drivers/net/wwan/t7xx/t7xx_modem_ops.c
Normal file
@ -0,0 +1,498 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2021, MediaTek Inc.
|
||||
* Copyright (c) 2021-2022, Intel Corporation.
|
||||
*
|
||||
* Authors:
|
||||
* Haijun Liu <haijun.liu@mediatek.com>
|
||||
* Eliot Lee <eliot.lee@intel.com>
|
||||
* Moises Veleta <moises.veleta@intel.com>
|
||||
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
||||
*
|
||||
* Contributors:
|
||||
* Amir Hanania <amir.hanania@intel.com>
|
||||
* Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
|
||||
* Sreehari Kancharla <sreehari.kancharla@intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irqreturn.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "t7xx_cldma.h"
|
||||
#include "t7xx_hif_cldma.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_state_monitor.h"
|
||||
|
||||
#define RGU_RESET_DELAY_MS 10
|
||||
#define PORT_RESET_DELAY_MS 2000
|
||||
#define EX_HS_TIMEOUT_MS 5000
|
||||
#define EX_HS_POLL_DELAY_MS 10
|
||||
|
||||
static unsigned int t7xx_get_interrupt_status(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
return t7xx_mhccif_read_sw_int_sts(t7xx_dev) & D2H_SW_INT_MASK;
|
||||
}
|
||||
|
||||
/**
|
||||
* t7xx_pci_mhccif_isr() - Process MHCCIF interrupts.
|
||||
* @t7xx_dev: MTK device.
|
||||
*
|
||||
* Check the interrupt status and queue commands accordingly.
|
||||
*
|
||||
* Returns:
|
||||
** 0 - Success.
|
||||
** -EINVAL - Failure to get FSM control.
|
||||
*/
|
||||
int t7xx_pci_mhccif_isr(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
struct t7xx_modem *md = t7xx_dev->md;
|
||||
struct t7xx_fsm_ctl *ctl;
|
||||
unsigned int int_sta;
|
||||
int ret = 0;
|
||||
u32 mask;
|
||||
|
||||
ctl = md->fsm_ctl;
|
||||
if (!ctl) {
|
||||
dev_err_ratelimited(&t7xx_dev->pdev->dev,
|
||||
"MHCCIF interrupt received before initializing MD monitor\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
spin_lock_bh(&md->exp_lock);
|
||||
int_sta = t7xx_get_interrupt_status(t7xx_dev);
|
||||
md->exp_id |= int_sta;
|
||||
if (md->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->exp_id &= ~D2H_INT_EXCEPTION_INIT;
|
||||
ret = t7xx_fsm_recv_md_intr(ctl, MD_IRQ_CCIF_EX);
|
||||
}
|
||||
} else if (md->exp_id & D2H_INT_PORT_ENUM) {
|
||||
md->exp_id &= ~D2H_INT_PORT_ENUM;
|
||||
|
||||
if (ctl->curr_state == FSM_STATE_INIT || ctl->curr_state == FSM_STATE_PRE_START ||
|
||||
ctl->curr_state == FSM_STATE_STOPPED)
|
||||
ret = t7xx_fsm_recv_md_intr(ctl, MD_IRQ_PORT_ENUM);
|
||||
} else if (ctl->md_state == MD_STATE_WAITING_FOR_HS1) {
|
||||
mask = t7xx_mhccif_mask_get(t7xx_dev);
|
||||
if ((md->exp_id & D2H_INT_ASYNC_MD_HK) && !(mask & D2H_INT_ASYNC_MD_HK)) {
|
||||
md->exp_id &= ~D2H_INT_ASYNC_MD_HK;
|
||||
queue_work(md->handshake_wq, &md->handshake_work);
|
||||
}
|
||||
}
|
||||
spin_unlock_bh(&md->exp_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void t7xx_clr_device_irq_via_pcie(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
struct t7xx_addr_base *pbase_addr = &t7xx_dev->base_addr;
|
||||
void __iomem *reset_pcie_reg;
|
||||
u32 val;
|
||||
|
||||
reset_pcie_reg = pbase_addr->pcie_ext_reg_base + TOPRGU_CH_PCIE_IRQ_STA -
|
||||
pbase_addr->pcie_dev_reg_trsl_addr;
|
||||
val = ioread32(reset_pcie_reg);
|
||||
iowrite32(val, reset_pcie_reg);
|
||||
}
|
||||
|
||||
void t7xx_clear_rgu_irq(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
/* Clear L2 */
|
||||
t7xx_clr_device_irq_via_pcie(t7xx_dev);
|
||||
/* Clear L1 */
|
||||
t7xx_pcie_mac_clear_int_status(t7xx_dev, SAP_RGU_INT);
|
||||
}
|
||||
|
||||
static int t7xx_acpi_reset(struct t7xx_pci_dev *t7xx_dev, char *fn_name)
|
||||
{
|
||||
#ifdef CONFIG_ACPI
|
||||
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
struct device *dev = &t7xx_dev->pdev->dev;
|
||||
acpi_status acpi_ret;
|
||||
acpi_handle handle;
|
||||
|
||||
handle = ACPI_HANDLE(dev);
|
||||
if (!handle) {
|
||||
dev_err(dev, "ACPI handle not found\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (!acpi_has_method(handle, fn_name)) {
|
||||
dev_err(dev, "%s method not 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 t7xx_acpi_fldr_func(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
return t7xx_acpi_reset(t7xx_dev, "_RST");
|
||||
}
|
||||
|
||||
static void t7xx_reset_device_via_pmic(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = ioread32(IREG_BASE(t7xx_dev) + T7XX_PCIE_MISC_DEV_STATUS);
|
||||
if (val & MISC_RESET_TYPE_PLDR)
|
||||
t7xx_acpi_reset(t7xx_dev, "MRST._RST");
|
||||
else if (val & MISC_RESET_TYPE_FLDR)
|
||||
t7xx_acpi_fldr_func(t7xx_dev);
|
||||
}
|
||||
|
||||
static irqreturn_t t7xx_rgu_isr_thread(int irq, void *data)
|
||||
{
|
||||
struct t7xx_pci_dev *t7xx_dev = data;
|
||||
|
||||
msleep(RGU_RESET_DELAY_MS);
|
||||
t7xx_reset_device_via_pmic(t7xx_dev);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t t7xx_rgu_isr_handler(int irq, void *data)
|
||||
{
|
||||
struct t7xx_pci_dev *t7xx_dev = data;
|
||||
struct t7xx_modem *modem;
|
||||
|
||||
t7xx_clear_rgu_irq(t7xx_dev);
|
||||
if (!t7xx_dev->rgu_pci_irq_en)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
modem = t7xx_dev->md;
|
||||
modem->rgu_irq_asserted = true;
|
||||
t7xx_pcie_mac_clear_int(t7xx_dev, SAP_RGU_INT);
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
static void t7xx_pcie_register_rgu_isr(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
/* Registers RGU callback ISR with PCIe driver */
|
||||
t7xx_pcie_mac_clear_int(t7xx_dev, SAP_RGU_INT);
|
||||
t7xx_pcie_mac_clear_int_status(t7xx_dev, SAP_RGU_INT);
|
||||
|
||||
t7xx_dev->intr_handler[SAP_RGU_INT] = t7xx_rgu_isr_handler;
|
||||
t7xx_dev->intr_thread[SAP_RGU_INT] = t7xx_rgu_isr_thread;
|
||||
t7xx_dev->callback_param[SAP_RGU_INT] = t7xx_dev;
|
||||
t7xx_pcie_mac_set_int(t7xx_dev, SAP_RGU_INT);
|
||||
}
|
||||
|
||||
/**
|
||||
* t7xx_cldma_exception() - CLDMA exception handler.
|
||||
* @md_ctrl: modem control struct.
|
||||
* @stage: exception stage.
|
||||
*
|
||||
* Part of the modem exception recovery.
|
||||
* Stages are one after the other as describe below:
|
||||
* HIF_EX_INIT: Disable and clear TXQ.
|
||||
* HIF_EX_CLEARQ_DONE: Disable RX, flush TX/RX workqueues and clear RX.
|
||||
* HIF_EX_ALLQ_RESET: HW is back in safe mode for re-initialization and restart.
|
||||
*/
|
||||
|
||||
/* Modem Exception Handshake Flow
|
||||
*
|
||||
* Modem HW Exception interrupt received
|
||||
* (MD_IRQ_CCIF_EX)
|
||||
* |
|
||||
* +---------v--------+
|
||||
* | HIF_EX_INIT | : Disable and clear TXQ
|
||||
* +------------------+
|
||||
* |
|
||||
* +---------v--------+
|
||||
* | HIF_EX_INIT_DONE | : Wait for the init to be done
|
||||
* +------------------+
|
||||
* |
|
||||
* +---------v--------+
|
||||
* |HIF_EX_CLEARQ_DONE| : Disable and clear RXQ
|
||||
* +------------------+ : Flush TX/RX workqueues
|
||||
* |
|
||||
* +---------v--------+
|
||||
* |HIF_EX_ALLQ_RESET | : Restart HW and CLDMA
|
||||
* +------------------+
|
||||
*/
|
||||
static void t7xx_cldma_exception(struct cldma_ctrl *md_ctrl, enum hif_ex_stage stage)
|
||||
{
|
||||
switch (stage) {
|
||||
case HIF_EX_INIT:
|
||||
t7xx_cldma_stop_all_qs(md_ctrl, MTK_TX);
|
||||
t7xx_cldma_clear_all_qs(md_ctrl, MTK_TX);
|
||||
break;
|
||||
|
||||
case HIF_EX_CLEARQ_DONE:
|
||||
/* We do not want to get CLDMA IRQ when MD is
|
||||
* resetting CLDMA after it got clearq_ack.
|
||||
*/
|
||||
t7xx_cldma_stop_all_qs(md_ctrl, MTK_RX);
|
||||
t7xx_cldma_stop(md_ctrl);
|
||||
|
||||
if (md_ctrl->hif_id == CLDMA_ID_MD)
|
||||
t7xx_cldma_hw_reset(md_ctrl->t7xx_dev->base_addr.infracfg_ao_base);
|
||||
|
||||
t7xx_cldma_clear_all_qs(md_ctrl, MTK_RX);
|
||||
break;
|
||||
|
||||
case HIF_EX_ALLQ_RESET:
|
||||
t7xx_cldma_hw_init(&md_ctrl->hw_info);
|
||||
t7xx_cldma_start(md_ctrl);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void t7xx_md_exception(struct t7xx_modem *md, enum hif_ex_stage stage)
|
||||
{
|
||||
struct t7xx_pci_dev *t7xx_dev = md->t7xx_dev;
|
||||
|
||||
if (stage == HIF_EX_CLEARQ_DONE) {
|
||||
/* Give DHL time to flush data */
|
||||
msleep(PORT_RESET_DELAY_MS);
|
||||
}
|
||||
|
||||
t7xx_cldma_exception(md->md_ctrl[CLDMA_ID_MD], stage);
|
||||
|
||||
if (stage == HIF_EX_INIT)
|
||||
t7xx_mhccif_h2d_swint_trigger(t7xx_dev, H2D_CH_EXCEPTION_ACK);
|
||||
else if (stage == HIF_EX_CLEARQ_DONE)
|
||||
t7xx_mhccif_h2d_swint_trigger(t7xx_dev, H2D_CH_EXCEPTION_CLEARQ_ACK);
|
||||
}
|
||||
|
||||
static int t7xx_wait_hif_ex_hk_event(struct t7xx_modem *md, int event_id)
|
||||
{
|
||||
unsigned int waited_time_ms = 0;
|
||||
|
||||
do {
|
||||
if (md->exp_id & event_id)
|
||||
return 0;
|
||||
|
||||
waited_time_ms += EX_HS_POLL_DELAY_MS;
|
||||
msleep(EX_HS_POLL_DELAY_MS);
|
||||
} while (waited_time_ms < EX_HS_TIMEOUT_MS);
|
||||
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
static void t7xx_md_sys_sw_init(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
/* Register the MHCCIF ISR for MD exception, port enum and
|
||||
* async handshake notifications.
|
||||
*/
|
||||
t7xx_mhccif_mask_set(t7xx_dev, D2H_SW_INT_MASK);
|
||||
t7xx_mhccif_mask_clr(t7xx_dev, D2H_INT_PORT_ENUM);
|
||||
|
||||
/* Register RGU IRQ handler for sAP exception notification */
|
||||
t7xx_dev->rgu_pci_irq_en = true;
|
||||
t7xx_pcie_register_rgu_isr(t7xx_dev);
|
||||
}
|
||||
|
||||
static void t7xx_md_hk_wq(struct work_struct *work)
|
||||
{
|
||||
struct t7xx_modem *md = container_of(work, struct t7xx_modem, handshake_work);
|
||||
struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
|
||||
|
||||
t7xx_cldma_switch_cfg(md->md_ctrl[CLDMA_ID_MD]);
|
||||
t7xx_cldma_start(md->md_ctrl[CLDMA_ID_MD]);
|
||||
t7xx_fsm_broadcast_state(ctl, MD_STATE_WAITING_FOR_HS2);
|
||||
md->core_md.ready = true;
|
||||
wake_up(&ctl->async_hk_wq);
|
||||
}
|
||||
|
||||
void t7xx_md_event_notify(struct t7xx_modem *md, enum md_event_id evt_id)
|
||||
{
|
||||
struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
|
||||
void __iomem *mhccif_base;
|
||||
unsigned int int_sta;
|
||||
unsigned long flags;
|
||||
|
||||
switch (evt_id) {
|
||||
case FSM_PRE_START:
|
||||
t7xx_mhccif_mask_clr(md->t7xx_dev, D2H_INT_PORT_ENUM);
|
||||
break;
|
||||
|
||||
case FSM_START:
|
||||
t7xx_mhccif_mask_set(md->t7xx_dev, D2H_INT_PORT_ENUM);
|
||||
|
||||
spin_lock_irqsave(&md->exp_lock, flags);
|
||||
int_sta = t7xx_get_interrupt_status(md->t7xx_dev);
|
||||
md->exp_id |= int_sta;
|
||||
if (md->exp_id & D2H_INT_EXCEPTION_INIT) {
|
||||
ctl->exp_flg = true;
|
||||
md->exp_id &= ~D2H_INT_EXCEPTION_INIT;
|
||||
md->exp_id &= ~D2H_INT_ASYNC_MD_HK;
|
||||
} else if (ctl->exp_flg) {
|
||||
md->exp_id &= ~D2H_INT_ASYNC_MD_HK;
|
||||
} else if (md->exp_id & D2H_INT_ASYNC_MD_HK) {
|
||||
queue_work(md->handshake_wq, &md->handshake_work);
|
||||
md->exp_id &= ~D2H_INT_ASYNC_MD_HK;
|
||||
mhccif_base = md->t7xx_dev->base_addr.mhccif_rc_base;
|
||||
iowrite32(D2H_INT_ASYNC_MD_HK, mhccif_base + REG_EP2RC_SW_INT_ACK);
|
||||
t7xx_mhccif_mask_set(md->t7xx_dev, D2H_INT_ASYNC_MD_HK);
|
||||
} else {
|
||||
t7xx_mhccif_mask_clr(md->t7xx_dev, D2H_INT_ASYNC_MD_HK);
|
||||
}
|
||||
spin_unlock_irqrestore(&md->exp_lock, flags);
|
||||
|
||||
t7xx_mhccif_mask_clr(md->t7xx_dev,
|
||||
D2H_INT_EXCEPTION_INIT |
|
||||
D2H_INT_EXCEPTION_INIT_DONE |
|
||||
D2H_INT_EXCEPTION_CLEARQ_DONE |
|
||||
D2H_INT_EXCEPTION_ALLQ_RESET);
|
||||
break;
|
||||
|
||||
case FSM_READY:
|
||||
t7xx_mhccif_mask_set(md->t7xx_dev, D2H_INT_ASYNC_MD_HK);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void t7xx_md_exception_handshake(struct t7xx_modem *md)
|
||||
{
|
||||
struct device *dev = &md->t7xx_dev->pdev->dev;
|
||||
int ret;
|
||||
|
||||
t7xx_md_exception(md, HIF_EX_INIT);
|
||||
ret = t7xx_wait_hif_ex_hk_event(md, D2H_INT_EXCEPTION_INIT_DONE);
|
||||
if (ret)
|
||||
dev_err(dev, "EX CCIF HS timeout, RCH 0x%lx\n", D2H_INT_EXCEPTION_INIT_DONE);
|
||||
|
||||
t7xx_md_exception(md, HIF_EX_INIT_DONE);
|
||||
ret = t7xx_wait_hif_ex_hk_event(md, D2H_INT_EXCEPTION_CLEARQ_DONE);
|
||||
if (ret)
|
||||
dev_err(dev, "EX CCIF HS timeout, RCH 0x%lx\n", D2H_INT_EXCEPTION_CLEARQ_DONE);
|
||||
|
||||
t7xx_md_exception(md, HIF_EX_CLEARQ_DONE);
|
||||
ret = t7xx_wait_hif_ex_hk_event(md, D2H_INT_EXCEPTION_ALLQ_RESET);
|
||||
if (ret)
|
||||
dev_err(dev, "EX CCIF HS timeout, RCH 0x%lx\n", D2H_INT_EXCEPTION_ALLQ_RESET);
|
||||
|
||||
t7xx_md_exception(md, HIF_EX_ALLQ_RESET);
|
||||
}
|
||||
|
||||
static struct t7xx_modem *t7xx_md_alloc(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
struct device *dev = &t7xx_dev->pdev->dev;
|
||||
struct t7xx_modem *md;
|
||||
|
||||
md = devm_kzalloc(dev, sizeof(*md), GFP_KERNEL);
|
||||
if (!md)
|
||||
return NULL;
|
||||
|
||||
md->t7xx_dev = t7xx_dev;
|
||||
t7xx_dev->md = md;
|
||||
spin_lock_init(&md->exp_lock);
|
||||
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, t7xx_md_hk_wq);
|
||||
return md;
|
||||
}
|
||||
|
||||
int t7xx_md_reset(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
struct t7xx_modem *md = t7xx_dev->md;
|
||||
|
||||
md->md_init_finish = false;
|
||||
md->exp_id = 0;
|
||||
t7xx_fsm_reset(md);
|
||||
t7xx_cldma_reset(md->md_ctrl[CLDMA_ID_MD]);
|
||||
md->md_init_finish = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* t7xx_md_init() - Initialize modem.
|
||||
* @t7xx_dev: MTK device.
|
||||
*
|
||||
* Allocate and initialize MD control block, and initialize data path.
|
||||
* Register MHCCIF ISR and RGU ISR, and start the state machine.
|
||||
*
|
||||
* Return:
|
||||
** 0 - Success.
|
||||
** -ENOMEM - Allocation failure.
|
||||
*/
|
||||
int t7xx_md_init(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
struct t7xx_modem *md;
|
||||
int ret;
|
||||
|
||||
md = t7xx_md_alloc(t7xx_dev);
|
||||
if (!md)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = t7xx_cldma_alloc(CLDMA_ID_MD, t7xx_dev);
|
||||
if (ret)
|
||||
goto err_destroy_hswq;
|
||||
|
||||
ret = t7xx_fsm_init(md);
|
||||
if (ret)
|
||||
goto err_destroy_hswq;
|
||||
|
||||
ret = t7xx_cldma_init(md->md_ctrl[CLDMA_ID_MD]);
|
||||
if (ret)
|
||||
goto err_uninit_fsm;
|
||||
|
||||
ret = t7xx_fsm_append_cmd(md->fsm_ctl, FSM_CMD_START, 0);
|
||||
if (ret) /* fsm_uninit flushes cmd queue */
|
||||
goto err_uninit_md_cldma;
|
||||
|
||||
t7xx_md_sys_sw_init(t7xx_dev);
|
||||
md->md_init_finish = true;
|
||||
return 0;
|
||||
|
||||
err_uninit_md_cldma:
|
||||
t7xx_cldma_exit(md->md_ctrl[CLDMA_ID_MD]);
|
||||
|
||||
err_uninit_fsm:
|
||||
t7xx_fsm_uninit(md);
|
||||
|
||||
err_destroy_hswq:
|
||||
destroy_workqueue(md->handshake_wq);
|
||||
dev_err(&t7xx_dev->pdev->dev, "Modem init failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
void t7xx_md_exit(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
struct t7xx_modem *md = t7xx_dev->md;
|
||||
|
||||
t7xx_pcie_mac_clear_int(t7xx_dev, SAP_RGU_INT);
|
||||
|
||||
if (!md->md_init_finish)
|
||||
return;
|
||||
|
||||
t7xx_fsm_append_cmd(md->fsm_ctl, FSM_CMD_PRE_STOP, FSM_CMD_FLAG_WAIT_FOR_COMPLETION);
|
||||
t7xx_cldma_exit(md->md_ctrl[CLDMA_ID_MD]);
|
||||
t7xx_fsm_uninit(md);
|
||||
destroy_workqueue(md->handshake_wq);
|
||||
}
|
85
drivers/net/wwan/t7xx/t7xx_modem_ops.h
Normal file
85
drivers/net/wwan/t7xx/t7xx_modem_ops.h
Normal file
@ -0,0 +1,85 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* Copyright (c) 2021, MediaTek Inc.
|
||||
* Copyright (c) 2021-2022, Intel Corporation.
|
||||
*
|
||||
* Authors:
|
||||
* Haijun Liu <haijun.liu@mediatek.com>
|
||||
* Eliot Lee <eliot.lee@intel.com>
|
||||
* Moises Veleta <moises.veleta@intel.com>
|
||||
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
||||
*
|
||||
* Contributors:
|
||||
* Amir Hanania <amir.hanania@intel.com>
|
||||
* Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
|
||||
* Sreehari Kancharla <sreehari.kancharla@intel.com>
|
||||
*/
|
||||
|
||||
#ifndef __T7XX_MODEM_OPS_H__
|
||||
#define __T7XX_MODEM_OPS_H__
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "t7xx_hif_cldma.h"
|
||||
#include "t7xx_pci.h"
|
||||
|
||||
#define FEATURE_COUNT 64
|
||||
|
||||
/**
|
||||
* enum hif_ex_stage - HIF exception handshake stages with the HW.
|
||||
* @HIF_EX_INIT: Disable and clear TXQ.
|
||||
* @HIF_EX_INIT_DONE: Polling for initialization 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 re-initialization and restart.
|
||||
*/
|
||||
enum hif_ex_stage {
|
||||
HIF_EX_INIT,
|
||||
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];
|
||||
__le32 data_len;
|
||||
__le32 data[];
|
||||
};
|
||||
|
||||
enum md_event_id {
|
||||
FSM_PRE_START,
|
||||
FSM_START,
|
||||
FSM_READY,
|
||||
};
|
||||
|
||||
struct t7xx_sys_info {
|
||||
bool ready;
|
||||
};
|
||||
|
||||
struct t7xx_modem {
|
||||
struct cldma_ctrl *md_ctrl[CLDMA_NUM];
|
||||
struct t7xx_pci_dev *t7xx_dev;
|
||||
struct t7xx_sys_info core_md;
|
||||
bool md_init_finish;
|
||||
bool rgu_irq_asserted;
|
||||
struct workqueue_struct *handshake_wq;
|
||||
struct work_struct handshake_work;
|
||||
struct t7xx_fsm_ctl *fsm_ctl;
|
||||
struct port_proxy *port_prox;
|
||||
unsigned int exp_id;
|
||||
spinlock_t exp_lock; /* Protects exception events */
|
||||
};
|
||||
|
||||
void t7xx_md_exception_handshake(struct t7xx_modem *md);
|
||||
void t7xx_md_event_notify(struct t7xx_modem *md, enum md_event_id evt_id);
|
||||
int t7xx_md_reset(struct t7xx_pci_dev *t7xx_dev);
|
||||
int t7xx_md_init(struct t7xx_pci_dev *t7xx_dev);
|
||||
void t7xx_md_exit(struct t7xx_pci_dev *t7xx_dev);
|
||||
void t7xx_clear_rgu_irq(struct t7xx_pci_dev *t7xx_dev);
|
||||
int t7xx_acpi_fldr_func(struct t7xx_pci_dev *t7xx_dev);
|
||||
int t7xx_pci_mhccif_isr(struct t7xx_pci_dev *t7xx_dev);
|
||||
|
||||
#endif /* __T7XX_MODEM_OPS_H__ */
|
225
drivers/net/wwan/t7xx/t7xx_pci.c
Normal file
225
drivers/net/wwan/t7xx/t7xx_pci.c
Normal file
@ -0,0 +1,225 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2021, MediaTek Inc.
|
||||
* Copyright (c) 2021-2022, Intel Corporation.
|
||||
*
|
||||
* Authors:
|
||||
* Haijun Liu <haijun.liu@mediatek.com>
|
||||
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
||||
* Sreehari Kancharla <sreehari.kancharla@intel.com>
|
||||
*
|
||||
* Contributors:
|
||||
* Amir Hanania <amir.hanania@intel.com>
|
||||
* Andy Shevchenko <andriy.shevchenko@linux.intel.com>
|
||||
* Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
|
||||
* Eliot Lee <eliot.lee@intel.com>
|
||||
* Moises Veleta <moises.veleta@intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.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"
|
||||
|
||||
#define T7XX_PCI_IREG_BASE 0
|
||||
#define T7XX_PCI_EREG_BASE 2
|
||||
|
||||
static int t7xx_request_irq(struct pci_dev *pdev)
|
||||
{
|
||||
struct t7xx_pci_dev *t7xx_dev;
|
||||
int ret, i;
|
||||
|
||||
t7xx_dev = pci_get_drvdata(pdev);
|
||||
|
||||
for (i = 0; i < EXT_INT_NUM; i++) {
|
||||
const char *irq_descr;
|
||||
int irq_vec;
|
||||
|
||||
if (!t7xx_dev->intr_handler[i])
|
||||
continue;
|
||||
|
||||
irq_descr = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s_%d",
|
||||
dev_driver_string(&pdev->dev), i);
|
||||
if (!irq_descr) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
|
||||
irq_vec = pci_irq_vector(pdev, i);
|
||||
ret = request_threaded_irq(irq_vec, t7xx_dev->intr_handler[i],
|
||||
t7xx_dev->intr_thread[i], 0, irq_descr,
|
||||
t7xx_dev->callback_param[i]);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
while (i--) {
|
||||
if (!t7xx_dev->intr_handler[i])
|
||||
continue;
|
||||
|
||||
free_irq(pci_irq_vector(pdev, i), t7xx_dev->callback_param[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int t7xx_setup_msix(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
struct pci_dev *pdev = t7xx_dev->pdev;
|
||||
int ret;
|
||||
|
||||
/* Only using 6 interrupts, but HW-design requires power-of-2 IRQs allocation */
|
||||
ret = pci_alloc_irq_vectors(pdev, EXT_INT_NUM, EXT_INT_NUM, PCI_IRQ_MSIX);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Failed to allocate MSI-X entry: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = t7xx_request_irq(pdev);
|
||||
if (ret) {
|
||||
pci_free_irq_vectors(pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
t7xx_pcie_set_mac_msix_cfg(t7xx_dev, EXT_INT_NUM);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int t7xx_interrupt_init(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
if (!t7xx_dev->pdev->msix_cap)
|
||||
return -EINVAL;
|
||||
|
||||
ret = t7xx_setup_msix(t7xx_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* IPs enable interrupts when ready */
|
||||
for (i = 0; i < EXT_INT_NUM; i++)
|
||||
t7xx_pcie_mac_set_int(t7xx_dev, i);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void t7xx_pci_infracfg_ao_calc(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
t7xx_dev->base_addr.infracfg_ao_base = t7xx_dev->base_addr.pcie_ext_reg_base +
|
||||
INFRACFG_AO_DEV_CHIP -
|
||||
t7xx_dev->base_addr.pcie_dev_reg_trsl_addr;
|
||||
}
|
||||
|
||||
static int t7xx_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
{
|
||||
struct t7xx_pci_dev *t7xx_dev;
|
||||
int ret;
|
||||
|
||||
t7xx_dev = devm_kzalloc(&pdev->dev, sizeof(*t7xx_dev), GFP_KERNEL);
|
||||
if (!t7xx_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
pci_set_drvdata(pdev, t7xx_dev);
|
||||
t7xx_dev->pdev = pdev;
|
||||
|
||||
ret = pcim_enable_device(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pci_set_master(pdev);
|
||||
|
||||
ret = pcim_iomap_regions(pdev, BIT(T7XX_PCI_IREG_BASE) | BIT(T7XX_PCI_EREG_BASE),
|
||||
pci_name(pdev));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Could not request BARs: %d\n", ret);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Could not set PCI DMA mask: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Could not set consistent PCI DMA mask: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
IREG_BASE(t7xx_dev) = pcim_iomap_table(pdev)[T7XX_PCI_IREG_BASE];
|
||||
t7xx_dev->base_addr.pcie_ext_reg_base = pcim_iomap_table(pdev)[T7XX_PCI_EREG_BASE];
|
||||
|
||||
t7xx_pcie_mac_atr_init(t7xx_dev);
|
||||
t7xx_pci_infracfg_ao_calc(t7xx_dev);
|
||||
t7xx_mhccif_init(t7xx_dev);
|
||||
|
||||
ret = t7xx_md_init(t7xx_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
t7xx_pcie_mac_interrupts_dis(t7xx_dev);
|
||||
|
||||
ret = t7xx_interrupt_init(t7xx_dev);
|
||||
if (ret) {
|
||||
t7xx_md_exit(t7xx_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
t7xx_pcie_mac_set_int(t7xx_dev, MHCCIF_INT);
|
||||
t7xx_pcie_mac_interrupts_en(t7xx_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void t7xx_pci_remove(struct pci_dev *pdev)
|
||||
{
|
||||
struct t7xx_pci_dev *t7xx_dev;
|
||||
int i;
|
||||
|
||||
t7xx_dev = pci_get_drvdata(pdev);
|
||||
t7xx_md_exit(t7xx_dev);
|
||||
|
||||
for (i = 0; i < EXT_INT_NUM; i++) {
|
||||
if (!t7xx_dev->intr_handler[i])
|
||||
continue;
|
||||
|
||||
free_irq(pci_irq_vector(pdev, i), t7xx_dev->callback_param[i]);
|
||||
}
|
||||
|
||||
pci_free_irq_vectors(t7xx_dev->pdev);
|
||||
}
|
||||
|
||||
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 t7xx_pci_driver = {
|
||||
.name = "mtk_t7xx",
|
||||
.id_table = t7xx_pci_table,
|
||||
.probe = t7xx_pci_probe,
|
||||
.remove = t7xx_pci_remove,
|
||||
};
|
||||
|
||||
module_pci_driver(t7xx_pci_driver);
|
||||
|
||||
MODULE_AUTHOR("MediaTek Inc");
|
||||
MODULE_DESCRIPTION("MediaTek PCIe 5G WWAN modem T7xx driver");
|
||||
MODULE_LICENSE("GPL");
|
64
drivers/net/wwan/t7xx/t7xx_pci.h
Normal file
64
drivers/net/wwan/t7xx/t7xx_pci.h
Normal file
@ -0,0 +1,64 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* Copyright (c) 2021, MediaTek Inc.
|
||||
* Copyright (c) 2021-2022, Intel Corporation.
|
||||
*
|
||||
* Authors:
|
||||
* Haijun Liu <haijun.liu@mediatek.com>
|
||||
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
||||
* Sreehari Kancharla <sreehari.kancharla@intel.com>
|
||||
*
|
||||
* Contributors:
|
||||
* Amir Hanania <amir.hanania@intel.com>
|
||||
* Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
|
||||
* Moises Veleta <moises.veleta@intel.com>
|
||||
*/
|
||||
|
||||
#ifndef __T7XX_PCI_H__
|
||||
#define __T7XX_PCI_H__
|
||||
|
||||
#include <linux/irqreturn.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "t7xx_reg.h"
|
||||
|
||||
/* struct t7xx_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 t7xx_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 (*t7xx_intr_callback)(int irq, void *param);
|
||||
|
||||
/* struct t7xx_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
|
||||
* @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
|
||||
*/
|
||||
struct t7xx_pci_dev {
|
||||
t7xx_intr_callback intr_handler[EXT_INT_NUM];
|
||||
t7xx_intr_callback intr_thread[EXT_INT_NUM];
|
||||
void *callback_param[EXT_INT_NUM];
|
||||
struct pci_dev *pdev;
|
||||
struct t7xx_addr_base base_addr;
|
||||
struct t7xx_modem *md;
|
||||
struct t7xx_ccmni_ctrl *ccmni_ctlb;
|
||||
bool rgu_pci_irq_en;
|
||||
};
|
||||
|
||||
#endif /* __T7XX_PCI_H__ */
|
262
drivers/net/wwan/t7xx/t7xx_pcie_mac.c
Normal file
262
drivers/net/wwan/t7xx/t7xx_pcie_mac.c
Normal file
@ -0,0 +1,262 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2021, MediaTek Inc.
|
||||
* Copyright (c) 2021-2022, Intel Corporation.
|
||||
*
|
||||
* Authors:
|
||||
* Haijun Liu <haijun.liu@mediatek.com>
|
||||
* Moises Veleta <moises.veleta@intel.com>
|
||||
* Sreehari Kancharla <sreehari.kancharla@intel.com>
|
||||
*
|
||||
* Contributors:
|
||||
* Amir Hanania <amir.hanania@intel.com>
|
||||
* Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
|
||||
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io-64-nonatomic-lo-hi.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "t7xx_pci.h"
|
||||
#include "t7xx_pcie_mac.h"
|
||||
#include "t7xx_reg.h"
|
||||
|
||||
#define T7XX_PCIE_REG_BAR 2
|
||||
#define T7XX_PCIE_REG_PORT ATR_SRC_PCI_WIN0
|
||||
#define T7XX_PCIE_REG_TABLE_NUM 0
|
||||
#define T7XX_PCIE_REG_TRSL_PORT ATR_DST_AXIM_0
|
||||
|
||||
#define T7XX_PCIE_DEV_DMA_PORT_START ATR_SRC_AXIS_0
|
||||
#define T7XX_PCIE_DEV_DMA_PORT_END ATR_SRC_AXIS_2
|
||||
#define T7XX_PCIE_DEV_DMA_TABLE_NUM 0
|
||||
#define T7XX_PCIE_DEV_DMA_TRSL_ADDR 0
|
||||
#define T7XX_PCIE_DEV_DMA_SRC_ADDR 0
|
||||
#define T7XX_PCIE_DEV_DMA_TRANSPARENT 1
|
||||
#define T7XX_PCIE_DEV_DMA_SIZE 0
|
||||
|
||||
enum t7xx_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 t7xx_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 t7xx_atr_config {
|
||||
u64 src_addr;
|
||||
u64 trsl_addr;
|
||||
u64 size;
|
||||
u32 port;
|
||||
u32 table;
|
||||
enum t7xx_atr_dst_port trsl_id;
|
||||
u32 transparent;
|
||||
};
|
||||
|
||||
static void t7xx_pcie_mac_atr_tables_dis(void __iomem *pbase, enum t7xx_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;
|
||||
reg = pbase + ATR_PCIE_WIN0_T0_ATR_PARAM_SRC_ADDR + offset;
|
||||
iowrite64(0, reg);
|
||||
}
|
||||
}
|
||||
|
||||
static int t7xx_pcie_mac_atr_cfg(struct t7xx_pci_dev *t7xx_dev, struct t7xx_atr_config *cfg)
|
||||
{
|
||||
struct device *dev = &t7xx_dev->pdev->dev;
|
||||
void __iomem *pbase = IREG_BASE(t7xx_dev);
|
||||
int atr_size, pos, offset;
|
||||
void __iomem *reg;
|
||||
u64 value;
|
||||
|
||||
if (cfg->transparent) {
|
||||
/* No address conversion is performed */
|
||||
atr_size = ATR_TRANSPARENT_SIZE;
|
||||
} else {
|
||||
if (cfg->src_addr & (cfg->size - 1)) {
|
||||
dev_err(dev, "Source address is not aligned to size\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (cfg->trsl_addr & (cfg->size - 1)) {
|
||||
dev_err(dev, "Translation address %llx is not aligned to size %llx\n",
|
||||
cfg->trsl_addr, cfg->size - 1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pos = __ffs64(cfg->size);
|
||||
|
||||
/* HW calculates the address translation space as 2^(atr_size + 1) */
|
||||
atr_size = pos - 1;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* Ensure ATR is set */
|
||||
ioread64(reg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* t7xx_pcie_mac_atr_init() - Initialize address translation.
|
||||
* @t7xx_dev: MTK device.
|
||||
*
|
||||
* Setup ATR for ports & device.
|
||||
*/
|
||||
void t7xx_pcie_mac_atr_init(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
struct t7xx_atr_config cfg;
|
||||
u32 i;
|
||||
|
||||
/* Disable for all ports */
|
||||
for (i = ATR_SRC_PCI_WIN0; i <= ATR_SRC_AXIS_3; i++)
|
||||
t7xx_pcie_mac_atr_tables_dis(IREG_BASE(t7xx_dev), i);
|
||||
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
/* Config ATR for RC to access device's register */
|
||||
cfg.src_addr = pci_resource_start(t7xx_dev->pdev, T7XX_PCIE_REG_BAR);
|
||||
cfg.size = T7XX_PCIE_REG_SIZE_CHIP;
|
||||
cfg.trsl_addr = T7XX_PCIE_REG_TRSL_ADDR_CHIP;
|
||||
cfg.port = T7XX_PCIE_REG_PORT;
|
||||
cfg.table = T7XX_PCIE_REG_TABLE_NUM;
|
||||
cfg.trsl_id = T7XX_PCIE_REG_TRSL_PORT;
|
||||
t7xx_pcie_mac_atr_tables_dis(IREG_BASE(t7xx_dev), cfg.port);
|
||||
t7xx_pcie_mac_atr_cfg(t7xx_dev, &cfg);
|
||||
|
||||
t7xx_dev->base_addr.pcie_dev_reg_trsl_addr = T7XX_PCIE_REG_TRSL_ADDR_CHIP;
|
||||
|
||||
/* Config ATR for EP to access RC's memory */
|
||||
for (i = T7XX_PCIE_DEV_DMA_PORT_START; i <= T7XX_PCIE_DEV_DMA_PORT_END; i++) {
|
||||
cfg.src_addr = T7XX_PCIE_DEV_DMA_SRC_ADDR;
|
||||
cfg.size = T7XX_PCIE_DEV_DMA_SIZE;
|
||||
cfg.trsl_addr = T7XX_PCIE_DEV_DMA_TRSL_ADDR;
|
||||
cfg.port = i;
|
||||
cfg.table = T7XX_PCIE_DEV_DMA_TABLE_NUM;
|
||||
cfg.trsl_id = ATR_DST_PCI_TRX;
|
||||
cfg.transparent = T7XX_PCIE_DEV_DMA_TRANSPARENT;
|
||||
t7xx_pcie_mac_atr_tables_dis(IREG_BASE(t7xx_dev), cfg.port);
|
||||
t7xx_pcie_mac_atr_cfg(t7xx_dev, &cfg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* t7xx_pcie_mac_enable_disable_int() - Enable/disable interrupts.
|
||||
* @t7xx_dev: MTK device.
|
||||
* @enable: Enable/disable.
|
||||
*
|
||||
* Enable or disable device interrupts.
|
||||
*/
|
||||
static void t7xx_pcie_mac_enable_disable_int(struct t7xx_pci_dev *t7xx_dev, bool enable)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = ioread32(IREG_BASE(t7xx_dev) + ISTAT_HST_CTRL);
|
||||
|
||||
if (enable)
|
||||
value &= ~ISTAT_HST_CTRL_DIS;
|
||||
else
|
||||
value |= ISTAT_HST_CTRL_DIS;
|
||||
|
||||
iowrite32(value, IREG_BASE(t7xx_dev) + ISTAT_HST_CTRL);
|
||||
}
|
||||
|
||||
void t7xx_pcie_mac_interrupts_en(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
t7xx_pcie_mac_enable_disable_int(t7xx_dev, true);
|
||||
}
|
||||
|
||||
void t7xx_pcie_mac_interrupts_dis(struct t7xx_pci_dev *t7xx_dev)
|
||||
{
|
||||
t7xx_pcie_mac_enable_disable_int(t7xx_dev, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* t7xx_pcie_mac_clear_set_int() - Clear/set interrupt by type.
|
||||
* @t7xx_dev: MTK device.
|
||||
* @int_type: Interrupt type.
|
||||
* @clear: Clear/set.
|
||||
*
|
||||
* Clear or set device interrupt by type.
|
||||
*/
|
||||
static void t7xx_pcie_mac_clear_set_int(struct t7xx_pci_dev *t7xx_dev,
|
||||
enum t7xx_int int_type, bool clear)
|
||||
{
|
||||
void __iomem *reg;
|
||||
u32 val;
|
||||
|
||||
if (clear)
|
||||
reg = IREG_BASE(t7xx_dev) + IMASK_HOST_MSIX_CLR_GRP0_0;
|
||||
else
|
||||
reg = IREG_BASE(t7xx_dev) + IMASK_HOST_MSIX_SET_GRP0_0;
|
||||
|
||||
val = BIT(EXT_INT_START + int_type);
|
||||
iowrite32(val, reg);
|
||||
}
|
||||
|
||||
void t7xx_pcie_mac_clear_int(struct t7xx_pci_dev *t7xx_dev, enum t7xx_int int_type)
|
||||
{
|
||||
t7xx_pcie_mac_clear_set_int(t7xx_dev, int_type, true);
|
||||
}
|
||||
|
||||
void t7xx_pcie_mac_set_int(struct t7xx_pci_dev *t7xx_dev, enum t7xx_int int_type)
|
||||
{
|
||||
t7xx_pcie_mac_clear_set_int(t7xx_dev, int_type, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* t7xx_pcie_mac_clear_int_status() - Clear interrupt status by type.
|
||||
* @t7xx_dev: MTK device.
|
||||
* @int_type: Interrupt type.
|
||||
*
|
||||
* Enable or disable device interrupts' status by type.
|
||||
*/
|
||||
void t7xx_pcie_mac_clear_int_status(struct t7xx_pci_dev *t7xx_dev, enum t7xx_int int_type)
|
||||
{
|
||||
void __iomem *reg = IREG_BASE(t7xx_dev) + MSIX_ISTAT_HST_GRP0_0;
|
||||
u32 val = BIT(EXT_INT_START + int_type);
|
||||
|
||||
iowrite32(val, reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* t7xx_pcie_set_mac_msix_cfg() - Write MSIX control configuration.
|
||||
* @t7xx_dev: MTK device.
|
||||
* @irq_count: Number of MSIX IRQ vectors.
|
||||
*
|
||||
* Write IRQ count to device.
|
||||
*/
|
||||
void t7xx_pcie_set_mac_msix_cfg(struct t7xx_pci_dev *t7xx_dev, unsigned int irq_count)
|
||||
{
|
||||
u32 val = ffs(irq_count) * 2 - 1;
|
||||
|
||||
iowrite32(val, IREG_BASE(t7xx_dev) + T7XX_PCIE_CFG_MSIX);
|
||||
}
|
31
drivers/net/wwan/t7xx/t7xx_pcie_mac.h
Normal file
31
drivers/net/wwan/t7xx/t7xx_pcie_mac.h
Normal file
@ -0,0 +1,31 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* Copyright (c) 2021, MediaTek Inc.
|
||||
* Copyright (c) 2021-2022, Intel Corporation.
|
||||
*
|
||||
* Authors:
|
||||
* Haijun Liu <haijun.liu@mediatek.com>
|
||||
* Sreehari Kancharla <sreehari.kancharla@intel.com>
|
||||
*
|
||||
* Contributors:
|
||||
* Moises Veleta <moises.veleta@intel.com>
|
||||
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
||||
*/
|
||||
|
||||
#ifndef __T7XX_PCIE_MAC_H__
|
||||
#define __T7XX_PCIE_MAC_H__
|
||||
|
||||
#include "t7xx_pci.h"
|
||||
#include "t7xx_reg.h"
|
||||
|
||||
#define IREG_BASE(t7xx_dev) ((t7xx_dev)->base_addr.pcie_mac_ireg_base)
|
||||
|
||||
void t7xx_pcie_mac_interrupts_en(struct t7xx_pci_dev *t7xx_dev);
|
||||
void t7xx_pcie_mac_interrupts_dis(struct t7xx_pci_dev *t7xx_dev);
|
||||
void t7xx_pcie_mac_atr_init(struct t7xx_pci_dev *t7xx_dev);
|
||||
void t7xx_pcie_mac_clear_int(struct t7xx_pci_dev *t7xx_dev, enum t7xx_int int_type);
|
||||
void t7xx_pcie_mac_set_int(struct t7xx_pci_dev *t7xx_dev, enum t7xx_int int_type);
|
||||
void t7xx_pcie_mac_clear_int_status(struct t7xx_pci_dev *t7xx_dev, enum t7xx_int int_type);
|
||||
void t7xx_pcie_set_mac_msix_cfg(struct t7xx_pci_dev *t7xx_dev, unsigned int irq_count);
|
||||
|
||||
#endif /* __T7XX_PCIE_MAC_H__ */
|
@ -19,6 +19,110 @@
|
||||
#ifndef __T7XX_REG_H__
|
||||
#define __T7XX_REG_H__
|
||||
|
||||
#include <linux/bits.h>
|
||||
|
||||
/* Device base address offset */
|
||||
#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)
|
||||
|
||||
/* Register base */
|
||||
#define INFRACFG_AO_DEV_CHIP 0x10001000
|
||||
|
||||
/* ATR setting */
|
||||
#define T7XX_PCIE_REG_TRSL_ADDR_CHIP 0x10000000
|
||||
#define T7XX_PCIE_REG_SIZE_CHIP 0x00400000
|
||||
|
||||
/* Reset Generic Unit (RGU) */
|
||||
#define TOPRGU_CH_PCIE_IRQ_STA 0x1000790c
|
||||
|
||||
#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 ISTAT_HST_CTRL 0x01ac
|
||||
#define ISTAT_HST_CTRL_DIS BIT(0)
|
||||
|
||||
#define T7XX_PCIE_MISC_CTRL 0x0348
|
||||
#define T7XX_PCIE_MISC_MAC_SLEEP_DIS BIT(7)
|
||||
|
||||
#define T7XX_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 T7XX_PCIE_PM_RESUME_STATE 0x0d0c
|
||||
|
||||
enum t7xx_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 T7XX_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 T7XX_PCIE_RESOURCE_STATUS 0x0d28
|
||||
#define T7XX_PCIE_RESOURCE_STS_MSK GENMASK(4, 0)
|
||||
|
||||
#define DISABLE_ASPM_LOWPWR 0x0e50
|
||||
#define ENABLE_ASPM_LOWPWR 0x0e54
|
||||
#define T7XX_L1_BIT(i) BIT((i) * 4 + 1)
|
||||
#define T7XX_L1_1_BIT(i) BIT((i) * 4 + 2)
|
||||
#define T7XX_L1_2_BIT(i) BIT((i) * 4 + 3)
|
||||
|
||||
#define MSIX_ISTAT_HST_GRP0_0 0x0f00
|
||||
#define IMASK_HOST_MSIX_SET_GRP0_0 0x3000
|
||||
#define IMASK_HOST_MSIX_CLR_GRP0_0 0x3080
|
||||
#define EXT_INT_START 24
|
||||
#define EXT_INT_NUM 8
|
||||
#define MSIX_MSK_SET_ALL GENMASK(31, 24)
|
||||
|
||||
enum t7xx_int {
|
||||
DPMAIF_INT,
|
||||
CLDMA0_INT,
|
||||
|
540
drivers/net/wwan/t7xx/t7xx_state_monitor.c
Normal file
540
drivers/net/wwan/t7xx/t7xx_state_monitor.c
Normal file
@ -0,0 +1,540 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2021, MediaTek Inc.
|
||||
* Copyright (c) 2021-2022, Intel Corporation.
|
||||
*
|
||||
* Authors:
|
||||
* Haijun Liu <haijun.liu@mediatek.com>
|
||||
* Eliot Lee <eliot.lee@intel.com>
|
||||
* Moises Veleta <moises.veleta@intel.com>
|
||||
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
||||
*
|
||||
* Contributors:
|
||||
* Amir Hanania <amir.hanania@intel.com>
|
||||
* Sreehari Kancharla <sreehari.kancharla@intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include "t7xx_hif_cldma.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_state_monitor.h"
|
||||
|
||||
#define FSM_DRM_DISABLE_DELAY_MS 200
|
||||
#define FSM_EVENT_POLL_INTERVAL_MS 20
|
||||
#define FSM_MD_EX_REC_OK_TIMEOUT_MS 10000
|
||||
#define FSM_MD_EX_PASS_TIMEOUT_MS 45000
|
||||
#define FSM_CMD_TIMEOUT_MS 2000
|
||||
|
||||
void t7xx_fsm_notifier_register(struct t7xx_modem *md, struct t7xx_fsm_notifier *notifier)
|
||||
{
|
||||
struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ctl->notifier_lock, flags);
|
||||
list_add_tail(¬ifier->entry, &ctl->notifier_list);
|
||||
spin_unlock_irqrestore(&ctl->notifier_lock, flags);
|
||||
}
|
||||
|
||||
void t7xx_fsm_notifier_unregister(struct t7xx_modem *md, struct t7xx_fsm_notifier *notifier)
|
||||
{
|
||||
struct t7xx_fsm_notifier *notifier_cur, *notifier_next;
|
||||
struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
|
||||
unsigned long flags;
|
||||
|
||||
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(struct t7xx_modem *md, enum md_state state)
|
||||
{
|
||||
struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
|
||||
struct t7xx_fsm_notifier *notifier;
|
||||
unsigned long flags;
|
||||
|
||||
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 t7xx_fsm_broadcast_state(struct t7xx_fsm_ctl *ctl, enum md_state state)
|
||||
{
|
||||
ctl->md_state = state;
|
||||
fsm_state_notify(ctl->md, state);
|
||||
}
|
||||
|
||||
static void fsm_finish_command(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd, int result)
|
||||
{
|
||||
if (cmd->flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETION) {
|
||||
*cmd->ret = result;
|
||||
complete_all(cmd->done);
|
||||
}
|
||||
|
||||
kfree(cmd);
|
||||
}
|
||||
|
||||
static void fsm_del_kf_event(struct t7xx_fsm_event *event)
|
||||
{
|
||||
list_del(&event->entry);
|
||||
kfree(event);
|
||||
}
|
||||
|
||||
static void fsm_flush_event_cmd_qs(struct t7xx_fsm_ctl *ctl)
|
||||
{
|
||||
struct device *dev = &ctl->md->t7xx_dev->pdev->dev;
|
||||
struct t7xx_fsm_event *event, *evt_next;
|
||||
struct t7xx_fsm_command *cmd, *cmd_next;
|
||||
unsigned long flags;
|
||||
|
||||
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, -EINVAL);
|
||||
}
|
||||
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_del_kf_event(event);
|
||||
}
|
||||
spin_unlock_irqrestore(&ctl->event_lock, flags);
|
||||
}
|
||||
|
||||
static void fsm_wait_for_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_expected,
|
||||
enum t7xx_fsm_event_state event_ignore, int retries)
|
||||
{
|
||||
struct t7xx_fsm_event *event;
|
||||
bool event_received = false;
|
||||
unsigned long flags;
|
||||
int cnt = 0;
|
||||
|
||||
while (cnt++ < retries && !event_received) {
|
||||
bool sleep_required = true;
|
||||
|
||||
if (kthread_should_stop())
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&ctl->event_lock, flags);
|
||||
event = list_first_entry_or_null(&ctl->event_queue, struct t7xx_fsm_event, entry);
|
||||
if (event) {
|
||||
event_received = event->event_id == event_expected;
|
||||
if (event_received || event->event_id == event_ignore) {
|
||||
fsm_del_kf_event(event);
|
||||
sleep_required = false;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&ctl->event_lock, flags);
|
||||
|
||||
if (sleep_required)
|
||||
msleep(FSM_EVENT_POLL_INTERVAL_MS);
|
||||
}
|
||||
}
|
||||
|
||||
static void fsm_routine_exception(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd,
|
||||
enum t7xx_ex_reason reason)
|
||||
{
|
||||
struct device *dev = &ctl->md->t7xx_dev->pdev->dev;
|
||||
|
||||
if (ctl->curr_state != FSM_STATE_READY && ctl->curr_state != FSM_STATE_STARTING) {
|
||||
if (cmd)
|
||||
fsm_finish_command(ctl, cmd, -EINVAL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ctl->curr_state = FSM_STATE_EXCEPTION;
|
||||
|
||||
switch (reason) {
|
||||
case EXCEPTION_HS_TIMEOUT:
|
||||
dev_err(dev, "Boot Handshake failure\n");
|
||||
break;
|
||||
|
||||
case EXCEPTION_EVENT:
|
||||
dev_err(dev, "Exception event\n");
|
||||
t7xx_fsm_broadcast_state(ctl, MD_STATE_EXCEPTION);
|
||||
t7xx_md_exception_handshake(ctl->md);
|
||||
|
||||
fsm_wait_for_event(ctl, FSM_EVENT_MD_EX_REC_OK, FSM_EVENT_MD_EX,
|
||||
FSM_MD_EX_REC_OK_TIMEOUT_MS / FSM_EVENT_POLL_INTERVAL_MS);
|
||||
fsm_wait_for_event(ctl, FSM_EVENT_MD_EX_PASS, FSM_EVENT_INVALID,
|
||||
FSM_MD_EX_PASS_TIMEOUT_MS / FSM_EVENT_POLL_INTERVAL_MS);
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(dev, "Exception %d\n", reason);
|
||||
break;
|
||||
}
|
||||
|
||||
if (cmd)
|
||||
fsm_finish_command(ctl, cmd, 0);
|
||||
}
|
||||
|
||||
static int fsm_stopped_handler(struct t7xx_fsm_ctl *ctl)
|
||||
{
|
||||
ctl->curr_state = FSM_STATE_STOPPED;
|
||||
|
||||
t7xx_fsm_broadcast_state(ctl, MD_STATE_STOPPED);
|
||||
return t7xx_md_reset(ctl->md->t7xx_dev);
|
||||
}
|
||||
|
||||
static void fsm_routine_stopped(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd)
|
||||
{
|
||||
if (ctl->curr_state == FSM_STATE_STOPPED) {
|
||||
fsm_finish_command(ctl, cmd, -EINVAL);
|
||||
return;
|
||||
}
|
||||
|
||||
fsm_finish_command(ctl, cmd, fsm_stopped_handler(ctl));
|
||||
}
|
||||
|
||||
static void fsm_routine_stopping(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd)
|
||||
{
|
||||
struct t7xx_pci_dev *t7xx_dev;
|
||||
struct cldma_ctrl *md_ctrl;
|
||||
int err;
|
||||
|
||||
if (ctl->curr_state == FSM_STATE_STOPPED || ctl->curr_state == FSM_STATE_STOPPING) {
|
||||
fsm_finish_command(ctl, cmd, -EINVAL);
|
||||
return;
|
||||
}
|
||||
|
||||
md_ctrl = ctl->md->md_ctrl[CLDMA_ID_MD];
|
||||
t7xx_dev = ctl->md->t7xx_dev;
|
||||
|
||||
ctl->curr_state = FSM_STATE_STOPPING;
|
||||
t7xx_fsm_broadcast_state(ctl, MD_STATE_WAITING_TO_STOP);
|
||||
t7xx_cldma_stop(md_ctrl);
|
||||
|
||||
if (!ctl->md->rgu_irq_asserted) {
|
||||
t7xx_mhccif_h2d_swint_trigger(t7xx_dev, H2D_CH_DRM_DISABLE_AP);
|
||||
/* Wait for the DRM disable to take effect */
|
||||
msleep(FSM_DRM_DISABLE_DELAY_MS);
|
||||
|
||||
err = t7xx_acpi_fldr_func(t7xx_dev);
|
||||
if (err)
|
||||
t7xx_mhccif_h2d_swint_trigger(t7xx_dev, H2D_CH_DEVICE_RESET);
|
||||
}
|
||||
|
||||
fsm_finish_command(ctl, cmd, fsm_stopped_handler(ctl));
|
||||
}
|
||||
|
||||
static void t7xx_fsm_broadcast_ready_state(struct t7xx_fsm_ctl *ctl)
|
||||
{
|
||||
if (ctl->md_state != MD_STATE_WAITING_FOR_HS2)
|
||||
return;
|
||||
|
||||
ctl->md_state = MD_STATE_READY;
|
||||
|
||||
fsm_state_notify(ctl->md, MD_STATE_READY);
|
||||
}
|
||||
|
||||
static void fsm_routine_ready(struct t7xx_fsm_ctl *ctl)
|
||||
{
|
||||
struct t7xx_modem *md = ctl->md;
|
||||
|
||||
ctl->curr_state = FSM_STATE_READY;
|
||||
t7xx_fsm_broadcast_ready_state(ctl);
|
||||
t7xx_md_event_notify(md, FSM_READY);
|
||||
}
|
||||
|
||||
static int fsm_routine_starting(struct t7xx_fsm_ctl *ctl)
|
||||
{
|
||||
struct t7xx_modem *md = ctl->md;
|
||||
struct device *dev;
|
||||
|
||||
ctl->curr_state = FSM_STATE_STARTING;
|
||||
|
||||
t7xx_fsm_broadcast_state(ctl, MD_STATE_WAITING_FOR_HS1);
|
||||
t7xx_md_event_notify(md, FSM_START);
|
||||
|
||||
wait_event_interruptible_timeout(ctl->async_hk_wq, md->core_md.ready || ctl->exp_flg,
|
||||
HZ * 60);
|
||||
dev = &md->t7xx_dev->pdev->dev;
|
||||
|
||||
if (ctl->exp_flg)
|
||||
dev_err(dev, "MD exception is captured during handshake\n");
|
||||
|
||||
if (!md->core_md.ready) {
|
||||
dev_err(dev, "MD handshake timeout\n");
|
||||
fsm_routine_exception(ctl, NULL, EXCEPTION_HS_TIMEOUT);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
fsm_routine_ready(ctl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fsm_routine_start(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd)
|
||||
{
|
||||
struct t7xx_modem *md = ctl->md;
|
||||
u32 dev_status;
|
||||
int ret;
|
||||
|
||||
if (!md)
|
||||
return;
|
||||
|
||||
if (ctl->curr_state != FSM_STATE_INIT && ctl->curr_state != FSM_STATE_PRE_START &&
|
||||
ctl->curr_state != FSM_STATE_STOPPED) {
|
||||
fsm_finish_command(ctl, cmd, -EINVAL);
|
||||
return;
|
||||
}
|
||||
|
||||
ctl->curr_state = FSM_STATE_PRE_START;
|
||||
t7xx_md_event_notify(md, FSM_PRE_START);
|
||||
|
||||
ret = read_poll_timeout(ioread32, dev_status,
|
||||
(dev_status & MISC_STAGE_MASK) == LINUX_STAGE, 20000, 2000000,
|
||||
false, IREG_BASE(md->t7xx_dev) + T7XX_PCIE_MISC_DEV_STATUS);
|
||||
if (ret) {
|
||||
struct device *dev = &md->t7xx_dev->pdev->dev;
|
||||
|
||||
fsm_finish_command(ctl, cmd, -ETIMEDOUT);
|
||||
dev_err(dev, "Invalid device status 0x%lx\n", dev_status & MISC_STAGE_MASK);
|
||||
return;
|
||||
}
|
||||
|
||||
t7xx_cldma_hif_hw_init(md->md_ctrl[CLDMA_ID_MD]);
|
||||
fsm_finish_command(ctl, cmd, fsm_routine_starting(ctl));
|
||||
}
|
||||
|
||||
static int fsm_main_thread(void *data)
|
||||
{
|
||||
struct t7xx_fsm_ctl *ctl = data;
|
||||
struct t7xx_fsm_command *cmd;
|
||||
unsigned long flags;
|
||||
|
||||
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 t7xx_fsm_command, entry);
|
||||
list_del(&cmd->entry);
|
||||
spin_unlock_irqrestore(&ctl->command_lock, flags);
|
||||
|
||||
switch (cmd->cmd_id) {
|
||||
case FSM_CMD_START:
|
||||
fsm_routine_start(ctl, cmd);
|
||||
break;
|
||||
|
||||
case FSM_CMD_EXCEPTION:
|
||||
fsm_routine_exception(ctl, cmd, FIELD_GET(FSM_CMD_EX_REASON, cmd->flag));
|
||||
break;
|
||||
|
||||
case FSM_CMD_PRE_STOP:
|
||||
fsm_routine_stopping(ctl, cmd);
|
||||
break;
|
||||
|
||||
case FSM_CMD_STOP:
|
||||
fsm_routine_stopped(ctl, cmd);
|
||||
break;
|
||||
|
||||
default:
|
||||
fsm_finish_command(ctl, cmd, -EINVAL);
|
||||
fsm_flush_event_cmd_qs(ctl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int t7xx_fsm_append_cmd(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_cmd_state cmd_id, unsigned int flag)
|
||||
{
|
||||
DECLARE_COMPLETION_ONSTACK(done);
|
||||
struct t7xx_fsm_command *cmd;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
cmd = kzalloc(sizeof(*cmd), flag & FSM_CMD_FLAG_IN_INTERRUPT ? GFP_ATOMIC : GFP_KERNEL);
|
||||
if (!cmd)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&cmd->entry);
|
||||
cmd->cmd_id = cmd_id;
|
||||
cmd->flag = flag;
|
||||
if (flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETION) {
|
||||
cmd->done = &done;
|
||||
cmd->ret = &ret;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&ctl->command_lock, flags);
|
||||
list_add_tail(&cmd->entry, &ctl->command_queue);
|
||||
spin_unlock_irqrestore(&ctl->command_lock, flags);
|
||||
|
||||
wake_up(&ctl->command_wq);
|
||||
|
||||
if (flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETION) {
|
||||
unsigned long wait_ret;
|
||||
|
||||
wait_ret = wait_for_completion_timeout(&done,
|
||||
msecs_to_jiffies(FSM_CMD_TIMEOUT_MS));
|
||||
if (!wait_ret)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int t7xx_fsm_append_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_id,
|
||||
unsigned char *data, unsigned int length)
|
||||
{
|
||||
struct device *dev = &ctl->md->t7xx_dev->pdev->dev;
|
||||
struct t7xx_fsm_event *event;
|
||||
unsigned long flags;
|
||||
|
||||
if (event_id <= FSM_EVENT_INVALID || event_id >= FSM_EVENT_MAX) {
|
||||
dev_err(dev, "Invalid event %d\n", event_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
event = kmalloc(sizeof(*event) + 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, 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 t7xx_fsm_clr_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_id)
|
||||
{
|
||||
struct t7xx_fsm_event *event, *evt_next;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ctl->event_lock, flags);
|
||||
list_for_each_entry_safe(event, evt_next, &ctl->event_queue, entry) {
|
||||
if (event->event_id == event_id)
|
||||
fsm_del_kf_event(event);
|
||||
}
|
||||
spin_unlock_irqrestore(&ctl->event_lock, flags);
|
||||
}
|
||||
|
||||
enum md_state t7xx_fsm_get_md_state(struct t7xx_fsm_ctl *ctl)
|
||||
{
|
||||
if (ctl)
|
||||
return ctl->md_state;
|
||||
|
||||
return MD_STATE_INVALID;
|
||||
}
|
||||
|
||||
unsigned int t7xx_fsm_get_ctl_state(struct t7xx_fsm_ctl *ctl)
|
||||
{
|
||||
if (ctl)
|
||||
return ctl->curr_state;
|
||||
|
||||
return FSM_STATE_STOPPED;
|
||||
}
|
||||
|
||||
int t7xx_fsm_recv_md_intr(struct t7xx_fsm_ctl *ctl, enum t7xx_md_irq_type type)
|
||||
{
|
||||
unsigned int cmd_flags = FSM_CMD_FLAG_IN_INTERRUPT;
|
||||
|
||||
if (type == MD_IRQ_PORT_ENUM) {
|
||||
return t7xx_fsm_append_cmd(ctl, FSM_CMD_START, cmd_flags);
|
||||
} else if (type == MD_IRQ_CCIF_EX) {
|
||||
ctl->exp_flg = true;
|
||||
wake_up(&ctl->async_hk_wq);
|
||||
cmd_flags |= FIELD_PREP(FSM_CMD_EX_REASON, EXCEPTION_EVENT);
|
||||
return t7xx_fsm_append_cmd(ctl, FSM_CMD_EXCEPTION, cmd_flags);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
void t7xx_fsm_reset(struct t7xx_modem *md)
|
||||
{
|
||||
struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
|
||||
|
||||
fsm_flush_event_cmd_qs(ctl);
|
||||
ctl->curr_state = FSM_STATE_STOPPED;
|
||||
ctl->exp_flg = false;
|
||||
}
|
||||
|
||||
int t7xx_fsm_init(struct t7xx_modem *md)
|
||||
{
|
||||
struct device *dev = &md->t7xx_dev->pdev->dev;
|
||||
struct t7xx_fsm_ctl *ctl;
|
||||
|
||||
ctl = devm_kzalloc(dev, sizeof(*ctl), GFP_KERNEL);
|
||||
if (!ctl)
|
||||
return -ENOMEM;
|
||||
|
||||
md->fsm_ctl = ctl;
|
||||
ctl->md = md;
|
||||
ctl->curr_state = FSM_STATE_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);
|
||||
ctl->exp_flg = false;
|
||||
spin_lock_init(&ctl->notifier_lock);
|
||||
|
||||
ctl->fsm_thread = kthread_run(fsm_main_thread, ctl, "t7xx_fsm");
|
||||
return PTR_ERR_OR_ZERO(ctl->fsm_thread);
|
||||
}
|
||||
|
||||
void t7xx_fsm_uninit(struct t7xx_modem *md)
|
||||
{
|
||||
struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
|
||||
|
||||
if (!ctl)
|
||||
return;
|
||||
|
||||
if (ctl->fsm_thread)
|
||||
kthread_stop(ctl->fsm_thread);
|
||||
|
||||
fsm_flush_event_cmd_qs(ctl);
|
||||
}
|
133
drivers/net/wwan/t7xx/t7xx_state_monitor.h
Normal file
133
drivers/net/wwan/t7xx/t7xx_state_monitor.h
Normal file
@ -0,0 +1,133 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* Copyright (c) 2021, MediaTek Inc.
|
||||
* Copyright (c) 2021-2022, Intel Corporation.
|
||||
*
|
||||
* Authors:
|
||||
* Amir Hanania <amir.hanania@intel.com>
|
||||
* Haijun Liu <haijun.liu@mediatek.com>
|
||||
* Moises Veleta <moises.veleta@intel.com>
|
||||
*
|
||||
* Contributors:
|
||||
* Eliot Lee <eliot.lee@intel.com>
|
||||
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
||||
* Sreehari Kancharla <sreehari.kancharla@intel.com>
|
||||
*/
|
||||
|
||||
#ifndef __T7XX_MONITOR_H__
|
||||
#define __T7XX_MONITOR_H__
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include "t7xx_modem_ops.h"
|
||||
|
||||
enum t7xx_fsm_state {
|
||||
FSM_STATE_INIT,
|
||||
FSM_STATE_PRE_START,
|
||||
FSM_STATE_STARTING,
|
||||
FSM_STATE_READY,
|
||||
FSM_STATE_EXCEPTION,
|
||||
FSM_STATE_STOPPING,
|
||||
FSM_STATE_STOPPED,
|
||||
};
|
||||
|
||||
enum t7xx_fsm_event_state {
|
||||
FSM_EVENT_INVALID,
|
||||
FSM_EVENT_MD_EX,
|
||||
FSM_EVENT_MD_EX_REC_OK,
|
||||
FSM_EVENT_MD_EX_PASS,
|
||||
FSM_EVENT_MAX
|
||||
};
|
||||
|
||||
enum t7xx_fsm_cmd_state {
|
||||
FSM_CMD_INVALID,
|
||||
FSM_CMD_START,
|
||||
FSM_CMD_EXCEPTION,
|
||||
FSM_CMD_PRE_STOP,
|
||||
FSM_CMD_STOP,
|
||||
};
|
||||
|
||||
enum t7xx_ex_reason {
|
||||
EXCEPTION_HS_TIMEOUT,
|
||||
EXCEPTION_EVENT,
|
||||
};
|
||||
|
||||
enum t7xx_md_irq_type {
|
||||
MD_IRQ_WDT,
|
||||
MD_IRQ_CCIF_EX,
|
||||
MD_IRQ_PORT_ENUM,
|
||||
};
|
||||
|
||||
enum md_state {
|
||||
MD_STATE_INVALID,
|
||||
MD_STATE_WAITING_FOR_HS1,
|
||||
MD_STATE_WAITING_FOR_HS2,
|
||||
MD_STATE_READY,
|
||||
MD_STATE_EXCEPTION,
|
||||
MD_STATE_WAITING_TO_STOP,
|
||||
MD_STATE_STOPPED,
|
||||
};
|
||||
|
||||
#define FSM_CMD_FLAG_WAIT_FOR_COMPLETION BIT(0)
|
||||
#define FSM_CMD_FLAG_FLIGHT_MODE BIT(1)
|
||||
#define FSM_CMD_FLAG_IN_INTERRUPT BIT(2)
|
||||
#define FSM_CMD_EX_REASON GENMASK(23, 16)
|
||||
|
||||
struct t7xx_fsm_ctl {
|
||||
struct t7xx_modem *md;
|
||||
enum md_state md_state;
|
||||
unsigned int curr_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 */
|
||||
struct task_struct *fsm_thread;
|
||||
bool exp_flg;
|
||||
spinlock_t notifier_lock; /* Protects notifier list */
|
||||
struct list_head notifier_list;
|
||||
};
|
||||
|
||||
struct t7xx_fsm_event {
|
||||
struct list_head entry;
|
||||
enum t7xx_fsm_event_state event_id;
|
||||
unsigned int length;
|
||||
unsigned char data[];
|
||||
};
|
||||
|
||||
struct t7xx_fsm_command {
|
||||
struct list_head entry;
|
||||
enum t7xx_fsm_cmd_state cmd_id;
|
||||
unsigned int flag;
|
||||
struct completion *done;
|
||||
int *ret;
|
||||
};
|
||||
|
||||
struct t7xx_fsm_notifier {
|
||||
struct list_head entry;
|
||||
int (*notifier_fn)(enum md_state state, void *data);
|
||||
void *data;
|
||||
};
|
||||
|
||||
int t7xx_fsm_append_cmd(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_cmd_state cmd_id,
|
||||
unsigned int flag);
|
||||
int t7xx_fsm_append_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_id,
|
||||
unsigned char *data, unsigned int length);
|
||||
void t7xx_fsm_clr_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_id);
|
||||
void t7xx_fsm_broadcast_state(struct t7xx_fsm_ctl *ctl, enum md_state state);
|
||||
void t7xx_fsm_reset(struct t7xx_modem *md);
|
||||
int t7xx_fsm_init(struct t7xx_modem *md);
|
||||
void t7xx_fsm_uninit(struct t7xx_modem *md);
|
||||
int t7xx_fsm_recv_md_intr(struct t7xx_fsm_ctl *ctl, enum t7xx_md_irq_type type);
|
||||
enum md_state t7xx_fsm_get_md_state(struct t7xx_fsm_ctl *ctl);
|
||||
unsigned int t7xx_fsm_get_ctl_state(struct t7xx_fsm_ctl *ctl);
|
||||
void t7xx_fsm_notifier_register(struct t7xx_modem *md, struct t7xx_fsm_notifier *notifier);
|
||||
void t7xx_fsm_notifier_unregister(struct t7xx_modem *md, struct t7xx_fsm_notifier *notifier);
|
||||
|
||||
#endif /* __T7XX_MONITOR_H__ */
|
Loading…
Reference in New Issue
Block a user