Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

watchdog: Exar/MaxLinear XR28V38x driver

Simple driver for the watchdog present in some Exar/MaxLinear UART chips.
Please see https://www.maxlinear.com/product/interface/uarts/lpc-uarts/xr28v384
for more info.

Signed-off-by: David Müller <d.mueller@elsoft.ch>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Link: https://lore.kernel.org/r/20220914094605.93377-1-d.mueller@elsoft.ch
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@linux-watchdog.org>

authored by

David Müller and committed by
Wim Van Sebroeck
81126222 64ee9375

+439
+11
drivers/watchdog/Kconfig
··· 1089 1089 WinSystems EBC-C384 motherboard. The timeout may be configured via 1090 1090 the timeout module parameter. 1091 1091 1092 + config EXAR_WDT 1093 + tristate "Exar Watchdog Timer" 1094 + depends on X86 1095 + select WATCHDOG_CORE 1096 + help 1097 + Enables watchdog timer support for the watchdog timer present 1098 + in some Exar/MaxLinear UART chips like the XR28V38x. 1099 + 1100 + To compile this driver as a module, choose M here: the 1101 + module will be called exar_wdt. 1102 + 1092 1103 config F71808E_WDT 1093 1104 tristate "Fintek F718xx, F818xx Super I/O Watchdog" 1094 1105 depends on X86
+1
drivers/watchdog/Makefile
··· 105 105 obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o 106 106 obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o 107 107 obj-$(CONFIG_EBC_C384_WDT) += ebc-c384_wdt.o 108 + obj-$(CONFIG_EXAR_WDT) += exar_wdt.o 108 109 obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o 109 110 obj-$(CONFIG_SP5100_TCO) += sp5100_tco.o 110 111 obj-$(CONFIG_GEODE_WDT) += geodewdt.o
+427
drivers/watchdog/exar_wdt.c
··· 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 + kfree(n); 359 + return PTR_ERR(n->pdev); 360 + } 361 + 362 + list_add_tail(&n->list, &pdev_list); 363 + 364 + return 0; 365 + } 366 + 367 + static void exar_wdt_unregister(void) 368 + { 369 + struct wdt_pdev_node *n, *t; 370 + 371 + list_for_each_entry_safe(n, t, &pdev_list, list) { 372 + platform_device_unregister(n->pdev); 373 + list_del(&n->list); 374 + kfree(n); 375 + } 376 + } 377 + 378 + static int __init exar_wdt_init(void) 379 + { 380 + int ret, i, j, idx = 0; 381 + 382 + /* search for active Exar watchdogs on all possible locations */ 383 + for (i = 0; i < ARRAY_SIZE(sio_config_ports); i++) { 384 + for (j = 0; j < ARRAY_SIZE(sio_enter_keys); j++) { 385 + unsigned short did, rt_base = 0; 386 + 387 + did = exar_detect(sio_config_ports[i], 388 + sio_enter_keys[j], 389 + &rt_base); 390 + 391 + if (did) { 392 + struct wdt_priv priv = { 393 + .wdt_res = DEFINE_RES_IO(rt_base, 2), 394 + .did = did, 395 + .config_port = sio_config_ports[i], 396 + .enter_key = sio_enter_keys[j], 397 + }; 398 + 399 + ret = exar_wdt_register(&priv, idx); 400 + if (!ret) 401 + idx++; 402 + } 403 + } 404 + } 405 + 406 + if (!idx) 407 + return -ENODEV; 408 + 409 + ret = platform_driver_probe(&exar_wdt_driver, exar_wdt_probe); 410 + if (ret) 411 + exar_wdt_unregister(); 412 + 413 + return ret; 414 + } 415 + 416 + static void __exit exar_wdt_exit(void) 417 + { 418 + exar_wdt_unregister(); 419 + platform_driver_unregister(&exar_wdt_driver); 420 + } 421 + 422 + module_init(exar_wdt_init); 423 + module_exit(exar_wdt_exit); 424 + 425 + MODULE_AUTHOR("David Müller <d.mueller@elsoft.ch>"); 426 + MODULE_DESCRIPTION("Exar/MaxLinear Watchdog Driver"); 427 + MODULE_LICENSE("GPL");