]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
PCI: rockchip: Add system PM support
authorShawn Lin <shawn.lin@rock-chips.com>
Mon, 12 Dec 2016 11:50:07 +0000 (19:50 +0800)
committerBjorn Helgaas <bhelgaas@google.com>
Wed, 11 Jan 2017 18:22:47 +0000 (12:22 -0600)
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 <shawn.lin@rock-chips.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Brian Norris <briannorris@chromium.org>
drivers/pci/host/pcie-rockchip.c

index f2dca7bb0b39f8d02537ba985f958a19d1bfcf14..03923494825d8c61a0b0434ecb4d4bc92b7c2571 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/gpio/consumer.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
+#include <linux/iopoll.h>
 #include <linux/irq.h>
 #include <linux/irqchip/chained_irq.h>
 #include <linux/irqdomain.h>
 #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
 #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)
 #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,