Skip to content

Commit

Permalink
soc/tegra: pmc: Query PCLK clock rate at probe time
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Dmitry Osipenko authored and Thierry Reding committed Oct 29, 2019
1 parent 7838074 commit e57a243
Showing 1 changed file with 59 additions and 15 deletions.
74 changes: 59 additions & 15 deletions drivers/soc/tegra/pmc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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:
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -2254,6 +2296,8 @@ static int tegra_pmc_probe(struct platform_device *pdev)
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;
}

Expand Down

0 comments on commit e57a243

Please sign in to comment.