Commit f4620f62 authored by Bjorn Helgaas's avatar Bjorn Helgaas
Browse files

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
parents 12390db2 8d4ec3fb
Loading
Loading
Loading
Loading
+196 −13
Original line number Diff line number Diff line
@@ -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: