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