12e62c498SMarcus Folkesson // SPDX-License-Identifier: GPL-2.0+ 2502a0106SMark Brown /* 3502a0106SMark Brown * Watchdog driver for the wm831x PMICs 4502a0106SMark Brown * 5502a0106SMark Brown * Copyright (C) 2009 Wolfson Microelectronics 6502a0106SMark Brown */ 7502a0106SMark Brown 8502a0106SMark Brown #include <linux/module.h> 9502a0106SMark Brown #include <linux/moduleparam.h> 10502a0106SMark Brown #include <linux/types.h> 11502a0106SMark Brown #include <linux/kernel.h> 1200411ee9SMark Brown #include <linux/slab.h> 13502a0106SMark Brown #include <linux/platform_device.h> 14502a0106SMark Brown #include <linux/watchdog.h> 15502a0106SMark Brown #include <linux/uaccess.h> 16502a0106SMark Brown #include <linux/gpio.h> 17502a0106SMark Brown 18502a0106SMark Brown #include <linux/mfd/wm831x/core.h> 19502a0106SMark Brown #include <linux/mfd/wm831x/pdata.h> 20502a0106SMark Brown #include <linux/mfd/wm831x/watchdog.h> 21502a0106SMark Brown 2286a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT; 2386a1e189SWim Van Sebroeck module_param(nowayout, bool, 0); 24502a0106SMark Brown MODULE_PARM_DESC(nowayout, 25502a0106SMark Brown "Watchdog cannot be stopped once started (default=" 26502a0106SMark Brown __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 27502a0106SMark Brown 2800411ee9SMark Brown struct wm831x_wdt_drvdata { 2900411ee9SMark Brown struct watchdog_device wdt; 3000411ee9SMark Brown struct wm831x *wm831x; 3100411ee9SMark Brown struct mutex lock; 3200411ee9SMark Brown int update_gpio; 3300411ee9SMark Brown int update_state; 3400411ee9SMark Brown }; 35502a0106SMark Brown 36502a0106SMark Brown /* We can't use the sub-second values here but they're included 37502a0106SMark Brown * for completeness. */ 38502a0106SMark Brown static struct { 3900411ee9SMark Brown unsigned int time; /* Seconds */ 40502a0106SMark Brown u16 val; /* WDOG_TO value */ 41502a0106SMark Brown } wm831x_wdt_cfgs[] = { 42502a0106SMark Brown { 1, 2 }, 43502a0106SMark Brown { 2, 3 }, 44502a0106SMark Brown { 4, 4 }, 45502a0106SMark Brown { 8, 5 }, 46502a0106SMark Brown { 16, 6 }, 47502a0106SMark Brown { 32, 7 }, 48502a0106SMark Brown { 33, 7 }, /* Actually 32.768s so include both, others round down */ 49502a0106SMark Brown }; 50502a0106SMark Brown 5100411ee9SMark Brown static int wm831x_wdt_start(struct watchdog_device *wdt_dev) 52502a0106SMark Brown { 5300411ee9SMark Brown struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev); 5400411ee9SMark Brown struct wm831x *wm831x = driver_data->wm831x; 55502a0106SMark Brown int ret; 56502a0106SMark Brown 5700411ee9SMark Brown mutex_lock(&driver_data->lock); 58502a0106SMark Brown 59502a0106SMark Brown ret = wm831x_reg_unlock(wm831x); 60502a0106SMark Brown if (ret == 0) { 61502a0106SMark Brown ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG, 62502a0106SMark Brown WM831X_WDOG_ENA, WM831X_WDOG_ENA); 63502a0106SMark Brown wm831x_reg_lock(wm831x); 64502a0106SMark Brown } else { 65502a0106SMark Brown dev_err(wm831x->dev, "Failed to unlock security key: %d\n", 66502a0106SMark Brown ret); 67502a0106SMark Brown } 68502a0106SMark Brown 6900411ee9SMark Brown mutex_unlock(&driver_data->lock); 70502a0106SMark Brown 71502a0106SMark Brown return ret; 72502a0106SMark Brown } 73502a0106SMark Brown 7400411ee9SMark Brown static int wm831x_wdt_stop(struct watchdog_device *wdt_dev) 75502a0106SMark Brown { 7600411ee9SMark Brown struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev); 7700411ee9SMark Brown struct wm831x *wm831x = driver_data->wm831x; 78502a0106SMark Brown int ret; 79502a0106SMark Brown 8000411ee9SMark Brown mutex_lock(&driver_data->lock); 81502a0106SMark Brown 82502a0106SMark Brown ret = wm831x_reg_unlock(wm831x); 83502a0106SMark Brown if (ret == 0) { 84502a0106SMark Brown ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG, 85502a0106SMark Brown WM831X_WDOG_ENA, 0); 86502a0106SMark Brown wm831x_reg_lock(wm831x); 87502a0106SMark Brown } else { 88502a0106SMark Brown dev_err(wm831x->dev, "Failed to unlock security key: %d\n", 89502a0106SMark Brown ret); 90502a0106SMark Brown } 91502a0106SMark Brown 9200411ee9SMark Brown mutex_unlock(&driver_data->lock); 93502a0106SMark Brown 94502a0106SMark Brown return ret; 95502a0106SMark Brown } 96502a0106SMark Brown 9700411ee9SMark Brown static int wm831x_wdt_ping(struct watchdog_device *wdt_dev) 98502a0106SMark Brown { 9900411ee9SMark Brown struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev); 10000411ee9SMark Brown struct wm831x *wm831x = driver_data->wm831x; 101502a0106SMark Brown int ret; 102502a0106SMark Brown u16 reg; 103502a0106SMark Brown 10400411ee9SMark Brown mutex_lock(&driver_data->lock); 105502a0106SMark Brown 10600411ee9SMark Brown if (driver_data->update_gpio) { 10700411ee9SMark Brown gpio_set_value_cansleep(driver_data->update_gpio, 10800411ee9SMark Brown driver_data->update_state); 10900411ee9SMark Brown driver_data->update_state = !driver_data->update_state; 110502a0106SMark Brown ret = 0; 111502a0106SMark Brown goto out; 112502a0106SMark Brown } 113502a0106SMark Brown 114502a0106SMark Brown reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG); 115502a0106SMark Brown 116502a0106SMark Brown if (!(reg & WM831X_WDOG_RST_SRC)) { 117502a0106SMark Brown dev_err(wm831x->dev, "Hardware watchdog update unsupported\n"); 118502a0106SMark Brown ret = -EINVAL; 119502a0106SMark Brown goto out; 120502a0106SMark Brown } 121502a0106SMark Brown 122502a0106SMark Brown reg |= WM831X_WDOG_RESET; 123502a0106SMark Brown 124502a0106SMark Brown ret = wm831x_reg_unlock(wm831x); 125502a0106SMark Brown if (ret == 0) { 126502a0106SMark Brown ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg); 127502a0106SMark Brown wm831x_reg_lock(wm831x); 128502a0106SMark Brown } else { 129502a0106SMark Brown dev_err(wm831x->dev, "Failed to unlock security key: %d\n", 130502a0106SMark Brown ret); 131502a0106SMark Brown } 132502a0106SMark Brown 133502a0106SMark Brown out: 13400411ee9SMark Brown mutex_unlock(&driver_data->lock); 135502a0106SMark Brown 136502a0106SMark Brown return ret; 137502a0106SMark Brown } 138502a0106SMark Brown 13900411ee9SMark Brown static int wm831x_wdt_set_timeout(struct watchdog_device *wdt_dev, 14000411ee9SMark Brown unsigned int timeout) 141502a0106SMark Brown { 14200411ee9SMark Brown struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev); 14300411ee9SMark Brown struct wm831x *wm831x = driver_data->wm831x; 14400411ee9SMark Brown int ret, i; 145502a0106SMark Brown 14600411ee9SMark Brown for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++) 14700411ee9SMark Brown if (wm831x_wdt_cfgs[i].time == timeout) 14800411ee9SMark Brown break; 14900411ee9SMark Brown if (i == ARRAY_SIZE(wm831x_wdt_cfgs)) 150f9849100SMark Brown return -EINVAL; 151502a0106SMark Brown 15200411ee9SMark Brown ret = wm831x_reg_unlock(wm831x); 15300411ee9SMark Brown if (ret == 0) { 15400411ee9SMark Brown ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG, 15500411ee9SMark Brown WM831X_WDOG_TO_MASK, 15600411ee9SMark Brown wm831x_wdt_cfgs[i].val); 15700411ee9SMark Brown wm831x_reg_lock(wm831x); 15800411ee9SMark Brown } else { 15900411ee9SMark Brown dev_err(wm831x->dev, "Failed to unlock security key: %d\n", 16000411ee9SMark Brown ret); 16100411ee9SMark Brown } 162502a0106SMark Brown 1630197c1c4SWim Van Sebroeck wdt_dev->timeout = timeout; 1640197c1c4SWim Van Sebroeck 165502a0106SMark Brown return ret; 166502a0106SMark Brown } 167502a0106SMark Brown 16800411ee9SMark Brown static const struct watchdog_info wm831x_wdt_info = { 169502a0106SMark Brown .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 170502a0106SMark Brown .identity = "WM831x Watchdog", 171502a0106SMark Brown }; 172502a0106SMark Brown 17300411ee9SMark Brown static const struct watchdog_ops wm831x_wdt_ops = { 174502a0106SMark Brown .owner = THIS_MODULE, 17500411ee9SMark Brown .start = wm831x_wdt_start, 17600411ee9SMark Brown .stop = wm831x_wdt_stop, 17700411ee9SMark Brown .ping = wm831x_wdt_ping, 17800411ee9SMark Brown .set_timeout = wm831x_wdt_set_timeout, 179502a0106SMark Brown }; 180502a0106SMark Brown 1812d991a16SBill Pemberton static int wm831x_wdt_probe(struct platform_device *pdev) 182502a0106SMark Brown { 183*30f57e0fSGuenter Roeck struct device *dev = &pdev->dev; 184*30f57e0fSGuenter Roeck struct wm831x *wm831x = dev_get_drvdata(dev->parent); 185*30f57e0fSGuenter Roeck struct wm831x_pdata *chip_pdata = dev_get_platdata(dev->parent); 186502a0106SMark Brown struct wm831x_watchdog_pdata *pdata; 18700411ee9SMark Brown struct wm831x_wdt_drvdata *driver_data; 18800411ee9SMark Brown struct watchdog_device *wm831x_wdt; 18900411ee9SMark Brown int reg, ret, i; 190502a0106SMark Brown 191502a0106SMark Brown ret = wm831x_reg_read(wm831x, WM831X_WATCHDOG); 192502a0106SMark Brown if (ret < 0) { 193502a0106SMark Brown dev_err(wm831x->dev, "Failed to read watchdog status: %d\n", 194502a0106SMark Brown ret); 19530cba9a1SGuenter Roeck return ret; 196502a0106SMark Brown } 197502a0106SMark Brown reg = ret; 198502a0106SMark Brown 199502a0106SMark Brown if (reg & WM831X_WDOG_DEBUG) 200502a0106SMark Brown dev_warn(wm831x->dev, "Watchdog is paused\n"); 201502a0106SMark Brown 202*30f57e0fSGuenter Roeck driver_data = devm_kzalloc(dev, sizeof(*driver_data), GFP_KERNEL); 20330cba9a1SGuenter Roeck if (!driver_data) 20430cba9a1SGuenter Roeck return -ENOMEM; 20500411ee9SMark Brown 20600411ee9SMark Brown mutex_init(&driver_data->lock); 20700411ee9SMark Brown driver_data->wm831x = wm831x; 20800411ee9SMark Brown 20900411ee9SMark Brown wm831x_wdt = &driver_data->wdt; 21000411ee9SMark Brown 21100411ee9SMark Brown wm831x_wdt->info = &wm831x_wdt_info; 21200411ee9SMark Brown wm831x_wdt->ops = &wm831x_wdt_ops; 213*30f57e0fSGuenter Roeck wm831x_wdt->parent = dev; 214ff0b3cd4SWim Van Sebroeck watchdog_set_nowayout(wm831x_wdt, nowayout); 21500411ee9SMark Brown watchdog_set_drvdata(wm831x_wdt, driver_data); 21600411ee9SMark Brown 21700411ee9SMark Brown reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG); 21800411ee9SMark Brown reg &= WM831X_WDOG_TO_MASK; 21900411ee9SMark Brown for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++) 22000411ee9SMark Brown if (wm831x_wdt_cfgs[i].val == reg) 22100411ee9SMark Brown break; 22200411ee9SMark Brown if (i == ARRAY_SIZE(wm831x_wdt_cfgs)) 22300411ee9SMark Brown dev_warn(wm831x->dev, 22400411ee9SMark Brown "Unknown watchdog timeout: %x\n", reg); 22500411ee9SMark Brown else 22600411ee9SMark Brown wm831x_wdt->timeout = wm831x_wdt_cfgs[i].time; 22700411ee9SMark Brown 228502a0106SMark Brown /* Apply any configuration */ 229bc8fdfbeSJingoo Han if (chip_pdata) 230502a0106SMark Brown pdata = chip_pdata->watchdog; 231bc8fdfbeSJingoo Han else 232502a0106SMark Brown pdata = NULL; 233502a0106SMark Brown 234502a0106SMark Brown if (pdata) { 235502a0106SMark Brown reg &= ~(WM831X_WDOG_SECACT_MASK | WM831X_WDOG_PRIMACT_MASK | 236502a0106SMark Brown WM831X_WDOG_RST_SRC); 237502a0106SMark Brown 238502a0106SMark Brown reg |= pdata->primary << WM831X_WDOG_PRIMACT_SHIFT; 239502a0106SMark Brown reg |= pdata->secondary << WM831X_WDOG_SECACT_SHIFT; 240502a0106SMark Brown reg |= pdata->software << WM831X_WDOG_RST_SRC_SHIFT; 241502a0106SMark Brown 242502a0106SMark Brown if (pdata->update_gpio) { 243*30f57e0fSGuenter Roeck ret = devm_gpio_request_one(dev, pdata->update_gpio, 2447a5da030SJingoo Han GPIOF_OUT_INIT_LOW, 245502a0106SMark Brown "Watchdog update"); 246502a0106SMark Brown if (ret < 0) { 247502a0106SMark Brown dev_err(wm831x->dev, 248502a0106SMark Brown "Failed to request update GPIO: %d\n", 249502a0106SMark Brown ret); 25030cba9a1SGuenter Roeck return ret; 251502a0106SMark Brown } 252502a0106SMark Brown 25300411ee9SMark Brown driver_data->update_gpio = pdata->update_gpio; 254502a0106SMark Brown 255502a0106SMark Brown /* Make sure the watchdog takes hardware updates */ 256502a0106SMark Brown reg |= WM831X_WDOG_RST_SRC; 257502a0106SMark Brown } 258502a0106SMark Brown 259502a0106SMark Brown ret = wm831x_reg_unlock(wm831x); 260502a0106SMark Brown if (ret == 0) { 261502a0106SMark Brown ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg); 262502a0106SMark Brown wm831x_reg_lock(wm831x); 263502a0106SMark Brown } else { 264502a0106SMark Brown dev_err(wm831x->dev, 265502a0106SMark Brown "Failed to unlock security key: %d\n", ret); 26630cba9a1SGuenter Roeck return ret; 267502a0106SMark Brown } 268502a0106SMark Brown } 269502a0106SMark Brown 270*30f57e0fSGuenter Roeck ret = devm_watchdog_register_device(dev, &driver_data->wdt); 271502a0106SMark Brown if (ret != 0) { 27200411ee9SMark Brown dev_err(wm831x->dev, "watchdog_register_device() failed: %d\n", 27300411ee9SMark Brown ret); 274502a0106SMark Brown return ret; 275502a0106SMark Brown } 276502a0106SMark Brown 277502a0106SMark Brown return 0; 278502a0106SMark Brown } 279502a0106SMark Brown 280502a0106SMark Brown static struct platform_driver wm831x_wdt_driver = { 281502a0106SMark Brown .probe = wm831x_wdt_probe, 282502a0106SMark Brown .driver = { 283502a0106SMark Brown .name = "wm831x-watchdog", 284502a0106SMark Brown }, 285502a0106SMark Brown }; 286502a0106SMark Brown 287216f3ad9SMark Brown module_platform_driver(wm831x_wdt_driver); 288502a0106SMark Brown 289502a0106SMark Brown MODULE_AUTHOR("Mark Brown"); 290502a0106SMark Brown MODULE_DESCRIPTION("WM831x Watchdog"); 291502a0106SMark Brown MODULE_LICENSE("GPL"); 292502a0106SMark Brown MODULE_ALIAS("platform:wm831x-watchdog"); 293