1 /* 2 * Watchdog driver for DA9063 PMICs. 3 * 4 * Copyright(c) 2012 Dialog Semiconductor Ltd. 5 * 6 * Author: Mariusz Wojtasik <mariusz.wojtasik@diasemi.com> 7 * 8 * This program is free software; you can redistribute it and/or modify it 9 * under the terms of the GNU General Public License as published by the 10 * Free Software Foundation; either version 2 of the License, or (at your 11 * option) any later version. 12 */ 13 14 #include <linux/kernel.h> 15 #include <linux/module.h> 16 #include <linux/watchdog.h> 17 #include <linux/platform_device.h> 18 #include <linux/uaccess.h> 19 #include <linux/slab.h> 20 #include <linux/delay.h> 21 #include <linux/mfd/da9063/registers.h> 22 #include <linux/mfd/da9063/core.h> 23 #include <linux/regmap.h> 24 25 /* 26 * Watchdog selector to timeout in seconds. 27 * 0: WDT disabled; 28 * others: timeout = 2048 ms * 2^(TWDSCALE-1). 29 */ 30 static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 }; 31 #define DA9063_TWDSCALE_DISABLE 0 32 #define DA9063_TWDSCALE_MIN 1 33 #define DA9063_TWDSCALE_MAX (ARRAY_SIZE(wdt_timeout) - 1) 34 #define DA9063_WDT_MIN_TIMEOUT wdt_timeout[DA9063_TWDSCALE_MIN] 35 #define DA9063_WDT_MAX_TIMEOUT wdt_timeout[DA9063_TWDSCALE_MAX] 36 #define DA9063_WDG_TIMEOUT wdt_timeout[3] 37 #define DA9063_RESET_PROTECTION_MS 256 38 39 static unsigned int da9063_wdt_timeout_to_sel(unsigned int secs) 40 { 41 unsigned int i; 42 43 for (i = DA9063_TWDSCALE_MIN; i <= DA9063_TWDSCALE_MAX; i++) { 44 if (wdt_timeout[i] >= secs) 45 return i; 46 } 47 48 return DA9063_TWDSCALE_MAX; 49 } 50 51 static int _da9063_wdt_set_timeout(struct da9063 *da9063, unsigned int regval) 52 { 53 return regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D, 54 DA9063_TWDSCALE_MASK, regval); 55 } 56 57 static int da9063_wdt_start(struct watchdog_device *wdd) 58 { 59 struct da9063 *da9063 = watchdog_get_drvdata(wdd); 60 unsigned int selector; 61 int ret; 62 63 selector = da9063_wdt_timeout_to_sel(wdd->timeout); 64 ret = _da9063_wdt_set_timeout(da9063, selector); 65 if (ret) 66 dev_err(da9063->dev, "Watchdog failed to start (err = %d)\n", 67 ret); 68 69 return ret; 70 } 71 72 static int da9063_wdt_stop(struct watchdog_device *wdd) 73 { 74 struct da9063 *da9063 = watchdog_get_drvdata(wdd); 75 int ret; 76 77 ret = regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D, 78 DA9063_TWDSCALE_MASK, DA9063_TWDSCALE_DISABLE); 79 if (ret) 80 dev_alert(da9063->dev, "Watchdog failed to stop (err = %d)\n", 81 ret); 82 83 return ret; 84 } 85 86 static int da9063_wdt_ping(struct watchdog_device *wdd) 87 { 88 struct da9063 *da9063 = watchdog_get_drvdata(wdd); 89 int ret; 90 91 ret = regmap_write(da9063->regmap, DA9063_REG_CONTROL_F, 92 DA9063_WATCHDOG); 93 if (ret) 94 dev_alert(da9063->dev, "Failed to ping the watchdog (err = %d)\n", 95 ret); 96 97 return ret; 98 } 99 100 static int da9063_wdt_set_timeout(struct watchdog_device *wdd, 101 unsigned int timeout) 102 { 103 struct da9063 *da9063 = watchdog_get_drvdata(wdd); 104 unsigned int selector; 105 int ret; 106 107 selector = da9063_wdt_timeout_to_sel(timeout); 108 ret = _da9063_wdt_set_timeout(da9063, selector); 109 if (ret) 110 dev_err(da9063->dev, "Failed to set watchdog timeout (err = %d)\n", 111 ret); 112 else 113 wdd->timeout = wdt_timeout[selector]; 114 115 return ret; 116 } 117 118 static int da9063_wdt_restart(struct watchdog_device *wdd, unsigned long action, 119 void *data) 120 { 121 struct da9063 *da9063 = watchdog_get_drvdata(wdd); 122 int ret; 123 124 ret = regmap_write(da9063->regmap, DA9063_REG_CONTROL_F, 125 DA9063_SHUTDOWN); 126 if (ret) 127 dev_alert(da9063->dev, "Failed to shutdown (err = %d)\n", 128 ret); 129 130 return ret; 131 } 132 133 static const struct watchdog_info da9063_watchdog_info = { 134 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 135 .identity = "DA9063 Watchdog", 136 }; 137 138 static const struct watchdog_ops da9063_watchdog_ops = { 139 .owner = THIS_MODULE, 140 .start = da9063_wdt_start, 141 .stop = da9063_wdt_stop, 142 .ping = da9063_wdt_ping, 143 .set_timeout = da9063_wdt_set_timeout, 144 .restart = da9063_wdt_restart, 145 }; 146 147 static int da9063_wdt_probe(struct platform_device *pdev) 148 { 149 struct da9063 *da9063; 150 struct watchdog_device *wdd; 151 152 if (!pdev->dev.parent) 153 return -EINVAL; 154 155 da9063 = dev_get_drvdata(pdev->dev.parent); 156 if (!da9063) 157 return -EINVAL; 158 159 wdd = devm_kzalloc(&pdev->dev, sizeof(*wdd), GFP_KERNEL); 160 if (!wdd) 161 return -ENOMEM; 162 163 wdd->info = &da9063_watchdog_info; 164 wdd->ops = &da9063_watchdog_ops; 165 wdd->min_timeout = DA9063_WDT_MIN_TIMEOUT; 166 wdd->max_timeout = DA9063_WDT_MAX_TIMEOUT; 167 wdd->min_hw_heartbeat_ms = DA9063_RESET_PROTECTION_MS; 168 wdd->timeout = DA9063_WDG_TIMEOUT; 169 wdd->parent = &pdev->dev; 170 171 wdd->status = WATCHDOG_NOWAYOUT_INIT_STATUS; 172 173 watchdog_set_restart_priority(wdd, 128); 174 175 watchdog_set_drvdata(wdd, da9063); 176 177 return devm_watchdog_register_device(&pdev->dev, wdd); 178 } 179 180 static struct platform_driver da9063_wdt_driver = { 181 .probe = da9063_wdt_probe, 182 .driver = { 183 .name = DA9063_DRVNAME_WATCHDOG, 184 }, 185 }; 186 module_platform_driver(da9063_wdt_driver); 187 188 MODULE_AUTHOR("Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>"); 189 MODULE_DESCRIPTION("Watchdog driver for Dialog DA9063"); 190 MODULE_LICENSE("GPL"); 191 MODULE_ALIAS("platform:" DA9063_DRVNAME_WATCHDOG); 192