From 2165bf524da5f5e496d1cdb8c5afae1345ecce1e Mon Sep 17 00:00:00 2001 From: Damien Riegel Date: Mon, 16 Nov 2015 12:27:59 -0500 Subject: watchdog: core: add restart handler support Many watchdog drivers implement the same code to register a restart handler. This patch provides a generic way to set such a function. The patch adds a new restart watchdog operation. If a restart priority greater than 0 is needed, the driver can call watchdog_set_restart_priority to set it. Suggested-by: Vivien Didelot Signed-off-by: Damien Riegel Reviewed-by: Guenter Roeck Reviewed-by: Vivien Didelot Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- Documentation/watchdog/watchdog-kernel-api.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'Documentation') diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index d8b0d3367706..dbc6a65f0bd1 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt @@ -53,6 +53,7 @@ struct watchdog_device { unsigned int timeout; unsigned int min_timeout; unsigned int max_timeout; + struct notifier_block restart_nb; void *driver_data; struct mutex lock; unsigned long status; @@ -75,6 +76,10 @@ It contains following fields: * timeout: the watchdog timer's timeout value (in seconds). * min_timeout: the watchdog timer's minimum timeout value (in seconds). * max_timeout: the watchdog timer's maximum timeout value (in seconds). +* restart_nb: notifier block that is registered for machine restart, for + internal use only. If a watchdog is capable of restarting the machine, it + should define ops->restart. Priority can be changed through + watchdog_set_restart_priority. * bootstatus: status of the device after booting (reported with watchdog WDIOF_* status bits). * driver_data: a pointer to the drivers private data of a watchdog device. @@ -100,6 +105,7 @@ struct watchdog_ops { unsigned int (*status)(struct watchdog_device *); int (*set_timeout)(struct watchdog_device *, unsigned int); unsigned int (*get_timeleft)(struct watchdog_device *); + int (*restart)(struct watchdog_device *); void (*ref)(struct watchdog_device *); void (*unref)(struct watchdog_device *); long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long); @@ -164,6 +170,8 @@ they are supported. These optional routines/operations are: (Note: the WDIOF_SETTIMEOUT needs to be set in the options field of the watchdog's info structure). * get_timeleft: this routines returns the time that's left before a reset. +* restart: this routine restarts the machine. It returns 0 on success or a + negative errno code for failure. * ref: the operation that calls kref_get on the kref of a dynamically allocated watchdog_device struct. * unref: the operation that calls kref_put on the kref of a dynamically @@ -231,3 +239,14 @@ the device tree (if the module timeout parameter is invalid). Best practice is to set the default timeout value as timeout value in the watchdog_device and then use this function to set the user "preferred" timeout value. This routine returns zero on success and a negative errno code for failure. + +To change the priority of the restart handler the following helper should be +used: + +void watchdog_set_restart_priority(struct watchdog_device *wdd, int priority); + +User should follow the following guidelines for setting the priority: +* 0: should be called in last resort, has limited restart capabilities +* 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: highest priority, will preempt all other restart handlers -- cgit v1.2.3 From e131319669e0ef5e6fcd75174daeffa40492135c Mon Sep 17 00:00:00 2001 From: Damien Riegel Date: Fri, 20 Nov 2015 16:54:51 -0500 Subject: watchdog: core: add reboot notifier support Many watchdog drivers register a reboot notifier in order to stop the watchdog on system reboot. Thus we can factorize this code in the watchdog core. For that purpose, a new notifier block is added in watchdog_device for internal use only, as well as a new watchdog_stop_on_reboot helper function. If this helper is called, watchdog core registers the related notifier block and will stop the watchdog when SYS_HALT or SYS_DOWN is received. Since this operation can be critical on some platforms, abort the device registration if the reboot notifier registration fails. Suggested-by: Vivien Didelot Signed-off-by: Damien Riegel Reviewed-by: Vivien Didelot Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- Documentation/watchdog/watchdog-kernel-api.txt | 8 ++++++ drivers/watchdog/watchdog_core.c | 37 ++++++++++++++++++++++++++ include/linux/watchdog.h | 9 +++++++ 3 files changed, 54 insertions(+) (limited to 'Documentation') diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index dbc6a65f0bd1..0a37da76acef 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt @@ -53,6 +53,7 @@ struct watchdog_device { unsigned int timeout; unsigned int min_timeout; unsigned int max_timeout; + struct notifier_block reboot_nb; struct notifier_block restart_nb; void *driver_data; struct mutex lock; @@ -76,6 +77,9 @@ It contains following fields: * timeout: the watchdog timer's timeout value (in seconds). * min_timeout: the watchdog timer's minimum timeout value (in seconds). * max_timeout: the watchdog timer's maximum timeout value (in seconds). +* reboot_nb: notifier block that is registered for reboot notifications, for + internal use only. If the driver calls watchdog_stop_on_reboot, watchdog core + will stop the watchdog on such notifications. * restart_nb: notifier block that is registered for machine restart, for internal use only. If a watchdog is capable of restarting the machine, it should define ops->restart. Priority can be changed through @@ -240,6 +244,10 @@ to set the default timeout value as timeout value in the watchdog_device and then use this function to set the user "preferred" timeout value. This routine returns zero on success and a negative errno code for failure. +To disable the watchdog on reboot, the user must call the following helper: + +static inline void watchdog_stop_on_reboot(struct watchdog_device *wdd); + To change the priority of the restart handler the following helper should be used: diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 88a34efac400..0bb32a487f46 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -138,6 +138,25 @@ 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) { @@ -238,6 +257,21 @@ static int __watchdog_register_device(struct watchdog_device *wdd) 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) { + dev_err(wdd->dev, "Cannot register reboot notifier (%d)\n", + ret); + watchdog_dev_unregister(wdd); + device_destroy(watchdog_class, devno); + ida_simple_remove(&watchdog_ida, wdd->id); + wdd->dev = NULL; + return ret; + } + } + if (wdd->ops->restart) { wdd->restart_nb.notifier_call = watchdog_restart_notifier; @@ -286,6 +320,9 @@ static void __watchdog_unregister_device(struct watchdog_device *wdd) 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); + devno = wdd->cdev.dev; ret = watchdog_dev_unregister(wdd); if (ret) diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index 5b52c834f7aa..a88f955fde92 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -65,6 +65,7 @@ struct watchdog_ops { * @timeout: The watchdog devices timeout value (in seconds). * @min_timeout:The watchdog devices minimum timeout value (in seconds). * @max_timeout:The watchdog devices maximum timeout value (in seconds). + * @reboot_nb: The notifier block to stop watchdog on reboot. * @restart_nb: The notifier block to register a restart function. * @driver-data:Pointer to the drivers private data. * @lock: Lock for watchdog core internal use only. @@ -92,6 +93,7 @@ struct watchdog_device { unsigned int timeout; unsigned int min_timeout; unsigned int max_timeout; + struct notifier_block reboot_nb; struct notifier_block restart_nb; void *driver_data; struct mutex lock; @@ -102,6 +104,7 @@ struct watchdog_device { #define WDOG_ALLOW_RELEASE 2 /* Did we receive the magic char ? */ #define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */ #define WDOG_UNREGISTERED 4 /* Has the device been unregistered */ +#define WDOG_STOP_ON_REBOOT 5 /* Should be stopped on reboot */ struct list_head deferred; }; @@ -121,6 +124,12 @@ static inline void watchdog_set_nowayout(struct watchdog_device *wdd, bool noway set_bit(WDOG_NO_WAY_OUT, &wdd->status); } +/* Use the following function to stop the watchdog on reboot */ +static inline void watchdog_stop_on_reboot(struct watchdog_device *wdd) +{ + set_bit(WDOG_STOP_ON_REBOOT, &wdd->status); +} + /* Use the following function to check if a timeout value is invalid */ static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigned int t) { -- cgit v1.2.3 From 33b711269ade3f6bc9d9d15e4343e6fa922d999b Mon Sep 17 00:00:00 2001 From: Pratyush Anand Date: Thu, 17 Dec 2015 17:53:59 +0530 Subject: watchdog: Read device status through sysfs attributes This patch adds following attributes to watchdog device's sysfs interface to read its different status. * state - reads whether device is active or not * identity - reads Watchdog device's identity string. * timeout - reads current timeout. * timeleft - reads timeleft before watchdog generates a reset * bootstatus - reads status of the watchdog device at boot * status - reads watchdog device's internal status bits * nowayout - reads whether nowayout feature was set or not Testing with iTCO_wdt: # cd /sys/class/watchdog/watchdog1/ # ls bootstatus dev device identity nowayout power state subsystem timeleft timeout uevent # cat identity iTCO_wdt # cat timeout 30 # cat state inactive # echo > /dev/watchdog1 # cat timeleft 26 # cat state active # cat bootstatus 0 # cat nowayout 0 Signed-off-by: Pratyush Anand Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- Documentation/ABI/testing/sysfs-class-watchdog | 51 +++++++++++ drivers/watchdog/Kconfig | 7 ++ drivers/watchdog/watchdog_core.c | 2 +- drivers/watchdog/watchdog_dev.c | 114 +++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-class-watchdog (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-class-watchdog b/Documentation/ABI/testing/sysfs-class-watchdog new file mode 100644 index 000000000000..736046b33040 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-watchdog @@ -0,0 +1,51 @@ +What: /sys/class/watchdog/watchdogn/bootstatus +Date: August 2015 +Contact: Wim Van Sebroeck +Description: + It is a read only file. It contains status of the watchdog + device at boot. It is equivalent to WDIOC_GETBOOTSTATUS of + ioctl interface. + +What: /sys/class/watchdog/watchdogn/identity +Date: August 2015 +Contact: Wim Van Sebroeck +Description: + It is a read only file. It contains identity string of + watchdog device. + +What: /sys/class/watchdog/watchdogn/nowayout +Date: August 2015 +Contact: Wim Van Sebroeck +Description: + It is a read only file. While reading, it gives '1' if that + device supports nowayout feature else, it gives '0'. + +What: /sys/class/watchdog/watchdogn/state +Date: August 2015 +Contact: Wim Van Sebroeck +Description: + It is a read only file. It gives active/inactive status of + watchdog device. + +What: /sys/class/watchdog/watchdogn/status +Date: August 2015 +Contact: Wim Van Sebroeck +Description: + It is a read only file. It contains watchdog device's + internal status bits. It is equivalent to WDIOC_GETSTATUS + of ioctl interface. + +What: /sys/class/watchdog/watchdogn/timeleft +Date: August 2015 +Contact: Wim Van Sebroeck +Description: + It is a read only file. It contains value of time left for + reset generation. It is equivalent to WDIOC_GETTIMELEFT of + ioctl interface. + +What: /sys/class/watchdog/watchdogn/timeout +Date: August 2015 +Contact: Wim Van Sebroeck +Description: + It is a read only file. It is read to know about current + value of timeout programmed. diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1c427beffadd..71e47dde7d4a 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 # diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 357d23c79545..551af042867c 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -249,7 +249,7 @@ 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); + wdd, "watchdog%d", wdd->id); if (IS_ERR(wdd->dev)) { watchdog_dev_unregister(wdd); ida_simple_remove(&watchdog_ida, id); diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 055a4e1b4c13..f06fbcf0bea2 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -247,6 +247,119 @@ out_timeleft: return err; } +#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); + + 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); + ssize_t status; + unsigned int val; + + status = watchdog_get_status(wdd, &val); + if (!status) + status = sprintf(buf, "%u\n", val); + + return 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); + ssize_t status; + unsigned int val; + + status = watchdog_get_timeleft(wdd, &val); + 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 @@ -584,6 +697,7 @@ int watchdog_dev_unregister(struct watchdog_device *wdd) static struct class watchdog_class = { .name = "watchdog", .owner = THIS_MODULE, + .dev_groups = wdt_groups, }; /* -- cgit v1.2.3 From bf9006399939762e6cd32445e848e56727df9d98 Mon Sep 17 00:00:00 2001 From: Damien Riegel Date: Tue, 8 Dec 2015 11:37:28 -0500 Subject: watchdog: ts4800: add driver for TS-4800 watchdog This watchdog is instantiated in a FPGA that is memory mapped. It is made of only one register, called the feed register. Writing to this register will re-arm the watchdog for a given time (and enable it if it was disable). It can be disabled by writing a special value into it. It is part of a syscon block, and the watchdog register offset in this block varies from board to board. This offset is passed in the syscon property after the phandle to the syscon node. Signed-off-by: Damien Riegel Acked-by: Rob Herring Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../devicetree/bindings/watchdog/ts4800-wdt.txt | 25 +++ drivers/watchdog/Kconfig | 10 + drivers/watchdog/Makefile | 1 + drivers/watchdog/ts4800_wdt.c | 215 +++++++++++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 Documentation/devicetree/bindings/watchdog/ts4800-wdt.txt create mode 100644 drivers/watchdog/ts4800_wdt.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/watchdog/ts4800-wdt.txt b/Documentation/devicetree/bindings/watchdog/ts4800-wdt.txt new file mode 100644 index 000000000000..8f6caad4258d --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/ts4800-wdt.txt @@ -0,0 +1,25 @@ +Technologic Systems Watchdog + +Required properties: +- compatible: must be "technologic,ts4800-wdt" +- syscon: phandle / integer array that points to the syscon node which + describes the FPGA's syscon registers. + - phandle to FPGA's syscon + - offset to the watchdog register + +Optional property: +- timeout-sec: contains the watchdog timeout in seconds. + +Example: + +syscon: syscon@b0010000 { + compatible = "syscon", "simple-mfd"; + reg = <0xb0010000 0x3d>; + reg-io-width = <2>; + + wdt@e { + compatible = "technologic,ts4800-wdt"; + syscon = <&syscon 0xe>; + timeout-sec = <10>; + }; +} diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 2116e088fd25..62ac67f8bcab 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -433,6 +433,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 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index e2bc52c79aa7..7e44c9ae7b03 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -53,6 +53,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 diff --git a/drivers/watchdog/ts4800_wdt.c b/drivers/watchdog/ts4800_wdt.c new file mode 100644 index 000000000000..2b8de8602b67 --- /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 +#include +#include +#include +#include +#include +#include + +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 "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:ts4800_wdt"); -- cgit v1.2.3 From a62a7231899606655e98f060e52c4760cf556811 Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Wed, 25 Nov 2015 20:33:23 +0100 Subject: DT: watchdog: add Alphascale asm9260 watchdog binding documentation. Signed-off-by: Oleksij Rempel Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../bindings/watchdog/alphascale-asm9260.txt | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Documentation/devicetree/bindings/watchdog/alphascale-asm9260.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/watchdog/alphascale-asm9260.txt b/Documentation/devicetree/bindings/watchdog/alphascale-asm9260.txt new file mode 100644 index 000000000000..75b265a04047 --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/alphascale-asm9260.txt @@ -0,0 +1,35 @@ +Alphascale asm9260 Watchdog timer + +Required properties: + +- compatible : should be "alphascale,asm9260-wdt". +- reg : Specifies base physical address and size of the registers. +- clocks : the clocks feeding the watchdog timer. See clock-bindings.txt +- clock-names : should be set to + "mod" - source for tick counter. + "ahb" - ahb gate. +- resets : phandle pointing to the system reset controller with + line index for the watchdog. +- reset-names : should be set to "wdt_rst". + +Optional properties: +- timeout-sec : shall contain the default watchdog timeout in seconds, + if unset, the default timeout is 30 seconds. +- alphascale,mode : three modes are supported + "hw" - hw reset (default). + "sw" - sw reset. + "debug" - no action is taken. + +Example: + +watchdog0: watchdog@80048000 { + compatible = "alphascale,asm9260-wdt"; + reg = <0x80048000 0x10>; + clocks = <&acc CLKID_SYS_WDT>, <&acc CLKID_AHB_WDT>; + clock-names = "mod", "ahb"; + interrupts = <55>; + resets = <&rst WDT_RESET>; + reset-names = "wdt_rst"; + timeout-sec = <30>; + alphascale,mode = "hw"; +}; -- cgit v1.2.3 From 5c6bb88a1b8890225d83fd00d14cf10ae00df91f Mon Sep 17 00:00:00 2001 From: Martyn Welch Date: Wed, 25 Nov 2015 12:03:34 +0000 Subject: Add binding documentation for Zodiac Watchdog Timer This patchs adds documentation for the binding of the Zodiac RAVE Switch Watchdog Processor. This is an i2c based watchdog. Cc: Pawel Moll Cc: Mark Rutland Cc: Ian Campbell Cc: Kumar Gala Cc: devicetree@vger.kernel.org Signed-off-by: Martyn Welch Acked-by: Rob Herring Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../devicetree/bindings/watchdog/ziirave-wdt.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Documentation/devicetree/bindings/watchdog/ziirave-wdt.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/watchdog/ziirave-wdt.txt b/Documentation/devicetree/bindings/watchdog/ziirave-wdt.txt new file mode 100644 index 000000000000..3d878184ec3f --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/ziirave-wdt.txt @@ -0,0 +1,19 @@ +Zodiac RAVE Watchdog Timer + +Required properties: +- compatible: must be "zii,rave-wdt" +- reg: i2c slave address of device, usually 0x38 + +Optional Properties: +- timeout-sec: Watchdog timeout value in seconds. +- reset-duration-ms: Duration of the pulse generated when the watchdog times + out. Value in milliseconds. + +Example: + + watchdog@38 { + compatible = "zii,rave-wdt"; + reg = <0x38>; + timeout-sec = <30>; + reset-duration-ms = <30>; + }; -- cgit v1.2.3 From 2bfc9beb61ed042c4c7632bd1c96c9dfbcedcc05 Mon Sep 17 00:00:00 2001 From: Mans Rullgard Date: Wed, 18 Nov 2015 17:55:42 +0000 Subject: devicetree: watchdog: add binding for Sigma Designs SMP8642 watchdog This adds a binding for the watchdog in Sigma Designs SMP8642 and similar devices. Signed-off-by: Mans Rullgard Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../devicetree/bindings/watchdog/sigma,smp8642-wdt.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Documentation/devicetree/bindings/watchdog/sigma,smp8642-wdt.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/watchdog/sigma,smp8642-wdt.txt b/Documentation/devicetree/bindings/watchdog/sigma,smp8642-wdt.txt new file mode 100644 index 000000000000..5b7ec2c707d8 --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/sigma,smp8642-wdt.txt @@ -0,0 +1,18 @@ +Sigma Designs SMP86xx/SMP87xx watchdog + +Required properties: +- compatible: Should be "sigma,smp8642-wdt" +- reg: Specifies the physical address region +- clocks: Should be a phandle to the clock + +Optional properties: +- timeout-sec: watchdog timeout in seconds + +Example: + +watchdog@1fd00 { + compatible = "sigma,smp8642-wdt"; + reg = <0x1fd00 8>; + clocks = <&xtal_in_clk>; + timeout-sec = <30>; +}; -- cgit v1.2.3 From b4ffb1909843b28f3b1b60197d517b123b7a9b66 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 25 Dec 2015 16:01:42 -0800 Subject: watchdog: Separate and maintain variables based on variable lifetime All variables required by the watchdog core to manage a watchdog are currently stored in struct watchdog_device. The lifetime of those variables is determined by the watchdog driver. However, the lifetime of variables used by the watchdog core differs from the lifetime of struct watchdog_device. To remedy this situation, watchdog drivers can implement ref and unref callbacks, to be used by the watchdog core to lock struct watchdog_device in memory. While this solves the immediate problem, it depends on watchdog drivers to actually implement the ref/unref callbacks. This is error prone, often not implemented in the first place, or not implemented correctly. To solve the problem without requiring driver support, split the variables in struct watchdog_device into two data structures - one for variables associated with the watchdog driver, one for variables associated with the watchdog core. With this approach, the watchdog core can keep track of its variable lifetime and no longer depends on ref/unref callbacks in the driver. As a side effect, some of the variables originally in struct watchdog_driver are now private to the watchdog core and no longer visible in watchdog drivers. As a side effect of the changes made, an ioctl will now always fail with -ENODEV after a watchdog device was unregistered with the character device still open. Previously, it would only fail with -ENODEV in some situations. Also, ioctl operations are now atomic from driver perspective. With this change, it is now guaranteed that the driver will not unregister a watchdog between a timeout change and the subsequent ping. The 'ref' and 'unref' callbacks in struct watchdog_driver are no longer used and marked as deprecated. Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- Documentation/watchdog/watchdog-kernel-api.txt | 45 +-- drivers/watchdog/watchdog_core.c | 2 - drivers/watchdog/watchdog_dev.c | 383 +++++++++++++------------ include/linux/watchdog.h | 22 +- 4 files changed, 218 insertions(+), 234 deletions(-) (limited to 'Documentation') diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index 0a37da76acef..72a009478b15 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt @@ -44,7 +44,6 @@ The watchdog device structure looks like this: struct watchdog_device { int id; - struct cdev cdev; struct device *dev; struct device *parent; const struct watchdog_info *info; @@ -56,7 +55,7 @@ struct watchdog_device { struct notifier_block reboot_nb; struct notifier_block restart_nb; void *driver_data; - struct mutex lock; + struct watchdog_core_data *wd_data; unsigned long status; struct list_head deferred; }; @@ -66,8 +65,6 @@ It contains following fields: /dev/watchdog0 cdev (dynamic major, minor 0) as well as the old /dev/watchdog miscdev. The id is set automatically when calling watchdog_register_device. -* cdev: cdev for the dynamic /dev/watchdog device nodes. This - field is also populated by watchdog_register_device. * dev: device under the watchdog class (created by watchdog_register_device). * parent: set this to the parent device (or NULL) before calling watchdog_register_device. @@ -89,11 +86,10 @@ It contains following fields: * driver_data: a pointer to the drivers private data of a watchdog device. This data should only be accessed via the watchdog_set_drvdata and watchdog_get_drvdata routines. -* lock: Mutex for WatchDog Timer Driver Core internal use only. +* wd_data: a pointer to watchdog core internal data. * status: this field contains a number of status bits that give extra information about the status of the device (Like: is the watchdog timer - running/active, is the nowayout bit set, is the device opened via - the /dev/watchdog interface or not, ...). + running/active, or is the nowayout bit set). * deferred: entry in wtd_deferred_reg_list which is used to register early initialized watchdogs. @@ -110,8 +106,8 @@ struct watchdog_ops { int (*set_timeout)(struct watchdog_device *, unsigned int); unsigned int (*get_timeleft)(struct watchdog_device *); int (*restart)(struct watchdog_device *); - void (*ref)(struct watchdog_device *); - void (*unref)(struct watchdog_device *); + void (*ref)(struct watchdog_device *) __deprecated; + void (*unref)(struct watchdog_device *) __deprecated; long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long); }; @@ -120,20 +116,6 @@ driver's operations. This module owner will be used to lock the module when the watchdog is active. (This to avoid a system crash when you unload the module and /dev/watchdog is still open). -If the watchdog_device struct is dynamically allocated, just locking the module -is not enough and a driver also needs to define the ref and unref operations to -ensure the structure holding the watchdog_device does not go away. - -The simplest (and usually sufficient) implementation of this is to: -1) Add a kref struct to the same structure which is holding the watchdog_device -2) Define a release callback for the kref which frees the struct holding both -3) Call kref_init on this kref *before* calling watchdog_register_device() -4) Define a ref operation calling kref_get on this kref -5) Define a unref operation calling kref_put on this kref -6) When it is time to cleanup: - * Do not kfree() the struct holding both, the last kref_put will do this! - * *After* calling watchdog_unregister_device() call kref_put on the kref - Some operations are mandatory and some are optional. The mandatory operations are: * start: this is a pointer to the routine that starts the watchdog timer @@ -176,34 +158,21 @@ they are supported. These optional routines/operations are: * get_timeleft: this routines returns the time that's left before a reset. * restart: this routine restarts the machine. It returns 0 on success or a negative errno code for failure. -* ref: the operation that calls kref_get on the kref of a dynamically - allocated watchdog_device struct. -* unref: the operation that calls kref_put on the kref of a dynamically - allocated watchdog_device struct. * ioctl: if this routine is present then it will be called first before we do our own internal ioctl call handling. This routine should return -ENOIOCTLCMD if a command is not supported. The parameters that are passed to the ioctl call are: watchdog_device, cmd and arg. +The 'ref' and 'unref' operations are no longer used and deprecated. + The status bits should (preferably) be set with the set_bit and clear_bit alike bit-operations. The status bits that are defined are: * WDOG_ACTIVE: this status bit indicates whether or not a watchdog timer device is active or not. When the watchdog is active after booting, then you should set this status bit (Note: when you register the watchdog timer device with this bit set, then opening /dev/watchdog will skip the start operation) -* WDOG_DEV_OPEN: this status bit shows whether or not the watchdog device - was opened via /dev/watchdog. - (This bit should only be used by the WatchDog Timer Driver Core). -* WDOG_ALLOW_RELEASE: this bit stores whether or not the magic close character - has been sent (so that we can support the magic close feature). - (This bit should only be used by the WatchDog Timer Driver Core). * WDOG_NO_WAY_OUT: this bit stores the nowayout setting for the watchdog. If this bit is set then the watchdog timer will not be able to stop. -* WDOG_UNREGISTERED: this bit gets set by the WatchDog Timer Driver Core - after calling watchdog_unregister_device, and then checked before calling - any watchdog_ops, so that you can be sure that no operations (other then - unref) will get called after unregister, even if userspace still holds a - reference to /dev/watchdog To set the WDOG_NO_WAY_OUT status bit (before registering your watchdog timer device) you can either: diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index f0293f7d2b80..ec1ab6c1a80b 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -210,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"); diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 7ba3fc6157c7..3cab6f6e7f1c 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 /* For module stuff/... */ -#include /* For standard types (like size_t) */ +#include /* For character device */ #include /* For the -ENODEV/... values */ -#include /* For printk/panic/... */ #include /* For file operations */ -#include /* For watchdog specific items */ -#include /* For handling misc devices */ #include /* For __init/__exit/... */ +#include /* For printk/panic/... */ +#include /* For data references */ +#include /* For handling misc devices */ +#include /* For module stuff/... */ +#include /* For mutexes */ +#include /* For memory functions */ +#include /* For standard types (like size_t) */ +#include /* For watchdog specific items */ #include /* 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,58 @@ 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; + 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,30 +196,22 @@ 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); - - if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { - err = -ENODEV; - goto out_timeleft; - } - *timeleft = wdd->ops->get_timeleft(wdd); -out_timeleft: - mutex_unlock(&wdd->lock); - return err; + return 0; } #ifdef CONFIG_WATCHDOG_SYSFS @@ -261,14 +228,14 @@ static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf) { struct watchdog_device *wdd = dev_get_drvdata(dev); - ssize_t status; - unsigned int val; + struct watchdog_core_data *wd_data = wdd->wd_data; + unsigned int status; - status = watchdog_get_status(wdd, &val); - if (!status) - status = sprintf(buf, "%u\n", val); + mutex_lock(&wd_data->lock); + status = watchdog_get_status(wdd); + mutex_unlock(&wd_data->lock); - return status; + return sprintf(buf, "%u\n", status); } static DEVICE_ATTR_RO(status); @@ -285,10 +252,13 @@ 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); @@ -365,28 +335,17 @@ __ATTRIBUTE_GROUPS(wdt); * @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); } /* @@ -404,10 +363,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; @@ -416,18 +376,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; @@ -447,71 +414,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; } /* @@ -526,45 +516,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 @@ -577,9 +581,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 @@ -587,29 +598,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); + dev_crit(wdd->dev, "watchdog did not stop!\n"); 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; } @@ -639,10 +645,20 @@ static struct miscdevice watchdog_miscdev = { static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t 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) { @@ -651,23 +667,25 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) 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 */ - 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; @@ -683,15 +701,20 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) 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; } + + 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 = { @@ -742,9 +765,9 @@ int watchdog_dev_register(struct watchdog_device *wdd) void watchdog_dev_unregister(struct watchdog_device *wdd) { - watchdog_cdev_unregister(wdd); device_destroy(&watchdog_class, wdd->dev->devt); wdd->dev = NULL; + watchdog_cdev_unregister(wdd); } /* diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index a88f955fde92..850af04fe0c7 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -17,6 +17,7 @@ struct watchdog_ops; struct watchdog_device; +struct watchdog_core_data; /** struct watchdog_ops - The watchdog-devices operations * @@ -28,8 +29,6 @@ struct watchdog_device; * @set_timeout:The routine for setting the watchdog devices timeout value (in seconds). * @get_timeleft:The routine that gets the time left before a reset (in seconds). * @restart: The routine for restarting the machine. - * @ref: The ref operation for dyn. allocated watchdog_device structs - * @unref: The unref operation for dyn. allocated watchdog_device structs * @ioctl: The routines that handles extra ioctl calls. * * The watchdog_ops structure contains a list of low-level operations @@ -48,15 +47,14 @@ struct watchdog_ops { int (*set_timeout)(struct watchdog_device *, unsigned int); unsigned int (*get_timeleft)(struct watchdog_device *); int (*restart)(struct watchdog_device *); - void (*ref)(struct watchdog_device *); - void (*unref)(struct watchdog_device *); + void (*ref)(struct watchdog_device *) __deprecated; + void (*unref)(struct watchdog_device *) __deprecated; long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long); }; /** struct watchdog_device - The structure that defines a watchdog device * * @id: The watchdog's ID. (Allocated by watchdog_register_device) - * @cdev: The watchdog's Character device. * @dev: The device for our watchdog * @parent: The parent bus device * @info: Pointer to a watchdog_info structure. @@ -67,8 +65,8 @@ struct watchdog_ops { * @max_timeout:The watchdog devices maximum timeout value (in seconds). * @reboot_nb: The notifier block to stop watchdog on reboot. * @restart_nb: The notifier block to register a restart function. - * @driver-data:Pointer to the drivers private data. - * @lock: Lock for watchdog core internal use only. + * @driver_data:Pointer to the drivers private data. + * @wd_data: Pointer to watchdog core internal data. * @status: Field that contains the devices internal status bits. * @deferred: entry in wtd_deferred_reg_list which is used to * register early initialized watchdogs. @@ -84,7 +82,6 @@ struct watchdog_ops { */ struct watchdog_device { int id; - struct cdev cdev; struct device *dev; struct device *parent; const struct watchdog_info *info; @@ -96,15 +93,12 @@ struct watchdog_device { struct notifier_block reboot_nb; struct notifier_block restart_nb; void *driver_data; - struct mutex lock; + struct watchdog_core_data *wd_data; unsigned long status; /* Bit numbers for status flags */ #define WDOG_ACTIVE 0 /* Is the watchdog running/active */ -#define WDOG_DEV_OPEN 1 /* Opened via /dev/watchdog ? */ -#define WDOG_ALLOW_RELEASE 2 /* Did we receive the magic char ? */ -#define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */ -#define WDOG_UNREGISTERED 4 /* Has the device been unregistered */ -#define WDOG_STOP_ON_REBOOT 5 /* Should be stopped on reboot */ +#define WDOG_NO_WAY_OUT 1 /* Is 'nowayout' feature set ? */ +#define WDOG_STOP_ON_REBOOT 2 /* Should be stopped on reboot */ struct list_head deferred; }; -- cgit v1.2.3 From ab3f09fe16d158cb4f84e558c61ec5d6d601f2e0 Mon Sep 17 00:00:00 2001 From: John Crispin Date: Mon, 4 Jan 2016 20:36:38 +0100 Subject: watchdog: add MT7621 watchdog support This patch adds support for the watchdog core found on newer MediaTek Wifi SoCs MT7621 and MT7628. There is no symbol for MT7628 as it is a subtype of MT7620 so we depend on that instead. Signed-off-by: John Crispin Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../devicetree/bindings/watchdog/mt7621-wdt.txt | 12 ++ drivers/watchdog/Kconfig | 7 + drivers/watchdog/Makefile | 1 + drivers/watchdog/mt7621_wdt.c | 186 +++++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 Documentation/devicetree/bindings/watchdog/mt7621-wdt.txt create mode 100644 drivers/watchdog/mt7621_wdt.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/watchdog/mt7621-wdt.txt b/Documentation/devicetree/bindings/watchdog/mt7621-wdt.txt new file mode 100644 index 000000000000..c15ef0ef609f --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/mt7621-wdt.txt @@ -0,0 +1,12 @@ +Ralink Watchdog Timers + +Required properties: +- compatible: must be "mediatek,mt7621-wdt" +- reg: physical base address of the controller and length of the register range + +Example: + + watchdog@100 { + compatible = "mediatek,mt7621-wdt"; + reg = <0x100 0x10>; + }; diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index afb7f91795cb..4f0e7be0da34 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1403,6 +1403,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 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 2d203fc3cfdb..f566753256ab 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -152,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 diff --git a/drivers/watchdog/mt7621_wdt.c b/drivers/watchdog/mt7621_wdt.c new file mode 100644 index 000000000000..4a2290f900a8 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +#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 Date: Sun, 3 Jan 2016 15:11:56 -0800 Subject: watchdog: Add support for creating driver specific sysfs attributes The Zodiac watchdog driver attaches additional sysfs attributes to the watchdog device. This has a number of problems: The watchdog device lifetime differs from the driver lifetime, and the device structure should therefore not be accessed from drivers. Also, creating sysfs attributes after driver registration results in a potential race condition if user space expects the attributes to exist but they don't exist yet. Add support for creating driver specific sysfs attributes to the watchdog core to solve the problems. Signed-off-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- Documentation/watchdog/watchdog-kernel-api.txt | 3 +++ drivers/watchdog/watchdog_dev.c | 5 +++-- include/linux/watchdog.h | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index 72a009478b15..312f60009c3e 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt @@ -46,6 +46,7 @@ struct watchdog_device { int id; struct device *dev; struct device *parent; + const struct attribute_group **groups; const struct watchdog_info *info; const struct watchdog_ops *ops; unsigned int bootstatus; @@ -68,6 +69,8 @@ It contains following fields: * dev: device under the watchdog class (created by watchdog_register_device). * parent: set this to the parent device (or NULL) before calling watchdog_register_device. +* groups: List of sysfs attribute groups to create when creating the watchdog + device. * info: a pointer to a watchdog_info structure. This structure gives some additional information about the watchdog timer itself. (Like it's unique name) * ops: a pointer to the list of watchdog operations that the watchdog supports. diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 3cab6f6e7f1c..e89ccb2e9603 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -744,8 +744,9 @@ int watchdog_dev_register(struct watchdog_device *wdd) if (ret) return ret; - dev = device_create(&watchdog_class, wdd->parent, devno, wdd, - "watchdog%d", wdd->id); + 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); diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index aaabd4703b46..076df50ea0da 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -55,6 +55,8 @@ struct watchdog_ops { * @id: The watchdog's ID. (Allocated by watchdog_register_device) * @dev: The device for our watchdog * @parent: The parent bus device + * @groups: List of sysfs attribute groups to create when creating the + * watchdog device. * @info: Pointer to a watchdog_info structure. * @ops: Pointer to the list of watchdog operations. * @bootstatus: Status of the watchdog device at boot. @@ -82,6 +84,7 @@ struct watchdog_device { int id; struct device *dev; struct device *parent; + const struct attribute_group **groups; const struct watchdog_info *info; const struct watchdog_ops *ops; unsigned int bootstatus; -- cgit v1.2.3 From 0254e953537c92df3e7d0176f401a211e944fd61 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 3 Jan 2016 15:11:58 -0800 Subject: watchdog: Drop pointer to watchdog device from struct watchdog_device The lifetime of the watchdog device pointer is different from the lifetime of its character device. Remove it entirely to avoid race conditions. Signed-off-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- Documentation/watchdog/watchdog-kernel-api.txt | 2 -- drivers/watchdog/watchdog_core.c | 8 ++++---- drivers/watchdog/watchdog_dev.c | 9 ++++----- include/linux/watchdog.h | 2 -- 4 files changed, 8 insertions(+), 13 deletions(-) (limited to 'Documentation') diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index 312f60009c3e..55120a055a14 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt @@ -44,7 +44,6 @@ The watchdog device structure looks like this: struct watchdog_device { int id; - struct device *dev; struct device *parent; const struct attribute_group **groups; const struct watchdog_info *info; @@ -66,7 +65,6 @@ It contains following fields: /dev/watchdog0 cdev (dynamic major, minor 0) as well as the old /dev/watchdog miscdev. The id is set automatically when calling watchdog_register_device. -* dev: device under the watchdog class (created by watchdog_register_device). * parent: set this to the parent device (or NULL) before calling watchdog_register_device. * groups: List of sysfs attribute groups to create when creating the watchdog diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index ec1ab6c1a80b..e600fd93b7de 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -249,8 +249,8 @@ static int __watchdog_register_device(struct watchdog_device *wdd) ret = register_reboot_notifier(&wdd->reboot_nb); if (ret) { - dev_err(wdd->dev, "Cannot register reboot notifier (%d)\n", - 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; @@ -262,8 +262,8 @@ static int __watchdog_register_device(struct watchdog_device *wdd) ret = register_restart_handler(&wdd->restart_nb); if (ret) - dev_warn(wdd->dev, "Cannot register restart handler (%d)\n", - ret); + pr_warn("watchog%d: Cannot register restart handler (%d)\n", + wdd->id, ret); } return 0; diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index e89ccb2e9603..ba2ecce4aae6 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -143,7 +143,8 @@ static int watchdog_stop(struct watchdog_device *wdd) return 0; if (test_bit(WDOG_NO_WAY_OUT, &wdd->status)) { - dev_info(wdd->dev, "nowayout prevents watchdog being stopped!\n"); + pr_info("watchdog%d: nowayout prevents watchdog being stopped!\n", + wdd->id); return -EBUSY; } @@ -604,7 +605,7 @@ static int watchdog_release(struct inode *inode, struct file *file) /* If the watchdog was not stopped, send a keepalive ping */ if (err < 0) { - dev_crit(wdd->dev, "watchdog did not stop!\n"); + pr_crit("watchdog%d: watchdog did not stop!\n", wdd->id); watchdog_ping(wdd); } @@ -751,7 +752,6 @@ int watchdog_dev_register(struct watchdog_device *wdd) watchdog_cdev_unregister(wdd); return PTR_ERR(dev); } - wdd->dev = dev; return ret; } @@ -766,8 +766,7 @@ int watchdog_dev_register(struct watchdog_device *wdd) void watchdog_dev_unregister(struct watchdog_device *wdd) { - device_destroy(&watchdog_class, wdd->dev->devt); - wdd->dev = NULL; + device_destroy(&watchdog_class, wdd->wd_data->cdev.dev); watchdog_cdev_unregister(wdd); } diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index 076df50ea0da..b585fa2507ee 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -53,7 +53,6 @@ struct watchdog_ops { /** struct watchdog_device - The structure that defines a watchdog device * * @id: The watchdog's ID. (Allocated by watchdog_register_device) - * @dev: The device for our watchdog * @parent: The parent bus device * @groups: List of sysfs attribute groups to create when creating the * watchdog device. @@ -82,7 +81,6 @@ struct watchdog_ops { */ struct watchdog_device { int id; - struct device *dev; struct device *parent; const struct attribute_group **groups; const struct watchdog_info *info; -- cgit v1.2.3