From 013dd3d5e1835c2cfa9c824e61465b61509afa54 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 12 Dec 2016 19:50:07 +0800 Subject: PCI: rockchip: Add system PM support Add system PM support for Rockchip's RC. For pre S3, the EP is configured into D3 state which guarantees the link state should be in L1. So we could send PME_Turn_Off message to the EP and wait for its ACK to make the link state into L2 or L3 without the aux-supply. This could help save more power which I think should be very important for mobile devices. As note that there is a 5s timeout for RC to wait for the PMA_ACK after sending PME_Turn_Off. Technically it should depend on the hierarchy of devices but seems PCIe core framework doesn't handle the L2/3 for S3 at all. So that means we should presume to set a default value for PME_ACK. From the bug report[1], we could find a statement that Microsoft Windows versions typically wait for 5 seconds. So we are prone to take 5s for this timeout here. [1] https://lists.launchpad.net/kernel-packages/msg123315.html Signed-off-by: Shawn Lin Signed-off-by: Bjorn Helgaas Reviewed-by: Brian Norris --- drivers/pci/host/pcie-rockchip.c | 106 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c index f2dca7bb0b39..03923494825d 100644 --- a/drivers/pci/host/pcie-rockchip.c +++ b/drivers/pci/host/pcie-rockchip.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,10 @@ #define PCIE_CLIENT_MODE_RC HIWORD_UPDATE_BIT(0x0040) #define PCIE_CLIENT_GEN_SEL_1 HIWORD_UPDATE(0x0080, 0) #define PCIE_CLIENT_GEN_SEL_2 HIWORD_UPDATE_BIT(0x0080) +#define PCIE_CLIENT_DEBUG_OUT_0 (PCIE_CLIENT_BASE + 0x3c) +#define PCIE_CLIENT_DEBUG_LTSSM_MASK GENMASK(5, 0) +#define PCIE_CLIENT_DEBUG_LTSSM_L1 0x18 +#define PCIE_CLIENT_DEBUG_LTSSM_L2 0x19 #define PCIE_CLIENT_BASIC_STATUS1 (PCIE_CLIENT_BASE + 0x48) #define PCIE_CLIENT_LINK_STATUS_UP 0x00300000 #define PCIE_CLIENT_LINK_STATUS_MASK 0x00300000 @@ -167,9 +172,11 @@ #define IB_ROOT_PORT_REG_SIZE_SHIFT 3 #define AXI_WRAPPER_IO_WRITE 0x6 #define AXI_WRAPPER_MEM_WRITE 0x2 +#define AXI_WRAPPER_NOR_MSG 0xc #define MAX_AXI_IB_ROOTPORT_REGION_NUM 3 #define MIN_AXI_ADDR_BITS_PASSED 8 +#define PCIE_RC_SEND_PME_OFF 0x11960 #define ROCKCHIP_VENDOR_ID 0x1d87 #define PCIE_ECAM_BUS(x) (((x) & 0xff) << 20) #define PCIE_ECAM_DEV(x) (((x) & 0x1f) << 15) @@ -178,6 +185,9 @@ #define PCIE_ECAM_ADDR(bus, dev, func, reg) \ (PCIE_ECAM_BUS(bus) | PCIE_ECAM_DEV(dev) | \ PCIE_ECAM_FUNC(func) | PCIE_ECAM_REG(reg)) +#define PCIE_LINK_IS_L2(x) \ + (((x) & PCIE_CLIENT_DEBUG_LTSSM_MASK) == \ + PCIE_CLIENT_DEBUG_LTSSM_L2) #define RC_REGION_0_ADDR_TRANS_H 0x00000000 #define RC_REGION_0_ADDR_TRANS_L 0x00000000 @@ -211,7 +221,9 @@ struct rockchip_pcie { u32 io_size; int offset; phys_addr_t io_bus_addr; + void __iomem *msg_region; u32 mem_size; + phys_addr_t msg_bus_addr; phys_addr_t mem_bus_addr; }; @@ -1186,6 +1198,85 @@ static int rockchip_cfg_atu(struct rockchip_pcie *rockchip) } } + /* assign message regions */ + rockchip_pcie_prog_ob_atu(rockchip, reg_no + 1 + offset, + AXI_WRAPPER_NOR_MSG, + 20 - 1, 0, 0); + + rockchip->msg_bus_addr = rockchip->mem_bus_addr + + ((reg_no + offset) << 20); + return err; +} + +static int rockchip_pcie_wait_l2(struct rockchip_pcie *rockchip) +{ + u32 value; + int err; + + /* send PME_TURN_OFF message */ + writel(0x0, rockchip->msg_region + PCIE_RC_SEND_PME_OFF); + + /* read LTSSM and wait for falling into L2 link state */ + err = readl_poll_timeout(rockchip->apb_base + PCIE_CLIENT_DEBUG_OUT_0, + value, PCIE_LINK_IS_L2(value), 20, + jiffies_to_usecs(5 * HZ)); + if (err) { + dev_err(rockchip->dev, "PCIe link enter L2 timeout!\n"); + return err; + } + + return 0; +} + +static int rockchip_pcie_suspend_noirq(struct device *dev) +{ + struct rockchip_pcie *rockchip = dev_get_drvdata(dev); + int ret; + + /* disable core and cli int since we don't need to ack PME_ACK */ + rockchip_pcie_write(rockchip, (PCIE_CLIENT_INT_CLI << 16) | + PCIE_CLIENT_INT_CLI, PCIE_CLIENT_INT_MASK); + rockchip_pcie_write(rockchip, (u32)PCIE_CORE_INT, PCIE_CORE_INT_MASK); + + ret = rockchip_pcie_wait_l2(rockchip); + if (ret) { + rockchip_pcie_enable_interrupts(rockchip); + return ret; + } + + phy_power_off(rockchip->phy); + phy_exit(rockchip->phy); + + clk_disable_unprepare(rockchip->clk_pcie_pm); + clk_disable_unprepare(rockchip->hclk_pcie); + clk_disable_unprepare(rockchip->aclk_perf_pcie); + clk_disable_unprepare(rockchip->aclk_pcie); + + return ret; +} + +static int rockchip_pcie_resume_noirq(struct device *dev) +{ + struct rockchip_pcie *rockchip = dev_get_drvdata(dev); + int err; + + clk_prepare_enable(rockchip->clk_pcie_pm); + clk_prepare_enable(rockchip->hclk_pcie); + clk_prepare_enable(rockchip->aclk_perf_pcie); + clk_prepare_enable(rockchip->aclk_pcie); + + err = rockchip_pcie_init_port(rockchip); + if (err) + return err; + + err = rockchip_cfg_atu(rockchip); + if (err) + return err; + + /* Need this to enter L1 again */ + rockchip_pcie_update_txcredit_mui(rockchip); + rockchip_pcie_enable_interrupts(rockchip); + return 0; } @@ -1209,6 +1300,7 @@ static int rockchip_pcie_probe(struct platform_device *pdev) if (!rockchip) return -ENOMEM; + platform_set_drvdata(pdev, rockchip); rockchip->dev = dev; err = rockchip_pcie_parse_dt(rockchip); @@ -1296,6 +1388,14 @@ static int rockchip_pcie_probe(struct platform_device *pdev) err = rockchip_cfg_atu(rockchip); if (err) goto err_vpcie; + + rockchip->msg_region = devm_ioremap(rockchip->dev, + rockchip->msg_bus_addr, SZ_1M); + if (!rockchip->msg_region) { + err = -ENOMEM; + goto err_vpcie; + } + bus = pci_scan_root_bus(&pdev->dev, 0, &rockchip_pcie_ops, rockchip, &res); if (!bus) { err = -ENOMEM; @@ -1329,6 +1429,11 @@ err_aclk_pcie: return err; } +static const struct dev_pm_ops rockchip_pcie_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(rockchip_pcie_suspend_noirq, + rockchip_pcie_resume_noirq) +}; + static const struct of_device_id rockchip_pcie_of_match[] = { { .compatible = "rockchip,rk3399-pcie", }, {} @@ -1338,6 +1443,7 @@ static struct platform_driver rockchip_pcie_driver = { .driver = { .name = "rockchip-pcie", .of_match_table = rockchip_pcie_of_match, + .pm = &rockchip_pcie_pm_ops, }, .probe = rockchip_pcie_probe, -- cgit v1.2.3-58-ga151 From afc9595ea4770f0157ae06fb3acedff703e169b6 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 12 Jan 2017 09:53:17 +0800 Subject: PCI: rockchip: Disable RC's ASPM L0s based on DT "aspm-no-l0s" Rockchip's RC produces a 100MHz reference clock but there are two methods for the PHY to generate it: (1) Use the system PLL to generate a 100MHz clock. The PHY will relock it, filter signal noise, and output the reference clock. ASPM L0s works correctly, but circuit noise issues make it difficult to pass the TX compatibility test. (2) Share the SoC's 24MHZ crystal oscillator with the PHY and force the PHY's PLL to generate 100MHz internally. In this case, exit from ASPM L0s sometimes fails due to a design error in the RC receiver circuit. Even if we use extended-synch, the PHY sometimes fails to relock the bits from FTS, which will hang the system. We want the flexibility to use both clocking methods, so add a DT property, "aspm-no-l0s". If that's present, disable L0s to avoid the issues with case (2). [bhelgaas: changelog] Reported-by: Jeffy Chen Signed-off-by: Shawn Lin Signed-off-by: Bjorn Helgaas Reviewed-by: Brian Norris Acked-by: Rob Herring --- Documentation/devicetree/bindings/pci/rockchip-pcie.txt | 2 ++ drivers/pci/host/pcie-rockchip.c | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/rockchip-pcie.txt b/Documentation/devicetree/bindings/pci/rockchip-pcie.txt index 71aeda1ca055..1453a734c2f5 100644 --- a/Documentation/devicetree/bindings/pci/rockchip-pcie.txt +++ b/Documentation/devicetree/bindings/pci/rockchip-pcie.txt @@ -43,6 +43,8 @@ Required properties: - interrupt-map-mask and interrupt-map: standard PCI properties Optional Property: +- aspm-no-l0s: RC won't support ASPM L0s. This property is needed if + using 24MHz OSC for RC's PHY. - ep-gpios: contain the entry for pre-reset gpio - num-lanes: number of lanes to use - vpcie3v3-supply: The phandle to the 3.3v regulator to use for PCIe. diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c index 03923494825d..0d6e8ee5b017 100644 --- a/drivers/pci/host/pcie-rockchip.c +++ b/drivers/pci/host/pcie-rockchip.c @@ -145,6 +145,8 @@ #define PCIE_RC_CONFIG_DCR_CSPL_SHIFT 18 #define PCIE_RC_CONFIG_DCR_CSPL_LIMIT 0xff #define PCIE_RC_CONFIG_DCR_CPLS_SHIFT 26 +#define PCIE_RC_CONFIG_LINK_CAP (PCIE_RC_CONFIG_BASE + 0xcc) +#define PCIE_RC_CONFIG_LINK_CAP_L0S BIT(10) #define PCIE_RC_CONFIG_LCS (PCIE_RC_CONFIG_BASE + 0xd0) #define PCIE_RC_CONFIG_L1_SUBSTATE_CTRL2 (PCIE_RC_CONFIG_BASE + 0x90c) #define PCIE_RC_CONFIG_THP_CAP (PCIE_RC_CONFIG_BASE + 0x274) @@ -665,6 +667,13 @@ static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip) status &= ~PCIE_RC_CONFIG_THP_CAP_NEXT_MASK; rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_THP_CAP); + /* Clear L0s from RC's link cap */ + if (of_property_read_bool(dev->of_node, "aspm-no-l0s")) { + status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_LINK_CAP); + status &= ~PCIE_RC_CONFIG_LINK_CAP_L0S; + rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_LINK_CAP); + } + rockchip_pcie_write(rockchip, 0x0, PCIE_RC_BAR_CONF); rockchip_pcie_write(rockchip, -- cgit v1.2.3-58-ga151 From 7faebda21d573a6889bab1e0100ed4092a5a4716 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Wed, 18 Jan 2017 16:29:15 +0800 Subject: PCI: rockchip: Use readl_poll_timeout() instead of open-coding it Use readl_poll_timeout() instead of open-coding it. Signed-off-by: Shawn Lin Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pcie-rockchip.c | 52 +++++++++++++--------------------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c index 0d6e8ee5b017..17374a9efdb8 100644 --- a/drivers/pci/host/pcie-rockchip.c +++ b/drivers/pci/host/pcie-rockchip.c @@ -188,8 +188,11 @@ (PCIE_ECAM_BUS(bus) | PCIE_ECAM_DEV(dev) | \ PCIE_ECAM_FUNC(func) | PCIE_ECAM_REG(reg)) #define PCIE_LINK_IS_L2(x) \ - (((x) & PCIE_CLIENT_DEBUG_LTSSM_MASK) == \ - PCIE_CLIENT_DEBUG_LTSSM_L2) + (((x) & PCIE_CLIENT_DEBUG_LTSSM_MASK) == PCIE_CLIENT_DEBUG_LTSSM_L2) +#define PCIE_LINK_UP(x) \ + (((x) & PCIE_CLIENT_LINK_STATUS_MASK) == PCIE_CLIENT_LINK_STATUS_UP) +#define PCIE_LINK_IS_GEN2(x) \ + (((x) & PCIE_CORE_PL_CONF_SPEED_MASK) == PCIE_CORE_PL_CONF_SPEED_5G) #define RC_REGION_0_ADDR_TRANS_H 0x00000000 #define RC_REGION_0_ADDR_TRANS_L 0x00000000 @@ -463,7 +466,6 @@ static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip) struct device *dev = rockchip->dev; int err; u32 status; - unsigned long timeout; gpiod_set_value(rockchip->ep_gpio, 0); @@ -604,23 +606,12 @@ static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip) gpiod_set_value(rockchip->ep_gpio, 1); /* 500ms timeout value should be enough for Gen1/2 training */ - timeout = jiffies + msecs_to_jiffies(500); - - for (;;) { - status = rockchip_pcie_read(rockchip, - PCIE_CLIENT_BASIC_STATUS1); - if ((status & PCIE_CLIENT_LINK_STATUS_MASK) == - PCIE_CLIENT_LINK_STATUS_UP) { - dev_dbg(dev, "PCIe link training gen1 pass!\n"); - break; - } - - if (time_after(jiffies, timeout)) { - dev_err(dev, "PCIe link training gen1 timeout!\n"); - return -ETIMEDOUT; - } - - msleep(20); + err = readl_poll_timeout(rockchip->apb_base + PCIE_CLIENT_BASIC_STATUS1, + status, PCIE_LINK_UP(status), 20, + 500 * USEC_PER_MSEC); + if (err) { + dev_err(dev, "PCIe link training gen1 timeout!\n"); + return -ETIMEDOUT; } if (rockchip->link_gen == 2) { @@ -632,22 +623,11 @@ static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip) status |= PCI_EXP_LNKCTL_RL; rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_LCS); - timeout = jiffies + msecs_to_jiffies(500); - for (;;) { - status = rockchip_pcie_read(rockchip, PCIE_CORE_CTRL); - if ((status & PCIE_CORE_PL_CONF_SPEED_MASK) == - PCIE_CORE_PL_CONF_SPEED_5G) { - dev_dbg(dev, "PCIe link training gen2 pass!\n"); - break; - } - - if (time_after(jiffies, timeout)) { - dev_dbg(dev, "PCIe link training gen2 timeout, fall back to gen1!\n"); - break; - } - - msleep(20); - } + err = readl_poll_timeout(rockchip->apb_base + PCIE_CORE_CTRL, + status, PCIE_LINK_IS_GEN2(status), 20, + 500 * USEC_PER_MSEC); + if (err) + dev_dbg(dev, "PCIe link training gen2 timeout, fall back to gen1!\n"); } /* Check the final link width from negotiated lane counter from MGMT */ -- cgit v1.2.3-58-ga151 From 0b351c986a5ffedb502632c1b27690c9730d79c4 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 20 Jan 2017 17:24:30 +0100 Subject: PCI: rockchip: Mark PM functions as __maybe_unused When CONFIG_PM_SLEEP is disabled, we get harmless build warnings: host/pcie-rockchip.c:1267:12: error: 'rockchip_pcie_resume_noirq' defined but not used [-Werror=unused-function] host/pcie-rockchip.c:1240:12: error: 'rockchip_pcie_suspend_noirq' defined but not used [-Werror=unused-function] Marking both functions as __maybe_unused avoids the warning without the need for #ifdef around them. Fixes: 013dd3d5e183 ("PCI: rockchip: Add system PM support") Signed-off-by: Arnd Bergmann Signed-off-by: Bjorn Helgaas Acked-by: Shawn Lin --- drivers/pci/host/pcie-rockchip.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c index 17374a9efdb8..ff43a7e96c22 100644 --- a/drivers/pci/host/pcie-rockchip.c +++ b/drivers/pci/host/pcie-rockchip.c @@ -1217,7 +1217,7 @@ static int rockchip_pcie_wait_l2(struct rockchip_pcie *rockchip) return 0; } -static int rockchip_pcie_suspend_noirq(struct device *dev) +static int __maybe_unused rockchip_pcie_suspend_noirq(struct device *dev) { struct rockchip_pcie *rockchip = dev_get_drvdata(dev); int ret; @@ -1244,7 +1244,7 @@ static int rockchip_pcie_suspend_noirq(struct device *dev) return ret; } -static int rockchip_pcie_resume_noirq(struct device *dev) +static int __maybe_unused rockchip_pcie_resume_noirq(struct device *dev) { struct rockchip_pcie *rockchip = dev_get_drvdata(dev); int err; -- cgit v1.2.3-58-ga151 From f1d722b607d610b66785f7f00d2e1d457260647c Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 10 Feb 2017 14:52:02 +0800 Subject: PCI: rockchip: Fix rockchip_pcie_probe() error path to free resource list rockchip_pcie_probe() calls of_pci_get_host_bridge_resources() to parse resources from DT and build a resource list. The caller is responsible for disposing of the resource list. This is normally done by pci_release_host_bridge_dev() when the host bridge is removed. If the host bridge probe fails, dispose of the resource list in the probe error path. [bhelgaas: changelog] Suggested-by: Bjorn Helgaas Signed-off-by: Shawn Lin Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pcie-rockchip.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c index ff43a7e96c22..c0b3b6513a47 100644 --- a/drivers/pci/host/pcie-rockchip.c +++ b/drivers/pci/host/pcie-rockchip.c @@ -1343,7 +1343,7 @@ static int rockchip_pcie_probe(struct platform_device *pdev) err = devm_request_pci_bus_resources(dev, &res); if (err) - goto err_vpcie; + goto err_free_res; /* Get the I/O and memory ranges from DT */ resource_list_for_each_entry(win, &res) { @@ -1376,19 +1376,19 @@ static int rockchip_pcie_probe(struct platform_device *pdev) err = rockchip_cfg_atu(rockchip); if (err) - goto err_vpcie; + goto err_free_res; rockchip->msg_region = devm_ioremap(rockchip->dev, rockchip->msg_bus_addr, SZ_1M); if (!rockchip->msg_region) { err = -ENOMEM; - goto err_vpcie; + goto err_free_res; } bus = pci_scan_root_bus(&pdev->dev, 0, &rockchip_pcie_ops, rockchip, &res); if (!bus) { err = -ENOMEM; - goto err_vpcie; + goto err_free_res; } pci_bus_size_bridges(bus); @@ -1399,6 +1399,8 @@ static int rockchip_pcie_probe(struct platform_device *pdev) pci_bus_add_devices(bus); return err; +err_free_res: + pci_free_resource_list(&res); err_vpcie: if (!IS_ERR(rockchip->vpcie3v3)) regulator_disable(rockchip->vpcie3v3); -- cgit v1.2.3-58-ga151 From 5800790a925b0aefb621ae3da86668c3a2867750 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 16 Feb 2017 15:29:35 +0800 Subject: PCI: rockchip: Set vendor ID from local core config space The TRM says the vendor ID in the RC's configure space can be rewritten and the value must be the same as the value read from the local core configure space. But we misread that and didn't notice it before. Actually we should only able to rewrite it from the local core configure space. Fix that issue to make lspci show the correct IP vendor infomation. Signed-off-by: Shawn Lin Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pcie-rockchip.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c index c0b3b6513a47..26ddd3535272 100644 --- a/drivers/pci/host/pcie-rockchip.c +++ b/drivers/pci/host/pcie-rockchip.c @@ -125,6 +125,7 @@ #define PCIE_CORE_INT_CT BIT(11) #define PCIE_CORE_INT_UTC BIT(18) #define PCIE_CORE_INT_MMVC BIT(19) +#define PCIE_CORE_CONFIG_VENDOR (PCIE_CORE_CTRL_MGMT_BASE + 0x44) #define PCIE_CORE_INT_MASK (PCIE_CORE_CTRL_MGMT_BASE + 0x210) #define PCIE_RC_BAR_CONF (PCIE_CORE_CTRL_MGMT_BASE + 0x300) @@ -138,7 +139,6 @@ PCIE_CORE_INT_MMVC) #define PCIE_RC_CONFIG_BASE 0xa00000 -#define PCIE_RC_CONFIG_VENDOR (PCIE_RC_CONFIG_BASE + 0x00) #define PCIE_RC_CONFIG_RID_CCR (PCIE_RC_CONFIG_BASE + 0x08) #define PCIE_RC_CONFIG_SCC_SHIFT 16 #define PCIE_RC_CONFIG_DCR (PCIE_RC_CONFIG_BASE + 0xc4) @@ -637,7 +637,7 @@ static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip) dev_dbg(dev, "current link width is x%d\n", status); rockchip_pcie_write(rockchip, ROCKCHIP_VENDOR_ID, - PCIE_RC_CONFIG_VENDOR); + PCIE_CORE_CONFIG_VENDOR); rockchip_pcie_write(rockchip, PCI_CLASS_BRIDGE_PCI << PCIE_RC_CONFIG_SCC_SHIFT, PCIE_RC_CONFIG_RID_CCR); -- cgit v1.2.3-58-ga151