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) {}
+#endifdiff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c
index e6c13c1a35253e6630b193827f8dadcd22a6198a..5ba760b2db43fb86b241b89d96e4b4354672bec3 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
@@ -1037,6 +1038,7 @@ struct mhi_pci_device {
struct work_struct recovery_work;
struct timer_list health_check_timer;
unsigned long status;
+ bool mhi_phc_init_done;
};
#ifdef readq@@ -1077,6 +1079,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) {@@ -1084,9 +1087,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;@@ -1227,6 +1242,10 @@ static void mhi_pci_recovery_work(struct work_struct *work)
timer_delete(&mhi_pdev->health_check_timer);
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);@@ -1427,6 +1446,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