mirror of https://gitee.com/openkylin/linux.git
Merge branch 'remotes/lorenzo/pci/aardvark'
- Train link immediately after enabling link training to avoid issues with Compex WLE900VX and Turris MOX devices (Pali Rohár) - Remove ASPM config and let the PCI core do it (Pali Rohár) - Interpret zero 'max-link-speed' value as invalid (Pali Rohár) - Respect the 'max-link-speed' property and improve link training (Marek Behún) - Issue PERST via GPIO (Pali Rohár) - Add PHY support (Marek Behún) - Use standard PCIe capability macros (Pali Rohár) - Document new 'max-link-speed', 'phys', and 'reset-gpios' properties (Marek Behún) * remotes/lorenzo/pci/aardvark: dt-bindings: PCI: aardvark: Describe new properties PCI: aardvark: Replace custom macros by standard linux/pci_regs.h macros PCI: aardvark: Add PHY support PCI: aardvark: Add FIXME comment for PCIE_CORE_CMD_STATUS_REG access PCI: aardvark: Issue PERST via GPIO PCI: aardvark: Improve link training PCI: of: Zero max-link-speed value is invalid PCI: aardvark: Don't blindly enable ASPM L0s and don't write to read-only register PCI: aardvark: Train link immediately after enabling training
This commit is contained in:
commit
075a383389
|
@ -19,6 +19,9 @@ contain the following properties:
|
|||
- interrupt-map-mask and interrupt-map: standard PCI properties to
|
||||
define the mapping of the PCIe interface to interrupt numbers.
|
||||
- bus-range: PCI bus numbers covered
|
||||
- phys: the PCIe PHY handle
|
||||
- max-link-speed: see pci.txt
|
||||
- reset-gpios: see pci.txt
|
||||
|
||||
In addition, the Device Tree describing an Aardvark PCIe controller
|
||||
must include a sub-node that describes the legacy interrupt controller
|
||||
|
@ -48,6 +51,7 @@ Example:
|
|||
<0 0 0 2 &pcie_intc 1>,
|
||||
<0 0 0 3 &pcie_intc 2>,
|
||||
<0 0 0 4 &pcie_intc 3>;
|
||||
phys = <&comphy1 0>;
|
||||
pcie_intc: interrupt-controller {
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <1>;
|
||||
|
|
|
@ -9,15 +9,18 @@
|
|||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_pci.h>
|
||||
|
||||
#include "../pci.h"
|
||||
|
@ -31,16 +34,6 @@
|
|||
#define PCIE_CORE_CMD_MEM_IO_REQ_EN BIT(2)
|
||||
#define PCIE_CORE_DEV_REV_REG 0x8
|
||||
#define PCIE_CORE_PCIEXP_CAP 0xc0
|
||||
#define PCIE_CORE_DEV_CTRL_STATS_REG 0xc8
|
||||
#define PCIE_CORE_DEV_CTRL_STATS_RELAX_ORDER_DISABLE (0 << 4)
|
||||
#define PCIE_CORE_DEV_CTRL_STATS_MAX_PAYLOAD_SZ_SHIFT 5
|
||||
#define PCIE_CORE_DEV_CTRL_STATS_SNOOP_DISABLE (0 << 11)
|
||||
#define PCIE_CORE_DEV_CTRL_STATS_MAX_RD_REQ_SIZE_SHIFT 12
|
||||
#define PCIE_CORE_DEV_CTRL_STATS_MAX_RD_REQ_SZ 0x2
|
||||
#define PCIE_CORE_LINK_CTRL_STAT_REG 0xd0
|
||||
#define PCIE_CORE_LINK_L0S_ENTRY BIT(0)
|
||||
#define PCIE_CORE_LINK_TRAINING BIT(5)
|
||||
#define PCIE_CORE_LINK_WIDTH_SHIFT 20
|
||||
#define PCIE_CORE_ERR_CAPCTL_REG 0x118
|
||||
#define PCIE_CORE_ERR_CAPCTL_ECRC_CHK_TX BIT(5)
|
||||
#define PCIE_CORE_ERR_CAPCTL_ECRC_CHK_TX_EN BIT(6)
|
||||
|
@ -101,6 +94,8 @@
|
|||
#define PCIE_CORE_CTRL2_STRICT_ORDER_ENABLE BIT(5)
|
||||
#define PCIE_CORE_CTRL2_OB_WIN_ENABLE BIT(6)
|
||||
#define PCIE_CORE_CTRL2_MSI_ENABLE BIT(10)
|
||||
#define PCIE_CORE_REF_CLK_REG (CONTROL_BASE_ADDR + 0x14)
|
||||
#define PCIE_CORE_REF_CLK_TX_ENABLE BIT(1)
|
||||
#define PCIE_MSG_LOG_REG (CONTROL_BASE_ADDR + 0x30)
|
||||
#define PCIE_ISR0_REG (CONTROL_BASE_ADDR + 0x40)
|
||||
#define PCIE_MSG_PM_PME_MASK BIT(7)
|
||||
|
@ -201,7 +196,10 @@ struct advk_pcie {
|
|||
struct mutex msi_used_lock;
|
||||
u16 msi_msg;
|
||||
int root_bus_nr;
|
||||
int link_gen;
|
||||
struct pci_bridge_emul bridge;
|
||||
struct gpio_desc *reset_gpio;
|
||||
struct phy *phy;
|
||||
};
|
||||
|
||||
static inline void advk_writel(struct advk_pcie *pcie, u32 val, u64 reg)
|
||||
|
@ -214,6 +212,11 @@ static inline u32 advk_readl(struct advk_pcie *pcie, u64 reg)
|
|||
return readl(pcie->base + reg);
|
||||
}
|
||||
|
||||
static inline u16 advk_read16(struct advk_pcie *pcie, u64 reg)
|
||||
{
|
||||
return advk_readl(pcie, (reg & ~0x3)) >> ((reg & 0x3) * 8);
|
||||
}
|
||||
|
||||
static int advk_pcie_link_up(struct advk_pcie *pcie)
|
||||
{
|
||||
u32 val, ltssm_state;
|
||||
|
@ -225,20 +228,16 @@ static int advk_pcie_link_up(struct advk_pcie *pcie)
|
|||
|
||||
static int advk_pcie_wait_for_link(struct advk_pcie *pcie)
|
||||
{
|
||||
struct device *dev = &pcie->pdev->dev;
|
||||
int retries;
|
||||
|
||||
/* check if the link is up or not */
|
||||
for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) {
|
||||
if (advk_pcie_link_up(pcie)) {
|
||||
dev_info(dev, "link up\n");
|
||||
if (advk_pcie_link_up(pcie))
|
||||
return 0;
|
||||
}
|
||||
|
||||
usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX);
|
||||
}
|
||||
|
||||
dev_err(dev, "link never came up\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
|
@ -253,10 +252,115 @@ static void advk_pcie_wait_for_retrain(struct advk_pcie *pcie)
|
|||
}
|
||||
}
|
||||
|
||||
static int advk_pcie_train_at_gen(struct advk_pcie *pcie, int gen)
|
||||
{
|
||||
int ret, neg_gen;
|
||||
u32 reg;
|
||||
|
||||
/* Setup link speed */
|
||||
reg = advk_readl(pcie, PCIE_CORE_CTRL0_REG);
|
||||
reg &= ~PCIE_GEN_SEL_MSK;
|
||||
if (gen == 3)
|
||||
reg |= SPEED_GEN_3;
|
||||
else if (gen == 2)
|
||||
reg |= SPEED_GEN_2;
|
||||
else
|
||||
reg |= SPEED_GEN_1;
|
||||
advk_writel(pcie, reg, PCIE_CORE_CTRL0_REG);
|
||||
|
||||
/*
|
||||
* Enable link training. This is not needed in every call to this
|
||||
* function, just once suffices, but it does not break anything either.
|
||||
*/
|
||||
reg = advk_readl(pcie, PCIE_CORE_CTRL0_REG);
|
||||
reg |= LINK_TRAINING_EN;
|
||||
advk_writel(pcie, reg, PCIE_CORE_CTRL0_REG);
|
||||
|
||||
/*
|
||||
* Start link training immediately after enabling it.
|
||||
* This solves problems for some buggy cards.
|
||||
*/
|
||||
reg = advk_readl(pcie, PCIE_CORE_PCIEXP_CAP + PCI_EXP_LNKCTL);
|
||||
reg |= PCI_EXP_LNKCTL_RL;
|
||||
advk_writel(pcie, reg, PCIE_CORE_PCIEXP_CAP + PCI_EXP_LNKCTL);
|
||||
|
||||
ret = advk_pcie_wait_for_link(pcie);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg = advk_read16(pcie, PCIE_CORE_PCIEXP_CAP + PCI_EXP_LNKSTA);
|
||||
neg_gen = reg & PCI_EXP_LNKSTA_CLS;
|
||||
|
||||
return neg_gen;
|
||||
}
|
||||
|
||||
static void advk_pcie_train_link(struct advk_pcie *pcie)
|
||||
{
|
||||
struct device *dev = &pcie->pdev->dev;
|
||||
int neg_gen = -1, gen;
|
||||
|
||||
/*
|
||||
* Try link training at link gen specified by device tree property
|
||||
* 'max-link-speed'. If this fails, iteratively train at lower gen.
|
||||
*/
|
||||
for (gen = pcie->link_gen; gen > 0; --gen) {
|
||||
neg_gen = advk_pcie_train_at_gen(pcie, gen);
|
||||
if (neg_gen > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (neg_gen < 0)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* After successful training if negotiated gen is lower than requested,
|
||||
* train again on negotiated gen. This solves some stability issues for
|
||||
* some buggy gen1 cards.
|
||||
*/
|
||||
if (neg_gen < gen) {
|
||||
gen = neg_gen;
|
||||
neg_gen = advk_pcie_train_at_gen(pcie, gen);
|
||||
}
|
||||
|
||||
if (neg_gen == gen) {
|
||||
dev_info(dev, "link up at gen %i\n", gen);
|
||||
return;
|
||||
}
|
||||
|
||||
err:
|
||||
dev_err(dev, "link never came up\n");
|
||||
}
|
||||
|
||||
static void advk_pcie_issue_perst(struct advk_pcie *pcie)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
if (!pcie->reset_gpio)
|
||||
return;
|
||||
|
||||
/* PERST does not work for some cards when link training is enabled */
|
||||
reg = advk_readl(pcie, PCIE_CORE_CTRL0_REG);
|
||||
reg &= ~LINK_TRAINING_EN;
|
||||
advk_writel(pcie, reg, PCIE_CORE_CTRL0_REG);
|
||||
|
||||
/* 10ms delay is needed for some cards */
|
||||
dev_info(&pcie->pdev->dev, "issuing PERST via reset GPIO for 10ms\n");
|
||||
gpiod_set_value_cansleep(pcie->reset_gpio, 1);
|
||||
usleep_range(10000, 11000);
|
||||
gpiod_set_value_cansleep(pcie->reset_gpio, 0);
|
||||
}
|
||||
|
||||
static void advk_pcie_setup_hw(struct advk_pcie *pcie)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
advk_pcie_issue_perst(pcie);
|
||||
|
||||
/* Enable TX */
|
||||
reg = advk_readl(pcie, PCIE_CORE_REF_CLK_REG);
|
||||
reg |= PCIE_CORE_REF_CLK_TX_ENABLE;
|
||||
advk_writel(pcie, reg, PCIE_CORE_REF_CLK_REG);
|
||||
|
||||
/* Set to Direct mode */
|
||||
reg = advk_readl(pcie, CTRL_CONFIG_REG);
|
||||
reg &= ~(CTRL_MODE_MASK << CTRL_MODE_SHIFT);
|
||||
|
@ -275,36 +379,26 @@ static void advk_pcie_setup_hw(struct advk_pcie *pcie)
|
|||
PCIE_CORE_ERR_CAPCTL_ECRC_CHCK_RCV;
|
||||
advk_writel(pcie, reg, PCIE_CORE_ERR_CAPCTL_REG);
|
||||
|
||||
/* Set PCIe Device Control and Status 1 PF0 register */
|
||||
reg = PCIE_CORE_DEV_CTRL_STATS_RELAX_ORDER_DISABLE |
|
||||
(7 << PCIE_CORE_DEV_CTRL_STATS_MAX_PAYLOAD_SZ_SHIFT) |
|
||||
PCIE_CORE_DEV_CTRL_STATS_SNOOP_DISABLE |
|
||||
(PCIE_CORE_DEV_CTRL_STATS_MAX_RD_REQ_SZ <<
|
||||
PCIE_CORE_DEV_CTRL_STATS_MAX_RD_REQ_SIZE_SHIFT);
|
||||
advk_writel(pcie, reg, PCIE_CORE_DEV_CTRL_STATS_REG);
|
||||
/* Set PCIe Device Control register */
|
||||
reg = advk_readl(pcie, PCIE_CORE_PCIEXP_CAP + PCI_EXP_DEVCTL);
|
||||
reg &= ~PCI_EXP_DEVCTL_RELAX_EN;
|
||||
reg &= ~PCI_EXP_DEVCTL_NOSNOOP_EN;
|
||||
reg &= ~PCI_EXP_DEVCTL_READRQ;
|
||||
reg |= PCI_EXP_DEVCTL_PAYLOAD; /* Set max payload size */
|
||||
reg |= PCI_EXP_DEVCTL_READRQ_512B;
|
||||
advk_writel(pcie, reg, PCIE_CORE_PCIEXP_CAP + PCI_EXP_DEVCTL);
|
||||
|
||||
/* Program PCIe Control 2 to disable strict ordering */
|
||||
reg = PCIE_CORE_CTRL2_RESERVED |
|
||||
PCIE_CORE_CTRL2_TD_ENABLE;
|
||||
advk_writel(pcie, reg, PCIE_CORE_CTRL2_REG);
|
||||
|
||||
/* Set GEN2 */
|
||||
reg = advk_readl(pcie, PCIE_CORE_CTRL0_REG);
|
||||
reg &= ~PCIE_GEN_SEL_MSK;
|
||||
reg |= SPEED_GEN_2;
|
||||
advk_writel(pcie, reg, PCIE_CORE_CTRL0_REG);
|
||||
|
||||
/* Set lane X1 */
|
||||
reg = advk_readl(pcie, PCIE_CORE_CTRL0_REG);
|
||||
reg &= ~LANE_CNT_MSK;
|
||||
reg |= LANE_COUNT_1;
|
||||
advk_writel(pcie, reg, PCIE_CORE_CTRL0_REG);
|
||||
|
||||
/* Enable link training */
|
||||
reg = advk_readl(pcie, PCIE_CORE_CTRL0_REG);
|
||||
reg |= LINK_TRAINING_EN;
|
||||
advk_writel(pcie, reg, PCIE_CORE_CTRL0_REG);
|
||||
|
||||
/* Enable MSI */
|
||||
reg = advk_readl(pcie, PCIE_CORE_CTRL2_REG);
|
||||
reg |= PCIE_CORE_CTRL2_MSI_ENABLE;
|
||||
|
@ -340,23 +434,22 @@ static void advk_pcie_setup_hw(struct advk_pcie *pcie)
|
|||
|
||||
/*
|
||||
* PERST# signal could have been asserted by pinctrl subsystem before
|
||||
* probe() callback has been called, making the endpoint going into
|
||||
* probe() callback has been called or issued explicitly by reset gpio
|
||||
* function advk_pcie_issue_perst(), making the endpoint going into
|
||||
* fundamental reset. As required by PCI Express spec a delay for at
|
||||
* least 100ms after such a reset before link training is needed.
|
||||
*/
|
||||
msleep(PCI_PM_D3COLD_WAIT);
|
||||
|
||||
/* Start link training */
|
||||
reg = advk_readl(pcie, PCIE_CORE_LINK_CTRL_STAT_REG);
|
||||
reg |= PCIE_CORE_LINK_TRAINING;
|
||||
advk_writel(pcie, reg, PCIE_CORE_LINK_CTRL_STAT_REG);
|
||||
|
||||
advk_pcie_wait_for_link(pcie);
|
||||
|
||||
reg = PCIE_CORE_LINK_L0S_ENTRY |
|
||||
(1 << PCIE_CORE_LINK_WIDTH_SHIFT);
|
||||
advk_writel(pcie, reg, PCIE_CORE_LINK_CTRL_STAT_REG);
|
||||
advk_pcie_train_link(pcie);
|
||||
|
||||
/*
|
||||
* FIXME: The following register update is suspicious. This register is
|
||||
* applicable only when the PCI controller is configured for Endpoint
|
||||
* mode, not as a Root Complex. But apparently when this code is
|
||||
* removed, some cards stop working. This should be investigated and
|
||||
* a comment explaining this should be put here.
|
||||
*/
|
||||
reg = advk_readl(pcie, PCIE_CORE_CMD_STATUS_REG);
|
||||
reg |= PCIE_CORE_CMD_MEM_ACCESS_EN |
|
||||
PCIE_CORE_CMD_IO_ACCESS_EN |
|
||||
|
@ -952,6 +1045,62 @@ static irqreturn_t advk_pcie_irq_handler(int irq, void *arg)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void __maybe_unused advk_pcie_disable_phy(struct advk_pcie *pcie)
|
||||
{
|
||||
phy_power_off(pcie->phy);
|
||||
phy_exit(pcie->phy);
|
||||
}
|
||||
|
||||
static int advk_pcie_enable_phy(struct advk_pcie *pcie)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!pcie->phy)
|
||||
return 0;
|
||||
|
||||
ret = phy_init(pcie->phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = phy_set_mode(pcie->phy, PHY_MODE_PCIE);
|
||||
if (ret) {
|
||||
phy_exit(pcie->phy);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = phy_power_on(pcie->phy);
|
||||
if (ret) {
|
||||
phy_exit(pcie->phy);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int advk_pcie_setup_phy(struct advk_pcie *pcie)
|
||||
{
|
||||
struct device *dev = &pcie->pdev->dev;
|
||||
struct device_node *node = dev->of_node;
|
||||
int ret = 0;
|
||||
|
||||
pcie->phy = devm_of_phy_get(dev, node, NULL);
|
||||
if (IS_ERR(pcie->phy) && (PTR_ERR(pcie->phy) == -EPROBE_DEFER))
|
||||
return PTR_ERR(pcie->phy);
|
||||
|
||||
/* Old bindings miss the PHY handle */
|
||||
if (IS_ERR(pcie->phy)) {
|
||||
dev_warn(dev, "PHY unavailable (%ld)\n", PTR_ERR(pcie->phy));
|
||||
pcie->phy = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = advk_pcie_enable_phy(pcie);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to initialize PHY (%d)\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int advk_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
|
@ -992,6 +1141,32 @@ static int advk_pcie_probe(struct platform_device *pdev)
|
|||
}
|
||||
pcie->root_bus_nr = bus->start;
|
||||
|
||||
pcie->reset_gpio = devm_gpiod_get_from_of_node(dev, dev->of_node,
|
||||
"reset-gpios", 0,
|
||||
GPIOD_OUT_LOW,
|
||||
"pcie1-reset");
|
||||
ret = PTR_ERR_OR_ZERO(pcie->reset_gpio);
|
||||
if (ret) {
|
||||
if (ret == -ENOENT) {
|
||||
pcie->reset_gpio = NULL;
|
||||
} else {
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(dev, "Failed to get reset-gpio: %i\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = of_pci_get_max_link_speed(dev->of_node);
|
||||
if (ret <= 0 || ret > 3)
|
||||
pcie->link_gen = 3;
|
||||
else
|
||||
pcie->link_gen = ret;
|
||||
|
||||
ret = advk_pcie_setup_phy(pcie);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
advk_pcie_setup_hw(pcie);
|
||||
|
||||
advk_sw_pci_bridge_init(pcie);
|
||||
|
|
|
@ -592,7 +592,7 @@ int of_pci_get_max_link_speed(struct device_node *node)
|
|||
u32 max_link_speed;
|
||||
|
||||
if (of_property_read_u32(node, "max-link-speed", &max_link_speed) ||
|
||||
max_link_speed > 4)
|
||||
max_link_speed == 0 || max_link_speed > 4)
|
||||
return -EINVAL;
|
||||
|
||||
return max_link_speed;
|
||||
|
|
Loading…
Reference in New Issue