| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0+ |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * intel TCO Watchdog Driver |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * (c) Copyright 2006-2011 Wim Van Sebroeck <wim@iguana.be>. |
|---|
| 5 | | - * |
|---|
| 6 | | - * This program is free software; you can redistribute it and/or |
|---|
| 7 | | - * modify it under the terms of the GNU General Public License |
|---|
| 8 | | - * as published by the Free Software Foundation; either version |
|---|
| 9 | | - * 2 of the License, or (at your option) any later version. |
|---|
| 10 | 6 | * |
|---|
| 11 | 7 | * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor |
|---|
| 12 | 8 | * provide warranty for any of this software. This material is |
|---|
| .. | .. |
|---|
| 52 | 48 | |
|---|
| 53 | 49 | /* Includes */ |
|---|
| 54 | 50 | #include <linux/acpi.h> /* For ACPI support */ |
|---|
| 51 | +#include <linux/bits.h> /* For BIT() */ |
|---|
| 55 | 52 | #include <linux/module.h> /* For module specific items */ |
|---|
| 56 | 53 | #include <linux/moduleparam.h> /* For new moduleparam's */ |
|---|
| 57 | 54 | #include <linux/types.h> /* For standard types (like size_t) */ |
|---|
| .. | .. |
|---|
| 67 | 64 | #include <linux/uaccess.h> /* For copy_to_user/put_user/... */ |
|---|
| 68 | 65 | #include <linux/io.h> /* For inb/outb/... */ |
|---|
| 69 | 66 | #include <linux/platform_data/itco_wdt.h> |
|---|
| 67 | +#include <linux/mfd/intel_pmc_bxt.h> |
|---|
| 70 | 68 | |
|---|
| 71 | 69 | #include "iTCO_vendor.h" |
|---|
| 72 | 70 | |
|---|
| .. | .. |
|---|
| 219 | 217 | return 0; |
|---|
| 220 | 218 | } |
|---|
| 221 | 219 | |
|---|
| 222 | | -static void iTCO_wdt_no_reboot_bit_setup(struct iTCO_wdt_private *p, |
|---|
| 223 | | - struct itco_wdt_platform_data *pdata) |
|---|
| 220 | +static int update_no_reboot_bit_cnt(void *priv, bool set) |
|---|
| 224 | 221 | { |
|---|
| 225 | | - if (pdata->update_no_reboot_bit) { |
|---|
| 226 | | - p->update_no_reboot_bit = pdata->update_no_reboot_bit; |
|---|
| 227 | | - p->no_reboot_priv = pdata->no_reboot_priv; |
|---|
| 222 | + struct iTCO_wdt_private *p = priv; |
|---|
| 223 | + u16 val, newval; |
|---|
| 224 | + |
|---|
| 225 | + val = inw(TCO1_CNT(p)); |
|---|
| 226 | + if (set) |
|---|
| 227 | + val |= BIT(0); |
|---|
| 228 | + else |
|---|
| 229 | + val &= ~BIT(0); |
|---|
| 230 | + outw(val, TCO1_CNT(p)); |
|---|
| 231 | + newval = inw(TCO1_CNT(p)); |
|---|
| 232 | + |
|---|
| 233 | + /* make sure the update is successful */ |
|---|
| 234 | + return val != newval ? -EIO : 0; |
|---|
| 235 | +} |
|---|
| 236 | + |
|---|
| 237 | +static int update_no_reboot_bit_pmc(void *priv, bool set) |
|---|
| 238 | +{ |
|---|
| 239 | + struct intel_pmc_dev *pmc = priv; |
|---|
| 240 | + u32 bits = PMC_CFG_NO_REBOOT_EN; |
|---|
| 241 | + u32 value = set ? bits : 0; |
|---|
| 242 | + |
|---|
| 243 | + return intel_pmc_gcr_update(pmc, PMC_GCR_PMC_CFG_REG, bits, value); |
|---|
| 244 | +} |
|---|
| 245 | + |
|---|
| 246 | +static void iTCO_wdt_no_reboot_bit_setup(struct iTCO_wdt_private *p, |
|---|
| 247 | + struct platform_device *pdev, |
|---|
| 248 | + struct itco_wdt_platform_data *pdata) |
|---|
| 249 | +{ |
|---|
| 250 | + if (pdata->no_reboot_use_pmc) { |
|---|
| 251 | + struct intel_pmc_dev *pmc = dev_get_drvdata(pdev->dev.parent); |
|---|
| 252 | + |
|---|
| 253 | + p->update_no_reboot_bit = update_no_reboot_bit_pmc; |
|---|
| 254 | + p->no_reboot_priv = pmc; |
|---|
| 228 | 255 | return; |
|---|
| 229 | 256 | } |
|---|
| 230 | 257 | |
|---|
| 231 | | - if (p->iTCO_version >= 2) |
|---|
| 258 | + if (p->iTCO_version >= 6) |
|---|
| 259 | + p->update_no_reboot_bit = update_no_reboot_bit_cnt; |
|---|
| 260 | + else if (p->iTCO_version >= 2) |
|---|
| 232 | 261 | p->update_no_reboot_bit = update_no_reboot_bit_mem; |
|---|
| 233 | 262 | else if (p->iTCO_version == 1) |
|---|
| 234 | 263 | p->update_no_reboot_bit = update_no_reboot_bit_pci; |
|---|
| .. | .. |
|---|
| 304 | 333 | |
|---|
| 305 | 334 | spin_lock(&p->io_lock); |
|---|
| 306 | 335 | |
|---|
| 307 | | - iTCO_vendor_pre_keepalive(p->smi_res, wd_dev->timeout); |
|---|
| 308 | | - |
|---|
| 309 | 336 | /* Reload the timer by writing to the TCO Timer Counter register */ |
|---|
| 310 | 337 | if (p->iTCO_version >= 2) { |
|---|
| 311 | 338 | outw(0x01, TCO_RLD(p)); |
|---|
| .. | .. |
|---|
| 341 | 368 | if ((p->iTCO_version >= 2 && tmrval > 0x3ff) || |
|---|
| 342 | 369 | (p->iTCO_version == 1 && tmrval > 0x03f)) |
|---|
| 343 | 370 | return -EINVAL; |
|---|
| 344 | | - |
|---|
| 345 | | - iTCO_vendor_pre_set_heartbeat(tmrval); |
|---|
| 346 | 371 | |
|---|
| 347 | 372 | /* Write new heartbeat to watchdog */ |
|---|
| 348 | 373 | if (p->iTCO_version >= 2) { |
|---|
| .. | .. |
|---|
| 447 | 472 | if (!p->tco_res) |
|---|
| 448 | 473 | return -ENODEV; |
|---|
| 449 | 474 | |
|---|
| 450 | | - p->smi_res = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_IO_SMI); |
|---|
| 451 | | - if (!p->smi_res) |
|---|
| 452 | | - return -ENODEV; |
|---|
| 453 | | - |
|---|
| 454 | 475 | p->iTCO_version = pdata->version; |
|---|
| 455 | 476 | p->pci_dev = to_pci_dev(dev->parent); |
|---|
| 456 | 477 | |
|---|
| 457 | | - iTCO_wdt_no_reboot_bit_setup(p, pdata); |
|---|
| 478 | + p->smi_res = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_IO_SMI); |
|---|
| 479 | + if (p->smi_res) { |
|---|
| 480 | + /* The TCO logic uses the TCO_EN bit in the SMI_EN register */ |
|---|
| 481 | + if (!devm_request_region(dev, p->smi_res->start, |
|---|
| 482 | + resource_size(p->smi_res), |
|---|
| 483 | + pdev->name)) { |
|---|
| 484 | + pr_err("I/O address 0x%04llx already in use, device disabled\n", |
|---|
| 485 | + (u64)SMI_EN(p)); |
|---|
| 486 | + return -EBUSY; |
|---|
| 487 | + } |
|---|
| 488 | + } else if (iTCO_vendorsupport || |
|---|
| 489 | + turn_SMI_watchdog_clear_off >= p->iTCO_version) { |
|---|
| 490 | + pr_err("SMI I/O resource is missing\n"); |
|---|
| 491 | + return -ENODEV; |
|---|
| 492 | + } |
|---|
| 493 | + |
|---|
| 494 | + iTCO_wdt_no_reboot_bit_setup(p, pdev, pdata); |
|---|
| 458 | 495 | |
|---|
| 459 | 496 | /* |
|---|
| 460 | 497 | * Get the Memory-Mapped GCS or PMC register, we need it for the |
|---|
| 461 | 498 | * NO_REBOOT flag (TCO v2 and v3). |
|---|
| 462 | 499 | */ |
|---|
| 463 | | - if (p->iTCO_version >= 2 && !pdata->update_no_reboot_bit) { |
|---|
| 500 | + if (p->iTCO_version >= 2 && p->iTCO_version < 6 && |
|---|
| 501 | + !pdata->no_reboot_use_pmc) { |
|---|
| 464 | 502 | p->gcs_pmc_res = platform_get_resource(pdev, |
|---|
| 465 | 503 | IORESOURCE_MEM, |
|---|
| 466 | 504 | ICH_RES_MEM_GCS_PMC); |
|---|
| .. | .. |
|---|
| 479 | 517 | /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ |
|---|
| 480 | 518 | p->update_no_reboot_bit(p->no_reboot_priv, true); |
|---|
| 481 | 519 | |
|---|
| 482 | | - /* The TCO logic uses the TCO_EN bit in the SMI_EN register */ |
|---|
| 483 | | - if (!devm_request_region(dev, p->smi_res->start, |
|---|
| 484 | | - resource_size(p->smi_res), |
|---|
| 485 | | - pdev->name)) { |
|---|
| 486 | | - pr_err("I/O address 0x%04llx already in use, device disabled\n", |
|---|
| 487 | | - (u64)SMI_EN(p)); |
|---|
| 488 | | - return -EBUSY; |
|---|
| 489 | | - } |
|---|
| 490 | 520 | if (turn_SMI_watchdog_clear_off >= p->iTCO_version) { |
|---|
| 491 | 521 | /* |
|---|
| 492 | 522 | * Bit 13: TCO_EN -> 0 |
|---|
| .. | .. |
|---|
| 510 | 540 | |
|---|
| 511 | 541 | /* Clear out the (probably old) status */ |
|---|
| 512 | 542 | switch (p->iTCO_version) { |
|---|
| 543 | + case 6: |
|---|
| 513 | 544 | case 5: |
|---|
| 514 | 545 | case 4: |
|---|
| 515 | 546 | outw(0x0008, TCO1_STS(p)); /* Clear the Time Out Status bit */ |
|---|
| .. | .. |
|---|
| 549 | 580 | } |
|---|
| 550 | 581 | |
|---|
| 551 | 582 | watchdog_stop_on_reboot(&p->wddev); |
|---|
| 583 | + watchdog_stop_on_unregister(&p->wddev); |
|---|
| 552 | 584 | ret = devm_watchdog_register_device(dev, &p->wddev); |
|---|
| 553 | 585 | if (ret != 0) { |
|---|
| 554 | 586 | pr_err("cannot register watchdog device (err=%d)\n", ret); |
|---|
| .. | .. |
|---|
| 557 | 589 | |
|---|
| 558 | 590 | pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", |
|---|
| 559 | 591 | heartbeat, nowayout); |
|---|
| 560 | | - |
|---|
| 561 | | - return 0; |
|---|
| 562 | | -} |
|---|
| 563 | | - |
|---|
| 564 | | -static int iTCO_wdt_remove(struct platform_device *pdev) |
|---|
| 565 | | -{ |
|---|
| 566 | | - struct iTCO_wdt_private *p = platform_get_drvdata(pdev); |
|---|
| 567 | | - |
|---|
| 568 | | - /* Stop the timer before we leave */ |
|---|
| 569 | | - if (!nowayout) |
|---|
| 570 | | - iTCO_wdt_stop(&p->wddev); |
|---|
| 571 | 592 | |
|---|
| 572 | 593 | return 0; |
|---|
| 573 | 594 | } |
|---|
| .. | .. |
|---|
| 624 | 645 | |
|---|
| 625 | 646 | static struct platform_driver iTCO_wdt_driver = { |
|---|
| 626 | 647 | .probe = iTCO_wdt_probe, |
|---|
| 627 | | - .remove = iTCO_wdt_remove, |
|---|
| 628 | 648 | .driver = { |
|---|
| 629 | 649 | .name = DRV_NAME, |
|---|
| 630 | 650 | .pm = ITCO_WDT_PM_OPS, |
|---|