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/of.h> 21 #include <linux/of_device.h> 22 #include <linux/platform_device.h> 23 #include <linux/types.h> 24 #include <linux/watchdog.h> 25 26 #define DRV_NAME "meson_wdt" 27 28 #define MESON_WDT_TC 0x00 29 #define MESON_WDT_DC_RESET (3 << 24) 30 31 #define MESON_WDT_RESET 0x04 32 33 #define MESON_WDT_TIMEOUT 30 34 #define MESON_WDT_MIN_TIMEOUT 1 35 36 #define MESON_SEC_TO_TC(s, c) ((s) * (c)) 37 38 static bool nowayout = WATCHDOG_NOWAYOUT; 39 static unsigned int timeout = MESON_WDT_TIMEOUT; 40 41 struct meson_wdt_data { 42 unsigned int enable; 43 unsigned int terminal_count_mask; 44 unsigned int count_unit; 45 }; 46 47 static struct meson_wdt_data meson6_wdt_data = { 48 .enable = BIT(22), 49 .terminal_count_mask = 0x3fffff, 50 .count_unit = 100000, /* 10 us */ 51 }; 52 53 static struct meson_wdt_data meson8b_wdt_data = { 54 .enable = BIT(19), 55 .terminal_count_mask = 0xffff, 56 .count_unit = 7812, /* 128 us */ 57 }; 58 59 struct meson_wdt_dev { 60 struct watchdog_device wdt_dev; 61 void __iomem *wdt_base; 62 const struct meson_wdt_data *data; 63 }; 64 65 static int meson_wdt_restart(struct watchdog_device *wdt_dev) 66 { 67 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 68 u32 tc_reboot = MESON_WDT_DC_RESET; 69 70 tc_reboot |= meson_wdt->data->enable; 71 72 while (1) { 73 writel(tc_reboot, meson_wdt->wdt_base + MESON_WDT_TC); 74 mdelay(5); 75 } 76 77 return 0; 78 } 79 80 static int meson_wdt_ping(struct watchdog_device *wdt_dev) 81 { 82 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 83 84 writel(0, meson_wdt->wdt_base + MESON_WDT_RESET); 85 86 return 0; 87 } 88 89 static void meson_wdt_change_timeout(struct watchdog_device *wdt_dev, 90 unsigned int timeout) 91 { 92 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 93 u32 reg; 94 95 reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); 96 reg &= ~meson_wdt->data->terminal_count_mask; 97 reg |= MESON_SEC_TO_TC(timeout, meson_wdt->data->count_unit); 98 writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); 99 } 100 101 static int meson_wdt_set_timeout(struct watchdog_device *wdt_dev, 102 unsigned int timeout) 103 { 104 wdt_dev->timeout = timeout; 105 106 meson_wdt_change_timeout(wdt_dev, timeout); 107 meson_wdt_ping(wdt_dev); 108 109 return 0; 110 } 111 112 static int meson_wdt_stop(struct watchdog_device *wdt_dev) 113 { 114 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 115 u32 reg; 116 117 reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); 118 reg &= ~meson_wdt->data->enable; 119 writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); 120 121 return 0; 122 } 123 124 static int meson_wdt_start(struct watchdog_device *wdt_dev) 125 { 126 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 127 u32 reg; 128 129 meson_wdt_change_timeout(wdt_dev, meson_wdt->wdt_dev.timeout); 130 meson_wdt_ping(wdt_dev); 131 132 reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); 133 reg |= meson_wdt->data->enable; 134 writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); 135 136 return 0; 137 } 138 139 static const struct watchdog_info meson_wdt_info = { 140 .identity = DRV_NAME, 141 .options = WDIOF_SETTIMEOUT | 142 WDIOF_KEEPALIVEPING | 143 WDIOF_MAGICCLOSE, 144 }; 145 146 static const struct watchdog_ops meson_wdt_ops = { 147 .owner = THIS_MODULE, 148 .start = meson_wdt_start, 149 .stop = meson_wdt_stop, 150 .ping = meson_wdt_ping, 151 .set_timeout = meson_wdt_set_timeout, 152 .restart = meson_wdt_restart, 153 }; 154 155 static const struct of_device_id meson_wdt_dt_ids[] = { 156 { .compatible = "amlogic,meson6-wdt", .data = &meson6_wdt_data }, 157 { .compatible = "amlogic,meson8b-wdt", .data = &meson8b_wdt_data }, 158 { /* sentinel */ } 159 }; 160 MODULE_DEVICE_TABLE(of, meson_wdt_dt_ids); 161 162 static int meson_wdt_probe(struct platform_device *pdev) 163 { 164 struct resource *res; 165 struct meson_wdt_dev *meson_wdt; 166 const struct of_device_id *of_id; 167 int err; 168 169 meson_wdt = devm_kzalloc(&pdev->dev, sizeof(*meson_wdt), GFP_KERNEL); 170 if (!meson_wdt) 171 return -ENOMEM; 172 173 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 174 meson_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); 175 if (IS_ERR(meson_wdt->wdt_base)) 176 return PTR_ERR(meson_wdt->wdt_base); 177 178 of_id = of_match_device(meson_wdt_dt_ids, &pdev->dev); 179 if (!of_id) { 180 dev_err(&pdev->dev, "Unable to initialize WDT data\n"); 181 return -ENODEV; 182 } 183 meson_wdt->data = of_id->data; 184 185 meson_wdt->wdt_dev.parent = &pdev->dev; 186 meson_wdt->wdt_dev.info = &meson_wdt_info; 187 meson_wdt->wdt_dev.ops = &meson_wdt_ops; 188 meson_wdt->wdt_dev.max_timeout = 189 meson_wdt->data->terminal_count_mask / meson_wdt->data->count_unit; 190 meson_wdt->wdt_dev.min_timeout = MESON_WDT_MIN_TIMEOUT; 191 meson_wdt->wdt_dev.timeout = min_t(unsigned int, 192 MESON_WDT_TIMEOUT, 193 meson_wdt->wdt_dev.max_timeout); 194 195 watchdog_set_drvdata(&meson_wdt->wdt_dev, meson_wdt); 196 197 watchdog_init_timeout(&meson_wdt->wdt_dev, timeout, &pdev->dev); 198 watchdog_set_nowayout(&meson_wdt->wdt_dev, nowayout); 199 watchdog_set_restart_priority(&meson_wdt->wdt_dev, 128); 200 201 meson_wdt_stop(&meson_wdt->wdt_dev); 202 203 err = watchdog_register_device(&meson_wdt->wdt_dev); 204 if (err) 205 return err; 206 207 platform_set_drvdata(pdev, meson_wdt); 208 209 dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", 210 meson_wdt->wdt_dev.timeout, nowayout); 211 212 return 0; 213 } 214 215 static int meson_wdt_remove(struct platform_device *pdev) 216 { 217 struct meson_wdt_dev *meson_wdt = platform_get_drvdata(pdev); 218 219 watchdog_unregister_device(&meson_wdt->wdt_dev); 220 221 return 0; 222 } 223 224 static void meson_wdt_shutdown(struct platform_device *pdev) 225 { 226 struct meson_wdt_dev *meson_wdt = platform_get_drvdata(pdev); 227 228 meson_wdt_stop(&meson_wdt->wdt_dev); 229 } 230 231 static struct platform_driver meson_wdt_driver = { 232 .probe = meson_wdt_probe, 233 .remove = meson_wdt_remove, 234 .shutdown = meson_wdt_shutdown, 235 .driver = { 236 .name = DRV_NAME, 237 .of_match_table = meson_wdt_dt_ids, 238 }, 239 }; 240 241 module_platform_driver(meson_wdt_driver); 242 243 module_param(timeout, uint, 0); 244 MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); 245 246 module_param(nowayout, bool, 0); 247 MODULE_PARM_DESC(nowayout, 248 "Watchdog cannot be stopped once started (default=" 249 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 250 251 MODULE_LICENSE("GPL"); 252 MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); 253 MODULE_DESCRIPTION("Meson Watchdog Timer Driver"); 254