From 57f0f512b273f60d52568b8c6b77e17f5636edc0 Mon Sep 17 00:00:00 2001 From: André Fabian Silva Delgado Date: Wed, 5 Aug 2015 17:04:01 -0300 Subject: Initial import --- drivers/watchdog/mpc8xxx_wdt.c | 296 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 drivers/watchdog/mpc8xxx_wdt.c (limited to 'drivers/watchdog/mpc8xxx_wdt.c') diff --git a/drivers/watchdog/mpc8xxx_wdt.c b/drivers/watchdog/mpc8xxx_wdt.c new file mode 100644 index 000000000..689381a24 --- /dev/null +++ b/drivers/watchdog/mpc8xxx_wdt.c @@ -0,0 +1,296 @@ +/* + * mpc8xxx_wdt.c - MPC8xx/MPC83xx/MPC86xx watchdog userspace interface + * + * Authors: Dave Updegraff + * Kumar Gala + * Attribution: from 83xx_wst: Florian Schirmer + * ..and from sc520_wdt + * Copyright (c) 2008 MontaVista Software, Inc. + * Anton Vorontsov + * + * Note: it appears that you can only actually ENABLE or DISABLE the thing + * once after POR. Once enabled, you cannot disable, and vice versa. + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct mpc8xxx_wdt { + __be32 res0; + __be32 swcrr; /* System watchdog control register */ +#define SWCRR_SWTC 0xFFFF0000 /* Software Watchdog Time Count. */ +#define SWCRR_SWEN 0x00000004 /* Watchdog Enable bit. */ +#define SWCRR_SWRI 0x00000002 /* Software Watchdog Reset/Interrupt Select bit.*/ +#define SWCRR_SWPR 0x00000001 /* Software Watchdog Counter Prescale bit. */ + __be32 swcnr; /* System watchdog count register */ + u8 res1[2]; + __be16 swsrr; /* System watchdog service register */ + u8 res2[0xF0]; +}; + +struct mpc8xxx_wdt_type { + int prescaler; + bool hw_enabled; +}; + +static struct mpc8xxx_wdt __iomem *wd_base; +static int mpc8xxx_wdt_init_late(void); + +static u16 timeout = 0xffff; +module_param(timeout, ushort, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in ticks. (0swsrr, 0x556c); + out_be16(&wd_base->swsrr, 0xaa39); + spin_unlock(&wdt_spinlock); +} + +static struct watchdog_device mpc8xxx_wdt_dev; +static void mpc8xxx_wdt_timer_ping(unsigned long arg); +static DEFINE_TIMER(wdt_timer, mpc8xxx_wdt_timer_ping, 0, + (unsigned long)&mpc8xxx_wdt_dev); + +static void mpc8xxx_wdt_timer_ping(unsigned long arg) +{ + struct watchdog_device *w = (struct watchdog_device *)arg; + + mpc8xxx_wdt_keepalive(); + /* We're pinging it twice faster than needed, just to be sure. */ + mod_timer(&wdt_timer, jiffies + HZ * w->timeout / 2); +} + +static int mpc8xxx_wdt_start(struct watchdog_device *w) +{ + u32 tmp = SWCRR_SWEN; + + /* Good, fire up the show */ + if (prescale) + tmp |= SWCRR_SWPR; + if (reset) + tmp |= SWCRR_SWRI; + + tmp |= timeout << 16; + + out_be32(&wd_base->swcrr, tmp); + + del_timer_sync(&wdt_timer); + + return 0; +} + +static int mpc8xxx_wdt_ping(struct watchdog_device *w) +{ + mpc8xxx_wdt_keepalive(); + return 0; +} + +static int mpc8xxx_wdt_stop(struct watchdog_device *w) +{ + mod_timer(&wdt_timer, jiffies); + return 0; +} + +static struct watchdog_info mpc8xxx_wdt_info = { + .options = WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = "MPC8xxx", +}; + +static struct watchdog_ops mpc8xxx_wdt_ops = { + .owner = THIS_MODULE, + .start = mpc8xxx_wdt_start, + .ping = mpc8xxx_wdt_ping, + .stop = mpc8xxx_wdt_stop, +}; + +static struct watchdog_device mpc8xxx_wdt_dev = { + .info = &mpc8xxx_wdt_info, + .ops = &mpc8xxx_wdt_ops, +}; + +static const struct of_device_id mpc8xxx_wdt_match[]; +static int mpc8xxx_wdt_probe(struct platform_device *ofdev) +{ + int ret; + const struct of_device_id *match; + struct device_node *np = ofdev->dev.of_node; + const struct mpc8xxx_wdt_type *wdt_type; + u32 freq = fsl_get_sys_freq(); + bool enabled; + unsigned int timeout_sec; + + match = of_match_device(mpc8xxx_wdt_match, &ofdev->dev); + if (!match) + return -EINVAL; + wdt_type = match->data; + + if (!freq || freq == -1) + return -EINVAL; + + wd_base = of_iomap(np, 0); + if (!wd_base) + return -ENOMEM; + + enabled = in_be32(&wd_base->swcrr) & SWCRR_SWEN; + if (!enabled && wdt_type->hw_enabled) { + pr_info("could not be enabled in software\n"); + ret = -ENOSYS; + goto err_unmap; + } + + /* Calculate the timeout in seconds */ + if (prescale) + timeout_sec = (timeout * wdt_type->prescaler) / freq; + else + timeout_sec = timeout / freq; + + mpc8xxx_wdt_dev.timeout = timeout_sec; +#ifdef MODULE + ret = mpc8xxx_wdt_init_late(); + if (ret) + goto err_unmap; +#endif + + pr_info("WDT driver for MPC8xxx initialized. mode:%s timeout=%d (%d seconds)\n", + reset ? "reset" : "interrupt", timeout, timeout_sec); + + /* + * If the watchdog was previously enabled or we're running on + * MPC8xxx, we should ping the wdt from the kernel until the + * userspace handles it. + */ + if (enabled) + mod_timer(&wdt_timer, jiffies); + return 0; +err_unmap: + iounmap(wd_base); + wd_base = NULL; + return ret; +} + +static int mpc8xxx_wdt_remove(struct platform_device *ofdev) +{ + pr_crit("Watchdog removed, expect the %s soon!\n", + reset ? "reset" : "machine check exception"); + del_timer_sync(&wdt_timer); + watchdog_unregister_device(&mpc8xxx_wdt_dev); + iounmap(wd_base); + + return 0; +} + +static const struct of_device_id mpc8xxx_wdt_match[] = { + { + .compatible = "mpc83xx_wdt", + .data = &(struct mpc8xxx_wdt_type) { + .prescaler = 0x10000, + }, + }, + { + .compatible = "fsl,mpc8610-wdt", + .data = &(struct mpc8xxx_wdt_type) { + .prescaler = 0x10000, + .hw_enabled = true, + }, + }, + { + .compatible = "fsl,mpc823-wdt", + .data = &(struct mpc8xxx_wdt_type) { + .prescaler = 0x800, + .hw_enabled = true, + }, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mpc8xxx_wdt_match); + +static struct platform_driver mpc8xxx_wdt_driver = { + .probe = mpc8xxx_wdt_probe, + .remove = mpc8xxx_wdt_remove, + .driver = { + .name = "mpc8xxx_wdt", + .of_match_table = mpc8xxx_wdt_match, + }, +}; + +/* + * We do wdt initialization in two steps: arch_initcall probes the wdt + * very early to start pinging the watchdog (misc devices are not yet + * available), and later module_init() just registers the misc device. + */ +static int mpc8xxx_wdt_init_late(void) +{ + int ret; + + if (!wd_base) + return -ENODEV; + + watchdog_set_nowayout(&mpc8xxx_wdt_dev, nowayout); + + ret = watchdog_register_device(&mpc8xxx_wdt_dev); + if (ret) { + pr_err("cannot register watchdog device (err=%d)\n", ret); + return ret; + } + return 0; +} +#ifndef MODULE +module_init(mpc8xxx_wdt_init_late); +#endif + +static int __init mpc8xxx_wdt_init(void) +{ + return platform_driver_register(&mpc8xxx_wdt_driver); +} +arch_initcall(mpc8xxx_wdt_init); + +static void __exit mpc8xxx_wdt_exit(void) +{ + platform_driver_unregister(&mpc8xxx_wdt_driver); +} +module_exit(mpc8xxx_wdt_exit); + +MODULE_AUTHOR("Dave Updegraff, Kumar Gala"); +MODULE_DESCRIPTION("Driver for watchdog timer in MPC8xx/MPC83xx/MPC86xx " + "uProcessors"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-54-g00ecf