diff mbox

power: reset: Add MAX77620 support

Message ID 20170112161507.23774-1-thierry.reding@gmail.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Thierry Reding Jan. 12, 2017, 4:15 p.m. UTC
From: Thierry Reding <treding@nvidia.com>

The Maxim MAX77620 PMIC has the ability to power off and restart the
system. Add a driver that supports power off (via pm_power_off()) and
restart (via arm_pm_restart() on 32-bit and 64-bit ARM).

Based on work by Chaitanya Bandi <bandik@nvidia.com>.

Cc: Chaitanya Bandi <bandik@nvidia.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
---
 drivers/power/reset/Kconfig             |   6 ++
 drivers/power/reset/Makefile            |   1 +
 drivers/power/reset/max77620-poweroff.c | 146 ++++++++++++++++++++++++++++++++
 3 files changed, 153 insertions(+)
 create mode 100644 drivers/power/reset/max77620-poweroff.c

Comments

Laxman Dewangan Jan. 12, 2017, 4:36 p.m. UTC | #1
On Thursday 12 January 2017 09:45 PM, Thierry Reding wrote:
> From: Thierry Reding <treding@nvidia.com>
>
> The Maxim MAX77620 PMIC has the ability to power off and restart the
> system. Add a driver that supports power off (via pm_power_off()) and
> restart (via arm_pm_restart() on 32-bit and 64-bit ARM).
>
> Based on work by Chaitanya Bandi <bandik@nvidia.com>.
>
> Cc: Chaitanya Bandi <bandik@nvidia.com>
> Signed-off-by: Thierry Reding <treding@nvidia.com>
> ---
>   drivers/power/reset/Kconfig             |   6 ++
>   drivers/power/reset/Makefile            |   1 +
>   drivers/power/reset/max77620-poweroff.c | 146 ++++++++++++++++++++++++++++++++
>   3 files changed, 153 insertions(+)
>   create mode 100644 drivers/power/reset/max77620-poweroff.c
>
> diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
> index abeb77217a21..f0d0c20632f8 100644
> --- a/drivers/power/reset/Kconfig
> +++ b/drivers/power/reset/Kconfig
> @@ -98,6 +98,12 @@ config POWER_RESET_IMX
>   	  say N here or disable in dts to make sure pm_power_off never be
>   	  overwrote wrongly by this driver.
>   
> +config POWER_RESET_MAX77620
> +	bool "Maxim MAX77620 PMIC power-off driver"
> +	depends on MFD_MAX77620
> +	help
> +	  Power off and restart support for Maxim MAX77620 PMICs.
> +
>   config POWER_RESET_MSM
>   	bool "Qualcomm MSM power-off driver"
>   	depends on ARCH_QCOM
> diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
> index 11dae3b56ff9..74511d2f037a 100644
> --- a/drivers/power/reset/Makefile
> +++ b/drivers/power/reset/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
>   obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
>   obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
>   obj-$(CONFIG_POWER_RESET_IMX) += imx-snvs-poweroff.o
> +obj-$(CONFIG_POWER_RESET_MAX77620) += max77620-poweroff.o
>   obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
>   obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o
>   obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o
> diff --git a/drivers/power/reset/max77620-poweroff.c b/drivers/power/reset/max77620-poweroff.c
> new file mode 100644
> index 000000000000..4f2682d10925
> --- /dev/null
> +++ b/drivers/power/reset/max77620-poweroff.c
> @@ -0,0 +1,146 @@
> +/*
> + * Power off driver for Maxim MAX77620 device.
> + *
> + * Copyright (c) 2014-2016, NVIDIA CORPORATION.  All rights reserved.

Should we change year here?

> + *
> + * Based on work by Chaitanya Bandi <bandik@nvidia.com>.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation version 2.
> + *
> + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
> + * whether express or implied; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + */
> +
> +#include <linux/errno.h>
> +#include <linux/mfd/max77620.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/reboot.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
> +#include <asm/system_misc.h>
> +#endif
> +
> +struct max77620_power {
> +	struct regmap *regmap;
> +	struct device *dev;
> +};
> +
> +static struct max77620_power *system_power_controller = NULL;

As this is static and so already initialized as NULL. Hence we may not 
need to explicitly NULL initialization.
> +
> +static void max77620_pm_power_off(void)
> +{
> +	struct max77620_power *power = system_power_controller;
> +	unsigned int value;
> +	int err;
> +
> +	if (!power)
> +		return;
> +
> +	/* clear power key interrupts */
> +	err = regmap_read(power->regmap, MAX77620_REG_ONOFFIRQ, &value);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to clear power key interrupts: %d\n", err);
> +
> +	/* clear RTC interrupts */
> +	/*
> +	err = regmap_read(power->regmap, MAX77620_REG_RTCINT, &value);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to clear RTC interrupts: %d\n", err);
> +	*/
> +
> +	/* clear TOP interrupts */
> +	err = regmap_read(power->regmap, MAX77620_REG_IRQTOP, &value);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to clear interrupts: %d\n", err);
> +
> +	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG2,
> +				 MAX77620_ONOFFCNFG2_SFT_RST_WK, 0);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to clear SFT_RST_WK: %d\n", err);
> +
> +	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG1,
> +				 MAX77620_ONOFFCNFG1_SFT_RST,
> +				 MAX77620_ONOFFCNFG1_SFT_RST);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to set SFT_RST: %d\n", err);
> +}
> +
> +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
> +static void max77620_pm_restart(enum reboot_mode mode, const char *cmd)
> +{
> +	struct max77620_power *power = system_power_controller;
> +	int err;
> +
> +	if (!power)
> +		return;
> +
> +	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG2,
> +				 MAX77620_ONOFFCNFG2_SFT_RST_WK,
> +				 MAX77620_ONOFFCNFG2_SFT_RST_WK);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to set SFT_RST_WK: %d\n", err);
> +
> +	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG1,
> +				 MAX77620_ONOFFCNFG1_SFT_RST,
> +				 MAX77620_ONOFFCNFG1_SFT_RST);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to set SFT_RST: %d\n", err);
> +}
> +#endif
> +
> +static int max77620_poweroff_probe(struct platform_device *pdev)
> +{
> +	struct max77620_chip *max77620 = dev_get_drvdata(pdev->dev.parent);
> +	struct device_node *np = pdev->dev.parent->of_node;
> +	struct max77620_power *power;
> +	unsigned int value;
> +	int err;
> +
> +	if (!of_property_read_bool(np, "system-power-controller"))
> +		return 0;
> +
> +	power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
> +	if (!power)
> +		return -ENOMEM;
> +
> +	power->regmap = max77620->rmap;

We can use the function info->regmap = dev_get_regmap(parent, NULL); to 
get the regmap. This will make this driver independent of max77620 chip 
and extend same for other Maxim Chips.

> +	power->dev = &pdev->dev;
> +
> +	err = regmap_read(power->regmap, MAX77620_REG_NVERC, &value);
> +	if (err < 0) {
> +		dev_err(power->dev, "failed to read event recorder: %d\n", err);
> +		return err;
> +	}
> +
> +	dev_dbg(&pdev->dev, "event recorder: %#x\n", value);
> +
> +	system_power_controller = power;
> +	pm_power_off = max77620_pm_power_off;
> +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
> +	arm_pm_restart = max77620_pm_restart;

What if we want to reset via the Tegra and not through PMIC?
> +#endif
> +
> +	return 0;
> +}
> +
> +static struct platform_driver max77620_poweroff_driver = {
> +	.driver = {
> +		.name = "max77620-power",
> +	},
> +	.probe = max77620_poweroff_probe,
> +};
> +module_platform_driver(max77620_poweroff_driver);
> +
> +MODULE_DESCRIPTION("Maxim MAX77620 PMIC power off and restart driver");
> +MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
> +MODULE_ALIAS("platform:max77620-power");
> +MODULE_LICENSE("GPL v2");

--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Laxman Dewangan Jan. 12, 2017, 5:35 p.m. UTC | #2
On Thursday 12 January 2017 11:05 PM, Thierry Reding wrote:
> * PGP Signed by an unknown key
>
> On Thu, Jan 12, 2017 at 10:06:24PM +0530, Laxman Dewangan wrote:
>> On Thursday 12 January 2017 09:45 PM, Thierry Reding wrote:
>>>
>>> +	dev_dbg(&pdev->dev, "event recorder: %#x\n", value);
>>> +
>>> +	system_power_controller = power;
>>> +	pm_power_off = max77620_pm_power_off;
>>> +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
>>> +	arm_pm_restart = max77620_pm_restart;
>> What if we want to reset via the Tegra and not through PMIC?
> In that case I assume we either don't have a PMIC, or we should not add
> the system-power-controller device tree property to the PMIC node. In
> the latter case this driver won't install any power off or restart
> callbacks.


We need reset from Tegra and power of from PMIC. Tegra does not support 
power OFF.



--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thierry Reding Jan. 12, 2017, 5:35 p.m. UTC | #3
On Thu, Jan 12, 2017 at 10:06:24PM +0530, Laxman Dewangan wrote:
> 
> On Thursday 12 January 2017 09:45 PM, Thierry Reding wrote:
> > From: Thierry Reding <treding@nvidia.com>
> > 
> > The Maxim MAX77620 PMIC has the ability to power off and restart the
> > system. Add a driver that supports power off (via pm_power_off()) and
> > restart (via arm_pm_restart() on 32-bit and 64-bit ARM).
> > 
> > Based on work by Chaitanya Bandi <bandik@nvidia.com>.
> > 
> > Cc: Chaitanya Bandi <bandik@nvidia.com>
> > Signed-off-by: Thierry Reding <treding@nvidia.com>
> > ---
> >   drivers/power/reset/Kconfig             |   6 ++
> >   drivers/power/reset/Makefile            |   1 +
> >   drivers/power/reset/max77620-poweroff.c | 146 ++++++++++++++++++++++++++++++++
> >   3 files changed, 153 insertions(+)
> >   create mode 100644 drivers/power/reset/max77620-poweroff.c
> > 
> > diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
> > index abeb77217a21..f0d0c20632f8 100644
> > --- a/drivers/power/reset/Kconfig
> > +++ b/drivers/power/reset/Kconfig
> > @@ -98,6 +98,12 @@ config POWER_RESET_IMX
> >   	  say N here or disable in dts to make sure pm_power_off never be
> >   	  overwrote wrongly by this driver.
> > +config POWER_RESET_MAX77620
> > +	bool "Maxim MAX77620 PMIC power-off driver"
> > +	depends on MFD_MAX77620
> > +	help
> > +	  Power off and restart support for Maxim MAX77620 PMICs.
> > +
> >   config POWER_RESET_MSM
> >   	bool "Qualcomm MSM power-off driver"
> >   	depends on ARCH_QCOM
> > diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
> > index 11dae3b56ff9..74511d2f037a 100644
> > --- a/drivers/power/reset/Makefile
> > +++ b/drivers/power/reset/Makefile
> > @@ -9,6 +9,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
> >   obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
> >   obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
> >   obj-$(CONFIG_POWER_RESET_IMX) += imx-snvs-poweroff.o
> > +obj-$(CONFIG_POWER_RESET_MAX77620) += max77620-poweroff.o
> >   obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
> >   obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o
> >   obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o
> > diff --git a/drivers/power/reset/max77620-poweroff.c b/drivers/power/reset/max77620-poweroff.c
> > new file mode 100644
> > index 000000000000..4f2682d10925
> > --- /dev/null
> > +++ b/drivers/power/reset/max77620-poweroff.c
> > @@ -0,0 +1,146 @@
> > +/*
> > + * Power off driver for Maxim MAX77620 device.
> > + *
> > + * Copyright (c) 2014-2016, NVIDIA CORPORATION.  All rights reserved.
> 
> Should we change year here?

Heh, indeed. It's probably okay to even leave out the range and make
this 2017 because the current driver is fairly far removed from the one
downstream.

> > + *
> > + * Based on work by Chaitanya Bandi <bandik@nvidia.com>.
> > + *
> > + * This program is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU General Public License as
> > + * published by the Free Software Foundation version 2.
> > + *
> > + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
> > + * whether express or implied; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> > + * General Public License for more details.
> > + */
> > +
> > +#include <linux/errno.h>
> > +#include <linux/mfd/max77620.h>
> > +#include <linux/module.h>
> > +#include <linux/notifier.h>
> > +#include <linux/of.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/reboot.h>
> > +#include <linux/regmap.h>
> > +#include <linux/slab.h>
> > +
> > +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
> > +#include <asm/system_misc.h>
> > +#endif
> > +
> > +struct max77620_power {
> > +	struct regmap *regmap;
> > +	struct device *dev;
> > +};
> > +
> > +static struct max77620_power *system_power_controller = NULL;
> 
> As this is static and so already initialized as NULL. Hence we may not need
> to explicitly NULL initialization.

Yes, I'll drop the explicit assignment.

> > +static void max77620_pm_power_off(void)
> > +{
> > +	struct max77620_power *power = system_power_controller;
> > +	unsigned int value;
> > +	int err;
> > +
> > +	if (!power)
> > +		return;
> > +
> > +	/* clear power key interrupts */
> > +	err = regmap_read(power->regmap, MAX77620_REG_ONOFFIRQ, &value);
> > +	if (err < 0)
> > +		dev_err(power->dev, "failed to clear power key interrupts: %d\n", err);
> > +
> > +	/* clear RTC interrupts */
> > +	/*
> > +	err = regmap_read(power->regmap, MAX77620_REG_RTCINT, &value);
> > +	if (err < 0)
> > +		dev_err(power->dev, "failed to clear RTC interrupts: %d\n", err);
> > +	*/
> > +
> > +	/* clear TOP interrupts */
> > +	err = regmap_read(power->regmap, MAX77620_REG_IRQTOP, &value);
> > +	if (err < 0)
> > +		dev_err(power->dev, "failed to clear interrupts: %d\n", err);
> > +
> > +	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG2,
> > +				 MAX77620_ONOFFCNFG2_SFT_RST_WK, 0);
> > +	if (err < 0)
> > +		dev_err(power->dev, "failed to clear SFT_RST_WK: %d\n", err);
> > +
> > +	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG1,
> > +				 MAX77620_ONOFFCNFG1_SFT_RST,
> > +				 MAX77620_ONOFFCNFG1_SFT_RST);
> > +	if (err < 0)
> > +		dev_err(power->dev, "failed to set SFT_RST: %d\n", err);
> > +}
> > +
> > +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
> > +static void max77620_pm_restart(enum reboot_mode mode, const char *cmd)
> > +{
> > +	struct max77620_power *power = system_power_controller;
> > +	int err;
> > +
> > +	if (!power)
> > +		return;
> > +
> > +	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG2,
> > +				 MAX77620_ONOFFCNFG2_SFT_RST_WK,
> > +				 MAX77620_ONOFFCNFG2_SFT_RST_WK);
> > +	if (err < 0)
> > +		dev_err(power->dev, "failed to set SFT_RST_WK: %d\n", err);
> > +
> > +	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG1,
> > +				 MAX77620_ONOFFCNFG1_SFT_RST,
> > +				 MAX77620_ONOFFCNFG1_SFT_RST);
> > +	if (err < 0)
> > +		dev_err(power->dev, "failed to set SFT_RST: %d\n", err);
> > +}
> > +#endif
> > +
> > +static int max77620_poweroff_probe(struct platform_device *pdev)
> > +{
> > +	struct max77620_chip *max77620 = dev_get_drvdata(pdev->dev.parent);
> > +	struct device_node *np = pdev->dev.parent->of_node;
> > +	struct max77620_power *power;
> > +	unsigned int value;
> > +	int err;
> > +
> > +	if (!of_property_read_bool(np, "system-power-controller"))
> > +		return 0;
> > +
> > +	power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
> > +	if (!power)
> > +		return -ENOMEM;
> > +
> > +	power->regmap = max77620->rmap;
> 
> We can use the function info->regmap = dev_get_regmap(parent, NULL); to get
> the regmap. This will make this driver independent of max77620 chip and
> extend same for other Maxim Chips.

Good point. I'll make that change.

> > +	power->dev = &pdev->dev;
> > +
> > +	err = regmap_read(power->regmap, MAX77620_REG_NVERC, &value);
> > +	if (err < 0) {
> > +		dev_err(power->dev, "failed to read event recorder: %d\n", err);
> > +		return err;
> > +	}
> > +
> > +	dev_dbg(&pdev->dev, "event recorder: %#x\n", value);
> > +
> > +	system_power_controller = power;
> > +	pm_power_off = max77620_pm_power_off;
> > +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
> > +	arm_pm_restart = max77620_pm_restart;
> 
> What if we want to reset via the Tegra and not through PMIC?

In that case I assume we either don't have a PMIC, or we should not add
the system-power-controller device tree property to the PMIC node. In
the latter case this driver won't install any power off or restart
callbacks.

Thierry
Sebastian Reichel Jan. 13, 2017, 3:44 a.m. UTC | #4
Hi Thierry,

I have a few comments inline.

On Thu, Jan 12, 2017 at 05:15:07PM +0100, Thierry Reding wrote:
> The Maxim MAX77620 PMIC has the ability to power off and restart the
> system. Add a driver that supports power off (via pm_power_off()) and
> restart (via arm_pm_restart() on 32-bit and 64-bit ARM).
> 
> Based on work by Chaitanya Bandi <bandik@nvidia.com>.
> 
> Cc: Chaitanya Bandi <bandik@nvidia.com>
> Signed-off-by: Thierry Reding <treding@nvidia.com>
> ---
>  drivers/power/reset/Kconfig             |   6 ++
>  drivers/power/reset/Makefile            |   1 +
>  drivers/power/reset/max77620-poweroff.c | 146 ++++++++++++++++++++++++++++++++
>  3 files changed, 153 insertions(+)
>  create mode 100644 drivers/power/reset/max77620-poweroff.c
> 
> diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
> index abeb77217a21..f0d0c20632f8 100644
> --- a/drivers/power/reset/Kconfig
> +++ b/drivers/power/reset/Kconfig
> @@ -98,6 +98,12 @@ config POWER_RESET_IMX
>  	  say N here or disable in dts to make sure pm_power_off never be
>  	  overwrote wrongly by this driver.
>  
> +config POWER_RESET_MAX77620
> +	bool "Maxim MAX77620 PMIC power-off driver"
> +	depends on MFD_MAX77620
> +	help
> +	  Power off and restart support for Maxim MAX77620 PMICs.
> +
>  config POWER_RESET_MSM
>  	bool "Qualcomm MSM power-off driver"
>  	depends on ARCH_QCOM
> diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
> index 11dae3b56ff9..74511d2f037a 100644
> --- a/drivers/power/reset/Makefile
> +++ b/drivers/power/reset/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
>  obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
>  obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
>  obj-$(CONFIG_POWER_RESET_IMX) += imx-snvs-poweroff.o
> +obj-$(CONFIG_POWER_RESET_MAX77620) += max77620-poweroff.o
>  obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
>  obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o
>  obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o
> diff --git a/drivers/power/reset/max77620-poweroff.c b/drivers/power/reset/max77620-poweroff.c
> new file mode 100644
> index 000000000000..4f2682d10925
> --- /dev/null
> +++ b/drivers/power/reset/max77620-poweroff.c
> @@ -0,0 +1,146 @@
> +/*
> + * Power off driver for Maxim MAX77620 device.
> + *
> + * Copyright (c) 2014-2016, NVIDIA CORPORATION.  All rights reserved.
> + *
> + * Based on work by Chaitanya Bandi <bandik@nvidia.com>.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation version 2.
> + *
> + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
> + * whether express or implied; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + */
> +
> +#include <linux/errno.h>
> +#include <linux/mfd/max77620.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/reboot.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
> +#include <asm/system_misc.h>
> +#endif
> +
> +struct max77620_power {
> +	struct regmap *regmap;
> +	struct device *dev;
> +};
> +
> +static struct max77620_power *system_power_controller = NULL;
> +
> +static void max77620_pm_power_off(void)
> +{
> +	struct max77620_power *power = system_power_controller;
> +	unsigned int value;
> +	int err;
> +
> +	if (!power)
> +		return;
> +
> +	/* clear power key interrupts */
> +	err = regmap_read(power->regmap, MAX77620_REG_ONOFFIRQ, &value);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to clear power key interrupts: %d\n", err);
> +
> +	/* clear RTC interrupts */
> +	/*
> +	err = regmap_read(power->regmap, MAX77620_REG_RTCINT, &value);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to clear RTC interrupts: %d\n", err);
> +	*/
> +
> +	/* clear TOP interrupts */
> +	err = regmap_read(power->regmap, MAX77620_REG_IRQTOP, &value);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to clear interrupts: %d\n", err);
> +
> +	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG2,
> +				 MAX77620_ONOFFCNFG2_SFT_RST_WK, 0);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to clear SFT_RST_WK: %d\n", err);
> +
> +	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG1,
> +				 MAX77620_ONOFFCNFG1_SFT_RST,
> +				 MAX77620_ONOFFCNFG1_SFT_RST);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to set SFT_RST: %d\n", err);
> +}
> +
> +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
> +static void max77620_pm_restart(enum reboot_mode mode, const char *cmd)
> +{
> +	struct max77620_power *power = system_power_controller;
> +	int err;
> +
> +	if (!power)
> +		return;
> +
> +	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG2,
> +				 MAX77620_ONOFFCNFG2_SFT_RST_WK,
> +				 MAX77620_ONOFFCNFG2_SFT_RST_WK);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to set SFT_RST_WK: %d\n", err);
> +
> +	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG1,
> +				 MAX77620_ONOFFCNFG1_SFT_RST,
> +				 MAX77620_ONOFFCNFG1_SFT_RST);
> +	if (err < 0)
> +		dev_err(power->dev, "failed to set SFT_RST: %d\n", err);
> +}
> +#endif
> +
> +static int max77620_poweroff_probe(struct platform_device *pdev)
> +{
> +	struct max77620_chip *max77620 = dev_get_drvdata(pdev->dev.parent);
> +	struct device_node *np = pdev->dev.parent->of_node;
> +	struct max77620_power *power;
> +	unsigned int value;
> +	int err;

if (system_power_controller)
    return -EINVAL;

> +	if (!of_property_read_bool(np, "system-power-controller"))
> +		return 0;
> +
> +	power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
> +	if (!power)
> +		return -ENOMEM;
> +
> +	power->regmap = max77620->rmap;
> +	power->dev = &pdev->dev;
> +
> +	err = regmap_read(power->regmap, MAX77620_REG_NVERC, &value);
> +	if (err < 0) {
> +		dev_err(power->dev, "failed to read event recorder: %d\n", err);
> +		return err;
> +	}
> +
> +	dev_dbg(&pdev->dev, "event recorder: %#x\n", value);
> +
> +	system_power_controller = power;
> +	pm_power_off = max77620_pm_power_off;

> +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
> +	arm_pm_restart = max77620_pm_restart;
> +#endif

Please use register_restart_handler() instead. It has support for
priorities, is not arm specific and properly supports unregistering
(some handler with lower priority will take over). You can check
gpio-restart as an example for the API.

If you have some other arm_pm_restart handler (those are tried first)
I suggest to convert that to the restart handler framework. Actually
it may be a good idea to convert all of them and drop arm_pm_restart,
since there are only 4 left:

$ git grep "arm_pm_restart ="
drivers/firmware/psci.c:        arm_pm_restart = psci_sys_reset;
arch/arm/mach-prima2/rstc.c:    arm_pm_restart = sirfsoc_restart;
arch/arm/xen/enlighten.c:       arm_pm_restart = xen_restart;
arch/arm/kernel/setup.c:                arm_pm_restart = mdesc->restart;

The first 3 should be easy to update and the last one could
be solved by registering a wrapper function as restart handler,
which calls mdesc->restart().

> +	return 0;
> +}

This is missing a removal function, which should unregister the
restart handler and do something like this:

if (pm_power_off == max77620_pm_power_off)
    pm_power_off = NULL;

> +static struct platform_driver max77620_poweroff_driver = {
> +	.driver = {
> +		.name = "max77620-power",
> +	},
> +	.probe = max77620_poweroff_probe,
> +};
> +module_platform_driver(max77620_poweroff_driver);
> +
> +MODULE_DESCRIPTION("Maxim MAX77620 PMIC power off and restart driver");
> +MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
> +MODULE_ALIAS("platform:max77620-power");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.11.0

-- Sebastian
Thierry Reding Jan. 19, 2017, 12:23 p.m. UTC | #5
On Fri, Jan 13, 2017 at 04:44:25AM +0100, Sebastian Reichel wrote:
> On Thu, Jan 12, 2017 at 05:15:07PM +0100, Thierry Reding wrote:
[...]
> > +	if (!of_property_read_bool(np, "system-power-controller"))
> > +		return 0;
> > +
> > +	power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
> > +	if (!power)
> > +		return -ENOMEM;
> > +
> > +	power->regmap = max77620->rmap;
> > +	power->dev = &pdev->dev;
> > +
> > +	err = regmap_read(power->regmap, MAX77620_REG_NVERC, &value);
> > +	if (err < 0) {
> > +		dev_err(power->dev, "failed to read event recorder: %d\n", err);
> > +		return err;
> > +	}
> > +
> > +	dev_dbg(&pdev->dev, "event recorder: %#x\n", value);
> > +
> > +	system_power_controller = power;
> > +	pm_power_off = max77620_pm_power_off;
> 
> > +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
> > +	arm_pm_restart = max77620_pm_restart;
> > +#endif
> 
> Please use register_restart_handler() instead. It has support for
> priorities, is not arm specific and properly supports unregistering
> (some handler with lower priority will take over). You can check
> gpio-restart as an example for the API.
> 
> If you have some other arm_pm_restart handler (those are tried first)
> I suggest to convert that to the restart handler framework. Actually
> it may be a good idea to convert all of them and drop arm_pm_restart,
> since there are only 4 left:
> 
> $ git grep "arm_pm_restart ="
> drivers/firmware/psci.c:        arm_pm_restart = psci_sys_reset;
> arch/arm/mach-prima2/rstc.c:    arm_pm_restart = sirfsoc_restart;
> arch/arm/xen/enlighten.c:       arm_pm_restart = xen_restart;
> arch/arm/kernel/setup.c:                arm_pm_restart = mdesc->restart;
> 
> The first 3 should be easy to update and the last one could
> be solved by registering a wrapper function as restart handler,
> which calls mdesc->restart().

I remember this not being the first time that this confuses me. And from
looking around a little it seems like I'm not the only one.

Do you know if there's ever been any attempts to formalize all of this
by adding some sort of framework for this? That way some of the
confusion may be removed and architecture-specific bits could be
eliminated more easily.

Thierry
Thierry Reding Jan. 19, 2017, 12:27 p.m. UTC | #6
On Thu, Jan 12, 2017 at 11:05:05PM +0530, Laxman Dewangan wrote:
> 
> On Thursday 12 January 2017 11:05 PM, Thierry Reding wrote:
> > * PGP Signed by an unknown key
> > 
> > On Thu, Jan 12, 2017 at 10:06:24PM +0530, Laxman Dewangan wrote:
> > > On Thursday 12 January 2017 09:45 PM, Thierry Reding wrote:
> > > > 
> > > > +	dev_dbg(&pdev->dev, "event recorder: %#x\n", value);
> > > > +
> > > > +	system_power_controller = power;
> > > > +	pm_power_off = max77620_pm_power_off;
> > > > +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
> > > > +	arm_pm_restart = max77620_pm_restart;
> > > What if we want to reset via the Tegra and not through PMIC?
> > In that case I assume we either don't have a PMIC, or we should not add
> > the system-power-controller device tree property to the PMIC node. In
> > the latter case this driver won't install any power off or restart
> > callbacks.
> 
> 
> We need reset from Tegra and power of from PMIC. Tegra does not support
> power OFF.

Can you explain in more details what exactly the combination is that we
need to support? Why can't the PMIC both be the reset and power off
controller? Are there any advantages to resetting with the SoC rather
than the PMIC?

Thierry
Sebastian Reichel Jan. 19, 2017, 11 p.m. UTC | #7
Hi Thierry,

> > > [...]
> >
> > Please use register_restart_handler() instead. It has support for
> > priorities, is not arm specific and properly supports unregistering
> > (some handler with lower priority will take over). You can check
> > gpio-restart as an example for the API.
> > 
> > If you have some other arm_pm_restart handler (those are tried first)
> > I suggest to convert that to the restart handler framework. Actually
> > it may be a good idea to convert all of them and drop arm_pm_restart,
> > since there are only 4 left:
> > 
> > $ git grep "arm_pm_restart ="
> > drivers/firmware/psci.c:        arm_pm_restart = psci_sys_reset;
> > arch/arm/mach-prima2/rstc.c:    arm_pm_restart = sirfsoc_restart;
> > arch/arm/xen/enlighten.c:       arm_pm_restart = xen_restart;
> > arch/arm/kernel/setup.c:                arm_pm_restart = mdesc->restart;
> > 
> > The first 3 should be easy to update and the last one could
> > be solved by registering a wrapper function as restart handler,
> > which calls mdesc->restart().
> 
> I remember this not being the first time that this confuses me. And from
> looking around a little it seems like I'm not the only one.
> 
> Do you know if there's ever been any attempts to formalize all of this
> by adding some sort of framework for this? That way some of the
> confusion may be removed and architecture-specific bits could be
> eliminated more easily.

We have a framework for restart handlers, which has been written
by Guenter Roeck [0] in 2014. You just have to call register_restart_handler()
during probe and unregister_restart_handler() during removal of
your reboot driver. All reboot drivers in drivers/power/reset use
that framework.

The restart handlers are invoked by calling do_kernel_restart()
based on their configured priority. That function is called by
the architecture's machine_restart() function. It's currently
used by mips, ppc, arm & arm64 as far as I can see. ARM's
implementation looks like this:

if (arm_pm_restart)
	arm_pm_restart()
else
	do_kernel_restart() /* reboot handler framework */

No such thing exists for poweroff. Guenter also wrote something for
that [1], but Linus intervened [2]. Anyways, pm_power_off is at
least architecture independent.

[0] https://lwn.net/Articles/605298/
[1] https://lwn.net/Articles/617345/
[2] http://lists.infradead.org/pipermail/linux-arm-kernel/2015-June/351369.html

-- Sebastian
Guenter Roeck Jan. 19, 2017, 11:29 p.m. UTC | #8
On Fri, Jan 20, 2017 at 12:00:36AM +0100, Sebastian Reichel wrote:
> Hi Thierry,
> 
> > > > [...]
> > >
> > > Please use register_restart_handler() instead. It has support for
> > > priorities, is not arm specific and properly supports unregistering
> > > (some handler with lower priority will take over). You can check
> > > gpio-restart as an example for the API.
> > > 
> > > If you have some other arm_pm_restart handler (those are tried first)
> > > I suggest to convert that to the restart handler framework. Actually
> > > it may be a good idea to convert all of them and drop arm_pm_restart,
> > > since there are only 4 left:
> > > 
> > > $ git grep "arm_pm_restart ="
> > > drivers/firmware/psci.c:        arm_pm_restart = psci_sys_reset;
> > > arch/arm/mach-prima2/rstc.c:    arm_pm_restart = sirfsoc_restart;
> > > arch/arm/xen/enlighten.c:       arm_pm_restart = xen_restart;
> > > arch/arm/kernel/setup.c:                arm_pm_restart = mdesc->restart;
> > > 
> > > The first 3 should be easy to update and the last one could
> > > be solved by registering a wrapper function as restart handler,
> > > which calls mdesc->restart().
> > 
> > I remember this not being the first time that this confuses me. And from
> > looking around a little it seems like I'm not the only one.
> > 
> > Do you know if there's ever been any attempts to formalize all of this
> > by adding some sort of framework for this? That way some of the
> > confusion may be removed and architecture-specific bits could be
> > eliminated more easily.
> 
> We have a framework for restart handlers, which has been written
> by Guenter Roeck [0] in 2014. You just have to call register_restart_handler()
> during probe and unregister_restart_handler() during removal of
> your reboot driver. All reboot drivers in drivers/power/reset use
> that framework.
> 
> The restart handlers are invoked by calling do_kernel_restart()
> based on their configured priority. That function is called by
> the architecture's machine_restart() function. It's currently
> used by mips, ppc, arm & arm64 as far as I can see. ARM's
> implementation looks like this:
> 
> if (arm_pm_restart)
> 	arm_pm_restart()
> else
> 	do_kernel_restart() /* reboot handler framework */
> 
I actually have a set of patches somewhere which transforms the remaining
direct users of arm_pm_restart to use the framework (unless I removed it
from my trees - I don't really recall). I just never got around to submit
it, and then [2] happened and I lost interest.

> No such thing exists for poweroff. Guenter also wrote something for
> that [1], but Linus intervened [2]. Anyways, pm_power_off is at
> least architecture independent.
> 
That was by far the most frustrating kernel development experience
I ever head :-(.

Guenter

> [0] https://lwn.net/Articles/605298/
> [1] https://lwn.net/Articles/617345/
> [2] http://lists.infradead.org/pipermail/linux-arm-kernel/2015-June/351369.html
> 
> -- Sebastian


--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thierry Reding Jan. 20, 2017, 8:38 a.m. UTC | #9
On Thu, Jan 19, 2017 at 03:29:34PM -0800, Guenter Roeck wrote:
> On Fri, Jan 20, 2017 at 12:00:36AM +0100, Sebastian Reichel wrote:
> > Hi Thierry,
> > 
> > > > > [...]
> > > >
> > > > Please use register_restart_handler() instead. It has support for
> > > > priorities, is not arm specific and properly supports unregistering
> > > > (some handler with lower priority will take over). You can check
> > > > gpio-restart as an example for the API.
> > > > 
> > > > If you have some other arm_pm_restart handler (those are tried first)
> > > > I suggest to convert that to the restart handler framework. Actually
> > > > it may be a good idea to convert all of them and drop arm_pm_restart,
> > > > since there are only 4 left:
> > > > 
> > > > $ git grep "arm_pm_restart ="
> > > > drivers/firmware/psci.c:        arm_pm_restart = psci_sys_reset;
> > > > arch/arm/mach-prima2/rstc.c:    arm_pm_restart = sirfsoc_restart;
> > > > arch/arm/xen/enlighten.c:       arm_pm_restart = xen_restart;
> > > > arch/arm/kernel/setup.c:                arm_pm_restart = mdesc->restart;
> > > > 
> > > > The first 3 should be easy to update and the last one could
> > > > be solved by registering a wrapper function as restart handler,
> > > > which calls mdesc->restart().
> > > 
> > > I remember this not being the first time that this confuses me. And from
> > > looking around a little it seems like I'm not the only one.
> > > 
> > > Do you know if there's ever been any attempts to formalize all of this
> > > by adding some sort of framework for this? That way some of the
> > > confusion may be removed and architecture-specific bits could be
> > > eliminated more easily.
> > 
> > We have a framework for restart handlers, which has been written
> > by Guenter Roeck [0] in 2014. You just have to call register_restart_handler()
> > during probe and unregister_restart_handler() during removal of
> > your reboot driver. All reboot drivers in drivers/power/reset use
> > that framework.
> > 
> > The restart handlers are invoked by calling do_kernel_restart()
> > based on their configured priority. That function is called by
> > the architecture's machine_restart() function. It's currently
> > used by mips, ppc, arm & arm64 as far as I can see. ARM's
> > implementation looks like this:
> > 
> > if (arm_pm_restart)
> > 	arm_pm_restart()
> > else
> > 	do_kernel_restart() /* reboot handler framework */
> > 
> I actually have a set of patches somewhere which transforms the remaining
> direct users of arm_pm_restart to use the framework (unless I removed it
> from my trees - I don't really recall). I just never got around to submit
> it, and then [2] happened and I lost interest.
> 
> > No such thing exists for poweroff. Guenter also wrote something for
> > that [1], but Linus intervened [2]. Anyways, pm_power_off is at
> > least architecture independent.
> > 
> That was by far the most frustrating kernel development experience
> I ever head :-(.

It was a little difficult to track down all the discussion around this,
but reading through what I could find, I can understand why this would
have been frustrating. You obviously spent an enormous amount of work
only to get it shot down.

I have to say that I have similar concerns to those voiced by Linus and
Rafael. Notifier chains and priority seem too little restrictive for
this kind of functionality, in my opinion. I think those concerns could
be removed if things got formalized using a framework.

Without having spent the amount of time on the topic that you have, the
following straw-man proposal is perhaps a little naive:

	struct system_power_controller;

	struct system_power_ops {
		int (*power_off)(struct system_power_controller *spc);
		int (*restart)(struct system_power_controller *spc,
			       enum reboot_mode mode,
			       const char *cmd);
	};

	struct system_power_controller {
		struct device *dev;
		const struct system_power_ops *ops;
		struct list_head list;
		...
	};

	int system_power_controller_add(struct system_power_controller *spc);
	void system_power_controller_remove(struct system_power_controller *spc);

	int system_power_off(void);
	int system_restart(enum reboot_mode mode, const char *cmd);

The above is based on what other subsystems use. Drivers would embed the
struct system_power_controller in a driver-specific data structure and
use container_of() to get at that from the callbacks.

Controllers can be added and removed to the subsystem. Internally it may
be worth to keep all of the controllers in a list, but only use the most
appropriate ones. The above would need some sort of flag or type list
that drivers can set in addition to operations to indicate what your
proposal had done via priorities. I'm thinking of something along the
lines of:

	enum system_power_controller_type {
		SYSTEM_POWER_CONTROLLER_TYPE_CPU,
		SYSTEM_POWER_CONTROLLER_TYPE_SOC,
		SYSTEM_POWER_CONTROLLER_TYPE_BOARD,
	};

	struct system_power_controller {
		...
		enum system_power_controller_type type;
		...
	};

There's a fairly natural ordering in the above "levels". Obviously the
board-level controller would be highest priority because it controls
power or reset of the complete board. This would likely be implemented
by GPIO-based or PMIC type of drivers. SoC-level controllers would use
mechanisms provided by the SoC in order to power off or reset only the
SoC. This is obviously lower priority than board-level because some of
the components on the board may end up remaining powered on or not
getting reset. But it could be a useful fallback in case a board-level
controller isn't present or fails. Finally there's the CPU-level code
that would most likely be implemented by architecture code. In the most
basic version this could be a WFI kind of instruction, or a busy loop,
or perhaps even a simple call to panic().

One complication might be that there are things like PSCI that have an
architecture-specific implementation (CPU-level) but could actually be
a board-level "controller".

To keep things simple, I think it would be okay to allow only one of
each type of controller in any running system. It's very unlikely that
board designers would devise two different ways of powering off or
restarting a system, while in a similar way an SoC or CPU would only
ever provide one way to do so. Even if theoretically multiple
possibilities exist, I think the board code should pick which ones are
appropriate.

Another missing feature in the above is that sometimes a mechanism
exists to record in persistent storage (registers, memory, ...) what
kind of reset was executed and which boot code will inspect and use to
run different paths during boot. One such example is Tegra where the
PMC has "scratch" registers that don't get reset on reboot. Software
sets some of the bits to enable things like forced-recovery mode or
Android-type recovery booting. I'm sure other similar mechanisms exist
on other SoCs. Upon board-level reset, we would still want to have the
SoC-level reset prep code execute, but not reset the SoC, otherwise
the board-level code wouldn't have a chance of running anymore. This
could possibly be solved by adding more fine-grained operations in the
struct system_power_ops.

One other possible solution that comes to mind is that system power
controllers could be explicitly stacked. This would be complicated to
achieve in code, but I'm thinking it could have interesting use-cases
in device tree based systems, for example. I'm just brainstorming here,
this may not be a good idea at all:

	pmc: pmc@... {
		system-power-controller;
	};

	i2c@... {
		pmic: pmic@... {
			...
			system-power-parent = <&pmc>;
			...
		};
	};

The PMIC would in the above call into PMC in order to reset on a SoC
level before performing the board-level reset. I suppose it could use
a separate callback (->prepare({RESET,POWER})) instead of the usual
->restart() and ->power_off().

Does the above sound at all sane to you, given your broad experience
in this field? Anything I missed that I'd need to consider in a design
for such a framework?

Sebastian, any thoughts from you on the above?

Thierry
Sebastian Reichel Jan. 20, 2017, 12:44 p.m. UTC | #10
Hi,

On Thu, Jan 19, 2017 at 03:29:34PM -0800, Guenter Roeck wrote:
> On Fri, Jan 20, 2017 at 12:00:36AM +0100, Sebastian Reichel wrote:
> I actually have a set of patches somewhere which transforms the remaining
> direct users of arm_pm_restart to use the framework (unless I removed it
> from my trees - I don't really recall). I just never got around to submit
> it, and then [2] happened and I lost interest.

I found a patchset from you from 2016. I see a few Acks and no
negative feedback. Looks like they only need to be uploaded at
Russel's patch system?

arch/arm/mach-prima2/rstc.c			https://patchwork.kernel.org/patch/8844441/
arch/arm/xen/enlighten.c			https://patchwork.kernel.org/patch/8844481/
drivers/firmware/psci.c				https://patchwork.kernel.org/patch/8844411/
arch/arm/kernel/setup.c				https://patchwork.kernel.org/patch/8844451/
removal of ARM64 arm_pm_restart		https://patchwork.kernel.org/patch/8844471/
removal of ARM32 arm_pm_restart		https://patchwork.kernel.org/patch/8844391/

> > No such thing exists for poweroff. Guenter also wrote something for
> > that [1], but Linus intervened [2]. Anyways, pm_power_off is at
> > least architecture independent.
> > 
> That was by far the most frustrating kernel development experience
> I ever head :-(.

Sorry for the reminder.

-- Sebastian
Thierry Reding Jan. 20, 2017, 1:11 p.m. UTC | #11
On Fri, Jan 20, 2017 at 01:44:04PM +0100, Sebastian Reichel wrote:
> Hi,
> 
> On Thu, Jan 19, 2017 at 03:29:34PM -0800, Guenter Roeck wrote:
> > On Fri, Jan 20, 2017 at 12:00:36AM +0100, Sebastian Reichel wrote:
> > I actually have a set of patches somewhere which transforms the remaining
> > direct users of arm_pm_restart to use the framework (unless I removed it
> > from my trees - I don't really recall). I just never got around to submit
> > it, and then [2] happened and I lost interest.
> 
> I found a patchset from you from 2016. I see a few Acks and no
> negative feedback. Looks like they only need to be uploaded at
> Russel's patch system?
> 
> arch/arm/mach-prima2/rstc.c			https://patchwork.kernel.org/patch/8844441/
> arch/arm/xen/enlighten.c			https://patchwork.kernel.org/patch/8844481/
> drivers/firmware/psci.c				https://patchwork.kernel.org/patch/8844411/
> arch/arm/kernel/setup.c				https://patchwork.kernel.org/patch/8844451/
> removal of ARM64 arm_pm_restart		https://patchwork.kernel.org/patch/8844471/
> removal of ARM32 arm_pm_restart		https://patchwork.kernel.org/patch/8844391/

Nice, those would be excellent preparatory work for my earlier framework
proposal.

Do you want me to pick those up and run with them a bit?

Thierry
Sebastian Reichel Jan. 20, 2017, 1:47 p.m. UTC | #12
Hi,

On Fri, Jan 20, 2017 at 02:11:16PM +0100, Thierry Reding wrote:
> On Fri, Jan 20, 2017 at 01:44:04PM +0100, Sebastian Reichel wrote:
> > Hi,
> > 
> > On Thu, Jan 19, 2017 at 03:29:34PM -0800, Guenter Roeck wrote:
> > > On Fri, Jan 20, 2017 at 12:00:36AM +0100, Sebastian Reichel wrote:
> > > I actually have a set of patches somewhere which transforms the remaining
> > > direct users of arm_pm_restart to use the framework (unless I removed it
> > > from my trees - I don't really recall). I just never got around to submit
> > > it, and then [2] happened and I lost interest.
> > 
> > I found a patchset from you from 2016. I see a few Acks and no
> > negative feedback. Looks like they only need to be uploaded at
> > Russel's patch system?
> > 
> > arch/arm/mach-prima2/rstc.c			https://patchwork.kernel.org/patch/8844441/
> > arch/arm/xen/enlighten.c			https://patchwork.kernel.org/patch/8844481/
> > drivers/firmware/psci.c				https://patchwork.kernel.org/patch/8844411/
> > arch/arm/kernel/setup.c				https://patchwork.kernel.org/patch/8844451/
> > removal of ARM64 arm_pm_restart		https://patchwork.kernel.org/patch/8844471/
> > removal of ARM32 arm_pm_restart		https://patchwork.kernel.org/patch/8844391/
> 
> Nice, those would be excellent preparatory work for my earlier
> framework proposal. Do you want me to pick those up and run with
> them a bit?

They implement what I suggested earlier in this thread, so I think
they should get upstream. If you pick them up, you can add my
Reviewed-By to all patches. After having them upstream, ARM, ARM64,
MIPS and PPC use do_kernel_restart() for reboot.

-- Sebastian
Guenter Roeck Jan. 20, 2017, 2:32 p.m. UTC | #13
On 01/20/2017 05:11 AM, Thierry Reding wrote:
> On Fri, Jan 20, 2017 at 01:44:04PM +0100, Sebastian Reichel wrote:
>> Hi,
>>
>> On Thu, Jan 19, 2017 at 03:29:34PM -0800, Guenter Roeck wrote:
>>> On Fri, Jan 20, 2017 at 12:00:36AM +0100, Sebastian Reichel wrote:
>>> I actually have a set of patches somewhere which transforms the remaining
>>> direct users of arm_pm_restart to use the framework (unless I removed it
>>> from my trees - I don't really recall). I just never got around to submit
>>> it, and then [2] happened and I lost interest.
>>
>> I found a patchset from you from 2016. I see a few Acks and no
>> negative feedback. Looks like they only need to be uploaded at
>> Russel's patch system?
>>
>> arch/arm/mach-prima2/rstc.c			https://patchwork.kernel.org/patch/8844441/
>> arch/arm/xen/enlighten.c			https://patchwork.kernel.org/patch/8844481/
>> drivers/firmware/psci.c				https://patchwork.kernel.org/patch/8844411/
>> arch/arm/kernel/setup.c				https://patchwork.kernel.org/patch/8844451/
>> removal of ARM64 arm_pm_restart		https://patchwork.kernel.org/patch/8844471/
>> removal of ARM32 arm_pm_restart		https://patchwork.kernel.org/patch/8844391/
>
> Nice, those would be excellent preparatory work for my earlier framework
> proposal.
>
> Do you want me to pick those up and run with them a bit?
>
> Thierry
>

I didn't recall that I ever submitted them. They are all yours ...

Guenter

--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Guenter Roeck Jan. 20, 2017, 5:53 p.m. UTC | #14
On Fri, Jan 20, 2017 at 09:38:04AM +0100, Thierry Reding wrote:
> > 
> > > No such thing exists for poweroff. Guenter also wrote something for
> > > that [1], but Linus intervened [2]. Anyways, pm_power_off is at
> > > least architecture independent.
> > > 
> > That was by far the most frustrating kernel development experience
> > I ever head :-(.
> 
> It was a little difficult to track down all the discussion around this,
> but reading through what I could find, I can understand why this would
> have been frustrating. You obviously spent an enormous amount of work
> only to get it shot down.
> 
> I have to say that I have similar concerns to those voiced by Linus and
> Rafael. Notifier chains and priority seem too little restrictive for
> this kind of functionality, in my opinion. I think those concerns could
> be removed if things got formalized using a framework.
> 

Exactly the same argument applies to the restart handler. I am just glad
I got it in before the objections came up.

I do understand the concerns, which is why I tried to accommodate them
and come up with another solution. However, it boils down to the following:

- Priorities are needed, no matter how they are called.
- Data structures have to be allocated statically, since the code
  still needs to work if the system is out of memory.
- Data structurs need to be maintained in a prioritized list or similar,
  to be able to handle insertions and removals at will, and to be able
  to determine which function should be called first.

So ultimately, no matter what you do, you'll end up re-implementing
parts of the notifier code.

To me the argument, ultimately, boils down to arguing with Amazon that they
should always use the smallest possible packaging (and not ship flash drives
in such a large box that it is hard to find). Just like Amazon has good reasons
to standardize on a limited number of box sizes, an argument can be made to
have only a limited amount of infrastructure code. If some infrastructure code
is "too little restrictive", but does the job, so be it. That should not be
a reason to have separate and highly specialized infrastructure for everything,
just to make it a 100% fit.

That is, however, my personal opinion. If people want to have and implement
a large amount of highly specialized infrastructure instead of more generic
(and less restrictive) infrastructure, so be it. Just don't expect _me_
to implement it.

> Without having spent the amount of time on the topic that you have, the
> following straw-man proposal is perhaps a little naive:
> 
> 	struct system_power_controller;
> 
> 	struct system_power_ops {
> 		int (*power_off)(struct system_power_controller *spc);
> 		int (*restart)(struct system_power_controller *spc,
> 			       enum reboot_mode mode,
> 			       const char *cmd);
> 	};
> 
> 	struct system_power_controller {
> 		struct device *dev;
> 		const struct system_power_ops *ops;
> 		struct list_head list;
> 		...
> 	};
> 
> 	int system_power_controller_add(struct system_power_controller *spc);
> 	void system_power_controller_remove(struct system_power_controller *spc);
> 
> 	int system_power_off(void);
> 	int system_restart(enum reboot_mode mode, const char *cmd);
> 
It seems to me that restart and power off are inherently independent of each
other. I would personally not try to unify them. Having said that, that is my
personal opinion only. I would for sure not object to anything in this area
(nor get involved - once was enough ;-).

Thanks,
Guenter
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Sebastian Reichel Jan. 29, 2017, 8:02 p.m. UTC | #15
Hi,

On Fri, Jan 20, 2017 at 09:38:04AM +0100, Thierry Reding wrote:
> On Thu, Jan 19, 2017 at 03:29:34PM -0800, Guenter Roeck wrote:
> > On Fri, Jan 20, 2017 at 12:00:36AM +0100, Sebastian Reichel wrote:
> > > Hi Thierry,
> > > 
> > > > > > [...]
> > > > >
> > > > > Please use register_restart_handler() instead. It has support for
> > > > > priorities, is not arm specific and properly supports unregistering
> > > > > (some handler with lower priority will take over). You can check
> > > > > gpio-restart as an example for the API.
> > > > > 
> > > > > If you have some other arm_pm_restart handler (those are tried first)
> > > > > I suggest to convert that to the restart handler framework. Actually
> > > > > it may be a good idea to convert all of them and drop arm_pm_restart,
> > > > > since there are only 4 left:
> > > > > 
> > > > > $ git grep "arm_pm_restart ="
> > > > > drivers/firmware/psci.c:        arm_pm_restart = psci_sys_reset;
> > > > > arch/arm/mach-prima2/rstc.c:    arm_pm_restart = sirfsoc_restart;
> > > > > arch/arm/xen/enlighten.c:       arm_pm_restart = xen_restart;
> > > > > arch/arm/kernel/setup.c:                arm_pm_restart = mdesc->restart;
> > > > > 
> > > > > The first 3 should be easy to update and the last one could
> > > > > be solved by registering a wrapper function as restart handler,
> > > > > which calls mdesc->restart().
> > > > 
> > > > I remember this not being the first time that this confuses me. And from
> > > > looking around a little it seems like I'm not the only one.
> > > > 
> > > > Do you know if there's ever been any attempts to formalize all of this
> > > > by adding some sort of framework for this? That way some of the
> > > > confusion may be removed and architecture-specific bits could be
> > > > eliminated more easily.
> > > 
> > > We have a framework for restart handlers, which has been written
> > > by Guenter Roeck [0] in 2014. You just have to call register_restart_handler()
> > > during probe and unregister_restart_handler() during removal of
> > > your reboot driver. All reboot drivers in drivers/power/reset use
> > > that framework.
> > > 
> > > The restart handlers are invoked by calling do_kernel_restart()
> > > based on their configured priority. That function is called by
> > > the architecture's machine_restart() function. It's currently
> > > used by mips, ppc, arm & arm64 as far as I can see. ARM's
> > > implementation looks like this:
> > > 
> > > if (arm_pm_restart)
> > > 	arm_pm_restart()
> > > else
> > > 	do_kernel_restart() /* reboot handler framework */
> > > 
> > I actually have a set of patches somewhere which transforms the remaining
> > direct users of arm_pm_restart to use the framework (unless I removed it
> > from my trees - I don't really recall). I just never got around to submit
> > it, and then [2] happened and I lost interest.
> > 
> > > No such thing exists for poweroff. Guenter also wrote something for
> > > that [1], but Linus intervened [2]. Anyways, pm_power_off is at
> > > least architecture independent.
> > > 
> > That was by far the most frustrating kernel development experience
> > I ever head :-(.
> 
> It was a little difficult to track down all the discussion around this,
> but reading through what I could find, I can understand why this would
> have been frustrating. You obviously spent an enormous amount of work
> only to get it shot down.
> 
> I have to say that I have similar concerns to those voiced by Linus and
> Rafael. Notifier chains and priority seem too little restrictive for
> this kind of functionality, in my opinion. I think those concerns could
> be removed if things got formalized using a framework.
> 
> Without having spent the amount of time on the topic that you have, the
> following straw-man proposal is perhaps a little naive:
> 
> 	struct system_power_controller;
> 
> 	struct system_power_ops {
> 		int (*power_off)(struct system_power_controller *spc);
> 		int (*restart)(struct system_power_controller *spc,
> 			       enum reboot_mode mode,
> 			       const char *cmd);
> 	};
> 
> 	struct system_power_controller {
> 		struct device *dev;
> 		const struct system_power_ops *ops;
> 		struct list_head list;
> 		...
> 	};
> 
> 	int system_power_controller_add(struct system_power_controller *spc);
> 	void system_power_controller_remove(struct system_power_controller *spc);
> 
> 	int system_power_off(void);
> 	int system_restart(enum reboot_mode mode, const char *cmd);
> 
> The above is based on what other subsystems use. Drivers would embed the
> struct system_power_controller in a driver-specific data structure and
> use container_of() to get at that from the callbacks.
> 
> Controllers can be added and removed to the subsystem. Internally it may
> be worth to keep all of the controllers in a list, but only use the most
> appropriate ones. The above would need some sort of flag or type list
> that drivers can set in addition to operations to indicate what your
> proposal had done via priorities. I'm thinking of something along the
> lines of:
> 
> 	enum system_power_controller_type {
> 		SYSTEM_POWER_CONTROLLER_TYPE_CPU,
> 		SYSTEM_POWER_CONTROLLER_TYPE_SOC,
> 		SYSTEM_POWER_CONTROLLER_TYPE_BOARD,
> 	};
> 
> 	struct system_power_controller {
> 		...
> 		enum system_power_controller_type type;
> 		...
> 	};
> 
> There's a fairly natural ordering in the above "levels". Obviously the
> board-level controller would be highest priority because it controls
> power or reset of the complete board. This would likely be implemented
> by GPIO-based or PMIC type of drivers. SoC-level controllers would use
> mechanisms provided by the SoC in order to power off or reset only the
> SoC. This is obviously lower priority than board-level because some of
> the components on the board may end up remaining powered on or not
> getting reset. But it could be a useful fallback in case a board-level
> controller isn't present or fails. Finally there's the CPU-level code
> that would most likely be implemented by architecture code. In the most
> basic version this could be a WFI kind of instruction, or a busy loop,
> or perhaps even a simple call to panic().
> 
> One complication might be that there are things like PSCI that have an
> architecture-specific implementation (CPU-level) but could actually be
> a board-level "controller".
> 
> To keep things simple, I think it would be okay to allow only one of
> each type of controller in any running system. It's very unlikely that
> board designers would devise two different ways of powering off or
> restarting a system, while in a similar way an SoC or CPU would only
> ever provide one way to do so. Even if theoretically multiple
> possibilities exist, I think the board code should pick which ones are
> appropriate.

Using that logic we may also advice, that board-code should only
register the board-level reset/poweroff and it's enough to have
a callback again... I wonder if that is really feasible.

> Another missing feature in the above is that sometimes a mechanism
> exists to record in persistent storage (registers, memory, ...) what
> kind of reset was executed and which boot code will inspect and use to
> run different paths during boot. One such example is Tegra where the
> PMC has "scratch" registers that don't get reset on reboot. Software
> sets some of the bits to enable things like forced-recovery mode or
> Android-type recovery booting. I'm sure other similar mechanisms exist
> on other SoCs. Upon board-level reset, we would still want to have the
> SoC-level reset prep code execute, but not reset the SoC, otherwise
> the board-level code wouldn't have a chance of running anymore. This
> could possibly be solved by adding more fine-grained operations in the
> struct system_power_ops.

This kind of hardware is currently supported via
drivers/power/reset/reboot-mode.c

> One other possible solution that comes to mind is that system power
> controllers could be explicitly stacked. This would be complicated to
> achieve in code, but I'm thinking it could have interesting use-cases
> in device tree based systems, for example. I'm just brainstorming here,
> this may not be a good idea at all:
> 
> 	pmc: pmc@... {
> 		system-power-controller;
> 	};
> 
> 	i2c@... {
> 		pmic: pmic@... {
> 			...
> 			system-power-parent = <&pmc>;
> 			...
> 		};
> 	};
> 
> The PMIC would in the above call into PMC in order to reset on a SoC
> level before performing the board-level reset. I suppose it could use
> a separate callback (->prepare({RESET,POWER})) instead of the usual
> ->restart() and ->power_off().

Wouldn't SoC level reset stop the execution so board is never
resetted?

> Does the above sound at all sane to you, given your broad experience
> in this field? Anything I missed that I'd need to consider in a design
> for such a framework?
> 
> Sebastian, any thoughts from you on the above?

I would accept patches implementing the above, but I'm also totally
fine with the notifier chains. IMHO it would be enough to define
something like this:

#define RESTART_PRIORITY_CPU_LEVEL 32
#define RESTART_PRIORITY_SOC_LEVEL 64
#define RESTART_PRIORITY_BOARD_LEVEL 128

-- Sebastian
Guenter Roeck Jan. 29, 2017, 8:47 p.m. UTC | #16
On 01/29/2017 12:02 PM, Sebastian Reichel wrote:

>>
>> To keep things simple, I think it would be okay to allow only one of
>> each type of controller in any running system. It's very unlikely that
>> board designers would devise two different ways of powering off or
>> restarting a system, while in a similar way an SoC or CPU would only
>> ever provide one way to do so. Even if theoretically multiple
>> possibilities exist, I think the board code should pick which ones are
>> appropriate.
>
> Using that logic we may also advice, that board-code should only
> register the board-level reset/poweroff and it's enough to have
> a callback again... I wonder if that is really feasible.
>

FWIW, it is also not true. There is a reason why many of the restart
handlers used to have code saying "install restart handler, but only
if none is installed yet". Which of course is racy, and gets more
interesting if the restart handler installed first is unloaded at a
later time, leaving the system with no restart handler. Or both are
unloaded, leaving the system with a pointer to a no longer existing
handler.

One could then argue that anything implementing a restart handler must
not unload. Which results in more restrictions. And drivers loaded
on hardware which don't need it. And more corner cases to deal with.
And more inconsistencies.

In reality, many systems or system variants will have more than one means
to restart it. Yes, board designers do devise multiple ways of powering off
or restarting a system. There may be and likely are valid reasons for doing
so; I would not want to claim or suggest that board designers would design
such hardware without reason. Even "standard" PCs tend to have have more
than one means to reset it. There _was_ a reason for introducing that
framework; I didn't just do it for fun.

However, as I had mentioned before, I am not really interested in this
topic anymore. Just treat this as my final word of caution, or feel free
to ignore it. I hope you'll find a much better solution than mine
to implement "the board code should pick which ones are appropriate".

Thanks,
Guenter

--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Sebastian Reichel Jan. 29, 2017, 10:43 p.m. UTC | #17
Hi,

On Sun, Jan 29, 2017 at 12:47:57PM -0800, Guenter Roeck wrote:
> On 01/29/2017 12:02 PM, Sebastian Reichel wrote:
> 
> > > 
> > > To keep things simple, I think it would be okay to allow only one of
> > > each type of controller in any running system. It's very unlikely that
> > > board designers would devise two different ways of powering off or
> > > restarting a system, while in a similar way an SoC or CPU would only
> > > ever provide one way to do so. Even if theoretically multiple
> > > possibilities exist, I think the board code should pick which ones are
> > > appropriate.
> > 
> > Using that logic we may also advice, that board-code should only
> > register the board-level reset/poweroff and it's enough to have
> > a callback again... I wonder if that is really feasible.
> > 
> 
> FWIW, it is also not true.

It seems this was misunderstood. I do not expect this to work.

> There is a reason why many of the restart handlers used to have
> code saying "install restart handler, but only if none is
> installed yet". Which of course is racy, and gets more interesting
> if the restart handler installed first is unloaded at a later
> time, leaving the system with no restart handler. Or both are
> unloaded, leaving the system with a pointer to a no longer
> existing handler.
>
> One could then argue that anything implementing a restart handler must
> not unload. Which results in more restrictions. And drivers loaded
> on hardware which don't need it. And more corner cases to deal with.
> And more inconsistencies.
> 
> In reality, many systems or system variants will have more than one means
> to restart it. Yes, board designers do devise multiple ways of powering off
> or restarting a system. There may be and likely are valid reasons for doing
> so; I would not want to claim or suggest that board designers would design
> such hardware without reason. Even "standard" PCs tend to have have more
> than one means to reset it. There _was_ a reason for introducing that
> framework; I didn't just do it for fun.
> 
> However, as I had mentioned before, I am not really interested in this
> topic anymore. Just treat this as my final word of caution, or feel free
> to ignore it. I hope you'll find a much better solution than mine
> to implement "the board code should pick which ones are appropriate".

In case I was unclear: I'm fine with the current state of reboot
code using notifier chain and really thankful for the work. IMHO
it improved the status-quo a lot.

However I'm not fine with the current poweroff stuff and if somebody
offers to implement a solution compatible with Linus (and other people,
which disliked the notifier chain approach): Thanks, please do!

-- Sebastian
diff mbox

Patch

diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index abeb77217a21..f0d0c20632f8 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -98,6 +98,12 @@  config POWER_RESET_IMX
 	  say N here or disable in dts to make sure pm_power_off never be
 	  overwrote wrongly by this driver.
 
+config POWER_RESET_MAX77620
+	bool "Maxim MAX77620 PMIC power-off driver"
+	depends on MFD_MAX77620
+	help
+	  Power off and restart support for Maxim MAX77620 PMICs.
+
 config POWER_RESET_MSM
 	bool "Qualcomm MSM power-off driver"
 	depends on ARCH_QCOM
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index 11dae3b56ff9..74511d2f037a 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -9,6 +9,7 @@  obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
 obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
 obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
 obj-$(CONFIG_POWER_RESET_IMX) += imx-snvs-poweroff.o
+obj-$(CONFIG_POWER_RESET_MAX77620) += max77620-poweroff.o
 obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
 obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o
 obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o
diff --git a/drivers/power/reset/max77620-poweroff.c b/drivers/power/reset/max77620-poweroff.c
new file mode 100644
index 000000000000..4f2682d10925
--- /dev/null
+++ b/drivers/power/reset/max77620-poweroff.c
@@ -0,0 +1,146 @@ 
+/*
+ * Power off driver for Maxim MAX77620 device.
+ *
+ * Copyright (c) 2014-2016, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * Based on work by Chaitanya Bandi <bandik@nvidia.com>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
+ * whether express or implied; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/errno.h>
+#include <linux/mfd/max77620.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
+#include <asm/system_misc.h>
+#endif
+
+struct max77620_power {
+	struct regmap *regmap;
+	struct device *dev;
+};
+
+static struct max77620_power *system_power_controller = NULL;
+
+static void max77620_pm_power_off(void)
+{
+	struct max77620_power *power = system_power_controller;
+	unsigned int value;
+	int err;
+
+	if (!power)
+		return;
+
+	/* clear power key interrupts */
+	err = regmap_read(power->regmap, MAX77620_REG_ONOFFIRQ, &value);
+	if (err < 0)
+		dev_err(power->dev, "failed to clear power key interrupts: %d\n", err);
+
+	/* clear RTC interrupts */
+	/*
+	err = regmap_read(power->regmap, MAX77620_REG_RTCINT, &value);
+	if (err < 0)
+		dev_err(power->dev, "failed to clear RTC interrupts: %d\n", err);
+	*/
+
+	/* clear TOP interrupts */
+	err = regmap_read(power->regmap, MAX77620_REG_IRQTOP, &value);
+	if (err < 0)
+		dev_err(power->dev, "failed to clear interrupts: %d\n", err);
+
+	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG2,
+				 MAX77620_ONOFFCNFG2_SFT_RST_WK, 0);
+	if (err < 0)
+		dev_err(power->dev, "failed to clear SFT_RST_WK: %d\n", err);
+
+	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG1,
+				 MAX77620_ONOFFCNFG1_SFT_RST,
+				 MAX77620_ONOFFCNFG1_SFT_RST);
+	if (err < 0)
+		dev_err(power->dev, "failed to set SFT_RST: %d\n", err);
+}
+
+#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
+static void max77620_pm_restart(enum reboot_mode mode, const char *cmd)
+{
+	struct max77620_power *power = system_power_controller;
+	int err;
+
+	if (!power)
+		return;
+
+	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG2,
+				 MAX77620_ONOFFCNFG2_SFT_RST_WK,
+				 MAX77620_ONOFFCNFG2_SFT_RST_WK);
+	if (err < 0)
+		dev_err(power->dev, "failed to set SFT_RST_WK: %d\n", err);
+
+	err = regmap_update_bits(power->regmap, MAX77620_REG_ONOFFCNFG1,
+				 MAX77620_ONOFFCNFG1_SFT_RST,
+				 MAX77620_ONOFFCNFG1_SFT_RST);
+	if (err < 0)
+		dev_err(power->dev, "failed to set SFT_RST: %d\n", err);
+}
+#endif
+
+static int max77620_poweroff_probe(struct platform_device *pdev)
+{
+	struct max77620_chip *max77620 = dev_get_drvdata(pdev->dev.parent);
+	struct device_node *np = pdev->dev.parent->of_node;
+	struct max77620_power *power;
+	unsigned int value;
+	int err;
+
+	if (!of_property_read_bool(np, "system-power-controller"))
+		return 0;
+
+	power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
+	if (!power)
+		return -ENOMEM;
+
+	power->regmap = max77620->rmap;
+	power->dev = &pdev->dev;
+
+	err = regmap_read(power->regmap, MAX77620_REG_NVERC, &value);
+	if (err < 0) {
+		dev_err(power->dev, "failed to read event recorder: %d\n", err);
+		return err;
+	}
+
+	dev_dbg(&pdev->dev, "event recorder: %#x\n", value);
+
+	system_power_controller = power;
+	pm_power_off = max77620_pm_power_off;
+#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
+	arm_pm_restart = max77620_pm_restart;
+#endif
+
+	return 0;
+}
+
+static struct platform_driver max77620_poweroff_driver = {
+	.driver = {
+		.name = "max77620-power",
+	},
+	.probe = max77620_poweroff_probe,
+};
+module_platform_driver(max77620_poweroff_driver);
+
+MODULE_DESCRIPTION("Maxim MAX77620 PMIC power off and restart driver");
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_ALIAS("platform:max77620-power");
+MODULE_LICENSE("GPL v2");