[PATCH v2 6/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI

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

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