sonic-buildimage/platform/broadcom/saibcm-modules/systems/bde/shared/shbde_iproc.c
2019-05-03 11:29:31 +00:00

481 lines
14 KiB
C

/*
* Copyright 2017 Broadcom
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation (the "GPL").
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 (GPLv2) for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 (GPLv2) along with this source code.
*/
/*
* $Id: $
* $Copyright: (c) 2014 Broadcom Corp.
* All Rights Reserved.$
*
*/
#include <shbde_iproc.h>
#include <shbde_mdio.h>
#include <shbde_pci.h>
/* PAXB register offsets within PCI BAR0 window */
#define BAR0_PAXB_ENDIANESS 0x2030
#define BAR0_PAXB_PCIE_EP_AXI_CONFIG 0x2104
#define BAR0_PAXB_CONFIG_IND_ADDR 0x2120
#define BAR0_PAXB_CONFIG_IND_DATA 0x2124
#define BAR0_PAXB_IMAP0_0 (0x2c00)
#define BAR0_PAXB_IMAP0_1 (0x2c04)
#define BAR0_PAXB_IMAP0_2 (0x2c08)
#define BAR0_PAXB_IMAP0_7 (0x2c1c)
#define BAR0_PAXB_OARR_FUNC0_MSI_PAGE 0x2d34
#define BAR0_PAXB_OARR_2 0x2d60
#define BAR0_PAXB_OARR_2_UPPER 0x2d64
#define BAR0_DMU_PCU_PCIE_SLAVE_RESET_MODE 0x7024
#define PAXB_0_FUNC0_IMAP1_3 0x2d88
/* Force byte pointer for offset adjustments */
#define ROFFS(_ptr, _offset) ((unsigned char*)(_ptr) + (_offset))
#define PAXB_CONFIG_IND_ADDRr_PROTOCOL_LAYERf_SHFT 11
#define PAXB_CONFIG_IND_ADDRr_PROTOCOL_LAYERf_MASK 0x3
#define PAXB_CONFIG_IND_ADDRr_ADDRESSf_SHFT 0
#define PAXB_CONFIG_IND_ADDRr_ADDRESSf_MASK 0x7ff
#define PAXB_0_FUNC0_IMAP1_3_ADDR_SHIFT 20
/* Register value set/get by field */
#define REG_FIELD_SET(_r, _f, _r_val, _f_val) \
_r_val = ((_r_val) & ~(_r##_##_f##_MASK << _r##_##_f##_SHFT)) | \
(((_f_val) & _r##_##_f##_MASK) << _r##_##_f##_SHFT)
#define REG_FIELD_GET(_r, _f, _r_val) \
(((_r_val) >> _r##_##_f##_SHFT) & _r##_##_f##_MASK)
/* PCIe capabilities definition */
#ifndef PCI_EXP_LNKSTA
#define PCI_EXP_LNKSTA 0x12
#endif
/* Current Link Speed 5.0GT/s */
#ifndef PCI_EXP_LNKSTA_CLS_5_0GB
#define PCI_EXP_LNKSTA_CLS_5_0GB 2
#endif
#ifndef PCI_EXP_LNKSTA2
#define PCI_EXP_LNKSTA2 0x32
#endif
/* Current Deemphasis Level -3.5 dB */
#ifndef PCI_EXP_LNKSTA2_CDL_3_5DB
#define PCI_EXP_LNKSTA2_CDL_3_5DB 0x1
#endif
static unsigned int
iproc32_read(shbde_hal_t *shbde, void *addr)
{
if (!shbde || !shbde->io32_read) {
return 0;
}
return shbde->io32_read(addr);
}
static void
iproc32_write(shbde_hal_t *shbde, void *addr, unsigned int data)
{
if (!shbde || !shbde->io32_write) {
return;
}
shbde->io32_write(addr, data);
}
static void
wait_usec(shbde_hal_t *shbde, int usec)
{
if (shbde && shbde->usleep) {
shbde->usleep(usec);
} else {
int idx;
volatile int count;
for (idx = 0; idx < usec; idx++) {
for (count = 0; count < 100; count++);
}
}
}
/*
* Function:
* shbde_iproc_config_init
* Purpose:
* Initialize iProc configuration parameters
* Parameters:
* icfg - pointer to empty iProc configuration structure
* Returns:
* -1 if error, otherwise 0
*/
int
shbde_iproc_config_init(shbde_iproc_config_t *icfg,
unsigned int dev_id, unsigned int dev_rev)
{
if (!icfg) {
return -1;
}
/* Save device ID and revision */
icfg->dev_id = dev_id;
icfg->dev_rev = dev_rev;
/* Check device families first */
switch (icfg->dev_id & 0xfff0) {
case 0x8400: /* Greyhound Lite */
case 0x8410: /* Greyhound */
case 0x8420: /* Bloodhound */
case 0x8450: /* Elkhound */
case 0xb060: /* Ranger2(Greyhound) */
case 0x8360: /* Greyhound 53365 & 53369 */
case 0xb260: /* saber2 */
case 0xb460: /* saber2+ */
case 0xb170: /* Hurricane3-MG */
case 0x8570: /* Greyhound2 */
case 0xb070: /* Greyhound2(emulation) */
case 0x8580: /* Greyhound2(emulation) */
case 0xb230: /* Dagger2 */
icfg->iproc_ver = 7;
icfg->dma_hi_bits = 0x2;
break;
case 0xb560: /* Apache */
case 0xb670: /* MO */
case 0xb760: /* Maverick */
icfg->iproc_ver = 0xB;
break;
case 0xb160: /* Hurricane3 */
case 0x8440: /* Buckhound2 */
case 0x8430: /* Foxhound2 */
case 0x8540: /* Wolfhound2 */
icfg->iproc_ver = 10;
icfg->dma_hi_bits = 0x2;
break;
default:
break;
}
/* Check for exceptions */
switch (icfg->dev_id) {
case 0xb069:
case 0xb068:
icfg->iproc_ver = 0xB; /*Ranger2+ Apache Family */
icfg->dma_hi_bits = 0;
break;
case 0xb168: /* Ranger3+ */
case 0xb169:
icfg->iproc_ver = 0;
icfg->dma_hi_bits = 0;
break;
default:
break;
}
/* Check for PCIe PHY address that needs PCIe preemphasis and
* assign the MDIO base address
*/
switch (icfg->dev_id & 0xfff0) {
case 0xb150: /* Hurricane2 */
case 0x8340: /* Wolfhound */
case 0x8330: /* Foxhound */
case 0x8390: /* Dearhound */
icfg->mdio_base_addr = 0x18032000;
icfg->pcie_phy_addr = 0x2;
break;
case 0xb340: /* Helilx4 */
case 0xb540: /* FireScout */
case 0xb040: /* Spiral, Ranger */
icfg->mdio_base_addr = 0x18032000;
icfg->pcie_phy_addr = 0x5;
icfg->adjust_pcie_preemphasis = 1;
break;
case 0xa450: /* Katana2 */
case 0xb240:
case 0xb450:
icfg->mdio_base_addr = 0x18032000;
icfg->pcie_phy_addr = 0x5;
icfg->adjust_pcie_preemphasis = 1;
break;
default:
break;
}
/* Check for exceptions */
switch (icfg->dev_id) {
default:
break;
}
return 0;
}
/*
* Function:
* shbde_iproc_paxb_init
* Purpose:
* Initialize iProc PCI-AXI bridge for CMIC access
* Parameters:
* shbde - pointer to initialized hardware abstraction module
* iproc_regs - memory mapped iProc registers in PCI BAR
* icfg - iProc configuration parameters
* Returns:
* -1 if error, otherwise 0
*/
int
shbde_iproc_paxb_init(shbde_hal_t *shbde, void *iproc_regs,
shbde_iproc_config_t *icfg)
{
void *reg;
unsigned int data;
int pci_num;
if (!iproc_regs || !icfg) {
return -1;
}
/*
* The following code attempts to auto-detect the correct
* iProc PCI endianess configuration by reading a well-known
* register (the endianess configuration register itself).
* Note that the PCI endianess may be different for different
* big endian host processors.
*/
reg = ROFFS(iproc_regs, BAR0_PAXB_ENDIANESS);
/* Select big endian */
iproc32_write(shbde, reg, 0x01010101);
/* Check if endianess register itself is correct endian */
if (iproc32_read(shbde, reg) != 1) {
/* If not, then assume little endian */
iproc32_write(shbde, reg, 0x0);
}
/* Select which PCI core to use */
pci_num = 0;
reg = ROFFS(iproc_regs, BAR0_PAXB_IMAP0_2);
data = iproc32_read(shbde, reg);
if (data & 0x1000) {
/* PAXB_1 is mapped to sub-window 2 */
pci_num = 1;
}
/* Default DMA mapping if uninitialized */
if (icfg->dma_hi_bits == 0) {
icfg->dma_hi_bits = 0x1;
if (pci_num == 1) {
icfg->dma_hi_bits = 0x2;
}
}
/* Enable iProc DMA to external host memory */
reg = ROFFS(iproc_regs, BAR0_PAXB_PCIE_EP_AXI_CONFIG);
iproc32_write(shbde, reg, 0x0);
if(icfg->cmic_ver < 4) { /* Non-CMICX */
reg = ROFFS(iproc_regs, BAR0_PAXB_OARR_2);
iproc32_write(shbde, reg, 0x1);
reg = ROFFS(iproc_regs, BAR0_PAXB_OARR_2_UPPER);
iproc32_write(shbde, reg, icfg->dma_hi_bits);
/* Configure MSI interrupt page */
if (icfg->use_msi) {
reg = ROFFS(iproc_regs, BAR0_PAXB_OARR_FUNC0_MSI_PAGE);
data = iproc32_read(shbde, reg);
iproc32_write(shbde, reg, data | 0x1);
}
}
/* Configure MSIX interrupt page, only need for iproc ver == 0x10 */
if ((icfg->use_msi == 2) && (icfg->iproc_ver == 0x10)) {
unsigned int mask = (0x1 << PAXB_0_FUNC0_IMAP1_3_ADDR_SHIFT) - 1;
reg = ROFFS(iproc_regs, PAXB_0_FUNC0_IMAP1_3);
data = iproc32_read(shbde, reg);
data &= mask;
data |= 0x410 << PAXB_0_FUNC0_IMAP1_3_ADDR_SHIFT;
iproc32_write(shbde, reg, data);
}
return pci_num;
}
/*
* Function:
* shbde_iproc_pci_read
* Purpose:
* Read iProc register through PCI BAR 0
* Parameters:
* shbde - pointer to initialized hardware abstraction module
* iproc_regs - memory mapped iProc registers in PCI BAR
* addr - iProc register address in AXI memory space
* Returns:
* Register value
*/
unsigned int
shbde_iproc_pci_read(shbde_hal_t *shbde, void *iproc_regs,
unsigned int addr)
{
unsigned int subwin_base;
void *reg;
shbde_iproc_config_t *icfg = &shbde->icfg;
if (!iproc_regs) {
return -1;
}
/* Sub-window size is 0x1000 (4K) */
subwin_base = (addr & ~0xfff);
if((icfg->cmic_ver >= 4) &&
((subwin_base == 0x10231000) || (subwin_base == 0x18013000))) {
/* Route the INTC block access through IMAP0_6 */
reg = ROFFS(iproc_regs, 0x6000 + (addr & 0xfff));
} else {
/* Update base address for sub-window 7 */
subwin_base |= 1; /* Valid bit */
reg = ROFFS(iproc_regs, BAR0_PAXB_IMAP0_7);
iproc32_write(shbde, reg, subwin_base);
/* Read it to make sure the write actually goes through */
subwin_base = iproc32_read(shbde, reg);
/* Read register through sub-window 7 */
reg = ROFFS(iproc_regs, 0x7000 + (addr & 0xfff));
}
return iproc32_read(shbde, reg);
}
/*
* Function:
* shbde_iproc_pci_write
* Purpose:
* Write iProc register through PCI BAR 0
* Parameters:
* shbde - pointer to initialized hardware abstraction module
* iproc_regs - memory mapped iProc registers in PCI BAR
* addr - iProc register address in AXI memory space
* data - data to write to iProc register
* Returns:
* Register value
*/
void
shbde_iproc_pci_write(shbde_hal_t *shbde, void *iproc_regs,
unsigned int addr, unsigned int data)
{
unsigned int subwin_base;
void *reg;
shbde_iproc_config_t *icfg = &shbde->icfg;
if (!iproc_regs) {
return;
}
/* Sub-window size is 0x1000 (4K) */
subwin_base = (addr & ~0xfff);
if((icfg->cmic_ver >= 4) &&
((subwin_base == 0x10231000) || (subwin_base == 0x18013000))) {
/* Route the INTC block access through IMAP0_6 */
reg = ROFFS(iproc_regs, 0x6000 + (addr & 0xfff));
} else {
/* Update base address for sub-window 7 */
subwin_base |= 1; /* Valid bit */
reg = ROFFS(iproc_regs, BAR0_PAXB_IMAP0_7);
iproc32_write(shbde, reg, subwin_base);
/* Read it to make sure the write actually goes through */
subwin_base = iproc32_read(shbde, reg);
/* Read register through sub-window 7 */
reg = ROFFS(iproc_regs, 0x7000 + (addr & 0xfff));
}
iproc32_write(shbde, reg, data);
}
int
shbde_iproc_pcie_preemphasis_set(shbde_hal_t *shbde, void *iproc_regs,
shbde_iproc_config_t *icfg, void *pci_dev)
{
shbde_mdio_ctrl_t mdio_ctrl, *smc = &mdio_ctrl;
unsigned int phy_addr, data;
void *reg;
unsigned int pcie_cap_base;
unsigned short link_stat, link_stat2;
if (!icfg) {
return -1;
}
/* PHY address for PCIe link */
phy_addr = icfg->pcie_phy_addr;
if (phy_addr == 0 || icfg->mdio_base_addr == 0) {
return 0;
}
/* Initialize MDIO control */
smc->shbde = shbde;
smc->regs = iproc_regs;
smc->base_addr = icfg->mdio_base_addr;
smc->io32_read = shbde_iproc_pci_read;
smc->io32_write = shbde_iproc_pci_write;
shbde_iproc_mdio_init(smc);
/* PCIe SerDes Gen1/Gen2 CDR Track Bandwidth Adjustment
* for Better Jitter Tolerance
*/
shbde_iproc_mdio_write(smc, phy_addr, 0x1f, 0x8630);
shbde_iproc_mdio_write(smc, phy_addr, 0x13, 0x190);
shbde_iproc_mdio_write(smc, phy_addr, 0x19, 0x191);
if (!icfg->adjust_pcie_preemphasis) {
return 0;
}
/* Check to see if the PCIe SerDes deemphasis needs to be changed
* based on the advertisement from the root complex
*/
/* Find PCIe capability base */
if (!shbde || !shbde->pcic16_read || !pci_dev) {
return -1;
}
pcie_cap_base = shbde_pci_pcie_cap(shbde, pci_dev);
if (pcie_cap_base) {
link_stat = shbde->pcic16_read(pci_dev,
pcie_cap_base + PCI_EXP_LNKSTA);
link_stat2 = shbde->pcic16_read(pci_dev,
pcie_cap_base + PCI_EXP_LNKSTA2);
if (((link_stat & 0xf) == PCI_EXP_LNKSTA_CLS_5_0GB) &&
(link_stat2 & PCI_EXP_LNKSTA2_CDL_3_5DB)) {
/* Device is operating at Gen2 speeds and RC requested -3.5dB */
/* Change the transmitter setting */
shbde_iproc_mdio_write(smc, phy_addr, 0x1f, 0x8610);
shbde_iproc_mdio_read(smc, phy_addr, 0x17, &data);
data &= ~0xf00;
data |= 0x700;
shbde_iproc_mdio_write(smc, phy_addr, 0x17, data);
/* Force the PCIe link to retrain */
data = 0;
REG_FIELD_SET(PAXB_CONFIG_IND_ADDRr, PROTOCOL_LAYERf, data, 0x2);
REG_FIELD_SET(PAXB_CONFIG_IND_ADDRr, ADDRESSf, data, 0x4);
reg = ROFFS(iproc_regs, BAR0_PAXB_CONFIG_IND_ADDR);
iproc32_write(shbde, reg, data);
reg = ROFFS(iproc_regs, BAR0_PAXB_CONFIG_IND_DATA);
data = iproc32_read(shbde, reg);
data &= ~0x4000;
iproc32_write(shbde, reg, data);
data |= 0x4000;
iproc32_write(shbde, reg, data);
data &= ~0x4000;
iproc32_write(shbde, reg, data);
/* Wait a short while for the retraining to complete */
wait_usec(shbde, 1000);
}
}
return 0;
}