summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorDmitry Osipenko <digetx@gmail.com>2021-03-02 15:24:59 +0300
committerThierry Reding <treding@nvidia.com>2021-03-26 15:10:25 +0300
commitc45e66a6b9f40f2e95bc6d97fbf3daa1ebe88c6b (patch)
treee6a539ff6cca5c855e699e3551376515efbf63c4 /drivers
parent19221e3083020bd9537624caa0ee0145ed92ba36 (diff)
downloadlinux-c45e66a6b9f40f2e95bc6d97fbf3daa1ebe88c6b.tar.xz
soc/tegra: pmc: Fix completion of power-gate toggling
The SW-initiated power gate toggling is dropped by PMC if there is contention with a HW-initiated toggling, i.e. when one of CPU cores is gated by cpuidle driver. Software should retry the toggling after 10 microseconds on Tegra20/30 SoCs, hence add the retrying. On Tegra114+ the toggling method was changed in hardware, the TOGGLE_START bit indicates whether PMC is busy or could accept the command to toggle, hence handle that bit properly. The problem pops up after enabling dynamic power gating of 3D hardware, where 3D power domain fails to turn on/off "randomly". The programming sequence and quirks are documented in TRMs, but PMC driver obliviously re-used the Tegra20 logic for Tegra30+, which strikes back now. The 10 microseconds and other timeouts aren't documented in TRM, they are taken from downstream kernel. Link: https://nv-tegra.nvidia.com/gitweb/?p=linux-2.6.git;a=commit;h=311dd1c318b70e93bcefec15456a10ff2b9eb0ff Link: https://nv-tegra.nvidia.com/gitweb/?p=linux-3.10.git;a=commit;h=7f36693c47cb23730a6b2822e0975be65fb0c51d Tested-by: Peter Geis <pgwipeout@gmail.com> # Ouya T30 Tested-by: Nicolas Chauvet <kwizart@gmail.com> # PAZ00 T20 and TK1 T124 Tested-by: Matt Merhar <mattmerhar@protonmail.com> # Ouya T30 Signed-off-by: Dmitry Osipenko <digetx@gmail.com> Signed-off-by: Thierry Reding <treding@nvidia.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/soc/tegra/pmc.c70
1 files changed, 65 insertions, 5 deletions
diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index 7a77e8d06163..1c601dce1897 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -338,6 +338,8 @@ struct tegra_pmc_soc {
bool invert);
int (*irq_set_wake)(struct irq_data *data, unsigned int on);
int (*irq_set_type)(struct irq_data *data, unsigned int type);
+ int (*powergate_set)(struct tegra_pmc *pmc, unsigned int id,
+ bool new_state);
const char * const *reset_sources;
unsigned int num_reset_sources;
@@ -539,6 +541,63 @@ static int tegra_powergate_lookup(struct tegra_pmc *pmc, const char *name)
return -ENODEV;
}
+static int tegra20_powergate_set(struct tegra_pmc *pmc, unsigned int id,
+ bool new_state)
+{
+ unsigned int retries = 100;
+ bool status;
+ int ret;
+
+ /*
+ * As per TRM documentation, the toggle command will be dropped by PMC
+ * if there is contention with a HW-initiated toggling (i.e. CPU core
+ * power-gated), the command should be retried in that case.
+ */
+ do {
+ tegra_pmc_writel(pmc, PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
+
+ /* wait for PMC to execute the command */
+ ret = readx_poll_timeout(tegra_powergate_state, id, status,
+ status == new_state, 1, 10);
+ } while (ret == -ETIMEDOUT && retries--);
+
+ return ret;
+}
+
+static inline bool tegra_powergate_toggle_ready(struct tegra_pmc *pmc)
+{
+ return !(tegra_pmc_readl(pmc, PWRGATE_TOGGLE) & PWRGATE_TOGGLE_START);
+}
+
+static int tegra114_powergate_set(struct tegra_pmc *pmc, unsigned int id,
+ bool new_state)
+{
+ bool status;
+ int err;
+
+ /* wait while PMC power gating is contended */
+ err = readx_poll_timeout(tegra_powergate_toggle_ready, pmc, status,
+ status == true, 1, 100);
+ if (err)
+ return err;
+
+ tegra_pmc_writel(pmc, PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
+
+ /* wait for PMC to accept the command */
+ err = readx_poll_timeout(tegra_powergate_toggle_ready, pmc, status,
+ status == true, 1, 100);
+ if (err)
+ return err;
+
+ /* wait for PMC to execute the command */
+ err = readx_poll_timeout(tegra_powergate_state, id, status,
+ status == new_state, 10, 100000);
+ if (err)
+ return err;
+
+ return 0;
+}
+
/**
* tegra_powergate_set() - set the state of a partition
* @pmc: power management controller
@@ -548,7 +607,6 @@ static int tegra_powergate_lookup(struct tegra_pmc *pmc, const char *name)
static int tegra_powergate_set(struct tegra_pmc *pmc, unsigned int id,
bool new_state)
{
- bool status;
int err;
if (id == TEGRA_POWERGATE_3D && pmc->soc->has_gpu_clamps)
@@ -561,10 +619,7 @@ static int tegra_powergate_set(struct tegra_pmc *pmc, unsigned int id,
return 0;
}
- tegra_pmc_writel(pmc, PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
-
- err = readx_poll_timeout(tegra_powergate_state, id, status,
- status == new_state, 10, 100000);
+ err = pmc->soc->powergate_set(pmc, id, new_state);
mutex_unlock(&pmc->powergates_lock);
@@ -2786,6 +2841,7 @@ static const struct tegra_pmc_soc tegra20_pmc_soc = {
.regs = &tegra20_pmc_regs,
.init = tegra20_pmc_init,
.setup_irq_polarity = tegra20_pmc_setup_irq_polarity,
+ .powergate_set = tegra20_powergate_set,
.reset_sources = NULL,
.num_reset_sources = 0,
.reset_levels = NULL,
@@ -2845,6 +2901,7 @@ static const struct tegra_pmc_soc tegra30_pmc_soc = {
.regs = &tegra20_pmc_regs,
.init = tegra20_pmc_init,
.setup_irq_polarity = tegra20_pmc_setup_irq_polarity,
+ .powergate_set = tegra20_powergate_set,
.reset_sources = tegra30_reset_sources,
.num_reset_sources = ARRAY_SIZE(tegra30_reset_sources),
.reset_levels = NULL,
@@ -2900,6 +2957,7 @@ static const struct tegra_pmc_soc tegra114_pmc_soc = {
.regs = &tegra20_pmc_regs,
.init = tegra20_pmc_init,
.setup_irq_polarity = tegra20_pmc_setup_irq_polarity,
+ .powergate_set = tegra114_powergate_set,
.reset_sources = tegra30_reset_sources,
.num_reset_sources = ARRAY_SIZE(tegra30_reset_sources),
.reset_levels = NULL,
@@ -3015,6 +3073,7 @@ static const struct tegra_pmc_soc tegra124_pmc_soc = {
.regs = &tegra20_pmc_regs,
.init = tegra20_pmc_init,
.setup_irq_polarity = tegra20_pmc_setup_irq_polarity,
+ .powergate_set = tegra114_powergate_set,
.reset_sources = tegra30_reset_sources,
.num_reset_sources = ARRAY_SIZE(tegra30_reset_sources),
.reset_levels = NULL,
@@ -3139,6 +3198,7 @@ static const struct tegra_pmc_soc tegra210_pmc_soc = {
.regs = &tegra20_pmc_regs,
.init = tegra20_pmc_init,
.setup_irq_polarity = tegra20_pmc_setup_irq_polarity,
+ .powergate_set = tegra114_powergate_set,
.irq_set_wake = tegra210_pmc_irq_set_wake,
.irq_set_type = tegra210_pmc_irq_set_type,
.reset_sources = tegra210_reset_sources,