xref: /linux/drivers/watchdog/wm831x_wdt.c (revision ead5d1f4d877e92c051e1a1ade623d0d30e71619)
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