bd9576_wdt.c (6828B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (C) 2020 ROHM Semiconductors 4 * 5 * ROHM BD9576MUF and BD9573MUF Watchdog driver 6 */ 7 8#include <linux/err.h> 9#include <linux/gpio/consumer.h> 10#include <linux/mfd/rohm-bd957x.h> 11#include <linux/module.h> 12#include <linux/of.h> 13#include <linux/platform_device.h> 14#include <linux/regmap.h> 15#include <linux/watchdog.h> 16 17static bool nowayout; 18module_param(nowayout, bool, 0); 19MODULE_PARM_DESC(nowayout, 20 "Watchdog cannot be stopped once started (default=\"false\")"); 21 22#define HW_MARGIN_MIN 2 23#define HW_MARGIN_MAX 4416 24#define BD957X_WDT_DEFAULT_MARGIN 4416 25#define WATCHDOG_TIMEOUT 30 26 27struct bd9576_wdt_priv { 28 struct gpio_desc *gpiod_ping; 29 struct gpio_desc *gpiod_en; 30 struct device *dev; 31 struct regmap *regmap; 32 bool always_running; 33 struct watchdog_device wdd; 34}; 35 36static void bd9576_wdt_disable(struct bd9576_wdt_priv *priv) 37{ 38 gpiod_set_value_cansleep(priv->gpiod_en, 0); 39} 40 41static int bd9576_wdt_ping(struct watchdog_device *wdd) 42{ 43 struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd); 44 45 /* Pulse */ 46 gpiod_set_value_cansleep(priv->gpiod_ping, 1); 47 gpiod_set_value_cansleep(priv->gpiod_ping, 0); 48 49 return 0; 50} 51 52static int bd9576_wdt_start(struct watchdog_device *wdd) 53{ 54 struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd); 55 56 gpiod_set_value_cansleep(priv->gpiod_en, 1); 57 58 return bd9576_wdt_ping(wdd); 59} 60 61static int bd9576_wdt_stop(struct watchdog_device *wdd) 62{ 63 struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd); 64 65 if (!priv->always_running) 66 bd9576_wdt_disable(priv); 67 else 68 set_bit(WDOG_HW_RUNNING, &wdd->status); 69 70 return 0; 71} 72 73static const struct watchdog_info bd957x_wdt_ident = { 74 .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | 75 WDIOF_SETTIMEOUT, 76 .identity = "BD957x Watchdog", 77}; 78 79static const struct watchdog_ops bd957x_wdt_ops = { 80 .owner = THIS_MODULE, 81 .start = bd9576_wdt_start, 82 .stop = bd9576_wdt_stop, 83 .ping = bd9576_wdt_ping, 84}; 85 86/* Unit is hundreds of uS */ 87#define FASTNG_MIN 23 88 89static int find_closest_fast(int target, int *sel, int *val) 90{ 91 int i; 92 int window = FASTNG_MIN; 93 94 for (i = 0; i < 8 && window < target; i++) 95 window <<= 1; 96 97 *val = window; 98 *sel = i; 99 100 if (i == 8) 101 return -EINVAL; 102 103 return 0; 104 105} 106 107static int find_closest_slow_by_fast(int fast_val, int target, int *slowsel) 108{ 109 int sel; 110 static const int multipliers[] = {2, 3, 7, 15}; 111 112 for (sel = 0; sel < ARRAY_SIZE(multipliers) && 113 multipliers[sel] * fast_val < target; sel++) 114 ; 115 116 if (sel == ARRAY_SIZE(multipliers)) 117 return -EINVAL; 118 119 *slowsel = sel; 120 121 return 0; 122} 123 124static int find_closest_slow(int target, int *slow_sel, int *fast_sel) 125{ 126 static const int multipliers[] = {2, 3, 7, 15}; 127 int i, j; 128 int val = 0; 129 int window = FASTNG_MIN; 130 131 for (i = 0; i < 8; i++) { 132 for (j = 0; j < ARRAY_SIZE(multipliers); j++) { 133 int slow; 134 135 slow = window * multipliers[j]; 136 if (slow >= target && (!val || slow < val)) { 137 val = slow; 138 *fast_sel = i; 139 *slow_sel = j; 140 } 141 } 142 window <<= 1; 143 } 144 if (!val) 145 return -EINVAL; 146 147 return 0; 148} 149 150#define BD957X_WDG_TYPE_WINDOW BIT(5) 151#define BD957X_WDG_TYPE_SLOW 0 152#define BD957X_WDG_TYPE_MASK BIT(5) 153#define BD957X_WDG_NG_RATIO_MASK 0x18 154#define BD957X_WDG_FASTNG_MASK 0x7 155 156static int bd957x_set_wdt_mode(struct bd9576_wdt_priv *priv, int hw_margin, 157 int hw_margin_min) 158{ 159 int ret, fastng, slowng, type, reg, mask; 160 struct device *dev = priv->dev; 161 162 /* convert to 100uS */ 163 hw_margin *= 10; 164 hw_margin_min *= 10; 165 if (hw_margin_min) { 166 int min; 167 168 type = BD957X_WDG_TYPE_WINDOW; 169 dev_dbg(dev, "Setting type WINDOW 0x%x\n", type); 170 ret = find_closest_fast(hw_margin_min, &fastng, &min); 171 if (ret) { 172 dev_err(dev, "bad WDT window for fast timeout\n"); 173 return ret; 174 } 175 176 ret = find_closest_slow_by_fast(min, hw_margin, &slowng); 177 if (ret) { 178 dev_err(dev, "bad WDT window\n"); 179 return ret; 180 } 181 182 } else { 183 type = BD957X_WDG_TYPE_SLOW; 184 dev_dbg(dev, "Setting type SLOW 0x%x\n", type); 185 ret = find_closest_slow(hw_margin, &slowng, &fastng); 186 if (ret) { 187 dev_err(dev, "bad WDT window\n"); 188 return ret; 189 } 190 } 191 192 slowng <<= ffs(BD957X_WDG_NG_RATIO_MASK) - 1; 193 reg = type | slowng | fastng; 194 mask = BD957X_WDG_TYPE_MASK | BD957X_WDG_NG_RATIO_MASK | 195 BD957X_WDG_FASTNG_MASK; 196 ret = regmap_update_bits(priv->regmap, BD957X_REG_WDT_CONF, 197 mask, reg); 198 199 return ret; 200} 201 202static int bd9576_wdt_probe(struct platform_device *pdev) 203{ 204 struct device *dev = &pdev->dev; 205 struct device_node *np = dev->parent->of_node; 206 struct bd9576_wdt_priv *priv; 207 u32 hw_margin[2]; 208 u32 hw_margin_max = BD957X_WDT_DEFAULT_MARGIN, hw_margin_min = 0; 209 int ret; 210 211 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 212 if (!priv) 213 return -ENOMEM; 214 215 platform_set_drvdata(pdev, priv); 216 217 priv->dev = dev; 218 priv->regmap = dev_get_regmap(dev->parent, NULL); 219 if (!priv->regmap) { 220 dev_err(dev, "No regmap found\n"); 221 return -ENODEV; 222 } 223 224 priv->gpiod_en = devm_gpiod_get_from_of_node(dev, dev->parent->of_node, 225 "rohm,watchdog-enable-gpios", 226 0, GPIOD_OUT_LOW, 227 "watchdog-enable"); 228 if (IS_ERR(priv->gpiod_en)) 229 return dev_err_probe(dev, PTR_ERR(priv->gpiod_en), 230 "getting watchdog-enable GPIO failed\n"); 231 232 priv->gpiod_ping = devm_gpiod_get_from_of_node(dev, dev->parent->of_node, 233 "rohm,watchdog-ping-gpios", 234 0, GPIOD_OUT_LOW, 235 "watchdog-ping"); 236 if (IS_ERR(priv->gpiod_ping)) 237 return dev_err_probe(dev, PTR_ERR(priv->gpiod_ping), 238 "getting watchdog-ping GPIO failed\n"); 239 240 ret = of_property_read_variable_u32_array(np, "rohm,hw-timeout-ms", 241 &hw_margin[0], 1, 2); 242 if (ret < 0 && ret != -EINVAL) 243 return ret; 244 245 if (ret == 1) 246 hw_margin_max = hw_margin[0]; 247 248 if (ret == 2) { 249 hw_margin_max = hw_margin[1]; 250 hw_margin_min = hw_margin[0]; 251 } 252 253 ret = bd957x_set_wdt_mode(priv, hw_margin_max, hw_margin_min); 254 if (ret) 255 return ret; 256 257 priv->always_running = of_property_read_bool(np, "always-running"); 258 259 watchdog_set_drvdata(&priv->wdd, priv); 260 261 priv->wdd.info = &bd957x_wdt_ident; 262 priv->wdd.ops = &bd957x_wdt_ops; 263 priv->wdd.min_hw_heartbeat_ms = hw_margin_min; 264 priv->wdd.max_hw_heartbeat_ms = hw_margin_max; 265 priv->wdd.parent = dev; 266 priv->wdd.timeout = WATCHDOG_TIMEOUT; 267 268 watchdog_init_timeout(&priv->wdd, 0, dev); 269 watchdog_set_nowayout(&priv->wdd, nowayout); 270 271 watchdog_stop_on_reboot(&priv->wdd); 272 273 if (priv->always_running) 274 bd9576_wdt_start(&priv->wdd); 275 276 return devm_watchdog_register_device(dev, &priv->wdd); 277} 278 279static struct platform_driver bd9576_wdt_driver = { 280 .driver = { 281 .name = "bd9576-wdt", 282 }, 283 .probe = bd9576_wdt_probe, 284}; 285 286module_platform_driver(bd9576_wdt_driver); 287 288MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>"); 289MODULE_DESCRIPTION("ROHM BD9576/BD9573 Watchdog driver"); 290MODULE_LICENSE("GPL"); 291MODULE_ALIAS("platform:bd9576-wdt");