Introduce new, common PCIe setup code for QC/A WiSoCs
authorPiotr Dymacz <pepe2k@gmail.com>
Tue, 14 Jun 2016 19:16:52 +0000 (21:16 +0200)
committerPiotr Dymacz <pepe2k@gmail.com>
Tue, 14 Jun 2016 19:16:52 +0000 (21:16 +0200)
New code setups PCIe PLL and resets PCIe controller.
For now, it was tested only on real hardware based
on QCA9531 and AR9344.

For information purposes, include also new line in
board info message, with vendor and device ID of
device/s connected on PCIe bus/es.

u-boot/board/ar7240/common/common.c
u-boot/cpu/mips/ar7240/Makefile
u-boot/cpu/mips/ar7240/qca_pci.c [new file with mode: 0644]
u-boot/include/soc/qca_soc_common.h

index 7b71871420318eecfa2f2102178ba04a2d7c0e31..3eae06093810b4470bd3e35975e392435d4c54ac 100644 (file)
@@ -86,6 +86,9 @@ void qca_soc_name_rev(char *buf)
 void print_board_info(void)
 {
        u32 ahb_clk, cpu_clk, ddr_clk, spi_clk, ref_clk;
+#if defined(CONFIG_PCI)
+       u32 did, vid;
+#endif
        u32 bank;
        bd_t *bd = gd->bd;
        char buffer[24];
@@ -149,6 +152,46 @@ void print_board_info(void)
                puts("\n");
        }
 
+       /* PCIE device/s info */
+#if defined(CONFIG_PCI)
+       printf("%" ALIGN_SIZE "s ", "PCIe:");
+
+       #if (SOC_TYPE & QCA_AR934X_SOC) |\
+               (SOC_TYPE & QCA_QCA955X_SOC)
+       if (!qca_pcie0_in_ep_mode()) {
+               if (qca_pcie_dev_info(0, &vid, &did)) {
+                       printf("%04X:%04X", vid, did);
+               } else {
+                       puts("no device");
+               }
+       } else {
+               puts("EP mode");
+       }
+       #elif (SOC_TYPE & QCA_QCA953X_SOC)
+       if (qca_pcie_dev_info(0, &vid, &did)) {
+               printf("%04X:%04X", vid, did);
+       } else {
+               puts("no device");
+       }
+       #endif
+
+       #if (SOC_TYPE & QCA_QCA956X_SOC)
+       if (qca_pcie_dev_info(1, &vid, &did)) {
+               printf("%04X:%04X", vid, did);
+       } else {
+               puts("no device");
+       }
+       #elif (SOC_TYPE & QCA_QCA955X_SOC)
+       if (qca_pcie_dev_info(1, &vid, &did)) {
+               printf(", %04X:%04X", vid, did);
+       } else {
+               puts(", no device");
+       }
+       #endif
+
+       puts("\n");
+#endif
+
        /* MAC address */
        printf("%" ALIGN_SIZE "s %02X:%02X:%02X:%02X:%02X:%02X", "MAC:",
                bd->bi_enetaddr[0],bd->bi_enetaddr[1], bd->bi_enetaddr[2],
index c1e4feb2a1a532bdf60229471a9333ea7263fa62..91eb4af31f00754f76e3e6afd211066d010ed745 100644 (file)
@@ -10,6 +10,7 @@ OBJS  += qca_common.o
 OBJS   += qca_clocks.o
 OBJS   += qca_sf.o
 OBJS   += qca_dram.o
+OBJS   += qca_pci.o
 SOBJS  += qca_gpio_init.o
 
 ifeq ($(BOARD), ap121)
diff --git a/u-boot/cpu/mips/ar7240/qca_pci.c b/u-boot/cpu/mips/ar7240/qca_pci.c
new file mode 100644 (file)
index 0000000..8d2e64a
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * Qualcomm/Atheros WiSoCs PCIE RC related
+ * functions for WiSoC families:
+ * - Atheros AR934x
+ * - Qualcomm/Atheros QCA953x
+ * - Qualcomm/Atheros QCA955x
+ * - Qualcomm/Atheros QCA956x
+ *
+ * Copyright (C) 2016 Piotr Dymacz <piotr@dymacz.pl>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <config.h>
+#include <common.h>
+#include <asm/addrspace.h>
+#include <soc/qca_soc_common.h>
+
+#if defined(CONFIG_PCI)
+
+/*
+ * Returns 1 if PCIE0 is in EP mode
+ */
+#if (SOC_TYPE & QCA_AR934X_SOC) |\
+       (SOC_TYPE & QCA_QCA955X_SOC)
+u32 qca_pcie0_in_ep_mode(void)
+{
+       if (!(qca_soc_reg_read(QCA_RST_BOOTSTRAP_REG)
+                 & QCA_RST_BOOTSTRAP_PCIE_RC_MODE_MASK))
+               return 1;
+
+       return 0;
+}
+#endif
+
+/*
+ * Returns 1 if link is up and provides
+ * vendor and device IDs of endpoint
+ */
+u32 qca_pcie_dev_info(u32 rc_num, u32 *vid, u32 *did)
+{
+       u32 reg;
+
+       if (rc_num == 0) {
+               reg = qca_soc_reg_read(QCA_PCIE_RC0_CTRL_RST_REG);
+       } else {
+               reg = qca_soc_reg_read(QCA_PCIE_RC1_CTRL_RST_REG);
+       }
+
+       if (reg & QCA_PCIE_RCX_CTRL_RST_LINK_UP_MASK) {
+               if (rc_num == 0) {
+                       reg = qca_soc_reg_read(QCA_PCIE_RC0_SLAVE_CFG_BASE_REG);
+               } else {
+                       reg = qca_soc_reg_read(QCA_PCIE_RC1_SLAVE_CFG_BASE_REG);
+               }
+
+               /* Vendor (lower 16-bit) and device (higher 16-bit) ID */
+               *vid = reg & BITS(0, 16);
+               *did = (reg >> 16) & BITS(0, 16);
+
+               return 1;
+       }
+
+       return 0;
+}
+
+/*
+ * This allows to define custom NFRAC range for SSC clocking
+ */
+#ifndef CONFIG_QCA_PCIE_PLL_NFRAC_MAX_VAL
+       #define CONFIG_QCA_PCIE_PLL_NFRAC_MAX_VAL       0x3FFF
+#endif
+
+#ifndef CONFIG_QCA_PCIE_PLL_NFRAC_MIN_VAL
+       #define CONFIG_QCA_PCIE_PLL_NFRAC_MIN_VAL       0x3F84
+#endif
+
+/*
+ * Setups PCIE PLL
+ *
+ * Recommended target clock should be ~100 MHz
+ *
+ * PCIE PLL clock formula:
+ *            REF_CLK    NFRAC            1
+ * PCIE_PLL = ------- x (----- + NINT) x ---
+ *            REF_DIV    2^14            DIV
+ *
+ * For  AR934x/QCA955x, default DIV is 8
+ * For QCA953x/QCA956x, default DIV is 6
+ *
+ * TODO: confirm formula for QCA953x/QCA956x
+ *       because datasheets have different
+ *       and strange formulas, especially
+ *       for QCA956x
+ *
+ * TODO: PLL tune/test (SQSUM_DVC)?
+ */
+static void qca_pcie_pll_init(void)
+{
+       u32 nfrac_max, nfrac_min, nint, reg;
+       u32 ref_div = 1;
+
+#if (SOC_TYPE & QCA_QCA953X_SOC) |\
+       (SOC_TYPE & QCA_QCA956X_SOC)
+       if (qca_xtal_is_40mhz()) {
+               nint = 29;
+               ref_div = 2;
+       } else {
+               nint = 23;
+       }
+#else
+       if (qca_xtal_is_40mhz()) {
+               nint = 39;
+               ref_div = 2;
+       } else {
+               nint = 31;
+       }
+#endif
+
+       /* Power down and bypass PLL, setup ref_div */
+       reg = QCA_PLL_PCIE_PLL_CFG_PLLPWD_MASK |
+                 QCA_PLL_PCIE_PLL_CFG_BYPASS_MASK |
+                 (ref_div << QCA_PLL_PCIE_PLL_CFG_REFDIV_SHIFT);
+
+       qca_soc_reg_write(QCA_PLL_PCIE_PLL_CFG_REG, reg);
+       udelay(100);
+
+       /*
+        * Use dithering/SSC (spread spectrum clocking)
+        * as in QC/A SDK (for minimize/reduce EMI)
+        */
+       nfrac_max = CONFIG_QCA_PCIE_PLL_NFRAC_MAX_VAL
+                               << QCA_PLL_PCIE_PLL_DITHER_MAX_NFRAC_MAX_SHIFT;
+       nfrac_max = nfrac_max & QCA_PLL_PCIE_PLL_DITHER_MAX_NFRAC_MAX_MASK;
+
+       nfrac_min = CONFIG_QCA_PCIE_PLL_NFRAC_MIN_VAL
+                               << QCA_PLL_PCIE_PLL_DITHER_MIN_NFRAC_MIN_SHIFT;
+       nfrac_min = nfrac_max & QCA_PLL_PCIE_PLL_DITHER_MIN_NFRAC_MIN_MASK;
+
+       reg = QCA_PLL_PCIE_PLL_DITHER_MAX_DITHER_EN_MASK |
+                 QCA_PLL_PCIE_PLL_DITHER_MAX_USE_MAX_MASK |
+                 (nint << QCA_PLL_PCIE_PLL_DITHER_MAX_NINT_MAX_SHIFT) | nfrac_max;
+       qca_soc_reg_write(QCA_PLL_PCIE_PLL_DITHER_MAX_REG, reg);
+
+       reg = (nint << QCA_PLL_PCIE_PLL_DITHER_MIN_NINT_MIN_SHIFT) | nfrac_min;
+       qca_soc_reg_write(QCA_PLL_PCIE_PLL_DITHER_MIN_REG, reg);
+
+       /* Power up PLL ... */
+       qca_soc_reg_read_clear(QCA_PLL_PCIE_PLL_CFG_REG,
+                                                  QCA_PLL_PCIE_PLL_CFG_PLLPWD_MASK);
+       udelay(100);
+
+       /* ... and wait for update complete */
+       do {
+               udelay(10);
+               reg = qca_soc_reg_read(QCA_PLL_PCIE_PLL_CFG_REG);
+       } while (reg & QCA_PLL_PCIE_PLL_CFG_UPDATING_MASK);
+
+       /* Disable PLL bypassing */
+       qca_soc_reg_read_clear(QCA_PLL_PCIE_PLL_CFG_REG,
+                                                  QCA_PLL_PCIE_PLL_CFG_BYPASS_MASK);
+       udelay(100);
+}
+
+#if (SOC_TYPE & QCA_AR934X_SOC)  |\
+       (SOC_TYPE & QCA_QCA953X_SOC) |\
+       (SOC_TYPE & QCA_QCA955X_SOC)
+/*
+ * Takes out PCIE0 RC from reset
+ */
+static void qca_pcie0_rst_di(void)
+{
+       /* Take PCIE PHY and core out of reset */
+       qca_soc_reg_read_clear(QCA_RST_RESET_REG,
+                                                  QCA_RST_RESET_PCIE_RST_MASK |
+                                                  QCA_RST_RESET_PCIE_PHY_RST_MASK);
+       milisecdelay(10);
+
+       /* Enable LTSSM */
+       qca_soc_reg_read_set(QCA_PCIE_RC0_CTRL_APP_REG,
+                                                QCA_PCIE_RCX_CTRL_APP_LTSSM_EN_MASK);
+
+       /* Take EP out of reset (this sets PERSTn high) */
+       qca_soc_reg_write(QCA_PCIE_RC0_CTRL_RST_REG,
+                                         QCA_PCIE_RCX_CTRL_RST_EP_RST_L_MASK);
+       milisecdelay(100);
+}
+
+/*
+ * Resets PCIE0 RC
+ */
+static void qca_pcie0_rst_en(void)
+{
+       /* Reset PCIE PHY and core */
+       qca_soc_reg_read_set(QCA_RST_RESET_REG,
+                                                QCA_RST_RESET_PCIE_RST_MASK |
+                                                QCA_RST_RESET_PCIE_PHY_RST_MASK);
+       milisecdelay(10);
+
+       /* Put EP in reset (this sets PERSTn low) */
+       qca_soc_reg_write(QCA_PCIE_RC0_CTRL_RST_REG, 0x0);
+       milisecdelay(10);
+}
+#endif
+
+#if (SOC_TYPE & QCA_QCA955X_SOC) |\
+       (SOC_TYPE & QCA_QCA956X_SOC)
+/*
+ * Takes out PCIE1 RC from reset
+ */
+static void qca_pcie1_rst_di(void)
+{
+       /* Take PCIE PHY and core out of reset */
+       qca_soc_reg_read_clear(QCA_RST_RESET2_REG,
+                                                  QCA_RST_RESET2_PCIE2_RST_MASK |
+                                                  QCA_RST_RESET2_PCIE2_PHY_RST_MASK);
+       milisecdelay(10);
+
+       /* Enable LTSSM */
+       qca_soc_reg_read_set(QCA_PCIE_RC1_CTRL_APP_REG,
+                                                QCA_PCIE_RCX_CTRL_APP_LTSSM_EN_MASK);
+
+       /* Take EP out of reset (this sets PERSTn high) */
+       qca_soc_reg_write(QCA_PCIE_RC1_CTRL_RST_REG,
+                                         QCA_PCIE_RCX_CTRL_RST_EP_RST_L_MASK);
+       milisecdelay(100);
+}
+
+/*
+ * Resets PCIE1 RC
+ */
+static void qca_pcie1_rst_en(void)
+{
+       /* Reset PCIE PHY and core */
+       qca_soc_reg_read_set(QCA_RST_RESET2_REG,
+                                                QCA_RST_RESET2_PCIE2_RST_MASK |
+                                                QCA_RST_RESET2_PCIE2_PHY_RST_MASK);
+       milisecdelay(10);
+
+       /* Put EP in reset (this sets PERSTn low) */
+       qca_soc_reg_write(QCA_PCIE_RC1_CTRL_RST_REG, 0x0);
+       milisecdelay(10);
+}
+#endif
+
+/*
+ * Initializes PCIE
+ */
+void pci_init(void)
+{
+#if (SOC_TYPE & QCA_AR934X_SOC)
+       /*
+        * AR934x contains one PCIE bus controller
+        * which can work in both modes (RC and EP)
+        */
+       if (qca_pcie0_in_ep_mode()) {
+               return;
+       }
+#endif
+
+       /* Init PCIE PLL (common for both RC) */
+       qca_pcie_pll_init();
+
+       /* Reset PCIE */
+#if (SOC_TYPE & QCA_AR934X_SOC) |\
+       (SOC_TYPE & QCA_QCA953X_SOC)
+       qca_pcie0_rst_en();
+       qca_pcie0_rst_di();
+#elif (SOC_TYPE & QCA_QCA955X_SOC)
+       if (!qca_pcie0_in_ep_mode()) {
+               qca_pcie0_rst_en();
+               qca_pcie0_rst_di();
+       }
+#endif
+
+#if (SOC_TYPE & QCA_QCA955X_SOC) |\
+       (SOC_TYPE & QCA_QCA956X_SOC)
+       qca_pcie1_rst_en();
+       qca_pcie1_rst_di();
+#endif
+}
+
+#endif /* defined(CONFIG_PCI) */
index aa107c85188c99a8d817afb8465b56ce591513e7..922f5c558e65824b3a746ba4c42d5675dfeea3e1 100644 (file)
@@ -1945,6 +1945,8 @@ u32  qca_dram_trcd_lat(void);
 u32  qca_dram_trp_lat(void);
 u32  qca_dram_type(void);
 void qca_full_chip_reset(void);
+u32  qca_pcie_dev_info(u32 rc_num, u32 *vid, u32 *did);
+u32  qca_pcie0_in_ep_mode(void);
 void qca_sf_bulk_erase(u32 bank);
 u32  qca_sf_jedec_id(u32 bank);
 u32  qca_sf_sect_erase(u32 bank, u32 address, u32 sect_size, u8 erase_cmd);