| From 55f1815cb899dc0bb48c250bf753c12ead069f27 Mon Sep 17 00:00:00 2001 |
| From: Soren Brinkmann <soren.brinkmann@xilinx.com> |
| Date: Tue, 24 Dec 2013 09:22:35 +0900 |
| Subject: watchdog: xilinx: merge support for xilinx watchdog |
| |
| This merges support for the Xilinx watchdong from the Xilinx |
| repository (commit efc27505715e64526653f35274717c0fc56491e3 in |
| master branch). It has been tested by using the watchdog |
| command. |
| |
| Signed-off-by: Daniel Sangorrin <daniel.sangorrin@toshiba.co.jp> |
| Signed-off-by: Yoshitake Kobayashi <yoshitake.kobayashi@toshiba.co.jp> |
| --- |
| drivers/watchdog/Kconfig | 11 |
| drivers/watchdog/Makefile | 1 |
| drivers/watchdog/of_xilinx_wdt.c | 1 |
| drivers/watchdog/xilinx_wdtps.c | 545 +++++++++++++++++++++++++++++++++++++++ |
| 4 files changed, 557 insertions(+), 1 deletion(-) |
| create mode 100644 drivers/watchdog/xilinx_wdtps.c |
| |
| --- a/drivers/watchdog/Kconfig |
| +++ b/drivers/watchdog/Kconfig |
| @@ -224,6 +224,7 @@ config DW_WATCHDOG |
| config MPCORE_WATCHDOG |
| tristate "MPcore watchdog" |
| depends on HAVE_ARM_TWD |
| + select WATCHDOG_CORE |
| help |
| Watchdog timer embedded into the MPcore system. |
| |
| @@ -337,6 +338,14 @@ config NUC900_WATCHDOG |
| To compile this driver as a module, choose M here: the |
| module will be called nuc900_wdt. |
| |
| +config XILINX_PS_WATCHDOG |
| + tristate "Xilinx PS Watchdog Timer" |
| + depends on ARCH_ZYNQ |
| + select WATCHDOG_CORE |
| + help |
| + Say Y here if you want to include support for the watchdog |
| + timer in the Xilinx PS. |
| + |
| config TS72XX_WATCHDOG |
| tristate "TS-72XX SBC Watchdog" |
| depends on MACH_TS72XX |
| @@ -976,7 +985,7 @@ config M54xx_WATCHDOG |
| |
| config XILINX_WATCHDOG |
| tristate "Xilinx Watchdog timer" |
| - depends on MICROBLAZE |
| + depends on MICROBLAZE || ARCH_ZYNQ |
| ---help--- |
| Watchdog driver for the xps_timebase_wdt ip core. |
| |
| --- a/drivers/watchdog/Makefile |
| +++ b/drivers/watchdog/Makefile |
| @@ -50,6 +50,7 @@ obj-$(CONFIG_ORION_WATCHDOG) += orion_wd |
| obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o |
| obj-$(CONFIG_STMP3XXX_RTC_WATCHDOG) += stmp3xxx_rtc_wdt.o |
| obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o |
| +obj-$(CONFIG_XILINX_PS_WATCHDOG) += xilinx_wdtps.o |
| obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o |
| obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o |
| obj-$(CONFIG_UX500_WATCHDOG) += ux500_wdt.o |
| --- a/drivers/watchdog/of_xilinx_wdt.c |
| +++ b/drivers/watchdog/of_xilinx_wdt.c |
| @@ -405,3 +405,4 @@ module_platform_driver(xwdt_driver); |
| MODULE_AUTHOR("Alejandro Cabrera <aldaya@gmail.com>"); |
| MODULE_DESCRIPTION("Xilinx Watchdog driver"); |
| MODULE_LICENSE("GPL v2"); |
| +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |
| --- /dev/null |
| +++ b/drivers/watchdog/xilinx_wdtps.c |
| @@ -0,0 +1,545 @@ |
| +/* |
| + * Xilinx Zynq WDT driver |
| + * |
| + * Copyright (c) 2010-2013 Xilinx Inc. |
| + * |
| + * 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. |
| + * |
| + * You should have received a copy of the GNU General Public |
| + * License along with this program; if not, write to the Free |
| + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA |
| + * 02139, USA. |
| + */ |
| + |
| +#include <linux/clk.h> |
| +#include <linux/export.h> |
| +#include <linux/fs.h> |
| +#include <linux/init.h> |
| +#include <linux/interrupt.h> |
| +#include <linux/io.h> |
| +#include <linux/irq.h> |
| +#include <linux/kernel.h> |
| +#include <linux/miscdevice.h> |
| +#include <linux/module.h> |
| +#include <linux/of.h> |
| +#include <linux/platform_device.h> |
| +#include <linux/reboot.h> |
| +#include <linux/slab.h> |
| +#include <linux/uaccess.h> |
| +#include <linux/watchdog.h> |
| + |
| +#define XWDTPS_DEFAULT_TIMEOUT 10 |
| +/* Supports 1 - 516 sec */ |
| +#define XWDTPS_MIN_TIMEOUT 1 |
| +#define XWDTPS_MAX_TIMEOUT 516 |
| + |
| +static int wdt_timeout = XWDTPS_DEFAULT_TIMEOUT; |
| +static int nowayout = WATCHDOG_NOWAYOUT; |
| + |
| +module_param(wdt_timeout, int, 0); |
| +MODULE_PARM_DESC(wdt_timeout, |
| + "Watchdog time in seconds. (default=" |
| + __MODULE_STRING(XWDTPS_DEFAULT_TIMEOUT) ")"); |
| + |
| +module_param(nowayout, int, 0); |
| +MODULE_PARM_DESC(nowayout, |
| + "Watchdog cannot be stopped once started (default=" |
| + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); |
| + |
| +/** |
| + * struct xwdtps - Watchdog device structure. |
| + * @regs: baseaddress of device. |
| + * @busy: flag for the device. |
| + * |
| + * Structure containing parameters specific to ps watchdog. |
| + */ |
| +struct xwdtps { |
| + void __iomem *regs; /* Base address */ |
| + unsigned long busy; /* Device Status */ |
| + int rst; /* Reset flag */ |
| + struct clk *clk; |
| + u32 prescalar; |
| + u32 ctrl_clksel; |
| + spinlock_t io_lock; |
| +}; |
| +static struct xwdtps *wdt; |
| + |
| +/* |
| + * Info structure used to indicate the features supported by the device |
| + * to the upper layers. This is defined in watchdog.h header file. |
| + */ |
| +static struct watchdog_info xwdtps_info = { |
| + .identity = "xwdtps watchdog", |
| + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | |
| + WDIOF_MAGICCLOSE, |
| +}; |
| + |
| +/* Write access to Registers */ |
| +#define xwdtps_writereg(val, offset) __raw_writel(val, (wdt->regs) + offset) |
| + |
| +/*************************Register Map**************************************/ |
| + |
| +/* Register Offsets for the WDT */ |
| +#define XWDTPS_ZMR_OFFSET 0x0 /* Zero Mode Register */ |
| +#define XWDTPS_CCR_OFFSET 0x4 /* Counter Control Register */ |
| +#define XWDTPS_RESTART_OFFSET 0x8 /* Restart Register */ |
| +#define XWDTPS_SR_OFFSET 0xC /* Status Register */ |
| + |
| +/* |
| + * Zero Mode Register - This register controls how the time out is indicated |
| + * and also contains the access code to allow writes to the register (0xABC). |
| + */ |
| +#define XWDTPS_ZMR_WDEN_MASK 0x00000001 /* Enable the WDT */ |
| +#define XWDTPS_ZMR_RSTEN_MASK 0x00000002 /* Enable the reset output */ |
| +#define XWDTPS_ZMR_IRQEN_MASK 0x00000004 /* Enable IRQ output */ |
| +#define XWDTPS_ZMR_RSTLEN_16 0x00000030 /* Reset pulse of 16 pclk cycles */ |
| +#define XWDTPS_ZMR_ZKEY_VAL 0x00ABC000 /* Access key, 0xABC << 12 */ |
| +/* |
| + * Counter Control register - This register controls how fast the timer runs |
| + * and the reset value and also contains the access code to allow writes to |
| + * the register. |
| + */ |
| +#define XWDTPS_CCR_CRV_MASK 0x00003FFC /* Counter reset value */ |
| + |
| +/** |
| + * xwdtps_stop - Stop the watchdog. |
| + * |
| + * Read the contents of the ZMR register, clear the WDEN bit |
| + * in the register and set the access key for successful write. |
| + */ |
| +static int xwdtps_stop(struct watchdog_device *wdd) |
| +{ |
| + spin_lock(&wdt->io_lock); |
| + xwdtps_writereg((XWDTPS_ZMR_ZKEY_VAL & (~XWDTPS_ZMR_WDEN_MASK)), |
| + XWDTPS_ZMR_OFFSET); |
| + spin_unlock(&wdt->io_lock); |
| + return 0; |
| +} |
| + |
| +/** |
| + * xwdtps_reload - Reload the watchdog timer (i.e. pat the watchdog). |
| + * |
| + * Write the restart key value (0x00001999) to the restart register. |
| + */ |
| +static int xwdtps_reload(struct watchdog_device *wdd) |
| +{ |
| + spin_lock(&wdt->io_lock); |
| + xwdtps_writereg(0x00001999, XWDTPS_RESTART_OFFSET); |
| + spin_unlock(&wdt->io_lock); |
| + return 0; |
| +} |
| + |
| +/** |
| + * xwdtps_start - Enable and start the watchdog. |
| + * |
| + * The counter value is calculated according to the formula: |
| + * calculated count = (timeout * clock) / prescalar + 1. |
| + * The calculated count is divided by 0x1000 to obtain the field value |
| + * to write to counter control register. |
| + * Clears the contents of prescalar and counter reset value. Sets the |
| + * prescalar to 4096 and the calculated count and access key |
| + * to write to CCR Register. |
| + * Sets the WDT (WDEN bit) and either the Reset signal(RSTEN bit) |
| + * or Interrupt signal(IRQEN) with a specified cycles and the access |
| + * key to write to ZMR Register. |
| + */ |
| +static int xwdtps_start(struct watchdog_device *wdd) |
| +{ |
| + unsigned int data = 0; |
| + unsigned short count; |
| + unsigned long clock_f = clk_get_rate(wdt->clk); |
| + |
| + /* |
| + * 0x1000 - Counter Value Divide, to obtain the value of counter |
| + * reset to write to control register. |
| + */ |
| + count = (wdd->timeout * (clock_f / (wdt->prescalar))) / 0x1000 + 1; |
| + |
| + /* Check for boundary conditions of counter value */ |
| + if (count > 0xFFF) |
| + count = 0xFFF; |
| + |
| + spin_lock(&wdt->io_lock); |
| + xwdtps_writereg(XWDTPS_ZMR_ZKEY_VAL, XWDTPS_ZMR_OFFSET); |
| + |
| + /* Shift the count value to correct bit positions */ |
| + count = (count << 2) & XWDTPS_CCR_CRV_MASK; |
| + |
| + /* 0x00920000 - Counter register key value. */ |
| + data = (count | 0x00920000 | (wdt->ctrl_clksel)); |
| + xwdtps_writereg(data, XWDTPS_CCR_OFFSET); |
| + data = XWDTPS_ZMR_WDEN_MASK | XWDTPS_ZMR_RSTLEN_16 | |
| + XWDTPS_ZMR_ZKEY_VAL; |
| + |
| + /* Reset on timeout if specified in device tree. */ |
| + if (wdt->rst) { |
| + data |= XWDTPS_ZMR_RSTEN_MASK; |
| + data &= ~XWDTPS_ZMR_IRQEN_MASK; |
| + } else { |
| + data &= ~XWDTPS_ZMR_RSTEN_MASK; |
| + data |= XWDTPS_ZMR_IRQEN_MASK; |
| + } |
| + xwdtps_writereg(data, XWDTPS_ZMR_OFFSET); |
| + spin_unlock(&wdt->io_lock); |
| + xwdtps_writereg(0x00001999, XWDTPS_RESTART_OFFSET); |
| + return 0; |
| +} |
| + |
| +/** |
| + * xwdtps_settimeout - Set a new timeout value for the watchdog device. |
| + * |
| + * @new_time: new timeout value that needs to be set. |
| + * Returns 0 on success. |
| + * |
| + * Update the watchdog_device timeout with new value which is used when |
| + * xwdtps_start is called. |
| + */ |
| +static int xwdtps_settimeout(struct watchdog_device *wdd, unsigned int new_time) |
| +{ |
| + wdd->timeout = new_time; |
| + return xwdtps_start(wdd); |
| +} |
| + |
| +/** |
| + * xwdtps_irq_handler - Notifies of watchdog timeout. |
| + * |
| + * @irq: interrupt number |
| + * @dev_id: pointer to a platform device structure |
| + * Returns IRQ_HANDLED |
| + * |
| + * The handler is invoked when the watchdog times out and a |
| + * reset on timeout has not been enabled. |
| + */ |
| +static irqreturn_t xwdtps_irq_handler(int irq, void *dev_id) |
| +{ |
| + struct platform_device *pdev = dev_id; |
| + dev_info(&pdev->dev, "Watchdog timed out.\n"); |
| + return IRQ_HANDLED; |
| +} |
| + |
| +/* Watchdog Core Ops */ |
| +static struct watchdog_ops xwdtps_ops = { |
| + .owner = THIS_MODULE, |
| + .start = xwdtps_start, |
| + .stop = xwdtps_stop, |
| + .ping = xwdtps_reload, |
| + .set_timeout = xwdtps_settimeout, |
| +}; |
| + |
| +/* Watchdog Core Device */ |
| +static struct watchdog_device xwdtps_device = { |
| + .info = &xwdtps_info, |
| + .ops = &xwdtps_ops, |
| + .timeout = XWDTPS_DEFAULT_TIMEOUT, |
| + .min_timeout = XWDTPS_MIN_TIMEOUT, |
| + .max_timeout = XWDTPS_MAX_TIMEOUT, |
| +}; |
| + |
| +/** |
| + * xwdtps_notify_sys - Notifier for reboot or shutdown. |
| + * |
| + * @this: handle to notifier block. |
| + * @code: turn off indicator. |
| + * @unused: unused. |
| + * Returns NOTIFY_DONE. |
| + * |
| + * This notifier is invoked whenever the system reboot or shutdown occur |
| + * because we need to disable the WDT before system goes down as WDT might |
| + * reset on the next boot. |
| + */ |
| +static int xwdtps_notify_sys(struct notifier_block *this, unsigned long code, |
| + void *unused) |
| +{ |
| + if (code == SYS_DOWN || code == SYS_HALT) |
| + /* Stop the watchdog */ |
| + xwdtps_stop(&xwdtps_device); |
| + return NOTIFY_DONE; |
| +} |
| + |
| +/* Notifier Structure */ |
| +static struct notifier_block xwdtps_notifier = { |
| + .notifier_call = xwdtps_notify_sys, |
| +}; |
| + |
| +/************************Platform Operations*****************************/ |
| +/** |
| + * xwdtps_probe - Probe call for the device. |
| + * |
| + * @pdev: handle to the platform device structure. |
| + * Returns 0 on success, negative error otherwise. |
| + * |
| + * It does all the memory allocation and registration for the device. |
| + */ |
| +static int xwdtps_probe(struct platform_device *pdev) |
| +{ |
| + struct resource *regs; |
| + int res; |
| + const void *prop; |
| + int irq; |
| + unsigned long clock_f; |
| + |
| + /* Check whether WDT is in use, just for safety */ |
| + if (wdt) { |
| + dev_err(&pdev->dev, |
| + "Device Busy, only 1 xwdtps instance supported.\n"); |
| + return -EBUSY; |
| + } |
| + |
| + /* Get the device base address */ |
| + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| + if (!regs) { |
| + dev_err(&pdev->dev, "Unable to locate mmio resource\n"); |
| + return -ENODEV; |
| + } |
| + |
| + /* Allocate an instance of the xwdtps structure */ |
| + wdt = kzalloc(sizeof(*wdt), GFP_KERNEL); |
| + if (!wdt) { |
| + dev_err(&pdev->dev, "No memory for wdt structure\n"); |
| + return -ENOMEM; |
| + } |
| + |
| + wdt->regs = ioremap(regs->start, regs->end - regs->start + 1); |
| + if (!wdt->regs) { |
| + res = -ENOMEM; |
| + dev_err(&pdev->dev, "Could not map I/O memory\n"); |
| + goto err_free; |
| + } |
| + |
| + /* Register the reboot notifier */ |
| + res = register_reboot_notifier(&xwdtps_notifier); |
| + if (res != 0) { |
| + dev_err(&pdev->dev, "cannot register reboot notifier err=%d)\n", |
| + res); |
| + goto err_iounmap; |
| + } |
| + |
| + /* Register the interrupt */ |
| + prop = of_get_property(pdev->dev.of_node, "reset", NULL); |
| + wdt->rst = prop ? be32_to_cpup(prop) : 0; |
| + irq = platform_get_irq(pdev, 0); |
| + if (!wdt->rst && irq >= 0) { |
| + res = request_irq(irq, xwdtps_irq_handler, 0, pdev->name, pdev); |
| + if (res) { |
| + dev_err(&pdev->dev, |
| + "cannot register interrupt handler err=%d\n", |
| + res); |
| + goto err_notifier; |
| + } |
| + } |
| + |
| + /* Initialize the members of xwdtps structure */ |
| + xwdtps_device.parent = &pdev->dev; |
| + prop = of_get_property(pdev->dev.of_node, "timeout", NULL); |
| + if (prop) { |
| + xwdtps_device.timeout = be32_to_cpup(prop); |
| + } else if (wdt_timeout < XWDTPS_MAX_TIMEOUT && |
| + wdt_timeout > XWDTPS_MIN_TIMEOUT) { |
| + xwdtps_device.timeout = wdt_timeout; |
| + } else { |
| + dev_info(&pdev->dev, |
| + "timeout limited to 1 - %d sec, using default=%d\n", |
| + XWDTPS_MAX_TIMEOUT, XWDTPS_DEFAULT_TIMEOUT); |
| + xwdtps_device.timeout = XWDTPS_DEFAULT_TIMEOUT; |
| + } |
| + |
| + watchdog_set_nowayout(&xwdtps_device, nowayout); |
| + watchdog_set_drvdata(&xwdtps_device, &wdt); |
| + |
| + wdt->clk = clk_get(&pdev->dev, NULL); |
| + if (IS_ERR(wdt->clk)) { |
| + dev_err(&pdev->dev, "input clock not found\n"); |
| + res = PTR_ERR(wdt->clk); |
| + goto err_irq; |
| + } |
| + |
| + res = clk_prepare_enable(wdt->clk); |
| + if (res) { |
| + dev_err(&pdev->dev, "unable to enable clock\n"); |
| + goto err_clk_put; |
| + } |
| + |
| + clock_f = clk_get_rate(wdt->clk); |
| + if (clock_f <= 10000000) {/* For PEEP */ |
| + wdt->prescalar = 64; |
| + wdt->ctrl_clksel = 1; |
| + } else if (clock_f <= 75000000) { |
| + wdt->prescalar = 256; |
| + wdt->ctrl_clksel = 2; |
| + } else { /* For Zynq */ |
| + wdt->prescalar = 4096; |
| + wdt->ctrl_clksel = 3; |
| + } |
| + |
| + /* Initialize the busy flag to zero */ |
| + clear_bit(0, &wdt->busy); |
| + spin_lock_init(&wdt->io_lock); |
| + |
| + /* Register the WDT */ |
| + res = watchdog_register_device(&xwdtps_device); |
| + if (res) { |
| + dev_err(&pdev->dev, "Failed to register wdt device\n"); |
| + goto err_clk_disable; |
| + } |
| + platform_set_drvdata(pdev, wdt); |
| + |
| + dev_info(&pdev->dev, "Xilinx Watchdog Timer at %p with timeout %ds%s\n", |
| + wdt->regs, xwdtps_device.timeout, nowayout ? ", nowayout" : ""); |
| + |
| + return 0; |
| + |
| +err_clk_disable: |
| + clk_disable_unprepare(wdt->clk); |
| +err_clk_put: |
| + clk_put(wdt->clk); |
| +err_irq: |
| + free_irq(irq, pdev); |
| +err_notifier: |
| + unregister_reboot_notifier(&xwdtps_notifier); |
| +err_iounmap: |
| + iounmap(wdt->regs); |
| +err_free: |
| + kfree(wdt); |
| + wdt = NULL; |
| + return res; |
| +} |
| + |
| +/** |
| + * xwdtps_remove - Probe call for the device. |
| + * |
| + * @pdev: handle to the platform device structure. |
| + * Returns 0 on success, otherwise negative error. |
| + * |
| + * Unregister the device after releasing the resources. |
| + * Stop is allowed only when nowayout is disabled. |
| + */ |
| +static int __exit xwdtps_remove(struct platform_device *pdev) |
| +{ |
| + int res = 0; |
| + int irq; |
| + |
| + if (wdt && !nowayout) { |
| + xwdtps_stop(&xwdtps_device); |
| + watchdog_unregister_device(&xwdtps_device); |
| + unregister_reboot_notifier(&xwdtps_notifier); |
| + irq = platform_get_irq(pdev, 0); |
| + free_irq(irq, pdev); |
| + iounmap(wdt->regs); |
| + clk_disable_unprepare(wdt->clk); |
| + clk_put(wdt->clk); |
| + kfree(wdt); |
| + wdt = NULL; |
| + platform_set_drvdata(pdev, NULL); |
| + } else { |
| + dev_err(&pdev->dev, "Cannot stop watchdog, still ticking\n"); |
| + return -ENOTSUPP; |
| + } |
| + return res; |
| +} |
| + |
| +/** |
| + * xwdtps_shutdown - Stop the device. |
| + * |
| + * @pdev: handle to the platform structure. |
| + * |
| + */ |
| +static void xwdtps_shutdown(struct platform_device *pdev) |
| +{ |
| + /* Stop the device */ |
| + xwdtps_stop(&xwdtps_device); |
| + clk_disable_unprepare(wdt->clk); |
| + clk_put(wdt->clk); |
| +} |
| + |
| +#ifdef CONFIG_PM_SLEEP |
| +/** |
| + * xwdtps_suspend - Stop the device. |
| + * |
| + * @dev: handle to the device structure. |
| + * Returns 0 always. |
| + */ |
| +static int xwdtps_suspend(struct device *dev) |
| +{ |
| + /* Stop the device */ |
| + xwdtps_stop(&xwdtps_device); |
| + clk_disable(wdt->clk); |
| + return 0; |
| +} |
| + |
| +/** |
| + * xwdtps_resume - Resume the device. |
| + * |
| + * @dev: handle to the device structure. |
| + * Returns 0 on success, errno otherwise. |
| + */ |
| +static int xwdtps_resume(struct device *dev) |
| +{ |
| + int ret; |
| + |
| + ret = clk_enable(wdt->clk); |
| + if (ret) { |
| + dev_err(dev, "unable to enable clock\n"); |
| + return ret; |
| + } |
| + /* Start the device */ |
| + xwdtps_start(&xwdtps_device); |
| + return 0; |
| +} |
| +#endif |
| + |
| +static SIMPLE_DEV_PM_OPS(xwdtps_pm_ops, xwdtps_suspend, xwdtps_resume); |
| + |
| +static struct of_device_id xwdtps_of_match[] = { |
| + { .compatible = "xlnx,ps7-wdt-1.00.a", }, |
| + { /* end of table */} |
| +}; |
| +MODULE_DEVICE_TABLE(of, xwdtps_of_match); |
| + |
| +/* Driver Structure */ |
| +static struct platform_driver xwdtps_driver = { |
| + .probe = xwdtps_probe, |
| + .remove = xwdtps_remove, |
| + .shutdown = xwdtps_shutdown, |
| + .driver = { |
| + .name = "xwdtps", |
| + .owner = THIS_MODULE, |
| + .of_match_table = xwdtps_of_match, |
| + .pm = &xwdtps_pm_ops, |
| + }, |
| +}; |
| + |
| +/** |
| + * xwdtps_init - Register the WDT. |
| + * |
| + * Returns 0 on success, otherwise negative error. |
| + * |
| + * If using noway out, the use count will be incremented. |
| + * This will prevent unloading the module. An attempt to |
| + * unload the module will result in a warning from the kernel. |
| + */ |
| +static int __init xwdtps_init(void) |
| +{ |
| + int res = platform_driver_register(&xwdtps_driver); |
| + if (!res && nowayout) |
| + try_module_get(THIS_MODULE); |
| + return res; |
| +} |
| + |
| +/** |
| + * xwdtps_exit - Unregister the WDT. |
| + */ |
| +static void __exit xwdtps_exit(void) |
| +{ |
| + platform_driver_unregister(&xwdtps_driver); |
| +} |
| + |
| +module_init(xwdtps_init); |
| +module_exit(xwdtps_exit); |
| + |
| +MODULE_AUTHOR("Xilinx, Inc."); |
| +MODULE_DESCRIPTION("Watchdog driver for PS WDT"); |
| +MODULE_LICENSE("GPL"); |
| +MODULE_ALIAS("platform: xwdtps"); |