diff mbox series

[3/4] PCI: dwc: Add sysfs support for PTM

Message ID 20250218-pcie-qcom-ptm-v1-3-16d7e480d73e@linaro.org (mailing list archive)
State New
Delegated to: Krzysztof WilczyƄski
Headers show
Series PCI: dwc: Add PTM sysfs support | expand

Commit Message

Manivannan Sadhasivam via B4 Relay Feb. 18, 2025, 2:36 p.m. UTC
From: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>

Precision Time Management (PTM) mechanism defined in PCIe spec r6.0,
sec 6.22 allows precise coordination of timing information across multiple
components in a PCIe hierarchy with independent local time clocks.

While the PTM support itself is indicated by the presence of PTM Extended
Capability structure, Synopsys Designware IPs expose the PTM context
(timing information) through Vendor Specific Extended Capability (VSEC)
registers.

Hence, add the sysfs support to expose the PTM context information to
userspace from both PCIe RC and EP controllers. Below PTM context are
exposed through sysfs:

PCIe RC
=======

1. PTM Local clock
2. PTM T2 timestamp
3. PTM T3 timestamp
4. PTM Context valid

PCIe EP
=======

1. PTM Local clock
2. PTM T1 timestamp
3. PTM T4 timestamp
4. PTM Master clock
5. PTM Context update

Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
---
 Documentation/ABI/testing/sysfs-platform-dwc-pcie  |  70 ++++++
 MAINTAINERS                                        |   1 +
 drivers/pci/controller/dwc/Makefile                |   2 +-
 drivers/pci/controller/dwc/pcie-designware-ep.c    |   3 +
 drivers/pci/controller/dwc/pcie-designware-host.c  |   4 +
 drivers/pci/controller/dwc/pcie-designware-sysfs.c | 278 +++++++++++++++++++++
 drivers/pci/controller/dwc/pcie-designware.c       |   6 +
 drivers/pci/controller/dwc/pcie-designware.h       |  22 ++
 include/linux/pcie-dwc.h                           |   8 +
 9 files changed, 393 insertions(+), 1 deletion(-)

Comments

Dmitry Baryshkov Feb. 18, 2025, 5:54 p.m. UTC | #1
On Tue, Feb 18, 2025 at 08:06:42PM +0530, Manivannan Sadhasivam via B4 Relay wrote:
> From: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> 
> Precision Time Management (PTM) mechanism defined in PCIe spec r6.0,
> sec 6.22 allows precise coordination of timing information across multiple
> components in a PCIe hierarchy with independent local time clocks.
> 
> While the PTM support itself is indicated by the presence of PTM Extended
> Capability structure, Synopsys Designware IPs expose the PTM context
> (timing information) through Vendor Specific Extended Capability (VSEC)
> registers.
> 
> Hence, add the sysfs support to expose the PTM context information to
> userspace from both PCIe RC and EP controllers. Below PTM context are
> exposed through sysfs:
> 
> PCIe RC
> =======
> 
> 1. PTM Local clock
> 2. PTM T2 timestamp
> 3. PTM T3 timestamp
> 4. PTM Context valid
> 
> PCIe EP
> =======
> 
> 1. PTM Local clock
> 2. PTM T1 timestamp
> 3. PTM T4 timestamp
> 4. PTM Master clock
> 5. PTM Context update
> 
> Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> ---
>  Documentation/ABI/testing/sysfs-platform-dwc-pcie  |  70 ++++++
>  MAINTAINERS                                        |   1 +
>  drivers/pci/controller/dwc/Makefile                |   2 +-
>  drivers/pci/controller/dwc/pcie-designware-ep.c    |   3 +
>  drivers/pci/controller/dwc/pcie-designware-host.c  |   4 +
>  drivers/pci/controller/dwc/pcie-designware-sysfs.c | 278 +++++++++++++++++++++
>  drivers/pci/controller/dwc/pcie-designware.c       |   6 +
>  drivers/pci/controller/dwc/pcie-designware.h       |  22 ++
>  include/linux/pcie-dwc.h                           |   8 +
>  9 files changed, 393 insertions(+), 1 deletion(-)
> 
> diff --git a/Documentation/ABI/testing/sysfs-platform-dwc-pcie b/Documentation/ABI/testing/sysfs-platform-dwc-pcie
> new file mode 100644
> index 000000000000..6b429108cd09
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-platform-dwc-pcie

Should be a class or just a ptm group in the PCIe controller device? How
generic are those attributes?

> @@ -0,0 +1,70 @@
> +What:		/sys/devices/platform/*/dwc/ptm/ptm_local_clock
> +Date:		February 2025
> +Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> +Description:
> +		(RO) PTM local clock in nanoseconds. Applicable for both Root
> +		Complex and Endpoint mode.
> +
> +What:		/sys/devices/platform/*/dwc/ptm/ptm_master_clock
> +Date:		February 2025
> +Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> +Description:
> +		(RO) PTM master clock in nanoseconds. Applicable only for
> +		Endpoint mode.
> +
> +What:		/sys/devices/platform/*/dwc/ptm/ptm_t1
> +Date:		February 2025
> +Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> +Description:
> +		(RO) PTM T1 timestamp in nanoseconds. Applicable only for
> +		Endpoint mode.
> +
> +What:		/sys/devices/platform/*/dwc/ptm/ptm_t2
> +Date:		February 2025
> +Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> +Description:
> +		(RO) PTM T2 timestamp in nanoseconds. Applicable only for
> +		Root Complex mode.
> +
> +What:		/sys/devices/platform/*/dwc/ptm/ptm_t3
> +Date:		February 2025
> +Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> +Description:
> +		(RO) PTM T3 timestamp in nanoseconds. Applicable only for
> +		Root Complex mode.
> +
> +What:		/sys/devices/platform/*/dwc/ptm/ptm_t4
> +Date:		February 2025
> +Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> +Description:
> +		(RO) PTM T4 timestamp in nanoseconds. Applicable only for
> +		Endpoint mode.
> +
> +What:		/sys/devices/platform/*/dwc/ptm/ptm_context_update
> +Date:		February 2025
> +Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> +Description:
> +		(RW) Control the PTM context update mode. Applicable only for
> +		Endpoint mode.
> +
> +		Following values are supported:
> +
> +		* auto = PTM context auto update trigger for every 10ms
> +
> +		* manual = PTM context manual update. Writing 'manual' to this
> +			   file triggers PTM context update (default)
> +
> +What:		/sys/devices/platform/*/dwc/ptm/ptm_context_valid
> +Date:		February 2025
> +Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> +Description:
> +		(RW) Control the PTM context validity (local clock timing).
> +		Applicable only for Root Complex mode. PTM context is
> +		invalidated by hardware if the Root Complex enters low power
> +		mode or changes link frequency.
> +
> +		Following values are supported:
> +
> +		* 0 = PTM context invalid (default)
> +
> +		* 1 = PTM context valid
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b4d09d52a750..1c3e21cfbc6e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18120,6 +18120,7 @@ M:	Jingoo Han <jingoohan1@gmail.com>
>  M:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
>  L:	linux-pci@vger.kernel.org
>  S:	Maintained
> +F:	Documentation/ABI/testing/sysfs-platform-dwc-pcie
>  F:	Documentation/devicetree/bindings/pci/snps,dw-pcie-ep.yaml
>  F:	Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml
>  F:	drivers/pci/controller/dwc/*designware*

[...]

> +
> +static struct attribute *ptm_attrs[] = {
> +	&dev_attr_ptm_context_update.attr,
> +	&dev_attr_ptm_context_valid.attr,
> +	&dev_attr_ptm_local_clock.attr,
> +	&dev_attr_ptm_master_clock.attr,
> +	&dev_attr_ptm_t1.attr,
> +	&dev_attr_ptm_t2.attr,
> +	&dev_attr_ptm_t3.attr,
> +	&dev_attr_ptm_t4.attr,
> +	NULL
> +};
> +
> +static umode_t ptm_attr_visible(struct kobject *kobj, struct attribute *attr,
> +				int n)
> +{
> +	struct device *dev = container_of(kobj, struct device, kobj);
> +	struct dw_pcie *pci = dev_get_drvdata(dev);
> +
> +	/* RC only needs local, t2 and t3 clocks and context_valid */
> +	if ((attr == &dev_attr_ptm_t1.attr && pci->mode == DW_PCIE_RC_TYPE) ||
> +	    (attr == &dev_attr_ptm_t4.attr && pci->mode == DW_PCIE_RC_TYPE) ||
> +	    (attr == &dev_attr_ptm_master_clock.attr && pci->mode == DW_PCIE_RC_TYPE) ||
> +	    (attr == &dev_attr_ptm_context_update.attr && pci->mode == DW_PCIE_RC_TYPE))
> +		return 0;

The pci->mode checks definitely can be refactored to a top-level instead
of being repeated on each line.

> +
> +	/* EP only needs local, master, t1, and t4 clocks and context_update */
> +	if ((attr == &dev_attr_ptm_t2.attr && pci->mode == DW_PCIE_EP_TYPE) ||
> +	    (attr == &dev_attr_ptm_t3.attr && pci->mode == DW_PCIE_EP_TYPE) ||
> +	    (attr == &dev_attr_ptm_context_valid.attr && pci->mode == DW_PCIE_EP_TYPE))
> +		return 0;
> +
> +	return attr->mode;

I think it might be better to register two separate groups, one for RC,
one for EP and use presense of the corresponding capability in the
.is_visible callback to check if the PTM attributes should be visible at
all.

> +}
> +
> +static const struct attribute_group ptm_attr_group = {
> +	.name = "ptm",
> +	.attrs = ptm_attrs,
> +	.is_visible = ptm_attr_visible,
> +};
> +
> +static const struct attribute_group *dwc_pcie_attr_groups[] = {
> +	&ptm_attr_group,
> +	NULL,
> +};
> +
> +static void pcie_designware_sysfs_release(struct device *dev)
> +{
> +	kfree(dev);
> +}
> +
> +void pcie_designware_sysfs_init(struct dw_pcie *pci,
> +				    enum dw_pcie_device_mode mode)
> +{
> +	struct device *dev;
> +	int ret;
> +
> +	/* Check for capabilities before creating sysfs attrbutes */
> +	ret = dw_pcie_find_ptm_capability(pci);
> +	if (!ret) {
> +		dev_dbg(pci->dev, "PTM capability not present\n");
> +		return;
> +	}
> +
> +	pci->ptm_vsec_offset = ret;
> +	pci->mode = mode;
> +
> +	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
> +	if (!dev)
> +		return;
> +
> +	device_initialize(dev);
> +	dev->groups = dwc_pcie_attr_groups;
> +	dev->release = pcie_designware_sysfs_release;
> +	dev->parent = pci->dev;
> +	dev_set_drvdata(dev, pci);
> +
> +	ret = dev_set_name(dev, "dwc");
> +	if (ret)
> +		goto err_free;
> +
> +	ret = device_add(dev);
> +	if (ret)
> +		goto err_free;
> +
> +	pci->sysfs_dev = dev;

Why do you need to add a new device under the PCIe controller?

> +
> +	return;
> +
> +err_free:
> +	put_device(dev);
> +}
> +
> +void pcie_designware_sysfs_exit(struct dw_pcie *pci)
> +{
> +	if (pci->sysfs_dev)
> +		device_unregister(pci->sysfs_dev);
> +}
> diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
> index a7c0671c6715..30825ec0648e 100644
> --- a/drivers/pci/controller/dwc/pcie-designware.c
> +++ b/drivers/pci/controller/dwc/pcie-designware.c
> @@ -323,6 +323,12 @@ static u16 dw_pcie_find_vsec_capability(struct dw_pcie *pci,
>  	return 0;
>  }
>  
> +u16 dw_pcie_find_ptm_capability(struct dw_pcie *pci)
> +{
> +	return dw_pcie_find_vsec_capability(pci, dwc_pcie_ptm_vsec_ids);
> +}
> +EXPORT_SYMBOL_GPL(dw_pcie_find_ptm_capability);

This API should go into the previous patch. Otherwise it will result in
unused function warnings.

> +
>  int dw_pcie_read(void __iomem *addr, int size, u32 *val)
>  {
>  	if (!IS_ALIGNED((uintptr_t)addr, size)) {
> diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
> index 501d9ddfea16..7d3cbdce37c8 100644
> --- a/drivers/pci/controller/dwc/pcie-designware.h
> +++ b/drivers/pci/controller/dwc/pcie-designware.h
> @@ -260,6 +260,21 @@
>  
>  #define PCIE_RAS_DES_EVENT_COUNTER_DATA		0xc
>  
> +/* PTM register definitions */
> +#define PTM_RES_REQ_CTRL		0x8
> +#define PTM_RES_CCONTEXT_VALID		BIT(0)
> +#define PTM_REQ_AUTO_UPDATE_ENABLED	BIT(0)
> +#define PTM_REQ_START_UPDATE		BIT(1)
> +
> +#define PTM_LOCAL_LSB			0x10
> +#define PTM_LOCAL_MSB			0x14
> +#define PTM_T1_T2_LSB			0x18
> +#define PTM_T1_T2_MSB			0x1c
> +#define PTM_T3_T4_LSB			0x28
> +#define PTM_T3_T4_MSB			0x2c
> +#define PTM_MASTER_LSB			0x38
> +#define PTM_MASTER_MSB			0x3c
> +
>  /*
>   * The default address offset between dbi_base and atu_base. Root controller
>   * drivers are not required to initialize atu_base if the offset matches this
> @@ -439,6 +454,7 @@ struct dw_pcie_ops {
>  
>  struct dw_pcie {
>  	struct device		*dev;
> +	struct device		*sysfs_dev;
>  	void __iomem		*dbi_base;
>  	resource_size_t		dbi_phys_addr;
>  	void __iomem		*dbi_base2;
> @@ -464,6 +480,8 @@ struct dw_pcie {
>  	struct reset_control_bulk_data	app_rsts[DW_PCIE_NUM_APP_RSTS];
>  	struct reset_control_bulk_data	core_rsts[DW_PCIE_NUM_CORE_RSTS];
>  	struct gpio_desc		*pe_rst;
> +	u16			ptm_vsec_offset;
> +	enum			dw_pcie_device_mode mode;
>  	bool			suspended;
>  };
>  
> @@ -478,6 +496,7 @@ void dw_pcie_version_detect(struct dw_pcie *pci);
>  
>  u8 dw_pcie_find_capability(struct dw_pcie *pci, u8 cap);
>  u16 dw_pcie_find_ext_capability(struct dw_pcie *pci, u8 cap);
> +u16 dw_pcie_find_ptm_capability(struct dw_pcie *pci);
>  
>  int dw_pcie_read(void __iomem *addr, int size, u32 *val);
>  int dw_pcie_write(void __iomem *addr, int size, u32 val);
> @@ -499,6 +518,9 @@ void dw_pcie_setup(struct dw_pcie *pci);
>  void dw_pcie_iatu_detect(struct dw_pcie *pci);
>  int dw_pcie_edma_detect(struct dw_pcie *pci);
>  void dw_pcie_edma_remove(struct dw_pcie *pci);
> +void pcie_designware_sysfs_init(struct dw_pcie *pci,
> +				enum dw_pcie_device_mode mode);
> +void pcie_designware_sysfs_exit(struct dw_pcie *pci);
>  
>  static inline void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val)
>  {
> diff --git a/include/linux/pcie-dwc.h b/include/linux/pcie-dwc.h
> index 261ae11d75a4..13835896290a 100644
> --- a/include/linux/pcie-dwc.h
> +++ b/include/linux/pcie-dwc.h
> @@ -31,4 +31,12 @@ static const struct dwc_pcie_vsec_id dwc_pcie_pmu_vsec_ids[] = {
>  	{} /* terminator */
>  };
>  
> +static const struct dwc_pcie_vsec_id dwc_pcie_ptm_vsec_ids[] = {
> +	{ .vendor_id = PCI_VENDOR_ID_QCOM, /* EP */
> +	  .vsec_id = 0x03, .vsec_rev = 0x1 },
> +	{ .vendor_id = PCI_VENDOR_ID_QCOM, /* RC */
> +	  .vsec_id = 0x04, .vsec_rev = 0x1 },
> +	{ }
> +};
> +
>  #endif /* LINUX_PCIE_DWC_H */
> 
> -- 
> 2.25.1
> 
>
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-platform-dwc-pcie b/Documentation/ABI/testing/sysfs-platform-dwc-pcie
new file mode 100644
index 000000000000..6b429108cd09
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-dwc-pcie
@@ -0,0 +1,70 @@ 
+What:		/sys/devices/platform/*/dwc/ptm/ptm_local_clock
+Date:		February 2025
+Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+Description:
+		(RO) PTM local clock in nanoseconds. Applicable for both Root
+		Complex and Endpoint mode.
+
+What:		/sys/devices/platform/*/dwc/ptm/ptm_master_clock
+Date:		February 2025
+Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+Description:
+		(RO) PTM master clock in nanoseconds. Applicable only for
+		Endpoint mode.
+
+What:		/sys/devices/platform/*/dwc/ptm/ptm_t1
+Date:		February 2025
+Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+Description:
+		(RO) PTM T1 timestamp in nanoseconds. Applicable only for
+		Endpoint mode.
+
+What:		/sys/devices/platform/*/dwc/ptm/ptm_t2
+Date:		February 2025
+Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+Description:
+		(RO) PTM T2 timestamp in nanoseconds. Applicable only for
+		Root Complex mode.
+
+What:		/sys/devices/platform/*/dwc/ptm/ptm_t3
+Date:		February 2025
+Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+Description:
+		(RO) PTM T3 timestamp in nanoseconds. Applicable only for
+		Root Complex mode.
+
+What:		/sys/devices/platform/*/dwc/ptm/ptm_t4
+Date:		February 2025
+Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+Description:
+		(RO) PTM T4 timestamp in nanoseconds. Applicable only for
+		Endpoint mode.
+
+What:		/sys/devices/platform/*/dwc/ptm/ptm_context_update
+Date:		February 2025
+Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+Description:
+		(RW) Control the PTM context update mode. Applicable only for
+		Endpoint mode.
+
+		Following values are supported:
+
+		* auto = PTM context auto update trigger for every 10ms
+
+		* manual = PTM context manual update. Writing 'manual' to this
+			   file triggers PTM context update (default)
+
+What:		/sys/devices/platform/*/dwc/ptm/ptm_context_valid
+Date:		February 2025
+Contact:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+Description:
+		(RW) Control the PTM context validity (local clock timing).
+		Applicable only for Root Complex mode. PTM context is
+		invalidated by hardware if the Root Complex enters low power
+		mode or changes link frequency.
+
+		Following values are supported:
+
+		* 0 = PTM context invalid (default)
+
+		* 1 = PTM context valid
diff --git a/MAINTAINERS b/MAINTAINERS
index b4d09d52a750..1c3e21cfbc6e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18120,6 +18120,7 @@  M:	Jingoo Han <jingoohan1@gmail.com>
 M:	Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
 L:	linux-pci@vger.kernel.org
 S:	Maintained
+F:	Documentation/ABI/testing/sysfs-platform-dwc-pcie
 F:	Documentation/devicetree/bindings/pci/snps,dw-pcie-ep.yaml
 F:	Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml
 F:	drivers/pci/controller/dwc/*designware*
diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
index a8308d9ea986..3a962971249d 100644
--- a/drivers/pci/controller/dwc/Makefile
+++ b/drivers/pci/controller/dwc/Makefile
@@ -1,5 +1,5 @@ 
 # SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_PCIE_DW) += pcie-designware.o
+obj-$(CONFIG_PCIE_DW) += pcie-designware.o pcie-designware-sysfs.o
 obj-$(CONFIG_PCIE_DW_HOST) += pcie-designware-host.o
 obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
 obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
index 8e07d432e74f..4bb7fd5d2fb0 100644
--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -666,6 +666,7 @@  void dw_pcie_ep_cleanup(struct dw_pcie_ep *ep)
 {
 	struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
 
+	pcie_designware_sysfs_exit(pci);
 	dw_pcie_edma_remove(pci);
 }
 EXPORT_SYMBOL_GPL(dw_pcie_ep_cleanup);
@@ -837,6 +838,8 @@  int dw_pcie_ep_init_registers(struct dw_pcie_ep *ep)
 
 	dw_pcie_ep_init_non_sticky_registers(pci);
 
+	pcie_designware_sysfs_init(pci, DW_PCIE_EP_TYPE);
+
 	return 0;
 
 err_remove_edma:
diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c
index ffaded8f2df7..bd3195de923c 100644
--- a/drivers/pci/controller/dwc/pcie-designware-host.c
+++ b/drivers/pci/controller/dwc/pcie-designware-host.c
@@ -548,6 +548,8 @@  int dw_pcie_host_init(struct dw_pcie_rp *pp)
 	if (pp->ops->post_init)
 		pp->ops->post_init(pp);
 
+	pcie_designware_sysfs_init(pci, DW_PCIE_RC_TYPE);
+
 	return 0;
 
 err_stop_link:
@@ -572,6 +574,8 @@  void dw_pcie_host_deinit(struct dw_pcie_rp *pp)
 {
 	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
 
+	pcie_designware_sysfs_exit(pci);
+
 	pci_stop_root_bus(pp->bridge->bus);
 	pci_remove_root_bus(pp->bridge->bus);
 
diff --git a/drivers/pci/controller/dwc/pcie-designware-sysfs.c b/drivers/pci/controller/dwc/pcie-designware-sysfs.c
new file mode 100644
index 000000000000..1ba41163cf58
--- /dev/null
+++ b/drivers/pci/controller/dwc/pcie-designware-sysfs.c
@@ -0,0 +1,278 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 Linaro Ltd.
+ * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+
+#include "pcie-designware.h"
+
+static ssize_t ptm_context_update_store(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct dw_pcie *pci = dev_get_drvdata(dev);
+	u32 val;
+
+	if (sysfs_streq(buf, "auto")) {
+		val = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_RES_REQ_CTRL);
+		val |= PTM_REQ_AUTO_UPDATE_ENABLED;
+		dw_pcie_writel_dbi(pci, pci->ptm_vsec_offset + PTM_RES_REQ_CTRL, val);
+	} else if (sysfs_streq(buf, "manual")) {
+		val = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_RES_REQ_CTRL);
+		val &= ~PTM_REQ_AUTO_UPDATE_ENABLED;
+		val |= PTM_REQ_START_UPDATE;
+		dw_pcie_writel_dbi(pci, pci->ptm_vsec_offset + PTM_RES_REQ_CTRL, val);
+	} else {
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static ssize_t ptm_context_update_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct dw_pcie *pci = dev_get_drvdata(dev);
+	u32 val;
+
+	val = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_RES_REQ_CTRL);
+	if (FIELD_GET(PTM_REQ_AUTO_UPDATE_ENABLED, val))
+		return sysfs_emit(buf, "auto\n");
+
+	/*
+	 * PTM_REQ_START_UPDATE is a self clearing register bit. So if
+	 * PTM_REQ_AUTO_UPDATE_ENABLED is not set, then it implies that
+	 * manual update is used.
+	 */
+	return sysfs_emit(buf, "manual\n");
+}
+
+static ssize_t ptm_context_valid_store(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct dw_pcie *pci = dev_get_drvdata(dev);
+	unsigned long arg;
+	u32 val;
+
+	if (kstrtoul(buf, 0, &arg) < 0)
+		return -EINVAL;
+
+	if (!!arg) {
+		val = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_RES_REQ_CTRL);
+		val |= PTM_RES_CCONTEXT_VALID;
+		dw_pcie_writel_dbi(pci, pci->ptm_vsec_offset + PTM_RES_REQ_CTRL, val);
+	} else {
+		val = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_RES_REQ_CTRL);
+		val &= ~PTM_RES_CCONTEXT_VALID;
+		dw_pcie_writel_dbi(pci, pci->ptm_vsec_offset + PTM_RES_REQ_CTRL, val);
+	}
+
+	return count;
+}
+
+static ssize_t ptm_context_valid_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct dw_pcie *pci = dev_get_drvdata(dev);
+	u32 val;
+
+	val = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_RES_REQ_CTRL);
+
+	return sysfs_emit(buf, "%u\n", !!FIELD_GET(PTM_RES_CCONTEXT_VALID, val));
+}
+
+static ssize_t ptm_local_clock_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct dw_pcie *pci = dev_get_drvdata(dev);
+	u32 msb, lsb;
+
+	do {
+		msb = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_LOCAL_MSB);
+		lsb = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_LOCAL_LSB);
+	} while (msb != dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_LOCAL_MSB));
+
+	return sysfs_emit(buf, "%llu\n", ((u64) msb) << 32 | lsb);
+}
+
+static ssize_t ptm_master_clock_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct dw_pcie *pci = dev_get_drvdata(dev);
+	u32 msb, lsb;
+
+	do {
+		msb = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_MASTER_MSB);
+		lsb = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_MASTER_LSB);
+	} while (msb != dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_MASTER_MSB));
+
+	return sysfs_emit(buf, "%llu\n", ((u64) msb) << 32 | lsb);
+}
+
+static ssize_t ptm_t1_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct dw_pcie *pci = dev_get_drvdata(dev);
+	u32 msb, lsb;
+
+	do {
+		msb = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_T1_T2_MSB);
+		lsb = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_T1_T2_LSB);
+	} while (msb != dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_T1_T2_MSB));
+
+	return sysfs_emit(buf, "%llu\n", ((u64) msb) << 32 | lsb);
+}
+
+static ssize_t ptm_t2_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct dw_pcie *pci = dev_get_drvdata(dev);
+	u32 msb, lsb;
+
+	do {
+		msb = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_T1_T2_MSB);
+		lsb = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_T1_T2_LSB);
+	} while (msb != dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_T1_T2_MSB));
+
+	return sysfs_emit(buf, "%llu\n", ((u64) msb) << 32 | lsb);
+}
+
+static ssize_t ptm_t3_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct dw_pcie *pci = dev_get_drvdata(dev);
+	u32 msb, lsb;
+
+	do {
+		msb = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_T3_T4_MSB);
+		lsb = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_T3_T4_LSB);
+	} while (msb != dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_T3_T4_MSB));
+
+	return sysfs_emit(buf, "%llu\n", ((u64) msb) << 32 | lsb);
+}
+
+static ssize_t ptm_t4_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct dw_pcie *pci = dev_get_drvdata(dev);
+	u32 msb, lsb;
+
+	do {
+		msb = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_T3_T4_MSB);
+		lsb = dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_T3_T4_LSB);
+	} while (msb != dw_pcie_readl_dbi(pci, pci->ptm_vsec_offset + PTM_T3_T4_MSB));
+
+	return sysfs_emit(buf, "%llu\n", ((u64) msb) << 32 | lsb);
+}
+
+static DEVICE_ATTR_RW(ptm_context_update);
+static DEVICE_ATTR_RW(ptm_context_valid);
+static DEVICE_ATTR_RO(ptm_local_clock);
+static DEVICE_ATTR_RO(ptm_master_clock);
+static DEVICE_ATTR_RO(ptm_t1);
+static DEVICE_ATTR_RO(ptm_t2);
+static DEVICE_ATTR_RO(ptm_t3);
+static DEVICE_ATTR_RO(ptm_t4);
+
+static struct attribute *ptm_attrs[] = {
+	&dev_attr_ptm_context_update.attr,
+	&dev_attr_ptm_context_valid.attr,
+	&dev_attr_ptm_local_clock.attr,
+	&dev_attr_ptm_master_clock.attr,
+	&dev_attr_ptm_t1.attr,
+	&dev_attr_ptm_t2.attr,
+	&dev_attr_ptm_t3.attr,
+	&dev_attr_ptm_t4.attr,
+	NULL
+};
+
+static umode_t ptm_attr_visible(struct kobject *kobj, struct attribute *attr,
+				int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct dw_pcie *pci = dev_get_drvdata(dev);
+
+	/* RC only needs local, t2 and t3 clocks and context_valid */
+	if ((attr == &dev_attr_ptm_t1.attr && pci->mode == DW_PCIE_RC_TYPE) ||
+	    (attr == &dev_attr_ptm_t4.attr && pci->mode == DW_PCIE_RC_TYPE) ||
+	    (attr == &dev_attr_ptm_master_clock.attr && pci->mode == DW_PCIE_RC_TYPE) ||
+	    (attr == &dev_attr_ptm_context_update.attr && pci->mode == DW_PCIE_RC_TYPE))
+		return 0;
+
+	/* EP only needs local, master, t1, and t4 clocks and context_update */
+	if ((attr == &dev_attr_ptm_t2.attr && pci->mode == DW_PCIE_EP_TYPE) ||
+	    (attr == &dev_attr_ptm_t3.attr && pci->mode == DW_PCIE_EP_TYPE) ||
+	    (attr == &dev_attr_ptm_context_valid.attr && pci->mode == DW_PCIE_EP_TYPE))
+		return 0;
+
+	return attr->mode;
+}
+
+static const struct attribute_group ptm_attr_group = {
+	.name = "ptm",
+	.attrs = ptm_attrs,
+	.is_visible = ptm_attr_visible,
+};
+
+static const struct attribute_group *dwc_pcie_attr_groups[] = {
+	&ptm_attr_group,
+	NULL,
+};
+
+static void pcie_designware_sysfs_release(struct device *dev)
+{
+	kfree(dev);
+}
+
+void pcie_designware_sysfs_init(struct dw_pcie *pci,
+				    enum dw_pcie_device_mode mode)
+{
+	struct device *dev;
+	int ret;
+
+	/* Check for capabilities before creating sysfs attrbutes */
+	ret = dw_pcie_find_ptm_capability(pci);
+	if (!ret) {
+		dev_dbg(pci->dev, "PTM capability not present\n");
+		return;
+	}
+
+	pci->ptm_vsec_offset = ret;
+	pci->mode = mode;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return;
+
+	device_initialize(dev);
+	dev->groups = dwc_pcie_attr_groups;
+	dev->release = pcie_designware_sysfs_release;
+	dev->parent = pci->dev;
+	dev_set_drvdata(dev, pci);
+
+	ret = dev_set_name(dev, "dwc");
+	if (ret)
+		goto err_free;
+
+	ret = device_add(dev);
+	if (ret)
+		goto err_free;
+
+	pci->sysfs_dev = dev;
+
+	return;
+
+err_free:
+	put_device(dev);
+}
+
+void pcie_designware_sysfs_exit(struct dw_pcie *pci)
+{
+	if (pci->sysfs_dev)
+		device_unregister(pci->sysfs_dev);
+}
diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
index a7c0671c6715..30825ec0648e 100644
--- a/drivers/pci/controller/dwc/pcie-designware.c
+++ b/drivers/pci/controller/dwc/pcie-designware.c
@@ -323,6 +323,12 @@  static u16 dw_pcie_find_vsec_capability(struct dw_pcie *pci,
 	return 0;
 }
 
+u16 dw_pcie_find_ptm_capability(struct dw_pcie *pci)
+{
+	return dw_pcie_find_vsec_capability(pci, dwc_pcie_ptm_vsec_ids);
+}
+EXPORT_SYMBOL_GPL(dw_pcie_find_ptm_capability);
+
 int dw_pcie_read(void __iomem *addr, int size, u32 *val)
 {
 	if (!IS_ALIGNED((uintptr_t)addr, size)) {
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index 501d9ddfea16..7d3cbdce37c8 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -260,6 +260,21 @@ 
 
 #define PCIE_RAS_DES_EVENT_COUNTER_DATA		0xc
 
+/* PTM register definitions */
+#define PTM_RES_REQ_CTRL		0x8
+#define PTM_RES_CCONTEXT_VALID		BIT(0)
+#define PTM_REQ_AUTO_UPDATE_ENABLED	BIT(0)
+#define PTM_REQ_START_UPDATE		BIT(1)
+
+#define PTM_LOCAL_LSB			0x10
+#define PTM_LOCAL_MSB			0x14
+#define PTM_T1_T2_LSB			0x18
+#define PTM_T1_T2_MSB			0x1c
+#define PTM_T3_T4_LSB			0x28
+#define PTM_T3_T4_MSB			0x2c
+#define PTM_MASTER_LSB			0x38
+#define PTM_MASTER_MSB			0x3c
+
 /*
  * The default address offset between dbi_base and atu_base. Root controller
  * drivers are not required to initialize atu_base if the offset matches this
@@ -439,6 +454,7 @@  struct dw_pcie_ops {
 
 struct dw_pcie {
 	struct device		*dev;
+	struct device		*sysfs_dev;
 	void __iomem		*dbi_base;
 	resource_size_t		dbi_phys_addr;
 	void __iomem		*dbi_base2;
@@ -464,6 +480,8 @@  struct dw_pcie {
 	struct reset_control_bulk_data	app_rsts[DW_PCIE_NUM_APP_RSTS];
 	struct reset_control_bulk_data	core_rsts[DW_PCIE_NUM_CORE_RSTS];
 	struct gpio_desc		*pe_rst;
+	u16			ptm_vsec_offset;
+	enum			dw_pcie_device_mode mode;
 	bool			suspended;
 };
 
@@ -478,6 +496,7 @@  void dw_pcie_version_detect(struct dw_pcie *pci);
 
 u8 dw_pcie_find_capability(struct dw_pcie *pci, u8 cap);
 u16 dw_pcie_find_ext_capability(struct dw_pcie *pci, u8 cap);
+u16 dw_pcie_find_ptm_capability(struct dw_pcie *pci);
 
 int dw_pcie_read(void __iomem *addr, int size, u32 *val);
 int dw_pcie_write(void __iomem *addr, int size, u32 val);
@@ -499,6 +518,9 @@  void dw_pcie_setup(struct dw_pcie *pci);
 void dw_pcie_iatu_detect(struct dw_pcie *pci);
 int dw_pcie_edma_detect(struct dw_pcie *pci);
 void dw_pcie_edma_remove(struct dw_pcie *pci);
+void pcie_designware_sysfs_init(struct dw_pcie *pci,
+				enum dw_pcie_device_mode mode);
+void pcie_designware_sysfs_exit(struct dw_pcie *pci);
 
 static inline void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val)
 {
diff --git a/include/linux/pcie-dwc.h b/include/linux/pcie-dwc.h
index 261ae11d75a4..13835896290a 100644
--- a/include/linux/pcie-dwc.h
+++ b/include/linux/pcie-dwc.h
@@ -31,4 +31,12 @@  static const struct dwc_pcie_vsec_id dwc_pcie_pmu_vsec_ids[] = {
 	{} /* terminator */
 };
 
+static const struct dwc_pcie_vsec_id dwc_pcie_ptm_vsec_ids[] = {
+	{ .vendor_id = PCI_VENDOR_ID_QCOM, /* EP */
+	  .vsec_id = 0x03, .vsec_rev = 0x1 },
+	{ .vendor_id = PCI_VENDOR_ID_QCOM, /* RC */
+	  .vsec_id = 0x04, .vsec_rev = 0x1 },
+	{ }
+};
+
 #endif /* LINUX_PCIE_DWC_H */