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:
parent
783807436f
commit
e57a243f5d
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue