diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/power/reset | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/power/reset')
39 files changed, 5795 insertions, 0 deletions
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig new file mode 100644 index 000000000..54201f037 --- /dev/null +++ b/drivers/power/reset/Kconfig @@ -0,0 +1,306 @@ +# SPDX-License-Identifier: GPL-2.0-only +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_SAM9X60 || 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_SAM9X60 || 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_ATC260X + tristate "Actions Semi ATC260x PMIC power-off driver" + depends on MFD_ATC260X + help + This driver provides power-off and restart support for a system + through Actions Semi ATC260x series PMICs. + +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_LINKSTATION + tristate "Buffalo LinkStation power-off driver" + depends on ARCH_MVEBU || COMPILE_TEST + depends on OF_MDIO && PHYLIB + help + This driver supports turning off some Buffalo LinkStations by + setting an output pin at the ethernet PHY to the correct state. + It also makes the device compatible with the WoL function. + + Say Y here if you have a Buffalo LinkStation LS421D/E. + +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 || ARCH_SPARX5 || COMPILE_TEST + select MFD_SYSCON + help + This driver supports restart for Microsemi Ocelot SoC and similar. + +config POWER_RESET_OXNAS + bool "OXNAS SoC restart driver" + depends on ARCH_OXNAS + default MACH_OX820 + help + Restart support for OXNAS/PLXTECH OX820 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_MT6323 + bool "MediaTek MT6323 power-off driver" + depends on MFD_MT6397 + help + The power-off driver is responsible for externally shutdown down + the power of a remote MediaTek SoC MT6323 is connected to through + controlling a tiny circuit BBPU inside MT6323 RTC. + + Say Y if you have a board where MT6323 could be found. + +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_REGULATOR + bool "Regulator subsystem power-off driver" + depends on OF && REGULATOR + help + This driver supports turning off your board by disabling a + power regulator defined in the devicetree. + +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_TPS65086 + bool "TPS65086 restart driver" + depends on MFD_TPS65086 + help + This driver adds support for resetting the TPS65086 PMIC on restart. + +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=y + 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 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 + tristate "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. + +config NVMEM_REBOOT_MODE + tristate "Generic NVMEM reboot mode driver" + depends on OF + select REBOOT_MODE + help + Say y here will enable reboot mode driver. This will + get reboot mode arguments and store it in a NVMEM cell, + then the bootloader can read it and take different + action according to the mode. + +config POWER_MLXBF + tristate "Mellanox BlueField power handling driver" + depends on (GPIO_MLXBF2 || GPIO_MLXBF3) && ACPI + help + This driver supports reset or low power mode handling for Mellanox BlueField. + +endif diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile new file mode 100644 index 000000000..0a39424fc --- /dev/null +++ b/drivers/power/reset/Makefile @@ -0,0 +1,38 @@ +# 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_ATC260X) += atc260x-poweroff.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_LINKSTATION) += linkstation-poweroff.o +obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o +obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o +obj-$(CONFIG_POWER_RESET_OXNAS) += oxnas-restart.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_REGULATOR) += regulator-poweroff.o +obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o +obj-$(CONFIG_POWER_RESET_ST) += st-poweroff.o +obj-$(CONFIG_POWER_RESET_TPS65086) += tps65086-restart.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_REBOOT_MODE) += reboot-mode.o +obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o +obj-$(CONFIG_POWER_RESET_SC27XX) += sc27xx-poweroff.o +obj-$(CONFIG_NVMEM_REBOOT_MODE) += nvmem-reboot-mode.o +obj-$(CONFIG_POWER_MLXBF) += pwr-mlxbf.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..c7624d761 --- /dev/null +++ b/drivers/power/reset/arm-versatile-reboot.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Linaro Ltd. + * + * Author: Linus Walleij <linus.walleij@linaro.org> + */ +#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); + of_node_put(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..661e1c67f --- /dev/null +++ b/drivers/power/reset/as3722-poweroff.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Power off driver for ams AS3722 device. + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * Author: Laxman Dewangan <ldewangan@nvidia.com> + */ + +#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..9e74e131c --- /dev/null +++ b/drivers/power/reset/at91-poweroff.c @@ -0,0 +1,239 @@ +/* + * 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 struct shdwc { + struct clk *sclk; + void __iomem *shdwc_base; + void __iomem *mpddrc_base; +} at91_shdwc; + +static void __init at91_wakeup_status(struct platform_device *pdev) +{ + const char *reason; + u32 reg = readl(at91_shdwc.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) +{ + 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 */ + " tst %0, #0\n\t" + " beq 1f\n\t" + " str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t" + /* Shutdown CPU */ + "1: str %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t" + + " b .\n\t" + : + : "r" (at91_shdwc.mpddrc_base), + "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF), + "r" (at91_shdwc.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.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.shdwc_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(at91_shdwc.shdwc_base)) + return PTR_ERR(at91_shdwc.shdwc_base); + + at91_shdwc.sclk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(at91_shdwc.sclk)) + return PTR_ERR(at91_shdwc.sclk); + + ret = clk_prepare_enable(at91_shdwc.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); + + np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc"); + if (np) { + at91_shdwc.mpddrc_base = of_iomap(np, 0); + of_node_put(np); + + if (!at91_shdwc.mpddrc_base) { + ret = -ENOMEM; + goto clk_disable; + } + + ddr_type = readl(at91_shdwc.mpddrc_base + AT91_DDRSDRC_MDR) & + AT91_DDRSDRC_MD; + if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 && + ddr_type != AT91_DDRSDRC_MD_LPDDR3) { + iounmap(at91_shdwc.mpddrc_base); + at91_shdwc.mpddrc_base = NULL; + } + } + + pm_power_off = at91_poweroff; + + return 0; + +clk_disable: + clk_disable_unprepare(at91_shdwc.sclk); + return ret; +} + +static int __exit at91_poweroff_remove(struct platform_device *pdev) +{ + if (pm_power_off == at91_poweroff) + pm_power_off = NULL; + + if (at91_shdwc.mpddrc_base) + iounmap(at91_shdwc.mpddrc_base); + + clk_disable_unprepare(at91_shdwc.sclk); + + return 0; +} + +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..741e44a01 --- /dev/null +++ b/drivers/power/reset/at91-reset.c @@ -0,0 +1,425 @@ +/* + * 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 <linux/reset-controller.h> + +#include <soc/at91/at91sam9_ddrsdr.h> +#include <soc/at91/at91sam9_sdramc.h> + +#include <dt-bindings/reset/sama7g5-reset.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_URSTASYNC BIT(2) /* User Reset Asynchronous Control */ +#define AT91_RSTC_URSTIEN BIT(4) /* User Reset Interrupt Enable */ +#define AT91_RSTC_ERSTL GENMASK(11, 8) /* External Reset Length */ + +/** + * enum reset_type - reset types + * @RESET_TYPE_GENERAL: first power-up reset + * @RESET_TYPE_WAKEUP: return from backup mode + * @RESET_TYPE_WATCHDOG: watchdog fault + * @RESET_TYPE_SOFTWARE: processor reset required by software + * @RESET_TYPE_USER: NRST pin detected low + * @RESET_TYPE_CPU_FAIL: CPU clock failure detection + * @RESET_TYPE_XTAL_FAIL: 32KHz crystal failure dectection fault + * @RESET_TYPE_ULP2: ULP2 reset + */ +enum reset_type { + RESET_TYPE_GENERAL = 0, + RESET_TYPE_WAKEUP = 1, + RESET_TYPE_WATCHDOG = 2, + RESET_TYPE_SOFTWARE = 3, + RESET_TYPE_USER = 4, + RESET_TYPE_CPU_FAIL = 6, + RESET_TYPE_XTAL_FAIL = 7, + RESET_TYPE_ULP2 = 8, +}; + +/** + * struct at91_reset - AT91 reset specific data structure + * @rstc_base: base address for system reset + * @ramc_base: array with base addresses of RAM controllers + * @dev_base: base address for devices reset + * @sclk: slow clock + * @data: platform specific reset data + * @rcdev: reset controller device + * @lock: lock for devices reset register access + * @nb: reset notifier block + * @args: SoC specific system reset arguments + * @ramc_lpr: SDRAM Controller Low Power Register + */ +struct at91_reset { + void __iomem *rstc_base; + void __iomem *ramc_base[2]; + void __iomem *dev_base; + struct clk *sclk; + const struct at91_reset_data *data; + struct reset_controller_dev rcdev; + spinlock_t lock; + struct notifier_block nb; + u32 args; + u32 ramc_lpr; +}; + +#define to_at91_reset(r) container_of(r, struct at91_reset, rcdev) + +/** + * struct at91_reset_data - AT91 reset data + * @reset_args: SoC specific system reset arguments + * @n_device_reset: number of device resets + * @device_reset_min_id: min id for device reset + * @device_reset_max_id: max id for device reset + */ +struct at91_reset_data { + u32 reset_args; + u32 n_device_reset; + u8 device_reset_min_id; + u8 device_reset_max_id; +}; + +/* +* 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 at91_reset(struct notifier_block *this, unsigned long mode, + void *cmd) +{ + struct at91_reset *reset = container_of(this, struct at91_reset, nb); + + asm volatile( + /* Align to cache lines */ + ".balign 32\n\t" + + /* Disable SDRAM0 accesses */ + " tst %0, #0\n\t" + " beq 1f\n\t" + " str %3, [%0, #" __stringify(AT91_DDRSDRC_RTR) "]\n\t" + /* Power down SDRAM0 */ + " str %4, [%0, %6]\n\t" + /* Disable SDRAM1 accesses */ + "1: tst %1, #0\n\t" + " beq 2f\n\t" + " strne %3, [%1, #" __stringify(AT91_DDRSDRC_RTR) "]\n\t" + /* Power down SDRAM1 */ + " strne %4, [%1, %6]\n\t" + /* Reset CPU */ + "2: str %5, [%2, #" __stringify(AT91_RSTC_CR) "]\n\t" + + " b .\n\t" + : + : "r" (reset->ramc_base[0]), + "r" (reset->ramc_base[1]), + "r" (reset->rstc_base), + "r" (1), + "r" cpu_to_le32(AT91_DDRSDRC_LPCB_POWER_DOWN), + "r" (reset->data->reset_args), + "r" (reset->ramc_lpr) + : "r4"); + + return NOTIFY_DONE; +} + +static void __init at91_reset_status(struct platform_device *pdev, + void __iomem *base) +{ + const char *reason; + u32 reg = readl(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; + case RESET_TYPE_CPU_FAIL: + reason = "CPU clock failure detection"; + break; + case RESET_TYPE_XTAL_FAIL: + reason = "32.768 kHz crystal failure detection"; + break; + case RESET_TYPE_ULP2: + reason = "ULP2 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", + .data = (void *)AT91_SDRAMC_LPR, + }, + { + .compatible = "atmel,at91sam9g45-ddramc", + .data = (void *)AT91_DDRSDRC_LPR, + }, + { /* sentinel */ } +}; + +static const struct at91_reset_data sam9260 = { + .reset_args = AT91_RSTC_KEY | AT91_RSTC_PERRST | AT91_RSTC_PROCRST, +}; + +static const struct at91_reset_data samx7 = { + .reset_args = AT91_RSTC_KEY | AT91_RSTC_PROCRST, +}; + +static const struct at91_reset_data sama7g5 = { + .reset_args = AT91_RSTC_KEY | AT91_RSTC_PROCRST, + .n_device_reset = 3, + .device_reset_min_id = SAMA7G5_RESET_USB_PHY1, + .device_reset_max_id = SAMA7G5_RESET_USB_PHY3, +}; + +static const struct of_device_id at91_reset_of_match[] = { + { + .compatible = "atmel,at91sam9260-rstc", + .data = &sam9260, + }, + { + .compatible = "atmel,at91sam9g45-rstc", + .data = &sam9260, + }, + { + .compatible = "atmel,sama5d3-rstc", + .data = &sam9260, + }, + { + .compatible = "atmel,samx7-rstc", + .data = &samx7, + }, + { + .compatible = "microchip,sam9x60-rstc", + .data = &samx7, + }, + { + .compatible = "microchip,sama7g5-rstc", + .data = &sama7g5, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, at91_reset_of_match); + +static int at91_reset_update(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct at91_reset *reset = to_at91_reset(rcdev); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&reset->lock, flags); + val = readl_relaxed(reset->dev_base); + if (assert) + val |= BIT(id); + else + val &= ~BIT(id); + writel_relaxed(val, reset->dev_base); + spin_unlock_irqrestore(&reset->lock, flags); + + return 0; +} + +static int at91_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return at91_reset_update(rcdev, id, true); +} + +static int at91_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return at91_reset_update(rcdev, id, false); +} + +static int at91_reset_dev_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct at91_reset *reset = to_at91_reset(rcdev); + u32 val; + + val = readl_relaxed(reset->dev_base); + + return !!(val & BIT(id)); +} + +static const struct reset_control_ops at91_reset_ops = { + .assert = at91_reset_assert, + .deassert = at91_reset_deassert, + .status = at91_reset_dev_status, +}; + +static int at91_reset_of_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + struct at91_reset *reset = to_at91_reset(rcdev); + + if (!reset->data->n_device_reset || + (reset_spec->args[0] < reset->data->device_reset_min_id || + reset_spec->args[0] > reset->data->device_reset_max_id)) + return -EINVAL; + + return reset_spec->args[0]; +} + +static int at91_rcdev_init(struct at91_reset *reset, + struct platform_device *pdev) +{ + if (!reset->data->n_device_reset) + return 0; + + reset->dev_base = devm_of_iomap(&pdev->dev, pdev->dev.of_node, 1, + NULL); + if (IS_ERR(reset->dev_base)) + return -ENODEV; + + spin_lock_init(&reset->lock); + reset->rcdev.ops = &at91_reset_ops; + reset->rcdev.owner = THIS_MODULE; + reset->rcdev.of_node = pdev->dev.of_node; + reset->rcdev.nr_resets = reset->data->n_device_reset; + reset->rcdev.of_reset_n_cells = 1; + reset->rcdev.of_xlate = at91_reset_of_xlate; + + return devm_reset_controller_register(&pdev->dev, &reset->rcdev); +} + +static int __init at91_reset_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct at91_reset *reset; + struct device_node *np; + int ret, idx = 0; + + reset = devm_kzalloc(&pdev->dev, sizeof(*reset), GFP_KERNEL); + if (!reset) + return -ENOMEM; + + reset->rstc_base = devm_of_iomap(&pdev->dev, pdev->dev.of_node, 0, NULL); + if (IS_ERR(reset->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_and_match(np, at91_ramc_of_match, &match) { + reset->ramc_lpr = (u32)match->data; + reset->ramc_base[idx] = devm_of_iomap(&pdev->dev, np, 0, NULL); + if (IS_ERR(reset->ramc_base[idx])) { + dev_err(&pdev->dev, "Could not map ram controller address\n"); + of_node_put(np); + return -ENODEV; + } + idx++; + } + } + + reset->data = device_get_match_data(&pdev->dev); + if (!reset->data) + return -ENODEV; + + reset->nb.notifier_call = at91_reset; + reset->nb.priority = 192; + + reset->sclk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(reset->sclk)) + return PTR_ERR(reset->sclk); + + ret = clk_prepare_enable(reset->sclk); + if (ret) { + dev_err(&pdev->dev, "Could not enable slow clock\n"); + return ret; + } + + platform_set_drvdata(pdev, reset); + + ret = at91_rcdev_init(reset, pdev); + if (ret) + goto disable_clk; + + if (of_device_is_compatible(pdev->dev.of_node, "microchip,sam9x60-rstc")) { + u32 val = readl(reset->rstc_base + AT91_RSTC_MR); + + writel(AT91_RSTC_KEY | AT91_RSTC_URSTASYNC | val, + reset->rstc_base + AT91_RSTC_MR); + } + + ret = register_restart_handler(&reset->nb); + if (ret) + goto disable_clk; + + at91_reset_status(pdev, reset->rstc_base); + + return 0; + +disable_clk: + clk_disable_unprepare(reset->sclk); + return ret; +} + +static int __exit at91_reset_remove(struct platform_device *pdev) +{ + struct at91_reset *reset = platform_get_drvdata(pdev); + + unregister_restart_handler(&reset->nb); + clk_disable_unprepare(reset->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..d8ecffe72 --- /dev/null +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -0,0 +1,457 @@ +/* + * 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/clk/at91_pmc.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_RTTWK(reg, cfg) (((reg) >> ((cfg)->sr_rttwk_shift)) & 0x1) +#define SHDW_RTCWKEN(cfg) (1 << ((cfg)->mr_rtcwk_shift)) +#define SHDW_RTTWKEN(cfg) (1 << ((cfg)->mr_rttwk_shift)) + +#define DBC_PERIOD_US(x) DIV_ROUND_UP_ULL((1000000 * (x)), \ + SLOW_CLOCK_FREQ) + +#define SHDW_CFG_NOT_USED (32) + +struct shdwc_reg_config { + u8 wkup_pin_input; + u8 mr_rtcwk_shift; + u8 mr_rttwk_shift; + u8 sr_rtcwk_shift; + u8 sr_rttwk_shift; +}; + +struct pmc_reg_config { + u8 mckr; +}; + +struct ddrc_reg_config { + u32 type_offset; + u32 type_mask; +}; + +struct reg_config { + struct shdwc_reg_config shdwc; + struct pmc_reg_config pmc; + struct ddrc_reg_config ddrc; +}; + +struct shdwc { + const struct reg_config *rcfg; + struct clk *sclk; + void __iomem *shdwc_base; + void __iomem *mpddrc_base; + void __iomem *pmc_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 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); + const struct reg_config *rcfg = shdw->rcfg; + u32 reg; + char *reason = "unknown"; + + reg = readl(shdw->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, &rcfg->shdwc)) + reason = "WKUP pin"; + else if (SHDW_RTCWK(reg, &rcfg->shdwc)) + reason = "RTC"; + else if (SHDW_RTTWK(reg, &rcfg->shdwc)) + reason = "RTT"; + + pr_info("AT91: Wake-Up source: %s\n", reason); +} + +static void at91_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 */ + " tst %0, #0\n\t" + " beq 1f\n\t" + " str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t" + + /* Switch the master clock source to slow clock. */ + "1: ldr r6, [%4, %5]\n\t" + " bic r6, r6, #" __stringify(AT91_PMC_CSS) "\n\t" + " str r6, [%4, %5]\n\t" + /* Wait for clock switch. */ + "2: ldr r6, [%4, #" __stringify(AT91_PMC_SR) "]\n\t" + " tst r6, #" __stringify(AT91_PMC_MCKRDY) "\n\t" + " beq 2b\n\t" + + /* Shutdown CPU */ + " str %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t" + + " b .\n\t" + : + : "r" (at91_shdwc->mpddrc_base), + "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF), + "r" (at91_shdwc->shdwc_base), + "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW), + "r" (at91_shdwc->pmc_base), + "r" (at91_shdwc->rcfg->pmc.mckr) + : "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); + const struct reg_config *rcfg = shdw->rcfg; + 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(&rcfg->shdwc); + + if (of_property_read_bool(np, "atmel,wakeup-rtt-timer")) + mode |= SHDW_RTTWKEN(&rcfg->shdwc); + + dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode); + writel(mode, shdw->shdwc_base + AT91_SHDW_MR); + + input = at91_shdwc_get_wakeup_input(pdev, np); + writel(input, shdw->shdwc_base + AT91_SHDW_WUIR); +} + +static const struct reg_config sama5d2_reg_config = { + .shdwc = { + .wkup_pin_input = 0, + .mr_rtcwk_shift = 17, + .mr_rttwk_shift = SHDW_CFG_NOT_USED, + .sr_rtcwk_shift = 5, + .sr_rttwk_shift = SHDW_CFG_NOT_USED, + }, + .pmc = { + .mckr = 0x30, + }, + .ddrc = { + .type_offset = AT91_DDRSDRC_MDR, + .type_mask = AT91_DDRSDRC_MD + }, +}; + +static const struct reg_config sam9x60_reg_config = { + .shdwc = { + .wkup_pin_input = 0, + .mr_rtcwk_shift = 17, + .mr_rttwk_shift = 16, + .sr_rtcwk_shift = 5, + .sr_rttwk_shift = 4, + }, + .pmc = { + .mckr = 0x28, + }, + .ddrc = { + .type_offset = AT91_DDRSDRC_MDR, + .type_mask = AT91_DDRSDRC_MD + }, +}; + +static const struct reg_config sama7g5_reg_config = { + .shdwc = { + .wkup_pin_input = 0, + .mr_rtcwk_shift = 17, + .mr_rttwk_shift = 16, + .sr_rtcwk_shift = 5, + .sr_rttwk_shift = 4, + }, + .pmc = { + .mckr = 0x28, + }, +}; + +static const struct of_device_id at91_shdwc_of_match[] = { + { + .compatible = "atmel,sama5d2-shdwc", + .data = &sama5d2_reg_config, + }, + { + .compatible = "microchip,sam9x60-shdwc", + .data = &sam9x60_reg_config, + }, + { + .compatible = "microchip,sama7g5-shdwc", + .data = &sama7g5_reg_config, + }, { + /*sentinel*/ + } +}; +MODULE_DEVICE_TABLE(of, at91_shdwc_of_match); + +static const struct of_device_id at91_pmc_ids[] = { + { .compatible = "atmel,sama5d2-pmc" }, + { .compatible = "microchip,sam9x60-pmc" }, + { .compatible = "microchip,sama7g5-pmc" }, + { /* Sentinel. */ } +}; + +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->shdwc_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(at91_shdwc->shdwc_base)) + return PTR_ERR(at91_shdwc->shdwc_base); + + match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node); + at91_shdwc->rcfg = match->data; + + at91_shdwc->sclk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(at91_shdwc->sclk)) + return PTR_ERR(at91_shdwc->sclk); + + ret = clk_prepare_enable(at91_shdwc->sclk); + if (ret) { + dev_err(&pdev->dev, "Could not enable slow clock\n"); + return ret; + } + + at91_wakeup_status(pdev); + + at91_shdwc_dt_configure(pdev); + + np = of_find_matching_node(NULL, at91_pmc_ids); + if (!np) { + ret = -ENODEV; + goto clk_disable; + } + + at91_shdwc->pmc_base = of_iomap(np, 0); + of_node_put(np); + + if (!at91_shdwc->pmc_base) { + ret = -ENOMEM; + goto clk_disable; + } + + if (at91_shdwc->rcfg->ddrc.type_mask) { + np = of_find_compatible_node(NULL, NULL, + "atmel,sama5d3-ddramc"); + if (!np) { + ret = -ENODEV; + goto unmap; + } + + at91_shdwc->mpddrc_base = of_iomap(np, 0); + of_node_put(np); + + if (!at91_shdwc->mpddrc_base) { + ret = -ENOMEM; + goto unmap; + } + + ddr_type = readl(at91_shdwc->mpddrc_base + + at91_shdwc->rcfg->ddrc.type_offset) & + at91_shdwc->rcfg->ddrc.type_mask; + if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 && + ddr_type != AT91_DDRSDRC_MD_LPDDR3) { + iounmap(at91_shdwc->mpddrc_base); + at91_shdwc->mpddrc_base = NULL; + } + } + + pm_power_off = at91_poweroff; + + return 0; + +unmap: + iounmap(at91_shdwc->pmc_base); +clk_disable: + clk_disable_unprepare(at91_shdwc->sclk); + + return ret; +} + +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 = NULL; + + /* Reset values to disable wake-up features */ + writel(0, shdw->shdwc_base + AT91_SHDW_MR); + writel(0, shdw->shdwc_base + AT91_SHDW_WUIR); + + if (shdw->mpddrc_base) + iounmap(shdw->mpddrc_base); + iounmap(shdw->pmc_base); + + clk_disable_unprepare(shdw->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/atc260x-poweroff.c b/drivers/power/reset/atc260x-poweroff.c new file mode 100644 index 000000000..98f20251a --- /dev/null +++ b/drivers/power/reset/atc260x-poweroff.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Poweroff & reset driver for Actions Semi ATC260x PMICs + * + * Copyright (c) 2020 Cristian Ciocaltea <cristian.ciocaltea@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/mfd/atc260x/core.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/reboot.h> +#include <linux/regmap.h> + +struct atc260x_pwrc { + struct device *dev; + struct regmap *regmap; + struct notifier_block restart_nb; + int (*do_poweroff)(const struct atc260x_pwrc *pwrc, bool restart); +}; + +/* Global variable needed only for pm_power_off */ +static struct atc260x_pwrc *atc260x_pwrc_data; + +static int atc2603c_do_poweroff(const struct atc260x_pwrc *pwrc, bool restart) +{ + int ret, deep_sleep = 0; + uint reg_mask, reg_val; + + /* S4-Deep Sleep Mode is NOT available for WALL/USB power */ + if (!restart && !power_supply_is_system_supplied()) { + deep_sleep = 1; + dev_info(pwrc->dev, "Enabling S4-Deep Sleep Mode"); + } + + /* Update wakeup sources */ + reg_val = ATC2603C_PMU_SYS_CTL0_ONOFF_LONG_WK_EN | + (restart ? ATC2603C_PMU_SYS_CTL0_RESET_WK_EN + : ATC2603C_PMU_SYS_CTL0_ONOFF_SHORT_WK_EN); + + ret = regmap_update_bits(pwrc->regmap, ATC2603C_PMU_SYS_CTL0, + ATC2603C_PMU_SYS_CTL0_WK_ALL, reg_val); + if (ret) + dev_warn(pwrc->dev, "failed to write SYS_CTL0: %d\n", ret); + + /* Update power mode */ + reg_mask = ATC2603C_PMU_SYS_CTL3_EN_S2 | ATC2603C_PMU_SYS_CTL3_EN_S3; + + ret = regmap_update_bits(pwrc->regmap, ATC2603C_PMU_SYS_CTL3, reg_mask, + deep_sleep ? 0 : ATC2603C_PMU_SYS_CTL3_EN_S3); + if (ret) { + dev_err(pwrc->dev, "failed to write SYS_CTL3: %d\n", ret); + return ret; + } + + /* Trigger poweroff / restart sequence */ + reg_mask = restart ? ATC2603C_PMU_SYS_CTL0_RESTART_EN + : ATC2603C_PMU_SYS_CTL1_EN_S1; + reg_val = restart ? ATC2603C_PMU_SYS_CTL0_RESTART_EN : 0; + + ret = regmap_update_bits(pwrc->regmap, + restart ? ATC2603C_PMU_SYS_CTL0 : ATC2603C_PMU_SYS_CTL1, + reg_mask, reg_val); + if (ret) { + dev_err(pwrc->dev, "failed to write SYS_CTL%d: %d\n", + restart ? 0 : 1, ret); + return ret; + } + + /* Wait for trigger completion */ + mdelay(200); + + return 0; +} + +static int atc2609a_do_poweroff(const struct atc260x_pwrc *pwrc, bool restart) +{ + int ret, deep_sleep = 0; + uint reg_mask, reg_val; + + /* S4-Deep Sleep Mode is NOT available for WALL/USB power */ + if (!restart && !power_supply_is_system_supplied()) { + deep_sleep = 1; + dev_info(pwrc->dev, "Enabling S4-Deep Sleep Mode"); + } + + /* Update wakeup sources */ + reg_val = ATC2609A_PMU_SYS_CTL0_ONOFF_LONG_WK_EN | + (restart ? ATC2609A_PMU_SYS_CTL0_RESET_WK_EN + : ATC2609A_PMU_SYS_CTL0_ONOFF_SHORT_WK_EN); + + ret = regmap_update_bits(pwrc->regmap, ATC2609A_PMU_SYS_CTL0, + ATC2609A_PMU_SYS_CTL0_WK_ALL, reg_val); + if (ret) + dev_warn(pwrc->dev, "failed to write SYS_CTL0: %d\n", ret); + + /* Update power mode */ + reg_mask = ATC2609A_PMU_SYS_CTL3_EN_S2 | ATC2609A_PMU_SYS_CTL3_EN_S3; + + ret = regmap_update_bits(pwrc->regmap, ATC2609A_PMU_SYS_CTL3, reg_mask, + deep_sleep ? 0 : ATC2609A_PMU_SYS_CTL3_EN_S3); + if (ret) { + dev_err(pwrc->dev, "failed to write SYS_CTL3: %d\n", ret); + return ret; + } + + /* Trigger poweroff / restart sequence */ + reg_mask = restart ? ATC2609A_PMU_SYS_CTL0_RESTART_EN + : ATC2609A_PMU_SYS_CTL1_EN_S1; + reg_val = restart ? ATC2609A_PMU_SYS_CTL0_RESTART_EN : 0; + + ret = regmap_update_bits(pwrc->regmap, + restart ? ATC2609A_PMU_SYS_CTL0 : ATC2609A_PMU_SYS_CTL1, + reg_mask, reg_val); + if (ret) { + dev_err(pwrc->dev, "failed to write SYS_CTL%d: %d\n", + restart ? 0 : 1, ret); + return ret; + } + + /* Wait for trigger completion */ + mdelay(200); + + return 0; +} + +static int atc2603c_init(const struct atc260x_pwrc *pwrc) +{ + int ret; + + /* + * Delay transition from S2/S3 to S1 in order to avoid + * DDR init failure in Bootloader. + */ + ret = regmap_update_bits(pwrc->regmap, ATC2603C_PMU_SYS_CTL3, + ATC2603C_PMU_SYS_CTL3_S2S3TOS1_TIMER_EN, + ATC2603C_PMU_SYS_CTL3_S2S3TOS1_TIMER_EN); + if (ret) + dev_warn(pwrc->dev, "failed to write SYS_CTL3: %d\n", ret); + + /* Set wakeup sources */ + ret = regmap_update_bits(pwrc->regmap, ATC2603C_PMU_SYS_CTL0, + ATC2603C_PMU_SYS_CTL0_WK_ALL, + ATC2603C_PMU_SYS_CTL0_HDSW_WK_EN | + ATC2603C_PMU_SYS_CTL0_ONOFF_LONG_WK_EN); + if (ret) + dev_warn(pwrc->dev, "failed to write SYS_CTL0: %d\n", ret); + + return ret; +} + +static int atc2609a_init(const struct atc260x_pwrc *pwrc) +{ + int ret; + + /* Set wakeup sources */ + ret = regmap_update_bits(pwrc->regmap, ATC2609A_PMU_SYS_CTL0, + ATC2609A_PMU_SYS_CTL0_WK_ALL, + ATC2609A_PMU_SYS_CTL0_HDSW_WK_EN | + ATC2609A_PMU_SYS_CTL0_ONOFF_LONG_WK_EN); + if (ret) + dev_warn(pwrc->dev, "failed to write SYS_CTL0: %d\n", ret); + + return ret; +} + +static void atc260x_pwrc_pm_handler(void) +{ + atc260x_pwrc_data->do_poweroff(atc260x_pwrc_data, false); + + WARN_ONCE(1, "Unable to power off system\n"); +} + +static int atc260x_pwrc_restart_handler(struct notifier_block *nb, + unsigned long mode, void *cmd) +{ + struct atc260x_pwrc *pwrc = container_of(nb, struct atc260x_pwrc, + restart_nb); + pwrc->do_poweroff(pwrc, true); + + return NOTIFY_DONE; +} + +static int atc260x_pwrc_probe(struct platform_device *pdev) +{ + struct atc260x *atc260x = dev_get_drvdata(pdev->dev.parent); + struct atc260x_pwrc *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + priv->regmap = atc260x->regmap; + priv->restart_nb.notifier_call = atc260x_pwrc_restart_handler; + priv->restart_nb.priority = 192; + + switch (atc260x->ic_type) { + case ATC2603C: + priv->do_poweroff = atc2603c_do_poweroff; + ret = atc2603c_init(priv); + break; + case ATC2609A: + priv->do_poweroff = atc2609a_do_poweroff; + ret = atc2609a_init(priv); + break; + default: + dev_err(priv->dev, + "Poweroff not supported for ATC260x PMIC type: %u\n", + atc260x->ic_type); + return -EINVAL; + } + + if (ret) + return ret; + + platform_set_drvdata(pdev, priv); + + if (!pm_power_off) { + atc260x_pwrc_data = priv; + pm_power_off = atc260x_pwrc_pm_handler; + } else { + dev_warn(priv->dev, "Poweroff callback already assigned\n"); + } + + ret = register_restart_handler(&priv->restart_nb); + if (ret) + dev_err(priv->dev, "failed to register restart handler: %d\n", + ret); + + return ret; +} + +static int atc260x_pwrc_remove(struct platform_device *pdev) +{ + struct atc260x_pwrc *priv = platform_get_drvdata(pdev); + + if (atc260x_pwrc_data == priv) { + pm_power_off = NULL; + atc260x_pwrc_data = NULL; + } + + unregister_restart_handler(&priv->restart_nb); + + return 0; +} + +static struct platform_driver atc260x_pwrc_driver = { + .probe = atc260x_pwrc_probe, + .remove = atc260x_pwrc_remove, + .driver = { + .name = "atc260x-pwrc", + }, +}; + +module_platform_driver(atc260x_pwrc_driver); + +MODULE_DESCRIPTION("Poweroff & reset driver for ATC260x PMICs"); +MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/reset/axxia-reset.c b/drivers/power/reset/axxia-reset.c new file mode 100644 index 000000000..f7b40be5d --- /dev/null +++ b/drivers/power/reset/axxia-reset.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Reset driver for Axxia devices + * + * Copyright (C) 2014 LSI + */ +#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("%pOFn: syscon lookup failed\n", dev->of_node); + 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..3de024e3c --- /dev/null +++ b/drivers/power/reset/brcm-kona-reset.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2016 Broadcom + +#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..0f2944dc9 --- /dev/null +++ b/drivers/power/reset/brcmstb-reboot.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2013 Broadcom Corporation + +#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..1c5af2fef --- /dev/null +++ b/drivers/power/reset/gpio-poweroff.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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 + */ +#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 u32 active_delay = 100; +static u32 inactive_delay = 100; + +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(active_delay); + + /* drive inactive, also active->inactive edge */ + gpiod_set_value_cansleep(reset_gpio, 0); + mdelay(inactive_delay); + + /* 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\n", + __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, "active-delay-ms", &active_delay); + device_property_read_u32(&pdev->dev, "inactive-delay-ms", + &inactive_delay); + 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..5466eeea2 --- /dev/null +++ b/drivers/power/reset/gpio-restart.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Toggles a GPIO pin to restart a device + * + * Copyright (C) 2014 Google, Inc. + * + * 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); + ret = PTR_ERR_OR_ZERO(gpio_restart->reset_gpio); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Could not get reset GPIO\n"); + return ret; + } + + 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..344093405 --- /dev/null +++ b/drivers/power/reset/hisi-reboot.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HiSilicon SoC reset code + * + * Copyright (c) 2014 HiSilicon Ltd. + * Copyright (c) 2014 Linaro Ltd. + * + * Author: Haojian Zhuang <haojian.zhuang@linaro.org> + */ + +#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" }, + {} +}; +MODULE_DEVICE_TABLE(of, hisi_reboot_of_match); + +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..c720112db --- /dev/null +++ b/drivers/power/reset/keystone-reset.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI keystone reboot driver + * + * Copyright (C) 2014 Texas Instruments Incorporated. https://www.ti.com/ + * + * Author: Ivan Khoronzhuk <ivan.khoronzhuk@ti.com> + */ + +#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", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rsctrl_of_match); + +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/linkstation-poweroff.c b/drivers/power/reset/linkstation-poweroff.c new file mode 100644 index 000000000..02f5fdb8f --- /dev/null +++ b/drivers/power/reset/linkstation-poweroff.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LinkStation power off restart driver + * Copyright (C) 2020 Daniel González Cabanelas <dgcbueu@gmail.com> + */ + +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/of_mdio.h> +#include <linux/of_platform.h> +#include <linux/reboot.h> +#include <linux/phy.h> + +/* Defines from the eth phy Marvell driver */ +#define MII_MARVELL_COPPER_PAGE 0 +#define MII_MARVELL_LED_PAGE 3 +#define MII_MARVELL_WOL_PAGE 17 +#define MII_MARVELL_PHY_PAGE 22 + +#define MII_PHY_LED_CTRL 16 +#define MII_PHY_LED_POL_CTRL 17 +#define MII_88E1318S_PHY_LED_TCR 18 +#define MII_88E1318S_PHY_WOL_CTRL 16 +#define MII_M1011_IEVENT 19 + +#define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7) +#define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15) +#define MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS BIT(12) +#define LED2_FORCE_ON (0x8 << 8) +#define LEDMASK GENMASK(11,8) + +#define MII_88E1318S_PHY_LED_POL_LED2 BIT(4) + +struct power_off_cfg { + char *mdio_node_name; + void (*phy_set_reg)(bool restart); +}; + +static struct phy_device *phydev; +static const struct power_off_cfg *cfg; + +static void linkstation_mvphy_reg_intn(bool restart) +{ + int rc = 0, saved_page; + u16 data = 0; + + if (restart) + data = MII_88E1318S_PHY_LED_TCR_FORCE_INT; + + saved_page = phy_select_page(phydev, MII_MARVELL_LED_PAGE); + if (saved_page < 0) + goto err; + + /* Force manual LED2 control to let INTn work */ + __phy_modify(phydev, MII_PHY_LED_CTRL, LEDMASK, LED2_FORCE_ON); + + /* Set the LED[2]/INTn pin to the required state */ + __phy_modify(phydev, MII_88E1318S_PHY_LED_TCR, + MII_88E1318S_PHY_LED_TCR_FORCE_INT, + MII_88E1318S_PHY_LED_TCR_INTn_ENABLE | data); + + if (!data) { + /* Clear interrupts to ensure INTn won't be holded in high state */ + __phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_COPPER_PAGE); + __phy_read(phydev, MII_M1011_IEVENT); + + /* If WOL was enabled and a magic packet was received before powering + * off, we won't be able to wake up by sending another magic packet. + * Clear WOL status. + */ + __phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_WOL_PAGE); + __phy_set_bits(phydev, MII_88E1318S_PHY_WOL_CTRL, + MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS); + } +err: + rc = phy_restore_page(phydev, saved_page, rc); + if (rc < 0) + dev_err(&phydev->mdio.dev, "Write register failed, %d\n", rc); +} + +static void readynas_mvphy_set_reg(bool restart) +{ + int rc = 0, saved_page; + u16 data = 0; + + if (restart) + data = MII_88E1318S_PHY_LED_POL_LED2; + + saved_page = phy_select_page(phydev, MII_MARVELL_LED_PAGE); + if (saved_page < 0) + goto err; + + /* Set the LED[2].0 Polarity bit to the required state */ + __phy_modify(phydev, MII_PHY_LED_POL_CTRL, + MII_88E1318S_PHY_LED_POL_LED2, data); + + if (!data) { + /* If WOL was enabled and a magic packet was received before powering + * off, we won't be able to wake up by sending another magic packet. + * Clear WOL status. + */ + __phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_WOL_PAGE); + __phy_set_bits(phydev, MII_88E1318S_PHY_WOL_CTRL, + MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS); + } +err: + rc = phy_restore_page(phydev, saved_page, rc); + if (rc < 0) + dev_err(&phydev->mdio.dev, "Write register failed, %d\n", rc); +} + +static const struct power_off_cfg linkstation_power_off_cfg = { + .mdio_node_name = "mdio", + .phy_set_reg = linkstation_mvphy_reg_intn, +}; + +static const struct power_off_cfg readynas_power_off_cfg = { + .mdio_node_name = "mdio-bus", + .phy_set_reg = readynas_mvphy_set_reg, +}; + +static int linkstation_reboot_notifier(struct notifier_block *nb, + unsigned long action, void *unused) +{ + if (action == SYS_RESTART) + cfg->phy_set_reg(true); + + return NOTIFY_DONE; +} + +static struct notifier_block linkstation_reboot_nb = { + .notifier_call = linkstation_reboot_notifier, +}; + +static void linkstation_poweroff(void) +{ + unregister_reboot_notifier(&linkstation_reboot_nb); + cfg->phy_set_reg(false); + + kernel_restart("Power off"); +} + +static const struct of_device_id ls_poweroff_of_match[] = { + { .compatible = "buffalo,ls421d", + .data = &linkstation_power_off_cfg, + }, + { .compatible = "buffalo,ls421de", + .data = &linkstation_power_off_cfg, + }, + { .compatible = "netgear,readynas-duo-v2", + .data = &readynas_power_off_cfg, + }, + { }, +}; + +static int __init linkstation_poweroff_init(void) +{ + struct mii_bus *bus; + struct device_node *dn; + const struct of_device_id *match; + + dn = of_find_matching_node(NULL, ls_poweroff_of_match); + if (!dn) + return -ENODEV; + of_node_put(dn); + + match = of_match_node(ls_poweroff_of_match, dn); + cfg = match->data; + + dn = of_find_node_by_name(NULL, cfg->mdio_node_name); + if (!dn) + return -ENODEV; + + bus = of_mdio_find_bus(dn); + of_node_put(dn); + if (!bus) + return -EPROBE_DEFER; + + phydev = phy_find_first(bus); + put_device(&bus->dev); + if (!phydev) + return -EPROBE_DEFER; + + register_reboot_notifier(&linkstation_reboot_nb); + pm_power_off = linkstation_poweroff; + + return 0; +} + +static void __exit linkstation_poweroff_exit(void) +{ + pm_power_off = NULL; + unregister_reboot_notifier(&linkstation_reboot_nb); +} + +module_init(linkstation_poweroff_init); +module_exit(linkstation_poweroff_exit); + +MODULE_AUTHOR("Daniel González Cabanelas <dgcbueu@gmail.com>"); +MODULE_DESCRIPTION("LinkStation power off driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/ltc2952-poweroff.c b/drivers/power/reset/ltc2952-poweroff.c new file mode 100644 index 000000000..65d9528cc --- /dev/null +++ b/drivers/power/reset/ltc2952-poweroff.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LTC2952 (PowerPath) driver + * + * Copyright (C) 2014, Xsens Technologies BV <info@xsens.com> + * Maintainer: René Moll <linux@r-moll.nl> + * + * ---------------------------------------- + * - 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/panic_notifier.h> +#include <linux/mod_devicetable.h> +#include <linux/gpio/consumer.h> +#include <linux/reboot.h> +#include <linux/property.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) +{ + int state; + 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); + + hrtimer_forward_now(timer, 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; + u32 trigger_delay_ms; + struct ltc2952_poweroff *data = platform_get_drvdata(pdev); + + ltc2952_poweroff_default(data); + + if (!device_property_read_u32(&pdev->dev, "trigger-delay-ms", + &trigger_delay_ms)) { + data->trigger_delay = ktime_set(trigger_delay_ms / MSEC_PER_SEC, + (trigger_delay_ms % MSEC_PER_SEC) * NSEC_PER_MSEC); + } + + 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..0c439f83b --- /dev/null +++ b/drivers/power/reset/msm-poweroff.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + */ + +#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/mt6323-poweroff.c b/drivers/power/reset/mt6323-poweroff.c new file mode 100644 index 000000000..d90e76fcb --- /dev/null +++ b/drivers/power/reset/mt6323-poweroff.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Power off through MediaTek PMIC + * + * Copyright (C) 2018 MediaTek Inc. + * + * Author: Sean Wang <sean.wang@mediatek.com> + * + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/mfd/mt6397/core.h> +#include <linux/mfd/mt6397/rtc.h> + +struct mt6323_pwrc { + struct device *dev; + struct regmap *regmap; + u32 base; +}; + +static struct mt6323_pwrc *mt_pwrc; + +static void mt6323_do_pwroff(void) +{ + struct mt6323_pwrc *pwrc = mt_pwrc; + unsigned int val; + int ret; + + regmap_write(pwrc->regmap, pwrc->base + RTC_BBPU, RTC_BBPU_KEY); + regmap_write(pwrc->regmap, pwrc->base + RTC_WRTGR_MT6323, 1); + + ret = regmap_read_poll_timeout(pwrc->regmap, + pwrc->base + RTC_BBPU, val, + !(val & RTC_BBPU_CBUSY), + MTK_RTC_POLL_DELAY_US, + MTK_RTC_POLL_TIMEOUT); + if (ret) + dev_err(pwrc->dev, "failed to write BBPU: %d\n", ret); + + /* Wait some time until system down, otherwise, notice with a warn */ + mdelay(1000); + + WARN_ONCE(1, "Unable to power off system\n"); +} + +static int mt6323_pwrc_probe(struct platform_device *pdev) +{ + struct mt6397_chip *mt6397_chip = dev_get_drvdata(pdev->dev.parent); + struct mt6323_pwrc *pwrc; + struct resource *res; + + pwrc = devm_kzalloc(&pdev->dev, sizeof(*pwrc), GFP_KERNEL); + if (!pwrc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + pwrc->base = res->start; + pwrc->regmap = mt6397_chip->regmap; + pwrc->dev = &pdev->dev; + mt_pwrc = pwrc; + + pm_power_off = &mt6323_do_pwroff; + + return 0; +} + +static int mt6323_pwrc_remove(struct platform_device *pdev) +{ + if (pm_power_off == &mt6323_do_pwroff) + pm_power_off = NULL; + + return 0; +} + +static const struct of_device_id mt6323_pwrc_dt_match[] = { + { .compatible = "mediatek,mt6323-pwrc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, mt6323_pwrc_dt_match); + +static struct platform_driver mt6323_pwrc_driver = { + .probe = mt6323_pwrc_probe, + .remove = mt6323_pwrc_remove, + .driver = { + .name = "mt6323-pwrc", + .of_match_table = mt6323_pwrc_dt_match, + }, +}; + +module_platform_driver(mt6323_pwrc_driver); + +MODULE_DESCRIPTION("Poweroff driver for MT6323 PMIC"); +MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/nvmem-reboot-mode.c b/drivers/power/reset/nvmem-reboot-mode.c new file mode 100644 index 000000000..e229308d4 --- /dev/null +++ b/drivers/power/reset/nvmem-reboot-mode.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) Vaisala Oyj. All rights reserved. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/nvmem-consumer.h> +#include <linux/platform_device.h> +#include <linux/reboot-mode.h> + +struct nvmem_reboot_mode { + struct reboot_mode_driver reboot; + struct nvmem_cell *cell; +}; + +static int nvmem_reboot_mode_write(struct reboot_mode_driver *reboot, + unsigned int magic) +{ + int ret; + struct nvmem_reboot_mode *nvmem_rbm; + + nvmem_rbm = container_of(reboot, struct nvmem_reboot_mode, reboot); + + ret = nvmem_cell_write(nvmem_rbm->cell, &magic, sizeof(magic)); + if (ret < 0) + dev_err(reboot->dev, "update reboot mode bits failed\n"); + + return ret; +} + +static int nvmem_reboot_mode_probe(struct platform_device *pdev) +{ + int ret; + struct nvmem_reboot_mode *nvmem_rbm; + + nvmem_rbm = devm_kzalloc(&pdev->dev, sizeof(*nvmem_rbm), GFP_KERNEL); + if (!nvmem_rbm) + return -ENOMEM; + + nvmem_rbm->reboot.dev = &pdev->dev; + nvmem_rbm->reboot.write = nvmem_reboot_mode_write; + + nvmem_rbm->cell = devm_nvmem_cell_get(&pdev->dev, "reboot-mode"); + if (IS_ERR(nvmem_rbm->cell)) { + dev_err(&pdev->dev, "failed to get the nvmem cell reboot-mode\n"); + return PTR_ERR(nvmem_rbm->cell); + } + + ret = devm_reboot_mode_register(&pdev->dev, &nvmem_rbm->reboot); + if (ret) + dev_err(&pdev->dev, "can't register reboot mode\n"); + + return ret; +} + +static const struct of_device_id nvmem_reboot_mode_of_match[] = { + { .compatible = "nvmem-reboot-mode" }, + {} +}; +MODULE_DEVICE_TABLE(of, nvmem_reboot_mode_of_match); + +static struct platform_driver nvmem_reboot_mode_driver = { + .probe = nvmem_reboot_mode_probe, + .driver = { + .name = "nvmem-reboot-mode", + .of_match_table = nvmem_reboot_mode_of_match, + }, +}; +module_platform_driver(nvmem_reboot_mode_driver); + +MODULE_AUTHOR("Nandor Han <nandor.han@vaisala.com>"); +MODULE_DESCRIPTION("NVMEM reboot mode driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/reset/ocelot-reset.c b/drivers/power/reset/ocelot-reset.c new file mode 100644 index 000000000..8caa90cb5 --- /dev/null +++ b/drivers/power/reset/ocelot-reset.c @@ -0,0 +1,155 @@ +// 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 reset_props { + const char *syscon; + u32 protect_reg; + u32 vcore_protect; + u32 if_si_owner_bit; +}; + +struct ocelot_reset_context { + void __iomem *base; + struct regmap *cpu_ctrl; + const struct reset_props *props; + struct notifier_block restart_handler; +}; + +#define BIT_OFF_INVALID 32 + +#define SOFT_CHIP_RST BIT(0) + +#define ICPU_CFG_CPU_SYSTEM_CTRL_GENERAL_CTRL 0x24 +#define IF_SI_OWNER_MASK GENMASK(1, 0) +#define IF_SI_OWNER_SISL 0 +#define IF_SI_OWNER_SIBM 1 +#define IF_SI_OWNER_SIMC 2 + +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); + u32 if_si_owner_bit = ctx->props->if_si_owner_bit; + + /* Make sure the core is not protected from reset */ + regmap_update_bits(ctx->cpu_ctrl, ctx->props->protect_reg, + ctx->props->vcore_protect, 0); + + /* Make the SI back to boot mode */ + if (if_si_owner_bit != BIT_OFF_INVALID) + regmap_update_bits(ctx->cpu_ctrl, + ICPU_CFG_CPU_SYSTEM_CTRL_GENERAL_CTRL, + IF_SI_OWNER_MASK << if_si_owner_bit, + IF_SI_OWNER_SIBM << if_si_owner_bit); + + pr_emerg("Resetting SoC\n"); + + 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->props = device_get_match_data(dev); + + ctx->cpu_ctrl = syscon_regmap_lookup_by_compatible(ctx->props->syscon); + if (IS_ERR(ctx->cpu_ctrl)) { + dev_err(dev, "No syscon map: %s\n", ctx->props->syscon); + 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 reset_props reset_props_jaguar2 = { + .syscon = "mscc,ocelot-cpu-syscon", + .protect_reg = 0x20, + .vcore_protect = BIT(2), + .if_si_owner_bit = 6, +}; + +static const struct reset_props reset_props_luton = { + .syscon = "mscc,ocelot-cpu-syscon", + .protect_reg = 0x20, + .vcore_protect = BIT(2), + .if_si_owner_bit = BIT_OFF_INVALID, /* n/a */ +}; + +static const struct reset_props reset_props_ocelot = { + .syscon = "mscc,ocelot-cpu-syscon", + .protect_reg = 0x20, + .vcore_protect = BIT(2), + .if_si_owner_bit = 4, +}; + +static const struct reset_props reset_props_sparx5 = { + .syscon = "microchip,sparx5-cpu-syscon", + .protect_reg = 0x84, + .vcore_protect = BIT(10), + .if_si_owner_bit = 6, +}; + +static const struct of_device_id ocelot_reset_of_match[] = { + { + .compatible = "mscc,jaguar2-chip-reset", + .data = &reset_props_jaguar2 + }, { + .compatible = "mscc,luton-chip-reset", + .data = &reset_props_luton + }, { + .compatible = "mscc,ocelot-chip-reset", + .data = &reset_props_ocelot + }, { + .compatible = "microchip,sparx5-chip-reset", + .data = &reset_props_sparx5 + }, + { /*sentinel*/ } +}; + +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/oxnas-restart.c b/drivers/power/reset/oxnas-restart.c new file mode 100644 index 000000000..13090bec0 --- /dev/null +++ b/drivers/power/reset/oxnas-restart.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: (GPL-2.0) +/* + * oxnas SoC reset driver + * based on: + * Microsemi MIPS SoC reset driver + * and ox820_assert_system_reset() written by Ma Hajun <mahaijuns@gmail.com> + * + * Copyright (c) 2013 Ma Hajun <mahaijuns@gmail.com> + * Copyright (c) 2017 Microsemi Corporation + * Copyright (c) 2020 Daniel Golle <daniel@makrotopia.org> + */ +#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> + +/* bit numbers of reset control register */ +#define OX820_SYS_CTRL_RST_SCU 0 +#define OX820_SYS_CTRL_RST_COPRO 1 +#define OX820_SYS_CTRL_RST_ARM0 2 +#define OX820_SYS_CTRL_RST_ARM1 3 +#define OX820_SYS_CTRL_RST_USBHS 4 +#define OX820_SYS_CTRL_RST_USBHSPHYA 5 +#define OX820_SYS_CTRL_RST_MACA 6 +#define OX820_SYS_CTRL_RST_MAC OX820_SYS_CTRL_RST_MACA +#define OX820_SYS_CTRL_RST_PCIEA 7 +#define OX820_SYS_CTRL_RST_SGDMA 8 +#define OX820_SYS_CTRL_RST_CIPHER 9 +#define OX820_SYS_CTRL_RST_DDR 10 +#define OX820_SYS_CTRL_RST_SATA 11 +#define OX820_SYS_CTRL_RST_SATA_LINK 12 +#define OX820_SYS_CTRL_RST_SATA_PHY 13 +#define OX820_SYS_CTRL_RST_PCIEPHY 14 +#define OX820_SYS_CTRL_RST_STATIC 15 +#define OX820_SYS_CTRL_RST_GPIO 16 +#define OX820_SYS_CTRL_RST_UART1 17 +#define OX820_SYS_CTRL_RST_UART2 18 +#define OX820_SYS_CTRL_RST_MISC 19 +#define OX820_SYS_CTRL_RST_I2S 20 +#define OX820_SYS_CTRL_RST_SD 21 +#define OX820_SYS_CTRL_RST_MACB 22 +#define OX820_SYS_CTRL_RST_PCIEB 23 +#define OX820_SYS_CTRL_RST_VIDEO 24 +#define OX820_SYS_CTRL_RST_DDR_PHY 25 +#define OX820_SYS_CTRL_RST_USBHSPHYB 26 +#define OX820_SYS_CTRL_RST_USBDEV 27 +#define OX820_SYS_CTRL_RST_ARMDBG 29 +#define OX820_SYS_CTRL_RST_PLLA 30 +#define OX820_SYS_CTRL_RST_PLLB 31 + +/* bit numbers of clock control register */ +#define OX820_SYS_CTRL_CLK_COPRO 0 +#define OX820_SYS_CTRL_CLK_DMA 1 +#define OX820_SYS_CTRL_CLK_CIPHER 2 +#define OX820_SYS_CTRL_CLK_SD 3 +#define OX820_SYS_CTRL_CLK_SATA 4 +#define OX820_SYS_CTRL_CLK_I2S 5 +#define OX820_SYS_CTRL_CLK_USBHS 6 +#define OX820_SYS_CTRL_CLK_MACA 7 +#define OX820_SYS_CTRL_CLK_MAC OX820_SYS_CTRL_CLK_MACA +#define OX820_SYS_CTRL_CLK_PCIEA 8 +#define OX820_SYS_CTRL_CLK_STATIC 9 +#define OX820_SYS_CTRL_CLK_MACB 10 +#define OX820_SYS_CTRL_CLK_PCIEB 11 +#define OX820_SYS_CTRL_CLK_REF600 12 +#define OX820_SYS_CTRL_CLK_USBDEV 13 +#define OX820_SYS_CTRL_CLK_DDR 14 +#define OX820_SYS_CTRL_CLK_DDRPHY 15 +#define OX820_SYS_CTRL_CLK_DDRCK 16 + +/* Regmap offsets */ +#define OX820_CLK_SET_REGOFFSET 0x2c +#define OX820_CLK_CLR_REGOFFSET 0x30 +#define OX820_RST_SET_REGOFFSET 0x34 +#define OX820_RST_CLR_REGOFFSET 0x38 +#define OX820_SECONDARY_SEL_REGOFFSET 0x14 +#define OX820_TERTIARY_SEL_REGOFFSET 0x8c +#define OX820_QUATERNARY_SEL_REGOFFSET 0x94 +#define OX820_DEBUG_SEL_REGOFFSET 0x9c +#define OX820_ALTERNATIVE_SEL_REGOFFSET 0xa4 +#define OX820_PULLUP_SEL_REGOFFSET 0xac +#define OX820_SEC_SECONDARY_SEL_REGOFFSET 0x100014 +#define OX820_SEC_TERTIARY_SEL_REGOFFSET 0x10008c +#define OX820_SEC_QUATERNARY_SEL_REGOFFSET 0x100094 +#define OX820_SEC_DEBUG_SEL_REGOFFSET 0x10009c +#define OX820_SEC_ALTERNATIVE_SEL_REGOFFSET 0x1000a4 +#define OX820_SEC_PULLUP_SEL_REGOFFSET 0x1000ac + +struct oxnas_restart_context { + struct regmap *sys_ctrl; + struct notifier_block restart_handler; +}; + +static int ox820_restart_handle(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + struct oxnas_restart_context *ctx = container_of(this, struct + oxnas_restart_context, + restart_handler); + u32 value; + + /* + * Assert reset to cores as per power on defaults + * Don't touch the DDR interface as things will come to an impromptu + * stop NB Possibly should be asserting reset for PLLB, but there are + * timing concerns here according to the docs + */ + value = BIT(OX820_SYS_CTRL_RST_COPRO) | + BIT(OX820_SYS_CTRL_RST_USBHS) | + BIT(OX820_SYS_CTRL_RST_USBHSPHYA) | + BIT(OX820_SYS_CTRL_RST_MACA) | + BIT(OX820_SYS_CTRL_RST_PCIEA) | + BIT(OX820_SYS_CTRL_RST_SGDMA) | + BIT(OX820_SYS_CTRL_RST_CIPHER) | + BIT(OX820_SYS_CTRL_RST_SATA) | + BIT(OX820_SYS_CTRL_RST_SATA_LINK) | + BIT(OX820_SYS_CTRL_RST_SATA_PHY) | + BIT(OX820_SYS_CTRL_RST_PCIEPHY) | + BIT(OX820_SYS_CTRL_RST_STATIC) | + BIT(OX820_SYS_CTRL_RST_UART1) | + BIT(OX820_SYS_CTRL_RST_UART2) | + BIT(OX820_SYS_CTRL_RST_MISC) | + BIT(OX820_SYS_CTRL_RST_I2S) | + BIT(OX820_SYS_CTRL_RST_SD) | + BIT(OX820_SYS_CTRL_RST_MACB) | + BIT(OX820_SYS_CTRL_RST_PCIEB) | + BIT(OX820_SYS_CTRL_RST_VIDEO) | + BIT(OX820_SYS_CTRL_RST_USBHSPHYB) | + BIT(OX820_SYS_CTRL_RST_USBDEV); + + regmap_write(ctx->sys_ctrl, OX820_RST_SET_REGOFFSET, value); + + /* Release reset to cores as per power on defaults */ + regmap_write(ctx->sys_ctrl, OX820_RST_CLR_REGOFFSET, + BIT(OX820_SYS_CTRL_RST_GPIO)); + + /* + * Disable clocks to cores as per power-on defaults - must leave DDR + * related clocks enabled otherwise we'll stop rather abruptly. + */ + value = BIT(OX820_SYS_CTRL_CLK_COPRO) | + BIT(OX820_SYS_CTRL_CLK_DMA) | + BIT(OX820_SYS_CTRL_CLK_CIPHER) | + BIT(OX820_SYS_CTRL_CLK_SD) | + BIT(OX820_SYS_CTRL_CLK_SATA) | + BIT(OX820_SYS_CTRL_CLK_I2S) | + BIT(OX820_SYS_CTRL_CLK_USBHS) | + BIT(OX820_SYS_CTRL_CLK_MAC) | + BIT(OX820_SYS_CTRL_CLK_PCIEA) | + BIT(OX820_SYS_CTRL_CLK_STATIC) | + BIT(OX820_SYS_CTRL_CLK_MACB) | + BIT(OX820_SYS_CTRL_CLK_PCIEB) | + BIT(OX820_SYS_CTRL_CLK_REF600) | + BIT(OX820_SYS_CTRL_CLK_USBDEV); + + regmap_write(ctx->sys_ctrl, OX820_CLK_CLR_REGOFFSET, value); + + /* Enable clocks to cores as per power-on defaults */ + + /* Set sys-control pin mux'ing as per power-on defaults */ + regmap_write(ctx->sys_ctrl, OX820_SECONDARY_SEL_REGOFFSET, 0); + regmap_write(ctx->sys_ctrl, OX820_TERTIARY_SEL_REGOFFSET, 0); + regmap_write(ctx->sys_ctrl, OX820_QUATERNARY_SEL_REGOFFSET, 0); + regmap_write(ctx->sys_ctrl, OX820_DEBUG_SEL_REGOFFSET, 0); + regmap_write(ctx->sys_ctrl, OX820_ALTERNATIVE_SEL_REGOFFSET, 0); + regmap_write(ctx->sys_ctrl, OX820_PULLUP_SEL_REGOFFSET, 0); + + regmap_write(ctx->sys_ctrl, OX820_SEC_SECONDARY_SEL_REGOFFSET, 0); + regmap_write(ctx->sys_ctrl, OX820_SEC_TERTIARY_SEL_REGOFFSET, 0); + regmap_write(ctx->sys_ctrl, OX820_SEC_QUATERNARY_SEL_REGOFFSET, 0); + regmap_write(ctx->sys_ctrl, OX820_SEC_DEBUG_SEL_REGOFFSET, 0); + regmap_write(ctx->sys_ctrl, OX820_SEC_ALTERNATIVE_SEL_REGOFFSET, 0); + regmap_write(ctx->sys_ctrl, OX820_SEC_PULLUP_SEL_REGOFFSET, 0); + + /* + * No need to save any state, as the ROM loader can determine whether + * reset is due to power cycling or programatic action, just hit the + * (self-clearing) CPU reset bit of the block reset register + */ + value = + BIT(OX820_SYS_CTRL_RST_SCU) | + BIT(OX820_SYS_CTRL_RST_ARM0) | + BIT(OX820_SYS_CTRL_RST_ARM1); + + regmap_write(ctx->sys_ctrl, OX820_RST_SET_REGOFFSET, value); + + pr_emerg("Unable to restart system\n"); + return NOTIFY_DONE; +} + +static int ox820_restart_probe(struct platform_device *pdev) +{ + struct oxnas_restart_context *ctx; + struct regmap *sys_ctrl; + struct device *dev = &pdev->dev; + int err = 0; + + sys_ctrl = syscon_node_to_regmap(pdev->dev.of_node); + if (IS_ERR(sys_ctrl)) + return PTR_ERR(sys_ctrl); + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->sys_ctrl = sys_ctrl; + ctx->restart_handler.notifier_call = ox820_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 ox820_restart_of_match[] = { + { .compatible = "oxsemi,ox820-sys-ctrl" }, + {} +}; + +static struct platform_driver ox820_restart_driver = { + .probe = ox820_restart_probe, + .driver = { + .name = "ox820-chip-reset", + .of_match_table = ox820_restart_of_match, + }, +}; +builtin_platform_driver(ox820_restart_driver); diff --git a/drivers/power/reset/piix4-poweroff.c b/drivers/power/reset/piix4-poweroff.c new file mode 100644 index 000000000..7f308292d --- /dev/null +++ b/drivers/power/reset/piix4-poweroff.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 Imagination Technologies + * Author: Paul Burton <paul.burton@mips.com> + */ + +#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/pwr-mlxbf.c b/drivers/power/reset/pwr-mlxbf.c new file mode 100644 index 000000000..12dedf841 --- /dev/null +++ b/drivers/power/reset/pwr-mlxbf.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only or BSD-3-Clause + +/* + * Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/devm-helpers.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/reboot.h> +#include <linux/types.h> + +struct pwr_mlxbf { + struct work_struct send_work; + const char *hid; +}; + +static void pwr_mlxbf_send_work(struct work_struct *work) +{ + acpi_bus_generate_netlink_event("button/power.*", "Power Button", 0x80, 1); +} + +static irqreturn_t pwr_mlxbf_irq(int irq, void *ptr) +{ + const char *rst_pwr_hid = "MLNXBF24"; + const char *low_pwr_hid = "MLNXBF29"; + struct pwr_mlxbf *priv = ptr; + + if (!strncmp(priv->hid, rst_pwr_hid, 8)) + emergency_restart(); + + if (!strncmp(priv->hid, low_pwr_hid, 8)) + schedule_work(&priv->send_work); + + return IRQ_HANDLED; +} + +static int pwr_mlxbf_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct acpi_device *adev; + struct pwr_mlxbf *priv; + const char *hid; + int irq, err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + adev = ACPI_COMPANION(dev); + if (!adev) + return -ENXIO; + + hid = acpi_device_hid(adev); + priv->hid = hid; + + irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 0); + if (irq < 0) + return dev_err_probe(dev, irq, "Error getting %s irq.\n", priv->hid); + + err = devm_work_autocancel(dev, &priv->send_work, pwr_mlxbf_send_work); + if (err) + return err; + + err = devm_request_irq(dev, irq, pwr_mlxbf_irq, 0, hid, priv); + if (err) + dev_err(dev, "Failed request of %s irq\n", priv->hid); + + return err; +} + +static const struct acpi_device_id __maybe_unused pwr_mlxbf_acpi_match[] = { + { "MLNXBF24", 0 }, + { "MLNXBF29", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, pwr_mlxbf_acpi_match); + +static struct platform_driver pwr_mlxbf_driver = { + .driver = { + .name = "pwr_mlxbf", + .acpi_match_table = pwr_mlxbf_acpi_match, + }, + .probe = pwr_mlxbf_probe, +}; + +module_platform_driver(pwr_mlxbf_driver); + +MODULE_DESCRIPTION("Mellanox BlueField power driver"); +MODULE_AUTHOR("Asmaa Mnebhi <asmaa@nvidia.com>"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c new file mode 100644 index 000000000..16bc01738 --- /dev/null +++ b/drivers/power/reset/qcom-pon.c @@ -0,0 +1,100 @@ +// 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 + +#define GEN1_REASON_SHIFT 2 +#define GEN2_REASON_SHIFT 1 + +struct pm8916_pon { + struct device *dev; + struct regmap *regmap; + u32 baseaddr; + struct reboot_mode_driver reboot_mode; + long reason_shift; +}; + +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, + GENMASK(7, pon->reason_shift), + magic << pon->reason_shift); + 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->reason_shift = (long)of_device_get_match_data(&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", .data = (void *)GEN1_REASON_SHIFT }, + { .compatible = "qcom,pms405-pon", .data = (void *)GEN1_REASON_SHIFT }, + { .compatible = "qcom,pm8998-pon", .data = (void *)GEN2_REASON_SHIFT }, + { .compatible = "qcom,pmk8350-pon", .data = (void *)GEN2_REASON_SHIFT }, + { } +}; +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..0ddf7f25f --- /dev/null +++ b/drivers/power/reset/qnap-poweroff.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/serial_reg.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; + + 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) { + dev_err(&pdev->dev, "pm_power_off already claimed for %ps", + pm_power_off); + 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..b4076b10b --- /dev/null +++ b/drivers/power/reset/reboot-mode.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd + */ + +#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/regulator-poweroff.c b/drivers/power/reset/regulator-poweroff.c new file mode 100644 index 000000000..207012039 --- /dev/null +++ b/drivers/power/reset/regulator-poweroff.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Force-disables a regulator to power down a device + * + * Michael Klein <michael@fossekall.de> + * + * Copyright (C) 2020 Michael Klein + * + * Based on the gpio-poweroff driver. + */ +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/regulator/consumer.h> + +#define TIMEOUT_MS 3000 + +/* + * Hold configuration here, cannot be more than one instance of the driver + * since pm_power_off itself is global. + */ +static struct regulator *cpu_regulator; + +static void regulator_poweroff_do_poweroff(void) +{ + if (cpu_regulator && regulator_is_enabled(cpu_regulator)) + regulator_force_disable(cpu_regulator); + + /* give it some time */ + mdelay(TIMEOUT_MS); + + WARN_ON(1); +} + +static int regulator_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, + "%s: pm_power_off function already registered\n", + __func__); + return -EBUSY; + } + + cpu_regulator = devm_regulator_get(&pdev->dev, "cpu"); + if (IS_ERR(cpu_regulator)) + return PTR_ERR(cpu_regulator); + + pm_power_off = ®ulator_poweroff_do_poweroff; + return 0; +} + +static int regulator_poweroff_remove(__maybe_unused struct platform_device *pdev) +{ + if (pm_power_off == ®ulator_poweroff_do_poweroff) + pm_power_off = NULL; + + return 0; +} + +static const struct of_device_id of_regulator_poweroff_match[] = { + { .compatible = "regulator-poweroff", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_regulator_poweroff_match); + +static struct platform_driver regulator_poweroff_driver = { + .probe = regulator_poweroff_probe, + .remove = regulator_poweroff_remove, + .driver = { + .name = "poweroff-regulator", + .of_match_table = of_regulator_poweroff_match, + }, +}; + +module_platform_driver(regulator_poweroff_driver); + +MODULE_AUTHOR("Michael Klein <michael@fossekall.de>"); +MODULE_DESCRIPTION("Regulator poweroff driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:poweroff-regulator"); diff --git a/drivers/power/reset/restart-poweroff.c b/drivers/power/reset/restart-poweroff.c new file mode 100644 index 000000000..04d422811 --- /dev/null +++ b/drivers/power/reset/restart-poweroff.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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 + */ +#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", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_restart_poweroff_match); + +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..bd3b39655 --- /dev/null +++ b/drivers/power/reset/rmobile-reset.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas R-Mobile Reset Driver + * + * Copyright (C) 2014 Glider bvba + */ + +#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..90287c319 --- /dev/null +++ b/drivers/power/reset/sc27xx-poweroff.c @@ -0,0 +1,79 @@ +// 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/module.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) +#define SC27XX_SLP_CTRL 0xdf0 +#define SC27XX_LDO_XTL_EN BIT(3) + +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_HOTPLUG_CPU + int cpu; + + for_each_online_cpu(cpu) { + if (cpu != smp_processor_id()) + remove_cpu(cpu); + } +#endif +} + +static struct syscore_ops poweroff_syscore_ops = { + .shutdown = sc27xx_poweroff_shutdown, +}; + +static void sc27xx_poweroff_do_poweroff(void) +{ + /* Disable the external subsys connection's power firstly */ + regmap_write(regmap, SC27XX_SLP_CTRL, SC27XX_LDO_XTL_EN); + + 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", + }, +}; +module_platform_driver(sc27xx_poweroff_driver); + +MODULE_DESCRIPTION("Power off driver for SC27XX PMIC Device"); +MODULE_AUTHOR("Baolin Wang <baolin.wang@unisoc.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/st-poweroff.c b/drivers/power/reset/st-poweroff.c new file mode 100644 index 000000000..5ccaacffa --- /dev/null +++ b/drivers/power/reset/st-poweroff.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 STMicroelectronics + * + * Power off Restart driver, used in STMicroelectronics devices. + * + * Author: Christophe Kerello <christophe.kerello@st.com> + */ + +#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..ed58bdf41 --- /dev/null +++ b/drivers/power/reset/syscon-poweroff.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Generic Syscon Poweroff Driver + * + * Copyright (c) 2015, National Instruments Corp. + * Author: Moritz Fischer <moritz.fischer@ettus.com> + */ + +#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) +{ + 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) { + dev_err(&pdev->dev, "pm_power_off already claimed for %ps", + pm_power_off); + 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..e0772c9f7 --- /dev/null +++ b/drivers/power/reset/syscon-reboot-mode.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd + */ + +#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..510e36338 --- /dev/null +++ b/drivers/power/reset/syscon-reboot.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Generic Syscon Reboot Driver + * + * Copyright (c) 2013, Applied Micro Circuits Corporation + * Author: Feng Kan <fkan@apm.com> + */ +#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 value; + 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_update_bits(ctx->map, ctx->offset, ctx->mask, ctx->value); + + 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 mask_err, value_err; + 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)) { + ctx->map = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(ctx->map)) + return PTR_ERR(ctx->map); + } + + if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset)) + return -EINVAL; + + value_err = of_property_read_u32(pdev->dev.of_node, "value", &ctx->value); + mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask); + if (value_err && mask_err) { + dev_err(dev, "unable to read 'value' and 'mask'"); + return -EINVAL; + } + + if (value_err) { + /* support old binding */ + ctx->value = ctx->mask; + ctx->mask = 0xFFFFFFFF; + } else if (mask_err) { + /* support value without mask*/ + ctx->mask = 0xFFFFFFFF; + } + + 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/tps65086-restart.c b/drivers/power/reset/tps65086-restart.c new file mode 100644 index 000000000..78b89f745 --- /dev/null +++ b/drivers/power/reset/tps65086-restart.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Emil Renner Berthing + */ + +#include <linux/mfd/tps65086.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> + +struct tps65086_restart { + struct notifier_block handler; + struct device *dev; +}; + +static int tps65086_restart_notify(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + struct tps65086_restart *tps65086_restart = + container_of(this, struct tps65086_restart, handler); + struct tps65086 *tps65086 = dev_get_drvdata(tps65086_restart->dev->parent); + int ret; + + ret = regmap_write(tps65086->regmap, TPS65086_FORCESHUTDN, 1); + if (ret) { + dev_err(tps65086_restart->dev, "%s: error writing to tps65086 pmic: %d\n", + __func__, ret); + return NOTIFY_DONE; + } + + /* give it a little time */ + mdelay(200); + + WARN_ON(1); + + return NOTIFY_DONE; +} + +static int tps65086_restart_probe(struct platform_device *pdev) +{ + struct tps65086_restart *tps65086_restart; + int ret; + + tps65086_restart = devm_kzalloc(&pdev->dev, sizeof(*tps65086_restart), GFP_KERNEL); + if (!tps65086_restart) + return -ENOMEM; + + platform_set_drvdata(pdev, tps65086_restart); + + tps65086_restart->handler.notifier_call = tps65086_restart_notify; + tps65086_restart->handler.priority = 192; + tps65086_restart->dev = &pdev->dev; + + ret = register_restart_handler(&tps65086_restart->handler); + if (ret) { + dev_err(&pdev->dev, "%s: cannot register restart handler: %d\n", + __func__, ret); + return -ENODEV; + } + + return 0; +} + +static int tps65086_restart_remove(struct platform_device *pdev) +{ + struct tps65086_restart *tps65086_restart = platform_get_drvdata(pdev); + int ret; + + ret = unregister_restart_handler(&tps65086_restart->handler); + if (ret) { + dev_err(&pdev->dev, "%s: cannot unregister restart handler: %d\n", + __func__, ret); + return -ENODEV; + } + + return 0; +} + +static const struct platform_device_id tps65086_restart_id_table[] = { + { "tps65086-reset", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, tps65086_restart_id_table); + +static struct platform_driver tps65086_restart_driver = { + .driver = { + .name = "tps65086-restart", + }, + .probe = tps65086_restart_probe, + .remove = tps65086_restart_remove, + .id_table = tps65086_restart_id_table, +}; +module_platform_driver(tps65086_restart_driver); + +MODULE_AUTHOR("Emil Renner Berthing <kernel@esmil.dk>"); +MODULE_DESCRIPTION("TPS65086 restart driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/vexpress-poweroff.c b/drivers/power/reset/vexpress-poweroff.c new file mode 100644 index 000000000..447ffdacd --- /dev/null +++ b/drivers/power/reset/vexpress-poweroff.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * 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, + }, +}; +builtin_platform_driver(vexpress_reset_driver); diff --git a/drivers/power/reset/xgene-reboot.c b/drivers/power/reset/xgene-reboot.c new file mode 100644 index 000000000..0b0d2fd2b --- /dev/null +++ b/drivers/power/reset/xgene-reboot.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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 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); |