new file mode 100644
@@ -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
@@ -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*
@@ -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
@@ -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:
@@ -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);
new file mode 100644
@@ -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);
+}
@@ -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)) {
@@ -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)
{
@@ -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 */