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 bool locked; 45 }; 46 47 static int heartbeat; 48 module_param(heartbeat, uint, 0); 49 MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. (default=" 50 __MODULE_STRING(WDT_HEARTBEAT) ")"); 51 52 static bool nowayout = WATCHDOG_NOWAYOUT; 53 module_param(nowayout, bool, 0); 54 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 55 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 56 57 static int intel_oc_wdt_start(struct watchdog_device *wdd) 58 { 59 struct intel_oc_wdt *oc_wdt = watchdog_get_drvdata(wdd); 60 61 if (oc_wdt->locked) 62 return 0; 63 64 outl(inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)) | INTEL_OC_WDT_EN, 65 INTEL_OC_WDT_CTRL_REG(oc_wdt)); 66 67 return 0; 68 } 69 70 static int intel_oc_wdt_stop(struct watchdog_device *wdd) 71 { 72 struct intel_oc_wdt *oc_wdt = watchdog_get_drvdata(wdd); 73 74 outl(inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)) & ~INTEL_OC_WDT_EN, 75 INTEL_OC_WDT_CTRL_REG(oc_wdt)); 76 77 return 0; 78 } 79 80 static int intel_oc_wdt_ping(struct watchdog_device *wdd) 81 { 82 struct intel_oc_wdt *oc_wdt = watchdog_get_drvdata(wdd); 83 84 outl(inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)) | INTEL_OC_WDT_RLD, 85 INTEL_OC_WDT_CTRL_REG(oc_wdt)); 86 87 return 0; 88 } 89 90 static int intel_oc_wdt_set_timeout(struct watchdog_device *wdd, 91 unsigned int t) 92 { 93 struct intel_oc_wdt *oc_wdt = watchdog_get_drvdata(wdd); 94 95 outl((inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)) & ~INTEL_OC_WDT_TOV) | (t - 1), 96 INTEL_OC_WDT_CTRL_REG(oc_wdt)); 97 98 wdd->timeout = t; 99 100 return 0; 101 } 102 103 static const struct watchdog_info intel_oc_wdt_info = { 104 .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, 105 .identity = DRV_NAME, 106 }; 107 108 static const struct watchdog_ops intel_oc_wdt_ops = { 109 .owner = THIS_MODULE, 110 .start = intel_oc_wdt_start, 111 .stop = intel_oc_wdt_stop, 112 .ping = intel_oc_wdt_ping, 113 .set_timeout = intel_oc_wdt_set_timeout, 114 }; 115 116 static int intel_oc_wdt_setup(struct intel_oc_wdt *oc_wdt) 117 { 118 struct watchdog_info *info; 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 info = (struct watchdog_info *)&intel_oc_wdt_info; 138 /* 139 * Set nowayout unconditionally as we cannot stop 140 * the watchdog. 141 */ 142 nowayout = true; 143 /* 144 * If we are locked read the current timeout value 145 * and inform the core we can't change it. 146 */ 147 oc_wdt->wdd.timeout = (val & INTEL_OC_WDT_TOV) + 1; 148 info->options &= ~WDIOF_SETTIMEOUT; 149 150 dev_info(oc_wdt->wdd.parent, 151 "Register access locked, heartbeat fixed at: %u s\n", 152 oc_wdt->wdd.timeout); 153 } 154 } else if (oc_wdt->locked) { 155 /* 156 * In case the watchdog is disabled and locked there 157 * is nothing we can do with it so just fail probing. 158 */ 159 return -EACCES; 160 } 161 162 val &= ~INTEL_OC_WDT_TOV; 163 outl(val | (oc_wdt->wdd.timeout - 1), INTEL_OC_WDT_CTRL_REG(oc_wdt)); 164 165 return 0; 166 } 167 168 static int intel_oc_wdt_probe(struct platform_device *pdev) 169 { 170 struct device *dev = &pdev->dev; 171 struct intel_oc_wdt *oc_wdt; 172 struct watchdog_device *wdd; 173 int ret; 174 175 oc_wdt = devm_kzalloc(&pdev->dev, sizeof(*oc_wdt), GFP_KERNEL); 176 if (!oc_wdt) 177 return -ENOMEM; 178 179 oc_wdt->ctrl_res = platform_get_resource(pdev, IORESOURCE_IO, 0); 180 if (!oc_wdt->ctrl_res) { 181 dev_err(&pdev->dev, "missing I/O resource\n"); 182 return -ENODEV; 183 } 184 185 if (!devm_request_region(&pdev->dev, oc_wdt->ctrl_res->start, 186 resource_size(oc_wdt->ctrl_res), pdev->name)) { 187 dev_err(dev, "resource %pR already in use, device disabled\n", 188 oc_wdt->ctrl_res); 189 return -EBUSY; 190 } 191 192 wdd = &oc_wdt->wdd; 193 wdd->min_timeout = INTEL_OC_WDT_MIN_TOV; 194 wdd->max_timeout = INTEL_OC_WDT_MAX_TOV; 195 wdd->timeout = INTEL_OC_WDT_DEF_TOV; 196 wdd->info = &intel_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