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

Jie Gan
2026-04-11T13:50:44Z

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);
>