mirror of https://github.com/torvalds/linux.git
Merge branch 'pci/controller/brcmstb'
- Disable advertising ASPM L0s support correctly (Jim Quinlan) - Add a panic/die handler to print diagnostic info in case PCIe caused an unrecoverable abort (Jim Quinlan) * pci/controller/brcmstb: PCI: brcmstb: Add panic/die handler to driver PCI: brcmstb: Add a way to indicate if PCIe bridge is active PCI: brcmstb: Fix disabling L0s capability
This commit is contained in:
commit
f4620f6216
|
|
@ -14,15 +14,18 @@
|
|||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/irqchip/irq-msi-lib.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/kdebug.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/panic_notifier.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pci-ecam.h>
|
||||
#include <linux/printk.h>
|
||||
|
|
@ -30,7 +33,9 @@
|
|||
#include <linux/reset.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/string_choices.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "../pci.h"
|
||||
|
|
@ -48,7 +53,6 @@
|
|||
|
||||
#define PCIE_RC_CFG_PRIV1_LINK_CAPABILITY 0x04dc
|
||||
#define PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_MAX_LINK_WIDTH_MASK 0x1f0
|
||||
#define PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_ASPM_SUPPORT_MASK 0xc00
|
||||
|
||||
#define PCIE_RC_CFG_PRIV1_ROOT_CAP 0x4f8
|
||||
#define PCIE_RC_CFG_PRIV1_ROOT_CAP_L1SS_MODE_MASK 0xf8
|
||||
|
|
@ -155,8 +159,40 @@
|
|||
#define MSI_INT_MASK_SET 0x10
|
||||
#define MSI_INT_MASK_CLR 0x14
|
||||
|
||||
/* Error report registers */
|
||||
#define PCIE_OUTB_ERR_TREAT 0x6000
|
||||
#define PCIE_OUTB_ERR_TREAT_CONFIG 0x1
|
||||
#define PCIE_OUTB_ERR_TREAT_MEM 0x2
|
||||
#define PCIE_OUTB_ERR_VALID 0x6004
|
||||
#define PCIE_OUTB_ERR_CLEAR 0x6008
|
||||
#define PCIE_OUTB_ERR_ACC_INFO 0x600c
|
||||
#define PCIE_OUTB_ERR_ACC_INFO_CFG_ERR BIT(0)
|
||||
#define PCIE_OUTB_ERR_ACC_INFO_MEM_ERR BIT(1)
|
||||
#define PCIE_OUTB_ERR_ACC_INFO_TYPE_64 BIT(2)
|
||||
#define PCIE_OUTB_ERR_ACC_INFO_DIR_WRITE BIT(4)
|
||||
#define PCIE_OUTB_ERR_ACC_INFO_BYTE_LANES 0xff00
|
||||
#define PCIE_OUTB_ERR_ACC_ADDR 0x6010
|
||||
#define PCIE_OUTB_ERR_ACC_ADDR_BUS 0xff00000
|
||||
#define PCIE_OUTB_ERR_ACC_ADDR_DEV 0xf8000
|
||||
#define PCIE_OUTB_ERR_ACC_ADDR_FUNC 0x7000
|
||||
#define PCIE_OUTB_ERR_ACC_ADDR_REG 0xfff
|
||||
#define PCIE_OUTB_ERR_CFG_CAUSE 0x6014
|
||||
#define PCIE_OUTB_ERR_CFG_CAUSE_TIMEOUT BIT(6)
|
||||
#define PCIE_OUTB_ERR_CFG_CAUSE_ABORT BIT(5)
|
||||
#define PCIE_OUTB_ERR_CFG_CAUSE_UNSUPP_REQ BIT(4)
|
||||
#define PCIE_OUTB_ERR_CFG_CAUSE_ACC_TIMEOUT BIT(2)
|
||||
#define PCIE_OUTB_ERR_CFG_CAUSE_ACC_DISABLED BIT(1)
|
||||
#define PCIE_OUTB_ERR_CFG_CAUSE_ACC_64BIT BIT(0)
|
||||
#define PCIE_OUTB_ERR_MEM_ADDR_LO 0x6018
|
||||
#define PCIE_OUTB_ERR_MEM_ADDR_HI 0x601c
|
||||
#define PCIE_OUTB_ERR_MEM_CAUSE 0x6020
|
||||
#define PCIE_OUTB_ERR_MEM_CAUSE_TIMEOUT BIT(6)
|
||||
#define PCIE_OUTB_ERR_MEM_CAUSE_ABORT BIT(5)
|
||||
#define PCIE_OUTB_ERR_MEM_CAUSE_UNSUPP_REQ BIT(4)
|
||||
#define PCIE_OUTB_ERR_MEM_CAUSE_ACC_DISABLED BIT(1)
|
||||
#define PCIE_OUTB_ERR_MEM_CAUSE_BAD_ADDR BIT(0)
|
||||
|
||||
#define PCIE_RGR1_SW_INIT_1_PERST_MASK 0x1
|
||||
#define PCIE_RGR1_SW_INIT_1_PERST_SHIFT 0x0
|
||||
|
||||
#define RGR1_SW_INIT_1_INIT_GENERIC_MASK 0x2
|
||||
#define RGR1_SW_INIT_1_INIT_GENERIC_SHIFT 0x1
|
||||
|
|
@ -259,6 +295,7 @@ struct pcie_cfg_data {
|
|||
int (*perst_set)(struct brcm_pcie *pcie, u32 val);
|
||||
int (*bridge_sw_init_set)(struct brcm_pcie *pcie, u32 val);
|
||||
int (*post_setup)(struct brcm_pcie *pcie);
|
||||
bool has_err_report;
|
||||
};
|
||||
|
||||
struct subdev_regulators {
|
||||
|
|
@ -303,6 +340,10 @@ struct brcm_pcie {
|
|||
struct subdev_regulators *sr;
|
||||
bool ep_wakeup_capable;
|
||||
const struct pcie_cfg_data *cfg;
|
||||
bool bridge_in_reset;
|
||||
struct notifier_block die_notifier;
|
||||
struct notifier_block panic_notifier;
|
||||
spinlock_t bridge_lock;
|
||||
};
|
||||
|
||||
static inline bool is_bmips(const struct brcm_pcie *pcie)
|
||||
|
|
@ -310,6 +351,24 @@ static inline bool is_bmips(const struct brcm_pcie *pcie)
|
|||
return pcie->cfg->soc_base == BCM7435 || pcie->cfg->soc_base == BCM7425;
|
||||
}
|
||||
|
||||
static int brcm_pcie_bridge_sw_init_set(struct brcm_pcie *pcie, u32 val)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
if (pcie->cfg->has_err_report)
|
||||
spin_lock_irqsave(&pcie->bridge_lock, flags);
|
||||
|
||||
ret = pcie->cfg->bridge_sw_init_set(pcie, val);
|
||||
/* If we fail, assume the bridge is in reset (off) */
|
||||
pcie->bridge_in_reset = ret ? true : val;
|
||||
|
||||
if (pcie->cfg->has_err_report)
|
||||
spin_unlock_irqrestore(&pcie->bridge_lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is to convert the size of the inbound "BAR" region to the
|
||||
* non-linear values of PCIE_X_MISC_RC_BAR[123]_CONFIG_LO.SIZE
|
||||
|
|
@ -1075,13 +1134,13 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie)
|
|||
void __iomem *base = pcie->base;
|
||||
struct pci_host_bridge *bridge;
|
||||
struct resource_entry *entry;
|
||||
u32 tmp, burst, aspm_support, num_lanes, num_lanes_cap;
|
||||
u32 tmp, burst, num_lanes, num_lanes_cap;
|
||||
u8 num_out_wins = 0;
|
||||
int num_inbound_wins = 0;
|
||||
int memc, ret;
|
||||
|
||||
/* Reset the bridge */
|
||||
ret = pcie->cfg->bridge_sw_init_set(pcie, 1);
|
||||
ret = brcm_pcie_bridge_sw_init_set(pcie, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
@ -1097,7 +1156,7 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie)
|
|||
usleep_range(100, 200);
|
||||
|
||||
/* Take the bridge out of reset */
|
||||
ret = pcie->cfg->bridge_sw_init_set(pcie, 0);
|
||||
ret = brcm_pcie_bridge_sw_init_set(pcie, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
@ -1175,12 +1234,9 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie)
|
|||
|
||||
|
||||
/* Don't advertise L0s capability if 'aspm-no-l0s' */
|
||||
aspm_support = PCIE_LINK_STATE_L1;
|
||||
if (!of_property_read_bool(pcie->np, "aspm-no-l0s"))
|
||||
aspm_support |= PCIE_LINK_STATE_L0S;
|
||||
tmp = readl(base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY);
|
||||
u32p_replace_bits(&tmp, aspm_support,
|
||||
PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_ASPM_SUPPORT_MASK);
|
||||
if (of_property_read_bool(pcie->np, "aspm-no-l0s"))
|
||||
tmp &= ~PCI_EXP_LNKCAP_ASPM_L0S;
|
||||
writel(tmp, base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY);
|
||||
|
||||
/* 'tmp' still holds the contents of PRIV1_LINK_CAPABILITY */
|
||||
|
|
@ -1565,7 +1621,7 @@ static int brcm_pcie_turn_off(struct brcm_pcie *pcie)
|
|||
|
||||
if (!(pcie->cfg->quirks & CFG_QUIRK_AVOID_BRIDGE_SHUTDOWN))
|
||||
/* Shutdown PCIe bridge */
|
||||
ret = pcie->cfg->bridge_sw_init_set(pcie, 1);
|
||||
ret = brcm_pcie_bridge_sw_init_set(pcie, 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1653,7 +1709,9 @@ static int brcm_pcie_resume_noirq(struct device *dev)
|
|||
goto err_reset;
|
||||
|
||||
/* Take bridge out of reset so we can access the SERDES reg */
|
||||
pcie->cfg->bridge_sw_init_set(pcie, 0);
|
||||
ret = brcm_pcie_bridge_sw_init_set(pcie, 0);
|
||||
if (ret)
|
||||
goto err_reset;
|
||||
|
||||
/* SERDES_IDDQ = 0 */
|
||||
tmp = readl(base + HARD_DEBUG(pcie));
|
||||
|
|
@ -1707,6 +1765,119 @@ static int brcm_pcie_resume_noirq(struct device *dev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Dump out PCIe errors on die or panic */
|
||||
static int brcm_pcie_dump_err(struct brcm_pcie *pcie,
|
||||
const char *type)
|
||||
{
|
||||
void __iomem *base = pcie->base;
|
||||
int i, is_cfg_err, is_mem_err, lanes;
|
||||
const char *width_str, *direction_str;
|
||||
u32 info, cfg_addr, cfg_cause, mem_cause, lo, hi;
|
||||
struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie);
|
||||
unsigned long flags;
|
||||
char lanes_str[9];
|
||||
|
||||
spin_lock_irqsave(&pcie->bridge_lock, flags);
|
||||
/* Don't access registers when the bridge is off */
|
||||
if (pcie->bridge_in_reset || readl(base + PCIE_OUTB_ERR_VALID) == 0) {
|
||||
spin_unlock_irqrestore(&pcie->bridge_lock, flags);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
/* Read all necessary registers so we can release the spinlock ASAP */
|
||||
info = readl(base + PCIE_OUTB_ERR_ACC_INFO);
|
||||
is_cfg_err = !!(info & PCIE_OUTB_ERR_ACC_INFO_CFG_ERR);
|
||||
is_mem_err = !!(info & PCIE_OUTB_ERR_ACC_INFO_MEM_ERR);
|
||||
if (is_cfg_err) {
|
||||
cfg_addr = readl(base + PCIE_OUTB_ERR_ACC_ADDR);
|
||||
cfg_cause = readl(base + PCIE_OUTB_ERR_CFG_CAUSE);
|
||||
}
|
||||
if (is_mem_err) {
|
||||
mem_cause = readl(base + PCIE_OUTB_ERR_MEM_CAUSE);
|
||||
lo = readl(base + PCIE_OUTB_ERR_MEM_ADDR_LO);
|
||||
hi = readl(base + PCIE_OUTB_ERR_MEM_ADDR_HI);
|
||||
}
|
||||
/* We've got all of the info, clear the error */
|
||||
writel(1, base + PCIE_OUTB_ERR_CLEAR);
|
||||
spin_unlock_irqrestore(&pcie->bridge_lock, flags);
|
||||
|
||||
dev_err(pcie->dev, "reporting PCIe info which may be related to %s error\n",
|
||||
type);
|
||||
width_str = (info & PCIE_OUTB_ERR_ACC_INFO_TYPE_64) ? "64bit" : "32bit";
|
||||
direction_str = str_read_write(!(info & PCIE_OUTB_ERR_ACC_INFO_DIR_WRITE));
|
||||
lanes = FIELD_GET(PCIE_OUTB_ERR_ACC_INFO_BYTE_LANES, info);
|
||||
for (i = 0, lanes_str[8] = 0; i < 8; i++)
|
||||
lanes_str[i] = (lanes & (1 << i)) ? '1' : '0';
|
||||
|
||||
if (is_cfg_err) {
|
||||
int bus = FIELD_GET(PCIE_OUTB_ERR_ACC_ADDR_BUS, cfg_addr);
|
||||
int dev = FIELD_GET(PCIE_OUTB_ERR_ACC_ADDR_DEV, cfg_addr);
|
||||
int func = FIELD_GET(PCIE_OUTB_ERR_ACC_ADDR_FUNC, cfg_addr);
|
||||
int reg = FIELD_GET(PCIE_OUTB_ERR_ACC_ADDR_REG, cfg_addr);
|
||||
|
||||
dev_err(pcie->dev, "Error: CFG Acc, %s, %s (%04x:%02x:%02x.%d) reg=0x%x, lanes=%s\n",
|
||||
width_str, direction_str, bridge->domain_nr, bus, dev,
|
||||
func, reg, lanes_str);
|
||||
dev_err(pcie->dev, " Type: TO=%d Abt=%d UnsupReq=%d AccTO=%d AccDsbld=%d Acc64bit=%d\n",
|
||||
!!(cfg_cause & PCIE_OUTB_ERR_CFG_CAUSE_TIMEOUT),
|
||||
!!(cfg_cause & PCIE_OUTB_ERR_CFG_CAUSE_ABORT),
|
||||
!!(cfg_cause & PCIE_OUTB_ERR_CFG_CAUSE_UNSUPP_REQ),
|
||||
!!(cfg_cause & PCIE_OUTB_ERR_CFG_CAUSE_ACC_TIMEOUT),
|
||||
!!(cfg_cause & PCIE_OUTB_ERR_CFG_CAUSE_ACC_DISABLED),
|
||||
!!(cfg_cause & PCIE_OUTB_ERR_CFG_CAUSE_ACC_64BIT));
|
||||
}
|
||||
|
||||
if (is_mem_err) {
|
||||
u64 addr = ((u64)hi << 32) | (u64)lo;
|
||||
|
||||
dev_err(pcie->dev, "Error: Mem Acc, %s, %s, @0x%llx, lanes=%s\n",
|
||||
width_str, direction_str, addr, lanes_str);
|
||||
dev_err(pcie->dev, " Type: TO=%d Abt=%d UnsupReq=%d AccDsble=%d BadAddr=%d\n",
|
||||
!!(mem_cause & PCIE_OUTB_ERR_MEM_CAUSE_TIMEOUT),
|
||||
!!(mem_cause & PCIE_OUTB_ERR_MEM_CAUSE_ABORT),
|
||||
!!(mem_cause & PCIE_OUTB_ERR_MEM_CAUSE_UNSUPP_REQ),
|
||||
!!(mem_cause & PCIE_OUTB_ERR_MEM_CAUSE_ACC_DISABLED),
|
||||
!!(mem_cause & PCIE_OUTB_ERR_MEM_CAUSE_BAD_ADDR));
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int brcm_pcie_die_notify_cb(struct notifier_block *self,
|
||||
unsigned long v, void *p)
|
||||
{
|
||||
struct brcm_pcie *pcie =
|
||||
container_of(self, struct brcm_pcie, die_notifier);
|
||||
|
||||
return brcm_pcie_dump_err(pcie, "Die");
|
||||
}
|
||||
|
||||
static int brcm_pcie_panic_notify_cb(struct notifier_block *self,
|
||||
unsigned long v, void *p)
|
||||
{
|
||||
struct brcm_pcie *pcie =
|
||||
container_of(self, struct brcm_pcie, panic_notifier);
|
||||
|
||||
return brcm_pcie_dump_err(pcie, "Panic");
|
||||
}
|
||||
|
||||
static void brcm_register_die_notifiers(struct brcm_pcie *pcie)
|
||||
{
|
||||
pcie->panic_notifier.notifier_call = brcm_pcie_panic_notify_cb;
|
||||
atomic_notifier_chain_register(&panic_notifier_list,
|
||||
&pcie->panic_notifier);
|
||||
|
||||
pcie->die_notifier.notifier_call = brcm_pcie_die_notify_cb;
|
||||
register_die_notifier(&pcie->die_notifier);
|
||||
}
|
||||
|
||||
static void brcm_unregister_die_notifiers(struct brcm_pcie *pcie)
|
||||
{
|
||||
unregister_die_notifier(&pcie->die_notifier);
|
||||
atomic_notifier_chain_unregister(&panic_notifier_list,
|
||||
&pcie->panic_notifier);
|
||||
}
|
||||
|
||||
static void __brcm_pcie_remove(struct brcm_pcie *pcie)
|
||||
{
|
||||
brcm_msi_remove(pcie);
|
||||
|
|
@ -1725,6 +1896,9 @@ static void brcm_pcie_remove(struct platform_device *pdev)
|
|||
|
||||
pci_stop_root_bus(bridge->bus);
|
||||
pci_remove_root_bus(bridge->bus);
|
||||
if (pcie->cfg->has_err_report)
|
||||
brcm_unregister_die_notifiers(pcie);
|
||||
|
||||
__brcm_pcie_remove(pcie);
|
||||
}
|
||||
|
||||
|
|
@ -1825,6 +1999,7 @@ static const struct pcie_cfg_data bcm7216_cfg = {
|
|||
.bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_7278,
|
||||
.has_phy = true,
|
||||
.num_inbound_wins = 3,
|
||||
.has_err_report = true,
|
||||
};
|
||||
|
||||
static const struct pcie_cfg_data bcm7712_cfg = {
|
||||
|
|
@ -1921,7 +2096,10 @@ static int brcm_pcie_probe(struct platform_device *pdev)
|
|||
if (ret)
|
||||
return dev_err_probe(&pdev->dev, ret, "could not enable clock\n");
|
||||
|
||||
pcie->cfg->bridge_sw_init_set(pcie, 0);
|
||||
ret = brcm_pcie_bridge_sw_init_set(pcie, 0);
|
||||
if (ret)
|
||||
return dev_err_probe(&pdev->dev, ret,
|
||||
"could not de-assert bridge reset\n");
|
||||
|
||||
if (pcie->swinit_reset) {
|
||||
ret = reset_control_assert(pcie->swinit_reset);
|
||||
|
|
@ -1996,6 +2174,11 @@ static int brcm_pcie_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (pcie->cfg->has_err_report) {
|
||||
spin_lock_init(&pcie->bridge_lock);
|
||||
brcm_register_die_notifiers(pcie);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
|
|
|
|||
Loading…
Reference in New Issue