xref: /linux/drivers/watchdog/ts4800_wdt.c (revision 327fceff3b634e6f21bbe60bd1d28e41d5b1d924)
1a0d261ccSBagas Sanjaya // SPDX-License-Identifier: GPL-2.0-only
2bf900639SDamien Riegel /*
3bf900639SDamien Riegel  * Watchdog driver for TS-4800 based boards
4bf900639SDamien Riegel  *
5bf900639SDamien Riegel  * Copyright (c) 2015 - Savoir-faire Linux
6bf900639SDamien Riegel  *
7bf900639SDamien Riegel  */
8bf900639SDamien Riegel 
9bf900639SDamien Riegel #include <linux/kernel.h>
10bf900639SDamien Riegel #include <linux/mfd/syscon.h>
11bf900639SDamien Riegel #include <linux/module.h>
12bf900639SDamien Riegel #include <linux/of.h>
13bf900639SDamien Riegel #include <linux/platform_device.h>
14bf900639SDamien Riegel #include <linux/regmap.h>
15bf900639SDamien Riegel #include <linux/watchdog.h>
16bf900639SDamien Riegel 
17bf900639SDamien Riegel static bool nowayout = WATCHDOG_NOWAYOUT;
18bf900639SDamien Riegel module_param(nowayout, bool, 0);
19bf900639SDamien Riegel MODULE_PARM_DESC(nowayout,
20bf900639SDamien Riegel 	"Watchdog cannot be stopped once started (default="
21bf900639SDamien Riegel 	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
22bf900639SDamien Riegel 
23bf900639SDamien Riegel /* possible feed values */
24bf900639SDamien Riegel #define TS4800_WDT_FEED_2S       0x1
25bf900639SDamien Riegel #define TS4800_WDT_FEED_10S      0x2
26bf900639SDamien Riegel #define TS4800_WDT_DISABLE       0x3
27bf900639SDamien Riegel 
28bf900639SDamien Riegel struct ts4800_wdt {
29bf900639SDamien Riegel 	struct watchdog_device  wdd;
30bf900639SDamien Riegel 	struct regmap           *regmap;
31bf900639SDamien Riegel 	u32                     feed_offset;
32bf900639SDamien Riegel 	u32                     feed_val;
33bf900639SDamien Riegel };
34bf900639SDamien Riegel 
35bf900639SDamien Riegel /*
36bf900639SDamien Riegel  * TS-4800 supports the following timeout values:
37bf900639SDamien Riegel  *
38bf900639SDamien Riegel  *   value desc
39bf900639SDamien Riegel  *   ---------------------
40bf900639SDamien Riegel  *     0    feed for 338ms
41bf900639SDamien Riegel  *     1    feed for 2.706s
42bf900639SDamien Riegel  *     2    feed for 10.824s
43bf900639SDamien Riegel  *     3    disable watchdog
44bf900639SDamien Riegel  *
45bf900639SDamien Riegel  * Keep the regmap/timeout map ordered by timeout
46bf900639SDamien Riegel  */
47bf900639SDamien Riegel static const struct {
48bf900639SDamien Riegel 	const int timeout;
49bf900639SDamien Riegel 	const int regval;
50bf900639SDamien Riegel } ts4800_wdt_map[] = {
51bf900639SDamien Riegel 	{ 2,  TS4800_WDT_FEED_2S },
52bf900639SDamien Riegel 	{ 10, TS4800_WDT_FEED_10S },
53bf900639SDamien Riegel };
54bf900639SDamien Riegel 
55bf900639SDamien Riegel #define MAX_TIMEOUT_INDEX       (ARRAY_SIZE(ts4800_wdt_map) - 1)
56bf900639SDamien Riegel 
ts4800_write_feed(struct ts4800_wdt * wdt,u32 val)57bf900639SDamien Riegel static void ts4800_write_feed(struct ts4800_wdt *wdt, u32 val)
58bf900639SDamien Riegel {
59bf900639SDamien Riegel 	regmap_write(wdt->regmap, wdt->feed_offset, val);
60bf900639SDamien Riegel }
61bf900639SDamien Riegel 
ts4800_wdt_start(struct watchdog_device * wdd)62bf900639SDamien Riegel static int ts4800_wdt_start(struct watchdog_device *wdd)
63bf900639SDamien Riegel {
64bf900639SDamien Riegel 	struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd);
65bf900639SDamien Riegel 
66bf900639SDamien Riegel 	ts4800_write_feed(wdt, wdt->feed_val);
67bf900639SDamien Riegel 	return 0;
68bf900639SDamien Riegel }
69bf900639SDamien Riegel 
ts4800_wdt_stop(struct watchdog_device * wdd)70bf900639SDamien Riegel static int ts4800_wdt_stop(struct watchdog_device *wdd)
71bf900639SDamien Riegel {
72bf900639SDamien Riegel 	struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd);
73bf900639SDamien Riegel 
74bf900639SDamien Riegel 	ts4800_write_feed(wdt, TS4800_WDT_DISABLE);
75bf900639SDamien Riegel 	return 0;
76bf900639SDamien Riegel }
77bf900639SDamien Riegel 
ts4800_wdt_set_timeout(struct watchdog_device * wdd,unsigned int timeout)78bf900639SDamien Riegel static int ts4800_wdt_set_timeout(struct watchdog_device *wdd,
79bf900639SDamien Riegel 				  unsigned int timeout)
80bf900639SDamien Riegel {
81bf900639SDamien Riegel 	struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd);
82bf900639SDamien Riegel 	int i;
83bf900639SDamien Riegel 
84bf900639SDamien Riegel 	for (i = 0; i < MAX_TIMEOUT_INDEX; i++) {
85bf900639SDamien Riegel 		if (ts4800_wdt_map[i].timeout >= timeout)
86bf900639SDamien Riegel 			break;
87bf900639SDamien Riegel 	}
88bf900639SDamien Riegel 
89bf900639SDamien Riegel 	wdd->timeout = ts4800_wdt_map[i].timeout;
90bf900639SDamien Riegel 	wdt->feed_val = ts4800_wdt_map[i].regval;
91bf900639SDamien Riegel 
92bf900639SDamien Riegel 	return 0;
93bf900639SDamien Riegel }
94bf900639SDamien Riegel 
95bf900639SDamien Riegel static const struct watchdog_ops ts4800_wdt_ops = {
96bf900639SDamien Riegel 	.owner = THIS_MODULE,
97bf900639SDamien Riegel 	.start = ts4800_wdt_start,
98bf900639SDamien Riegel 	.stop = ts4800_wdt_stop,
99bf900639SDamien Riegel 	.set_timeout = ts4800_wdt_set_timeout,
100bf900639SDamien Riegel };
101bf900639SDamien Riegel 
102bf900639SDamien Riegel static const struct watchdog_info ts4800_wdt_info = {
103bf900639SDamien Riegel 	.options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
104bf900639SDamien Riegel 	.identity = "TS-4800 Watchdog",
105bf900639SDamien Riegel };
106bf900639SDamien Riegel 
ts4800_wdt_probe(struct platform_device * pdev)107bf900639SDamien Riegel static int ts4800_wdt_probe(struct platform_device *pdev)
108bf900639SDamien Riegel {
10944affc08SGuenter Roeck 	struct device *dev = &pdev->dev;
11044affc08SGuenter Roeck 	struct device_node *np = dev->of_node;
111bf900639SDamien Riegel 	struct device_node *syscon_np;
112bf900639SDamien Riegel 	struct watchdog_device *wdd;
113bf900639SDamien Riegel 	struct ts4800_wdt *wdt;
114bf900639SDamien Riegel 	u32 reg;
115bf900639SDamien Riegel 	int ret;
116bf900639SDamien Riegel 
117bf900639SDamien Riegel 	syscon_np = of_parse_phandle(np, "syscon", 0);
118bf900639SDamien Riegel 	if (!syscon_np) {
11944affc08SGuenter Roeck 		dev_err(dev, "no syscon property\n");
120bf900639SDamien Riegel 		return -ENODEV;
121bf900639SDamien Riegel 	}
122bf900639SDamien Riegel 
123bf900639SDamien Riegel 	ret = of_property_read_u32_index(np, "syscon", 1, &reg);
124bf900639SDamien Riegel 	if (ret < 0) {
12544affc08SGuenter Roeck 		dev_err(dev, "no offset in syscon\n");
1265d24df3dSMiaoqian Lin 		of_node_put(syscon_np);
127bf900639SDamien Riegel 		return ret;
128bf900639SDamien Riegel 	}
129bf900639SDamien Riegel 
130bf900639SDamien Riegel 	/* allocate memory for watchdog struct */
13144affc08SGuenter Roeck 	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
1325d24df3dSMiaoqian Lin 	if (!wdt) {
1335d24df3dSMiaoqian Lin 		of_node_put(syscon_np);
134bf900639SDamien Riegel 		return -ENOMEM;
1355d24df3dSMiaoqian Lin 	}
136bf900639SDamien Riegel 
137bf900639SDamien Riegel 	/* set regmap and offset to know where to write */
138bf900639SDamien Riegel 	wdt->feed_offset = reg;
139bf900639SDamien Riegel 	wdt->regmap = syscon_node_to_regmap(syscon_np);
140cd6ba41cSAlexey Khoroshilov 	of_node_put(syscon_np);
141bf900639SDamien Riegel 	if (IS_ERR(wdt->regmap)) {
14244affc08SGuenter Roeck 		dev_err(dev, "cannot get parent's regmap\n");
143bf900639SDamien Riegel 		return PTR_ERR(wdt->regmap);
144bf900639SDamien Riegel 	}
145bf900639SDamien Riegel 
146bf900639SDamien Riegel 	/* Initialize struct watchdog_device */
147bf900639SDamien Riegel 	wdd = &wdt->wdd;
14844affc08SGuenter Roeck 	wdd->parent = dev;
149bf900639SDamien Riegel 	wdd->info = &ts4800_wdt_info;
150bf900639SDamien Riegel 	wdd->ops = &ts4800_wdt_ops;
151bf900639SDamien Riegel 	wdd->min_timeout = ts4800_wdt_map[0].timeout;
152bf900639SDamien Riegel 	wdd->max_timeout = ts4800_wdt_map[MAX_TIMEOUT_INDEX].timeout;
153bf900639SDamien Riegel 
154bf900639SDamien Riegel 	watchdog_set_drvdata(wdd, wdt);
155bf900639SDamien Riegel 	watchdog_set_nowayout(wdd, nowayout);
15644affc08SGuenter Roeck 	watchdog_init_timeout(wdd, 0, dev);
157bf900639SDamien Riegel 
158bf900639SDamien Riegel 	/*
159bf900639SDamien Riegel 	 * As this watchdog supports only a few values, ts4800_wdt_set_timeout
160bf900639SDamien Riegel 	 * must be called to initialize timeout and feed_val with valid values.
161bf900639SDamien Riegel 	 * Default to maximum timeout if none, or an invalid one, is provided in
162bf900639SDamien Riegel 	 * device tree.
163bf900639SDamien Riegel 	 */
164bf900639SDamien Riegel 	if (!wdd->timeout)
165bf900639SDamien Riegel 		wdd->timeout = wdd->max_timeout;
166bf900639SDamien Riegel 	ts4800_wdt_set_timeout(wdd, wdd->timeout);
167bf900639SDamien Riegel 
168bf900639SDamien Riegel 	/*
169bf900639SDamien Riegel 	 * The feed register is write-only, so it is not possible to determine
170bf900639SDamien Riegel 	 * watchdog's state. Disable it to be in a known state.
171bf900639SDamien Riegel 	 */
172bf900639SDamien Riegel 	ts4800_wdt_stop(wdd);
173bf900639SDamien Riegel 
17444affc08SGuenter Roeck 	ret = devm_watchdog_register_device(dev, wdd);
175c8c844f2SWolfram Sang 	if (ret)
176bf900639SDamien Riegel 		return ret;
177bf900639SDamien Riegel 
178bf900639SDamien Riegel 	platform_set_drvdata(pdev, wdt);
179bf900639SDamien Riegel 
18044affc08SGuenter Roeck 	dev_info(dev, "initialized (timeout = %d sec, nowayout = %d)\n",
181bf900639SDamien Riegel 		 wdd->timeout, nowayout);
182bf900639SDamien Riegel 
183bf900639SDamien Riegel 	return 0;
184bf900639SDamien Riegel }
185bf900639SDamien Riegel 
186bf900639SDamien Riegel static const struct of_device_id ts4800_wdt_of_match[] = {
187bf900639SDamien Riegel 	{ .compatible = "technologic,ts4800-wdt", },
188bf900639SDamien Riegel 	{ },
189bf900639SDamien Riegel };
190bf900639SDamien Riegel MODULE_DEVICE_TABLE(of, ts4800_wdt_of_match);
191bf900639SDamien Riegel 
192bf900639SDamien Riegel static struct platform_driver ts4800_wdt_driver = {
193bf900639SDamien Riegel 	.probe		= ts4800_wdt_probe,
194bf900639SDamien Riegel 	.driver		= {
195bf900639SDamien Riegel 		.name	= "ts4800_wdt",
196bf900639SDamien Riegel 		.of_match_table = ts4800_wdt_of_match,
197bf900639SDamien Riegel 	},
198bf900639SDamien Riegel };
199bf900639SDamien Riegel 
200bf900639SDamien Riegel module_platform_driver(ts4800_wdt_driver);
201bf900639SDamien Riegel 
202bf900639SDamien Riegel MODULE_AUTHOR("Damien Riegel <damien.riegel@savoirfairelinux.com>");
203*acf9e67aSJeff Johnson MODULE_DESCRIPTION("Watchdog driver for TS-4800 based boards");
204bf900639SDamien Riegel MODULE_LICENSE("GPL v2");
205bf900639SDamien Riegel MODULE_ALIAS("platform:ts4800_wdt");
206