1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Watchdog device driver for DA9062 and DA9061 PMICs 4 * Copyright (C) 2015 Dialog Semiconductor Ltd. 5 * 6 */ 7 8 #include <linux/kernel.h> 9 #include <linux/module.h> 10 #include <linux/watchdog.h> 11 #include <linux/platform_device.h> 12 #include <linux/uaccess.h> 13 #include <linux/slab.h> 14 #include <linux/delay.h> 15 #include <linux/jiffies.h> 16 #include <linux/mfd/da9062/registers.h> 17 #include <linux/mfd/da9062/core.h> 18 #include <linux/regmap.h> 19 #include <linux/of.h> 20 21 static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 }; 22 #define DA9062_TWDSCALE_DISABLE 0 23 #define DA9062_TWDSCALE_MIN 1 24 #define DA9062_TWDSCALE_MAX (ARRAY_SIZE(wdt_timeout) - 1) 25 #define DA9062_WDT_MIN_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MIN] 26 #define DA9062_WDT_MAX_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MAX] 27 #define DA9062_WDG_DEFAULT_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MAX-1] 28 #define DA9062_RESET_PROTECTION_MS 300 29 30 struct da9062_watchdog { 31 struct da9062 *hw; 32 struct watchdog_device wdtdev; 33 unsigned long j_time_stamp; 34 }; 35 36 static void da9062_set_window_start(struct da9062_watchdog *wdt) 37 { 38 wdt->j_time_stamp = jiffies; 39 } 40 41 static unsigned int da9062_wdt_timeout_to_sel(unsigned int secs) 42 { 43 unsigned int i; 44 45 for (i = DA9062_TWDSCALE_MIN; i <= DA9062_TWDSCALE_MAX; i++) { 46 if (wdt_timeout[i] >= secs) 47 return i; 48 } 49 50 return DA9062_TWDSCALE_MAX; 51 } 52 53 static int da9062_reset_watchdog_timer(struct da9062_watchdog *wdt) 54 { 55 int ret; 56 57 ret = regmap_update_bits(wdt->hw->regmap, 58 DA9062AA_CONTROL_F, 59 DA9062AA_WATCHDOG_MASK, 60 DA9062AA_WATCHDOG_MASK); 61 62 da9062_set_window_start(wdt); 63 64 return ret; 65 } 66 67 static int da9062_wdt_update_timeout_register(struct da9062_watchdog *wdt, 68 unsigned int regval) 69 { 70 struct da9062 *chip = wdt->hw; 71 int ret; 72 73 ret = da9062_reset_watchdog_timer(wdt); 74 if (ret) 75 return ret; 76 77 regmap_update_bits(chip->regmap, 78 DA9062AA_CONTROL_D, 79 DA9062AA_TWDSCALE_MASK, 80 DA9062_TWDSCALE_DISABLE); 81 82 usleep_range(150, 300); 83 84 return regmap_update_bits(chip->regmap, 85 DA9062AA_CONTROL_D, 86 DA9062AA_TWDSCALE_MASK, 87 regval); 88 } 89 90 static int da9062_wdt_start(struct watchdog_device *wdd) 91 { 92 struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); 93 unsigned int selector; 94 int ret; 95 96 selector = da9062_wdt_timeout_to_sel(wdt->wdtdev.timeout); 97 ret = da9062_wdt_update_timeout_register(wdt, selector); 98 if (ret) 99 dev_err(wdt->hw->dev, "Watchdog failed to start (err = %d)\n", 100 ret); 101 102 return ret; 103 } 104 105 static int da9062_wdt_stop(struct watchdog_device *wdd) 106 { 107 struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); 108 int ret; 109 110 ret = da9062_reset_watchdog_timer(wdt); 111 if (ret) { 112 dev_err(wdt->hw->dev, "Failed to ping the watchdog (err = %d)\n", 113 ret); 114 return ret; 115 } 116 117 ret = regmap_update_bits(wdt->hw->regmap, 118 DA9062AA_CONTROL_D, 119 DA9062AA_TWDSCALE_MASK, 120 DA9062_TWDSCALE_DISABLE); 121 if (ret) 122 dev_err(wdt->hw->dev, "Watchdog failed to stop (err = %d)\n", 123 ret); 124 125 return ret; 126 } 127 128 static int da9062_wdt_ping(struct watchdog_device *wdd) 129 { 130 struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); 131 int ret; 132 133 ret = da9062_reset_watchdog_timer(wdt); 134 if (ret) 135 dev_err(wdt->hw->dev, "Failed to ping the watchdog (err = %d)\n", 136 ret); 137 138 return ret; 139 } 140 141 static int da9062_wdt_set_timeout(struct watchdog_device *wdd, 142 unsigned int timeout) 143 { 144 struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); 145 unsigned int selector; 146 int ret; 147 148 selector = da9062_wdt_timeout_to_sel(timeout); 149 ret = da9062_wdt_update_timeout_register(wdt, selector); 150 if (ret) 151 dev_err(wdt->hw->dev, "Failed to set watchdog timeout (err = %d)\n", 152 ret); 153 else 154 wdd->timeout = wdt_timeout[selector]; 155 156 return ret; 157 } 158 159 static int da9062_wdt_restart(struct watchdog_device *wdd, unsigned long action, 160 void *data) 161 { 162 struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd); 163 int ret; 164 165 ret = regmap_write(wdt->hw->regmap, 166 DA9062AA_CONTROL_F, 167 DA9062AA_SHUTDOWN_MASK); 168 if (ret) 169 dev_alert(wdt->hw->dev, "Failed to shutdown (err = %d)\n", 170 ret); 171 172 /* wait for reset to assert... */ 173 mdelay(500); 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 .restart = da9062_wdt_restart, 190 }; 191 192 static const struct of_device_id da9062_compatible_id_table[] = { 193 { .compatible = "dlg,da9062-watchdog", }, 194 { }, 195 }; 196 197 MODULE_DEVICE_TABLE(of, da9062_compatible_id_table); 198 199 static int da9062_wdt_probe(struct platform_device *pdev) 200 { 201 int ret; 202 struct da9062 *chip; 203 struct da9062_watchdog *wdt; 204 205 chip = dev_get_drvdata(pdev->dev.parent); 206 if (!chip) 207 return -EINVAL; 208 209 wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); 210 if (!wdt) 211 return -ENOMEM; 212 213 wdt->hw = chip; 214 215 wdt->wdtdev.info = &da9062_watchdog_info; 216 wdt->wdtdev.ops = &da9062_watchdog_ops; 217 wdt->wdtdev.min_timeout = DA9062_WDT_MIN_TIMEOUT; 218 wdt->wdtdev.max_timeout = DA9062_WDT_MAX_TIMEOUT; 219 wdt->wdtdev.min_hw_heartbeat_ms = DA9062_RESET_PROTECTION_MS; 220 wdt->wdtdev.timeout = DA9062_WDG_DEFAULT_TIMEOUT; 221 wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS; 222 wdt->wdtdev.parent = &pdev->dev; 223 224 watchdog_set_restart_priority(&wdt->wdtdev, 128); 225 226 watchdog_set_drvdata(&wdt->wdtdev, wdt); 227 228 ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdtdev); 229 if (ret < 0) { 230 dev_err(wdt->hw->dev, 231 "watchdog registration failed (%d)\n", ret); 232 return ret; 233 } 234 235 da9062_set_window_start(wdt); 236 237 return da9062_wdt_ping(&wdt->wdtdev); 238 } 239 240 static struct platform_driver da9062_wdt_driver = { 241 .probe = da9062_wdt_probe, 242 .driver = { 243 .name = "da9062-watchdog", 244 .of_match_table = da9062_compatible_id_table, 245 }, 246 }; 247 module_platform_driver(da9062_wdt_driver); 248 249 MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>"); 250 MODULE_DESCRIPTION("WDT device driver for Dialog DA9062 and DA9061"); 251 MODULE_LICENSE("GPL"); 252 MODULE_ALIAS("platform:da9062-watchdog"); 253