diff --git a/drivers/misc/mods/Makefile b/drivers/misc/mods/Makefile
index a37c0aa0..4d9132a0 100644
--- a/drivers/misc/mods/Makefile
+++ b/drivers/misc/mods/Makefile
@@ -16,6 +16,7 @@ mods-y += mods_mem.o
mods-$(CONFIG_ACPI) += mods_acpi.o
mods-$(CONFIG_TEGRA_NVADSP) += mods_adsp.o
mods-$(CONFIG_ARM_FFA_TRANSPORT) += mods_arm_ffa.o
+mods-$(CONFIG_TEGRA_IVC) += mods_bpmpipc.o
mods-$(CONFIG_COMMON_CLK) += mods_clock.o
mods-$(CONFIG_DEBUG_FS) += mods_debugfs.o
mods-$(CONFIG_DMA_ENGINE) += mods_dma.o
diff --git a/drivers/misc/mods/mods_bpmpipc.c b/drivers/misc/mods/mods_bpmpipc.c
new file mode 100644
index 00000000..f1ba81a0
--- /dev/null
+++ b/drivers/misc/mods/mods_bpmpipc.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file is part of NVIDIA MODS kernel driver.
+ *
+ * Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
+ *
+ * NVIDIA MODS kernel driver is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * NVIDIA MODS kernel driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with NVIDIA MODS kernel driver.
+ * If not, see .
+ */
+
+#include "mods_internal.h"
+
+#include
+#include
+#include
+
+#include
+#include
+
+#define IVC_CHANNEL_SIZE 256
+#define MRQ_MSG_SIZE 128
+#define BPMP_MAIL_DO_ACK (1U << 0U)
+#define BPMP_IVC_TIMEOUT 120000 /* really large timeout to support simulation platforms */
+
+static DEFINE_MUTEX(mods_bpmpipc_lock);
+
+static const u32 MODS_CMD_UPHY_LANE_EOM_SCAN = 9;
+
+struct mods_cmd_uphy_lane_eom_scan_request {
+ u32 brick;
+ u32 lane;
+ u32 pcie_gen5;
+};
+
+struct mods_cmd_uphy_lane_eom_scan_response {
+ u32 data;
+};
+
+struct mods_mrq_uphy_request {
+ u16 lane;
+ u16 cmd;
+ struct mods_cmd_uphy_lane_eom_scan_request lane_eom_scan;
+};
+
+struct mods_mrq_uphy_response {
+ struct mods_cmd_uphy_lane_eom_scan_response eom_status;
+};
+
+struct bpmp_ipc_ch {
+ bool is_init;
+ struct tegra_ivc ivc;
+ void __iomem *db_base;
+ void __iomem *req_base;
+ void __iomem *resp_base;
+ phys_addr_t db_phys_addr;
+ phys_addr_t req_phys_addr;
+ phys_addr_t resp_phys_addr;
+};
+
+static struct bpmp_ipc_ch mods_bpmp_ch = {.is_init = false};
+
+static void bpmp_ivc_notify(struct tegra_ivc *ivc, void *data)
+{
+ struct bpmp_ipc_ch *bpmp_ipc_ch = (struct bpmp_ipc_ch *)data;
+
+ __raw_writel(1, bpmp_ipc_ch->db_base);
+}
+
+static int bpmp_ipc_send(struct mods_client *client,
+ struct tegra_ivc *ivc,
+ const void *data,
+ size_t sz)
+{
+ void *frame;
+
+ frame = tegra_ivc_write_get_next_frame(ivc);
+ if (IS_ERR(frame)) {
+ cl_error("failed to get next tegra-ivc output frame!\n");
+ return PTR_ERR(frame);
+ }
+
+ memcpy_toio(frame, data, sz);
+
+ tegra_ivc_write_advance(ivc);
+
+ return 0;
+}
+
+static int bpmp_ipc_recv(struct mods_client *client,
+ struct tegra_ivc *ivc,
+ void *data,
+ size_t sz,
+ u32 timeout_ms)
+{
+ int err;
+ const void *frame;
+ ktime_t end;
+
+ end = ktime_add_us(ktime_get(), timeout_ms * 1000);
+ do {
+ frame = tegra_ivc_read_get_next_frame(ivc);
+ if (!IS_ERR(frame))
+ break;
+ } while (ktime_before(ktime_get(), end));
+
+ if (IS_ERR(frame)) {
+ frame = tegra_ivc_read_get_next_frame(ivc);
+
+ if (IS_ERR(frame)) {
+ cl_error("get next tegra-ivc input frame timeout\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ memcpy_fromio(data, frame, sz);
+
+ err = tegra_ivc_read_advance(ivc);
+ if (err < 0)
+ cl_error("tegra_ivc read failed: %d\n", err);
+
+ return err;
+}
+
+static int bpmp_transfer(struct mods_client *client,
+ struct tegra_bpmp_message *msg)
+{
+ int err;
+ struct tegra_bpmp_mb_data req;
+ struct tegra_bpmp_mb_data resp;
+
+ req.code = msg->mrq;
+ req.flags = BPMP_MAIL_DO_ACK;
+ memcpy(req.data, msg->tx.data, msg->tx.size);
+ err = bpmp_ipc_send(client, &mods_bpmp_ch.ivc, &req, sizeof(req));
+
+ if (err == 0) {
+ err = bpmp_ipc_recv(client, &mods_bpmp_ch.ivc,
+ &resp,
+ sizeof(resp),
+ BPMP_IVC_TIMEOUT);
+ }
+
+ if (err == 0) {
+ memcpy(msg->rx.data, resp.data, msg->rx.size);
+ msg->rx.ret = resp.code;
+ }
+
+ return err;
+}
+
+static int mrq_uphy_lane_eom_scan(struct mods_client *client,
+ u32 brick,
+ u32 lane,
+ u32 pcie_gen5,
+ u32 *data)
+{
+ int err;
+ struct mods_mrq_uphy_request req = {
+ .cmd = cpu_to_le32(MODS_CMD_UPHY_LANE_EOM_SCAN)
+ };
+ struct mods_mrq_uphy_response resp;
+ struct tegra_bpmp_message msg = {
+ .mrq = MRQ_UPHY,
+ .tx = {
+ .data = &req,
+ .size = sizeof(req),
+ },
+ .rx = {
+ .data = &resp,
+ .size = sizeof(resp),
+ },
+ };
+
+ req.lane_eom_scan.brick = brick;
+ req.lane_eom_scan.lane = lane;
+ req.lane_eom_scan.pcie_gen5 = pcie_gen5;
+
+ err = bpmp_transfer(client, &msg);
+
+ if (err < 0) {
+ return err;
+ } else if (msg.rx.ret < 0) {
+ err = -EINVAL;
+ return err;
+ }
+
+ *data = resp.eom_status.data;
+ return err;
+}
+
+static int bpmp_ioremap(struct mods_client *client,
+ struct bpmp_ipc_ch *bpmp_ipc_ch,
+ u64 db_phys_addr,
+ u64 req_phys_addr,
+ u64 resp_phys_addr)
+{
+ bpmp_ipc_ch->db_phys_addr = db_phys_addr;
+ bpmp_ipc_ch->req_phys_addr = req_phys_addr;
+ bpmp_ipc_ch->resp_phys_addr = resp_phys_addr;
+
+ bpmp_ipc_ch->db_base = ioremap(bpmp_ipc_ch->db_phys_addr, 64);
+ if (!bpmp_ipc_ch->db_base) {
+ cl_error("failed to remap aperture: 0x%llx\n",
+ (unsigned long long)bpmp_ipc_ch->db_phys_addr);
+ return -ENOMEM;
+ }
+ bpmp_ipc_ch->req_base = ioremap(bpmp_ipc_ch->req_phys_addr, IVC_CHANNEL_SIZE);
+ if (!bpmp_ipc_ch->req_base) {
+ iounmap(bpmp_ipc_ch->db_base);
+ cl_error("failed to remap aperture: 0x%llx\n",
+ (unsigned long long)bpmp_ipc_ch->req_phys_addr);
+ return -ENOMEM;
+ }
+ bpmp_ipc_ch->resp_base = ioremap(bpmp_ipc_ch->resp_phys_addr, IVC_CHANNEL_SIZE);
+ if (!bpmp_ipc_ch->resp_base) {
+ iounmap(bpmp_ipc_ch->db_base);
+ iounmap(bpmp_ipc_ch->req_base);
+ cl_error("failed to remap aperture: 0x%llx\n",
+ (unsigned long long)bpmp_ipc_ch->resp_phys_addr);
+ return -ENOMEM;
+ }
+
+ return OK;
+}
+
+static void bpmp_iounmap(struct bpmp_ipc_ch *bpmp_ipc_ch)
+{
+ iounmap(bpmp_ipc_ch->db_base);
+ iounmap(bpmp_ipc_ch->req_base);
+ iounmap(bpmp_ipc_ch->resp_base);
+
+ bpmp_ipc_ch->db_phys_addr = 0;
+ bpmp_ipc_ch->req_phys_addr = 0;
+ bpmp_ipc_ch->resp_phys_addr = 0;
+}
+
+static int bpmp_ipc_channel_init(struct mods_client *client,
+ struct bpmp_ipc_ch *bpmp_ipc_ch)
+{
+ int err;
+ ktime_t end;
+
+ err = tegra_ivc_init(&bpmp_ipc_ch->ivc, NULL,
+ bpmp_ipc_ch->resp_base, 0,
+ bpmp_ipc_ch->req_base, 0,
+ 1, MRQ_MSG_SIZE,
+ bpmp_ivc_notify, bpmp_ipc_ch);
+
+
+ if (err != 0) {
+ cl_error("tegra-ivc init failed: %d\n", err);
+ return err;
+ }
+
+ tegra_ivc_reset(&bpmp_ipc_ch->ivc);
+
+ end = ktime_add_us(ktime_get(), 2000 * 1000);
+
+ while (tegra_ivc_notified(&bpmp_ipc_ch->ivc) != 0) {
+ usleep_range(100, 200);
+ if (ktime_after(ktime_get(), end)) {
+ cl_error("initialize IVC connection timeout\n");
+ err = -ETIMEDOUT;
+ break;
+ }
+ }
+
+ bpmp_ipc_ch->is_init = true;
+
+ return err;
+}
+
+static void bpmp_ipc_channel_uninit(struct bpmp_ipc_ch *bpmp_ipc_ch)
+{
+ tegra_ivc_cleanup(&bpmp_ipc_ch->ivc);
+}
+
+int mods_bpmpipc_init(struct mods_client *client,
+ u64 db_phys_addr,
+ u64 req_phys_addr,
+ u64 resp_phys_addr)
+{
+ int err = OK;
+
+ if (mods_bpmp_ch.is_init) {
+ if (mods_bpmp_ch.db_phys_addr == db_phys_addr &&
+ mods_bpmp_ch.req_phys_addr == req_phys_addr &&
+ mods_bpmp_ch.resp_phys_addr == resp_phys_addr)
+ return OK;
+ mods_bpmpipc_cleanup();
+ }
+
+ err = bpmp_ioremap(client,
+ &mods_bpmp_ch,
+ db_phys_addr,
+ req_phys_addr,
+ resp_phys_addr);
+ if (err != OK)
+ return err;
+
+ err = bpmp_ipc_channel_init(client, &mods_bpmp_ch);
+ if (err != OK) {
+ bpmp_iounmap(&mods_bpmp_ch);
+ return err;
+ }
+
+ mods_bpmp_ch.is_init = true;
+ mods_debug_printk(DEBUG_TEGRADMA, "bpmp ipc init done\n");
+
+ return err;
+}
+
+void mods_bpmpipc_cleanup(void)
+{
+ if (!mods_bpmp_ch.is_init)
+ return;
+
+ bpmp_ipc_channel_uninit(&mods_bpmp_ch);
+ bpmp_iounmap(&mods_bpmp_ch);
+ mods_bpmp_ch.is_init = false;
+}
+
+int esc_mods_bpmp_uphy_lane_eom_scan(struct mods_client *client,
+ struct MODS_BPMP_UPHY_LANE_EOM_SCAN_PARAMS *p)
+{
+ int err = OK;
+
+ mutex_lock(&mods_bpmpipc_lock);
+
+ err = mods_bpmpipc_init(client,
+ p->db_phys_addr,
+ p->req_phys_addr,
+ p->resp_phys_addr);
+ if (err != OK)
+ goto error;
+
+ err = mrq_uphy_lane_eom_scan(client,
+ p->brick,
+ p->lane,
+ p->pcie_gen5,
+ &p->data);
+
+ if (err != OK)
+ cl_error("mrq uphy lane eom scan failed with brick(%u), lane(%u), pcie_gen5(%u)\n",
+ p->brick, p->lane, p->pcie_gen5);
+
+error:
+ mutex_unlock(&mods_bpmpipc_lock);
+ return err;
+}
diff --git a/drivers/misc/mods/mods_internal.h b/drivers/misc/mods/mods_internal.h
index 76290b98..a93793d2 100644
--- a/drivers/misc/mods/mods_internal.h
+++ b/drivers/misc/mods/mods_internal.h
@@ -755,4 +755,14 @@ int mods_ffa_abi_register(void);
void mods_ffa_abi_unregister(void);
#endif
+#if defined(CONFIG_TEGRA_IVC)
+int esc_mods_bpmp_uphy_lane_eom_scan(struct mods_client *client,
+ struct MODS_BPMP_UPHY_LANE_EOM_SCAN_PARAMS *p);
+int mods_bpmpipc_init(struct mods_client *client,
+ u64 db_phys_addr,
+ u64 req_phys_addr,
+ u64 resp_phys_addr);
+void mods_bpmpipc_cleanup(void);
+#endif
+
#endif /* _MODS_INTERNAL_H_ */
diff --git a/drivers/misc/mods/mods_krnl.c b/drivers/misc/mods/mods_krnl.c
index 0ca658f8..526cc8fd 100644
--- a/drivers/misc/mods/mods_krnl.c
+++ b/drivers/misc/mods/mods_krnl.c
@@ -1035,6 +1035,10 @@ static int mods_krnl_close(struct inode *ip, struct file *fp)
final_err = err;
#endif
+#if defined(CONFIG_TEGRA_IVC)
+ mods_bpmpipc_cleanup();
+#endif
+
mods_disable_all_devices(client);
{
@@ -2714,6 +2718,14 @@ static long mods_krnl_ioctl(struct file *fp,
esc_mods_get_driver_stats, MODS_GET_DRIVER_STATS);
break;
+#ifdef CONFIG_TEGRA_IVC
+ case MODS_ESC_BPMP_UPHY_LANE_EOM_SCAN:
+ MODS_IOCTL(MODS_ESC_BPMP_UPHY_LANE_EOM_SCAN,
+ esc_mods_bpmp_uphy_lane_eom_scan,
+ MODS_BPMP_UPHY_LANE_EOM_SCAN_PARAMS);
+ break;
+#endif
+
default:
cl_error(
"unrecognized ioctl 0x%x, dir %u, type 0x%x, nr %u, size 0x%x\n",
diff --git a/include/uapi/misc/mods.h b/include/uapi/misc/mods.h
index dc140810..837ba93e 100644
--- a/include/uapi/misc/mods.h
+++ b/include/uapi/misc/mods.h
@@ -1927,6 +1927,26 @@ struct MODS_PROXIMITY_TO_NUMA_NODE {
__s32 numa_node;
};
+/* Used by MODS_ESC_BPMP_UPHY_LANE_EOM_SCAN
+ *
+ * Get EOM data of UPHY lane from bpmp
+ */
+struct MODS_BPMP_UPHY_LANE_EOM_SCAN_PARAMS {
+ /* IN */
+ /* the address of bpmp db/req/resp is various for different
+ * chip/socket, the value of them will be passed from user.
+ */
+ __u64 db_phys_addr;
+ __u64 req_phys_addr;
+ __u64 resp_phys_addr;
+ __u32 brick;
+ __u32 lane;
+ __u32 pcie_gen5;
+
+ /* OUT */
+ __u32 data;
+};
+
#pragma pack(pop)
#define MODS_IOC_MAGIC 'x'
@@ -2133,5 +2153,8 @@ struct MODS_PROXIMITY_TO_NUMA_NODE {
#define MODS_ESC_PROXIMITY_TO_NUMA_NODE MODSIO(WR, 143, MODS_PROXIMITY_TO_NUMA_NODE)
#define MODS_ESC_MODS_SEND_IPI MODSIO(W, 144, MODS_SEND_IPI)
#define MODS_ESC_FFA_CMD MODSIO(WR, 145, MODS_FFA_PARAMS)
+#define MODS_ESC_BPMP_UPHY_LANE_EOM_SCAN MODSIO(WR, 146, \
+ MODS_BPMP_UPHY_LANE_EOM_SCAN_PARAMS)
+
#endif /* _UAPI_MODS_H_ */