summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2022-08-17 11:53:53 +0100
committerDavid S. Miller <davem@davemloft.net>2022-08-17 11:53:53 +0100
commit5417197dd516a8e115aa69f62a7b7554b0c3829c (patch)
tree0c8b529138475bb10a209a1980d92bef2e53e6d4
parent0630f64d25a0f0a8c6a9ce9fde8750b3b561e6f5 (diff)
parentb0bc1709b7688a094a70099f21669202588e7c06 (diff)
downloadlinux-5417197dd516a8e115aa69f62a7b7554b0c3829c.tar.bz2
Merge branch 'wwan-t7xx-fw-flashing-and-coredump-support'
M Chetan Kumar says: ==================== net: wwan: t7xx: fw flashing & coredump support This patch series brings-in the support for FM350 wwan device firmware flashing & coredump collection using devlink interface. Below is the high level description of individual patches. Refer to individual patch commit message for details. PATCH1: Enables AP CLDMA communication for firmware flashing & coredump collection. PATCH2: Enables the infrastructure & queue configuration required for early ports enumeration. PATCH3: Implements device reset and rescan logic required to enter or exit fastboot mode. PATCH4: Implements devlink interface & uses the fastboot protocol for fw flashing and coredump collection. PATCH5: t7xx devlink commands documentation. ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--Documentation/networking/devlink/index.rst1
-rw-r--r--Documentation/networking/devlink/t7xx.rst145
-rw-r--r--drivers/net/wwan/Kconfig1
-rw-r--r--drivers/net/wwan/t7xx/Makefile5
-rw-r--r--drivers/net/wwan/t7xx/t7xx_hif_cldma.c55
-rw-r--r--drivers/net/wwan/t7xx/t7xx_hif_cldma.h26
-rw-r--r--drivers/net/wwan/t7xx/t7xx_mhccif.h1
-rw-r--r--drivers/net/wwan/t7xx/t7xx_modem_ops.c92
-rw-r--r--drivers/net/wwan/t7xx/t7xx_modem_ops.h3
-rw-r--r--drivers/net/wwan/t7xx/t7xx_pci.c65
-rw-r--r--drivers/net/wwan/t7xx/t7xx_pci.h3
-rw-r--r--drivers/net/wwan/t7xx/t7xx_pci_rescan.c117
-rw-r--r--drivers/net/wwan/t7xx/t7xx_pci_rescan.h29
-rw-r--r--drivers/net/wwan/t7xx/t7xx_port.h12
-rw-r--r--drivers/net/wwan/t7xx/t7xx_port_ctrl_msg.c8
-rw-r--r--drivers/net/wwan/t7xx/t7xx_port_devlink.c705
-rw-r--r--drivers/net/wwan/t7xx/t7xx_port_devlink.h85
-rw-r--r--drivers/net/wwan/t7xx/t7xx_port_proxy.c132
-rw-r--r--drivers/net/wwan/t7xx/t7xx_port_proxy.h12
-rw-r--r--drivers/net/wwan/t7xx/t7xx_port_wwan.c9
-rw-r--r--drivers/net/wwan/t7xx/t7xx_reg.h31
-rw-r--r--drivers/net/wwan/t7xx/t7xx_state_monitor.c158
-rw-r--r--drivers/net/wwan/t7xx/t7xx_state_monitor.h4
-rw-r--r--drivers/net/wwan/t7xx/t7xx_uevent.c41
-rw-r--r--drivers/net/wwan/t7xx/t7xx_uevent.h39
25 files changed, 1706 insertions, 73 deletions
diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst
index e3a5f985673e..e22e643f7fcf 100644
--- a/Documentation/networking/devlink/index.rst
+++ b/Documentation/networking/devlink/index.rst
@@ -67,3 +67,4 @@ parameters, info versions, and other features it supports.
prestera
iosm
octeontx2
+ t7xx
diff --git a/Documentation/networking/devlink/t7xx.rst b/Documentation/networking/devlink/t7xx.rst
new file mode 100644
index 000000000000..c0c83ed2d38b
--- /dev/null
+++ b/Documentation/networking/devlink/t7xx.rst
@@ -0,0 +1,145 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================
+t7xx devlink support
+====================
+
+This document describes the devlink features implemented by the ``t7xx``
+device driver.
+
+Flash Update
+============
+
+The ``t7xx`` driver implements the flash update using the ``devlink-flash``
+interface.
+
+The driver uses DEVLINK_SUPPORT_FLASH_UPDATE_COMPONENT to identify the type of
+firmware image that need to be programmed upon the request by user space application.
+
+The supported list of firmware image types is described below.
+
+.. list-table:: Firmware Image types
+ :widths: 15 85
+
+ * - Name
+ - Description
+ * - ``preloader``
+ - The first-stage bootloader image
+ * - ``loader_ext1``
+ - Preloader extension image
+ * - ``tee1``
+ - ARM trusted firmware and TEE (Trusted Execution Environment) image
+ * - ``lk``
+ - The second-stage bootloader image
+ * - ``spmfw``
+ - MediaTek in-house ASIC for power management image
+ * - ``sspm_1``
+ - MediaTek in-house ASIC for power management under secure world image
+ * - ``mcupm_1``
+ - MediaTek in-house ASIC for cpu power management image
+ * - ``dpm_1``
+ - MediaTek in-house ASIC for dram power management image
+ * - ``boot``
+ - The kernel and dtb image
+ * - ``rootfs``
+ - Root filesystem image
+ * - ``md1img``
+ - Modem image
+ * - ``md1dsp``
+ - Modem DSP image
+ * - ``mcf1``
+ - Modem OTA image (Modem Configuration Framework) for operators
+ * - ``mcf2``
+ - Modem OTA image (Modem Configuration Framework) for OEM vendors
+ * - ``mcf3``
+ - Modem OTA image (other usage) for OEM configurations
+
+``t7xx`` driver uses fastboot protocol for fw flashing. In the fw flashing
+procedure, fastboot command's & response's are exchanged between driver
+and wwan device.
+
+The wwan device is put into fastboot mode via devlink reload command, by
+passing "driver_reinit" action.
+
+$ devlink dev reload pci/0000:$bdf action driver_reinit
+
+Upon completion of fw flashing or coredump collection the wwan device is
+reset to normal mode using devlink reload command, by passing "fw_activate"
+action.
+
+$ devlink dev reload pci/0000:$bdf action fw_activate
+
+Flash Commands:
+===============
+
+$ devlink dev flash pci/0000:$bdf file preloader_k6880v1_mdot2_datacard.bin component "preloader"
+
+$ devlink dev flash pci/0000:$bdf file loader_ext-verified.img component "loader_ext1"
+
+$ devlink dev flash pci/0000:$bdf file tee-verified.img component "tee1"
+
+$ devlink dev flash pci/0000:$bdf file lk-verified.img component "lk"
+
+$ devlink dev flash pci/0000:$bdf file spmfw-verified.img component "spmfw"
+
+$ devlink dev flash pci/0000:$bdf file sspm-verified.img component "sspm_1"
+
+$ devlink dev flash pci/0000:$bdf file mcupm-verified.img component "mcupm_1"
+
+$ devlink dev flash pci/0000:$bdf file dpm-verified.img component "dpm_1"
+
+$ devlink dev flash pci/0000:$bdf file boot-verified.img component "boot"
+
+$ devlink dev flash pci/0000:$bdf file root.squashfs component "rootfs"
+
+$ devlink dev flash pci/0000:$bdf file modem-verified.img component "md1img"
+
+$ devlink dev flash pci/0000:$bdf file dsp-verified.bin component "md1dsp"
+
+$ devlink dev flash pci/0000:$bdf file OP_OTA.img component "mcf1"
+
+$ devlink dev flash pci/0000:$bdf file OEM_OTA.img component "mcf2"
+
+$ devlink dev flash pci/0000:$bdf file DEV_OTA.img component "mcf3"
+
+Note: component "value" represents the partition type to be programmed.
+
+Regions
+=======
+
+The ``t7xx`` driver supports core dump collection when device encounters
+an exception. When wwan device encounters an exception, a snapshot of device
+internal data will be taken by the driver using fastboot commands.
+
+Following regions are accessed for device internal data.
+
+.. list-table:: Regions implemented
+ :widths: 15 85
+
+ * - Name
+ - Description
+ * - ``mr_dump``
+ - The detailed modem components log are captured in this region
+ * - ``lk_dump``
+ - This region dumps the current snapshot of lk
+
+
+Region commands
+===============
+
+$ devlink region show
+
+
+$ devlink region new mr_dump
+
+$ devlink region read mr_dump snapshot 0 address 0 length $len
+
+$ devlink region del mr_dump snapshot 0
+
+$ devlink region new lk_dump
+
+$ devlink region read lk_dump snapshot 0 address 0 length $len
+
+$ devlink region del lk_dump snapshot 0
+
+Note: $len is actual len to be dumped.
diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig
index 3486ffe94ac4..73b8cc1db0bd 100644
--- a/drivers/net/wwan/Kconfig
+++ b/drivers/net/wwan/Kconfig
@@ -108,6 +108,7 @@ config IOSM
config MTK_T7XX
tristate "MediaTek PCIe 5G WWAN modem T7xx device"
depends on PCI
+ select NET_DEVLINK
help
Enables MediaTek PCIe based 5G WWAN modem (T7xx series) device.
Adapts WWAN framework and provides network interface like wwan0
diff --git a/drivers/net/wwan/t7xx/Makefile b/drivers/net/wwan/t7xx/Makefile
index dc6a7d682c15..91ecabf29dd1 100644
--- a/drivers/net/wwan/t7xx/Makefile
+++ b/drivers/net/wwan/t7xx/Makefile
@@ -17,4 +17,7 @@ mtk_t7xx-y:= t7xx_pci.o \
t7xx_hif_dpmaif_tx.o \
t7xx_hif_dpmaif_rx.o \
t7xx_dpmaif.o \
- t7xx_netdev.o
+ t7xx_netdev.o \
+ t7xx_pci_rescan.o \
+ t7xx_uevent.o \
+ t7xx_port_devlink.o
diff --git a/drivers/net/wwan/t7xx/t7xx_hif_cldma.c b/drivers/net/wwan/t7xx/t7xx_hif_cldma.c
index 6ff30cb8eb16..f26e6138f187 100644
--- a/drivers/net/wwan/t7xx/t7xx_hif_cldma.c
+++ b/drivers/net/wwan/t7xx/t7xx_hif_cldma.c
@@ -57,8 +57,6 @@
#define CHECK_Q_STOP_TIMEOUT_US 1000000
#define CHECK_Q_STOP_STEP_US 10000
-#define CLDMA_JUMBO_BUFF_SZ (63 * 1024 + sizeof(struct ccci_header))
-
static void md_cd_queue_struct_reset(struct cldma_queue *queue, struct cldma_ctrl *md_ctrl,
enum mtk_txrx tx_rx, unsigned int index)
{
@@ -993,6 +991,34 @@ allow_sleep:
return ret;
}
+static void t7xx_cldma_adjust_config(struct cldma_ctrl *md_ctrl, enum cldma_cfg cfg_id)
+{
+ int qno;
+
+ for (qno = 0; qno < CLDMA_RXQ_NUM; qno++) {
+ md_ctrl->rx_ring[qno].pkt_size = CLDMA_SHARED_Q_BUFF_SZ;
+ md_ctrl->rxq[qno].q_type = CLDMA_SHARED_Q;
+ }
+
+ md_ctrl->rx_ring[CLDMA_RXQ_NUM - 1].pkt_size = CLDMA_JUMBO_BUFF_SZ;
+
+ for (qno = 0; qno < CLDMA_TXQ_NUM; qno++) {
+ md_ctrl->tx_ring[qno].pkt_size = CLDMA_SHARED_Q_BUFF_SZ;
+ md_ctrl->txq[qno].q_type = CLDMA_SHARED_Q;
+ }
+
+ if (cfg_id == CLDMA_DEDICATED_Q_CFG) {
+ md_ctrl->rxq[DOWNLOAD_PORT_ID].q_type = CLDMA_DEDICATED_Q;
+ md_ctrl->txq[DOWNLOAD_PORT_ID].q_type = CLDMA_DEDICATED_Q;
+ md_ctrl->tx_ring[DOWNLOAD_PORT_ID].pkt_size = CLDMA_DEDICATED_Q_BUFF_SZ;
+ md_ctrl->rx_ring[DOWNLOAD_PORT_ID].pkt_size = CLDMA_DEDICATED_Q_BUFF_SZ;
+ md_ctrl->rxq[DUMP_PORT_ID].q_type = CLDMA_DEDICATED_Q;
+ md_ctrl->txq[DUMP_PORT_ID].q_type = CLDMA_DEDICATED_Q;
+ md_ctrl->tx_ring[DUMP_PORT_ID].pkt_size = CLDMA_DEDICATED_Q_BUFF_SZ;
+ md_ctrl->rx_ring[DUMP_PORT_ID].pkt_size = CLDMA_DEDICATED_Q_BUFF_SZ;
+ }
+}
+
static int t7xx_cldma_late_init(struct cldma_ctrl *md_ctrl)
{
char dma_pool_name[32];
@@ -1021,11 +1047,6 @@ static int t7xx_cldma_late_init(struct cldma_ctrl *md_ctrl)
}
for (j = 0; j < CLDMA_RXQ_NUM; j++) {
- md_ctrl->rx_ring[j].pkt_size = CLDMA_MTU;
-
- if (j == CLDMA_RXQ_NUM - 1)
- md_ctrl->rx_ring[j].pkt_size = CLDMA_JUMBO_BUFF_SZ;
-
ret = t7xx_cldma_rx_ring_init(md_ctrl, &md_ctrl->rx_ring[j]);
if (ret) {
dev_err(md_ctrl->dev, "Control RX ring init fail\n");
@@ -1064,13 +1085,18 @@ static void t7xx_hw_info_init(struct cldma_ctrl *md_ctrl)
struct t7xx_cldma_hw *hw_info = &md_ctrl->hw_info;
u32 phy_ao_base, phy_pd_base;
- if (md_ctrl->hif_id != CLDMA_ID_MD)
- return;
-
- phy_ao_base = CLDMA1_AO_BASE;
- phy_pd_base = CLDMA1_PD_BASE;
- hw_info->phy_interrupt_id = CLDMA1_INT;
hw_info->hw_mode = MODE_BIT_64;
+
+ if (md_ctrl->hif_id == CLDMA_ID_MD) {
+ phy_ao_base = CLDMA1_AO_BASE;
+ phy_pd_base = CLDMA1_PD_BASE;
+ hw_info->phy_interrupt_id = CLDMA1_INT;
+ } else {
+ phy_ao_base = CLDMA0_AO_BASE;
+ phy_pd_base = CLDMA0_PD_BASE;
+ hw_info->phy_interrupt_id = CLDMA0_INT;
+ }
+
hw_info->ap_ao_base = t7xx_pcie_addr_transfer(pbase->pcie_ext_reg_base,
pbase->pcie_dev_reg_trsl_addr, phy_ao_base);
hw_info->ap_pdn_base = t7xx_pcie_addr_transfer(pbase->pcie_ext_reg_base,
@@ -1324,9 +1350,10 @@ err_workqueue:
return -ENOMEM;
}
-void t7xx_cldma_switch_cfg(struct cldma_ctrl *md_ctrl)
+void t7xx_cldma_switch_cfg(struct cldma_ctrl *md_ctrl, enum cldma_cfg cfg_id)
{
t7xx_cldma_late_release(md_ctrl);
+ t7xx_cldma_adjust_config(md_ctrl, cfg_id);
t7xx_cldma_late_init(md_ctrl);
}
diff --git a/drivers/net/wwan/t7xx/t7xx_hif_cldma.h b/drivers/net/wwan/t7xx/t7xx_hif_cldma.h
index 47a35e552da7..da3aa21c01eb 100644
--- a/drivers/net/wwan/t7xx/t7xx_hif_cldma.h
+++ b/drivers/net/wwan/t7xx/t7xx_hif_cldma.h
@@ -31,10 +31,14 @@
#include "t7xx_cldma.h"
#include "t7xx_pci.h"
+#define CLDMA_JUMBO_BUFF_SZ (63 * 1024 + sizeof(struct ccci_header))
+#define CLDMA_SHARED_Q_BUFF_SZ 3584
+#define CLDMA_DEDICATED_Q_BUFF_SZ 2048
+
/**
* enum cldma_id - Identifiers for CLDMA HW units.
* @CLDMA_ID_MD: Modem control channel.
- * @CLDMA_ID_AP: Application Processor control channel (not used at the moment).
+ * @CLDMA_ID_AP: Application Processor control channel.
* @CLDMA_NUM: Number of CLDMA HW units available.
*/
enum cldma_id {
@@ -55,6 +59,16 @@ struct cldma_gpd {
__le16 not_used2;
};
+enum cldma_queue_type {
+ CLDMA_SHARED_Q,
+ CLDMA_DEDICATED_Q,
+};
+
+enum cldma_cfg {
+ CLDMA_SHARED_Q_CFG,
+ CLDMA_DEDICATED_Q_CFG,
+};
+
struct cldma_request {
struct cldma_gpd *gpd; /* Virtual address for CPU */
dma_addr_t gpd_addr; /* Physical address for DMA */
@@ -77,6 +91,7 @@ struct cldma_queue {
struct cldma_request *tr_done;
struct cldma_request *rx_refill;
struct cldma_request *tx_next;
+ enum cldma_queue_type q_type;
int budget; /* Same as ring buffer size by default */
spinlock_t ring_lock;
wait_queue_head_t req_wq; /* Only for TX */
@@ -104,17 +119,20 @@ struct cldma_ctrl {
int (*recv_skb)(struct cldma_queue *queue, struct sk_buff *skb);
};
+enum cldma_txq_rxq_port_id {
+ DOWNLOAD_PORT_ID = 0,
+ DUMP_PORT_ID = 1
+};
+
#define GPD_FLAGS_HWO BIT(0)
#define GPD_FLAGS_IOC BIT(7)
#define GPD_DMAPOOL_ALIGN 16
-#define CLDMA_MTU 3584 /* 3.5kB */
-
int t7xx_cldma_alloc(enum cldma_id hif_id, struct t7xx_pci_dev *t7xx_dev);
void t7xx_cldma_hif_hw_init(struct cldma_ctrl *md_ctrl);
int t7xx_cldma_init(struct cldma_ctrl *md_ctrl);
void t7xx_cldma_exit(struct cldma_ctrl *md_ctrl);
-void t7xx_cldma_switch_cfg(struct cldma_ctrl *md_ctrl);
+void t7xx_cldma_switch_cfg(struct cldma_ctrl *md_ctrl, enum cldma_cfg cfg_id);
void t7xx_cldma_start(struct cldma_ctrl *md_ctrl);
int t7xx_cldma_stop(struct cldma_ctrl *md_ctrl);
void t7xx_cldma_reset(struct cldma_ctrl *md_ctrl);
diff --git a/drivers/net/wwan/t7xx/t7xx_mhccif.h b/drivers/net/wwan/t7xx/t7xx_mhccif.h
index 209b386bc088..20c50dce9fc3 100644
--- a/drivers/net/wwan/t7xx/t7xx_mhccif.h
+++ b/drivers/net/wwan/t7xx/t7xx_mhccif.h
@@ -25,6 +25,7 @@
D2H_INT_EXCEPTION_CLEARQ_DONE | \
D2H_INT_EXCEPTION_ALLQ_RESET | \
D2H_INT_PORT_ENUM | \
+ D2H_INT_ASYNC_AP_HK | \
D2H_INT_ASYNC_MD_HK)
void t7xx_mhccif_mask_set(struct t7xx_pci_dev *t7xx_dev, u32 val);
diff --git a/drivers/net/wwan/t7xx/t7xx_modem_ops.c b/drivers/net/wwan/t7xx/t7xx_modem_ops.c
index 3458af31e864..fb79d041dbf5 100644
--- a/drivers/net/wwan/t7xx/t7xx_modem_ops.c
+++ b/drivers/net/wwan/t7xx/t7xx_modem_ops.c
@@ -37,6 +37,7 @@
#include "t7xx_modem_ops.h"
#include "t7xx_netdev.h"
#include "t7xx_pci.h"
+#include "t7xx_pci_rescan.h"
#include "t7xx_pcie_mac.h"
#include "t7xx_port.h"
#include "t7xx_port_proxy.h"
@@ -44,6 +45,7 @@
#include "t7xx_state_monitor.h"
#define RT_ID_MD_PORT_ENUM 0
+#define RT_ID_AP_PORT_ENUM 1
/* Modem feature query identification code - "ICCC" */
#define MD_FEATURE_QUERY_ID 0x49434343
@@ -191,6 +193,10 @@ static irqreturn_t t7xx_rgu_isr_thread(int irq, void *data)
msleep(RGU_RESET_DELAY_MS);
t7xx_reset_device_via_pmic(t7xx_dev);
+
+ if (!t7xx_dev->hp_enable)
+ t7xx_rescan_queue_work(t7xx_dev->pdev);
+
return IRQ_HANDLED;
}
@@ -296,6 +302,7 @@ static void t7xx_md_exception(struct t7xx_modem *md, enum hif_ex_stage stage)
}
t7xx_cldma_exception(md->md_ctrl[CLDMA_ID_MD], stage);
+ t7xx_cldma_exception(md->md_ctrl[CLDMA_ID_AP], stage);
if (stage == HIF_EX_INIT)
t7xx_mhccif_h2d_swint_trigger(t7xx_dev, H2D_CH_EXCEPTION_ACK);
@@ -424,7 +431,7 @@ static int t7xx_parse_host_rt_data(struct t7xx_fsm_ctl *ctl, struct t7xx_sys_inf
if (ft_spt_st != MTK_FEATURE_MUST_BE_SUPPORTED)
return -EINVAL;
- if (i == RT_ID_MD_PORT_ENUM)
+ if (i == RT_ID_MD_PORT_ENUM || i == RT_ID_AP_PORT_ENUM)
t7xx_port_enum_msg_handler(ctl->md, rt_feature->data);
}
@@ -454,12 +461,12 @@ static int t7xx_core_reset(struct t7xx_modem *md)
return 0;
}
-static void t7xx_core_hk_handler(struct t7xx_modem *md, struct t7xx_fsm_ctl *ctl,
+static void t7xx_core_hk_handler(struct t7xx_modem *md, struct t7xx_sys_info *core_info,
+ struct t7xx_fsm_ctl *ctl,
enum t7xx_fsm_event_state event_id,
enum t7xx_fsm_event_state err_detect)
{
struct t7xx_fsm_event *event = NULL, *event_next;
- struct t7xx_sys_info *core_info = &md->core_md;
struct device *dev = &md->t7xx_dev->pdev->dev;
unsigned long flags;
int ret;
@@ -525,23 +532,37 @@ static void t7xx_md_hk_wq(struct work_struct *work)
/* Clear the HS2 EXIT event appended in core_reset() */
t7xx_fsm_clr_event(ctl, FSM_EVENT_MD_HS2_EXIT);
- t7xx_cldma_switch_cfg(md->md_ctrl[CLDMA_ID_MD]);
+ t7xx_cldma_switch_cfg(md->md_ctrl[CLDMA_ID_MD], CLDMA_SHARED_Q_CFG);
t7xx_cldma_start(md->md_ctrl[CLDMA_ID_MD]);
t7xx_fsm_broadcast_state(ctl, MD_STATE_WAITING_FOR_HS2);
md->core_md.handshake_ongoing = true;
- t7xx_core_hk_handler(md, ctl, FSM_EVENT_MD_HS2, FSM_EVENT_MD_HS2_EXIT);
+ t7xx_core_hk_handler(md, &md->core_md, ctl, FSM_EVENT_MD_HS2, FSM_EVENT_MD_HS2_EXIT);
+}
+
+static void t7xx_ap_hk_wq(struct work_struct *work)
+{
+ struct t7xx_modem *md = container_of(work, struct t7xx_modem, ap_handshake_work);
+ struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
+
+ /* Clear the HS2 EXIT event appended in t7xx_core_reset(). */
+ t7xx_fsm_clr_event(ctl, FSM_EVENT_AP_HS2_EXIT);
+ t7xx_cldma_stop(md->md_ctrl[CLDMA_ID_AP]);
+ t7xx_cldma_switch_cfg(md->md_ctrl[CLDMA_ID_AP], CLDMA_SHARED_Q_CFG);
+ t7xx_cldma_start(md->md_ctrl[CLDMA_ID_AP]);
+ md->core_ap.handshake_ongoing = true;
+ t7xx_core_hk_handler(md, &md->core_ap, ctl, FSM_EVENT_AP_HS2, FSM_EVENT_AP_HS2_EXIT);
}
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);
+ t7xx_mhccif_mask_clr(md->t7xx_dev, D2H_INT_PORT_ENUM | D2H_INT_ASYNC_MD_HK |
+ D2H_INT_ASYNC_AP_HK);
break;
case FSM_START:
@@ -554,16 +575,26 @@ void t7xx_md_event_notify(struct t7xx_modem *md, enum md_event_id evt_id)
ctl->exp_flg = true;
md->exp_id &= ~D2H_INT_EXCEPTION_INIT;
md->exp_id &= ~D2H_INT_ASYNC_MD_HK;
+ md->exp_id &= ~D2H_INT_ASYNC_AP_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);
+ md->exp_id &= ~D2H_INT_ASYNC_AP_HK;
} else {
- t7xx_mhccif_mask_clr(md->t7xx_dev, D2H_INT_ASYNC_MD_HK);
+ void __iomem *mhccif_base = md->t7xx_dev->base_addr.mhccif_rc_base;
+
+ 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;
+ 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);
+ }
+
+ if (md->exp_id & D2H_INT_ASYNC_AP_HK) {
+ queue_work(md->ap_handshake_wq, &md->ap_handshake_work);
+ md->exp_id &= ~D2H_INT_ASYNC_AP_HK;
+ iowrite32(D2H_INT_ASYNC_AP_HK, mhccif_base + REG_EP2RC_SW_INT_ACK);
+ t7xx_mhccif_mask_set(md->t7xx_dev, D2H_INT_ASYNC_AP_HK);
+ }
}
spin_unlock_irqrestore(&md->exp_lock, flags);
@@ -576,6 +607,7 @@ void t7xx_md_event_notify(struct t7xx_modem *md, enum md_event_id evt_id)
case FSM_READY:
t7xx_mhccif_mask_set(md->t7xx_dev, D2H_INT_ASYNC_MD_HK);
+ t7xx_mhccif_mask_set(md->t7xx_dev, D2H_INT_ASYNC_AP_HK);
break;
default:
@@ -627,6 +659,19 @@ static struct t7xx_modem *t7xx_md_alloc(struct t7xx_pci_dev *t7xx_dev)
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);
+
+ md->ap_handshake_wq = alloc_workqueue("%s", WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI,
+ 0, "ap_hk_wq");
+ if (!md->ap_handshake_wq) {
+ destroy_workqueue(md->handshake_wq);
+ return NULL;
+ }
+
+ INIT_WORK(&md->ap_handshake_work, t7xx_ap_hk_wq);
+ md->core_ap.feature_set[RT_ID_AP_PORT_ENUM] &= ~FEATURE_MSK;
+ md->core_ap.feature_set[RT_ID_AP_PORT_ENUM] |=
+ FIELD_PREP(FEATURE_MSK, MTK_FEATURE_MUST_BE_SUPPORTED);
+
return md;
}
@@ -638,6 +683,7 @@ int t7xx_md_reset(struct t7xx_pci_dev *t7xx_dev)
md->exp_id = 0;
t7xx_fsm_reset(md);
t7xx_cldma_reset(md->md_ctrl[CLDMA_ID_MD]);
+ t7xx_cldma_reset(md->md_ctrl[CLDMA_ID_AP]);
t7xx_port_proxy_reset(md->port_prox);
md->md_init_finish = true;
return t7xx_core_reset(md);
@@ -667,6 +713,10 @@ int t7xx_md_init(struct t7xx_pci_dev *t7xx_dev)
if (ret)
goto err_destroy_hswq;
+ ret = t7xx_cldma_alloc(CLDMA_ID_AP, t7xx_dev);
+ if (ret)
+ goto err_destroy_hswq;
+
ret = t7xx_fsm_init(md);
if (ret)
goto err_destroy_hswq;
@@ -679,12 +729,16 @@ int t7xx_md_init(struct t7xx_pci_dev *t7xx_dev)
if (ret)
goto err_uninit_ccmni;
- ret = t7xx_port_proxy_init(md);
+ ret = t7xx_cldma_init(md->md_ctrl[CLDMA_ID_AP]);
if (ret)
goto err_uninit_md_cldma;
+ ret = t7xx_port_proxy_init(md);
+ if (ret)
+ goto err_uninit_ap_cldma;
+
ret = t7xx_fsm_append_cmd(md->fsm_ctl, FSM_CMD_START, 0);
- if (ret) /* fsm_uninit flushes cmd queue */
+ if (ret) /* t7xx_fsm_uninit() flushes cmd queue */
goto err_uninit_proxy;
t7xx_md_sys_sw_init(t7xx_dev);
@@ -694,6 +748,9 @@ int t7xx_md_init(struct t7xx_pci_dev *t7xx_dev)
err_uninit_proxy:
t7xx_port_proxy_uninit(md->port_prox);
+err_uninit_ap_cldma:
+ t7xx_cldma_exit(md->md_ctrl[CLDMA_ID_AP]);
+
err_uninit_md_cldma:
t7xx_cldma_exit(md->md_ctrl[CLDMA_ID_MD]);
@@ -705,6 +762,7 @@ err_uninit_fsm:
err_destroy_hswq:
destroy_workqueue(md->handshake_wq);
+ destroy_workqueue(md->ap_handshake_wq);
dev_err(&t7xx_dev->pdev->dev, "Modem init failed\n");
return ret;
}
@@ -720,8 +778,10 @@ void t7xx_md_exit(struct t7xx_pci_dev *t7xx_dev)
t7xx_fsm_append_cmd(md->fsm_ctl, FSM_CMD_PRE_STOP, FSM_CMD_FLAG_WAIT_FOR_COMPLETION);
t7xx_port_proxy_uninit(md->port_prox);
+ t7xx_cldma_exit(md->md_ctrl[CLDMA_ID_AP]);
t7xx_cldma_exit(md->md_ctrl[CLDMA_ID_MD]);
t7xx_ccmni_exit(t7xx_dev);
t7xx_fsm_uninit(md);
destroy_workqueue(md->handshake_wq);
+ destroy_workqueue(md->ap_handshake_wq);
}
diff --git a/drivers/net/wwan/t7xx/t7xx_modem_ops.h b/drivers/net/wwan/t7xx/t7xx_modem_ops.h
index 7469ed636ae8..c93e870ce696 100644
--- a/drivers/net/wwan/t7xx/t7xx_modem_ops.h
+++ b/drivers/net/wwan/t7xx/t7xx_modem_ops.h
@@ -66,10 +66,13 @@ struct t7xx_modem {
struct cldma_ctrl *md_ctrl[CLDMA_NUM];
struct t7xx_pci_dev *t7xx_dev;
struct t7xx_sys_info core_md;
+ struct t7xx_sys_info core_ap;
bool md_init_finish;
bool rgu_irq_asserted;
struct workqueue_struct *handshake_wq;
struct work_struct handshake_work;
+ struct workqueue_struct *ap_handshake_wq;
+ struct work_struct ap_handshake_work;
struct t7xx_fsm_ctl *fsm_ctl;
struct port_proxy *port_prox;
unsigned int exp_id;
diff --git a/drivers/net/wwan/t7xx/t7xx_pci.c b/drivers/net/wwan/t7xx/t7xx_pci.c
index 871f2a27a398..14cdf00cac8e 100644
--- a/drivers/net/wwan/t7xx/t7xx_pci.c
+++ b/drivers/net/wwan/t7xx/t7xx_pci.c
@@ -38,7 +38,9 @@
#include "t7xx_mhccif.h"
#include "t7xx_modem_ops.h"
#include "t7xx_pci.h"
+#include "t7xx_pci_rescan.h"
#include "t7xx_pcie_mac.h"
+#include "t7xx_port_devlink.h"
#include "t7xx_reg.h"
#include "t7xx_state_monitor.h"
@@ -703,22 +705,33 @@ static int t7xx_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
t7xx_pci_infracfg_ao_calc(t7xx_dev);
t7xx_mhccif_init(t7xx_dev);
- ret = t7xx_md_init(t7xx_dev);
+ ret = t7xx_devlink_register(t7xx_dev);
if (ret)
return ret;
+ ret = t7xx_md_init(t7xx_dev);
+ if (ret)
+ goto err_devlink_unregister;
+
t7xx_pcie_mac_interrupts_dis(t7xx_dev);
ret = t7xx_interrupt_init(t7xx_dev);
if (ret) {
t7xx_md_exit(t7xx_dev);
- return ret;
+ goto err_devlink_unregister;
}
+ t7xx_rescan_done();
t7xx_pcie_mac_set_int(t7xx_dev, MHCCIF_INT);
t7xx_pcie_mac_interrupts_en(t7xx_dev);
+ if (!t7xx_dev->hp_enable)
+ pci_ignore_hotplug(pdev);
return 0;
+
+err_devlink_unregister:
+ t7xx_devlink_unregister(t7xx_dev);
+ return ret;
}
static void t7xx_pci_remove(struct pci_dev *pdev)
@@ -728,6 +741,7 @@ static void t7xx_pci_remove(struct pci_dev *pdev)
t7xx_dev = pci_get_drvdata(pdev);
t7xx_md_exit(t7xx_dev);
+ t7xx_devlink_unregister(t7xx_dev);
for (i = 0; i < EXT_INT_NUM; i++) {
if (!t7xx_dev->intr_handler[i])
@@ -754,7 +768,52 @@ static struct pci_driver t7xx_pci_driver = {
.shutdown = t7xx_pci_shutdown,
};
-module_pci_driver(t7xx_pci_driver);
+static int __init t7xx_pci_init(void)
+{
+ int ret;
+
+ t7xx_pci_dev_rescan();
+ ret = t7xx_rescan_init();
+ if (ret) {
+ pr_err("Failed to init t7xx rescan work\n");
+ return ret;
+ }
+
+ return pci_register_driver(&t7xx_pci_driver);
+}
+module_init(t7xx_pci_init);
+
+static int t7xx_always_match(struct device *dev, const void *data)
+{
+ return dev->parent->fwnode == data;
+}
+
+static void __exit t7xx_pci_cleanup(void)
+{
+ int remove_flag = 0;
+ struct device *dev;
+
+ dev = driver_find_device(&t7xx_pci_driver.driver, NULL, NULL, t7xx_always_match);
+ if (dev) {
+ pr_debug("unregister t7xx PCIe driver while device is still exist.\n");
+ put_device(dev);
+ remove_flag = 1;
+ } else {
+ pr_debug("no t7xx PCIe driver found.\n");
+ }
+
+ pci_lock_rescan_remove();
+ pci_unregister_driver(&t7xx_pci_driver);
+ pci_unlock_rescan_remove();
+ t7xx_rescan_deinit();
+
+ if (remove_flag) {
+ pr_debug("remove t7xx PCI device\n");
+ pci_stop_and_remove_bus_device_locked(to_pci_dev(dev));
+ }
+}
+
+module_exit(t7xx_pci_cleanup);
MODULE_AUTHOR("MediaTek Inc");
MODULE_DESCRIPTION("MediaTek PCIe 5G WWAN modem T7xx driver");
diff --git a/drivers/net/wwan/t7xx/t7xx_pci.h b/drivers/net/wwan/t7xx/t7xx_pci.h
index 50b37056ce5a..1017d21aad59 100644
--- a/drivers/net/wwan/t7xx/t7xx_pci.h
+++ b/drivers/net/wwan/t7xx/t7xx_pci.h
@@ -59,6 +59,7 @@ typedef irqreturn_t (*t7xx_intr_callback)(int irq, void *param);
* @md_pm_lock: protects PCIe sleep lock
* @sleep_disable_count: PCIe L1.2 lock counter
* @sleep_lock_acquire: indicates that sleep has been disabled
+ * @dl: devlink struct
*/
struct t7xx_pci_dev {
t7xx_intr_callback intr_handler[EXT_INT_NUM];
@@ -69,6 +70,7 @@ struct t7xx_pci_dev {
struct t7xx_modem *md;
struct t7xx_ccmni_ctrl *ccmni_ctlb;
bool rgu_pci_irq_en;
+ bool hp_enable;
/* Low Power Items */
struct list_head md_pm_entities;
@@ -78,6 +80,7 @@ struct t7xx_pci_dev {
spinlock_t md_pm_lock; /* Protects PCI resource lock */
unsigned int sleep_disable_count;
struct completion sleep_lock_acquire;
+ struct t7xx_devlink *dl;
};
enum t7xx_pm_id {
diff --git a/drivers/net/wwan/t7xx/t7xx_pci_rescan.c b/drivers/net/wwan/t7xx/t7xx_pci_rescan.c
new file mode 100644
index 000000000000..045777d8a843
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_pci_rescan.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021-2022, Intel Corporation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ":t7xx:%s: " fmt, __func__
+#define dev_fmt(fmt) "t7xx: " fmt
+
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+#include "t7xx_pci.h"
+#include "t7xx_pci_rescan.h"
+
+static struct remove_rescan_context g_mtk_rescan_context;
+
+void t7xx_pci_dev_rescan(void)
+{
+ struct pci_bus *b = NULL;
+
+ pci_lock_rescan_remove();
+ while ((b = pci_find_next_bus(b)))
+ pci_rescan_bus(b);
+
+ pci_unlock_rescan_remove();
+}
+
+void t7xx_rescan_done(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&g_mtk_rescan_context.dev_lock, flags);
+ if (g_mtk_rescan_context.rescan_done == 0) {
+ pr_debug("this is a rescan probe\n");
+ g_mtk_rescan_context.rescan_done = 1;
+ } else {
+ pr_debug("this is a init probe\n");
+ }
+ spin_unlock_irqrestore(&g_mtk_rescan_context.dev_lock, flags);
+}
+
+static void t7xx_remove_rescan(struct work_struct *work)
+{
+ struct pci_dev *pdev;
+ int num_retries = RESCAN_RETRIES;
+ unsigned long flags;
+
+ spin_lock_irqsave(&g_mtk_rescan_context.dev_lock, flags);
+ g_mtk_rescan_context.rescan_done = 0;
+ pdev = g_mtk_rescan_context.dev;
+ spin_unlock_irqrestore(&g_mtk_rescan_context.dev_lock, flags);
+
+ if (pdev) {
+ pci_stop_and_remove_bus_device_locked(pdev);
+ pr_debug("start remove and rescan flow\n");
+ }
+
+ do {
+ t7xx_pci_dev_rescan();
+ spin_lock_irqsave(&g_mtk_rescan_context.dev_lock, flags);
+ if (g_mtk_rescan_context.rescan_done) {
+ spin_unlock_irqrestore(&g_mtk_rescan_context.dev_lock, flags);
+ break;
+ }
+
+ spin_unlock_irqrestore(&g_mtk_rescan_context.dev_lock, flags);
+ msleep(DELAY_RESCAN_MTIME);
+ } while (num_retries--);
+}
+
+void t7xx_rescan_queue_work(struct pci_dev *pdev)
+{
+ unsigned long flags;
+
+ dev_info(&pdev->dev, "start queue_mtk_rescan_work\n");
+ spin_lock_irqsave(&g_mtk_rescan_context.dev_lock, flags);
+ if (!g_mtk_rescan_context.rescan_done) {
+ dev_err(&pdev->dev, "rescan failed because last rescan undone\n");
+ spin_unlock_irqrestore(&g_mtk_rescan_context.dev_lock, flags);
+ return;
+ }
+
+ g_mtk_rescan_context.dev = pdev;
+ spin_unlock_irqrestore(&g_mtk_rescan_context.dev_lock, flags);
+ queue_work(g_mtk_rescan_context.pcie_rescan_wq, &g_mtk_rescan_context.service_task);
+}
+
+int t7xx_rescan_init(void)
+{
+ spin_lock_init(&g_mtk_rescan_context.dev_lock);
+ g_mtk_rescan_context.rescan_done = 1;
+ g_mtk_rescan_context.dev = NULL;
+ g_mtk_rescan_context.pcie_rescan_wq = create_singlethread_workqueue(MTK_RESCAN_WQ);
+ if (!g_mtk_rescan_context.pcie_rescan_wq) {
+ pr_err("Failed to create workqueue: %s\n", MTK_RESCAN_WQ);
+ return -ENOMEM;
+ }
+
+ INIT_WORK(&g_mtk_rescan_context.service_task, t7xx_remove_rescan);
+
+ return 0;
+}
+
+void t7xx_rescan_deinit(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&g_mtk_rescan_context.dev_lock, flags);
+ g_mtk_rescan_context.rescan_done = 0;
+ g_mtk_rescan_context.dev = NULL;
+ spin_unlock_irqrestore(&g_mtk_rescan_context.dev_lock, flags);
+ cancel_work_sync(&g_mtk_rescan_context.service_task);
+ destroy_workqueue(g_mtk_rescan_context.pcie_rescan_wq);
+}
diff --git a/drivers/net/wwan/t7xx/t7xx_pci_rescan.h b/drivers/net/wwan/t7xx/t7xx_pci_rescan.h
new file mode 100644
index 000000000000..de4ca1363bb0
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_pci_rescan.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2021, MediaTek Inc.
+ * Copyright (c) 2021-2022, Intel Corporation.
+ */
+
+#ifndef __T7XX_PCI_RESCAN_H__
+#define __T7XX_PCI_RESCAN_H__
+
+#define MTK_RESCAN_WQ "mtk_rescan_wq"
+
+#define DELAY_RESCAN_MTIME 1000
+#define RESCAN_RETRIES 35
+
+struct remove_rescan_context {
+ struct work_struct service_task;
+ struct workqueue_struct *pcie_rescan_wq;
+ spinlock_t dev_lock; /* protects device */
+ struct pci_dev *dev;
+ int rescan_done;
+};
+
+void t7xx_pci_dev_rescan(void);
+void t7xx_rescan_queue_work(struct pci_dev *pdev);
+int t7xx_rescan_init(void);
+void t7xx_rescan_deinit(void);
+void t7xx_rescan_done(void);
+
+#endif /* __T7XX_PCI_RESCAN_H__ */
diff --git a/drivers/net/wwan/t7xx/t7xx_port.h b/drivers/net/wwan/t7xx/t7xx_port.h
index dc4133eb433a..070097a658d1 100644
--- a/drivers/net/wwan/t7xx/t7xx_port.h
+++ b/drivers/net/wwan/t7xx/t7xx_port.h
@@ -36,9 +36,15 @@
/* Channel ID and Message ID definitions.
* The channel number consists of peer_id(15:12) , channel_id(11:0)
* peer_id:
- * 0:reserved, 1: to sAP, 2: to MD
+ * 0:reserved, 1: to AP, 2: to MD
*/
enum port_ch {
+ /* to AP */
+ PORT_CH_AP_CONTROL_RX = 0x1000,
+ PORT_CH_AP_CONTROL_TX = 0x1001,
+ PORT_CH_AP_LOG_RX = 0x1008,
+ PORT_CH_AP_LOG_TX = 0x1009,
+
/* to MD */
PORT_CH_CONTROL_RX = 0x2000,
PORT_CH_CONTROL_TX = 0x2001,
@@ -94,6 +100,7 @@ struct t7xx_port_conf {
struct port_ops *ops;
char *name;
enum wwan_port_type port_type;
+ bool is_early_port;
};
struct t7xx_port {
@@ -122,11 +129,14 @@ struct t7xx_port {
int rx_length_th;
bool chan_enable;
struct task_struct *thread;
+ struct t7xx_devlink *dl;
};
+int t7xx_get_port_mtu(struct t7xx_port *port);
struct sk_buff *t7xx_port_alloc_skb(int payload);
struct sk_buff *t7xx_ctrl_alloc_skb(int payload);
int t7xx_port_enqueue_skb(struct t7xx_port *port, struct sk_buff *skb);
+int t7xx_port_send_raw_skb(struct t7xx_port *port, struct sk_buff *skb);
int t7xx_port_send_skb(struct t7xx_port *port, struct sk_buff *skb, unsigned int pkt_header,
unsigned int ex_msg);
int t7xx_port_send_ctl_skb(struct t7xx_port *port, struct sk_buff *skb, unsigned int msg,
diff --git a/drivers/net/wwan/t7xx/t7xx_port_ctrl_msg.c b/drivers/net/wwan/t7xx/t7xx_port_ctrl_msg.c
index 68430b130a67..ae632ef96698 100644
--- a/drivers/net/wwan/t7xx/t7xx_port_ctrl_msg.c
+++ b/drivers/net/wwan/t7xx/t7xx_port_ctrl_msg.c
@@ -167,8 +167,12 @@ static int control_msg_handler(struct t7xx_port *port, struct sk_buff *skb)
case CTL_ID_HS2_MSG:
skb_pull(skb, sizeof(*ctrl_msg_h));
- if (port_conf->rx_ch == PORT_CH_CONTROL_RX) {
- ret = t7xx_fsm_append_event(ctl, FSM_EVENT_MD_HS2, skb->data,
+ if (port_conf->rx_ch == PORT_CH_CONTROL_RX ||
+ port_conf->rx_ch == PORT_CH_AP_CONTROL_RX) {
+ int event = port_conf->rx_ch == PORT_CH_CONTROL_RX ?
+ FSM_EVENT_MD_HS2 : FSM_EVENT_AP_HS2;
+
+ ret = t7xx_fsm_append_event(ctl, event, skb->data,
le32_to_cpu(ctrl_msg_h->data_length));
if (ret)
dev_err(port->dev, "Failed to append Handshake 2 event");
diff --git a/drivers/net/wwan/t7xx/t7xx_port_devlink.c b/drivers/net/wwan/t7xx/t7xx_port_devlink.c
new file mode 100644
index 000000000000..026a1db42f69
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_port_devlink.c
@@ -0,0 +1,705 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, Intel Corporation.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/vmalloc.h>
+
+#include "t7xx_hif_cldma.h"
+#include "t7xx_pci_rescan.h"
+#include "t7xx_port_devlink.h"
+#include "t7xx_port_proxy.h"
+#include "t7xx_state_monitor.h"
+#include "t7xx_uevent.h"
+
+static struct t7xx_devlink_region_info t7xx_devlink_region_list[T7XX_TOTAL_REGIONS] = {
+ {"mr_dump", T7XX_MRDUMP_SIZE},
+ {"lk_dump", T7XX_LKDUMP_SIZE},
+};
+
+static int t7xx_devlink_port_read(struct t7xx_port *port, char *buf, size_t count)
+{
+ int ret = 0, read_len;
+ struct sk_buff *skb;
+
+ spin_lock_irq(&port->rx_wq.lock);
+ if (skb_queue_empty(&port->rx_skb_list)) {
+ ret = wait_event_interruptible_locked_irq(port->rx_wq,
+ !skb_queue_empty(&port->rx_skb_list));
+ if (ret == -ERESTARTSYS) {
+ spin_unlock_irq(&port->rx_wq.lock);
+ return -EINTR;
+ }
+ }
+ skb = skb_dequeue(&port->rx_skb_list);
+ spin_unlock_irq(&port->rx_wq.lock);
+
+ read_len = count > skb->len ? skb->len : count;
+ memcpy(buf, skb->data, read_len);
+ dev_kfree_skb(skb);
+
+ return ret ? ret : read_len;
+}
+
+static int t7xx_devlink_port_write(struct t7xx_port *port, const char *buf, size_t count)
+{
+ const struct t7xx_port_conf *port_conf = port->port_conf;
+ size_t actual_count;
+ struct sk_buff *skb;
+ int ret, txq_mtu;
+
+ txq_mtu = t7xx_get_port_mtu(port);
+ if (txq_mtu < 0)
+ return -EINVAL;
+
+ actual_count = count > txq_mtu ? txq_mtu : count;
+ skb = __dev_alloc_skb(actual_count, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ skb_put_data(skb, buf, actual_count);
+ ret = t7xx_port_send_raw_skb(port, skb);
+ if (ret) {
+ dev_err(port->dev, "write error on %s, size: %zu, ret: %d\n",
+ port_conf->name, actual_count, ret);
+ dev_kfree_skb(skb);
+ return ret;
+ }
+
+ return actual_count;
+}
+
+static int t7xx_devlink_fb_handle_response(struct t7xx_port *port, int *data)
+{
+ int ret = 0, index = 0, return_data = 0, read_bytes;
+ char status[T7XX_FB_RESPONSE_SIZE + 1];
+
+ while (index < T7XX_FB_RESP_COUNT) {
+ index++;
+ read_bytes = t7xx_devlink_port_read(port, status, T7XX_FB_RESPONSE_SIZE);
+ if (read_bytes < 0) {
+ dev_err(port->dev, "status read failed");
+ ret = -EIO;
+ break;
+ }
+
+ status[read_bytes] = '\0';
+ if (!strncmp(status, T7XX_FB_RESP_INFO, strlen(T7XX_FB_RESP_INFO))) {
+ break;
+ } else if (!strncmp(status, T7XX_FB_RESP_OKAY, strlen(T7XX_FB_RESP_OKAY))) {
+ break;
+ } else if (!strncmp(status, T7XX_FB_RESP_FAIL, strlen(T7XX_FB_RESP_FAIL))) {
+ ret = -EPROTO;
+ break;
+ } else if (!strncmp(status, T7XX_FB_RESP_DATA, strlen(T7XX_FB_RESP_DATA))) {
+ if (data) {
+ if (!kstrtoint(status + strlen(T7XX_FB_RESP_DATA), 16,
+ &return_data)) {
+ *data = return_data;
+ } else {
+ dev_err(port->dev, "kstrtoint error!\n");
+ ret = -EPROTO;
+ }
+ }
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int t7xx_devlink_fb_raw_command(char *cmd, struct t7xx_port *port, int *data)
+{
+ int ret, cmd_size = strlen(cmd);
+
+ if (cmd_size > T7XX_FB_COMMAND_SIZE) {
+ dev_err(port->dev, "command length %d is long\n", cmd_size);
+ return -EINVAL;
+ }
+
+ if (cmd_size != t7xx_devlink_port_write(port, cmd, cmd_size)) {
+ dev_err(port->dev, "raw command = %s write failed\n", cmd);
+ return -EIO;
+ }
+
+ dev_dbg(port->dev, "raw command = %s written to the device\n", cmd);
+ ret = t7xx_devlink_fb_handle_response(port, data);
+ if (ret)
+ dev_err(port->dev, "raw command = %s response FAILURE:%d\n", cmd, ret);
+
+ return ret;
+}
+
+static int t7xx_devlink_fb_send_buffer(struct t7xx_port *port, const u8 *buf, size_t size)
+{
+ size_t remaining = size, offset = 0, len;
+ int write_done;
+
+ if (!size)
+ return -EINVAL;
+
+ while (remaining) {
+ len = min_t(size_t, remaining, CLDMA_DEDICATED_Q_BUFF_SZ);
+ write_done = t7xx_devlink_port_write(port, buf + offset, len);
+
+ if (write_done < 0) {
+ dev_err(port->dev, "write to device failed in %s", __func__);
+ return -EIO;
+ } else if (write_done != len) {
+ dev_err(port->dev, "write Error. Only %d/%zu bytes written",
+ write_done, len);
+ return -EIO;
+ }
+
+ remaining -= len;
+ offset += len;
+ }
+
+ return 0;
+}
+
+static int t7xx_devlink_fb_download_command(struct t7xx_port *port, size_t size)
+{
+ char download_command[T7XX_FB_COMMAND_SIZE];
+
+ snprintf(download_command, sizeof(download_command), "%s:%08zx",
+ T7XX_FB_CMD_DOWNLOAD, size);
+ return t7xx_devlink_fb_raw_command(download_command, port, NULL);
+}
+
+static int t7xx_devlink_fb_download(struct t7xx_port *port, const u8 *buf, size_t size)
+{
+ int ret;
+
+ if (size <= 0 || size > SIZE_MAX) {
+ dev_err(port->dev, "file is too large to download");
+ return -EINVAL;
+ }
+
+ ret = t7xx_devlink_fb_download_command(port, size);
+ if (ret)
+ return ret;
+
+ ret = t7xx_devlink_fb_send_buffer(port, buf, size);
+ if (ret)
+ return ret;
+
+ return t7xx_devlink_fb_handle_response(port, NULL);
+}
+
+static int t7xx_devlink_fb_flash(const char *cmd, struct t7xx_port *port)
+{
+ char flash_command[T7XX_FB_COMMAND_SIZE];
+
+ snprintf(flash_command, sizeof(flash_command), "%s:%s", T7XX_FB_CMD_FLASH, cmd);
+ return t7xx_devlink_fb_raw_command(flash_command, port, NULL);
+}
+
+static int t7xx_devlink_fb_flash_partition(const char *partition, const u8 *buf,
+ struct t7xx_port *port, size_t size)
+{
+ int ret;
+
+ ret = t7xx_devlink_fb_download(port, buf, size);
+ if (ret)
+ return ret;
+
+ return t7xx_devlink_fb_flash(partition, port);
+}
+
+static int t7xx_devlink_fb_get_core(struct t7xx_port *port)
+{
+ struct t7xx_devlink_region_info *mrdump_region;
+ char mrdump_complete_event[T7XX_FB_EVENT_SIZE];
+ u32 mrd_mb = T7XX_MRDUMP_SIZE / (1024 * 1024);
+ struct t7xx_devlink *dl = port->dl;
+ int clen, dlen = 0, result = 0;
+ unsigned long long zipsize = 0;
+ char mcmd[T7XX_FB_MCMD_SIZE];
+ size_t offset_dlen = 0;
+ char *mdata;
+
+ set_bit(T7XX_MRDUMP_STATUS, &dl->status);
+ mdata = kmalloc(T7XX_FB_MDATA_SIZE, GFP_KERNEL);
+ if (!mdata) {
+ result = -ENOMEM;
+ goto get_core_exit;
+ }
+
+ mrdump_region = dl->dl_region_info[T7XX_MRDUMP_INDEX];
+ mrdump_region->dump = vmalloc(mrdump_region->default_size);
+ if (!mrdump_region->dump) {
+ kfree(mdata);
+ result = -ENOMEM;
+ goto get_core_exit;
+ }
+
+ result = t7xx_devlink_fb_raw_command(T7XX_FB_CMD_OEM_MRDUMP, port, NULL);
+ if (result) {
+ dev_err(port->dev, "%s command failed\n", T7XX_FB_CMD_OEM_MRDUMP);
+ vfree(mrdump_region->dump);
+ kfree(mdata);
+ goto get_core_exit;
+ }
+
+ while (mrdump_region->default_size > offset_dlen) {
+ clen = t7xx_devlink_port_read(port, mcmd, sizeof(mcmd));
+ if (clen == strlen(T7XX_FB_CMD_RTS) &&
+ (!strncmp(mcmd, T7XX_FB_CMD_RTS, strlen(T7XX_FB_CMD_RTS)))) {
+ memset(mdata, 0, T7XX_FB_MDATA_SIZE);
+ dlen = 0;
+ memset(mcmd, 0, sizeof(mcmd));
+ clen = snprintf(mcmd, sizeof(mcmd), "%s", T7XX_FB_CMD_CTS);
+
+ if (t7xx_devlink_port_write(port, mcmd, clen) != clen) {
+ dev_err(port->dev, "write for _CTS failed:%d\n", clen);
+ goto get_core_free_mem;
+ }
+
+ dlen = t7xx_devlink_port_read(port, mdata, T7XX_FB_MDATA_SIZE);
+ if (dlen <= 0) {
+ dev_err(port->dev, "read data error(%d)\n", dlen);
+ goto get_core_free_mem;
+ }
+
+ zipsize += (unsigned long long)(dlen);
+ memcpy(mrdump_region->dump + offset_dlen, mdata, dlen);
+ offset_dlen += dlen;
+ memset(mcmd, 0, sizeof(mcmd));
+ clen = snprintf(mcmd, sizeof(mcmd), "%s", T7XX_FB_CMD_FIN);
+ if (t7xx_devlink_port_write(port, mcmd, clen) != clen) {
+ dev_err(port->dev, "%s: _FIN failed, (Read %05d:%05llu)\n",
+ __func__, clen, zipsize);
+ goto get_core_free_mem;
+ }
+ } else if ((clen == strlen(T7XX_FB_RESP_MRDUMP_DONE)) &&
+ (!strncmp(mcmd, T7XX_FB_RESP_MRDUMP_DONE,
+ strlen(T7XX_FB_RESP_MRDUMP_DONE)))) {
+ dev_dbg(port->dev, "%s! size:%zd\n", T7XX_FB_RESP_MRDUMP_DONE, offset_dlen);
+ mrdump_region->actual_size = offset_dlen;
+ snprintf(mrdump_complete_event, sizeof(mrdump_complete_event),
+ "%s size=%zu", T7XX_UEVENT_MRDUMP_READY, offset_dlen);
+ t7xx_uevent_send(dl->dev, mrdump_complete_event);
+ kfree(mdata);
+ result = 0;
+ goto get_core_exit;
+ } else {
+ dev_err(port->dev, "getcore protocol error (read len %05d)\n", clen);
+ goto get_core_free_mem;
+ }
+ }
+
+ dev_err(port->dev, "mrdump exceeds %uMB size. Discarded!", mrd_mb);
+ t7xx_uevent_send(port->dev, T7XX_UEVENT_MRD_DISCD);
+
+get_core_free_mem:
+ kfree(mdata);
+ vfree(mrdump_region->dump);
+ clear_bit(T7XX_MRDUMP_STATUS, &dl->status);
+ return -EPROTO;
+
+get_core_exit:
+ clear_bit(T7XX_MRDUMP_STATUS, &dl->status);
+ return result;
+}
+
+static int t7xx_devlink_fb_dump_log(struct t7xx_port *port)
+{
+ struct t7xx_devlink_region_info *lkdump_region;
+ char lkdump_complete_event[T7XX_FB_EVENT_SIZE];
+ struct t7xx_devlink *dl = port->dl;
+ int dlen, datasize = 0, result;
+ size_t offset_dlen = 0;
+ u8 *data;
+
+ set_bit(T7XX_LKDUMP_STATUS, &dl->status);
+ result = t7xx_devlink_fb_raw_command(T7XX_FB_CMD_OEM_LKDUMP, port, &datasize);
+ if (result) {
+ dev_err(port->dev, "%s command returns failure\n", T7XX_FB_CMD_OEM_LKDUMP);
+ goto lkdump_exit;
+ }
+
+ lkdump_region = dl->dl_region_info[T7XX_LKDUMP_INDEX];
+ if (datasize > lkdump_region->default_size) {
+ dev_err(port->dev, "lkdump size is more than %dKB. Discarded!",
+ T7XX_LKDUMP_SIZE / 1024);
+ t7xx_uevent_send(dl->dev, T7XX_UEVENT_LKD_DISCD);
+ result = -EPROTO;
+ goto lkdump_exit;
+ }
+
+ data = kzalloc(datasize, GFP_KERNEL);
+ if (!data) {
+ result = -ENOMEM;
+ goto lkdump_exit;
+ }
+
+ lkdump_region->dump = vmalloc(lkdump_region->default_size);
+ if (!lkdump_region->dump) {
+ kfree(data);
+ result = -ENOMEM;
+ goto lkdump_exit;
+ }
+
+ while (datasize > 0) {
+ dlen = t7xx_devlink_port_read(port, data, datasize);
+ if (dlen <= 0) {
+ dev_err(port->dev, "lkdump read error ret = %d", dlen);
+ kfree(data);
+ result = -EPROTO;
+ goto lkdump_exit;
+ }
+
+ memcpy(lkdump_region->dump + offset_dlen, data, dlen);
+ datasize -= dlen;
+ offset_dlen += dlen;
+ }
+
+ dev_dbg(port->dev, "LKDUMP DONE! size:%zd\n", offset_dlen);
+ lkdump_region->actual_size = offset_dlen;
+ snprintf(lkdump_complete_event, sizeof(lkdump_complete_event), "%s size=%zu",
+ T7XX_UEVENT_LKDUMP_READY, offset_dlen);
+ t7xx_uevent_send(dl->dev, lkdump_complete_event);
+ kfree(data);
+ clear_bit(T7XX_LKDUMP_STATUS, &dl->status);
+ return t7xx_devlink_fb_handle_response(port, NULL);
+
+lkdump_exit:
+ clear_bit(T7XX_LKDUMP_STATUS, &dl->status);
+ return result;
+}
+
+static int t7xx_devlink_flash_update(struct devlink *devlink,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack)
+{
+ struct t7xx_devlink *dl = devlink_priv(devlink);
+ const char *component = params->component;
+ const struct firmware *fw = params->fw;
+ char flash_event[T7XX_FB_EVENT_SIZE];
+ struct t7xx_port *port;
+ int ret;
+
+ port = dl->port;
+ if (port->dl->mode != T7XX_FB_DL_MODE) {
+ dev_err(port->dev, "Modem is not in fastboot download mode!");
+ ret = -EPERM;
+ goto err_out;
+ }
+
+ if (dl->status != T7XX_DEVLINK_IDLE) {
+ dev_err(port->dev, "Modem is busy!");
+ ret = -EBUSY;
+ goto err_out;
+ }
+
+ if (!component || !fw->data) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ set_bit(T7XX_FLASH_STATUS, &dl->status);
+ dev_dbg(port->dev, "flash partition name:%s binary size:%zu\n", component, fw->size);
+ ret = t7xx_devlink_fb_flash_partition(component, fw->data, port, fw->size);
+ if (ret) {
+ devlink_flash_update_status_notify(devlink, "flashing failure!",
+ params->component, 0, 0);
+ snprintf(flash_event, sizeof(flash_event), "%s for [%s]",
+ T7XX_UEVENT_FLASHING_FAILURE, params->component);
+ } else {
+ devlink_flash_update_status_notify(devlink, "flashing success!",
+ params->component, 0, 0);
+ snprintf(flash_event, sizeof(flash_event), "%s for [%s]",
+ T7XX_UEVENT_FLASHING_SUCCESS, params->component);
+ }
+
+ t7xx_uevent_send(dl->dev, flash_event);
+
+err_out:
+ clear_bit(T7XX_FLASH_STATUS, &dl->status);
+ return ret;
+}
+
+static int t7xx_devlink_reload_down(struct devlink *devlink, bool netns_change,
+ enum devlink_reload_action action,
+ enum devlink_reload_limit limit,
+ struct netlink_ext_ack *extack)
+{
+ struct t7xx_devlink *dl = devlink_priv(devlink);
+
+ switch (action) {
+ case DEVLINK_RELOAD_ACTION_DRIVER_REINIT:
+ dl->set_fastboot_dl = 1;
+ return 0;
+ case DEVLINK_RELOAD_ACTION_FW_ACTIVATE:
+ return t7xx_devlink_fb_raw_command(T7XX_FB_CMD_REBOOT, dl->port, NULL);
+ default:
+ /* Unsupported action should not get to this function */
+ return -EOPNOTSUPP;
+ }
+}
+
+static int t7xx_devlink_reload_up(struct devlink *devlink,
+ enum devlink_reload_action action,
+ enum devlink_reload_limit limit,
+ u32 *actions_performed,
+ struct netlink_ext_ack *extack)
+{
+ struct t7xx_devlink *dl = devlink_priv(devlink);
+ *actions_performed = BIT(action);
+ switch (action) {
+ case DEVLINK_RELOAD_ACTION_DRIVER_REINIT:
+ case DEVLINK_RELOAD_ACTION_FW_ACTIVATE:
+ t7xx_rescan_queue_work(dl->mtk_dev->pdev);
+ return 0;
+ default:
+ /* Unsupported action should not get to this function */
+ return -EOPNOTSUPP;
+ }
+}
+
+/* Call back function for devlink ops */
+static const struct devlink_ops devlink_flash_ops = {
+ .supported_flash_update_params = DEVLINK_SUPPORT_FLASH_UPDATE_COMPONENT,
+ .flash_update = t7xx_devlink_flash_update,
+ .reload_actions = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT) |
+ BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE),
+ .reload_down = t7xx_devlink_reload_down,
+ .reload_up = t7xx_devlink_reload_up,
+};
+
+static int t7xx_devlink_region_snapshot(struct devlink *dl, const struct devlink_region_ops *ops,
+ struct netlink_ext_ack *extack, u8 **data)
+{
+ struct t7xx_devlink_region_info *region_info = ops->priv;
+ struct t7xx_devlink *t7xx_dl = devlink_priv(dl);
+ u8 *snapshot_mem;
+
+ if (t7xx_dl->status != T7XX_DEVLINK_IDLE) {
+ dev_err(t7xx_dl->dev, "Modem is busy!");
+ return -EBUSY;
+ }
+
+ dev_dbg(t7xx_dl->dev, "accessed devlink region:%s index:%d", ops->name, region_info->entry);
+ if (!strncmp(ops->name, "mr_dump", strlen("mr_dump"))) {
+ if (!region_info->dump) {
+ dev_err(t7xx_dl->dev, "devlink region:%s dump memory is not valid!",
+ region_info->region_name);
+ return -ENOMEM;
+ }
+
+ snapshot_mem = vmalloc(region_info->default_size);
+ if (!snapshot_mem)
+ return -ENOMEM;
+
+ memcpy(snapshot_mem, region_info->dump, region_info->default_size);
+ *data = snapshot_mem;
+ } else if (!strncmp(ops->name, "lk_dump", strlen("lk_dump"))) {
+ int ret;
+
+ ret = t7xx_devlink_fb_dump_log(t7xx_dl->port);
+ if (ret)
+ return ret;
+
+ *data = region_info->dump;
+ }
+
+ return 0;
+}
+
+/* To create regions for dump files */
+static int t7xx_devlink_create_region(struct t7xx_devlink *dl)
+{
+ struct devlink_region_ops *region_ops;
+ int rc, i;
+
+ region_ops = dl->dl_region_ops;
+ for (i = 0; i < T7XX_TOTAL_REGIONS; i++) {
+ region_ops[i].name = t7xx_devlink_region_list[i].region_name;
+ region_ops[i].snapshot = t7xx_devlink_region_snapshot;
+ region_ops[i].destructor = vfree;
+ dl->dl_region[i] =
+ devlink_region_create(dl->dl_ctx, &region_ops[i], T7XX_MAX_SNAPSHOTS,
+ t7xx_devlink_region_list[i].default_size);
+
+ if (IS_ERR(dl->dl_region[i])) {
+ rc = PTR_ERR(dl->dl_region[i]);
+ dev_err(dl->dev, "devlink region fail,err %d", rc);
+ for ( ; i >= 0; i--)
+ devlink_region_destroy(dl->dl_region[i]);
+
+ return rc;
+ }
+
+ t7xx_devlink_region_list[i].entry = i;
+ region_ops[i].priv = t7xx_devlink_region_list + i;
+ }
+
+ return 0;
+}
+
+/* To Destroy devlink regions */
+static void t7xx_devlink_destroy_region(struct t7xx_devlink *dl)
+{
+ u8 i;
+
+ for (i = 0; i < T7XX_TOTAL_REGIONS; i++)
+ devlink_region_destroy(dl->dl_region[i]);
+}
+
+int t7xx_devlink_register(struct t7xx_pci_dev *t7xx_dev)
+{
+ struct devlink *dl_ctx;
+
+ dl_ctx = devlink_alloc(&devlink_flash_ops, sizeof(struct t7xx_devlink),
+ &t7xx_dev->pdev->dev);
+ if (!dl_ctx)
+ return -ENOMEM;
+
+ devlink_set_features(dl_ctx, DEVLINK_F_RELOAD);
+ devlink_register(dl_ctx);
+ t7xx_dev->dl = devlink_priv(dl_ctx);
+ t7xx_dev->dl->dl_ctx = dl_ctx;
+
+ return 0;
+}
+
+void t7xx_devlink_unregister(struct t7xx_pci_dev *t7xx_dev)
+{
+ struct devlink *dl_ctx = priv_to_devlink(t7xx_dev->dl);
+
+ devlink_unregister(dl_ctx);
+ devlink_free(dl_ctx);
+}
+
+/**
+ * t7xx_devlink_region_init - Initialize/register devlink to t7xx driver
+ * @port: Pointer to port structure
+ * @dw: Pointer to devlink work structure
+ * @wq: Pointer to devlink workqueue structure
+ *
+ * Returns: Pointer to t7xx_devlink on success and NULL on failure
+ */
+static struct t7xx_devlink *t7xx_devlink_region_init(struct t7xx_port *port,
+ struct t7xx_devlink_work *dw,
+ struct workqueue_struct *wq)
+{
+ struct t7xx_pci_dev *mtk_dev = port->t7xx_dev;
+ struct t7xx_devlink *dl = mtk_dev->dl;
+ int rc, i;
+
+ dl->dl_ctx = mtk_dev->dl->dl_ctx;
+ dl->mtk_dev = mtk_dev;
+ dl->dev = &mtk_dev->pdev->dev;
+ dl->mode = T7XX_FB_NO_MODE;
+ dl->status = T7XX_DEVLINK_IDLE;
+ dl->dl_work = dw;
+ dl->dl_wq = wq;
+ for (i = 0; i < T7XX_TOTAL_REGIONS; i++) {
+ dl->dl_region_info[i] = &t7xx_devlink_region_list[i];
+ dl->dl_region_info[i]->dump = NULL;
+ }
+ dl->port = port;
+ port->dl = dl;
+
+ rc = t7xx_devlink_create_region(dl);
+ if (rc) {
+ dev_err(dl->dev, "devlink region creation failed, rc %d", rc);
+ return NULL;
+ }
+
+ return dl;
+}
+
+/**
+ * t7xx_devlink_region_deinit - To unintialize the devlink from T7XX driver.
+ * @dl: Devlink instance
+ */
+static void t7xx_devlink_region_deinit(struct t7xx_devlink *dl)
+{
+ dl->mode = T7XX_FB_NO_MODE;
+ t7xx_devlink_destroy_region(dl);
+}
+
+static void t7xx_devlink_work_handler(struct work_struct *data)
+{
+ struct t7xx_devlink_work *dl_work;
+
+ dl_work = container_of(data, struct t7xx_devlink_work, work);
+ t7xx_devlink_fb_get_core(dl_work->port);
+}
+
+static int t7xx_devlink_init(struct t7xx_port *port)
+{
+ struct t7xx_devlink_work *dl_work;
+ struct workqueue_struct *wq;
+
+ dl_work = kmalloc(sizeof(*dl_work), GFP_KERNEL);
+ if (!dl_work)
+ return -ENOMEM;
+
+ wq = create_workqueue("t7xx_devlink");
+ if (!wq) {
+ kfree(dl_work);
+ dev_err(port->dev, "create_workqueue failed\n");
+ return -ENODATA;
+ }
+
+ INIT_WORK(&dl_work->work, t7xx_devlink_work_handler);
+ dl_work->port = port;
+ port->rx_length_th = T7XX_MAX_QUEUE_LENGTH;
+
+ if (!t7xx_devlink_region_init(port, dl_work, wq))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void t7xx_devlink_uninit(struct t7xx_port *port)
+{
+ struct t7xx_devlink *dl = port->dl;
+ struct sk_buff *skb;
+ unsigned long flags;
+
+ vfree(dl->dl_region_info[T7XX_MRDUMP_INDEX]->dump);
+ if (dl->dl_wq)
+ destroy_workqueue(dl->dl_wq);
+ kfree(dl->dl_work);
+
+ t7xx_devlink_region_deinit(port->dl);
+ spin_lock_irqsave(&port->rx_skb_list.lock, flags);
+ while ((skb = __skb_dequeue(&port->rx_skb_list)) != NULL)
+ dev_kfree_skb(skb);
+ spin_unlock_irqrestore(&port->rx_skb_list.lock, flags);
+}
+
+static int t7xx_devlink_enable_chl(struct t7xx_port *port)
+{
+ spin_lock(&port->port_update_lock);
+ port->chan_enable = true;
+ spin_unlock(&port->port_update_lock);
+
+ if (port->dl->dl_wq && port->dl->mode == T7XX_FB_DUMP_MODE)
+ queue_work(port->dl->dl_wq, &port->dl->dl_work->work);
+
+ return 0;
+}
+
+static int t7xx_devlink_disable_chl(struct t7xx_port *port)
+{
+ spin_lock(&port->port_update_lock);
+ port->chan_enable = false;
+ spin_unlock(&port->port_update_lock);
+
+ return 0;
+}
+
+struct port_ops devlink_port_ops = {
+ .init = &t7xx_devlink_init,
+ .recv_skb = &t7xx_port_enqueue_skb,
+ .uninit = &t7xx_devlink_uninit,
+ .enable_chl = &t7xx_devlink_enable_chl,
+ .disable_chl = &t7xx_devlink_disable_chl,
+};
diff --git a/drivers/net/wwan/t7xx/t7xx_port_devlink.h b/drivers/net/wwan/t7xx/t7xx_port_devlink.h
new file mode 100644
index 000000000000..85384e40519e
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_port_devlink.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, Intel Corporation.
+ */
+
+#ifndef __T7XX_PORT_DEVLINK_H__
+#define __T7XX_PORT_DEVLINK_H__
+
+#include <net/devlink.h>
+
+#include "t7xx_pci.h"
+
+#define T7XX_MAX_QUEUE_LENGTH 32
+#define T7XX_FB_COMMAND_SIZE 64
+#define T7XX_FB_RESPONSE_SIZE 64
+#define T7XX_FB_MCMD_SIZE 64
+#define T7XX_FB_MDATA_SIZE 1024
+#define T7XX_FB_RESP_COUNT 30
+
+#define T7XX_FB_CMD_RTS "_RTS"
+#define T7XX_FB_CMD_CTS "_CTS"
+#define T7XX_FB_CMD_FIN "_FIN"
+#define T7XX_FB_CMD_OEM_MRDUMP "oem mrdump"
+#define T7XX_FB_CMD_OEM_LKDUMP "oem dump_pllk_log"
+#define T7XX_FB_CMD_DOWNLOAD "download"
+#define T7XX_FB_CMD_FLASH "flash"
+#define T7XX_FB_CMD_REBOOT "reboot"
+#define T7XX_FB_RESP_MRDUMP_DONE "MRDUMP08_DONE"
+#define T7XX_FB_RESP_OKAY "OKAY"
+#define T7XX_FB_RESP_FAIL "FAIL"
+#define T7XX_FB_RESP_DATA "DATA"
+#define T7XX_FB_RESP_INFO "INFO"
+
+#define T7XX_FB_EVENT_SIZE 50
+
+#define T7XX_MAX_SNAPSHOTS 1
+#define T7XX_MAX_REGION_NAME_LENGTH 20
+#define T7XX_MRDUMP_SIZE (160 * 1024 * 1024)
+#define T7XX_LKDUMP_SIZE (256 * 1024)
+#define T7XX_TOTAL_REGIONS 2
+
+#define T7XX_FLASH_STATUS 0
+#define T7XX_MRDUMP_STATUS 1
+#define T7XX_LKDUMP_STATUS 2
+#define T7XX_DEVLINK_IDLE 0
+
+#define T7XX_FB_NO_MODE 0
+#define T7XX_FB_DL_MODE 1
+#define T7XX_FB_DUMP_MODE 2
+
+#define T7XX_MRDUMP_INDEX 0
+#define T7XX_LKDUMP_INDEX 1
+
+struct t7xx_devlink_work {
+ struct work_struct work;
+ struct t7xx_port *port;
+};
+
+struct t7xx_devlink_region_info {
+ char region_name[T7XX_MAX_REGION_NAME_LENGTH];
+ u32 default_size;
+ u32 actual_size;
+ u32 entry;
+ u8 *dump;
+};
+
+struct t7xx_devlink {
+ struct t7xx_pci_dev *mtk_dev;
+ struct t7xx_port *port;
+ struct device *dev;
+ struct devlink *dl_ctx;
+ struct t7xx_devlink_work *dl_work;
+ struct workqueue_struct *dl_wq;
+ struct t7xx_devlink_region_info *dl_region_info[T7XX_TOTAL_REGIONS];
+ struct devlink_region_ops dl_region_ops[T7XX_TOTAL_REGIONS];
+ struct devlink_region *dl_region[T7XX_TOTAL_REGIONS];
+ u8 mode;
+ unsigned long status;
+ int set_fastboot_dl;
+};
+
+int t7xx_devlink_register(struct t7xx_pci_dev *t7xx_dev);
+void t7xx_devlink_unregister(struct t7xx_pci_dev *t7xx_dev);
+
+#endif /*__T7XX_PORT_DEVLINK_H__*/
diff --git a/drivers/net/wwan/t7xx/t7xx_port_proxy.c b/drivers/net/wwan/t7xx/t7xx_port_proxy.c
index d4de047ff0d4..fdf0c6e5ed6d 100644
--- a/drivers/net/wwan/t7xx/t7xx_port_proxy.c
+++ b/drivers/net/wwan/t7xx/t7xx_port_proxy.c
@@ -77,6 +77,29 @@ static const struct t7xx_port_conf t7xx_md_port_conf[] = {
.path_id = CLDMA_ID_MD,
.ops = &ctl_port_ops,
.name = "t7xx_ctrl",
+ }, {
+ .tx_ch = PORT_CH_AP_CONTROL_TX,
+ .rx_ch = PORT_CH_AP_CONTROL_RX,
+ .txq_index = Q_IDX_CTRL,
+ .rxq_index = Q_IDX_CTRL,
+ .path_id = CLDMA_ID_AP,
+ .ops = &ctl_port_ops,
+ .name = "t7xx_ap_ctrl",
+ },
+};
+
+static struct t7xx_port_conf t7xx_early_port_conf[] = {
+ {
+ .tx_ch = 0xffff,
+ .rx_ch = 0xffff,
+ .txq_index = 1,
+ .rxq_index = 1,
+ .txq_exp_index = 1,
+ .rxq_exp_index = 1,
+ .path_id = CLDMA_ID_AP,
+ .is_early_port = true,
+ .ops = &devlink_port_ops,
+ .name = "ttyDUMP",
},
};
@@ -194,7 +217,17 @@ int t7xx_port_enqueue_skb(struct t7xx_port *port, struct sk_buff *skb)
return 0;
}
-static int t7xx_port_send_raw_skb(struct t7xx_port *port, struct sk_buff *skb)
+int t7xx_get_port_mtu(struct t7xx_port *port)
+{
+ enum cldma_id path_id = port->port_conf->path_id;
+ int tx_qno = t7xx_port_get_queue_no(port);
+ struct cldma_ctrl *md_ctrl;
+
+ md_ctrl = port->t7xx_dev->md->md_ctrl[path_id];
+ return md_ctrl->tx_ring[tx_qno].pkt_size;
+}
+
+int t7xx_port_send_raw_skb(struct t7xx_port *port, struct sk_buff *skb)
{
enum cldma_id path_id = port->port_conf->path_id;
struct cldma_ctrl *md_ctrl;
@@ -309,6 +342,26 @@ static void t7xx_proxy_setup_ch_mapping(struct port_proxy *port_prox)
}
}
+static int t7xx_port_proxy_recv_skb_from_queue(struct t7xx_pci_dev *t7xx_dev,
+ struct cldma_queue *queue, struct sk_buff *skb)
+{
+ struct port_proxy *port_prox = t7xx_dev->md->port_prox;
+ const struct t7xx_port_conf *port_conf;
+ struct t7xx_port *port;
+ int ret;
+
+ port = port_prox->ports;
+ port_conf = port->port_conf;
+
+ ret = port_conf->ops->recv_skb(port, skb);
+ if (ret < 0 && ret != -ENOBUFS) {
+ dev_err(port->dev, "drop on RX ch %d, %d\n", port_conf->rx_ch, ret);
+ dev_kfree_skb_any(skb);
+ }
+
+ return ret;
+}
+
static struct t7xx_port *t7xx_port_proxy_find_port(struct t7xx_pci_dev *t7xx_dev,
struct cldma_queue *queue, u16 channel)
{
@@ -330,6 +383,22 @@ static struct t7xx_port *t7xx_port_proxy_find_port(struct t7xx_pci_dev *t7xx_dev
return NULL;
}
+struct t7xx_port *t7xx_port_proxy_get_port_by_name(struct port_proxy *port_prox, char *port_name)
+{
+ const struct t7xx_port_conf *port_conf;
+ struct t7xx_port *port;
+ int i;
+
+ for_each_proxy_port(i, port, port_prox) {
+ port_conf = port->port_conf;
+
+ if (!strncmp(port_conf->name, port_name, strlen(port_conf->name)))
+ return port;
+ }
+
+ return NULL;
+}
+
/**
* t7xx_port_proxy_recv_skb() - Dispatch received skb.
* @queue: CLDMA queue.
@@ -350,6 +419,9 @@ static int t7xx_port_proxy_recv_skb(struct cldma_queue *queue, struct sk_buff *s
u16 seq_num, channel;
int ret;
+ if (queue->q_type == CLDMA_DEDICATED_Q)
+ return t7xx_port_proxy_recv_skb_from_queue(t7xx_dev, queue, skb);
+
channel = FIELD_GET(CCCI_H_CHN_FLD, le32_to_cpu(ccci_h->status));
if (t7xx_fsm_get_md_state(ctl) == MD_STATE_INVALID) {
dev_err_ratelimited(dev, "Packet drop on channel 0x%x, modem not ready\n", channel);
@@ -364,7 +436,8 @@ static int t7xx_port_proxy_recv_skb(struct cldma_queue *queue, struct sk_buff *s
seq_num = t7xx_port_next_rx_seq_num(port, ccci_h);
port_conf = port->port_conf;
- skb_pull(skb, sizeof(*ccci_h));
+ if (!port->port_conf->is_early_port)
+ skb_pull(skb, sizeof(*ccci_h));
ret = port_conf->ops->recv_skb(port, skb);
/* Error indicates to try again later */
@@ -416,8 +489,12 @@ static void t7xx_proxy_init_all_ports(struct t7xx_modem *md)
if (port_conf->tx_ch == PORT_CH_CONTROL_TX)
md->core_md.ctl_port = port;
+ if (port_conf->tx_ch == PORT_CH_AP_CONTROL_TX)
+ md->core_ap.ctl_port = port;
+
port->t7xx_dev = md->t7xx_dev;
port->dev = &md->t7xx_dev->pdev->dev;
+ port->dl = md->t7xx_dev->dl;
spin_lock_init(&port->port_update_lock);
port->chan_enable = false;
@@ -428,26 +505,58 @@ static void t7xx_proxy_init_all_ports(struct t7xx_modem *md)
t7xx_proxy_setup_ch_mapping(port_prox);
}
+void t7xx_port_proxy_set_cfg(struct t7xx_modem *md, enum port_cfg_id cfg_id)
+{
+ struct port_proxy *port_prox = md->port_prox;
+ const struct t7xx_port_conf *port_conf;
+ struct device *dev = port_prox->dev;
+ unsigned int port_count;
+ struct t7xx_port *port;
+ int i;
+
+ if (port_prox->cfg_id == cfg_id)
+ return;
+
+ if (port_prox->cfg_id != PORT_CFG_ID_INVALID) {
+ for_each_proxy_port(i, port, port_prox)
+ port->port_conf->ops->uninit(port);
+
+ devm_kfree(dev, port_prox->ports);
+ }
+
+ if (cfg_id == PORT_CFG_ID_EARLY) {
+ port_conf = t7xx_early_port_conf;
+ port_count = ARRAY_SIZE(t7xx_early_port_conf);
+ } else {
+ port_conf = t7xx_md_port_conf;
+ port_count = ARRAY_SIZE(t7xx_md_port_conf);
+ }
+
+ port_prox->ports = devm_kzalloc(dev, sizeof(struct t7xx_port) * port_count, GFP_KERNEL);
+ if (!port_prox->ports)
+ return;
+
+ for (i = 0; i < port_count; i++)
+ port_prox->ports[i].port_conf = &port_conf[i];
+
+ port_prox->cfg_id = cfg_id;
+ port_prox->port_count = port_count;
+ t7xx_proxy_init_all_ports(md);
+}
+
static int t7xx_proxy_alloc(struct t7xx_modem *md)
{
- unsigned int port_count = ARRAY_SIZE(t7xx_md_port_conf);
struct device *dev = &md->t7xx_dev->pdev->dev;
struct port_proxy *port_prox;
- int i;
- port_prox = devm_kzalloc(dev, sizeof(*port_prox) + sizeof(struct t7xx_port) * port_count,
- GFP_KERNEL);
+ port_prox = devm_kzalloc(dev, sizeof(*port_prox), GFP_KERNEL);
if (!port_prox)
return -ENOMEM;
md->port_prox = port_prox;
port_prox->dev = dev;
+ t7xx_port_proxy_set_cfg(md, PORT_CFG_ID_EARLY);
- for (i = 0; i < port_count; i++)
- port_prox->ports[i].port_conf = &t7xx_md_port_conf[i];
-
- port_prox->port_count = port_count;
- t7xx_proxy_init_all_ports(md);
return 0;
}
@@ -469,6 +578,7 @@ int t7xx_port_proxy_init(struct t7xx_modem *md)
if (ret)
return ret;
+ t7xx_cldma_set_recv_skb(md->md_ctrl[CLDMA_ID_AP], t7xx_port_proxy_recv_skb);
t7xx_cldma_set_recv_skb(md->md_ctrl[CLDMA_ID_MD], t7xx_port_proxy_recv_skb);
return 0;
}
diff --git a/drivers/net/wwan/t7xx/t7xx_port_proxy.h b/drivers/net/wwan/t7xx/t7xx_port_proxy.h
index bc1ff5c6c700..7298a2d09fa0 100644
--- a/drivers/net/wwan/t7xx/t7xx_port_proxy.h
+++ b/drivers/net/wwan/t7xx/t7xx_port_proxy.h
@@ -31,12 +31,19 @@
#define RX_QUEUE_MAXLEN 32
#define CTRL_QUEUE_MAXLEN 16
+enum port_cfg_id {
+ PORT_CFG_ID_INVALID,
+ PORT_CFG_ID_NORMAL,
+ PORT_CFG_ID_EARLY,
+};
+
struct port_proxy {
int port_count;
struct list_head rx_ch_ports[PORT_CH_ID_MASK + 1];
struct list_head queue_ports[CLDMA_NUM][MTK_QUEUES];
struct device *dev;
- struct t7xx_port ports[];
+ enum port_cfg_id cfg_id;
+ struct t7xx_port *ports;
};
struct ccci_header {
@@ -86,6 +93,7 @@ struct ctrl_msg_header {
/* Port operations mapping */
extern struct port_ops wwan_sub_port_ops;
extern struct port_ops ctl_port_ops;
+extern struct port_ops devlink_port_ops;
void t7xx_port_proxy_reset(struct port_proxy *port_prox);
void t7xx_port_proxy_uninit(struct port_proxy *port_prox);
@@ -94,5 +102,7 @@ void t7xx_port_proxy_md_status_notify(struct port_proxy *port_prox, unsigned int
int t7xx_port_enum_msg_handler(struct t7xx_modem *md, void *msg);
int t7xx_port_proxy_chl_enable_disable(struct port_proxy *port_prox, unsigned int ch_id,
bool en_flag);
+struct t7xx_port *t7xx_port_proxy_get_port_by_name(struct port_proxy *port_prox, char *port_name);
+void t7xx_port_proxy_set_cfg(struct t7xx_modem *md, enum port_cfg_id cfg_id);
#endif /* __T7XX_PORT_PROXY_H__ */
diff --git a/drivers/net/wwan/t7xx/t7xx_port_wwan.c b/drivers/net/wwan/t7xx/t7xx_port_wwan.c
index 33931bfd78fd..dfd7fb487fc0 100644
--- a/drivers/net/wwan/t7xx/t7xx_port_wwan.c
+++ b/drivers/net/wwan/t7xx/t7xx_port_wwan.c
@@ -54,7 +54,7 @@ static void t7xx_port_ctrl_stop(struct wwan_port *port)
static int t7xx_port_ctrl_tx(struct wwan_port *port, struct sk_buff *skb)
{
struct t7xx_port *port_private = wwan_port_get_drvdata(port);
- size_t len, offset, chunk_len = 0, txq_mtu = CLDMA_MTU;
+ size_t len, offset, chunk_len = 0, txq_mtu;
const struct t7xx_port_conf *port_conf;
struct t7xx_fsm_ctl *ctl;
enum md_state md_state;
@@ -72,6 +72,7 @@ static int t7xx_port_ctrl_tx(struct wwan_port *port, struct sk_buff *skb)
return -ENODEV;
}
+ txq_mtu = t7xx_get_port_mtu(port_private);
for (offset = 0; offset < len; offset += chunk_len) {
struct sk_buff *skb_ccci;
int ret;
@@ -155,6 +156,12 @@ static void t7xx_port_wwan_md_state_notify(struct t7xx_port *port, unsigned int
{
const struct t7xx_port_conf *port_conf = port->port_conf;
+ if (state == MD_STATE_EXCEPTION) {
+ if (port->wwan_port)
+ wwan_port_txoff(port->wwan_port);
+ return;
+ }
+
if (state != MD_STATE_READY)
return;
diff --git a/drivers/net/wwan/t7xx/t7xx_reg.h b/drivers/net/wwan/t7xx/t7xx_reg.h
index 7c1b81091a0f..3a758bf79a4e 100644
--- a/drivers/net/wwan/t7xx/t7xx_reg.h
+++ b/drivers/net/wwan/t7xx/t7xx_reg.h
@@ -56,7 +56,7 @@
#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_AP_HK BIT(15)
#define D2H_INT_ASYNC_MD_HK BIT(16)
/* Register base */
@@ -101,11 +101,34 @@ enum t7xx_pm_resume_state {
PM_RESUME_REG_STATE_L2_EXP,
};
+enum host_event_e {
+ HOST_EVENT_INIT = 0,
+ FASTBOOT_DL_NOTY = 0x3,
+};
+
#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 MISC_RESET_TYPE_PLDR BIT(26)
+#define MISC_DEV_STATUS_MASK GENMASK(15, 0)
+#define LK_EVENT_MASK GENMASK(11, 8)
+#define HOST_EVENT_MASK GENMASK(31, 28)
+
+enum lk_event_id {
+ LK_EVENT_NORMAL = 0,
+ LK_EVENT_CREATE_PD_PORT = 1,
+ LK_EVENT_CREATE_POST_DL_PORT = 2,
+ LK_EVENT_RESET = 7,
+};
+
+#define MISC_STAGE_MASK GENMASK(2, 0)
+
+enum t7xx_device_stage {
+ INIT_STAGE = 0,
+ PRE_BROM_STAGE = 1,
+ POST_BROM_STAGE = 2,
+ LK_STAGE = 3,
+ LINUX_STAGE = 4,
+};
#define T7XX_PCIE_RESOURCE_STATUS 0x0d28
#define T7XX_PCIE_RESOURCE_STS_MSK GENMASK(4, 0)
diff --git a/drivers/net/wwan/t7xx/t7xx_state_monitor.c b/drivers/net/wwan/t7xx/t7xx_state_monitor.c
index 0bcca08ff2bd..00e143c8d568 100644
--- a/drivers/net/wwan/t7xx/t7xx_state_monitor.c
+++ b/drivers/net/wwan/t7xx/t7xx_state_monitor.c
@@ -35,11 +35,15 @@
#include "t7xx_hif_cldma.h"
#include "t7xx_mhccif.h"
#include "t7xx_modem_ops.h"
+#include "t7xx_netdev.h"
#include "t7xx_pci.h"
#include "t7xx_pcie_mac.h"
+#include "t7xx_port_devlink.h"
#include "t7xx_port_proxy.h"
+#include "t7xx_pci_rescan.h"
#include "t7xx_reg.h"
#include "t7xx_state_monitor.h"
+#include "t7xx_uevent.h"
#define FSM_DRM_DISABLE_DELAY_MS 200
#define FSM_EVENT_POLL_INTERVAL_MS 20
@@ -47,6 +51,10 @@
#define FSM_MD_EX_PASS_TIMEOUT_MS 45000
#define FSM_CMD_TIMEOUT_MS 2000
+/* As per MTK, AP to MD Handshake time is ~15s*/
+#define DEVICE_STAGE_POLL_INTERVAL_MS 100
+#define DEVICE_STAGE_POLL_COUNT 150
+
void t7xx_fsm_notifier_register(struct t7xx_modem *md, struct t7xx_fsm_notifier *notifier)
{
struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
@@ -206,6 +214,65 @@ static void fsm_routine_exception(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_comm
fsm_finish_command(ctl, cmd, 0);
}
+static void t7xx_host_event_notify(struct t7xx_modem *md, unsigned int event_id)
+{
+ u32 value;
+
+ value = ioread32(IREG_BASE(md->t7xx_dev) + T7XX_PCIE_MISC_DEV_STATUS);
+ value &= ~HOST_EVENT_MASK;
+ value |= FIELD_PREP(HOST_EVENT_MASK, event_id);
+ iowrite32(value, IREG_BASE(md->t7xx_dev) + T7XX_PCIE_MISC_DEV_STATUS);
+}
+
+static void t7xx_lk_stage_event_handling(struct t7xx_fsm_ctl *ctl, unsigned int dev_status)
+{
+ struct t7xx_modem *md = ctl->md;
+ struct cldma_ctrl *md_ctrl;
+ enum lk_event_id lk_event;
+ struct t7xx_port *port;
+ struct device *dev;
+
+ dev = &md->t7xx_dev->pdev->dev;
+ lk_event = FIELD_GET(LK_EVENT_MASK, dev_status);
+ dev_info(dev, "Device enter next stage from LK stage/n");
+ switch (lk_event) {
+ case LK_EVENT_NORMAL:
+ break;
+
+ case LK_EVENT_CREATE_PD_PORT:
+ case LK_EVENT_CREATE_POST_DL_PORT:
+ md_ctrl = md->md_ctrl[CLDMA_ID_AP];
+ t7xx_cldma_hif_hw_init(md_ctrl);
+ t7xx_cldma_stop(md_ctrl);
+ t7xx_cldma_switch_cfg(md_ctrl, CLDMA_DEDICATED_Q_CFG);
+ dev_info(dev, "creating the ttyDUMP port\n");
+ port = t7xx_port_proxy_get_port_by_name(md->port_prox, "ttyDUMP");
+ if (!port) {
+ dev_err(dev, "ttyDUMP port not found\n");
+ return;
+ }
+
+ if (lk_event == LK_EVENT_CREATE_PD_PORT)
+ port->dl->mode = T7XX_FB_DUMP_MODE;
+ else
+ port->dl->mode = T7XX_FB_DL_MODE;
+ port->port_conf->ops->enable_chl(port);
+ t7xx_cldma_start(md_ctrl);
+ if (lk_event == LK_EVENT_CREATE_PD_PORT)
+ t7xx_uevent_send(dev, T7XX_UEVENT_MODEM_FASTBOOT_DUMP_MODE);
+ else
+ t7xx_uevent_send(dev, T7XX_UEVENT_MODEM_FASTBOOT_DL_MODE);
+ break;
+
+ case LK_EVENT_RESET:
+ break;
+
+ default:
+ dev_err(dev, "Invalid BROM event\n");
+ break;
+ }
+}
+
static int fsm_stopped_handler(struct t7xx_fsm_ctl *ctl)
{
ctl->curr_state = FSM_STATE_STOPPED;
@@ -243,13 +310,23 @@ static void fsm_routine_stopping(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_comma
t7xx_cldma_stop(md_ctrl);
if (!ctl->md->rgu_irq_asserted) {
+ if (t7xx_dev->dl->set_fastboot_dl)
+ t7xx_host_event_notify(ctl->md, FASTBOOT_DL_NOTY);
+
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)
+ if (t7xx_dev->dl->set_fastboot_dl) {
+ /* Do not try fldr because device will always wait for
+ * MHCCIF bit 13 in fastboot download flow.
+ */
t7xx_mhccif_h2d_swint_trigger(t7xx_dev, H2D_CH_DEVICE_RESET);
+ } else {
+ 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));
@@ -272,6 +349,7 @@ static void fsm_routine_ready(struct t7xx_fsm_ctl *ctl)
ctl->curr_state = FSM_STATE_READY;
t7xx_fsm_broadcast_ready_state(ctl);
+ t7xx_uevent_send(&md->t7xx_dev->pdev->dev, T7XX_UEVENT_MODEM_READY);
t7xx_md_event_notify(md, FSM_READY);
}
@@ -285,8 +363,9 @@ static int fsm_routine_starting(struct t7xx_fsm_ctl *ctl)
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);
+ wait_event_interruptible_timeout(ctl->async_hk_wq,
+ (md->core_md.ready && md->core_ap.ready) ||
+ ctl->exp_flg, HZ * 60);
dev = &md->t7xx_dev->pdev->dev;
if (ctl->exp_flg)
@@ -299,6 +378,13 @@ static int fsm_routine_starting(struct t7xx_fsm_ctl *ctl)
fsm_routine_exception(ctl, NULL, EXCEPTION_HS_TIMEOUT);
return -ETIMEDOUT;
+ } else if (!md->core_ap.ready) {
+ dev_err(dev, "AP handshake timeout\n");
+ if (md->core_ap.handshake_ongoing)
+ t7xx_fsm_append_event(ctl, FSM_EVENT_AP_HS2_EXIT, NULL, 0);
+
+ fsm_routine_exception(ctl, NULL, EXCEPTION_HS_TIMEOUT);
+ return -ETIMEDOUT;
}
t7xx_pci_pm_init_late(md->t7xx_dev);
@@ -309,8 +395,10 @@ static int fsm_routine_starting(struct t7xx_fsm_ctl *ctl)
static void fsm_routine_start(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd)
{
struct t7xx_modem *md = ctl->md;
+ unsigned int device_stage;
+ struct device *dev;
u32 dev_status;
- int ret;
+ int ret = 0;
if (!md)
return;
@@ -321,22 +409,60 @@ static void fsm_routine_start(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command
return;
}
+ dev = &md->t7xx_dev->pdev->dev;
+ dev_status = ioread32(IREG_BASE(md->t7xx_dev) + T7XX_PCIE_MISC_DEV_STATUS);
+ dev_status &= MISC_DEV_STATUS_MASK;
+ dev_dbg(dev, "dev_status = %x modem state = %d\n", dev_status, ctl->md_state);
+
+ if (dev_status == MISC_DEV_STATUS_MASK) {
+ dev_err(dev, "invalid device status\n");
+ ret = -EINVAL;
+ goto finish_command;
+ }
+
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;
+ device_stage = FIELD_GET(MISC_STAGE_MASK, dev_status);
+ if (dev_status == ctl->prev_dev_status) {
+ if (ctl->device_stage_check_cnt++ >= DEVICE_STAGE_POLL_COUNT) {
+ dev_err(dev, "Timeout at device stage 0x%x\n", device_stage);
+ ctl->device_stage_check_cnt = 0;
+ ret = -ETIMEDOUT;
+ } else {
+ msleep(DEVICE_STAGE_POLL_INTERVAL_MS);
+ ret = t7xx_fsm_append_cmd(ctl, FSM_CMD_START, 0);
+ }
- fsm_finish_command(ctl, cmd, -ETIMEDOUT);
- dev_err(dev, "Invalid device status 0x%lx\n", dev_status & MISC_STAGE_MASK);
- return;
+ goto finish_command;
+ }
+
+ switch (device_stage) {
+ case INIT_STAGE:
+ case PRE_BROM_STAGE:
+ case POST_BROM_STAGE:
+ ret = t7xx_fsm_append_cmd(ctl, FSM_CMD_START, 0);
+ break;
+
+ case LK_STAGE:
+ dev_info(dev, "LK_STAGE Entered");
+ t7xx_lk_stage_event_handling(ctl, dev_status);
+ break;
+
+ case LINUX_STAGE:
+ t7xx_cldma_hif_hw_init(md->md_ctrl[CLDMA_ID_AP]);
+ t7xx_cldma_hif_hw_init(md->md_ctrl[CLDMA_ID_MD]);
+ t7xx_port_proxy_set_cfg(md, PORT_CFG_ID_NORMAL);
+ ret = fsm_routine_starting(ctl);
+ break;
+
+ default:
+ break;
}
- t7xx_cldma_hif_hw_init(md->md_ctrl[CLDMA_ID_MD]);
- fsm_finish_command(ctl, cmd, fsm_routine_starting(ctl));
+finish_command:
+ ctl->prev_dev_status = dev_status;
+ fsm_finish_command(ctl, cmd, ret);
}
static int fsm_main_thread(void *data)
@@ -507,6 +633,8 @@ void t7xx_fsm_reset(struct t7xx_modem *md)
fsm_flush_event_cmd_qs(ctl);
ctl->curr_state = FSM_STATE_STOPPED;
ctl->exp_flg = false;
+ ctl->prev_dev_status = 0;
+ ctl->device_stage_check_cnt = 0;
}
int t7xx_fsm_init(struct t7xx_modem *md)
diff --git a/drivers/net/wwan/t7xx/t7xx_state_monitor.h b/drivers/net/wwan/t7xx/t7xx_state_monitor.h
index b1af0259d4c5..b2459bd58624 100644
--- a/drivers/net/wwan/t7xx/t7xx_state_monitor.h
+++ b/drivers/net/wwan/t7xx/t7xx_state_monitor.h
@@ -38,10 +38,12 @@ enum t7xx_fsm_state {
enum t7xx_fsm_event_state {
FSM_EVENT_INVALID,
FSM_EVENT_MD_HS2,
+ FSM_EVENT_AP_HS2,
FSM_EVENT_MD_EX,
FSM_EVENT_MD_EX_REC_OK,
FSM_EVENT_MD_EX_PASS,
FSM_EVENT_MD_HS2_EXIT,
+ FSM_EVENT_AP_HS2_EXIT,
FSM_EVENT_MAX
};
@@ -94,6 +96,8 @@ struct t7xx_fsm_ctl {
bool exp_flg;
spinlock_t notifier_lock; /* Protects notifier list */
struct list_head notifier_list;
+ u32 prev_dev_status;
+ unsigned int device_stage_check_cnt;
};
struct t7xx_fsm_event {
diff --git a/drivers/net/wwan/t7xx/t7xx_uevent.c b/drivers/net/wwan/t7xx/t7xx_uevent.c
new file mode 100644
index 000000000000..5a320cf3f94b
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_uevent.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, Intel Corporation.
+ */
+
+#include <linux/slab.h>
+
+#include "t7xx_uevent.h"
+
+/* Update the uevent in work queue context */
+static void t7xx_uevent_work(struct work_struct *data)
+{
+ struct t7xx_uevent_info *info;
+ char *envp[2] = { NULL, NULL };
+
+ info = container_of(data, struct t7xx_uevent_info, work);
+ envp[0] = info->uevent;
+
+ if (kobject_uevent_env(&info->dev->kobj, KOBJ_CHANGE, envp))
+ pr_err("uevent %s failed to sent", info->uevent);
+
+ kfree(info);
+}
+
+/**
+ * t7xx_uevent_send - Send modem event to user space.
+ * @dev: Generic device pointer
+ * @uevent: Uevent information
+ */
+void t7xx_uevent_send(struct device *dev, char *uevent)
+{
+ struct t7xx_uevent_info *info = kzalloc(sizeof(*info), GFP_ATOMIC);
+
+ if (!info)
+ return;
+
+ INIT_WORK(&info->work, t7xx_uevent_work);
+ info->dev = dev;
+ snprintf(info->uevent, T7XX_MAX_UEVENT_LEN, "T7XX_EVENT=%s", uevent);
+ schedule_work(&info->work);
+}
diff --git a/drivers/net/wwan/t7xx/t7xx_uevent.h b/drivers/net/wwan/t7xx/t7xx_uevent.h
new file mode 100644
index 000000000000..e871dc0e9444
--- /dev/null
+++ b/drivers/net/wwan/t7xx/t7xx_uevent.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, Intel Corporation.
+ */
+
+#ifndef __T7XX_UEVENT_H__
+#define __T7XX_UEVENT_H__
+
+#include <linux/device.h>
+#include <linux/kobject.h>
+
+/* Maximum length of user events */
+#define T7XX_MAX_UEVENT_LEN 64
+
+/* T7XX Host driver uevents */
+#define T7XX_UEVENT_MODEM_READY "T7XX_MODEM_READY"
+#define T7XX_UEVENT_MODEM_FASTBOOT_DL_MODE "T7XX_MODEM_FASTBOOT_DL_MODE"
+#define T7XX_UEVENT_MODEM_FASTBOOT_DUMP_MODE "T7XX_MODEM_FASTBOOT_DUMP_MODE"
+#define T7XX_UEVENT_MRDUMP_READY "T7XX_MRDUMP_READY"
+#define T7XX_UEVENT_LKDUMP_READY "T7XX_LKDUMP_READY"
+#define T7XX_UEVENT_MRD_DISCD "T7XX_MRDUMP_DISCARDED"
+#define T7XX_UEVENT_LKD_DISCD "T7XX_LKDUMP_DISCARDED"
+#define T7XX_UEVENT_FLASHING_SUCCESS "T7XX_FLASHING_SUCCESS"
+#define T7XX_UEVENT_FLASHING_FAILURE "T7XX_FLASHING_FAILURE"
+
+/**
+ * struct t7xx_uevent_info - Uevent information structure.
+ * @dev: Pointer to device structure
+ * @uevent: Uevent information
+ * @work: Uevent work struct
+ */
+struct t7xx_uevent_info {
+ struct device *dev;
+ char uevent[T7XX_MAX_UEVENT_LEN];
+ struct work_struct work;
+};
+
+void t7xx_uevent_send(struct device *dev, char *uevent);
+#endif