diff options
Diffstat (limited to 'drivers/watchdog')
-rw-r--r-- | drivers/watchdog/Kconfig | 14 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/da9052_wdt.c | 251 | ||||
-rw-r--r-- | drivers/watchdog/iTCO_vendor.h | 6 | ||||
-rw-r--r-- | drivers/watchdog/iTCO_vendor_support.c | 43 | ||||
-rw-r--r-- | drivers/watchdog/iTCO_wdt.c | 533 | ||||
-rw-r--r-- | drivers/watchdog/lantiq_wdt.c | 56 | ||||
-rw-r--r-- | drivers/watchdog/sp805_wdt.c | 249 | ||||
-rw-r--r-- | drivers/watchdog/via_wdt.c | 2 | ||||
-rw-r--r-- | drivers/watchdog/watchdog_core.c | 74 | ||||
-rw-r--r-- | drivers/watchdog/watchdog_core.h (renamed from drivers/watchdog/watchdog_dev.h) | 8 | ||||
-rw-r--r-- | drivers/watchdog/watchdog_dev.c | 375 |
12 files changed, 882 insertions, 730 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index a18bf6358eb8..fe819b76de56 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -64,6 +64,18 @@ config SOFT_WATCHDOG To compile this driver as a module, choose M here: the module will be called softdog. +config DA9052_WATCHDOG + tristate "Dialog DA9052 Watchdog" + depends on PMIC_DA9052 + select WATCHDOG_CORE + help + Support for the watchdog in the DA9052 PMIC. Watchdog trigger + cause system reset. + + Say Y here to include support for the DA9052 watchdog. + Alternatively say M to compile the driver as a module, + which will be called da9052_wdt. + config WM831X_WATCHDOG tristate "WM831x watchdog" depends on MFD_WM831X @@ -87,6 +99,7 @@ config WM8350_WATCHDOG config ARM_SP805_WATCHDOG tristate "ARM SP805 Watchdog" depends on ARM_AMBA + select WATCHDOG_CORE help ARM Primecell SP805 Watchdog timer. This will reboot your system when the timeout is reached. @@ -565,6 +578,7 @@ config INTEL_SCU_WATCHDOG config ITCO_WDT tristate "Intel TCO Timer/Watchdog" depends on (X86 || IA64) && PCI + select LPC_ICH ---help--- Hardware driver for the intel TCO timer based watchdog devices. These drivers are included in the Intel 82801 I/O Controller diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 442bfbe0882a..572b39bed06a 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -163,6 +163,7 @@ obj-$(CONFIG_WATCHDOG_CP1XXX) += cpwd.o obj-$(CONFIG_XEN_WDT) += xen_wdt.o # Architecture Independent +obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o diff --git a/drivers/watchdog/da9052_wdt.c b/drivers/watchdog/da9052_wdt.c new file mode 100644 index 000000000000..3f75129eb0a9 --- /dev/null +++ b/drivers/watchdog/da9052_wdt.c @@ -0,0 +1,251 @@ +/* + * System monitoring driver for DA9052 PMICs. + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: Anthony Olech <Anthony.Olech@diasemi.com> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> +#include <linux/time.h> +#include <linux/watchdog.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/jiffies.h> +#include <linux/delay.h> + +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/da9052.h> + +#define DA9052_DEF_TIMEOUT 4 +#define DA9052_TWDMIN 256 + +struct da9052_wdt_data { + struct watchdog_device wdt; + struct da9052 *da9052; + struct kref kref; + unsigned long jpast; +}; + +static const struct { + u8 reg_val; + int time; /* Seconds */ +} da9052_wdt_maps[] = { + { 1, 2 }, + { 2, 4 }, + { 3, 8 }, + { 4, 16 }, + { 5, 32 }, + { 5, 33 }, /* Actual time 32.768s so included both 32s and 33s */ + { 6, 65 }, + { 6, 66 }, /* Actual time 65.536s so include both, 65s and 66s */ + { 7, 131 }, +}; + + +static void da9052_wdt_release_resources(struct kref *r) +{ + struct da9052_wdt_data *driver_data = + container_of(r, struct da9052_wdt_data, kref); + + kfree(driver_data); +} + +static int da9052_wdt_set_timeout(struct watchdog_device *wdt_dev, + unsigned int timeout) +{ + struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); + struct da9052 *da9052 = driver_data->da9052; + int ret, i; + + /* + * Disable the Watchdog timer before setting + * new time out. + */ + ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, + DA9052_CONTROLD_TWDSCALE, 0); + if (ret < 0) { + dev_err(da9052->dev, "Failed to disable watchdog bit, %d\n", + ret); + return ret; + } + if (timeout) { + /* + * To change the timeout, da9052 needs to + * be disabled for at least 150 us. + */ + udelay(150); + + /* Set the desired timeout */ + for (i = 0; i < ARRAY_SIZE(da9052_wdt_maps); i++) + if (da9052_wdt_maps[i].time == timeout) + break; + + if (i == ARRAY_SIZE(da9052_wdt_maps)) + ret = -EINVAL; + else + ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, + DA9052_CONTROLD_TWDSCALE, + da9052_wdt_maps[i].reg_val); + if (ret < 0) { + dev_err(da9052->dev, + "Failed to update timescale bit, %d\n", ret); + return ret; + } + + wdt_dev->timeout = timeout; + driver_data->jpast = jiffies; + } + + return 0; +} + +static void da9052_wdt_ref(struct watchdog_device *wdt_dev) +{ + struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); + + kref_get(&driver_data->kref); +} + +static void da9052_wdt_unref(struct watchdog_device *wdt_dev) +{ + struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); + + kref_put(&driver_data->kref, da9052_wdt_release_resources); +} + +static int da9052_wdt_start(struct watchdog_device *wdt_dev) +{ + return da9052_wdt_set_timeout(wdt_dev, wdt_dev->timeout); +} + +static int da9052_wdt_stop(struct watchdog_device *wdt_dev) +{ + return da9052_wdt_set_timeout(wdt_dev, 0); +} + +static int da9052_wdt_ping(struct watchdog_device *wdt_dev) +{ + struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); + struct da9052 *da9052 = driver_data->da9052; + unsigned long msec, jnow = jiffies; + int ret; + + /* + * We have a minimum time for watchdog window called TWDMIN. A write + * to the watchdog before this elapsed time should cause an error. + */ + msec = (jnow - driver_data->jpast) * 1000/HZ; + if (msec < DA9052_TWDMIN) + mdelay(msec); + + /* Reset the watchdog timer */ + ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, + DA9052_CONTROLD_WATCHDOG, 1 << 7); + if (ret < 0) + goto err_strobe; + + /* + * FIXME: Reset the watchdog core, in general PMIC + * is supposed to do this + */ + ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, + DA9052_CONTROLD_WATCHDOG, 0 << 7); +err_strobe: + return ret; +} + +static struct watchdog_info da9052_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "DA9052 Watchdog", +}; + +static const struct watchdog_ops da9052_wdt_ops = { + .owner = THIS_MODULE, + .start = da9052_wdt_start, + .stop = da9052_wdt_stop, + .ping = da9052_wdt_ping, + .set_timeout = da9052_wdt_set_timeout, + .ref = da9052_wdt_ref, + .unref = da9052_wdt_unref, +}; + + +static int __devinit da9052_wdt_probe(struct platform_device *pdev) +{ + struct da9052 *da9052 = dev_get_drvdata(pdev->dev.parent); + struct da9052_wdt_data *driver_data; + struct watchdog_device *da9052_wdt; + int ret; + + driver_data = devm_kzalloc(&pdev->dev, sizeof(*driver_data), + GFP_KERNEL); + if (!driver_data) { + dev_err(da9052->dev, "Unable to alloacate watchdog device\n"); + ret = -ENOMEM; + goto err; + } + driver_data->da9052 = da9052; + + da9052_wdt = &driver_data->wdt; + + da9052_wdt->timeout = DA9052_DEF_TIMEOUT; + da9052_wdt->info = &da9052_wdt_info; + da9052_wdt->ops = &da9052_wdt_ops; + watchdog_set_drvdata(da9052_wdt, driver_data); + + kref_init(&driver_data->kref); + + ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, + DA9052_CONTROLD_TWDSCALE, 0); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to disable watchdog bits, %d\n", + ret); + goto err; + } + + ret = watchdog_register_device(&driver_data->wdt); + if (ret != 0) { + dev_err(da9052->dev, "watchdog_register_device() failed: %d\n", + ret); + goto err; + } + + dev_set_drvdata(&pdev->dev, driver_data); +err: + return ret; +} + +static int __devexit da9052_wdt_remove(struct platform_device *pdev) +{ + struct da9052_wdt_data *driver_data = dev_get_drvdata(&pdev->dev); + + watchdog_unregister_device(&driver_data->wdt); + kref_put(&driver_data->kref, da9052_wdt_release_resources); + + return 0; +} + +static struct platform_driver da9052_wdt_driver = { + .probe = da9052_wdt_probe, + .remove = __devexit_p(da9052_wdt_remove), + .driver = { + .name = "da9052-watchdog", + }, +}; + +module_platform_driver(da9052_wdt_driver); + +MODULE_AUTHOR("Anthony Olech <Anthony.Olech@diasemi.com>"); +MODULE_DESCRIPTION("DA9052 SM Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9052-watchdog"); diff --git a/drivers/watchdog/iTCO_vendor.h b/drivers/watchdog/iTCO_vendor.h index 9e27e6422f66..3c57b45537a2 100644 --- a/drivers/watchdog/iTCO_vendor.h +++ b/drivers/watchdog/iTCO_vendor.h @@ -1,8 +1,8 @@ /* iTCO Vendor Specific Support hooks */ #ifdef CONFIG_ITCO_VENDOR_SUPPORT -extern void iTCO_vendor_pre_start(unsigned long, unsigned int); -extern void iTCO_vendor_pre_stop(unsigned long); -extern void iTCO_vendor_pre_keepalive(unsigned long, unsigned int); +extern void iTCO_vendor_pre_start(struct resource *, unsigned int); +extern void iTCO_vendor_pre_stop(struct resource *); +extern void iTCO_vendor_pre_keepalive(struct resource *, unsigned int); extern void iTCO_vendor_pre_set_heartbeat(unsigned int); extern int iTCO_vendor_check_noreboot_on(void); #else diff --git a/drivers/watchdog/iTCO_vendor_support.c b/drivers/watchdog/iTCO_vendor_support.c index 2721d29ce243..b6b2f90b5d44 100644 --- a/drivers/watchdog/iTCO_vendor_support.c +++ b/drivers/watchdog/iTCO_vendor_support.c @@ -35,11 +35,6 @@ #include "iTCO_vendor.h" -/* iTCO defines */ -#define SMI_EN (acpibase + 0x30) /* SMI Control and Enable Register */ -#define TCOBASE (acpibase + 0x60) /* TCO base address */ -#define TCO1_STS (TCOBASE + 0x04) /* TCO1 Status Register */ - /* List of vendor support modes */ /* SuperMicro Pentium 3 Era 370SSE+-OEM1/P3TSSE */ #define SUPERMICRO_OLD_BOARD 1 @@ -82,24 +77,24 @@ MODULE_PARM_DESC(vendorsupport, "iTCO vendor specific support mode, default=" * 20.6 seconds. */ -static void supermicro_old_pre_start(unsigned long acpibase) +static void supermicro_old_pre_start(struct resource *smires) { unsigned long val32; /* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI# */ - val32 = inl(SMI_EN); + val32 = inl(smires->start); val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */ - outl(val32, SMI_EN); /* Needed to activate watchdog */ + outl(val32, smires->start); /* Needed to activate watchdog */ } -static void supermicro_old_pre_stop(unsigned long acpibase) +static void supermicro_old_pre_stop(struct resource *smires) { unsigned long val32; /* Bit 13: TCO_EN -> 1 = Enables the TCO logic to generate SMI# */ - val32 = inl(SMI_EN); + val32 = inl(smires->start); val32 |= 0x00002000; /* Turn on SMI clearing watchdog */ - outl(val32, SMI_EN); /* Needed to deactivate watchdog */ + outl(val32, smires->start); /* Needed to deactivate watchdog */ } /* @@ -270,66 +265,66 @@ static void supermicro_new_pre_set_heartbeat(unsigned int heartbeat) * Don't use this fix if you don't need to!!! */ -static void broken_bios_start(unsigned long acpibase) +static void broken_bios_start(struct resource *smires) { unsigned long val32; - val32 = inl(SMI_EN); + val32 = inl(smires->start); /* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI# Bit 0: GBL_SMI_EN -> 0 = No SMI# will be generated by ICH. */ val32 &= 0xffffdffe; - outl(val32, SMI_EN); + outl(val32, smires->start); } -static void broken_bios_stop(unsigned long acpibase) +static void broken_bios_stop(struct resource *smires) { unsigned long val32; - val32 = inl(SMI_EN); + val32 = inl(smires->start); /* Bit 13: TCO_EN -> 1 = Enables TCO logic generating an SMI# Bit 0: GBL_SMI_EN -> 1 = Turn global SMI on again. */ val32 |= 0x00002001; - outl(val32, SMI_EN); + outl(val32, smires->start); } /* * Generic Support Functions */ -void iTCO_vendor_pre_start(unsigned long acpibase, +void iTCO_vendor_pre_start(struct resource *smires, unsigned int heartbeat) { switch (vendorsupport) { case SUPERMICRO_OLD_BOARD: - supermicro_old_pre_start(acpibase); + supermicro_old_pre_start(smires); break; case SUPERMICRO_NEW_BOARD: supermicro_new_pre_start(heartbeat); break; case BROKEN_BIOS: - broken_bios_start(acpibase); + broken_bios_start(smires); break; } } EXPORT_SYMBOL(iTCO_vendor_pre_start); -void iTCO_vendor_pre_stop(unsigned long acpibase) +void iTCO_vendor_pre_stop(struct resource *smires) { switch (vendorsupport) { case SUPERMICRO_OLD_BOARD: - supermicro_old_pre_stop(acpibase); + supermicro_old_pre_stop(smires); break; case SUPERMICRO_NEW_BOARD: supermicro_new_pre_stop(); break; case BROKEN_BIOS: - broken_bios_stop(acpibase); + broken_bios_stop(smires); break; } } EXPORT_SYMBOL(iTCO_vendor_pre_stop); -void iTCO_vendor_pre_keepalive(unsigned long acpibase, unsigned int heartbeat) +void iTCO_vendor_pre_keepalive(struct resource *smires, unsigned int heartbeat) { if (vendorsupport == SUPERMICRO_NEW_BOARD) supermicro_new_pre_set_heartbeat(heartbeat); diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c index 9fecb95645a3..bc47e9012f37 100644 --- a/drivers/watchdog/iTCO_wdt.c +++ b/drivers/watchdog/iTCO_wdt.c @@ -66,316 +66,16 @@ #include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */ #include <linux/uaccess.h> /* For copy_to_user/put_user/... */ #include <linux/io.h> /* For inb/outb/... */ +#include <linux/mfd/core.h> +#include <linux/mfd/lpc_ich.h> #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 */ @@ -393,19 +93,18 @@ 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; + struct resource *tco_res; + struct resource *smi_res; + struct resource *gcs_res; /* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2)*/ unsigned long __iomem *gcs; /* 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 */ @@ -485,7 +184,7 @@ static int iTCO_wdt_start(void) 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, heartbeat); /* disable chipset's NO_REBOOT bit */ if (iTCO_wdt_unset_NO_REBOOT_bit()) { @@ -519,7 +218,7 @@ static int iTCO_wdt_stop(void) 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); @@ -541,7 +240,7 @@ static int iTCO_wdt_keepalive(void) { 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, heartbeat); /* Reload the timer by writing to the TCO Timer Counter register */ if (iTCO_wdt_private.iTCO_version == 2) @@ -786,83 +485,120 @@ static struct miscdevice iTCO_wdt_miscdev = { * Init & exit routines */ -static int __devinit iTCO_wdt_init(struct pci_dev *pdev, - const struct pci_device_id *ent, struct platform_device *dev) +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 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); + release_mem_region(iTCO_wdt_private.gcs_res->start, + resource_size(iTCO_wdt_private.gcs_res)); + } + + iTCO_wdt_private.tco_res = NULL; + iTCO_wdt_private.smi_res = NULL; + iTCO_wdt_private.gcs_res = NULL; + iTCO_wdt_private.gcs = NULL; +} + +static int __devinit iTCO_wdt_probe(struct platform_device *dev) { - int ret; - u32 base_address; - unsigned long RCBA; + int ret = -ENODEV; unsigned long val32; + struct lpc_ich_info *ich_info = dev->dev.platform_data; + + if (!ich_info) + goto out; + + spin_lock_init(&iTCO_wdt_private.io_lock); + + iTCO_wdt_private.tco_res = + platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_TCO); + if (!iTCO_wdt_private.tco_res) + goto out; + + iTCO_wdt_private.smi_res = + platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_SMI); + if (!iTCO_wdt_private.smi_res) + goto out; + + 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 register, we need it for the + * NO_REBOOT flag (TCO v2). */ - pci_read_config_dword(pdev, 0x40, &base_address); - base_address &= 0x0000ff80; - if (base_address == 0x00000000) { - /* Something's wrong here, ACPIBASE has to be set */ - pr_err("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) { - pr_err("RCBA is disabled by hardware/BIOS, device disabled\n"); - ret = -ENODEV; + iTCO_wdt_private.gcs_res = platform_get_resource(dev, + IORESOURCE_MEM, + ICH_RES_MEM_GCS); + + if (!iTCO_wdt_private.gcs_res) + goto out; + + if (!request_mem_region(iTCO_wdt_private.gcs_res->start, + resource_size(iTCO_wdt_private.gcs_res), dev->name)) { + ret = -EBUSY; goto out; } - RCBA = base_address & 0xffffc000; - iTCO_wdt_private.gcs = ioremap((RCBA + 0x3410), 4); + iTCO_wdt_private.gcs = ioremap(iTCO_wdt_private.gcs_res->start, + resource_size(iTCO_wdt_private.gcs_res)); + if (!iTCO_wdt_private.gcs) { + ret = -EIO; + goto unreg_gcs; + } } /* Check chipset's NO_REBOOT bit */ if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) { pr_info("unable to reset NO_REBOOT flag, device disabled by hardware/BIOS\n"); ret = -ENODEV; /* Cannot reset NO_REBOOT bit */ - goto out_unmap; + goto unmap_gcs; } /* 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")) { - pr_err("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; } 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")) { - pr_err("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; } - pr_info("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 */ @@ -883,7 +619,7 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev, if (ret != 0) { pr_err("cannot register miscdev on minor=%d (err=%d)\n", WATCHDOG_MINOR, ret); - goto unreg_region; + goto unreg_tco; } pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", @@ -891,62 +627,31 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev, return 0; -unreg_region: - release_region(TCOBASE, 0x20); -unreg_smi_en: - release_region(SMI_EN, 4); -out_unmap: +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: if (iTCO_wdt_private.iTCO_version == 2) iounmap(iTCO_wdt_private.gcs); -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); +unreg_gcs: 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) - pr_info("No device detected\n"); + release_mem_region(iTCO_wdt_private.gcs_res->start, + resource_size(iTCO_wdt_private.gcs_res)); +out: + iTCO_wdt_private.tco_res = NULL; + iTCO_wdt_private.smi_res = NULL; + iTCO_wdt_private.gcs_res = NULL; + iTCO_wdt_private.gcs = NULL; return ret; } static int __devexit 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; @@ -977,23 +682,11 @@ static int __init iTCO_wdt_init_module(void) 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); pr_info("Watchdog Module Unloaded\n"); } diff --git a/drivers/watchdog/lantiq_wdt.c b/drivers/watchdog/lantiq_wdt.c index a9593a3a32a0..2e74c3a8ee58 100644 --- a/drivers/watchdog/lantiq_wdt.c +++ b/drivers/watchdog/lantiq_wdt.c @@ -13,14 +13,15 @@ #include <linux/fs.h> #include <linux/miscdevice.h> #include <linux/watchdog.h> -#include <linux/platform_device.h> +#include <linux/of_platform.h> #include <linux/uaccess.h> #include <linux/clk.h> #include <linux/io.h> -#include <lantiq.h> +#include <lantiq_soc.h> -/* Section 3.4 of the datasheet +/* + * Section 3.4 of the datasheet * The password sequence protects the WDT control register from unintended * write actions, which might cause malfunction of the WDT. * @@ -70,7 +71,8 @@ ltq_wdt_disable(void) { /* write the first password magic */ ltq_w32(LTQ_WDT_PW1, ltq_wdt_membase + LTQ_WDT_CR); - /* write the second password magic with no config + /* + * write the second password magic with no config * this turns the watchdog off */ ltq_w32(LTQ_WDT_PW2, ltq_wdt_membase + LTQ_WDT_CR); @@ -184,7 +186,7 @@ static struct miscdevice ltq_wdt_miscdev = { .fops = <q_wdt_fops, }; -static int __init +static int __devinit ltq_wdt_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -194,28 +196,27 @@ ltq_wdt_probe(struct platform_device *pdev) dev_err(&pdev->dev, "cannot obtain I/O memory region"); return -ENOENT; } - res = devm_request_mem_region(&pdev->dev, res->start, - resource_size(res), dev_name(&pdev->dev)); - if (!res) { - dev_err(&pdev->dev, "cannot request I/O memory region"); - return -EBUSY; - } - ltq_wdt_membase = devm_ioremap_nocache(&pdev->dev, res->start, - resource_size(res)); + + ltq_wdt_membase = devm_request_and_ioremap(&pdev->dev, res); if (!ltq_wdt_membase) { dev_err(&pdev->dev, "cannot remap I/O memory region\n"); return -ENOMEM; } /* we do not need to enable the clock as it is always running */ - clk = clk_get(&pdev->dev, "io"); - WARN_ON(!clk); + clk = clk_get_io(); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Failed to get clock\n"); + return -ENOENT; + } ltq_io_region_clk_rate = clk_get_rate(clk); clk_put(clk); + /* find out if the watchdog caused the last reboot */ if (ltq_reset_cause() == LTQ_RST_CAUSE_WDTRST) ltq_wdt_bootstatus = WDIOF_CARDRESET; + dev_info(&pdev->dev, "Init done\n"); return misc_register(<q_wdt_miscdev); } @@ -227,33 +228,26 @@ ltq_wdt_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id ltq_wdt_match[] = { + { .compatible = "lantiq,wdt" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ltq_wdt_match); static struct platform_driver ltq_wdt_driver = { + .probe = ltq_wdt_probe, .remove = __devexit_p(ltq_wdt_remove), .driver = { - .name = "ltq_wdt", + .name = "wdt", .owner = THIS_MODULE, + .of_match_table = ltq_wdt_match, }, }; -static int __init -init_ltq_wdt(void) -{ - return platform_driver_probe(<q_wdt_driver, ltq_wdt_probe); -} - -static void __exit -exit_ltq_wdt(void) -{ - return platform_driver_unregister(<q_wdt_driver); -} - -module_init(init_ltq_wdt); -module_exit(exit_ltq_wdt); +module_platform_driver(ltq_wdt_driver); module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); - MODULE_AUTHOR("John Crispin <blogic@openwrt.org>"); MODULE_DESCRIPTION("Lantiq SoC Watchdog"); MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c index bbb170e50055..afcd13676542 100644 --- a/drivers/watchdog/sp805_wdt.c +++ b/drivers/watchdog/sp805_wdt.c @@ -16,20 +16,17 @@ #include <linux/amba/bus.h> #include <linux/bitops.h> #include <linux/clk.h> -#include <linux/fs.h> #include <linux/init.h> #include <linux/io.h> #include <linux/ioport.h> #include <linux/kernel.h> #include <linux/math64.h> -#include <linux/miscdevice.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/pm.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/types.h> -#include <linux/uaccess.h> #include <linux/watchdog.h> /* default timeout in seconds */ @@ -56,6 +53,7 @@ /** * struct sp805_wdt: sp805 wdt device structure + * @wdd: instance of struct watchdog_device * @lock: spin lock protecting dev structure and io access * @base: base address of wdt * @clk: clock structure of wdt @@ -65,24 +63,24 @@ * @timeout: current programmed timeout */ struct sp805_wdt { + struct watchdog_device wdd; spinlock_t lock; void __iomem *base; struct clk *clk; struct amba_device *adev; - unsigned long status; - #define WDT_BUSY 0 - #define WDT_CAN_BE_CLOSED 1 unsigned int load_val; unsigned int timeout; }; -/* local variables */ -static struct sp805_wdt *wdt; static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Set to 1 to keep watchdog running after device release"); /* This routine finds load value that will reset system in required timout */ -static void wdt_setload(unsigned int timeout) +static int wdt_setload(struct watchdog_device *wdd, unsigned int timeout) { + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); u64 load, rate; rate = clk_get_rate(wdt->clk); @@ -103,11 +101,14 @@ static void wdt_setload(unsigned int timeout) /* roundup timeout to closest positive integer value */ wdt->timeout = div_u64((load + 1) * 2 + (rate / 2), rate); spin_unlock(&wdt->lock); + + return 0; } /* returns number of seconds left for reset to occur */ -static u32 wdt_timeleft(void) +static unsigned int wdt_timeleft(struct watchdog_device *wdd) { + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); u64 load, rate; rate = clk_get_rate(wdt->clk); @@ -123,166 +124,96 @@ static u32 wdt_timeleft(void) return div_u64(load, rate); } -/* enables watchdog timers reset */ -static void wdt_enable(void) +static int wdt_config(struct watchdog_device *wdd, bool ping) { - spin_lock(&wdt->lock); + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); + int ret; - writel_relaxed(UNLOCK, wdt->base + WDTLOCK); - writel_relaxed(wdt->load_val, wdt->base + WDTLOAD); - writel_relaxed(INT_MASK, wdt->base + WDTINTCLR); - writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base + WDTCONTROL); - writel_relaxed(LOCK, wdt->base + WDTLOCK); + if (!ping) { + ret = clk_prepare(wdt->clk); + if (ret) { + dev_err(&wdt->adev->dev, "clock prepare fail"); + return ret; + } - /* Flush posted writes. */ - readl_relaxed(wdt->base + WDTLOCK); - spin_unlock(&wdt->lock); -} + ret = clk_enable(wdt->clk); + if (ret) { + dev_err(&wdt->adev->dev, "clock enable fail"); + clk_unprepare(wdt->clk); + return ret; + } + } -/* disables watchdog timers reset */ -static void wdt_disable(void) -{ spin_lock(&wdt->lock); writel_relaxed(UNLOCK, wdt->base + WDTLOCK); - writel_relaxed(0, wdt->base + WDTCONTROL); + writel_relaxed(wdt->load_val, wdt->base + WDTLOAD); + + if (!ping) { + writel_relaxed(INT_MASK, wdt->base + WDTINTCLR); + writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base + + WDTCONTROL); + } + writel_relaxed(LOCK, wdt->base + WDTLOCK); /* Flush posted writes. */ readl_relaxed(wdt->base + WDTLOCK); spin_unlock(&wdt->lock); + + return 0; } -static ssize_t sp805_wdt_write(struct file *file, const char *data, - size_t len, loff_t *ppos) +static int wdt_ping(struct watchdog_device *wdd) { - if (len) { - if (!nowayout) { - size_t i; - - clear_bit(WDT_CAN_BE_CLOSED, &wdt->status); - - for (i = 0; i != len; i++) { - char c; - - if (get_user(c, data + i)) - return -EFAULT; - /* Check for Magic Close character */ - if (c == 'V') { - set_bit(WDT_CAN_BE_CLOSED, - &wdt->status); - break; - } - } - } - wdt_enable(); - } - return len; + return wdt_config(wdd, true); } -static const struct watchdog_info ident = { - .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, - .identity = MODULE_NAME, -}; - -static long sp805_wdt_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) +/* enables watchdog timers reset */ +static int wdt_enable(struct watchdog_device *wdd) { - int ret = -ENOTTY; - unsigned int timeout; - - switch (cmd) { - case WDIOC_GETSUPPORT: - ret = copy_to_user((struct watchdog_info *)arg, &ident, - sizeof(ident)) ? -EFAULT : 0; - break; - - case WDIOC_GETSTATUS: - ret = put_user(0, (int *)arg); - break; - - case WDIOC_KEEPALIVE: - wdt_enable(); - ret = 0; - break; - - case WDIOC_SETTIMEOUT: - ret = get_user(timeout, (unsigned int *)arg); - if (ret) - break; - - wdt_setload(timeout); - - wdt_enable(); - /* Fall through */ - - case WDIOC_GETTIMEOUT: - ret = put_user(wdt->timeout, (unsigned int *)arg); - break; - case WDIOC_GETTIMELEFT: - ret = put_user(wdt_timeleft(), (unsigned int *)arg); - break; - } - return ret; + return wdt_config(wdd, false); } -static int sp805_wdt_open(struct inode *inode, struct file *file) +/* disables watchdog timers reset */ +static int wdt_disable(struct watchdog_device *wdd) { - int ret = 0; - - if (test_and_set_bit(WDT_BUSY, &wdt->status)) - return -EBUSY; - - ret = clk_enable(wdt->clk); - if (ret) { - dev_err(&wdt->adev->dev, "clock enable fail"); - goto err; - } - - wdt_enable(); + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); - /* can not be closed, once enabled */ - clear_bit(WDT_CAN_BE_CLOSED, &wdt->status); - return nonseekable_open(inode, file); + spin_lock(&wdt->lock); -err: - clear_bit(WDT_BUSY, &wdt->status); - return ret; -} + writel_relaxed(UNLOCK, wdt->base + WDTLOCK); + writel_relaxed(0, wdt->base + WDTCONTROL); + writel_relaxed(LOCK, wdt->base + WDTLOCK); -static int sp805_wdt_release(struct inode *inode, struct file *file) -{ - if (!test_bit(WDT_CAN_BE_CLOSED, &wdt->status)) { - clear_bit(WDT_BUSY, &wdt->status); - dev_warn(&wdt->adev->dev, "Device closed unexpectedly\n"); - return 0; - } + /* Flush posted writes. */ + readl_relaxed(wdt->base + WDTLOCK); + spin_unlock(&wdt->lock); - wdt_disable(); clk_disable(wdt->clk); - clear_bit(WDT_BUSY, &wdt->status); + clk_unprepare(wdt->clk); return 0; } -static const struct file_operations sp805_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = sp805_wdt_write, - .unlocked_ioctl = sp805_wdt_ioctl, - .open = sp805_wdt_open, - .release = sp805_wdt_release, +static const struct watchdog_info wdt_info = { + .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = MODULE_NAME, }; -static struct miscdevice sp805_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &sp805_wdt_fops, +static const struct watchdog_ops wdt_ops = { + .owner = THIS_MODULE, + .start = wdt_enable, + .stop = wdt_disable, + .ping = wdt_ping, + .set_timeout = wdt_setload, + .get_timeleft = wdt_timeleft, }; static int __devinit sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) { + struct sp805_wdt *wdt; int ret = 0; if (!devm_request_mem_region(&adev->dev, adev->res.start, @@ -315,19 +246,26 @@ sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) } wdt->adev = adev; + wdt->wdd.info = &wdt_info; + wdt->wdd.ops = &wdt_ops; + spin_lock_init(&wdt->lock); - wdt_setload(DEFAULT_TIMEOUT); + watchdog_set_nowayout(&wdt->wdd, nowayout); + watchdog_set_drvdata(&wdt->wdd, wdt); + wdt_setload(&wdt->wdd, DEFAULT_TIMEOUT); - ret = misc_register(&sp805_wdt_miscdev); - if (ret < 0) { - dev_warn(&adev->dev, "cannot register misc device\n"); - goto err_misc_register; + ret = watchdog_register_device(&wdt->wdd); + if (ret) { + dev_err(&adev->dev, "watchdog_register_device() failed: %d\n", + ret); + goto err_register; } + amba_set_drvdata(adev, wdt); dev_info(&adev->dev, "registration successful\n"); return 0; -err_misc_register: +err_register: clk_put(wdt->clk); err: dev_err(&adev->dev, "Probe Failed!!!\n"); @@ -336,7 +274,11 @@ err: static int __devexit sp805_wdt_remove(struct amba_device *adev) { - misc_deregister(&sp805_wdt_miscdev); + struct sp805_wdt *wdt = amba_get_drvdata(adev); + + watchdog_unregister_device(&wdt->wdd); + amba_set_drvdata(adev, NULL); + watchdog_set_drvdata(&wdt->wdd, NULL); clk_put(wdt->clk); return 0; @@ -345,28 +287,22 @@ static int __devexit sp805_wdt_remove(struct amba_device *adev) #ifdef CONFIG_PM static int sp805_wdt_suspend(struct device *dev) { - if (test_bit(WDT_BUSY, &wdt->status)) { - wdt_disable(); - clk_disable(wdt->clk); - } + struct sp805_wdt *wdt = dev_get_drvdata(dev); + + if (watchdog_active(&wdt->wdd)) + return wdt_disable(&wdt->wdd); return 0; } static int sp805_wdt_resume(struct device *dev) { - int ret = 0; + struct sp805_wdt *wdt = dev_get_drvdata(dev); - if (test_bit(WDT_BUSY, &wdt->status)) { - ret = clk_enable(wdt->clk); - if (ret) { - dev_err(dev, "clock enable fail"); - return ret; - } - wdt_enable(); - } + if (watchdog_active(&wdt->wdd)) + return wdt_enable(&wdt->wdd); - return ret; + return 0; } #endif /* CONFIG_PM */ @@ -395,11 +331,6 @@ static struct amba_driver sp805_wdt_driver = { module_amba_driver(sp805_wdt_driver); -module_param(nowayout, bool, 0); -MODULE_PARM_DESC(nowayout, - "Set to 1 to keep watchdog running after device release"); - MODULE_AUTHOR("Viresh Kumar <viresh.kumar@st.com>"); MODULE_DESCRIPTION("ARM SP805 Watchdog Driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/via_wdt.c b/drivers/watchdog/via_wdt.c index 5603e31afdab..aa50da3ccfe3 100644 --- a/drivers/watchdog/via_wdt.c +++ b/drivers/watchdog/via_wdt.c @@ -91,7 +91,7 @@ static inline void wdt_reset(void) static void wdt_timer_tick(unsigned long data) { if (time_before(jiffies, next_heartbeat) || - (!test_bit(WDOG_ACTIVE, &wdt_dev.status))) { + (!watchdog_active(&wdt_dev))) { wdt_reset(); mod_timer(&timer, jiffies + WDT_HEARTBEAT); } else diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 14d768bfa267..6aa46a90ff02 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -34,8 +34,13 @@ #include <linux/kernel.h> /* For printk/panic/... */ #include <linux/watchdog.h> /* For watchdog specific items */ #include <linux/init.h> /* For __init/__exit/... */ +#include <linux/idr.h> /* For ida_* macros */ +#include <linux/err.h> /* For IS_ERR macros */ -#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; /** * watchdog_register_device() - register a watchdog device @@ -49,7 +54,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; @@ -74,10 +79,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,6 +128,7 @@ EXPORT_SYMBOL_GPL(watchdog_register_device); void watchdog_unregister_device(struct watchdog_device *wdd) { int ret; + int devno = wdd->cdev.dev; if (wdd == NULL) return; @@ -102,9 +136,41 @@ void watchdog_unregister_device(struct watchdog_device *wdd) ret = watchdog_dev_unregister(wdd); if (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 <alan@lxorguk.ukuu.org.uk>"); MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>"); MODULE_DESCRIPTION("WatchDog Timer Driver Core"); diff --git a/drivers/watchdog/watchdog_dev.h b/drivers/watchdog/watchdog_core.h index bc7612be25ce..6c951418fca7 100644 --- a/drivers/watchdog/watchdog_dev.h +++ b/drivers/watchdog/watchdog_core.h @@ -26,8 +26,12 @@ * 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 */ -int watchdog_dev_register(struct watchdog_device *); -int watchdog_dev_unregister(struct watchdog_device *); +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/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 8558da912c42..672d169bf1da 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -42,10 +42,12 @@ #include <linux/init.h> /* For __init/__exit/... */ #include <linux/uaccess.h> /* 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,155 @@ 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 ((wddev->max_timeout != 0) && + (timeout < wddev->min_timeout || timeout > wddev->max_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 +293,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 +333,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) + return err; return put_user(val, p); case WDIOC_GETBOOTSTATUS: return put_user(wdd->bootstatus, p); @@ -215,15 +374,9 @@ 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; /* If the watchdog is active then we send a keepalive ping @@ -237,21 +390,21 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, return -EOPNOTSUPP; return put_user(wdd->timeout, p); case WDIOC_GETTIMELEFT: - if (!wdd->ops->get_timeleft) - return -EOPNOTSUPP; - - return put_user(wdd->ops->get_timeleft(wdd), p); + 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. */ @@ -259,6 +412,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)) @@ -275,6 +435,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); @@ -286,9 +451,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 @@ -297,6 +462,7 @@ out: static int watchdog_release(struct inode *inode, struct file *file) { + struct watchdog_device *wdd = file->private_data; int err = -EBUSY; /* @@ -310,7 +476,10 @@ static int watchdog_release(struct inode *inode, struct file *file) /* 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); } @@ -320,6 +489,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; } @@ -338,62 +511,92 @@ 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) { + 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); + return err; + } + old_wdd = watchdog; } - 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); +} |