ARM: tegra: disable LP2 cpuidle state if PCIe is enabled
authorStephen Warren <swarren@nvidia.com>
Mon, 6 May 2013 20:19:19 +0000 (14:19 -0600)
committerStephen Warren <swarren@nvidia.com>
Tue, 13 Aug 2013 18:07:56 +0000 (12:07 -0600)
Tegra20 HW appears to have a bug such that PCIe device interrupts,
whether they are legacy IRQs or MSI, are lost when LP2 is enabled. To
work around this, simply disable LP2 if any PCIe devices with interrupts
are present. Detect this via the IRQ domain map operation. This is
slightly over-conservative; if a device with an interrupt is present but
the driver does not actually use them, LP2 will still be disabled.
However, this is a reasonable trade-off which enables a simpler
workaround.

Signed-off-by: Stephen Warren <swarren@nvidia.com>
Tested-by: Thierry Reding <treding@nvidia.com>
Acked-by: Thierry Reding <treding@nvidia.com>
arch/arm/mach-tegra/cpuidle-tegra20.c
arch/arm/mach-tegra/cpuidle.c
arch/arm/mach-tegra/cpuidle.h
drivers/pci/host/pci-tegra.c
include/linux/tegra-cpuidle.h [new file with mode: 0644]

index 706aa4215c3636ffd39a2211dda5cc1319ce52db..b82dcaee2ef4eb80ae215bf3d0dbe4ceb1cc8ba0 100644 (file)
@@ -211,6 +211,18 @@ static int tegra20_idle_lp2_coupled(struct cpuidle_device *dev,
 }
 #endif
 
+/*
+ * Tegra20 HW appears to have a bug such that PCIe device interrupts, whether
+ * they are legacy IRQs or MSI, are lost when LP2 is enabled. To work around
+ * this, simply disable LP2 if the PCI driver and DT node are both enabled.
+ */
+void tegra20_cpuidle_pcie_irqs_in_use(void)
+{
+       pr_info_once(
+               "Disabling cpuidle LP2 state, since PCIe IRQs are in use\n");
+       tegra_idle_driver.states[1].disabled = true;
+}
+
 int __init tegra20_cpuidle_init(void)
 {
        return cpuidle_register(&tegra_idle_driver, cpu_possible_mask);
index e85973cef037e02509cb1a774df7285682795806..0961dfcf83a4af4e2395f114a3608f6d35ac4349 100644 (file)
@@ -44,3 +44,13 @@ void __init tegra_cpuidle_init(void)
                break;
        }
 }
+
+void tegra_cpuidle_pcie_irqs_in_use(void)
+{
+       switch (tegra_chip_id) {
+       case TEGRA20:
+               if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC))
+                       tegra20_cpuidle_pcie_irqs_in_use();
+               break;
+       }
+}
index 9ec2c1ab0fa4c76a390bfc9673ba9436e18983af..c017dab60ffa593af6f20e276ce8b46324527f44 100644 (file)
@@ -19,6 +19,7 @@
 
 #ifdef CONFIG_CPU_IDLE
 int tegra20_cpuidle_init(void);
+void tegra20_cpuidle_pcie_irqs_in_use(void);
 int tegra30_cpuidle_init(void);
 int tegra114_cpuidle_init(void);
 void tegra_cpuidle_init(void);
index ad95c406a6d08bf5a2c8e3fda6a9be4650aa0b80..7356741de36bfb874b0059cb2aa0d595741eaafe 100644 (file)
@@ -41,6 +41,7 @@
 #include <linux/platform_device.h>
 #include <linux/sizes.h>
 #include <linux/slab.h>
+#include <linux/tegra-cpuidle.h>
 #include <linux/tegra-powergate.h>
 #include <linux/vmalloc.h>
 #include <linux/regulator/consumer.h>
@@ -636,6 +637,8 @@ static int tegra_pcie_map_irq(const struct pci_dev *pdev, u8 slot, u8 pin)
 {
        struct tegra_pcie *pcie = sys_to_pcie(pdev->bus->sysdata);
 
+       tegra_cpuidle_pcie_irqs_in_use();
+
        return pcie->irq;
 }
 
@@ -1221,6 +1224,8 @@ static int tegra_msi_map(struct irq_domain *domain, unsigned int irq,
        irq_set_chip_data(irq, domain->host_data);
        set_irq_flags(irq, IRQF_VALID);
 
+       tegra_cpuidle_pcie_irqs_in_use();
+
        return 0;
 }
 
diff --git a/include/linux/tegra-cpuidle.h b/include/linux/tegra-cpuidle.h
new file mode 100644 (file)
index 0000000..dda3647
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#ifndef __LINUX_TEGRA_CPUIDLE_H__
+#define __LINUX_TEGRA_CPUIDLE_H__
+
+void tegra_cpuidle_pcie_irqs_in_use(void);
+
+#endif