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:
Bjorn Helgaas 2025-12-03 14:18:35 -06:00
commit f4620f6216
1 changed files with 196 additions and 13 deletions

View File

@ -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: