1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * exar_wdt.c - Driver for the watchdog present in some 4 * Exar/MaxLinear UART chips like the XR28V38x. 5 * 6 * (c) Copyright 2022 D. Müller <d.mueller@elsoft.ch>. 7 * 8 */ 9 10 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 11 12 #include <linux/io.h> 13 #include <linux/list.h> 14 #include <linux/module.h> 15 #include <linux/platform_device.h> 16 #include <linux/slab.h> 17 #include <linux/watchdog.h> 18 19 #define DRV_NAME "exar_wdt" 20 21 static const unsigned short sio_config_ports[] = { 0x2e, 0x4e }; 22 static const unsigned char sio_enter_keys[] = { 0x67, 0x77, 0x87, 0xA0 }; 23 #define EXAR_EXIT_KEY 0xAA 24 25 #define EXAR_LDN 0x07 26 #define EXAR_DID 0x20 27 #define EXAR_VID 0x23 28 #define EXAR_WDT 0x26 29 #define EXAR_ACT 0x30 30 #define EXAR_RTBASE 0x60 31 32 #define EXAR_WDT_LDEV 0x08 33 34 #define EXAR_VEN_ID 0x13A8 35 #define EXAR_DEV_382 0x0382 36 #define EXAR_DEV_384 0x0384 37 38 /* WDT runtime registers */ 39 #define WDT_CTRL 0x00 40 #define WDT_VAL 0x01 41 42 #define WDT_UNITS_10MS 0x0 /* the 10 millisec unit of the HW is not used */ 43 #define WDT_UNITS_SEC 0x2 44 #define WDT_UNITS_MIN 0x4 45 46 /* default WDT control for WDTOUT signal activ / rearm by read */ 47 #define EXAR_WDT_DEF_CONF 0 48 49 struct wdt_pdev_node { 50 struct list_head list; 51 struct platform_device *pdev; 52 const char name[16]; 53 }; 54 55 struct wdt_priv { 56 /* the lock for WDT io operations */ 57 spinlock_t io_lock; 58 struct resource wdt_res; 59 struct watchdog_device wdt_dev; 60 unsigned short did; 61 unsigned short config_port; 62 unsigned char enter_key; 63 unsigned char unit; 64 unsigned char timeout; 65 }; 66 67 #define WATCHDOG_TIMEOUT 60 68 69 static int timeout = WATCHDOG_TIMEOUT; 70 module_param(timeout, int, 0); 71 MODULE_PARM_DESC(timeout, 72 "Watchdog timeout in seconds. 1<=timeout<=15300, default=" 73 __MODULE_STRING(WATCHDOG_TIMEOUT) "."); 74 75 static bool nowayout = WATCHDOG_NOWAYOUT; 76 module_param(nowayout, bool, 0); 77 MODULE_PARM_DESC(nowayout, 78 "Watchdog cannot be stopped once started (default=" 79 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 80 81 static int exar_sio_enter(const unsigned short config_port, 82 const unsigned char key) 83 { 84 if (!request_muxed_region(config_port, 2, DRV_NAME)) 85 return -EBUSY; 86 87 /* write the ENTER-KEY twice */ 88 outb(key, config_port); 89 outb(key, config_port); 90 91 return 0; 92 } 93 94 static void exar_sio_exit(const unsigned short config_port) 95 { 96 outb(EXAR_EXIT_KEY, config_port); 97 release_region(config_port, 2); 98 } 99 100 static unsigned char exar_sio_read(const unsigned short config_port, 101 const unsigned char reg) 102 { 103 outb(reg, config_port); 104 return inb(config_port + 1); 105 } 106 107 static void exar_sio_write(const unsigned short config_port, 108 const unsigned char reg, const unsigned char val) 109 { 110 outb(reg, config_port); 111 outb(val, config_port + 1); 112 } 113 114 static unsigned short exar_sio_read16(const unsigned short config_port, 115 const unsigned char reg) 116 { 117 unsigned char msb, lsb; 118 119 msb = exar_sio_read(config_port, reg); 120 lsb = exar_sio_read(config_port, reg + 1); 121 122 return (msb << 8) | lsb; 123 } 124 125 static void exar_sio_select_wdt(const unsigned short config_port) 126 { 127 exar_sio_write(config_port, EXAR_LDN, EXAR_WDT_LDEV); 128 } 129 130 static void exar_wdt_arm(const struct wdt_priv *priv) 131 { 132 unsigned short rt_base = priv->wdt_res.start; 133 134 /* write timeout value twice to arm watchdog */ 135 outb(priv->timeout, rt_base + WDT_VAL); 136 outb(priv->timeout, rt_base + WDT_VAL); 137 } 138 139 static void exar_wdt_disarm(const struct wdt_priv *priv) 140 { 141 unsigned short rt_base = priv->wdt_res.start; 142 143 /* 144 * use two accesses with different values to make sure 145 * that a combination of a previous single access and 146 * the ones below with the same value are not falsely 147 * interpreted as "arm watchdog" 148 */ 149 outb(0xFF, rt_base + WDT_VAL); 150 outb(0, rt_base + WDT_VAL); 151 } 152 153 static int exar_wdt_start(struct watchdog_device *wdog) 154 { 155 struct wdt_priv *priv = watchdog_get_drvdata(wdog); 156 unsigned short rt_base = priv->wdt_res.start; 157 158 spin_lock(&priv->io_lock); 159 160 exar_wdt_disarm(priv); 161 outb(priv->unit, rt_base + WDT_CTRL); 162 exar_wdt_arm(priv); 163 164 spin_unlock(&priv->io_lock); 165 return 0; 166 } 167 168 static int exar_wdt_stop(struct watchdog_device *wdog) 169 { 170 struct wdt_priv *priv = watchdog_get_drvdata(wdog); 171 172 spin_lock(&priv->io_lock); 173 174 exar_wdt_disarm(priv); 175 176 spin_unlock(&priv->io_lock); 177 return 0; 178 } 179 180 static int exar_wdt_keepalive(struct watchdog_device *wdog) 181 { 182 struct wdt_priv *priv = watchdog_get_drvdata(wdog); 183 unsigned short rt_base = priv->wdt_res.start; 184 185 spin_lock(&priv->io_lock); 186 187 /* reading the WDT_VAL reg will feed the watchdog */ 188 inb(rt_base + WDT_VAL); 189 190 spin_unlock(&priv->io_lock); 191 return 0; 192 } 193 194 static int exar_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t) 195 { 196 struct wdt_priv *priv = watchdog_get_drvdata(wdog); 197 bool unit_min = false; 198 199 /* 200 * if new timeout is bigger then 255 seconds, change the 201 * unit to minutes and round the timeout up to the next whole minute 202 */ 203 if (t > 255) { 204 unit_min = true; 205 t = DIV_ROUND_UP(t, 60); 206 } 207 208 /* save for later use in exar_wdt_start() */ 209 priv->unit = unit_min ? WDT_UNITS_MIN : WDT_UNITS_SEC; 210 priv->timeout = t; 211 212 wdog->timeout = unit_min ? t * 60 : t; 213 214 if (watchdog_hw_running(wdog)) 215 exar_wdt_start(wdog); 216 217 return 0; 218 } 219 220 static const struct watchdog_info exar_wdt_info = { 221 .options = WDIOF_KEEPALIVEPING | 222 WDIOF_SETTIMEOUT | 223 WDIOF_MAGICCLOSE, 224 .identity = "Exar/MaxLinear XR28V38x Watchdog", 225 }; 226 227 static const struct watchdog_ops exar_wdt_ops = { 228 .owner = THIS_MODULE, 229 .start = exar_wdt_start, 230 .stop = exar_wdt_stop, 231 .ping = exar_wdt_keepalive, 232 .set_timeout = exar_wdt_set_timeout, 233 }; 234 235 static int exar_wdt_config(struct watchdog_device *wdog, 236 const unsigned char conf) 237 { 238 struct wdt_priv *priv = watchdog_get_drvdata(wdog); 239 int ret; 240 241 ret = exar_sio_enter(priv->config_port, priv->enter_key); 242 if (ret) 243 return ret; 244 245 exar_sio_select_wdt(priv->config_port); 246 exar_sio_write(priv->config_port, EXAR_WDT, conf); 247 248 exar_sio_exit(priv->config_port); 249 250 return 0; 251 } 252 253 static int __init exar_wdt_probe(struct platform_device *pdev) 254 { 255 struct device *dev = &pdev->dev; 256 struct wdt_priv *priv = dev->platform_data; 257 struct watchdog_device *wdt_dev = &priv->wdt_dev; 258 struct resource *res; 259 int ret; 260 261 res = platform_get_resource(pdev, IORESOURCE_IO, 0); 262 if (!res) 263 return -ENXIO; 264 265 spin_lock_init(&priv->io_lock); 266 267 wdt_dev->info = &exar_wdt_info; 268 wdt_dev->ops = &exar_wdt_ops; 269 wdt_dev->min_timeout = 1; 270 wdt_dev->max_timeout = 255 * 60; 271 272 watchdog_init_timeout(wdt_dev, timeout, NULL); 273 watchdog_set_nowayout(wdt_dev, nowayout); 274 watchdog_stop_on_reboot(wdt_dev); 275 watchdog_stop_on_unregister(wdt_dev); 276 watchdog_set_drvdata(wdt_dev, priv); 277 278 ret = exar_wdt_config(wdt_dev, EXAR_WDT_DEF_CONF); 279 if (ret) 280 return ret; 281 282 exar_wdt_set_timeout(wdt_dev, timeout); 283 /* Make sure that the watchdog is not running */ 284 exar_wdt_stop(wdt_dev); 285 286 ret = devm_watchdog_register_device(dev, wdt_dev); 287 if (ret) 288 return ret; 289 290 dev_info(dev, "XR28V%X WDT initialized. timeout=%d sec (nowayout=%d)\n", 291 priv->did, timeout, nowayout); 292 293 return 0; 294 } 295 296 static unsigned short __init exar_detect(const unsigned short config_port, 297 const unsigned char key, 298 unsigned short *rt_base) 299 { 300 int ret; 301 unsigned short base = 0; 302 unsigned short vid, did; 303 304 ret = exar_sio_enter(config_port, key); 305 if (ret) 306 return 0; 307 308 vid = exar_sio_read16(config_port, EXAR_VID); 309 did = exar_sio_read16(config_port, EXAR_DID); 310 311 /* check for the vendor and device IDs we currently know about */ 312 if (vid == EXAR_VEN_ID && 313 (did == EXAR_DEV_382 || 314 did == EXAR_DEV_384)) { 315 exar_sio_select_wdt(config_port); 316 /* is device active? */ 317 if (exar_sio_read(config_port, EXAR_ACT) == 0x01) 318 base = exar_sio_read16(config_port, EXAR_RTBASE); 319 } 320 321 exar_sio_exit(config_port); 322 323 if (base) { 324 pr_debug("Found a XR28V%X WDT (conf: 0x%x / rt: 0x%04x)\n", 325 did, config_port, base); 326 *rt_base = base; 327 return did; 328 } 329 330 return 0; 331 } 332 333 static struct platform_driver exar_wdt_driver = { 334 .driver = { 335 .name = DRV_NAME, 336 }, 337 }; 338 339 static LIST_HEAD(pdev_list); 340 341 static int __init exar_wdt_register(struct wdt_priv *priv, const int idx) 342 { 343 struct wdt_pdev_node *n; 344 345 n = kzalloc(sizeof(*n), GFP_KERNEL); 346 if (!n) 347 return -ENOMEM; 348 349 INIT_LIST_HEAD(&n->list); 350 351 scnprintf((char *)n->name, sizeof(n->name), DRV_NAME ".%d", idx); 352 priv->wdt_res.name = n->name; 353 354 n->pdev = platform_device_register_resndata(NULL, DRV_NAME, idx, 355 &priv->wdt_res, 1, 356 priv, sizeof(*priv)); 357 if (IS_ERR(n->pdev)) { 358 int err = PTR_ERR(n->pdev); 359 360 kfree(n); 361 return err; 362 } 363 364 list_add_tail(&n->list, &pdev_list); 365 366 return 0; 367 } 368 369 static void exar_wdt_unregister(void) 370 { 371 struct wdt_pdev_node *n, *t; 372 373 list_for_each_entry_safe(n, t, &pdev_list, list) { 374 platform_device_unregister(n->pdev); 375 list_del(&n->list); 376 kfree(n); 377 } 378 } 379 380 static int __init exar_wdt_init(void) 381 { 382 int ret, i, j, idx = 0; 383 384 /* search for active Exar watchdogs on all possible locations */ 385 for (i = 0; i < ARRAY_SIZE(sio_config_ports); i++) { 386 for (j = 0; j < ARRAY_SIZE(sio_enter_keys); j++) { 387 unsigned short did, rt_base = 0; 388 389 did = exar_detect(sio_config_ports[i], 390 sio_enter_keys[j], 391 &rt_base); 392 393 if (did) { 394 struct wdt_priv priv = { 395 .wdt_res = DEFINE_RES_IO(rt_base, 2), 396 .did = did, 397 .config_port = sio_config_ports[i], 398 .enter_key = sio_enter_keys[j], 399 }; 400 401 ret = exar_wdt_register(&priv, idx); 402 if (!ret) 403 idx++; 404 } 405 } 406 } 407 408 if (!idx) 409 return -ENODEV; 410 411 ret = platform_driver_probe(&exar_wdt_driver, exar_wdt_probe); 412 if (ret) 413 exar_wdt_unregister(); 414 415 return ret; 416 } 417 418 static void __exit exar_wdt_exit(void) 419 { 420 exar_wdt_unregister(); 421 platform_driver_unregister(&exar_wdt_driver); 422 } 423 424 module_init(exar_wdt_init); 425 module_exit(exar_wdt_exit); 426 427 MODULE_AUTHOR("David Müller <d.mueller@elsoft.ch>"); 428 MODULE_DESCRIPTION("Exar/MaxLinear Watchdog Driver"); 429 MODULE_LICENSE("GPL"); 430