1d00680edSCarlo Caione /* 2d00680edSCarlo Caione * sunxi Watchdog Driver 3d00680edSCarlo Caione * 4d00680edSCarlo Caione * Copyright (c) 2013 Carlo Caione 5d00680edSCarlo Caione * 2012 Henrik Nordstrom 6d00680edSCarlo Caione * 7d00680edSCarlo Caione * This program is free software; you can redistribute it and/or 8d00680edSCarlo Caione * modify it under the terms of the GNU General Public License 9d00680edSCarlo Caione * as published by the Free Software Foundation; either version 10d00680edSCarlo Caione * 2 of the License, or (at your option) any later version. 11d00680edSCarlo Caione * 12d00680edSCarlo Caione * Based on xen_wdt.c 13d00680edSCarlo Caione * (c) Copyright 2010 Novell, Inc. 14d00680edSCarlo Caione */ 15d00680edSCarlo Caione 16d00680edSCarlo Caione #include <linux/clk.h> 17440e96bcSMaxime Ripard #include <linux/delay.h> 18d00680edSCarlo Caione #include <linux/err.h> 19d00680edSCarlo Caione #include <linux/init.h> 20d00680edSCarlo Caione #include <linux/io.h> 21d00680edSCarlo Caione #include <linux/kernel.h> 22d00680edSCarlo Caione #include <linux/module.h> 23d00680edSCarlo Caione #include <linux/moduleparam.h> 24*d20a1d90SGuenter Roeck #include <linux/notifier.h> 25d00680edSCarlo Caione #include <linux/of.h> 26d00680edSCarlo Caione #include <linux/platform_device.h> 27440e96bcSMaxime Ripard #include <linux/reboot.h> 28d00680edSCarlo Caione #include <linux/types.h> 29d00680edSCarlo Caione #include <linux/watchdog.h> 30d00680edSCarlo Caione 31d00680edSCarlo Caione #define WDT_MAX_TIMEOUT 16 32d00680edSCarlo Caione #define WDT_MIN_TIMEOUT 1 33d00680edSCarlo Caione #define WDT_MODE_TIMEOUT(n) ((n) << 3) 34d00680edSCarlo Caione #define WDT_TIMEOUT_MASK WDT_MODE_TIMEOUT(0x0F) 35d00680edSCarlo Caione 36d00680edSCarlo Caione #define WDT_CTRL 0x00 37d00680edSCarlo Caione #define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1)) 38d00680edSCarlo Caione 39d00680edSCarlo Caione #define WDT_MODE 0x04 40d00680edSCarlo Caione #define WDT_MODE_EN (1 << 0) 41d00680edSCarlo Caione #define WDT_MODE_RST_EN (1 << 1) 42d00680edSCarlo Caione 43d00680edSCarlo Caione #define DRV_NAME "sunxi-wdt" 44d00680edSCarlo Caione #define DRV_VERSION "1.0" 45d00680edSCarlo Caione 46d00680edSCarlo Caione static bool nowayout = WATCHDOG_NOWAYOUT; 47d00680edSCarlo Caione static unsigned int timeout = WDT_MAX_TIMEOUT; 48d00680edSCarlo Caione 49d00680edSCarlo Caione struct sunxi_wdt_dev { 50d00680edSCarlo Caione struct watchdog_device wdt_dev; 51d00680edSCarlo Caione void __iomem *wdt_base; 52*d20a1d90SGuenter Roeck struct notifier_block restart_handler; 53d00680edSCarlo Caione }; 54d00680edSCarlo Caione 55d00680edSCarlo Caione /* 56d00680edSCarlo Caione * wdt_timeout_map maps the watchdog timer interval value in seconds to 57d00680edSCarlo Caione * the value of the register WDT_MODE bit 3:6 58d00680edSCarlo Caione * 59d00680edSCarlo Caione * [timeout seconds] = register value 60d00680edSCarlo Caione * 61d00680edSCarlo Caione */ 62d00680edSCarlo Caione 63d00680edSCarlo Caione static const int wdt_timeout_map[] = { 6451ee34abSEmilio López [1] = 0x1, /* 1s */ 6551ee34abSEmilio López [2] = 0x2, /* 2s */ 6651ee34abSEmilio López [3] = 0x3, /* 3s */ 6751ee34abSEmilio López [4] = 0x4, /* 4s */ 6851ee34abSEmilio López [5] = 0x5, /* 5s */ 6951ee34abSEmilio López [6] = 0x6, /* 6s */ 7051ee34abSEmilio López [8] = 0x7, /* 8s */ 7151ee34abSEmilio López [10] = 0x8, /* 10s */ 7251ee34abSEmilio López [12] = 0x9, /* 12s */ 7351ee34abSEmilio López [14] = 0xA, /* 14s */ 7451ee34abSEmilio López [16] = 0xB, /* 16s */ 75d00680edSCarlo Caione }; 76d00680edSCarlo Caione 77440e96bcSMaxime Ripard 78*d20a1d90SGuenter Roeck static int sunxi_restart_handle(struct notifier_block *this, unsigned long mode, 79*d20a1d90SGuenter Roeck void *cmd) 80440e96bcSMaxime Ripard { 81*d20a1d90SGuenter Roeck struct sunxi_wdt_dev *sunxi_wdt = container_of(this, 82*d20a1d90SGuenter Roeck struct sunxi_wdt_dev, 83*d20a1d90SGuenter Roeck restart_handler); 84*d20a1d90SGuenter Roeck void __iomem *wdt_base = sunxi_wdt->wdt_base; 85*d20a1d90SGuenter Roeck 86440e96bcSMaxime Ripard /* Enable timer and set reset bit in the watchdog */ 87*d20a1d90SGuenter Roeck writel(WDT_MODE_EN | WDT_MODE_RST_EN, wdt_base + WDT_MODE); 88440e96bcSMaxime Ripard 89440e96bcSMaxime Ripard /* 90440e96bcSMaxime Ripard * Restart the watchdog. The default (and lowest) interval 91440e96bcSMaxime Ripard * value for the watchdog is 0.5s. 92440e96bcSMaxime Ripard */ 93*d20a1d90SGuenter Roeck writel(WDT_CTRL_RELOAD, wdt_base + WDT_CTRL); 94440e96bcSMaxime Ripard 95440e96bcSMaxime Ripard while (1) { 96440e96bcSMaxime Ripard mdelay(5); 97*d20a1d90SGuenter Roeck writel(WDT_MODE_EN | WDT_MODE_RST_EN, wdt_base + WDT_MODE); 98440e96bcSMaxime Ripard } 99*d20a1d90SGuenter Roeck return NOTIFY_DONE; 100440e96bcSMaxime Ripard } 101440e96bcSMaxime Ripard 102d00680edSCarlo Caione static int sunxi_wdt_ping(struct watchdog_device *wdt_dev) 103d00680edSCarlo Caione { 104d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 105d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base; 106d00680edSCarlo Caione 107d00680edSCarlo Caione iowrite32(WDT_CTRL_RELOAD, wdt_base + WDT_CTRL); 108d00680edSCarlo Caione 109d00680edSCarlo Caione return 0; 110d00680edSCarlo Caione } 111d00680edSCarlo Caione 112d00680edSCarlo Caione static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev, 113d00680edSCarlo Caione unsigned int timeout) 114d00680edSCarlo Caione { 115d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 116d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base; 117d00680edSCarlo Caione u32 reg; 118d00680edSCarlo Caione 119d00680edSCarlo Caione if (wdt_timeout_map[timeout] == 0) 120d00680edSCarlo Caione timeout++; 121d00680edSCarlo Caione 122d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout = timeout; 123d00680edSCarlo Caione 124d00680edSCarlo Caione reg = ioread32(wdt_base + WDT_MODE); 125d00680edSCarlo Caione reg &= ~WDT_TIMEOUT_MASK; 126d00680edSCarlo Caione reg |= WDT_MODE_TIMEOUT(wdt_timeout_map[timeout]); 127d00680edSCarlo Caione iowrite32(reg, wdt_base + WDT_MODE); 128d00680edSCarlo Caione 129d00680edSCarlo Caione sunxi_wdt_ping(wdt_dev); 130d00680edSCarlo Caione 131d00680edSCarlo Caione return 0; 132d00680edSCarlo Caione } 133d00680edSCarlo Caione 134d00680edSCarlo Caione static int sunxi_wdt_stop(struct watchdog_device *wdt_dev) 135d00680edSCarlo Caione { 136d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 137d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base; 138d00680edSCarlo Caione 139d00680edSCarlo Caione iowrite32(0, wdt_base + WDT_MODE); 140d00680edSCarlo Caione 141d00680edSCarlo Caione return 0; 142d00680edSCarlo Caione } 143d00680edSCarlo Caione 144d00680edSCarlo Caione static int sunxi_wdt_start(struct watchdog_device *wdt_dev) 145d00680edSCarlo Caione { 146d00680edSCarlo Caione u32 reg; 147d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 148d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base; 149d00680edSCarlo Caione int ret; 150d00680edSCarlo Caione 151d00680edSCarlo Caione ret = sunxi_wdt_set_timeout(&sunxi_wdt->wdt_dev, 152d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout); 153d00680edSCarlo Caione if (ret < 0) 154d00680edSCarlo Caione return ret; 155d00680edSCarlo Caione 156d00680edSCarlo Caione reg = ioread32(wdt_base + WDT_MODE); 157d00680edSCarlo Caione reg |= (WDT_MODE_RST_EN | WDT_MODE_EN); 158d00680edSCarlo Caione iowrite32(reg, wdt_base + WDT_MODE); 159d00680edSCarlo Caione 160d00680edSCarlo Caione return 0; 161d00680edSCarlo Caione } 162d00680edSCarlo Caione 163d00680edSCarlo Caione static const struct watchdog_info sunxi_wdt_info = { 164d00680edSCarlo Caione .identity = DRV_NAME, 165d00680edSCarlo Caione .options = WDIOF_SETTIMEOUT | 166d00680edSCarlo Caione WDIOF_KEEPALIVEPING | 167d00680edSCarlo Caione WDIOF_MAGICCLOSE, 168d00680edSCarlo Caione }; 169d00680edSCarlo Caione 170d00680edSCarlo Caione static const struct watchdog_ops sunxi_wdt_ops = { 171d00680edSCarlo Caione .owner = THIS_MODULE, 172d00680edSCarlo Caione .start = sunxi_wdt_start, 173d00680edSCarlo Caione .stop = sunxi_wdt_stop, 174d00680edSCarlo Caione .ping = sunxi_wdt_ping, 175d00680edSCarlo Caione .set_timeout = sunxi_wdt_set_timeout, 176d00680edSCarlo Caione }; 177d00680edSCarlo Caione 1781d5898b4SMaxime Ripard static int sunxi_wdt_probe(struct platform_device *pdev) 179d00680edSCarlo Caione { 180d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt; 181d00680edSCarlo Caione struct resource *res; 182d00680edSCarlo Caione int err; 183d00680edSCarlo Caione 184d00680edSCarlo Caione sunxi_wdt = devm_kzalloc(&pdev->dev, sizeof(*sunxi_wdt), GFP_KERNEL); 185d00680edSCarlo Caione if (!sunxi_wdt) 186d00680edSCarlo Caione return -EINVAL; 187d00680edSCarlo Caione 188d00680edSCarlo Caione platform_set_drvdata(pdev, sunxi_wdt); 189d00680edSCarlo Caione 190d00680edSCarlo Caione res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 191d00680edSCarlo Caione sunxi_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); 192d00680edSCarlo Caione if (IS_ERR(sunxi_wdt->wdt_base)) 193d00680edSCarlo Caione return PTR_ERR(sunxi_wdt->wdt_base); 194d00680edSCarlo Caione 195d00680edSCarlo Caione sunxi_wdt->wdt_dev.info = &sunxi_wdt_info; 196d00680edSCarlo Caione sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops; 197d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT; 198d00680edSCarlo Caione sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT; 199d00680edSCarlo Caione sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT; 200d00680edSCarlo Caione sunxi_wdt->wdt_dev.parent = &pdev->dev; 201d00680edSCarlo Caione 202d00680edSCarlo Caione watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, &pdev->dev); 203d00680edSCarlo Caione watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout); 204d00680edSCarlo Caione 205d00680edSCarlo Caione watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt); 206d00680edSCarlo Caione 207d00680edSCarlo Caione sunxi_wdt_stop(&sunxi_wdt->wdt_dev); 208d00680edSCarlo Caione 209d00680edSCarlo Caione err = watchdog_register_device(&sunxi_wdt->wdt_dev); 210d00680edSCarlo Caione if (unlikely(err)) 211d00680edSCarlo Caione return err; 212d00680edSCarlo Caione 213*d20a1d90SGuenter Roeck sunxi_wdt->restart_handler.notifier_call = sunxi_restart_handle; 214*d20a1d90SGuenter Roeck sunxi_wdt->restart_handler.priority = 128; 215*d20a1d90SGuenter Roeck err = register_restart_handler(&sunxi_wdt->restart_handler); 216*d20a1d90SGuenter Roeck if (err) 217*d20a1d90SGuenter Roeck dev_err(&pdev->dev, 218*d20a1d90SGuenter Roeck "cannot register restart handler (err=%d)\n", err); 219440e96bcSMaxime Ripard 220d00680edSCarlo Caione dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", 221d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout, nowayout); 222d00680edSCarlo Caione 223d00680edSCarlo Caione return 0; 224d00680edSCarlo Caione } 225d00680edSCarlo Caione 2261d5898b4SMaxime Ripard static int sunxi_wdt_remove(struct platform_device *pdev) 227d00680edSCarlo Caione { 228d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = platform_get_drvdata(pdev); 229d00680edSCarlo Caione 230*d20a1d90SGuenter Roeck unregister_restart_handler(&sunxi_wdt->restart_handler); 231440e96bcSMaxime Ripard 232d00680edSCarlo Caione watchdog_unregister_device(&sunxi_wdt->wdt_dev); 233d00680edSCarlo Caione watchdog_set_drvdata(&sunxi_wdt->wdt_dev, NULL); 234d00680edSCarlo Caione 235d00680edSCarlo Caione return 0; 236d00680edSCarlo Caione } 237d00680edSCarlo Caione 238d00680edSCarlo Caione static void sunxi_wdt_shutdown(struct platform_device *pdev) 239d00680edSCarlo Caione { 240d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = platform_get_drvdata(pdev); 241d00680edSCarlo Caione 242d00680edSCarlo Caione sunxi_wdt_stop(&sunxi_wdt->wdt_dev); 243d00680edSCarlo Caione } 244d00680edSCarlo Caione 245d00680edSCarlo Caione static const struct of_device_id sunxi_wdt_dt_ids[] = { 246b0f1d8beSMaxime Ripard { .compatible = "allwinner,sun4i-a10-wdt" }, 247d00680edSCarlo Caione { /* sentinel */ } 248d00680edSCarlo Caione }; 249d00680edSCarlo Caione MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids); 250d00680edSCarlo Caione 251d00680edSCarlo Caione static struct platform_driver sunxi_wdt_driver = { 252d00680edSCarlo Caione .probe = sunxi_wdt_probe, 253d00680edSCarlo Caione .remove = sunxi_wdt_remove, 254d00680edSCarlo Caione .shutdown = sunxi_wdt_shutdown, 255d00680edSCarlo Caione .driver = { 256d00680edSCarlo Caione .owner = THIS_MODULE, 257d00680edSCarlo Caione .name = DRV_NAME, 25885eee819SSachin Kamat .of_match_table = sunxi_wdt_dt_ids, 259d00680edSCarlo Caione }, 260d00680edSCarlo Caione }; 261d00680edSCarlo Caione 262d00680edSCarlo Caione module_platform_driver(sunxi_wdt_driver); 263d00680edSCarlo Caione 264d00680edSCarlo Caione module_param(timeout, uint, 0); 265d00680edSCarlo Caione MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); 266d00680edSCarlo Caione 267d00680edSCarlo Caione module_param(nowayout, bool, 0); 268d00680edSCarlo Caione MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 269d00680edSCarlo Caione "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 270d00680edSCarlo Caione 271d00680edSCarlo Caione MODULE_LICENSE("GPL"); 272d00680edSCarlo Caione MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>"); 273d00680edSCarlo Caione MODULE_AUTHOR("Henrik Nordstrom <henrik@henriknordstrom.net>"); 274d00680edSCarlo Caione MODULE_DESCRIPTION("sunxi WatchDog Timer Driver"); 275d00680edSCarlo Caione MODULE_VERSION(DRV_VERSION); 276