1/* 2 * Meson Watchdog Driver 3 * 4 * Copyright (c) 2014 Carlo Caione 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 9 * 2 of the License, or (at your option) any later version. 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/notifier.h> 21#include <linux/of.h> 22#include <linux/platform_device.h> 23#include <linux/reboot.h> 24#include <linux/types.h> 25#include <linux/watchdog.h> 26 27#define DRV_NAME "meson_wdt" 28 29#define MESON_WDT_TC 0x00 30#define MESON_WDT_TC_EN BIT(22) 31#define MESON_WDT_TC_TM_MASK 0x3fffff 32#define MESON_WDT_DC_RESET (3 << 24) 33 34#define MESON_WDT_RESET 0x04 35 36#define MESON_WDT_TIMEOUT 30 37#define MESON_WDT_MIN_TIMEOUT 1 38#define MESON_WDT_MAX_TIMEOUT (MESON_WDT_TC_TM_MASK / 100000) 39 40#define MESON_SEC_TO_TC(s) ((s) * 100000) 41 42static bool nowayout = WATCHDOG_NOWAYOUT; 43static unsigned int timeout = MESON_WDT_TIMEOUT; 44 45struct meson_wdt_dev { 46 struct watchdog_device wdt_dev; 47 void __iomem *wdt_base; 48 struct notifier_block restart_handler; 49}; 50 51static int meson_restart_handle(struct notifier_block *this, unsigned long mode, 52 void *cmd) 53{ 54 u32 tc_reboot = MESON_WDT_DC_RESET | MESON_WDT_TC_EN; 55 struct meson_wdt_dev *meson_wdt = container_of(this, 56 struct meson_wdt_dev, 57 restart_handler); 58 59 while (1) { 60 writel(tc_reboot, meson_wdt->wdt_base + MESON_WDT_TC); 61 mdelay(5); 62 } 63 64 return NOTIFY_DONE; 65} 66 67static int meson_wdt_ping(struct watchdog_device *wdt_dev) 68{ 69 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 70 71 writel(0, meson_wdt->wdt_base + MESON_WDT_RESET); 72 73 return 0; 74} 75 76static void meson_wdt_change_timeout(struct watchdog_device *wdt_dev, 77 unsigned int timeout) 78{ 79 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 80 u32 reg; 81 82 reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); 83 reg &= ~MESON_WDT_TC_TM_MASK; 84 reg |= MESON_SEC_TO_TC(timeout); 85 writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); 86} 87 88static int meson_wdt_set_timeout(struct watchdog_device *wdt_dev, 89 unsigned int timeout) 90{ 91 wdt_dev->timeout = timeout; 92 93 meson_wdt_change_timeout(wdt_dev, timeout); 94 meson_wdt_ping(wdt_dev); 95 96 return 0; 97} 98 99static int meson_wdt_stop(struct watchdog_device *wdt_dev) 100{ 101 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 102 u32 reg; 103 104 reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); 105 reg &= ~MESON_WDT_TC_EN; 106 writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); 107 108 return 0; 109} 110 111static int meson_wdt_start(struct watchdog_device *wdt_dev) 112{ 113 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 114 u32 reg; 115 116 meson_wdt_change_timeout(wdt_dev, meson_wdt->wdt_dev.timeout); 117 meson_wdt_ping(wdt_dev); 118 119 reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); 120 reg |= MESON_WDT_TC_EN; 121 writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); 122 123 return 0; 124} 125 126static const struct watchdog_info meson_wdt_info = { 127 .identity = DRV_NAME, 128 .options = WDIOF_SETTIMEOUT | 129 WDIOF_KEEPALIVEPING | 130 WDIOF_MAGICCLOSE, 131}; 132 133static const struct watchdog_ops meson_wdt_ops = { 134 .owner = THIS_MODULE, 135 .start = meson_wdt_start, 136 .stop = meson_wdt_stop, 137 .ping = meson_wdt_ping, 138 .set_timeout = meson_wdt_set_timeout, 139}; 140 141static int meson_wdt_probe(struct platform_device *pdev) 142{ 143 struct resource *res; 144 struct meson_wdt_dev *meson_wdt; 145 int err; 146 147 meson_wdt = devm_kzalloc(&pdev->dev, sizeof(*meson_wdt), GFP_KERNEL); 148 if (!meson_wdt) 149 return -ENOMEM; 150 151 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 152 meson_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); 153 if (IS_ERR(meson_wdt->wdt_base)) 154 return PTR_ERR(meson_wdt->wdt_base); 155 156 meson_wdt->wdt_dev.parent = &pdev->dev; 157 meson_wdt->wdt_dev.info = &meson_wdt_info; 158 meson_wdt->wdt_dev.ops = &meson_wdt_ops; 159 meson_wdt->wdt_dev.timeout = MESON_WDT_TIMEOUT; 160 meson_wdt->wdt_dev.max_timeout = MESON_WDT_MAX_TIMEOUT; 161 meson_wdt->wdt_dev.min_timeout = MESON_WDT_MIN_TIMEOUT; 162 163 watchdog_set_drvdata(&meson_wdt->wdt_dev, meson_wdt); 164 165 watchdog_init_timeout(&meson_wdt->wdt_dev, timeout, &pdev->dev); 166 watchdog_set_nowayout(&meson_wdt->wdt_dev, nowayout); 167 168 meson_wdt_stop(&meson_wdt->wdt_dev); 169 170 err = watchdog_register_device(&meson_wdt->wdt_dev); 171 if (err) 172 return err; 173 174 platform_set_drvdata(pdev, meson_wdt); 175 176 meson_wdt->restart_handler.notifier_call = meson_restart_handle; 177 meson_wdt->restart_handler.priority = 128; 178 err = register_restart_handler(&meson_wdt->restart_handler); 179 if (err) 180 dev_err(&pdev->dev, 181 "cannot register restart handler (err=%d)\n", err); 182 183 dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", 184 meson_wdt->wdt_dev.timeout, nowayout); 185 186 return 0; 187} 188 189static int meson_wdt_remove(struct platform_device *pdev) 190{ 191 struct meson_wdt_dev *meson_wdt = platform_get_drvdata(pdev); 192 193 unregister_restart_handler(&meson_wdt->restart_handler); 194 195 watchdog_unregister_device(&meson_wdt->wdt_dev); 196 197 return 0; 198} 199 200static void meson_wdt_shutdown(struct platform_device *pdev) 201{ 202 struct meson_wdt_dev *meson_wdt = platform_get_drvdata(pdev); 203 204 meson_wdt_stop(&meson_wdt->wdt_dev); 205} 206 207static const struct of_device_id meson_wdt_dt_ids[] = { 208 { .compatible = "amlogic,meson6-wdt" }, 209 { /* sentinel */ } 210}; 211MODULE_DEVICE_TABLE(of, meson_wdt_dt_ids); 212 213static struct platform_driver meson_wdt_driver = { 214 .probe = meson_wdt_probe, 215 .remove = meson_wdt_remove, 216 .shutdown = meson_wdt_shutdown, 217 .driver = { 218 .owner = THIS_MODULE, 219 .name = DRV_NAME, 220 .of_match_table = meson_wdt_dt_ids, 221 }, 222}; 223 224module_platform_driver(meson_wdt_driver); 225 226module_param(timeout, uint, 0); 227MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); 228 229module_param(nowayout, bool, 0); 230MODULE_PARM_DESC(nowayout, 231 "Watchdog cannot be stopped once started (default=" 232 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 233 234MODULE_LICENSE("GPL"); 235MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); 236MODULE_DESCRIPTION("Meson Watchdog Timer Driver"); 237