soc/tegra: pmc: Query PCLK clock rate at probe time
authorDmitry Osipenko <digetx@gmail.com>
Thu, 26 Sep 2019 19:17:54 +0000 (22:17 +0300)
committerThierry Reding <treding@nvidia.com>
Tue, 29 Oct 2019 13:35:06 +0000 (14:35 +0100)
It is possible to get a lockup if kernel decides to enter LP2 cpuidle
from some clk-notifier, in that case CCF's "prepare" mutex is kept locked
and thus clk_get_rate(pclk) blocks on the same mutex with interrupts being
disabled, hanging machine.

Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
Acked-By: Peter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
drivers/soc/tegra/pmc.c

index c9b1baa..1f78e84 100644 (file)
@@ -324,6 +324,7 @@ static const char * const tegra210_reset_sources[] = {
  * @pctl_dev: pin controller exposed by the PMC
  * @domain: IRQ domain provided by the PMC
  * @irq: chip implementation for the IRQ domain
+ * @clk_nb: pclk clock changes handler
  */
 struct tegra_pmc {
        struct device *dev;
@@ -359,6 +360,8 @@ struct tegra_pmc {
 
        struct irq_domain *domain;
        struct irq_chip irq;
+
+       struct notifier_block clk_nb;
 };
 
 static struct tegra_pmc *pmc = &(struct tegra_pmc) {
@@ -1207,7 +1210,7 @@ static int tegra_io_pad_prepare(struct tegra_pmc *pmc, enum tegra_io_pad id,
                return err;
 
        if (pmc->clk) {
-               rate = clk_get_rate(pmc->clk);
+               rate = pmc->rate;
                if (!rate) {
                        dev_err(pmc->dev, "failed to get clock rate\n");
                        return -ENODEV;
@@ -1448,6 +1451,7 @@ void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode)
 void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
 {
        unsigned long long rate = 0;
+       u64 ticks;
        u32 value;
 
        switch (mode) {
@@ -1456,7 +1460,7 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
                break;
 
        case TEGRA_SUSPEND_LP2:
-               rate = clk_get_rate(pmc->clk);
+               rate = pmc->rate;
                break;
 
        default:
@@ -1466,21 +1470,15 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
        if (WARN_ON_ONCE(rate == 0))
                rate = 100000000;
 
-       if (rate != pmc->rate) {
-               u64 ticks;
-
-               ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1;
-               do_div(ticks, USEC_PER_SEC);
-               tegra_pmc_writel(pmc, ticks, PMC_CPUPWRGOOD_TIMER);
+       ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1;
+       do_div(ticks, USEC_PER_SEC);
+       tegra_pmc_writel(pmc, ticks, PMC_CPUPWRGOOD_TIMER);
 
-               ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1;
-               do_div(ticks, USEC_PER_SEC);
-               tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER);
+       ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1;
+       do_div(ticks, USEC_PER_SEC);
+       tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER);
 
-               wmb();
-
-               pmc->rate = rate;
-       }
+       wmb();
 
        value = tegra_pmc_readl(pmc, PMC_CNTRL);
        value &= ~PMC_CNTRL_SIDE_EFFECT_LP0;
@@ -2140,6 +2138,33 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc)
        return 0;
 }
 
+static int tegra_pmc_clk_notify_cb(struct notifier_block *nb,
+                                  unsigned long action, void *ptr)
+{
+       struct tegra_pmc *pmc = container_of(nb, struct tegra_pmc, clk_nb);
+       struct clk_notifier_data *data = ptr;
+
+       switch (action) {
+       case PRE_RATE_CHANGE:
+               mutex_lock(&pmc->powergates_lock);
+               break;
+
+       case POST_RATE_CHANGE:
+               pmc->rate = data->new_rate;
+               /* fall through */
+
+       case ABORT_RATE_CHANGE:
+               mutex_unlock(&pmc->powergates_lock);
+               break;
+
+       default:
+               WARN_ON_ONCE(1);
+               return notifier_from_errno(-EINVAL);
+       }
+
+       return NOTIFY_OK;
+}
+
 static int tegra_pmc_probe(struct platform_device *pdev)
 {
        void __iomem *base;
@@ -2203,6 +2228,23 @@ static int tegra_pmc_probe(struct platform_device *pdev)
                pmc->clk = NULL;
        }
 
+       /*
+        * PCLK clock rate can't be retrieved using CLK API because it
+        * causes lockup if CPU enters LP2 idle state from some other
+        * CLK notifier, hence we're caching the rate's value locally.
+        */
+       if (pmc->clk) {
+               pmc->clk_nb.notifier_call = tegra_pmc_clk_notify_cb;
+               err = clk_notifier_register(pmc->clk, &pmc->clk_nb);
+               if (err) {
+                       dev_err(&pdev->dev,
+                               "failed to register clk notifier\n");
+                       return err;
+               }
+
+               pmc->rate = clk_get_rate(pmc->clk);
+       }
+
        pmc->dev = &pdev->dev;
 
        tegra_pmc_init(pmc);
@@ -2254,6 +2296,8 @@ cleanup_debugfs:
 cleanup_sysfs:
        device_remove_file(&pdev->dev, &dev_attr_reset_reason);
        device_remove_file(&pdev->dev, &dev_attr_reset_level);
+       clk_notifier_unregister(pmc->clk, &pmc->clk_nb);
+
        return err;
 }