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 21static const unsigned short sio_config_ports[] = { 0x2e, 0x4e }; 22static 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 49struct wdt_pdev_node { 50 struct list_head list; 51 struct platform_device *pdev; 52 const char name[16]; 53}; 54 55struct 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 69static int timeout = WATCHDOG_TIMEOUT; 70module_param(timeout, int, 0); 71MODULE_PARM_DESC(timeout, 72 "Watchdog timeout in seconds. 1<=timeout<=15300, default=" 73 __MODULE_STRING(WATCHDOG_TIMEOUT) "."); 74 75static bool nowayout = WATCHDOG_NOWAYOUT; 76module_param(nowayout, bool, 0); 77MODULE_PARM_DESC(nowayout, 78 "Watchdog cannot be stopped once started (default=" 79 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 80 81static 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 94static 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 100static 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 107static 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 114static 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 125static void exar_sio_select_wdt(const unsigned short config_port) 126{ 127 exar_sio_write(config_port, EXAR_LDN, EXAR_WDT_LDEV); 128} 129 130static 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 139static 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 153static 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 168static 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 180static 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 194static 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 220static 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 227static 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 235static 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 253static 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 296static 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 333static struct platform_driver exar_wdt_driver = { 334 .driver = { 335 .name = DRV_NAME, 336 }, 337}; 338 339static LIST_HEAD(pdev_list); 340 341static 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 369static 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 380static 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 418static void __exit exar_wdt_exit(void) 419{ 420 exar_wdt_unregister(); 421 platform_driver_unregister(&exar_wdt_driver); 422} 423 424module_init(exar_wdt_init); 425module_exit(exar_wdt_exit); 426 427MODULE_AUTHOR("David M��ller <d.mueller@elsoft.ch>"); 428MODULE_DESCRIPTION("Exar/MaxLinear Watchdog Driver"); 429MODULE_LICENSE("GPL"); 430