From c5fbeec738994fff87795eba74b510541d16270d Mon Sep 17 00:00:00 2001 From: Lewis Kang Date: Mon, 14 Mar 2016 10:16:21 +0800 Subject: [PATCH] support hardware watchdog for Intel Rangely CPU --- .../configs/x86_64-all/x86_64-all.config | 55 +- .../patches/driver-mfd-lpc-ich.patch | 1190 +++++++++ .../patches/driver-watchdog-itco-wd.patch | 2150 +++++++++++++++++ .../kernels/3.2.65-1+deb7u2/patches/series | 2 + 4 files changed, 3396 insertions(+), 1 deletion(-) create mode 100644 packages/base/any/kernels/3.2.65-1+deb7u2/patches/driver-mfd-lpc-ich.patch create mode 100644 packages/base/any/kernels/3.2.65-1+deb7u2/patches/driver-watchdog-itco-wd.patch diff --git a/packages/base/any/kernels/3.2.65-1+deb7u2/configs/x86_64-all/x86_64-all.config b/packages/base/any/kernels/3.2.65-1+deb7u2/configs/x86_64-all/x86_64-all.config index 779308ac..424fc652 100644 --- a/packages/base/any/kernels/3.2.65-1+deb7u2/configs/x86_64-all/x86_64-all.config +++ b/packages/base/any/kernels/3.2.65-1+deb7u2/configs/x86_64-all/x86_64-all.config @@ -1935,7 +1935,59 @@ CONFIG_SENSORS_YM2651Y=y # CONFIG_SENSORS_ATK0110 is not set CONFIG_THERMAL=y CONFIG_THERMAL_HWMON=y -# CONFIG_WATCHDOG is not set +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_CORE=y +# CONFIG_WATCHDOG_NOWAYOUT is not set + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +# CONFIG_ACQUIRE_WDT is not set +# CONFIG_ADVANTECH_WDT is not set +# CONFIG_ALIM1535_WDT is not set +# CONFIG_ALIM7101_WDT is not set +# CONFIG_F71808E_WDT is not set +# CONFIG_SP5100_TCO is not set +# CONFIG_SC520_WDT is not set +# CONFIG_SBC_FITPC2_WATCHDOG is not set +# CONFIG_EUROTECH_WDT is not set +# CONFIG_IB700_WDT is not set +# CONFIG_IBMASR is not set +# CONFIG_WAFER_WDT is not set +# CONFIG_I6300ESB_WDT is not set +CONFIG_ITCO_WDT=y +# CONFIG_ITCO_VENDOR_SUPPORT is not set +# CONFIG_IT8712F_WDT is not set +# CONFIG_IT87_WDT is not set +# CONFIG_HP_WATCHDOG is not set +# CONFIG_SC1200_WDT is not set +# CONFIG_PC87413_WDT is not set +# CONFIG_NV_TCO is not set +# CONFIG_60XX_WDT is not set +# CONFIG_SBC8360_WDT is not set +# CONFIG_CPU5_WDT is not set +# CONFIG_SMSC_SCH311X_WDT is not set +# CONFIG_SMSC37B787_WDT is not set +# CONFIG_W83627HF_WDT is not set +# CONFIG_W83697HF_WDT is not set +# CONFIG_W83697UG_WDT is not set +# CONFIG_W83877F_WDT is not set +# CONFIG_W83977F_WDT is not set +# CONFIG_MACHZ_WDT is not set +# CONFIG_SBC_EPX_C3_WATCHDOG is not set +# CONFIG_XEN_WDT is not set + +# +# PCI-based Watchdog Cards +# +# CONFIG_PCIPCWATCHDOG is not set +# CONFIG_WDTPCI is not set + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set CONFIG_SSB_POSSIBLE=y # @@ -1999,6 +2051,7 @@ CONFIG_MFD_CORE=y # CONFIG_EZX_PCAP is not set # CONFIG_MFD_CS5535 is not set # CONFIG_MFD_TIMBERDALE is not set +CONFIG_LPC_ICH=y CONFIG_LPC_SCH=y # CONFIG_MFD_RDC321X is not set # CONFIG_MFD_JANZ_CMODIO is not set diff --git a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/driver-mfd-lpc-ich.patch b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/driver-mfd-lpc-ich.patch new file mode 100644 index 00000000..ce555d14 --- /dev/null +++ b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/driver-mfd-lpc-ich.patch @@ -0,0 +1,1190 @@ +Add LPC interface for Intel ICH + +diff --git a/drivers/mfd/lpc_ich.c b/drivers/mfd/lpc_ich.c +new file mode 100644 +index 0000000..c58fc62 +--- /dev/null ++++ b/drivers/mfd/lpc_ich.c +@@ -0,0 +1,1091 @@ ++/* ++ * lpc_ich.c - LPC interface for Intel ICH ++ * ++ * LPC bridge function of the Intel ICH contains many other ++ * functional units, such as Interrupt controllers, Timers, ++ * Power Management, System Management, GPIO, RTC, and LPC ++ * Configuration Registers. ++ * ++ * This driver is derived from lpc_sch. ++ ++ * Copyright (c) 2011 Extreme Engineering Solution, Inc. ++ * Author: Aaron Sierra ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License 2 as published ++ * by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; see the file COPYING. If not, write to ++ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. ++ * ++ * This driver supports the following I/O Controller hubs: ++ * (See the intel documentation on http://developer.intel.com.) ++ * document number 290655-003, 290677-014: 82801AA (ICH), 82801AB (ICHO) ++ * document number 290687-002, 298242-027: 82801BA (ICH2) ++ * document number 290733-003, 290739-013: 82801CA (ICH3-S) ++ * document number 290716-001, 290718-007: 82801CAM (ICH3-M) ++ * document number 290744-001, 290745-025: 82801DB (ICH4) ++ * document number 252337-001, 252663-008: 82801DBM (ICH4-M) ++ * document number 273599-001, 273645-002: 82801E (C-ICH) ++ * document number 252516-001, 252517-028: 82801EB (ICH5), 82801ER (ICH5R) ++ * document number 300641-004, 300884-013: 6300ESB ++ * document number 301473-002, 301474-026: 82801F (ICH6) ++ * document number 313082-001, 313075-006: 631xESB, 632xESB ++ * document number 307013-003, 307014-024: 82801G (ICH7) ++ * document number 322896-001, 322897-001: NM10 ++ * document number 313056-003, 313057-017: 82801H (ICH8) ++ * document number 316972-004, 316973-012: 82801I (ICH9) ++ * document number 319973-002, 319974-002: 82801J (ICH10) ++ * document number 322169-001, 322170-003: 5 Series, 3400 Series (PCH) ++ * document number 320066-003, 320257-008: EP80597 (IICH) ++ * document number 324645-001, 324646-001: Cougar Point (CPT) ++ * document number TBD : Patsburg (PBG) ++ * document number TBD : DH89xxCC ++ * document number TBD : Panther Point ++ * document number TBD : Lynx Point ++ * document number TBD : Lynx Point-LP ++ * document number TBD : Wellsburg ++ * document number TBD : Avoton SoC ++ * document number TBD : Coleto Creek ++ * document number TBD : Wildcat Point-LP ++ * document number TBD : 9 Series ++ */ ++ ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define ACPIBASE 0x40 ++#define ACPIBASE_GPE_OFF 0x28 ++#define ACPIBASE_GPE_END 0x2f ++#define ACPIBASE_SMI_OFF 0x30 ++#define ACPIBASE_SMI_END 0x33 ++#define ACPIBASE_PMC_OFF 0x08 ++#define ACPIBASE_PMC_END 0x0c ++#define ACPIBASE_TCO_OFF 0x60 ++#define ACPIBASE_TCO_END 0x7f ++#define ACPICTRL_PMCBASE 0x44 ++ ++#define ACPIBASE_GCS_OFF 0x3410 ++#define ACPIBASE_GCS_END 0x3414 ++ ++#define GPIOBASE_ICH0 0x58 ++#define GPIOCTRL_ICH0 0x5C ++#define GPIOBASE_ICH6 0x48 ++#define GPIOCTRL_ICH6 0x4C ++ ++#define RCBABASE 0xf0 ++ ++#define wdt_io_res(i) wdt_res(0, i) ++#define wdt_mem_res(i) wdt_res(ICH_RES_MEM_OFF, i) ++#define wdt_res(b, i) (&wdt_ich_res[(b) + (i)]) ++ ++struct lpc_ich_priv { ++ int chipset; ++ ++ int abase; /* ACPI base */ ++ int actrl_pbase; /* ACPI control or PMC base */ ++ int gbase; /* GPIO base */ ++ int gctrl; /* GPIO control */ ++ ++ int abase_save; /* Cached ACPI base value */ ++ int actrl_pbase_save; /* Cached ACPI control or PMC base value */ ++ int gctrl_save; /* Cached GPIO control value */ ++}; ++ ++static struct resource wdt_ich_res[] = { ++ /* ACPI - TCO */ ++ { ++ .flags = IORESOURCE_IO, ++ }, ++ /* ACPI - SMI */ ++ { ++ .flags = IORESOURCE_IO, ++ }, ++ /* GCS or PMC */ ++ { ++ .flags = IORESOURCE_MEM, ++ }, ++}; ++ ++static struct resource gpio_ich_res[] = { ++ /* GPIO */ ++ { ++ .flags = IORESOURCE_IO, ++ }, ++ /* ACPI - GPE0 */ ++ { ++ .flags = IORESOURCE_IO, ++ }, ++}; ++ ++enum lpc_cells { ++ LPC_WDT = 0, ++ LPC_GPIO, ++}; ++ ++static struct mfd_cell lpc_ich_cells[] = { ++ [LPC_WDT] = { ++ .name = "iTCO_wdt", ++ .num_resources = ARRAY_SIZE(wdt_ich_res), ++ .resources = wdt_ich_res, ++ .ignore_resource_conflicts = true, ++ }, ++ [LPC_GPIO] = { ++ .name = "gpio_ich", ++ .num_resources = ARRAY_SIZE(gpio_ich_res), ++ .resources = gpio_ich_res, ++ .ignore_resource_conflicts = true, ++ }, ++}; ++ ++/* chipset related info */ ++enum lpc_chipsets { ++ LPC_ICH = 0, /* ICH */ ++ LPC_ICH0, /* ICH0 */ ++ LPC_ICH2, /* ICH2 */ ++ LPC_ICH2M, /* ICH2-M */ ++ LPC_ICH3, /* ICH3-S */ ++ LPC_ICH3M, /* ICH3-M */ ++ LPC_ICH4, /* ICH4 */ ++ LPC_ICH4M, /* ICH4-M */ ++ LPC_CICH, /* C-ICH */ ++ LPC_ICH5, /* ICH5 & ICH5R */ ++ LPC_6300ESB, /* 6300ESB */ ++ LPC_ICH6, /* ICH6 & ICH6R */ ++ LPC_ICH6M, /* ICH6-M */ ++ LPC_ICH6W, /* ICH6W & ICH6RW */ ++ LPC_631XESB, /* 631xESB/632xESB */ ++ LPC_ICH7, /* ICH7 & ICH7R */ ++ LPC_ICH7DH, /* ICH7DH */ ++ LPC_ICH7M, /* ICH7-M & ICH7-U */ ++ LPC_ICH7MDH, /* ICH7-M DH */ ++ LPC_NM10, /* NM10 */ ++ LPC_ICH8, /* ICH8 & ICH8R */ ++ LPC_ICH8DH, /* ICH8DH */ ++ LPC_ICH8DO, /* ICH8DO */ ++ LPC_ICH8M, /* ICH8M */ ++ LPC_ICH8ME, /* ICH8M-E */ ++ LPC_ICH9, /* ICH9 */ ++ LPC_ICH9R, /* ICH9R */ ++ LPC_ICH9DH, /* ICH9DH */ ++ LPC_ICH9DO, /* ICH9DO */ ++ LPC_ICH9M, /* ICH9M */ ++ LPC_ICH9ME, /* ICH9M-E */ ++ LPC_ICH10, /* ICH10 */ ++ LPC_ICH10R, /* ICH10R */ ++ LPC_ICH10D, /* ICH10D */ ++ LPC_ICH10DO, /* ICH10DO */ ++ LPC_PCH, /* PCH Desktop Full Featured */ ++ LPC_PCHM, /* PCH Mobile Full Featured */ ++ LPC_P55, /* P55 */ ++ LPC_PM55, /* PM55 */ ++ LPC_H55, /* H55 */ ++ LPC_QM57, /* QM57 */ ++ LPC_H57, /* H57 */ ++ LPC_HM55, /* HM55 */ ++ LPC_Q57, /* Q57 */ ++ LPC_HM57, /* HM57 */ ++ LPC_PCHMSFF, /* PCH Mobile SFF Full Featured */ ++ LPC_QS57, /* QS57 */ ++ LPC_3400, /* 3400 */ ++ LPC_3420, /* 3420 */ ++ LPC_3450, /* 3450 */ ++ LPC_EP80579, /* EP80579 */ ++ LPC_CPT, /* Cougar Point */ ++ LPC_CPTD, /* Cougar Point Desktop */ ++ LPC_CPTM, /* Cougar Point Mobile */ ++ LPC_PBG, /* Patsburg */ ++ LPC_DH89XXCC, /* DH89xxCC */ ++ LPC_PPT, /* Panther Point */ ++ LPC_LPT, /* Lynx Point */ ++ LPC_LPT_LP, /* Lynx Point-LP */ ++ LPC_WBG, /* Wellsburg */ ++ LPC_AVN, /* Avoton SoC */ ++ LPC_BAYTRAIL, /* Bay Trail SoC */ ++ LPC_COLETO, /* Coleto Creek */ ++ LPC_WPT_LP, /* Wildcat Point-LP */ ++ LPC_BRASWELL, /* Braswell SoC */ ++ LPC_9S, /* 9 Series */ ++}; ++ ++static struct lpc_ich_info lpc_chipset_info[] = { ++ [LPC_ICH] = { ++ .name = "ICH", ++ .iTCO_version = 1, ++ }, ++ [LPC_ICH0] = { ++ .name = "ICH0", ++ .iTCO_version = 1, ++ }, ++ [LPC_ICH2] = { ++ .name = "ICH2", ++ .iTCO_version = 1, ++ }, ++ [LPC_ICH2M] = { ++ .name = "ICH2-M", ++ .iTCO_version = 1, ++ }, ++ [LPC_ICH3] = { ++ .name = "ICH3-S", ++ .iTCO_version = 1, ++ }, ++ [LPC_ICH3M] = { ++ .name = "ICH3-M", ++ .iTCO_version = 1, ++ }, ++ [LPC_ICH4] = { ++ .name = "ICH4", ++ .iTCO_version = 1, ++ }, ++ [LPC_ICH4M] = { ++ .name = "ICH4-M", ++ .iTCO_version = 1, ++ }, ++ [LPC_CICH] = { ++ .name = "C-ICH", ++ .iTCO_version = 1, ++ }, ++ [LPC_ICH5] = { ++ .name = "ICH5 or ICH5R", ++ .iTCO_version = 1, ++ }, ++ [LPC_6300ESB] = { ++ .name = "6300ESB", ++ .iTCO_version = 1, ++ }, ++ [LPC_ICH6] = { ++ .name = "ICH6 or ICH6R", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V6_GPIO, ++ }, ++ [LPC_ICH6M] = { ++ .name = "ICH6-M", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V6_GPIO, ++ }, ++ [LPC_ICH6W] = { ++ .name = "ICH6W or ICH6RW", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V6_GPIO, ++ }, ++ [LPC_631XESB] = { ++ .name = "631xESB/632xESB", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V6_GPIO, ++ }, ++ [LPC_ICH7] = { ++ .name = "ICH7 or ICH7R", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V7_GPIO, ++ }, ++ [LPC_ICH7DH] = { ++ .name = "ICH7DH", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V7_GPIO, ++ }, ++ [LPC_ICH7M] = { ++ .name = "ICH7-M or ICH7-U", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V7_GPIO, ++ }, ++ [LPC_ICH7MDH] = { ++ .name = "ICH7-M DH", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V7_GPIO, ++ }, ++ [LPC_NM10] = { ++ .name = "NM10", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V7_GPIO, ++ }, ++ [LPC_ICH8] = { ++ .name = "ICH8 or ICH8R", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V7_GPIO, ++ }, ++ [LPC_ICH8DH] = { ++ .name = "ICH8DH", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V7_GPIO, ++ }, ++ [LPC_ICH8DO] = { ++ .name = "ICH8DO", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V7_GPIO, ++ }, ++ [LPC_ICH8M] = { ++ .name = "ICH8M", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V7_GPIO, ++ }, ++ [LPC_ICH8ME] = { ++ .name = "ICH8M-E", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V7_GPIO, ++ }, ++ [LPC_ICH9] = { ++ .name = "ICH9", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V9_GPIO, ++ }, ++ [LPC_ICH9R] = { ++ .name = "ICH9R", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V9_GPIO, ++ }, ++ [LPC_ICH9DH] = { ++ .name = "ICH9DH", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V9_GPIO, ++ }, ++ [LPC_ICH9DO] = { ++ .name = "ICH9DO", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V9_GPIO, ++ }, ++ [LPC_ICH9M] = { ++ .name = "ICH9M", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V9_GPIO, ++ }, ++ [LPC_ICH9ME] = { ++ .name = "ICH9M-E", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V9_GPIO, ++ }, ++ [LPC_ICH10] = { ++ .name = "ICH10", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V10CONS_GPIO, ++ }, ++ [LPC_ICH10R] = { ++ .name = "ICH10R", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V10CONS_GPIO, ++ }, ++ [LPC_ICH10D] = { ++ .name = "ICH10D", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V10CORP_GPIO, ++ }, ++ [LPC_ICH10DO] = { ++ .name = "ICH10DO", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V10CORP_GPIO, ++ }, ++ [LPC_PCH] = { ++ .name = "PCH Desktop Full Featured", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_PCHM] = { ++ .name = "PCH Mobile Full Featured", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_P55] = { ++ .name = "P55", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_PM55] = { ++ .name = "PM55", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_H55] = { ++ .name = "H55", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_QM57] = { ++ .name = "QM57", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_H57] = { ++ .name = "H57", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_HM55] = { ++ .name = "HM55", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_Q57] = { ++ .name = "Q57", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_HM57] = { ++ .name = "HM57", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_PCHMSFF] = { ++ .name = "PCH Mobile SFF Full Featured", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_QS57] = { ++ .name = "QS57", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_3400] = { ++ .name = "3400", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_3420] = { ++ .name = "3420", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_3450] = { ++ .name = "3450", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_EP80579] = { ++ .name = "EP80579", ++ .iTCO_version = 2, ++ }, ++ [LPC_CPT] = { ++ .name = "Cougar Point", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_CPTD] = { ++ .name = "Cougar Point Desktop", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_CPTM] = { ++ .name = "Cougar Point Mobile", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_PBG] = { ++ .name = "Patsburg", ++ .iTCO_version = 2, ++ }, ++ [LPC_DH89XXCC] = { ++ .name = "DH89xxCC", ++ .iTCO_version = 2, ++ }, ++ [LPC_PPT] = { ++ .name = "Panther Point", ++ .iTCO_version = 2, ++ .gpio_version = ICH_V5_GPIO, ++ }, ++ [LPC_LPT] = { ++ .name = "Lynx Point", ++ .iTCO_version = 2, ++ }, ++ [LPC_LPT_LP] = { ++ .name = "Lynx Point_LP", ++ .iTCO_version = 2, ++ }, ++ [LPC_WBG] = { ++ .name = "Wellsburg", ++ .iTCO_version = 2, ++ }, ++ [LPC_AVN] = { ++ .name = "Avoton SoC", ++ .iTCO_version = 3, ++ .gpio_version = AVOTON_GPIO, ++ }, ++ [LPC_BAYTRAIL] = { ++ .name = "Bay Trail SoC", ++ .iTCO_version = 3, ++ }, ++ [LPC_COLETO] = { ++ .name = "Coleto Creek", ++ .iTCO_version = 2, ++ }, ++ [LPC_WPT_LP] = { ++ .name = "Wildcat Point_LP", ++ .iTCO_version = 2, ++ }, ++ [LPC_BRASWELL] = { ++ .name = "Braswell SoC", ++ .iTCO_version = 3, ++ }, ++ [LPC_9S] = { ++ .name = "9 Series", ++ .iTCO_version = 2, ++ }, ++}; ++ ++/* ++ * This data only exists for exporting the supported PCI ids ++ * via MODULE_DEVICE_TABLE. We do not actually register a ++ * pci_driver, because the I/O Controller Hub has also other ++ * functions that probably will be registered by other drivers. ++ */ ++static const struct pci_device_id lpc_ich_ids[] = { ++ { PCI_VDEVICE(INTEL, 0x2410), LPC_ICH}, ++ { PCI_VDEVICE(INTEL, 0x2420), LPC_ICH0}, ++ { PCI_VDEVICE(INTEL, 0x2440), LPC_ICH2}, ++ { PCI_VDEVICE(INTEL, 0x244c), LPC_ICH2M}, ++ { PCI_VDEVICE(INTEL, 0x2480), LPC_ICH3}, ++ { PCI_VDEVICE(INTEL, 0x248c), LPC_ICH3M}, ++ { PCI_VDEVICE(INTEL, 0x24c0), LPC_ICH4}, ++ { PCI_VDEVICE(INTEL, 0x24cc), LPC_ICH4M}, ++ { PCI_VDEVICE(INTEL, 0x2450), LPC_CICH}, ++ { PCI_VDEVICE(INTEL, 0x24d0), LPC_ICH5}, ++ { PCI_VDEVICE(INTEL, 0x25a1), LPC_6300ESB}, ++ { PCI_VDEVICE(INTEL, 0x2640), LPC_ICH6}, ++ { PCI_VDEVICE(INTEL, 0x2641), LPC_ICH6M}, ++ { PCI_VDEVICE(INTEL, 0x2642), LPC_ICH6W}, ++ { PCI_VDEVICE(INTEL, 0x2670), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x2671), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x2672), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x2673), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x2674), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x2675), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x2676), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x2677), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x2678), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x2679), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x267a), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x267b), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x267c), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x267d), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x267e), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x267f), LPC_631XESB}, ++ { PCI_VDEVICE(INTEL, 0x27b8), LPC_ICH7}, ++ { PCI_VDEVICE(INTEL, 0x27b0), LPC_ICH7DH}, ++ { PCI_VDEVICE(INTEL, 0x27b9), LPC_ICH7M}, ++ { PCI_VDEVICE(INTEL, 0x27bd), LPC_ICH7MDH}, ++ { PCI_VDEVICE(INTEL, 0x27bc), LPC_NM10}, ++ { PCI_VDEVICE(INTEL, 0x2810), LPC_ICH8}, ++ { PCI_VDEVICE(INTEL, 0x2812), LPC_ICH8DH}, ++ { PCI_VDEVICE(INTEL, 0x2814), LPC_ICH8DO}, ++ { PCI_VDEVICE(INTEL, 0x2815), LPC_ICH8M}, ++ { PCI_VDEVICE(INTEL, 0x2811), LPC_ICH8ME}, ++ { PCI_VDEVICE(INTEL, 0x2918), LPC_ICH9}, ++ { PCI_VDEVICE(INTEL, 0x2916), LPC_ICH9R}, ++ { PCI_VDEVICE(INTEL, 0x2912), LPC_ICH9DH}, ++ { PCI_VDEVICE(INTEL, 0x2914), LPC_ICH9DO}, ++ { PCI_VDEVICE(INTEL, 0x2919), LPC_ICH9M}, ++ { PCI_VDEVICE(INTEL, 0x2917), LPC_ICH9ME}, ++ { PCI_VDEVICE(INTEL, 0x3a18), LPC_ICH10}, ++ { PCI_VDEVICE(INTEL, 0x3a16), LPC_ICH10R}, ++ { PCI_VDEVICE(INTEL, 0x3a1a), LPC_ICH10D}, ++ { PCI_VDEVICE(INTEL, 0x3a14), LPC_ICH10DO}, ++ { PCI_VDEVICE(INTEL, 0x3b00), LPC_PCH}, ++ { PCI_VDEVICE(INTEL, 0x3b01), LPC_PCHM}, ++ { PCI_VDEVICE(INTEL, 0x3b02), LPC_P55}, ++ { PCI_VDEVICE(INTEL, 0x3b03), LPC_PM55}, ++ { PCI_VDEVICE(INTEL, 0x3b06), LPC_H55}, ++ { PCI_VDEVICE(INTEL, 0x3b07), LPC_QM57}, ++ { PCI_VDEVICE(INTEL, 0x3b08), LPC_H57}, ++ { PCI_VDEVICE(INTEL, 0x3b09), LPC_HM55}, ++ { PCI_VDEVICE(INTEL, 0x3b0a), LPC_Q57}, ++ { PCI_VDEVICE(INTEL, 0x3b0b), LPC_HM57}, ++ { PCI_VDEVICE(INTEL, 0x3b0d), LPC_PCHMSFF}, ++ { PCI_VDEVICE(INTEL, 0x3b0f), LPC_QS57}, ++ { PCI_VDEVICE(INTEL, 0x3b12), LPC_3400}, ++ { PCI_VDEVICE(INTEL, 0x3b14), LPC_3420}, ++ { PCI_VDEVICE(INTEL, 0x3b16), LPC_3450}, ++ { PCI_VDEVICE(INTEL, 0x5031), LPC_EP80579}, ++ { PCI_VDEVICE(INTEL, 0x1c41), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c42), LPC_CPTD}, ++ { PCI_VDEVICE(INTEL, 0x1c43), LPC_CPTM}, ++ { PCI_VDEVICE(INTEL, 0x1c44), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c45), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c46), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c47), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c48), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c49), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c4a), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c4b), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c4c), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c4d), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c4e), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c4f), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c50), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c51), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c52), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c53), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c54), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c55), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c56), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c57), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c58), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c59), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c5a), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c5b), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c5c), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c5d), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c5e), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1c5f), LPC_CPT}, ++ { PCI_VDEVICE(INTEL, 0x1d40), LPC_PBG}, ++ { PCI_VDEVICE(INTEL, 0x1d41), LPC_PBG}, ++ { PCI_VDEVICE(INTEL, 0x2310), LPC_DH89XXCC}, ++ { PCI_VDEVICE(INTEL, 0x1e40), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e41), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e42), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e43), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e44), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e45), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e46), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e47), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e48), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e49), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e4a), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e4b), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e4c), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e4d), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e4e), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e4f), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e50), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e51), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e52), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e53), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e54), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e55), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e56), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e57), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e58), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e59), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e5a), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e5b), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e5c), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e5d), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e5e), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x1e5f), LPC_PPT}, ++ { PCI_VDEVICE(INTEL, 0x8c40), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c41), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c42), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c43), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c44), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c45), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c46), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c47), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c48), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c49), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c4a), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c4b), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c4c), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c4d), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c4e), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c4f), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c50), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c51), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c52), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c53), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c54), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c55), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c56), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c57), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c58), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c59), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c5a), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c5b), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c5c), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c5d), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c5e), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x8c5f), LPC_LPT}, ++ { PCI_VDEVICE(INTEL, 0x9c40), LPC_LPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9c41), LPC_LPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9c42), LPC_LPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9c43), LPC_LPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9c44), LPC_LPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9c45), LPC_LPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9c46), LPC_LPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9c47), LPC_LPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x8d40), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d41), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d42), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d43), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d44), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d45), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d46), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d47), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d48), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d49), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d4a), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d4b), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d4c), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d4d), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d4e), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d4f), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d50), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d51), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d52), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d53), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d54), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d55), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d56), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d57), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d58), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d59), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d5a), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d5b), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d5c), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d5d), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d5e), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x8d5f), LPC_WBG}, ++ { PCI_VDEVICE(INTEL, 0x1f38), LPC_AVN}, ++ { PCI_VDEVICE(INTEL, 0x1f39), LPC_AVN}, ++ { PCI_VDEVICE(INTEL, 0x1f3a), LPC_AVN}, ++ { PCI_VDEVICE(INTEL, 0x1f3b), LPC_AVN}, ++ { PCI_VDEVICE(INTEL, 0x0f1c), LPC_BAYTRAIL}, ++ { PCI_VDEVICE(INTEL, 0x2390), LPC_COLETO}, ++ { PCI_VDEVICE(INTEL, 0x9cc1), LPC_WPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9cc2), LPC_WPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9cc3), LPC_WPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9cc5), LPC_WPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9cc6), LPC_WPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9cc7), LPC_WPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x9cc9), LPC_WPT_LP}, ++ { PCI_VDEVICE(INTEL, 0x229c), LPC_BRASWELL}, ++ { PCI_VDEVICE(INTEL, 0x8cc1), LPC_9S}, ++ { PCI_VDEVICE(INTEL, 0x8cc2), LPC_9S}, ++ { PCI_VDEVICE(INTEL, 0x8cc3), LPC_9S}, ++ { PCI_VDEVICE(INTEL, 0x8cc4), LPC_9S}, ++ { PCI_VDEVICE(INTEL, 0x8cc6), LPC_9S}, ++ { 0, }, /* End of list */ ++}; ++MODULE_DEVICE_TABLE(pci, lpc_ich_ids); ++ ++static void lpc_ich_restore_config_space(struct pci_dev *dev) ++{ ++ struct lpc_ich_priv *priv = pci_get_drvdata(dev); ++ ++ if (priv->abase_save >= 0) { ++ pci_write_config_byte(dev, priv->abase, priv->abase_save); ++ priv->abase_save = -1; ++ } ++ ++ if (priv->actrl_pbase_save >= 0) { ++ pci_write_config_byte(dev, priv->actrl_pbase, ++ priv->actrl_pbase_save); ++ priv->actrl_pbase_save = -1; ++ } ++ ++ if (priv->gctrl_save >= 0) { ++ pci_write_config_byte(dev, priv->gctrl, priv->gctrl_save); ++ priv->gctrl_save = -1; ++ } ++} ++ ++static void lpc_ich_enable_acpi_space(struct pci_dev *dev) ++{ ++ struct lpc_ich_priv *priv = pci_get_drvdata(dev); ++ u8 reg_save; ++ ++ switch (lpc_chipset_info[priv->chipset].iTCO_version) { ++ case 3: ++ /* ++ * Some chipsets (eg Avoton) enable the ACPI space in the ++ * ACPI BASE register. ++ */ ++ pci_read_config_byte(dev, priv->abase, ®_save); ++ pci_write_config_byte(dev, priv->abase, reg_save | 0x2); ++ priv->abase_save = reg_save; ++ break; ++ default: ++ /* ++ * Most chipsets enable the ACPI space in the ACPI control ++ * register. ++ */ ++ pci_read_config_byte(dev, priv->actrl_pbase, ®_save); ++ pci_write_config_byte(dev, priv->actrl_pbase, reg_save | 0x80); ++ priv->actrl_pbase_save = reg_save; ++ break; ++ } ++} ++ ++static void lpc_ich_enable_gpio_space(struct pci_dev *dev) ++{ ++ struct lpc_ich_priv *priv = pci_get_drvdata(dev); ++ u8 reg_save; ++ ++ pci_read_config_byte(dev, priv->gctrl, ®_save); ++ pci_write_config_byte(dev, priv->gctrl, reg_save | 0x10); ++ priv->gctrl_save = reg_save; ++} ++ ++static void lpc_ich_enable_pmc_space(struct pci_dev *dev) ++{ ++ struct lpc_ich_priv *priv = pci_get_drvdata(dev); ++ u8 reg_save; ++ ++ pci_read_config_byte(dev, priv->actrl_pbase, ®_save); ++ pci_write_config_byte(dev, priv->actrl_pbase, reg_save | 0x2); ++ ++ priv->actrl_pbase_save = reg_save; ++} ++ ++static void lpc_ich_finalize_cell(struct pci_dev *dev, struct mfd_cell *cell) ++{ ++ struct lpc_ich_priv *priv = pci_get_drvdata(dev); ++ ++ cell->platform_data = &lpc_chipset_info[priv->chipset]; ++ cell->pdata_size = sizeof(struct lpc_ich_info); ++} ++ ++/* ++ * We don't check for resource conflict globally. There are 2 or 3 independent ++ * GPIO groups and it's enough to have access to one of these to instantiate ++ * the device. ++ */ ++static int lpc_ich_check_conflict_gpio(struct resource *res) ++{ ++ int ret; ++ u8 use_gpio = 0; ++ ++ if (resource_size(res) >= 0x50 && ++ !acpi_check_region(res->start + 0x40, 0x10, "LPC ICH GPIO3")) ++ use_gpio |= 1 << 2; ++ ++ if (!acpi_check_region(res->start + 0x30, 0x10, "LPC ICH GPIO2")) ++ use_gpio |= 1 << 1; ++ ++ ret = acpi_check_region(res->start + 0x00, 0x30, "LPC ICH GPIO1"); ++ if (!ret) ++ use_gpio |= 1 << 0; ++ ++ return use_gpio ? use_gpio : ret; ++} ++ ++static int lpc_ich_init_gpio(struct pci_dev *dev) ++{ ++ struct lpc_ich_priv *priv = pci_get_drvdata(dev); ++ u32 base_addr_cfg; ++ u32 base_addr; ++ int ret; ++ bool acpi_conflict = false; ++ struct resource *res; ++ ++ /* Setup power management base register */ ++ pci_read_config_dword(dev, priv->abase, &base_addr_cfg); ++ base_addr = base_addr_cfg & 0x0000ff80; ++ if (!base_addr) { ++ dev_notice(&dev->dev, "I/O space for ACPI uninitialized\n"); ++ lpc_ich_cells[LPC_GPIO].num_resources--; ++ goto gpe0_done; ++ } ++ ++ res = &gpio_ich_res[ICH_RES_GPE0]; ++ res->start = base_addr + ACPIBASE_GPE_OFF; ++ res->end = base_addr + ACPIBASE_GPE_END; ++ ret = acpi_check_resource_conflict(res); ++ if (ret) { ++ /* ++ * This isn't fatal for the GPIO, but we have to make sure that ++ * the platform_device subsystem doesn't see this resource ++ * or it will register an invalid region. ++ */ ++ lpc_ich_cells[LPC_GPIO].num_resources--; ++ acpi_conflict = true; ++ } else { ++ lpc_ich_enable_acpi_space(dev); ++ } ++ ++gpe0_done: ++ /* Setup GPIO base register */ ++ pci_read_config_dword(dev, priv->gbase, &base_addr_cfg); ++ base_addr = base_addr_cfg & 0x0000ff80; ++ if (!base_addr) { ++ dev_notice(&dev->dev, "I/O space for GPIO uninitialized\n"); ++ ret = -ENODEV; ++ goto gpio_done; ++ } ++ ++ /* Older devices provide fewer GPIO and have a smaller resource size. */ ++ res = &gpio_ich_res[ICH_RES_GPIO]; ++ res->start = base_addr; ++ switch (lpc_chipset_info[priv->chipset].gpio_version) { ++ case ICH_V5_GPIO: ++ case ICH_V10CORP_GPIO: ++ res->end = res->start + 128 - 1; ++ break; ++ default: ++ res->end = res->start + 64 - 1; ++ break; ++ } ++ ++ ret = lpc_ich_check_conflict_gpio(res); ++ if (ret < 0) { ++ /* this isn't necessarily fatal for the GPIO */ ++ acpi_conflict = true; ++ goto gpio_done; ++ } ++ lpc_chipset_info[priv->chipset].use_gpio = ret; ++ lpc_ich_enable_gpio_space(dev); ++ ++ lpc_ich_finalize_cell(dev, &lpc_ich_cells[LPC_GPIO]); ++ ret = mfd_add_devices(&dev->dev, PLATFORM_DEVID_AUTO, ++ &lpc_ich_cells[LPC_GPIO], 1, NULL, 0); ++ ++gpio_done: ++ if (acpi_conflict) ++ pr_warn("Resource conflict(s) found affecting %s\n", ++ lpc_ich_cells[LPC_GPIO].name); ++ return ret; ++} ++ ++static int lpc_ich_init_wdt(struct pci_dev *dev) ++{ ++ struct lpc_ich_priv *priv = pci_get_drvdata(dev); ++ u32 base_addr_cfg; ++ u32 base_addr; ++ int ret; ++ struct resource *res; ++ ++ /* Setup power management base register */ ++ pci_read_config_dword(dev, priv->abase, &base_addr_cfg); ++ base_addr = base_addr_cfg & 0x0000ff80; ++ if (!base_addr) { ++ dev_notice(&dev->dev, "I/O space for ACPI uninitialized\n"); ++ ret = -ENODEV; ++ goto wdt_done; ++ } ++ ++ res = wdt_io_res(ICH_RES_IO_TCO); ++ res->start = base_addr + ACPIBASE_TCO_OFF; ++ res->end = base_addr + ACPIBASE_TCO_END; ++ ++ res = wdt_io_res(ICH_RES_IO_SMI); ++ res->start = base_addr + ACPIBASE_SMI_OFF; ++ res->end = base_addr + ACPIBASE_SMI_END; ++ ++ lpc_ich_enable_acpi_space(dev); ++ ++ /* ++ * iTCO v2: ++ * Get the Memory-Mapped GCS register. To get access to it ++ * we have to read RCBA from PCI Config space 0xf0 and use ++ * it as base. GCS = RCBA + ICH6_GCS(0x3410). ++ * ++ * iTCO v3: ++ * Get the Power Management Configuration register. To get access ++ * to it we have to read the PMC BASE from config space and address ++ * the register at offset 0x8. ++ */ ++ if (lpc_chipset_info[priv->chipset].iTCO_version == 1) { ++ /* Don't register iomem for TCO ver 1 */ ++ lpc_ich_cells[LPC_WDT].num_resources--; ++ } else if (lpc_chipset_info[priv->chipset].iTCO_version == 2) { ++ pci_read_config_dword(dev, RCBABASE, &base_addr_cfg); ++ base_addr = base_addr_cfg & 0xffffc000; ++ if (!(base_addr_cfg & 1)) { ++ dev_notice(&dev->dev, "RCBA is disabled by " ++ "hardware/BIOS, device disabled\n"); ++ ret = -ENODEV; ++ goto wdt_done; ++ } ++ res = wdt_mem_res(ICH_RES_MEM_GCS_PMC); ++ res->start = base_addr + ACPIBASE_GCS_OFF; ++ res->end = base_addr + ACPIBASE_GCS_END; ++ } else if (lpc_chipset_info[priv->chipset].iTCO_version == 3) { ++ lpc_ich_enable_pmc_space(dev); ++ pci_read_config_dword(dev, ACPICTRL_PMCBASE, &base_addr_cfg); ++ base_addr = base_addr_cfg & 0xfffffe00; ++ ++ res = wdt_mem_res(ICH_RES_MEM_GCS_PMC); ++ res->start = base_addr + ACPIBASE_PMC_OFF; ++ res->end = base_addr + ACPIBASE_PMC_END; ++ } ++ ++ lpc_ich_finalize_cell(dev, &lpc_ich_cells[LPC_WDT]); ++ ret = mfd_add_devices(&dev->dev, PLATFORM_DEVID_AUTO, ++ &lpc_ich_cells[LPC_WDT], 1, NULL, 0); ++ ++wdt_done: ++ return ret; ++} ++ ++static int lpc_ich_probe(struct pci_dev *dev, ++ const struct pci_device_id *id) ++{ ++ struct lpc_ich_priv *priv; ++ int ret; ++ bool cell_added = false; ++ ++ priv = devm_kzalloc(&dev->dev, ++ sizeof(struct lpc_ich_priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ priv->chipset = id->driver_data; ++ ++ priv->actrl_pbase_save = -1; ++ priv->abase_save = -1; ++ ++ priv->abase = ACPIBASE; ++ priv->actrl_pbase = ACPICTRL_PMCBASE; ++ ++ priv->gctrl_save = -1; ++ if (priv->chipset <= LPC_ICH5) { ++ priv->gbase = GPIOBASE_ICH0; ++ priv->gctrl = GPIOCTRL_ICH0; ++ } else { ++ priv->gbase = GPIOBASE_ICH6; ++ priv->gctrl = GPIOCTRL_ICH6; ++ } ++ ++ pci_set_drvdata(dev, priv); ++ ++ if (lpc_chipset_info[priv->chipset].iTCO_version) { ++ ret = lpc_ich_init_wdt(dev); ++ if (!ret) ++ cell_added = true; ++ } ++ ++ if (lpc_chipset_info[priv->chipset].gpio_version) { ++ ret = lpc_ich_init_gpio(dev); ++ if (!ret) ++ cell_added = true; ++ } ++ ++ /* ++ * We only care if at least one or none of the cells registered ++ * successfully. ++ */ ++ if (!cell_added) { ++ dev_warn(&dev->dev, "No MFD cells added\n"); ++ lpc_ich_restore_config_space(dev); ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++static void lpc_ich_remove(struct pci_dev *dev) ++{ ++ mfd_remove_devices(&dev->dev); ++ lpc_ich_restore_config_space(dev); ++} ++ ++static struct pci_driver lpc_ich_driver = { ++ .name = "lpc_ich", ++ .id_table = lpc_ich_ids, ++ .probe = lpc_ich_probe, ++ .remove = lpc_ich_remove, ++}; ++ ++module_pci_driver(lpc_ich_driver); ++ ++MODULE_AUTHOR("Aaron Sierra "); ++MODULE_DESCRIPTION("LPC interface for Intel ICH"); ++MODULE_LICENSE("GPL"); +diff --git a/include/linux/mfd/lpc_ich.h b/include/linux/mfd/lpc_ich.h +new file mode 100644 +index 0000000..8feac78 +--- /dev/null ++++ b/include/linux/mfd/lpc_ich.h +@@ -0,0 +1,52 @@ ++/* ++ * linux/drivers/mfd/lpc_ich.h ++ * ++ * Copyright (c) 2012 Extreme Engineering Solution, Inc. ++ * Author: Aaron Sierra ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License 2 as published ++ * by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; see the file COPYING. If not, write to ++ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++#ifndef LPC_ICH_H ++#define LPC_ICH_H ++ ++/* Watchdog resources */ ++#define ICH_RES_IO_TCO 0 ++#define ICH_RES_IO_SMI 1 ++#define ICH_RES_MEM_OFF 2 ++#define ICH_RES_MEM_GCS_PMC 0 ++ ++/* GPIO resources */ ++#define ICH_RES_GPIO 0 ++#define ICH_RES_GPE0 1 ++ ++/* GPIO compatibility */ ++enum { ++ ICH_I3100_GPIO, ++ ICH_V5_GPIO, ++ ICH_V6_GPIO, ++ ICH_V7_GPIO, ++ ICH_V9_GPIO, ++ ICH_V10CORP_GPIO, ++ ICH_V10CONS_GPIO, ++ AVOTON_GPIO, ++}; ++ ++struct lpc_ich_info { ++ char name[32]; ++ unsigned int iTCO_version; ++ unsigned int gpio_version; ++ u8 use_gpio; ++}; ++ ++#endif +diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig +index b2b6916..66a9bdf 100644 +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -658,6 +658,16 @@ config MFD_TIMBERDALE + The timberdale FPGA can be found on the Intel Atom development board + for in-vehicle infontainment, called Russellville. + ++config LPC_ICH ++ tristate "Intel ICH LPC" ++ depends on PCI ++ select MFD_CORE ++ help ++ The LPC bridge function of the Intel ICH provides support for ++ many functional units. This driver provides needed support for ++ other drivers to control these functions, currently GPIO and ++ watchdog. ++ + config LPC_SCH + tristate "Intel SCH LPC" + depends on PCI +diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile +index b2292eb..4aaf80c 100644 +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -91,6 +91,7 @@ obj-$(CONFIG_MFD_DB5500_PRCMU) += db5500-prcmu.o + obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o + obj-$(CONFIG_PMIC_ADP5520) += adp5520.o + obj-$(CONFIG_LPC_SCH) += lpc_sch.o ++obj-$(CONFIG_LPC_ICH) += lpc_ich.o + obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o + obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o + obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o diff --git a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/driver-watchdog-itco-wd.patch b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/driver-watchdog-itco-wd.patch new file mode 100644 index 00000000..da449cd7 --- /dev/null +++ b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/driver-watchdog-itco-wd.patch @@ -0,0 +1,2150 @@ +Upgrade to version 1.11 to support more chipsets + +diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c +index bdf401b..0ba1b7c 100644 +--- a/drivers/watchdog/iTCO_wdt.c ++++ b/drivers/watchdog/iTCO_wdt.c +@@ -37,16 +37,18 @@ + * document number TBD : DH89xxCC + * document number TBD : Panther Point + * document number TBD : Lynx Point ++ * document number TBD : Lynx Point-LP + */ + + /* + * Includes, defines, variables, module parameters, ... + */ + ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ + /* Module and version information */ + #define DRV_NAME "iTCO_wdt" +-#define DRV_VERSION "1.07" +-#define PFX DRV_NAME ": " ++#define DRV_VERSION "1.11" + + /* Includes */ + #include /* For module specific items */ +@@ -54,8 +56,6 @@ + #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 */ +@@ -65,316 +65,16 @@ + #include /* For spin_lock/spin_unlock/... */ + #include /* For copy_to_user/put_user/... */ + #include /* For inb/outb/... */ ++#include ++#include + + #include "iTCO_vendor.h" + +-/* TCO related info */ +-enum iTCO_chipsets { +- TCO_ICH = 0, /* ICH */ +- TCO_ICH0, /* ICH0 */ +- TCO_ICH2, /* ICH2 */ +- TCO_ICH2M, /* ICH2-M */ +- TCO_ICH3, /* ICH3-S */ +- TCO_ICH3M, /* ICH3-M */ +- TCO_ICH4, /* ICH4 */ +- TCO_ICH4M, /* ICH4-M */ +- TCO_CICH, /* C-ICH */ +- TCO_ICH5, /* ICH5 & ICH5R */ +- TCO_6300ESB, /* 6300ESB */ +- TCO_ICH6, /* ICH6 & ICH6R */ +- TCO_ICH6M, /* ICH6-M */ +- TCO_ICH6W, /* ICH6W & ICH6RW */ +- TCO_631XESB, /* 631xESB/632xESB */ +- TCO_ICH7, /* ICH7 & ICH7R */ +- TCO_ICH7DH, /* ICH7DH */ +- TCO_ICH7M, /* ICH7-M & ICH7-U */ +- TCO_ICH7MDH, /* ICH7-M DH */ +- TCO_NM10, /* NM10 */ +- TCO_ICH8, /* ICH8 & ICH8R */ +- TCO_ICH8DH, /* ICH8DH */ +- TCO_ICH8DO, /* ICH8DO */ +- TCO_ICH8M, /* ICH8M */ +- TCO_ICH8ME, /* ICH8M-E */ +- TCO_ICH9, /* ICH9 */ +- TCO_ICH9R, /* ICH9R */ +- TCO_ICH9DH, /* ICH9DH */ +- TCO_ICH9DO, /* ICH9DO */ +- TCO_ICH9M, /* ICH9M */ +- TCO_ICH9ME, /* ICH9M-E */ +- TCO_ICH10, /* ICH10 */ +- TCO_ICH10R, /* ICH10R */ +- TCO_ICH10D, /* ICH10D */ +- TCO_ICH10DO, /* ICH10DO */ +- TCO_PCH, /* PCH Desktop Full Featured */ +- TCO_PCHM, /* PCH Mobile Full Featured */ +- TCO_P55, /* P55 */ +- TCO_PM55, /* PM55 */ +- TCO_H55, /* H55 */ +- TCO_QM57, /* QM57 */ +- TCO_H57, /* H57 */ +- TCO_HM55, /* HM55 */ +- TCO_Q57, /* Q57 */ +- TCO_HM57, /* HM57 */ +- TCO_PCHMSFF, /* PCH Mobile SFF Full Featured */ +- TCO_QS57, /* QS57 */ +- TCO_3400, /* 3400 */ +- TCO_3420, /* 3420 */ +- TCO_3450, /* 3450 */ +- TCO_EP80579, /* EP80579 */ +- TCO_CPT, /* Cougar Point */ +- TCO_CPTD, /* Cougar Point Desktop */ +- TCO_CPTM, /* Cougar Point Mobile */ +- TCO_PBG, /* Patsburg */ +- TCO_DH89XXCC, /* DH89xxCC */ +- TCO_PPT, /* Panther Point */ +- TCO_LPT, /* Lynx Point */ +-}; +- +-static struct { +- char *name; +- unsigned int iTCO_version; +-} iTCO_chipset_info[] __devinitdata = { +- {"ICH", 1}, +- {"ICH0", 1}, +- {"ICH2", 1}, +- {"ICH2-M", 1}, +- {"ICH3-S", 1}, +- {"ICH3-M", 1}, +- {"ICH4", 1}, +- {"ICH4-M", 1}, +- {"C-ICH", 1}, +- {"ICH5 or ICH5R", 1}, +- {"6300ESB", 1}, +- {"ICH6 or ICH6R", 2}, +- {"ICH6-M", 2}, +- {"ICH6W or ICH6RW", 2}, +- {"631xESB/632xESB", 2}, +- {"ICH7 or ICH7R", 2}, +- {"ICH7DH", 2}, +- {"ICH7-M or ICH7-U", 2}, +- {"ICH7-M DH", 2}, +- {"NM10", 2}, +- {"ICH8 or ICH8R", 2}, +- {"ICH8DH", 2}, +- {"ICH8DO", 2}, +- {"ICH8M", 2}, +- {"ICH8M-E", 2}, +- {"ICH9", 2}, +- {"ICH9R", 2}, +- {"ICH9DH", 2}, +- {"ICH9DO", 2}, +- {"ICH9M", 2}, +- {"ICH9M-E", 2}, +- {"ICH10", 2}, +- {"ICH10R", 2}, +- {"ICH10D", 2}, +- {"ICH10DO", 2}, +- {"PCH Desktop Full Featured", 2}, +- {"PCH Mobile Full Featured", 2}, +- {"P55", 2}, +- {"PM55", 2}, +- {"H55", 2}, +- {"QM57", 2}, +- {"H57", 2}, +- {"HM55", 2}, +- {"Q57", 2}, +- {"HM57", 2}, +- {"PCH Mobile SFF Full Featured", 2}, +- {"QS57", 2}, +- {"3400", 2}, +- {"3420", 2}, +- {"3450", 2}, +- {"EP80579", 2}, +- {"Cougar Point", 2}, +- {"Cougar Point Desktop", 2}, +- {"Cougar Point Mobile", 2}, +- {"Patsburg", 2}, +- {"DH89xxCC", 2}, +- {"Panther Point", 2}, +- {"Lynx Point", 2}, +- {NULL, 0} +-}; +- +-/* +- * This data only exists for exporting the supported PCI ids +- * via MODULE_DEVICE_TABLE. We do not actually register a +- * pci_driver, because the I/O Controller Hub has also other +- * functions that probably will be registered by other drivers. +- */ +-static DEFINE_PCI_DEVICE_TABLE(iTCO_wdt_pci_tbl) = { +- { PCI_VDEVICE(INTEL, 0x2410), TCO_ICH}, +- { PCI_VDEVICE(INTEL, 0x2420), TCO_ICH0}, +- { PCI_VDEVICE(INTEL, 0x2440), TCO_ICH2}, +- { PCI_VDEVICE(INTEL, 0x244c), TCO_ICH2M}, +- { PCI_VDEVICE(INTEL, 0x2480), TCO_ICH3}, +- { PCI_VDEVICE(INTEL, 0x248c), TCO_ICH3M}, +- { PCI_VDEVICE(INTEL, 0x24c0), TCO_ICH4}, +- { PCI_VDEVICE(INTEL, 0x24cc), TCO_ICH4M}, +- { PCI_VDEVICE(INTEL, 0x2450), TCO_CICH}, +- { PCI_VDEVICE(INTEL, 0x24d0), TCO_ICH5}, +- { PCI_VDEVICE(INTEL, 0x25a1), TCO_6300ESB}, +- { PCI_VDEVICE(INTEL, 0x2640), TCO_ICH6}, +- { PCI_VDEVICE(INTEL, 0x2641), TCO_ICH6M}, +- { PCI_VDEVICE(INTEL, 0x2642), TCO_ICH6W}, +- { PCI_VDEVICE(INTEL, 0x2670), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x2671), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x2672), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x2673), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x2674), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x2675), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x2676), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x2677), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x2678), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x2679), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x267a), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x267b), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x267c), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x267d), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x267e), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x267f), TCO_631XESB}, +- { PCI_VDEVICE(INTEL, 0x27b8), TCO_ICH7}, +- { PCI_VDEVICE(INTEL, 0x27b0), TCO_ICH7DH}, +- { PCI_VDEVICE(INTEL, 0x27b9), TCO_ICH7M}, +- { PCI_VDEVICE(INTEL, 0x27bd), TCO_ICH7MDH}, +- { PCI_VDEVICE(INTEL, 0x27bc), TCO_NM10}, +- { PCI_VDEVICE(INTEL, 0x2810), TCO_ICH8}, +- { PCI_VDEVICE(INTEL, 0x2812), TCO_ICH8DH}, +- { PCI_VDEVICE(INTEL, 0x2814), TCO_ICH8DO}, +- { PCI_VDEVICE(INTEL, 0x2815), TCO_ICH8M}, +- { PCI_VDEVICE(INTEL, 0x2811), TCO_ICH8ME}, +- { PCI_VDEVICE(INTEL, 0x2918), TCO_ICH9}, +- { PCI_VDEVICE(INTEL, 0x2916), TCO_ICH9R}, +- { PCI_VDEVICE(INTEL, 0x2912), TCO_ICH9DH}, +- { PCI_VDEVICE(INTEL, 0x2914), TCO_ICH9DO}, +- { PCI_VDEVICE(INTEL, 0x2919), TCO_ICH9M}, +- { PCI_VDEVICE(INTEL, 0x2917), TCO_ICH9ME}, +- { PCI_VDEVICE(INTEL, 0x3a18), TCO_ICH10}, +- { PCI_VDEVICE(INTEL, 0x3a16), TCO_ICH10R}, +- { PCI_VDEVICE(INTEL, 0x3a1a), TCO_ICH10D}, +- { PCI_VDEVICE(INTEL, 0x3a14), TCO_ICH10DO}, +- { PCI_VDEVICE(INTEL, 0x3b00), TCO_PCH}, +- { PCI_VDEVICE(INTEL, 0x3b01), TCO_PCHM}, +- { PCI_VDEVICE(INTEL, 0x3b02), TCO_P55}, +- { PCI_VDEVICE(INTEL, 0x3b03), TCO_PM55}, +- { PCI_VDEVICE(INTEL, 0x3b06), TCO_H55}, +- { PCI_VDEVICE(INTEL, 0x3b07), TCO_QM57}, +- { PCI_VDEVICE(INTEL, 0x3b08), TCO_H57}, +- { PCI_VDEVICE(INTEL, 0x3b09), TCO_HM55}, +- { PCI_VDEVICE(INTEL, 0x3b0a), TCO_Q57}, +- { PCI_VDEVICE(INTEL, 0x3b0b), TCO_HM57}, +- { PCI_VDEVICE(INTEL, 0x3b0d), TCO_PCHMSFF}, +- { PCI_VDEVICE(INTEL, 0x3b0f), TCO_QS57}, +- { PCI_VDEVICE(INTEL, 0x3b12), TCO_3400}, +- { PCI_VDEVICE(INTEL, 0x3b14), TCO_3420}, +- { PCI_VDEVICE(INTEL, 0x3b16), TCO_3450}, +- { PCI_VDEVICE(INTEL, 0x5031), TCO_EP80579}, +- { PCI_VDEVICE(INTEL, 0x1c41), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c42), TCO_CPTD}, +- { PCI_VDEVICE(INTEL, 0x1c43), TCO_CPTM}, +- { PCI_VDEVICE(INTEL, 0x1c44), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c45), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c46), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c47), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c48), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c49), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c4a), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c4b), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c4c), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c4d), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c4e), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c4f), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c50), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c51), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c52), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c53), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c54), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c55), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c56), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c57), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c58), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c59), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c5a), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c5b), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c5c), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c5d), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c5e), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1c5f), TCO_CPT}, +- { PCI_VDEVICE(INTEL, 0x1d40), TCO_PBG}, +- { PCI_VDEVICE(INTEL, 0x1d41), TCO_PBG}, +- { PCI_VDEVICE(INTEL, 0x2310), TCO_DH89XXCC}, +- { PCI_VDEVICE(INTEL, 0x1e40), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e41), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e42), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e43), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e44), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e45), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e46), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e47), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e48), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e49), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e4a), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e4b), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e4c), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e4d), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e4e), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e4f), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e50), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e51), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e52), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e53), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e54), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e55), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e56), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e57), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e58), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e59), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e5a), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e5b), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e5c), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e5d), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e5e), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x1e5f), TCO_PPT}, +- { PCI_VDEVICE(INTEL, 0x8c40), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c41), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c42), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c43), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c44), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c45), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c46), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c47), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c48), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c49), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c4a), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c4b), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c4c), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c4d), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c4e), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c4f), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c50), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c51), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c52), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c53), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c54), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c55), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c56), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c57), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c58), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c59), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c5a), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c5b), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c5c), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c5d), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c5e), TCO_LPT}, +- { PCI_VDEVICE(INTEL, 0x8c5f), TCO_LPT}, +- { 0, }, /* End of list */ +-}; +-MODULE_DEVICE_TABLE(pci, iTCO_wdt_pci_tbl); +- + /* Address definitions for the TCO */ + /* TCO base address */ +-#define TCOBASE (iTCO_wdt_private.ACPIBASE + 0x60) ++#define TCOBASE (iTCO_wdt_private.tco_res->start) + /* SMI Control and Enable Register */ +-#define SMI_EN (iTCO_wdt_private.ACPIBASE + 0x30) ++#define SMI_EN (iTCO_wdt_private.smi_res->start) + + #define TCO_RLD (TCOBASE + 0x00) /* TCO Timer Reload and Curr. Value */ + #define TCOv1_TMR (TCOBASE + 0x01) /* TCOv1 Timer Initial Value */ +@@ -387,34 +87,34 @@ MODULE_DEVICE_TABLE(pci, iTCO_wdt_pci_tbl); + #define TCOv2_TMR (TCOBASE + 0x12) /* TCOv2 Timer Initial Value */ + + /* internal variables */ +-static unsigned long is_active; +-static char expect_release; + static struct { /* this is private data for the iTCO_wdt device */ + /* TCO version/generation */ + unsigned int iTCO_version; +- /* The device's ACPIBASE address (TCOBASE = ACPIBASE+0x60) */ +- unsigned long ACPIBASE; +- /* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2)*/ +- unsigned long __iomem *gcs; ++ struct resource *tco_res; ++ struct resource *smi_res; ++ /* ++ * 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; + } iTCO_wdt_private; + +-/* the watchdog platform device */ +-static struct platform_device *iTCO_wdt_platform_device; +- + /* module parameters */ +-#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */ +-static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ ++#define WATCHDOG_TIMEOUT 30 /* 30 sec default heartbeat */ ++static int heartbeat = WATCHDOG_TIMEOUT; /* in seconds */ + module_param(heartbeat, int, 0); + MODULE_PARM_DESC(heartbeat, "Watchdog timeout in seconds. " + "5..76 (TCO v1) or 3..614 (TCO v2), default=" +- __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); ++ __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +-static int nowayout = WATCHDOG_NOWAYOUT; +-module_param(nowayout, int, 0); ++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) ")"); +@@ -428,11 +128,19 @@ MODULE_PARM_DESC(turn_SMI_watchdog_clear_off, + * 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 void iTCO_wdt_set_NO_REBOOT_bit(void) +@@ -440,10 +148,14 @@ static void iTCO_wdt_set_NO_REBOOT_bit(void) + u32 val32; + + /* Set the NO_REBOOT bit: this disables reboots */ +- if (iTCO_wdt_private.iTCO_version == 2) { +- val32 = readl(iTCO_wdt_private.gcs); ++ if (iTCO_wdt_private.iTCO_version == 3) { ++ val32 = readl(iTCO_wdt_private.gcs_pmc); ++ val32 |= 0x00000010; ++ writel(val32, iTCO_wdt_private.gcs_pmc); ++ } else if (iTCO_wdt_private.iTCO_version == 2) { ++ val32 = readl(iTCO_wdt_private.gcs_pmc); + val32 |= 0x00000020; +- writel(val32, iTCO_wdt_private.gcs); ++ 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; +@@ -457,12 +169,20 @@ static int iTCO_wdt_unset_NO_REBOOT_bit(void) + u32 val32; + + /* Unset the NO_REBOOT bit: this enables reboots */ +- if (iTCO_wdt_private.iTCO_version == 2) { +- val32 = readl(iTCO_wdt_private.gcs); ++ if (iTCO_wdt_private.iTCO_version == 3) { ++ val32 = readl(iTCO_wdt_private.gcs_pmc); ++ val32 &= 0xffffffef; ++ writel(val32, iTCO_wdt_private.gcs_pmc); ++ ++ val32 = readl(iTCO_wdt_private.gcs_pmc); ++ if (val32 & 0x00000010) ++ ret = -EIO; ++ } else if (iTCO_wdt_private.iTCO_version == 2) { ++ val32 = readl(iTCO_wdt_private.gcs_pmc); + val32 &= 0xffffffdf; +- writel(val32, iTCO_wdt_private.gcs); ++ writel(val32, iTCO_wdt_private.gcs_pmc); + +- val32 = readl(iTCO_wdt_private.gcs); ++ val32 = readl(iTCO_wdt_private.gcs_pmc); + if (val32 & 0x00000020) + ret = -EIO; + } else if (iTCO_wdt_private.iTCO_version == 1) { +@@ -478,25 +198,24 @@ static int iTCO_wdt_unset_NO_REBOOT_bit(void) + return ret; /* returns: 0 = OK, -EIO = Error */ + } + +-static int iTCO_wdt_start(void) ++static int iTCO_wdt_start(struct watchdog_device *wd_dev) + { + unsigned int val; + + spin_lock(&iTCO_wdt_private.io_lock); + +- iTCO_vendor_pre_start(iTCO_wdt_private.ACPIBASE, heartbeat); ++ iTCO_vendor_pre_start(iTCO_wdt_private.smi_res, wd_dev->timeout); + + /* disable chipset's NO_REBOOT bit */ + if (iTCO_wdt_unset_NO_REBOOT_bit()) { + spin_unlock(&iTCO_wdt_private.io_lock); +- printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, " +- "reboot disabled by hardware/BIOS\n"); ++ pr_err("failed to reset NO_REBOOT flag, reboot disabled by hardware/BIOS\n"); + return -EIO; + } + + /* 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); +@@ -513,13 +232,13 @@ static int iTCO_wdt_start(void) + return 0; + } + +-static int iTCO_wdt_stop(void) ++static int iTCO_wdt_stop(struct watchdog_device *wd_dev) + { + unsigned int val; + + spin_lock(&iTCO_wdt_private.io_lock); + +- iTCO_vendor_pre_stop(iTCO_wdt_private.ACPIBASE); ++ iTCO_vendor_pre_stop(iTCO_wdt_private.smi_res); + + /* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */ + val = inw(TCO1_CNT); +@@ -537,16 +256,16 @@ static int iTCO_wdt_stop(void) + return 0; + } + +-static int iTCO_wdt_keepalive(void) ++static int iTCO_wdt_ping(struct watchdog_device *wd_dev) + { + spin_lock(&iTCO_wdt_private.io_lock); + +- iTCO_vendor_pre_keepalive(iTCO_wdt_private.ACPIBASE, heartbeat); ++ 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 */ +@@ -558,7 +277,7 @@ static int iTCO_wdt_keepalive(void) + return 0; + } + +-static int iTCO_wdt_set_heartbeat(int t) ++static int iTCO_wdt_set_timeout(struct watchdog_device *wd_dev, unsigned int t) + { + unsigned int val16; + unsigned char val8; +@@ -574,14 +293,14 @@ static int iTCO_wdt_set_heartbeat(int t) + /* "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; +@@ -605,23 +324,24 @@ static int iTCO_wdt_set_heartbeat(int t) + return -EINVAL; + } + +- heartbeat = t; ++ wd_dev->timeout = t; + return 0; + } + +-static int iTCO_wdt_get_timeleft(int *time_left) ++static unsigned int iTCO_wdt_get_timeleft(struct watchdog_device *wd_dev) + { + unsigned int val16; + unsigned char val8; ++ 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); +@@ -630,331 +350,217 @@ static int iTCO_wdt_get_timeleft(int *time_left) + val8 += (inb(TCOv1_TMR) & 0x3f); + spin_unlock(&iTCO_wdt_private.io_lock); + +- *time_left = (val8 * 6) / 10; +- } else +- return -EINVAL; +- return 0; ++ time_left = ticks_to_seconds(val8); ++ } ++ return time_left; + } + + /* +- * /dev/watchdog handling ++ * Kernel Interfaces + */ + +-static int iTCO_wdt_open(struct inode *inode, struct file *file) +-{ +- /* /dev/watchdog can only be opened once */ +- if (test_and_set_bit(0, &is_active)) +- return -EBUSY; ++static const struct watchdog_info ident = { ++ .options = WDIOF_SETTIMEOUT | ++ WDIOF_KEEPALIVEPING | ++ WDIOF_MAGICCLOSE, ++ .firmware_version = 0, ++ .identity = DRV_NAME, ++}; + +- /* +- * Reload and activate timer +- */ +- iTCO_wdt_start(); +- return nonseekable_open(inode, file); +-} ++static const struct watchdog_ops iTCO_wdt_ops = { ++ .owner = THIS_MODULE, ++ .start = iTCO_wdt_start, ++ .stop = iTCO_wdt_stop, ++ .ping = iTCO_wdt_ping, ++ .set_timeout = iTCO_wdt_set_timeout, ++ .get_timeleft = iTCO_wdt_get_timeleft, ++}; + +-static int iTCO_wdt_release(struct inode *inode, struct file *file) +-{ +- /* +- * Shut off the timer. +- */ +- if (expect_release == 42) { +- iTCO_wdt_stop(); +- } else { +- printk(KERN_CRIT PFX +- "Unexpected close, not stopping watchdog!\n"); +- iTCO_wdt_keepalive(); +- } +- clear_bit(0, &is_active); +- expect_release = 0; +- return 0; +-} ++static struct watchdog_device iTCO_wdt_watchdog_dev = { ++ .info = &ident, ++ .ops = &iTCO_wdt_ops, ++}; + +-static ssize_t iTCO_wdt_write(struct file *file, const char __user *data, +- size_t len, loff_t *ppos) ++/* ++ * Init & exit routines ++ */ ++ ++static void iTCO_wdt_cleanup(void) + { +- /* See if we got the magic character 'V' and reload the timer */ +- if (len) { +- if (!nowayout) { +- size_t i; +- +- /* note: just in case someone wrote the magic +- character five months ago... */ +- expect_release = 0; +- +- /* scan to see whether or not we got the +- magic character */ +- for (i = 0; i != len; i++) { +- char c; +- if (get_user(c, data + i)) +- return -EFAULT; +- if (c == 'V') +- expect_release = 42; +- } +- } ++ /* Stop the timer before we leave */ ++ if (!nowayout) ++ iTCO_wdt_stop(&iTCO_wdt_watchdog_dev); + +- /* someone wrote to us, we should reload the timer */ +- iTCO_wdt_keepalive(); ++ /* Deregister */ ++ watchdog_unregister_device(&iTCO_wdt_watchdog_dev); ++ ++ /* release resources */ ++ release_region(iTCO_wdt_private.tco_res->start, ++ 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_pmc); ++ release_mem_region(iTCO_wdt_private.gcs_pmc_res->start, ++ resource_size(iTCO_wdt_private.gcs_pmc_res)); + } +- return len; ++ ++ iTCO_wdt_private.tco_res = NULL; ++ iTCO_wdt_private.smi_res = NULL; ++ iTCO_wdt_private.gcs_pmc_res = NULL; ++ iTCO_wdt_private.gcs_pmc = NULL; + } + +-static long iTCO_wdt_ioctl(struct file *file, unsigned int cmd, +- unsigned long arg) ++static int iTCO_wdt_probe(struct platform_device *dev) + { +- int new_options, retval = -EINVAL; +- int new_heartbeat; +- void __user *argp = (void __user *)arg; +- int __user *p = argp; +- static const struct watchdog_info ident = { +- .options = WDIOF_SETTIMEOUT | +- WDIOF_KEEPALIVEPING | +- WDIOF_MAGICCLOSE, +- .firmware_version = 0, +- .identity = DRV_NAME, +- }; +- +- switch (cmd) { +- case WDIOC_GETSUPPORT: +- return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; +- case WDIOC_GETSTATUS: +- case WDIOC_GETBOOTSTATUS: +- return put_user(0, p); +- +- case WDIOC_SETOPTIONS: +- { +- if (get_user(new_options, p)) +- return -EFAULT; +- +- if (new_options & WDIOS_DISABLECARD) { +- iTCO_wdt_stop(); +- retval = 0; +- } +- if (new_options & WDIOS_ENABLECARD) { +- iTCO_wdt_keepalive(); +- iTCO_wdt_start(); +- retval = 0; +- } +- return retval; +- } +- case WDIOC_KEEPALIVE: +- iTCO_wdt_keepalive(); +- return 0; +- +- case WDIOC_SETTIMEOUT: +- { +- if (get_user(new_heartbeat, p)) +- return -EFAULT; +- if (iTCO_wdt_set_heartbeat(new_heartbeat)) +- return -EINVAL; +- iTCO_wdt_keepalive(); +- /* Fall */ +- } +- case WDIOC_GETTIMEOUT: +- return put_user(heartbeat, p); +- case WDIOC_GETTIMELEFT: +- { +- int time_left; +- if (iTCO_wdt_get_timeleft(&time_left)) +- return -EINVAL; +- return put_user(time_left, p); +- } +- default: +- return -ENOTTY; +- } +-} ++ int ret = -ENODEV; ++ unsigned long val32; ++ struct lpc_ich_info *ich_info = dev_get_platdata(&dev->dev); + +-/* +- * Kernel Interfaces +- */ ++ if (!ich_info) ++ goto out; + +-static const struct file_operations iTCO_wdt_fops = { +- .owner = THIS_MODULE, +- .llseek = no_llseek, +- .write = iTCO_wdt_write, +- .unlocked_ioctl = iTCO_wdt_ioctl, +- .open = iTCO_wdt_open, +- .release = iTCO_wdt_release, +-}; ++ spin_lock_init(&iTCO_wdt_private.io_lock); + +-static struct miscdevice iTCO_wdt_miscdev = { +- .minor = WATCHDOG_MINOR, +- .name = "watchdog", +- .fops = &iTCO_wdt_fops, +-}; ++ iTCO_wdt_private.tco_res = ++ platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_TCO); ++ if (!iTCO_wdt_private.tco_res) ++ goto out; + +-/* +- * Init & exit routines +- */ ++ iTCO_wdt_private.smi_res = ++ platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_SMI); ++ if (!iTCO_wdt_private.smi_res) ++ goto out; + +-static int __devinit iTCO_wdt_init(struct pci_dev *pdev, +- const struct pci_device_id *ent, struct platform_device *dev) +-{ +- int ret; +- u32 base_address; +- unsigned long RCBA; +- unsigned long val32; ++ iTCO_wdt_private.iTCO_version = ich_info->iTCO_version; ++ iTCO_wdt_private.dev = dev; ++ iTCO_wdt_private.pdev = to_pci_dev(dev->dev.parent); + + /* +- * Find the ACPI/PM base I/O address which is the base +- * for the TCO registers (TCOBASE=ACPIBASE + 0x60) +- * ACPIBASE is bits [15:7] from 0x40-0x43 ++ * Get the Memory-Mapped GCS or PMC register, we need it for the ++ * NO_REBOOT flag (TCO v2 and v3). + */ +- pci_read_config_dword(pdev, 0x40, &base_address); +- base_address &= 0x0000ff80; +- if (base_address == 0x00000000) { +- /* Something's wrong here, ACPIBASE has to be set */ +- printk(KERN_ERR PFX "failed to get TCOBASE address, " +- "device disabled by hardware/BIOS\n"); +- return -ENODEV; +- } +- iTCO_wdt_private.iTCO_version = +- iTCO_chipset_info[ent->driver_data].iTCO_version; +- iTCO_wdt_private.ACPIBASE = base_address; +- iTCO_wdt_private.pdev = pdev; +- +- /* Get the Memory-Mapped GCS register, we need it for the +- NO_REBOOT flag (TCO v2). To get access to it you have to +- read RCBA from PCI Config space 0xf0 and use it as base. +- GCS = RCBA + ICH6_GCS(0x3410). */ +- if (iTCO_wdt_private.iTCO_version == 2) { +- pci_read_config_dword(pdev, 0xf0, &base_address); +- if ((base_address & 1) == 0) { +- printk(KERN_ERR PFX "RCBA is disabled by hardware" +- "/BIOS, device disabled\n"); +- ret = -ENODEV; ++ if (iTCO_wdt_private.iTCO_version >= 2) { ++ iTCO_wdt_private.gcs_pmc_res = platform_get_resource(dev, ++ IORESOURCE_MEM, ++ ICH_RES_MEM_GCS_PMC); ++ ++ if (!iTCO_wdt_private.gcs_pmc_res) ++ goto out; ++ ++ 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; + } +- RCBA = base_address & 0xffffc000; +- iTCO_wdt_private.gcs = ioremap((RCBA + 0x3410), 4); ++ 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_pmc; ++ } + } + + /* Check chipset's NO_REBOOT bit */ + if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) { +- printk(KERN_INFO PFX "unable to reset NO_REBOOT flag, " +- "device disabled by hardware/BIOS\n"); ++ pr_info("unable to reset NO_REBOOT flag, device disabled by hardware/BIOS\n"); + ret = -ENODEV; /* Cannot reset NO_REBOOT bit */ +- goto out_unmap; ++ goto unmap_gcs_pmc; + } + + /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ + iTCO_wdt_set_NO_REBOOT_bit(); + + /* The TCO logic uses the TCO_EN bit in the SMI_EN register */ +- if (!request_region(SMI_EN, 4, "iTCO_wdt")) { +- printk(KERN_ERR PFX +- "I/O address 0x%04lx already in use, " +- "device disabled\n", SMI_EN); +- ret = -EIO; +- goto out_unmap; ++ if (!request_region(iTCO_wdt_private.smi_res->start, ++ resource_size(iTCO_wdt_private.smi_res), dev->name)) { ++ pr_err("I/O address 0x%04llx already in use, device disabled\n", ++ (u64)SMI_EN); ++ ret = -EBUSY; ++ goto unmap_gcs_pmc; + } + if (turn_SMI_watchdog_clear_off >= iTCO_wdt_private.iTCO_version) { +- /* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI# */ ++ /* ++ * Bit 13: TCO_EN -> 0 ++ * Disables TCO logic generating an SMI# ++ */ + val32 = inl(SMI_EN); + val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */ + outl(val32, SMI_EN); + } + +- /* The TCO I/O registers reside in a 32-byte range pointed to +- by the TCOBASE value */ +- if (!request_region(TCOBASE, 0x20, "iTCO_wdt")) { +- printk(KERN_ERR PFX "I/O address 0x%04lx already in use " +- "device disabled\n", TCOBASE); +- ret = -EIO; +- goto unreg_smi_en; ++ if (!request_region(iTCO_wdt_private.tco_res->start, ++ resource_size(iTCO_wdt_private.tco_res), dev->name)) { ++ pr_err("I/O address 0x%04llx already in use, device disabled\n", ++ (u64)TCOBASE); ++ ret = -EBUSY; ++ goto unreg_smi; + } + +- printk(KERN_INFO PFX +- "Found a %s TCO device (Version=%d, TCOBASE=0x%04lx)\n", +- iTCO_chipset_info[ent->driver_data].name, +- iTCO_chipset_info[ent->driver_data].iTCO_version, +- TCOBASE); ++ pr_info("Found a %s TCO device (Version=%d, TCOBASE=0x%04llx)\n", ++ ich_info->name, ich_info->iTCO_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 */ ++ if (iTCO_wdt_private.iTCO_version == 3) { ++ outl(0x20008, TCO1_STS); ++ } else { ++ 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 */ ++ } ++ ++ 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; + + /* Make sure the watchdog is not running */ +- iTCO_wdt_stop(); ++ iTCO_wdt_stop(&iTCO_wdt_watchdog_dev); + + /* Check that the heartbeat value is within it's range; + if not reset to the default */ +- if (iTCO_wdt_set_heartbeat(heartbeat)) { +- iTCO_wdt_set_heartbeat(WATCHDOG_HEARTBEAT); +- printk(KERN_INFO PFX +- "timeout value out of range, using %d\n", heartbeat); ++ if (iTCO_wdt_set_timeout(&iTCO_wdt_watchdog_dev, heartbeat)) { ++ iTCO_wdt_set_timeout(&iTCO_wdt_watchdog_dev, WATCHDOG_TIMEOUT); ++ pr_info("timeout value out of range, using %d\n", ++ WATCHDOG_TIMEOUT); + } + +- ret = misc_register(&iTCO_wdt_miscdev); ++ ret = watchdog_register_device(&iTCO_wdt_watchdog_dev); + if (ret != 0) { +- printk(KERN_ERR PFX +- "cannot register miscdev on minor=%d (err=%d)\n", +- WATCHDOG_MINOR, ret); +- goto unreg_region; ++ pr_err("cannot register watchdog device (err=%d)\n", ret); ++ goto unreg_tco; + } + +- printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", +- heartbeat, nowayout); ++ pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", ++ heartbeat, nowayout); + + return 0; + +-unreg_region: +- release_region(TCOBASE, 0x20); +-unreg_smi_en: +- release_region(SMI_EN, 4); +-out_unmap: +- if (iTCO_wdt_private.iTCO_version == 2) +- iounmap(iTCO_wdt_private.gcs); ++unreg_tco: ++ release_region(iTCO_wdt_private.tco_res->start, ++ resource_size(iTCO_wdt_private.tco_res)); ++unreg_smi: ++ release_region(iTCO_wdt_private.smi_res->start, ++ resource_size(iTCO_wdt_private.smi_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.ACPIBASE = 0; +- return ret; +-} +- +-static void __devexit iTCO_wdt_cleanup(void) +-{ +- /* Stop the timer before we leave */ +- if (!nowayout) +- iTCO_wdt_stop(); +- +- /* Deregister */ +- misc_deregister(&iTCO_wdt_miscdev); +- release_region(TCOBASE, 0x20); +- release_region(SMI_EN, 4); +- if (iTCO_wdt_private.iTCO_version == 2) +- iounmap(iTCO_wdt_private.gcs); +- pci_dev_put(iTCO_wdt_private.pdev); +- iTCO_wdt_private.ACPIBASE = 0; +-} +- +-static int __devinit iTCO_wdt_probe(struct platform_device *dev) +-{ +- int ret = -ENODEV; +- int found = 0; +- struct pci_dev *pdev = NULL; +- const struct pci_device_id *ent; +- +- spin_lock_init(&iTCO_wdt_private.io_lock); +- +- for_each_pci_dev(pdev) { +- ent = pci_match_id(iTCO_wdt_pci_tbl, pdev); +- if (ent) { +- found++; +- ret = iTCO_wdt_init(pdev, ent, dev); +- if (!ret) +- break; +- } +- } +- +- if (!found) +- printk(KERN_INFO PFX "No device detected.\n"); ++ iTCO_wdt_private.tco_res = NULL; ++ iTCO_wdt_private.smi_res = NULL; ++ iTCO_wdt_private.gcs_pmc_res = NULL; ++ iTCO_wdt_private.gcs_pmc = NULL; + + return ret; + } + +-static int __devexit iTCO_wdt_remove(struct platform_device *dev) ++static int iTCO_wdt_remove(struct platform_device *dev) + { +- if (iTCO_wdt_private.ACPIBASE) ++ if (iTCO_wdt_private.tco_res || iTCO_wdt_private.smi_res) + iTCO_wdt_cleanup(); + + return 0; +@@ -962,12 +568,12 @@ static int __devexit iTCO_wdt_remove(struct platform_device *dev) + + static void iTCO_wdt_shutdown(struct platform_device *dev) + { +- iTCO_wdt_stop(); ++ iTCO_wdt_stop(NULL); + } + + static struct platform_driver iTCO_wdt_driver = { + .probe = iTCO_wdt_probe, +- .remove = __devexit_p(iTCO_wdt_remove), ++ .remove = iTCO_wdt_remove, + .shutdown = iTCO_wdt_shutdown, + .driver = { + .owner = THIS_MODULE, +@@ -979,32 +585,19 @@ static int __init iTCO_wdt_init_module(void) + { + int err; + +- printk(KERN_INFO PFX "Intel TCO WatchDog Timer Driver v%s\n", +- DRV_VERSION); ++ pr_info("Intel TCO WatchDog Timer Driver v%s\n", DRV_VERSION); + + err = platform_driver_register(&iTCO_wdt_driver); + if (err) + return err; + +- iTCO_wdt_platform_device = platform_device_register_simple(DRV_NAME, +- -1, NULL, 0); +- if (IS_ERR(iTCO_wdt_platform_device)) { +- err = PTR_ERR(iTCO_wdt_platform_device); +- goto unreg_platform_driver; +- } +- + return 0; +- +-unreg_platform_driver: +- platform_driver_unregister(&iTCO_wdt_driver); +- return err; + } + + static void __exit iTCO_wdt_cleanup_module(void) + { +- platform_device_unregister(iTCO_wdt_platform_device); + platform_driver_unregister(&iTCO_wdt_driver); +- printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); ++ pr_info("Watchdog Module Unloaded\n"); + } + + module_init(iTCO_wdt_init_module); +@@ -1014,4 +607,4 @@ MODULE_AUTHOR("Wim Van Sebroeck "); + MODULE_DESCRIPTION("Intel TCO WatchDog Timer Driver"); + MODULE_VERSION(DRV_VERSION); + MODULE_LICENSE("GPL"); +-MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); ++MODULE_ALIAS("platform:" DRV_NAME); +diff --git a/drivers/watchdog/watchdog_core.h b/drivers/watchdog/watchdog_core.h +new file mode 100644 +index 0000000..6c95141 +--- /dev/null ++++ b/drivers/watchdog/watchdog_core.h +@@ -0,0 +1,37 @@ ++/* ++ * watchdog_core.h ++ * ++ * (c) Copyright 2008-2011 Alan Cox , ++ * All Rights Reserved. ++ * ++ * (c) Copyright 2008-2011 Wim Van Sebroeck . ++ * ++ * This source code is part of the generic code that can be used ++ * by all the watchdog timer drivers. ++ * ++ * Based on source code of the following authors: ++ * Matt Domsch , ++ * Rob Radez , ++ * Rusty Lynch ++ * Satyam Sharma ++ * Randy Dunlap ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ * ++ * Neither Alan Cox, CymruNet Ltd., Wim Van Sebroeck nor Iguana vzw. ++ * admit liability nor provide warranty for any of this software. ++ * This material is provided "AS-IS" and at no charge. ++ */ ++ ++#define MAX_DOGS 32 /* Maximum number of watchdog devices */ ++ ++/* ++ * Functions/procedures to be called by the core ++ */ ++extern int watchdog_dev_register(struct watchdog_device *); ++extern int watchdog_dev_unregister(struct watchdog_device *); ++extern int __init watchdog_dev_init(void); ++extern void __exit watchdog_dev_exit(void); +diff --git a/include/uapi/linux/watchdog.h b/include/uapi/linux/watchdog.h +new file mode 100644 +index 0000000..2babe72 +--- /dev/null ++++ b/include/uapi/linux/watchdog.h +@@ -0,0 +1,57 @@ ++/* ++ * Generic watchdog defines. Derived from.. ++ * ++ * Berkshire PC Watchdog Defines ++ * by Ken Hollis ++ * ++ */ ++ ++#ifndef _UAPI_LINUX_WATCHDOG_H ++#define _UAPI_LINUX_WATCHDOG_H ++ ++#include ++#include ++ ++#define WATCHDOG_IOCTL_BASE 'W' ++ ++struct watchdog_info { ++ __u32 options; /* Options the card/driver supports */ ++ __u32 firmware_version; /* Firmware version of the card */ ++ __u8 identity[32]; /* Identity of the board */ ++}; ++ ++#define WDIOC_GETSUPPORT _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info) ++#define WDIOC_GETSTATUS _IOR(WATCHDOG_IOCTL_BASE, 1, int) ++#define WDIOC_GETBOOTSTATUS _IOR(WATCHDOG_IOCTL_BASE, 2, int) ++#define WDIOC_GETTEMP _IOR(WATCHDOG_IOCTL_BASE, 3, int) ++#define WDIOC_SETOPTIONS _IOR(WATCHDOG_IOCTL_BASE, 4, int) ++#define WDIOC_KEEPALIVE _IOR(WATCHDOG_IOCTL_BASE, 5, int) ++#define WDIOC_SETTIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 6, int) ++#define WDIOC_GETTIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 7, int) ++#define WDIOC_SETPRETIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 8, int) ++#define WDIOC_GETPRETIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 9, int) ++#define WDIOC_GETTIMELEFT _IOR(WATCHDOG_IOCTL_BASE, 10, int) ++ ++#define WDIOF_UNKNOWN -1 /* Unknown flag error */ ++#define WDIOS_UNKNOWN -1 /* Unknown status error */ ++ ++#define WDIOF_OVERHEAT 0x0001 /* Reset due to CPU overheat */ ++#define WDIOF_FANFAULT 0x0002 /* Fan failed */ ++#define WDIOF_EXTERN1 0x0004 /* External relay 1 */ ++#define WDIOF_EXTERN2 0x0008 /* External relay 2 */ ++#define WDIOF_POWERUNDER 0x0010 /* Power bad/power fault */ ++#define WDIOF_CARDRESET 0x0020 /* Card previously reset the CPU */ ++#define WDIOF_POWEROVER 0x0040 /* Power over voltage */ ++#define WDIOF_SETTIMEOUT 0x0080 /* Set timeout (in seconds) */ ++#define WDIOF_MAGICCLOSE 0x0100 /* Supports magic close char */ ++#define WDIOF_PRETIMEOUT 0x0200 /* Pretimeout (in seconds), get/set */ ++#define WDIOF_ALARMONLY 0x0400 /* Watchdog triggers a management or ++ other external alarm not a reboot */ ++#define WDIOF_KEEPALIVEPING 0x8000 /* Keep alive ping reply */ ++ ++#define WDIOS_DISABLECARD 0x0001 /* Turn off the watchdog timer */ ++#define WDIOS_ENABLECARD 0x0002 /* Turn on the watchdog timer */ ++#define WDIOS_TEMPPANIC 0x0004 /* Kernel panic on temperature trip */ ++ ++ ++#endif /* _UAPI_LINUX_WATCHDOG_H */ +diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c +index cfa1a15..cec9b55 100644 +--- a/drivers/watchdog/watchdog_core.c ++++ b/drivers/watchdog/watchdog_core.c +@@ -34,8 +34,69 @@ + #include /* For printk/panic/... */ + #include /* For watchdog specific items */ + #include /* For __init/__exit/... */ ++#include /* For ida_* macros */ ++#include /* For IS_ERR macros */ ++#include /* For of_get_timeout_sec */ + +-#include "watchdog_dev.h" /* For watchdog_dev_register/... */ ++#include "watchdog_core.h" /* For watchdog_dev_register/... */ ++ ++static DEFINE_IDA(watchdog_ida); ++static struct class *watchdog_class; ++ ++static void watchdog_check_min_max_timeout(struct watchdog_device *wdd) ++{ ++ /* ++ * Check that we have valid min and max timeout values, if ++ * not reset them both to 0 (=not used or unknown) ++ */ ++ if (wdd->min_timeout > wdd->max_timeout) { ++ pr_info("Invalid min and max timeout values, resetting to 0!\n"); ++ wdd->min_timeout = 0; ++ wdd->max_timeout = 0; ++ } ++} ++ ++/** ++ * watchdog_init_timeout() - initialize the timeout field ++ * @timeout_parm: timeout module parameter ++ * @dev: Device that stores the timeout-sec property ++ * ++ * Initialize the timeout field of the watchdog_device struct with either the ++ * timeout module parameter (if it is valid value) or the timeout-sec property ++ * (only if it is a valid value and the timeout_parm is out of bounds). ++ * If none of them are valid then we keep the old value (which should normally ++ * be the default timeout value. ++ * ++ * A zero is returned on success and -EINVAL for failure. ++ */ ++int watchdog_init_timeout(struct watchdog_device *wdd, ++ unsigned int timeout_parm, struct device *dev) ++{ ++ unsigned int t = 0; ++ int ret = 0; ++ ++ watchdog_check_min_max_timeout(wdd); ++ ++ /* try to get the timeout module parameter first */ ++ if (!watchdog_timeout_invalid(wdd, timeout_parm) && timeout_parm) { ++ wdd->timeout = timeout_parm; ++ return ret; ++ } ++ if (timeout_parm) ++ ret = -EINVAL; ++ ++ /* try to get the timeout_sec property */ ++ if (dev == NULL || dev->of_node == NULL) ++ return ret; ++ of_property_read_u32(dev->of_node, "timeout-sec", &t); ++ if (!watchdog_timeout_invalid(wdd, t) && t) ++ wdd->timeout = t; ++ else ++ ret = -EINVAL; ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(watchdog_init_timeout); + + /** + * watchdog_register_device() - register a watchdog device +@@ -49,7 +110,7 @@ + */ + int watchdog_register_device(struct watchdog_device *wdd) + { +- int ret; ++ int ret, id, devno; + + if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL) + return -EINVAL; +@@ -58,15 +119,7 @@ int watchdog_register_device(struct watchdog_device *wdd) + if (wdd->ops->start == NULL || wdd->ops->stop == NULL) + return -EINVAL; + +- /* +- * Check that we have valid min and max timeout values, if +- * not reset them both to 0 (=not used or unknown) +- */ +- if (wdd->min_timeout > wdd->max_timeout) { +- pr_info("Invalid min and max timeout values, resetting to 0!\n"); +- wdd->min_timeout = 0; +- wdd->max_timeout = 0; +- } ++ watchdog_check_min_max_timeout(wdd); + + /* + * Note: now that all watchdog_device data has been verified, we +@@ -74,10 +127,38 @@ int watchdog_register_device(struct watchdog_device *wdd) + * corrupted in a later stage then we expect a kernel panic! + */ + +- /* We only support 1 watchdog device via the /dev/watchdog interface */ ++ mutex_init(&wdd->lock); ++ id = ida_simple_get(&watchdog_ida, 0, MAX_DOGS, GFP_KERNEL); ++ if (id < 0) ++ return id; ++ wdd->id = id; ++ + ret = watchdog_dev_register(wdd); + if (ret) { +- pr_err("error registering /dev/watchdog (err=%d).\n", ret); ++ ida_simple_remove(&watchdog_ida, id); ++ if (!(id == 0 && ret == -EBUSY)) ++ return ret; ++ ++ /* Retry in case a legacy watchdog module exists */ ++ id = ida_simple_get(&watchdog_ida, 1, MAX_DOGS, GFP_KERNEL); ++ if (id < 0) ++ return id; ++ wdd->id = id; ++ ++ ret = watchdog_dev_register(wdd); ++ if (ret) { ++ ida_simple_remove(&watchdog_ida, id); ++ return ret; ++ } ++ } ++ ++ devno = wdd->cdev.dev; ++ wdd->dev = device_create(watchdog_class, wdd->parent, devno, ++ NULL, "watchdog%d", wdd->id); ++ if (IS_ERR(wdd->dev)) { ++ watchdog_dev_unregister(wdd); ++ ida_simple_remove(&watchdog_ida, id); ++ ret = PTR_ERR(wdd->dev); + return ret; + } + +@@ -95,16 +176,50 @@ EXPORT_SYMBOL_GPL(watchdog_register_device); + void watchdog_unregister_device(struct watchdog_device *wdd) + { + int ret; ++ int devno; + + if (wdd == NULL) + return; + ++ devno = wdd->cdev.dev; + ret = watchdog_dev_unregister(wdd); + if (ret) +- pr_err("error unregistering /dev/watchdog (err=%d).\n", ret); ++ pr_err("error unregistering /dev/watchdog (err=%d)\n", ret); ++ device_destroy(watchdog_class, devno); ++ ida_simple_remove(&watchdog_ida, wdd->id); ++ wdd->dev = NULL; + } + EXPORT_SYMBOL_GPL(watchdog_unregister_device); + ++static int __init watchdog_init(void) ++{ ++ int err; ++ ++ watchdog_class = class_create(THIS_MODULE, "watchdog"); ++ if (IS_ERR(watchdog_class)) { ++ pr_err("couldn't create class\n"); ++ return PTR_ERR(watchdog_class); ++ } ++ ++ err = watchdog_dev_init(); ++ if (err < 0) { ++ class_destroy(watchdog_class); ++ return err; ++ } ++ ++ return 0; ++} ++ ++static void __exit watchdog_exit(void) ++{ ++ watchdog_dev_exit(); ++ class_destroy(watchdog_class); ++ ida_destroy(&watchdog_ida); ++} ++ ++subsys_initcall(watchdog_init); ++module_exit(watchdog_exit); ++ + MODULE_AUTHOR("Alan Cox "); + MODULE_AUTHOR("Wim Van Sebroeck "); + MODULE_DESCRIPTION("WatchDog Timer Driver Core"); +diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c +index 1199da0..6aaefba 100644 +--- a/drivers/watchdog/watchdog_dev.c ++++ b/drivers/watchdog/watchdog_dev.c +@@ -42,10 +42,12 @@ + #include /* For __init/__exit/... */ + #include /* For copy_to_user/put_user/... */ + +-/* make sure we only register one /dev/watchdog device */ +-static unsigned long watchdog_dev_busy; ++#include "watchdog_core.h" ++ ++/* the dev_t structure to store the dynamically allocated watchdog devices */ ++static dev_t watchdog_devt; + /* the watchdog device behind /dev/watchdog */ +-static struct watchdog_device *wdd; ++static struct watchdog_device *old_wdd; + + /* + * watchdog_ping: ping the watchdog. +@@ -59,13 +61,26 @@ static struct watchdog_device *wdd; + + static int watchdog_ping(struct watchdog_device *wddev) + { +- if (test_bit(WDOG_ACTIVE, &wddev->status)) { +- if (wddev->ops->ping) +- return wddev->ops->ping(wddev); /* ping the watchdog */ +- else +- return wddev->ops->start(wddev); /* restart watchdog */ ++ int err = 0; ++ ++ mutex_lock(&wddev->lock); ++ ++ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { ++ err = -ENODEV; ++ goto out_ping; + } +- return 0; ++ ++ if (!watchdog_active(wddev)) ++ goto out_ping; ++ ++ if (wddev->ops->ping) ++ err = wddev->ops->ping(wddev); /* ping the watchdog */ ++ else ++ err = wddev->ops->start(wddev); /* restart watchdog */ ++ ++out_ping: ++ mutex_unlock(&wddev->lock); ++ return err; + } + + /* +@@ -79,16 +94,25 @@ static int watchdog_ping(struct watchdog_device *wddev) + + static int watchdog_start(struct watchdog_device *wddev) + { +- int err; ++ int err = 0; + +- if (!test_bit(WDOG_ACTIVE, &wddev->status)) { +- err = wddev->ops->start(wddev); +- if (err < 0) +- return err; ++ mutex_lock(&wddev->lock); + +- set_bit(WDOG_ACTIVE, &wddev->status); ++ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { ++ err = -ENODEV; ++ goto out_start; + } +- return 0; ++ ++ if (watchdog_active(wddev)) ++ goto out_start; ++ ++ err = wddev->ops->start(wddev); ++ if (err == 0) ++ set_bit(WDOG_ACTIVE, &wddev->status); ++ ++out_start: ++ mutex_unlock(&wddev->lock); ++ return err; + } + + /* +@@ -103,22 +127,154 @@ static int watchdog_start(struct watchdog_device *wddev) + + static int watchdog_stop(struct watchdog_device *wddev) + { +- int err = -EBUSY; ++ int err = 0; + +- if (test_bit(WDOG_NO_WAY_OUT, &wddev->status)) { +- pr_info("%s: nowayout prevents watchdog to be stopped!\n", +- wddev->info->identity); +- return err; ++ mutex_lock(&wddev->lock); ++ ++ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { ++ err = -ENODEV; ++ goto out_stop; + } + +- if (test_bit(WDOG_ACTIVE, &wddev->status)) { +- err = wddev->ops->stop(wddev); +- if (err < 0) +- return err; ++ if (!watchdog_active(wddev)) ++ goto out_stop; ++ ++ if (test_bit(WDOG_NO_WAY_OUT, &wddev->status)) { ++ dev_info(wddev->dev, "nowayout prevents watchdog being stopped!\n"); ++ err = -EBUSY; ++ goto out_stop; ++ } + ++ err = wddev->ops->stop(wddev); ++ if (err == 0) + clear_bit(WDOG_ACTIVE, &wddev->status); ++ ++out_stop: ++ mutex_unlock(&wddev->lock); ++ return err; ++} ++ ++/* ++ * watchdog_get_status: wrapper to get the watchdog status ++ * @wddev: the watchdog device to get the status from ++ * @status: the status of the watchdog device ++ * ++ * Get the watchdog's status flags. ++ */ ++ ++static int watchdog_get_status(struct watchdog_device *wddev, ++ unsigned int *status) ++{ ++ int err = 0; ++ ++ *status = 0; ++ if (!wddev->ops->status) ++ return -EOPNOTSUPP; ++ ++ mutex_lock(&wddev->lock); ++ ++ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { ++ err = -ENODEV; ++ goto out_status; + } +- return 0; ++ ++ *status = wddev->ops->status(wddev); ++ ++out_status: ++ mutex_unlock(&wddev->lock); ++ return err; ++} ++ ++/* ++ * watchdog_set_timeout: set the watchdog timer timeout ++ * @wddev: the watchdog device to set the timeout for ++ * @timeout: timeout to set in seconds ++ */ ++ ++static int watchdog_set_timeout(struct watchdog_device *wddev, ++ unsigned int timeout) ++{ ++ int err; ++ ++ if ((wddev->ops->set_timeout == NULL) || ++ !(wddev->info->options & WDIOF_SETTIMEOUT)) ++ return -EOPNOTSUPP; ++ ++ if (watchdog_timeout_invalid(wddev, timeout)) ++ return -EINVAL; ++ ++ mutex_lock(&wddev->lock); ++ ++ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { ++ err = -ENODEV; ++ goto out_timeout; ++ } ++ ++ err = wddev->ops->set_timeout(wddev, timeout); ++ ++out_timeout: ++ mutex_unlock(&wddev->lock); ++ return err; ++} ++ ++/* ++ * watchdog_get_timeleft: wrapper to get the time left before a reboot ++ * @wddev: the watchdog device to get the remaining time from ++ * @timeleft: the time that's left ++ * ++ * Get the time before a watchdog will reboot (if not pinged). ++ */ ++ ++static int watchdog_get_timeleft(struct watchdog_device *wddev, ++ unsigned int *timeleft) ++{ ++ int err = 0; ++ ++ *timeleft = 0; ++ if (!wddev->ops->get_timeleft) ++ return -EOPNOTSUPP; ++ ++ mutex_lock(&wddev->lock); ++ ++ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { ++ err = -ENODEV; ++ goto out_timeleft; ++ } ++ ++ *timeleft = wddev->ops->get_timeleft(wddev); ++ ++out_timeleft: ++ mutex_unlock(&wddev->lock); ++ return err; ++} ++ ++/* ++ * watchdog_ioctl_op: call the watchdog drivers ioctl op if defined ++ * @wddev: the watchdog device to do the ioctl on ++ * @cmd: watchdog command ++ * @arg: argument pointer ++ */ ++ ++static int watchdog_ioctl_op(struct watchdog_device *wddev, unsigned int cmd, ++ unsigned long arg) ++{ ++ int err; ++ ++ if (!wddev->ops->ioctl) ++ return -ENOIOCTLCMD; ++ ++ mutex_lock(&wddev->lock); ++ ++ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { ++ err = -ENODEV; ++ goto out_ioctl; ++ } ++ ++ err = wddev->ops->ioctl(wddev, cmd, arg); ++ ++out_ioctl: ++ mutex_unlock(&wddev->lock); ++ return err; + } + + /* +@@ -136,6 +292,7 @@ static int watchdog_stop(struct watchdog_device *wddev) + static ssize_t watchdog_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) + { ++ struct watchdog_device *wdd = file->private_data; + size_t i; + char c; + +@@ -175,23 +332,24 @@ static ssize_t watchdog_write(struct file *file, const char __user *data, + static long watchdog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) + { ++ struct watchdog_device *wdd = file->private_data; + void __user *argp = (void __user *)arg; + int __user *p = argp; + unsigned int val; + int err; + +- if (wdd->ops->ioctl) { +- err = wdd->ops->ioctl(wdd, cmd, arg); +- if (err != -ENOIOCTLCMD) +- return err; +- } ++ err = watchdog_ioctl_op(wdd, cmd, arg); ++ if (err != -ENOIOCTLCMD) ++ return err; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, wdd->info, + sizeof(struct watchdog_info)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: +- val = wdd->ops->status ? wdd->ops->status(wdd) : 0; ++ err = watchdog_get_status(wdd, &val); ++ if (err == -ENODEV) ++ return err; + return put_user(val, p); + case WDIOC_GETBOOTSTATUS: + return put_user(wdd->bootstatus, p); +@@ -215,18 +373,11 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, + watchdog_ping(wdd); + return 0; + case WDIOC_SETTIMEOUT: +- if ((wdd->ops->set_timeout == NULL) || +- !(wdd->info->options & WDIOF_SETTIMEOUT)) +- return -EOPNOTSUPP; + if (get_user(val, p)) + return -EFAULT; +- if ((wdd->max_timeout != 0) && +- (val < wdd->min_timeout || val > wdd->max_timeout)) +- return -EINVAL; +- err = wdd->ops->set_timeout(wdd, val); ++ err = watchdog_set_timeout(wdd, val); + if (err < 0) + return err; +- wdd->timeout = val; + /* If the watchdog is active then we send a keepalive ping + * to make sure that the watchdog keep's running (and if + * possible that it takes the new timeout) */ +@@ -237,17 +388,22 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, + if (wdd->timeout == 0) + return -EOPNOTSUPP; + return put_user(wdd->timeout, p); ++ case WDIOC_GETTIMELEFT: ++ err = watchdog_get_timeleft(wdd, &val); ++ if (err) ++ return err; ++ return put_user(val, p); + default: + return -ENOTTY; + } + } + + /* +- * watchdog_open: open the /dev/watchdog device. ++ * watchdog_open: open the /dev/watchdog* devices. + * @inode: inode of device + * @file: file handle to device + * +- * When the /dev/watchdog device gets opened, we start the watchdog. ++ * When the /dev/watchdog* device gets opened, we start the watchdog. + * Watch out: the /dev/watchdog device is single open, so we make sure + * it can only be opened once. + */ +@@ -255,6 +411,13 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, + static int watchdog_open(struct inode *inode, struct file *file) + { + int err = -EBUSY; ++ struct watchdog_device *wdd; ++ ++ /* Get the corresponding watchdog device */ ++ if (imajor(inode) == MISC_MAJOR) ++ wdd = old_wdd; ++ else ++ wdd = container_of(inode->i_cdev, struct watchdog_device, cdev); + + /* the watchdog is single open! */ + if (test_and_set_bit(WDOG_DEV_OPEN, &wdd->status)) +@@ -271,6 +434,11 @@ static int watchdog_open(struct inode *inode, struct file *file) + if (err < 0) + goto out_mod; + ++ file->private_data = wdd; ++ ++ if (wdd->ops->ref) ++ wdd->ops->ref(wdd); ++ + /* dev/watchdog is a virtual (and thus non-seekable) filesystem */ + return nonseekable_open(inode, file); + +@@ -282,9 +450,9 @@ out: + } + + /* +- * watchdog_release: release the /dev/watchdog device. +- * @inode: inode of device +- * @file: file handle to device ++ * watchdog_release: release the watchdog device. ++ * @inode: inode of device ++ * @file: file handle to device + * + * This is the code for when /dev/watchdog gets closed. We will only + * stop the watchdog when we have received the magic char (and nowayout +@@ -293,6 +461,7 @@ out: + + static int watchdog_release(struct inode *inode, struct file *file) + { ++ struct watchdog_device *wdd = file->private_data; + int err = -EBUSY; + + /* +@@ -300,13 +469,18 @@ static int watchdog_release(struct inode *inode, struct file *file) + * or if WDIOF_MAGICCLOSE is not set. If nowayout was set then + * watchdog_stop will fail. + */ +- if (test_and_clear_bit(WDOG_ALLOW_RELEASE, &wdd->status) || +- !(wdd->info->options & WDIOF_MAGICCLOSE)) ++ if (!test_bit(WDOG_ACTIVE, &wdd->status)) ++ err = 0; ++ else if (test_and_clear_bit(WDOG_ALLOW_RELEASE, &wdd->status) || ++ !(wdd->info->options & WDIOF_MAGICCLOSE)) + err = watchdog_stop(wdd); + + /* If the watchdog was not stopped, send a keepalive ping */ + if (err < 0) { +- pr_crit("%s: watchdog did not stop!\n", wdd->info->identity); ++ mutex_lock(&wdd->lock); ++ if (!test_bit(WDOG_UNREGISTERED, &wdd->status)) ++ dev_crit(wdd->dev, "watchdog did not stop!\n"); ++ mutex_unlock(&wdd->lock); + watchdog_ping(wdd); + } + +@@ -316,6 +490,10 @@ static int watchdog_release(struct inode *inode, struct file *file) + /* make sure that /dev/watchdog can be re-opened */ + clear_bit(WDOG_DEV_OPEN, &wdd->status); + ++ /* Note wdd may be gone after this, do not use after this! */ ++ if (wdd->ops->unref) ++ wdd->ops->unref(wdd); ++ + return 0; + } + +@@ -334,62 +512,93 @@ static struct miscdevice watchdog_miscdev = { + }; + + /* +- * watchdog_dev_register: ++ * watchdog_dev_register: register a watchdog device + * @watchdog: watchdog device + * +- * Register a watchdog device as /dev/watchdog. /dev/watchdog +- * is actually a miscdevice and thus we set it up like that. ++ * Register a watchdog device including handling the legacy ++ * /dev/watchdog node. /dev/watchdog is actually a miscdevice and ++ * thus we set it up like that. + */ + + int watchdog_dev_register(struct watchdog_device *watchdog) + { +- int err; +- +- /* Only one device can register for /dev/watchdog */ +- if (test_and_set_bit(0, &watchdog_dev_busy)) { +- pr_err("only one watchdog can use /dev/watchdog.\n"); +- return -EBUSY; ++ int err, devno; ++ ++ if (watchdog->id == 0) { ++ old_wdd = watchdog; ++ watchdog_miscdev.parent = watchdog->parent; ++ err = misc_register(&watchdog_miscdev); ++ if (err != 0) { ++ pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n", ++ watchdog->info->identity, WATCHDOG_MINOR, err); ++ if (err == -EBUSY) ++ pr_err("%s: a legacy watchdog module is probably present.\n", ++ watchdog->info->identity); ++ old_wdd = NULL; ++ return err; ++ } + } + +- wdd = watchdog; +- +- err = misc_register(&watchdog_miscdev); +- if (err != 0) { +- pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n", +- watchdog->info->identity, WATCHDOG_MINOR, err); +- goto out; ++ /* Fill in the data structures */ ++ devno = MKDEV(MAJOR(watchdog_devt), watchdog->id); ++ cdev_init(&watchdog->cdev, &watchdog_fops); ++ watchdog->cdev.owner = watchdog->ops->owner; ++ ++ /* Add the device */ ++ err = cdev_add(&watchdog->cdev, devno, 1); ++ if (err) { ++ pr_err("watchdog%d unable to add device %d:%d\n", ++ watchdog->id, MAJOR(watchdog_devt), watchdog->id); ++ if (watchdog->id == 0) { ++ misc_deregister(&watchdog_miscdev); ++ old_wdd = NULL; ++ } + } +- +- return 0; +- +-out: +- wdd = NULL; +- clear_bit(0, &watchdog_dev_busy); + return err; + } + + /* +- * watchdog_dev_unregister: ++ * watchdog_dev_unregister: unregister a watchdog device + * @watchdog: watchdog device + * +- * Deregister the /dev/watchdog device. ++ * Unregister the watchdog and if needed the legacy /dev/watchdog device. + */ + + int watchdog_dev_unregister(struct watchdog_device *watchdog) + { +- /* Check that a watchdog device was registered in the past */ +- if (!test_bit(0, &watchdog_dev_busy) || !wdd) +- return -ENODEV; +- +- /* We can only unregister the watchdog device that was registered */ +- if (watchdog != wdd) { +- pr_err("%s: watchdog was not registered as /dev/watchdog.\n", +- watchdog->info->identity); +- return -ENODEV; ++ mutex_lock(&watchdog->lock); ++ set_bit(WDOG_UNREGISTERED, &watchdog->status); ++ mutex_unlock(&watchdog->lock); ++ ++ cdev_del(&watchdog->cdev); ++ if (watchdog->id == 0) { ++ misc_deregister(&watchdog_miscdev); ++ old_wdd = NULL; + } +- +- misc_deregister(&watchdog_miscdev); +- wdd = NULL; +- clear_bit(0, &watchdog_dev_busy); + return 0; + } ++ ++/* ++ * watchdog_dev_init: init dev part of watchdog core ++ * ++ * Allocate a range of chardev nodes to use for watchdog devices ++ */ ++ ++int __init watchdog_dev_init(void) ++{ ++ int err = alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog"); ++ if (err < 0) ++ pr_err("watchdog: unable to allocate char dev region\n"); ++ return err; ++} ++ ++/* ++ * watchdog_dev_exit: exit dev part of watchdog core ++ * ++ * Release the range of chardev nodes used for watchdog devices ++ */ ++ ++void __exit watchdog_dev_exit(void) ++{ ++ unregister_chrdev_region(watchdog_devt, MAX_DOGS); ++} +diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h +index 111843f..395b70e 100644 +--- a/include/linux/watchdog.h ++++ b/include/linux/watchdog.h +@@ -5,59 +5,14 @@ + * by Ken Hollis + * + */ +- + #ifndef _LINUX_WATCHDOG_H + #define _LINUX_WATCHDOG_H + +-#include +-#include +- +-#define WATCHDOG_IOCTL_BASE 'W' +- +-struct watchdog_info { +- __u32 options; /* Options the card/driver supports */ +- __u32 firmware_version; /* Firmware version of the card */ +- __u8 identity[32]; /* Identity of the board */ +-}; +- +-#define WDIOC_GETSUPPORT _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info) +-#define WDIOC_GETSTATUS _IOR(WATCHDOG_IOCTL_BASE, 1, int) +-#define WDIOC_GETBOOTSTATUS _IOR(WATCHDOG_IOCTL_BASE, 2, int) +-#define WDIOC_GETTEMP _IOR(WATCHDOG_IOCTL_BASE, 3, int) +-#define WDIOC_SETOPTIONS _IOR(WATCHDOG_IOCTL_BASE, 4, int) +-#define WDIOC_KEEPALIVE _IOR(WATCHDOG_IOCTL_BASE, 5, int) +-#define WDIOC_SETTIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 6, int) +-#define WDIOC_GETTIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 7, int) +-#define WDIOC_SETPRETIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 8, int) +-#define WDIOC_GETPRETIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 9, int) +-#define WDIOC_GETTIMELEFT _IOR(WATCHDOG_IOCTL_BASE, 10, int) +- +-#define WDIOF_UNKNOWN -1 /* Unknown flag error */ +-#define WDIOS_UNKNOWN -1 /* Unknown status error */ + +-#define WDIOF_OVERHEAT 0x0001 /* Reset due to CPU overheat */ +-#define WDIOF_FANFAULT 0x0002 /* Fan failed */ +-#define WDIOF_EXTERN1 0x0004 /* External relay 1 */ +-#define WDIOF_EXTERN2 0x0008 /* External relay 2 */ +-#define WDIOF_POWERUNDER 0x0010 /* Power bad/power fault */ +-#define WDIOF_CARDRESET 0x0020 /* Card previously reset the CPU */ +-#define WDIOF_POWEROVER 0x0040 /* Power over voltage */ +-#define WDIOF_SETTIMEOUT 0x0080 /* Set timeout (in seconds) */ +-#define WDIOF_MAGICCLOSE 0x0100 /* Supports magic close char */ +-#define WDIOF_PRETIMEOUT 0x0200 /* Pretimeout (in seconds), get/set */ +-#define WDIOF_KEEPALIVEPING 0x8000 /* Keep alive ping reply */ +- +-#define WDIOS_DISABLECARD 0x0001 /* Turn off the watchdog timer */ +-#define WDIOS_ENABLECARD 0x0002 /* Turn on the watchdog timer */ +-#define WDIOS_TEMPPANIC 0x0004 /* Kernel panic on temperature trip */ +- +-#ifdef __KERNEL__ +- +-#ifdef CONFIG_WATCHDOG_NOWAYOUT +-#define WATCHDOG_NOWAYOUT 1 +-#else +-#define WATCHDOG_NOWAYOUT 0 +-#endif ++#include ++#include ++#include ++#include + + struct watchdog_ops; + struct watchdog_device; +@@ -70,6 +25,9 @@ struct watchdog_device; + * @ping: The routine that sends a keepalive ping to the watchdog device. + * @status: The routine that shows the status of the watchdog device. + * @set_timeout:The routine for setting the watchdog devices timeout value. ++ * @get_timeleft:The routine that get's the time that's left before a reset. ++ * @ref: The ref operation for dyn. allocated watchdog_device structs ++ * @unref: The unref operation for dyn. allocated watchdog_device structs + * @ioctl: The routines that handles extra ioctl calls. + * + * The watchdog_ops structure contains a list of low-level operations +@@ -86,11 +44,18 @@ struct watchdog_ops { + int (*ping)(struct watchdog_device *); + unsigned int (*status)(struct watchdog_device *); + int (*set_timeout)(struct watchdog_device *, unsigned int); ++ unsigned int (*get_timeleft)(struct watchdog_device *); ++ void (*ref)(struct watchdog_device *); ++ void (*unref)(struct watchdog_device *); + long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long); + }; + + /** struct watchdog_device - The structure that defines a watchdog device + * ++ * @id: The watchdog's ID. (Allocated by watchdog_register_device) ++ * @cdev: The watchdog's Character device. ++ * @dev: The device for our watchdog ++ * @parent: The parent bus device + * @info: Pointer to a watchdog_info structure. + * @ops: Pointer to the list of watchdog operations. + * @bootstatus: Status of the watchdog device at boot. +@@ -98,6 +63,7 @@ struct watchdog_ops { + * @min_timeout:The watchdog devices minimum timeout value. + * @max_timeout:The watchdog devices maximum timeout value. + * @driver-data:Pointer to the drivers private data. ++ * @lock: Lock for watchdog core internal use only. + * @status: Field that contains the devices internal status bits. + * + * The watchdog_device structure contains all information about a +@@ -105,8 +71,15 @@ struct watchdog_ops { + * + * The driver-data field may not be accessed directly. It must be accessed + * via the watchdog_set_drvdata and watchdog_get_drvdata helpers. ++ * ++ * The lock field is for watchdog core internal use only and should not be ++ * touched. + */ + struct watchdog_device { ++ int id; ++ struct cdev cdev; ++ struct device *dev; ++ struct device *parent; + const struct watchdog_info *info; + const struct watchdog_ops *ops; + unsigned int bootstatus; +@@ -114,14 +87,39 @@ struct watchdog_device { + unsigned int min_timeout; + unsigned int max_timeout; + void *driver_data; ++ struct mutex lock; + unsigned long status; + /* Bit numbers for status flags */ + #define WDOG_ACTIVE 0 /* Is the watchdog running/active */ + #define WDOG_DEV_OPEN 1 /* Opened via /dev/watchdog ? */ + #define WDOG_ALLOW_RELEASE 2 /* Did we receive the magic char ? */ + #define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */ ++#define WDOG_UNREGISTERED 4 /* Has the device been unregistered */ + }; + ++#define WATCHDOG_NOWAYOUT IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT) ++#define WATCHDOG_NOWAYOUT_INIT_STATUS (WATCHDOG_NOWAYOUT << WDOG_NO_WAY_OUT) ++ ++/* Use the following function to check whether or not the watchdog is active */ ++static inline bool watchdog_active(struct watchdog_device *wdd) ++{ ++ return test_bit(WDOG_ACTIVE, &wdd->status); ++} ++ ++/* Use the following function to set the nowayout feature */ ++static inline void watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout) ++{ ++ if (nowayout) ++ set_bit(WDOG_NO_WAY_OUT, &wdd->status); ++} ++ ++/* Use the following function to check if a timeout value is invalid */ ++static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigned int t) ++{ ++ return ((wdd->max_timeout != 0) && ++ (t < wdd->min_timeout || t > wdd->max_timeout)); ++} ++ + /* Use the following functions to manipulate watchdog driver specific data */ + static inline void watchdog_set_drvdata(struct watchdog_device *wdd, void *data) + { +@@ -133,10 +131,10 @@ static inline void *watchdog_get_drvdata(struct watchdog_device *wdd) + return wdd->driver_data; + } + +-/* drivers/watchdog/core/watchdog_core.c */ ++/* drivers/watchdog/watchdog_core.c */ ++extern int watchdog_init_timeout(struct watchdog_device *wdd, ++ unsigned int timeout_parm, struct device *dev); + extern int watchdog_register_device(struct watchdog_device *); + extern void watchdog_unregister_device(struct watchdog_device *); + +-#endif /* __KERNEL__ */ +- + #endif /* ifndef _LINUX_WATCHDOG_H */ diff --git a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/series b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/series index f4d87389..f958b6b7 100644 --- a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/series +++ b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/series @@ -244,3 +244,5 @@ platform-accton-as7712_32x-device-drivers.patch platform-accton-as5812_54x-device-drivers.patch platform-accton-as6812_32x-device-drivers.patch platform-accton-as5812_54t-device-drivers.patch +driver-mfd-lpc-ich.patch +driver-watchdog-itco-wd.patch