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

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
Loading
Loading
Loading
Loading
+59 −15
Original line number Diff line number Diff line
@@ -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,9 +1470,6 @@ 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);
@@ -1479,9 +1480,6 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)

	wmb();

		pmc->rate = rate;
	}

	value = tegra_pmc_readl(pmc, PMC_CNTRL);
	value &= ~PMC_CNTRL_SIDE_EFFECT_LP0;
	value |= PMC_CNTRL_CPU_PWRREQ_OE;
@@ -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;
}