PCI: mvebu: Add support for sending Set_Slot_Power_Limit message
authorPali Rohár <pali@kernel.org>
Tue, 12 Apr 2022 09:49:46 +0000 (11:49 +0200)
committerLorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Mon, 25 Apr 2022 09:53:39 +0000 (10:53 +0100)
If DT supplies the 'slot-power-limit-milliwatt' property, program
the value in the Slot Power Limit in the Slot Capabilities register
and program the Root Port to send a Set_Slot_Power_Limit Message
when the Link transitions to DL_Up.

Link: https://lore.kernel.org/r/20220412094946.27069-5-pali@kernel.org
Signed-off-by: Pali Rohár <pali@kernel.org>
Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Reviewed-by: Rob Herring <robh@kernel.org>
drivers/pci/controller/pci-mvebu.c

index 8f76d4bda356500a0c25a572339e74e6cd4ab66f..c1ffdb06c97168b2a4e6d29ba8a772e831f97bb8 100644 (file)
@@ -8,6 +8,7 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/pci.h>
+#include <linux/bitfield.h>
 #include <linux/clk.h>
 #include <linux/delay.h>
 #include <linux/gpio.h>
 #define  PCIE_STAT_BUS                  0xff00
 #define  PCIE_STAT_DEV                  0x1f0000
 #define  PCIE_STAT_LINK_DOWN           BIT(0)
+#define PCIE_SSPL_OFF          0x1a0c
+#define  PCIE_SSPL_VALUE_SHIFT         0
+#define  PCIE_SSPL_VALUE_MASK          GENMASK(7, 0)
+#define  PCIE_SSPL_SCALE_SHIFT         8
+#define  PCIE_SSPL_SCALE_MASK          GENMASK(9, 8)
+#define  PCIE_SSPL_ENABLE              BIT(16)
 #define PCIE_RC_RTSTA          0x1a14
 #define PCIE_DEBUG_CTRL         0x1a60
 #define  PCIE_DEBUG_SOFT_RESET         BIT(20)
@@ -111,6 +118,8 @@ struct mvebu_pcie_port {
        struct mvebu_pcie_window iowin;
        u32 saved_pcie_stat;
        struct resource regs;
+       u8 slot_power_limit_value;
+       u8 slot_power_limit_scale;
        struct irq_domain *intx_irq_domain;
        raw_spinlock_t irq_lock;
        int intx_irq;
@@ -239,7 +248,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
 
 static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
 {
-       u32 ctrl, lnkcap, cmd, dev_rev, unmask;
+       u32 ctrl, lnkcap, cmd, dev_rev, unmask, sspl;
 
        /* Setup PCIe controller to Root Complex mode. */
        ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
@@ -292,6 +301,20 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
        /* Point PCIe unit MBUS decode windows to DRAM space. */
        mvebu_pcie_setup_wins(port);
 
+       /*
+        * Program Root Port to automatically send Set_Slot_Power_Limit
+        * PCIe Message when changing status from Dl_Down to Dl_Up and valid
+        * slot power limit was specified.
+        */
+       sspl = mvebu_readl(port, PCIE_SSPL_OFF);
+       sspl &= ~(PCIE_SSPL_VALUE_MASK | PCIE_SSPL_SCALE_MASK | PCIE_SSPL_ENABLE);
+       if (port->slot_power_limit_value) {
+               sspl |= port->slot_power_limit_value << PCIE_SSPL_VALUE_SHIFT;
+               sspl |= port->slot_power_limit_scale << PCIE_SSPL_SCALE_SHIFT;
+               sspl |= PCIE_SSPL_ENABLE;
+       }
+       mvebu_writel(port, sspl, PCIE_SSPL_OFF);
+
        /* Mask all interrupt sources. */
        mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
 
@@ -628,9 +651,24 @@ mvebu_pci_bridge_emul_pcie_conf_read(struct pci_bridge_emul *bridge,
                          (PCI_EXP_LNKSTA_DLLLA << 16) : 0);
                break;
 
-       case PCI_EXP_SLTCTL:
-               *value = PCI_EXP_SLTSTA_PDS << 16;
+       case PCI_EXP_SLTCTL: {
+               u16 slotctl = le16_to_cpu(bridge->pcie_conf.slotctl);
+               u16 slotsta = le16_to_cpu(bridge->pcie_conf.slotsta);
+               u32 val = 0;
+               /*
+                * When slot power limit was not specified in DT then
+                * ASPL_DISABLE bit is stored only in emulated config space.
+                * Otherwise reflect status of PCIE_SSPL_ENABLE bit in HW.
+                */
+               if (!port->slot_power_limit_value)
+                       val |= slotctl & PCI_EXP_SLTCTL_ASPL_DISABLE;
+               else if (!(mvebu_readl(port, PCIE_SSPL_OFF) & PCIE_SSPL_ENABLE))
+                       val |= PCI_EXP_SLTCTL_ASPL_DISABLE;
+               /* This callback is 32-bit and in high bits is slot status. */
+               val |= slotsta << 16;
+               *value = val;
                break;
+       }
 
        case PCI_EXP_RTSTA:
                *value = mvebu_readl(port, PCIE_RC_RTSTA);
@@ -774,6 +812,22 @@ mvebu_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
                mvebu_writel(port, new, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL);
                break;
 
+       case PCI_EXP_SLTCTL:
+               /*
+                * Allow to change PCIE_SSPL_ENABLE bit only when slot power
+                * limit was specified in DT and configured into HW.
+                */
+               if ((mask & PCI_EXP_SLTCTL_ASPL_DISABLE) &&
+                   port->slot_power_limit_value) {
+                       u32 sspl = mvebu_readl(port, PCIE_SSPL_OFF);
+                       if (new & PCI_EXP_SLTCTL_ASPL_DISABLE)
+                               sspl &= ~PCIE_SSPL_ENABLE;
+                       else
+                               sspl |= PCIE_SSPL_ENABLE;
+                       mvebu_writel(port, sspl, PCIE_SSPL_OFF);
+               }
+               break;
+
        case PCI_EXP_RTSTA:
                /*
                 * PME Status bit in Root Status Register (PCIE_RC_RTSTA)
@@ -868,8 +922,26 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
        /*
         * Older mvebu hardware provides PCIe Capability structure only in
         * version 1. New hardware provides it in version 2.
+        * Enable slot support which is emulated.
         */
-       bridge->pcie_conf.cap = cpu_to_le16(pcie_cap_ver);
+       bridge->pcie_conf.cap = cpu_to_le16(pcie_cap_ver | PCI_EXP_FLAGS_SLOT);
+
+       /*
+        * Set Presence Detect State bit permanently as there is no support for
+        * unplugging PCIe card from the slot. Assume that PCIe card is always
+        * connected in slot.
+        *
+        * Set physical slot number to port+1 as mvebu ports are indexed from
+        * zero and zero value is reserved for ports within the same silicon
+        * as Root Port which is not mvebu case.
+        *
+        * Also set correct slot power limit.
+        */
+       bridge->pcie_conf.slotcap = cpu_to_le32(
+               FIELD_PREP(PCI_EXP_SLTCAP_SPLV, port->slot_power_limit_value) |
+               FIELD_PREP(PCI_EXP_SLTCAP_SPLS, port->slot_power_limit_scale) |
+               FIELD_PREP(PCI_EXP_SLTCAP_PSN, port->port+1));
+       bridge->pcie_conf.slotsta = cpu_to_le16(PCI_EXP_SLTSTA_PDS);
 
        bridge->subsystem_vendor_id = ssdev_id & 0xffff;
        bridge->subsystem_id = ssdev_id >> 16;
@@ -1191,6 +1263,7 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
 {
        struct device *dev = &pcie->pdev->dev;
        enum of_gpio_flags flags;
+       u32 slot_power_limit;
        int reset_gpio, ret;
        u32 num_lanes;
 
@@ -1291,6 +1364,15 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
                port->reset_gpio = gpio_to_desc(reset_gpio);
        }
 
+       slot_power_limit = of_pci_get_slot_power_limit(child,
+                               &port->slot_power_limit_value,
+                               &port->slot_power_limit_scale);
+       if (slot_power_limit)
+               dev_info(dev, "%s: Slot power limit %u.%uW\n",
+                        port->name,
+                        slot_power_limit / 1000,
+                        (slot_power_limit / 100) % 10);
+
        port->clk = of_clk_get_by_name(child, NULL);
        if (IS_ERR(port->clk)) {
                dev_err(dev, "%s: cannot get clock\n", port->name);
@@ -1588,7 +1670,7 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
 {
        struct mvebu_pcie *pcie = platform_get_drvdata(pdev);
        struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie);
-       u32 cmd;
+       u32 cmd, sspl;
        int i;
 
        /* Remove PCI bus with all devices. */
@@ -1625,6 +1707,11 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
                /* Free config space for emulated root bridge. */
                pci_bridge_emul_cleanup(&port->bridge);
 
+               /* Disable sending Set_Slot_Power_Limit PCIe Message. */
+               sspl = mvebu_readl(port, PCIE_SSPL_OFF);
+               sspl &= ~(PCIE_SSPL_VALUE_MASK | PCIE_SSPL_SCALE_MASK | PCIE_SSPL_ENABLE);
+               mvebu_writel(port, sspl, PCIE_SSPL_OFF);
+
                /* Disable and clear BARs and windows. */
                mvebu_pcie_disable_wins(port);