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

Merge branch 'pci/host/brcmstb'

- Declare bitmap correctly for use by bitmap interfaces (Christophe
  JAILLET)

- Clean up computation of legacy and non-legacy MSI bitmasks (Florian
  Fainelli)

- Update suspend/resume/remove error handling to warn about errors and not
  fail the operation (Jim Quinlan)

- Correct the "pcie" and "msi" interrupt descriptions in DT binding (Jim
  Quinlan)

- Add DT bindings for endpoint voltage regulators (Jim Quinlan)

- Split brcm_pcie_setup() into two functions (Jim Quinlan)

- Add mechanism for turning on voltage regulators for connected devices
  (Jim Quinlan)

- Turn voltage regulators for connected devices on/off when bus is added or
  removed (Jim Quinlan)

- When suspending, don't turn off voltage regulators for wakeup devices
  (Jim Quinlan)

* pci/host/brcmstb:
  PCI: brcmstb: Do not turn off WOL regulators on suspend
  PCI: brcmstb: Add control of subdevice voltage regulators
  PCI: brcmstb: Add mechanism to turn on subdev regulators
  PCI: brcmstb: Split brcm_pcie_setup() into two funcs
  dt-bindings: PCI: Add bindings for Brcmstb EP voltage regulators
  dt-bindings: PCI: Correct brcmstb interrupts, interrupt-map.
  PCI: brcmstb: Fix function return value handling
  PCI: brcmstb: Do not use __GENMASK
  PCI: brcmstb: Declare 'used' as bitmap, not unsigned long
parents 3164f27b 11ed8b86
Loading
Loading
Loading
Loading
+25 −2
Original line number Diff line number Diff line
@@ -143,11 +143,15 @@ examples:
                    #address-cells = <3>;
                    #size-cells = <2>;
                    #interrupt-cells = <1>;
                    interrupts = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>,
                    interrupts = <GIC_SPI 147 IRQ_TYPE_LEVEL_HIGH>,
                                 <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
                    interrupt-names = "pcie", "msi";
                    interrupt-map-mask = <0x0 0x0 0x0 0x7>;
                    interrupt-map = <0 0 0 1 &gicv2 GIC_SPI 143 IRQ_TYPE_LEVEL_HIGH>;
                    interrupt-map = <0 0 0 1 &gicv2 GIC_SPI 143 IRQ_TYPE_LEVEL_HIGH
                                     0 0 0 2 &gicv2 GIC_SPI 144 IRQ_TYPE_LEVEL_HIGH
                                     0 0 0 3 &gicv2 GIC_SPI 145 IRQ_TYPE_LEVEL_HIGH
                                     0 0 0 4 &gicv2 GIC_SPI 146 IRQ_TYPE_LEVEL_HIGH>;

                    msi-parent = <&pcie0>;
                    msi-controller;
                    ranges = <0x02000000 0x0 0xf8000000 0x6 0x00000000 0x0 0x04000000>;
@@ -155,5 +159,24 @@ examples:
                                 <0x42000000 0x1 0x80000000 0x3 0x00000000 0x0 0x80000000>;
                    brcm,enable-ssc;
                    brcm,scb-sizes =  <0x0000000080000000 0x0000000080000000>;

                    /* PCIe bridge, Root Port */
                    pci@0,0 {
                            #address-cells = <3>;
                            #size-cells = <2>;
                            reg = <0x0 0x0 0x0 0x0 0x0>;
                            compatible = "pciclass,0604";
                            device_type = "pci";
                            vpcie3v3-supply = <&vreg7>;
                            ranges;

                            /* PCIe endpoint */
                            pci-ep@0,0 {
                                    assigned-addresses =
                                        <0x82010000 0x0 0xf8000000 0x6 0x00000000 0x0 0x2000>;
                                    reg = <0x0 0x0 0x0 0x0 0x0>;
                                    compatible = "pci14e4,1688";
                            };
                    };
            };
    };
+263 −41
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
#include <linux/pci.h>
#include <linux/pci-ecam.h>
#include <linux/printk.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/sizes.h>
#include <linux/slab.h>
@@ -144,6 +145,9 @@
#define BRCM_INT_PCI_MSI_NR		32
#define BRCM_INT_PCI_MSI_LEGACY_NR	8
#define BRCM_INT_PCI_MSI_SHIFT		0
#define BRCM_INT_PCI_MSI_MASK		GENMASK(BRCM_INT_PCI_MSI_NR - 1, 0)
#define BRCM_INT_PCI_MSI_LEGACY_MASK	GENMASK(31, \
						32 - BRCM_INT_PCI_MSI_LEGACY_NR)

/* MSI target addresses */
#define BRCM_MSI_TARGET_ADDR_LT_4GB	0x0fffffffcULL
@@ -191,6 +195,8 @@ static inline void brcm_pcie_bridge_sw_init_set_generic(struct brcm_pcie *pcie,
static inline void brcm_pcie_perst_set_4908(struct brcm_pcie *pcie, u32 val);
static inline void brcm_pcie_perst_set_7278(struct brcm_pcie *pcie, u32 val);
static inline void brcm_pcie_perst_set_generic(struct brcm_pcie *pcie, u32 val);
static int brcm_pcie_linkup(struct brcm_pcie *pcie);
static int brcm_pcie_add_bus(struct pci_bus *bus);

enum {
	RGR1_SW_INIT_1,
@@ -257,6 +263,14 @@ static const struct pcie_cfg_data bcm2711_cfg = {
	.bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_generic,
};

struct subdev_regulators {
	unsigned int num_supplies;
	struct regulator_bulk_data supplies[];
};

static int pci_subdev_regulators_add_bus(struct pci_bus *bus);
static void pci_subdev_regulators_remove_bus(struct pci_bus *bus);

struct brcm_msi {
	struct device		*dev;
	void __iomem		*base;
@@ -266,8 +280,7 @@ struct brcm_msi {
	struct mutex		lock; /* guards the alloc/free operations */
	u64			target_addr;
	int			irq;
	/* used indicates which MSI interrupts have been alloc'd */
	unsigned long		used;
	DECLARE_BITMAP(used, BRCM_INT_PCI_MSI_NR);
	bool			legacy;
	/* Some chips have MSIs in bits [31..24] of a shared register. */
	int			legacy_shift;
@@ -295,6 +308,9 @@ struct brcm_pcie {
	u32			hw_rev;
	void			(*perst_set)(struct brcm_pcie *pcie, u32 val);
	void			(*bridge_sw_init_set)(struct brcm_pcie *pcie, u32 val);
	bool			refusal_mode;
	struct subdev_regulators *sr;
	bool			ep_wakeup_capable;
};

/*
@@ -406,6 +422,99 @@ static int brcm_pcie_set_ssc(struct brcm_pcie *pcie)
	return ssc && pll ? 0 : -EIO;
}

static void *alloc_subdev_regulators(struct device *dev)
{
	static const char * const supplies[] = {
		"vpcie3v3",
		"vpcie3v3aux",
		"vpcie12v",
	};
	const size_t size = sizeof(struct subdev_regulators)
		+ sizeof(struct regulator_bulk_data) * ARRAY_SIZE(supplies);
	struct subdev_regulators *sr;
	int i;

	sr = devm_kzalloc(dev, size, GFP_KERNEL);
	if (sr) {
		sr->num_supplies = ARRAY_SIZE(supplies);
		for (i = 0; i < ARRAY_SIZE(supplies); i++)
			sr->supplies[i].supply = supplies[i];
	}

	return sr;
}

static int pci_subdev_regulators_add_bus(struct pci_bus *bus)
{
	struct device *dev = &bus->dev;
	struct subdev_regulators *sr;
	int ret;

	if (!dev->of_node || !bus->parent || !pci_is_root_bus(bus->parent))
		return 0;

	if (dev->driver_data)
		dev_err(dev, "dev.driver_data unexpectedly non-NULL\n");

	sr = alloc_subdev_regulators(dev);
	if (!sr)
		return -ENOMEM;

	dev->driver_data = sr;
	ret = regulator_bulk_get(dev, sr->num_supplies, sr->supplies);
	if (ret)
		return ret;

	ret = regulator_bulk_enable(sr->num_supplies, sr->supplies);
	if (ret) {
		dev_err(dev, "failed to enable regulators for downstream device\n");
		return ret;
	}

	return 0;
}

static int brcm_pcie_add_bus(struct pci_bus *bus)
{
	struct device *dev = &bus->dev;
	struct brcm_pcie *pcie = (struct brcm_pcie *) bus->sysdata;
	int ret;

	if (!dev->of_node || !bus->parent || !pci_is_root_bus(bus->parent))
		return 0;

	ret = pci_subdev_regulators_add_bus(bus);
	if (ret)
		return ret;

	/* Grab the regulators for suspend/resume */
	pcie->sr = bus->dev.driver_data;

	/*
	 * If we have failed linkup there is no point to return an error as
	 * currently it will cause a WARNING() from pci_alloc_child_bus().
	 * We return 0 and turn on the "refusal_mode" so that any further
	 * accesses to the pci_dev just get 0xffffffff
	 */
	if (brcm_pcie_linkup(pcie) != 0)
		pcie->refusal_mode = true;

	return 0;
}

static void pci_subdev_regulators_remove_bus(struct pci_bus *bus)
{
	struct device *dev = &bus->dev;
	struct subdev_regulators *sr = dev->driver_data;

	if (!sr || !bus->parent || !pci_is_root_bus(bus->parent))
		return;

	if (regulator_bulk_disable(sr->num_supplies, sr->supplies))
		dev_err(dev, "failed to disable regulators for downstream device\n");
	dev->driver_data = NULL;
}

/* Limits operation to a specific generation (1, 2, or 3) */
static void brcm_pcie_set_gen(struct brcm_pcie *pcie, int gen)
{
@@ -534,7 +643,7 @@ static int brcm_msi_alloc(struct brcm_msi *msi)
	int hwirq;

	mutex_lock(&msi->lock);
	hwirq = bitmap_find_free_region(&msi->used, msi->nr, 0);
	hwirq = bitmap_find_free_region(msi->used, msi->nr, 0);
	mutex_unlock(&msi->lock);

	return hwirq;
@@ -543,7 +652,7 @@ static int brcm_msi_alloc(struct brcm_msi *msi)
static void brcm_msi_free(struct brcm_msi *msi, unsigned long hwirq)
{
	mutex_lock(&msi->lock);
	bitmap_release_region(&msi->used, hwirq, 0);
	bitmap_release_region(msi->used, hwirq, 0);
	mutex_unlock(&msi->lock);
}

@@ -619,7 +728,8 @@ static void brcm_msi_remove(struct brcm_pcie *pcie)

static void brcm_msi_set_regs(struct brcm_msi *msi)
{
	u32 val = __GENMASK(31, msi->legacy_shift);
	u32 val = msi->legacy ? BRCM_INT_PCI_MSI_LEGACY_MASK :
				BRCM_INT_PCI_MSI_MASK;

	writel(val, msi->intr_base + MSI_INT_MASK_CLR);
	writel(val, msi->intr_base + MSI_INT_CLR);
@@ -661,6 +771,12 @@ static int brcm_pcie_enable_msi(struct brcm_pcie *pcie)
	msi->irq = irq;
	msi->legacy = pcie->hw_rev < BRCM_PCIE_HW_REV_33;

	/*
	 * Sanity check to make sure that the 'used' bitmap in struct brcm_msi
	 * is large enough.
	 */
	BUILD_BUG_ON(BRCM_INT_PCI_MSI_LEGACY_NR > BRCM_INT_PCI_MSI_NR);

	if (msi->legacy) {
		msi->intr_base = msi->base + PCIE_INTR2_CPU_BASE;
		msi->nr = BRCM_INT_PCI_MSI_LEGACY_NR;
@@ -711,6 +827,18 @@ static void __iomem *brcm_pcie_map_conf(struct pci_bus *bus, unsigned int devfn,
	/* Accesses to the RC go right to the RC registers if slot==0 */
	if (pci_is_root_bus(bus))
		return PCI_SLOT(devfn) ? NULL : base + where;
	if (pcie->refusal_mode) {
		/*
		 * At this point we do not have link.  There will be a CPU
		 * abort -- a quirk with this controller --if Linux tries
		 * to read any config-space registers besides those
		 * targeting the host bridge.  To prevent this we hijack
		 * the address to point to a safe access that will return
		 * 0xffffffff.
		 */
		writel(0xffffffff, base + PCIE_MISC_RC_BAR2_CONFIG_HI);
		return base + PCIE_MISC_RC_BAR2_CONFIG_HI + (where & 0x3);
	}

	/* For devices, write to the config space index register */
	idx = PCIE_ECAM_OFFSET(bus->number, devfn, 0);
@@ -722,6 +850,8 @@ static struct pci_ops brcm_pcie_ops = {
	.map_bus = brcm_pcie_map_conf,
	.read = pci_generic_config_read,
	.write = pci_generic_config_write,
	.add_bus = brcm_pcie_add_bus,
	.remove_bus = pci_subdev_regulators_remove_bus,
};

static inline void brcm_pcie_bridge_sw_init_set_generic(struct brcm_pcie *pcie, u32 val)
@@ -863,16 +993,9 @@ static inline int brcm_pcie_get_rc_bar2_size_and_offset(struct brcm_pcie *pcie,

static int brcm_pcie_setup(struct brcm_pcie *pcie)
{
	struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie);
	u64 rc_bar2_offset, rc_bar2_size;
	void __iomem *base = pcie->base;
	struct device *dev = pcie->dev;
	struct resource_entry *entry;
	bool ssc_good = false;
	struct resource *res;
	int num_out_wins = 0;
	u16 nlw, cls, lnksta;
	int i, ret, memc;
	int ret, memc;
	u32 tmp, burst, aspm_support;

	/* Reset the bridge */
@@ -957,6 +1080,40 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie)
	if (pcie->gen)
		brcm_pcie_set_gen(pcie, pcie->gen);

	/* 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);
	writel(tmp, base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY);

	/*
	 * For config space accesses on the RC, show the right class for
	 * a PCIe-PCIe bridge (the default setting is to be EP mode).
	 */
	tmp = readl(base + PCIE_RC_CFG_PRIV1_ID_VAL3);
	u32p_replace_bits(&tmp, 0x060400,
			  PCIE_RC_CFG_PRIV1_ID_VAL3_CLASS_CODE_MASK);
	writel(tmp, base + PCIE_RC_CFG_PRIV1_ID_VAL3);

	return 0;
}

static int brcm_pcie_linkup(struct brcm_pcie *pcie)
{
	struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie);
	struct device *dev = pcie->dev;
	void __iomem *base = pcie->base;
	struct resource_entry *entry;
	struct resource *res;
	int num_out_wins = 0;
	u16 nlw, cls, lnksta;
	bool ssc_good = false;
	u32 tmp;
	int ret, i;

	/* Unassert the fundamental reset */
	pcie->perst_set(pcie, 0);

@@ -994,24 +1151,6 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie)
		num_out_wins++;
	}

	/* 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);
	writel(tmp, base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY);

	/*
	 * For config space accesses on the RC, show the right class for
	 * a PCIe-PCIe bridge (the default setting is to be EP mode).
	 */
	tmp = readl(base + PCIE_RC_CFG_PRIV1_ID_VAL3);
	u32p_replace_bits(&tmp, 0x060400,
			  PCIE_RC_CFG_PRIV1_ID_VAL3_CLASS_CODE_MASK);
	writel(tmp, base + PCIE_RC_CFG_PRIV1_ID_VAL3);

	if (pcie->ssc) {
		ret = brcm_pcie_set_ssc(pcie);
		if (ret == 0)
@@ -1140,18 +1279,61 @@ static void brcm_pcie_turn_off(struct brcm_pcie *pcie)
	pcie->bridge_sw_init_set(pcie, 1);
}

static int pci_dev_may_wakeup(struct pci_dev *dev, void *data)
{
	bool *ret = data;

	if (device_may_wakeup(&dev->dev)) {
		*ret = true;
		dev_info(&dev->dev, "disable cancelled for wake-up device\n");
	}
	return (int) *ret;
}

static int brcm_pcie_suspend(struct device *dev)
{
	struct brcm_pcie *pcie = dev_get_drvdata(dev);
	struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie);
	int ret;

	brcm_pcie_turn_off(pcie);
	ret = brcm_phy_stop(pcie);
	reset_control_rearm(pcie->rescal);
	clk_disable_unprepare(pcie->clk);
	/*
	 * If brcm_phy_stop() returns an error, just dev_err(). If we
	 * return the error it will cause the suspend to fail and this is a
	 * forgivable offense that will probably be erased on resume.
	 */
	if (brcm_phy_stop(pcie))
		dev_err(dev, "Could not stop phy for suspend\n");

	ret = reset_control_rearm(pcie->rescal);
	if (ret) {
		dev_err(dev, "Could not rearm rescal reset\n");
		return ret;
	}

	if (pcie->sr) {
		/*
		 * Now turn off the regulators, but if at least one
		 * downstream device is enabled as a wake-up source, do not
		 * turn off regulators.
		 */
		pcie->ep_wakeup_capable = false;
		pci_walk_bus(bridge->bus, pci_dev_may_wakeup,
			     &pcie->ep_wakeup_capable);
		if (!pcie->ep_wakeup_capable) {
			ret = regulator_bulk_disable(pcie->sr->num_supplies,
						     pcie->sr->supplies);
			if (ret) {
				dev_err(dev, "Could not turn off regulators\n");
				reset_control_reset(pcie->rescal);
				return ret;
			}
		}
	}
	clk_disable_unprepare(pcie->clk);

	return 0;
}

static int brcm_pcie_resume(struct device *dev)
{
@@ -1161,11 +1343,32 @@ static int brcm_pcie_resume(struct device *dev)
	int ret;

	base = pcie->base;
	clk_prepare_enable(pcie->clk);
	ret = clk_prepare_enable(pcie->clk);
	if (ret)
		return ret;

	if (pcie->sr) {
		if (pcie->ep_wakeup_capable) {
			/*
			 * We are resuming from a suspend.  In the suspend we
			 * did not disable the power supplies, so there is
			 * no need to enable them (and falsely increase their
			 * usage count).
			 */
			pcie->ep_wakeup_capable = false;
		} else {
			ret = regulator_bulk_enable(pcie->sr->num_supplies,
						    pcie->sr->supplies);
			if (ret) {
				dev_err(dev, "Could not turn on regulators\n");
				goto err_disable_clk;
			}
		}
	}

	ret = reset_control_reset(pcie->rescal);
	if (ret)
		goto err_disable_clk;
		goto err_regulator;

	ret = brcm_phy_start(pcie);
	if (ret)
@@ -1186,6 +1389,10 @@ static int brcm_pcie_resume(struct device *dev)
	if (ret)
		goto err_reset;

	ret = brcm_pcie_linkup(pcie);
	if (ret)
		goto err_reset;

	if (pcie->msi)
		brcm_msi_set_regs(pcie->msi);

@@ -1193,6 +1400,9 @@ static int brcm_pcie_resume(struct device *dev)

err_reset:
	reset_control_rearm(pcie->rescal);
err_regulator:
	if (pcie->sr)
		regulator_bulk_disable(pcie->sr->num_supplies, pcie->sr->supplies);
err_disable_clk:
	clk_disable_unprepare(pcie->clk);
	return ret;
@@ -1202,8 +1412,10 @@ static void __brcm_pcie_remove(struct brcm_pcie *pcie)
{
	brcm_msi_remove(pcie);
	brcm_pcie_turn_off(pcie);
	brcm_phy_stop(pcie);
	reset_control_rearm(pcie->rescal);
	if (brcm_phy_stop(pcie))
		dev_err(pcie->dev, "Could not stop phy\n");
	if (reset_control_rearm(pcie->rescal))
		dev_err(pcie->dev, "Could not rearm rescal reset\n");
	clk_disable_unprepare(pcie->clk);
}

@@ -1320,7 +1532,17 @@ static int brcm_pcie_probe(struct platform_device *pdev)

	platform_set_drvdata(pdev, pcie);

	return pci_host_probe(bridge);
	ret = pci_host_probe(bridge);
	if (!ret && !brcm_pcie_link_up(pcie))
		ret = -ENODEV;

	if (ret) {
		brcm_pcie_remove(pdev);
		return ret;
	}

	return 0;

fail:
	__brcm_pcie_remove(pcie);
	return ret;
@@ -1329,8 +1551,8 @@ static int brcm_pcie_probe(struct platform_device *pdev)
MODULE_DEVICE_TABLE(of, brcm_pcie_match);

static const struct dev_pm_ops brcm_pcie_pm_ops = {
	.suspend = brcm_pcie_suspend,
	.resume = brcm_pcie_resume,
	.suspend_noirq = brcm_pcie_suspend,
	.resume_noirq = brcm_pcie_resume,
};

static struct platform_driver brcm_pcie_driver = {