[PATCH v2 26/45] soc/tegra: pmc: Utilize power-handler API to power off Nexus 7 properly

Dmitry Osipenko
2021-10-27T21:16:56Z

Nexus 7 Android tablet can be turned off using a special bootloader
command which is conveyed to bootloader by putting magic value into
specific scratch register and then rebooting normally. This power-off
method should be invoked if USB cable is connected. Bootloader then will
display battery status and power off the device. This behaviour is
borrowed from downstream kernel and matches user expectations, otherwise
it looks like device got hung during power-off and it may wake up on
USB disconnect.

Switch PMC driver to power-handler API, which provides drivers with
combined power-off+restart call chains functionality, replacing the
restart-only call chain API.

Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
drivers/soc/tegra/pmc.c | 54 +++++++++++++++++++++++++++--------------
1 file changed, 36 insertions(+), 18 deletions(-)

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index 575d6d5b4294..a01330099e1a 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -39,6 +39,7 @@
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_opp.h>
+#include <linux/power_supply.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/reset.h>
@@ -107,6 +108,7 @@
#define PMC_USB_DEBOUNCE_DEL 0xec
#define PMC_USB_AO 0xf0

+#define PMC_SCRATCH37 0x130
#define PMC_SCRATCH41 0x140

#define PMC_WAKE2_MASK 0x160
@@ -1064,10 +1066,8 @@ int tegra_pmc_cpu_remove_clamping(unsigned int cpuid)
return tegra_powergate_remove_clamping(id);
}

-static int tegra_pmc_restart_notify(struct notifier_block *this,
- unsigned long action, void *data)
+static void tegra_pmc_restart(const char *cmd)
{
- const char *cmd = data;
u32 value;

value = tegra_pmc_scratch_readl(pmc, pmc->soc->regs->scratch0);
@@ -1090,13 +1090,33 @@ static int tegra_pmc_restart_notify(struct notifier_block *this,
value = tegra_pmc_readl(pmc, PMC_CNTRL);
value |= PMC_CNTRL_MAIN_RST;
tegra_pmc_writel(pmc, value, PMC_CNTRL);
+}

- return NOTIFY_DONE;
+static void tegra_pmc_restart_handler(struct restart_data *data)
+{
+ tegra_pmc_restart(data->cmd);
}

-static struct notifier_block tegra_pmc_restart_handler = {
- .notifier_call = tegra_pmc_restart_notify,
- .priority = 128,
+static void tegra_pmc_power_off_handler(struct power_off_data *data)
+{
+ /*
+ * Reboot Nexus 7 into special bootloader mode if USB cable is
+ * connected in order to display battery status and power off.
+ */
+ if (of_machine_is_compatible("asus,grouper") &&
+ power_supply_is_system_supplied()) {
+ const u32 go_to_charger_mode = 0xa5a55a5a;
+
+ tegra_pmc_writel(pmc, go_to_charger_mode, PMC_SCRATCH37);
+ tegra_pmc_restart(NULL);
+ }
+}
+
+static struct power_handler tegra_pmc_power_handler = {
+ .restart_cb = tegra_pmc_restart_handler,
+ .power_off_cb = tegra_pmc_power_off_handler,
+ .power_off_priority = POWEROFF_PRIO_FIRMWARE,
+ .power_off_chaining_allowed = true,
};

static int powergate_show(struct seq_file *s, void *data)
@@ -2859,6 +2879,13 @@ static int tegra_pmc_probe(struct platform_device *pdev)
pmc->clk = NULL;
}

+ err = devm_register_power_handler(&pdev->dev, &tegra_pmc_power_handler);
+ if (err) {
+ dev_err(&pdev->dev, "unable to register power handler, %d\n",
+ err);
+ return err;
+ }
+
/*
* PCLK clock rate can't be retrieved using CLK API because it
* causes lockup if CPU enters LP2 idle state from some other
@@ -2890,20 +2917,13 @@ static int tegra_pmc_probe(struct platform_device *pdev)
goto cleanup_sysfs;
}

- err = register_restart_handler(&tegra_pmc_restart_handler);
- if (err) {
- dev_err(&pdev->dev, "unable to register restart handler, %d\n",
- err);
- goto cleanup_debugfs;
- }
-
err = tegra_pmc_pinctrl_init(pmc);
if (err)
- goto cleanup_restart_handler;
+ goto cleanup_debugfs;

err = tegra_pmc_regmap_init(pmc);
if (err < 0)
- goto cleanup_restart_handler;
+ goto cleanup_debugfs;

err = tegra_powergate_init(pmc, pdev->dev.of_node);
if (err < 0)
@@ -2926,8 +2946,6 @@ static int tegra_pmc_probe(struct platform_device *pdev)

cleanup_powergates:
tegra_powergate_remove_all(pdev->dev.of_node);
-cleanup_restart_handler:
- unregister_restart_handler(&tegra_pmc_restart_handler);
cleanup_debugfs:
debugfs_remove(pmc->debugfs);
cleanup_sysfs:
--
2.33.1