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