1 /* 2 * Watchdog device driver for DA9062 and DA9061 PMICs 3 * Copyright (C) 2015 Dialog Semiconductor Ltd. 4 * 5 * This program is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU General Public License 7 * as published by the Free Software Foundation; either version 2 8 * of the License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 */ 15 16 #include <linux/kernel.h> 17 #include <linux/module.h> 18 #include <linux/watchdog.h> 19 #include <linux/platform_device.h> 20 #include <linux/uaccess.h> 21 #include <linux/slab.h> 22 #include <linux/delay.h> 23 #include <linux/jiffies.h> 24 #include <linux/mfd/da9062/registers.h> 25 #include <linux/mfd/da9062/core.h> 26 #include <linux/regmap.h> 27 #include <linux/of.h> 28 29 static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 }; 30 #define DA9062_TWDSCALE_DISABLE 0 31 #define DA9062_TWDSCALE_MIN 1 32 #define DA9062_TWDSCALE_MAX (ARRAY_SIZE(wdt_timeout) - 1) 33 #define DA9062_WDT_MIN_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MIN] 34 #define DA9062_WDT_MAX_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MAX] 35 #define DA9062_WDG_DEFAULT_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MAX-1] 36 #define DA9062_RESET_PROTECTION_MS 300 37 38 struct da9062_watchdog { 39 struct da9062 *hw; 40 struct watchdog_device wdtdev; 41 unsigned long j_time_stamp; 42 }; 43 44 static void da9062_set_window_start(struct da9062_watchdog *wdt) 45 { 46 wdt->j_time_stamp = jiffies; 47 } 48 49 static void da9062_apply_window_protection(struct da9062_watchdog *wdt) 50 { 51 unsigned long delay = msecs_to_jiffies(DA9062_RESET_PROTECTION_MS); 52 unsigned long timeout = wdt->j_time_stamp + delay; 53 unsigned long now = jiffies; 54 unsigned int diff_ms; 55 56 /* if time-limit has not elapsed then wait for remainder */ 57 if (time_before(now, timeout)) { 58 diff_ms = jiffies_to_msecs(timeout-now); 59 dev_dbg(wdt->hw->dev, 60 "Kicked too quickly. Delaying %u msecs\n", diff_ms); 61 msleep(diff_ms); 62 } 63 } 64 65 static unsigned int da9062_wdt_timeout_to_sel(unsigned int secs) 66 { 67 unsigned int i; 68 69 for (i = DA9062_TWDSCALE_MIN; i <= DA9062_TWDSCALE_MAX; i++) { 70 if (wdt_timeout[i] >= secs) 71 return i; 72 } 73 74 return DA9062_TWDSCALE_MAX; 75 } 76 77 static int da9062_reset_watchdog_timer(struct da9062_watchdog *wdt) 78 { 79 int ret; 80 81 da9062_apply_window_protection(wdt); 82 83 ret = regmap_update_bits(wdt->hw->regmap, 84 DA9062AA_CONTROL_F, 85 DA9062AA_WATCHDOG_MASK, 86 DA9062AA_WATCHDOG_MASK); 87 88 da9062_set_window_start(wdt); 89 90 return ret; 91 } 92 93 static int da9062_wdt_update_timeout_register(struct da9062_watchdog *wdt, 94 unsigned int regval) 95 { 96 struct da9062 *chip = wdt->hw; 97 int ret; 98 99 ret = da9062_reset_watchdog_timer(wdt); 100 if (ret) 101 return ret; 102 103 return regmap_update_bits(chip->regmap, 104 DA9062AA_CONTROL_D, 105 DA9062AA_TWDSCALE_MASK, 106 regval); 107 } 108 109 static int da9062_wdt_start(struct watchdog_device *wdd) 110 { 111 struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); 112 unsigned int selector; 113 int ret; 114 115 selector = da9062_wdt_timeout_to_sel(wdt->wdtdev.timeout); 116 ret = da9062_wdt_update_timeout_register(wdt, selector); 117 if (ret) 118 dev_err(wdt->hw->dev, "Watchdog failed to start (err = %d)\n", 119 ret); 120 121 return ret; 122 } 123 124 static int da9062_wdt_stop(struct watchdog_device *wdd) 125 { 126 struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); 127 int ret; 128 129 ret = da9062_reset_watchdog_timer(wdt); 130 if (ret) { 131 dev_err(wdt->hw->dev, "Failed to ping the watchdog (err = %d)\n", 132 ret); 133 return ret; 134 } 135 136 ret = regmap_update_bits(wdt->hw->regmap, 137 DA9062AA_CONTROL_D, 138 DA9062AA_TWDSCALE_MASK, 139 DA9062_TWDSCALE_DISABLE); 140 if (ret) 141 dev_err(wdt->hw->dev, "Watchdog failed to stop (err = %d)\n", 142 ret); 143 144 return ret; 145 } 146 147 static int da9062_wdt_ping(struct watchdog_device *wdd) 148 { 149 struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); 150 int ret; 151 152 ret = da9062_reset_watchdog_timer(wdt); 153 if (ret) 154 dev_err(wdt->hw->dev, "Failed to ping the watchdog (err = %d)\n", 155 ret); 156 157 return ret; 158 } 159 160 static int da9062_wdt_set_timeout(struct watchdog_device *wdd, 161 unsigned int timeout) 162 { 163 struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); 164 unsigned int selector; 165 int ret; 166 167 selector = da9062_wdt_timeout_to_sel(timeout); 168 ret = da9062_wdt_update_timeout_register(wdt, selector); 169 if (ret) 170 dev_err(wdt->hw->dev, "Failed to set watchdog timeout (err = %d)\n", 171 ret); 172 else 173 wdd->timeout = wdt_timeout[selector]; 174 175 return ret; 176 } 177 178 static const struct watchdog_info da9062_watchdog_info = { 179 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 180 .identity = "DA9062 WDT", 181 }; 182 183 static const struct watchdog_ops da9062_watchdog_ops = { 184 .owner = THIS_MODULE, 185 .start = da9062_wdt_start, 186 .stop = da9062_wdt_stop, 187 .ping = da9062_wdt_ping, 188 .set_timeout = da9062_wdt_set_timeout, 189 }; 190 191 static const struct of_device_id da9062_compatible_id_table[] = { 192 { .compatible = "dlg,da9062-watchdog", }, 193 { }, 194 }; 195 196 MODULE_DEVICE_TABLE(of, da9062_compatible_id_table); 197 198 static int da9062_wdt_probe(struct platform_device *pdev) 199 { 200 int ret; 201 struct da9062 *chip; 202 struct da9062_watchdog *wdt; 203 204 chip = dev_get_drvdata(pdev->dev.parent); 205 if (!chip) 206 return -EINVAL; 207 208 wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); 209 if (!wdt) 210 return -ENOMEM; 211 212 wdt->hw = chip; 213 214 wdt->wdtdev.info = &da9062_watchdog_info; 215 wdt->wdtdev.ops = &da9062_watchdog_ops; 216 wdt->wdtdev.min_timeout = DA9062_WDT_MIN_TIMEOUT; 217 wdt->wdtdev.max_timeout = DA9062_WDT_MAX_TIMEOUT; 218 wdt->wdtdev.timeout = DA9062_WDG_DEFAULT_TIMEOUT; 219 wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS; 220 wdt->wdtdev.parent = &pdev->dev; 221 222 watchdog_set_drvdata(&wdt->wdtdev, wdt); 223 224 ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdtdev); 225 if (ret < 0) { 226 dev_err(wdt->hw->dev, 227 "watchdog registration failed (%d)\n", ret); 228 return ret; 229 } 230 231 da9062_set_window_start(wdt); 232 233 return da9062_wdt_ping(&wdt->wdtdev); 234 } 235 236 static struct platform_driver da9062_wdt_driver = { 237 .probe = da9062_wdt_probe, 238 .driver = { 239 .name = "da9062-watchdog", 240 .of_match_table = da9062_compatible_id_table, 241 }, 242 }; 243 module_platform_driver(da9062_wdt_driver); 244 245 MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>"); 246 MODULE_DESCRIPTION("WDT device driver for Dialog DA9062 and DA9061"); 247 MODULE_LICENSE("GPL"); 248 MODULE_ALIAS("platform:da9062-watchdog"); 249