* [PATCH v2 1/6] bus: mhi: host: Add support to read MHI capabilities
2026-04-11 8:12 [PATCH v2 0/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI Krishna Chaitanya Chundru
@ 2026-04-11 8:12 ` Krishna Chaitanya Chundru
2026-04-11 8:12 ` [PATCH v2 2/6] bus: mhi: host: Add support for non-posted TSC timesync feature Krishna Chaitanya Chundru
` (5 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-04-11 8:12 UTC (permalink / raw)
To: Manivannan Sadhasivam, Richard Cochran
Cc: mhi, linux-arm-msm, linux-kernel, netdev,
Krishna Chaitanya Chundru, Vivek Pernamitta, Sivareddy Surasani
From: Vivek Pernamitta <vivek.pernamitta@oss.qualcomm.com>
As per MHI spec v1.2,sec 6.6, MHI has capability registers which are
located after the ERDB array. The location of this group of registers is
indicated by the MISCOFF register. Each capability has a capability ID to
determine which functionality is supported and each capability will point
to the next capability supported.
Add a basic function to read those capabilities offsets.
Signed-off-by: Vivek Pernamitta <vivek.pernamitta@oss.qualcomm.com>
Signed-off-by: Sivareddy Surasani <sivareddy.surasani@oss.qualcomm.com>
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/bus/mhi/common.h | 11 +++++++++++
drivers/bus/mhi/host/init.c | 32 ++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+)
diff --git a/drivers/bus/mhi/common.h b/drivers/bus/mhi/common.h
index dda340aaed95a5573a2ec776ca712e11a1ed0b52..4c316f3d5a68beb01f15cf575b03747096fdcf2c 100644
--- a/drivers/bus/mhi/common.h
+++ b/drivers/bus/mhi/common.h
@@ -16,6 +16,7 @@
#define MHICFG 0x10
#define CHDBOFF 0x18
#define ERDBOFF 0x20
+#define MISCOFF 0x24
#define BHIOFF 0x28
#define BHIEOFF 0x2c
#define DEBUGOFF 0x30
@@ -113,6 +114,9 @@
#define MHISTATUS_MHISTATE_MASK GENMASK(15, 8)
#define MHISTATUS_SYSERR_MASK BIT(2)
#define MHISTATUS_READY_MASK BIT(0)
+#define MISC_CAP_MASK GENMASK(31, 0)
+#define CAP_CAPID_MASK GENMASK(31, 24)
+#define CAP_NEXT_CAP_MASK GENMASK(23, 12)
/* Command Ring Element macros */
/* No operation command */
@@ -204,6 +208,13 @@
#define MHI_RSCTRE_DATA_DWORD1 cpu_to_le32(FIELD_PREP(GENMASK(23, 16), \
MHI_PKT_TYPE_COALESCING))
+#define MHI_CAP_ID_INTX 0x1
+#define MHI_CAP_ID_TIME_SYNC 0x2
+#define MHI_CAP_ID_BW_SCALE 0x3
+#define MHI_CAP_ID_TSC_TIME_SYNC 0x4
+#define MHI_CAP_ID_MAX_TRB_LEN 0x5
+#define MHI_CAP_ID_MAX 0x6
+
enum mhi_pkt_type {
MHI_PKT_TYPE_INVALID = 0x0,
MHI_PKT_TYPE_NOOP_CMD = 0x1,
diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c
index 0a728ca2c494836b0e0ce4c3f4aea41794c0868b..c2162aa04e810e45ccfbedd20aaa62f892420d31 100644
--- a/drivers/bus/mhi/host/init.c
+++ b/drivers/bus/mhi/host/init.c
@@ -466,6 +466,38 @@ static int mhi_init_dev_ctxt(struct mhi_controller *mhi_cntrl)
return ret;
}
+static int mhi_find_capability(struct mhi_controller *mhi_cntrl, u32 capability)
+{
+ u32 val, cur_cap, next_offset, cur_offset;
+ int ret;
+
+ /* Get the first supported capability offset */
+ ret = mhi_read_reg_field(mhi_cntrl, mhi_cntrl->regs, MISCOFF, MISC_CAP_MASK, &cur_offset);
+ if (ret)
+ return 0;
+
+ do {
+ if (cur_offset >= mhi_cntrl->reg_len)
+ return 0;
+
+ ret = mhi_read_reg(mhi_cntrl, mhi_cntrl->regs, cur_offset, &val);
+ if (ret)
+ return 0;
+
+ cur_cap = FIELD_GET(CAP_CAPID_MASK, val);
+ next_offset = FIELD_GET(CAP_NEXT_CAP_MASK, val);
+ if (cur_cap >= MHI_CAP_ID_MAX)
+ return 0;
+
+ if (cur_cap == capability)
+ return cur_offset;
+
+ cur_offset = next_offset;
+ } while (next_offset);
+
+ return 0;
+}
+
int mhi_init_mmio(struct mhi_controller *mhi_cntrl)
{
u32 val;
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v2 2/6] bus: mhi: host: Add support for non-posted TSC timesync feature
2026-04-11 8:12 [PATCH v2 0/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI Krishna Chaitanya Chundru
2026-04-11 8:12 ` [PATCH v2 1/6] bus: mhi: host: Add support to read MHI capabilities Krishna Chaitanya Chundru
@ 2026-04-11 8:12 ` Krishna Chaitanya Chundru
2026-04-11 13:43 ` Jie Gan
` (3 more replies)
2026-04-11 8:12 ` [PATCH v2 3/6] bus: mhi: host: Add support for 64bit register reads and writes Krishna Chaitanya Chundru
` (4 subsequent siblings)
6 siblings, 4 replies; 15+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-04-11 8:12 UTC (permalink / raw)
To: Manivannan Sadhasivam, Richard Cochran
Cc: mhi, linux-arm-msm, linux-kernel, netdev,
Krishna Chaitanya Chundru, Vivek Pernamitta
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
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v2 2/6] bus: mhi: host: Add support for non-posted TSC timesync feature
2026-04-11 8:12 ` [PATCH v2 2/6] bus: mhi: host: Add support for non-posted TSC timesync feature Krishna Chaitanya Chundru
@ 2026-04-11 13:43 ` Jie Gan
2026-04-13 6:42 ` Manivannan Sadhasivam
` (2 subsequent siblings)
3 siblings, 0 replies; 15+ messages in thread
From: Jie Gan @ 2026-04-11 13:43 UTC (permalink / raw)
To: Krishna Chaitanya Chundru, Manivannan Sadhasivam, Richard Cochran
Cc: mhi, linux-arm-msm, linux-kernel, netdev, Vivek Pernamitta
On 4/11/2026 4:12 PM, Krishna Chaitanya Chundru wrote:
> 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) {
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);
ret is assigned but no where checked.
Thanks,
Jie
> + }
> +
> + 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_ */
>
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 2/6] bus: mhi: host: Add support for non-posted TSC timesync feature
2026-04-11 8:12 ` [PATCH v2 2/6] bus: mhi: host: Add support for non-posted TSC timesync feature Krishna Chaitanya Chundru
2026-04-11 13:43 ` Jie Gan
@ 2026-04-13 6:42 ` Manivannan Sadhasivam
2026-04-13 7:27 ` Manivannan Sadhasivam
2026-04-14 17:46 ` Vadim Fedorenko
3 siblings, 0 replies; 15+ messages in thread
From: Manivannan Sadhasivam @ 2026-04-13 6:42 UTC (permalink / raw)
To: Krishna Chaitanya Chundru
Cc: Richard Cochran, mhi, linux-arm-msm, linux-kernel, netdev,
Vivek Pernamitta
On Sat, Apr 11, 2026 at 01:42:02PM +0530, Krishna Chaitanya Chundru wrote:
> 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;
> +}
Move all the function definitions from MHI core code to mhi_phc.c.
> +
> 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");
And just keep this call in the MHI core.
> +
> 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;
-EOPNOTSUPP
> + }
> +
> + 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.
> + */
You should be doing these back-to-back reads only if ASPM is enabled. You can
check that using pcie_aspm_enabled(). Also, see if you can hide this call inside
pci_generic driver to make mhi_phc truly transport agnostic.
> + 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);
What is the difference between 'TSC' and 'Time Sync'?
Nit: Space after 'value:'
> +
> + 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
Get rid of 'soc', it provides no value.
> + * @t_dev_lo: Mhi device time of lower dword
> + * @t_dev_hi: Mhi device time of higher dword
s/Mhi/MHI
> + */
> +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
Same comment as above
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 2/6] bus: mhi: host: Add support for non-posted TSC timesync feature
2026-04-11 8:12 ` [PATCH v2 2/6] bus: mhi: host: Add support for non-posted TSC timesync feature Krishna Chaitanya Chundru
2026-04-11 13:43 ` Jie Gan
2026-04-13 6:42 ` Manivannan Sadhasivam
@ 2026-04-13 7:27 ` Manivannan Sadhasivam
2026-04-14 17:46 ` Vadim Fedorenko
3 siblings, 0 replies; 15+ messages in thread
From: Manivannan Sadhasivam @ 2026-04-13 7:27 UTC (permalink / raw)
To: Krishna Chaitanya Chundru
Cc: Richard Cochran, mhi, linux-arm-msm, linux-kernel, netdev,
Vivek Pernamitta
On Sat, Apr 11, 2026 at 01:42:02PM +0530, Krishna Chaitanya Chundru wrote:
> 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.
> + */
Thinking more, to be deterministic, ASPM should be disabled at this point. But
we cannot do that safely now from PCI client drivers until this series gets in:
https://lore.kernel.org/linux-arm-msm/20250825-ath-aspm-fix-v2-0-61b2f2db7d89@oss.qualcomm.com/
So let's keep it this way for now, but add a FIXME to disable ASPM.
> + 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);
And an idiomatic way to read 64bit MMIO value in two 32bit read is:
do {
high = read_high();
low = read_low();
} while(high != read_high());
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 2/6] bus: mhi: host: Add support for non-posted TSC timesync feature
2026-04-11 8:12 ` [PATCH v2 2/6] bus: mhi: host: Add support for non-posted TSC timesync feature Krishna Chaitanya Chundru
` (2 preceding siblings ...)
2026-04-13 7:27 ` Manivannan Sadhasivam
@ 2026-04-14 17:46 ` Vadim Fedorenko
3 siblings, 0 replies; 15+ messages in thread
From: Vadim Fedorenko @ 2026-04-14 17:46 UTC (permalink / raw)
To: Krishna Chaitanya Chundru, Manivannan Sadhasivam, Richard Cochran
Cc: mhi, linux-arm-msm, linux-kernel, netdev, Vivek Pernamitta
On 11/04/2026 09:12, Krishna Chaitanya Chundru wrote:
> 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.
[...]
> + /*
> + * 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();
PTP_SYS_OFFSET_EXTENDED receives the amount of samples to read from user
space, you can use it instead of MHI_NUM_BACK_TO_BACK_READS, and in this
case it's better to grab host-pre and host-post time for a single
register read.
Also, PTP_SYS_OFFSET_EXTENDED was improved and currently supports
multiple clockids as system time, it's good to account for it.
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 3/6] bus: mhi: host: Add support for 64bit register reads and writes
2026-04-11 8:12 [PATCH v2 0/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI Krishna Chaitanya Chundru
2026-04-11 8:12 ` [PATCH v2 1/6] bus: mhi: host: Add support to read MHI capabilities Krishna Chaitanya Chundru
2026-04-11 8:12 ` [PATCH v2 2/6] bus: mhi: host: Add support for non-posted TSC timesync feature Krishna Chaitanya Chundru
@ 2026-04-11 8:12 ` Krishna Chaitanya Chundru
2026-04-11 8:12 ` [PATCH v2 4/6] bus: mhi: pci_generic: Add support for 64 bit register read & write Krishna Chaitanya Chundru
` (3 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-04-11 8:12 UTC (permalink / raw)
To: Manivannan Sadhasivam, Richard Cochran
Cc: mhi, linux-arm-msm, linux-kernel, netdev,
Krishna Chaitanya Chundru
Some mhi registers are of 64 bit size, instead of reading high value
and low value separately provide a new function op to read & write to
64 bit register.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/bus/mhi/host/main.c | 12 ++++++++++++
include/linux/mhi.h | 6 ++++++
2 files changed, 18 insertions(+)
diff --git a/drivers/bus/mhi/host/main.c b/drivers/bus/mhi/host/main.c
index b7a727b1a5d1f20b570c62707a991ec5b85bfec7..99917593e1da06f1dece7b5b0037c2485953410f 100644
--- a/drivers/bus/mhi/host/main.c
+++ b/drivers/bus/mhi/host/main.c
@@ -67,6 +67,18 @@ void mhi_write_reg(struct mhi_controller *mhi_cntrl, void __iomem *base,
mhi_cntrl->write_reg(mhi_cntrl, base + offset, val);
}
+static int __must_check mhi_read_reg64(struct mhi_controller *mhi_cntrl,
+ void __iomem *base, u32 offset, u64 *out)
+{
+ return mhi_cntrl->read_reg64(mhi_cntrl, base + offset, out);
+}
+
+static void __maybe_unused mhi_write_reg64(struct mhi_controller *mhi_cntrl, void __iomem *base,
+ u32 offset, u64 val)
+{
+ mhi_cntrl->write_reg64(mhi_cntrl, base + offset, val);
+}
+
int __must_check mhi_write_reg_field(struct mhi_controller *mhi_cntrl,
void __iomem *base, u32 offset, u32 mask,
u32 val)
diff --git a/include/linux/mhi.h b/include/linux/mhi.h
index f39c8ca7c251954f2d83c1227d206b600b88c75f..8e7257a9c907fb03571a86e29db5534f492678c7 100644
--- a/include/linux/mhi.h
+++ b/include/linux/mhi.h
@@ -376,6 +376,8 @@ struct mhi_timesync_info {
* @unmap_single: CB function to destroy TRE buffer
* @read_reg: Read a MHI register via the physical link (required)
* @write_reg: Write a MHI register via the physical link (required)
+ * @read_reg64: Read a 64 bit MHI register via the physical link (optional)
+ * @write_reg64: Write a 64 bit MHI register via the physical link (optional)
* @reset: Controller specific reset function (optional)
* @edl_trigger: CB function to trigger EDL mode (optional)
* @buffer_len: Bounce buffer length
@@ -462,6 +464,10 @@ struct mhi_controller {
u32 *out);
void (*write_reg)(struct mhi_controller *mhi_cntrl, void __iomem *addr,
u32 val);
+ int (*read_reg64)(struct mhi_controller *mhi_cntrl, void __iomem *addr,
+ u64 *out);
+ void (*write_reg64)(struct mhi_controller *mhi_cntrl, void __iomem *addr,
+ u64 val);
void (*reset)(struct mhi_controller *mhi_cntrl);
int (*edl_trigger)(struct mhi_controller *mhi_cntrl);
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v2 4/6] bus: mhi: pci_generic: Add support for 64 bit register read & write
2026-04-11 8:12 [PATCH v2 0/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI Krishna Chaitanya Chundru
` (2 preceding siblings ...)
2026-04-11 8:12 ` [PATCH v2 3/6] bus: mhi: host: Add support for 64bit register reads and writes Krishna Chaitanya Chundru
@ 2026-04-11 8:12 ` Krishna Chaitanya Chundru
2026-04-11 8:12 ` [PATCH v2 5/6] bus: mhi: host: Update the Time sync logic to read 64 bit register value Krishna Chaitanya Chundru
` (2 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-04-11 8:12 UTC (permalink / raw)
To: Manivannan Sadhasivam, Richard Cochran
Cc: mhi, linux-arm-msm, linux-kernel, netdev,
Krishna Chaitanya Chundru
Add functions which does read and write on 64 bit registers.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/bus/mhi/host/pci_generic.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c
index 0884a384b77fc3f56fa62a12351933132ffc9293..b1122c7224bdd469406d96af6d3df342040e1002 100644
--- a/drivers/bus/mhi/host/pci_generic.c
+++ b/drivers/bus/mhi/host/pci_generic.c
@@ -1046,6 +1046,27 @@ struct mhi_pci_device {
bool reset_on_remove;
};
+#ifdef readq
+static int mhi_pci_read_reg64(struct mhi_controller *mhi_cntrl,
+ void __iomem *addr, u64 *out)
+{
+ *out = readq(addr);
+ return 0;
+}
+#else
+#define mhi_pci_read_reg64 NULL
+#endif
+
+#ifdef writeq
+static void mhi_pci_write_reg64(struct mhi_controller *mhi_cntrl,
+ void __iomem *addr, u64 val)
+{
+ writeq(val, addr);
+}
+#else
+#define mhi_pci_write_reg64 NULL
+#endif
+
static int mhi_pci_read_reg(struct mhi_controller *mhi_cntrl,
void __iomem *addr, u32 *out)
{
@@ -1347,6 +1368,8 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
mhi_cntrl->read_reg = mhi_pci_read_reg;
mhi_cntrl->write_reg = mhi_pci_write_reg;
+ mhi_cntrl->read_reg64 = mhi_pci_read_reg64;
+ mhi_cntrl->write_reg64 = mhi_pci_write_reg64;
mhi_cntrl->status_cb = mhi_pci_status_cb;
mhi_cntrl->runtime_get = mhi_pci_runtime_get;
mhi_cntrl->runtime_put = mhi_pci_runtime_put;
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v2 5/6] bus: mhi: host: Update the Time sync logic to read 64 bit register value
2026-04-11 8:12 [PATCH v2 0/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI Krishna Chaitanya Chundru
` (3 preceding siblings ...)
2026-04-11 8:12 ` [PATCH v2 4/6] bus: mhi: pci_generic: Add support for 64 bit register read & write Krishna Chaitanya Chundru
@ 2026-04-11 8:12 ` Krishna Chaitanya Chundru
2026-04-11 8:12 ` [PATCH v2 6/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI Krishna Chaitanya Chundru
2026-04-12 15:09 ` [PATCH v2 0/6] " Jakub Kicinski
6 siblings, 0 replies; 15+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-04-11 8:12 UTC (permalink / raw)
To: Manivannan Sadhasivam, Richard Cochran
Cc: mhi, linux-arm-msm, linux-kernel, netdev,
Krishna Chaitanya Chundru
Instead of reading low and high of the mhi registers twice use 64 bit
register value to avoid any time penality.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/bus/mhi/host/main.c | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/drivers/bus/mhi/host/main.c b/drivers/bus/mhi/host/main.c
index 99917593e1da06f1dece7b5b0037c2485953410f..e853419a0195dff4a18123631cb1f74242ab4428 100644
--- a/drivers/bus/mhi/host/main.c
+++ b/drivers/bus/mhi/host/main.c
@@ -1643,6 +1643,7 @@ static int mhi_get_remote_time(struct mhi_controller *mhi_cntrl, struct mhi_time
struct mhi_timesync_info *time)
{
struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ u64 val = U64_MAX;
int ret, i;
if (!mhi_tsync && !mhi_tsync->time_reg) {
@@ -1680,15 +1681,25 @@ static int mhi_get_remote_time(struct mhi_controller *mhi_cntrl, struct mhi_time
* 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);
+ if (mhi_cntrl->read_reg64) {
+ ret = mhi_read_reg64(mhi_cntrl, mhi_tsync->time_reg,
+ TSC_TIMESYNC_TIME_LOW_OFFSET, &val);
+ } else {
+ 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();
+ if (mhi_cntrl->read_reg64) {
+ time->t_dev_lo = (u32)val;
+ time->t_dev_hi = (u32)(val >> 32);
+ }
+
local_irq_enable();
preempt_enable();
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v2 6/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI
2026-04-11 8:12 [PATCH v2 0/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI Krishna Chaitanya Chundru
` (4 preceding siblings ...)
2026-04-11 8:12 ` [PATCH v2 5/6] bus: mhi: host: Update the Time sync logic to read 64 bit register value Krishna Chaitanya Chundru
@ 2026-04-11 8:12 ` Krishna Chaitanya Chundru
2026-04-11 13:50 ` Jie Gan
2026-04-13 8:06 ` Manivannan Sadhasivam
2026-04-12 15:09 ` [PATCH v2 0/6] " Jakub Kicinski
6 siblings, 2 replies; 15+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-04-11 8:12 UTC (permalink / raw)
To: Manivannan Sadhasivam, Richard Cochran
Cc: mhi, linux-arm-msm, linux-kernel, netdev,
Krishna Chaitanya Chundru, Imran Shaik, Taniya Das
From: Imran Shaik <imran.shaik@oss.qualcomm.com>
This patch introduces the MHI PHC (PTP Hardware Clock) driver, which
registers a PTP (Precision Time Protocol) clock and communicates with
the MHI core to get the device side timestamps. These timestamps are
then exposed to the PTP subsystem, enabling precise time synchronization
between the host and the device.
The following diagram illustrates the architecture and data flow:
+-------------+ +--------------------+ +--------------+
|Userspace App|<-->|Kernel PTP framework|<-->|MHI PHC Driver|
+-------------+ +--------------------+ +--------------+
|
v
+-------------------------------+ +-----------------+
| MHI Device (Timestamp source) |<------->| MHI Core Driver |
+-------------------------------+ +-----------------+
- User space applications use the standard Linux PTP interface.
- The PTP subsystem routes IOCTLs to the MHI PHC driver.
- The MHI PHC driver communicates with the MHI core to fetch timestamps.
- The MHI core interacts with the device to retrieve accurate time data.
Co-developed-by: Taniya Das <taniya.das@oss.qualcomm.com>
Signed-off-by: Taniya Das <taniya.das@oss.qualcomm.com>
Signed-off-by: Imran Shaik <imran.shaik@oss.qualcomm.com>
---
drivers/bus/mhi/host/Kconfig | 8 ++
drivers/bus/mhi/host/Makefile | 1 +
drivers/bus/mhi/host/mhi_phc.c | 150 +++++++++++++++++++++++++++++++++++++
drivers/bus/mhi/host/mhi_phc.h | 28 +++++++
drivers/bus/mhi/host/pci_generic.c | 23 ++++++
5 files changed, 210 insertions(+)
diff --git a/drivers/bus/mhi/host/Kconfig b/drivers/bus/mhi/host/Kconfig
index da5cd0c9fc620ab595e742c422f1a22a2a84c7b9..b4eabf3e5c56907de93232f02962040e979c3110 100644
--- a/drivers/bus/mhi/host/Kconfig
+++ b/drivers/bus/mhi/host/Kconfig
@@ -29,3 +29,11 @@ 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
+ bool "MHI PHC driver"
+ depends on MHI_BUS_PCI_GENERIC
+ 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 859c2f38451c669b3d3014c374b2b957c99a1cfe..5ba244fe7d596834ea535797efd3428963ba0ed0 100644
--- a/drivers/bus/mhi/host/Makefile
+++ b/drivers/bus/mhi/host/Makefile
@@ -4,3 +4,4 @@ mhi-$(CONFIG_MHI_BUS_DEBUG) += debugfs.o
obj-$(CONFIG_MHI_BUS_PCI_GENERIC) += mhi_pci_generic.o
mhi_pci_generic-y += pci_generic.o
+mhi_pci_generic-$(CONFIG_MHI_BUS_PHC) += mhi_phc.o
diff --git a/drivers/bus/mhi/host/mhi_phc.c b/drivers/bus/mhi/host/mhi_phc.c
new file mode 100644
index 0000000000000000000000000000000000000000..fa04eb7f6025fa281d86c0a45b5f7d3e61f5ce12
--- /dev/null
+++ b/drivers/bus/mhi/host/mhi_phc.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025, Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mhi.h>
+#include <linux/ptp_clock_kernel.h>
+#include "mhi_phc.h"
+
+#define NSEC 1000000000ULL
+
+/**
+ * struct mhi_phc_dev - MHI PHC device
+ * @ptp_clock: associated PTP clock
+ * @ptp_clock_info: PTP clock information
+ * @mhi_dev: associated mhi device object
+ * @lock: spinlock
+ * @enabled: Flag to track the state of the MHI device
+ */
+struct mhi_phc_dev {
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info ptp_clock_info;
+ struct mhi_device *mhi_dev;
+ spinlock_t lock;
+ bool enabled;
+};
+
+static int qcom_ptp_gettimex64(struct ptp_clock_info *ptp, struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ struct mhi_phc_dev *phc_dev = container_of(ptp, struct mhi_phc_dev, ptp_clock_info);
+ struct mhi_timesync_info time;
+ ktime_t ktime_cur;
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&phc_dev->lock, flags);
+ if (!phc_dev->enabled) {
+ ret = -ENODEV;
+ goto err;
+ }
+
+ ret = mhi_get_remote_tsc_time_sync(phc_dev->mhi_dev, &time);
+ if (ret)
+ goto err;
+
+ ktime_cur = time.t_dev_hi * NSEC + time.t_dev_lo;
+ *ts = ktime_to_timespec64(ktime_cur);
+
+ dev_dbg(&phc_dev->mhi_dev->dev, "TSC time stamps sec:%u nsec:%u current:%lld\n",
+ time.t_dev_hi, time.t_dev_lo, ktime_cur);
+
+ /* Update pre and post timestamps for PTP_SYS_OFFSET_EXTENDED*/
+ if (sts != NULL) {
+ sts->pre_ts = ktime_to_timespec64(time.t_host_pre);
+ sts->post_ts = ktime_to_timespec64(time.t_host_post);
+ dev_dbg(&phc_dev->mhi_dev->dev, "pre:%lld post:%lld\n",
+ time.t_host_pre, time.t_host_post);
+ }
+
+err:
+ spin_unlock_irqrestore(&phc_dev->lock, flags);
+
+ return ret;
+}
+
+int mhi_phc_start(struct mhi_controller *mhi_cntrl)
+{
+ struct mhi_phc_dev *phc_dev = dev_get_drvdata(&mhi_cntrl->mhi_dev->dev);
+ unsigned long flags;
+
+ if (!phc_dev) {
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Driver data is NULL\n");
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&phc_dev->lock, flags);
+ phc_dev->enabled = true;
+ spin_unlock_irqrestore(&phc_dev->lock, flags);
+
+ return 0;
+}
+
+int mhi_phc_stop(struct mhi_controller *mhi_cntrl)
+{
+ struct mhi_phc_dev *phc_dev = dev_get_drvdata(&mhi_cntrl->mhi_dev->dev);
+ unsigned long flags;
+
+ if (!phc_dev) {
+ dev_err(&mhi_cntrl->mhi_dev->dev, "Driver data is NULL\n");
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&phc_dev->lock, flags);
+ phc_dev->enabled = false;
+ spin_unlock_irqrestore(&phc_dev->lock, flags);
+
+ 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_phc_dev *phc_dev;
+ int ret;
+
+ phc_dev = devm_kzalloc(&mhi_dev->dev, sizeof(*phc_dev), GFP_KERNEL);
+ if (!phc_dev)
+ return -ENOMEM;
+
+ phc_dev->mhi_dev = mhi_dev;
+
+ phc_dev->ptp_clock_info = qcom_ptp_clock_info;
+ strscpy(phc_dev->ptp_clock_info.name, mhi_dev->name, PTP_CLOCK_NAME_LEN);
+
+ spin_lock_init(&phc_dev->lock);
+
+ phc_dev->ptp_clock = ptp_clock_register(&phc_dev->ptp_clock_info, &mhi_dev->dev);
+ if (IS_ERR(phc_dev->ptp_clock)) {
+ ret = PTR_ERR(phc_dev->ptp_clock);
+ dev_err(&mhi_dev->dev, "Failed to register PTP clock\n");
+ phc_dev->ptp_clock = NULL;
+ return ret;
+ }
+
+ dev_set_drvdata(&mhi_dev->dev, phc_dev);
+
+ dev_dbg(&mhi_dev->dev, "probed MHI PHC dev: %s\n", mhi_dev->name);
+ return 0;
+};
+
+void mhi_phc_exit(struct mhi_controller *mhi_cntrl)
+{
+ struct mhi_phc_dev *phc_dev = dev_get_drvdata(&mhi_cntrl->mhi_dev->dev);
+
+ if (!phc_dev)
+ return;
+
+ /* disable the node */
+ ptp_clock_unregister(phc_dev->ptp_clock);
+ phc_dev->enabled = false;
+}
diff --git a/drivers/bus/mhi/host/mhi_phc.h b/drivers/bus/mhi/host/mhi_phc.h
new file mode 100644
index 0000000000000000000000000000000000000000..e6b0866bc768ba5a8ac3e4c40a99aa2050db1389
--- /dev/null
+++ b/drivers/bus/mhi/host/mhi_phc.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2025, Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifdef CONFIG_MHI_BUS_PHC
+int mhi_phc_init(struct mhi_controller *mhi_cntrl);
+int mhi_phc_start(struct mhi_controller *mhi_cntrl);
+int mhi_phc_stop(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 int mhi_phc_start(struct mhi_controller *mhi_cntrl)
+{
+ return 0;
+}
+
+static inline int mhi_phc_stop(struct mhi_controller *mhi_cntrl)
+{
+ return 0;
+}
+
+static inline void mhi_phc_exit(struct mhi_controller *mhi_cntrl) {}
+#endif
diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c
index b1122c7224bdd469406d96af6d3df342040e1002..6cba5cecd1adb40396bba30c9b2a551898dce871 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
@@ -1044,6 +1045,7 @@ struct mhi_pci_device {
struct timer_list health_check_timer;
unsigned long status;
bool reset_on_remove;
+ bool mhi_phc_init_done;
};
#ifdef readq
@@ -1084,6 +1086,7 @@ static void mhi_pci_status_cb(struct mhi_controller *mhi_cntrl,
enum mhi_callback cb)
{
struct pci_dev *pdev = to_pci_dev(mhi_cntrl->cntrl_dev);
+ struct mhi_pci_device *mhi_pdev = pci_get_drvdata(pdev);
/* Nothing to do for now */
switch (cb) {
@@ -1091,9 +1094,21 @@ static void mhi_pci_status_cb(struct mhi_controller *mhi_cntrl,
case MHI_CB_SYS_ERROR:
dev_warn(&pdev->dev, "firmware crashed (%u)\n", cb);
pm_runtime_forbid(&pdev->dev);
+ /* Stop PHC */
+ if (mhi_cntrl->tsc_timesync)
+ mhi_phc_stop(mhi_cntrl);
break;
case MHI_CB_EE_MISSION_MODE:
pm_runtime_allow(&pdev->dev);
+ /* Start PHC */
+ if (mhi_cntrl->tsc_timesync) {
+ if (!mhi_pdev->mhi_phc_init_done) {
+ mhi_phc_init(mhi_cntrl);
+ mhi_pdev->mhi_phc_init_done = true;
+ }
+
+ mhi_phc_start(mhi_cntrl);
+ }
break;
default:
break;
@@ -1236,6 +1251,10 @@ static void mhi_pci_recovery_work(struct work_struct *work)
pm_runtime_forbid(&pdev->dev);
+ /* Stop PHC */
+ if (mhi_cntrl->tsc_timesync)
+ mhi_phc_stop(mhi_cntrl);
+
/* Clean up MHI state */
if (test_and_clear_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status)) {
mhi_power_down(mhi_cntrl, false);
@@ -1457,6 +1476,10 @@ 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 */
+ if (mhi_cntrl->tsc_timesync)
+ 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);
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v2 6/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI
2026-04-11 8:12 ` [PATCH v2 6/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI Krishna Chaitanya Chundru
@ 2026-04-11 13:50 ` Jie Gan
2026-04-13 8:06 ` Manivannan Sadhasivam
1 sibling, 0 replies; 15+ messages in thread
From: Jie Gan @ 2026-04-11 13:50 UTC (permalink / raw)
To: Krishna Chaitanya Chundru, Manivannan Sadhasivam, Richard Cochran
Cc: mhi, linux-arm-msm, linux-kernel, netdev, Imran Shaik, Taniya Das
On 4/11/2026 4:12 PM, Krishna Chaitanya Chundru wrote:
> From: Imran Shaik <imran.shaik@oss.qualcomm.com>
>
> This patch introduces the MHI PHC (PTP Hardware Clock) driver, which
> registers a PTP (Precision Time Protocol) clock and communicates with
> the MHI core to get the device side timestamps. These timestamps are
> then exposed to the PTP subsystem, enabling precise time synchronization
> between the host and the device.
>
> The following diagram illustrates the architecture and data flow:
>
> +-------------+ +--------------------+ +--------------+
> |Userspace App|<-->|Kernel PTP framework|<-->|MHI PHC Driver|
> +-------------+ +--------------------+ +--------------+
> |
> v
> +-------------------------------+ +-----------------+
> | MHI Device (Timestamp source) |<------->| MHI Core Driver |
> +-------------------------------+ +-----------------+
>
> - User space applications use the standard Linux PTP interface.
> - The PTP subsystem routes IOCTLs to the MHI PHC driver.
> - The MHI PHC driver communicates with the MHI core to fetch timestamps.
> - The MHI core interacts with the device to retrieve accurate time data.
>
> Co-developed-by: Taniya Das <taniya.das@oss.qualcomm.com>
> Signed-off-by: Taniya Das <taniya.das@oss.qualcomm.com>
> Signed-off-by: Imran Shaik <imran.shaik@oss.qualcomm.com>
> ---
> drivers/bus/mhi/host/Kconfig | 8 ++
> drivers/bus/mhi/host/Makefile | 1 +
> drivers/bus/mhi/host/mhi_phc.c | 150 +++++++++++++++++++++++++++++++++++++
> drivers/bus/mhi/host/mhi_phc.h | 28 +++++++
> drivers/bus/mhi/host/pci_generic.c | 23 ++++++
> 5 files changed, 210 insertions(+)
>
> diff --git a/drivers/bus/mhi/host/Kconfig b/drivers/bus/mhi/host/Kconfig
> index da5cd0c9fc620ab595e742c422f1a22a2a84c7b9..b4eabf3e5c56907de93232f02962040e979c3110 100644
> --- a/drivers/bus/mhi/host/Kconfig
> +++ b/drivers/bus/mhi/host/Kconfig
> @@ -29,3 +29,11 @@ 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
> + bool "MHI PHC driver"
> + depends on MHI_BUS_PCI_GENERIC
> + 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 859c2f38451c669b3d3014c374b2b957c99a1cfe..5ba244fe7d596834ea535797efd3428963ba0ed0 100644
> --- a/drivers/bus/mhi/host/Makefile
> +++ b/drivers/bus/mhi/host/Makefile
> @@ -4,3 +4,4 @@ mhi-$(CONFIG_MHI_BUS_DEBUG) += debugfs.o
>
> obj-$(CONFIG_MHI_BUS_PCI_GENERIC) += mhi_pci_generic.o
> mhi_pci_generic-y += pci_generic.o
> +mhi_pci_generic-$(CONFIG_MHI_BUS_PHC) += mhi_phc.o
> diff --git a/drivers/bus/mhi/host/mhi_phc.c b/drivers/bus/mhi/host/mhi_phc.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..fa04eb7f6025fa281d86c0a45b5f7d3e61f5ce12
> --- /dev/null
> +++ b/drivers/bus/mhi/host/mhi_phc.c
> @@ -0,0 +1,150 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2025, Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/mhi.h>
> +#include <linux/ptp_clock_kernel.h>
> +#include "mhi_phc.h"
> +
> +#define NSEC 1000000000ULL
> +
> +/**
> + * struct mhi_phc_dev - MHI PHC device
> + * @ptp_clock: associated PTP clock
> + * @ptp_clock_info: PTP clock information
> + * @mhi_dev: associated mhi device object
> + * @lock: spinlock
> + * @enabled: Flag to track the state of the MHI device
> + */
> +struct mhi_phc_dev {
> + struct ptp_clock *ptp_clock;
> + struct ptp_clock_info ptp_clock_info;
> + struct mhi_device *mhi_dev;
> + spinlock_t lock;
> + bool enabled;
> +};
> +
> +static int qcom_ptp_gettimex64(struct ptp_clock_info *ptp, struct timespec64 *ts,
> + struct ptp_system_timestamp *sts)
> +{
> + struct mhi_phc_dev *phc_dev = container_of(ptp, struct mhi_phc_dev, ptp_clock_info);
> + struct mhi_timesync_info time;
> + ktime_t ktime_cur;
> + unsigned long flags;
> + int ret;
> +
> + spin_lock_irqsave(&phc_dev->lock, flags);
> + if (!phc_dev->enabled) {
> + ret = -ENODEV;
> + goto err;
> + }
> +
> + ret = mhi_get_remote_tsc_time_sync(phc_dev->mhi_dev, &time);
mhi_get_remote_tsc_time_sync -> mhi_device_get_sync ->
__mhi_device_get_sync -> wait_event_timeout (can sleep with spinlock)
Be careful to use the spinlock here.
Thanks,
Jie
> + if (ret)
> + goto err;
> +
> + ktime_cur = time.t_dev_hi * NSEC + time.t_dev_lo;
> + *ts = ktime_to_timespec64(ktime_cur);
> +
> + dev_dbg(&phc_dev->mhi_dev->dev, "TSC time stamps sec:%u nsec:%u current:%lld\n",
> + time.t_dev_hi, time.t_dev_lo, ktime_cur);
> +
> + /* Update pre and post timestamps for PTP_SYS_OFFSET_EXTENDED*/
> + if (sts != NULL) {
> + sts->pre_ts = ktime_to_timespec64(time.t_host_pre);
> + sts->post_ts = ktime_to_timespec64(time.t_host_post);
> + dev_dbg(&phc_dev->mhi_dev->dev, "pre:%lld post:%lld\n",
> + time.t_host_pre, time.t_host_post);
> + }
> +
> +err:
> + spin_unlock_irqrestore(&phc_dev->lock, flags);
> +
> + return ret;
> +}
> +
> +int mhi_phc_start(struct mhi_controller *mhi_cntrl)
> +{
> + struct mhi_phc_dev *phc_dev = dev_get_drvdata(&mhi_cntrl->mhi_dev->dev);
> + unsigned long flags;
> +
> + if (!phc_dev) {
> + dev_err(&mhi_cntrl->mhi_dev->dev, "Driver data is NULL\n");
> + return -ENODEV;
> + }
> +
> + spin_lock_irqsave(&phc_dev->lock, flags);
> + phc_dev->enabled = true;
> + spin_unlock_irqrestore(&phc_dev->lock, flags);
> +
> + return 0;
> +}
> +
> +int mhi_phc_stop(struct mhi_controller *mhi_cntrl)
> +{
> + struct mhi_phc_dev *phc_dev = dev_get_drvdata(&mhi_cntrl->mhi_dev->dev);
> + unsigned long flags;
> +
> + if (!phc_dev) {
> + dev_err(&mhi_cntrl->mhi_dev->dev, "Driver data is NULL\n");
> + return -ENODEV;
> + }
> +
> + spin_lock_irqsave(&phc_dev->lock, flags);
> + phc_dev->enabled = false;
> + spin_unlock_irqrestore(&phc_dev->lock, flags);
> +
> + 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_phc_dev *phc_dev;
> + int ret;
> +
> + phc_dev = devm_kzalloc(&mhi_dev->dev, sizeof(*phc_dev), GFP_KERNEL);
> + if (!phc_dev)
> + return -ENOMEM;
> +
> + phc_dev->mhi_dev = mhi_dev;
> +
> + phc_dev->ptp_clock_info = qcom_ptp_clock_info;
> + strscpy(phc_dev->ptp_clock_info.name, mhi_dev->name, PTP_CLOCK_NAME_LEN);
> +
> + spin_lock_init(&phc_dev->lock);
> +
> + phc_dev->ptp_clock = ptp_clock_register(&phc_dev->ptp_clock_info, &mhi_dev->dev);
> + if (IS_ERR(phc_dev->ptp_clock)) {
> + ret = PTR_ERR(phc_dev->ptp_clock);
> + dev_err(&mhi_dev->dev, "Failed to register PTP clock\n");
> + phc_dev->ptp_clock = NULL;
> + return ret;
> + }
> +
> + dev_set_drvdata(&mhi_dev->dev, phc_dev);
> +
> + dev_dbg(&mhi_dev->dev, "probed MHI PHC dev: %s\n", mhi_dev->name);
> + return 0;
> +};
> +
> +void mhi_phc_exit(struct mhi_controller *mhi_cntrl)
> +{
> + struct mhi_phc_dev *phc_dev = dev_get_drvdata(&mhi_cntrl->mhi_dev->dev);
> +
> + if (!phc_dev)
> + return;
> +
> + /* disable the node */
> + ptp_clock_unregister(phc_dev->ptp_clock);
> + phc_dev->enabled = false;
> +}
> diff --git a/drivers/bus/mhi/host/mhi_phc.h b/drivers/bus/mhi/host/mhi_phc.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..e6b0866bc768ba5a8ac3e4c40a99aa2050db1389
> --- /dev/null
> +++ b/drivers/bus/mhi/host/mhi_phc.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2025, Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#ifdef CONFIG_MHI_BUS_PHC
> +int mhi_phc_init(struct mhi_controller *mhi_cntrl);
> +int mhi_phc_start(struct mhi_controller *mhi_cntrl);
> +int mhi_phc_stop(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 int mhi_phc_start(struct mhi_controller *mhi_cntrl)
> +{
> + return 0;
> +}
> +
> +static inline int mhi_phc_stop(struct mhi_controller *mhi_cntrl)
> +{
> + return 0;
> +}
> +
> +static inline void mhi_phc_exit(struct mhi_controller *mhi_cntrl) {}
> +#endif
> diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c
> index b1122c7224bdd469406d96af6d3df342040e1002..6cba5cecd1adb40396bba30c9b2a551898dce871 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
>
> @@ -1044,6 +1045,7 @@ struct mhi_pci_device {
> struct timer_list health_check_timer;
> unsigned long status;
> bool reset_on_remove;
> + bool mhi_phc_init_done;
> };
>
> #ifdef readq
> @@ -1084,6 +1086,7 @@ static void mhi_pci_status_cb(struct mhi_controller *mhi_cntrl,
> enum mhi_callback cb)
> {
> struct pci_dev *pdev = to_pci_dev(mhi_cntrl->cntrl_dev);
> + struct mhi_pci_device *mhi_pdev = pci_get_drvdata(pdev);
>
> /* Nothing to do for now */
> switch (cb) {
> @@ -1091,9 +1094,21 @@ static void mhi_pci_status_cb(struct mhi_controller *mhi_cntrl,
> case MHI_CB_SYS_ERROR:
> dev_warn(&pdev->dev, "firmware crashed (%u)\n", cb);
> pm_runtime_forbid(&pdev->dev);
> + /* Stop PHC */
> + if (mhi_cntrl->tsc_timesync)
> + mhi_phc_stop(mhi_cntrl);
> break;
> case MHI_CB_EE_MISSION_MODE:
> pm_runtime_allow(&pdev->dev);
> + /* Start PHC */
> + if (mhi_cntrl->tsc_timesync) {
> + if (!mhi_pdev->mhi_phc_init_done) {
> + mhi_phc_init(mhi_cntrl);
> + mhi_pdev->mhi_phc_init_done = true;
> + }
> +
> + mhi_phc_start(mhi_cntrl);
> + }
> break;
> default:
> break;
> @@ -1236,6 +1251,10 @@ static void mhi_pci_recovery_work(struct work_struct *work)
>
> pm_runtime_forbid(&pdev->dev);
>
> + /* Stop PHC */
> + if (mhi_cntrl->tsc_timesync)
> + mhi_phc_stop(mhi_cntrl);
> +
> /* Clean up MHI state */
> if (test_and_clear_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status)) {
> mhi_power_down(mhi_cntrl, false);
> @@ -1457,6 +1476,10 @@ 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 */
> + if (mhi_cntrl->tsc_timesync)
> + 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);
>
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 6/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI
2026-04-11 8:12 ` [PATCH v2 6/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI Krishna Chaitanya Chundru
2026-04-11 13:50 ` Jie Gan
@ 2026-04-13 8:06 ` Manivannan Sadhasivam
1 sibling, 0 replies; 15+ messages in thread
From: Manivannan Sadhasivam @ 2026-04-13 8:06 UTC (permalink / raw)
To: Krishna Chaitanya Chundru
Cc: Richard Cochran, mhi, linux-arm-msm, linux-kernel, netdev,
Imran Shaik, Taniya Das
On Sat, Apr 11, 2026 at 01:42:06PM +0530, Krishna Chaitanya Chundru wrote:
> From: Imran Shaik <imran.shaik@oss.qualcomm.com>
>
> This patch introduces the MHI PHC (PTP Hardware Clock) driver, which
> registers a PTP (Precision Time Protocol) clock and communicates with
> the MHI core to get the device side timestamps. These timestamps are
> then exposed to the PTP subsystem, enabling precise time synchronization
> between the host and the device.
>
> The following diagram illustrates the architecture and data flow:
>
> +-------------+ +--------------------+ +--------------+
> |Userspace App|<-->|Kernel PTP framework|<-->|MHI PHC Driver|
> +-------------+ +--------------------+ +--------------+
> |
> v
> +-------------------------------+ +-----------------+
> | MHI Device (Timestamp source) |<------->| MHI Core Driver |
> +-------------------------------+ +-----------------+
>
> - User space applications use the standard Linux PTP interface.
> - The PTP subsystem routes IOCTLs to the MHI PHC driver.
> - The MHI PHC driver communicates with the MHI core to fetch timestamps.
> - The MHI core interacts with the device to retrieve accurate time data.
As mentioned in cover letter, this is misleading.
>
> Co-developed-by: Taniya Das <taniya.das@oss.qualcomm.com>
> Signed-off-by: Taniya Das <taniya.das@oss.qualcomm.com>
> Signed-off-by: Imran Shaik <imran.shaik@oss.qualcomm.com>
> ---
> drivers/bus/mhi/host/Kconfig | 8 ++
> drivers/bus/mhi/host/Makefile | 1 +
> drivers/bus/mhi/host/mhi_phc.c | 150 +++++++++++++++++++++++++++++++++++++
> drivers/bus/mhi/host/mhi_phc.h | 28 +++++++
> drivers/bus/mhi/host/pci_generic.c | 23 ++++++
> 5 files changed, 210 insertions(+)
>
> diff --git a/drivers/bus/mhi/host/Kconfig b/drivers/bus/mhi/host/Kconfig
> index da5cd0c9fc620ab595e742c422f1a22a2a84c7b9..b4eabf3e5c56907de93232f02962040e979c3110 100644
> --- a/drivers/bus/mhi/host/Kconfig
> +++ b/drivers/bus/mhi/host/Kconfig
> @@ -29,3 +29,11 @@ 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
> + bool "MHI PHC driver"
Why not tristate?
> + depends on MHI_BUS_PCI_GENERIC
No, this generic TSC driver cannot depend on a controller driver. It should
purely act as a library.
> + 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 859c2f38451c669b3d3014c374b2b957c99a1cfe..5ba244fe7d596834ea535797efd3428963ba0ed0 100644
> --- a/drivers/bus/mhi/host/Makefile
> +++ b/drivers/bus/mhi/host/Makefile
> @@ -4,3 +4,4 @@ mhi-$(CONFIG_MHI_BUS_DEBUG) += debugfs.o
>
> obj-$(CONFIG_MHI_BUS_PCI_GENERIC) += mhi_pci_generic.o
> mhi_pci_generic-y += pci_generic.o
> +mhi_pci_generic-$(CONFIG_MHI_BUS_PHC) += mhi_phc.o
> diff --git a/drivers/bus/mhi/host/mhi_phc.c b/drivers/bus/mhi/host/mhi_phc.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..fa04eb7f6025fa281d86c0a45b5f7d3e61f5ce12
> --- /dev/null
> +++ b/drivers/bus/mhi/host/mhi_phc.c
> @@ -0,0 +1,150 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2025, Qualcomm Technologies, Inc. and/or its subsidiaries.
2026
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
Are these headers really needed?
> +#include <linux/mhi.h>
> +#include <linux/ptp_clock_kernel.h>
> +#include "mhi_phc.h"
> +
> +#define NSEC 1000000000ULL
Use existing NSEC_PER_SEC
> +
> +/**
> + * struct mhi_phc_dev - MHI PHC device
> + * @ptp_clock: associated PTP clock
> + * @ptp_clock_info: PTP clock information
> + * @mhi_dev: associated mhi device object
> + * @lock: spinlock
What spinlock? and what is it used for?
> + * @enabled: Flag to track the state of the MHI device
> + */
> +struct mhi_phc_dev {
> + struct ptp_clock *ptp_clock;
> + struct ptp_clock_info ptp_clock_info;
Use single space.
> + struct mhi_device *mhi_dev;
> + spinlock_t lock;
> + bool enabled;
> +};
> +
> +static int qcom_ptp_gettimex64(struct ptp_clock_info *ptp, struct timespec64 *ts,
> + struct ptp_system_timestamp *sts)
> +{
> + struct mhi_phc_dev *phc_dev = container_of(ptp, struct mhi_phc_dev, ptp_clock_info);
> + struct mhi_timesync_info time;
> + ktime_t ktime_cur;
> + unsigned long flags;
> + int ret;
> +
> + spin_lock_irqsave(&phc_dev->lock, flags);
Why spinlock ant not mutex, especially when mhi_get_remote_tsc_time_sync()
performs MMIO reads 4 times with ASPM enabled?
I also doubt that you really need lock here.
> + if (!phc_dev->enabled) {
I don't see a a value of this check. If the intention is to prevent PHC from
being disabled when gettimex64() callback is executing, then kernel POSIX clock
layer already provides that guarantee for you. You don't need to reinvent the
wheel again.
> + ret = -ENODEV;
> + goto err;
> + }
> +
> + ret = mhi_get_remote_tsc_time_sync(phc_dev->mhi_dev, &time);
> + if (ret)
> + goto err;
> +
> + ktime_cur = time.t_dev_hi * NSEC + time.t_dev_lo;
> + *ts = ktime_to_timespec64(ktime_cur);
> +
> + dev_dbg(&phc_dev->mhi_dev->dev, "TSC time stamps sec:%u nsec:%u current:%lld\n",
> + time.t_dev_hi, time.t_dev_lo, ktime_cur);
Move to tracepoint.
> +
> + /* Update pre and post timestamps for PTP_SYS_OFFSET_EXTENDED*/
> + if (sts != NULL) {
if (sts)
> + sts->pre_ts = ktime_to_timespec64(time.t_host_pre);
> + sts->post_ts = ktime_to_timespec64(time.t_host_post);
> + dev_dbg(&phc_dev->mhi_dev->dev, "pre:%lld post:%lld\n",
> + time.t_host_pre, time.t_host_post);
> + }
> +
> +err:
> + spin_unlock_irqrestore(&phc_dev->lock, flags);
> +
> + return ret;
> +}
> +
> +int mhi_phc_start(struct mhi_controller *mhi_cntrl)
> +{
> + struct mhi_phc_dev *phc_dev = dev_get_drvdata(&mhi_cntrl->mhi_dev->dev);
> + unsigned long flags;
> +
> + if (!phc_dev) {
> + dev_err(&mhi_cntrl->mhi_dev->dev, "Driver data is NULL\n");
Can this really happen? Even so, I wouldn't add an error print for this cosmetic
check.
> + return -ENODEV;
> + }
> +
> + spin_lock_irqsave(&phc_dev->lock, flags);
> + phc_dev->enabled = true;
> + spin_unlock_irqrestore(&phc_dev->lock, flags);
> +
> + return 0;
> +}
> +
> +int mhi_phc_stop(struct mhi_controller *mhi_cntrl)
> +{
> + struct mhi_phc_dev *phc_dev = dev_get_drvdata(&mhi_cntrl->mhi_dev->dev);
> + unsigned long flags;
> +
> + if (!phc_dev) {
> + dev_err(&mhi_cntrl->mhi_dev->dev, "Driver data is NULL\n");
> + return -ENODEV;
> + }
Same here.
> +
> + spin_lock_irqsave(&phc_dev->lock, flags);
> + phc_dev->enabled = false;
> + spin_unlock_irqrestore(&phc_dev->lock, flags);
> +
> + return 0;
Getting rid of the check and 'phc_dev->enabled' flag means, I see no point in
mhi_phc_{start/stop}() functions.
> +}
> +
> +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_phc_dev *phc_dev;
> + int ret;
> +
> + phc_dev = devm_kzalloc(&mhi_dev->dev, sizeof(*phc_dev), GFP_KERNEL);
> + if (!phc_dev)
> + return -ENOMEM;
> +
> + phc_dev->mhi_dev = mhi_dev;
> +
> + phc_dev->ptp_clock_info = qcom_ptp_clock_info;
> + strscpy(phc_dev->ptp_clock_info.name, mhi_dev->name, PTP_CLOCK_NAME_LEN);
> +
> + spin_lock_init(&phc_dev->lock);
> +
> + phc_dev->ptp_clock = ptp_clock_register(&phc_dev->ptp_clock_info, &mhi_dev->dev);
> + if (IS_ERR(phc_dev->ptp_clock)) {
> + ret = PTR_ERR(phc_dev->ptp_clock);
> + dev_err(&mhi_dev->dev, "Failed to register PTP clock\n");
> + phc_dev->ptp_clock = NULL;
> + return ret;
> + }
> +
> + dev_set_drvdata(&mhi_dev->dev, phc_dev);
> +
> + dev_dbg(&mhi_dev->dev, "probed MHI PHC dev: %s\n", mhi_dev->name);
Drop this spam.
> + return 0;
> +};
> +
> +void mhi_phc_exit(struct mhi_controller *mhi_cntrl)
> +{
> + struct mhi_phc_dev *phc_dev = dev_get_drvdata(&mhi_cntrl->mhi_dev->dev);
> +
> + if (!phc_dev)
> + return;
> +
> + /* disable the node */
Remove this comment.
> + ptp_clock_unregister(phc_dev->ptp_clock);
> + phc_dev->enabled = false;
> +}
> diff --git a/drivers/bus/mhi/host/mhi_phc.h b/drivers/bus/mhi/host/mhi_phc.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..e6b0866bc768ba5a8ac3e4c40a99aa2050db1389
> --- /dev/null
> +++ b/drivers/bus/mhi/host/mhi_phc.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2025, Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#ifdef CONFIG_MHI_BUS_PHC
#if IS_ENABLED()
> +int mhi_phc_init(struct mhi_controller *mhi_cntrl);
> +int mhi_phc_start(struct mhi_controller *mhi_cntrl);
> +int mhi_phc_stop(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 int mhi_phc_start(struct mhi_controller *mhi_cntrl)
> +{
> + return 0;
> +}
> +
> +static inline int mhi_phc_stop(struct mhi_controller *mhi_cntrl)
> +{
> + return 0;
> +}
> +
> +static inline void mhi_phc_exit(struct mhi_controller *mhi_cntrl) {}
> +#endif
> diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c
> index b1122c7224bdd469406d96af6d3df342040e1002..6cba5cecd1adb40396bba30c9b2a551898dce871 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
>
> @@ -1044,6 +1045,7 @@ struct mhi_pci_device {
> struct timer_list health_check_timer;
> unsigned long status;
> bool reset_on_remove;
> + bool mhi_phc_init_done;
> };
>
> #ifdef readq
> @@ -1084,6 +1086,7 @@ static void mhi_pci_status_cb(struct mhi_controller *mhi_cntrl,
> enum mhi_callback cb)
> {
> struct pci_dev *pdev = to_pci_dev(mhi_cntrl->cntrl_dev);
> + struct mhi_pci_device *mhi_pdev = pci_get_drvdata(pdev);
>
> /* Nothing to do for now */
> switch (cb) {
> @@ -1091,9 +1094,21 @@ static void mhi_pci_status_cb(struct mhi_controller *mhi_cntrl,
> case MHI_CB_SYS_ERROR:
> dev_warn(&pdev->dev, "firmware crashed (%u)\n", cb);
> pm_runtime_forbid(&pdev->dev);
> + /* Stop PHC */
> + if (mhi_cntrl->tsc_timesync)
> + mhi_phc_stop(mhi_cntrl);
> break;
> case MHI_CB_EE_MISSION_MODE:
> pm_runtime_allow(&pdev->dev);
> + /* Start PHC */
> + if (mhi_cntrl->tsc_timesync) {
> + if (!mhi_pdev->mhi_phc_init_done) {
> + mhi_phc_init(mhi_cntrl);
> + mhi_pdev->mhi_phc_init_done = true;
> + }
This looks weird. Since MISSION_MODE can happen multiple times when the device
is attached, shouldn't you be doing mhi_phc_init() multiple times with the
corresponding mhi_phc_exit() during MHI_CB_SYS_ERROR?
Right now, you call mhi_phc_init() during MISSION_MODE and mhi_phc_exit()
during mhi_pci_remove(). What is the point of keeping /dev/ptpX if the firmware
is crashed?
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 0/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI
2026-04-11 8:12 [PATCH v2 0/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI Krishna Chaitanya Chundru
` (5 preceding siblings ...)
2026-04-11 8:12 ` [PATCH v2 6/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI Krishna Chaitanya Chundru
@ 2026-04-12 15:09 ` Jakub Kicinski
2026-04-13 5:44 ` Manivannan Sadhasivam
6 siblings, 1 reply; 15+ messages in thread
From: Jakub Kicinski @ 2026-04-12 15:09 UTC (permalink / raw)
To: Krishna Chaitanya Chundru
Cc: Manivannan Sadhasivam, Richard Cochran, mhi, linux-arm-msm,
linux-kernel, netdev, Vivek Pernamitta, Sivareddy Surasani,
Vivek Pernamitta, Imran Shaik, Taniya Das
On Sat, 11 Apr 2026 13:42:00 +0530 Krishna Chaitanya Chundru wrote:
> - User space applications use the standard Linux PTP interface.
> - The PTP subsystem routes IOCTLs to the MHI PHC driver.
> - The MHI PHC driver communicates with the MHI core to fetch timestamps.
> - The MHI core interacts with the device to retrieve accurate time data.
Nack, stop adding functionality under the mhi "bus".
Bus is supposed to be an abstraction into which real drivers plug in.
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 0/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI
2026-04-12 15:09 ` [PATCH v2 0/6] " Jakub Kicinski
@ 2026-04-13 5:44 ` Manivannan Sadhasivam
0 siblings, 0 replies; 15+ messages in thread
From: Manivannan Sadhasivam @ 2026-04-13 5:44 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Krishna Chaitanya Chundru, Richard Cochran, mhi, linux-arm-msm,
linux-kernel, netdev, Vivek Pernamitta, Sivareddy Surasani,
Vivek Pernamitta, Imran Shaik, Taniya Das
On Sun, Apr 12, 2026 at 08:09:51AM -0700, Jakub Kicinski wrote:
> On Sat, 11 Apr 2026 13:42:00 +0530 Krishna Chaitanya Chundru wrote:
> > - User space applications use the standard Linux PTP interface.
> > - The PTP subsystem routes IOCTLs to the MHI PHC driver.
> > - The MHI PHC driver communicates with the MHI core to fetch timestamps.
> > - The MHI core interacts with the device to retrieve accurate time data.
This is a misleading statement. Only the 'pci_generic' controller driver interacts
with the device for querying timestamp. MHI bus just acts as a messenger.
>
> Nack, stop adding functionality under the mhi "bus".
> Bus is supposed to be an abstraction into which real drivers plug in.
MHI bus is very similar to the PCI bus. Just like PCI capabilities, MHI also has
capabilities to discover the supported functionalities including timesync. So
for making use of the timesync feature, we need to add some hooks into the MHI
bus layer, but the functionality is added to the separate driver,
'drivers/bus/mhi/host/mhi_phc*'. This is also quite similar to how PCI(e)
features like AER, hotplug are structured.
In this series, timesync API definitions are added mistakenly to the core bus
code, which should be moved to the mhi_phc driver instead.
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply [flat|nested] 15+ messages in thread