Merge branch 'pci/host/brcmstb'
authorBjorn Helgaas <bhelgaas@google.com>
Thu, 13 Jan 2022 15:57:46 +0000 (09:57 -0600)
committerBjorn Helgaas <bhelgaas@google.com>
Thu, 13 Jan 2022 15:57:46 +0000 (09:57 -0600)
- 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

Documentation/devicetree/bindings/pci/brcm,stb-pcie.yaml
drivers/pci/controller/pcie-brcmstb.c

index 1fe1027..3e3c892 100644 (file)
@@ -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";
+                            };
+                    };
             };
     };
index 1fc7bd4..1cba6e6 100644 (file)
@@ -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>
 #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,17 +1279,60 @@ 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);
+       /*
+        * 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 ret;
+       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 @@ fail:
 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 = {