diff options
Diffstat (limited to 'drivers/watchdog')
-rw-r--r-- | drivers/watchdog/Kconfig | 28 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 2 | ||||
-rw-r--r-- | drivers/watchdog/ebc-c384_wdt.c | 43 | ||||
-rw-r--r-- | drivers/watchdog/pic32-dmt.c | 257 | ||||
-rw-r--r-- | drivers/watchdog/pic32-wdt.c | 263 | ||||
-rw-r--r-- | drivers/watchdog/watchdog_core.c | 2 |
6 files changed, 560 insertions, 35 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index fb947655badd..5b45e277697b 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -738,7 +738,7 @@ config ALIM7101_WDT config EBC_C384_WDT tristate "WinSystems EBC-C384 Watchdog Timer" - depends on X86 + depends on X86 && ISA select WATCHDOG_CORE help Enables watchdog timer support for the watchdog timer on the @@ -1475,6 +1475,32 @@ config MT7621_WDT help Hardware driver for the Mediatek/Ralink MT7621/8 SoC Watchdog Timer. +config PIC32_WDT + tristate "Microchip PIC32 hardware watchdog" + select WATCHDOG_CORE + depends on MACH_PIC32 + help + Watchdog driver for the built in watchdog hardware in a PIC32. + + Configuration bits must be set appropriately for the watchdog to be + controlled by this driver. + + To compile this driver as a loadable module, choose M here. + The module will be called pic32-wdt. + +config PIC32_DMT + tristate "Microchip PIC32 Deadman Timer" + select WATCHDOG_CORE + depends on MACH_PIC32 + help + Watchdog driver for PIC32 instruction fetch counting timer. This specific + timer is typically be used in misson critical and safety critical + applications, where any single failure of the software functionality + and sequencing must be detected. + + To compile this driver as a loadable module, choose M here. + The module will be called pic32-dmt. + # PARISC Architecture # POWERPC Architecture diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index feb6270fdbde..9bde095ff691 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -157,6 +157,8 @@ 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 +obj-$(CONFIG_PIC32_WDT) += pic32-wdt.o +obj-$(CONFIG_PIC32_DMT) += pic32-dmt.o # PARISC Architecture diff --git a/drivers/watchdog/ebc-c384_wdt.c b/drivers/watchdog/ebc-c384_wdt.c index 77fda0b4b90e..4b849b8e37c2 100644 --- a/drivers/watchdog/ebc-c384_wdt.c +++ b/drivers/watchdog/ebc-c384_wdt.c @@ -16,10 +16,10 @@ #include <linux/errno.h> #include <linux/io.h> #include <linux/ioport.h> +#include <linux/isa.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/moduleparam.h> -#include <linux/platform_device.h> #include <linux/types.h> #include <linux/watchdog.h> @@ -95,9 +95,8 @@ static const struct watchdog_info ebc_c384_wdt_info = { .identity = MODULE_NAME }; -static int __init ebc_c384_wdt_probe(struct platform_device *pdev) +static int ebc_c384_wdt_probe(struct device *dev, unsigned int id) { - struct device *dev = &pdev->dev; struct watchdog_device *wdd; if (!devm_request_region(dev, BASE_ADDR, ADDR_EXTENT, dev_name(dev))) { @@ -122,61 +121,39 @@ static int __init ebc_c384_wdt_probe(struct platform_device *pdev) dev_warn(dev, "Invalid timeout (%u seconds), using default (%u seconds)\n", timeout, WATCHDOG_TIMEOUT); - platform_set_drvdata(pdev, wdd); + dev_set_drvdata(dev, wdd); return watchdog_register_device(wdd); } -static int ebc_c384_wdt_remove(struct platform_device *pdev) +static int ebc_c384_wdt_remove(struct device *dev, unsigned int id) { - struct watchdog_device *wdd = platform_get_drvdata(pdev); + struct watchdog_device *wdd = dev_get_drvdata(dev); watchdog_unregister_device(wdd); return 0; } -static struct platform_driver ebc_c384_wdt_driver = { +static struct isa_driver ebc_c384_wdt_driver = { + .probe = ebc_c384_wdt_probe, .driver = { .name = MODULE_NAME }, .remove = ebc_c384_wdt_remove }; -static struct platform_device *ebc_c384_wdt_device; - static int __init ebc_c384_wdt_init(void) { - int err; - if (!dmi_match(DMI_BOARD_NAME, "EBC-C384 SBC")) return -ENODEV; - ebc_c384_wdt_device = platform_device_alloc(MODULE_NAME, -1); - if (!ebc_c384_wdt_device) - return -ENOMEM; - - err = platform_device_add(ebc_c384_wdt_device); - if (err) - goto err_platform_device; - - err = platform_driver_probe(&ebc_c384_wdt_driver, ebc_c384_wdt_probe); - if (err) - goto err_platform_driver; - - return 0; - -err_platform_driver: - platform_device_del(ebc_c384_wdt_device); -err_platform_device: - platform_device_put(ebc_c384_wdt_device); - return err; + return isa_register_driver(&ebc_c384_wdt_driver, 1); } static void __exit ebc_c384_wdt_exit(void) { - platform_device_unregister(ebc_c384_wdt_device); - platform_driver_unregister(&ebc_c384_wdt_driver); + isa_unregister_driver(&ebc_c384_wdt_driver); } module_init(ebc_c384_wdt_init); @@ -185,4 +162,4 @@ module_exit(ebc_c384_wdt_exit); MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>"); MODULE_DESCRIPTION("WinSystems EBC-C384 watchdog timer driver"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" MODULE_NAME); +MODULE_ALIAS("isa:" MODULE_NAME); diff --git a/drivers/watchdog/pic32-dmt.c b/drivers/watchdog/pic32-dmt.c new file mode 100644 index 000000000000..962f58c03353 --- /dev/null +++ b/drivers/watchdog/pic32-dmt.c @@ -0,0 +1,257 @@ +/* + * PIC32 deadman timer driver + * + * Purna Chandra Mandal <purna.mandal@microchip.com> + * Copyright (c) 2016, Microchip Technology Inc. + * + * 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/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/watchdog.h> + +#include <asm/mach-pic32/pic32.h> + +/* Deadman Timer Regs */ +#define DMTCON_REG 0x00 +#define DMTPRECLR_REG 0x10 +#define DMTCLR_REG 0x20 +#define DMTSTAT_REG 0x30 +#define DMTCNT_REG 0x40 +#define DMTPSCNT_REG 0x60 +#define DMTPSINTV_REG 0x70 + +/* Deadman Timer Regs fields */ +#define DMT_ON BIT(15) +#define DMT_STEP1_KEY BIT(6) +#define DMT_STEP2_KEY BIT(3) +#define DMTSTAT_WINOPN BIT(0) +#define DMTSTAT_EVENT BIT(5) +#define DMTSTAT_BAD2 BIT(6) +#define DMTSTAT_BAD1 BIT(7) + +/* Reset Control Register fields for watchdog */ +#define RESETCON_DMT_TIMEOUT BIT(5) + +struct pic32_dmt { + void __iomem *regs; + struct clk *clk; +}; + +static inline void dmt_enable(struct pic32_dmt *dmt) +{ + writel(DMT_ON, PIC32_SET(dmt->regs + DMTCON_REG)); +} + +static inline void dmt_disable(struct pic32_dmt *dmt) +{ + writel(DMT_ON, PIC32_CLR(dmt->regs + DMTCON_REG)); + /* + * Cannot touch registers in the CPU cycle following clearing the + * ON bit. + */ + nop(); +} + +static inline int dmt_bad_status(struct pic32_dmt *dmt) +{ + u32 val; + + val = readl(dmt->regs + DMTSTAT_REG); + val &= (DMTSTAT_BAD1 | DMTSTAT_BAD2 | DMTSTAT_EVENT); + if (val) + return -EAGAIN; + + return 0; +} + +static inline int dmt_keepalive(struct pic32_dmt *dmt) +{ + u32 v; + u32 timeout = 500; + + /* set pre-clear key */ + writel(DMT_STEP1_KEY << 8, dmt->regs + DMTPRECLR_REG); + + /* wait for DMT window to open */ + while (--timeout) { + v = readl(dmt->regs + DMTSTAT_REG) & DMTSTAT_WINOPN; + if (v == DMTSTAT_WINOPN) + break; + } + + /* apply key2 */ + writel(DMT_STEP2_KEY, dmt->regs + DMTCLR_REG); + + /* check whether keys are latched correctly */ + return dmt_bad_status(dmt); +} + +static inline u32 pic32_dmt_get_timeout_secs(struct pic32_dmt *dmt) +{ + unsigned long rate; + + rate = clk_get_rate(dmt->clk); + if (rate) + return readl(dmt->regs + DMTPSCNT_REG) / rate; + + return 0; +} + +static inline u32 pic32_dmt_bootstatus(struct pic32_dmt *dmt) +{ + u32 v; + void __iomem *rst_base; + + rst_base = ioremap(PIC32_BASE_RESET, 0x10); + if (!rst_base) + return 0; + + v = readl(rst_base); + + writel(RESETCON_DMT_TIMEOUT, PIC32_CLR(rst_base)); + + iounmap(rst_base); + return v & RESETCON_DMT_TIMEOUT; +} + +static int pic32_dmt_start(struct watchdog_device *wdd) +{ + struct pic32_dmt *dmt = watchdog_get_drvdata(wdd); + + dmt_enable(dmt); + return dmt_keepalive(dmt); +} + +static int pic32_dmt_stop(struct watchdog_device *wdd) +{ + struct pic32_dmt *dmt = watchdog_get_drvdata(wdd); + + dmt_disable(dmt); + + return 0; +} + +static int pic32_dmt_ping(struct watchdog_device *wdd) +{ + struct pic32_dmt *dmt = watchdog_get_drvdata(wdd); + + return dmt_keepalive(dmt); +} + +static const struct watchdog_ops pic32_dmt_fops = { + .owner = THIS_MODULE, + .start = pic32_dmt_start, + .stop = pic32_dmt_stop, + .ping = pic32_dmt_ping, +}; + +static const struct watchdog_info pic32_dmt_ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .identity = "PIC32 Deadman Timer", +}; + +static struct watchdog_device pic32_dmt_wdd = { + .info = &pic32_dmt_ident, + .ops = &pic32_dmt_fops, +}; + +static int pic32_dmt_probe(struct platform_device *pdev) +{ + int ret; + struct pic32_dmt *dmt; + struct resource *mem; + struct watchdog_device *wdd = &pic32_dmt_wdd; + + dmt = devm_kzalloc(&pdev->dev, sizeof(*dmt), GFP_KERNEL); + if (IS_ERR(dmt)) + return PTR_ERR(dmt); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dmt->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(dmt->regs)) + return PTR_ERR(dmt->regs); + + dmt->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dmt->clk)) { + dev_err(&pdev->dev, "clk not found\n"); + return PTR_ERR(dmt->clk); + } + + ret = clk_prepare_enable(dmt->clk); + if (ret) + return ret; + + wdd->timeout = pic32_dmt_get_timeout_secs(dmt); + if (!wdd->timeout) { + dev_err(&pdev->dev, + "failed to read watchdog register timeout\n"); + ret = -EINVAL; + goto out_disable_clk; + } + + dev_info(&pdev->dev, "timeout %d\n", wdd->timeout); + + wdd->bootstatus = pic32_dmt_bootstatus(dmt) ? WDIOF_CARDRESET : 0; + + watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT); + watchdog_set_drvdata(wdd, dmt); + + ret = watchdog_register_device(wdd); + if (ret) { + dev_err(&pdev->dev, "watchdog register failed, err %d\n", ret); + goto out_disable_clk; + } + + platform_set_drvdata(pdev, wdd); + return 0; + +out_disable_clk: + clk_disable_unprepare(dmt->clk); + return ret; +} + +static int pic32_dmt_remove(struct platform_device *pdev) +{ + struct watchdog_device *wdd = platform_get_drvdata(pdev); + struct pic32_dmt *dmt = watchdog_get_drvdata(wdd); + + watchdog_unregister_device(wdd); + clk_disable_unprepare(dmt->clk); + + return 0; +} + +static const struct of_device_id pic32_dmt_of_ids[] = { + { .compatible = "microchip,pic32mzda-dmt",}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pic32_dmt_of_ids); + +static struct platform_driver pic32_dmt_driver = { + .probe = pic32_dmt_probe, + .remove = pic32_dmt_remove, + .driver = { + .name = "pic32-dmt", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pic32_dmt_of_ids), + } +}; + +module_platform_driver(pic32_dmt_driver); + +MODULE_AUTHOR("Purna Chandra Mandal <purna.mandal@microchip.com>"); +MODULE_DESCRIPTION("Microchip PIC32 DMT Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/pic32-wdt.c b/drivers/watchdog/pic32-wdt.c new file mode 100644 index 000000000000..6047aa89a4d3 --- /dev/null +++ b/drivers/watchdog/pic32-wdt.c @@ -0,0 +1,263 @@ +/* + * PIC32 watchdog driver + * + * Joshua Henderson <joshua.henderson@microchip.com> + * Copyright (c) 2016, Microchip Technology Inc. + * + * 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/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/watchdog.h> + +#include <asm/mach-pic32/pic32.h> + +/* Watchdog Timer Registers */ +#define WDTCON_REG 0x00 + +/* Watchdog Timer Control Register fields */ +#define WDTCON_WIN_EN BIT(0) +#define WDTCON_RMCS_MASK 0x0003 +#define WDTCON_RMCS_SHIFT 0x0006 +#define WDTCON_RMPS_MASK 0x001F +#define WDTCON_RMPS_SHIFT 0x0008 +#define WDTCON_ON BIT(15) +#define WDTCON_CLR_KEY 0x5743 + +/* Reset Control Register fields for watchdog */ +#define RESETCON_TIMEOUT_IDLE BIT(2) +#define RESETCON_TIMEOUT_SLEEP BIT(3) +#define RESETCON_WDT_TIMEOUT BIT(4) + +struct pic32_wdt { + void __iomem *regs; + void __iomem *rst_base; + struct clk *clk; +}; + +static inline bool pic32_wdt_is_win_enabled(struct pic32_wdt *wdt) +{ + return !!(readl(wdt->regs + WDTCON_REG) & WDTCON_WIN_EN); +} + +static inline u32 pic32_wdt_get_post_scaler(struct pic32_wdt *wdt) +{ + u32 v = readl(wdt->regs + WDTCON_REG); + + return (v >> WDTCON_RMPS_SHIFT) & WDTCON_RMPS_MASK; +} + +static inline u32 pic32_wdt_get_clk_id(struct pic32_wdt *wdt) +{ + u32 v = readl(wdt->regs + WDTCON_REG); + + return (v >> WDTCON_RMCS_SHIFT) & WDTCON_RMCS_MASK; +} + +static int pic32_wdt_bootstatus(struct pic32_wdt *wdt) +{ + u32 v = readl(wdt->rst_base); + + writel(RESETCON_WDT_TIMEOUT, PIC32_CLR(wdt->rst_base)); + + return v & RESETCON_WDT_TIMEOUT; +} + +static u32 pic32_wdt_get_timeout_secs(struct pic32_wdt *wdt, struct device *dev) +{ + unsigned long rate; + u32 period, ps, terminal; + + rate = clk_get_rate(wdt->clk); + + dev_dbg(dev, "wdt: clk_id %d, clk_rate %lu (prescale)\n", + pic32_wdt_get_clk_id(wdt), rate); + + /* default, prescaler of 32 (i.e. div-by-32) is implicit. */ + rate >>= 5; + if (!rate) + return 0; + + /* calculate terminal count from postscaler. */ + ps = pic32_wdt_get_post_scaler(wdt); + terminal = BIT(ps); + + /* find time taken (in secs) to reach terminal count */ + period = terminal / rate; + dev_dbg(dev, + "wdt: clk_rate %lu (postscale) / terminal %d, timeout %dsec\n", + rate, terminal, period); + + return period; +} + +static void pic32_wdt_keepalive(struct pic32_wdt *wdt) +{ + /* write key through single half-word */ + writew(WDTCON_CLR_KEY, wdt->regs + WDTCON_REG + 2); +} + +static int pic32_wdt_start(struct watchdog_device *wdd) +{ + struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); + + writel(WDTCON_ON, PIC32_SET(wdt->regs + WDTCON_REG)); + pic32_wdt_keepalive(wdt); + + return 0; +} + +static int pic32_wdt_stop(struct watchdog_device *wdd) +{ + struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); + + writel(WDTCON_ON, PIC32_CLR(wdt->regs + WDTCON_REG)); + + /* + * Cannot touch registers in the CPU cycle following clearing the + * ON bit. + */ + nop(); + + return 0; +} + +static int pic32_wdt_ping(struct watchdog_device *wdd) +{ + struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); + + pic32_wdt_keepalive(wdt); + + return 0; +} + +static const struct watchdog_ops pic32_wdt_fops = { + .owner = THIS_MODULE, + .start = pic32_wdt_start, + .stop = pic32_wdt_stop, + .ping = pic32_wdt_ping, +}; + +static const struct watchdog_info pic32_wdt_ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE | WDIOF_CARDRESET, + .identity = "PIC32 Watchdog", +}; + +static struct watchdog_device pic32_wdd = { + .info = &pic32_wdt_ident, + .ops = &pic32_wdt_fops, +}; + +static const struct of_device_id pic32_wdt_dt_ids[] = { + { .compatible = "microchip,pic32mzda-wdt", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pic32_wdt_dt_ids); + +static int pic32_wdt_drv_probe(struct platform_device *pdev) +{ + int ret; + struct watchdog_device *wdd = &pic32_wdd; + struct pic32_wdt *wdt; + struct resource *mem; + + wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); + if (IS_ERR(wdt)) + return PTR_ERR(wdt); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(wdt->regs)) + return PTR_ERR(wdt->regs); + + wdt->rst_base = devm_ioremap(&pdev->dev, PIC32_BASE_RESET, 0x10); + if (IS_ERR(wdt->rst_base)) + return PTR_ERR(wdt->rst_base); + + wdt->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(wdt->clk)) { + dev_err(&pdev->dev, "clk not found\n"); + return PTR_ERR(wdt->clk); + } + + ret = clk_prepare_enable(wdt->clk); + if (ret) { + dev_err(&pdev->dev, "clk enable failed\n"); + return ret; + } + + if (pic32_wdt_is_win_enabled(wdt)) { + dev_err(&pdev->dev, "windowed-clear mode is not supported.\n"); + ret = -ENODEV; + goto out_disable_clk; + } + + wdd->timeout = pic32_wdt_get_timeout_secs(wdt, &pdev->dev); + if (!wdd->timeout) { + dev_err(&pdev->dev, + "failed to read watchdog register timeout\n"); + ret = -EINVAL; + goto out_disable_clk; + } + + dev_info(&pdev->dev, "timeout %d\n", wdd->timeout); + + wdd->bootstatus = pic32_wdt_bootstatus(wdt) ? WDIOF_CARDRESET : 0; + + watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT); + watchdog_set_drvdata(wdd, wdt); + + ret = watchdog_register_device(wdd); + if (ret) { + dev_err(&pdev->dev, "watchdog register failed, err %d\n", ret); + goto out_disable_clk; + } + + platform_set_drvdata(pdev, wdd); + + return 0; + +out_disable_clk: + clk_disable_unprepare(wdt->clk); + + return ret; +} + +static int pic32_wdt_drv_remove(struct platform_device *pdev) +{ + struct watchdog_device *wdd = platform_get_drvdata(pdev); + struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); + + watchdog_unregister_device(wdd); + clk_disable_unprepare(wdt->clk); + + return 0; +} + +static struct platform_driver pic32_wdt_driver = { + .probe = pic32_wdt_drv_probe, + .remove = pic32_wdt_drv_remove, + .driver = { + .name = "pic32-wdt", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pic32_wdt_dt_ids), + } +}; + +module_platform_driver(pic32_wdt_driver); + +MODULE_AUTHOR("Joshua Henderson <joshua.henderson@microchip.com>"); +MODULE_DESCRIPTION("Microchip PIC32 Watchdog Timer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index c1658fe73d58..981a668b17e3 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -262,7 +262,7 @@ static int __watchdog_register_device(struct watchdog_device *wdd) ret = register_restart_handler(&wdd->restart_nb); if (ret) - pr_warn("watchog%d: Cannot register restart handler (%d)\n", + pr_warn("watchdog%d: Cannot register restart handler (%d)\n", wdd->id, ret); } |