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/platform_device.h> 13 #include <linux/property.h> 14 #include <linux/regmap.h> 15 #include <linux/watchdog.h> 16 17 static bool nowayout; 18 module_param(nowayout, bool, 0); 19 MODULE_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 27 struct 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 36 static void bd9576_wdt_disable(struct bd9576_wdt_priv *priv) 37 { 38 gpiod_set_value_cansleep(priv->gpiod_en, 0); 39 } 40 41 static 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 52 static 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 61 static 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 73 static const struct watchdog_info bd957x_wdt_ident = { 74 .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | 75 WDIOF_SETTIMEOUT, 76 .identity = "BD957x Watchdog", 77 }; 78 79 static 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 89 static 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 107 static 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 124 static 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 156 static 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 202 static int bd9576_wdt_probe(struct platform_device *pdev) 203 { 204 struct device *dev = &pdev->dev; 205 struct bd9576_wdt_priv *priv; 206 u32 hw_margin[2]; 207 u32 hw_margin_max = BD957X_WDT_DEFAULT_MARGIN, hw_margin_min = 0; 208 int count; 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_fwnode_gpiod_get(dev, dev_fwnode(dev->parent), 225 "rohm,watchdog-enable", 226 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_fwnode_gpiod_get(dev, dev_fwnode(dev->parent), 233 "rohm,watchdog-ping", 234 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 count = device_property_count_u32(dev->parent, "rohm,hw-timeout-ms"); 241 if (count < 0 && count != -EINVAL) 242 return count; 243 244 if (count > 0) { 245 if (count > ARRAY_SIZE(hw_margin)) 246 return -EINVAL; 247 248 ret = device_property_read_u32_array(dev->parent, 249 "rohm,hw-timeout-ms", 250 hw_margin, count); 251 if (ret < 0) 252 return ret; 253 254 if (count == 1) 255 hw_margin_max = hw_margin[0]; 256 257 if (count == 2) { 258 hw_margin_max = hw_margin[1]; 259 hw_margin_min = hw_margin[0]; 260 } 261 } 262 263 ret = bd957x_set_wdt_mode(priv, hw_margin_max, hw_margin_min); 264 if (ret) 265 return ret; 266 267 priv->always_running = device_property_read_bool(dev->parent, 268 "always-running"); 269 270 watchdog_set_drvdata(&priv->wdd, priv); 271 272 priv->wdd.info = &bd957x_wdt_ident; 273 priv->wdd.ops = &bd957x_wdt_ops; 274 priv->wdd.min_hw_heartbeat_ms = hw_margin_min; 275 priv->wdd.max_hw_heartbeat_ms = hw_margin_max; 276 priv->wdd.parent = dev; 277 priv->wdd.timeout = WATCHDOG_TIMEOUT; 278 279 watchdog_init_timeout(&priv->wdd, 0, dev); 280 watchdog_set_nowayout(&priv->wdd, nowayout); 281 282 watchdog_stop_on_reboot(&priv->wdd); 283 284 if (priv->always_running) 285 bd9576_wdt_start(&priv->wdd); 286 287 return devm_watchdog_register_device(dev, &priv->wdd); 288 } 289 290 static struct platform_driver bd9576_wdt_driver = { 291 .driver = { 292 .name = "bd9576-wdt", 293 }, 294 .probe = bd9576_wdt_probe, 295 }; 296 297 module_platform_driver(bd9576_wdt_driver); 298 299 MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>"); 300 MODULE_DESCRIPTION("ROHM BD9576/BD9573 Watchdog driver"); 301 MODULE_LICENSE("GPL"); 302 MODULE_ALIAS("platform:bd9576-wdt"); 303