Commit e57a243f authored by Dmitry Osipenko's avatar Dmitry Osipenko Committed by Thierry Reding

soc/tegra: pmc: Query PCLK clock rate at probe time

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: default avatarDmitry Osipenko <digetx@gmail.com>
Acked-By: default avatarPeter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent 78380743
...@@ -324,6 +324,7 @@ static const char * const tegra210_reset_sources[] = { ...@@ -324,6 +324,7 @@ static const char * const tegra210_reset_sources[] = {
* @pctl_dev: pin controller exposed by the PMC * @pctl_dev: pin controller exposed by the PMC
* @domain: IRQ domain provided by the PMC * @domain: IRQ domain provided by the PMC
* @irq: chip implementation for the IRQ domain * @irq: chip implementation for the IRQ domain
* @clk_nb: pclk clock changes handler
*/ */
struct tegra_pmc { struct tegra_pmc {
struct device *dev; struct device *dev;
...@@ -359,6 +360,8 @@ struct tegra_pmc { ...@@ -359,6 +360,8 @@ struct tegra_pmc {
struct irq_domain *domain; struct irq_domain *domain;
struct irq_chip irq; struct irq_chip irq;
struct notifier_block clk_nb;
}; };
static struct tegra_pmc *pmc = &(struct tegra_pmc) { 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, ...@@ -1207,7 +1210,7 @@ static int tegra_io_pad_prepare(struct tegra_pmc *pmc, enum tegra_io_pad id,
return err; return err;
if (pmc->clk) { if (pmc->clk) {
rate = clk_get_rate(pmc->clk); rate = pmc->rate;
if (!rate) { if (!rate) {
dev_err(pmc->dev, "failed to get clock rate\n"); dev_err(pmc->dev, "failed to get clock rate\n");
return -ENODEV; return -ENODEV;
...@@ -1448,6 +1451,7 @@ void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode) ...@@ -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) void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
{ {
unsigned long long rate = 0; unsigned long long rate = 0;
u64 ticks;
u32 value; u32 value;
switch (mode) { switch (mode) {
...@@ -1456,7 +1460,7 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) ...@@ -1456,7 +1460,7 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
break; break;
case TEGRA_SUSPEND_LP2: case TEGRA_SUSPEND_LP2:
rate = clk_get_rate(pmc->clk); rate = pmc->rate;
break; break;
default: default:
...@@ -1466,21 +1470,15 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) ...@@ -1466,21 +1470,15 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
if (WARN_ON_ONCE(rate == 0)) if (WARN_ON_ONCE(rate == 0))
rate = 100000000; rate = 100000000;
if (rate != pmc->rate) { ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1;
u64 ticks; 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; ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1;
do_div(ticks, USEC_PER_SEC); do_div(ticks, USEC_PER_SEC);
tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER); tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER);
wmb(); wmb();
pmc->rate = rate;
}
value = tegra_pmc_readl(pmc, PMC_CNTRL); value = tegra_pmc_readl(pmc, PMC_CNTRL);
value &= ~PMC_CNTRL_SIDE_EFFECT_LP0; value &= ~PMC_CNTRL_SIDE_EFFECT_LP0;
...@@ -2140,6 +2138,33 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc) ...@@ -2140,6 +2138,33 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc)
return 0; 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) static int tegra_pmc_probe(struct platform_device *pdev)
{ {
void __iomem *base; void __iomem *base;
...@@ -2203,6 +2228,23 @@ static int tegra_pmc_probe(struct platform_device *pdev) ...@@ -2203,6 +2228,23 @@ static int tegra_pmc_probe(struct platform_device *pdev)
pmc->clk = NULL; 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; pmc->dev = &pdev->dev;
tegra_pmc_init(pmc); tegra_pmc_init(pmc);
...@@ -2254,6 +2296,8 @@ static int tegra_pmc_probe(struct platform_device *pdev) ...@@ -2254,6 +2296,8 @@ static int tegra_pmc_probe(struct platform_device *pdev)
cleanup_sysfs: cleanup_sysfs:
device_remove_file(&pdev->dev, &dev_attr_reset_reason); device_remove_file(&pdev->dev, &dev_attr_reset_reason);
device_remove_file(&pdev->dev, &dev_attr_reset_level); device_remove_file(&pdev->dev, &dev_attr_reset_level);
clk_notifier_unregister(pmc->clk, &pmc->clk_nb);
return err; return err;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment