Rockchip platforms provide a 64x4 bytes debug FIFO to trace the
LTSSM history. Any LTSSM change will be recorded. It's useful
for debug purpose, for example link failure, etc.

Signed-off-by: Shawn Lin <[email protected]>
---

Changes in v4:
- skip trace if pci_ltssm_tp_enabled() is false.(Steven)
- wrap into 80 columns(Bjorn)

Changes in v3:
- reorder variables(Mani)
- rename loop to i; rename en to enable(Mani)
- use FIELD_GET(Mani)
- add comment about how the FIFO works(Mani)

Changes in v2:
- use tracepoint

 drivers/pci/controller/dwc/pcie-dw-rockchip.c | 111 ++++++++++++++++++++++++++
 1 file changed, 111 insertions(+)

diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c 
b/drivers/pci/controller/dwc/pcie-dw-rockchip.c
index 0fd7bb9..135326c 100644
--- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c
+++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c
@@ -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"
@@ -73,6 +75,20 @@
 #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 (!pci_ltssm_tp_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)
-- 
2.7.4


Reply via email to