diff options
author | Jay Agarwal <jagarwal@nvidia.com> | 2012-03-26 19:53:23 +0530 |
---|---|---|
committer | Rohan Somvanshi <rsomvanshi@nvidia.com> | 2012-03-30 08:02:24 -0700 |
commit | 77a69cc538a67146d94a2d34a76634c5b4360947 (patch) | |
tree | 14857a568700bbbf61f69a44541e5117c7a329ee | |
parent | acc671b9cc8f6c945ab0e98c1b5fa4987e1253b7 (diff) |
ARM:tegra:pcie:Avoid commenting PM noirq calls
1. disable read write operation while suspend/resume
noirq operation is performed to avoid hang
2. implement dev pm_ops for pcie tegra driver
3. use a backup buffer to save config space of
all pcie devices to avoid legacy PM calls.
Change-Id: I2d39f69a865b48e1e51ce2cd466e24007718a8b6
Signed-off-by: Jay Agarwal <jagarwal@nvidia.com>
Reviewed-on: http://git-master/r/90617
Reviewed-by: Automatic_Commit_Validation_User
Tested-by: Penny Chiu <pchiu@nvidia.com>
Reviewed-by: Varun Wadekar <vwadekar@nvidia.com>
Reviewed-by: Krishna Thota <kthota@nvidia.com>
Reviewed-by: Emily Jiang <ejiang@nvidia.com>
-rw-r--r-- | arch/arm/mach-tegra/pcie.c | 91 |
1 files changed, 85 insertions, 6 deletions
diff --git a/arch/arm/mach-tegra/pcie.c b/arch/arm/mach-tegra/pcie.c index b7d7eefe2661..b98d4892b5ee 100644 --- a/arch/arm/mach-tegra/pcie.c +++ b/arch/arm/mach-tegra/pcie.c @@ -33,6 +33,7 @@ #include <linux/clk.h> #include <linux/delay.h> #include <linux/msi.h> +#include <linux/slab.h> #include <linux/platform_device.h> #include <linux/regulator/consumer.h> @@ -343,6 +344,11 @@ static struct tegra_pcie_info tegra_pcie = { static struct resource pcie_io_space; static struct resource pcie_mem_space; static struct resource pcie_prefetch_mem_space; +/* disable read write while noirq operation + * is performed since pcie is powered off */ +static bool is_pcie_noirq_op = false; +/* used to backup config space registers of all pcie devices */ +static u32 *pbackup_config_space = NULL; void __iomem *tegra_pcie_io_base; EXPORT_SYMBOL(tegra_pcie_io_base); @@ -400,6 +406,10 @@ static int tegra_pcie_read_conf(struct pci_bus *bus, unsigned int devfn, struct tegra_pcie_port *pp = bus_to_port(bus->number); void __iomem *addr; + /* read reg is disabled without intr to avoid hang in suspend noirq */ + if (is_pcie_noirq_op) + return 0; + if (pp) { if (devfn != 0) { *val = 0xffffffff; @@ -432,6 +442,10 @@ static int tegra_pcie_write_conf(struct pci_bus *bus, unsigned int devfn, u32 mask; u32 tmp; + + /* write reg is disabled without intr to avoid hang in resume noirq */ + if (is_pcie_noirq_op) + return 0; /* pcie core is supposed to enable bus mastering and io/mem responses * if its not setting then enable corresponding bits in pci_command */ @@ -1185,19 +1199,78 @@ static int tegra_pci_probe(struct platform_device *pdev) return ret; } -static int tegra_pci_suspend(struct platform_device *pdev, pm_message_t state) +static int tegra_pci_suspend(struct device *dev) { + struct pci_dev *pdev = NULL; + int i, size, ndev = 0; + + for_each_pci_dev(pdev) { + /* save state of pcie devices before powering off regulators */ + pci_save_state(pdev); + size = sizeof(pdev->saved_config_space) / sizeof(u32); + ndev++; + } + + /* backup config space registers of all devices since it gets reset in + save state call from suspend noirq due to disabling of read in it */ + pbackup_config_space = kzalloc(ndev * size* sizeof(u32), GFP_KERNEL); + if (!pbackup_config_space) + return -ENODEV; + ndev = 0; + for_each_pci_dev(pdev) { + for (i = 0;i < size;i++) { + memcpy(&pbackup_config_space[i + size*ndev], + &pdev->saved_config_space[i], sizeof(u32)); + } + ndev++; + } + + /* disable read/write registers before powering off */ + is_pcie_noirq_op = true; + return tegra_pcie_power_off(); } +static int tegra_pci_resume_noirq(struct device *dev) +{ + struct pci_dev *pdev = NULL; + + for_each_pci_dev(pdev) { + /* set this flag to avoid restore state in resume noirq */ + pdev->state_saved = 0; + } + return 0; +} -static int tegra_pci_resume(struct platform_device *pdev) +static int tegra_pci_resume(struct device *dev) { int ret; + int i, size, ndev = 0; + struct pci_dev *pdev = NULL; ret = tegra_pcie_power_on(); tegra_pcie_enable_controller(); tegra_pcie_setup_translations(); + /* enable read/write registers after powering on */ + is_pcie_noirq_op = false; + + for_each_pci_dev(pdev) { + /* do fixup here for all dev's since not done in resume noirq */ + pci_fixup_device(pci_fixup_resume_early, pdev); + + /* set this flag to force restore state in resume */ + pdev->state_saved = 1; + + /* restore config space registers from backup buffer */ + size = sizeof(pdev->saved_config_space) / sizeof(u32); + for (i = 0;i < size;i++) { + memcpy(&pdev->saved_config_space[i], + &pbackup_config_space[i + size*ndev], sizeof(u32)); + } + ndev++; + } + kzfree(pbackup_config_space); + return ret; } @@ -1205,17 +1278,23 @@ static int tegra_pci_remove(struct platform_device *pdev) { return 0; } +#ifdef CONFIG_PM +static const struct dev_pm_ops tegra_pci_pm_ops = { + .suspend = tegra_pci_suspend, + .resume = tegra_pci_resume, + .resume_noirq = tegra_pci_resume_noirq, + }; +#endif static struct platform_driver tegra_pci_driver = { .probe = tegra_pci_probe, .remove = tegra_pci_remove, -#ifdef CONFIG_PM - .suspend = tegra_pci_suspend, - .resume = tegra_pci_resume, -#endif .driver = { .name = "tegra-pcie", .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &tegra_pci_pm_ops, +#endif }, }; |