From: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
To: Manivannan Sadhasivam <mani@kernel.org>,
Jeff Hugo <jeff.hugo@oss.qualcomm.com>,
Richard Cochran <richardcochran@gmail.com>
Cc: mhi@lists.linux.dev, linux-arm-msm@vger.kernel.org,
linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>,
Taniya Das <taniya.das@oss.qualcomm.com>,
Imran Shaik <imran.shaik@oss.qualcomm.com>,
Vivek Pernamitta <quic_vpernami@quicinc.com>
Subject: [PATCH v3 2/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI
Date: Thu, 17 Sep 2026 12:38:03 +0530 [thread overview]
Message-ID: <20260917-tsc_timesync-v3-2-6826d5d2d527@oss.qualcomm.com> (raw)
In-Reply-To: <20260917-tsc_timesync-v3-0-6826d5d2d527@oss.qualcomm.com>
Implement the non-posted time synchronization mechanism described in
section 5.1.1 of the MHI v1.2 specification and expose the device clock
as a PTP Hardware Clock.
Discover the TSC Timesync capability during MHI controller
initialization and use the capability offset to access the device TIME
registers. Register the PHC when the device enters mission mode and
unregister it when the device encounters a fatal error, enters a system
error state, or is removed.
When userspace requests a timestamp, keep the MHI device runtime-active
and bring it to the M0 state before accessing the device registers.
Capture system timestamps immediately before and after reading the
device timestamp, allowing userspace to correlate the host and device
clocks through the standard Linux PTP interface.
Co-developed-by: Taniya Das <taniya.das@oss.qualcomm.com>
Signed-off-by: Taniya Das <taniya.das@oss.qualcomm.com>
Co-developed-by: Imran Shaik <imran.shaik@oss.qualcomm.com>
Signed-off-by: Imran Shaik <imran.shaik@oss.qualcomm.com>
Co-developed-by: Vivek Pernamitta <quic_vpernami@quicinc.com>
Signed-off-by: Vivek Pernamitta <quic_vpernami@quicinc.com>
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/bus/mhi/common.h | 4 +
drivers/bus/mhi/host/Kconfig | 9 +++
drivers/bus/mhi/host/Makefile | 2 +
drivers/bus/mhi/host/init.c | 33 ++++++++
drivers/bus/mhi/host/internal.h | 31 ++++++++
drivers/bus/mhi/host/mhi_phc.c | 154 +++++++++++++++++++++++++++++++++++++
drivers/bus/mhi/host/mhi_phc.h | 21 +++++
drivers/bus/mhi/host/pci_generic.c | 6 ++
include/linux/mhi.h | 5 ++
9 files changed, 265 insertions(+)
diff --git a/drivers/bus/mhi/common.h b/drivers/bus/mhi/common.h
index 4c316f3d5a68..64f9b2b94387 100644
--- a/drivers/bus/mhi/common.h
+++ b/drivers/bus/mhi/common.h
@@ -118,6 +118,10 @@
#define CAP_CAPID_MASK GENMASK(31, 24)
#define CAP_NEXT_CAP_MASK GENMASK(23, 12)
+/* MHI TSC Timesync */
+#define TSC_TIMESYNC_TIME_LOW_OFFSET (0x8)
+#define TSC_TIMESYNC_TIME_HIGH_OFFSET (0xC)
+
/* Command Ring Element macros */
/* No operation command */
#define MHI_TRE_CMD_NOOP_PTR 0
diff --git a/drivers/bus/mhi/host/Kconfig b/drivers/bus/mhi/host/Kconfig
index da5cd0c9fc62..7609768c0efe 100644
--- a/drivers/bus/mhi/host/Kconfig
+++ b/drivers/bus/mhi/host/Kconfig
@@ -29,3 +29,12 @@ config MHI_BUS_PCI_GENERIC
This driver provides MHI PCI controller driver for devices such as
Qualcomm SDX55 based PCIe modems.
+config MHI_BUS_PHC
+ tristate "MHI PHC driver"
+ depends on MHI_BUS
+ depends on PTP_1588_CLOCK
+ help
+ This driver provides Precision Time Protocol (PTP) clock and
+ communicates with MHI PCI driver to get the device side timestamp,
+ which enables precise time synchronization between the host and
+ the device.
diff --git a/drivers/bus/mhi/host/Makefile b/drivers/bus/mhi/host/Makefile
index 859c2f38451c..f16b3195a03a 100644
--- a/drivers/bus/mhi/host/Makefile
+++ b/drivers/bus/mhi/host/Makefile
@@ -4,3 +4,5 @@ mhi-$(CONFIG_MHI_BUS_DEBUG) += debugfs.o
obj-$(CONFIG_MHI_BUS_PCI_GENERIC) += mhi_pci_generic.o
mhi_pci_generic-y += pci_generic.o
+
+obj-$(CONFIG_MHI_BUS_PHC) += mhi_phc.o
diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c
index 7ca2fe48c9ac..caa307c71890 100644
--- a/drivers/bus/mhi/host/init.c
+++ b/drivers/bus/mhi/host/init.c
@@ -497,6 +497,32 @@ static int mhi_find_capability(struct mhi_controller *mhi_cntrl, u32 capability)
return 0;
}
+#if IS_ENABLED(CONFIG_MHI_BUS_PHC)
+int mhi_init_tsc_timesync(struct mhi_controller *mhi_cntrl)
+{
+ struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ struct mhi_timesync *mhi_tsync;
+ u32 time_offset;
+
+ time_offset = mhi_find_capability(mhi_cntrl, MHI_CAP_ID_TSC_TIME_SYNC);
+ if (!time_offset)
+ return -ENXIO;
+
+ mhi_tsync = devm_kzalloc(dev, sizeof(*mhi_tsync), GFP_KERNEL);
+ if (!mhi_tsync)
+ return -ENOMEM;
+
+ mutex_init(&mhi_tsync->ts_mutex);
+
+ /* save time_offset for obtaining time via MMIO register reads */
+ mhi_tsync->time_reg = mhi_cntrl->regs + time_offset;
+
+ mhi_cntrl->tsc_timesync = mhi_tsync;
+
+ return 0;
+}
+#endif
+
int mhi_init_mmio(struct mhi_controller *mhi_cntrl)
{
u32 val;
@@ -634,6 +660,13 @@ int mhi_init_mmio(struct mhi_controller *mhi_cntrl)
return ret;
}
+ /* Discover TSC time sync capability, if present */
+ ret = mhi_init_tsc_timesync(mhi_cntrl);
+ if (ret && ret != -ENXIO) {
+ dev_err(dev, "Error configuring TSC time sync\n");
+ return ret;
+ }
+
return 0;
}
diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h
index 7b0ee5e3a12d..ca50961769fa 100644
--- a/drivers/bus/mhi/host/internal.h
+++ b/drivers/bus/mhi/host/internal.h
@@ -9,6 +9,10 @@
#include "../common.h"
+#if IS_ENABLED(CONFIG_MHI_BUS_PHC)
+#include <linux/ptp_clock_kernel.h>
+#endif
+
extern const struct bus_type mhi_bus_type;
/* Host request register */
@@ -24,6 +28,24 @@ struct mhi_ctxt {
dma_addr_t cmd_ctxt_addr;
};
+#if IS_ENABLED(CONFIG_MHI_BUS_PHC)
+/**
+ * struct mhi_timesync - MHI time synchronization structure
+ * @time_reg: Points to address of Timesync register
+ * @ts_mutex: Mutex for synchronization
+ * @ptp_clock: associated PTP clock
+ * @ptp_clock_info: PTP clock information
+ * @mhi_dev: associated mhi device object
+ */
+struct mhi_timesync {
+ void __iomem *time_reg;
+ struct mutex ts_mutex;
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info ptp_clock_info;
+ struct mhi_device *mhi_dev;
+};
+#endif
+
struct bhi_vec_entry {
__le64 dma_addr;
__le64 size;
@@ -388,6 +410,15 @@ int mhi_rddm_prepare(struct mhi_controller *mhi_cntrl,
struct image_info *img_info);
void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl);
+#if IS_ENABLED(CONFIG_MHI_BUS_PHC)
+int mhi_init_tsc_timesync(struct mhi_controller *mhi_cntrl);
+#else
+static inline int mhi_init_tsc_timesync(struct mhi_controller *mhi_cntrl)
+{
+ return 0;
+}
+#endif
+
int mhi_init_chan_ctxt(struct mhi_controller *mhi_cntrl,
struct mhi_chan *mhi_chan);
void mhi_deinit_chan_ctxt(struct mhi_controller *mhi_cntrl,
diff --git a/drivers/bus/mhi/host/mhi_phc.c b/drivers/bus/mhi/host/mhi_phc.c
new file mode 100644
index 000000000000..783f05b5d144
--- /dev/null
+++ b/drivers/bus/mhi/host/mhi_phc.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/module.h>
+#include <linux/mhi.h>
+#include <linux/ptp_clock_kernel.h>
+#include "../common.h"
+#include "internal.h"
+#include "mhi_phc.h"
+
+/**
+ * struct mhi_timesync_info - MHI time sync info structure
+ * @t_dev_lo: MHI device time of lower dword
+ * @t_dev_hi: MHI device time of higher dword
+ */
+struct mhi_timesync_info {
+ u32 t_dev_lo;
+ u32 t_dev_hi;
+};
+
+static int mhi_get_remote_tsc_time_sync(struct mhi_device *mhi_dev, struct mhi_timesync_info *time,
+ struct ptp_system_timestamp *sts)
+{
+ struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl;
+ struct mhi_timesync *mhi_tsync = mhi_cntrl->tsc_timesync;
+ struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ u64 val = U64_MAX;
+ u32 hi, lo = 0;
+ int ret;
+
+ if (!mhi_tsync || !mhi_tsync->time_reg) {
+ dev_err(dev, "Time sync is not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ mhi_cntrl->runtime_get(mhi_cntrl);
+
+ /* bring to M0 state */
+ ret = mhi_device_get_sync(mhi_cntrl->mhi_dev);
+ if (ret) {
+ mhi_cntrl->runtime_put(mhi_cntrl);
+ return ret;
+ }
+
+ guard(mutex)(&mhi_tsync->ts_mutex);
+
+ // TODO: Disable ASPM
+
+ /*
+ * time critical code to fetch device time, delay between these two steps
+ * should be deterministic as possible.
+ */
+ preempt_disable();
+ local_irq_disable();
+
+ ptp_read_system_prets(sts);
+
+ /*
+ * Read the high dword twice around the low dword and retry if it
+ * changed, to avoid a torn read across a device-side rollover.
+ */
+ do {
+ ret = mhi_read_reg(mhi_cntrl, mhi_tsync->time_reg,
+ TSC_TIMESYNC_TIME_HIGH_OFFSET, &hi);
+ if (ret)
+ break;
+
+ ret = mhi_read_reg(mhi_cntrl, mhi_tsync->time_reg,
+ TSC_TIMESYNC_TIME_LOW_OFFSET, &lo);
+ if (ret)
+ break;
+
+ ret = mhi_read_reg(mhi_cntrl, mhi_tsync->time_reg,
+ TSC_TIMESYNC_TIME_HIGH_OFFSET, &time->t_dev_hi);
+ } while (!ret && hi != time->t_dev_hi);
+
+ time->t_dev_lo = lo;
+
+ ptp_read_system_postts(sts);
+
+ local_irq_enable();
+ preempt_enable();
+
+ mhi_device_put(mhi_cntrl->mhi_dev);
+
+ mhi_cntrl->runtime_put(mhi_cntrl);
+
+ return ret;
+}
+
+static int qcom_ptp_gettimex64(struct ptp_clock_info *ptp, struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ struct mhi_timesync *mhi_tsync = container_of(ptp, struct mhi_timesync, ptp_clock_info);
+ struct mhi_timesync_info time;
+ ktime_t ktime_cur;
+ int ret;
+
+ ret = mhi_get_remote_tsc_time_sync(mhi_tsync->mhi_dev, &time, sts);
+ if (ret)
+ return ret;
+
+ ktime_cur = time.t_dev_hi * NSEC_PER_SEC + time.t_dev_lo;
+ *ts = ktime_to_timespec64(ktime_cur);
+
+ return 0;
+}
+
+static struct ptp_clock_info qcom_ptp_clock_info = {
+ .owner = THIS_MODULE,
+ .gettimex64 = qcom_ptp_gettimex64,
+};
+
+int mhi_phc_init(struct mhi_controller *mhi_cntrl)
+{
+ struct mhi_device *mhi_dev = mhi_cntrl->mhi_dev;
+ struct mhi_timesync *mhi_tsync = mhi_cntrl->tsc_timesync;
+ int ret;
+
+ if (!mhi_tsync)
+ return -ENODEV;
+
+ mhi_tsync->mhi_dev = mhi_dev;
+
+ mhi_tsync->ptp_clock_info = qcom_ptp_clock_info;
+ strscpy(mhi_tsync->ptp_clock_info.name, mhi_dev->name, PTP_CLOCK_NAME_LEN);
+
+ mhi_tsync->ptp_clock = ptp_clock_register(&mhi_tsync->ptp_clock_info, &mhi_dev->dev);
+ if (IS_ERR(mhi_tsync->ptp_clock)) {
+ ret = PTR_ERR(mhi_tsync->ptp_clock);
+ dev_err(&mhi_dev->dev, "Failed to register PTP clock\n");
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mhi_phc_init);
+
+void mhi_phc_exit(struct mhi_controller *mhi_cntrl)
+{
+ struct mhi_timesync *mhi_tsync = mhi_cntrl->tsc_timesync;
+
+ if (!mhi_tsync || !mhi_tsync->ptp_clock)
+ return;
+
+ ptp_clock_unregister(mhi_tsync->ptp_clock);
+ mhi_tsync->ptp_clock = NULL;
+}
+EXPORT_SYMBOL_GPL(mhi_phc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MHI PHC driver");
diff --git a/drivers/bus/mhi/host/mhi_phc.h b/drivers/bus/mhi/host/mhi_phc.h
new file mode 100644
index 000000000000..263574ca53da
--- /dev/null
+++ b/drivers/bus/mhi/host/mhi_phc.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef _MHI_PHC_H
+#define _MHI_PHC_H
+
+#if IS_ENABLED(CONFIG_MHI_BUS_PHC)
+int mhi_phc_init(struct mhi_controller *mhi_cntrl);
+void mhi_phc_exit(struct mhi_controller *mhi_cntrl);
+#else
+static inline int mhi_phc_init(struct mhi_controller *mhi_cntrl)
+{
+ return 0;
+}
+
+static inline void mhi_phc_exit(struct mhi_controller *mhi_cntrl) {}
+#endif
+
+#endif /* _MHI_PHC_H */
diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c
index b636e2c23b4d..f47867ced1d6 100644
--- a/drivers/bus/mhi/host/pci_generic.c
+++ b/drivers/bus/mhi/host/pci_generic.c
@@ -16,6 +16,7 @@
#include <linux/pm_runtime.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
+#include "mhi_phc.h"
#define MHI_PCI_DEFAULT_BAR_NUM 0
@@ -1123,9 +1124,11 @@ static void mhi_pci_status_cb(struct mhi_controller *mhi_cntrl,
case MHI_CB_FATAL_ERROR:
case MHI_CB_SYS_ERROR:
dev_warn(&pdev->dev, "firmware crashed (%u)\n", cb);
+ mhi_phc_exit(mhi_cntrl);
pm_runtime_forbid(&pdev->dev);
break;
case MHI_CB_EE_MISSION_MODE:
+ mhi_phc_init(mhi_cntrl);
pm_runtime_allow(&pdev->dev);
break;
default:
@@ -1491,6 +1494,9 @@ static void mhi_pci_remove(struct pci_dev *pdev)
timer_delete_sync(&mhi_pdev->health_check_timer);
cancel_work_sync(&mhi_pdev->recovery_work);
+ /* Remove PHC */
+ mhi_phc_exit(mhi_cntrl);
+
if (test_and_clear_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status)) {
mhi_power_down(mhi_cntrl, true);
mhi_unprepare_after_power_down(mhi_cntrl);
diff --git a/include/linux/mhi.h b/include/linux/mhi.h
index 8616bacd8675..e994ca4f9810 100644
--- a/include/linux/mhi.h
+++ b/include/linux/mhi.h
@@ -23,6 +23,8 @@ struct mhi_event;
struct mhi_ctxt;
struct mhi_cmd;
struct mhi_buf_info;
+struct mhi_timesync;
+
/**
* enum mhi_callback - MHI callback
@@ -341,6 +343,7 @@ struct mhi_controller_config {
* @mhi_event: MHI event ring configurations table
* @mhi_cmd: MHI command ring configurations table
* @mhi_ctxt: MHI device context, shared memory between host and device
+ * @tsc_timesync: MHI TSC timesync
* @pm_mutex: Mutex for suspend/resume operation
* @pm_lock: Lock for protecting MHI power management state
* @timeout_ms: Timeout in ms for state transitions
@@ -422,6 +425,8 @@ struct mhi_controller {
struct mhi_cmd *mhi_cmd;
struct mhi_ctxt *mhi_ctxt;
+ struct mhi_timesync *tsc_timesync;
+
struct mutex pm_mutex;
rwlock_t pm_lock;
u32 timeout_ms;
--
2.34.1
next prev parent reply other threads:[~2026-09-17 7:08 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-17 7:08 [PATCH v3 0/6] " Krishna Chaitanya Chundru
2026-09-17 7:08 ` [PATCH v3 1/6] bus: mhi: host: Add support to read MHI capabilities Krishna Chaitanya Chundru
2026-09-17 7:08 ` Krishna Chaitanya Chundru [this message]
2026-09-17 7:08 ` [PATCH v3 3/6] bus: mhi: host: Add support for 64bit register reads and writes Krishna Chaitanya Chundru
2026-09-17 7:08 ` [PATCH v3 4/6] bus: mhi: pci_generic: Add support for 64 bit register read & write Krishna Chaitanya Chundru
2026-09-17 7:08 ` [PATCH v3 5/6] bus: mhi: host: Export mhi_read_reg Krishna Chaitanya Chundru
2026-09-17 7:08 ` [PATCH v3 6/6] bus: mhi: host: mhi_phc: Update the Time sync logic to read 64 bit register value Krishna Chaitanya Chundru
2026-09-17 14:48 ` [PATCH v3 0/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI Jakub Kicinski
2026-09-18 10:52 ` Krishna Chaitanya Chundru
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260917-tsc_timesync-v3-2-6826d5d2d527@oss.qualcomm.com \
--to=krishna.chundru@oss.qualcomm.com \
--cc=imran.shaik@oss.qualcomm.com \
--cc=jeff.hugo@oss.qualcomm.com \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mani@kernel.org \
--cc=mhi@lists.linux.dev \
--cc=netdev@vger.kernel.org \
--cc=quic_vpernami@quicinc.com \
--cc=richardcochran@gmail.com \
--cc=taniya.das@oss.qualcomm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®