projects/linux·blob master · refs

226 lines · 5.5 KiB

1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * sl28cpld watchdog driver
4 *
5 * Copyright 2020 Kontron Europe GmbH
6 */
7
8#include <linux/kernel.h>
9#include <linux/module.h>
10#include <linux/platform_device.h>
11#include <linux/property.h>
12#include <linux/regmap.h>
13#include <linux/watchdog.h>
14
15/*
16 * Watchdog timer block registers.
17 */
18#define WDT_CTRL 0x00
19#define WDT_CTRL_EN BIT(0)
20#define WDT_CTRL_LOCK BIT(2)
21#define WDT_CTRL_ASSERT_SYS_RESET BIT(6)
22#define WDT_CTRL_ASSERT_WDT_TIMEOUT BIT(7)
23#define WDT_TIMEOUT 0x01
24#define WDT_KICK 0x02
25#define WDT_KICK_VALUE 0x6b
26#define WDT_COUNT 0x03
27
28#define WDT_DEFAULT_TIMEOUT 10
29
30static bool nowayout = WATCHDOG_NOWAYOUT;
31module_param(nowayout, bool, 0);
32MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
33 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
34
35static int timeout;
36module_param(timeout, int, 0);
37MODULE_PARM_DESC(timeout, "Initial watchdog timeout in seconds");
38
39struct sl28cpld_wdt {
40 struct watchdog_device wdd;
41 struct regmap *regmap;
42 u32 offset;
43 bool assert_wdt_timeout;
44};
45
46static int sl28cpld_wdt_ping(struct watchdog_device *wdd)
47{
48 struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
49
50 return regmap_write(wdt->regmap, wdt->offset + WDT_KICK,
51 WDT_KICK_VALUE);
52}
53
54static int sl28cpld_wdt_start(struct watchdog_device *wdd)
55{
56 struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
57 unsigned int val;
58
59 val = WDT_CTRL_EN | WDT_CTRL_ASSERT_SYS_RESET;
60 if (wdt->assert_wdt_timeout)
61 val |= WDT_CTRL_ASSERT_WDT_TIMEOUT;
62 if (nowayout)
63 val |= WDT_CTRL_LOCK;
64
65 return regmap_update_bits(wdt->regmap, wdt->offset + WDT_CTRL,
66 val, val);
67}
68
69static int sl28cpld_wdt_stop(struct watchdog_device *wdd)
70{
71 struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
72
73 return regmap_update_bits(wdt->regmap, wdt->offset + WDT_CTRL,
74 WDT_CTRL_EN, 0);
75}
76
77static unsigned int sl28cpld_wdt_get_timeleft(struct watchdog_device *wdd)
78{
79 struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
80 unsigned int val;
81 int ret;
82
83 ret = regmap_read(wdt->regmap, wdt->offset + WDT_COUNT, &val);
84 if (ret)
85 return 0;
86
87 return val;
88}
89
90static int sl28cpld_wdt_set_timeout(struct watchdog_device *wdd,
91 unsigned int timeout)
92{
93 struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
94 int ret;
95
96 ret = regmap_write(wdt->regmap, wdt->offset + WDT_TIMEOUT, timeout);
97 if (ret)
98 return ret;
99
100 wdd->timeout = timeout;
101
102 return 0;
103}
104
105static const struct watchdog_info sl28cpld_wdt_info = {
106 .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
107 .identity = "sl28cpld watchdog",
108};
109
110static const struct watchdog_ops sl28cpld_wdt_ops = {
111 .owner = THIS_MODULE,
112 .start = sl28cpld_wdt_start,
113 .stop = sl28cpld_wdt_stop,
114 .ping = sl28cpld_wdt_ping,
115 .set_timeout = sl28cpld_wdt_set_timeout,
116 .get_timeleft = sl28cpld_wdt_get_timeleft,
117};
118
119static int sl28cpld_wdt_probe(struct platform_device *pdev)
120{
121 struct watchdog_device *wdd;
122 struct sl28cpld_wdt *wdt;
123 unsigned int status;
124 unsigned int val;
125 int ret;
126
127 if (!pdev->dev.parent)
128 return -ENODEV;
129
130 wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
131 if (!wdt)
132 return -ENOMEM;
133
134 wdt->regmap = dev_get_regmap(pdev->dev.parent, NULL);
135 if (!wdt->regmap)
136 return -ENODEV;
137
138 ret = device_property_read_u32(&pdev->dev, "reg", &wdt->offset);
139 if (ret)
140 return -EINVAL;
141
142 wdt->assert_wdt_timeout = device_property_read_bool(&pdev->dev,
143 "kontron,assert-wdt-timeout-pin");
144
145 /* initialize struct watchdog_device */
146 wdd = &wdt->wdd;
147 wdd->parent = &pdev->dev;
148 wdd->info = &sl28cpld_wdt_info;
149 wdd->ops = &sl28cpld_wdt_ops;
150 wdd->min_timeout = 1;
151 wdd->max_timeout = 255;
152
153 watchdog_set_drvdata(wdd, wdt);
154 watchdog_stop_on_reboot(wdd);
155
156 /*
157 * Read the status early, in case of an error, we haven't modified the
158 * hardware.
159 */
160 ret = regmap_read(wdt->regmap, wdt->offset + WDT_CTRL, &status);
161 if (ret)
162 return ret;
163
164 /*
165 * Initial timeout value, may be overwritten by device tree or module
166 * parameter in watchdog_init_timeout().
167 *
168 * Reading a zero here means that either the hardware has a default
169 * value of zero (which is very unlikely and definitely a hardware
170 * bug) or the bootloader set it to zero. In any case, we handle
171 * this case gracefully and set out own timeout.
172 */
173 ret = regmap_read(wdt->regmap, wdt->offset + WDT_TIMEOUT, &val);
174 if (ret)
175 return ret;
176
177 if (val)
178 wdd->timeout = val;
179 else
180 wdd->timeout = WDT_DEFAULT_TIMEOUT;
181
182 watchdog_init_timeout(wdd, timeout, &pdev->dev);
183 sl28cpld_wdt_set_timeout(wdd, wdd->timeout);
184
185 /* if the watchdog is locked, we set nowayout */
186 if (status & WDT_CTRL_LOCK)
187 nowayout = true;
188 watchdog_set_nowayout(wdd, nowayout);
189
190 /*
191 * If watchdog is already running, keep it enabled, but make
192 * sure its mode is set correctly.
193 */
194 if (status & WDT_CTRL_EN) {
195 sl28cpld_wdt_start(wdd);
196 set_bit(WDOG_HW_RUNNING, &wdd->status);
197 }
198
199 ret = devm_watchdog_register_device(&pdev->dev, wdd);
200 if (ret < 0)
201 return ret;
202
203 dev_info(&pdev->dev, "initial timeout %d sec%s\n",
204 wdd->timeout, nowayout ? ", nowayout" : "");
205
206 return 0;
207}
208
209static const struct of_device_id sl28cpld_wdt_of_match[] = {
210 { .compatible = "kontron,sl28cpld-wdt" },
211 {}
212};
213MODULE_DEVICE_TABLE(of, sl28cpld_wdt_of_match);
214
215static struct platform_driver sl28cpld_wdt_driver = {
216 .probe = sl28cpld_wdt_probe,
217 .driver = {
218 .name = "sl28cpld-wdt",
219 .of_match_table = sl28cpld_wdt_of_match,
220 },
221};
222module_platform_driver(sl28cpld_wdt_driver);
223
224MODULE_DESCRIPTION("sl28cpld Watchdog Driver");
225MODULE_AUTHOR("Michael Walle <[email protected]>");
226MODULE_LICENSE("GPL");