| 1 | |
| 2 | |
| 3 | * ACPI Hardware Watchdog (WDAT) driver.
|
| 4 | *
|
| 5 | * Copyright (C) 2016, Intel Corporation
|
| 6 | * Author: Mika Westerberg <[email protected]>
|
| 7 |
|
| 8 |
|
| 9 | #include <linux/acpi.h>
|
| 10 | #include <linux/io.h>
|
| 11 | #include <linux/ioport.h>
|
| 12 | #include <linux/mm.h>
|
| 13 | #include <linux/module.h>
|
| 14 | #include <linux/platform_device.h>
|
| 15 | #include <linux/pm.h>
|
| 16 | #include <linux/watchdog.h>
|
| 17 |
|
| 18 | #define MAX_WDAT_ACTIONS ACPI_WDAT_ACTION_RESERVED
|
| 19 |
|
| 20 | |
| 21 | * struct wdat_instruction - Single ACPI WDAT instruction
|
| 22 | * @entry: Copy of the ACPI table instruction
|
| 23 | * @reg: Register the instruction is accessing
|
| 24 | * @node: Next instruction in action sequence
|
| 25 |
|
| 26 | struct wdat_instruction {
|
| 27 | struct acpi_wdat_entry entry;
|
| 28 | void __iomem *reg;
|
| 29 | struct list_head node;
|
| 30 | };
|
| 31 |
|
| 32 | |
| 33 | * struct wdat_wdt - ACPI WDAT watchdog device
|
| 34 | * @pdev: Parent platform device
|
| 35 | * @wdd: Watchdog core device
|
| 36 | * @period: How long is one watchdog period in ms
|
| 37 | * @stopped_in_sleep: Is this watchdog stopped by the firmware in S1-S5
|
| 38 | * @stopped: Was the watchdog stopped by the driver in suspend
|
| 39 | * @instructions: An array of instruction lists indexed by an action number from
|
| 40 | * the WDAT table. There can be %NULL entries for not implemented
|
| 41 | * actions.
|
| 42 |
|
| 43 | struct wdat_wdt {
|
| 44 | struct platform_device *pdev;
|
| 45 | struct watchdog_device wdd;
|
| 46 | unsigned int period;
|
| 47 | bool stopped_in_sleep;
|
| 48 | bool stopped;
|
| 49 | struct list_head *instructions[MAX_WDAT_ACTIONS];
|
| 50 | };
|
| 51 |
|
| 52 | #define to_wdat_wdt(wdd) container_of(wdd, struct wdat_wdt, wdd)
|
| 53 |
|
| 54 | static bool nowayout = WATCHDOG_NOWAYOUT;
|
| 55 | module_param(nowayout, bool, 0);
|
| 56 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
|
| 57 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
| 58 |
|
| 59 | #define WDAT_DEFAULT_TIMEOUT 30
|
| 60 |
|
| 61 | static int timeout = WDAT_DEFAULT_TIMEOUT;
|
| 62 | module_param(timeout, int, 0);
|
| 63 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default="
|
| 64 | __MODULE_STRING(WDAT_DEFAULT_TIMEOUT) ")");
|
| 65 |
|
| 66 | static int wdat_wdt_read(struct wdat_wdt *wdat,
|
| 67 | const struct wdat_instruction *instr, u32 *value)
|
| 68 | {
|
| 69 | const struct acpi_generic_address *gas = &instr->entry.register_region;
|
| 70 |
|
| 71 | switch (gas->access_width) {
|
| 72 | case 1:
|
| 73 | *value = ioread8(instr->reg);
|
| 74 | break;
|
| 75 | case 2:
|
| 76 | *value = ioread16(instr->reg);
|
| 77 | break;
|
| 78 | case 3:
|
| 79 | *value = ioread32(instr->reg);
|
| 80 | break;
|
| 81 | default:
|
| 82 | return -EINVAL;
|
| 83 | }
|
| 84 |
|
| 85 | dev_dbg(&wdat->pdev->dev, "Read %#x from 0x%08llx\n", *value,
|
| 86 | gas->address);
|
| 87 |
|
| 88 | return 0;
|
| 89 | }
|
| 90 |
|
| 91 | static int wdat_wdt_write(struct wdat_wdt *wdat,
|
| 92 | const struct wdat_instruction *instr, u32 value)
|
| 93 | {
|
| 94 | const struct acpi_generic_address *gas = &instr->entry.register_region;
|
| 95 |
|
| 96 | switch (gas->access_width) {
|
| 97 | case 1:
|
| 98 | iowrite8((u8)value, instr->reg);
|
| 99 | break;
|
| 100 | case 2:
|
| 101 | iowrite16((u16)value, instr->reg);
|
| 102 | break;
|
| 103 | case 3:
|
| 104 | iowrite32(value, instr->reg);
|
| 105 | break;
|
| 106 | default:
|
| 107 | return -EINVAL;
|
| 108 | }
|
| 109 |
|
| 110 | dev_dbg(&wdat->pdev->dev, "Wrote %#x to 0x%08llx\n", value,
|
| 111 | gas->address);
|
| 112 |
|
| 113 | return 0;
|
| 114 | }
|
| 115 |
|
| 116 | static int wdat_wdt_run_action(struct wdat_wdt *wdat, unsigned int action,
|
| 117 | u32 param, u32 *retval)
|
| 118 | {
|
| 119 | struct wdat_instruction *instr;
|
| 120 |
|
| 121 | if (action >= ARRAY_SIZE(wdat->instructions))
|
| 122 | return -EINVAL;
|
| 123 |
|
| 124 | if (!wdat->instructions[action])
|
| 125 | return -EOPNOTSUPP;
|
| 126 |
|
| 127 | dev_dbg(&wdat->pdev->dev, "Running action %#x\n", action);
|
| 128 |
|
| 129 |
|
| 130 | list_for_each_entry(instr, wdat->instructions[action], node) {
|
| 131 | const struct acpi_wdat_entry *entry = &instr->entry;
|
| 132 | const struct acpi_generic_address *gas;
|
| 133 | u32 flags, value, mask, x, y;
|
| 134 | bool preserve;
|
| 135 | int ret;
|
| 136 |
|
| 137 | gas = &entry->register_region;
|
| 138 |
|
| 139 | preserve = entry->instruction & ACPI_WDAT_PRESERVE_REGISTER;
|
| 140 | flags = entry->instruction & ~ACPI_WDAT_PRESERVE_REGISTER;
|
| 141 | value = entry->value;
|
| 142 | mask = entry->mask;
|
| 143 |
|
| 144 | switch (flags) {
|
| 145 | case ACPI_WDAT_READ_VALUE:
|
| 146 | ret = wdat_wdt_read(wdat, instr, &x);
|
| 147 | if (ret)
|
| 148 | return ret;
|
| 149 | x >>= gas->bit_offset;
|
| 150 | x &= mask;
|
| 151 | if (retval)
|
| 152 | *retval = x == value;
|
| 153 | break;
|
| 154 |
|
| 155 | case ACPI_WDAT_READ_COUNTDOWN:
|
| 156 | ret = wdat_wdt_read(wdat, instr, &x);
|
| 157 | if (ret)
|
| 158 | return ret;
|
| 159 | x >>= gas->bit_offset;
|
| 160 | x &= mask;
|
| 161 | if (retval)
|
| 162 | *retval = x;
|
| 163 | break;
|
| 164 |
|
| 165 | case ACPI_WDAT_WRITE_VALUE:
|
| 166 | x = value & mask;
|
| 167 | x <<= gas->bit_offset;
|
| 168 | if (preserve) {
|
| 169 | ret = wdat_wdt_read(wdat, instr, &y);
|
| 170 | if (ret)
|
| 171 | return ret;
|
| 172 | y = y & ~(mask << gas->bit_offset);
|
| 173 | x |= y;
|
| 174 | }
|
| 175 | ret = wdat_wdt_write(wdat, instr, x);
|
| 176 | if (ret)
|
| 177 | return ret;
|
| 178 | break;
|
| 179 |
|
| 180 | case ACPI_WDAT_WRITE_COUNTDOWN:
|
| 181 | x = param;
|
| 182 | x &= mask;
|
| 183 | x <<= gas->bit_offset;
|
| 184 | if (preserve) {
|
| 185 | ret = wdat_wdt_read(wdat, instr, &y);
|
| 186 | if (ret)
|
| 187 | return ret;
|
| 188 | y = y & ~(mask << gas->bit_offset);
|
| 189 | x |= y;
|
| 190 | }
|
| 191 | ret = wdat_wdt_write(wdat, instr, x);
|
| 192 | if (ret)
|
| 193 | return ret;
|
| 194 | break;
|
| 195 |
|
| 196 | default:
|
| 197 | dev_err(&wdat->pdev->dev, "Unknown instruction: %u\n",
|
| 198 | flags);
|
| 199 | return -EINVAL;
|
| 200 | }
|
| 201 | }
|
| 202 |
|
| 203 | return 0;
|
| 204 | }
|
| 205 |
|
| 206 | static int wdat_wdt_enable_reboot(struct wdat_wdt *wdat)
|
| 207 | {
|
| 208 | int ret;
|
| 209 |
|
| 210 | |
| 211 | * WDAT specification says that the watchdog is required to reboot
|
| 212 | * the system when it fires. However, it also states that it is
|
| 213 | * recommended to make it configurable through hardware register. We
|
| 214 | * enable reboot now if it is configurable, just in case.
|
| 215 |
|
| 216 | ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_REBOOT, 0, NULL);
|
| 217 | if (ret && ret != -EOPNOTSUPP) {
|
| 218 | dev_err(&wdat->pdev->dev,
|
| 219 | "Failed to enable reboot when watchdog triggers\n");
|
| 220 | return ret;
|
| 221 | }
|
| 222 |
|
| 223 | return 0;
|
| 224 | }
|
| 225 |
|
| 226 | static void wdat_wdt_boot_status(struct wdat_wdt *wdat)
|
| 227 | {
|
| 228 | u32 boot_status = 0;
|
| 229 | int ret;
|
| 230 |
|
| 231 | ret = wdat_wdt_run_action(wdat, ACPI_WDAT_GET_STATUS, 0, &boot_status);
|
| 232 | if (ret && ret != -EOPNOTSUPP) {
|
| 233 | dev_err(&wdat->pdev->dev, "Failed to read boot status\n");
|
| 234 | return;
|
| 235 | }
|
| 236 |
|
| 237 | if (boot_status)
|
| 238 | wdat->wdd.bootstatus = WDIOF_CARDRESET;
|
| 239 |
|
| 240 |
|
| 241 | ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_STATUS, 0, NULL);
|
| 242 | if (ret && ret != -EOPNOTSUPP)
|
| 243 | dev_err(&wdat->pdev->dev, "Failed to clear boot status\n");
|
| 244 | }
|
| 245 |
|
| 246 | static void wdat_wdt_set_running(struct wdat_wdt *wdat)
|
| 247 | {
|
| 248 | u32 running = 0;
|
| 249 | int ret;
|
| 250 |
|
| 251 | ret = wdat_wdt_run_action(wdat, ACPI_WDAT_GET_RUNNING_STATE, 0,
|
| 252 | &running);
|
| 253 | if (ret && ret != -EOPNOTSUPP)
|
| 254 | dev_err(&wdat->pdev->dev, "Failed to read running state\n");
|
| 255 |
|
| 256 | if (running)
|
| 257 | set_bit(WDOG_HW_RUNNING, &wdat->wdd.status);
|
| 258 | }
|
| 259 |
|
| 260 | static int wdat_wdt_start(struct watchdog_device *wdd)
|
| 261 | {
|
| 262 | return wdat_wdt_run_action(to_wdat_wdt(wdd),
|
| 263 | ACPI_WDAT_SET_RUNNING_STATE, 0, NULL);
|
| 264 | }
|
| 265 |
|
| 266 | static int wdat_wdt_stop(struct watchdog_device *wdd)
|
| 267 | {
|
| 268 | return wdat_wdt_run_action(to_wdat_wdt(wdd),
|
| 269 | ACPI_WDAT_SET_STOPPED_STATE, 0, NULL);
|
| 270 | }
|
| 271 |
|
| 272 | static int wdat_wdt_ping(struct watchdog_device *wdd)
|
| 273 | {
|
| 274 | return wdat_wdt_run_action(to_wdat_wdt(wdd), ACPI_WDAT_RESET, wdd->timeout, NULL);
|
| 275 | }
|
| 276 |
|
| 277 | static int wdat_wdt_set_timeout(struct watchdog_device *wdd,
|
| 278 | unsigned int timeout)
|
| 279 | {
|
| 280 | struct wdat_wdt *wdat = to_wdat_wdt(wdd);
|
| 281 | unsigned int periods;
|
| 282 | int ret;
|
| 283 |
|
| 284 | periods = timeout * 1000 / wdat->period;
|
| 285 | ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_COUNTDOWN, periods, NULL);
|
| 286 | if (!ret)
|
| 287 | wdd->timeout = timeout;
|
| 288 | return ret;
|
| 289 | }
|
| 290 |
|
| 291 | static unsigned int wdat_wdt_get_timeleft(struct watchdog_device *wdd)
|
| 292 | {
|
| 293 | struct wdat_wdt *wdat = to_wdat_wdt(wdd);
|
| 294 | u32 periods = 0;
|
| 295 |
|
| 296 | wdat_wdt_run_action(wdat, ACPI_WDAT_GET_CURRENT_COUNTDOWN, 0, &periods);
|
| 297 | return periods * wdat->period / 1000;
|
| 298 | }
|
| 299 |
|
| 300 | static const struct watchdog_info wdat_wdt_info = {
|
| 301 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
|
| 302 | .firmware_version = 0,
|
| 303 | .identity = "wdat_wdt",
|
| 304 | };
|
| 305 |
|
| 306 | static struct watchdog_ops wdat_wdt_ops = {
|
| 307 | .owner = THIS_MODULE,
|
| 308 | .start = wdat_wdt_start,
|
| 309 | .stop = wdat_wdt_stop,
|
| 310 | .ping = wdat_wdt_ping,
|
| 311 | .set_timeout = wdat_wdt_set_timeout,
|
| 312 | };
|
| 313 |
|
| 314 | static void __iomem *wdat_wdt_map_mem(struct device *dev, struct resource *res)
|
| 315 | {
|
| 316 | resource_size_t size = resource_size(res);
|
| 317 | void *addr;
|
| 318 |
|
| 319 |
|
| 320 | if (region_intersects(res->start, size, IORESOURCE_MEM,
|
| 321 | IORES_DESC_ACPI_NV_STORAGE) == REGION_INTERSECTS) {
|
| 322 | dev_warn(dev, "%pR is inside ACPI NVS, mapping without reservation\n",
|
| 323 | res);
|
| 324 |
|
| 325 | addr = devm_memremap(dev, res->start, size, MEMREMAP_WB);
|
| 326 | if (IS_ERR(addr)) {
|
| 327 | dev_err(dev, "failed to map resource %pR\n", res);
|
| 328 | return IOMEM_ERR_PTR(PTR_ERR(addr));
|
| 329 | }
|
| 330 |
|
| 331 | return (void __iomem __force *)addr;
|
| 332 | }
|
| 333 |
|
| 334 | return devm_ioremap_resource(dev, res);
|
| 335 | }
|
| 336 |
|
| 337 | static int wdat_wdt_probe(struct platform_device *pdev)
|
| 338 | {
|
| 339 | struct device *dev = &pdev->dev;
|
| 340 | const struct acpi_wdat_entry *entries;
|
| 341 | const struct acpi_table_wdat *tbl;
|
| 342 | struct wdat_wdt *wdat;
|
| 343 | struct resource *res;
|
| 344 | void __iomem **regs;
|
| 345 | acpi_status status;
|
| 346 | int i, ret;
|
| 347 |
|
| 348 | status = acpi_get_table(ACPI_SIG_WDAT, 0,
|
| 349 | (struct acpi_table_header **)&tbl);
|
| 350 | if (ACPI_FAILURE(status))
|
| 351 | return -ENODEV;
|
| 352 |
|
| 353 | wdat = devm_kzalloc(dev, sizeof(*wdat), GFP_KERNEL);
|
| 354 | if (!wdat) {
|
| 355 | ret = -ENOMEM;
|
| 356 | goto out_put_table;
|
| 357 | }
|
| 358 |
|
| 359 | regs = devm_kcalloc(dev, pdev->num_resources, sizeof(*regs),
|
| 360 | GFP_KERNEL);
|
| 361 | if (!regs) {
|
| 362 | ret = -ENOMEM;
|
| 363 | goto out_put_table;
|
| 364 | }
|
| 365 |
|
| 366 |
|
| 367 | if (tbl->timer_period < 1) {
|
| 368 | ret = -EINVAL;
|
| 369 | goto out_put_table;
|
| 370 | }
|
| 371 | if (tbl->min_count > tbl->max_count) {
|
| 372 | ret = -EINVAL;
|
| 373 | goto out_put_table;
|
| 374 | }
|
| 375 |
|
| 376 | wdat->period = tbl->timer_period;
|
| 377 | wdat->wdd.min_timeout = DIV_ROUND_UP(wdat->period * tbl->min_count, 1000);
|
| 378 | wdat->wdd.max_timeout = wdat->period * tbl->max_count / 1000;
|
| 379 | wdat->stopped_in_sleep = tbl->flags & ACPI_WDAT_STOPPED;
|
| 380 | wdat->wdd.info = &wdat_wdt_info;
|
| 381 | wdat->wdd.ops = &wdat_wdt_ops;
|
| 382 | wdat->pdev = pdev;
|
| 383 |
|
| 384 |
|
| 385 | for (i = 0; i < pdev->num_resources; i++) {
|
| 386 | void __iomem *reg;
|
| 387 |
|
| 388 | res = &pdev->resource[i];
|
| 389 | if (resource_type(res) == IORESOURCE_MEM) {
|
| 390 | reg = wdat_wdt_map_mem(dev, res);
|
| 391 | if (IS_ERR(reg)) {
|
| 392 | ret = PTR_ERR(reg);
|
| 393 | goto out_put_table;
|
| 394 | }
|
| 395 | } else if (resource_type(res) == IORESOURCE_IO) {
|
| 396 | reg = devm_ioport_map(dev, res->start, 1);
|
| 397 | if (!reg) {
|
| 398 | ret = -ENOMEM;
|
| 399 | goto out_put_table;
|
| 400 | }
|
| 401 | } else {
|
| 402 | dev_err(dev, "Unsupported resource\n");
|
| 403 | ret = -EINVAL;
|
| 404 | goto out_put_table;
|
| 405 | }
|
| 406 |
|
| 407 | regs[i] = reg;
|
| 408 | }
|
| 409 |
|
| 410 | entries = (struct acpi_wdat_entry *)(tbl + 1);
|
| 411 | for (i = 0; i < tbl->entries; i++) {
|
| 412 | const struct acpi_generic_address *gas;
|
| 413 | struct wdat_instruction *instr;
|
| 414 | struct list_head *instructions;
|
| 415 | unsigned int action;
|
| 416 | struct resource r;
|
| 417 | int j;
|
| 418 |
|
| 419 | action = entries[i].action;
|
| 420 | if (action >= MAX_WDAT_ACTIONS) {
|
| 421 | dev_dbg(dev, "Skipping unknown action: %u\n", action);
|
| 422 | continue;
|
| 423 | }
|
| 424 |
|
| 425 | instr = devm_kzalloc(dev, sizeof(*instr), GFP_KERNEL);
|
| 426 | if (!instr) {
|
| 427 | ret = -ENOMEM;
|
| 428 | goto out_put_table;
|
| 429 | }
|
| 430 |
|
| 431 | INIT_LIST_HEAD(&instr->node);
|
| 432 | instr->entry = entries[i];
|
| 433 |
|
| 434 | gas = &entries[i].register_region;
|
| 435 |
|
| 436 | memset(&r, 0, sizeof(r));
|
| 437 | r.start = gas->address;
|
| 438 | r.end = r.start + ACPI_ACCESS_BYTE_WIDTH(gas->access_width) - 1;
|
| 439 | if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
|
| 440 | r.flags = IORESOURCE_MEM;
|
| 441 | } else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
|
| 442 | r.flags = IORESOURCE_IO;
|
| 443 | } else {
|
| 444 | dev_dbg(dev, "Unsupported address space: %d\n",
|
| 445 | gas->space_id);
|
| 446 | continue;
|
| 447 | }
|
| 448 |
|
| 449 |
|
| 450 | for (j = 0; j < pdev->num_resources; j++) {
|
| 451 | res = &pdev->resource[j];
|
| 452 | if (resource_contains(res, &r)) {
|
| 453 | instr->reg = regs[j] + r.start - res->start;
|
| 454 | break;
|
| 455 | }
|
| 456 | }
|
| 457 |
|
| 458 | if (!instr->reg) {
|
| 459 | dev_err(dev, "I/O resource not found\n");
|
| 460 | ret = -EINVAL;
|
| 461 | goto out_put_table;
|
| 462 | }
|
| 463 |
|
| 464 | instructions = wdat->instructions[action];
|
| 465 | if (!instructions) {
|
| 466 | instructions = devm_kzalloc(dev,
|
| 467 | sizeof(*instructions),
|
| 468 | GFP_KERNEL);
|
| 469 | if (!instructions) {
|
| 470 | ret = -ENOMEM;
|
| 471 | goto out_put_table;
|
| 472 | }
|
| 473 |
|
| 474 | INIT_LIST_HEAD(instructions);
|
| 475 | wdat->instructions[action] = instructions;
|
| 476 | }
|
| 477 |
|
| 478 | list_add_tail(&instr->node, instructions);
|
| 479 | }
|
| 480 |
|
| 481 | if (wdat->instructions[ACPI_WDAT_GET_CURRENT_COUNTDOWN])
|
| 482 | wdat_wdt_ops.get_timeleft = wdat_wdt_get_timeleft;
|
| 483 |
|
| 484 | wdat_wdt_boot_status(wdat);
|
| 485 | wdat_wdt_set_running(wdat);
|
| 486 |
|
| 487 | ret = wdat_wdt_enable_reboot(wdat);
|
| 488 | if (ret)
|
| 489 | goto out_put_table;
|
| 490 |
|
| 491 | platform_set_drvdata(pdev, wdat);
|
| 492 |
|
| 493 | |
| 494 | * Set initial timeout so that userspace has time to configure the
|
| 495 | * watchdog properly after it has opened the device. In some cases
|
| 496 | * the BIOS default is too short and causes immediate reboot.
|
| 497 |
|
| 498 | if (watchdog_timeout_invalid(&wdat->wdd, timeout)) {
|
| 499 | dev_warn(dev, "Invalid timeout %d given, using %d\n",
|
| 500 | timeout, WDAT_DEFAULT_TIMEOUT);
|
| 501 | timeout = WDAT_DEFAULT_TIMEOUT;
|
| 502 | }
|
| 503 |
|
| 504 | ret = wdat_wdt_set_timeout(&wdat->wdd, timeout);
|
| 505 | if (ret)
|
| 506 | goto out_put_table;
|
| 507 |
|
| 508 | watchdog_set_nowayout(&wdat->wdd, nowayout);
|
| 509 | watchdog_stop_on_reboot(&wdat->wdd);
|
| 510 | watchdog_stop_on_unregister(&wdat->wdd);
|
| 511 | ret = devm_watchdog_register_device(dev, &wdat->wdd);
|
| 512 |
|
| 513 | out_put_table:
|
| 514 | acpi_put_table((struct acpi_table_header *)tbl);
|
| 515 | return ret;
|
| 516 | }
|
| 517 |
|
| 518 | static int wdat_wdt_suspend_noirq(struct device *dev)
|
| 519 | {
|
| 520 | struct wdat_wdt *wdat = dev_get_drvdata(dev);
|
| 521 | int ret;
|
| 522 |
|
| 523 | if (!watchdog_active(&wdat->wdd))
|
| 524 | return 0;
|
| 525 |
|
| 526 | |
| 527 | * We need to stop the watchdog if firmware is not doing it or if we
|
| 528 | * are going suspend to idle (where firmware is not involved). If
|
| 529 | * firmware is stopping the watchdog we kick it here one more time
|
| 530 | * to give it some time.
|
| 531 |
|
| 532 | wdat->stopped = false;
|
| 533 | if (acpi_target_system_state() == ACPI_STATE_S0 ||
|
| 534 | !wdat->stopped_in_sleep) {
|
| 535 | ret = wdat_wdt_stop(&wdat->wdd);
|
| 536 | if (!ret)
|
| 537 | wdat->stopped = true;
|
| 538 | } else {
|
| 539 | ret = wdat_wdt_ping(&wdat->wdd);
|
| 540 | }
|
| 541 |
|
| 542 | return ret;
|
| 543 | }
|
| 544 |
|
| 545 | static int wdat_wdt_resume_noirq(struct device *dev)
|
| 546 | {
|
| 547 | struct wdat_wdt *wdat = dev_get_drvdata(dev);
|
| 548 | int ret;
|
| 549 |
|
| 550 | if (!watchdog_active(&wdat->wdd))
|
| 551 | return 0;
|
| 552 |
|
| 553 | if (!wdat->stopped) {
|
| 554 | |
| 555 | * Looks like the boot firmware reinitializes the watchdog
|
| 556 | * before it hands off to the OS on resume from sleep so we
|
| 557 | * stop and reprogram the watchdog here.
|
| 558 |
|
| 559 | ret = wdat_wdt_stop(&wdat->wdd);
|
| 560 | if (ret)
|
| 561 | return ret;
|
| 562 |
|
| 563 | ret = wdat_wdt_set_timeout(&wdat->wdd, wdat->wdd.timeout);
|
| 564 | if (ret)
|
| 565 | return ret;
|
| 566 |
|
| 567 | ret = wdat_wdt_enable_reboot(wdat);
|
| 568 | if (ret)
|
| 569 | return ret;
|
| 570 |
|
| 571 | ret = wdat_wdt_ping(&wdat->wdd);
|
| 572 | if (ret)
|
| 573 | return ret;
|
| 574 | }
|
| 575 |
|
| 576 | return wdat_wdt_start(&wdat->wdd);
|
| 577 | }
|
| 578 |
|
| 579 | static const struct dev_pm_ops wdat_wdt_pm_ops = {
|
| 580 | NOIRQ_SYSTEM_SLEEP_PM_OPS(wdat_wdt_suspend_noirq, wdat_wdt_resume_noirq)
|
| 581 | };
|
| 582 |
|
| 583 | static struct platform_driver wdat_wdt_driver = {
|
| 584 | .probe = wdat_wdt_probe,
|
| 585 | .driver = {
|
| 586 | .name = "wdat_wdt",
|
| 587 | .pm = pm_sleep_ptr(&wdat_wdt_pm_ops),
|
| 588 | },
|
| 589 | };
|
| 590 |
|
| 591 | module_platform_driver(wdat_wdt_driver);
|
| 592 |
|
| 593 | MODULE_AUTHOR("Mika Westerberg <[email protected]>");
|
| 594 | MODULE_DESCRIPTION("ACPI Hardware Watchdog (WDAT) driver");
|
| 595 | MODULE_LICENSE("GPL v2");
|
| 596 | MODULE_ALIAS("platform:wdat_wdt");
|