summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-mvebu/serdes/a38x/ctrl_pex.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-mvebu/serdes/a38x/ctrl_pex.c')
-rw-r--r--arch/arm/mach-mvebu/serdes/a38x/ctrl_pex.c347
1 files changed, 347 insertions, 0 deletions
diff --git a/arch/arm/mach-mvebu/serdes/a38x/ctrl_pex.c b/arch/arm/mach-mvebu/serdes/a38x/ctrl_pex.c
new file mode 100644
index 0000000000..5f223f9b56
--- /dev/null
+++ b/arch/arm/mach-mvebu/serdes/a38x/ctrl_pex.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) Marvell International Ltd. and its affiliates
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <common.h>
+#include <spl.h>
+#include <asm/io.h>
+#include <asm/arch/cpu.h>
+#include <asm/arch/soc.h>
+
+#include "ctrl_pex.h"
+#include "sys_env_lib.h"
+
+int hws_pex_config(struct serdes_map *serdes_map)
+{
+ u32 pex_idx, tmp, next_busno, first_busno, temp_pex_reg,
+ temp_reg, addr, dev_id, ctrl_mode;
+ enum serdes_type serdes_type;
+ u32 idx, max_lane_num;
+
+ DEBUG_INIT_FULL_S("\n### hws_pex_config ###\n");
+
+ max_lane_num = hws_serdes_get_max_lane();
+ for (idx = 0; idx < max_lane_num; idx++) {
+ serdes_type = serdes_map[idx].serdes_type;
+ /* configuration for PEX only */
+ if ((serdes_type != PEX0) && (serdes_type != PEX1) &&
+ (serdes_type != PEX2) && (serdes_type != PEX3))
+ continue;
+
+ if ((serdes_type != PEX0) &&
+ ((serdes_map[idx].serdes_mode == PEX_ROOT_COMPLEX_X4) ||
+ (serdes_map[idx].serdes_mode == PEX_END_POINT_X4))) {
+ /* for PEX by4 - relevant for the first port only */
+ continue;
+ }
+
+ pex_idx = serdes_type - PEX0;
+ tmp = reg_read(PEX_CAPABILITIES_REG(pex_idx));
+ tmp &= ~(0xf << 20);
+ tmp |= (0x4 << 20);
+ reg_write(PEX_CAPABILITIES_REG(pex_idx), tmp);
+ }
+
+ tmp = reg_read(SOC_CTRL_REG);
+ tmp &= ~0x03;
+
+ for (idx = 0; idx < max_lane_num; idx++) {
+ serdes_type = serdes_map[idx].serdes_type;
+ if ((serdes_type != PEX0) &&
+ ((serdes_map[idx].serdes_mode == PEX_ROOT_COMPLEX_X4) ||
+ (serdes_map[idx].serdes_mode == PEX_END_POINT_X4))) {
+ /* for PEX by4 - relevant for the first port only */
+ continue;
+ }
+
+ switch (serdes_type) {
+ case PEX0:
+ tmp |= 0x1 << PCIE0_ENABLE_OFFS;
+ break;
+ case PEX1:
+ tmp |= 0x1 << PCIE1_ENABLE_OFFS;
+ break;
+ case PEX2:
+ tmp |= 0x1 << PCIE2_ENABLE_OFFS;
+ break;
+ case PEX3:
+ tmp |= 0x1 << PCIE3_ENABLE_OFFS;
+ break;
+ default:
+ break;
+ }
+ }
+
+ reg_write(SOC_CTRL_REG, tmp);
+
+ /* Support gen1/gen2 */
+ DEBUG_INIT_FULL_S("Support gen1/gen2\n");
+ next_busno = 0;
+ mdelay(150);
+
+ for (idx = 0; idx < max_lane_num; idx++) {
+ serdes_type = serdes_map[idx].serdes_type;
+ DEBUG_INIT_FULL_S(" serdes_type=0x");
+ DEBUG_INIT_FULL_D(serdes_type, 8);
+ DEBUG_INIT_FULL_S("\n");
+ DEBUG_INIT_FULL_S(" idx=0x");
+ DEBUG_INIT_FULL_D(idx, 8);
+ DEBUG_INIT_FULL_S("\n");
+
+ /* Configuration for PEX only */
+ if ((serdes_type != PEX0) && (serdes_type != PEX1) &&
+ (serdes_type != PEX2) && (serdes_type != PEX3))
+ continue;
+
+ if ((serdes_type != PEX0) &&
+ ((serdes_map[idx].serdes_mode == PEX_ROOT_COMPLEX_X4) ||
+ (serdes_map[idx].serdes_mode == PEX_END_POINT_X4))) {
+ /* for PEX by4 - relevant for the first port only */
+ continue;
+ }
+
+ pex_idx = serdes_type - PEX0;
+ tmp = reg_read(PEX_DBG_STATUS_REG(pex_idx));
+
+ first_busno = next_busno;
+ if ((tmp & 0x7f) != 0x7e) {
+ DEBUG_INIT_S("PCIe, Idx ");
+ DEBUG_INIT_D(pex_idx, 1);
+ DEBUG_INIT_S(": detected no link\n");
+ continue;
+ }
+
+ next_busno++;
+ temp_pex_reg = reg_read((PEX_CFG_DIRECT_ACCESS
+ (pex_idx, PEX_LINK_CAPABILITY_REG)));
+ temp_pex_reg &= 0xf;
+ if (temp_pex_reg != 0x2)
+ continue;
+
+ temp_reg = (reg_read(PEX_CFG_DIRECT_ACCESS(
+ pex_idx,
+ PEX_LINK_CTRL_STAT_REG)) &
+ 0xf0000) >> 16;
+
+ /* Check if the link established is GEN1 */
+ DEBUG_INIT_FULL_S
+ ("Checking if the link established is gen1\n");
+ if (temp_reg != 0x1)
+ continue;
+
+ pex_local_bus_num_set(pex_idx, first_busno);
+ pex_local_dev_num_set(pex_idx, 1);
+ DEBUG_INIT_FULL_S("PCIe, Idx ");
+ DEBUG_INIT_FULL_D(pex_idx, 1);
+
+ DEBUG_INIT_S(":** Link is Gen1, check the EP capability\n");
+ /* link is Gen1, check the EP capability */
+ addr = pex_config_read(pex_idx, first_busno, 0, 0, 0x34) & 0xff;
+ DEBUG_INIT_FULL_C("pex_config_read: return addr=0x%x", addr, 4);
+ if (addr == 0xff) {
+ DEBUG_INIT_FULL_C
+ ("pex_config_read: return 0xff -->PCIe (%d): Detected No Link.",
+ pex_idx, 1);
+ continue;
+ }
+
+ while ((pex_config_read(pex_idx, first_busno, 0, 0, addr)
+ & 0xff) != 0x10) {
+ addr = (pex_config_read(pex_idx, first_busno, 0,
+ 0, addr) & 0xff00) >> 8;
+ }
+
+ /* Check for Gen2 and above */
+ if ((pex_config_read(pex_idx, first_busno, 0, 0,
+ addr + 0xc) & 0xf) < 0x2) {
+ DEBUG_INIT_S("PCIe, Idx ");
+ DEBUG_INIT_D(pex_idx, 1);
+ DEBUG_INIT_S(": remains Gen1\n");
+ continue;
+ }
+
+ tmp = reg_read(PEX_LINK_CTRL_STATUS2_REG(pex_idx));
+ DEBUG_RD_REG(PEX_LINK_CTRL_STATUS2_REG(pex_idx), tmp);
+ tmp &= ~(BIT(0) | BIT(1));
+ tmp |= BIT(1);
+ tmp |= BIT(6); /* Select Deemphasize (-3.5d_b) */
+ reg_write(PEX_LINK_CTRL_STATUS2_REG(pex_idx), tmp);
+ DEBUG_WR_REG(PEX_LINK_CTRL_STATUS2_REG(pex_idx), tmp);
+
+ tmp = reg_read(PEX_CTRL_REG(pex_idx));
+ DEBUG_RD_REG(PEX_CTRL_REG(pex_idx), tmp);
+ tmp |= BIT(10);
+ reg_write(PEX_CTRL_REG(pex_idx), tmp);
+ DEBUG_WR_REG(PEX_CTRL_REG(pex_idx), tmp);
+
+ /*
+ * We need to wait 10ms before reading the PEX_DBG_STATUS_REG
+ * in order not to read the status of the former state
+ */
+ mdelay(10);
+
+ DEBUG_INIT_S("PCIe, Idx ");
+ DEBUG_INIT_D(pex_idx, 1);
+ DEBUG_INIT_S
+ (": Link upgraded to Gen2 based on client cpabilities\n");
+ }
+
+ /* Update pex DEVICE ID */
+ ctrl_mode = sys_env_model_get();
+
+ for (idx = 0; idx < max_lane_num; idx++) {
+ serdes_type = serdes_map[idx].serdes_type;
+ /* configuration for PEX only */
+ if ((serdes_type != PEX0) && (serdes_type != PEX1) &&
+ (serdes_type != PEX2) && (serdes_type != PEX3))
+ continue;
+
+ if ((serdes_type != PEX0) &&
+ ((serdes_map[idx].serdes_mode == PEX_ROOT_COMPLEX_X4) ||
+ (serdes_map[idx].serdes_mode == PEX_END_POINT_X4))) {
+ /* for PEX by4 - relevant for the first port only */
+ continue;
+ }
+
+ pex_idx = serdes_type - PEX0;
+ dev_id = reg_read(PEX_CFG_DIRECT_ACCESS
+ (pex_idx, PEX_DEVICE_AND_VENDOR_ID));
+ dev_id &= 0xffff;
+ dev_id |= ((ctrl_mode << 16) & 0xffff0000);
+ reg_write(PEX_CFG_DIRECT_ACCESS
+ (pex_idx, PEX_DEVICE_AND_VENDOR_ID), dev_id);
+ }
+ DEBUG_INIT_FULL_C("Update PEX Device ID ", ctrl_mode, 4);
+
+ return MV_OK;
+}
+
+int pex_local_bus_num_set(u32 pex_if, u32 bus_num)
+{
+ u32 pex_status;
+
+ DEBUG_INIT_FULL_S("\n### pex_local_bus_num_set ###\n");
+
+ if (bus_num >= MAX_PEX_BUSSES) {
+ DEBUG_INIT_C("pex_local_bus_num_set: Illegal bus number %d\n",
+ bus_num, 4);
+ return MV_BAD_PARAM;
+ }
+
+ pex_status = reg_read(PEX_STATUS_REG(pex_if));
+ pex_status &= ~PXSR_PEX_BUS_NUM_MASK;
+ pex_status |=
+ (bus_num << PXSR_PEX_BUS_NUM_OFFS) & PXSR_PEX_BUS_NUM_MASK;
+ reg_write(PEX_STATUS_REG(pex_if), pex_status);
+
+ return MV_OK;
+}
+
+int pex_local_dev_num_set(u32 pex_if, u32 dev_num)
+{
+ u32 pex_status;
+
+ DEBUG_INIT_FULL_S("\n### pex_local_dev_num_set ###\n");
+
+ pex_status = reg_read(PEX_STATUS_REG(pex_if));
+ pex_status &= ~PXSR_PEX_DEV_NUM_MASK;
+ pex_status |=
+ (dev_num << PXSR_PEX_DEV_NUM_OFFS) & PXSR_PEX_DEV_NUM_MASK;
+ reg_write(PEX_STATUS_REG(pex_if), pex_status);
+
+ return MV_OK;
+}
+
+/*
+ * pex_config_read - Read from configuration space
+ *
+ * DESCRIPTION:
+ * This function performs a 32 bit read from PEX configuration space.
+ * It supports both type 0 and type 1 of Configuration Transactions
+ * (local and over bridge). In order to read from local bus segment, use
+ * bus number retrieved from pex_local_bus_num_get(). Other bus numbers
+ * will result configuration transaction of type 1 (over bridge).
+ *
+ * INPUT:
+ * pex_if - PEX interface number.
+ * bus - PEX segment bus number.
+ * dev - PEX device number.
+ * func - Function number.
+ * reg_offs - Register offset.
+ *
+ * OUTPUT:
+ * None.
+ *
+ * RETURN:
+ * 32bit register data, 0xffffffff on error
+ */
+u32 pex_config_read(u32 pex_if, u32 bus, u32 dev, u32 func, u32 reg_off)
+{
+ u32 pex_data = 0;
+ u32 local_dev, local_bus;
+ u32 pex_status;
+
+ pex_status = reg_read(PEX_STATUS_REG(pex_if));
+ local_dev =
+ ((pex_status & PXSR_PEX_DEV_NUM_MASK) >> PXSR_PEX_DEV_NUM_OFFS);
+ local_bus =
+ ((pex_status & PXSR_PEX_BUS_NUM_MASK) >> PXSR_PEX_BUS_NUM_OFFS);
+
+ /*
+ * In PCI Express we have only one device number
+ * and this number is the first number we encounter
+ * else that the local_dev
+ * spec pex define return on config read/write on any device
+ */
+ if (bus == local_bus) {
+ if (local_dev == 0) {
+ /*
+ * if local dev is 0 then the first number we encounter
+ * after 0 is 1
+ */
+ if ((dev != 1) && (dev != local_dev))
+ return MV_ERROR;
+ } else {
+ /*
+ * if local dev is not 0 then the first number we
+ * encounter is 0
+ */
+ if ((dev != 0) && (dev != local_dev))
+ return MV_ERROR;
+ }
+ }
+
+ /* Creating PEX address to be passed */
+ pex_data = (bus << PXCAR_BUS_NUM_OFFS);
+ pex_data |= (dev << PXCAR_DEVICE_NUM_OFFS);
+ pex_data |= (func << PXCAR_FUNC_NUM_OFFS);
+ /* Legacy register space */
+ pex_data |= (reg_off & PXCAR_REG_NUM_MASK);
+ /* Extended register space */
+ pex_data |= (((reg_off & PXCAR_REAL_EXT_REG_NUM_MASK) >>
+ PXCAR_REAL_EXT_REG_NUM_OFFS) << PXCAR_EXT_REG_NUM_OFFS);
+ pex_data |= PXCAR_CONFIG_EN;
+
+ /* Write the address to the PEX configuration address register */
+ reg_write(PEX_CFG_ADDR_REG(pex_if), pex_data);
+
+ /*
+ * In order to let the PEX controller absorbed the address
+ * of the read transaction we perform a validity check that
+ * the address was written
+ */
+ if (pex_data != reg_read(PEX_CFG_ADDR_REG(pex_if)))
+ return MV_ERROR;
+
+ /* Cleaning Master Abort */
+ reg_bit_set(PEX_CFG_DIRECT_ACCESS(pex_if, PEX_STATUS_AND_COMMAND),
+ PXSAC_MABORT);
+ /* Read the Data returned in the PEX Data register */
+ pex_data = reg_read(PEX_CFG_DATA_REG(pex_if));
+
+ DEBUG_INIT_FULL_C(" --> ", pex_data, 4);
+
+ return pex_data;
+}
OpenPOWER on IntegriCloud