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> 17*440e96bcSMaxime 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> 24d00680edSCarlo Caione #include <linux/of.h> 25d00680edSCarlo Caione #include <linux/platform_device.h> 26*440e96bcSMaxime Ripard #include <linux/reboot.h> 27d00680edSCarlo Caione #include <linux/types.h> 28d00680edSCarlo Caione #include <linux/watchdog.h> 29d00680edSCarlo Caione 30*440e96bcSMaxime Ripard #include <asm/system_misc.h> 31*440e96bcSMaxime Ripard 32d00680edSCarlo Caione #define WDT_MAX_TIMEOUT 16 33d00680edSCarlo Caione #define WDT_MIN_TIMEOUT 1 34d00680edSCarlo Caione #define WDT_MODE_TIMEOUT(n) ((n) << 3) 35d00680edSCarlo Caione #define WDT_TIMEOUT_MASK WDT_MODE_TIMEOUT(0x0F) 36d00680edSCarlo Caione 37d00680edSCarlo Caione #define WDT_CTRL 0x00 38d00680edSCarlo Caione #define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1)) 39d00680edSCarlo Caione 40d00680edSCarlo Caione #define WDT_MODE 0x04 41d00680edSCarlo Caione #define WDT_MODE_EN (1 << 0) 42d00680edSCarlo Caione #define WDT_MODE_RST_EN (1 << 1) 43d00680edSCarlo Caione 44d00680edSCarlo Caione #define DRV_NAME "sunxi-wdt" 45d00680edSCarlo Caione #define DRV_VERSION "1.0" 46d00680edSCarlo Caione 47d00680edSCarlo Caione static bool nowayout = WATCHDOG_NOWAYOUT; 48d00680edSCarlo Caione static unsigned int timeout = WDT_MAX_TIMEOUT; 49d00680edSCarlo Caione 50d00680edSCarlo Caione struct sunxi_wdt_dev { 51d00680edSCarlo Caione struct watchdog_device wdt_dev; 52d00680edSCarlo Caione void __iomem *wdt_base; 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 77*440e96bcSMaxime Ripard static void __iomem *reboot_wdt_base; 78*440e96bcSMaxime Ripard 79*440e96bcSMaxime Ripard static void sun4i_wdt_restart(enum reboot_mode mode, const char *cmd) 80*440e96bcSMaxime Ripard { 81*440e96bcSMaxime Ripard /* Enable timer and set reset bit in the watchdog */ 82*440e96bcSMaxime Ripard writel(WDT_MODE_EN | WDT_MODE_RST_EN, reboot_wdt_base + WDT_MODE); 83*440e96bcSMaxime Ripard 84*440e96bcSMaxime Ripard /* 85*440e96bcSMaxime Ripard * Restart the watchdog. The default (and lowest) interval 86*440e96bcSMaxime Ripard * value for the watchdog is 0.5s. 87*440e96bcSMaxime Ripard */ 88*440e96bcSMaxime Ripard writel(WDT_CTRL_RELOAD, reboot_wdt_base + WDT_CTRL); 89*440e96bcSMaxime Ripard 90*440e96bcSMaxime Ripard while (1) { 91*440e96bcSMaxime Ripard mdelay(5); 92*440e96bcSMaxime Ripard writel(WDT_MODE_EN | WDT_MODE_RST_EN, 93*440e96bcSMaxime Ripard reboot_wdt_base + WDT_MODE); 94*440e96bcSMaxime Ripard } 95*440e96bcSMaxime Ripard } 96*440e96bcSMaxime Ripard 97d00680edSCarlo Caione static int sunxi_wdt_ping(struct watchdog_device *wdt_dev) 98d00680edSCarlo Caione { 99d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 100d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base; 101d00680edSCarlo Caione 102d00680edSCarlo Caione iowrite32(WDT_CTRL_RELOAD, wdt_base + WDT_CTRL); 103d00680edSCarlo Caione 104d00680edSCarlo Caione return 0; 105d00680edSCarlo Caione } 106d00680edSCarlo Caione 107d00680edSCarlo Caione static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev, 108d00680edSCarlo Caione unsigned int timeout) 109d00680edSCarlo Caione { 110d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 111d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base; 112d00680edSCarlo Caione u32 reg; 113d00680edSCarlo Caione 114d00680edSCarlo Caione if (wdt_timeout_map[timeout] == 0) 115d00680edSCarlo Caione timeout++; 116d00680edSCarlo Caione 117d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout = timeout; 118d00680edSCarlo Caione 119d00680edSCarlo Caione reg = ioread32(wdt_base + WDT_MODE); 120d00680edSCarlo Caione reg &= ~WDT_TIMEOUT_MASK; 121d00680edSCarlo Caione reg |= WDT_MODE_TIMEOUT(wdt_timeout_map[timeout]); 122d00680edSCarlo Caione iowrite32(reg, wdt_base + WDT_MODE); 123d00680edSCarlo Caione 124d00680edSCarlo Caione sunxi_wdt_ping(wdt_dev); 125d00680edSCarlo Caione 126d00680edSCarlo Caione return 0; 127d00680edSCarlo Caione } 128d00680edSCarlo Caione 129d00680edSCarlo Caione static int sunxi_wdt_stop(struct watchdog_device *wdt_dev) 130d00680edSCarlo Caione { 131d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 132d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base; 133d00680edSCarlo Caione 134d00680edSCarlo Caione iowrite32(0, wdt_base + WDT_MODE); 135d00680edSCarlo Caione 136d00680edSCarlo Caione return 0; 137d00680edSCarlo Caione } 138d00680edSCarlo Caione 139d00680edSCarlo Caione static int sunxi_wdt_start(struct watchdog_device *wdt_dev) 140d00680edSCarlo Caione { 141d00680edSCarlo Caione u32 reg; 142d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 143d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base; 144d00680edSCarlo Caione int ret; 145d00680edSCarlo Caione 146d00680edSCarlo Caione ret = sunxi_wdt_set_timeout(&sunxi_wdt->wdt_dev, 147d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout); 148d00680edSCarlo Caione if (ret < 0) 149d00680edSCarlo Caione return ret; 150d00680edSCarlo Caione 151d00680edSCarlo Caione reg = ioread32(wdt_base + WDT_MODE); 152d00680edSCarlo Caione reg |= (WDT_MODE_RST_EN | WDT_MODE_EN); 153d00680edSCarlo Caione iowrite32(reg, wdt_base + WDT_MODE); 154d00680edSCarlo Caione 155d00680edSCarlo Caione return 0; 156d00680edSCarlo Caione } 157d00680edSCarlo Caione 158d00680edSCarlo Caione static const struct watchdog_info sunxi_wdt_info = { 159d00680edSCarlo Caione .identity = DRV_NAME, 160d00680edSCarlo Caione .options = WDIOF_SETTIMEOUT | 161d00680edSCarlo Caione WDIOF_KEEPALIVEPING | 162d00680edSCarlo Caione WDIOF_MAGICCLOSE, 163d00680edSCarlo Caione }; 164d00680edSCarlo Caione 165d00680edSCarlo Caione static const struct watchdog_ops sunxi_wdt_ops = { 166d00680edSCarlo Caione .owner = THIS_MODULE, 167d00680edSCarlo Caione .start = sunxi_wdt_start, 168d00680edSCarlo Caione .stop = sunxi_wdt_stop, 169d00680edSCarlo Caione .ping = sunxi_wdt_ping, 170d00680edSCarlo Caione .set_timeout = sunxi_wdt_set_timeout, 171d00680edSCarlo Caione }; 172d00680edSCarlo Caione 1731d5898b4SMaxime Ripard static int sunxi_wdt_probe(struct platform_device *pdev) 174d00680edSCarlo Caione { 175d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt; 176d00680edSCarlo Caione struct resource *res; 177d00680edSCarlo Caione int err; 178d00680edSCarlo Caione 179d00680edSCarlo Caione sunxi_wdt = devm_kzalloc(&pdev->dev, sizeof(*sunxi_wdt), GFP_KERNEL); 180d00680edSCarlo Caione if (!sunxi_wdt) 181d00680edSCarlo Caione return -EINVAL; 182d00680edSCarlo Caione 183d00680edSCarlo Caione platform_set_drvdata(pdev, sunxi_wdt); 184d00680edSCarlo Caione 185d00680edSCarlo Caione res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 186d00680edSCarlo Caione sunxi_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); 187d00680edSCarlo Caione if (IS_ERR(sunxi_wdt->wdt_base)) 188d00680edSCarlo Caione return PTR_ERR(sunxi_wdt->wdt_base); 189d00680edSCarlo Caione 190d00680edSCarlo Caione sunxi_wdt->wdt_dev.info = &sunxi_wdt_info; 191d00680edSCarlo Caione sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops; 192d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT; 193d00680edSCarlo Caione sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT; 194d00680edSCarlo Caione sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT; 195d00680edSCarlo Caione sunxi_wdt->wdt_dev.parent = &pdev->dev; 196d00680edSCarlo Caione 197d00680edSCarlo Caione watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, &pdev->dev); 198d00680edSCarlo Caione watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout); 199d00680edSCarlo Caione 200d00680edSCarlo Caione watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt); 201d00680edSCarlo Caione 202d00680edSCarlo Caione sunxi_wdt_stop(&sunxi_wdt->wdt_dev); 203d00680edSCarlo Caione 204d00680edSCarlo Caione err = watchdog_register_device(&sunxi_wdt->wdt_dev); 205d00680edSCarlo Caione if (unlikely(err)) 206d00680edSCarlo Caione return err; 207d00680edSCarlo Caione 208*440e96bcSMaxime Ripard reboot_wdt_base = sunxi_wdt->wdt_base; 209*440e96bcSMaxime Ripard arm_pm_restart = sun4i_wdt_restart; 210*440e96bcSMaxime Ripard 211d00680edSCarlo Caione dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", 212d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout, nowayout); 213d00680edSCarlo Caione 214d00680edSCarlo Caione return 0; 215d00680edSCarlo Caione } 216d00680edSCarlo Caione 2171d5898b4SMaxime Ripard static int sunxi_wdt_remove(struct platform_device *pdev) 218d00680edSCarlo Caione { 219d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = platform_get_drvdata(pdev); 220d00680edSCarlo Caione 221*440e96bcSMaxime Ripard arm_pm_restart = NULL; 222*440e96bcSMaxime Ripard 223d00680edSCarlo Caione watchdog_unregister_device(&sunxi_wdt->wdt_dev); 224d00680edSCarlo Caione watchdog_set_drvdata(&sunxi_wdt->wdt_dev, NULL); 225d00680edSCarlo Caione 226d00680edSCarlo Caione return 0; 227d00680edSCarlo Caione } 228d00680edSCarlo Caione 229d00680edSCarlo Caione static void sunxi_wdt_shutdown(struct platform_device *pdev) 230d00680edSCarlo Caione { 231d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = platform_get_drvdata(pdev); 232d00680edSCarlo Caione 233d00680edSCarlo Caione sunxi_wdt_stop(&sunxi_wdt->wdt_dev); 234d00680edSCarlo Caione } 235d00680edSCarlo Caione 236d00680edSCarlo Caione static const struct of_device_id sunxi_wdt_dt_ids[] = { 237b0f1d8beSMaxime Ripard { .compatible = "allwinner,sun4i-a10-wdt" }, 238d00680edSCarlo Caione { /* sentinel */ } 239d00680edSCarlo Caione }; 240d00680edSCarlo Caione MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids); 241d00680edSCarlo Caione 242d00680edSCarlo Caione static struct platform_driver sunxi_wdt_driver = { 243d00680edSCarlo Caione .probe = sunxi_wdt_probe, 244d00680edSCarlo Caione .remove = sunxi_wdt_remove, 245d00680edSCarlo Caione .shutdown = sunxi_wdt_shutdown, 246d00680edSCarlo Caione .driver = { 247d00680edSCarlo Caione .owner = THIS_MODULE, 248d00680edSCarlo Caione .name = DRV_NAME, 24985eee819SSachin Kamat .of_match_table = sunxi_wdt_dt_ids, 250d00680edSCarlo Caione }, 251d00680edSCarlo Caione }; 252d00680edSCarlo Caione 253d00680edSCarlo Caione module_platform_driver(sunxi_wdt_driver); 254d00680edSCarlo Caione 255d00680edSCarlo Caione module_param(timeout, uint, 0); 256d00680edSCarlo Caione MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); 257d00680edSCarlo Caione 258d00680edSCarlo Caione module_param(nowayout, bool, 0); 259d00680edSCarlo Caione MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 260d00680edSCarlo Caione "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 261d00680edSCarlo Caione 262d00680edSCarlo Caione MODULE_LICENSE("GPL"); 263d00680edSCarlo Caione MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>"); 264d00680edSCarlo Caione MODULE_AUTHOR("Henrik Nordstrom <henrik@henriknordstrom.net>"); 265d00680edSCarlo Caione MODULE_DESCRIPTION("sunxi WatchDog Timer Driver"); 266d00680edSCarlo Caione MODULE_VERSION(DRV_VERSION); 267