diff options
Diffstat (limited to 'drivers/power/reset')
| -rw-r--r-- | drivers/power/reset/Kconfig | 82 | ||||
| -rw-r--r-- | drivers/power/reset/Makefile | 10 | ||||
| -rw-r--r-- | drivers/power/reset/as3722-poweroff.c | 96 | ||||
| -rw-r--r-- | drivers/power/reset/axxia-reset.c | 88 | ||||
| -rw-r--r-- | drivers/power/reset/gpio-poweroff.c | 126 | ||||
| -rw-r--r-- | drivers/power/reset/keystone-reset.c | 166 | ||||
| -rw-r--r-- | drivers/power/reset/msm-poweroff.c | 73 | ||||
| -rw-r--r-- | drivers/power/reset/qnap-poweroff.c | 141 | ||||
| -rw-r--r-- | drivers/power/reset/restart-poweroff.c | 66 | ||||
| -rw-r--r-- | drivers/power/reset/sun6i-reboot.c | 85 | ||||
| -rw-r--r-- | drivers/power/reset/vexpress-poweroff.c | 147 | ||||
| -rw-r--r-- | drivers/power/reset/xgene-reboot.c | 103 | 
12 files changed, 1183 insertions, 0 deletions
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig new file mode 100644 index 00000000000..bdcf5173e37 --- /dev/null +++ b/drivers/power/reset/Kconfig @@ -0,0 +1,82 @@ +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 + +config POWER_RESET_AS3722 +	bool "ams AS3722 power-off driver" +	depends on MFD_AS3722 && POWER_RESET +	help +	  This driver supports turning off board via a ams AS3722 power-off. + +config POWER_RESET_AXXIA +	bool "LSI Axxia reset driver" +	depends on POWER_RESET && ARCH_AXXIA +	help +	  This driver supports restart for Axxia SoC. + +	  Say Y if you have an Axxia family SoC. + +config POWER_RESET_GPIO +	bool "GPIO power-off driver" +	depends on OF_GPIO && POWER_RESET +	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_MSM +	bool "Qualcomm MSM power-off driver" +	depends on POWER_RESET && ARCH_QCOM +	help +	  Power off and restart support for Qualcomm boards. + +config POWER_RESET_QNAP +	bool "QNAP power-off driver" +	depends on OF_GPIO && POWER_RESET && PLAT_ORION +	help +	  This driver supports turning off QNAP NAS devices by sending +	  commands to the microcontroller which controls the main power. + +	  Say Y if you have a QNAP NAS. + +config POWER_RESET_RESTART +	bool "Restart power-off driver" +	depends on ARM +	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_SUN6I +	bool "Allwinner A31 SoC reset driver" +	depends on ARCH_SUNXI +	depends on POWER_RESET +	help +	  Reboot support for the Allwinner A31 SoCs. + +config POWER_RESET_VEXPRESS +	bool "ARM Versatile Express power-off and reset driver" +	depends on ARM || ARM64 +	depends on POWER_RESET && VEXPRESS_CONFIG +	help +	  Power off and reset support for the ARM Ltd. Versatile +	  Express boards. + +config POWER_RESET_XGENE +	bool "APM SoC X-Gene reset driver" +	depends on ARM64 +	depends on POWER_RESET +	help +	  Reboot support for the APM SoC X-Gene Eval boards. + +config POWER_RESET_KEYSTONE +	bool "Keystone reset driver" +	depends on ARCH_KEYSTONE +	select MFD_SYSCON +	help +	  Reboot support for the KEYSTONE SoCs. + diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile new file mode 100644 index 00000000000..dde2e8bbac5 --- /dev/null +++ b/drivers/power/reset/Makefile @@ -0,0 +1,10 @@ +obj-$(CONFIG_POWER_RESET_AS3722) += as3722-poweroff.o +obj-$(CONFIG_POWER_RESET_AXXIA) += axxia-reset.o +obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o +obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o +obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o +obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o +obj-$(CONFIG_POWER_RESET_SUN6I) += sun6i-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 diff --git a/drivers/power/reset/as3722-poweroff.c b/drivers/power/reset/as3722-poweroff.c new file mode 100644 index 00000000000..684971199bd --- /dev/null +++ b/drivers/power/reset/as3722-poweroff.c @@ -0,0 +1,96 @@ +/* + * Power off driver for ams AS3722 device. + * + * Copyright (c) 2013, NVIDIA CORPORATION.  All rights reserved. + * + * Author: Laxman Dewangan <ldewangan@nvidia.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + */ + +#include <linux/mfd/as3722.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +struct as3722_poweroff { +	struct device *dev; +	struct as3722 *as3722; +}; + +static struct as3722_poweroff *as3722_pm_poweroff; + +static void as3722_pm_power_off(void) +{ +	int ret; + +	if (!as3722_pm_poweroff) { +		pr_err("AS3722 poweroff is not initialised\n"); +		return; +	} + +	ret = as3722_update_bits(as3722_pm_poweroff->as3722, +		AS3722_RESET_CONTROL_REG, AS3722_POWER_OFF, AS3722_POWER_OFF); +	if (ret < 0) +		dev_err(as3722_pm_poweroff->dev, +			"RESET_CONTROL_REG update failed, %d\n", ret); +} + +static int as3722_poweroff_probe(struct platform_device *pdev) +{ +	struct as3722_poweroff *as3722_poweroff; +	struct device_node *np = pdev->dev.parent->of_node; + +	if (!np) +		return -EINVAL; + +	if (!of_property_read_bool(np, "ams,system-power-controller")) +		return 0; + +	as3722_poweroff = devm_kzalloc(&pdev->dev, sizeof(*as3722_poweroff), +				GFP_KERNEL); +	if (!as3722_poweroff) +		return -ENOMEM; + +	as3722_poweroff->as3722 = dev_get_drvdata(pdev->dev.parent); +	as3722_poweroff->dev = &pdev->dev; +	as3722_pm_poweroff = as3722_poweroff; +	if (!pm_power_off) +		pm_power_off = as3722_pm_power_off; + +	return 0; +} + +static int as3722_poweroff_remove(struct platform_device *pdev) +{ +	if (pm_power_off == as3722_pm_power_off) +		pm_power_off = NULL; +	as3722_pm_poweroff = NULL; + +	return 0; +} + +static struct platform_driver as3722_poweroff_driver = { +	.driver = { +		.name = "as3722-power-off", +		.owner = THIS_MODULE, +	}, +	.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/axxia-reset.c b/drivers/power/reset/axxia-reset.c new file mode 100644 index 00000000000..3b1f8d60178 --- /dev/null +++ b/drivers/power/reset/axxia-reset.c @@ -0,0 +1,88 @@ +/* + * Reset driver for Axxia devices + * + * Copyright (C) 2014 LSI + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ +#include <linux/init.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/regmap.h> + +#include <asm/system_misc.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 void do_axxia_restart(enum reboot_mode reboot_mode, const char *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); +} + +static int axxia_reset_probe(struct platform_device *pdev) +{ +	struct device *dev = &pdev->dev; + +	syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon"); +	if (IS_ERR(syscon)) { +		pr_err("%s: syscon lookup failed\n", dev->of_node->name); +		return PTR_ERR(syscon); +	} + +	arm_pm_restart = do_axxia_restart; + +	return 0; +} + +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/gpio-poweroff.c b/drivers/power/reset/gpio-poweroff.c new file mode 100644 index 00000000000..e290d48ddd9 --- /dev/null +++ b/drivers/power/reset/gpio-poweroff.c @@ -0,0 +1,126 @@ +/* + * Toggles a GPIO pin to power down a device + * + * Jamie Lentin <jm@lentin.co.uk> + * Andrew Lunn <andrew@lunn.ch> + * + * Copyright (C) 2012 Jamie Lentin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> +#include <linux/module.h> + +/* + * Hold configuration here, cannot be more than one instance of the driver + * since pm_power_off itself is global. + */ +static int gpio_num = -1; +static int gpio_active_low; + +static void gpio_poweroff_do_poweroff(void) +{ +	BUG_ON(!gpio_is_valid(gpio_num)); + +	/* drive it active, also inactive->active edge */ +	gpio_direction_output(gpio_num, !gpio_active_low); +	mdelay(100); +	/* drive inactive, also active->inactive edge */ +	gpio_set_value(gpio_num, gpio_active_low); +	mdelay(100); + +	/* drive it active, also inactive->active edge */ +	gpio_set_value(gpio_num, !gpio_active_low); + +	/* give it some time */ +	mdelay(3000); + +	WARN_ON(1); +} + +static int gpio_poweroff_probe(struct platform_device *pdev) +{ +	enum of_gpio_flags flags; +	bool input = false; +	int ret; + +	/* If a pm_power_off function has already been added, leave it alone */ +	if (pm_power_off != NULL) { +		pr_err("%s: pm_power_off function already registered", +		       __func__); +		return -EBUSY; +	} + +	gpio_num = of_get_gpio_flags(pdev->dev.of_node, 0, &flags); +	if (!gpio_is_valid(gpio_num)) +		return gpio_num; + +	gpio_active_low = flags & OF_GPIO_ACTIVE_LOW; + +	input = of_property_read_bool(pdev->dev.of_node, "input"); + +	ret = gpio_request(gpio_num, "poweroff-gpio"); +	if (ret) { +		pr_err("%s: Could not get GPIO %d", __func__, gpio_num); +		return ret; +	} +	if (input) { +		if (gpio_direction_input(gpio_num)) { +			pr_err("Could not set direction of GPIO %d to input", +			       gpio_num); +			goto err; +		} +	} else { +		if (gpio_direction_output(gpio_num, gpio_active_low)) { +			pr_err("Could not set direction of GPIO %d", gpio_num); +			goto err; +		} +	} + +	pm_power_off = &gpio_poweroff_do_poweroff; +	return 0; + +err: +	gpio_free(gpio_num); +	return -ENODEV; +} + +static int gpio_poweroff_remove(struct platform_device *pdev) +{ +	gpio_free(gpio_num); +	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", }, +	{}, +}; + +static struct platform_driver gpio_poweroff_driver = { +	.probe = gpio_poweroff_probe, +	.remove = gpio_poweroff_remove, +	.driver = { +		.name = "poweroff-gpio", +		.owner = THIS_MODULE, +		.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/keystone-reset.c b/drivers/power/reset/keystone-reset.c new file mode 100644 index 00000000000..408a18fd91c --- /dev/null +++ b/drivers/power/reset/keystone-reset.c @@ -0,0 +1,166 @@ +/* + * TI keystone reboot driver + * + * Copyright (C) 2014 Texas Instruments Incorporated. http://www.ti.com/ + * + * Author: Ivan Khoronzhuk <ivan.khoronzhuk@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <linux/regmap.h> +#include <asm/system_misc.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 void rsctrl_restart(enum reboot_mode mode, const char *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); +} + +static struct of_device_id rsctrl_of_match[] = { +	{.compatible = "ti,keystone-reset", }, +	{}, +}; + +static int rsctrl_probe(struct platform_device *pdev) +{ +	int i; +	int ret; +	u32 val; +	unsigned int rg; +	u32 rsmux_offset; +	struct regmap *devctrl_regs; +	struct device *dev = &pdev->dev; +	struct device_node *np = dev->of_node; + +	if (!np) +		return -ENODEV; + +	/* get regmaps */ +	pllctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-pll"); +	if (IS_ERR(pllctrl_regs)) +		return PTR_ERR(pllctrl_regs); + +	devctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-dev"); +	if (IS_ERR(devctrl_regs)) +		return PTR_ERR(devctrl_regs); + +	ret = of_property_read_u32_index(np, "ti,syscon-pll", 1, &rspll_offset); +	if (ret) { +		dev_err(dev, "couldn't read the reset pll offset!\n"); +		return -EINVAL; +	} + +	ret = of_property_read_u32_index(np, "ti,syscon-dev", 1, &rsmux_offset); +	if (ret) { +		dev_err(dev, "couldn't read the rsmux offset!\n"); +		return -EINVAL; +	} + +	/* set soft/hard reset */ +	val = of_property_read_bool(np, "ti,soft-reset"); +	val = val ? RSCFG_RSTYPE_SOFT : RSCFG_RSTYPE_HARD; + +	ret = rsctrl_enable_rspll_write(); +	if (ret) +		return ret; + +	ret = regmap_write(pllctrl_regs, rspll_offset + RSCFG_RG, val); +	if (ret) +		return ret; + +	arm_pm_restart = rsctrl_restart; + +	/* 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; +	} + +	return 0; +} + +static struct platform_driver rsctrl_driver = { +	.probe = rsctrl_probe, +	.driver = { +		.owner = THIS_MODULE, +		.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/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c new file mode 100644 index 00000000000..774f9a3b310 --- /dev/null +++ b/drivers/power/reset/msm-poweroff.c @@ -0,0 +1,73 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/reboot.h> + +#include <asm/system_misc.h> + +static void __iomem *msm_ps_hold; + +static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd) +{ +	writel(0, msm_ps_hold); +	mdelay(10000); +} + +static void do_msm_poweroff(void) +{ +	/* TODO: Add poweroff capability */ +	do_msm_restart(REBOOT_HARD, 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); + +	pm_power_off = do_msm_poweroff; +	arm_pm_restart = do_msm_restart; +	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/qnap-poweroff.c b/drivers/power/reset/qnap-poweroff.c new file mode 100644 index 00000000000..a75db7f8a92 --- /dev/null +++ b/drivers/power/reset/qnap-poweroff.c @@ -0,0 +1,141 @@ +/* + * QNAP Turbo NAS Board power off. Can also be used on Synology devices. + * + * Copyright (C) 2012 Andrew Lunn <andrew@lunn.ch> + * + * Based on the code from: + * + * Copyright (C) 2009  Martin Michlmayr <tbm@cyrius.com> + * Copyright (C) 2008  Byron Bradley <byron.bbradley@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/serial_reg.h> +#include <linux/kallsyms.h> +#include <linux/of.h> +#include <linux/io.h> +#include <linux/clk.h> + +#define UART1_REG(x)	(base + ((UART_##x) << 2)) + +struct power_off_cfg { +	u32 baud; +	char cmd; +}; + +static const struct power_off_cfg qnap_power_off_cfg = { +	.baud = 19200, +	.cmd = 'A', +}; + +static const struct power_off_cfg synology_power_off_cfg = { +	.baud = 9600, +	.cmd = '1', +}; + +static const struct of_device_id qnap_power_off_of_match_table[] = { +	{ .compatible = "qnap,power-off", +	  .data = &qnap_power_off_cfg, +	}, +	{ .compatible = "synology,power-off", +	  .data = &synology_power_off_cfg, +	}, +	{} +}; +MODULE_DEVICE_TABLE(of, qnap_power_off_of_match_table); + +static void __iomem *base; +static unsigned long tclk; +static const struct power_off_cfg *cfg; + +static void qnap_power_off(void) +{ +	const unsigned divisor = ((tclk + (8 * cfg->baud)) / (16 * cfg->baud)); + +	pr_err("%s: triggering power-off...\n", __func__); + +	/* hijack UART1 and reset into sane state */ +	writel(0x83, UART1_REG(LCR)); +	writel(divisor & 0xff, UART1_REG(DLL)); +	writel((divisor >> 8) & 0xff, UART1_REG(DLM)); +	writel(0x03, UART1_REG(LCR)); +	writel(0x00, UART1_REG(IER)); +	writel(0x00, UART1_REG(FCR)); +	writel(0x00, UART1_REG(MCR)); + +	/* send the power-off command to PIC */ +	writel(cfg->cmd, UART1_REG(TX)); +} + +static int qnap_power_off_probe(struct platform_device *pdev) +{ +	struct device_node *np = pdev->dev.of_node; +	struct resource *res; +	struct clk *clk; +	char symname[KSYM_NAME_LEN]; + +	const struct of_device_id *match = +		of_match_node(qnap_power_off_of_match_table, np); +	cfg = match->data; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) { +		dev_err(&pdev->dev, "Missing resource"); +		return -EINVAL; +	} + +	base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); +	if (!base) { +		dev_err(&pdev->dev, "Unable to map resource"); +		return -EINVAL; +	} + +	/* We need to know tclk in order to calculate the UART divisor */ +	clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(clk)) { +		dev_err(&pdev->dev, "Clk missing"); +		return PTR_ERR(clk); +	} + +	tclk = clk_get_rate(clk); + +	/* Check that nothing else has already setup a handler */ +	if (pm_power_off) { +		lookup_symbol_name((ulong)pm_power_off, symname); +		dev_err(&pdev->dev, +			"pm_power_off already claimed %p %s", +			pm_power_off, symname); +		return -EBUSY; +	} +	pm_power_off = qnap_power_off; + +	return 0; +} + +static int qnap_power_off_remove(struct platform_device *pdev) +{ +	pm_power_off = NULL; +	return 0; +} + +static struct platform_driver qnap_power_off_driver = { +	.probe	= qnap_power_off_probe, +	.remove	= qnap_power_off_remove, +	.driver	= { +		.owner	= THIS_MODULE, +		.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/restart-poweroff.c b/drivers/power/reset/restart-poweroff.c new file mode 100644 index 00000000000..5758033e0c1 --- /dev/null +++ b/drivers/power/reset/restart-poweroff.c @@ -0,0 +1,66 @@ +/* + * Power off by restarting and let u-boot keep hold of the machine + * until the user presses a button for example. + * + * Andrew Lunn <andrew@lunn.ch> + * + * Copyright (C) 2012 Andrew Lunn + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <asm/system_misc.h> + +static void restart_poweroff_do_poweroff(void) +{ +	arm_pm_restart(REBOOT_HARD, NULL); +} + +static int restart_poweroff_probe(struct platform_device *pdev) +{ +	/* If a pm_power_off function has already been added, leave it alone */ +	if (pm_power_off != NULL) { +		dev_err(&pdev->dev, +			"pm_power_off function already registered"); +		return -EBUSY; +	} + +	pm_power_off = &restart_poweroff_do_poweroff; +	return 0; +} + +static int restart_poweroff_remove(struct platform_device *pdev) +{ +	if (pm_power_off == &restart_poweroff_do_poweroff) +		pm_power_off = NULL; + +	return 0; +} + +static const struct of_device_id of_restart_poweroff_match[] = { +	{ .compatible = "restart-poweroff", }, +	{}, +}; + +static struct platform_driver restart_poweroff_driver = { +	.probe = restart_poweroff_probe, +	.remove = restart_poweroff_remove, +	.driver = { +		.name = "poweroff-restart", +		.owner = THIS_MODULE, +		.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("GPLv2"); +MODULE_ALIAS("platform:poweroff-restart"); diff --git a/drivers/power/reset/sun6i-reboot.c b/drivers/power/reset/sun6i-reboot.c new file mode 100644 index 00000000000..af2cd7ff2fe --- /dev/null +++ b/drivers/power/reset/sun6i-reboot.c @@ -0,0 +1,85 @@ +/* + * Allwinner A31 SoCs reset code + * + * Copyright (C) 2012-2014 Maxime Ripard + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * 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/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> + +#include <asm/system_misc.h> + +#define SUN6I_WATCHDOG1_IRQ_REG		0x00 +#define SUN6I_WATCHDOG1_CTRL_REG	0x10 +#define SUN6I_WATCHDOG1_CTRL_RESTART		BIT(0) +#define SUN6I_WATCHDOG1_CONFIG_REG	0x14 +#define SUN6I_WATCHDOG1_CONFIG_RESTART		BIT(0) +#define SUN6I_WATCHDOG1_CONFIG_IRQ		BIT(1) +#define SUN6I_WATCHDOG1_MODE_REG	0x18 +#define SUN6I_WATCHDOG1_MODE_ENABLE		BIT(0) + +static void __iomem *wdt_base; + +static void sun6i_wdt_restart(enum reboot_mode mode, const char *cmd) +{ +	if (!wdt_base) +		return; + +	/* Disable interrupts */ +	writel(0, wdt_base + SUN6I_WATCHDOG1_IRQ_REG); + +	/* We want to disable the IRQ and just reset the whole system */ +	writel(SUN6I_WATCHDOG1_CONFIG_RESTART, +		wdt_base + SUN6I_WATCHDOG1_CONFIG_REG); + +	/* Enable timer. The default and lowest interval value is 0.5s */ +	writel(SUN6I_WATCHDOG1_MODE_ENABLE, +		wdt_base + SUN6I_WATCHDOG1_MODE_REG); + +	/* Restart the watchdog. */ +	writel(SUN6I_WATCHDOG1_CTRL_RESTART, +		wdt_base + SUN6I_WATCHDOG1_CTRL_REG); + +	while (1) { +		mdelay(5); +		writel(SUN6I_WATCHDOG1_MODE_ENABLE, +			wdt_base + SUN6I_WATCHDOG1_MODE_REG); +	} +} + +static int sun6i_reboot_probe(struct platform_device *pdev) +{ +	wdt_base = of_iomap(pdev->dev.of_node, 0); +	if (!wdt_base) { +		WARN(1, "failed to map watchdog base address"); +		return -ENODEV; +	} + +	arm_pm_restart = sun6i_wdt_restart; + +	return 0; +} + +static struct of_device_id sun6i_reboot_of_match[] = { +	{ .compatible = "allwinner,sun6i-a31-wdt" }, +	{} +}; + +static struct platform_driver sun6i_reboot_driver = { +	.probe = sun6i_reboot_probe, +	.driver = { +		.name = "sun6i-reboot", +		.of_match_table = sun6i_reboot_of_match, +	}, +}; +module_platform_driver(sun6i_reboot_driver); diff --git a/drivers/power/reset/vexpress-poweroff.c b/drivers/power/reset/vexpress-poweroff.c new file mode 100644 index 00000000000..4dc102e2b23 --- /dev/null +++ b/drivers/power/reset/vexpress-poweroff.c @@ -0,0 +1,147 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * Copyright (C) 2012 ARM Limited + */ + +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/stat.h> +#include <linux/vexpress.h> + +#include <asm/system_misc.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 void vexpress_power_off(void) +{ +	vexpress_reset_do(vexpress_power_off_device, "power off"); +} + +static struct device *vexpress_restart_device; + +static void vexpress_restart(enum reboot_mode reboot_mode, const char *cmd) +{ +	vexpress_reset_do(vexpress_restart_device, "restart"); +} + +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; +} + +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 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_reset_probe(struct platform_device *pdev) +{ +	enum vexpress_reset_func func; +	const struct of_device_id *match = +			of_match_device(vexpress_reset_of_match, &pdev->dev); +	struct regmap *regmap; + +	if (match) +		func = (enum vexpress_reset_func)match->data; +	else +		func = pdev->id_entry->driver_data; + +	regmap = devm_regmap_init_vexpress_config(&pdev->dev); +	if (IS_ERR(regmap)) +		return PTR_ERR(regmap); +	dev_set_drvdata(&pdev->dev, regmap); + +	switch (func) { +	case FUNC_SHUTDOWN: +		vexpress_power_off_device = &pdev->dev; +		pm_power_off = vexpress_power_off; +		break; +	case FUNC_RESET: +		if (!vexpress_restart_device) +			vexpress_restart_device = &pdev->dev; +		arm_pm_restart = vexpress_restart; +		device_create_file(&pdev->dev, &dev_attr_active); +		break; +	case FUNC_REBOOT: +		vexpress_restart_device = &pdev->dev; +		arm_pm_restart = vexpress_restart; +		device_create_file(&pdev->dev, &dev_attr_active); +		break; +	}; + +	return 0; +} + +static const struct platform_device_id vexpress_reset_id_table[] = { +	{ .name = "vexpress-reset", .driver_data = FUNC_RESET, }, +	{ .name = "vexpress-shutdown", .driver_data = FUNC_SHUTDOWN, }, +	{ .name = "vexpress-reboot", .driver_data = FUNC_REBOOT, }, +	{} +}; + +static struct platform_driver vexpress_reset_driver = { +	.probe = vexpress_reset_probe, +	.driver = { +		.name = "vexpress-reset", +		.of_match_table = vexpress_reset_of_match, +	}, +	.id_table = vexpress_reset_id_table, +}; + +static int __init vexpress_reset_init(void) +{ +	return platform_driver_register(&vexpress_reset_driver); +} +device_initcall(vexpress_reset_init); diff --git a/drivers/power/reset/xgene-reboot.c b/drivers/power/reset/xgene-reboot.c new file mode 100644 index 00000000000..ecd55f81b9d --- /dev/null +++ b/drivers/power/reset/xgene-reboot.c @@ -0,0 +1,103 @@ +/* + * AppliedMicro X-Gene SoC Reboot Driver + * + * Copyright (c) 2013, Applied Micro Circuits Corporation + * Author: Feng Kan <fkan@apm.com> + * Author: Loc Ho <lho@apm.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * This driver provides system reboot functionality for APM X-Gene SoC. + * For system shutdown, this is board specify. If a board designer + * implements GPIO shutdown, use the gpio-poweroff.c driver. + */ +#include <linux/io.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/stat.h> +#include <linux/slab.h> +#include <asm/system_misc.h> + +struct xgene_reboot_context { +	struct platform_device *pdev; +	void *csr; +	u32 mask; +}; + +static struct xgene_reboot_context *xgene_restart_ctx; + +static void xgene_restart(char str, const char *cmd) +{ +	struct xgene_reboot_context *ctx = xgene_restart_ctx; +	unsigned long timeout; + +	/* Issue the reboot */ +	if (ctx) +		writel(ctx->mask, ctx->csr); + +	timeout = jiffies + HZ; +	while (time_before(jiffies, timeout)) +		cpu_relax(); + +	dev_emerg(&ctx->pdev->dev, "Unable to restart system\n"); +} + +static int xgene_reboot_probe(struct platform_device *pdev) +{ +	struct xgene_reboot_context *ctx; + +	ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); +	if (!ctx) { +		dev_err(&pdev->dev, "out of memory for context\n"); +		return -ENODEV; +	} + +	ctx->csr = of_iomap(pdev->dev.of_node, 0); +	if (!ctx->csr) { +		devm_kfree(&pdev->dev, ctx); +		dev_err(&pdev->dev, "can not map resource\n"); +		return -ENODEV; +	} + +	if (of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask)) +		ctx->mask = 0xFFFFFFFF; + +	ctx->pdev = pdev; +	arm_pm_restart = xgene_restart; +	xgene_restart_ctx = ctx; + +	return 0; +} + +static 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);  | 
