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> 17d00680edSCarlo Caione #include <linux/err.h> 18d00680edSCarlo Caione #include <linux/init.h> 19d00680edSCarlo Caione #include <linux/io.h> 20d00680edSCarlo Caione #include <linux/kernel.h> 21d00680edSCarlo Caione #include <linux/module.h> 22d00680edSCarlo Caione #include <linux/moduleparam.h> 23d00680edSCarlo Caione #include <linux/of.h> 24d00680edSCarlo Caione #include <linux/platform_device.h> 25d00680edSCarlo Caione #include <linux/types.h> 26d00680edSCarlo Caione #include <linux/watchdog.h> 27d00680edSCarlo Caione 28d00680edSCarlo Caione #define WDT_MAX_TIMEOUT 16 29d00680edSCarlo Caione #define WDT_MIN_TIMEOUT 1 30d00680edSCarlo Caione #define WDT_MODE_TIMEOUT(n) ((n) << 3) 31d00680edSCarlo Caione #define WDT_TIMEOUT_MASK WDT_MODE_TIMEOUT(0x0F) 32d00680edSCarlo Caione 33d00680edSCarlo Caione #define WDT_CTRL 0x00 34d00680edSCarlo Caione #define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1)) 35d00680edSCarlo Caione 36d00680edSCarlo Caione #define WDT_MODE 0x04 37d00680edSCarlo Caione #define WDT_MODE_EN (1 << 0) 38d00680edSCarlo Caione #define WDT_MODE_RST_EN (1 << 1) 39d00680edSCarlo Caione 40d00680edSCarlo Caione #define DRV_NAME "sunxi-wdt" 41d00680edSCarlo Caione #define DRV_VERSION "1.0" 42d00680edSCarlo Caione 43d00680edSCarlo Caione static bool nowayout = WATCHDOG_NOWAYOUT; 44d00680edSCarlo Caione static unsigned int timeout = WDT_MAX_TIMEOUT; 45d00680edSCarlo Caione 46d00680edSCarlo Caione struct sunxi_wdt_dev { 47d00680edSCarlo Caione struct watchdog_device wdt_dev; 48d00680edSCarlo Caione void __iomem *wdt_base; 49d00680edSCarlo Caione }; 50d00680edSCarlo Caione 51d00680edSCarlo Caione /* 52d00680edSCarlo Caione * wdt_timeout_map maps the watchdog timer interval value in seconds to 53d00680edSCarlo Caione * the value of the register WDT_MODE bit 3:6 54d00680edSCarlo Caione * 55d00680edSCarlo Caione * [timeout seconds] = register value 56d00680edSCarlo Caione * 57d00680edSCarlo Caione */ 58d00680edSCarlo Caione 59d00680edSCarlo Caione static const int wdt_timeout_map[] = { 60*51ee34abSEmilio López [1] = 0x1, /* 1s */ 61*51ee34abSEmilio López [2] = 0x2, /* 2s */ 62*51ee34abSEmilio López [3] = 0x3, /* 3s */ 63*51ee34abSEmilio López [4] = 0x4, /* 4s */ 64*51ee34abSEmilio López [5] = 0x5, /* 5s */ 65*51ee34abSEmilio López [6] = 0x6, /* 6s */ 66*51ee34abSEmilio López [8] = 0x7, /* 8s */ 67*51ee34abSEmilio López [10] = 0x8, /* 10s */ 68*51ee34abSEmilio López [12] = 0x9, /* 12s */ 69*51ee34abSEmilio López [14] = 0xA, /* 14s */ 70*51ee34abSEmilio López [16] = 0xB, /* 16s */ 71d00680edSCarlo Caione }; 72d00680edSCarlo Caione 73d00680edSCarlo Caione static int sunxi_wdt_ping(struct watchdog_device *wdt_dev) 74d00680edSCarlo Caione { 75d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 76d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base; 77d00680edSCarlo Caione 78d00680edSCarlo Caione iowrite32(WDT_CTRL_RELOAD, wdt_base + WDT_CTRL); 79d00680edSCarlo Caione 80d00680edSCarlo Caione return 0; 81d00680edSCarlo Caione } 82d00680edSCarlo Caione 83d00680edSCarlo Caione static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev, 84d00680edSCarlo Caione unsigned int timeout) 85d00680edSCarlo Caione { 86d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 87d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base; 88d00680edSCarlo Caione u32 reg; 89d00680edSCarlo Caione 90d00680edSCarlo Caione if (wdt_timeout_map[timeout] == 0) 91d00680edSCarlo Caione timeout++; 92d00680edSCarlo Caione 93d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout = timeout; 94d00680edSCarlo Caione 95d00680edSCarlo Caione reg = ioread32(wdt_base + WDT_MODE); 96d00680edSCarlo Caione reg &= ~WDT_TIMEOUT_MASK; 97d00680edSCarlo Caione reg |= WDT_MODE_TIMEOUT(wdt_timeout_map[timeout]); 98d00680edSCarlo Caione iowrite32(reg, wdt_base + WDT_MODE); 99d00680edSCarlo Caione 100d00680edSCarlo Caione sunxi_wdt_ping(wdt_dev); 101d00680edSCarlo Caione 102d00680edSCarlo Caione return 0; 103d00680edSCarlo Caione } 104d00680edSCarlo Caione 105d00680edSCarlo Caione static int sunxi_wdt_stop(struct watchdog_device *wdt_dev) 106d00680edSCarlo Caione { 107d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 108d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base; 109d00680edSCarlo Caione 110d00680edSCarlo Caione iowrite32(0, wdt_base + WDT_MODE); 111d00680edSCarlo Caione 112d00680edSCarlo Caione return 0; 113d00680edSCarlo Caione } 114d00680edSCarlo Caione 115d00680edSCarlo Caione static int sunxi_wdt_start(struct watchdog_device *wdt_dev) 116d00680edSCarlo Caione { 117d00680edSCarlo Caione u32 reg; 118d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 119d00680edSCarlo Caione void __iomem *wdt_base = sunxi_wdt->wdt_base; 120d00680edSCarlo Caione int ret; 121d00680edSCarlo Caione 122d00680edSCarlo Caione ret = sunxi_wdt_set_timeout(&sunxi_wdt->wdt_dev, 123d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout); 124d00680edSCarlo Caione if (ret < 0) 125d00680edSCarlo Caione return ret; 126d00680edSCarlo Caione 127d00680edSCarlo Caione reg = ioread32(wdt_base + WDT_MODE); 128d00680edSCarlo Caione reg |= (WDT_MODE_RST_EN | WDT_MODE_EN); 129d00680edSCarlo Caione iowrite32(reg, wdt_base + WDT_MODE); 130d00680edSCarlo Caione 131d00680edSCarlo Caione return 0; 132d00680edSCarlo Caione } 133d00680edSCarlo Caione 134d00680edSCarlo Caione static const struct watchdog_info sunxi_wdt_info = { 135d00680edSCarlo Caione .identity = DRV_NAME, 136d00680edSCarlo Caione .options = WDIOF_SETTIMEOUT | 137d00680edSCarlo Caione WDIOF_KEEPALIVEPING | 138d00680edSCarlo Caione WDIOF_MAGICCLOSE, 139d00680edSCarlo Caione }; 140d00680edSCarlo Caione 141d00680edSCarlo Caione static const struct watchdog_ops sunxi_wdt_ops = { 142d00680edSCarlo Caione .owner = THIS_MODULE, 143d00680edSCarlo Caione .start = sunxi_wdt_start, 144d00680edSCarlo Caione .stop = sunxi_wdt_stop, 145d00680edSCarlo Caione .ping = sunxi_wdt_ping, 146d00680edSCarlo Caione .set_timeout = sunxi_wdt_set_timeout, 147d00680edSCarlo Caione }; 148d00680edSCarlo Caione 1491d5898b4SMaxime Ripard static int sunxi_wdt_probe(struct platform_device *pdev) 150d00680edSCarlo Caione { 151d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt; 152d00680edSCarlo Caione struct resource *res; 153d00680edSCarlo Caione int err; 154d00680edSCarlo Caione 155d00680edSCarlo Caione sunxi_wdt = devm_kzalloc(&pdev->dev, sizeof(*sunxi_wdt), GFP_KERNEL); 156d00680edSCarlo Caione if (!sunxi_wdt) 157d00680edSCarlo Caione return -EINVAL; 158d00680edSCarlo Caione 159d00680edSCarlo Caione platform_set_drvdata(pdev, sunxi_wdt); 160d00680edSCarlo Caione 161d00680edSCarlo Caione res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 162d00680edSCarlo Caione sunxi_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); 163d00680edSCarlo Caione if (IS_ERR(sunxi_wdt->wdt_base)) 164d00680edSCarlo Caione return PTR_ERR(sunxi_wdt->wdt_base); 165d00680edSCarlo Caione 166d00680edSCarlo Caione sunxi_wdt->wdt_dev.info = &sunxi_wdt_info; 167d00680edSCarlo Caione sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops; 168d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT; 169d00680edSCarlo Caione sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT; 170d00680edSCarlo Caione sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT; 171d00680edSCarlo Caione sunxi_wdt->wdt_dev.parent = &pdev->dev; 172d00680edSCarlo Caione 173d00680edSCarlo Caione watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, &pdev->dev); 174d00680edSCarlo Caione watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout); 175d00680edSCarlo Caione 176d00680edSCarlo Caione watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt); 177d00680edSCarlo Caione 178d00680edSCarlo Caione sunxi_wdt_stop(&sunxi_wdt->wdt_dev); 179d00680edSCarlo Caione 180d00680edSCarlo Caione err = watchdog_register_device(&sunxi_wdt->wdt_dev); 181d00680edSCarlo Caione if (unlikely(err)) 182d00680edSCarlo Caione return err; 183d00680edSCarlo Caione 184d00680edSCarlo Caione dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", 185d00680edSCarlo Caione sunxi_wdt->wdt_dev.timeout, nowayout); 186d00680edSCarlo Caione 187d00680edSCarlo Caione return 0; 188d00680edSCarlo Caione } 189d00680edSCarlo Caione 1901d5898b4SMaxime Ripard static int sunxi_wdt_remove(struct platform_device *pdev) 191d00680edSCarlo Caione { 192d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = platform_get_drvdata(pdev); 193d00680edSCarlo Caione 194d00680edSCarlo Caione watchdog_unregister_device(&sunxi_wdt->wdt_dev); 195d00680edSCarlo Caione watchdog_set_drvdata(&sunxi_wdt->wdt_dev, NULL); 196d00680edSCarlo Caione 197d00680edSCarlo Caione return 0; 198d00680edSCarlo Caione } 199d00680edSCarlo Caione 200d00680edSCarlo Caione static void sunxi_wdt_shutdown(struct platform_device *pdev) 201d00680edSCarlo Caione { 202d00680edSCarlo Caione struct sunxi_wdt_dev *sunxi_wdt = platform_get_drvdata(pdev); 203d00680edSCarlo Caione 204d00680edSCarlo Caione sunxi_wdt_stop(&sunxi_wdt->wdt_dev); 205d00680edSCarlo Caione } 206d00680edSCarlo Caione 207d00680edSCarlo Caione static const struct of_device_id sunxi_wdt_dt_ids[] = { 208b0f1d8beSMaxime Ripard { .compatible = "allwinner,sun4i-a10-wdt" }, 209d00680edSCarlo Caione { /* sentinel */ } 210d00680edSCarlo Caione }; 211d00680edSCarlo Caione MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids); 212d00680edSCarlo Caione 213d00680edSCarlo Caione static struct platform_driver sunxi_wdt_driver = { 214d00680edSCarlo Caione .probe = sunxi_wdt_probe, 215d00680edSCarlo Caione .remove = sunxi_wdt_remove, 216d00680edSCarlo Caione .shutdown = sunxi_wdt_shutdown, 217d00680edSCarlo Caione .driver = { 218d00680edSCarlo Caione .owner = THIS_MODULE, 219d00680edSCarlo Caione .name = DRV_NAME, 22085eee819SSachin Kamat .of_match_table = sunxi_wdt_dt_ids, 221d00680edSCarlo Caione }, 222d00680edSCarlo Caione }; 223d00680edSCarlo Caione 224d00680edSCarlo Caione module_platform_driver(sunxi_wdt_driver); 225d00680edSCarlo Caione 226d00680edSCarlo Caione module_param(timeout, uint, 0); 227d00680edSCarlo Caione MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); 228d00680edSCarlo Caione 229d00680edSCarlo Caione module_param(nowayout, bool, 0); 230d00680edSCarlo Caione MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 231d00680edSCarlo Caione "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 232d00680edSCarlo Caione 233d00680edSCarlo Caione MODULE_LICENSE("GPL"); 234d00680edSCarlo Caione MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>"); 235d00680edSCarlo Caione MODULE_AUTHOR("Henrik Nordstrom <henrik@henriknordstrom.net>"); 236d00680edSCarlo Caione MODULE_DESCRIPTION("sunxi WatchDog Timer Driver"); 237d00680edSCarlo Caione MODULE_VERSION(DRV_VERSION); 238