[PATCH v2 2/6] bus: mhi: host: Add support for non-posted TSC timesync feature

Krishna Chaitanya Chundru
2026-04-11T08:12:19Z

From: Vivek Pernamitta <quic_vpernami@quicinc.com>

Implement non-posted time synchronization as described in section 5.1.1
of the MHI v1.2 specification. The host disables low-power link states
to minimize latency, reads the local time, issues a MMIO read to the
device's TIME register.

Add support for initializing this feature and export a function to be
used by the drivers which does the time synchronization.

MHI reads the device time registers in the MMIO address space pointed to
by the capability register after disabling all low power modes and keeping
MHI in M0. Before and after MHI reads, the local time is captured
and shared for processing.

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/init.c | 28 ++++++++++++++++
drivers/bus/mhi/host/internal.h | 9 +++++
drivers/bus/mhi/host/main.c | 74 +++++++++++++++++++++++++++++++++++++++++
include/linux/mhi.h | 37 +++++++++++++++++++++
5 files changed, 152 insertions(+)

diff --git a/drivers/bus/mhi/common.h b/drivers/bus/mhi/common.h
index 4c316f3d5a68beb01f15cf575b03747096fdcf2c..64f9b2b94387a112bb6b5e20c634c3ba8d6bc78e 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/init.c b/drivers/bus/mhi/host/init.c
index c2162aa04e810e45ccfbedd20aaa62f892420d31..eb720f671726d919646cbc450cd54bda655a1060 100644
--- a/drivers/bus/mhi/host/init.c
+++ b/drivers/bus/mhi/host/init.c
@@ -498,6 +498,30 @@ static int mhi_find_capability(struct mhi_controller *mhi_cntrl, u32 capability)
return 0;
}

+static int mhi_init_tsc_timesync(struct mhi_controller *mhi_cntrl)
+{
+ struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ struct mhi_timesync *mhi_tsc_tsync;
+ u32 time_offset;
+ int ret;
+
+ time_offset = mhi_find_capability(mhi_cntrl, MHI_CAP_ID_TSC_TIME_SYNC);
+ if (!time_offset)
+ return -ENXIO;
+
+ mhi_tsc_tsync = devm_kzalloc(dev, sizeof(*mhi_tsc_tsync), GFP_KERNEL);
+ if (!mhi_tsc_tsync)
+ return -ENOMEM;
+
+ mhi_cntrl->tsc_timesync = mhi_tsc_tsync;
+ mutex_init(&mhi_tsc_tsync->ts_mutex);
+
+ /* save time_offset for obtaining time via MMIO register reads */
+ mhi_tsc_tsync->time_reg = mhi_cntrl->regs + time_offset;
+
+ return 0;
+}
+
int mhi_init_mmio(struct mhi_controller *mhi_cntrl)
{
u32 val;
@@ -635,6 +659,10 @@ int mhi_init_mmio(struct mhi_controller *mhi_cntrl)
return ret;
}

+ ret = mhi_init_tsc_timesync(mhi_cntrl);
+ if (ret)
+ dev_dbg(dev, "TSC Time synchronization init failure\n");
+
return 0;
}

diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h
index 7b0ee5e3a12dd585064169b7b884750bf4d8c8db..a0e729e7a1198c1b82c70b6bfe3bc2ee24331229 100644
--- a/drivers/bus/mhi/host/internal.h
+++ b/drivers/bus/mhi/host/internal.h
@@ -15,6 +15,15 @@ extern const struct bus_type mhi_bus_type;
#define MHI_SOC_RESET_REQ_OFFSET 0xb0
#define MHI_SOC_RESET_REQ BIT(0)

+/*
+ * With ASPM enabled, the link may enter a low power state, requiring
+ * a wake-up sequence. Use a short burst of back-to-back reads to
+ * transition the link to the active state. Based on testing,
+ * 4 iterations are necessary to ensure reliable wake-up without
+ * excess latency.
+ */
+#define MHI_NUM_BACK_TO_BACK_READS 4
+
struct mhi_ctxt {
struct mhi_event_ctxt *er_ctxt;
struct mhi_chan_ctxt *chan_ctxt;
diff --git a/drivers/bus/mhi/host/main.c b/drivers/bus/mhi/host/main.c
index 53c0ffe300702bcc3caa8fd9ea8086203c75b186..b7a727b1a5d1f20b570c62707a991ec5b85bfec7 100644
--- a/drivers/bus/mhi/host/main.c
+++ b/drivers/bus/mhi/host/main.c
@@ -1626,3 +1626,77 @@ int mhi_get_channel_doorbell_offset(struct mhi_controller *mhi_cntrl, u32 *chdb_
return 0;
}
EXPORT_SYMBOL_GPL(mhi_get_channel_doorbell_offset);
+
+static int mhi_get_remote_time(struct mhi_controller *mhi_cntrl, struct mhi_timesync *mhi_tsync,
+ struct mhi_timesync_info *time)
+{
+ struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ int ret, i;
+
+ if (!mhi_tsync && !mhi_tsync->time_reg) {
+ dev_err(dev, "Time sync is not supported\n");
+ return -EINVAL;
+ }
+
+ if (unlikely(MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state))) {
+ dev_err(dev, "MHI is not in active state, pm_state:%s\n",
+ to_mhi_pm_state_str(mhi_cntrl->pm_state));
+ return -EIO;
+ }
+
+ /* bring to M0 state */
+ ret = mhi_device_get_sync(mhi_cntrl->mhi_dev);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&mhi_tsync->ts_mutex);
+ mhi_cntrl->runtime_get(mhi_cntrl);
+
+ /*
+ * time critical code to fetch device time, delay between these two steps
+ * should be deterministic as possible.
+ */
+ preempt_disable();
+ local_irq_disable();
+
+ time->t_host_pre = ktime_get_real();
+
+ /*
+ * To ensure the PCIe link is in L0 when ASPM is enabled, perform series
+ * of back-to-back reads. This is necessary because the link may be in a
+ * low-power state (e.g., L1 or L1ss), and need to be forced it to
+ * transition to L0.
+ */
+ for (i = 0; i < MHI_NUM_BACK_TO_BACK_READS; i++) {
+ ret = mhi_read_reg(mhi_cntrl, mhi_tsync->time_reg,
+ TSC_TIMESYNC_TIME_LOW_OFFSET, &time->t_dev_lo);
+
+ ret = mhi_read_reg(mhi_cntrl, mhi_tsync->time_reg,
+ TSC_TIMESYNC_TIME_HIGH_OFFSET, &time->t_dev_hi);
+ }
+
+ time->t_host_post = ktime_get_real();
+
+ local_irq_enable();
+ preempt_enable();
+
+ mhi_cntrl->runtime_put(mhi_cntrl);
+
+ mhi_device_put(mhi_cntrl->mhi_dev);
+
+ return 0;
+}
+
+int mhi_get_remote_tsc_time_sync(struct mhi_device *mhi_dev, struct mhi_timesync_info *time)
+{
+ struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl;
+ struct mhi_timesync *mhi_tsc_tsync = mhi_cntrl->tsc_timesync;
+ int ret;
+
+ ret = mhi_get_remote_time(mhi_cntrl, mhi_tsc_tsync, time);
+ if (ret)
+ dev_err(&mhi_dev->dev, "Failed to get TSC Time Sync value:%d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mhi_get_remote_tsc_time_sync);
diff --git a/include/linux/mhi.h b/include/linux/mhi.h
index 88ccb3e14f481d6b85c2a314eb74ba960c2d4c81..f39c8ca7c251954f2d83c1227d206b600b88c75f 100644
--- a/include/linux/mhi.h
+++ b/include/linux/mhi.h
@@ -286,6 +286,30 @@ struct mhi_controller_config {
bool m2_no_db;
};

+/**
+ * struct mhi_timesync - MHI time synchronization structure
+ * @time_reg: Points to address of Timesync register
+ * @ts_mutex: Mutex for synchronization
+ */
+struct mhi_timesync {
+ void __iomem *time_reg;
+ struct mutex ts_mutex;
+};
+
+/**
+ * struct mhi_timesync_info - MHI time sync info structure
+ * @t_host_pre: Pre host soc time
+ * @t_host_post: Post host soc time
+ * @t_dev_lo: Mhi device time of lower dword
+ * @t_dev_hi: Mhi device time of higher dword
+ */
+struct mhi_timesync_info {
+ ktime_t t_host_pre;
+ ktime_t t_host_post;
+ u32 t_dev_lo;
+ u32 t_dev_hi;
+};
+
/**
* struct mhi_controller - Master MHI controller structure
* @name: Device name of the MHI controller
@@ -323,6 +347,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
@@ -401,6 +426,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;
@@ -795,4 +822,14 @@ bool mhi_queue_is_full(struct mhi_device *mhi_dev, enum dma_data_direction dir);
*/
int mhi_get_channel_doorbell_offset(struct mhi_controller *mhi_cntrl, u32 *chdb_offset);

+/**
+ * mhi_get_remote_tsc_time_sync - get external soc time relative to local soc
+ * time pre and post using MMIO method.
+ * @mhi_dev: Device associated with the channels
+ * @time: mhi_timesync_info to get device time details
+ *
+ * Returns:
+ * 0 for success, error code for failure
+ */
+int mhi_get_remote_tsc_time_sync(struct mhi_device *mhi_dev, struct mhi_timesync_info *time);
#endif /* _MHI_H_ */

--
2.34.1