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