313 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * sunxi Watchdog Driver4 *5 * Copyright (c) 2013 Carlo Caione6 * 2012 Henrik Nordstrom7 *8 * Based on xen_wdt.c9 * (c) Copyright 2010 Novell, Inc.10 */11 12#include <linux/clk.h>13#include <linux/delay.h>14#include <linux/err.h>15#include <linux/init.h>16#include <linux/io.h>17#include <linux/kernel.h>18#include <linux/module.h>19#include <linux/moduleparam.h>20#include <linux/of.h>21#include <linux/platform_device.h>22#include <linux/types.h>23#include <linux/watchdog.h>24 25#define WDT_MAX_TIMEOUT 1626#define WDT_MIN_TIMEOUT 127#define WDT_TIMEOUT_MASK 0x0F28 29#define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1))30 31#define WDT_MODE_EN (1 << 0)32 33#define DRV_NAME "sunxi-wdt"34#define DRV_VERSION "1.0"35 36static bool nowayout = WATCHDOG_NOWAYOUT;37static unsigned int timeout;38 39/*40 * This structure stores the register offsets for different variants41 * of Allwinner's watchdog hardware.42 */43struct sunxi_wdt_reg {44 u8 wdt_ctrl;45 u8 wdt_cfg;46 u8 wdt_mode;47 u8 wdt_timeout_shift;48 u8 wdt_reset_mask;49 u8 wdt_reset_val;50 u32 wdt_key_val;51};52 53struct sunxi_wdt_dev {54 struct watchdog_device wdt_dev;55 void __iomem *wdt_base;56 const struct sunxi_wdt_reg *wdt_regs;57};58 59/*60 * wdt_timeout_map maps the watchdog timer interval value in seconds to61 * the value of the register WDT_MODE at bits .wdt_timeout_shift ~ +362 *63 * [timeout seconds] = register value64 *65 */66 67static const int wdt_timeout_map[] = {68 [1] = 0x1, /* 1s */69 [2] = 0x2, /* 2s */70 [3] = 0x3, /* 3s */71 [4] = 0x4, /* 4s */72 [5] = 0x5, /* 5s */73 [6] = 0x6, /* 6s */74 [8] = 0x7, /* 8s */75 [10] = 0x8, /* 10s */76 [12] = 0x9, /* 12s */77 [14] = 0xA, /* 14s */78 [16] = 0xB, /* 16s */79};80 81 82static int sunxi_wdt_restart(struct watchdog_device *wdt_dev,83 unsigned long action, void *data)84{85 struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);86 void __iomem *wdt_base = sunxi_wdt->wdt_base;87 const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;88 u32 val;89 90 /* Set system reset function */91 val = readl(wdt_base + regs->wdt_cfg);92 val &= ~(regs->wdt_reset_mask);93 val |= regs->wdt_reset_val;94 val |= regs->wdt_key_val;95 writel(val, wdt_base + regs->wdt_cfg);96 97 /* Set lowest timeout and enable watchdog */98 val = readl(wdt_base + regs->wdt_mode);99 val &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);100 val |= WDT_MODE_EN;101 val |= regs->wdt_key_val;102 writel(val, wdt_base + regs->wdt_mode);103 104 /*105 * Restart the watchdog. The default (and lowest) interval106 * value for the watchdog is 0.5s.107 */108 writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);109 110 while (1) {111 mdelay(5);112 val = readl(wdt_base + regs->wdt_mode);113 val |= WDT_MODE_EN;114 val |= regs->wdt_key_val;115 writel(val, wdt_base + regs->wdt_mode);116 }117 return 0;118}119 120static int sunxi_wdt_ping(struct watchdog_device *wdt_dev)121{122 struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);123 void __iomem *wdt_base = sunxi_wdt->wdt_base;124 const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;125 126 writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);127 128 return 0;129}130 131static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev,132 unsigned int timeout)133{134 struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);135 void __iomem *wdt_base = sunxi_wdt->wdt_base;136 const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;137 u32 reg;138 139 if (wdt_timeout_map[timeout] == 0)140 timeout++;141 142 sunxi_wdt->wdt_dev.timeout = timeout;143 144 reg = readl(wdt_base + regs->wdt_mode);145 reg &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);146 reg |= wdt_timeout_map[timeout] << regs->wdt_timeout_shift;147 reg |= regs->wdt_key_val;148 writel(reg, wdt_base + regs->wdt_mode);149 150 sunxi_wdt_ping(wdt_dev);151 152 return 0;153}154 155static int sunxi_wdt_stop(struct watchdog_device *wdt_dev)156{157 struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);158 void __iomem *wdt_base = sunxi_wdt->wdt_base;159 const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;160 161 writel(regs->wdt_key_val, wdt_base + regs->wdt_mode);162 163 return 0;164}165 166static int sunxi_wdt_start(struct watchdog_device *wdt_dev)167{168 u32 reg;169 struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);170 void __iomem *wdt_base = sunxi_wdt->wdt_base;171 const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;172 int ret;173 174 ret = sunxi_wdt_set_timeout(&sunxi_wdt->wdt_dev,175 sunxi_wdt->wdt_dev.timeout);176 if (ret < 0)177 return ret;178 179 /* Set system reset function */180 reg = readl(wdt_base + regs->wdt_cfg);181 reg &= ~(regs->wdt_reset_mask);182 reg |= regs->wdt_reset_val;183 reg |= regs->wdt_key_val;184 writel(reg, wdt_base + regs->wdt_cfg);185 186 /* Enable watchdog */187 reg = readl(wdt_base + regs->wdt_mode);188 reg |= WDT_MODE_EN;189 reg |= regs->wdt_key_val;190 writel(reg, wdt_base + regs->wdt_mode);191 192 return 0;193}194 195static const struct watchdog_info sunxi_wdt_info = {196 .identity = DRV_NAME,197 .options = WDIOF_SETTIMEOUT |198 WDIOF_KEEPALIVEPING |199 WDIOF_MAGICCLOSE,200};201 202static const struct watchdog_ops sunxi_wdt_ops = {203 .owner = THIS_MODULE,204 .start = sunxi_wdt_start,205 .stop = sunxi_wdt_stop,206 .ping = sunxi_wdt_ping,207 .set_timeout = sunxi_wdt_set_timeout,208 .restart = sunxi_wdt_restart,209};210 211static const struct sunxi_wdt_reg sun4i_wdt_reg = {212 .wdt_ctrl = 0x00,213 .wdt_cfg = 0x04,214 .wdt_mode = 0x04,215 .wdt_timeout_shift = 3,216 .wdt_reset_mask = 0x02,217 .wdt_reset_val = 0x02,218};219 220static const struct sunxi_wdt_reg sun6i_wdt_reg = {221 .wdt_ctrl = 0x10,222 .wdt_cfg = 0x14,223 .wdt_mode = 0x18,224 .wdt_timeout_shift = 4,225 .wdt_reset_mask = 0x03,226 .wdt_reset_val = 0x01,227};228 229static const struct sunxi_wdt_reg sun20i_wdt_reg = {230 .wdt_ctrl = 0x10,231 .wdt_cfg = 0x14,232 .wdt_mode = 0x18,233 .wdt_timeout_shift = 4,234 .wdt_reset_mask = 0x03,235 .wdt_reset_val = 0x01,236 .wdt_key_val = 0x16aa0000,237};238 239static const struct of_device_id sunxi_wdt_dt_ids[] = {240 { .compatible = "allwinner,sun4i-a10-wdt", .data = &sun4i_wdt_reg },241 { .compatible = "allwinner,sun6i-a31-wdt", .data = &sun6i_wdt_reg },242 { .compatible = "allwinner,sun20i-d1-wdt", .data = &sun20i_wdt_reg },243 { /* sentinel */ }244};245MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids);246 247static int sunxi_wdt_probe(struct platform_device *pdev)248{249 struct device *dev = &pdev->dev;250 struct sunxi_wdt_dev *sunxi_wdt;251 int err;252 253 sunxi_wdt = devm_kzalloc(dev, sizeof(*sunxi_wdt), GFP_KERNEL);254 if (!sunxi_wdt)255 return -ENOMEM;256 257 sunxi_wdt->wdt_regs = of_device_get_match_data(dev);258 if (!sunxi_wdt->wdt_regs)259 return -ENODEV;260 261 sunxi_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);262 if (IS_ERR(sunxi_wdt->wdt_base))263 return PTR_ERR(sunxi_wdt->wdt_base);264 265 sunxi_wdt->wdt_dev.info = &sunxi_wdt_info;266 sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops;267 sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT;268 sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT;269 sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;270 sunxi_wdt->wdt_dev.parent = dev;271 272 watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, dev);273 watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout);274 watchdog_set_restart_priority(&sunxi_wdt->wdt_dev, 128);275 276 watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt);277 278 sunxi_wdt_stop(&sunxi_wdt->wdt_dev);279 280 watchdog_stop_on_reboot(&sunxi_wdt->wdt_dev);281 err = devm_watchdog_register_device(dev, &sunxi_wdt->wdt_dev);282 if (unlikely(err))283 return err;284 285 dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",286 sunxi_wdt->wdt_dev.timeout, nowayout);287 288 return 0;289}290 291static struct platform_driver sunxi_wdt_driver = {292 .probe = sunxi_wdt_probe,293 .driver = {294 .name = DRV_NAME,295 .of_match_table = sunxi_wdt_dt_ids,296 },297};298 299module_platform_driver(sunxi_wdt_driver);300 301module_param(timeout, uint, 0);302MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");303 304module_param(nowayout, bool, 0);305MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "306 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");307 308MODULE_LICENSE("GPL");309MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>");310MODULE_AUTHOR("Henrik Nordstrom <henrik@henriknordstrom.net>");311MODULE_DESCRIPTION("sunxi WatchDog Timer Driver");312MODULE_VERSION(DRV_VERSION);313