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: Dmitry Osipenko <digetx@gmail.com>
Acked-By: Peter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
Dmitry Osipenko 2019-09-26 22:17:54 +03:00 committed by Thierry Reding
parent 783807436f
commit e57a243f5d
1 changed files with 59 additions and 15 deletions

View 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;
}