summaryrefslogtreecommitdiffstats
path: root/drivers/power/reset
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/reset')
-rw-r--r--drivers/power/reset/Kconfig249
-rw-r--r--drivers/power/reset/Makefile31
-rw-r--r--drivers/power/reset/arm-versatile-reboot.c163
-rw-r--r--drivers/power/reset/as3722-poweroff.c95
-rw-r--r--drivers/power/reset/at91-poweroff.c237
-rw-r--r--drivers/power/reset/at91-reset.c262
-rw-r--r--drivers/power/reset/at91-sama5d2_shdwc.c332
-rw-r--r--drivers/power/reset/axxia-reset.c97
-rw-r--r--drivers/power/reset/brcm-kona-reset.c73
-rw-r--r--drivers/power/reset/brcmstb-reboot.c156
-rw-r--r--drivers/power/reset/gemini-poweroff.c175
-rw-r--r--drivers/power/reset/gpio-poweroff.c107
-rw-r--r--drivers/power/reset/gpio-restart.c148
-rw-r--r--drivers/power/reset/hisi-reboot.c82
-rw-r--r--drivers/power/reset/keystone-reset.c175
-rw-r--r--drivers/power/reset/ltc2952-poweroff.c324
-rw-r--r--drivers/power/reset/msm-poweroff.c80
-rw-r--r--drivers/power/reset/ocelot-reset.c88
-rw-r--r--drivers/power/reset/piix4-poweroff.c113
-rw-r--r--drivers/power/reset/qcom-pon.c91
-rw-r--r--drivers/power/reset/qnap-poweroff.c140
-rw-r--r--drivers/power/reset/reboot-mode.c199
-rw-r--r--drivers/power/reset/restart-poweroff.c65
-rw-r--r--drivers/power/reset/rmobile-reset.c91
-rw-r--r--drivers/power/reset/sc27xx-poweroff.c66
-rw-r--r--drivers/power/reset/st-poweroff.c115
-rw-r--r--drivers/power/reset/syscon-poweroff.c115
-rw-r--r--drivers/power/reset/syscon-reboot-mode.c90
-rw-r--r--drivers/power/reset/syscon-reboot.c91
-rw-r--r--drivers/power/reset/vexpress-poweroff.c161
-rw-r--r--drivers/power/reset/xgene-reboot.c109
-rw-r--r--drivers/power/reset/zx-reboot.c89
32 files changed, 4409 insertions, 0 deletions
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
new file mode 100644
index 000000000..6533aa560
--- /dev/null
+++ b/drivers/power/reset/Kconfig
@@ -0,0 +1,249 @@
+menuconfig POWER_RESET
+ bool "Board level reset or power off"
+ help
+ Provides a number of drivers which either reset a complete board
+ or shut it down, by manipulating the main power supply on the board.
+
+ Say Y here to enable board reset and power off
+
+if POWER_RESET
+
+config POWER_RESET_AS3722
+ bool "ams AS3722 power-off driver"
+ depends on MFD_AS3722
+ help
+ This driver supports turning off board via a ams AS3722 power-off.
+
+config POWER_RESET_AT91_POWEROFF
+ tristate "Atmel AT91 poweroff driver"
+ depends on ARCH_AT91
+ default SOC_AT91SAM9 || SOC_SAMA5
+ help
+ This driver supports poweroff for Atmel AT91SAM9 and SAMA5
+ SoCs
+
+config POWER_RESET_AT91_RESET
+ tristate "Atmel AT91 reset driver"
+ depends on ARCH_AT91
+ default SOC_AT91SAM9 || SOC_SAMA5
+ help
+ This driver supports restart for Atmel AT91SAM9 and SAMA5
+ SoCs
+
+config POWER_RESET_AT91_SAMA5D2_SHDWC
+ tristate "Atmel AT91 SAMA5D2-Compatible shutdown controller driver"
+ depends on ARCH_AT91
+ default SOC_SAMA5
+ help
+ This driver supports the alternate shutdown controller for some Atmel
+ SAMA5 SoCs. It is present for example on SAMA5D2 SoC.
+
+config POWER_RESET_AXXIA
+ bool "LSI Axxia reset driver"
+ depends on ARCH_AXXIA
+ help
+ This driver supports restart for Axxia SoC.
+
+ Say Y if you have an Axxia family SoC.
+
+config POWER_RESET_BRCMKONA
+ bool "Broadcom Kona reset driver"
+ depends on ARM || COMPILE_TEST
+ default ARCH_BCM_MOBILE
+ help
+ This driver provides restart support for Broadcom Kona chips.
+
+ Say Y here if you have a Broadcom Kona-based board and you wish
+ to have restart support.
+
+config POWER_RESET_BRCMSTB
+ bool "Broadcom STB reset driver"
+ depends on ARM || ARM64 || MIPS || COMPILE_TEST
+ depends on MFD_SYSCON
+ default ARCH_BRCMSTB || BMIPS_GENERIC
+ help
+ This driver provides restart support for Broadcom STB boards.
+
+ Say Y here if you have a Broadcom STB board and you wish
+ to have restart support.
+
+config POWER_RESET_GEMINI_POWEROFF
+ bool "Cortina Gemini power-off driver"
+ depends on ARCH_GEMINI || COMPILE_TEST
+ depends on OF && HAS_IOMEM
+ default ARCH_GEMINI
+ help
+ This driver supports turning off the Cortina Gemini SoC.
+ Select this if you're building a kernel with Gemini SoC support.
+
+config POWER_RESET_GPIO
+ bool "GPIO power-off driver"
+ depends on OF_GPIO
+ help
+ This driver supports turning off your board via a GPIO line.
+ If your board needs a GPIO high/low to power down, say Y and
+ create a binding in your devicetree.
+
+config POWER_RESET_GPIO_RESTART
+ bool "GPIO restart driver"
+ depends on OF_GPIO
+ help
+ This driver supports restarting your board via a GPIO line.
+ If your board needs a GPIO high/low to restart, say Y and
+ create a binding in your devicetree.
+
+config POWER_RESET_HISI
+ bool "Hisilicon power-off driver"
+ depends on ARCH_HISI
+ help
+ Reboot support for Hisilicon boards.
+
+config POWER_RESET_MSM
+ bool "Qualcomm MSM power-off driver"
+ depends on ARCH_QCOM
+ help
+ Power off and restart support for Qualcomm boards.
+
+config POWER_RESET_QCOM_PON
+ tristate "Qualcomm power-on driver"
+ depends on ARCH_QCOM
+ depends on MFD_SPMI_PMIC
+ select REBOOT_MODE
+ help
+ Power On support for Qualcomm boards.
+ If you have a Qualcomm platform and need support for
+ power-on and reboot reason, Say Y.
+ If unsure, Say N.
+
+config POWER_RESET_OCELOT_RESET
+ bool "Microsemi Ocelot reset driver"
+ depends on MSCC_OCELOT || COMPILE_TEST
+ select MFD_SYSCON
+ help
+ This driver supports restart for Microsemi Ocelot SoC.
+
+config POWER_RESET_PIIX4_POWEROFF
+ tristate "Intel PIIX4 power-off driver"
+ depends on PCI
+ depends on MIPS || COMPILE_TEST
+ help
+ This driver supports powering off a system using the Intel PIIX4
+ southbridge, for example the MIPS Malta development board. The
+ southbridge SOff state is entered in response to a request to
+ power off the system.
+
+config POWER_RESET_LTC2952
+ bool "LTC2952 PowerPath power-off driver"
+ depends on OF_GPIO
+ help
+ This driver supports an external powerdown trigger and board power
+ down via the LTC2952. Bindings are made in the device tree.
+
+config POWER_RESET_QNAP
+ bool "QNAP power-off driver"
+ depends on OF_GPIO && PLAT_ORION
+ help
+ This driver supports turning off QNAP NAS devices by sending
+ commands to the microcontroller which controls the main power.
+
+ Say Y if you have a QNAP NAS.
+
+config POWER_RESET_RESTART
+ bool "Restart power-off driver"
+ help
+ Some boards don't actually have the ability to power off.
+ Instead they restart, and u-boot holds the SoC until the
+ user presses a key. u-boot then boots into Linux.
+
+config POWER_RESET_ST
+ bool "ST restart driver"
+ depends on ARCH_STI
+ help
+ Reset support for STMicroelectronics boards.
+
+config POWER_RESET_VERSATILE
+ bool "ARM Versatile family reboot driver"
+ depends on ARM
+ depends on MFD_SYSCON
+ depends on OF
+ help
+ Power off and restart support for ARM Versatile family of
+ reference boards.
+
+config POWER_RESET_VEXPRESS
+ bool "ARM Versatile Express power-off and reset driver"
+ depends on ARM || ARM64
+ depends on VEXPRESS_CONFIG
+ help
+ Power off and reset support for the ARM Ltd. Versatile
+ Express boards.
+
+config POWER_RESET_XGENE
+ bool "APM SoC X-Gene reset driver"
+ depends on ARM64
+ help
+ Reboot support for the APM SoC X-Gene Eval boards.
+
+config POWER_RESET_KEYSTONE
+ bool "Keystone reset driver"
+ depends on ARCH_KEYSTONE || COMPILE_TEST
+ depends on HAS_IOMEM
+ select MFD_SYSCON
+ help
+ Reboot support for the KEYSTONE SoCs.
+
+config POWER_RESET_SYSCON
+ bool "Generic SYSCON regmap reset driver"
+ depends on OF
+ depends on HAS_IOMEM
+ select MFD_SYSCON
+ help
+ Reboot support for generic SYSCON mapped register reset.
+
+config POWER_RESET_SYSCON_POWEROFF
+ bool "Generic SYSCON regmap poweroff driver"
+ depends on OF
+ depends on HAS_IOMEM
+ select MFD_SYSCON
+ help
+ Poweroff support for generic SYSCON mapped register poweroff.
+
+config POWER_RESET_RMOBILE
+ tristate "Renesas R-Mobile reset driver"
+ depends on ARCH_RMOBILE || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ Reboot support for Renesas R-Mobile and SH-Mobile SoCs.
+
+config POWER_RESET_ZX
+ tristate "ZTE SoCs reset driver"
+ depends on ARCH_ZX || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ Reboot support for ZTE SoCs.
+
+config REBOOT_MODE
+ tristate
+
+config SYSCON_REBOOT_MODE
+ tristate "Generic SYSCON regmap reboot mode driver"
+ depends on OF
+ depends on MFD_SYSCON
+ select REBOOT_MODE
+ help
+ Say y here will enable reboot mode driver. This will
+ get reboot mode arguments and store it in SYSCON mapped
+ register, then the bootloader can read it to take different
+ action according to the mode.
+
+config POWER_RESET_SC27XX
+ bool "Spreadtrum SC27xx PMIC power-off driver"
+ depends on MFD_SC27XX_PMIC || COMPILE_TEST
+ help
+ This driver supports powering off a system through
+ Spreadtrum SC27xx series PMICs. The SC27xx series
+ PMICs includes the SC2720, SC2721, SC2723, SC2730
+ and SC2731 chips.
+
+endif
+
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
new file mode 100644
index 000000000..0aebee954
--- /dev/null
+++ b/drivers/power/reset/Makefile
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_POWER_RESET_AS3722) += as3722-poweroff.o
+obj-$(CONFIG_POWER_RESET_AT91_POWEROFF) += at91-poweroff.o
+obj-$(CONFIG_POWER_RESET_AT91_RESET) += at91-reset.o
+obj-$(CONFIG_POWER_RESET_AT91_SAMA5D2_SHDWC) += at91-sama5d2_shdwc.o
+obj-$(CONFIG_POWER_RESET_AXXIA) += axxia-reset.o
+obj-$(CONFIG_POWER_RESET_BRCMKONA) += brcm-kona-reset.o
+obj-$(CONFIG_POWER_RESET_BRCMSTB) += brcmstb-reboot.o
+obj-$(CONFIG_POWER_RESET_GEMINI_POWEROFF) += gemini-poweroff.o
+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_MSM) += msm-poweroff.o
+obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o
+obj-$(CONFIG_POWER_RESET_OCELOT_RESET) += ocelot-reset.o
+obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o
+obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o
+obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
+obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o
+obj-$(CONFIG_POWER_RESET_ST) += st-poweroff.o
+obj-$(CONFIG_POWER_RESET_VERSATILE) += arm-versatile-reboot.o
+obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o
+obj-$(CONFIG_POWER_RESET_XGENE) += xgene-reboot.o
+obj-$(CONFIG_POWER_RESET_KEYSTONE) += keystone-reset.o
+obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o
+obj-$(CONFIG_POWER_RESET_SYSCON_POWEROFF) += syscon-poweroff.o
+obj-$(CONFIG_POWER_RESET_RMOBILE) += rmobile-reset.o
+obj-$(CONFIG_POWER_RESET_ZX) += zx-reboot.o
+obj-$(CONFIG_REBOOT_MODE) += reboot-mode.o
+obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o
+obj-$(CONFIG_POWER_RESET_SC27XX) += sc27xx-poweroff.o
diff --git a/drivers/power/reset/arm-versatile-reboot.c b/drivers/power/reset/arm-versatile-reboot.c
new file mode 100644
index 000000000..06d34ab47
--- /dev/null
+++ b/drivers/power/reset/arm-versatile-reboot.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2014 Linaro Ltd.
+ *
+ * Author: Linus Walleij <linus.walleij@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ *
+ */
+#include <linux/init.h>
+#include <linux/mfd/syscon.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+
+#define INTEGRATOR_HDR_CTRL_OFFSET 0x0C
+#define INTEGRATOR_HDR_LOCK_OFFSET 0x14
+#define INTEGRATOR_CM_CTRL_RESET (1 << 3)
+
+#define VERSATILE_SYS_LOCK_OFFSET 0x20
+#define VERSATILE_SYS_RESETCTL_OFFSET 0x40
+
+/* Magic unlocking token used on all Versatile boards */
+#define VERSATILE_LOCK_VAL 0xA05F
+
+/*
+ * We detect the different syscon types from the compatible strings.
+ */
+enum versatile_reboot {
+ INTEGRATOR_REBOOT_CM,
+ VERSATILE_REBOOT_CM,
+ REALVIEW_REBOOT_EB,
+ REALVIEW_REBOOT_PB1176,
+ REALVIEW_REBOOT_PB11MP,
+ REALVIEW_REBOOT_PBA8,
+ REALVIEW_REBOOT_PBX,
+};
+
+/* Pointer to the system controller */
+static struct regmap *syscon_regmap;
+static enum versatile_reboot versatile_reboot_type;
+
+static const struct of_device_id versatile_reboot_of_match[] = {
+ {
+ .compatible = "arm,core-module-integrator",
+ .data = (void *)INTEGRATOR_REBOOT_CM
+ },
+ {
+ .compatible = "arm,core-module-versatile",
+ .data = (void *)VERSATILE_REBOOT_CM,
+ },
+ {
+ .compatible = "arm,realview-eb-syscon",
+ .data = (void *)REALVIEW_REBOOT_EB,
+ },
+ {
+ .compatible = "arm,realview-pb1176-syscon",
+ .data = (void *)REALVIEW_REBOOT_PB1176,
+ },
+ {
+ .compatible = "arm,realview-pb11mp-syscon",
+ .data = (void *)REALVIEW_REBOOT_PB11MP,
+ },
+ {
+ .compatible = "arm,realview-pba8-syscon",
+ .data = (void *)REALVIEW_REBOOT_PBA8,
+ },
+ {
+ .compatible = "arm,realview-pbx-syscon",
+ .data = (void *)REALVIEW_REBOOT_PBX,
+ },
+ {},
+};
+
+static int versatile_reboot(struct notifier_block *this, unsigned long mode,
+ void *cmd)
+{
+ /* Unlock the reset register */
+ /* Then hit reset on the different machines */
+ switch (versatile_reboot_type) {
+ case INTEGRATOR_REBOOT_CM:
+ regmap_write(syscon_regmap, INTEGRATOR_HDR_LOCK_OFFSET,
+ VERSATILE_LOCK_VAL);
+ regmap_update_bits(syscon_regmap,
+ INTEGRATOR_HDR_CTRL_OFFSET,
+ INTEGRATOR_CM_CTRL_RESET,
+ INTEGRATOR_CM_CTRL_RESET);
+ break;
+ case VERSATILE_REBOOT_CM:
+ regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
+ VERSATILE_LOCK_VAL);
+ regmap_update_bits(syscon_regmap,
+ VERSATILE_SYS_RESETCTL_OFFSET,
+ 0x0107,
+ 0x0105);
+ regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
+ 0);
+ break;
+ case REALVIEW_REBOOT_EB:
+ regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
+ VERSATILE_LOCK_VAL);
+ regmap_write(syscon_regmap,
+ VERSATILE_SYS_RESETCTL_OFFSET, 0x0008);
+ break;
+ case REALVIEW_REBOOT_PB1176:
+ regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
+ VERSATILE_LOCK_VAL);
+ regmap_write(syscon_regmap,
+ VERSATILE_SYS_RESETCTL_OFFSET, 0x0100);
+ break;
+ case REALVIEW_REBOOT_PB11MP:
+ case REALVIEW_REBOOT_PBA8:
+ regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
+ VERSATILE_LOCK_VAL);
+ regmap_write(syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET,
+ 0x0000);
+ regmap_write(syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET,
+ 0x0004);
+ break;
+ case REALVIEW_REBOOT_PBX:
+ regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
+ VERSATILE_LOCK_VAL);
+ regmap_write(syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET,
+ 0x00f0);
+ regmap_write(syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET,
+ 0x00f4);
+ break;
+ }
+ dsb();
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block versatile_reboot_nb = {
+ .notifier_call = versatile_reboot,
+ .priority = 192,
+};
+
+static int __init versatile_reboot_probe(void)
+{
+ const struct of_device_id *reboot_id;
+ struct device_node *np;
+ int err;
+
+ np = of_find_matching_node_and_match(NULL, versatile_reboot_of_match,
+ &reboot_id);
+ if (!np)
+ return -ENODEV;
+ versatile_reboot_type = (enum versatile_reboot)reboot_id->data;
+
+ syscon_regmap = syscon_node_to_regmap(np);
+ if (IS_ERR(syscon_regmap))
+ return PTR_ERR(syscon_regmap);
+
+ err = register_restart_handler(&versatile_reboot_nb);
+ if (err)
+ return err;
+
+ pr_info("versatile reboot driver registered\n");
+ return 0;
+}
+device_initcall(versatile_reboot_probe);
diff --git a/drivers/power/reset/as3722-poweroff.c b/drivers/power/reset/as3722-poweroff.c
new file mode 100644
index 000000000..60d0295ff
--- /dev/null
+++ b/drivers/power/reset/as3722-poweroff.c
@@ -0,0 +1,95 @@
+/*
+ * Power off driver for ams AS3722 device.
+ *
+ * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Author: Laxman Dewangan <ldewangan@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/mfd/as3722.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct as3722_poweroff {
+ struct device *dev;
+ struct as3722 *as3722;
+};
+
+static struct as3722_poweroff *as3722_pm_poweroff;
+
+static void as3722_pm_power_off(void)
+{
+ int ret;
+
+ if (!as3722_pm_poweroff) {
+ pr_err("AS3722 poweroff is not initialised\n");
+ return;
+ }
+
+ ret = as3722_update_bits(as3722_pm_poweroff->as3722,
+ AS3722_RESET_CONTROL_REG, AS3722_POWER_OFF, AS3722_POWER_OFF);
+ if (ret < 0)
+ dev_err(as3722_pm_poweroff->dev,
+ "RESET_CONTROL_REG update failed, %d\n", ret);
+}
+
+static int as3722_poweroff_probe(struct platform_device *pdev)
+{
+ struct as3722_poweroff *as3722_poweroff;
+ struct device_node *np = pdev->dev.parent->of_node;
+
+ if (!np)
+ return -EINVAL;
+
+ if (!of_property_read_bool(np, "ams,system-power-controller"))
+ return 0;
+
+ as3722_poweroff = devm_kzalloc(&pdev->dev, sizeof(*as3722_poweroff),
+ GFP_KERNEL);
+ if (!as3722_poweroff)
+ return -ENOMEM;
+
+ as3722_poweroff->as3722 = dev_get_drvdata(pdev->dev.parent);
+ as3722_poweroff->dev = &pdev->dev;
+ as3722_pm_poweroff = as3722_poweroff;
+ if (!pm_power_off)
+ pm_power_off = as3722_pm_power_off;
+
+ return 0;
+}
+
+static int as3722_poweroff_remove(struct platform_device *pdev)
+{
+ if (pm_power_off == as3722_pm_power_off)
+ pm_power_off = NULL;
+ as3722_pm_poweroff = NULL;
+
+ return 0;
+}
+
+static struct platform_driver as3722_poweroff_driver = {
+ .driver = {
+ .name = "as3722-power-off",
+ },
+ .probe = as3722_poweroff_probe,
+ .remove = as3722_poweroff_remove,
+};
+
+module_platform_driver(as3722_poweroff_driver);
+
+MODULE_DESCRIPTION("Power off driver for ams AS3722 PMIC Device");
+MODULE_ALIAS("platform:as3722-power-off");
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/at91-poweroff.c b/drivers/power/reset/at91-poweroff.c
new file mode 100644
index 000000000..fb2fc8f74
--- /dev/null
+++ b/drivers/power/reset/at91-poweroff.c
@@ -0,0 +1,237 @@
+/*
+ * Atmel AT91 SAM9 SoCs reset code
+ *
+ * Copyright (C) 2007 Atmel Corporation.
+ * Copyright (C) 2011 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com>
+ * Copyright (C) 2014 Free Electrons
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+
+#include <soc/at91/at91sam9_ddrsdr.h>
+
+#define AT91_SHDW_CR 0x00 /* Shut Down Control Register */
+#define AT91_SHDW_SHDW BIT(0) /* Shut Down command */
+#define AT91_SHDW_KEY (0xa5 << 24) /* KEY Password */
+
+#define AT91_SHDW_MR 0x04 /* Shut Down Mode Register */
+#define AT91_SHDW_WKMODE0 GENMASK(2, 0) /* Wake-up 0 Mode Selection */
+#define AT91_SHDW_CPTWK0_MAX 0xf /* Maximum Counter On Wake Up 0 */
+#define AT91_SHDW_CPTWK0 (AT91_SHDW_CPTWK0_MAX << 4) /* Counter On Wake Up 0 */
+#define AT91_SHDW_CPTWK0_(x) ((x) << 4)
+#define AT91_SHDW_RTTWKEN BIT(16) /* Real Time Timer Wake-up Enable */
+#define AT91_SHDW_RTCWKEN BIT(17) /* Real Time Clock Wake-up Enable */
+
+#define AT91_SHDW_SR 0x08 /* Shut Down Status Register */
+#define AT91_SHDW_WAKEUP0 BIT(0) /* Wake-up 0 Status */
+#define AT91_SHDW_RTTWK BIT(16) /* Real-time Timer Wake-up */
+#define AT91_SHDW_RTCWK BIT(17) /* Real-time Clock Wake-up [SAM9RL] */
+
+enum wakeup_type {
+ AT91_SHDW_WKMODE0_NONE = 0,
+ AT91_SHDW_WKMODE0_HIGH = 1,
+ AT91_SHDW_WKMODE0_LOW = 2,
+ AT91_SHDW_WKMODE0_ANYLEVEL = 3,
+};
+
+static const char *shdwc_wakeup_modes[] = {
+ [AT91_SHDW_WKMODE0_NONE] = "none",
+ [AT91_SHDW_WKMODE0_HIGH] = "high",
+ [AT91_SHDW_WKMODE0_LOW] = "low",
+ [AT91_SHDW_WKMODE0_ANYLEVEL] = "any",
+};
+
+static void __iomem *at91_shdwc_base;
+static struct clk *sclk;
+static void __iomem *mpddrc_base;
+
+static void __init at91_wakeup_status(struct platform_device *pdev)
+{
+ const char *reason;
+ u32 reg = readl(at91_shdwc_base + AT91_SHDW_SR);
+
+ /* Simple power-on, just bail out */
+ if (!reg)
+ return;
+
+ if (reg & AT91_SHDW_RTTWK)
+ reason = "RTT";
+ else if (reg & AT91_SHDW_RTCWK)
+ reason = "RTC";
+ else
+ reason = "unknown";
+
+ dev_info(&pdev->dev, "Wake-Up source: %s\n", reason);
+}
+
+static void at91_poweroff(void)
+{
+ writel(AT91_SHDW_KEY | AT91_SHDW_SHDW, at91_shdwc_base + AT91_SHDW_CR);
+}
+
+static void at91_lpddr_poweroff(void)
+{
+ asm volatile(
+ /* Align to cache lines */
+ ".balign 32\n\t"
+
+ /* Ensure AT91_SHDW_CR is in the TLB by reading it */
+ " ldr r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
+
+ /* Power down SDRAM0 */
+ " str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
+ /* Shutdown CPU */
+ " str %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
+
+ " b .\n\t"
+ :
+ : "r" (mpddrc_base),
+ "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
+ "r" (at91_shdwc_base),
+ "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
+ : "r6");
+}
+
+static int at91_poweroff_get_wakeup_mode(struct device_node *np)
+{
+ const char *pm;
+ unsigned int i;
+ int err;
+
+ err = of_property_read_string(np, "atmel,wakeup-mode", &pm);
+ if (err < 0)
+ return AT91_SHDW_WKMODE0_ANYLEVEL;
+
+ for (i = 0; i < ARRAY_SIZE(shdwc_wakeup_modes); i++)
+ if (!strcasecmp(pm, shdwc_wakeup_modes[i]))
+ return i;
+
+ return -ENODEV;
+}
+
+static void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ int wakeup_mode;
+ u32 mode = 0, tmp;
+
+ wakeup_mode = at91_poweroff_get_wakeup_mode(np);
+ if (wakeup_mode < 0) {
+ dev_warn(&pdev->dev, "shdwc unknown wakeup mode\n");
+ return;
+ }
+
+ if (!of_property_read_u32(np, "atmel,wakeup-counter", &tmp)) {
+ if (tmp > AT91_SHDW_CPTWK0_MAX) {
+ dev_warn(&pdev->dev,
+ "shdwc wakeup counter 0x%x > 0x%x reduce it to 0x%x\n",
+ tmp, AT91_SHDW_CPTWK0_MAX, AT91_SHDW_CPTWK0_MAX);
+ tmp = AT91_SHDW_CPTWK0_MAX;
+ }
+ mode |= AT91_SHDW_CPTWK0_(tmp);
+ }
+
+ if (of_property_read_bool(np, "atmel,wakeup-rtc-timer"))
+ mode |= AT91_SHDW_RTCWKEN;
+
+ if (of_property_read_bool(np, "atmel,wakeup-rtt-timer"))
+ mode |= AT91_SHDW_RTTWKEN;
+
+ writel(wakeup_mode | mode, at91_shdwc_base + AT91_SHDW_MR);
+}
+
+static int __init at91_poweroff_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct device_node *np;
+ u32 ddr_type;
+ int ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ at91_shdwc_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(at91_shdwc_base))
+ return PTR_ERR(at91_shdwc_base);
+
+ sclk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(sclk))
+ return PTR_ERR(sclk);
+
+ ret = clk_prepare_enable(sclk);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not enable slow clock\n");
+ return ret;
+ }
+
+ at91_wakeup_status(pdev);
+
+ if (pdev->dev.of_node)
+ at91_poweroff_dt_set_wakeup_mode(pdev);
+
+ pm_power_off = at91_poweroff;
+
+ np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc");
+ if (!np)
+ return 0;
+
+ mpddrc_base = of_iomap(np, 0);
+ of_node_put(np);
+
+ if (!mpddrc_base)
+ return 0;
+
+ ddr_type = readl(mpddrc_base + AT91_DDRSDRC_MDR) & AT91_DDRSDRC_MD;
+ if ((ddr_type == AT91_DDRSDRC_MD_LPDDR2) ||
+ (ddr_type == AT91_DDRSDRC_MD_LPDDR3))
+ pm_power_off = at91_lpddr_poweroff;
+ else
+ iounmap(mpddrc_base);
+
+ return 0;
+}
+
+static int __exit at91_poweroff_remove(struct platform_device *pdev)
+{
+ if (pm_power_off == at91_poweroff ||
+ pm_power_off == at91_lpddr_poweroff)
+ pm_power_off = NULL;
+
+ clk_disable_unprepare(sclk);
+
+ return 0;
+}
+
+static const struct of_device_id at91_ramc_of_match[] = {
+ { .compatible = "atmel,sama5d3-ddramc", },
+ { /* sentinel */ }
+};
+
+static const struct of_device_id at91_poweroff_of_match[] = {
+ { .compatible = "atmel,at91sam9260-shdwc", },
+ { .compatible = "atmel,at91sam9rl-shdwc", },
+ { .compatible = "atmel,at91sam9x5-shdwc", },
+ { /*sentinel*/ }
+};
+MODULE_DEVICE_TABLE(of, at91_poweroff_of_match);
+
+static struct platform_driver at91_poweroff_driver = {
+ .remove = __exit_p(at91_poweroff_remove),
+ .driver = {
+ .name = "at91-poweroff",
+ .of_match_table = at91_poweroff_of_match,
+ },
+};
+module_platform_driver_probe(at91_poweroff_driver, at91_poweroff_probe);
+
+MODULE_AUTHOR("Atmel Corporation");
+MODULE_DESCRIPTION("Shutdown driver for Atmel SoCs");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c
new file mode 100644
index 000000000..f44a9ffcc
--- /dev/null
+++ b/drivers/power/reset/at91-reset.c
@@ -0,0 +1,262 @@
+/*
+ * Atmel AT91 SAM9 & SAMA5 SoCs reset code
+ *
+ * Copyright (C) 2007 Atmel Corporation.
+ * Copyright (C) BitBox Ltd 2010
+ * Copyright (C) 2011 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcosoft.com>
+ * Copyright (C) 2014 Free Electrons
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+
+#include <soc/at91/at91sam9_ddrsdr.h>
+#include <soc/at91/at91sam9_sdramc.h>
+
+#define AT91_RSTC_CR 0x00 /* Reset Controller Control Register */
+#define AT91_RSTC_PROCRST BIT(0) /* Processor Reset */
+#define AT91_RSTC_PERRST BIT(2) /* Peripheral Reset */
+#define AT91_RSTC_EXTRST BIT(3) /* External Reset */
+#define AT91_RSTC_KEY (0xa5 << 24) /* KEY Password */
+
+#define AT91_RSTC_SR 0x04 /* Reset Controller Status Register */
+#define AT91_RSTC_URSTS BIT(0) /* User Reset Status */
+#define AT91_RSTC_RSTTYP GENMASK(10, 8) /* Reset Type */
+#define AT91_RSTC_NRSTL BIT(16) /* NRST Pin Level */
+#define AT91_RSTC_SRCMP BIT(17) /* Software Reset Command in Progress */
+
+#define AT91_RSTC_MR 0x08 /* Reset Controller Mode Register */
+#define AT91_RSTC_URSTEN BIT(0) /* User Reset Enable */
+#define AT91_RSTC_URSTIEN BIT(4) /* User Reset Interrupt Enable */
+#define AT91_RSTC_ERSTL GENMASK(11, 8) /* External Reset Length */
+
+enum reset_type {
+ RESET_TYPE_GENERAL = 0,
+ RESET_TYPE_WAKEUP = 1,
+ RESET_TYPE_WATCHDOG = 2,
+ RESET_TYPE_SOFTWARE = 3,
+ RESET_TYPE_USER = 4,
+};
+
+static void __iomem *at91_ramc_base[2], *at91_rstc_base;
+static struct clk *sclk;
+
+/*
+* unless the SDRAM is cleanly shutdown before we hit the
+* reset register it can be left driving the data bus and
+* killing the chance of a subsequent boot from NAND
+*/
+static int at91sam9260_restart(struct notifier_block *this, unsigned long mode,
+ void *cmd)
+{
+ asm volatile(
+ /* Align to cache lines */
+ ".balign 32\n\t"
+
+ /* Disable SDRAM accesses */
+ "str %2, [%0, #" __stringify(AT91_SDRAMC_TR) "]\n\t"
+
+ /* Power down SDRAM */
+ "str %3, [%0, #" __stringify(AT91_SDRAMC_LPR) "]\n\t"
+
+ /* Reset CPU */
+ "str %4, [%1, #" __stringify(AT91_RSTC_CR) "]\n\t"
+
+ "b .\n\t"
+ :
+ : "r" (at91_ramc_base[0]),
+ "r" (at91_rstc_base),
+ "r" (1),
+ "r" cpu_to_le32(AT91_SDRAMC_LPCB_POWER_DOWN),
+ "r" cpu_to_le32(AT91_RSTC_KEY | AT91_RSTC_PERRST | AT91_RSTC_PROCRST));
+
+ return NOTIFY_DONE;
+}
+
+static int at91sam9g45_restart(struct notifier_block *this, unsigned long mode,
+ void *cmd)
+{
+ asm volatile(
+ /*
+ * Test wether we have a second RAM controller to care
+ * about.
+ *
+ * First, test that we can dereference the virtual address.
+ */
+ "cmp %1, #0\n\t"
+ "beq 1f\n\t"
+
+ /* Then, test that the RAM controller is enabled */
+ "ldr r0, [%1]\n\t"
+ "cmp r0, #0\n\t"
+
+ /* Align to cache lines */
+ ".balign 32\n\t"
+
+ /* Disable SDRAM0 accesses */
+ "1: str %3, [%0, #" __stringify(AT91_DDRSDRC_RTR) "]\n\t"
+ /* Power down SDRAM0 */
+ " str %4, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
+ /* Disable SDRAM1 accesses */
+ " strne %3, [%1, #" __stringify(AT91_DDRSDRC_RTR) "]\n\t"
+ /* Power down SDRAM1 */
+ " strne %4, [%1, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
+ /* Reset CPU */
+ " str %5, [%2, #" __stringify(AT91_RSTC_CR) "]\n\t"
+
+ " b .\n\t"
+ :
+ : "r" (at91_ramc_base[0]),
+ "r" (at91_ramc_base[1]),
+ "r" (at91_rstc_base),
+ "r" (1),
+ "r" cpu_to_le32(AT91_DDRSDRC_LPCB_POWER_DOWN),
+ "r" cpu_to_le32(AT91_RSTC_KEY | AT91_RSTC_PERRST | AT91_RSTC_PROCRST)
+ : "r0");
+
+ return NOTIFY_DONE;
+}
+
+static int sama5d3_restart(struct notifier_block *this, unsigned long mode,
+ void *cmd)
+{
+ writel(cpu_to_le32(AT91_RSTC_KEY | AT91_RSTC_PERRST | AT91_RSTC_PROCRST),
+ at91_rstc_base);
+
+ return NOTIFY_DONE;
+}
+
+static int samx7_restart(struct notifier_block *this, unsigned long mode,
+ void *cmd)
+{
+ writel(cpu_to_le32(AT91_RSTC_KEY | AT91_RSTC_PROCRST),
+ at91_rstc_base);
+
+ return NOTIFY_DONE;
+}
+
+static void __init at91_reset_status(struct platform_device *pdev)
+{
+ const char *reason;
+ u32 reg = readl(at91_rstc_base + AT91_RSTC_SR);
+
+ switch ((reg & AT91_RSTC_RSTTYP) >> 8) {
+ case RESET_TYPE_GENERAL:
+ reason = "general reset";
+ break;
+ case RESET_TYPE_WAKEUP:
+ reason = "wakeup";
+ break;
+ case RESET_TYPE_WATCHDOG:
+ reason = "watchdog reset";
+ break;
+ case RESET_TYPE_SOFTWARE:
+ reason = "software reset";
+ break;
+ case RESET_TYPE_USER:
+ reason = "user reset";
+ break;
+ default:
+ reason = "unknown reset";
+ break;
+ }
+
+ dev_info(&pdev->dev, "Starting after %s\n", reason);
+}
+
+static const struct of_device_id at91_ramc_of_match[] = {
+ { .compatible = "atmel,at91sam9260-sdramc", },
+ { .compatible = "atmel,at91sam9g45-ddramc", },
+ { /* sentinel */ }
+};
+
+static const struct of_device_id at91_reset_of_match[] = {
+ { .compatible = "atmel,at91sam9260-rstc", .data = at91sam9260_restart },
+ { .compatible = "atmel,at91sam9g45-rstc", .data = at91sam9g45_restart },
+ { .compatible = "atmel,sama5d3-rstc", .data = sama5d3_restart },
+ { .compatible = "atmel,samx7-rstc", .data = samx7_restart },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, at91_reset_of_match);
+
+static struct notifier_block at91_restart_nb = {
+ .priority = 192,
+};
+
+static int __init at91_reset_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match;
+ struct device_node *np;
+ int ret, idx = 0;
+
+ at91_rstc_base = of_iomap(pdev->dev.of_node, 0);
+ if (!at91_rstc_base) {
+ dev_err(&pdev->dev, "Could not map reset controller address\n");
+ return -ENODEV;
+ }
+
+ if (!of_device_is_compatible(pdev->dev.of_node, "atmel,sama5d3-rstc")) {
+ /* we need to shutdown the ddr controller, so get ramc base */
+ for_each_matching_node(np, at91_ramc_of_match) {
+ at91_ramc_base[idx] = of_iomap(np, 0);
+ if (!at91_ramc_base[idx]) {
+ dev_err(&pdev->dev, "Could not map ram controller address\n");
+ of_node_put(np);
+ return -ENODEV;
+ }
+ idx++;
+ }
+ }
+
+ match = of_match_node(at91_reset_of_match, pdev->dev.of_node);
+ at91_restart_nb.notifier_call = match->data;
+
+ sclk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(sclk))
+ return PTR_ERR(sclk);
+
+ ret = clk_prepare_enable(sclk);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not enable slow clock\n");
+ return ret;
+ }
+
+ ret = register_restart_handler(&at91_restart_nb);
+ if (ret) {
+ clk_disable_unprepare(sclk);
+ return ret;
+ }
+
+ at91_reset_status(pdev);
+
+ return 0;
+}
+
+static int __exit at91_reset_remove(struct platform_device *pdev)
+{
+ unregister_restart_handler(&at91_restart_nb);
+ clk_disable_unprepare(sclk);
+
+ return 0;
+}
+
+static struct platform_driver at91_reset_driver = {
+ .remove = __exit_p(at91_reset_remove),
+ .driver = {
+ .name = "at91-reset",
+ .of_match_table = at91_reset_of_match,
+ },
+};
+module_platform_driver_probe(at91_reset_driver, at91_reset_probe);
+
+MODULE_AUTHOR("Atmel Corporation");
+MODULE_DESCRIPTION("Reset driver for Atmel SoCs");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c
new file mode 100644
index 000000000..7cf59e764
--- /dev/null
+++ b/drivers/power/reset/at91-sama5d2_shdwc.c
@@ -0,0 +1,332 @@
+/*
+ * Atmel SAMA5D2-Compatible Shutdown Controller (SHDWC) driver.
+ * Found on some SoCs as the sama5d2 (obviously).
+ *
+ * Copyright (C) 2015 Atmel Corporation,
+ * Nicolas Ferre <nicolas.ferre@atmel.com>
+ *
+ * Evolved from driver at91-poweroff.c.
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ *
+ * TODO:
+ * - addition to status of other wake-up inputs [1 - 15]
+ * - Analog Comparator wake-up alarm
+ * - Serial RX wake-up alarm
+ * - low power debouncer
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+
+#include <soc/at91/at91sam9_ddrsdr.h>
+
+#define SLOW_CLOCK_FREQ 32768
+
+#define AT91_SHDW_CR 0x00 /* Shut Down Control Register */
+#define AT91_SHDW_SHDW BIT(0) /* Shut Down command */
+#define AT91_SHDW_KEY (0xa5UL << 24) /* KEY Password */
+
+#define AT91_SHDW_MR 0x04 /* Shut Down Mode Register */
+#define AT91_SHDW_WKUPDBC_SHIFT 24
+#define AT91_SHDW_WKUPDBC_MASK GENMASK(26, 24)
+#define AT91_SHDW_WKUPDBC(x) (((x) << AT91_SHDW_WKUPDBC_SHIFT) \
+ & AT91_SHDW_WKUPDBC_MASK)
+
+#define AT91_SHDW_SR 0x08 /* Shut Down Status Register */
+#define AT91_SHDW_WKUPIS_SHIFT 16
+#define AT91_SHDW_WKUPIS_MASK GENMASK(31, 16)
+#define AT91_SHDW_WKUPIS(x) ((1 << (x)) << AT91_SHDW_WKUPIS_SHIFT \
+ & AT91_SHDW_WKUPIS_MASK)
+
+#define AT91_SHDW_WUIR 0x0c /* Shutdown Wake-up Inputs Register */
+#define AT91_SHDW_WKUPEN_MASK GENMASK(15, 0)
+#define AT91_SHDW_WKUPEN(x) ((1 << (x)) & AT91_SHDW_WKUPEN_MASK)
+#define AT91_SHDW_WKUPT_SHIFT 16
+#define AT91_SHDW_WKUPT_MASK GENMASK(31, 16)
+#define AT91_SHDW_WKUPT(x) ((1 << (x)) << AT91_SHDW_WKUPT_SHIFT \
+ & AT91_SHDW_WKUPT_MASK)
+
+#define SHDW_WK_PIN(reg, cfg) ((reg) & AT91_SHDW_WKUPIS((cfg)->wkup_pin_input))
+#define SHDW_RTCWK(reg, cfg) (((reg) >> ((cfg)->sr_rtcwk_shift)) & 0x1)
+#define SHDW_RTCWKEN(cfg) (1 << ((cfg)->mr_rtcwk_shift))
+
+#define DBC_PERIOD_US(x) DIV_ROUND_UP_ULL((1000000 * (x)), \
+ SLOW_CLOCK_FREQ)
+
+struct shdwc_config {
+ u8 wkup_pin_input;
+ u8 mr_rtcwk_shift;
+ u8 sr_rtcwk_shift;
+};
+
+struct shdwc {
+ const struct shdwc_config *cfg;
+ void __iomem *at91_shdwc_base;
+};
+
+/*
+ * Hold configuration here, cannot be more than one instance of the driver
+ * since pm_power_off itself is global.
+ */
+static struct shdwc *at91_shdwc;
+static struct clk *sclk;
+static void __iomem *mpddrc_base;
+
+static const unsigned long long sdwc_dbc_period[] = {
+ 0, 3, 32, 512, 4096, 32768,
+};
+
+static void __init at91_wakeup_status(struct platform_device *pdev)
+{
+ struct shdwc *shdw = platform_get_drvdata(pdev);
+ u32 reg;
+ char *reason = "unknown";
+
+ reg = readl(shdw->at91_shdwc_base + AT91_SHDW_SR);
+
+ dev_dbg(&pdev->dev, "%s: status = %#x\n", __func__, reg);
+
+ /* Simple power-on, just bail out */
+ if (!reg)
+ return;
+
+ if (SHDW_WK_PIN(reg, shdw->cfg))
+ reason = "WKUP pin";
+ else if (SHDW_RTCWK(reg, shdw->cfg))
+ reason = "RTC";
+
+ pr_info("AT91: Wake-Up source: %s\n", reason);
+}
+
+static void at91_poweroff(void)
+{
+ writel(AT91_SHDW_KEY | AT91_SHDW_SHDW,
+ at91_shdwc->at91_shdwc_base + AT91_SHDW_CR);
+}
+
+static void at91_lpddr_poweroff(void)
+{
+ asm volatile(
+ /* Align to cache lines */
+ ".balign 32\n\t"
+
+ /* Ensure AT91_SHDW_CR is in the TLB by reading it */
+ " ldr r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
+
+ /* Power down SDRAM0 */
+ " str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
+ /* Shutdown CPU */
+ " str %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
+
+ " b .\n\t"
+ :
+ : "r" (mpddrc_base),
+ "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
+ "r" (at91_shdwc->at91_shdwc_base),
+ "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
+ : "r6");
+}
+
+static u32 at91_shdwc_debouncer_value(struct platform_device *pdev,
+ u32 in_period_us)
+{
+ int i;
+ int max_idx = ARRAY_SIZE(sdwc_dbc_period) - 1;
+ unsigned long long period_us;
+ unsigned long long max_period_us = DBC_PERIOD_US(sdwc_dbc_period[max_idx]);
+
+ if (in_period_us > max_period_us) {
+ dev_warn(&pdev->dev,
+ "debouncer period %u too big, reduced to %llu us\n",
+ in_period_us, max_period_us);
+ return max_idx;
+ }
+
+ for (i = max_idx - 1; i > 0; i--) {
+ period_us = DBC_PERIOD_US(sdwc_dbc_period[i]);
+ dev_dbg(&pdev->dev, "%s: ref[%d] = %llu\n",
+ __func__, i, period_us);
+ if (in_period_us > period_us)
+ break;
+ }
+
+ return i + 1;
+}
+
+static u32 at91_shdwc_get_wakeup_input(struct platform_device *pdev,
+ struct device_node *np)
+{
+ struct device_node *cnp;
+ u32 wk_input_mask;
+ u32 wuir = 0;
+ u32 wk_input;
+
+ for_each_child_of_node(np, cnp) {
+ if (of_property_read_u32(cnp, "reg", &wk_input)) {
+ dev_warn(&pdev->dev, "reg property is missing for %pOF\n",
+ cnp);
+ continue;
+ }
+
+ wk_input_mask = 1 << wk_input;
+ if (!(wk_input_mask & AT91_SHDW_WKUPEN_MASK)) {
+ dev_warn(&pdev->dev,
+ "wake-up input %d out of bounds ignore\n",
+ wk_input);
+ continue;
+ }
+ wuir |= wk_input_mask;
+
+ if (of_property_read_bool(cnp, "atmel,wakeup-active-high"))
+ wuir |= AT91_SHDW_WKUPT(wk_input);
+
+ dev_dbg(&pdev->dev, "%s: (child %d) wuir = %#x\n",
+ __func__, wk_input, wuir);
+ }
+
+ return wuir;
+}
+
+static void at91_shdwc_dt_configure(struct platform_device *pdev)
+{
+ struct shdwc *shdw = platform_get_drvdata(pdev);
+ struct device_node *np = pdev->dev.of_node;
+ u32 mode = 0, tmp, input;
+
+ if (!np) {
+ dev_err(&pdev->dev, "device node not found\n");
+ return;
+ }
+
+ if (!of_property_read_u32(np, "debounce-delay-us", &tmp))
+ mode |= AT91_SHDW_WKUPDBC(at91_shdwc_debouncer_value(pdev, tmp));
+
+ if (of_property_read_bool(np, "atmel,wakeup-rtc-timer"))
+ mode |= SHDW_RTCWKEN(shdw->cfg);
+
+ dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode);
+ writel(mode, shdw->at91_shdwc_base + AT91_SHDW_MR);
+
+ input = at91_shdwc_get_wakeup_input(pdev, np);
+ writel(input, shdw->at91_shdwc_base + AT91_SHDW_WUIR);
+}
+
+static const struct shdwc_config sama5d2_shdwc_config = {
+ .wkup_pin_input = 0,
+ .mr_rtcwk_shift = 17,
+ .sr_rtcwk_shift = 5,
+};
+
+static const struct of_device_id at91_shdwc_of_match[] = {
+ {
+ .compatible = "atmel,sama5d2-shdwc",
+ .data = &sama5d2_shdwc_config,
+ }, {
+ /*sentinel*/
+ }
+};
+MODULE_DEVICE_TABLE(of, at91_shdwc_of_match);
+
+static int __init at91_shdwc_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ const struct of_device_id *match;
+ struct device_node *np;
+ u32 ddr_type;
+ int ret;
+
+ if (!pdev->dev.of_node)
+ return -ENODEV;
+
+ if (at91_shdwc)
+ return -EBUSY;
+
+ at91_shdwc = devm_kzalloc(&pdev->dev, sizeof(*at91_shdwc), GFP_KERNEL);
+ if (!at91_shdwc)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, at91_shdwc);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ at91_shdwc->at91_shdwc_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(at91_shdwc->at91_shdwc_base)) {
+ dev_err(&pdev->dev, "Could not map reset controller address\n");
+ return PTR_ERR(at91_shdwc->at91_shdwc_base);
+ }
+
+ match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node);
+ at91_shdwc->cfg = match->data;
+
+ sclk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(sclk))
+ return PTR_ERR(sclk);
+
+ ret = clk_prepare_enable(sclk);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not enable slow clock\n");
+ return ret;
+ }
+
+ at91_wakeup_status(pdev);
+
+ at91_shdwc_dt_configure(pdev);
+
+ pm_power_off = at91_poweroff;
+
+ np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc");
+ if (!np)
+ return 0;
+
+ mpddrc_base = of_iomap(np, 0);
+ of_node_put(np);
+
+ if (!mpddrc_base)
+ return 0;
+
+ ddr_type = readl(mpddrc_base + AT91_DDRSDRC_MDR) & AT91_DDRSDRC_MD;
+ if ((ddr_type == AT91_DDRSDRC_MD_LPDDR2) ||
+ (ddr_type == AT91_DDRSDRC_MD_LPDDR3))
+ pm_power_off = at91_lpddr_poweroff;
+ else
+ iounmap(mpddrc_base);
+
+ return 0;
+}
+
+static int __exit at91_shdwc_remove(struct platform_device *pdev)
+{
+ struct shdwc *shdw = platform_get_drvdata(pdev);
+
+ if (pm_power_off == at91_poweroff ||
+ pm_power_off == at91_lpddr_poweroff)
+ pm_power_off = NULL;
+
+ /* Reset values to disable wake-up features */
+ writel(0, shdw->at91_shdwc_base + AT91_SHDW_MR);
+ writel(0, shdw->at91_shdwc_base + AT91_SHDW_WUIR);
+
+ clk_disable_unprepare(sclk);
+
+ return 0;
+}
+
+static struct platform_driver at91_shdwc_driver = {
+ .remove = __exit_p(at91_shdwc_remove),
+ .driver = {
+ .name = "at91-shdwc",
+ .of_match_table = at91_shdwc_of_match,
+ },
+};
+module_platform_driver_probe(at91_shdwc_driver, at91_shdwc_probe);
+
+MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>");
+MODULE_DESCRIPTION("Atmel shutdown controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/axxia-reset.c b/drivers/power/reset/axxia-reset.c
new file mode 100644
index 000000000..4e4cd1c8f
--- /dev/null
+++ b/drivers/power/reset/axxia-reset.c
@@ -0,0 +1,97 @@
+/*
+ * Reset driver for Axxia devices
+ *
+ * Copyright (C) 2014 LSI
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.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>
+
+#define SC_CRIT_WRITE_KEY 0x1000
+#define SC_LATCH_ON_RESET 0x1004
+#define SC_RESET_CONTROL 0x1008
+#define RSTCTL_RST_ZERO (1<<3)
+#define RSTCTL_RST_FAB (1<<2)
+#define RSTCTL_RST_CHIP (1<<1)
+#define RSTCTL_RST_SYS (1<<0)
+#define SC_EFUSE_INT_STATUS 0x180c
+#define EFUSE_READ_DONE (1<<31)
+
+static struct regmap *syscon;
+
+static int axxia_restart_handler(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ /* Access Key (0xab) */
+ regmap_write(syscon, SC_CRIT_WRITE_KEY, 0xab);
+ /* Select internal boot from 0xffff0000 */
+ regmap_write(syscon, SC_LATCH_ON_RESET, 0x00000040);
+ /* Assert ResetReadDone (to avoid hanging in boot ROM) */
+ regmap_write(syscon, SC_EFUSE_INT_STATUS, EFUSE_READ_DONE);
+ /* Assert chip reset */
+ regmap_update_bits(syscon, SC_RESET_CONTROL,
+ RSTCTL_RST_CHIP, RSTCTL_RST_CHIP);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block axxia_restart_nb = {
+ .notifier_call = axxia_restart_handler,
+ .priority = 128,
+};
+
+static int axxia_reset_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int err;
+
+ syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon");
+ if (IS_ERR(syscon)) {
+ pr_err("%s: syscon lookup failed\n", dev->of_node->name);
+ return PTR_ERR(syscon);
+ }
+
+ err = register_restart_handler(&axxia_restart_nb);
+ if (err)
+ dev_err(dev, "cannot register restart handler (err=%d)\n", err);
+
+ return err;
+}
+
+static const struct of_device_id of_axxia_reset_match[] = {
+ { .compatible = "lsi,axm55xx-reset", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_axxia_reset_match);
+
+static struct platform_driver axxia_reset_driver = {
+ .probe = axxia_reset_probe,
+ .driver = {
+ .name = "axxia-reset",
+ .of_match_table = of_match_ptr(of_axxia_reset_match),
+ },
+};
+
+static int __init axxia_reset_init(void)
+{
+ return platform_driver_register(&axxia_reset_driver);
+}
+device_initcall(axxia_reset_init);
diff --git a/drivers/power/reset/brcm-kona-reset.c b/drivers/power/reset/brcm-kona-reset.c
new file mode 100644
index 000000000..8eaa959d8
--- /dev/null
+++ b/drivers/power/reset/brcm-kona-reset.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 Broadcom
+ *
+ * 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/io.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/reboot.h>
+
+#define RSTMGR_REG_WR_ACCESS_OFFSET 0
+#define RSTMGR_REG_CHIP_SOFT_RST_OFFSET 4
+
+#define RSTMGR_WR_PASSWORD 0xa5a5
+#define RSTMGR_WR_PASSWORD_SHIFT 8
+#define RSTMGR_WR_ACCESS_ENABLE 1
+
+static void __iomem *kona_reset_base;
+
+static int kona_reset_handler(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ /*
+ * A soft reset is triggered by writing a 0 to bit 0 of the soft reset
+ * register. To write to that register we must first write the password
+ * and the enable bit in the write access enable register.
+ */
+ writel((RSTMGR_WR_PASSWORD << RSTMGR_WR_PASSWORD_SHIFT) |
+ RSTMGR_WR_ACCESS_ENABLE,
+ kona_reset_base + RSTMGR_REG_WR_ACCESS_OFFSET);
+ writel(0, kona_reset_base + RSTMGR_REG_CHIP_SOFT_RST_OFFSET);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block kona_reset_nb = {
+ .notifier_call = kona_reset_handler,
+ .priority = 128,
+};
+
+static int kona_reset_probe(struct platform_device *pdev)
+{
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ kona_reset_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(kona_reset_base))
+ return PTR_ERR(kona_reset_base);
+
+ return register_restart_handler(&kona_reset_nb);
+}
+
+static const struct of_device_id of_match[] = {
+ { .compatible = "brcm,bcm21664-resetmgr" },
+ {},
+};
+
+static struct platform_driver bcm_kona_reset_driver = {
+ .probe = kona_reset_probe,
+ .driver = {
+ .name = "brcm-kona-reset",
+ .of_match_table = of_match,
+ },
+};
+
+builtin_platform_driver(bcm_kona_reset_driver);
diff --git a/drivers/power/reset/brcmstb-reboot.c b/drivers/power/reset/brcmstb-reboot.c
new file mode 100644
index 000000000..884b53c48
--- /dev/null
+++ b/drivers/power/reset/brcmstb-reboot.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2013 Broadcom Corporation
+ *
+ * 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/bitops.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/notifier.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/smp.h>
+#include <linux/mfd/syscon.h>
+
+#define RESET_SOURCE_ENABLE_REG 1
+#define SW_MASTER_RESET_REG 2
+
+static struct regmap *regmap;
+static u32 rst_src_en;
+static u32 sw_mstr_rst;
+
+struct reset_reg_mask {
+ u32 rst_src_en_mask;
+ u32 sw_mstr_rst_mask;
+};
+
+static const struct reset_reg_mask *reset_masks;
+
+static int brcmstb_restart_handler(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ int rc;
+ u32 tmp;
+
+ rc = regmap_write(regmap, rst_src_en, reset_masks->rst_src_en_mask);
+ if (rc) {
+ pr_err("failed to write rst_src_en (%d)\n", rc);
+ return NOTIFY_DONE;
+ }
+
+ rc = regmap_read(regmap, rst_src_en, &tmp);
+ if (rc) {
+ pr_err("failed to read rst_src_en (%d)\n", rc);
+ return NOTIFY_DONE;
+ }
+
+ rc = regmap_write(regmap, sw_mstr_rst, reset_masks->sw_mstr_rst_mask);
+ if (rc) {
+ pr_err("failed to write sw_mstr_rst (%d)\n", rc);
+ return NOTIFY_DONE;
+ }
+
+ rc = regmap_read(regmap, sw_mstr_rst, &tmp);
+ if (rc) {
+ pr_err("failed to read sw_mstr_rst (%d)\n", rc);
+ return NOTIFY_DONE;
+ }
+
+ while (1)
+ ;
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block brcmstb_restart_nb = {
+ .notifier_call = brcmstb_restart_handler,
+ .priority = 128,
+};
+
+static const struct reset_reg_mask reset_bits_40nm = {
+ .rst_src_en_mask = BIT(0),
+ .sw_mstr_rst_mask = BIT(0),
+};
+
+static const struct reset_reg_mask reset_bits_65nm = {
+ .rst_src_en_mask = BIT(3),
+ .sw_mstr_rst_mask = BIT(31),
+};
+
+static const struct of_device_id of_match[] = {
+ { .compatible = "brcm,brcmstb-reboot", .data = &reset_bits_40nm },
+ { .compatible = "brcm,bcm7038-reboot", .data = &reset_bits_65nm },
+ {},
+};
+
+static int brcmstb_reboot_probe(struct platform_device *pdev)
+{
+ int rc;
+ struct device_node *np = pdev->dev.of_node;
+ const struct of_device_id *of_id;
+
+ of_id = of_match_node(of_match, np);
+ if (!of_id) {
+ pr_err("failed to look up compatible string\n");
+ return -EINVAL;
+ }
+ reset_masks = of_id->data;
+
+ regmap = syscon_regmap_lookup_by_phandle(np, "syscon");
+ if (IS_ERR(regmap)) {
+ pr_err("failed to get syscon phandle\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32_index(np, "syscon", RESET_SOURCE_ENABLE_REG,
+ &rst_src_en);
+ if (rc) {
+ pr_err("can't get rst_src_en offset (%d)\n", rc);
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32_index(np, "syscon", SW_MASTER_RESET_REG,
+ &sw_mstr_rst);
+ if (rc) {
+ pr_err("can't get sw_mstr_rst offset (%d)\n", rc);
+ return -EINVAL;
+ }
+
+ rc = register_restart_handler(&brcmstb_restart_nb);
+ if (rc)
+ dev_err(&pdev->dev,
+ "cannot register restart handler (err=%d)\n", rc);
+
+ return rc;
+}
+
+static struct platform_driver brcmstb_reboot_driver = {
+ .probe = brcmstb_reboot_probe,
+ .driver = {
+ .name = "brcmstb-reboot",
+ .of_match_table = of_match,
+ },
+};
+
+static int __init brcmstb_reboot_init(void)
+{
+ return platform_driver_probe(&brcmstb_reboot_driver,
+ brcmstb_reboot_probe);
+}
+subsys_initcall(brcmstb_reboot_init);
diff --git a/drivers/power/reset/gemini-poweroff.c b/drivers/power/reset/gemini-poweroff.c
new file mode 100644
index 000000000..b7f7a8225
--- /dev/null
+++ b/drivers/power/reset/gemini-poweroff.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Gemini power management controller
+ * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
+ *
+ * Inspired by code from the SL3516 board support by Jason Lee
+ * Inspired by code from Janos Laube <janos.dev@gmail.com>
+ */
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/reboot.h>
+
+#define GEMINI_PWC_ID 0x00010500
+#define GEMINI_PWC_IDREG 0x00
+#define GEMINI_PWC_CTRLREG 0x04
+#define GEMINI_PWC_STATREG 0x08
+
+#define GEMINI_CTRL_SHUTDOWN BIT(0)
+#define GEMINI_CTRL_ENABLE BIT(1)
+#define GEMINI_CTRL_IRQ_CLR BIT(2)
+
+#define GEMINI_STAT_CIR BIT(4)
+#define GEMINI_STAT_RTC BIT(5)
+#define GEMINI_STAT_POWERBUTTON BIT(6)
+
+struct gemini_powercon {
+ struct device *dev;
+ void __iomem *base;
+};
+
+static irqreturn_t gemini_powerbutton_interrupt(int irq, void *data)
+{
+ struct gemini_powercon *gpw = data;
+ u32 val;
+
+ /* ACK the IRQ */
+ val = readl(gpw->base + GEMINI_PWC_CTRLREG);
+ val |= GEMINI_CTRL_IRQ_CLR;
+ writel(val, gpw->base + GEMINI_PWC_CTRLREG);
+
+ val = readl(gpw->base + GEMINI_PWC_STATREG);
+ val &= 0x70U;
+ switch (val) {
+ case GEMINI_STAT_CIR:
+ /*
+ * We do not yet have a driver for the infrared
+ * controller so it can cause spurious poweroff
+ * events. Ignore those for now.
+ */
+ dev_info(gpw->dev, "infrared poweroff - ignored\n");
+ break;
+ case GEMINI_STAT_RTC:
+ dev_info(gpw->dev, "RTC poweroff\n");
+ orderly_poweroff(true);
+ break;
+ case GEMINI_STAT_POWERBUTTON:
+ dev_info(gpw->dev, "poweroff button pressed\n");
+ orderly_poweroff(true);
+ break;
+ default:
+ dev_info(gpw->dev, "other power management IRQ\n");
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+/* This callback needs this static local as it has void as argument */
+static struct gemini_powercon *gpw_poweroff;
+
+static void gemini_poweroff(void)
+{
+ struct gemini_powercon *gpw = gpw_poweroff;
+ u32 val;
+
+ dev_crit(gpw->dev, "Gemini power off\n");
+ val = readl(gpw->base + GEMINI_PWC_CTRLREG);
+ val |= GEMINI_CTRL_ENABLE | GEMINI_CTRL_IRQ_CLR;
+ writel(val, gpw->base + GEMINI_PWC_CTRLREG);
+
+ val &= ~GEMINI_CTRL_ENABLE;
+ val |= GEMINI_CTRL_SHUTDOWN;
+ writel(val, gpw->base + GEMINI_PWC_CTRLREG);
+}
+
+static int gemini_poweroff_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct gemini_powercon *gpw;
+ u32 val;
+ int irq;
+ int ret;
+
+ gpw = devm_kzalloc(dev, sizeof(*gpw), GFP_KERNEL);
+ if (!gpw)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ gpw->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(gpw->base))
+ return PTR_ERR(gpw->base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ gpw->dev = dev;
+
+ val = readl(gpw->base + GEMINI_PWC_IDREG);
+ val &= 0xFFFFFF00U;
+ if (val != GEMINI_PWC_ID) {
+ dev_err(dev, "wrong power controller ID: %08x\n",
+ val);
+ return -ENODEV;
+ }
+
+ /*
+ * Enable the power controller. This is crucial on Gemini
+ * systems: if this is not done, pressing the power button
+ * will result in unconditional poweroff without any warning.
+ * This makes the kernel handle the poweroff.
+ */
+ val = readl(gpw->base + GEMINI_PWC_CTRLREG);
+ val |= GEMINI_CTRL_ENABLE;
+ writel(val, gpw->base + GEMINI_PWC_CTRLREG);
+
+ /* Clear the IRQ */
+ val = readl(gpw->base + GEMINI_PWC_CTRLREG);
+ val |= GEMINI_CTRL_IRQ_CLR;
+ writel(val, gpw->base + GEMINI_PWC_CTRLREG);
+
+ /* Wait for this to clear */
+ val = readl(gpw->base + GEMINI_PWC_STATREG);
+ while (val & 0x70U)
+ val = readl(gpw->base + GEMINI_PWC_STATREG);
+
+ /* Clear the IRQ again */
+ val = readl(gpw->base + GEMINI_PWC_CTRLREG);
+ val |= GEMINI_CTRL_IRQ_CLR;
+ writel(val, gpw->base + GEMINI_PWC_CTRLREG);
+
+ ret = devm_request_irq(dev, irq, gemini_powerbutton_interrupt, 0,
+ "poweroff", gpw);
+ if (ret)
+ return ret;
+
+ pm_power_off = gemini_poweroff;
+ gpw_poweroff = gpw;
+
+ dev_info(dev, "Gemini poweroff driver registered\n");
+
+ return 0;
+}
+
+static const struct of_device_id gemini_poweroff_of_match[] = {
+ {
+ .compatible = "cortina,gemini-power-controller",
+ },
+ {}
+};
+
+static struct platform_driver gemini_poweroff_driver = {
+ .probe = gemini_poweroff_probe,
+ .driver = {
+ .name = "gemini-poweroff",
+ .of_match_table = gemini_poweroff_of_match,
+ },
+};
+builtin_platform_driver(gemini_poweroff_driver);
diff --git a/drivers/power/reset/gpio-poweroff.c b/drivers/power/reset/gpio-poweroff.c
new file mode 100644
index 000000000..5f2fa9c0f
--- /dev/null
+++ b/drivers/power/reset/gpio-poweroff.c
@@ -0,0 +1,107 @@
+/*
+ * Toggles a GPIO pin to power down a device
+ *
+ * Jamie Lentin <jm@lentin.co.uk>
+ * Andrew Lunn <andrew@lunn.ch>
+ *
+ * Copyright (C) 2012 Jamie Lentin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_platform.h>
+#include <linux/module.h>
+
+#define DEFAULT_TIMEOUT_MS 3000
+/*
+ * Hold configuration here, cannot be more than one instance of the driver
+ * since pm_power_off itself is global.
+ */
+static struct gpio_desc *reset_gpio;
+static u32 timeout = DEFAULT_TIMEOUT_MS;
+
+static void gpio_poweroff_do_poweroff(void)
+{
+ BUG_ON(!reset_gpio);
+
+ /* drive it active, also inactive->active edge */
+ gpiod_direction_output(reset_gpio, 1);
+ mdelay(100);
+ /* drive inactive, also active->inactive edge */
+ gpiod_set_value_cansleep(reset_gpio, 0);
+ mdelay(100);
+
+ /* drive it active, also inactive->active edge */
+ gpiod_set_value_cansleep(reset_gpio, 1);
+
+ /* give it some time */
+ mdelay(timeout);
+
+ WARN_ON(1);
+}
+
+static int gpio_poweroff_probe(struct platform_device *pdev)
+{
+ bool input = false;
+ enum gpiod_flags flags;
+
+ /* If a pm_power_off function has already been added, leave it alone */
+ if (pm_power_off != NULL) {
+ dev_err(&pdev->dev,
+ "%s: pm_power_off function already registered",
+ __func__);
+ return -EBUSY;
+ }
+
+ input = device_property_read_bool(&pdev->dev, "input");
+ if (input)
+ flags = GPIOD_IN;
+ else
+ flags = GPIOD_OUT_LOW;
+
+ device_property_read_u32(&pdev->dev, "timeout-ms", &timeout);
+
+ reset_gpio = devm_gpiod_get(&pdev->dev, NULL, flags);
+ if (IS_ERR(reset_gpio))
+ return PTR_ERR(reset_gpio);
+
+ pm_power_off = &gpio_poweroff_do_poweroff;
+ return 0;
+}
+
+static int gpio_poweroff_remove(struct platform_device *pdev)
+{
+ if (pm_power_off == &gpio_poweroff_do_poweroff)
+ pm_power_off = NULL;
+
+ return 0;
+}
+
+static const struct of_device_id of_gpio_poweroff_match[] = {
+ { .compatible = "gpio-poweroff", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_gpio_poweroff_match);
+
+static struct platform_driver gpio_poweroff_driver = {
+ .probe = gpio_poweroff_probe,
+ .remove = gpio_poweroff_remove,
+ .driver = {
+ .name = "poweroff-gpio",
+ .of_match_table = of_gpio_poweroff_match,
+ },
+};
+
+module_platform_driver(gpio_poweroff_driver);
+
+MODULE_AUTHOR("Jamie Lentin <jm@lentin.co.uk>");
+MODULE_DESCRIPTION("GPIO poweroff driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:poweroff-gpio");
diff --git a/drivers/power/reset/gpio-restart.c b/drivers/power/reset/gpio-restart.c
new file mode 100644
index 000000000..829b45f42
--- /dev/null
+++ b/drivers/power/reset/gpio-restart.c
@@ -0,0 +1,148 @@
+/*
+ * Toggles a GPIO pin to restart a device
+ *
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Based on the gpio-poweroff driver.
+ */
+#include <linux/reboot.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_platform.h>
+#include <linux/module.h>
+
+struct gpio_restart {
+ struct gpio_desc *reset_gpio;
+ struct notifier_block restart_handler;
+ u32 active_delay_ms;
+ u32 inactive_delay_ms;
+ u32 wait_delay_ms;
+};
+
+static int gpio_restart_notify(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct gpio_restart *gpio_restart =
+ container_of(this, struct gpio_restart, restart_handler);
+
+ /* drive it active, also inactive->active edge */
+ gpiod_direction_output(gpio_restart->reset_gpio, 1);
+ mdelay(gpio_restart->active_delay_ms);
+
+ /* drive inactive, also active->inactive edge */
+ gpiod_set_value(gpio_restart->reset_gpio, 0);
+ mdelay(gpio_restart->inactive_delay_ms);
+
+ /* drive it active, also inactive->active edge */
+ gpiod_set_value(gpio_restart->reset_gpio, 1);
+
+ /* give it some time */
+ mdelay(gpio_restart->wait_delay_ms);
+
+ WARN_ON(1);
+
+ return NOTIFY_DONE;
+}
+
+static int gpio_restart_probe(struct platform_device *pdev)
+{
+ struct gpio_restart *gpio_restart;
+ bool open_source = false;
+ u32 property;
+ int ret;
+
+ gpio_restart = devm_kzalloc(&pdev->dev, sizeof(*gpio_restart),
+ GFP_KERNEL);
+ if (!gpio_restart)
+ return -ENOMEM;
+
+ open_source = of_property_read_bool(pdev->dev.of_node, "open-source");
+
+ gpio_restart->reset_gpio = devm_gpiod_get(&pdev->dev, NULL,
+ open_source ? GPIOD_IN : GPIOD_OUT_LOW);
+ if (IS_ERR(gpio_restart->reset_gpio)) {
+ dev_err(&pdev->dev, "Could net get reset GPIO\n");
+ return PTR_ERR(gpio_restart->reset_gpio);
+ }
+
+ gpio_restart->restart_handler.notifier_call = gpio_restart_notify;
+ gpio_restart->restart_handler.priority = 129;
+ gpio_restart->active_delay_ms = 100;
+ gpio_restart->inactive_delay_ms = 100;
+ gpio_restart->wait_delay_ms = 3000;
+
+ ret = of_property_read_u32(pdev->dev.of_node, "priority", &property);
+ if (!ret) {
+ if (property > 255)
+ dev_err(&pdev->dev, "Invalid priority property: %u\n",
+ property);
+ else
+ gpio_restart->restart_handler.priority = property;
+ }
+
+ of_property_read_u32(pdev->dev.of_node, "active-delay",
+ &gpio_restart->active_delay_ms);
+ of_property_read_u32(pdev->dev.of_node, "inactive-delay",
+ &gpio_restart->inactive_delay_ms);
+ of_property_read_u32(pdev->dev.of_node, "wait-delay",
+ &gpio_restart->wait_delay_ms);
+
+ platform_set_drvdata(pdev, gpio_restart);
+
+ ret = register_restart_handler(&gpio_restart->restart_handler);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: cannot register restart handler, %d\n",
+ __func__, ret);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int gpio_restart_remove(struct platform_device *pdev)
+{
+ struct gpio_restart *gpio_restart = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = unregister_restart_handler(&gpio_restart->restart_handler);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "%s: cannot unregister restart handler, %d\n",
+ __func__, ret);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id of_gpio_restart_match[] = {
+ { .compatible = "gpio-restart", },
+ {},
+};
+
+static struct platform_driver gpio_restart_driver = {
+ .probe = gpio_restart_probe,
+ .remove = gpio_restart_remove,
+ .driver = {
+ .name = "restart-gpio",
+ .of_match_table = of_gpio_restart_match,
+ },
+};
+
+module_platform_driver(gpio_restart_driver);
+
+MODULE_AUTHOR("David Riley <davidriley@chromium.org>");
+MODULE_DESCRIPTION("GPIO restart driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/reset/hisi-reboot.c b/drivers/power/reset/hisi-reboot.c
new file mode 100644
index 000000000..f69387e12
--- /dev/null
+++ b/drivers/power/reset/hisi-reboot.c
@@ -0,0 +1,82 @@
+/*
+ * Hisilicon SoC reset code
+ *
+ * Copyright (c) 2014 Hisilicon Ltd.
+ * Copyright (c) 2014 Linaro Ltd.
+ *
+ * Author: Haojian Zhuang <haojian.zhuang@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+
+#include <asm/proc-fns.h>
+
+static void __iomem *base;
+static u32 reboot_offset;
+
+static int hisi_restart_handler(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ writel_relaxed(0xdeadbeef, base + reboot_offset);
+
+ while (1)
+ cpu_do_idle();
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block hisi_restart_nb = {
+ .notifier_call = hisi_restart_handler,
+ .priority = 128,
+};
+
+static int hisi_reboot_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ int err;
+
+ base = of_iomap(np, 0);
+ if (!base) {
+ WARN(1, "failed to map base address");
+ return -ENODEV;
+ }
+
+ if (of_property_read_u32(np, "reboot-offset", &reboot_offset) < 0) {
+ pr_err("failed to find reboot-offset property\n");
+ iounmap(base);
+ return -EINVAL;
+ }
+
+ err = register_restart_handler(&hisi_restart_nb);
+ if (err) {
+ dev_err(&pdev->dev, "cannot register restart handler (err=%d)\n",
+ err);
+ iounmap(base);
+ }
+
+ return err;
+}
+
+static const struct of_device_id hisi_reboot_of_match[] = {
+ { .compatible = "hisilicon,sysctrl" },
+ {}
+};
+
+static struct platform_driver hisi_reboot_driver = {
+ .probe = hisi_reboot_probe,
+ .driver = {
+ .name = "hisi-reboot",
+ .of_match_table = hisi_reboot_of_match,
+ },
+};
+module_platform_driver(hisi_reboot_driver);
diff --git a/drivers/power/reset/keystone-reset.c b/drivers/power/reset/keystone-reset.c
new file mode 100644
index 000000000..09380857a
--- /dev/null
+++ b/drivers/power/reset/keystone-reset.c
@@ -0,0 +1,175 @@
+/*
+ * TI keystone reboot driver
+ *
+ * Copyright (C) 2014 Texas Instruments Incorporated. http://www.ti.com/
+ *
+ * Author: Ivan Khoronzhuk <ivan.khoronzhuk@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_platform.h>
+
+#define RSTYPE_RG 0x0
+#define RSCTRL_RG 0x4
+#define RSCFG_RG 0x8
+#define RSISO_RG 0xc
+
+#define RSCTRL_KEY_MASK 0x0000ffff
+#define RSCTRL_RESET_MASK BIT(16)
+#define RSCTRL_KEY 0x5a69
+
+#define RSMUX_OMODE_MASK 0xe
+#define RSMUX_OMODE_RESET_ON 0xa
+#define RSMUX_OMODE_RESET_OFF 0x0
+#define RSMUX_LOCK_MASK 0x1
+#define RSMUX_LOCK_SET 0x1
+
+#define RSCFG_RSTYPE_SOFT 0x300f
+#define RSCFG_RSTYPE_HARD 0x0
+
+#define WDT_MUX_NUMBER 0x4
+
+static int rspll_offset;
+static struct regmap *pllctrl_regs;
+
+/**
+ * rsctrl_enable_rspll_write - enable access to RSCTRL, RSCFG
+ * To be able to access to RSCTRL, RSCFG registers
+ * we have to write a key before
+ */
+static inline int rsctrl_enable_rspll_write(void)
+{
+ return regmap_update_bits(pllctrl_regs, rspll_offset + RSCTRL_RG,
+ RSCTRL_KEY_MASK, RSCTRL_KEY);
+}
+
+static int rsctrl_restart_handler(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ /* enable write access to RSTCTRL */
+ rsctrl_enable_rspll_write();
+
+ /* reset the SOC */
+ regmap_update_bits(pllctrl_regs, rspll_offset + RSCTRL_RG,
+ RSCTRL_RESET_MASK, 0);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block rsctrl_restart_nb = {
+ .notifier_call = rsctrl_restart_handler,
+ .priority = 128,
+};
+
+static const struct of_device_id rsctrl_of_match[] = {
+ {.compatible = "ti,keystone-reset", },
+ {},
+};
+
+static int rsctrl_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret;
+ u32 val;
+ unsigned int rg;
+ u32 rsmux_offset;
+ struct regmap *devctrl_regs;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+
+ if (!np)
+ return -ENODEV;
+
+ /* get regmaps */
+ pllctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-pll");
+ if (IS_ERR(pllctrl_regs))
+ return PTR_ERR(pllctrl_regs);
+
+ devctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-dev");
+ if (IS_ERR(devctrl_regs))
+ return PTR_ERR(devctrl_regs);
+
+ ret = of_property_read_u32_index(np, "ti,syscon-pll", 1, &rspll_offset);
+ if (ret) {
+ dev_err(dev, "couldn't read the reset pll offset!\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_index(np, "ti,syscon-dev", 1, &rsmux_offset);
+ if (ret) {
+ dev_err(dev, "couldn't read the rsmux offset!\n");
+ return -EINVAL;
+ }
+
+ /* set soft/hard reset */
+ val = of_property_read_bool(np, "ti,soft-reset");
+ val = val ? RSCFG_RSTYPE_SOFT : RSCFG_RSTYPE_HARD;
+
+ ret = rsctrl_enable_rspll_write();
+ if (ret)
+ return ret;
+
+ ret = regmap_write(pllctrl_regs, rspll_offset + RSCFG_RG, val);
+ if (ret)
+ return ret;
+
+ /* disable a reset isolation for all module clocks */
+ ret = regmap_write(pllctrl_regs, rspll_offset + RSISO_RG, 0);
+ if (ret)
+ return ret;
+
+ /* enable a reset for watchdogs from wdt-list */
+ for (i = 0; i < WDT_MUX_NUMBER; i++) {
+ ret = of_property_read_u32_index(np, "ti,wdt-list", i, &val);
+ if (ret == -EOVERFLOW && !i) {
+ dev_err(dev, "ti,wdt-list property has to contain at"
+ "least one entry\n");
+ return -EINVAL;
+ } else if (ret) {
+ break;
+ }
+
+ if (val >= WDT_MUX_NUMBER) {
+ dev_err(dev, "ti,wdt-list property can contain "
+ "only numbers < 4\n");
+ return -EINVAL;
+ }
+
+ rg = rsmux_offset + val * 4;
+
+ ret = regmap_update_bits(devctrl_regs, rg, RSMUX_OMODE_MASK,
+ RSMUX_OMODE_RESET_ON |
+ RSMUX_LOCK_SET);
+ if (ret)
+ return ret;
+ }
+
+ ret = register_restart_handler(&rsctrl_restart_nb);
+ if (ret)
+ dev_err(dev, "cannot register restart handler (err=%d)\n", ret);
+
+ return ret;
+}
+
+static struct platform_driver rsctrl_driver = {
+ .probe = rsctrl_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = rsctrl_of_match,
+ },
+};
+module_platform_driver(rsctrl_driver);
+
+MODULE_AUTHOR("Ivan Khoronzhuk <ivan.khoronzhuk@ti.com>");
+MODULE_DESCRIPTION("Texas Instruments keystone reset driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
diff --git a/drivers/power/reset/ltc2952-poweroff.c b/drivers/power/reset/ltc2952-poweroff.c
new file mode 100644
index 000000000..8a76ef841
--- /dev/null
+++ b/drivers/power/reset/ltc2952-poweroff.c
@@ -0,0 +1,324 @@
+/*
+ * LTC2952 (PowerPath) driver
+ *
+ * Copyright (C) 2014, Xsens Technologies BV <info@xsens.com>
+ * Maintainer: René Moll <linux@r-moll.nl>
+ *
+ * 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * ----------------------------------------
+ * - Description
+ * ----------------------------------------
+ *
+ * This driver is to be used with an external PowerPath Controller (LTC2952).
+ * Its function is to determine when a external shut down is triggered
+ * and react by properly shutting down the system.
+ *
+ * This driver expects a device tree with a ltc2952 entry for pin mapping.
+ *
+ * ----------------------------------------
+ * - GPIO
+ * ----------------------------------------
+ *
+ * The following GPIOs are used:
+ * - trigger (input)
+ * A level change indicates the shut-down trigger. If it's state reverts
+ * within the time-out defined by trigger_delay, the shut down is not
+ * executed. If no pin is assigned to this input, the driver will start the
+ * watchdog toggle immediately. The chip will only power off the system if
+ * it is requested to do so through the kill line.
+ *
+ * - watchdog (output)
+ * Once a shut down is triggered, the driver will toggle this signal,
+ * with an internal (wde_interval) to stall the hardware shut down.
+ *
+ * - kill (output)
+ * The last action during shut down is triggering this signalling, such
+ * that the PowerPath Control will power down the hardware.
+ *
+ * ----------------------------------------
+ * - Interrupts
+ * ----------------------------------------
+ *
+ * The driver requires a non-shared, edge-triggered interrupt on the trigger
+ * GPIO.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/ktime.h>
+#include <linux/slab.h>
+#include <linux/kmod.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/gpio/consumer.h>
+#include <linux/reboot.h>
+
+struct ltc2952_poweroff {
+ struct hrtimer timer_trigger;
+ struct hrtimer timer_wde;
+
+ ktime_t trigger_delay;
+ ktime_t wde_interval;
+
+ struct device *dev;
+
+ struct gpio_desc *gpio_trigger;
+ struct gpio_desc *gpio_watchdog;
+ struct gpio_desc *gpio_kill;
+
+ bool kernel_panic;
+ struct notifier_block panic_notifier;
+};
+
+#define to_ltc2952(p, m) container_of(p, struct ltc2952_poweroff, m)
+
+/*
+ * This global variable is only needed for pm_power_off. We should
+ * remove it entirely once we don't need the global state anymore.
+ */
+static struct ltc2952_poweroff *ltc2952_data;
+
+/**
+ * ltc2952_poweroff_timer_wde - Timer callback
+ * Toggles the watchdog reset signal each wde_interval
+ *
+ * @timer: corresponding timer
+ *
+ * Returns HRTIMER_RESTART for an infinite loop which will only stop when the
+ * machine actually shuts down
+ */
+static enum hrtimer_restart ltc2952_poweroff_timer_wde(struct hrtimer *timer)
+{
+ ktime_t now;
+ int state;
+ unsigned long overruns;
+ struct ltc2952_poweroff *data = to_ltc2952(timer, timer_wde);
+
+ if (data->kernel_panic)
+ return HRTIMER_NORESTART;
+
+ state = gpiod_get_value(data->gpio_watchdog);
+ gpiod_set_value(data->gpio_watchdog, !state);
+
+ now = hrtimer_cb_get_time(timer);
+ overruns = hrtimer_forward(timer, now, data->wde_interval);
+
+ return HRTIMER_RESTART;
+}
+
+static void ltc2952_poweroff_start_wde(struct ltc2952_poweroff *data)
+{
+ hrtimer_start(&data->timer_wde, data->wde_interval, HRTIMER_MODE_REL);
+}
+
+static enum hrtimer_restart
+ltc2952_poweroff_timer_trigger(struct hrtimer *timer)
+{
+ struct ltc2952_poweroff *data = to_ltc2952(timer, timer_trigger);
+
+ ltc2952_poweroff_start_wde(data);
+ dev_info(data->dev, "executing shutdown\n");
+ orderly_poweroff(true);
+
+ return HRTIMER_NORESTART;
+}
+
+/**
+ * ltc2952_poweroff_handler - Interrupt handler
+ * Triggered each time the trigger signal changes state and (de)activates a
+ * time-out (timer_trigger). Once the time-out is actually reached the shut
+ * down is executed.
+ *
+ * @irq: IRQ number
+ * @dev_id: pointer to the main data structure
+ */
+static irqreturn_t ltc2952_poweroff_handler(int irq, void *dev_id)
+{
+ struct ltc2952_poweroff *data = dev_id;
+
+ if (data->kernel_panic || hrtimer_active(&data->timer_wde)) {
+ /* shutdown is already triggered, nothing to do any more */
+ return IRQ_HANDLED;
+ }
+
+ if (gpiod_get_value(data->gpio_trigger)) {
+ hrtimer_start(&data->timer_trigger, data->trigger_delay,
+ HRTIMER_MODE_REL);
+ } else {
+ hrtimer_cancel(&data->timer_trigger);
+ }
+ return IRQ_HANDLED;
+}
+
+static void ltc2952_poweroff_kill(void)
+{
+ gpiod_set_value(ltc2952_data->gpio_kill, 1);
+}
+
+static void ltc2952_poweroff_default(struct ltc2952_poweroff *data)
+{
+ data->wde_interval = 300L * NSEC_PER_MSEC;
+ data->trigger_delay = ktime_set(2, 500L * NSEC_PER_MSEC);
+
+ hrtimer_init(&data->timer_trigger, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ data->timer_trigger.function = ltc2952_poweroff_timer_trigger;
+
+ hrtimer_init(&data->timer_wde, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ data->timer_wde.function = ltc2952_poweroff_timer_wde;
+}
+
+static int ltc2952_poweroff_init(struct platform_device *pdev)
+{
+ int ret;
+ struct ltc2952_poweroff *data = platform_get_drvdata(pdev);
+
+ ltc2952_poweroff_default(data);
+
+ data->gpio_watchdog = devm_gpiod_get(&pdev->dev, "watchdog",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(data->gpio_watchdog)) {
+ ret = PTR_ERR(data->gpio_watchdog);
+ dev_err(&pdev->dev, "unable to claim gpio \"watchdog\"\n");
+ return ret;
+ }
+
+ data->gpio_kill = devm_gpiod_get(&pdev->dev, "kill", GPIOD_OUT_LOW);
+ if (IS_ERR(data->gpio_kill)) {
+ ret = PTR_ERR(data->gpio_kill);
+ dev_err(&pdev->dev, "unable to claim gpio \"kill\"\n");
+ return ret;
+ }
+
+ data->gpio_trigger = devm_gpiod_get_optional(&pdev->dev, "trigger",
+ GPIOD_IN);
+ if (IS_ERR(data->gpio_trigger)) {
+ /*
+ * It's not a problem if the trigger gpio isn't available, but
+ * it is worth a warning if its use was defined in the device
+ * tree.
+ */
+ dev_err(&pdev->dev, "unable to claim gpio \"trigger\"\n");
+ data->gpio_trigger = NULL;
+ }
+
+ if (devm_request_irq(&pdev->dev, gpiod_to_irq(data->gpio_trigger),
+ ltc2952_poweroff_handler,
+ (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING),
+ "ltc2952-poweroff",
+ data)) {
+ /*
+ * Some things may have happened:
+ * - No trigger input was defined
+ * - Claiming the GPIO failed
+ * - We could not map to an IRQ
+ * - We couldn't register an interrupt handler
+ *
+ * None of these really are problems, but all of them
+ * disqualify the push button from controlling the power.
+ *
+ * It is therefore important to note that if the ltc2952
+ * detects a button press for long enough, it will still start
+ * its own powerdown window and cut the power on us if we don't
+ * start the watchdog trigger.
+ */
+ if (data->gpio_trigger) {
+ dev_warn(&pdev->dev,
+ "unable to configure the trigger interrupt\n");
+ devm_gpiod_put(&pdev->dev, data->gpio_trigger);
+ data->gpio_trigger = NULL;
+ }
+ dev_info(&pdev->dev,
+ "power down trigger input will not be used\n");
+ ltc2952_poweroff_start_wde(data);
+ }
+
+ return 0;
+}
+
+static int ltc2952_poweroff_notify_panic(struct notifier_block *nb,
+ unsigned long code, void *unused)
+{
+ struct ltc2952_poweroff *data = to_ltc2952(nb, panic_notifier);
+
+ data->kernel_panic = true;
+ return NOTIFY_DONE;
+}
+
+static int ltc2952_poweroff_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct ltc2952_poweroff *data;
+
+ if (pm_power_off) {
+ dev_err(&pdev->dev, "pm_power_off already registered");
+ return -EBUSY;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = &pdev->dev;
+ platform_set_drvdata(pdev, data);
+
+ ret = ltc2952_poweroff_init(pdev);
+ if (ret)
+ return ret;
+
+ /* TODO: remove ltc2952_data */
+ ltc2952_data = data;
+ pm_power_off = ltc2952_poweroff_kill;
+
+ data->panic_notifier.notifier_call = ltc2952_poweroff_notify_panic;
+ atomic_notifier_chain_register(&panic_notifier_list,
+ &data->panic_notifier);
+ dev_info(&pdev->dev, "probe successful\n");
+
+ return 0;
+}
+
+static int ltc2952_poweroff_remove(struct platform_device *pdev)
+{
+ struct ltc2952_poweroff *data = platform_get_drvdata(pdev);
+
+ pm_power_off = NULL;
+ hrtimer_cancel(&data->timer_trigger);
+ hrtimer_cancel(&data->timer_wde);
+ atomic_notifier_chain_unregister(&panic_notifier_list,
+ &data->panic_notifier);
+ return 0;
+}
+
+static const struct of_device_id of_ltc2952_poweroff_match[] = {
+ { .compatible = "lltc,ltc2952"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_ltc2952_poweroff_match);
+
+static struct platform_driver ltc2952_poweroff_driver = {
+ .probe = ltc2952_poweroff_probe,
+ .remove = ltc2952_poweroff_remove,
+ .driver = {
+ .name = "ltc2952-poweroff",
+ .of_match_table = of_ltc2952_poweroff_match,
+ },
+};
+
+module_platform_driver(ltc2952_poweroff_driver);
+
+MODULE_AUTHOR("René Moll <rene.moll@xsens.com>");
+MODULE_DESCRIPTION("LTC PowerPath power-off driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c
new file mode 100644
index 000000000..01b8c7169
--- /dev/null
+++ b/drivers/power/reset/msm-poweroff.c
@@ -0,0 +1,80 @@
+/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+#include <linux/pm.h>
+
+static void __iomem *msm_ps_hold;
+static int deassert_pshold(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ writel(0, msm_ps_hold);
+ mdelay(10000);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block restart_nb = {
+ .notifier_call = deassert_pshold,
+ .priority = 128,
+};
+
+static void do_msm_poweroff(void)
+{
+ deassert_pshold(&restart_nb, 0, NULL);
+}
+
+static int msm_restart_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *mem;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ msm_ps_hold = devm_ioremap_resource(dev, mem);
+ if (IS_ERR(msm_ps_hold))
+ return PTR_ERR(msm_ps_hold);
+
+ register_restart_handler(&restart_nb);
+
+ pm_power_off = do_msm_poweroff;
+
+ return 0;
+}
+
+static const struct of_device_id of_msm_restart_match[] = {
+ { .compatible = "qcom,pshold", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_msm_restart_match);
+
+static struct platform_driver msm_restart_driver = {
+ .probe = msm_restart_probe,
+ .driver = {
+ .name = "msm-restart",
+ .of_match_table = of_match_ptr(of_msm_restart_match),
+ },
+};
+
+static int __init msm_restart_init(void)
+{
+ return platform_driver_register(&msm_restart_driver);
+}
+device_initcall(msm_restart_init);
diff --git a/drivers/power/reset/ocelot-reset.c b/drivers/power/reset/ocelot-reset.c
new file mode 100644
index 000000000..5a13a5cc8
--- /dev/null
+++ b/drivers/power/reset/ocelot-reset.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Microsemi MIPS SoC reset driver
+ *
+ * License: Dual MIT/GPL
+ * Copyright (c) 2017 Microsemi Corporation
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/notifier.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+
+struct ocelot_reset_context {
+ void __iomem *base;
+ struct regmap *cpu_ctrl;
+ struct notifier_block restart_handler;
+};
+
+#define ICPU_CFG_CPU_SYSTEM_CTRL_RESET 0x20
+#define CORE_RST_PROTECT BIT(2)
+
+#define SOFT_CHIP_RST BIT(0)
+
+static int ocelot_restart_handle(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct ocelot_reset_context *ctx = container_of(this, struct
+ ocelot_reset_context,
+ restart_handler);
+
+ /* Make sure the core is not protected from reset */
+ regmap_update_bits(ctx->cpu_ctrl, ICPU_CFG_CPU_SYSTEM_CTRL_RESET,
+ CORE_RST_PROTECT, 0);
+
+ writel(SOFT_CHIP_RST, ctx->base);
+
+ pr_emerg("Unable to restart system\n");
+ return NOTIFY_DONE;
+}
+
+static int ocelot_reset_probe(struct platform_device *pdev)
+{
+ struct ocelot_reset_context *ctx;
+ struct resource *res;
+
+ struct device *dev = &pdev->dev;
+ int err;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ ctx->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(ctx->base))
+ return PTR_ERR(ctx->base);
+
+ ctx->cpu_ctrl = syscon_regmap_lookup_by_compatible("mscc,ocelot-cpu-syscon");
+ if (IS_ERR(ctx->cpu_ctrl))
+ return PTR_ERR(ctx->cpu_ctrl);
+
+ ctx->restart_handler.notifier_call = ocelot_restart_handle;
+ ctx->restart_handler.priority = 192;
+ err = register_restart_handler(&ctx->restart_handler);
+ if (err)
+ dev_err(dev, "can't register restart notifier (err=%d)\n", err);
+
+ return err;
+}
+
+static const struct of_device_id ocelot_reset_of_match[] = {
+ { .compatible = "mscc,ocelot-chip-reset" },
+ {}
+};
+
+static struct platform_driver ocelot_reset_driver = {
+ .probe = ocelot_reset_probe,
+ .driver = {
+ .name = "ocelot-chip-reset",
+ .of_match_table = ocelot_reset_of_match,
+ },
+};
+builtin_platform_driver(ocelot_reset_driver);
diff --git a/drivers/power/reset/piix4-poweroff.c b/drivers/power/reset/piix4-poweroff.c
new file mode 100644
index 000000000..20ce3ff5e
--- /dev/null
+++ b/drivers/power/reset/piix4-poweroff.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 Imagination Technologies
+ * Author: Paul Burton <paul.burton@mips.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; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+
+static struct pci_dev *pm_dev;
+static resource_size_t io_offset;
+
+enum piix4_pm_io_reg {
+ PIIX4_FUNC3IO_PMSTS = 0x00,
+#define PIIX4_FUNC3IO_PMSTS_PWRBTN_STS BIT(8)
+ PIIX4_FUNC3IO_PMCNTRL = 0x04,
+#define PIIX4_FUNC3IO_PMCNTRL_SUS_EN BIT(13)
+#define PIIX4_FUNC3IO_PMCNTRL_SUS_TYP_SOFF (0x0 << 10)
+};
+
+#define PIIX4_SUSPEND_MAGIC 0x00120002
+
+static const int piix4_pm_io_region = PCI_BRIDGE_RESOURCES;
+
+static void piix4_poweroff(void)
+{
+ int spec_devid;
+ u16 sts;
+
+ /* Ensure the power button status is clear */
+ while (1) {
+ sts = inw(io_offset + PIIX4_FUNC3IO_PMSTS);
+ if (!(sts & PIIX4_FUNC3IO_PMSTS_PWRBTN_STS))
+ break;
+ outw(sts, io_offset + PIIX4_FUNC3IO_PMSTS);
+ }
+
+ /* Enable entry to suspend */
+ outw(PIIX4_FUNC3IO_PMCNTRL_SUS_TYP_SOFF | PIIX4_FUNC3IO_PMCNTRL_SUS_EN,
+ io_offset + PIIX4_FUNC3IO_PMCNTRL);
+
+ /* If the special cycle occurs too soon this doesn't work... */
+ mdelay(10);
+
+ /*
+ * The PIIX4 will enter the suspend state only after seeing a special
+ * cycle with the correct magic data on the PCI bus. Generate that
+ * cycle now.
+ */
+ spec_devid = PCI_DEVID(0, PCI_DEVFN(0x1f, 0x7));
+ pci_bus_write_config_dword(pm_dev->bus, spec_devid, 0,
+ PIIX4_SUSPEND_MAGIC);
+
+ /* Give the system some time to power down, then error */
+ mdelay(1000);
+ pr_emerg("Unable to poweroff system\n");
+}
+
+static int piix4_poweroff_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ int res;
+
+ if (pm_dev)
+ return -EINVAL;
+
+ /* Request access to the PIIX4 PM IO registers */
+ res = pci_request_region(dev, piix4_pm_io_region,
+ "PIIX4 PM IO registers");
+ if (res) {
+ dev_err(&dev->dev, "failed to request PM IO registers: %d\n",
+ res);
+ return res;
+ }
+
+ pm_dev = dev;
+ io_offset = pci_resource_start(dev, piix4_pm_io_region);
+ pm_power_off = piix4_poweroff;
+
+ return 0;
+}
+
+static void piix4_poweroff_remove(struct pci_dev *dev)
+{
+ if (pm_power_off == piix4_poweroff)
+ pm_power_off = NULL;
+
+ pci_release_region(dev, piix4_pm_io_region);
+ pm_dev = NULL;
+}
+
+static const struct pci_device_id piix4_poweroff_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3) },
+ { 0 },
+};
+
+static struct pci_driver piix4_poweroff_driver = {
+ .name = "piix4-poweroff",
+ .id_table = piix4_poweroff_ids,
+ .probe = piix4_poweroff_probe,
+ .remove = piix4_poweroff_remove,
+};
+
+module_pci_driver(piix4_poweroff_driver);
+MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c
new file mode 100644
index 000000000..0c4caaa7e
--- /dev/null
+++ b/drivers/power/reset/qcom-pon.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2017-18 Linaro Limited
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/reboot-mode.h>
+#include <linux/regmap.h>
+
+#define PON_SOFT_RB_SPARE 0x8f
+
+struct pm8916_pon {
+ struct device *dev;
+ struct regmap *regmap;
+ u32 baseaddr;
+ struct reboot_mode_driver reboot_mode;
+};
+
+static int pm8916_reboot_mode_write(struct reboot_mode_driver *reboot,
+ unsigned int magic)
+{
+ struct pm8916_pon *pon = container_of
+ (reboot, struct pm8916_pon, reboot_mode);
+ int ret;
+
+ ret = regmap_update_bits(pon->regmap,
+ pon->baseaddr + PON_SOFT_RB_SPARE,
+ 0xfc, magic << 2);
+ if (ret < 0)
+ dev_err(pon->dev, "update reboot mode bits failed\n");
+
+ return ret;
+}
+
+static int pm8916_pon_probe(struct platform_device *pdev)
+{
+ struct pm8916_pon *pon;
+ int error;
+
+ pon = devm_kzalloc(&pdev->dev, sizeof(*pon), GFP_KERNEL);
+ if (!pon)
+ return -ENOMEM;
+
+ pon->dev = &pdev->dev;
+
+ pon->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!pon->regmap) {
+ dev_err(&pdev->dev, "failed to locate regmap\n");
+ return -ENODEV;
+ }
+
+ error = of_property_read_u32(pdev->dev.of_node, "reg",
+ &pon->baseaddr);
+ if (error)
+ return error;
+
+ pon->reboot_mode.dev = &pdev->dev;
+ pon->reboot_mode.write = pm8916_reboot_mode_write;
+ error = devm_reboot_mode_register(&pdev->dev, &pon->reboot_mode);
+ if (error) {
+ dev_err(&pdev->dev, "can't register reboot mode\n");
+ return error;
+ }
+
+ platform_set_drvdata(pdev, pon);
+
+ return devm_of_platform_populate(&pdev->dev);
+}
+
+static const struct of_device_id pm8916_pon_id_table[] = {
+ { .compatible = "qcom,pm8916-pon" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pm8916_pon_id_table);
+
+static struct platform_driver pm8916_pon_driver = {
+ .probe = pm8916_pon_probe,
+ .driver = {
+ .name = "pm8916-pon",
+ .of_match_table = of_match_ptr(pm8916_pon_id_table),
+ },
+};
+module_platform_driver(pm8916_pon_driver);
+
+MODULE_DESCRIPTION("pm8916 Power On driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/qnap-poweroff.c b/drivers/power/reset/qnap-poweroff.c
new file mode 100644
index 000000000..2789a61ce
--- /dev/null
+++ b/drivers/power/reset/qnap-poweroff.c
@@ -0,0 +1,140 @@
+/*
+ * QNAP Turbo NAS Board power off. Can also be used on Synology devices.
+ *
+ * Copyright (C) 2012 Andrew Lunn <andrew@lunn.ch>
+ *
+ * Based on the code from:
+ *
+ * Copyright (C) 2009 Martin Michlmayr <tbm@cyrius.com>
+ * Copyright (C) 2008 Byron Bradley <byron.bbradley@gmail.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; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/serial_reg.h>
+#include <linux/kallsyms.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+
+#define UART1_REG(x) (base + ((UART_##x) << 2))
+
+struct power_off_cfg {
+ u32 baud;
+ char cmd;
+};
+
+static const struct power_off_cfg qnap_power_off_cfg = {
+ .baud = 19200,
+ .cmd = 'A',
+};
+
+static const struct power_off_cfg synology_power_off_cfg = {
+ .baud = 9600,
+ .cmd = '1',
+};
+
+static const struct of_device_id qnap_power_off_of_match_table[] = {
+ { .compatible = "qnap,power-off",
+ .data = &qnap_power_off_cfg,
+ },
+ { .compatible = "synology,power-off",
+ .data = &synology_power_off_cfg,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, qnap_power_off_of_match_table);
+
+static void __iomem *base;
+static unsigned long tclk;
+static const struct power_off_cfg *cfg;
+
+static void qnap_power_off(void)
+{
+ const unsigned divisor = ((tclk + (8 * cfg->baud)) / (16 * cfg->baud));
+
+ pr_err("%s: triggering power-off...\n", __func__);
+
+ /* hijack UART1 and reset into sane state */
+ writel(0x83, UART1_REG(LCR));
+ writel(divisor & 0xff, UART1_REG(DLL));
+ writel((divisor >> 8) & 0xff, UART1_REG(DLM));
+ writel(0x03, UART1_REG(LCR));
+ writel(0x00, UART1_REG(IER));
+ writel(0x00, UART1_REG(FCR));
+ writel(0x00, UART1_REG(MCR));
+
+ /* send the power-off command to PIC */
+ writel(cfg->cmd, UART1_REG(TX));
+}
+
+static int qnap_power_off_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct resource *res;
+ struct clk *clk;
+ char symname[KSYM_NAME_LEN];
+
+ const struct of_device_id *match =
+ of_match_node(qnap_power_off_of_match_table, np);
+ cfg = match->data;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Missing resource");
+ return -EINVAL;
+ }
+
+ base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+ if (!base) {
+ dev_err(&pdev->dev, "Unable to map resource");
+ return -EINVAL;
+ }
+
+ /* We need to know tclk in order to calculate the UART divisor */
+ clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(&pdev->dev, "Clk missing");
+ return PTR_ERR(clk);
+ }
+
+ tclk = clk_get_rate(clk);
+
+ /* Check that nothing else has already setup a handler */
+ if (pm_power_off) {
+ lookup_symbol_name((ulong)pm_power_off, symname);
+ dev_err(&pdev->dev,
+ "pm_power_off already claimed %p %s",
+ pm_power_off, symname);
+ return -EBUSY;
+ }
+ pm_power_off = qnap_power_off;
+
+ return 0;
+}
+
+static int qnap_power_off_remove(struct platform_device *pdev)
+{
+ pm_power_off = NULL;
+ return 0;
+}
+
+static struct platform_driver qnap_power_off_driver = {
+ .probe = qnap_power_off_probe,
+ .remove = qnap_power_off_remove,
+ .driver = {
+ .name = "qnap_power_off",
+ .of_match_table = of_match_ptr(qnap_power_off_of_match_table),
+ },
+};
+module_platform_driver(qnap_power_off_driver);
+
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
+MODULE_DESCRIPTION("QNAP Power off driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c
new file mode 100644
index 000000000..8f975ca0a
--- /dev/null
+++ b/drivers/power/reset/reboot-mode.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/reboot.h>
+#include <linux/reboot-mode.h>
+
+#define PREFIX "mode-"
+
+struct mode_info {
+ const char *mode;
+ u32 magic;
+ struct list_head list;
+};
+
+static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot,
+ const char *cmd)
+{
+ const char *normal = "normal";
+ int magic = 0;
+ struct mode_info *info;
+
+ if (!cmd)
+ cmd = normal;
+
+ list_for_each_entry(info, &reboot->head, list) {
+ if (!strcmp(info->mode, cmd)) {
+ magic = info->magic;
+ break;
+ }
+ }
+
+ return magic;
+}
+
+static int reboot_mode_notify(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct reboot_mode_driver *reboot;
+ unsigned int magic;
+
+ reboot = container_of(this, struct reboot_mode_driver, reboot_notifier);
+ magic = get_reboot_mode_magic(reboot, cmd);
+ if (magic)
+ reboot->write(reboot, magic);
+
+ return NOTIFY_DONE;
+}
+
+/**
+ * reboot_mode_register - register a reboot mode driver
+ * @reboot: reboot mode driver
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ */
+int reboot_mode_register(struct reboot_mode_driver *reboot)
+{
+ struct mode_info *info;
+ struct property *prop;
+ struct device_node *np = reboot->dev->of_node;
+ size_t len = strlen(PREFIX);
+ int ret;
+
+ INIT_LIST_HEAD(&reboot->head);
+
+ for_each_property_of_node(np, prop) {
+ if (strncmp(prop->name, PREFIX, len))
+ continue;
+
+ info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ if (of_property_read_u32(np, prop->name, &info->magic)) {
+ dev_err(reboot->dev, "reboot mode %s without magic number\n",
+ info->mode);
+ devm_kfree(reboot->dev, info);
+ continue;
+ }
+
+ info->mode = kstrdup_const(prop->name + len, GFP_KERNEL);
+ if (!info->mode) {
+ ret = -ENOMEM;
+ goto error;
+ } else if (info->mode[0] == '\0') {
+ kfree_const(info->mode);
+ ret = -EINVAL;
+ dev_err(reboot->dev, "invalid mode name(%s): too short!\n",
+ prop->name);
+ goto error;
+ }
+
+ list_add_tail(&info->list, &reboot->head);
+ }
+
+ reboot->reboot_notifier.notifier_call = reboot_mode_notify;
+ register_reboot_notifier(&reboot->reboot_notifier);
+
+ return 0;
+
+error:
+ list_for_each_entry(info, &reboot->head, list)
+ kfree_const(info->mode);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(reboot_mode_register);
+
+/**
+ * reboot_mode_unregister - unregister a reboot mode driver
+ * @reboot: reboot mode driver
+ */
+int reboot_mode_unregister(struct reboot_mode_driver *reboot)
+{
+ struct mode_info *info;
+
+ unregister_reboot_notifier(&reboot->reboot_notifier);
+
+ list_for_each_entry(info, &reboot->head, list)
+ kfree_const(info->mode);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(reboot_mode_unregister);
+
+static void devm_reboot_mode_release(struct device *dev, void *res)
+{
+ reboot_mode_unregister(*(struct reboot_mode_driver **)res);
+}
+
+/**
+ * devm_reboot_mode_register() - resource managed reboot_mode_register()
+ * @dev: device to associate this resource with
+ * @reboot: reboot mode driver
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ */
+int devm_reboot_mode_register(struct device *dev,
+ struct reboot_mode_driver *reboot)
+{
+ struct reboot_mode_driver **dr;
+ int rc;
+
+ dr = devres_alloc(devm_reboot_mode_release, sizeof(*dr), GFP_KERNEL);
+ if (!dr)
+ return -ENOMEM;
+
+ rc = reboot_mode_register(reboot);
+ if (rc) {
+ devres_free(dr);
+ return rc;
+ }
+
+ *dr = reboot;
+ devres_add(dev, dr);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devm_reboot_mode_register);
+
+static int devm_reboot_mode_match(struct device *dev, void *res, void *data)
+{
+ struct reboot_mode_driver **p = res;
+
+ if (WARN_ON(!p || !*p))
+ return 0;
+
+ return *p == data;
+}
+
+/**
+ * devm_reboot_mode_unregister() - resource managed reboot_mode_unregister()
+ * @dev: device to associate this resource with
+ * @reboot: reboot mode driver
+ */
+void devm_reboot_mode_unregister(struct device *dev,
+ struct reboot_mode_driver *reboot)
+{
+ WARN_ON(devres_release(dev,
+ devm_reboot_mode_release,
+ devm_reboot_mode_match, reboot));
+}
+EXPORT_SYMBOL_GPL(devm_reboot_mode_unregister);
+
+MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com");
+MODULE_DESCRIPTION("System reboot mode core library");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/restart-poweroff.c b/drivers/power/reset/restart-poweroff.c
new file mode 100644
index 000000000..41b22c4d5
--- /dev/null
+++ b/drivers/power/reset/restart-poweroff.c
@@ -0,0 +1,65 @@
+/*
+ * Power off by restarting and let u-boot keep hold of the machine
+ * until the user presses a button for example.
+ *
+ * Andrew Lunn <andrew@lunn.ch>
+ *
+ * Copyright (C) 2012 Andrew Lunn
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+
+static void restart_poweroff_do_poweroff(void)
+{
+ reboot_mode = REBOOT_HARD;
+ machine_restart(NULL);
+}
+
+static int restart_poweroff_probe(struct platform_device *pdev)
+{
+ /* If a pm_power_off function has already been added, leave it alone */
+ if (pm_power_off != NULL) {
+ dev_err(&pdev->dev,
+ "pm_power_off function already registered");
+ return -EBUSY;
+ }
+
+ pm_power_off = &restart_poweroff_do_poweroff;
+ return 0;
+}
+
+static int restart_poweroff_remove(struct platform_device *pdev)
+{
+ if (pm_power_off == &restart_poweroff_do_poweroff)
+ pm_power_off = NULL;
+
+ return 0;
+}
+
+static const struct of_device_id of_restart_poweroff_match[] = {
+ { .compatible = "restart-poweroff", },
+ {},
+};
+
+static struct platform_driver restart_poweroff_driver = {
+ .probe = restart_poweroff_probe,
+ .remove = restart_poweroff_remove,
+ .driver = {
+ .name = "poweroff-restart",
+ .of_match_table = of_restart_poweroff_match,
+ },
+};
+module_platform_driver(restart_poweroff_driver);
+
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch");
+MODULE_DESCRIPTION("restart poweroff driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:poweroff-restart");
diff --git a/drivers/power/reset/rmobile-reset.c b/drivers/power/reset/rmobile-reset.c
new file mode 100644
index 000000000..e6569df76
--- /dev/null
+++ b/drivers/power/reset/rmobile-reset.c
@@ -0,0 +1,91 @@
+/*
+ * Renesas R-Mobile Reset Driver
+ *
+ * Copyright (C) 2014 Glider bvba
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <linux/reboot.h>
+
+/* SYSC Register Bank 2 */
+#define RESCNT2 0x20 /* Reset Control Register 2 */
+
+/* Reset Control Register 2 */
+#define RESCNT2_PRES 0x80000000 /* Soft power-on reset */
+
+static void __iomem *sysc_base2;
+
+static int rmobile_reset_handler(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ pr_debug("%s %lu\n", __func__, mode);
+
+ /* Let's assume we have acquired the HPB semaphore */
+ writel(RESCNT2_PRES, sysc_base2 + RESCNT2);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block rmobile_reset_nb = {
+ .notifier_call = rmobile_reset_handler,
+ .priority = 192,
+};
+
+static int rmobile_reset_probe(struct platform_device *pdev)
+{
+ int error;
+
+ sysc_base2 = of_iomap(pdev->dev.of_node, 1);
+ if (!sysc_base2)
+ return -ENODEV;
+
+ error = register_restart_handler(&rmobile_reset_nb);
+ if (error) {
+ dev_err(&pdev->dev,
+ "cannot register restart handler (err=%d)\n", error);
+ goto fail_unmap;
+ }
+
+ return 0;
+
+fail_unmap:
+ iounmap(sysc_base2);
+ return error;
+}
+
+static int rmobile_reset_remove(struct platform_device *pdev)
+{
+ unregister_restart_handler(&rmobile_reset_nb);
+ iounmap(sysc_base2);
+ return 0;
+}
+
+static const struct of_device_id rmobile_reset_of_match[] = {
+ { .compatible = "renesas,sysc-rmobile", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rmobile_reset_of_match);
+
+static struct platform_driver rmobile_reset_driver = {
+ .probe = rmobile_reset_probe,
+ .remove = rmobile_reset_remove,
+ .driver = {
+ .name = "rmobile_reset",
+ .of_match_table = rmobile_reset_of_match,
+ },
+};
+
+module_platform_driver(rmobile_reset_driver);
+
+MODULE_DESCRIPTION("Renesas R-Mobile Reset Driver");
+MODULE_AUTHOR("Geert Uytterhoeven <geert+renesas@glider.be>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/sc27xx-poweroff.c b/drivers/power/reset/sc27xx-poweroff.c
new file mode 100644
index 000000000..29fb08b8f
--- /dev/null
+++ b/drivers/power/reset/sc27xx-poweroff.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Spreadtrum Communications Inc.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/cpu.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/syscore_ops.h>
+
+#define SC27XX_PWR_PD_HW 0xc2c
+#define SC27XX_PWR_OFF_EN BIT(0)
+
+static struct regmap *regmap;
+
+/*
+ * On Spreadtrum platform, we need power off system through external SC27xx
+ * series PMICs, and it is one similar SPI bus mapped by regmap to access PMIC,
+ * which is not fast io access.
+ *
+ * So before stopping other cores, we need release other cores' resource by
+ * taking cpus down to avoid racing regmap or spi mutex lock when poweroff
+ * system through PMIC.
+ */
+static void sc27xx_poweroff_shutdown(void)
+{
+#ifdef CONFIG_PM_SLEEP_SMP
+ int cpu = smp_processor_id();
+
+ freeze_secondary_cpus(cpu);
+#endif
+}
+
+static struct syscore_ops poweroff_syscore_ops = {
+ .shutdown = sc27xx_poweroff_shutdown,
+};
+
+static void sc27xx_poweroff_do_poweroff(void)
+{
+ regmap_write(regmap, SC27XX_PWR_PD_HW, SC27XX_PWR_OFF_EN);
+}
+
+static int sc27xx_poweroff_probe(struct platform_device *pdev)
+{
+ if (regmap)
+ return -EINVAL;
+
+ regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!regmap)
+ return -ENODEV;
+
+ pm_power_off = sc27xx_poweroff_do_poweroff;
+ register_syscore_ops(&poweroff_syscore_ops);
+ return 0;
+}
+
+static struct platform_driver sc27xx_poweroff_driver = {
+ .probe = sc27xx_poweroff_probe,
+ .driver = {
+ .name = "sc27xx-poweroff",
+ },
+};
+builtin_platform_driver(sc27xx_poweroff_driver);
diff --git a/drivers/power/reset/st-poweroff.c b/drivers/power/reset/st-poweroff.c
new file mode 100644
index 000000000..2046b3123
--- /dev/null
+++ b/drivers/power/reset/st-poweroff.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 STMicroelectronics
+ *
+ * Power off Restart driver, used in STMicroelectronics devices.
+ *
+ * Author: Christophe Kerello <christophe.kerello@st.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+
+struct reset_syscfg {
+ struct regmap *regmap;
+ /* syscfg used for reset */
+ unsigned int offset_rst;
+ unsigned int mask_rst;
+ /* syscfg used for unmask the reset */
+ unsigned int offset_rst_msk;
+ unsigned int mask_rst_msk;
+};
+
+/* STiH407 */
+#define STIH407_SYSCFG_4000 0x0
+#define STIH407_SYSCFG_4008 0x20
+
+static struct reset_syscfg stih407_reset = {
+ .offset_rst = STIH407_SYSCFG_4000,
+ .mask_rst = BIT(0),
+ .offset_rst_msk = STIH407_SYSCFG_4008,
+ .mask_rst_msk = BIT(0)
+};
+
+
+static struct reset_syscfg *st_restart_syscfg;
+
+static int st_restart(struct notifier_block *this, unsigned long mode,
+ void *cmd)
+{
+ /* reset syscfg updated */
+ regmap_update_bits(st_restart_syscfg->regmap,
+ st_restart_syscfg->offset_rst,
+ st_restart_syscfg->mask_rst,
+ 0);
+
+ /* unmask the reset */
+ regmap_update_bits(st_restart_syscfg->regmap,
+ st_restart_syscfg->offset_rst_msk,
+ st_restart_syscfg->mask_rst_msk,
+ 0);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block st_restart_nb = {
+ .notifier_call = st_restart,
+ .priority = 192,
+};
+
+static const struct of_device_id st_reset_of_match[] = {
+ {
+ .compatible = "st,stih407-restart",
+ .data = (void *)&stih407_reset,
+ },
+ {}
+};
+
+static int st_reset_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ const struct of_device_id *match;
+ struct device *dev = &pdev->dev;
+
+ match = of_match_device(st_reset_of_match, dev);
+ if (!match)
+ return -ENODEV;
+
+ st_restart_syscfg = (struct reset_syscfg *)match->data;
+
+ st_restart_syscfg->regmap =
+ syscon_regmap_lookup_by_phandle(np, "st,syscfg");
+ if (IS_ERR(st_restart_syscfg->regmap)) {
+ dev_err(dev, "No syscfg phandle specified\n");
+ return PTR_ERR(st_restart_syscfg->regmap);
+ }
+
+ return register_restart_handler(&st_restart_nb);
+}
+
+static struct platform_driver st_reset_driver = {
+ .probe = st_reset_probe,
+ .driver = {
+ .name = "st_reset",
+ .of_match_table = st_reset_of_match,
+ },
+};
+
+static int __init st_reset_init(void)
+{
+ return platform_driver_register(&st_reset_driver);
+}
+
+device_initcall(st_reset_init);
+
+MODULE_AUTHOR("Christophe Kerello <christophe.kerello@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics Power off Restart driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/syscon-poweroff.c b/drivers/power/reset/syscon-poweroff.c
new file mode 100644
index 000000000..f9f1cb54f
--- /dev/null
+++ b/drivers/power/reset/syscon-poweroff.c
@@ -0,0 +1,115 @@
+/*
+ * Generic Syscon Poweroff Driver
+ *
+ * Copyright (c) 2015, National Instruments Corp.
+ * Author: Moritz Fischer <moritz.fischer@ettus.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; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kallsyms.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/notifier.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+
+static struct regmap *map;
+static u32 offset;
+static u32 value;
+static u32 mask;
+
+static void syscon_poweroff(void)
+{
+ /* Issue the poweroff */
+ regmap_update_bits(map, offset, mask, value);
+
+ mdelay(1000);
+
+ pr_emerg("Unable to poweroff system\n");
+}
+
+static int syscon_poweroff_probe(struct platform_device *pdev)
+{
+ char symname[KSYM_NAME_LEN];
+ int mask_err, value_err;
+
+ map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap");
+ if (IS_ERR(map)) {
+ dev_err(&pdev->dev, "unable to get syscon");
+ return PTR_ERR(map);
+ }
+
+ if (of_property_read_u32(pdev->dev.of_node, "offset", &offset)) {
+ dev_err(&pdev->dev, "unable to read 'offset'");
+ return -EINVAL;
+ }
+
+ value_err = of_property_read_u32(pdev->dev.of_node, "value", &value);
+ mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &mask);
+ if (value_err && mask_err) {
+ dev_err(&pdev->dev, "unable to read 'value' and 'mask'");
+ return -EINVAL;
+ }
+
+ if (value_err) {
+ /* support old binding */
+ value = mask;
+ mask = 0xFFFFFFFF;
+ } else if (mask_err) {
+ /* support value without mask*/
+ mask = 0xFFFFFFFF;
+ }
+
+ if (pm_power_off) {
+ lookup_symbol_name((ulong)pm_power_off, symname);
+ dev_err(&pdev->dev,
+ "pm_power_off already claimed %p %s",
+ pm_power_off, symname);
+ return -EBUSY;
+ }
+
+ pm_power_off = syscon_poweroff;
+
+ return 0;
+}
+
+static int syscon_poweroff_remove(struct platform_device *pdev)
+{
+ if (pm_power_off == syscon_poweroff)
+ pm_power_off = NULL;
+
+ return 0;
+}
+
+static const struct of_device_id syscon_poweroff_of_match[] = {
+ { .compatible = "syscon-poweroff" },
+ {}
+};
+
+static struct platform_driver syscon_poweroff_driver = {
+ .probe = syscon_poweroff_probe,
+ .remove = syscon_poweroff_remove,
+ .driver = {
+ .name = "syscon-poweroff",
+ .of_match_table = syscon_poweroff_of_match,
+ },
+};
+
+static int __init syscon_poweroff_register(void)
+{
+ return platform_driver_register(&syscon_poweroff_driver);
+}
+device_initcall(syscon_poweroff_register);
diff --git a/drivers/power/reset/syscon-reboot-mode.c b/drivers/power/reset/syscon-reboot-mode.c
new file mode 100644
index 000000000..563a97d7f
--- /dev/null
+++ b/drivers/power/reset/syscon-reboot-mode.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/reboot-mode.h>
+
+struct syscon_reboot_mode {
+ struct regmap *map;
+ struct reboot_mode_driver reboot;
+ u32 offset;
+ u32 mask;
+};
+
+static int syscon_reboot_mode_write(struct reboot_mode_driver *reboot,
+ unsigned int magic)
+{
+ struct syscon_reboot_mode *syscon_rbm;
+ int ret;
+
+ syscon_rbm = container_of(reboot, struct syscon_reboot_mode, reboot);
+
+ ret = regmap_update_bits(syscon_rbm->map, syscon_rbm->offset,
+ syscon_rbm->mask, magic);
+ if (ret < 0)
+ dev_err(reboot->dev, "update reboot mode bits failed\n");
+
+ return ret;
+}
+
+static int syscon_reboot_mode_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct syscon_reboot_mode *syscon_rbm;
+
+ syscon_rbm = devm_kzalloc(&pdev->dev, sizeof(*syscon_rbm), GFP_KERNEL);
+ if (!syscon_rbm)
+ return -ENOMEM;
+
+ syscon_rbm->reboot.dev = &pdev->dev;
+ syscon_rbm->reboot.write = syscon_reboot_mode_write;
+ syscon_rbm->mask = 0xffffffff;
+
+ syscon_rbm->map = syscon_node_to_regmap(pdev->dev.parent->of_node);
+ if (IS_ERR(syscon_rbm->map))
+ return PTR_ERR(syscon_rbm->map);
+
+ if (of_property_read_u32(pdev->dev.of_node, "offset",
+ &syscon_rbm->offset))
+ return -EINVAL;
+
+ of_property_read_u32(pdev->dev.of_node, "mask", &syscon_rbm->mask);
+
+ ret = devm_reboot_mode_register(&pdev->dev, &syscon_rbm->reboot);
+ if (ret)
+ dev_err(&pdev->dev, "can't register reboot mode\n");
+
+ return ret;
+}
+
+static const struct of_device_id syscon_reboot_mode_of_match[] = {
+ { .compatible = "syscon-reboot-mode" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, syscon_reboot_mode_of_match);
+
+static struct platform_driver syscon_reboot_mode_driver = {
+ .probe = syscon_reboot_mode_probe,
+ .driver = {
+ .name = "syscon-reboot-mode",
+ .of_match_table = syscon_reboot_mode_of_match,
+ },
+};
+module_platform_driver(syscon_reboot_mode_driver);
+
+MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com");
+MODULE_DESCRIPTION("SYSCON reboot mode driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/syscon-reboot.c b/drivers/power/reset/syscon-reboot.c
new file mode 100644
index 000000000..7d0d269a0
--- /dev/null
+++ b/drivers/power/reset/syscon-reboot.c
@@ -0,0 +1,91 @@
+/*
+ * Generic Syscon Reboot Driver
+ *
+ * Copyright (c) 2013, Applied Micro Circuits Corporation
+ * Author: Feng Kan <fkan@apm.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; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/notifier.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+
+struct syscon_reboot_context {
+ struct regmap *map;
+ u32 offset;
+ u32 mask;
+ struct notifier_block restart_handler;
+};
+
+static int syscon_restart_handle(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct syscon_reboot_context *ctx =
+ container_of(this, struct syscon_reboot_context,
+ restart_handler);
+
+ /* Issue the reboot */
+ regmap_write(ctx->map, ctx->offset, ctx->mask);
+
+ mdelay(1000);
+
+ pr_emerg("Unable to restart system\n");
+ return NOTIFY_DONE;
+}
+
+static int syscon_reboot_probe(struct platform_device *pdev)
+{
+ struct syscon_reboot_context *ctx;
+ struct device *dev = &pdev->dev;
+ int err;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->map = syscon_regmap_lookup_by_phandle(dev->of_node, "regmap");
+ if (IS_ERR(ctx->map))
+ return PTR_ERR(ctx->map);
+
+ if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset))
+ return -EINVAL;
+
+ if (of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask))
+ return -EINVAL;
+
+ ctx->restart_handler.notifier_call = syscon_restart_handle;
+ ctx->restart_handler.priority = 192;
+ err = register_restart_handler(&ctx->restart_handler);
+ if (err)
+ dev_err(dev, "can't register restart notifier (err=%d)\n", err);
+
+ return err;
+}
+
+static const struct of_device_id syscon_reboot_of_match[] = {
+ { .compatible = "syscon-reboot" },
+ {}
+};
+
+static struct platform_driver syscon_reboot_driver = {
+ .probe = syscon_reboot_probe,
+ .driver = {
+ .name = "syscon-reboot",
+ .of_match_table = syscon_reboot_of_match,
+ },
+};
+builtin_platform_driver(syscon_reboot_driver);
diff --git a/drivers/power/reset/vexpress-poweroff.c b/drivers/power/reset/vexpress-poweroff.c
new file mode 100644
index 000000000..8fb43c443
--- /dev/null
+++ b/drivers/power/reset/vexpress-poweroff.c
@@ -0,0 +1,161 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Copyright (C) 2012 ARM Limited
+ */
+
+#include <linux/delay.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/stat.h>
+#include <linux/vexpress.h>
+
+static void vexpress_reset_do(struct device *dev, const char *what)
+{
+ int err = -ENOENT;
+ struct regmap *reg = dev_get_drvdata(dev);
+
+ if (reg) {
+ err = regmap_write(reg, 0, 0);
+ if (!err)
+ mdelay(1000);
+ }
+
+ dev_emerg(dev, "Unable to %s (%d)\n", what, err);
+}
+
+static struct device *vexpress_power_off_device;
+static atomic_t vexpress_restart_nb_refcnt = ATOMIC_INIT(0);
+
+static void vexpress_power_off(void)
+{
+ vexpress_reset_do(vexpress_power_off_device, "power off");
+}
+
+static struct device *vexpress_restart_device;
+
+static int vexpress_restart(struct notifier_block *this, unsigned long mode,
+ void *cmd)
+{
+ vexpress_reset_do(vexpress_restart_device, "restart");
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block vexpress_restart_nb = {
+ .notifier_call = vexpress_restart,
+ .priority = 128,
+};
+
+static ssize_t vexpress_reset_active_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", vexpress_restart_device == dev);
+}
+
+static ssize_t vexpress_reset_active_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ long value;
+ int err = kstrtol(buf, 0, &value);
+
+ if (!err && value)
+ vexpress_restart_device = dev;
+
+ return err ? err : count;
+}
+
+static DEVICE_ATTR(active, S_IRUGO | S_IWUSR, vexpress_reset_active_show,
+ vexpress_reset_active_store);
+
+
+enum vexpress_reset_func { FUNC_RESET, FUNC_SHUTDOWN, FUNC_REBOOT };
+
+static const struct of_device_id vexpress_reset_of_match[] = {
+ {
+ .compatible = "arm,vexpress-reset",
+ .data = (void *)FUNC_RESET,
+ }, {
+ .compatible = "arm,vexpress-shutdown",
+ .data = (void *)FUNC_SHUTDOWN
+ }, {
+ .compatible = "arm,vexpress-reboot",
+ .data = (void *)FUNC_REBOOT
+ },
+ {}
+};
+
+static int _vexpress_register_restart_handler(struct device *dev)
+{
+ int err;
+
+ vexpress_restart_device = dev;
+ if (atomic_inc_return(&vexpress_restart_nb_refcnt) == 1) {
+ err = register_restart_handler(&vexpress_restart_nb);
+ if (err) {
+ dev_err(dev, "cannot register restart handler (err=%d)\n", err);
+ atomic_dec(&vexpress_restart_nb_refcnt);
+ return err;
+ }
+ }
+ device_create_file(dev, &dev_attr_active);
+
+ return 0;
+}
+
+static int vexpress_reset_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match =
+ of_match_device(vexpress_reset_of_match, &pdev->dev);
+ struct regmap *regmap;
+ int ret = 0;
+
+ if (!match)
+ return -EINVAL;
+
+ regmap = devm_regmap_init_vexpress_config(&pdev->dev);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+ dev_set_drvdata(&pdev->dev, regmap);
+
+ switch ((enum vexpress_reset_func)match->data) {
+ case FUNC_SHUTDOWN:
+ vexpress_power_off_device = &pdev->dev;
+ pm_power_off = vexpress_power_off;
+ break;
+ case FUNC_RESET:
+ if (!vexpress_restart_device)
+ ret = _vexpress_register_restart_handler(&pdev->dev);
+ break;
+ case FUNC_REBOOT:
+ ret = _vexpress_register_restart_handler(&pdev->dev);
+ break;
+ };
+
+ return ret;
+}
+
+static struct platform_driver vexpress_reset_driver = {
+ .probe = vexpress_reset_probe,
+ .driver = {
+ .name = "vexpress-reset",
+ .of_match_table = vexpress_reset_of_match,
+ .suppress_bind_attrs = true,
+ },
+};
+
+static int __init vexpress_reset_init(void)
+{
+ return platform_driver_register(&vexpress_reset_driver);
+}
+device_initcall(vexpress_reset_init);
diff --git a/drivers/power/reset/xgene-reboot.c b/drivers/power/reset/xgene-reboot.c
new file mode 100644
index 000000000..73c3d93e5
--- /dev/null
+++ b/drivers/power/reset/xgene-reboot.c
@@ -0,0 +1,109 @@
+/*
+ * AppliedMicro X-Gene SoC Reboot Driver
+ *
+ * Copyright (c) 2013, Applied Micro Circuits Corporation
+ * Author: Feng Kan <fkan@apm.com>
+ * Author: Loc Ho <lho@apm.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; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ * This driver provides system reboot functionality for APM X-Gene SoC.
+ * For system shutdown, this is board specify. If a board designer
+ * implements GPIO shutdown, use the gpio-poweroff.c driver.
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/notifier.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/stat.h>
+#include <linux/slab.h>
+
+struct xgene_reboot_context {
+ struct device *dev;
+ void *csr;
+ u32 mask;
+ struct notifier_block restart_handler;
+};
+
+static int xgene_restart_handler(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct xgene_reboot_context *ctx =
+ container_of(this, struct xgene_reboot_context,
+ restart_handler);
+
+ /* Issue the reboot */
+ writel(ctx->mask, ctx->csr);
+
+ mdelay(1000);
+
+ dev_emerg(ctx->dev, "Unable to restart system\n");
+
+ return NOTIFY_DONE;
+}
+
+static int xgene_reboot_probe(struct platform_device *pdev)
+{
+ struct xgene_reboot_context *ctx;
+ struct device *dev = &pdev->dev;
+ int err;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->csr = of_iomap(dev->of_node, 0);
+ if (!ctx->csr) {
+ dev_err(dev, "can not map resource\n");
+ return -ENODEV;
+ }
+
+ if (of_property_read_u32(dev->of_node, "mask", &ctx->mask))
+ ctx->mask = 0xFFFFFFFF;
+
+ ctx->dev = dev;
+ ctx->restart_handler.notifier_call = xgene_restart_handler;
+ ctx->restart_handler.priority = 128;
+ err = register_restart_handler(&ctx->restart_handler);
+ if (err) {
+ iounmap(ctx->csr);
+ dev_err(dev, "cannot register restart handler (err=%d)\n", err);
+ }
+
+ return err;
+}
+
+static const struct of_device_id xgene_reboot_of_match[] = {
+ { .compatible = "apm,xgene-reboot" },
+ {}
+};
+
+static struct platform_driver xgene_reboot_driver = {
+ .probe = xgene_reboot_probe,
+ .driver = {
+ .name = "xgene-reboot",
+ .of_match_table = xgene_reboot_of_match,
+ },
+};
+
+static int __init xgene_reboot_init(void)
+{
+ return platform_driver_register(&xgene_reboot_driver);
+}
+device_initcall(xgene_reboot_init);
diff --git a/drivers/power/reset/zx-reboot.c b/drivers/power/reset/zx-reboot.c
new file mode 100644
index 000000000..186901c96
--- /dev/null
+++ b/drivers/power/reset/zx-reboot.c
@@ -0,0 +1,89 @@
+/*
+ * ZTE zx296702 SoC reset code
+ *
+ * Copyright (c) 2015 Linaro Ltd.
+ *
+ * Author: Jun Nie <jun.nie@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+
+static void __iomem *base;
+static void __iomem *pcu_base;
+
+static int zx_restart_handler(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ writel_relaxed(1, base + 0xb0);
+ writel_relaxed(1, pcu_base + 0x34);
+
+ mdelay(50);
+ pr_emerg("Unable to restart system\n");
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block zx_restart_nb = {
+ .notifier_call = zx_restart_handler,
+ .priority = 128,
+};
+
+static int zx_reboot_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ int err;
+
+ base = of_iomap(np, 0);
+ if (!base) {
+ WARN(1, "failed to map base address");
+ return -ENODEV;
+ }
+
+ np = of_find_compatible_node(NULL, NULL, "zte,zx296702-pcu");
+ pcu_base = of_iomap(np, 0);
+ of_node_put(np);
+ if (!pcu_base) {
+ iounmap(base);
+ WARN(1, "failed to map pcu_base address");
+ return -ENODEV;
+ }
+
+ err = register_restart_handler(&zx_restart_nb);
+ if (err) {
+ iounmap(base);
+ iounmap(pcu_base);
+ dev_err(&pdev->dev, "Register restart handler failed(err=%d)\n",
+ err);
+ }
+
+ return err;
+}
+
+static const struct of_device_id zx_reboot_of_match[] = {
+ { .compatible = "zte,sysctrl" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, zx_reboot_of_match);
+
+static struct platform_driver zx_reboot_driver = {
+ .probe = zx_reboot_probe,
+ .driver = {
+ .name = "zx-reboot",
+ .of_match_table = zx_reboot_of_match,
+ },
+};
+module_platform_driver(zx_reboot_driver);
+
+MODULE_DESCRIPTION("ZTE SoCs reset driver");
+MODULE_AUTHOR("Jun Nie <jun.nie@linaro.org>");
+MODULE_LICENSE("GPL v2");