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 42 static bool nowayout = WATCHDOG_NOWAYOUT; 43 static unsigned int timeout = MESON_WDT_TIMEOUT; 44 45 struct meson_wdt_dev { 46 struct watchdog_device wdt_dev; 47 void __iomem *wdt_base; 48 struct notifier_block restart_handler; 49 }; 50 51 static 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 67 static 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 76 static 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 88 static 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 99 static 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 111 static 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 126 static const struct watchdog_info meson_wdt_info = { 127 .identity = DRV_NAME, 128 .options = WDIOF_SETTIMEOUT | 129 WDIOF_KEEPALIVEPING | 130 WDIOF_MAGICCLOSE, 131 }; 132 133 static 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 141 static 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 189 static 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 200 static 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 207 static const struct of_device_id meson_wdt_dt_ids[] = { 208 { .compatible = "amlogic,meson6-wdt" }, 209 { /* sentinel */ } 210 }; 211 MODULE_DEVICE_TABLE(of, meson_wdt_dt_ids); 212 213 static struct platform_driver meson_wdt_driver = { 214 .probe = meson_wdt_probe, 215 .remove = meson_wdt_remove, 216 .shutdown = meson_wdt_shutdown, 217 .driver = { 218 .name = DRV_NAME, 219 .of_match_table = meson_wdt_dt_ids, 220 }, 221 }; 222 223 module_platform_driver(meson_wdt_driver); 224 225 module_param(timeout, uint, 0); 226 MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); 227 228 module_param(nowayout, bool, 0); 229 MODULE_PARM_DESC(nowayout, 230 "Watchdog cannot be stopped once started (default=" 231 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 232 233 MODULE_LICENSE("GPL"); 234 MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); 235 MODULE_DESCRIPTION("Meson Watchdog Timer Driver"); 236