Re: [PATCH v2 6/6] bus: mhi: host: mhi_phc: Add support for PHC over MHI
Jie Gan <[email protected]> Sat, 11 Apr 2026 21:50:37 +0800
| Newsgroups | dev.linux.lists.mhi,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-kernel,org.kernel.vger.netdev |
|---|---|
| Message-ID | <[email protected]> |
On 4/11/2026 4:12 PM, Krishna Chaitanya Chundru wrote: > From: Imran Shaik <[email protected]> > > 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 <[email protected]> > Signed-off-by: Taniya Das <[email protected]> > Signed-off-by: Imran Shaik <[email protected]> > --- > 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); >