1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2473cf939SJohn Crispin /* 3473cf939SJohn Crispin * Ralink RT288x/RT3xxx/MT76xx built-in hardware watchdog timer 4473cf939SJohn Crispin * 5473cf939SJohn Crispin * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org> 6f3519a66SJohn Crispin * Copyright (C) 2013 John Crispin <john@phrozen.org> 7473cf939SJohn Crispin * 8473cf939SJohn Crispin * This driver was based on: drivers/watchdog/softdog.c 9473cf939SJohn Crispin */ 10473cf939SJohn Crispin 11473cf939SJohn Crispin #include <linux/clk.h> 12473cf939SJohn Crispin #include <linux/reset.h> 13473cf939SJohn Crispin #include <linux/module.h> 14473cf939SJohn Crispin #include <linux/kernel.h> 15473cf939SJohn Crispin #include <linux/watchdog.h> 16473cf939SJohn Crispin #include <linux/moduleparam.h> 17473cf939SJohn Crispin #include <linux/platform_device.h> 183aa8b8bbSNeilBrown #include <linux/mod_devicetable.h> 19473cf939SJohn Crispin 20473cf939SJohn Crispin #include <asm/mach-ralink/ralink_regs.h> 21473cf939SJohn Crispin 22473cf939SJohn Crispin #define SYSC_RSTSTAT 0x38 23473cf939SJohn Crispin #define WDT_RST_CAUSE BIT(1) 24473cf939SJohn Crispin 25473cf939SJohn Crispin #define RALINK_WDT_TIMEOUT 30 26473cf939SJohn Crispin #define RALINK_WDT_PRESCALE 65536 27473cf939SJohn Crispin 28473cf939SJohn Crispin #define TIMER_REG_TMR1LOAD 0x00 29473cf939SJohn Crispin #define TIMER_REG_TMR1CTL 0x08 30473cf939SJohn Crispin 31473cf939SJohn Crispin #define TMRSTAT_TMR1RST BIT(5) 32473cf939SJohn Crispin 33473cf939SJohn Crispin #define TMR1CTL_ENABLE BIT(7) 34473cf939SJohn Crispin #define TMR1CTL_MODE_SHIFT 4 35473cf939SJohn Crispin #define TMR1CTL_MODE_MASK 0x3 36473cf939SJohn Crispin #define TMR1CTL_MODE_FREE_RUNNING 0x0 37473cf939SJohn Crispin #define TMR1CTL_MODE_PERIODIC 0x1 38473cf939SJohn Crispin #define TMR1CTL_MODE_TIMEOUT 0x2 39473cf939SJohn Crispin #define TMR1CTL_MODE_WDT 0x3 40473cf939SJohn Crispin #define TMR1CTL_PRESCALE_MASK 0xf 41473cf939SJohn Crispin #define TMR1CTL_PRESCALE_65536 0xf 42473cf939SJohn Crispin 4376ad36bfSSergio Paracuellos struct rt2880_wdt_data { 4476ad36bfSSergio Paracuellos void __iomem *base; 4576ad36bfSSergio Paracuellos unsigned long freq; 4676ad36bfSSergio Paracuellos struct clk *clk; 4776ad36bfSSergio Paracuellos struct reset_control *rst; 4876ad36bfSSergio Paracuellos struct watchdog_device wdt; 4976ad36bfSSergio Paracuellos }; 50473cf939SJohn Crispin 51473cf939SJohn Crispin static bool nowayout = WATCHDOG_NOWAYOUT; 52473cf939SJohn Crispin module_param(nowayout, bool, 0); 53473cf939SJohn Crispin MODULE_PARM_DESC(nowayout, 54473cf939SJohn Crispin "Watchdog cannot be stopped once started (default=" 55473cf939SJohn Crispin __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 56473cf939SJohn Crispin 57*af3ac8e4SSergio Paracuellos static inline void rt_wdt_w32(void __iomem *base, unsigned int reg, u32 val) 58473cf939SJohn Crispin { 5976ad36bfSSergio Paracuellos iowrite32(val, base + reg); 60473cf939SJohn Crispin } 61473cf939SJohn Crispin 62*af3ac8e4SSergio Paracuellos static inline u32 rt_wdt_r32(void __iomem *base, unsigned int reg) 63473cf939SJohn Crispin { 6476ad36bfSSergio Paracuellos return ioread32(base + reg); 65473cf939SJohn Crispin } 66473cf939SJohn Crispin 67473cf939SJohn Crispin static int rt288x_wdt_ping(struct watchdog_device *w) 68473cf939SJohn Crispin { 6976ad36bfSSergio Paracuellos struct rt2880_wdt_data *drvdata = watchdog_get_drvdata(w); 7076ad36bfSSergio Paracuellos 7176ad36bfSSergio Paracuellos rt_wdt_w32(drvdata->base, TIMER_REG_TMR1LOAD, w->timeout * drvdata->freq); 72473cf939SJohn Crispin 73473cf939SJohn Crispin return 0; 74473cf939SJohn Crispin } 75473cf939SJohn Crispin 76473cf939SJohn Crispin static int rt288x_wdt_start(struct watchdog_device *w) 77473cf939SJohn Crispin { 7876ad36bfSSergio Paracuellos struct rt2880_wdt_data *drvdata = watchdog_get_drvdata(w); 79473cf939SJohn Crispin u32 t; 80473cf939SJohn Crispin 8176ad36bfSSergio Paracuellos t = rt_wdt_r32(drvdata->base, TIMER_REG_TMR1CTL); 82473cf939SJohn Crispin t &= ~(TMR1CTL_MODE_MASK << TMR1CTL_MODE_SHIFT | 83473cf939SJohn Crispin TMR1CTL_PRESCALE_MASK); 84473cf939SJohn Crispin t |= (TMR1CTL_MODE_WDT << TMR1CTL_MODE_SHIFT | 85473cf939SJohn Crispin TMR1CTL_PRESCALE_65536); 8676ad36bfSSergio Paracuellos rt_wdt_w32(drvdata->base, TIMER_REG_TMR1CTL, t); 87473cf939SJohn Crispin 88473cf939SJohn Crispin rt288x_wdt_ping(w); 89473cf939SJohn Crispin 9076ad36bfSSergio Paracuellos t = rt_wdt_r32(drvdata->base, TIMER_REG_TMR1CTL); 91473cf939SJohn Crispin t |= TMR1CTL_ENABLE; 9276ad36bfSSergio Paracuellos rt_wdt_w32(drvdata->base, TIMER_REG_TMR1CTL, t); 93473cf939SJohn Crispin 94473cf939SJohn Crispin return 0; 95473cf939SJohn Crispin } 96473cf939SJohn Crispin 97473cf939SJohn Crispin static int rt288x_wdt_stop(struct watchdog_device *w) 98473cf939SJohn Crispin { 9976ad36bfSSergio Paracuellos struct rt2880_wdt_data *drvdata = watchdog_get_drvdata(w); 100473cf939SJohn Crispin u32 t; 101473cf939SJohn Crispin 102473cf939SJohn Crispin rt288x_wdt_ping(w); 103473cf939SJohn Crispin 10476ad36bfSSergio Paracuellos t = rt_wdt_r32(drvdata->base, TIMER_REG_TMR1CTL); 105473cf939SJohn Crispin t &= ~TMR1CTL_ENABLE; 10676ad36bfSSergio Paracuellos rt_wdt_w32(drvdata->base, TIMER_REG_TMR1CTL, t); 107473cf939SJohn Crispin 108473cf939SJohn Crispin return 0; 109473cf939SJohn Crispin } 110473cf939SJohn Crispin 111473cf939SJohn Crispin static int rt288x_wdt_set_timeout(struct watchdog_device *w, unsigned int t) 112473cf939SJohn Crispin { 113473cf939SJohn Crispin w->timeout = t; 114473cf939SJohn Crispin rt288x_wdt_ping(w); 115473cf939SJohn Crispin 116473cf939SJohn Crispin return 0; 117473cf939SJohn Crispin } 118473cf939SJohn Crispin 119473cf939SJohn Crispin static int rt288x_wdt_bootcause(void) 120473cf939SJohn Crispin { 121473cf939SJohn Crispin if (rt_sysc_r32(SYSC_RSTSTAT) & WDT_RST_CAUSE) 122473cf939SJohn Crispin return WDIOF_CARDRESET; 123473cf939SJohn Crispin 124473cf939SJohn Crispin return 0; 125473cf939SJohn Crispin } 126473cf939SJohn Crispin 127323edb2eSJulia Lawall static const struct watchdog_info rt288x_wdt_info = { 128473cf939SJohn Crispin .identity = "Ralink Watchdog", 129473cf939SJohn Crispin .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 130473cf939SJohn Crispin }; 131473cf939SJohn Crispin 132b893e344SBhumika Goyal static const struct watchdog_ops rt288x_wdt_ops = { 133473cf939SJohn Crispin .owner = THIS_MODULE, 134473cf939SJohn Crispin .start = rt288x_wdt_start, 135473cf939SJohn Crispin .stop = rt288x_wdt_stop, 136473cf939SJohn Crispin .ping = rt288x_wdt_ping, 137473cf939SJohn Crispin .set_timeout = rt288x_wdt_set_timeout, 138473cf939SJohn Crispin }; 139473cf939SJohn Crispin 140473cf939SJohn Crispin static int rt288x_wdt_probe(struct platform_device *pdev) 141473cf939SJohn Crispin { 142570927dfSGuenter Roeck struct device *dev = &pdev->dev; 14376ad36bfSSergio Paracuellos struct watchdog_device *wdt; 14476ad36bfSSergio Paracuellos struct rt2880_wdt_data *drvdata; 145473cf939SJohn Crispin int ret; 146473cf939SJohn Crispin 14776ad36bfSSergio Paracuellos drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); 14876ad36bfSSergio Paracuellos if (!drvdata) 14976ad36bfSSergio Paracuellos return -ENOMEM; 150473cf939SJohn Crispin 15176ad36bfSSergio Paracuellos drvdata->base = devm_platform_ioremap_resource(pdev, 0); 15276ad36bfSSergio Paracuellos if (IS_ERR(drvdata->base)) 15376ad36bfSSergio Paracuellos return PTR_ERR(drvdata->base); 154473cf939SJohn Crispin 15576ad36bfSSergio Paracuellos drvdata->clk = devm_clk_get(dev, NULL); 15676ad36bfSSergio Paracuellos if (IS_ERR(drvdata->clk)) 15776ad36bfSSergio Paracuellos return PTR_ERR(drvdata->clk); 158473cf939SJohn Crispin 15976ad36bfSSergio Paracuellos drvdata->rst = devm_reset_control_get_exclusive(dev, NULL); 16076ad36bfSSergio Paracuellos if (!IS_ERR(drvdata->rst)) 16176ad36bfSSergio Paracuellos reset_control_deassert(drvdata->rst); 162473cf939SJohn Crispin 16376ad36bfSSergio Paracuellos drvdata->freq = clk_get_rate(drvdata->clk) / RALINK_WDT_PRESCALE; 164473cf939SJohn Crispin 16576ad36bfSSergio Paracuellos wdt = &drvdata->wdt; 16676ad36bfSSergio Paracuellos wdt->info = &rt288x_wdt_info; 16776ad36bfSSergio Paracuellos wdt->ops = &rt288x_wdt_ops; 16876ad36bfSSergio Paracuellos wdt->min_timeout = 1; 16976ad36bfSSergio Paracuellos wdt->max_timeout = (0xfffful / drvdata->freq); 17076ad36bfSSergio Paracuellos wdt->parent = dev; 17176ad36bfSSergio Paracuellos wdt->bootstatus = rt288x_wdt_bootcause(); 172473cf939SJohn Crispin 17376ad36bfSSergio Paracuellos watchdog_init_timeout(wdt, wdt->max_timeout, dev); 17476ad36bfSSergio Paracuellos watchdog_set_nowayout(wdt, nowayout); 17576ad36bfSSergio Paracuellos watchdog_set_drvdata(wdt, drvdata); 17676ad36bfSSergio Paracuellos 17776ad36bfSSergio Paracuellos watchdog_stop_on_reboot(wdt); 17876ad36bfSSergio Paracuellos ret = devm_watchdog_register_device(dev, &drvdata->wdt); 179473cf939SJohn Crispin if (!ret) 180570927dfSGuenter Roeck dev_info(dev, "Initialized\n"); 181473cf939SJohn Crispin 182473cf939SJohn Crispin return 0; 183473cf939SJohn Crispin } 184473cf939SJohn Crispin 185473cf939SJohn Crispin static const struct of_device_id rt288x_wdt_match[] = { 186473cf939SJohn Crispin { .compatible = "ralink,rt2880-wdt" }, 187473cf939SJohn Crispin {}, 188473cf939SJohn Crispin }; 189473cf939SJohn Crispin MODULE_DEVICE_TABLE(of, rt288x_wdt_match); 190473cf939SJohn Crispin 191473cf939SJohn Crispin static struct platform_driver rt288x_wdt_driver = { 192473cf939SJohn Crispin .probe = rt288x_wdt_probe, 193473cf939SJohn Crispin .driver = { 194473cf939SJohn Crispin .name = KBUILD_MODNAME, 195473cf939SJohn Crispin .of_match_table = rt288x_wdt_match, 196473cf939SJohn Crispin }, 197473cf939SJohn Crispin }; 198473cf939SJohn Crispin 199473cf939SJohn Crispin module_platform_driver(rt288x_wdt_driver); 200473cf939SJohn Crispin 201473cf939SJohn Crispin MODULE_DESCRIPTION("MediaTek/Ralink RT288x/RT3xxx hardware watchdog driver"); 202473cf939SJohn Crispin MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org"); 203473cf939SJohn Crispin MODULE_LICENSE("GPL v2"); 204