xref: /linux/drivers/watchdog/intel_oc_wdt.c (revision 169c9d06a2656772285d3dd2c387e338b2e2b915)
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 
41 struct intel_oc_wdt {
42 	struct watchdog_device wdd;
43 	struct resource *ctrl_res;
44 	struct watchdog_info info;
45 	bool locked;
46 };
47 
48 static int heartbeat;
49 module_param(heartbeat, uint, 0);
50 MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. (default="
51 		 __MODULE_STRING(WDT_HEARTBEAT) ")");
52 
53 static bool nowayout = WATCHDOG_NOWAYOUT;
54 module_param(nowayout, bool, 0);
55 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
56 		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
57 
intel_oc_wdt_start(struct watchdog_device * wdd)58 static 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 
intel_oc_wdt_stop(struct watchdog_device * wdd)71 static 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 
intel_oc_wdt_ping(struct watchdog_device * wdd)81 static 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 
intel_oc_wdt_set_timeout(struct watchdog_device * wdd,unsigned int t)91 static 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((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 
104 static const struct watchdog_info intel_oc_wdt_info = {
105 	.options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
106 	.identity = DRV_NAME,
107 };
108 
109 static 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 
intel_oc_wdt_setup(struct intel_oc_wdt * oc_wdt)117 static 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, &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(val | (oc_wdt->wdd.timeout - 1), INTEL_OC_WDT_CTRL_REG(oc_wdt));
163 
164 	return 0;
165 }
166 
intel_oc_wdt_probe(struct platform_device * pdev)167 static 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(&pdev->dev, 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, heartbeat, dev);
201 
202 	ret = intel_oc_wdt_setup(oc_wdt);
203 	if (ret)
204 		return ret;
205 
206 	watchdog_set_drvdata(wdd, 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 
214 static const struct acpi_device_id intel_oc_wdt_match[] = {
215 	{ "INT3F0D" },
216 	{ "INTC1099" },
217 	{ },
218 };
219 MODULE_DEVICE_TABLE(acpi, intel_oc_wdt_match);
220 
221 static 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 
229 module_platform_driver(intel_oc_wdt_platform_driver);
230 
231 MODULE_AUTHOR("Diogo Ivo <diogo.ivo@siemens.com>");
232 MODULE_LICENSE("GPL");
233 MODULE_DESCRIPTION("Intel OC Watchdog driver");
234