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 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 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 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 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 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 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