--- zzzz-none-000/linux-3.10.107/drivers/watchdog/iTCO_wdt.c 2017-06-27 09:49:32.000000000 +0000 +++ scorpion-7490-727/linux-3.10.107/drivers/watchdog/iTCO_wdt.c 2021-02-04 17:41:59.000000000 +0000 @@ -48,16 +48,15 @@ /* Module and version information */ #define DRV_NAME "iTCO_wdt" -#define DRV_VERSION "1.10" +#define DRV_VERSION "1.11" /* Includes */ +#include /* For ACPI support */ #include /* For module specific items */ #include /* For new moduleparam's */ #include /* For standard types (like size_t) */ #include /* For the -ENODEV/... values */ #include /* For printk/panic/... */ -#include /* For MODULE_ALIAS_MISCDEV - (WATCHDOG_MINOR) */ #include /* For the watchdog specific items */ #include /* For __init/__exit/... */ #include /* For file operations */ @@ -67,8 +66,7 @@ #include /* For spin_lock/spin_unlock/... */ #include /* For copy_to_user/put_user/... */ #include /* For inb/outb/... */ -#include -#include +#include #include "iTCO_vendor.h" @@ -94,14 +92,19 @@ unsigned int iTCO_version; struct resource *tco_res; struct resource *smi_res; - struct resource *gcs_res; - /* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2)*/ - unsigned long __iomem *gcs; + /* + * NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2), + * or memory-mapped PMC register bit 4 (TCO version 3). + */ + struct resource *gcs_pmc_res; + unsigned long __iomem *gcs_pmc; /* the lock for io operations */ spinlock_t io_lock; struct platform_device *dev; /* the PCI-device */ struct pci_dev *pdev; + /* whether or not the watchdog has been suspended */ + bool suspended; } iTCO_wdt_private; /* module parameters */ @@ -127,11 +130,40 @@ * Some TCO specific functions */ -static inline unsigned int seconds_to_ticks(int seconds) +/* + * The iTCO v1 and v2's internal timer is stored as ticks which decrement + * every 0.6 seconds. v3's internal timer is stored as seconds (some + * datasheets incorrectly state 0.6 seconds). + */ +static inline unsigned int seconds_to_ticks(int secs) { - /* the internal timer is stored as ticks which decrement - * every 0.6 seconds */ - return (seconds * 10) / 6; + return iTCO_wdt_private.iTCO_version == 3 ? secs : (secs * 10) / 6; +} + +static inline unsigned int ticks_to_seconds(int ticks) +{ + return iTCO_wdt_private.iTCO_version == 3 ? ticks : (ticks * 6) / 10; +} + +static inline u32 no_reboot_bit(void) +{ + u32 enable_bit; + + switch (iTCO_wdt_private.iTCO_version) { + case 3: + enable_bit = 0x00000010; + break; + case 2: + enable_bit = 0x00000020; + break; + case 4: + case 1: + default: + enable_bit = 0x00000002; + break; + } + + return enable_bit; } static void iTCO_wdt_set_NO_REBOOT_bit(void) @@ -139,42 +171,41 @@ u32 val32; /* Set the NO_REBOOT bit: this disables reboots */ - if (iTCO_wdt_private.iTCO_version == 2) { - val32 = readl(iTCO_wdt_private.gcs); - val32 |= 0x00000020; - writel(val32, iTCO_wdt_private.gcs); + if (iTCO_wdt_private.iTCO_version >= 2) { + val32 = readl(iTCO_wdt_private.gcs_pmc); + val32 |= no_reboot_bit(); + writel(val32, iTCO_wdt_private.gcs_pmc); } else if (iTCO_wdt_private.iTCO_version == 1) { pci_read_config_dword(iTCO_wdt_private.pdev, 0xd4, &val32); - val32 |= 0x00000002; + val32 |= no_reboot_bit(); pci_write_config_dword(iTCO_wdt_private.pdev, 0xd4, val32); } } static int iTCO_wdt_unset_NO_REBOOT_bit(void) { - int ret = 0; - u32 val32; + u32 enable_bit = no_reboot_bit(); + u32 val32 = 0; /* Unset the NO_REBOOT bit: this enables reboots */ - if (iTCO_wdt_private.iTCO_version == 2) { - val32 = readl(iTCO_wdt_private.gcs); - val32 &= 0xffffffdf; - writel(val32, iTCO_wdt_private.gcs); + if (iTCO_wdt_private.iTCO_version >= 2) { + val32 = readl(iTCO_wdt_private.gcs_pmc); + val32 &= ~enable_bit; + writel(val32, iTCO_wdt_private.gcs_pmc); - val32 = readl(iTCO_wdt_private.gcs); - if (val32 & 0x00000020) - ret = -EIO; + val32 = readl(iTCO_wdt_private.gcs_pmc); } else if (iTCO_wdt_private.iTCO_version == 1) { pci_read_config_dword(iTCO_wdt_private.pdev, 0xd4, &val32); - val32 &= 0xfffffffd; + val32 &= ~enable_bit; pci_write_config_dword(iTCO_wdt_private.pdev, 0xd4, val32); pci_read_config_dword(iTCO_wdt_private.pdev, 0xd4, &val32); - if (val32 & 0x00000002) - ret = -EIO; } - return ret; /* returns: 0 = OK, -EIO = Error */ + if (val32 & enable_bit) + return -EIO; + + return 0; } static int iTCO_wdt_start(struct watchdog_device *wd_dev) @@ -194,7 +225,7 @@ /* Force the timer to its reload value by writing to the TCO_RLD register */ - if (iTCO_wdt_private.iTCO_version == 2) + if (iTCO_wdt_private.iTCO_version >= 2) outw(0x01, TCO_RLD); else if (iTCO_wdt_private.iTCO_version == 1) outb(0x01, TCO_RLD); @@ -242,9 +273,9 @@ iTCO_vendor_pre_keepalive(iTCO_wdt_private.smi_res, wd_dev->timeout); /* Reload the timer by writing to the TCO Timer Counter register */ - if (iTCO_wdt_private.iTCO_version == 2) + if (iTCO_wdt_private.iTCO_version >= 2) { outw(0x01, TCO_RLD); - else if (iTCO_wdt_private.iTCO_version == 1) { + } else if (iTCO_wdt_private.iTCO_version == 1) { /* Reset the timeout status bit so that the timer * needs to count down twice again before rebooting */ outw(0x0008, TCO1_STS); /* write 1 to clear bit */ @@ -272,14 +303,14 @@ /* "Values of 0h-3h are ignored and should not be attempted" */ if (tmrval < 0x04) return -EINVAL; - if (((iTCO_wdt_private.iTCO_version == 2) && (tmrval > 0x3ff)) || + if (((iTCO_wdt_private.iTCO_version >= 2) && (tmrval > 0x3ff)) || ((iTCO_wdt_private.iTCO_version == 1) && (tmrval > 0x03f))) return -EINVAL; iTCO_vendor_pre_set_heartbeat(tmrval); /* Write new heartbeat to watchdog */ - if (iTCO_wdt_private.iTCO_version == 2) { + if (iTCO_wdt_private.iTCO_version >= 2) { spin_lock(&iTCO_wdt_private.io_lock); val16 = inw(TCOv2_TMR); val16 &= 0xfc00; @@ -314,13 +345,13 @@ unsigned int time_left = 0; /* read the TCO Timer */ - if (iTCO_wdt_private.iTCO_version == 2) { + if (iTCO_wdt_private.iTCO_version >= 2) { spin_lock(&iTCO_wdt_private.io_lock); val16 = inw(TCO_RLD); val16 &= 0x3ff; spin_unlock(&iTCO_wdt_private.io_lock); - time_left = (val16 * 6) / 10; + time_left = ticks_to_seconds(val16); } else if (iTCO_wdt_private.iTCO_version == 1) { spin_lock(&iTCO_wdt_private.io_lock); val8 = inb(TCO_RLD); @@ -329,7 +360,7 @@ val8 += (inb(TCOv1_TMR) & 0x3f); spin_unlock(&iTCO_wdt_private.io_lock); - time_left = (val8 * 6) / 10; + time_left = ticks_to_seconds(val8); } return time_left; } @@ -349,15 +380,15 @@ static const struct watchdog_ops iTCO_wdt_ops = { .owner = THIS_MODULE, .start = iTCO_wdt_start, - .stop = iTCO_wdt_stop, - .ping = iTCO_wdt_ping, + .stop = iTCO_wdt_stop, + .ping = iTCO_wdt_ping, .set_timeout = iTCO_wdt_set_timeout, .get_timeleft = iTCO_wdt_get_timeleft, }; static struct watchdog_device iTCO_wdt_watchdog_dev = { .info = &ident, - .ops = &iTCO_wdt_ops, + .ops = &iTCO_wdt_ops, }; /* @@ -378,25 +409,25 @@ resource_size(iTCO_wdt_private.tco_res)); release_region(iTCO_wdt_private.smi_res->start, resource_size(iTCO_wdt_private.smi_res)); - if (iTCO_wdt_private.iTCO_version == 2) { - iounmap(iTCO_wdt_private.gcs); - release_mem_region(iTCO_wdt_private.gcs_res->start, - resource_size(iTCO_wdt_private.gcs_res)); + if (iTCO_wdt_private.iTCO_version >= 2) { + iounmap(iTCO_wdt_private.gcs_pmc); + release_mem_region(iTCO_wdt_private.gcs_pmc_res->start, + resource_size(iTCO_wdt_private.gcs_pmc_res)); } iTCO_wdt_private.tco_res = NULL; iTCO_wdt_private.smi_res = NULL; - iTCO_wdt_private.gcs_res = NULL; - iTCO_wdt_private.gcs = NULL; + iTCO_wdt_private.gcs_pmc_res = NULL; + iTCO_wdt_private.gcs_pmc = NULL; } static int iTCO_wdt_probe(struct platform_device *dev) { int ret = -ENODEV; unsigned long val32; - struct lpc_ich_info *ich_info = dev->dev.platform_data; + struct itco_wdt_platform_data *pdata = dev_get_platdata(&dev->dev); - if (!ich_info) + if (!pdata) goto out; spin_lock_init(&iTCO_wdt_private.io_lock); @@ -411,32 +442,32 @@ if (!iTCO_wdt_private.smi_res) goto out; - iTCO_wdt_private.iTCO_version = ich_info->iTCO_version; + iTCO_wdt_private.iTCO_version = pdata->version; iTCO_wdt_private.dev = dev; iTCO_wdt_private.pdev = to_pci_dev(dev->dev.parent); /* - * Get the Memory-Mapped GCS register, we need it for the - * NO_REBOOT flag (TCO v2). + * Get the Memory-Mapped GCS or PMC register, we need it for the + * NO_REBOOT flag (TCO v2 and v3). */ - if (iTCO_wdt_private.iTCO_version == 2) { - iTCO_wdt_private.gcs_res = platform_get_resource(dev, + if (iTCO_wdt_private.iTCO_version >= 2) { + iTCO_wdt_private.gcs_pmc_res = platform_get_resource(dev, IORESOURCE_MEM, - ICH_RES_MEM_GCS); + ICH_RES_MEM_GCS_PMC); - if (!iTCO_wdt_private.gcs_res) + if (!iTCO_wdt_private.gcs_pmc_res) goto out; - if (!request_mem_region(iTCO_wdt_private.gcs_res->start, - resource_size(iTCO_wdt_private.gcs_res), dev->name)) { + if (!request_mem_region(iTCO_wdt_private.gcs_pmc_res->start, + resource_size(iTCO_wdt_private.gcs_pmc_res), dev->name)) { ret = -EBUSY; goto out; } - iTCO_wdt_private.gcs = ioremap(iTCO_wdt_private.gcs_res->start, - resource_size(iTCO_wdt_private.gcs_res)); - if (!iTCO_wdt_private.gcs) { + iTCO_wdt_private.gcs_pmc = ioremap(iTCO_wdt_private.gcs_pmc_res->start, + resource_size(iTCO_wdt_private.gcs_pmc_res)); + if (!iTCO_wdt_private.gcs_pmc) { ret = -EIO; - goto unreg_gcs; + goto unreg_gcs_pmc; } } @@ -444,7 +475,7 @@ if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) { pr_info("unable to reset NO_REBOOT flag, device disabled by hardware/BIOS\n"); ret = -ENODEV; /* Cannot reset NO_REBOOT bit */ - goto unmap_gcs; + goto unmap_gcs_pmc; } /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ @@ -456,7 +487,7 @@ pr_err("I/O address 0x%04llx already in use, device disabled\n", (u64)SMI_EN); ret = -EBUSY; - goto unmap_gcs; + goto unmap_gcs_pmc; } if (turn_SMI_watchdog_clear_off >= iTCO_wdt_private.iTCO_version) { /* @@ -477,17 +508,30 @@ } pr_info("Found a %s TCO device (Version=%d, TCOBASE=0x%04llx)\n", - ich_info->name, ich_info->iTCO_version, (u64)TCOBASE); + pdata->name, pdata->version, (u64)TCOBASE); /* Clear out the (probably old) status */ - outw(0x0008, TCO1_STS); /* Clear the Time Out Status bit */ - outw(0x0002, TCO2_STS); /* Clear SECOND_TO_STS bit */ - outw(0x0004, TCO2_STS); /* Clear BOOT_STS bit */ + switch (iTCO_wdt_private.iTCO_version) { + case 4: + outw(0x0008, TCO1_STS); /* Clear the Time Out Status bit */ + outw(0x0002, TCO2_STS); /* Clear SECOND_TO_STS bit */ + break; + case 3: + outl(0x20008, TCO1_STS); + break; + case 2: + case 1: + default: + outw(0x0008, TCO1_STS); /* Clear the Time Out Status bit */ + outw(0x0002, TCO2_STS); /* Clear SECOND_TO_STS bit */ + outw(0x0004, TCO2_STS); /* Clear BOOT_STS bit */ + break; + } iTCO_wdt_watchdog_dev.bootstatus = 0; iTCO_wdt_watchdog_dev.timeout = WATCHDOG_TIMEOUT; watchdog_set_nowayout(&iTCO_wdt_watchdog_dev, nowayout); - iTCO_wdt_watchdog_dev.parent = dev->dev.parent; + iTCO_wdt_watchdog_dev.parent = &dev->dev; /* Make sure the watchdog is not running */ iTCO_wdt_stop(&iTCO_wdt_watchdog_dev); @@ -517,18 +561,18 @@ unreg_smi: release_region(iTCO_wdt_private.smi_res->start, resource_size(iTCO_wdt_private.smi_res)); -unmap_gcs: - if (iTCO_wdt_private.iTCO_version == 2) - iounmap(iTCO_wdt_private.gcs); -unreg_gcs: - if (iTCO_wdt_private.iTCO_version == 2) - release_mem_region(iTCO_wdt_private.gcs_res->start, - resource_size(iTCO_wdt_private.gcs_res)); +unmap_gcs_pmc: + if (iTCO_wdt_private.iTCO_version >= 2) + iounmap(iTCO_wdt_private.gcs_pmc); +unreg_gcs_pmc: + if (iTCO_wdt_private.iTCO_version >= 2) + release_mem_region(iTCO_wdt_private.gcs_pmc_res->start, + resource_size(iTCO_wdt_private.gcs_pmc_res)); out: iTCO_wdt_private.tco_res = NULL; iTCO_wdt_private.smi_res = NULL; - iTCO_wdt_private.gcs_res = NULL; - iTCO_wdt_private.gcs = NULL; + iTCO_wdt_private.gcs_pmc_res = NULL; + iTCO_wdt_private.gcs_pmc = NULL; return ret; } @@ -546,13 +590,60 @@ iTCO_wdt_stop(NULL); } +#ifdef CONFIG_PM_SLEEP +/* + * Suspend-to-idle requires this, because it stops the ticks and timekeeping, so + * the watchdog cannot be pinged while in that state. In ACPI sleep states the + * watchdog is stopped by the platform firmware. + */ + +#ifdef CONFIG_ACPI +static inline bool need_suspend(void) +{ + return acpi_target_system_state() == ACPI_STATE_S0; +} +#else +static inline bool need_suspend(void) { return true; } +#endif + +static int iTCO_wdt_suspend_noirq(struct device *dev) +{ + int ret = 0; + + iTCO_wdt_private.suspended = false; + if (watchdog_active(&iTCO_wdt_watchdog_dev) && need_suspend()) { + ret = iTCO_wdt_stop(&iTCO_wdt_watchdog_dev); + if (!ret) + iTCO_wdt_private.suspended = true; + } + return ret; +} + +static int iTCO_wdt_resume_noirq(struct device *dev) +{ + if (iTCO_wdt_private.suspended) + iTCO_wdt_start(&iTCO_wdt_watchdog_dev); + + return 0; +} + +static struct dev_pm_ops iTCO_wdt_pm = { + .suspend_noirq = iTCO_wdt_suspend_noirq, + .resume_noirq = iTCO_wdt_resume_noirq, +}; + +#define ITCO_WDT_PM_OPS (&iTCO_wdt_pm) +#else +#define ITCO_WDT_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + static struct platform_driver iTCO_wdt_driver = { .probe = iTCO_wdt_probe, .remove = iTCO_wdt_remove, .shutdown = iTCO_wdt_shutdown, .driver = { - .owner = THIS_MODULE, .name = DRV_NAME, + .pm = ITCO_WDT_PM_OPS, }, }; @@ -582,5 +673,4 @@ MODULE_DESCRIPTION("Intel TCO WatchDog Timer Driver"); MODULE_VERSION(DRV_VERSION); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); MODULE_ALIAS("platform:" DRV_NAME);