]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
PCI: dw-rockchip: Add pcie_ltssm_state_transition tracepoint support
authorShawn Lin <shawn.lin@rock-chips.com>
Wed, 25 Mar 2026 01:58:32 +0000 (09:58 +0800)
committerBjorn Helgaas <bhelgaas@google.com>
Tue, 7 Apr 2026 16:12:09 +0000 (11:12 -0500)
Rockchip platforms provide a 64x4 bytes debug FIFO to trace the LTSSM
transition and data rate change history. These will be useful for debugging
issues such as link failure, etc.

Hence, expose these information over pcie_ltssm_state_transition
tracepoint.

Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
[mani: commit log]
Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Tested-by: Anand Moon <linux.amoon@gmail.com>
Reviewed-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Link: https://patch.msgid.link/1774403912-210670-4-git-send-email-shawn.lin@rock-chips.com
drivers/pci/controller/dwc/pcie-dw-rockchip.c

index 5b17da63151d52f7bdc9e5b6fcd20b3420a3be20..c7f67472ec456619685738ebaca672e6f402c64e 100644 (file)
@@ -22,6 +22,8 @@
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
 #include <linux/reset.h>
+#include <linux/workqueue.h>
+#include <trace/events/pci_controller.h>
 
 #include "../../pci.h"
 #include "pcie-designware.h"
 #define  PCIE_CLIENT_CDM_RASDES_TBA_L1_1       BIT(4)
 #define  PCIE_CLIENT_CDM_RASDES_TBA_L1_2       BIT(5)
 
+/* Debug FIFO information */
+#define PCIE_CLIENT_DBG_FIFO_MODE_CON  0x310
+#define  PCIE_CLIENT_DBG_EN            0xffff0007
+#define  PCIE_CLIENT_DBG_DIS           0xffff0000
+#define PCIE_CLIENT_DBG_FIFO_PTN_HIT_D0        0x320
+#define PCIE_CLIENT_DBG_FIFO_PTN_HIT_D1        0x324
+#define PCIE_CLIENT_DBG_FIFO_TRN_HIT_D0        0x328
+#define PCIE_CLIENT_DBG_FIFO_TRN_HIT_D1        0x32c
+#define  PCIE_CLIENT_DBG_TRANSITION_DATA 0xffff0000
+#define PCIE_CLIENT_DBG_FIFO_STATUS    0x350
+#define  PCIE_DBG_FIFO_RATE_MASK       GENMASK(22, 20)
+#define  PCIE_DBG_FIFO_L1SUB_MASK      GENMASK(10, 8)
+#define PCIE_DBG_LTSSM_HISTORY_CNT     64
+
 /* Hot Reset Control Register */
 #define PCIE_CLIENT_HOT_RESET_CTRL     0x180
 #define  PCIE_LTSSM_APP_DLY2_EN                BIT(1)
@@ -98,6 +114,7 @@ struct rockchip_pcie {
        struct irq_domain *irq_domain;
        const struct rockchip_pcie_of_data *data;
        bool supports_clkreq;
+       struct delayed_work trace_work;
 };
 
 struct rockchip_pcie_of_data {
@@ -208,6 +225,96 @@ static enum dw_pcie_ltssm rockchip_pcie_get_ltssm(struct dw_pcie *pci)
        return rockchip_pcie_get_ltssm_reg(rockchip) & PCIE_LTSSM_STATUS_MASK;
 }
 
+#ifdef CONFIG_TRACING
+static void rockchip_pcie_ltssm_trace_work(struct work_struct *work)
+{
+       struct rockchip_pcie *rockchip = container_of(work,
+                                               struct rockchip_pcie,
+                                               trace_work.work);
+       struct dw_pcie *pci = &rockchip->pci;
+       enum dw_pcie_ltssm state;
+       u32 i, l1ss, prev_val = DW_PCIE_LTSSM_UNKNOWN, rate, val;
+
+       if (!trace_pcie_ltssm_state_transition_enabled())
+               goto skip_trace;
+
+       for (i = 0; i < PCIE_DBG_LTSSM_HISTORY_CNT; i++) {
+               val = rockchip_pcie_readl_apb(rockchip,
+                               PCIE_CLIENT_DBG_FIFO_STATUS);
+               rate = FIELD_GET(PCIE_DBG_FIFO_RATE_MASK, val);
+               l1ss = FIELD_GET(PCIE_DBG_FIFO_L1SUB_MASK, val);
+               val = FIELD_GET(PCIE_LTSSM_STATUS_MASK, val);
+
+               /*
+                * Hardware Mechanism: The ring FIFO employs two tracking
+                * counters:
+                * - 'last-read-point': maintains the user's last read position
+                * - 'last-valid-point': tracks the HW's last state update
+                *
+                * Software Handling: When two consecutive LTSSM states are
+                * identical, it indicates invalid subsequent data in the FIFO.
+                * In this case, we skip the remaining entries. The dual counter
+                * design ensures that on the next state transition, reading can
+                * resume from the last user position.
+                */
+               if ((i > 0 && val == prev_val) || val > DW_PCIE_LTSSM_RCVRY_EQ3)
+                       break;
+
+               state = prev_val = val;
+               if (val == DW_PCIE_LTSSM_L1_IDLE) {
+                       if (l1ss == 2)
+                               state = DW_PCIE_LTSSM_L1_2;
+                       else if (l1ss == 1)
+                               state = DW_PCIE_LTSSM_L1_1;
+               }
+
+               trace_pcie_ltssm_state_transition(dev_name(pci->dev),
+                               dw_pcie_ltssm_status_string(state),
+                               ((rate + 1) > pci->max_link_speed) ?
+                               PCI_SPEED_UNKNOWN : PCIE_SPEED_2_5GT + rate);
+       }
+
+skip_trace:
+       schedule_delayed_work(&rockchip->trace_work, msecs_to_jiffies(5000));
+}
+
+static void rockchip_pcie_ltssm_trace(struct rockchip_pcie *rockchip,
+                                     bool enable)
+{
+       if (enable) {
+               rockchip_pcie_writel_apb(rockchip,
+                                        PCIE_CLIENT_DBG_TRANSITION_DATA,
+                                        PCIE_CLIENT_DBG_FIFO_PTN_HIT_D0);
+               rockchip_pcie_writel_apb(rockchip,
+                                        PCIE_CLIENT_DBG_TRANSITION_DATA,
+                                        PCIE_CLIENT_DBG_FIFO_PTN_HIT_D1);
+               rockchip_pcie_writel_apb(rockchip,
+                                        PCIE_CLIENT_DBG_TRANSITION_DATA,
+                                        PCIE_CLIENT_DBG_FIFO_TRN_HIT_D0);
+               rockchip_pcie_writel_apb(rockchip,
+                                        PCIE_CLIENT_DBG_TRANSITION_DATA,
+                                        PCIE_CLIENT_DBG_FIFO_TRN_HIT_D1);
+               rockchip_pcie_writel_apb(rockchip,
+                                        PCIE_CLIENT_DBG_EN,
+                                        PCIE_CLIENT_DBG_FIFO_MODE_CON);
+
+               INIT_DELAYED_WORK(&rockchip->trace_work,
+                                 rockchip_pcie_ltssm_trace_work);
+               schedule_delayed_work(&rockchip->trace_work, 0);
+       } else {
+               rockchip_pcie_writel_apb(rockchip,
+                                        PCIE_CLIENT_DBG_DIS,
+                                        PCIE_CLIENT_DBG_FIFO_MODE_CON);
+               cancel_delayed_work_sync(&rockchip->trace_work);
+       }
+}
+#else
+static void rockchip_pcie_ltssm_trace(struct rockchip_pcie *rockchip,
+                                     bool enable)
+{
+}
+#endif
+
 static void rockchip_pcie_enable_ltssm(struct rockchip_pcie *rockchip)
 {
        rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_ENABLE_LTSSM,
@@ -291,6 +398,9 @@ static int rockchip_pcie_start_link(struct dw_pcie *pci)
         * 100us as we don't know how long should the device need to reset.
         */
        msleep(PCIE_T_PVPERL_MS);
+
+       rockchip_pcie_ltssm_trace(rockchip, true);
+
        gpiod_set_value_cansleep(rockchip->rst_gpio, 1);
 
        return 0;
@@ -301,6 +411,7 @@ static void rockchip_pcie_stop_link(struct dw_pcie *pci)
        struct rockchip_pcie *rockchip = to_rockchip_pcie(pci);
 
        rockchip_pcie_disable_ltssm(rockchip);
+       rockchip_pcie_ltssm_trace(rockchip, false);
 }
 
 static int rockchip_pcie_host_init(struct dw_pcie_rp *pp)