diff options
Diffstat (limited to 'drivers/watchdog')
41 files changed, 2660 insertions, 733 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1c427beff..80825a7e8 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -46,6 +46,13 @@ config WATCHDOG_NOWAYOUT get killed. If you say Y here, the watchdog cannot be stopped once it has been started. +config WATCHDOG_SYSFS + bool "Read different watchdog information through sysfs" + default n + help + Say Y here if you want to enable watchdog device status read through + sysfs attributes. + # # General Watchdog drivers # @@ -135,6 +142,17 @@ config MENF21BMC_WATCHDOG This driver can also be built as a module. If so the module will be called menf21bmc_wdt. +config TANGOX_WATCHDOG + tristate "Sigma Designs SMP86xx/SMP87xx watchdog" + select WATCHDOG_CORE + depends on ARCH_TANGO || COMPILE_TEST + depends on HAS_IOMEM + help + Support for the watchdog in Sigma Designs SMP86xx (tango3) + and SMP87xx (tango4) family chips. + + This driver can be built as a module. The module name is tangox_wdt. + config WM831X_WATCHDOG tristate "WM831x watchdog" depends on MFD_WM831X @@ -161,6 +179,17 @@ config XILINX_WATCHDOG To compile this driver as a module, choose M here: the module will be called of_xilinx_wdt. +config ZIIRAVE_WATCHDOG + tristate "Zodiac RAVE Watchdog Timer" + depends on I2C + select WATCHDOG_CORE + help + Watchdog driver for the Zodiac Aerospace RAVE Switch Watchdog + Processor. + + To compile this driver as a module, choose M here: the + module will be called ziirave_wdt. + # ALPHA Architecture # ARM Architecture @@ -173,6 +202,16 @@ config ARM_SP805_WATCHDOG ARM Primecell SP805 Watchdog timer. This will reboot your system when the timeout is reached. +config ASM9260_WATCHDOG + tristate "Alphascale ASM9260 watchdog" + depends on MACH_ASM9260 + depends on OF + select WATCHDOG_CORE + select RESET_CONTROLLER + help + Watchdog timer embedded into Alphascale asm9260 chips. This will reboot your + system when the timeout is reached. + config AT91RM9200_WATCHDOG tristate "AT91RM9200 watchdog" depends on SOC_AT91RM9200 && MFD_SYSCON @@ -426,6 +465,16 @@ config NUC900_WATCHDOG To compile this driver as a module, choose M here: the module will be called nuc900_wdt. +config TS4800_WATCHDOG + tristate "TS-4800 Watchdog" + depends on HAS_IOMEM && OF + select WATCHDOG_CORE + select MFD_SYSCON + help + Technologic Systems TS-4800 has watchdog timer implemented in + an external FPGA. Say Y here if you want to support for the + watchdog timer on TS-4800 board. + config TS72XX_WATCHDOG tristate "TS-72XX SBC Watchdog" depends on MACH_TS72XX @@ -570,6 +619,7 @@ config DIGICOLOR_WATCHDOG config LPC18XX_WATCHDOG tristate "LPC18xx/43xx Watchdog" depends on ARCH_LPC18XX || COMPILE_TEST + depends on HAS_IOMEM select WATCHDOG_CORE help Say Y here if to include support for the watchdog timer @@ -578,6 +628,16 @@ config LPC18XX_WATCHDOG To compile this driver as a module, choose M here: the module will be called lpc18xx_wdt. +config ATLAS7_WATCHDOG + tristate "CSRatlas7 watchdog" + depends on ARCH_ATLAS7 + help + Say Y here to include Watchdog timer support for the watchdog + existing on the CSRatlas7 series platforms. + + To compile this driver as a module, choose M here: the + module will be called atlas7_wdt. + # AVR32 Architecture config AT32AP700X_WDT @@ -1316,6 +1376,7 @@ config BCM_KONA_WDT_DEBUG config BCM7038_WDT tristate "BCM7038 Watchdog" select WATCHDOG_CORE + depends on HAS_IOMEM help Watchdog driver for the built-in hardware in Broadcom 7038 SoCs. @@ -1325,6 +1386,7 @@ config IMGPDC_WDT tristate "Imagination Technologies PDC Watchdog Timer" depends on HAS_IOMEM depends on METAG || MIPS || COMPILE_TEST + select WATCHDOG_CORE help Driver for Imagination Technologies PowerDown Controller Watchdog Timer. @@ -1345,6 +1407,13 @@ config RALINK_WDT help Hardware driver for the Ralink SoC Watchdog Timer. +config MT7621_WDT + tristate "Mediatek SoC watchdog" + select WATCHDOG_CORE + depends on SOC_MT7620 || SOC_MT7621 + help + Hardware driver for the Mediatek/Ralink MT7621/8 SoC Watchdog Timer. + # PARISC Architecture # POWERPC Architecture @@ -1500,6 +1569,17 @@ config WATCHDOG_RIO machines. The watchdog timeout period is normally one minute but can be changed with a boot-time parameter. +config WATCHDOG_SUN4V + tristate "Sun4v Watchdog support" + select WATCHDOG_CORE + depends on SPARC64 + help + Say Y here to support the hypervisor watchdog capability embedded + in the SPARC sun4v architecture. + + To compile this driver as a module, choose M here. The module will + be called sun4v_wdt. + # XTENSA Architecture # Xen Architecture diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 53d4827dd..f6a6a387c 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o # ARM Architecture obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o +obj-$(CONFIG_ASM9260_WATCHDOG) += asm9260_wdt.o obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o obj-$(CONFIG_CADENCE_WATCHDOG) += cadence_wdt.o @@ -53,6 +54,7 @@ obj-$(CONFIG_RN5T618_WATCHDOG) += rn5t618_wdt.o obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o obj-$(CONFIG_STMP3XXX_RTC_WATCHDOG) += stmp3xxx_rtc_wdt.o obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o +obj-$(CONFIG_TS4800_WATCHDOG) += ts4800_wdt.o obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o obj-$(CONFIG_UX500_WATCHDOG) += ux500_wdt.o @@ -69,6 +71,7 @@ obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o obj-$(CONFIG_DIGICOLOR_WATCHDOG) += digicolor_wdt.o obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o obj-$(CONFIG_BCM7038_WDT) += bcm7038_wdt.o +obj-$(CONFIG_ATLAS7_WATCHDOG) += atlas7_wdt.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o @@ -149,6 +152,7 @@ octeon-wdt-y := octeon-wdt-main.o octeon-wdt-nmi.o obj-$(CONFIG_LANTIQ_WDT) += lantiq_wdt.o obj-$(CONFIG_RALINK_WDT) += rt2880_wdt.o obj-$(CONFIG_IMGPDC_WDT) += imgpdc_wdt.o +obj-$(CONFIG_MT7621_WDT) += mt7621_wdt.o # PARISC Architecture @@ -175,6 +179,7 @@ obj-$(CONFIG_SH_WDT) += shwdt.o obj-$(CONFIG_WATCHDOG_RIO) += riowd.o obj-$(CONFIG_WATCHDOG_CP1XXX) += cpwd.o +obj-$(CONFIG_WATCHDOG_SUN4V) += sun4v_wdt.o # XTENSA Architecture @@ -187,8 +192,10 @@ obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o obj-$(CONFIG_GPIO_WATCHDOG) += gpio_wdt.o +obj-$(CONFIG_TANGOX_WATCHDOG) += tangox_wdt.o obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o +obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o diff --git a/drivers/watchdog/asm9260_wdt.c b/drivers/watchdog/asm9260_wdt.c new file mode 100644 index 000000000..c9686b2fd --- /dev/null +++ b/drivers/watchdog/asm9260_wdt.c @@ -0,0 +1,403 @@ +/* + * Watchdog driver for Alphascale ASM9260. + * + * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de> + * + * Licensed under GPLv2 or later. + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/reset.h> +#include <linux/watchdog.h> + +#define CLOCK_FREQ 1000000 + +/* Watchdog Mode register */ +#define HW_WDMOD 0x00 +/* Wake interrupt. Set by HW, can't be cleared. */ +#define BM_MOD_WDINT BIT(3) +/* This bit set if timeout reached. Cleared by SW. */ +#define BM_MOD_WDTOF BIT(2) +/* HW Reset on timeout */ +#define BM_MOD_WDRESET BIT(1) +/* WD enable */ +#define BM_MOD_WDEN BIT(0) + +/* + * Watchdog Timer Constant register + * Minimal value is 0xff, the meaning of this value + * depends on used clock: T = WDCLK * (0xff + 1) * 4 + */ +#define HW_WDTC 0x04 +#define BM_WDTC_MAX(freq) (0x7fffffff / (freq)) + +/* Watchdog Feed register */ +#define HW_WDFEED 0x08 + +/* Watchdog Timer Value register */ +#define HW_WDTV 0x0c + +#define ASM9260_WDT_DEFAULT_TIMEOUT 30 + +enum asm9260_wdt_mode { + HW_RESET, + SW_RESET, + DEBUG, +}; + +struct asm9260_wdt_priv { + struct device *dev; + struct watchdog_device wdd; + struct clk *clk; + struct clk *clk_ahb; + struct reset_control *rst; + struct notifier_block restart_handler; + + void __iomem *iobase; + int irq; + unsigned long wdt_freq; + enum asm9260_wdt_mode mode; +}; + +static int asm9260_wdt_feed(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + + iowrite32(0xaa, priv->iobase + HW_WDFEED); + iowrite32(0x55, priv->iobase + HW_WDFEED); + + return 0; +} + +static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + u32 counter; + + counter = ioread32(priv->iobase + HW_WDTV); + + return DIV_ROUND_CLOSEST(counter, priv->wdt_freq); +} + +static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + u32 counter; + + counter = wdd->timeout * priv->wdt_freq; + + iowrite32(counter, priv->iobase + HW_WDTC); + + return 0; +} + +static int asm9260_wdt_enable(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + u32 mode = 0; + + if (priv->mode == HW_RESET) + mode = BM_MOD_WDRESET; + + iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD); + + asm9260_wdt_updatetimeout(wdd); + + asm9260_wdt_feed(wdd); + + return 0; +} + +static int asm9260_wdt_disable(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + + /* The only way to disable WD is to reset it. */ + reset_control_assert(priv->rst); + reset_control_deassert(priv->rst); + + return 0; +} + +static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to) +{ + wdd->timeout = to; + asm9260_wdt_updatetimeout(wdd); + + return 0; +} + +static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv) +{ + /* init WD if it was not started */ + + iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD); + + iowrite32(0xff, priv->iobase + HW_WDTC); + /* first pass correct sequence */ + asm9260_wdt_feed(&priv->wdd); + /* + * Then write wrong pattern to the feed to trigger reset + * ASAP. + */ + iowrite32(0xff, priv->iobase + HW_WDFEED); + + mdelay(1000); +} + +static irqreturn_t asm9260_wdt_irq(int irq, void *devid) +{ + struct asm9260_wdt_priv *priv = devid; + u32 stat; + + stat = ioread32(priv->iobase + HW_WDMOD); + if (!(stat & BM_MOD_WDINT)) + return IRQ_NONE; + + if (priv->mode == DEBUG) { + dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n"); + } else { + dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n"); + asm9260_wdt_sys_reset(priv); + } + + return IRQ_HANDLED; +} + +static int asm9260_restart_handler(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + struct asm9260_wdt_priv *priv = + container_of(this, struct asm9260_wdt_priv, restart_handler); + + asm9260_wdt_sys_reset(priv); + + return NOTIFY_DONE; +} + +static const struct watchdog_info asm9260_wdt_ident = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING + | WDIOF_MAGICCLOSE, + .identity = "Alphascale asm9260 Watchdog", +}; + +static struct watchdog_ops asm9260_wdt_ops = { + .owner = THIS_MODULE, + .start = asm9260_wdt_enable, + .stop = asm9260_wdt_disable, + .get_timeleft = asm9260_wdt_gettimeleft, + .ping = asm9260_wdt_feed, + .set_timeout = asm9260_wdt_settimeout, +}; + +static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv) +{ + int err; + unsigned long clk; + + priv->clk = devm_clk_get(priv->dev, "mod"); + if (IS_ERR(priv->clk)) { + dev_err(priv->dev, "Failed to get \"mod\" clk\n"); + return PTR_ERR(priv->clk); + } + + /* configure AHB clock */ + priv->clk_ahb = devm_clk_get(priv->dev, "ahb"); + if (IS_ERR(priv->clk_ahb)) { + dev_err(priv->dev, "Failed to get \"ahb\" clk\n"); + return PTR_ERR(priv->clk_ahb); + } + + err = clk_prepare_enable(priv->clk_ahb); + if (err) { + dev_err(priv->dev, "Failed to enable ahb_clk!\n"); + return err; + } + + err = clk_set_rate(priv->clk, CLOCK_FREQ); + if (err) { + clk_disable_unprepare(priv->clk_ahb); + dev_err(priv->dev, "Failed to set rate!\n"); + return err; + } + + err = clk_prepare_enable(priv->clk); + if (err) { + clk_disable_unprepare(priv->clk_ahb); + dev_err(priv->dev, "Failed to enable clk!\n"); + return err; + } + + /* wdt has internal divider */ + clk = clk_get_rate(priv->clk); + if (!clk) { + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_ahb); + dev_err(priv->dev, "Failed, clk is 0!\n"); + return -EINVAL; + } + + priv->wdt_freq = clk / 2; + + return 0; +} + +static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv) +{ + const char *tmp; + int ret; + + /* default mode */ + priv->mode = HW_RESET; + + ret = of_property_read_string(priv->dev->of_node, + "alphascale,mode", &tmp); + if (ret < 0) + return; + + if (!strcmp(tmp, "hw")) + priv->mode = HW_RESET; + else if (!strcmp(tmp, "sw")) + priv->mode = SW_RESET; + else if (!strcmp(tmp, "debug")) + priv->mode = DEBUG; + else + dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.", + tmp); +} + +static int asm9260_wdt_probe(struct platform_device *pdev) +{ + struct asm9260_wdt_priv *priv; + struct watchdog_device *wdd; + struct resource *res; + int ret; + const char * const mode_name[] = { "hw", "sw", "debug", }; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_wdt_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->iobase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->iobase)) + return PTR_ERR(priv->iobase); + + ret = asm9260_wdt_get_dt_clks(priv); + if (ret) + return ret; + + priv->rst = devm_reset_control_get(&pdev->dev, "wdt_rst"); + if (IS_ERR(priv->rst)) + return PTR_ERR(priv->rst); + + wdd = &priv->wdd; + wdd->info = &asm9260_wdt_ident; + wdd->ops = &asm9260_wdt_ops; + wdd->min_timeout = 1; + wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq); + wdd->parent = &pdev->dev; + + watchdog_set_drvdata(wdd, priv); + + /* + * If 'timeout-sec' unspecified in devicetree, assume a 30 second + * default, unless the max timeout is less than 30 seconds, then use + * the max instead. + */ + wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT; + watchdog_init_timeout(wdd, 0, &pdev->dev); + + asm9260_wdt_get_dt_mode(priv); + + if (priv->mode != HW_RESET) + priv->irq = platform_get_irq(pdev, 0); + + if (priv->irq > 0) { + /* + * Not all supported platforms specify an interrupt for the + * watchdog, so let's make it optional. + */ + ret = devm_request_irq(&pdev->dev, priv->irq, + asm9260_wdt_irq, 0, pdev->name, priv); + if (ret < 0) + dev_warn(&pdev->dev, "failed to request IRQ\n"); + } + + ret = watchdog_register_device(wdd); + if (ret) + goto clk_off; + + platform_set_drvdata(pdev, priv); + + priv->restart_handler.notifier_call = asm9260_restart_handler; + priv->restart_handler.priority = 128; + ret = register_restart_handler(&priv->restart_handler); + if (ret) + dev_warn(&pdev->dev, "cannot register restart handler\n"); + + dev_info(&pdev->dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n", + wdd->timeout, mode_name[priv->mode]); + return 0; + +clk_off: + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_ahb); + return ret; +} + +static void asm9260_wdt_shutdown(struct platform_device *pdev) +{ + struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev); + + asm9260_wdt_disable(&priv->wdd); +} + +static int asm9260_wdt_remove(struct platform_device *pdev) +{ + struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev); + + asm9260_wdt_disable(&priv->wdd); + + unregister_restart_handler(&priv->restart_handler); + + watchdog_unregister_device(&priv->wdd); + + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_ahb); + + return 0; +} + +static const struct of_device_id asm9260_wdt_of_match[] = { + { .compatible = "alphascale,asm9260-wdt"}, + {}, +}; +MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match); + +static struct platform_driver asm9260_wdt_driver = { + .driver = { + .name = "asm9260-wdt", + .owner = THIS_MODULE, + .of_match_table = asm9260_wdt_of_match, + }, + .probe = asm9260_wdt_probe, + .remove = asm9260_wdt_remove, + .shutdown = asm9260_wdt_shutdown, +}; +module_platform_driver(asm9260_wdt_driver); + +MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver"); +MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/atlas7_wdt.c b/drivers/watchdog/atlas7_wdt.c new file mode 100644 index 000000000..df6d9242a --- /dev/null +++ b/drivers/watchdog/atlas7_wdt.c @@ -0,0 +1,242 @@ +/* + * Watchdog driver for CSR Atlas7 + * + * Copyright (c) 2015 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/watchdog.h> + +#define ATLAS7_TIMER_WDT_INDEX 5 +#define ATLAS7_WDT_DEFAULT_TIMEOUT 20 + +#define ATLAS7_WDT_CNT_CTRL (0 + 4 * ATLAS7_TIMER_WDT_INDEX) +#define ATLAS7_WDT_CNT_MATCH (0x18 + 4 * ATLAS7_TIMER_WDT_INDEX) +#define ATLAS7_WDT_CNT (0x48 + 4 * ATLAS7_TIMER_WDT_INDEX) +#define ATLAS7_WDT_CNT_EN (BIT(0) | BIT(1)) +#define ATLAS7_WDT_EN 0x64 + +static unsigned int timeout = ATLAS7_WDT_DEFAULT_TIMEOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; + +module_param(timeout, uint, 0); +module_param(nowayout, bool, 0); + +MODULE_PARM_DESC(timeout, "Default watchdog timeout (in seconds)"); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +struct atlas7_wdog { + struct device *dev; + void __iomem *base; + unsigned long tick_rate; + struct clk *clk; +}; + +static unsigned int atlas7_wdt_gettimeleft(struct watchdog_device *wdd) +{ + struct atlas7_wdog *wdt = watchdog_get_drvdata(wdd); + u32 counter, match, delta; + + counter = readl(wdt->base + ATLAS7_WDT_CNT); + match = readl(wdt->base + ATLAS7_WDT_CNT_MATCH); + delta = match - counter; + + return delta / wdt->tick_rate; +} + +static int atlas7_wdt_ping(struct watchdog_device *wdd) +{ + struct atlas7_wdog *wdt = watchdog_get_drvdata(wdd); + u32 counter, match, delta; + + counter = readl(wdt->base + ATLAS7_WDT_CNT); + delta = wdd->timeout * wdt->tick_rate; + match = counter + delta; + + writel(match, wdt->base + ATLAS7_WDT_CNT_MATCH); + + return 0; +} + +static int atlas7_wdt_enable(struct watchdog_device *wdd) +{ + struct atlas7_wdog *wdt = watchdog_get_drvdata(wdd); + + atlas7_wdt_ping(wdd); + + writel(readl(wdt->base + ATLAS7_WDT_CNT_CTRL) | ATLAS7_WDT_CNT_EN, + wdt->base + ATLAS7_WDT_CNT_CTRL); + writel(1, wdt->base + ATLAS7_WDT_EN); + + return 0; +} + +static int atlas7_wdt_disable(struct watchdog_device *wdd) +{ + struct atlas7_wdog *wdt = watchdog_get_drvdata(wdd); + + writel(0, wdt->base + ATLAS7_WDT_EN); + writel(readl(wdt->base + ATLAS7_WDT_CNT_CTRL) & ~ATLAS7_WDT_CNT_EN, + wdt->base + ATLAS7_WDT_CNT_CTRL); + + return 0; +} + +static int atlas7_wdt_settimeout(struct watchdog_device *wdd, unsigned int to) +{ + wdd->timeout = to; + + return 0; +} + +#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) + +static const struct watchdog_info atlas7_wdt_ident = { + .options = OPTIONS, + .firmware_version = 0, + .identity = "atlas7 Watchdog", +}; + +static struct watchdog_ops atlas7_wdt_ops = { + .owner = THIS_MODULE, + .start = atlas7_wdt_enable, + .stop = atlas7_wdt_disable, + .get_timeleft = atlas7_wdt_gettimeleft, + .ping = atlas7_wdt_ping, + .set_timeout = atlas7_wdt_settimeout, +}; + +static struct watchdog_device atlas7_wdd = { + .info = &atlas7_wdt_ident, + .ops = &atlas7_wdt_ops, + .timeout = ATLAS7_WDT_DEFAULT_TIMEOUT, +}; + +static const struct of_device_id atlas7_wdt_ids[] = { + { .compatible = "sirf,atlas7-tick"}, + {} +}; + +static int atlas7_wdt_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct atlas7_wdog *wdt; + struct resource *res; + struct clk *clk; + int ret; + + wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(wdt->base)) + return PTR_ERR(wdt->base); + + clk = of_clk_get(np, 0); + if (IS_ERR(clk)) + return PTR_ERR(clk); + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&pdev->dev, "clk enable failed\n"); + goto err; + } + + /* disable watchdog hardware */ + writel(0, wdt->base + ATLAS7_WDT_CNT_CTRL); + + wdt->tick_rate = clk_get_rate(clk); + wdt->clk = clk; + atlas7_wdd.min_timeout = 1; + atlas7_wdd.max_timeout = UINT_MAX / wdt->tick_rate; + + watchdog_init_timeout(&atlas7_wdd, 0, &pdev->dev); + watchdog_set_nowayout(&atlas7_wdd, nowayout); + + watchdog_set_drvdata(&atlas7_wdd, wdt); + platform_set_drvdata(pdev, &atlas7_wdd); + + ret = watchdog_register_device(&atlas7_wdd); + if (ret) + goto err1; + + return 0; + +err1: + clk_disable_unprepare(clk); +err: + clk_put(clk); + return ret; +} + +static void atlas7_wdt_shutdown(struct platform_device *pdev) +{ + struct watchdog_device *wdd = platform_get_drvdata(pdev); + struct atlas7_wdog *wdt = watchdog_get_drvdata(wdd); + + atlas7_wdt_disable(wdd); + clk_disable_unprepare(wdt->clk); +} + +static int atlas7_wdt_remove(struct platform_device *pdev) +{ + struct watchdog_device *wdd = platform_get_drvdata(pdev); + struct atlas7_wdog *wdt = watchdog_get_drvdata(wdd); + + atlas7_wdt_shutdown(pdev); + clk_put(wdt->clk); + return 0; +} + +static int __maybe_unused atlas7_wdt_suspend(struct device *dev) +{ + /* + * NOTE:timer controller registers settings are saved + * and restored back by the timer-atlas7.c + */ + return 0; +} + +static int __maybe_unused atlas7_wdt_resume(struct device *dev) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + /* + * NOTE: Since timer controller registers settings are saved + * and restored back by the timer-atlas7.c, so we need not + * update WD settings except refreshing timeout. + */ + atlas7_wdt_ping(wdd); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(atlas7_wdt_pm_ops, + atlas7_wdt_suspend, atlas7_wdt_resume); + +MODULE_DEVICE_TABLE(of, atlas7_wdt_ids); + +static struct platform_driver atlas7_wdt_driver = { + .driver = { + .name = "atlas7-wdt", + .pm = &atlas7_wdt_pm_ops, + .of_match_table = atlas7_wdt_ids, + }, + .probe = atlas7_wdt_probe, + .remove = atlas7_wdt_remove, + .shutdown = atlas7_wdt_shutdown, +}; +module_platform_driver(atlas7_wdt_driver); + +MODULE_DESCRIPTION("CSRatlas7 watchdog driver"); +MODULE_AUTHOR("Guo Zeng <Guo.Zeng@csr.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:atlas7-wdt"); diff --git a/drivers/watchdog/bcm2835_wdt.c b/drivers/watchdog/bcm2835_wdt.c index 8a5ce5b5a..2e6164c4a 100644 --- a/drivers/watchdog/bcm2835_wdt.c +++ b/drivers/watchdog/bcm2835_wdt.c @@ -79,7 +79,6 @@ static int bcm2835_wdt_stop(struct watchdog_device *wdog) struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog); writel_relaxed(PM_PASSWORD | PM_RSTC_RESET, wdt->base + PM_RSTC); - dev_info(wdog->dev, "Watchdog timer stopped"); return 0; } diff --git a/drivers/watchdog/bcm47xx_wdt.c b/drivers/watchdog/bcm47xx_wdt.c index 4064a43f1..df1c2a4b0 100644 --- a/drivers/watchdog/bcm47xx_wdt.c +++ b/drivers/watchdog/bcm47xx_wdt.c @@ -20,7 +20,6 @@ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/platform_device.h> -#include <linux/reboot.h> #include <linux/types.h> #include <linux/watchdog.h> #include <linux/timer.h> @@ -88,12 +87,22 @@ static int bcm47xx_wdt_hard_set_timeout(struct watchdog_device *wdd, return 0; } +static int bcm47xx_wdt_restart(struct watchdog_device *wdd) +{ + struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd); + + wdt->timer_set(wdt, 1); + + return 0; +} + static struct watchdog_ops bcm47xx_wdt_hard_ops = { .owner = THIS_MODULE, .start = bcm47xx_wdt_hard_start, .stop = bcm47xx_wdt_hard_stop, .ping = bcm47xx_wdt_hard_keepalive, .set_timeout = bcm47xx_wdt_hard_set_timeout, + .restart = bcm47xx_wdt_restart, }; static void bcm47xx_wdt_soft_timer_tick(unsigned long data) @@ -158,34 +167,13 @@ static const struct watchdog_info bcm47xx_wdt_info = { WDIOF_MAGICCLOSE, }; -static int bcm47xx_wdt_notify_sys(struct notifier_block *this, - unsigned long code, void *unused) -{ - struct bcm47xx_wdt *wdt; - - wdt = container_of(this, struct bcm47xx_wdt, notifier); - if (code == SYS_DOWN || code == SYS_HALT) - wdt->wdd.ops->stop(&wdt->wdd); - return NOTIFY_DONE; -} - -static int bcm47xx_wdt_restart(struct notifier_block *this, unsigned long mode, - void *cmd) -{ - struct bcm47xx_wdt *wdt; - - wdt = container_of(this, struct bcm47xx_wdt, restart_handler); - wdt->timer_set(wdt, 1); - - return NOTIFY_DONE; -} - static struct watchdog_ops bcm47xx_wdt_soft_ops = { .owner = THIS_MODULE, .start = bcm47xx_wdt_soft_start, .stop = bcm47xx_wdt_soft_stop, .ping = bcm47xx_wdt_soft_keepalive, .set_timeout = bcm47xx_wdt_soft_set_timeout, + .restart = bcm47xx_wdt_restart, }; static int bcm47xx_wdt_probe(struct platform_device *pdev) @@ -214,32 +202,18 @@ static int bcm47xx_wdt_probe(struct platform_device *pdev) if (ret) goto err_timer; watchdog_set_nowayout(&wdt->wdd, nowayout); - - wdt->notifier.notifier_call = &bcm47xx_wdt_notify_sys; - - ret = register_reboot_notifier(&wdt->notifier); - if (ret) - goto err_timer; - - wdt->restart_handler.notifier_call = &bcm47xx_wdt_restart; - wdt->restart_handler.priority = 64; - ret = register_restart_handler(&wdt->restart_handler); - if (ret) - goto err_notifier; + watchdog_set_restart_priority(&wdt->wdd, 64); + watchdog_stop_on_reboot(&wdt->wdd); ret = watchdog_register_device(&wdt->wdd); if (ret) - goto err_handler; + goto err_timer; dev_info(&pdev->dev, "BCM47xx Watchdog Timer enabled (%d seconds%s%s)\n", timeout, nowayout ? ", nowayout" : "", soft ? ", Software Timer" : ""); return 0; -err_handler: - unregister_restart_handler(&wdt->restart_handler); -err_notifier: - unregister_reboot_notifier(&wdt->notifier); err_timer: if (soft) del_timer_sync(&wdt->soft_timer); @@ -255,7 +229,6 @@ static int bcm47xx_wdt_remove(struct platform_device *pdev) return -ENXIO; watchdog_unregister_device(&wdt->wdd); - unregister_reboot_notifier(&wdt->notifier); return 0; } diff --git a/drivers/watchdog/cadence_wdt.c b/drivers/watchdog/cadence_wdt.c index bcfd2a222..4dda9024e 100644 --- a/drivers/watchdog/cadence_wdt.c +++ b/drivers/watchdog/cadence_wdt.c @@ -18,7 +18,6 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> -#include <linux/reboot.h> #include <linux/watchdog.h> #define CDNS_WDT_DEFAULT_TIMEOUT 10 @@ -72,7 +71,6 @@ MODULE_PARM_DESC(nowayout, * @ctrl_clksel: counter clock prescaler selection * @io_lock: spinlock for IO register access * @cdns_wdt_device: watchdog device structure - * @cdns_wdt_notifier: notifier structure * * Structure containing parameters specific to cadence watchdog. */ @@ -84,7 +82,6 @@ struct cdns_wdt { u32 ctrl_clksel; spinlock_t io_lock; struct watchdog_device cdns_wdt_device; - struct notifier_block cdns_wdt_notifier; }; /* Write access to Registers */ @@ -280,29 +277,6 @@ static struct watchdog_ops cdns_wdt_ops = { .set_timeout = cdns_wdt_settimeout, }; -/** - * cdns_wdt_notify_sys - Notifier for reboot or shutdown. - * - * @this: handle to notifier block - * @code: turn off indicator - * @unused: unused - * Return: NOTIFY_DONE - * - * This notifier is invoked whenever the system reboot or shutdown occur - * because we need to disable the WDT before system goes down as WDT might - * reset on the next boot. - */ -static int cdns_wdt_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) -{ - struct cdns_wdt *wdt = container_of(this, struct cdns_wdt, - cdns_wdt_notifier); - if (code == SYS_DOWN || code == SYS_HALT) - cdns_wdt_stop(&wdt->cdns_wdt_device); - - return NOTIFY_DONE; -} - /************************Platform Operations*****************************/ /** * cdns_wdt_probe - Probe call for the device. @@ -360,6 +334,7 @@ static int cdns_wdt_probe(struct platform_device *pdev) } watchdog_set_nowayout(cdns_wdt_device, nowayout); + watchdog_stop_on_reboot(cdns_wdt_device); watchdog_set_drvdata(cdns_wdt_device, wdt); wdt->clk = devm_clk_get(&pdev->dev, NULL); @@ -386,14 +361,6 @@ static int cdns_wdt_probe(struct platform_device *pdev) spin_lock_init(&wdt->io_lock); - wdt->cdns_wdt_notifier.notifier_call = &cdns_wdt_notify_sys; - ret = register_reboot_notifier(&wdt->cdns_wdt_notifier); - if (ret != 0) { - dev_err(&pdev->dev, "cannot register reboot notifier err=%d)\n", - ret); - goto err_clk_disable; - } - ret = watchdog_register_device(cdns_wdt_device); if (ret) { dev_err(&pdev->dev, "Failed to register wdt device\n"); @@ -427,7 +394,6 @@ static int cdns_wdt_remove(struct platform_device *pdev) cdns_wdt_stop(&wdt->cdns_wdt_device); watchdog_unregister_device(&wdt->cdns_wdt_device); - unregister_reboot_notifier(&wdt->cdns_wdt_notifier); clk_disable_unprepare(wdt->clk); return 0; @@ -455,8 +421,7 @@ static void cdns_wdt_shutdown(struct platform_device *pdev) */ static int __maybe_unused cdns_wdt_suspend(struct device *dev) { - struct platform_device *pdev = container_of(dev, - struct platform_device, dev); + struct platform_device *pdev = to_platform_device(dev); struct cdns_wdt *wdt = platform_get_drvdata(pdev); cdns_wdt_stop(&wdt->cdns_wdt_device); @@ -474,8 +439,7 @@ static int __maybe_unused cdns_wdt_suspend(struct device *dev) static int __maybe_unused cdns_wdt_resume(struct device *dev) { int ret; - struct platform_device *pdev = container_of(dev, - struct platform_device, dev); + struct platform_device *pdev = to_platform_device(dev); struct cdns_wdt *wdt = platform_get_drvdata(pdev); ret = clk_prepare_enable(wdt->clk); diff --git a/drivers/watchdog/da9052_wdt.c b/drivers/watchdog/da9052_wdt.c index 67e67977b..2fc19a32a 100644 --- a/drivers/watchdog/da9052_wdt.c +++ b/drivers/watchdog/da9052_wdt.c @@ -31,7 +31,6 @@ struct da9052_wdt_data { struct watchdog_device wdt; struct da9052 *da9052; - struct kref kref; unsigned long jpast; }; @@ -51,10 +50,6 @@ static const struct { }; -static void da9052_wdt_release_resources(struct kref *r) -{ -} - static int da9052_wdt_set_timeout(struct watchdog_device *wdt_dev, unsigned int timeout) { @@ -104,20 +99,6 @@ static int da9052_wdt_set_timeout(struct watchdog_device *wdt_dev, return 0; } -static void da9052_wdt_ref(struct watchdog_device *wdt_dev) -{ - struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); - - kref_get(&driver_data->kref); -} - -static void da9052_wdt_unref(struct watchdog_device *wdt_dev) -{ - struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); - - kref_put(&driver_data->kref, da9052_wdt_release_resources); -} - static int da9052_wdt_start(struct watchdog_device *wdt_dev) { return da9052_wdt_set_timeout(wdt_dev, wdt_dev->timeout); @@ -170,8 +151,6 @@ static const struct watchdog_ops da9052_wdt_ops = { .stop = da9052_wdt_stop, .ping = da9052_wdt_ping, .set_timeout = da9052_wdt_set_timeout, - .ref = da9052_wdt_ref, - .unref = da9052_wdt_unref, }; @@ -198,8 +177,6 @@ static int da9052_wdt_probe(struct platform_device *pdev) da9052_wdt->parent = &pdev->dev; watchdog_set_drvdata(da9052_wdt, driver_data); - kref_init(&driver_data->kref); - ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, DA9052_CONTROLD_TWDSCALE, 0); if (ret < 0) { @@ -225,7 +202,6 @@ static int da9052_wdt_remove(struct platform_device *pdev) struct da9052_wdt_data *driver_data = platform_get_drvdata(pdev); watchdog_unregister_device(&driver_data->wdt); - kref_put(&driver_data->kref, da9052_wdt_release_resources); return 0; } diff --git a/drivers/watchdog/da9055_wdt.c b/drivers/watchdog/da9055_wdt.c index 04d1430d9..8377c43f3 100644 --- a/drivers/watchdog/da9055_wdt.c +++ b/drivers/watchdog/da9055_wdt.c @@ -35,7 +35,6 @@ MODULE_PARM_DESC(nowayout, struct da9055_wdt_data { struct watchdog_device wdt; struct da9055 *da9055; - struct kref kref; }; static const struct { @@ -99,24 +98,6 @@ static int da9055_wdt_ping(struct watchdog_device *wdt_dev) DA9055_WATCHDOG_MASK, 1); } -static void da9055_wdt_release_resources(struct kref *r) -{ -} - -static void da9055_wdt_ref(struct watchdog_device *wdt_dev) -{ - struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); - - kref_get(&driver_data->kref); -} - -static void da9055_wdt_unref(struct watchdog_device *wdt_dev) -{ - struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); - - kref_put(&driver_data->kref, da9055_wdt_release_resources); -} - static int da9055_wdt_start(struct watchdog_device *wdt_dev) { return da9055_wdt_set_timeout(wdt_dev, wdt_dev->timeout); @@ -138,8 +119,6 @@ static const struct watchdog_ops da9055_wdt_ops = { .stop = da9055_wdt_stop, .ping = da9055_wdt_ping, .set_timeout = da9055_wdt_set_timeout, - .ref = da9055_wdt_ref, - .unref = da9055_wdt_unref, }; static int da9055_wdt_probe(struct platform_device *pdev) @@ -165,8 +144,6 @@ static int da9055_wdt_probe(struct platform_device *pdev) watchdog_set_nowayout(da9055_wdt, nowayout); watchdog_set_drvdata(da9055_wdt, driver_data); - kref_init(&driver_data->kref); - ret = da9055_wdt_stop(da9055_wdt); if (ret < 0) { dev_err(&pdev->dev, "Failed to stop watchdog, %d\n", ret); @@ -189,7 +166,6 @@ static int da9055_wdt_remove(struct platform_device *pdev) struct da9055_wdt_data *driver_data = platform_get_drvdata(pdev); watchdog_unregister_device(&driver_data->wdt); - kref_put(&driver_data->kref, da9055_wdt_release_resources); return 0; } diff --git a/drivers/watchdog/da9063_wdt.c b/drivers/watchdog/da9063_wdt.c index 6bf130bd8..11e887572 100644 --- a/drivers/watchdog/da9063_wdt.c +++ b/drivers/watchdog/da9063_wdt.c @@ -20,7 +20,6 @@ #include <linux/delay.h> #include <linux/mfd/da9063/registers.h> #include <linux/mfd/da9063/core.h> -#include <linux/reboot.h> #include <linux/regmap.h> /* @@ -39,7 +38,6 @@ static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 }; struct da9063_watchdog { struct da9063 *da9063; struct watchdog_device wdtdev; - struct notifier_block restart_handler; }; static unsigned int da9063_wdt_timeout_to_sel(unsigned int secs) @@ -121,12 +119,9 @@ static int da9063_wdt_set_timeout(struct watchdog_device *wdd, return ret; } -static int da9063_wdt_restart_handler(struct notifier_block *this, - unsigned long mode, void *cmd) +static int da9063_wdt_restart(struct watchdog_device *wdd) { - struct da9063_watchdog *wdt = container_of(this, - struct da9063_watchdog, - restart_handler); + struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd); int ret; ret = regmap_write(wdt->da9063->regmap, DA9063_REG_CONTROL_F, @@ -135,7 +130,7 @@ static int da9063_wdt_restart_handler(struct notifier_block *this, dev_alert(wdt->da9063->dev, "Failed to shutdown (err = %d)\n", ret); - return NOTIFY_DONE; + return ret; } static const struct watchdog_info da9063_watchdog_info = { @@ -149,6 +144,7 @@ static const struct watchdog_ops da9063_watchdog_ops = { .stop = da9063_wdt_stop, .ping = da9063_wdt_ping, .set_timeout = da9063_wdt_set_timeout, + .restart = da9063_wdt_restart, }; static int da9063_wdt_probe(struct platform_device *pdev) @@ -179,6 +175,8 @@ static int da9063_wdt_probe(struct platform_device *pdev) wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS; + watchdog_set_restart_priority(&wdt->wdtdev, 128); + watchdog_set_drvdata(&wdt->wdtdev, wdt); dev_set_drvdata(&pdev->dev, wdt); @@ -186,13 +184,6 @@ static int da9063_wdt_probe(struct platform_device *pdev) if (ret) return ret; - wdt->restart_handler.notifier_call = da9063_wdt_restart_handler; - wdt->restart_handler.priority = 128; - ret = register_restart_handler(&wdt->restart_handler); - if (ret) - dev_err(wdt->da9063->dev, - "Failed to register restart handler (err = %d)\n", ret); - return 0; } @@ -200,8 +191,6 @@ static int da9063_wdt_remove(struct platform_device *pdev) { struct da9063_watchdog *wdt = dev_get_drvdata(&pdev->dev); - unregister_restart_handler(&wdt->restart_handler); - watchdog_unregister_device(&wdt->wdtdev); return 0; diff --git a/drivers/watchdog/diag288_wdt.c b/drivers/watchdog/diag288_wdt.c index 3db9d0e06..861d3d313 100644 --- a/drivers/watchdog/diag288_wdt.c +++ b/drivers/watchdog/diag288_wdt.c @@ -106,6 +106,10 @@ static int __diag288_lpar(unsigned int func, unsigned int timeout, return __diag288(func, timeout, action, 0); } +static unsigned long wdt_status; + +#define DIAG_WDOG_BUSY 0 + static int wdt_start(struct watchdog_device *dev) { char *ebc_cmd; @@ -113,12 +117,17 @@ static int wdt_start(struct watchdog_device *dev) int ret; unsigned int func; + if (test_and_set_bit(DIAG_WDOG_BUSY, &wdt_status)) + return -EBUSY; + ret = -ENODEV; if (MACHINE_IS_VM) { ebc_cmd = kmalloc(MAX_CMDLEN, GFP_KERNEL); - if (!ebc_cmd) + if (!ebc_cmd) { + clear_bit(DIAG_WDOG_BUSY, &wdt_status); return -ENOMEM; + } len = strlcpy(ebc_cmd, wdt_cmd, MAX_CMDLEN); ASCEBC(ebc_cmd, MAX_CMDLEN); EBC_TOUPPER(ebc_cmd, MAX_CMDLEN); @@ -135,6 +144,7 @@ static int wdt_start(struct watchdog_device *dev) if (ret) { pr_err("The watchdog cannot be activated\n"); + clear_bit(DIAG_WDOG_BUSY, &wdt_status); return ret; } return 0; @@ -146,6 +156,9 @@ static int wdt_stop(struct watchdog_device *dev) diag_stat_inc(DIAG_STAT_X288); ret = __diag288(WDT_FUNC_CANCEL, 0, 0, 0); + + clear_bit(DIAG_WDOG_BUSY, &wdt_status); + return ret; } @@ -220,17 +233,10 @@ static struct watchdog_device wdt_dev = { * It makes no sense to go into suspend while the watchdog is running. * Depending on the memory size, the watchdog might trigger, while we * are still saving the memory. - * We reuse the open flag to ensure that suspend and watchdog open are - * exclusive operations */ static int wdt_suspend(void) { - if (test_and_set_bit(WDOG_DEV_OPEN, &wdt_dev.status)) { - pr_err("Linux cannot be suspended while the watchdog is in use\n"); - return notifier_from_errno(-EBUSY); - } - if (test_bit(WDOG_ACTIVE, &wdt_dev.status)) { - clear_bit(WDOG_DEV_OPEN, &wdt_dev.status); + if (test_and_set_bit(DIAG_WDOG_BUSY, &wdt_status)) { pr_err("Linux cannot be suspended while the watchdog is in use\n"); return notifier_from_errno(-EBUSY); } @@ -239,7 +245,7 @@ static int wdt_suspend(void) static int wdt_resume(void) { - clear_bit(WDOG_DEV_OPEN, &wdt_dev.status); + clear_bit(DIAG_WDOG_BUSY, &wdt_status); return NOTIFY_DONE; } diff --git a/drivers/watchdog/digicolor_wdt.c b/drivers/watchdog/digicolor_wdt.c index 50abe1bf6..1ccb0b239 100644 --- a/drivers/watchdog/digicolor_wdt.c +++ b/drivers/watchdog/digicolor_wdt.c @@ -15,7 +15,6 @@ #include <linux/delay.h> #include <linux/clk.h> #include <linux/watchdog.h> -#include <linux/reboot.h> #include <linux/platform_device.h> #include <linux/of_address.h> @@ -28,7 +27,6 @@ struct dc_wdt { void __iomem *base; struct clk *clk; - struct notifier_block restart_handler; spinlock_t lock; }; @@ -50,16 +48,15 @@ static void dc_wdt_set(struct dc_wdt *wdt, u32 ticks) spin_unlock_irqrestore(&wdt->lock, flags); } -static int dc_restart_handler(struct notifier_block *this, unsigned long mode, - void *cmd) +static int dc_wdt_restart(struct watchdog_device *wdog) { - struct dc_wdt *wdt = container_of(this, struct dc_wdt, restart_handler); + struct dc_wdt *wdt = watchdog_get_drvdata(wdog); dc_wdt_set(wdt, 1); /* wait for reset to assert... */ mdelay(500); - return NOTIFY_DONE; + return 0; } static int dc_wdt_start(struct watchdog_device *wdog) @@ -104,6 +101,7 @@ static struct watchdog_ops dc_wdt_ops = { .stop = dc_wdt_stop, .set_timeout = dc_wdt_set_timeout, .get_timeleft = dc_wdt_get_timeleft, + .restart = dc_wdt_restart, }; static struct watchdog_info dc_wdt_info = { @@ -148,6 +146,7 @@ static int dc_wdt_probe(struct platform_device *pdev) spin_lock_init(&wdt->lock); watchdog_set_drvdata(&dc_wdt_wdd, wdt); + watchdog_set_restart_priority(&dc_wdt_wdd, 128); watchdog_init_timeout(&dc_wdt_wdd, timeout, dev); ret = watchdog_register_device(&dc_wdt_wdd); if (ret) { @@ -155,12 +154,6 @@ static int dc_wdt_probe(struct platform_device *pdev) goto err_iounmap; } - wdt->restart_handler.notifier_call = dc_restart_handler; - wdt->restart_handler.priority = 128; - ret = register_restart_handler(&wdt->restart_handler); - if (ret) - dev_warn(&pdev->dev, "cannot register restart handler\n"); - return 0; err_iounmap: @@ -172,7 +165,6 @@ static int dc_wdt_remove(struct platform_device *pdev) { struct dc_wdt *wdt = platform_get_drvdata(pdev); - unregister_restart_handler(&wdt->restart_handler); watchdog_unregister_device(&dc_wdt_wdd); iounmap(wdt->base); diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c index 6ea063434..8fefa4ad4 100644 --- a/drivers/watchdog/dw_wdt.c +++ b/drivers/watchdog/dw_wdt.c @@ -81,7 +81,7 @@ static inline int dw_wdt_top_in_seconds(unsigned top) * There are 16 possible timeout values in 0..15 where the number of * cycles is 2 ^ (16 + i) and the watchdog counts down. */ - return (1 << (16 + top)) / clk_get_rate(dw_wdt.clk); + return (1U << (16 + top)) / clk_get_rate(dw_wdt.clk); } static int dw_wdt_get_top(void) diff --git a/drivers/watchdog/gpio_wdt.c b/drivers/watchdog/gpio_wdt.c index 90d59d3f3..ba066e4a7 100644 --- a/drivers/watchdog/gpio_wdt.c +++ b/drivers/watchdog/gpio_wdt.c @@ -12,10 +12,8 @@ #include <linux/err.h> #include <linux/delay.h> #include <linux/module.h> -#include <linux/notifier.h> #include <linux/of_gpio.h> #include <linux/platform_device.h> -#include <linux/reboot.h> #include <linux/watchdog.h> #define SOFT_TIMEOUT_MIN 1 @@ -36,7 +34,6 @@ struct gpio_wdt_priv { unsigned int hw_algo; unsigned int hw_margin; unsigned long last_jiffies; - struct notifier_block notifier; struct timer_list timer; struct watchdog_device wdd; }; @@ -57,7 +54,8 @@ static void gpio_wdt_hwping(unsigned long data) if (priv->armed && time_after(jiffies, priv->last_jiffies + msecs_to_jiffies(wdd->timeout * 1000))) { - dev_crit(wdd->dev, "Timer expired. System will reboot soon!\n"); + dev_crit(wdd->parent, + "Timer expired. System will reboot soon!\n"); return; } @@ -126,26 +124,6 @@ static int gpio_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) return gpio_wdt_ping(wdd); } -static int gpio_wdt_notify_sys(struct notifier_block *nb, unsigned long code, - void *unused) -{ - struct gpio_wdt_priv *priv = container_of(nb, struct gpio_wdt_priv, - notifier); - - mod_timer(&priv->timer, 0); - - switch (code) { - case SYS_HALT: - case SYS_POWER_OFF: - gpio_wdt_disable(priv); - break; - default: - break; - } - - return NOTIFY_DONE; -} - static const struct watchdog_info gpio_wdt_ident = { .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT, @@ -224,23 +202,16 @@ static int gpio_wdt_probe(struct platform_device *pdev) setup_timer(&priv->timer, gpio_wdt_hwping, (unsigned long)&priv->wdd); + watchdog_stop_on_reboot(&priv->wdd); + ret = watchdog_register_device(&priv->wdd); if (ret) return ret; - priv->notifier.notifier_call = gpio_wdt_notify_sys; - ret = register_reboot_notifier(&priv->notifier); - if (ret) - goto error_unregister; - if (priv->always_running) gpio_wdt_start_impl(priv); return 0; - -error_unregister: - watchdog_unregister_device(&priv->wdd); - return ret; } static int gpio_wdt_remove(struct platform_device *pdev) @@ -248,7 +219,6 @@ static int gpio_wdt_remove(struct platform_device *pdev) struct gpio_wdt_priv *priv = platform_get_drvdata(pdev); del_timer_sync(&priv->timer); - unregister_reboot_notifier(&priv->notifier); watchdog_unregister_device(&priv->wdd); return 0; diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index 286369d4f..92443c319 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -1,11 +1,11 @@ /* - * HP WatchDog Driver + * HPE WatchDog Driver * based on * * SoftDog 0.05: A Software Watchdog Device * - * (c) Copyright 2007 Hewlett-Packard Development Company, L.P. - * Thomas Mingarelli <thomas.mingarelli@hp.com> + * (c) Copyright 2015 Hewlett Packard Enterprise Development LP + * Thomas Mingarelli <thomas.mingarelli@hpe.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -580,7 +580,7 @@ static const struct watchdog_info ident = { .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, - .identity = "HP iLO2+ HW Watchdog Timer", + .identity = "HPE iLO2+ HW Watchdog Timer", }; static long hpwdt_ioctl(struct file *file, unsigned int cmd, @@ -758,7 +758,7 @@ static int hpwdt_init_nmi_decoding(struct pci_dev *dev) goto error2; dev_info(&dev->dev, - "HP Watchdog Timer Driver: NMI decoding initialized" + "HPE Watchdog Timer Driver: NMI decoding initialized" ", allow kernel dump: %s (default = 1/ON)\n", (allow_kdump == 0) ? "OFF" : "ON"); return 0; @@ -863,7 +863,7 @@ static int hpwdt_init_one(struct pci_dev *dev, goto error_misc_register; } - dev_info(&dev->dev, "HP Watchdog Timer Driver: %s" + dev_info(&dev->dev, "HPE Watchdog Timer Driver: %s" ", timer margin: %d seconds (nowayout=%d).\n", HPWDT_VERSION, soft_margin, nowayout); return 0; diff --git a/drivers/watchdog/imgpdc_wdt.c b/drivers/watchdog/imgpdc_wdt.c index 15ab07230..3679f2e19 100644 --- a/drivers/watchdog/imgpdc_wdt.c +++ b/drivers/watchdog/imgpdc_wdt.c @@ -45,7 +45,6 @@ #include <linux/log2.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/reboot.h> #include <linux/slab.h> #include <linux/watchdog.h> @@ -87,7 +86,6 @@ struct pdc_wdt_dev { struct clk *wdt_clk; struct clk *sys_clk; void __iomem *base; - struct notifier_block restart_handler; }; static int pdc_wdt_keepalive(struct watchdog_device *wdt_dev) @@ -152,6 +150,16 @@ static int pdc_wdt_start(struct watchdog_device *wdt_dev) return 0; } +static int pdc_wdt_restart(struct watchdog_device *wdt_dev) +{ + struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev); + + /* Assert SOFT_RESET */ + writel(0x1, wdt->base + PDC_WDT_SOFT_RESET); + + return 0; +} + static struct watchdog_info pdc_wdt_info = { .identity = "IMG PDC Watchdog", .options = WDIOF_SETTIMEOUT | @@ -165,20 +173,9 @@ static const struct watchdog_ops pdc_wdt_ops = { .stop = pdc_wdt_stop, .ping = pdc_wdt_keepalive, .set_timeout = pdc_wdt_set_timeout, + .restart = pdc_wdt_restart, }; -static int pdc_wdt_restart(struct notifier_block *this, unsigned long mode, - void *cmd) -{ - struct pdc_wdt_dev *wdt = container_of(this, struct pdc_wdt_dev, - restart_handler); - - /* Assert SOFT_RESET */ - writel(0x1, wdt->base + PDC_WDT_SOFT_RESET); - - return NOTIFY_OK; -} - static int pdc_wdt_probe(struct platform_device *pdev) { u64 div; @@ -282,6 +279,7 @@ static int pdc_wdt_probe(struct platform_device *pdev) } watchdog_set_nowayout(&pdc_wdt->wdt_dev, nowayout); + watchdog_set_restart_priority(&pdc_wdt->wdt_dev, 128); platform_set_drvdata(pdev, pdc_wdt); @@ -289,13 +287,6 @@ static int pdc_wdt_probe(struct platform_device *pdev) if (ret) goto disable_wdt_clk; - pdc_wdt->restart_handler.notifier_call = pdc_wdt_restart; - pdc_wdt->restart_handler.priority = 128; - ret = register_restart_handler(&pdc_wdt->restart_handler); - if (ret) - dev_warn(&pdev->dev, "failed to register restart handler: %d\n", - ret); - return 0; disable_wdt_clk: @@ -316,7 +307,6 @@ static int pdc_wdt_remove(struct platform_device *pdev) { struct pdc_wdt_dev *pdc_wdt = platform_get_drvdata(pdev); - unregister_restart_handler(&pdc_wdt->restart_handler); pdc_wdt_stop(&pdc_wdt->wdt_dev); watchdog_unregister_device(&pdc_wdt->wdt_dev); clk_disable_unprepare(pdc_wdt->wdt_clk); diff --git a/drivers/watchdog/imx2_wdt.c b/drivers/watchdog/imx2_wdt.c index 29ef719a6..e47966aa2 100644 --- a/drivers/watchdog/imx2_wdt.c +++ b/drivers/watchdog/imx2_wdt.c @@ -29,10 +29,8 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/moduleparam.h> -#include <linux/notifier.h> #include <linux/of_address.h> #include <linux/platform_device.h> -#include <linux/reboot.h> #include <linux/regmap.h> #include <linux/timer.h> #include <linux/watchdog.h> @@ -64,7 +62,6 @@ struct imx2_wdt_device { struct regmap *regmap; struct timer_list timer; /* Pings the watchdog when closed */ struct watchdog_device wdog; - struct notifier_block restart_handler; }; static bool nowayout = WATCHDOG_NOWAYOUT; @@ -83,13 +80,11 @@ static const struct watchdog_info imx2_wdt_info = { .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, }; -static int imx2_restart_handler(struct notifier_block *this, unsigned long mode, - void *cmd) +static int imx2_wdt_restart(struct watchdog_device *wdog) { + struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); unsigned int wcr_enable = IMX2_WDT_WCR_WDE; - struct imx2_wdt_device *wdev = container_of(this, - struct imx2_wdt_device, - restart_handler); + /* Assert SRS signal */ regmap_write(wdev->regmap, IMX2_WDT_WCR, wcr_enable); /* @@ -105,7 +100,7 @@ static int imx2_restart_handler(struct notifier_block *this, unsigned long mode, /* wait for reset to assert... */ mdelay(500); - return NOTIFY_DONE; + return 0; } static inline void imx2_wdt_setup(struct watchdog_device *wdog) @@ -213,6 +208,7 @@ static const struct watchdog_ops imx2_wdt_ops = { .stop = imx2_wdt_stop, .ping = imx2_wdt_ping, .set_timeout = imx2_wdt_set_timeout, + .restart = imx2_wdt_restart, }; static const struct regmap_config imx2_wdt_regmap_config = { @@ -275,6 +271,7 @@ static int __init imx2_wdt_probe(struct platform_device *pdev) platform_set_drvdata(pdev, wdog); watchdog_set_drvdata(wdog, wdev); watchdog_set_nowayout(wdog, nowayout); + watchdog_set_restart_priority(wdog, 128); watchdog_init_timeout(wdog, timeout, &pdev->dev); setup_timer(&wdev->timer, imx2_wdt_timer_ping, (unsigned long)wdog); @@ -294,12 +291,6 @@ static int __init imx2_wdt_probe(struct platform_device *pdev) goto disable_clk; } - wdev->restart_handler.notifier_call = imx2_restart_handler; - wdev->restart_handler.priority = 128; - ret = register_restart_handler(&wdev->restart_handler); - if (ret) - dev_err(&pdev->dev, "cannot register restart handler\n"); - dev_info(&pdev->dev, "timeout %d sec (nowayout=%d)\n", wdog->timeout, nowayout); @@ -315,8 +306,6 @@ static int __exit imx2_wdt_remove(struct platform_device *pdev) struct watchdog_device *wdog = platform_get_drvdata(pdev); struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); - unregister_restart_handler(&wdev->restart_handler); - watchdog_unregister_device(wdog); if (imx2_wdt_is_running(wdev)) { diff --git a/drivers/watchdog/lpc18xx_wdt.c b/drivers/watchdog/lpc18xx_wdt.c index ab7b8b185..6914c83aa 100644 --- a/drivers/watchdog/lpc18xx_wdt.c +++ b/drivers/watchdog/lpc18xx_wdt.c @@ -18,7 +18,6 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> -#include <linux/reboot.h> #include <linux/watchdog.h> /* Registers */ @@ -59,7 +58,6 @@ struct lpc18xx_wdt_dev { unsigned long clk_rate; void __iomem *base; struct timer_list timer; - struct notifier_block restart_handler; spinlock_t lock; }; @@ -155,27 +153,9 @@ static int lpc18xx_wdt_start(struct watchdog_device *wdt_dev) return 0; } -static struct watchdog_info lpc18xx_wdt_info = { - .identity = "NXP LPC18xx Watchdog", - .options = WDIOF_SETTIMEOUT | - WDIOF_KEEPALIVEPING | - WDIOF_MAGICCLOSE, -}; - -static const struct watchdog_ops lpc18xx_wdt_ops = { - .owner = THIS_MODULE, - .start = lpc18xx_wdt_start, - .stop = lpc18xx_wdt_stop, - .ping = lpc18xx_wdt_feed, - .set_timeout = lpc18xx_wdt_set_timeout, - .get_timeleft = lpc18xx_wdt_get_timeleft, -}; - -static int lpc18xx_wdt_restart(struct notifier_block *this, unsigned long mode, - void *cmd) +static int lpc18xx_wdt_restart(struct watchdog_device *wdt_dev) { - struct lpc18xx_wdt_dev *lpc18xx_wdt = container_of(this, - struct lpc18xx_wdt_dev, restart_handler); + struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev); unsigned long flags; int val; @@ -197,9 +177,26 @@ static int lpc18xx_wdt_restart(struct notifier_block *this, unsigned long mode, spin_unlock_irqrestore(&lpc18xx_wdt->lock, flags); - return NOTIFY_OK; + return 0; } +static struct watchdog_info lpc18xx_wdt_info = { + .identity = "NXP LPC18xx Watchdog", + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, +}; + +static const struct watchdog_ops lpc18xx_wdt_ops = { + .owner = THIS_MODULE, + .start = lpc18xx_wdt_start, + .stop = lpc18xx_wdt_stop, + .ping = lpc18xx_wdt_feed, + .set_timeout = lpc18xx_wdt_set_timeout, + .get_timeleft = lpc18xx_wdt_get_timeleft, + .restart = lpc18xx_wdt_restart, +}; + static int lpc18xx_wdt_probe(struct platform_device *pdev) { struct lpc18xx_wdt_dev *lpc18xx_wdt; @@ -273,6 +270,7 @@ static int lpc18xx_wdt_probe(struct platform_device *pdev) (unsigned long)&lpc18xx_wdt->wdt_dev); watchdog_set_nowayout(&lpc18xx_wdt->wdt_dev, nowayout); + watchdog_set_restart_priority(&lpc18xx_wdt->wdt_dev, 128); platform_set_drvdata(pdev, lpc18xx_wdt); @@ -280,12 +278,6 @@ static int lpc18xx_wdt_probe(struct platform_device *pdev) if (ret) goto disable_wdt_clk; - lpc18xx_wdt->restart_handler.notifier_call = lpc18xx_wdt_restart; - lpc18xx_wdt->restart_handler.priority = 128; - ret = register_restart_handler(&lpc18xx_wdt->restart_handler); - if (ret) - dev_warn(dev, "failed to register restart handler: %d\n", ret); - return 0; disable_wdt_clk: @@ -306,8 +298,6 @@ static int lpc18xx_wdt_remove(struct platform_device *pdev) { struct lpc18xx_wdt_dev *lpc18xx_wdt = platform_get_drvdata(pdev); - unregister_restart_handler(&lpc18xx_wdt->restart_handler); - dev_warn(&pdev->dev, "I quit now, hardware will probably reboot!\n"); del_timer(&lpc18xx_wdt->timer); diff --git a/drivers/watchdog/max63xx_wdt.c b/drivers/watchdog/max63xx_wdt.c index f36ca4be0..ac5840d96 100644 --- a/drivers/watchdog/max63xx_wdt.c +++ b/drivers/watchdog/max63xx_wdt.c @@ -292,4 +292,4 @@ MODULE_PARM_DESC(nodelay, "Force selection of a timeout setting without initial delay " "(max6373/74 only, default=0)"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/watchdog/mena21_wdt.c b/drivers/watchdog/mena21_wdt.c index 098fa9c34..af6a7c489 100644 --- a/drivers/watchdog/mena21_wdt.c +++ b/drivers/watchdog/mena21_wdt.c @@ -100,12 +100,12 @@ static int a21_wdt_set_timeout(struct watchdog_device *wdt, struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt); if (timeout != 1 && timeout != 30) { - dev_err(wdt->dev, "Only 1 and 30 allowed as timeout\n"); + dev_err(wdt->parent, "Only 1 and 30 allowed as timeout\n"); return -EINVAL; } if (timeout == 30 && wdt->timeout == 1) { - dev_err(wdt->dev, + dev_err(wdt->parent, "Transition from fast to slow mode not allowed\n"); return -EINVAL; } diff --git a/drivers/watchdog/meson_wdt.c b/drivers/watchdog/meson_wdt.c index 1f4155ee3..aea5d2f44 100644 --- a/drivers/watchdog/meson_wdt.c +++ b/drivers/watchdog/meson_wdt.c @@ -17,51 +17,64 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/moduleparam.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/types.h> #include <linux/watchdog.h> #define DRV_NAME "meson_wdt" #define MESON_WDT_TC 0x00 -#define MESON_WDT_TC_EN BIT(22) -#define MESON_WDT_TC_TM_MASK 0x3fffff #define MESON_WDT_DC_RESET (3 << 24) #define MESON_WDT_RESET 0x04 #define MESON_WDT_TIMEOUT 30 #define MESON_WDT_MIN_TIMEOUT 1 -#define MESON_WDT_MAX_TIMEOUT (MESON_WDT_TC_TM_MASK / 100000) -#define MESON_SEC_TO_TC(s) ((s) * 100000) +#define MESON_SEC_TO_TC(s, c) ((s) * (c)) static bool nowayout = WATCHDOG_NOWAYOUT; static unsigned int timeout = MESON_WDT_TIMEOUT; +struct meson_wdt_data { + unsigned int enable; + unsigned int terminal_count_mask; + unsigned int count_unit; +}; + +static struct meson_wdt_data meson6_wdt_data = { + .enable = BIT(22), + .terminal_count_mask = 0x3fffff, + .count_unit = 100000, /* 10 us */ +}; + +static struct meson_wdt_data meson8b_wdt_data = { + .enable = BIT(19), + .terminal_count_mask = 0xffff, + .count_unit = 7812, /* 128 us */ +}; + struct meson_wdt_dev { struct watchdog_device wdt_dev; void __iomem *wdt_base; - struct notifier_block restart_handler; + const struct meson_wdt_data *data; }; -static int meson_restart_handle(struct notifier_block *this, unsigned long mode, - void *cmd) +static int meson_wdt_restart(struct watchdog_device *wdt_dev) { - u32 tc_reboot = MESON_WDT_DC_RESET | MESON_WDT_TC_EN; - struct meson_wdt_dev *meson_wdt = container_of(this, - struct meson_wdt_dev, - restart_handler); + struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); + u32 tc_reboot = MESON_WDT_DC_RESET; + + tc_reboot |= meson_wdt->data->enable; while (1) { writel(tc_reboot, meson_wdt->wdt_base + MESON_WDT_TC); mdelay(5); } - return NOTIFY_DONE; + return 0; } static int meson_wdt_ping(struct watchdog_device *wdt_dev) @@ -80,8 +93,8 @@ static void meson_wdt_change_timeout(struct watchdog_device *wdt_dev, u32 reg; reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); - reg &= ~MESON_WDT_TC_TM_MASK; - reg |= MESON_SEC_TO_TC(timeout); + reg &= ~meson_wdt->data->terminal_count_mask; + reg |= MESON_SEC_TO_TC(timeout, meson_wdt->data->count_unit); writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); } @@ -102,7 +115,7 @@ static int meson_wdt_stop(struct watchdog_device *wdt_dev) u32 reg; reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); - reg &= ~MESON_WDT_TC_EN; + reg &= ~meson_wdt->data->enable; writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); return 0; @@ -117,7 +130,7 @@ static int meson_wdt_start(struct watchdog_device *wdt_dev) meson_wdt_ping(wdt_dev); reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); - reg |= MESON_WDT_TC_EN; + reg |= meson_wdt->data->enable; writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); return 0; @@ -136,12 +149,21 @@ static const struct watchdog_ops meson_wdt_ops = { .stop = meson_wdt_stop, .ping = meson_wdt_ping, .set_timeout = meson_wdt_set_timeout, + .restart = meson_wdt_restart, +}; + +static const struct of_device_id meson_wdt_dt_ids[] = { + { .compatible = "amlogic,meson6-wdt", .data = &meson6_wdt_data }, + { .compatible = "amlogic,meson8b-wdt", .data = &meson8b_wdt_data }, + { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, meson_wdt_dt_ids); static int meson_wdt_probe(struct platform_device *pdev) { struct resource *res; struct meson_wdt_dev *meson_wdt; + const struct of_device_id *of_id; int err; meson_wdt = devm_kzalloc(&pdev->dev, sizeof(*meson_wdt), GFP_KERNEL); @@ -153,17 +175,28 @@ static int meson_wdt_probe(struct platform_device *pdev) if (IS_ERR(meson_wdt->wdt_base)) return PTR_ERR(meson_wdt->wdt_base); + of_id = of_match_device(meson_wdt_dt_ids, &pdev->dev); + if (!of_id) { + dev_err(&pdev->dev, "Unable to initialize WDT data\n"); + return -ENODEV; + } + meson_wdt->data = of_id->data; + meson_wdt->wdt_dev.parent = &pdev->dev; meson_wdt->wdt_dev.info = &meson_wdt_info; meson_wdt->wdt_dev.ops = &meson_wdt_ops; - meson_wdt->wdt_dev.timeout = MESON_WDT_TIMEOUT; - meson_wdt->wdt_dev.max_timeout = MESON_WDT_MAX_TIMEOUT; + meson_wdt->wdt_dev.max_timeout = + meson_wdt->data->terminal_count_mask / meson_wdt->data->count_unit; meson_wdt->wdt_dev.min_timeout = MESON_WDT_MIN_TIMEOUT; + meson_wdt->wdt_dev.timeout = min_t(unsigned int, + MESON_WDT_TIMEOUT, + meson_wdt->wdt_dev.max_timeout); watchdog_set_drvdata(&meson_wdt->wdt_dev, meson_wdt); watchdog_init_timeout(&meson_wdt->wdt_dev, timeout, &pdev->dev); watchdog_set_nowayout(&meson_wdt->wdt_dev, nowayout); + watchdog_set_restart_priority(&meson_wdt->wdt_dev, 128); meson_wdt_stop(&meson_wdt->wdt_dev); @@ -173,13 +206,6 @@ static int meson_wdt_probe(struct platform_device *pdev) platform_set_drvdata(pdev, meson_wdt); - meson_wdt->restart_handler.notifier_call = meson_restart_handle; - meson_wdt->restart_handler.priority = 128; - err = register_restart_handler(&meson_wdt->restart_handler); - if (err) - dev_err(&pdev->dev, - "cannot register restart handler (err=%d)\n", err); - dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", meson_wdt->wdt_dev.timeout, nowayout); @@ -190,8 +216,6 @@ static int meson_wdt_remove(struct platform_device *pdev) { struct meson_wdt_dev *meson_wdt = platform_get_drvdata(pdev); - unregister_restart_handler(&meson_wdt->restart_handler); - watchdog_unregister_device(&meson_wdt->wdt_dev); return 0; @@ -204,12 +228,6 @@ static void meson_wdt_shutdown(struct platform_device *pdev) meson_wdt_stop(&meson_wdt->wdt_dev); } -static const struct of_device_id meson_wdt_dt_ids[] = { - { .compatible = "amlogic,meson6-wdt" }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, meson_wdt_dt_ids); - static struct platform_driver meson_wdt_driver = { .probe = meson_wdt_probe, .remove = meson_wdt_remove, diff --git a/drivers/watchdog/moxart_wdt.c b/drivers/watchdog/moxart_wdt.c index 60b0605bd..885c81bc4 100644 --- a/drivers/watchdog/moxart_wdt.c +++ b/drivers/watchdog/moxart_wdt.c @@ -15,9 +15,7 @@ #include <linux/module.h> #include <linux/err.h> #include <linux/kernel.h> -#include <linux/notifier.h> #include <linux/platform_device.h> -#include <linux/reboot.h> #include <linux/watchdog.h> #include <linux/moduleparam.h> @@ -29,22 +27,19 @@ struct moxart_wdt_dev { struct watchdog_device dev; void __iomem *base; unsigned int clock_frequency; - struct notifier_block restart_handler; }; static int heartbeat; -static int moxart_restart_handle(struct notifier_block *this, - unsigned long mode, void *cmd) +static int moxart_wdt_restart(struct watchdog_device *wdt_dev) { - struct moxart_wdt_dev *moxart_wdt = container_of(this, - struct moxart_wdt_dev, - restart_handler); + struct moxart_wdt_dev *moxart_wdt = watchdog_get_drvdata(wdt_dev); + writel(1, moxart_wdt->base + REG_COUNT); writel(0x5ab9, moxart_wdt->base + REG_MODE); writel(0x03, moxart_wdt->base + REG_ENABLE); - return NOTIFY_DONE; + return 0; } static int moxart_wdt_stop(struct watchdog_device *wdt_dev) @@ -87,6 +82,7 @@ static const struct watchdog_ops moxart_wdt_ops = { .start = moxart_wdt_start, .stop = moxart_wdt_stop, .set_timeout = moxart_wdt_set_timeout, + .restart = moxart_wdt_restart, }; static int moxart_wdt_probe(struct platform_device *pdev) @@ -134,6 +130,7 @@ static int moxart_wdt_probe(struct platform_device *pdev) watchdog_init_timeout(&moxart_wdt->dev, heartbeat, dev); watchdog_set_nowayout(&moxart_wdt->dev, nowayout); + watchdog_set_restart_priority(&moxart_wdt->dev, 128); watchdog_set_drvdata(&moxart_wdt->dev, moxart_wdt); @@ -141,13 +138,6 @@ static int moxart_wdt_probe(struct platform_device *pdev) if (err) return err; - moxart_wdt->restart_handler.notifier_call = moxart_restart_handle; - moxart_wdt->restart_handler.priority = 128; - err = register_restart_handler(&moxart_wdt->restart_handler); - if (err) - dev_err(dev, "cannot register restart notifier (err=%d)\n", - err); - dev_dbg(dev, "Watchdog enabled (heartbeat=%d sec, nowayout=%d)\n", moxart_wdt->dev.timeout, nowayout); @@ -158,7 +148,6 @@ static int moxart_wdt_remove(struct platform_device *pdev) { struct moxart_wdt_dev *moxart_wdt = platform_get_drvdata(pdev); - unregister_restart_handler(&moxart_wdt->restart_handler); moxart_wdt_stop(&moxart_wdt->dev); return 0; diff --git a/drivers/watchdog/mt7621_wdt.c b/drivers/watchdog/mt7621_wdt.c new file mode 100644 index 000000000..4a2290f90 --- /dev/null +++ b/drivers/watchdog/mt7621_wdt.c @@ -0,0 +1,186 @@ +/* + * Ralink MT7621/MT7628 built-in hardware watchdog timer + * + * Copyright (C) 2014 John Crispin <blogic@openwrt.org> + * + * This driver was based on: drivers/watchdog/rt2880_wdt.c + * + * 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/clk.h> +#include <linux/reset.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/watchdog.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> + +#include <asm/mach-ralink/ralink_regs.h> + +#define SYSC_RSTSTAT 0x38 +#define WDT_RST_CAUSE BIT(1) + +#define RALINK_WDT_TIMEOUT 30 + +#define TIMER_REG_TMRSTAT 0x00 +#define TIMER_REG_TMR1LOAD 0x24 +#define TIMER_REG_TMR1CTL 0x20 + +#define TMR1CTL_ENABLE BIT(7) +#define TMR1CTL_RESTART BIT(9) +#define TMR1CTL_PRESCALE_SHIFT 16 + +static void __iomem *mt7621_wdt_base; +static struct reset_control *mt7621_wdt_reset; + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static inline void rt_wdt_w32(unsigned reg, u32 val) +{ + iowrite32(val, mt7621_wdt_base + reg); +} + +static inline u32 rt_wdt_r32(unsigned reg) +{ + return ioread32(mt7621_wdt_base + reg); +} + +static int mt7621_wdt_ping(struct watchdog_device *w) +{ + rt_wdt_w32(TIMER_REG_TMRSTAT, TMR1CTL_RESTART); + + return 0; +} + +static int mt7621_wdt_set_timeout(struct watchdog_device *w, unsigned int t) +{ + w->timeout = t; + rt_wdt_w32(TIMER_REG_TMR1LOAD, t * 1000); + mt7621_wdt_ping(w); + + return 0; +} + +static int mt7621_wdt_start(struct watchdog_device *w) +{ + u32 t; + + /* set the prescaler to 1ms == 1000us */ + rt_wdt_w32(TIMER_REG_TMR1CTL, 1000 << TMR1CTL_PRESCALE_SHIFT); + + mt7621_wdt_set_timeout(w, w->timeout); + + t = rt_wdt_r32(TIMER_REG_TMR1CTL); + t |= TMR1CTL_ENABLE; + rt_wdt_w32(TIMER_REG_TMR1CTL, t); + + return 0; +} + +static int mt7621_wdt_stop(struct watchdog_device *w) +{ + u32 t; + + mt7621_wdt_ping(w); + + t = rt_wdt_r32(TIMER_REG_TMR1CTL); + t &= ~TMR1CTL_ENABLE; + rt_wdt_w32(TIMER_REG_TMR1CTL, t); + + return 0; +} + +static int mt7621_wdt_bootcause(void) +{ + if (rt_sysc_r32(SYSC_RSTSTAT) & WDT_RST_CAUSE) + return WDIOF_CARDRESET; + + return 0; +} + +static struct watchdog_info mt7621_wdt_info = { + .identity = "Mediatek Watchdog", + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, +}; + +static struct watchdog_ops mt7621_wdt_ops = { + .owner = THIS_MODULE, + .start = mt7621_wdt_start, + .stop = mt7621_wdt_stop, + .ping = mt7621_wdt_ping, + .set_timeout = mt7621_wdt_set_timeout, +}; + +static struct watchdog_device mt7621_wdt_dev = { + .info = &mt7621_wdt_info, + .ops = &mt7621_wdt_ops, + .min_timeout = 1, + .max_timeout = 0xfffful / 1000, +}; + +static int mt7621_wdt_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mt7621_wdt_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mt7621_wdt_base)) + return PTR_ERR(mt7621_wdt_base); + + mt7621_wdt_reset = devm_reset_control_get(&pdev->dev, NULL); + if (!IS_ERR(mt7621_wdt_reset)) + reset_control_deassert(mt7621_wdt_reset); + + mt7621_wdt_dev.dev = &pdev->dev; + mt7621_wdt_dev.bootstatus = mt7621_wdt_bootcause(); + + watchdog_init_timeout(&mt7621_wdt_dev, mt7621_wdt_dev.max_timeout, + &pdev->dev); + watchdog_set_nowayout(&mt7621_wdt_dev, nowayout); + + ret = watchdog_register_device(&mt7621_wdt_dev); + + return 0; +} + +static int mt7621_wdt_remove(struct platform_device *pdev) +{ + watchdog_unregister_device(&mt7621_wdt_dev); + + return 0; +} + +static void mt7621_wdt_shutdown(struct platform_device *pdev) +{ + mt7621_wdt_stop(&mt7621_wdt_dev); +} + +static const struct of_device_id mt7621_wdt_match[] = { + { .compatible = "mediatek,mt7621-wdt" }, + {}, +}; +MODULE_DEVICE_TABLE(of, mt7621_wdt_match); + +static struct platform_driver mt7621_wdt_driver = { + .probe = mt7621_wdt_probe, + .remove = mt7621_wdt_remove, + .shutdown = mt7621_wdt_shutdown, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = mt7621_wdt_match, + }, +}; + +module_platform_driver(mt7621_wdt_driver); + +MODULE_DESCRIPTION("MediaTek MT762x hardware watchdog driver"); +MODULE_AUTHOR("John Crispin <blogic@openwrt.org"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/watchdog/mtk_wdt.c b/drivers/watchdog/mtk_wdt.c index b751f43d7..b78776c05 100644 --- a/drivers/watchdog/mtk_wdt.c +++ b/drivers/watchdog/mtk_wdt.c @@ -28,8 +28,6 @@ #include <linux/platform_device.h> #include <linux/types.h> #include <linux/watchdog.h> -#include <linux/notifier.h> -#include <linux/reboot.h> #include <linux/delay.h> #define WDT_MAX_TIMEOUT 31 @@ -64,16 +62,13 @@ static unsigned int timeout = WDT_MAX_TIMEOUT; struct mtk_wdt_dev { struct watchdog_device wdt_dev; void __iomem *wdt_base; - struct notifier_block restart_handler; }; -static int mtk_reset_handler(struct notifier_block *this, unsigned long mode, - void *cmd) +static int mtk_wdt_restart(struct watchdog_device *wdt_dev) { - struct mtk_wdt_dev *mtk_wdt; + struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev); void __iomem *wdt_base; - mtk_wdt = container_of(this, struct mtk_wdt_dev, restart_handler); wdt_base = mtk_wdt->wdt_base; while (1) { @@ -81,7 +76,7 @@ static int mtk_reset_handler(struct notifier_block *this, unsigned long mode, mdelay(5); } - return NOTIFY_DONE; + return 0; } static int mtk_wdt_ping(struct watchdog_device *wdt_dev) @@ -161,6 +156,7 @@ static const struct watchdog_ops mtk_wdt_ops = { .stop = mtk_wdt_stop, .ping = mtk_wdt_ping, .set_timeout = mtk_wdt_set_timeout, + .restart = mtk_wdt_restart, }; static int mtk_wdt_probe(struct platform_device *pdev) @@ -189,6 +185,7 @@ static int mtk_wdt_probe(struct platform_device *pdev) watchdog_init_timeout(&mtk_wdt->wdt_dev, timeout, &pdev->dev); watchdog_set_nowayout(&mtk_wdt->wdt_dev, nowayout); + watchdog_set_restart_priority(&mtk_wdt->wdt_dev, 128); watchdog_set_drvdata(&mtk_wdt->wdt_dev, mtk_wdt); @@ -198,13 +195,6 @@ static int mtk_wdt_probe(struct platform_device *pdev) if (unlikely(err)) return err; - mtk_wdt->restart_handler.notifier_call = mtk_reset_handler; - mtk_wdt->restart_handler.priority = 128; - err = register_restart_handler(&mtk_wdt->restart_handler); - if (err) - dev_warn(&pdev->dev, - "cannot register restart handler (err=%d)\n", err); - dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)\n", mtk_wdt->wdt_dev.timeout, nowayout); @@ -223,8 +213,6 @@ static int mtk_wdt_remove(struct platform_device *pdev) { struct mtk_wdt_dev *mtk_wdt = platform_get_drvdata(pdev); - unregister_restart_handler(&mtk_wdt->restart_handler); - watchdog_unregister_device(&mtk_wdt->wdt_dev); return 0; diff --git a/drivers/watchdog/omap_wdt.c b/drivers/watchdog/omap_wdt.c index 6f17c935a..1b02bfa81 100644 --- a/drivers/watchdog/omap_wdt.c +++ b/drivers/watchdog/omap_wdt.c @@ -271,7 +271,8 @@ static int omap_wdt_probe(struct platform_device *pdev) wdev->wdog.bootstatus = WDIOF_CARDRESET; } - omap_wdt_disable(wdev); + if (!early_enable) + omap_wdt_disable(wdev); ret = watchdog_register_device(&wdev->wdog); if (ret) { @@ -283,11 +284,11 @@ static int omap_wdt_probe(struct platform_device *pdev) readl_relaxed(wdev->base + OMAP_WATCHDOG_REV) & 0xFF, wdev->wdog.timeout); - pm_runtime_put_sync(wdev->dev); - if (early_enable) omap_wdt_start(&wdev->wdog); + pm_runtime_put(wdev->dev); + return 0; } diff --git a/drivers/watchdog/pcwd_usb.c b/drivers/watchdog/pcwd_usb.c index 1a11aedc4..68952d9cc 100644 --- a/drivers/watchdog/pcwd_usb.c +++ b/drivers/watchdog/pcwd_usb.c @@ -608,7 +608,7 @@ static int usb_pcwd_probe(struct usb_interface *interface, struct usb_host_interface *iface_desc; struct usb_endpoint_descriptor *endpoint; struct usb_pcwd_private *usb_pcwd = NULL; - int pipe, maxp; + int pipe; int retval = -ENOMEM; int got_fw_rev; unsigned char fw_rev_major, fw_rev_minor; @@ -641,7 +641,6 @@ static int usb_pcwd_probe(struct usb_interface *interface, /* get a handle to the interrupt data pipe */ pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); - maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); /* allocate memory for our device and initialize it */ usb_pcwd = kzalloc(sizeof(struct usb_pcwd_private), GFP_KERNEL); diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c index 773dcfaee..424f9a952 100644 --- a/drivers/watchdog/qcom-wdt.c +++ b/drivers/watchdog/qcom-wdt.c @@ -17,7 +17,6 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> -#include <linux/reboot.h> #include <linux/watchdog.h> #define WDT_RST 0x38 @@ -28,7 +27,6 @@ struct qcom_wdt { struct watchdog_device wdd; struct clk *clk; unsigned long rate; - struct notifier_block restart_nb; void __iomem *base; }; @@ -72,25 +70,9 @@ static int qcom_wdt_set_timeout(struct watchdog_device *wdd, return qcom_wdt_start(wdd); } -static const struct watchdog_ops qcom_wdt_ops = { - .start = qcom_wdt_start, - .stop = qcom_wdt_stop, - .ping = qcom_wdt_ping, - .set_timeout = qcom_wdt_set_timeout, - .owner = THIS_MODULE, -}; - -static const struct watchdog_info qcom_wdt_info = { - .options = WDIOF_KEEPALIVEPING - | WDIOF_MAGICCLOSE - | WDIOF_SETTIMEOUT, - .identity = KBUILD_MODNAME, -}; - -static int qcom_wdt_restart(struct notifier_block *nb, unsigned long action, - void *data) +static int qcom_wdt_restart(struct watchdog_device *wdd) { - struct qcom_wdt *wdt = container_of(nb, struct qcom_wdt, restart_nb); + struct qcom_wdt *wdt = to_qcom_wdt(wdd); u32 timeout; /* @@ -110,9 +92,25 @@ static int qcom_wdt_restart(struct notifier_block *nb, unsigned long action, wmb(); msleep(150); - return NOTIFY_DONE; + return 0; } +static const struct watchdog_ops qcom_wdt_ops = { + .start = qcom_wdt_start, + .stop = qcom_wdt_stop, + .ping = qcom_wdt_ping, + .set_timeout = qcom_wdt_set_timeout, + .restart = qcom_wdt_restart, + .owner = THIS_MODULE, +}; + +static const struct watchdog_info qcom_wdt_info = { + .options = WDIOF_KEEPALIVEPING + | WDIOF_MAGICCLOSE + | WDIOF_SETTIMEOUT, + .identity = KBUILD_MODNAME, +}; + static int qcom_wdt_probe(struct platform_device *pdev) { struct qcom_wdt *wdt; @@ -166,7 +164,6 @@ static int qcom_wdt_probe(struct platform_device *pdev) goto err_clk_unprepare; } - wdt->wdd.dev = &pdev->dev; wdt->wdd.info = &qcom_wdt_info; wdt->wdd.ops = &qcom_wdt_ops; wdt->wdd.min_timeout = 1; @@ -187,14 +184,6 @@ static int qcom_wdt_probe(struct platform_device *pdev) goto err_clk_unprepare; } - /* - * WDT restart notifier has priority 0 (use as a last resort) - */ - wdt->restart_nb.notifier_call = qcom_wdt_restart; - ret = register_restart_handler(&wdt->restart_nb); - if (ret) - dev_err(&pdev->dev, "failed to setup restart handler\n"); - platform_set_drvdata(pdev, wdt); return 0; @@ -207,7 +196,6 @@ static int qcom_wdt_remove(struct platform_device *pdev) { struct qcom_wdt *wdt = platform_get_drvdata(pdev); - unregister_restart_handler(&wdt->restart_nb); watchdog_unregister_device(&wdt->wdd); clk_disable_unprepare(wdt->clk); return 0; diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c index d781000c7..009345044 100644 --- a/drivers/watchdog/s3c2410_wdt.c +++ b/drivers/watchdog/s3c2410_wdt.c @@ -41,7 +41,6 @@ #include <linux/of.h> #include <linux/mfd/syscon.h> #include <linux/regmap.h> -#include <linux/reboot.h> #include <linux/delay.h> #define S3C2410_WTCON 0x00 @@ -130,7 +129,6 @@ struct s3c2410_wdt { unsigned long wtdat_save; struct watchdog_device wdt_device; struct notifier_block freq_transition; - struct notifier_block restart_handler; struct s3c2410_wdt_variant *drv_data; struct regmap *pmureg; }; @@ -351,6 +349,29 @@ static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeou return 0; } +static int s3c2410wdt_restart(struct watchdog_device *wdd) +{ + struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); + void __iomem *wdt_base = wdt->reg_base; + + /* disable watchdog, to be safe */ + writel(0, wdt_base + S3C2410_WTCON); + + /* put initial values into count and data */ + writel(0x80, wdt_base + S3C2410_WTCNT); + writel(0x80, wdt_base + S3C2410_WTDAT); + + /* set the watchdog to go and reset... */ + writel(S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV16 | + S3C2410_WTCON_RSTEN | S3C2410_WTCON_PRESCALE(0x20), + wdt_base + S3C2410_WTCON); + + /* wait for reset to assert... */ + mdelay(500); + + return 0; +} + #define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) static const struct watchdog_info s3c2410_wdt_ident = { @@ -365,6 +386,7 @@ static struct watchdog_ops s3c2410wdt_ops = { .stop = s3c2410wdt_stop, .ping = s3c2410wdt_keepalive, .set_timeout = s3c2410wdt_set_heartbeat, + .restart = s3c2410wdt_restart, }; static struct watchdog_device s3c2410_wdd = { @@ -452,31 +474,6 @@ static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt) } #endif -static int s3c2410wdt_restart(struct notifier_block *this, - unsigned long mode, void *cmd) -{ - struct s3c2410_wdt *wdt = container_of(this, struct s3c2410_wdt, - restart_handler); - void __iomem *wdt_base = wdt->reg_base; - - /* disable watchdog, to be safe */ - writel(0, wdt_base + S3C2410_WTCON); - - /* put initial values into count and data */ - writel(0x80, wdt_base + S3C2410_WTCNT); - writel(0x80, wdt_base + S3C2410_WTDAT); - - /* set the watchdog to go and reset... */ - writel(S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV16 | - S3C2410_WTCON_RSTEN | S3C2410_WTCON_PRESCALE(0x20), - wdt_base + S3C2410_WTCON); - - /* wait for reset to assert... */ - mdelay(500); - - return NOTIFY_DONE; -} - static inline unsigned int s3c2410wdt_get_bootstatus(struct s3c2410_wdt *wdt) { unsigned int rst_stat; @@ -605,6 +602,7 @@ static int s3c2410wdt_probe(struct platform_device *pdev) } watchdog_set_nowayout(&wdt->wdt_device, nowayout); + watchdog_set_restart_priority(&wdt->wdt_device, 128); wdt->wdt_device.bootstatus = s3c2410wdt_get_bootstatus(wdt); wdt->wdt_device.parent = &pdev->dev; @@ -632,12 +630,6 @@ static int s3c2410wdt_probe(struct platform_device *pdev) platform_set_drvdata(pdev, wdt); - wdt->restart_handler.notifier_call = s3c2410wdt_restart; - wdt->restart_handler.priority = 128; - ret = register_restart_handler(&wdt->restart_handler); - if (ret) - pr_err("cannot register restart handler, %d\n", ret); - /* print out a statement of readiness */ wtcon = readl(wdt->reg_base + S3C2410_WTCON); @@ -667,8 +659,6 @@ static int s3c2410wdt_remove(struct platform_device *dev) int ret; struct s3c2410_wdt *wdt = platform_get_drvdata(dev); - unregister_restart_handler(&wdt->restart_handler); - ret = s3c2410wdt_mask_and_disable_reset(wdt, true); if (ret < 0) return ret; diff --git a/drivers/watchdog/softdog.c b/drivers/watchdog/softdog.c index 0dc5e323d..99a06f9e3 100644 --- a/drivers/watchdog/softdog.c +++ b/drivers/watchdog/softdog.c @@ -43,7 +43,6 @@ #include <linux/types.h> #include <linux/timer.h> #include <linux/watchdog.h> -#include <linux/notifier.h> #include <linux/reboot.h> #include <linux/init.h> #include <linux/jiffies.h> @@ -87,6 +86,7 @@ static struct timer_list watchdog_ticktock = static void watchdog_fire(unsigned long data) { + module_put(THIS_MODULE); if (soft_noboot) pr_crit("Triggered - Reboot ignored\n"); else if (soft_panic) { @@ -105,13 +105,16 @@ static void watchdog_fire(unsigned long data) static int softdog_ping(struct watchdog_device *w) { - mod_timer(&watchdog_ticktock, jiffies+(w->timeout*HZ)); + if (!mod_timer(&watchdog_ticktock, jiffies+(w->timeout*HZ))) + __module_get(THIS_MODULE); return 0; } static int softdog_stop(struct watchdog_device *w) { - del_timer(&watchdog_ticktock); + if (del_timer(&watchdog_ticktock)) + module_put(THIS_MODULE); + return 0; } @@ -122,26 +125,9 @@ static int softdog_set_timeout(struct watchdog_device *w, unsigned int t) } /* - * Notifier for system down - */ - -static int softdog_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) -{ - if (code == SYS_DOWN || code == SYS_HALT) - /* Turn the WDT off */ - softdog_stop(NULL); - return NOTIFY_DONE; -} - -/* * Kernel Interfaces */ -static struct notifier_block softdog_notifier = { - .notifier_call = softdog_notify_sys, -}; - static struct watchdog_info softdog_info = { .identity = "Software Watchdog", .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, @@ -175,18 +161,11 @@ static int __init watchdog_init(void) softdog_dev.timeout = soft_margin; watchdog_set_nowayout(&softdog_dev, nowayout); - - ret = register_reboot_notifier(&softdog_notifier); - if (ret) { - pr_err("cannot register reboot notifier (err=%d)\n", ret); - return ret; - } + watchdog_stop_on_reboot(&softdog_dev); ret = watchdog_register_device(&softdog_dev); - if (ret) { - unregister_reboot_notifier(&softdog_notifier); + if (ret) return ret; - } pr_info("Software Watchdog Timer: 0.08 initialized. soft_noboot=%d soft_margin=%d sec soft_panic=%d (nowayout=%d)\n", soft_noboot, soft_margin, soft_panic, nowayout); @@ -197,7 +176,6 @@ static int __init watchdog_init(void) static void __exit watchdog_exit(void) { watchdog_unregister_device(&softdog_dev); - unregister_reboot_notifier(&softdog_notifier); } module_init(watchdog_init); diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index eb8044ef0..6467b91f2 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -306,6 +306,10 @@ static struct miscdevice sp5100_tco_miscdev = { static const struct pci_device_id sp5100_tco_pci_tbl[] = { { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_HUDSON2_SMBUS, PCI_ANY_ID, + PCI_ANY_ID, }, + { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, PCI_ANY_ID, + PCI_ANY_ID, }, { 0, }, /* End of list */ }; MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl); @@ -331,21 +335,24 @@ static unsigned char sp5100_tco_setupdevice(void) if (!sp5100_tco_pci) return 0; - pr_info("PCI Revision ID: 0x%x\n", sp5100_tco_pci->revision); + pr_info("PCI Vendor ID: 0x%x, Device ID: 0x%x, Revision ID: 0x%x\n", + sp5100_tco_pci->vendor, sp5100_tco_pci->device, + sp5100_tco_pci->revision); /* * Determine type of southbridge chipset. */ - if (sp5100_tco_pci->revision >= 0x40) { - dev_name = SB800_DEVNAME; - index_reg = SB800_IO_PM_INDEX_REG; - data_reg = SB800_IO_PM_DATA_REG; - base_addr = SB800_PM_WATCHDOG_BASE; - } else { + if (sp5100_tco_pci->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS && + sp5100_tco_pci->revision < 0x40) { dev_name = SP5100_DEVNAME; index_reg = SP5100_IO_PM_INDEX_REG; data_reg = SP5100_IO_PM_DATA_REG; base_addr = SP5100_PM_WATCHDOG_BASE; + } else { + dev_name = SB800_DEVNAME; + index_reg = SB800_IO_PM_INDEX_REG; + data_reg = SB800_IO_PM_DATA_REG; + base_addr = SB800_PM_WATCHDOG_BASE; } /* Request the IO ports used by this driver */ @@ -381,7 +388,12 @@ static unsigned char sp5100_tco_setupdevice(void) * Secondly, Find the watchdog timer MMIO address * from SBResource_MMIO register. */ - if (sp5100_tco_pci->revision >= 0x40) { + if (sp5100_tco_pci->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS && + sp5100_tco_pci->revision < 0x40) { + /* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */ + pci_read_config_dword(sp5100_tco_pci, + SP5100_SB_RESOURCE_MMIO_BASE, &val); + } else { /* Read SBResource_MMIO from AcpiMmioEn(PM_Reg: 24h) */ outb(SB800_PM_ACPI_MMIO_EN+3, SB800_IO_PM_INDEX_REG); val = inb(SB800_IO_PM_DATA_REG); @@ -391,10 +403,6 @@ static unsigned char sp5100_tco_setupdevice(void) val = val << 8 | inb(SB800_IO_PM_DATA_REG); outb(SB800_PM_ACPI_MMIO_EN+0, SB800_IO_PM_INDEX_REG); val = val << 8 | inb(SB800_IO_PM_DATA_REG); - } else { - /* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */ - pci_read_config_dword(sp5100_tco_pci, - SP5100_SB_RESOURCE_MMIO_BASE, &val); } /* The SBResource_MMIO is enabled and mapped memory space? */ diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c index 01d816251..e7a715e82 100644 --- a/drivers/watchdog/sp805_wdt.c +++ b/drivers/watchdog/sp805_wdt.c @@ -139,12 +139,11 @@ static int wdt_config(struct watchdog_device *wdd, bool ping) writel_relaxed(UNLOCK, wdt->base + WDTLOCK); writel_relaxed(wdt->load_val, wdt->base + WDTLOAD); + writel_relaxed(INT_MASK, wdt->base + WDTINTCLR); - if (!ping) { - writel_relaxed(INT_MASK, wdt->base + WDTINTCLR); + if (!ping) writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base + WDTCONTROL); - } writel_relaxed(LOCK, wdt->base + WDTLOCK); diff --git a/drivers/watchdog/stmp3xxx_rtc_wdt.c b/drivers/watchdog/stmp3xxx_rtc_wdt.c index 3ee6128a5..d8b11eb26 100644 --- a/drivers/watchdog/stmp3xxx_rtc_wdt.c +++ b/drivers/watchdog/stmp3xxx_rtc_wdt.c @@ -14,6 +14,8 @@ #include <linux/watchdog.h> #include <linux/platform_device.h> #include <linux/stmp3xxx_rtc_wdt.h> +#include <linux/notifier.h> +#include <linux/reboot.h> #define WDOG_TICK_RATE 1000 /* 1 kHz clock */ #define STMP3XXX_DEFAULT_TIMEOUT 19 @@ -69,6 +71,25 @@ static struct watchdog_device stmp3xxx_wdd = { .status = WATCHDOG_NOWAYOUT_INIT_STATUS, }; +static int wdt_notify_sys(struct notifier_block *nb, unsigned long code, + void *unused) +{ + switch (code) { + case SYS_DOWN: /* keep enabled, system might crash while going down */ + break; + case SYS_HALT: /* allow the system to actually halt */ + case SYS_POWER_OFF: + wdt_stop(&stmp3xxx_wdd); + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + static int stmp3xxx_wdt_probe(struct platform_device *pdev) { int ret; @@ -84,6 +105,9 @@ static int stmp3xxx_wdt_probe(struct platform_device *pdev) return ret; } + if (register_reboot_notifier(&wdt_notifier)) + dev_warn(&pdev->dev, "cannot register reboot notifier\n"); + dev_info(&pdev->dev, "initialized watchdog with heartbeat %ds\n", stmp3xxx_wdd.timeout); return 0; @@ -91,6 +115,7 @@ static int stmp3xxx_wdt_probe(struct platform_device *pdev) static int stmp3xxx_wdt_remove(struct platform_device *pdev) { + unregister_reboot_notifier(&wdt_notifier); watchdog_unregister_device(&stmp3xxx_wdd); return 0; } diff --git a/drivers/watchdog/sun4v_wdt.c b/drivers/watchdog/sun4v_wdt.c new file mode 100644 index 000000000..1467fe50a --- /dev/null +++ b/drivers/watchdog/sun4v_wdt.c @@ -0,0 +1,191 @@ +/* + * sun4v watchdog timer + * (c) Copyright 2016 Oracle Corporation + * + * Implement a simple watchdog driver using the built-in sun4v hypervisor + * watchdog support. If time expires, the hypervisor stops or bounces + * the guest domain. + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/watchdog.h> +#include <asm/hypervisor.h> +#include <asm/mdesc.h> + +#define WDT_TIMEOUT 60 +#define WDT_MAX_TIMEOUT 31536000 +#define WDT_MIN_TIMEOUT 1 +#define WDT_DEFAULT_RESOLUTION_MS 1000 /* 1 second */ + +static unsigned int timeout; +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=" + __MODULE_STRING(WDT_TIMEOUT) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, S_IRUGO); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static int sun4v_wdt_stop(struct watchdog_device *wdd) +{ + sun4v_mach_set_watchdog(0, NULL); + + return 0; +} + +static int sun4v_wdt_ping(struct watchdog_device *wdd) +{ + int hverr; + + /* + * HV watchdog timer will round up the timeout + * passed in to the nearest multiple of the + * watchdog resolution in milliseconds. + */ + hverr = sun4v_mach_set_watchdog(wdd->timeout * 1000, NULL); + if (hverr == HV_EINVAL) + return -EINVAL; + + return 0; +} + +static int sun4v_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + wdd->timeout = timeout; + + return 0; +} + +static const struct watchdog_info sun4v_wdt_ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE | + WDIOF_KEEPALIVEPING, + .identity = "sun4v hypervisor watchdog", + .firmware_version = 0, +}; + +static struct watchdog_ops sun4v_wdt_ops = { + .owner = THIS_MODULE, + .start = sun4v_wdt_ping, + .stop = sun4v_wdt_stop, + .ping = sun4v_wdt_ping, + .set_timeout = sun4v_wdt_set_timeout, +}; + +static struct watchdog_device wdd = { + .info = &sun4v_wdt_ident, + .ops = &sun4v_wdt_ops, + .min_timeout = WDT_MIN_TIMEOUT, + .max_timeout = WDT_MAX_TIMEOUT, + .timeout = WDT_TIMEOUT, +}; + +static int __init sun4v_wdt_init(void) +{ + struct mdesc_handle *handle; + u64 node; + const u64 *value; + int err = 0; + unsigned long major = 1, minor = 1; + + /* + * There are 2 properties that can be set from the control + * domain for the watchdog. + * watchdog-resolution + * watchdog-max-timeout + * + * We can expect a handle to be returned otherwise something + * serious is wrong. Correct to return -ENODEV here. + */ + + handle = mdesc_grab(); + if (!handle) + return -ENODEV; + + node = mdesc_node_by_name(handle, MDESC_NODE_NULL, "platform"); + err = -ENODEV; + if (node == MDESC_NODE_NULL) + goto out_release; + + /* + * This is a safe way to validate if we are on the right + * platform. + */ + if (sun4v_hvapi_register(HV_GRP_CORE, major, &minor)) + goto out_hv_unreg; + + /* Allow value of watchdog-resolution up to 1s (default) */ + value = mdesc_get_property(handle, node, "watchdog-resolution", NULL); + err = -EINVAL; + if (value) { + if (*value == 0 || + *value > WDT_DEFAULT_RESOLUTION_MS) + goto out_hv_unreg; + } + + value = mdesc_get_property(handle, node, "watchdog-max-timeout", NULL); + if (value) { + /* + * If the property value (in ms) is smaller than + * min_timeout, return -EINVAL. + */ + if (*value < wdd.min_timeout * 1000) + goto out_hv_unreg; + + /* + * If the property value is smaller than + * default max_timeout then set watchdog max_timeout to + * the value of the property in seconds. + */ + if (*value < wdd.max_timeout * 1000) + wdd.max_timeout = *value / 1000; + } + + watchdog_init_timeout(&wdd, timeout, NULL); + + watchdog_set_nowayout(&wdd, nowayout); + + err = watchdog_register_device(&wdd); + if (err) + goto out_hv_unreg; + + pr_info("initialized (timeout=%ds, nowayout=%d)\n", + wdd.timeout, nowayout); + + mdesc_release(handle); + + return 0; + +out_hv_unreg: + sun4v_hvapi_unregister(HV_GRP_CORE); + +out_release: + mdesc_release(handle); + return err; +} + +static void __exit sun4v_wdt_exit(void) +{ + sun4v_hvapi_unregister(HV_GRP_CORE); + watchdog_unregister_device(&wdd); +} + +module_init(sun4v_wdt_init); +module_exit(sun4v_wdt_exit); + +MODULE_AUTHOR("Wim Coekaerts <wim.coekaerts@oracle.com>"); +MODULE_DESCRIPTION("sun4v watchdog driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/sunxi_wdt.c b/drivers/watchdog/sunxi_wdt.c index 47bd8a14d..e027deb54 100644 --- a/drivers/watchdog/sunxi_wdt.c +++ b/drivers/watchdog/sunxi_wdt.c @@ -21,11 +21,9 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/moduleparam.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/types.h> #include <linux/watchdog.h> @@ -60,7 +58,6 @@ struct sunxi_wdt_dev { struct watchdog_device wdt_dev; void __iomem *wdt_base; const struct sunxi_wdt_reg *wdt_regs; - struct notifier_block restart_handler; }; /* @@ -86,12 +83,9 @@ static const int wdt_timeout_map[] = { }; -static int sunxi_restart_handle(struct notifier_block *this, unsigned long mode, - void *cmd) +static int sunxi_wdt_restart(struct watchdog_device *wdt_dev) { - struct sunxi_wdt_dev *sunxi_wdt = container_of(this, - struct sunxi_wdt_dev, - restart_handler); + struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); void __iomem *wdt_base = sunxi_wdt->wdt_base; const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs; u32 val; @@ -120,7 +114,7 @@ static int sunxi_restart_handle(struct notifier_block *this, unsigned long mode, val |= WDT_MODE_EN; writel(val, wdt_base + regs->wdt_mode); } - return NOTIFY_DONE; + return 0; } static int sunxi_wdt_ping(struct watchdog_device *wdt_dev) @@ -208,6 +202,7 @@ static const struct watchdog_ops sunxi_wdt_ops = { .stop = sunxi_wdt_stop, .ping = sunxi_wdt_ping, .set_timeout = sunxi_wdt_set_timeout, + .restart = sunxi_wdt_restart, }; static const struct sunxi_wdt_reg sun4i_wdt_reg = { @@ -268,6 +263,7 @@ static int sunxi_wdt_probe(struct platform_device *pdev) watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, &pdev->dev); watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout); + watchdog_set_restart_priority(&sunxi_wdt->wdt_dev, 128); watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt); @@ -277,13 +273,6 @@ static int sunxi_wdt_probe(struct platform_device *pdev) if (unlikely(err)) return err; - sunxi_wdt->restart_handler.notifier_call = sunxi_restart_handle; - sunxi_wdt->restart_handler.priority = 128; - err = register_restart_handler(&sunxi_wdt->restart_handler); - if (err) - dev_err(&pdev->dev, - "cannot register restart handler (err=%d)\n", err); - dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", sunxi_wdt->wdt_dev.timeout, nowayout); @@ -294,8 +283,6 @@ static int sunxi_wdt_remove(struct platform_device *pdev) { struct sunxi_wdt_dev *sunxi_wdt = platform_get_drvdata(pdev); - unregister_restart_handler(&sunxi_wdt->restart_handler); - watchdog_unregister_device(&sunxi_wdt->wdt_dev); watchdog_set_drvdata(&sunxi_wdt->wdt_dev, NULL); diff --git a/drivers/watchdog/tangox_wdt.c b/drivers/watchdog/tangox_wdt.c new file mode 100644 index 000000000..709c1ed6f --- /dev/null +++ b/drivers/watchdog/tangox_wdt.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2015 Mans Rullgard <mans@mansr.com> + * SMP86xx/SMP87xx Watchdog driver + * + * 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/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/notifier.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/watchdog.h> + +#define DEFAULT_TIMEOUT 30 + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static unsigned int timeout; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout"); + +/* + * Counter counts down from programmed value. Reset asserts when + * the counter reaches 1. + */ +#define WD_COUNTER 0 + +#define WD_CONFIG 4 +#define WD_CONFIG_XTAL_IN BIT(0) +#define WD_CONFIG_DISABLE BIT(31) + +struct tangox_wdt_device { + struct watchdog_device wdt; + void __iomem *base; + unsigned long clk_rate; + struct clk *clk; + struct notifier_block restart; +}; + +static int tangox_wdt_set_timeout(struct watchdog_device *wdt, + unsigned int new_timeout) +{ + wdt->timeout = new_timeout; + + return 0; +} + +static int tangox_wdt_start(struct watchdog_device *wdt) +{ + struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); + u32 ticks; + + ticks = 1 + wdt->timeout * dev->clk_rate; + writel(ticks, dev->base + WD_COUNTER); + + return 0; +} + +static int tangox_wdt_stop(struct watchdog_device *wdt) +{ + struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); + + writel(0, dev->base + WD_COUNTER); + + return 0; +} + +static unsigned int tangox_wdt_get_timeleft(struct watchdog_device *wdt) +{ + struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); + u32 count; + + count = readl(dev->base + WD_COUNTER); + + if (!count) + return 0; + + return (count - 1) / dev->clk_rate; +} + +static const struct watchdog_info tangox_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "tangox watchdog", +}; + +static const struct watchdog_ops tangox_wdt_ops = { + .start = tangox_wdt_start, + .stop = tangox_wdt_stop, + .set_timeout = tangox_wdt_set_timeout, + .get_timeleft = tangox_wdt_get_timeleft, +}; + +static int tangox_wdt_restart(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct tangox_wdt_device *dev = + container_of(nb, struct tangox_wdt_device, restart); + + writel(1, dev->base + WD_COUNTER); + + return NOTIFY_DONE; +} + +static int tangox_wdt_probe(struct platform_device *pdev) +{ + struct tangox_wdt_device *dev; + struct resource *res; + u32 config; + int err; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dev->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->base)) + return PTR_ERR(dev->base); + + dev->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dev->clk)) + return PTR_ERR(dev->clk); + + err = clk_prepare_enable(dev->clk); + if (err) + return err; + + dev->clk_rate = clk_get_rate(dev->clk); + + dev->wdt.parent = &pdev->dev; + dev->wdt.info = &tangox_wdt_info; + dev->wdt.ops = &tangox_wdt_ops; + dev->wdt.timeout = DEFAULT_TIMEOUT; + dev->wdt.min_timeout = 1; + dev->wdt.max_timeout = (U32_MAX - 1) / dev->clk_rate; + + watchdog_init_timeout(&dev->wdt, timeout, &pdev->dev); + watchdog_set_nowayout(&dev->wdt, nowayout); + watchdog_set_drvdata(&dev->wdt, dev); + + /* + * Deactivate counter if disable bit is set to avoid + * accidental reset. + */ + config = readl(dev->base + WD_CONFIG); + if (config & WD_CONFIG_DISABLE) + writel(0, dev->base + WD_COUNTER); + + writel(WD_CONFIG_XTAL_IN, dev->base + WD_CONFIG); + + /* + * Mark as active and restart with configured timeout if + * already running. + */ + if (readl(dev->base + WD_COUNTER)) { + set_bit(WDOG_ACTIVE, &dev->wdt.status); + tangox_wdt_start(&dev->wdt); + } + + err = watchdog_register_device(&dev->wdt); + if (err) { + clk_disable_unprepare(dev->clk); + return err; + } + + platform_set_drvdata(pdev, dev); + + dev->restart.notifier_call = tangox_wdt_restart; + dev->restart.priority = 128; + err = register_restart_handler(&dev->restart); + if (err) + dev_warn(&pdev->dev, "failed to register restart handler\n"); + + dev_info(&pdev->dev, "SMP86xx/SMP87xx watchdog registered\n"); + + return 0; +} + +static int tangox_wdt_remove(struct platform_device *pdev) +{ + struct tangox_wdt_device *dev = platform_get_drvdata(pdev); + + tangox_wdt_stop(&dev->wdt); + clk_disable_unprepare(dev->clk); + + unregister_restart_handler(&dev->restart); + watchdog_unregister_device(&dev->wdt); + + return 0; +} + +static const struct of_device_id tangox_wdt_dt_ids[] = { + { .compatible = "sigma,smp8642-wdt" }, + { .compatible = "sigma,smp8759-wdt" }, + { } +}; +MODULE_DEVICE_TABLE(of, tangox_wdt_dt_ids); + +static struct platform_driver tangox_wdt_driver = { + .probe = tangox_wdt_probe, + .remove = tangox_wdt_remove, + .driver = { + .name = "tangox-wdt", + .of_match_table = tangox_wdt_dt_ids, + }, +}; + +module_platform_driver(tangox_wdt_driver); + +MODULE_AUTHOR("Mans Rullgard <mans@mansr.com>"); +MODULE_DESCRIPTION("SMP86xx/SMP87xx Watchdog driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/ts4800_wdt.c b/drivers/watchdog/ts4800_wdt.c new file mode 100644 index 000000000..2b8de8602 --- /dev/null +++ b/drivers/watchdog/ts4800_wdt.c @@ -0,0 +1,215 @@ +/* + * Watchdog driver for TS-4800 based boards + * + * Copyright (c) 2015 - Savoir-faire Linux + * + * 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/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/watchdog.h> + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* possible feed values */ +#define TS4800_WDT_FEED_2S 0x1 +#define TS4800_WDT_FEED_10S 0x2 +#define TS4800_WDT_DISABLE 0x3 + +struct ts4800_wdt { + struct watchdog_device wdd; + struct regmap *regmap; + u32 feed_offset; + u32 feed_val; +}; + +/* + * TS-4800 supports the following timeout values: + * + * value desc + * --------------------- + * 0 feed for 338ms + * 1 feed for 2.706s + * 2 feed for 10.824s + * 3 disable watchdog + * + * Keep the regmap/timeout map ordered by timeout + */ +static const struct { + const int timeout; + const int regval; +} ts4800_wdt_map[] = { + { 2, TS4800_WDT_FEED_2S }, + { 10, TS4800_WDT_FEED_10S }, +}; + +#define MAX_TIMEOUT_INDEX (ARRAY_SIZE(ts4800_wdt_map) - 1) + +static void ts4800_write_feed(struct ts4800_wdt *wdt, u32 val) +{ + regmap_write(wdt->regmap, wdt->feed_offset, val); +} + +static int ts4800_wdt_start(struct watchdog_device *wdd) +{ + struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd); + + ts4800_write_feed(wdt, wdt->feed_val); + return 0; +} + +static int ts4800_wdt_stop(struct watchdog_device *wdd) +{ + struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd); + + ts4800_write_feed(wdt, TS4800_WDT_DISABLE); + return 0; +} + +static int ts4800_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd); + int i; + + for (i = 0; i < MAX_TIMEOUT_INDEX; i++) { + if (ts4800_wdt_map[i].timeout >= timeout) + break; + } + + wdd->timeout = ts4800_wdt_map[i].timeout; + wdt->feed_val = ts4800_wdt_map[i].regval; + + return 0; +} + +static const struct watchdog_ops ts4800_wdt_ops = { + .owner = THIS_MODULE, + .start = ts4800_wdt_start, + .stop = ts4800_wdt_stop, + .set_timeout = ts4800_wdt_set_timeout, +}; + +static const struct watchdog_info ts4800_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, + .identity = "TS-4800 Watchdog", +}; + +static int ts4800_wdt_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *syscon_np; + struct watchdog_device *wdd; + struct ts4800_wdt *wdt; + u32 reg; + int ret; + + syscon_np = of_parse_phandle(np, "syscon", 0); + if (!syscon_np) { + dev_err(&pdev->dev, "no syscon property\n"); + return -ENODEV; + } + + ret = of_property_read_u32_index(np, "syscon", 1, ®); + if (ret < 0) { + dev_err(&pdev->dev, "no offset in syscon\n"); + return ret; + } + + /* allocate memory for watchdog struct */ + wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + /* set regmap and offset to know where to write */ + wdt->feed_offset = reg; + wdt->regmap = syscon_node_to_regmap(syscon_np); + if (IS_ERR(wdt->regmap)) { + dev_err(&pdev->dev, "cannot get parent's regmap\n"); + return PTR_ERR(wdt->regmap); + } + + /* Initialize struct watchdog_device */ + wdd = &wdt->wdd; + wdd->parent = &pdev->dev; + wdd->info = &ts4800_wdt_info; + wdd->ops = &ts4800_wdt_ops; + wdd->min_timeout = ts4800_wdt_map[0].timeout; + wdd->max_timeout = ts4800_wdt_map[MAX_TIMEOUT_INDEX].timeout; + + watchdog_set_drvdata(wdd, wdt); + watchdog_set_nowayout(wdd, nowayout); + watchdog_init_timeout(wdd, 0, &pdev->dev); + + /* + * As this watchdog supports only a few values, ts4800_wdt_set_timeout + * must be called to initialize timeout and feed_val with valid values. + * Default to maximum timeout if none, or an invalid one, is provided in + * device tree. + */ + if (!wdd->timeout) + wdd->timeout = wdd->max_timeout; + ts4800_wdt_set_timeout(wdd, wdd->timeout); + + /* + * The feed register is write-only, so it is not possible to determine + * watchdog's state. Disable it to be in a known state. + */ + ts4800_wdt_stop(wdd); + + ret = watchdog_register_device(wdd); + if (ret) { + dev_err(&pdev->dev, + "failed to register watchdog device\n"); + return ret; + } + + platform_set_drvdata(pdev, wdt); + + dev_info(&pdev->dev, + "initialized (timeout = %d sec, nowayout = %d)\n", + wdd->timeout, nowayout); + + return 0; +} + +static int ts4800_wdt_remove(struct platform_device *pdev) +{ + struct ts4800_wdt *wdt = platform_get_drvdata(pdev); + + watchdog_unregister_device(&wdt->wdd); + + return 0; +} + +static const struct of_device_id ts4800_wdt_of_match[] = { + { .compatible = "technologic,ts4800-wdt", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ts4800_wdt_of_match); + +static struct platform_driver ts4800_wdt_driver = { + .probe = ts4800_wdt_probe, + .remove = ts4800_wdt_remove, + .driver = { + .name = "ts4800_wdt", + .of_match_table = ts4800_wdt_of_match, + }, +}; + +module_platform_driver(ts4800_wdt_driver); + +MODULE_AUTHOR("Damien Riegel <damien.riegel@savoirfairelinux.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:ts4800_wdt"); diff --git a/drivers/watchdog/w83627hf_wdt.c b/drivers/watchdog/w83627hf_wdt.c index 5824e25ee..cab14bc91 100644 --- a/drivers/watchdog/w83627hf_wdt.c +++ b/drivers/watchdog/w83627hf_wdt.c @@ -36,8 +36,6 @@ #include <linux/types.h> #include <linux/watchdog.h> #include <linux/ioport.h> -#include <linux/notifier.h> -#include <linux/reboot.h> #include <linux/init.h> #include <linux/io.h> @@ -288,18 +286,6 @@ static unsigned int wdt_get_time(struct watchdog_device *wdog) } /* - * Notifier for system down - */ -static int wdt_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) -{ - if (code == SYS_DOWN || code == SYS_HALT) - wdt_set_time(0); /* Turn the WDT off */ - - return NOTIFY_DONE; -} - -/* * Kernel Interfaces */ @@ -329,10 +315,6 @@ static struct watchdog_device wdt_dev = { * turn the timebomb registers off. */ -static struct notifier_block wdt_notifier = { - .notifier_call = wdt_notify_sys, -}; - static int wdt_find(int addr) { u8 val; @@ -456,6 +438,7 @@ static int __init wdt_init(void) watchdog_init_timeout(&wdt_dev, timeout, NULL); watchdog_set_nowayout(&wdt_dev, nowayout); + watchdog_stop_on_reboot(&wdt_dev); ret = w83627hf_init(&wdt_dev, chip); if (ret) { @@ -463,30 +446,19 @@ static int __init wdt_init(void) return ret; } - ret = register_reboot_notifier(&wdt_notifier); - if (ret != 0) { - pr_err("cannot register reboot notifier (err=%d)\n", ret); - return ret; - } - ret = watchdog_register_device(&wdt_dev); if (ret) - goto unreg_reboot; + return ret; pr_info("initialized. timeout=%d sec (nowayout=%d)\n", wdt_dev.timeout, nowayout); return ret; - -unreg_reboot: - unregister_reboot_notifier(&wdt_notifier); - return ret; } static void __exit wdt_exit(void) { watchdog_unregister_device(&wdt_dev); - unregister_reboot_notifier(&wdt_notifier); } module_init(wdt_init); diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 873f13972..e600fd93b 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -32,6 +32,7 @@ #include <linux/types.h> /* For standard types */ #include <linux/errno.h> /* For the -ENODEV/... values */ #include <linux/kernel.h> /* For printk/panic/... */ +#include <linux/reboot.h> /* For restart handler */ #include <linux/watchdog.h> /* For watchdog specific items */ #include <linux/init.h> /* For __init/__exit/... */ #include <linux/idr.h> /* For ida_* macros */ @@ -41,7 +42,6 @@ #include "watchdog_core.h" /* For watchdog_dev_register/... */ static DEFINE_IDA(watchdog_ida); -static struct class *watchdog_class; /* * Deferred Registration infrastructure. @@ -137,9 +137,63 @@ int watchdog_init_timeout(struct watchdog_device *wdd, } EXPORT_SYMBOL_GPL(watchdog_init_timeout); +static int watchdog_reboot_notifier(struct notifier_block *nb, + unsigned long code, void *data) +{ + struct watchdog_device *wdd = container_of(nb, struct watchdog_device, + reboot_nb); + + if (code == SYS_DOWN || code == SYS_HALT) { + if (watchdog_active(wdd)) { + int ret; + + ret = wdd->ops->stop(wdd); + if (ret) + return NOTIFY_BAD; + } + } + + return NOTIFY_DONE; +} + +static int watchdog_restart_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct watchdog_device *wdd = container_of(nb, struct watchdog_device, + restart_nb); + + int ret; + + ret = wdd->ops->restart(wdd); + if (ret) + return NOTIFY_BAD; + + return NOTIFY_DONE; +} + +/** + * watchdog_set_restart_priority - Change priority of restart handler + * @wdd: watchdog device + * @priority: priority of the restart handler, should follow these guidelines: + * 0: use watchdog's restart function as last resort, has limited restart + * capabilies + * 128: default restart handler, use if no other handler is expected to be + * available and/or if restart is sufficient to restart the entire system + * 255: preempt all other handlers + * + * If a wdd->ops->restart function is provided when watchdog_register_device is + * called, it will be registered as a restart handler with the priority given + * here. + */ +void watchdog_set_restart_priority(struct watchdog_device *wdd, int priority) +{ + wdd->restart_nb.priority = priority; +} +EXPORT_SYMBOL_GPL(watchdog_set_restart_priority); + static int __watchdog_register_device(struct watchdog_device *wdd) { - int ret, id = -1, devno; + int ret, id = -1; if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL) return -EINVAL; @@ -156,8 +210,6 @@ static int __watchdog_register_device(struct watchdog_device *wdd) * corrupted in a later stage then we expect a kernel panic! */ - mutex_init(&wdd->lock); - /* Use alias for watchdog id if possible */ if (wdd->parent) { ret = of_alias_get_id(wdd->parent->of_node, "watchdog"); @@ -192,14 +244,26 @@ static int __watchdog_register_device(struct watchdog_device *wdd) } } - devno = wdd->cdev.dev; - wdd->dev = device_create(watchdog_class, wdd->parent, devno, - NULL, "watchdog%d", wdd->id); - if (IS_ERR(wdd->dev)) { - watchdog_dev_unregister(wdd); - ida_simple_remove(&watchdog_ida, id); - ret = PTR_ERR(wdd->dev); - return ret; + if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status)) { + wdd->reboot_nb.notifier_call = watchdog_reboot_notifier; + + ret = register_reboot_notifier(&wdd->reboot_nb); + if (ret) { + pr_err("watchdog%d: Cannot register reboot notifier (%d)\n", + wdd->id, ret); + watchdog_dev_unregister(wdd); + ida_simple_remove(&watchdog_ida, wdd->id); + return ret; + } + } + + if (wdd->ops->restart) { + wdd->restart_nb.notifier_call = watchdog_restart_notifier; + + ret = register_restart_handler(&wdd->restart_nb); + if (ret) + pr_warn("watchog%d: Cannot register restart handler (%d)\n", + wdd->id, ret); } return 0; @@ -232,19 +296,17 @@ EXPORT_SYMBOL_GPL(watchdog_register_device); static void __watchdog_unregister_device(struct watchdog_device *wdd) { - int ret; - int devno; - if (wdd == NULL) return; - devno = wdd->cdev.dev; - ret = watchdog_dev_unregister(wdd); - if (ret) - pr_err("error unregistering /dev/watchdog (err=%d)\n", ret); - device_destroy(watchdog_class, devno); + if (wdd->ops->restart) + unregister_restart_handler(&wdd->restart_nb); + + if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status)) + unregister_reboot_notifier(&wdd->reboot_nb); + + watchdog_dev_unregister(wdd); ida_simple_remove(&watchdog_ida, wdd->id); - wdd->dev = NULL; } /** @@ -287,17 +349,9 @@ static int __init watchdog_init(void) { int err; - watchdog_class = class_create(THIS_MODULE, "watchdog"); - if (IS_ERR(watchdog_class)) { - pr_err("couldn't create class\n"); - return PTR_ERR(watchdog_class); - } - err = watchdog_dev_init(); - if (err < 0) { - class_destroy(watchdog_class); + if (err < 0) return err; - } watchdog_deferred_registration(); return 0; @@ -306,7 +360,6 @@ static int __init watchdog_init(void) static void __exit watchdog_exit(void) { watchdog_dev_exit(); - class_destroy(watchdog_class); ida_destroy(&watchdog_ida); } diff --git a/drivers/watchdog/watchdog_core.h b/drivers/watchdog/watchdog_core.h index 6c951418f..86ff962d1 100644 --- a/drivers/watchdog/watchdog_core.h +++ b/drivers/watchdog/watchdog_core.h @@ -32,6 +32,6 @@ * Functions/procedures to be called by the core */ extern int watchdog_dev_register(struct watchdog_device *); -extern int watchdog_dev_unregister(struct watchdog_device *); +extern void watchdog_dev_unregister(struct watchdog_device *); extern int __init watchdog_dev_init(void); extern void __exit watchdog_dev_exit(void); diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 56a649e66..ba2ecce4a 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -32,27 +32,51 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include <linux/module.h> /* For module stuff/... */ -#include <linux/types.h> /* For standard types (like size_t) */ +#include <linux/cdev.h> /* For character device */ #include <linux/errno.h> /* For the -ENODEV/... values */ -#include <linux/kernel.h> /* For printk/panic/... */ #include <linux/fs.h> /* For file operations */ -#include <linux/watchdog.h> /* For watchdog specific items */ -#include <linux/miscdevice.h> /* For handling misc devices */ #include <linux/init.h> /* For __init/__exit/... */ +#include <linux/kernel.h> /* For printk/panic/... */ +#include <linux/kref.h> /* For data references */ +#include <linux/miscdevice.h> /* For handling misc devices */ +#include <linux/module.h> /* For module stuff/... */ +#include <linux/mutex.h> /* For mutexes */ +#include <linux/slab.h> /* For memory functions */ +#include <linux/types.h> /* For standard types (like size_t) */ +#include <linux/watchdog.h> /* For watchdog specific items */ #include <linux/uaccess.h> /* For copy_to_user/put_user/... */ #include "watchdog_core.h" +/* + * struct watchdog_core_data - watchdog core internal data + * @kref: Reference count. + * @cdev: The watchdog's Character device. + * @wdd: Pointer to watchdog device. + * @lock: Lock for watchdog core. + * @status: Watchdog core internal status bits. + */ +struct watchdog_core_data { + struct kref kref; + struct cdev cdev; + struct watchdog_device *wdd; + struct mutex lock; + unsigned long status; /* Internal status bits */ +#define _WDOG_DEV_OPEN 0 /* Opened ? */ +#define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */ +}; + /* the dev_t structure to store the dynamically allocated watchdog devices */ static dev_t watchdog_devt; -/* the watchdog device behind /dev/watchdog */ -static struct watchdog_device *old_wdd; +/* Reference to watchdog device behind /dev/watchdog */ +static struct watchdog_core_data *old_wd_data; /* * watchdog_ping: ping the watchdog. * @wdd: the watchdog device to ping * + * The caller must hold wd_data->lock. + * * If the watchdog has no own ping operation then it needs to be * restarted via the start operation. This wrapper function does * exactly that. @@ -61,25 +85,16 @@ static struct watchdog_device *old_wdd; static int watchdog_ping(struct watchdog_device *wdd) { - int err = 0; - - mutex_lock(&wdd->lock); - - if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { - err = -ENODEV; - goto out_ping; - } + int err; if (!watchdog_active(wdd)) - goto out_ping; + return 0; if (wdd->ops->ping) err = wdd->ops->ping(wdd); /* ping the watchdog */ else err = wdd->ops->start(wdd); /* restart watchdog */ -out_ping: - mutex_unlock(&wdd->lock); return err; } @@ -87,6 +102,8 @@ out_ping: * watchdog_start: wrapper to start the watchdog. * @wdd: the watchdog device to start * + * The caller must hold wd_data->lock. + * * Start the watchdog if it is not active and mark it active. * This function returns zero on success or a negative errno code for * failure. @@ -94,24 +111,15 @@ out_ping: static int watchdog_start(struct watchdog_device *wdd) { - int err = 0; - - mutex_lock(&wdd->lock); - - if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { - err = -ENODEV; - goto out_start; - } + int err; if (watchdog_active(wdd)) - goto out_start; + return 0; err = wdd->ops->start(wdd); if (err == 0) set_bit(WDOG_ACTIVE, &wdd->status); -out_start: - mutex_unlock(&wdd->lock); return err; } @@ -119,6 +127,8 @@ out_start: * watchdog_stop: wrapper to stop the watchdog. * @wdd: the watchdog device to stop * + * The caller must hold wd_data->lock. + * * Stop the watchdog if it is still active and unmark it active. * This function returns zero on success or a negative errno code for * failure. @@ -127,93 +137,59 @@ out_start: static int watchdog_stop(struct watchdog_device *wdd) { - int err = 0; - - mutex_lock(&wdd->lock); - - if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { - err = -ENODEV; - goto out_stop; - } + int err; if (!watchdog_active(wdd)) - goto out_stop; + return 0; if (test_bit(WDOG_NO_WAY_OUT, &wdd->status)) { - dev_info(wdd->dev, "nowayout prevents watchdog being stopped!\n"); - err = -EBUSY; - goto out_stop; + pr_info("watchdog%d: nowayout prevents watchdog being stopped!\n", + wdd->id); + return -EBUSY; } err = wdd->ops->stop(wdd); if (err == 0) clear_bit(WDOG_ACTIVE, &wdd->status); -out_stop: - mutex_unlock(&wdd->lock); return err; } /* * watchdog_get_status: wrapper to get the watchdog status * @wdd: the watchdog device to get the status from - * @status: the status of the watchdog device + * + * The caller must hold wd_data->lock. * * Get the watchdog's status flags. */ -static int watchdog_get_status(struct watchdog_device *wdd, - unsigned int *status) +static unsigned int watchdog_get_status(struct watchdog_device *wdd) { - int err = 0; - - *status = 0; if (!wdd->ops->status) - return -EOPNOTSUPP; - - mutex_lock(&wdd->lock); - - if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { - err = -ENODEV; - goto out_status; - } - - *status = wdd->ops->status(wdd); + return 0; -out_status: - mutex_unlock(&wdd->lock); - return err; + return wdd->ops->status(wdd); } /* * watchdog_set_timeout: set the watchdog timer timeout * @wdd: the watchdog device to set the timeout for * @timeout: timeout to set in seconds + * + * The caller must hold wd_data->lock. */ static int watchdog_set_timeout(struct watchdog_device *wdd, unsigned int timeout) { - int err; - if (!wdd->ops->set_timeout || !(wdd->info->options & WDIOF_SETTIMEOUT)) return -EOPNOTSUPP; if (watchdog_timeout_invalid(wdd, timeout)) return -EINVAL; - mutex_lock(&wdd->lock); - - if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { - err = -ENODEV; - goto out_timeout; - } - - err = wdd->ops->set_timeout(wdd, timeout); - -out_timeout: - mutex_unlock(&wdd->lock); - return err; + return wdd->ops->set_timeout(wdd, timeout); } /* @@ -221,59 +197,156 @@ out_timeout: * @wdd: the watchdog device to get the remaining time from * @timeleft: the time that's left * + * The caller must hold wd_data->lock. + * * Get the time before a watchdog will reboot (if not pinged). */ static int watchdog_get_timeleft(struct watchdog_device *wdd, unsigned int *timeleft) { - int err = 0; - *timeleft = 0; + if (!wdd->ops->get_timeleft) return -EOPNOTSUPP; - mutex_lock(&wdd->lock); + *timeleft = wdd->ops->get_timeleft(wdd); - if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { - err = -ENODEV; - goto out_timeleft; - } + return 0; +} - *timeleft = wdd->ops->get_timeleft(wdd); +#ifdef CONFIG_WATCHDOG_SYSFS +static ssize_t nowayout_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); -out_timeleft: - mutex_unlock(&wdd->lock); - return err; + return sprintf(buf, "%d\n", !!test_bit(WDOG_NO_WAY_OUT, &wdd->status)); +} +static DEVICE_ATTR_RO(nowayout); + +static ssize_t status_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + struct watchdog_core_data *wd_data = wdd->wd_data; + unsigned int status; + + mutex_lock(&wd_data->lock); + status = watchdog_get_status(wdd); + mutex_unlock(&wd_data->lock); + + return sprintf(buf, "%u\n", status); +} +static DEVICE_ATTR_RO(status); + +static ssize_t bootstatus_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", wdd->bootstatus); +} +static DEVICE_ATTR_RO(bootstatus); + +static ssize_t timeleft_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + struct watchdog_core_data *wd_data = wdd->wd_data; + ssize_t status; + unsigned int val; + + mutex_lock(&wd_data->lock); + status = watchdog_get_timeleft(wdd, &val); + mutex_unlock(&wd_data->lock); + if (!status) + status = sprintf(buf, "%u\n", val); + + return status; } +static DEVICE_ATTR_RO(timeleft); + +static ssize_t timeout_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", wdd->timeout); +} +static DEVICE_ATTR_RO(timeout); + +static ssize_t identity_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", wdd->info->identity); +} +static DEVICE_ATTR_RO(identity); + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + if (watchdog_active(wdd)) + return sprintf(buf, "active\n"); + + return sprintf(buf, "inactive\n"); +} +static DEVICE_ATTR_RO(state); + +static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr, + int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct watchdog_device *wdd = dev_get_drvdata(dev); + umode_t mode = attr->mode; + + if (attr == &dev_attr_status.attr && !wdd->ops->status) + mode = 0; + else if (attr == &dev_attr_timeleft.attr && !wdd->ops->get_timeleft) + mode = 0; + + return mode; +} +static struct attribute *wdt_attrs[] = { + &dev_attr_state.attr, + &dev_attr_identity.attr, + &dev_attr_timeout.attr, + &dev_attr_timeleft.attr, + &dev_attr_bootstatus.attr, + &dev_attr_status.attr, + &dev_attr_nowayout.attr, + NULL, +}; + +static const struct attribute_group wdt_group = { + .attrs = wdt_attrs, + .is_visible = wdt_is_visible, +}; +__ATTRIBUTE_GROUPS(wdt); +#else +#define wdt_groups NULL +#endif /* * watchdog_ioctl_op: call the watchdog drivers ioctl op if defined * @wdd: the watchdog device to do the ioctl on * @cmd: watchdog command * @arg: argument pointer + * + * The caller must hold wd_data->lock. */ static int watchdog_ioctl_op(struct watchdog_device *wdd, unsigned int cmd, unsigned long arg) { - int err; - if (!wdd->ops->ioctl) return -ENOIOCTLCMD; - mutex_lock(&wdd->lock); - - if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { - err = -ENODEV; - goto out_ioctl; - } - - err = wdd->ops->ioctl(wdd, cmd, arg); - -out_ioctl: - mutex_unlock(&wdd->lock); - return err; + return wdd->ops->ioctl(wdd, cmd, arg); } /* @@ -291,10 +364,11 @@ out_ioctl: static ssize_t watchdog_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { - struct watchdog_device *wdd = file->private_data; + struct watchdog_core_data *wd_data = file->private_data; + struct watchdog_device *wdd; + int err; size_t i; char c; - int err; if (len == 0) return 0; @@ -303,18 +377,25 @@ static ssize_t watchdog_write(struct file *file, const char __user *data, * Note: just in case someone wrote the magic character * five months ago... */ - clear_bit(WDOG_ALLOW_RELEASE, &wdd->status); + clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status); /* scan to see whether or not we got the magic character */ for (i = 0; i != len; i++) { if (get_user(c, data + i)) return -EFAULT; if (c == 'V') - set_bit(WDOG_ALLOW_RELEASE, &wdd->status); + set_bit(_WDOG_ALLOW_RELEASE, &wd_data->status); } /* someone wrote to us, so we send the watchdog a keepalive ping */ - err = watchdog_ping(wdd); + + err = -ENODEV; + mutex_lock(&wd_data->lock); + wdd = wd_data->wdd; + if (wdd) + err = watchdog_ping(wdd); + mutex_unlock(&wd_data->lock); + if (err < 0) return err; @@ -334,71 +415,94 @@ static ssize_t watchdog_write(struct file *file, const char __user *data, static long watchdog_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { - struct watchdog_device *wdd = file->private_data; + struct watchdog_core_data *wd_data = file->private_data; void __user *argp = (void __user *)arg; + struct watchdog_device *wdd; int __user *p = argp; unsigned int val; int err; + mutex_lock(&wd_data->lock); + + wdd = wd_data->wdd; + if (!wdd) { + err = -ENODEV; + goto out_ioctl; + } + err = watchdog_ioctl_op(wdd, cmd, arg); if (err != -ENOIOCTLCMD) - return err; + goto out_ioctl; switch (cmd) { case WDIOC_GETSUPPORT: - return copy_to_user(argp, wdd->info, + err = copy_to_user(argp, wdd->info, sizeof(struct watchdog_info)) ? -EFAULT : 0; + break; case WDIOC_GETSTATUS: - err = watchdog_get_status(wdd, &val); - if (err == -ENODEV) - return err; - return put_user(val, p); + val = watchdog_get_status(wdd); + err = put_user(val, p); + break; case WDIOC_GETBOOTSTATUS: - return put_user(wdd->bootstatus, p); + err = put_user(wdd->bootstatus, p); + break; case WDIOC_SETOPTIONS: - if (get_user(val, p)) - return -EFAULT; + if (get_user(val, p)) { + err = -EFAULT; + break; + } if (val & WDIOS_DISABLECARD) { err = watchdog_stop(wdd); if (err < 0) - return err; + break; } - if (val & WDIOS_ENABLECARD) { + if (val & WDIOS_ENABLECARD) err = watchdog_start(wdd); - if (err < 0) - return err; - } - return 0; + break; case WDIOC_KEEPALIVE: - if (!(wdd->info->options & WDIOF_KEEPALIVEPING)) - return -EOPNOTSUPP; - return watchdog_ping(wdd); + if (!(wdd->info->options & WDIOF_KEEPALIVEPING)) { + err = -EOPNOTSUPP; + break; + } + err = watchdog_ping(wdd); + break; case WDIOC_SETTIMEOUT: - if (get_user(val, p)) - return -EFAULT; + if (get_user(val, p)) { + err = -EFAULT; + break; + } err = watchdog_set_timeout(wdd, val); if (err < 0) - return err; + break; /* If the watchdog is active then we send a keepalive ping * to make sure that the watchdog keep's running (and if * possible that it takes the new timeout) */ err = watchdog_ping(wdd); if (err < 0) - return err; + break; /* Fall */ case WDIOC_GETTIMEOUT: /* timeout == 0 means that we don't know the timeout */ - if (wdd->timeout == 0) - return -EOPNOTSUPP; - return put_user(wdd->timeout, p); + if (wdd->timeout == 0) { + err = -EOPNOTSUPP; + break; + } + err = put_user(wdd->timeout, p); + break; case WDIOC_GETTIMELEFT: err = watchdog_get_timeleft(wdd, &val); - if (err) - return err; - return put_user(val, p); + if (err < 0) + break; + err = put_user(val, p); + break; default: - return -ENOTTY; + err = -ENOTTY; + break; } + +out_ioctl: + mutex_unlock(&wd_data->lock); + return err; } /* @@ -413,45 +517,59 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, static int watchdog_open(struct inode *inode, struct file *file) { - int err = -EBUSY; + struct watchdog_core_data *wd_data; struct watchdog_device *wdd; + int err; /* Get the corresponding watchdog device */ if (imajor(inode) == MISC_MAJOR) - wdd = old_wdd; + wd_data = old_wd_data; else - wdd = container_of(inode->i_cdev, struct watchdog_device, cdev); + wd_data = container_of(inode->i_cdev, struct watchdog_core_data, + cdev); /* the watchdog is single open! */ - if (test_and_set_bit(WDOG_DEV_OPEN, &wdd->status)) + if (test_and_set_bit(_WDOG_DEV_OPEN, &wd_data->status)) return -EBUSY; + wdd = wd_data->wdd; + /* * If the /dev/watchdog device is open, we don't want the module * to be unloaded. */ - if (!try_module_get(wdd->ops->owner)) - goto out; + if (!try_module_get(wdd->ops->owner)) { + err = -EBUSY; + goto out_clear; + } err = watchdog_start(wdd); if (err < 0) goto out_mod; - file->private_data = wdd; + file->private_data = wd_data; - if (wdd->ops->ref) - wdd->ops->ref(wdd); + kref_get(&wd_data->kref); /* dev/watchdog is a virtual (and thus non-seekable) filesystem */ return nonseekable_open(inode, file); out_mod: - module_put(wdd->ops->owner); -out: - clear_bit(WDOG_DEV_OPEN, &wdd->status); + module_put(wd_data->wdd->ops->owner); +out_clear: + clear_bit(_WDOG_DEV_OPEN, &wd_data->status); return err; } +static void watchdog_core_data_release(struct kref *kref) +{ + struct watchdog_core_data *wd_data; + + wd_data = container_of(kref, struct watchdog_core_data, kref); + + kfree(wd_data); +} + /* * watchdog_release: release the watchdog device. * @inode: inode of device @@ -464,9 +582,16 @@ out: static int watchdog_release(struct inode *inode, struct file *file) { - struct watchdog_device *wdd = file->private_data; + struct watchdog_core_data *wd_data = file->private_data; + struct watchdog_device *wdd; int err = -EBUSY; + mutex_lock(&wd_data->lock); + + wdd = wd_data->wdd; + if (!wdd) + goto done; + /* * We only stop the watchdog if we received the magic character * or if WDIOF_MAGICCLOSE is not set. If nowayout was set then @@ -474,29 +599,24 @@ static int watchdog_release(struct inode *inode, struct file *file) */ if (!test_bit(WDOG_ACTIVE, &wdd->status)) err = 0; - else if (test_and_clear_bit(WDOG_ALLOW_RELEASE, &wdd->status) || + else if (test_and_clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status) || !(wdd->info->options & WDIOF_MAGICCLOSE)) err = watchdog_stop(wdd); /* If the watchdog was not stopped, send a keepalive ping */ if (err < 0) { - mutex_lock(&wdd->lock); - if (!test_bit(WDOG_UNREGISTERED, &wdd->status)) - dev_crit(wdd->dev, "watchdog did not stop!\n"); - mutex_unlock(&wdd->lock); + pr_crit("watchdog%d: watchdog did not stop!\n", wdd->id); watchdog_ping(wdd); } - /* Allow the owner module to be unloaded again */ - module_put(wdd->ops->owner); - /* make sure that /dev/watchdog can be re-opened */ - clear_bit(WDOG_DEV_OPEN, &wdd->status); - - /* Note wdd may be gone after this, do not use after this! */ - if (wdd->ops->unref) - wdd->ops->unref(wdd); + clear_bit(_WDOG_DEV_OPEN, &wd_data->status); +done: + mutex_unlock(&wd_data->lock); + /* Allow the owner module to be unloaded again */ + module_put(wd_data->cdev.owner); + kref_put(&wd_data->kref, watchdog_core_data_release); return 0; } @@ -515,20 +635,31 @@ static struct miscdevice watchdog_miscdev = { }; /* - * watchdog_dev_register: register a watchdog device + * watchdog_cdev_register: register watchdog character device * @wdd: watchdog device + * @devno: character device number * - * Register a watchdog device including handling the legacy + * Register a watchdog character device including handling the legacy * /dev/watchdog node. /dev/watchdog is actually a miscdevice and * thus we set it up like that. */ -int watchdog_dev_register(struct watchdog_device *wdd) +static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) { - int err, devno; + struct watchdog_core_data *wd_data; + int err; + + wd_data = kzalloc(sizeof(struct watchdog_core_data), GFP_KERNEL); + if (!wd_data) + return -ENOMEM; + kref_init(&wd_data->kref); + mutex_init(&wd_data->lock); + + wd_data->wdd = wdd; + wdd->wd_data = wd_data; if (wdd->id == 0) { - old_wdd = wdd; + old_wd_data = wd_data; watchdog_miscdev.parent = wdd->parent; err = misc_register(&watchdog_miscdev); if (err != 0) { @@ -537,48 +668,106 @@ int watchdog_dev_register(struct watchdog_device *wdd) if (err == -EBUSY) pr_err("%s: a legacy watchdog module is probably present.\n", wdd->info->identity); - old_wdd = NULL; + old_wd_data = NULL; + kfree(wd_data); return err; } } /* Fill in the data structures */ - devno = MKDEV(MAJOR(watchdog_devt), wdd->id); - cdev_init(&wdd->cdev, &watchdog_fops); - wdd->cdev.owner = wdd->ops->owner; + cdev_init(&wd_data->cdev, &watchdog_fops); + wd_data->cdev.owner = wdd->ops->owner; /* Add the device */ - err = cdev_add(&wdd->cdev, devno, 1); + err = cdev_add(&wd_data->cdev, devno, 1); if (err) { pr_err("watchdog%d unable to add device %d:%d\n", wdd->id, MAJOR(watchdog_devt), wdd->id); if (wdd->id == 0) { misc_deregister(&watchdog_miscdev); - old_wdd = NULL; + old_wd_data = NULL; + kref_put(&wd_data->kref, watchdog_core_data_release); } } return err; } /* - * watchdog_dev_unregister: unregister a watchdog device + * watchdog_cdev_unregister: unregister watchdog character device * @watchdog: watchdog device * - * Unregister the watchdog and if needed the legacy /dev/watchdog device. + * Unregister watchdog character device and if needed the legacy + * /dev/watchdog device. */ -int watchdog_dev_unregister(struct watchdog_device *wdd) +static void watchdog_cdev_unregister(struct watchdog_device *wdd) { - mutex_lock(&wdd->lock); - set_bit(WDOG_UNREGISTERED, &wdd->status); - mutex_unlock(&wdd->lock); + struct watchdog_core_data *wd_data = wdd->wd_data; - cdev_del(&wdd->cdev); + cdev_del(&wd_data->cdev); if (wdd->id == 0) { misc_deregister(&watchdog_miscdev); - old_wdd = NULL; + old_wd_data = NULL; } - return 0; + + mutex_lock(&wd_data->lock); + wd_data->wdd = NULL; + wdd->wd_data = NULL; + mutex_unlock(&wd_data->lock); + + kref_put(&wd_data->kref, watchdog_core_data_release); +} + +static struct class watchdog_class = { + .name = "watchdog", + .owner = THIS_MODULE, + .dev_groups = wdt_groups, +}; + +/* + * watchdog_dev_register: register a watchdog device + * @wdd: watchdog device + * + * Register a watchdog device including handling the legacy + * /dev/watchdog node. /dev/watchdog is actually a miscdevice and + * thus we set it up like that. + */ + +int watchdog_dev_register(struct watchdog_device *wdd) +{ + struct device *dev; + dev_t devno; + int ret; + + devno = MKDEV(MAJOR(watchdog_devt), wdd->id); + + ret = watchdog_cdev_register(wdd, devno); + if (ret) + return ret; + + dev = device_create_with_groups(&watchdog_class, wdd->parent, + devno, wdd, wdd->groups, + "watchdog%d", wdd->id); + if (IS_ERR(dev)) { + watchdog_cdev_unregister(wdd); + return PTR_ERR(dev); + } + + return ret; +} + +/* + * watchdog_dev_unregister: unregister a watchdog device + * @watchdog: watchdog device + * + * Unregister watchdog device and if needed the legacy + * /dev/watchdog device. + */ + +void watchdog_dev_unregister(struct watchdog_device *wdd) +{ + device_destroy(&watchdog_class, wdd->wd_data->cdev.dev); + watchdog_cdev_unregister(wdd); } /* @@ -589,10 +778,22 @@ int watchdog_dev_unregister(struct watchdog_device *wdd) int __init watchdog_dev_init(void) { - int err = alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog"); - if (err < 0) + int err; + + err = class_register(&watchdog_class); + if (err < 0) { + pr_err("couldn't register class\n"); + return err; + } + + err = alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog"); + if (err < 0) { pr_err("watchdog: unable to allocate char dev region\n"); - return err; + class_unregister(&watchdog_class); + return err; + } + + return 0; } /* @@ -604,4 +805,5 @@ int __init watchdog_dev_init(void) void __exit watchdog_dev_exit(void) { unregister_chrdev_region(watchdog_devt, MAX_DOGS); + class_unregister(&watchdog_class); } diff --git a/drivers/watchdog/ziirave_wdt.c b/drivers/watchdog/ziirave_wdt.c new file mode 100644 index 000000000..0c7cb7302 --- /dev/null +++ b/drivers/watchdog/ziirave_wdt.c @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2015 Zodiac Inflight Innovations + * + * Author: Martyn Welch <martyn.welch@collabora.co.uk> + * + * Based on twl4030_wdt.c by Timo Kokkonen <timo.t.kokkonen at nokia.com>: + * + * Copyright (C) Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/version.h> +#include <linux/watchdog.h> + +#define ZIIRAVE_TIMEOUT_MIN 3 +#define ZIIRAVE_TIMEOUT_MAX 255 + +#define ZIIRAVE_PING_VALUE 0x0 + +#define ZIIRAVE_STATE_INITIAL 0x0 +#define ZIIRAVE_STATE_OFF 0x1 +#define ZIIRAVE_STATE_ON 0x2 + +static char *ziirave_reasons[] = {"power cycle", "triggered", NULL, NULL, + "host request", NULL, "illegal configuration", + "illegal instruction", "illegal trap", + "unknown"}; + +#define ZIIRAVE_WDT_FIRM_VER_MAJOR 0x1 +#define ZIIRAVE_WDT_BOOT_VER_MAJOR 0x3 +#define ZIIRAVE_WDT_RESET_REASON 0x5 +#define ZIIRAVE_WDT_STATE 0x6 +#define ZIIRAVE_WDT_TIMEOUT 0x7 +#define ZIIRAVE_WDT_TIME_LEFT 0x8 +#define ZIIRAVE_WDT_PING 0x9 +#define ZIIRAVE_WDT_RESET_DURATION 0xa + +struct ziirave_wdt_rev { + unsigned char major; + unsigned char minor; +}; + +struct ziirave_wdt_data { + struct watchdog_device wdd; + struct ziirave_wdt_rev bootloader_rev; + struct ziirave_wdt_rev firmware_rev; + int reset_reason; +}; + +static int wdt_timeout; +module_param(wdt_timeout, int, 0); +MODULE_PARM_DESC(wdt_timeout, "Watchdog timeout in seconds"); + +static int reset_duration; +module_param(reset_duration, int, 0); +MODULE_PARM_DESC(reset_duration, + "Watchdog reset pulse duration in milliseconds"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static int ziirave_wdt_revision(struct i2c_client *client, + struct ziirave_wdt_rev *rev, u8 command) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, command); + if (ret < 0) + return ret; + + rev->major = ret; + + ret = i2c_smbus_read_byte_data(client, command + 1); + if (ret < 0) + return ret; + + rev->minor = ret; + + return 0; +} + +static int ziirave_wdt_set_state(struct watchdog_device *wdd, int state) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + + return i2c_smbus_write_byte_data(client, ZIIRAVE_WDT_STATE, state); +} + +static int ziirave_wdt_start(struct watchdog_device *wdd) +{ + return ziirave_wdt_set_state(wdd, ZIIRAVE_STATE_ON); +} + +static int ziirave_wdt_stop(struct watchdog_device *wdd) +{ + return ziirave_wdt_set_state(wdd, ZIIRAVE_STATE_OFF); +} + +static int ziirave_wdt_ping(struct watchdog_device *wdd) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + + return i2c_smbus_write_byte_data(client, ZIIRAVE_WDT_PING, + ZIIRAVE_PING_VALUE); +} + +static int ziirave_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + int ret; + + ret = i2c_smbus_write_byte_data(client, ZIIRAVE_WDT_TIMEOUT, timeout); + if (!ret) + wdd->timeout = timeout; + + return ret; +} + +static unsigned int ziirave_wdt_get_timeleft(struct watchdog_device *wdd) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + int ret; + + ret = i2c_smbus_read_byte_data(client, ZIIRAVE_WDT_TIME_LEFT); + if (ret < 0) + ret = 0; + + return ret; +} + +static const struct watchdog_info ziirave_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, + .identity = "Zodiac RAVE Watchdog", +}; + +static const struct watchdog_ops ziirave_wdt_ops = { + .owner = THIS_MODULE, + .start = ziirave_wdt_start, + .stop = ziirave_wdt_stop, + .ping = ziirave_wdt_ping, + .set_timeout = ziirave_wdt_set_timeout, + .get_timeleft = ziirave_wdt_get_timeleft, +}; + +static ssize_t ziirave_wdt_sysfs_show_firm(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client); + + return sprintf(buf, "02.%02u.%02u", w_priv->firmware_rev.major, + w_priv->firmware_rev.minor); +} + +static DEVICE_ATTR(firmware_version, S_IRUGO, ziirave_wdt_sysfs_show_firm, + NULL); + +static ssize_t ziirave_wdt_sysfs_show_boot(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client); + + return sprintf(buf, "01.%02u.%02u", w_priv->bootloader_rev.major, + w_priv->bootloader_rev.minor); +} + +static DEVICE_ATTR(bootloader_version, S_IRUGO, ziirave_wdt_sysfs_show_boot, + NULL); + +static ssize_t ziirave_wdt_sysfs_show_reason(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client); + + return sprintf(buf, "%s", ziirave_reasons[w_priv->reset_reason]); +} + +static DEVICE_ATTR(reset_reason, S_IRUGO, ziirave_wdt_sysfs_show_reason, + NULL); + +static struct attribute *ziirave_wdt_attrs[] = { + &dev_attr_firmware_version.attr, + &dev_attr_bootloader_version.attr, + &dev_attr_reset_reason.attr, + NULL +}; +ATTRIBUTE_GROUPS(ziirave_wdt); + +static int ziirave_wdt_init_duration(struct i2c_client *client) +{ + int ret; + + if (!reset_duration) { + /* See if the reset pulse duration is provided in an of_node */ + if (!client->dev.of_node) + ret = -ENODEV; + else + ret = of_property_read_u32(client->dev.of_node, + "reset-duration-ms", + &reset_duration); + if (ret) { + dev_info(&client->dev, + "Unable to set reset pulse duration, using default\n"); + return 0; + } + } + + if (reset_duration < 1 || reset_duration > 255) + return -EINVAL; + + dev_info(&client->dev, "Setting reset duration to %dms", + reset_duration); + + return i2c_smbus_write_byte_data(client, ZIIRAVE_WDT_RESET_DURATION, + reset_duration); +} + +static int ziirave_wdt_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct ziirave_wdt_data *w_priv; + int val; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + w_priv = devm_kzalloc(&client->dev, sizeof(*w_priv), GFP_KERNEL); + if (!w_priv) + return -ENOMEM; + + w_priv->wdd.info = &ziirave_wdt_info; + w_priv->wdd.ops = &ziirave_wdt_ops; + w_priv->wdd.min_timeout = ZIIRAVE_TIMEOUT_MIN; + w_priv->wdd.max_timeout = ZIIRAVE_TIMEOUT_MAX; + w_priv->wdd.parent = &client->dev; + w_priv->wdd.groups = ziirave_wdt_groups; + + ret = watchdog_init_timeout(&w_priv->wdd, wdt_timeout, &client->dev); + if (ret) { + dev_info(&client->dev, + "Unable to select timeout value, using default\n"); + } + + /* + * The default value set in the watchdog should be perfectly valid, so + * pass that in if we haven't provided one via the module parameter or + * of property. + */ + if (w_priv->wdd.timeout == 0) { + val = i2c_smbus_read_byte_data(client, ZIIRAVE_WDT_TIMEOUT); + if (val < 0) + return val; + + if (val < ZIIRAVE_TIMEOUT_MIN) + return -ENODEV; + + w_priv->wdd.timeout = val; + } else { + ret = ziirave_wdt_set_timeout(&w_priv->wdd, + w_priv->wdd.timeout); + if (ret) + return ret; + + dev_info(&client->dev, "Timeout set to %ds.", + w_priv->wdd.timeout); + } + + watchdog_set_nowayout(&w_priv->wdd, nowayout); + + i2c_set_clientdata(client, w_priv); + + /* If in unconfigured state, set to stopped */ + val = i2c_smbus_read_byte_data(client, ZIIRAVE_WDT_STATE); + if (val < 0) + return val; + + if (val == ZIIRAVE_STATE_INITIAL) + ziirave_wdt_stop(&w_priv->wdd); + + ret = ziirave_wdt_init_duration(client); + if (ret) + return ret; + + ret = ziirave_wdt_revision(client, &w_priv->firmware_rev, + ZIIRAVE_WDT_FIRM_VER_MAJOR); + if (ret) + return ret; + + ret = ziirave_wdt_revision(client, &w_priv->bootloader_rev, + ZIIRAVE_WDT_BOOT_VER_MAJOR); + if (ret) + return ret; + + w_priv->reset_reason = i2c_smbus_read_byte_data(client, + ZIIRAVE_WDT_RESET_REASON); + if (w_priv->reset_reason < 0) + return w_priv->reset_reason; + + if (w_priv->reset_reason >= ARRAY_SIZE(ziirave_reasons) || + !ziirave_reasons[w_priv->reset_reason]) + return -ENODEV; + + ret = watchdog_register_device(&w_priv->wdd); + + return ret; +} + +static int ziirave_wdt_remove(struct i2c_client *client) +{ + struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client); + + watchdog_unregister_device(&w_priv->wdd); + + return 0; +} + +static struct i2c_device_id ziirave_wdt_id[] = { + { "ziirave-wdt", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ziirave_wdt_id); + +static const struct of_device_id zrv_wdt_of_match[] = { + { .compatible = "zii,rave-wdt", }, + { }, +}; +MODULE_DEVICE_TABLE(of, zrv_wdt_of_match); + +static struct i2c_driver ziirave_wdt_driver = { + .driver = { + .name = "ziirave_wdt", + .of_match_table = zrv_wdt_of_match, + }, + .probe = ziirave_wdt_probe, + .remove = ziirave_wdt_remove, + .id_table = ziirave_wdt_id, +}; + +module_i2c_driver(ziirave_wdt_driver); + +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk"); +MODULE_DESCRIPTION("Zodiac Aerospace RAVE Switch Watchdog Processor Driver"); +MODULE_LICENSE("GPL"); |