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
17502a0106SMark Brown #include <linux/mfd/wm831x/core.h>
18502a0106SMark Brown #include <linux/mfd/wm831x/pdata.h>
19502a0106SMark Brown #include <linux/mfd/wm831x/watchdog.h>
20502a0106SMark Brown
2186a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
2286a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
23502a0106SMark Brown MODULE_PARM_DESC(nowayout,
24502a0106SMark Brown "Watchdog cannot be stopped once started (default="
25502a0106SMark Brown __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
26502a0106SMark Brown
2700411ee9SMark Brown struct wm831x_wdt_drvdata {
2800411ee9SMark Brown struct watchdog_device wdt;
2900411ee9SMark Brown struct wm831x *wm831x;
3000411ee9SMark Brown struct mutex lock;
3100411ee9SMark Brown int update_state;
3200411ee9SMark Brown };
33502a0106SMark Brown
34502a0106SMark Brown /* We can't use the sub-second values here but they're included
35502a0106SMark Brown * for completeness. */
36502a0106SMark Brown static struct {
3700411ee9SMark Brown unsigned int time; /* Seconds */
38502a0106SMark Brown u16 val; /* WDOG_TO value */
39502a0106SMark Brown } wm831x_wdt_cfgs[] = {
40502a0106SMark Brown { 1, 2 },
41502a0106SMark Brown { 2, 3 },
42502a0106SMark Brown { 4, 4 },
43502a0106SMark Brown { 8, 5 },
44502a0106SMark Brown { 16, 6 },
45502a0106SMark Brown { 32, 7 },
46502a0106SMark Brown { 33, 7 }, /* Actually 32.768s so include both, others round down */
47502a0106SMark Brown };
48502a0106SMark Brown
wm831x_wdt_start(struct watchdog_device * wdt_dev)4900411ee9SMark Brown static int wm831x_wdt_start(struct watchdog_device *wdt_dev)
50502a0106SMark Brown {
5100411ee9SMark Brown struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev);
5200411ee9SMark Brown struct wm831x *wm831x = driver_data->wm831x;
53502a0106SMark Brown int ret;
54502a0106SMark Brown
5500411ee9SMark Brown mutex_lock(&driver_data->lock);
56502a0106SMark Brown
57502a0106SMark Brown ret = wm831x_reg_unlock(wm831x);
58502a0106SMark Brown if (ret == 0) {
59502a0106SMark Brown ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
60502a0106SMark Brown WM831X_WDOG_ENA, WM831X_WDOG_ENA);
61502a0106SMark Brown wm831x_reg_lock(wm831x);
62502a0106SMark Brown } else {
63502a0106SMark Brown dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
64502a0106SMark Brown ret);
65502a0106SMark Brown }
66502a0106SMark Brown
6700411ee9SMark Brown mutex_unlock(&driver_data->lock);
68502a0106SMark Brown
69502a0106SMark Brown return ret;
70502a0106SMark Brown }
71502a0106SMark Brown
wm831x_wdt_stop(struct watchdog_device * wdt_dev)7200411ee9SMark Brown static int wm831x_wdt_stop(struct watchdog_device *wdt_dev)
73502a0106SMark Brown {
7400411ee9SMark Brown struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev);
7500411ee9SMark Brown struct wm831x *wm831x = driver_data->wm831x;
76502a0106SMark Brown int ret;
77502a0106SMark Brown
7800411ee9SMark Brown mutex_lock(&driver_data->lock);
79502a0106SMark Brown
80502a0106SMark Brown ret = wm831x_reg_unlock(wm831x);
81502a0106SMark Brown if (ret == 0) {
82502a0106SMark Brown ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
83502a0106SMark Brown WM831X_WDOG_ENA, 0);
84502a0106SMark Brown wm831x_reg_lock(wm831x);
85502a0106SMark Brown } else {
86502a0106SMark Brown dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
87502a0106SMark Brown ret);
88502a0106SMark Brown }
89502a0106SMark Brown
9000411ee9SMark Brown mutex_unlock(&driver_data->lock);
91502a0106SMark Brown
92502a0106SMark Brown return ret;
93502a0106SMark Brown }
94502a0106SMark Brown
wm831x_wdt_ping(struct watchdog_device * wdt_dev)9500411ee9SMark Brown static int wm831x_wdt_ping(struct watchdog_device *wdt_dev)
96502a0106SMark Brown {
9700411ee9SMark Brown struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev);
9800411ee9SMark Brown struct wm831x *wm831x = driver_data->wm831x;
99502a0106SMark Brown int ret;
100502a0106SMark Brown u16 reg;
101502a0106SMark Brown
10200411ee9SMark Brown mutex_lock(&driver_data->lock);
103502a0106SMark Brown
104502a0106SMark Brown reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
105502a0106SMark Brown
106502a0106SMark Brown if (!(reg & WM831X_WDOG_RST_SRC)) {
107502a0106SMark Brown dev_err(wm831x->dev, "Hardware watchdog update unsupported\n");
108502a0106SMark Brown ret = -EINVAL;
109502a0106SMark Brown goto out;
110502a0106SMark Brown }
111502a0106SMark Brown
112502a0106SMark Brown reg |= WM831X_WDOG_RESET;
113502a0106SMark Brown
114502a0106SMark Brown ret = wm831x_reg_unlock(wm831x);
115502a0106SMark Brown if (ret == 0) {
116502a0106SMark Brown ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg);
117502a0106SMark Brown wm831x_reg_lock(wm831x);
118502a0106SMark Brown } else {
119502a0106SMark Brown dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
120502a0106SMark Brown ret);
121502a0106SMark Brown }
122502a0106SMark Brown
123502a0106SMark Brown out:
12400411ee9SMark Brown mutex_unlock(&driver_data->lock);
125502a0106SMark Brown
126502a0106SMark Brown return ret;
127502a0106SMark Brown }
128502a0106SMark Brown
wm831x_wdt_set_timeout(struct watchdog_device * wdt_dev,unsigned int timeout)12900411ee9SMark Brown static int wm831x_wdt_set_timeout(struct watchdog_device *wdt_dev,
13000411ee9SMark Brown unsigned int timeout)
131502a0106SMark Brown {
13200411ee9SMark Brown struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev);
13300411ee9SMark Brown struct wm831x *wm831x = driver_data->wm831x;
13400411ee9SMark Brown int ret, i;
135502a0106SMark Brown
13600411ee9SMark Brown for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++)
13700411ee9SMark Brown if (wm831x_wdt_cfgs[i].time == timeout)
13800411ee9SMark Brown break;
13900411ee9SMark Brown if (i == ARRAY_SIZE(wm831x_wdt_cfgs))
140f9849100SMark Brown return -EINVAL;
141502a0106SMark Brown
14200411ee9SMark Brown ret = wm831x_reg_unlock(wm831x);
14300411ee9SMark Brown if (ret == 0) {
14400411ee9SMark Brown ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
14500411ee9SMark Brown WM831X_WDOG_TO_MASK,
14600411ee9SMark Brown wm831x_wdt_cfgs[i].val);
14700411ee9SMark Brown wm831x_reg_lock(wm831x);
14800411ee9SMark Brown } else {
14900411ee9SMark Brown dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
15000411ee9SMark Brown ret);
15100411ee9SMark Brown }
152502a0106SMark Brown
1530197c1c4SWim Van Sebroeck wdt_dev->timeout = timeout;
1540197c1c4SWim Van Sebroeck
155502a0106SMark Brown return ret;
156502a0106SMark Brown }
157502a0106SMark Brown
15800411ee9SMark Brown static const struct watchdog_info wm831x_wdt_info = {
159502a0106SMark Brown .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
160502a0106SMark Brown .identity = "WM831x Watchdog",
161502a0106SMark Brown };
162502a0106SMark Brown
16300411ee9SMark Brown static const struct watchdog_ops wm831x_wdt_ops = {
164502a0106SMark Brown .owner = THIS_MODULE,
16500411ee9SMark Brown .start = wm831x_wdt_start,
16600411ee9SMark Brown .stop = wm831x_wdt_stop,
16700411ee9SMark Brown .ping = wm831x_wdt_ping,
16800411ee9SMark Brown .set_timeout = wm831x_wdt_set_timeout,
169502a0106SMark Brown };
170502a0106SMark Brown
wm831x_wdt_probe(struct platform_device * pdev)1712d991a16SBill Pemberton static int wm831x_wdt_probe(struct platform_device *pdev)
172502a0106SMark Brown {
17330f57e0fSGuenter Roeck struct device *dev = &pdev->dev;
17430f57e0fSGuenter Roeck struct wm831x *wm831x = dev_get_drvdata(dev->parent);
17530f57e0fSGuenter Roeck struct wm831x_pdata *chip_pdata = dev_get_platdata(dev->parent);
176502a0106SMark Brown struct wm831x_watchdog_pdata *pdata;
17700411ee9SMark Brown struct wm831x_wdt_drvdata *driver_data;
17800411ee9SMark Brown struct watchdog_device *wm831x_wdt;
17900411ee9SMark Brown int reg, ret, i;
180502a0106SMark Brown
181502a0106SMark Brown ret = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
182502a0106SMark Brown if (ret < 0) {
183502a0106SMark Brown dev_err(wm831x->dev, "Failed to read watchdog status: %d\n",
184502a0106SMark Brown ret);
18530cba9a1SGuenter Roeck return ret;
186502a0106SMark Brown }
187502a0106SMark Brown reg = ret;
188502a0106SMark Brown
189502a0106SMark Brown if (reg & WM831X_WDOG_DEBUG)
190502a0106SMark Brown dev_warn(wm831x->dev, "Watchdog is paused\n");
191502a0106SMark Brown
19230f57e0fSGuenter Roeck driver_data = devm_kzalloc(dev, sizeof(*driver_data), GFP_KERNEL);
19330cba9a1SGuenter Roeck if (!driver_data)
19430cba9a1SGuenter Roeck return -ENOMEM;
19500411ee9SMark Brown
19600411ee9SMark Brown mutex_init(&driver_data->lock);
19700411ee9SMark Brown driver_data->wm831x = wm831x;
19800411ee9SMark Brown
19900411ee9SMark Brown wm831x_wdt = &driver_data->wdt;
20000411ee9SMark Brown
20100411ee9SMark Brown wm831x_wdt->info = &wm831x_wdt_info;
20200411ee9SMark Brown wm831x_wdt->ops = &wm831x_wdt_ops;
20330f57e0fSGuenter Roeck wm831x_wdt->parent = dev;
204ff0b3cd4SWim Van Sebroeck watchdog_set_nowayout(wm831x_wdt, nowayout);
20500411ee9SMark Brown watchdog_set_drvdata(wm831x_wdt, driver_data);
20600411ee9SMark Brown
20700411ee9SMark Brown reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
20800411ee9SMark Brown reg &= WM831X_WDOG_TO_MASK;
20900411ee9SMark Brown for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++)
21000411ee9SMark Brown if (wm831x_wdt_cfgs[i].val == reg)
21100411ee9SMark Brown break;
21200411ee9SMark Brown if (i == ARRAY_SIZE(wm831x_wdt_cfgs))
21300411ee9SMark Brown dev_warn(wm831x->dev,
21400411ee9SMark Brown "Unknown watchdog timeout: %x\n", reg);
21500411ee9SMark Brown else
21600411ee9SMark Brown wm831x_wdt->timeout = wm831x_wdt_cfgs[i].time;
21700411ee9SMark Brown
218502a0106SMark Brown /* Apply any configuration */
219bc8fdfbeSJingoo Han if (chip_pdata)
220502a0106SMark Brown pdata = chip_pdata->watchdog;
221bc8fdfbeSJingoo Han else
222502a0106SMark Brown pdata = NULL;
223502a0106SMark Brown
224502a0106SMark Brown if (pdata) {
225502a0106SMark Brown reg &= ~(WM831X_WDOG_SECACT_MASK | WM831X_WDOG_PRIMACT_MASK |
226502a0106SMark Brown WM831X_WDOG_RST_SRC);
227502a0106SMark Brown
228502a0106SMark Brown reg |= pdata->primary << WM831X_WDOG_PRIMACT_SHIFT;
229502a0106SMark Brown reg |= pdata->secondary << WM831X_WDOG_SECACT_SHIFT;
230502a0106SMark Brown reg |= pdata->software << WM831X_WDOG_RST_SRC_SHIFT;
231502a0106SMark Brown
232502a0106SMark Brown ret = wm831x_reg_unlock(wm831x);
233502a0106SMark Brown if (ret == 0) {
234502a0106SMark Brown ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg);
235502a0106SMark Brown wm831x_reg_lock(wm831x);
236502a0106SMark Brown } else {
237502a0106SMark Brown dev_err(wm831x->dev,
238502a0106SMark Brown "Failed to unlock security key: %d\n", ret);
23930cba9a1SGuenter Roeck return ret;
240502a0106SMark Brown }
241502a0106SMark Brown }
242502a0106SMark Brown
243*f848a153SWolfram Sang return devm_watchdog_register_device(dev, &driver_data->wdt);
244502a0106SMark Brown }
245502a0106SMark Brown
246502a0106SMark Brown static struct platform_driver wm831x_wdt_driver = {
247502a0106SMark Brown .probe = wm831x_wdt_probe,
248502a0106SMark Brown .driver = {
249502a0106SMark Brown .name = "wm831x-watchdog",
250502a0106SMark Brown },
251502a0106SMark Brown };
252502a0106SMark Brown
253216f3ad9SMark Brown module_platform_driver(wm831x_wdt_driver);
254502a0106SMark Brown
255502a0106SMark Brown MODULE_AUTHOR("Mark Brown");
256502a0106SMark Brown MODULE_DESCRIPTION("WM831x Watchdog");
257502a0106SMark Brown MODULE_LICENSE("GPL");
258502a0106SMark Brown MODULE_ALIAS("platform:wm831x-watchdog");
259