diff options
author | Stefan Roese <sr@denx.de> | 2015-04-20 09:31:27 +0200 |
---|---|---|
committer | Luka Perkov <luka.perkov@sartura.hr> | 2015-07-23 10:38:14 +0200 |
commit | edb470253346f4a882ba9e891c8b102ce388b9cc (patch) | |
tree | 339a9b773b85f6f6780bcc75853838ca488f702d /arch/arm/mach-mvebu/serdes/a38x/ctrl_pex.c | |
parent | 29b103c733f6c17ecf8ee8d66140254788e2bdda (diff) | |
download | talos-obmc-uboot-edb470253346f4a882ba9e891c8b102ce388b9cc.tar.gz talos-obmc-uboot-edb470253346f4a882ba9e891c8b102ce388b9cc.zip |
arm: mvebu: Add Armada 38x SERDES / PHY init code from Marvell bin_hdr
This code is ported from the Marvell bin_hdr code into mainline
SPL U-Boot. It needs to be executed very early so that the devices
connected to the serdes PHY are configured correctly.
Signed-off-by: Stefan Roese <sr@denx.de>
Diffstat (limited to 'arch/arm/mach-mvebu/serdes/a38x/ctrl_pex.c')
-rw-r--r-- | arch/arm/mach-mvebu/serdes/a38x/ctrl_pex.c | 347 |
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; +} |