mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 01:10:04 +00:00
PCI: mvebu: Add support for sending Set_Slot_Power_Limit message
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>
This commit is contained in:
parent
35662423fb
commit
0d5b8c2985
1 changed files with 92 additions and 5 deletions
|
@ -8,6 +8,7 @@
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/pci.h>
|
#include <linux/pci.h>
|
||||||
|
#include <linux/bitfield.h>
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/gpio.h>
|
#include <linux/gpio.h>
|
||||||
|
@ -66,6 +67,12 @@
|
||||||
#define PCIE_STAT_BUS 0xff00
|
#define PCIE_STAT_BUS 0xff00
|
||||||
#define PCIE_STAT_DEV 0x1f0000
|
#define PCIE_STAT_DEV 0x1f0000
|
||||||
#define PCIE_STAT_LINK_DOWN BIT(0)
|
#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_RC_RTSTA 0x1a14
|
||||||
#define PCIE_DEBUG_CTRL 0x1a60
|
#define PCIE_DEBUG_CTRL 0x1a60
|
||||||
#define PCIE_DEBUG_SOFT_RESET BIT(20)
|
#define PCIE_DEBUG_SOFT_RESET BIT(20)
|
||||||
|
@ -111,6 +118,8 @@ struct mvebu_pcie_port {
|
||||||
struct mvebu_pcie_window iowin;
|
struct mvebu_pcie_window iowin;
|
||||||
u32 saved_pcie_stat;
|
u32 saved_pcie_stat;
|
||||||
struct resource regs;
|
struct resource regs;
|
||||||
|
u8 slot_power_limit_value;
|
||||||
|
u8 slot_power_limit_scale;
|
||||||
struct irq_domain *intx_irq_domain;
|
struct irq_domain *intx_irq_domain;
|
||||||
raw_spinlock_t irq_lock;
|
raw_spinlock_t irq_lock;
|
||||||
int intx_irq;
|
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)
|
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. */
|
/* Setup PCIe controller to Root Complex mode. */
|
||||||
ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
|
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. */
|
/* Point PCIe unit MBUS decode windows to DRAM space. */
|
||||||
mvebu_pcie_setup_wins(port);
|
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. */
|
/* Mask all interrupt sources. */
|
||||||
mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
|
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);
|
(PCI_EXP_LNKSTA_DLLLA << 16) : 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PCI_EXP_SLTCTL:
|
case PCI_EXP_SLTCTL: {
|
||||||
*value = PCI_EXP_SLTSTA_PDS << 16;
|
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;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case PCI_EXP_RTSTA:
|
case PCI_EXP_RTSTA:
|
||||||
*value = mvebu_readl(port, PCIE_RC_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);
|
mvebu_writel(port, new, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL);
|
||||||
break;
|
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:
|
case PCI_EXP_RTSTA:
|
||||||
/*
|
/*
|
||||||
* PME Status bit in Root Status Register (PCIE_RC_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
|
* Older mvebu hardware provides PCIe Capability structure only in
|
||||||
* version 1. New hardware provides it in version 2.
|
* 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_vendor_id = ssdev_id & 0xffff;
|
||||||
bridge->subsystem_id = ssdev_id >> 16;
|
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;
|
struct device *dev = &pcie->pdev->dev;
|
||||||
enum of_gpio_flags flags;
|
enum of_gpio_flags flags;
|
||||||
|
u32 slot_power_limit;
|
||||||
int reset_gpio, ret;
|
int reset_gpio, ret;
|
||||||
u32 num_lanes;
|
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);
|
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);
|
port->clk = of_clk_get_by_name(child, NULL);
|
||||||
if (IS_ERR(port->clk)) {
|
if (IS_ERR(port->clk)) {
|
||||||
dev_err(dev, "%s: cannot get clock\n", port->name);
|
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 mvebu_pcie *pcie = platform_get_drvdata(pdev);
|
||||||
struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie);
|
struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie);
|
||||||
u32 cmd;
|
u32 cmd, sspl;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* Remove PCI bus with all devices. */
|
/* 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. */
|
/* Free config space for emulated root bridge. */
|
||||||
pci_bridge_emul_cleanup(&port->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. */
|
/* Disable and clear BARs and windows. */
|
||||||
mvebu_pcie_disable_wins(port);
|
mvebu_pcie_disable_wins(port);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue