From mboxrd@z Thu Jan 1 00:00:00 1970 From: Guenter Roeck Subject: Re: [PATCH v2 1/4] ACPI / watchdog: Add support for WDAT hardware watchdog Date: Tue, 20 Sep 2016 07:49:47 -0700 Message-ID: References: <20160920123054.195021-1-mika.westerberg@linux.intel.com> <20160920123054.195021-2-mika.westerberg@linux.intel.com> Mime-Version: 1.0 Content-Type: text/plain; charset=windows-1252; format=flowed Content-Transfer-Encoding: 7bit Return-path: In-Reply-To: <20160920123054.195021-2-mika.westerberg@linux.intel.com> Sender: linux-kernel-owner@vger.kernel.org To: Mika Westerberg , "Rafael J . Wysocki" , Wim Van Sebroeck Cc: Len Brown , Jean Delvare , Wolfram Sang , Peter Tyser , Lee Jones , Zha Qipeng , Darren Hart , linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org List-Id: linux-acpi@vger.kernel.org On 09/20/2016 05:30 AM, Mika Westerberg wrote: > Starting from Intel Skylake the iTCO watchdog timer registers were moved to > reside in the same register space with SMBus host controller. Not all > needed registers are available though and we need to unhide P2SB (Primary > to Sideband) device briefly to be able to read status of required NO_REBOOT > bit. The i2c-i801.c SMBus driver used to handle this and creation of the > iTCO watchdog platform device. > > Windows, on the other hand, does not use the iTCO watchdog hardware > directly even if it is available. Instead it relies on ACPI Watchdog Action > Table (WDAT) table to describe the watchdog hardware to the OS. This table > contains necessary information about the the hardware and also set of > actions which are executed by a driver as needed. > > This patch implements a new watchdog driver that takes advantage of the > ACPI WDAT table. We split the functionality into two parts: first part > enumerates the WDAT table and if found, populates resources and creates > platform device for the actual driver. The second part is the driver > itself. > > The reason for the split is that this way we can make the driver itself to > be a module and loaded automatically if the WDAT table is found. Otherwise > the module is not loaded. > > Signed-off-by: Mika Westerberg Reviewed-by: Guenter Roeck > --- > drivers/acpi/Kconfig | 3 + > drivers/acpi/Makefile | 1 + > drivers/acpi/acpi_watchdog.c | 123 ++++++++++ > drivers/acpi/internal.h | 10 + > drivers/acpi/scan.c | 1 + > drivers/watchdog/Kconfig | 13 ++ > drivers/watchdog/Makefile | 1 + > drivers/watchdog/wdat_wdt.c | 525 +++++++++++++++++++++++++++++++++++++++++++ > include/linux/acpi.h | 6 + > 9 files changed, 683 insertions(+) > create mode 100644 drivers/acpi/acpi_watchdog.c > create mode 100644 drivers/watchdog/wdat_wdt.c > > diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig > index 445ce28475b3..dcfa7e9e92f5 100644 > --- a/drivers/acpi/Kconfig > +++ b/drivers/acpi/Kconfig > @@ -462,6 +462,9 @@ source "drivers/acpi/nfit/Kconfig" > source "drivers/acpi/apei/Kconfig" > source "drivers/acpi/dptf/Kconfig" > > +config ACPI_WATCHDOG > + bool > + > config ACPI_EXTLOG > tristate "Extended Error Log support" > depends on X86_MCE && X86_LOCAL_APIC > diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile > index 5ae9d85c5159..3a1fa8f03749 100644 > --- a/drivers/acpi/Makefile > +++ b/drivers/acpi/Makefile > @@ -56,6 +56,7 @@ acpi-$(CONFIG_ACPI_NUMA) += numa.o > acpi-$(CONFIG_ACPI_PROCFS_POWER) += cm_sbs.o > acpi-y += acpi_lpat.o > acpi-$(CONFIG_ACPI_GENERIC_GSI) += gsi.o > +acpi-$(CONFIG_ACPI_WATCHDOG) += acpi_watchdog.o > > # These are (potentially) separate modules > > diff --git a/drivers/acpi/acpi_watchdog.c b/drivers/acpi/acpi_watchdog.c > new file mode 100644 > index 000000000000..13caebd679f5 > --- /dev/null > +++ b/drivers/acpi/acpi_watchdog.c > @@ -0,0 +1,123 @@ > +/* > + * ACPI watchdog table parsing support. > + * > + * Copyright (C) 2016, Intel Corporation > + * Author: Mika Westerberg > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + */ > + > +#define pr_fmt(fmt) "ACPI: watchdog: " fmt > + > +#include > +#include > +#include > + > +#include "internal.h" > + > +/** > + * Returns true if this system should prefer ACPI based watchdog instead of > + * the native one (which are typically the same hardware). > + */ > +bool acpi_has_watchdog(void) > +{ > + struct acpi_table_header hdr; > + > + if (acpi_disabled) > + return false; > + > + return ACPI_SUCCESS(acpi_get_table_header(ACPI_SIG_WDAT, 0, &hdr)); > +} > +EXPORT_SYMBOL_GPL(acpi_has_watchdog); > + > +void __init acpi_watchdog_init(void) > +{ > + const struct acpi_wdat_entry *entries; > + const struct acpi_table_wdat *wdat; > + struct list_head resource_list; > + struct resource_entry *rentry; > + struct platform_device *pdev; > + struct resource *resources; > + size_t nresources = 0; > + acpi_status status; > + int i; > + > + status = acpi_get_table(ACPI_SIG_WDAT, 0, > + (struct acpi_table_header **)&wdat); > + if (ACPI_FAILURE(status)) { > + /* It is fine if there is no WDAT */ > + return; > + } > + > + /* Watchdog disabled by BIOS */ > + if (!(wdat->flags & ACPI_WDAT_ENABLED)) > + return; > + > + /* Skip legacy PCI WDT devices */ > + if (wdat->pci_segment != 0xff || wdat->pci_bus != 0xff || > + wdat->pci_device != 0xff || wdat->pci_function != 0xff) > + return; > + > + INIT_LIST_HEAD(&resource_list); > + > + entries = (struct acpi_wdat_entry *)(wdat + 1); > + for (i = 0; i < wdat->entries; i++) { > + const struct acpi_generic_address *gas; > + struct resource_entry *rentry; > + struct resource res; > + bool found; > + > + gas = &entries[i].register_region; > + > + res.start = gas->address; > + if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { > + res.flags = IORESOURCE_MEM; > + res.end = res.start + ALIGN(gas->access_width, 4); > + } else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { > + res.flags = IORESOURCE_IO; > + res.end = res.start + gas->access_width; > + } else { > + pr_warn("Unsupported address space: %u\n", > + gas->space_id); > + goto fail_free_resource_list; > + } > + > + found = false; > + resource_list_for_each_entry(rentry, &resource_list) { > + if (resource_contains(rentry->res, &res)) { > + found = true; > + break; > + } > + } > + > + if (!found) { > + rentry = resource_list_create_entry(NULL, 0); > + if (!rentry) > + goto fail_free_resource_list; > + > + *rentry->res = res; > + resource_list_add_tail(rentry, &resource_list); > + nresources++; > + } > + } > + > + resources = kcalloc(nresources, sizeof(*resources), GFP_KERNEL); > + if (!resources) > + goto fail_free_resource_list; > + > + i = 0; > + resource_list_for_each_entry(rentry, &resource_list) > + resources[i++] = *rentry->res; > + > + pdev = platform_device_register_simple("wdat_wdt", PLATFORM_DEVID_NONE, > + resources, nresources); > + if (IS_ERR(pdev)) > + pr_err("Failed to create platform device\n"); > + > + kfree(resources); > + > +fail_free_resource_list: > + resource_list_free(&resource_list); > +} > diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h > index 940218ff0193..fb9a7ad96756 100644 > --- a/drivers/acpi/internal.h > +++ b/drivers/acpi/internal.h > @@ -225,4 +225,14 @@ static inline void suspend_nvs_restore(void) {} > void acpi_init_properties(struct acpi_device *adev); > void acpi_free_properties(struct acpi_device *adev); > > +/*-------------------------------------------------------------------------- > + Watchdog > + -------------------------------------------------------------------------- */ > + > +#ifdef CONFIG_ACPI_WATCHDOG > +void acpi_watchdog_init(void); > +#else > +static inline void acpi_watchdog_init(void) {} > +#endif > + > #endif /* _ACPI_INTERNAL_H_ */ > diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c > index e878fc799af7..3b85d87021da 100644 > --- a/drivers/acpi/scan.c > +++ b/drivers/acpi/scan.c > @@ -2002,6 +2002,7 @@ int __init acpi_scan_init(void) > acpi_pnp_init(); > acpi_int340x_thermal_init(); > acpi_amba_init(); > + acpi_watchdog_init(); > > acpi_scan_add_handler(&generic_device_handler); > > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig > index 1bffe006ca9a..50dbaa805658 100644 > --- a/drivers/watchdog/Kconfig > +++ b/drivers/watchdog/Kconfig > @@ -152,6 +152,19 @@ config TANGOX_WATCHDOG > > This driver can be built as a module. The module name is tangox_wdt. > > +config WDAT_WDT > + tristate "ACPI Watchdog Action Table (WDAT)" > + depends on ACPI > + select ACPI_WATCHDOG > + help > + This driver adds support for systems with ACPI Watchdog Action > + Table (WDAT) table. Servers typically have this but it can be > + found on some desktop machines as well. This driver will take > + over the native iTCO watchdog driver found on many Intel CPUs. > + > + To compile this driver as module, choose M here: the module will > + be called wdat_wdt. > + > config WM831X_WATCHDOG > tristate "WM831x watchdog" > depends on MFD_WM831X > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile > index c22ad3ea3539..cba00430151b 100644 > --- a/drivers/watchdog/Makefile > +++ b/drivers/watchdog/Makefile > @@ -202,6 +202,7 @@ obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o > obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o > obj-$(CONFIG_GPIO_WATCHDOG) += gpio_wdt.o > obj-$(CONFIG_TANGOX_WATCHDOG) += tangox_wdt.o > +obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o > obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o > obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o > obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o > diff --git a/drivers/watchdog/wdat_wdt.c b/drivers/watchdog/wdat_wdt.c > new file mode 100644 > index 000000000000..6b6464596674 > --- /dev/null > +++ b/drivers/watchdog/wdat_wdt.c > @@ -0,0 +1,525 @@ > +/* > + * ACPI Hardware Watchdog (WDAT) driver. > + * > + * Copyright (C) 2016, Intel Corporation > + * Author: Mika Westerberg > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > + > +#define MAX_WDAT_ACTIONS ACPI_WDAT_ACTION_RESERVED > + > +/** > + * struct wdat_instruction - Single ACPI WDAT instruction > + * @entry: Copy of the ACPI table instruction > + * @reg: Register the instruction is accessing > + * @node: Next instruction in action sequence > + */ > +struct wdat_instruction { > + struct acpi_wdat_entry entry; > + void __iomem *reg; > + struct list_head node; > +}; > + > +/** > + * struct wdat_wdt - ACPI WDAT watchdog device > + * @pdev: Parent platform device > + * @wdd: Watchdog core device > + * @period: How long is one watchdog period in ms > + * @stopped_in_sleep: Is this watchdog stopped by the firmware in S1-S5 > + * @stopped: Was the watchdog stopped by the driver in suspend > + * @actions: An array of instruction lists indexed by an action number from > + * the WDAT table. There can be %NULL entries for not implemented > + * actions. > + */ > +struct wdat_wdt { > + struct platform_device *pdev; > + struct watchdog_device wdd; > + unsigned int period; > + bool stopped_in_sleep; > + bool stopped; > + struct list_head *instructions[MAX_WDAT_ACTIONS]; > +}; > + > +#define to_wdat_wdt(wdd) container_of(wdd, struct wdat_wdt, wdd) > + > +static bool nowayout = WATCHDOG_NOWAYOUT; > +module_param(nowayout, bool, 0); > +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" > + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); > + > +static int wdat_wdt_read(struct wdat_wdt *wdat, > + const struct wdat_instruction *instr, u32 *value) > +{ > + const struct acpi_generic_address *gas = &instr->entry.register_region; > + > + switch (gas->access_width) { > + case 1: > + *value = ioread8(instr->reg); > + break; > + case 2: > + *value = ioread16(instr->reg); > + break; > + case 3: > + *value = ioread32(instr->reg); > + break; > + default: > + return -EINVAL; > + } > + > + dev_dbg(&wdat->pdev->dev, "Read %#x from 0x%08llx\n", *value, > + gas->address); > + > + return 0; > +} > + > +static int wdat_wdt_write(struct wdat_wdt *wdat, > + const struct wdat_instruction *instr, u32 value) > +{ > + const struct acpi_generic_address *gas = &instr->entry.register_region; > + > + switch (gas->access_width) { > + case 1: > + iowrite8((u8)value, instr->reg); > + break; > + case 2: > + iowrite16((u16)value, instr->reg); > + break; > + case 3: > + iowrite32(value, instr->reg); > + break; > + default: > + return -EINVAL; > + } > + > + dev_dbg(&wdat->pdev->dev, "Wrote %#x to 0x%08llx\n", value, > + gas->address); > + > + return 0; > +} > + > +static int wdat_wdt_run_action(struct wdat_wdt *wdat, unsigned int action, > + u32 param, u32 *retval) > +{ > + struct wdat_instruction *instr; > + > + if (action >= ARRAY_SIZE(wdat->instructions)) > + return -EINVAL; > + > + if (!wdat->instructions[action]) > + return -EOPNOTSUPP; > + > + dev_dbg(&wdat->pdev->dev, "Running action %#x\n", action); > + > + /* Run each instruction sequentially */ > + list_for_each_entry(instr, wdat->instructions[action], node) { > + const struct acpi_wdat_entry *entry = &instr->entry; > + const struct acpi_generic_address *gas; > + u32 flags, value, mask, x, y; > + bool preserve; > + int ret; > + > + gas = &entry->register_region; > + > + preserve = entry->instruction & ACPI_WDAT_PRESERVE_REGISTER; > + flags = entry->instruction & ~ACPI_WDAT_PRESERVE_REGISTER; > + value = entry->value; > + mask = entry->mask; > + > + switch (flags) { > + case ACPI_WDAT_READ_VALUE: > + ret = wdat_wdt_read(wdat, instr, &x); > + if (ret) > + return ret; > + x >>= gas->bit_offset; > + x &= mask; > + if (retval) > + *retval = x == value; > + break; > + > + case ACPI_WDAT_READ_COUNTDOWN: > + ret = wdat_wdt_read(wdat, instr, &x); > + if (ret) > + return ret; > + x >>= gas->bit_offset; > + x &= mask; > + if (retval) > + *retval = x; > + break; > + > + case ACPI_WDAT_WRITE_VALUE: > + x = value & mask; > + x <<= gas->bit_offset; > + if (preserve) { > + ret = wdat_wdt_read(wdat, instr, &y); > + if (ret) > + return ret; > + y = y & ~(mask << gas->bit_offset); > + x |= y; > + } > + ret = wdat_wdt_write(wdat, instr, x); > + if (ret) > + return ret; > + break; > + > + case ACPI_WDAT_WRITE_COUNTDOWN: > + x = param; > + x &= mask; > + x <<= gas->bit_offset; > + if (preserve) { > + ret = wdat_wdt_read(wdat, instr, &y); > + if (ret) > + return ret; > + y = y & ~(mask << gas->bit_offset); > + x |= y; > + } > + ret = wdat_wdt_write(wdat, instr, x); > + if (ret) > + return ret; > + break; > + > + default: > + dev_err(&wdat->pdev->dev, "Unknown instruction: %u\n", > + flags); > + return -EINVAL; > + } > + } > + > + return 0; > +} > + > +static int wdat_wdt_enable_reboot(struct wdat_wdt *wdat) > +{ > + int ret; > + > + /* > + * WDAT specification says that the watchdog is required to reboot > + * the system when it fires. However, it also states that it is > + * recommeded to make it configurable through hardware register. We > + * enable reboot now if it is configrable, just in case. > + */ > + ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_REBOOT, 0, 0); > + if (ret && ret != -EOPNOTSUPP) { > + dev_err(&wdat->pdev->dev, > + "Failed to enable reboot when watchdog triggers\n"); > + return ret; > + } > + > + return 0; > +} > + > +static void wdat_wdt_boot_status(struct wdat_wdt *wdat) > +{ > + u32 boot_status = 0; > + int ret; > + > + ret = wdat_wdt_run_action(wdat, ACPI_WDAT_GET_STATUS, 0, &boot_status); > + if (ret && ret != -EOPNOTSUPP) { > + dev_err(&wdat->pdev->dev, "Failed to read boot status\n"); > + return; > + } > + > + if (boot_status) > + wdat->wdd.bootstatus = WDIOF_CARDRESET; > + > + /* Clear the boot status in case BIOS did not do it */ > + ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_STATUS, 0, 0); > + if (ret && ret != -EOPNOTSUPP) > + dev_err(&wdat->pdev->dev, "Failed to clear boot status\n"); > +} > + > +static void wdat_wdt_set_running(struct wdat_wdt *wdat) > +{ > + u32 running = 0; > + int ret; > + > + ret = wdat_wdt_run_action(wdat, ACPI_WDAT_GET_RUNNING_STATE, 0, > + &running); > + if (ret && ret != -EOPNOTSUPP) > + dev_err(&wdat->pdev->dev, "Failed to read running state\n"); > + > + if (running) > + set_bit(WDOG_HW_RUNNING, &wdat->wdd.status); > +} > + > +static int wdat_wdt_start(struct watchdog_device *wdd) > +{ > + return wdat_wdt_run_action(to_wdat_wdt(wdd), > + ACPI_WDAT_SET_RUNNING_STATE, 0, NULL); > +} > + > +static int wdat_wdt_stop(struct watchdog_device *wdd) > +{ > + return wdat_wdt_run_action(to_wdat_wdt(wdd), > + ACPI_WDAT_SET_STOPPED_STATE, 0, NULL); > +} > + > +static int wdat_wdt_ping(struct watchdog_device *wdd) > +{ > + return wdat_wdt_run_action(to_wdat_wdt(wdd), ACPI_WDAT_RESET, 0, NULL); > +} > + > +static int wdat_wdt_set_timeout(struct watchdog_device *wdd, > + unsigned int timeout) > +{ > + struct wdat_wdt *wdat = to_wdat_wdt(wdd); > + unsigned int periods; > + int ret; > + > + periods = timeout * 1000 / wdat->period; > + ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_COUNTDOWN, periods, NULL); > + if (!ret) > + wdd->timeout = timeout; > + return ret; > +} > + > +static unsigned int wdat_wdt_get_timeleft(struct watchdog_device *wdd) > +{ > + struct wdat_wdt *wdat = to_wdat_wdt(wdd); > + u32 periods = 0; > + > + wdat_wdt_run_action(wdat, ACPI_WDAT_GET_COUNTDOWN, 0, &periods); > + return periods * wdat->period / 1000; > +} > + > +static const struct watchdog_info wdat_wdt_info = { > + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, > + .firmware_version = 0, > + .identity = "wdat_wdt", > +}; > + > +static const struct watchdog_ops wdat_wdt_ops = { > + .owner = THIS_MODULE, > + .start = wdat_wdt_start, > + .stop = wdat_wdt_stop, > + .ping = wdat_wdt_ping, > + .set_timeout = wdat_wdt_set_timeout, > + .get_timeleft = wdat_wdt_get_timeleft, > +}; > + > +static int wdat_wdt_probe(struct platform_device *pdev) > +{ > + const struct acpi_wdat_entry *entries; > + const struct acpi_table_wdat *tbl; > + struct wdat_wdt *wdat; > + struct resource *res; > + void __iomem **regs; > + acpi_status status; > + int i, ret; > + > + status = acpi_get_table(ACPI_SIG_WDAT, 0, > + (struct acpi_table_header **)&tbl); > + if (ACPI_FAILURE(status)) > + return -ENODEV; > + > + wdat = devm_kzalloc(&pdev->dev, sizeof(*wdat), GFP_KERNEL); > + if (!wdat) > + return -ENOMEM; > + > + regs = devm_kcalloc(&pdev->dev, pdev->num_resources, sizeof(*regs), > + GFP_KERNEL); > + if (!regs) > + return -ENOMEM; > + > + /* WDAT specification wants to have >= 1ms period */ > + if (tbl->timer_period < 1) > + return -EINVAL; > + if (tbl->min_count > tbl->max_count) > + return -EINVAL; > + > + wdat->period = tbl->timer_period; > + wdat->wdd.min_hw_heartbeat_ms = wdat->period * tbl->min_count; > + wdat->wdd.max_hw_heartbeat_ms = wdat->period * tbl->max_count; > + wdat->stopped_in_sleep = tbl->flags & ACPI_WDAT_STOPPED; > + wdat->wdd.info = &wdat_wdt_info; > + wdat->wdd.ops = &wdat_wdt_ops; > + wdat->pdev = pdev; > + > + /* Request and map all resources */ > + for (i = 0; i < pdev->num_resources; i++) { > + void __iomem *reg; > + > + res = &pdev->resource[i]; > + if (resource_type(res) == IORESOURCE_MEM) { > + reg = devm_ioremap_resource(&pdev->dev, res); > + } else if (resource_type(res) == IORESOURCE_IO) { > + reg = devm_ioport_map(&pdev->dev, res->start, 1); > + } else { > + dev_err(&pdev->dev, "Unsupported resource\n"); > + return -EINVAL; > + } > + > + if (!reg) > + return -ENOMEM; > + > + regs[i] = reg; > + } > + > + entries = (struct acpi_wdat_entry *)(tbl + 1); > + for (i = 0; i < tbl->entries; i++) { > + const struct acpi_generic_address *gas; > + struct wdat_instruction *instr; > + struct list_head *instructions; > + unsigned int action; > + struct resource r; > + int j; > + > + action = entries[i].action; > + if (action >= MAX_WDAT_ACTIONS) { > + dev_dbg(&pdev->dev, "Skipping unknown action: %u\n", > + action); > + continue; > + } > + > + instr = devm_kzalloc(&pdev->dev, sizeof(*instr), GFP_KERNEL); > + if (!instr) > + return -ENOMEM; > + > + INIT_LIST_HEAD(&instr->node); > + instr->entry = entries[i]; > + > + gas = &entries[i].register_region; > + > + memset(&r, 0, sizeof(r)); > + r.start = gas->address; > + r.end = r.start + gas->access_width; > + if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { > + r.flags = IORESOURCE_MEM; > + } else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { > + r.flags = IORESOURCE_IO; > + } else { > + dev_dbg(&pdev->dev, "Unsupported address space: %d\n", > + gas->space_id); > + continue; > + } > + > + /* Find the matching resource */ > + for (j = 0; j < pdev->num_resources; j++) { > + res = &pdev->resource[j]; > + if (resource_contains(res, &r)) { > + instr->reg = regs[j] + r.start - res->start; > + break; > + } > + } > + > + if (!instr->reg) { > + dev_err(&pdev->dev, "I/O resource not found\n"); > + return -EINVAL; > + } > + > + instructions = wdat->instructions[action]; > + if (!instructions) { > + instructions = devm_kzalloc(&pdev->dev, > + sizeof(*instructions), GFP_KERNEL); > + if (!instructions) > + return -ENOMEM; > + > + INIT_LIST_HEAD(instructions); > + wdat->instructions[action] = instructions; > + } > + > + list_add_tail(&instr->node, instructions); > + } > + > + wdat_wdt_boot_status(wdat); > + wdat_wdt_set_running(wdat); > + > + ret = wdat_wdt_enable_reboot(wdat); > + if (ret) > + return ret; > + > + platform_set_drvdata(pdev, wdat); > + > + watchdog_set_nowayout(&wdat->wdd, nowayout); > + return devm_watchdog_register_device(&pdev->dev, &wdat->wdd); > +} > + > +#ifdef CONFIG_PM_SLEEP > +static int wdat_wdt_suspend_noirq(struct device *dev) > +{ > + struct platform_device *pdev = to_platform_device(dev); > + struct wdat_wdt *wdat = platform_get_drvdata(pdev); > + int ret; > + > + if (!watchdog_active(&wdat->wdd)) > + return 0; > + > + /* > + * We need to stop the watchdog if firmare is not doing it or if we > + * are going suspend to idle (where firmware is not involved). If > + * firmware is stopping the watchdog we kick it here one more time > + * to give it some time. > + */ > + wdat->stopped = false; > + if (acpi_target_system_state() == ACPI_STATE_S0 || > + !wdat->stopped_in_sleep) { > + ret = wdat_wdt_stop(&wdat->wdd); > + if (!ret) > + wdat->stopped = true; > + } else { > + ret = wdat_wdt_ping(&wdat->wdd); > + } > + > + return ret; > +} > + > +static int wdat_wdt_resume_noirq(struct device *dev) > +{ > + struct platform_device *pdev = to_platform_device(dev); > + struct wdat_wdt *wdat = platform_get_drvdata(pdev); > + int ret; > + > + if (!watchdog_active(&wdat->wdd)) > + return 0; > + > + if (!wdat->stopped) { > + /* > + * Looks like the boot firmware reinitializes the watchdog > + * before it hands off to the OS on resume from sleep so we > + * stop and reprogram the watchdog here. > + */ > + ret = wdat_wdt_stop(&wdat->wdd); > + if (ret) > + return ret; > + > + ret = wdat_wdt_set_timeout(&wdat->wdd, wdat->wdd.timeout); > + if (ret) > + return ret; > + > + ret = wdat_wdt_enable_reboot(wdat); > + if (ret) > + return ret; > + } > + > + return wdat_wdt_start(&wdat->wdd); > +} > +#endif > + > +static const struct dev_pm_ops wdat_wdt_pm_ops = { > + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(wdat_wdt_suspend_noirq, > + wdat_wdt_resume_noirq) > +}; > + > +static struct platform_driver wdat_wdt_driver = { > + .probe = wdat_wdt_probe, > + .driver = { > + .name = "wdat_wdt", > + .pm = &wdat_wdt_pm_ops, > + }, > +}; > + > +module_platform_driver(wdat_wdt_driver); > + > +MODULE_AUTHOR("Mika Westerberg "); > +MODULE_DESCRIPTION("ACPI Hardware Watchdog (WDAT) driver"); > +MODULE_LICENSE("GPL v2"); > +MODULE_ALIAS("platform:wdat_wdt"); > diff --git a/include/linux/acpi.h b/include/linux/acpi.h > index c5eaf2f80a4c..8ff6ca4a2639 100644 > --- a/include/linux/acpi.h > +++ b/include/linux/acpi.h > @@ -1074,4 +1074,10 @@ void acpi_table_upgrade(void); > static inline void acpi_table_upgrade(void) { } > #endif > > +#if defined(CONFIG_ACPI) && defined(CONFIG_ACPI_WATCHDOG) > +extern bool acpi_has_watchdog(void); > +#else > +static inline bool acpi_has_watchdog(void) { return false; } > +#endif > + > #endif /*_LINUX_ACPI_H*/ >