1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Intel OC Watchdog driver
4 *
5 * Copyright (C) 2025, Siemens
6 * Author: Diogo Ivo <diogo.ivo@siemens.com>
7 */
8
9#define DRV_NAME "intel_oc_wdt"
10
11#include <linux/acpi.h>
12#include <linux/bits.h>
13#include <linux/io.h>
14#include <linux/module.h>
15#include <linux/moduleparam.h>
16#include <linux/platform_device.h>
17#include <linux/watchdog.h>
18
19#define INTEL_OC_WDT_TOV GENMASK(9, 0)
20#define INTEL_OC_WDT_MIN_TOV 1
21#define INTEL_OC_WDT_MAX_TOV 1024
22#define INTEL_OC_WDT_DEF_TOV 60
23
24/*
25 * One-time writable lock bit. If set forbids
26 * modification of itself, _TOV and _EN until
27 * next reboot.
28 */
29#define INTEL_OC_WDT_CTL_LCK BIT(12)
30
31#define INTEL_OC_WDT_EN BIT(14)
32#define INTEL_OC_WDT_NO_ICCSURV_STS BIT(24)
33#define INTEL_OC_WDT_ICCSURV_STS BIT(25)
34#define INTEL_OC_WDT_RLD BIT(31)
35
36#define INTEL_OC_WDT_STS_BITS (INTEL_OC_WDT_NO_ICCSURV_STS | \
37 INTEL_OC_WDT_ICCSURV_STS)
38
39#define INTEL_OC_WDT_CTRL_REG(wdt) ((wdt)->ctrl_res->start)
40
41struct intel_oc_wdt {
42 struct watchdog_device wdd;
43 struct resource *ctrl_res;
44 struct watchdog_info info;
45 bool locked;
46};
47
48static int heartbeat;
49module_param(heartbeat, uint, 0);
50MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. (default="
51 __MODULE_STRING(WDT_HEARTBEAT) ")");
52
53static bool nowayout = WATCHDOG_NOWAYOUT;
54module_param(nowayout, bool, 0);
55MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
56 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
57
58static int intel_oc_wdt_start(struct watchdog_device *wdd)
59{
60 struct intel_oc_wdt *oc_wdt = watchdog_get_drvdata(wdd);
61
62 if (oc_wdt->locked)
63 return 0;
64
65 outl(inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)) | INTEL_OC_WDT_EN,
66 INTEL_OC_WDT_CTRL_REG(oc_wdt));
67
68 return 0;
69}
70
71static int intel_oc_wdt_stop(struct watchdog_device *wdd)
72{
73 struct intel_oc_wdt *oc_wdt = watchdog_get_drvdata(wdd);
74
75 outl(inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)) & ~INTEL_OC_WDT_EN,
76 INTEL_OC_WDT_CTRL_REG(oc_wdt));
77
78 return 0;
79}
80
81static int intel_oc_wdt_ping(struct watchdog_device *wdd)
82{
83 struct intel_oc_wdt *oc_wdt = watchdog_get_drvdata(wdd);
84
85 outl(inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)) | INTEL_OC_WDT_RLD,
86 INTEL_OC_WDT_CTRL_REG(oc_wdt));
87
88 return 0;
89}
90
91static int intel_oc_wdt_set_timeout(struct watchdog_device *wdd,
92 unsigned int t)
93{
94 struct intel_oc_wdt *oc_wdt = watchdog_get_drvdata(wdd);
95
96 outl(value: (inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)) & ~INTEL_OC_WDT_TOV) | (t - 1),
97 INTEL_OC_WDT_CTRL_REG(oc_wdt));
98
99 wdd->timeout = t;
100
101 return 0;
102}
103
104static const struct watchdog_info intel_oc_wdt_info = {
105 .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
106 .identity = DRV_NAME,
107};
108
109static const struct watchdog_ops intel_oc_wdt_ops = {
110 .owner = THIS_MODULE,
111 .start = intel_oc_wdt_start,
112 .stop = intel_oc_wdt_stop,
113 .ping = intel_oc_wdt_ping,
114 .set_timeout = intel_oc_wdt_set_timeout,
115};
116
117static int intel_oc_wdt_setup(struct intel_oc_wdt *oc_wdt)
118{
119 unsigned long val;
120
121 val = inl(INTEL_OC_WDT_CTRL_REG(oc_wdt));
122
123 if (val & INTEL_OC_WDT_STS_BITS)
124 oc_wdt->wdd.bootstatus |= WDIOF_CARDRESET;
125
126 oc_wdt->locked = !!(val & INTEL_OC_WDT_CTL_LCK);
127
128 if (val & INTEL_OC_WDT_EN) {
129 /*
130 * No need to issue a ping here to "commit" the new timeout
131 * value to hardware as the watchdog core schedules one
132 * immediately when registering the watchdog.
133 */
134 set_bit(WDOG_HW_RUNNING, addr: &oc_wdt->wdd.status);
135
136 if (oc_wdt->locked) {
137 /*
138 * Set nowayout unconditionally as we cannot stop
139 * the watchdog.
140 */
141 nowayout = true;
142 /*
143 * If we are locked read the current timeout value
144 * and inform the core we can't change it.
145 */
146 oc_wdt->wdd.timeout = (val & INTEL_OC_WDT_TOV) + 1;
147 oc_wdt->info.options &= ~WDIOF_SETTIMEOUT;
148
149 dev_info(oc_wdt->wdd.parent,
150 "Register access locked, heartbeat fixed at: %u s\n",
151 oc_wdt->wdd.timeout);
152 }
153 } else if (oc_wdt->locked) {
154 /*
155 * In case the watchdog is disabled and locked there
156 * is nothing we can do with it so just fail probing.
157 */
158 return -EACCES;
159 }
160
161 val &= ~INTEL_OC_WDT_TOV;
162 outl(value: val | (oc_wdt->wdd.timeout - 1), INTEL_OC_WDT_CTRL_REG(oc_wdt));
163
164 return 0;
165}
166
167static int intel_oc_wdt_probe(struct platform_device *pdev)
168{
169 struct device *dev = &pdev->dev;
170 struct intel_oc_wdt *oc_wdt;
171 struct watchdog_device *wdd;
172 int ret;
173
174 oc_wdt = devm_kzalloc(dev: &pdev->dev, size: sizeof(*oc_wdt), GFP_KERNEL);
175 if (!oc_wdt)
176 return -ENOMEM;
177
178 oc_wdt->ctrl_res = platform_get_resource(pdev, IORESOURCE_IO, 0);
179 if (!oc_wdt->ctrl_res) {
180 dev_err(&pdev->dev, "missing I/O resource\n");
181 return -ENODEV;
182 }
183
184 if (!devm_request_region(&pdev->dev, oc_wdt->ctrl_res->start,
185 resource_size(oc_wdt->ctrl_res), pdev->name)) {
186 dev_err(dev, "resource %pR already in use, device disabled\n",
187 oc_wdt->ctrl_res);
188 return -EBUSY;
189 }
190
191 wdd = &oc_wdt->wdd;
192 wdd->min_timeout = INTEL_OC_WDT_MIN_TOV;
193 wdd->max_timeout = INTEL_OC_WDT_MAX_TOV;
194 wdd->timeout = INTEL_OC_WDT_DEF_TOV;
195 oc_wdt->info = intel_oc_wdt_info;
196 wdd->info = &oc_wdt->info;
197 wdd->ops = &intel_oc_wdt_ops;
198 wdd->parent = dev;
199
200 watchdog_init_timeout(wdd, timeout_parm: heartbeat, dev);
201
202 ret = intel_oc_wdt_setup(oc_wdt);
203 if (ret)
204 return ret;
205
206 watchdog_set_drvdata(wdd, data: oc_wdt);
207 watchdog_set_nowayout(wdd, nowayout);
208 watchdog_stop_on_reboot(wdd);
209 watchdog_stop_on_unregister(wdd);
210
211 return devm_watchdog_register_device(dev, wdd);
212}
213
214static const struct acpi_device_id intel_oc_wdt_match[] = {
215 { "INT3F0D" },
216 { "INTC1099" },
217 { },
218};
219MODULE_DEVICE_TABLE(acpi, intel_oc_wdt_match);
220
221static struct platform_driver intel_oc_wdt_platform_driver = {
222 .driver = {
223 .name = DRV_NAME,
224 .acpi_match_table = intel_oc_wdt_match,
225 },
226 .probe = intel_oc_wdt_probe,
227};
228
229module_platform_driver(intel_oc_wdt_platform_driver);
230
231MODULE_AUTHOR("Diogo Ivo <diogo.ivo@siemens.com>");
232MODULE_LICENSE("GPL");
233MODULE_DESCRIPTION("Intel OC Watchdog driver");
234

source code of linux/drivers/watchdog/intel_oc_wdt.c