123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- /*
- * Kontron PLD watchdog driver
- *
- * Copyright (c) 2010-2013 Kontron Europe GmbH
- * Author: Michael Brunner <michael.brunner@kontron.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License 2 as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Note: From the PLD watchdog point of view timeout and pretimeout are
- * defined differently than in the kernel.
- * First the pretimeout stage runs out before the timeout stage gets
- * active.
- *
- * Kernel/API: P-----| pretimeout
- * |-----------------------T timeout
- * Watchdog: |-----------------P pretimeout_stage
- * |-----T timeout_stage
- */
- #include <linux/module.h>
- #include <linux/moduleparam.h>
- #include <linux/uaccess.h>
- #include <linux/watchdog.h>
- #include <linux/platform_device.h>
- #include <linux/mfd/kempld.h>
- #define KEMPLD_WDT_STAGE_TIMEOUT(x) (0x1b + (x) * 4)
- #define KEMPLD_WDT_STAGE_CFG(x) (0x18 + (x))
- #define STAGE_CFG_GET_PRESCALER(x) (((x) & 0x30) >> 4)
- #define STAGE_CFG_SET_PRESCALER(x) (((x) & 0x3) << 4)
- #define STAGE_CFG_PRESCALER_MASK 0x30
- #define STAGE_CFG_ACTION_MASK 0x7
- #define STAGE_CFG_ASSERT (1 << 3)
- #define KEMPLD_WDT_MAX_STAGES 2
- #define KEMPLD_WDT_KICK 0x16
- #define KEMPLD_WDT_CFG 0x17
- #define KEMPLD_WDT_CFG_ENABLE 0x10
- #define KEMPLD_WDT_CFG_ENABLE_LOCK 0x8
- #define KEMPLD_WDT_CFG_GLOBAL_LOCK 0x80
- enum {
- ACTION_NONE = 0,
- ACTION_RESET,
- ACTION_NMI,
- ACTION_SMI,
- ACTION_SCI,
- ACTION_DELAY,
- };
- enum {
- STAGE_TIMEOUT = 0,
- STAGE_PRETIMEOUT,
- };
- enum {
- PRESCALER_21 = 0,
- PRESCALER_17,
- PRESCALER_12,
- };
- static const u32 kempld_prescaler[] = {
- [PRESCALER_21] = (1 << 21) - 1,
- [PRESCALER_17] = (1 << 17) - 1,
- [PRESCALER_12] = (1 << 12) - 1,
- 0,
- };
- struct kempld_wdt_stage {
- unsigned int id;
- u32 mask;
- };
- struct kempld_wdt_data {
- struct kempld_device_data *pld;
- struct watchdog_device wdd;
- unsigned int pretimeout;
- struct kempld_wdt_stage stage[KEMPLD_WDT_MAX_STAGES];
- #ifdef CONFIG_PM
- u8 pm_status_store;
- #endif
- };
- #define DEFAULT_TIMEOUT 30 /* seconds */
- #define DEFAULT_PRETIMEOUT 0
- static unsigned int timeout = DEFAULT_TIMEOUT;
- module_param(timeout, uint, 0);
- MODULE_PARM_DESC(timeout,
- "Watchdog timeout in seconds. (>=0, default="
- __MODULE_STRING(DEFAULT_TIMEOUT) ")");
- static unsigned int pretimeout = DEFAULT_PRETIMEOUT;
- module_param(pretimeout, uint, 0);
- MODULE_PARM_DESC(pretimeout,
- "Watchdog pretimeout in seconds. (>=0, default="
- __MODULE_STRING(DEFAULT_PRETIMEOUT) ")");
- static bool nowayout = WATCHDOG_NOWAYOUT;
- module_param(nowayout, bool, 0);
- MODULE_PARM_DESC(nowayout,
- "Watchdog cannot be stopped once started (default="
- __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
- static int kempld_wdt_set_stage_action(struct kempld_wdt_data *wdt_data,
- struct kempld_wdt_stage *stage,
- u8 action)
- {
- struct kempld_device_data *pld = wdt_data->pld;
- u8 stage_cfg;
- if (!stage || !stage->mask)
- return -EINVAL;
- kempld_get_mutex(pld);
- stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
- stage_cfg &= ~STAGE_CFG_ACTION_MASK;
- stage_cfg |= (action & STAGE_CFG_ACTION_MASK);
- if (action == ACTION_RESET)
- stage_cfg |= STAGE_CFG_ASSERT;
- else
- stage_cfg &= ~STAGE_CFG_ASSERT;
- kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);
- kempld_release_mutex(pld);
- return 0;
- }
- static int kempld_wdt_set_stage_timeout(struct kempld_wdt_data *wdt_data,
- struct kempld_wdt_stage *stage,
- unsigned int timeout)
- {
- struct kempld_device_data *pld = wdt_data->pld;
- u32 prescaler;
- u64 stage_timeout64;
- u32 stage_timeout;
- u32 remainder;
- u8 stage_cfg;
- prescaler = kempld_prescaler[PRESCALER_21];
- if (!stage)
- return -EINVAL;
- stage_timeout64 = (u64)timeout * pld->pld_clock;
- remainder = do_div(stage_timeout64, prescaler);
- if (remainder)
- stage_timeout64++;
- if (stage_timeout64 > stage->mask)
- return -EINVAL;
- stage_timeout = stage_timeout64 & stage->mask;
- kempld_get_mutex(pld);
- stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
- stage_cfg &= ~STAGE_CFG_PRESCALER_MASK;
- stage_cfg |= STAGE_CFG_SET_PRESCALER(PRESCALER_21);
- kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);
- kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id),
- stage_timeout);
- kempld_release_mutex(pld);
- return 0;
- }
- /*
- * kempld_get_mutex must be called prior to calling this function.
- */
- static unsigned int kempld_wdt_get_timeout(struct kempld_wdt_data *wdt_data,
- struct kempld_wdt_stage *stage)
- {
- struct kempld_device_data *pld = wdt_data->pld;
- unsigned int timeout;
- u64 stage_timeout;
- u32 prescaler;
- u32 remainder;
- u8 stage_cfg;
- if (!stage->mask)
- return 0;
- stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
- stage_timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id));
- prescaler = kempld_prescaler[STAGE_CFG_GET_PRESCALER(stage_cfg)];
- stage_timeout = (stage_timeout & stage->mask) * prescaler;
- remainder = do_div(stage_timeout, pld->pld_clock);
- if (remainder)
- stage_timeout++;
- timeout = stage_timeout;
- WARN_ON_ONCE(timeout != stage_timeout);
- return timeout;
- }
- static int kempld_wdt_set_timeout(struct watchdog_device *wdd,
- unsigned int timeout)
- {
- struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
- struct kempld_wdt_stage *pretimeout_stage;
- struct kempld_wdt_stage *timeout_stage;
- int ret;
- timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
- pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
- if (pretimeout_stage->mask && wdt_data->pretimeout > 0)
- timeout = wdt_data->pretimeout;
- ret = kempld_wdt_set_stage_action(wdt_data, timeout_stage,
- ACTION_RESET);
- if (ret)
- return ret;
- ret = kempld_wdt_set_stage_timeout(wdt_data, timeout_stage,
- timeout);
- if (ret)
- return ret;
- wdd->timeout = timeout;
- return 0;
- }
- static int kempld_wdt_set_pretimeout(struct watchdog_device *wdd,
- unsigned int pretimeout)
- {
- struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
- struct kempld_wdt_stage *pretimeout_stage;
- u8 action = ACTION_NONE;
- int ret;
- pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
- if (!pretimeout_stage->mask)
- return -ENXIO;
- if (pretimeout > wdd->timeout)
- return -EINVAL;
- if (pretimeout > 0)
- action = ACTION_NMI;
- ret = kempld_wdt_set_stage_action(wdt_data, pretimeout_stage,
- action);
- if (ret)
- return ret;
- ret = kempld_wdt_set_stage_timeout(wdt_data, pretimeout_stage,
- wdd->timeout - pretimeout);
- if (ret)
- return ret;
- wdt_data->pretimeout = pretimeout;
- return 0;
- }
- static void kempld_wdt_update_timeouts(struct kempld_wdt_data *wdt_data)
- {
- struct kempld_device_data *pld = wdt_data->pld;
- struct kempld_wdt_stage *pretimeout_stage;
- struct kempld_wdt_stage *timeout_stage;
- unsigned int pretimeout, timeout;
- pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
- timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
- kempld_get_mutex(pld);
- pretimeout = kempld_wdt_get_timeout(wdt_data, pretimeout_stage);
- timeout = kempld_wdt_get_timeout(wdt_data, timeout_stage);
- kempld_release_mutex(pld);
- if (pretimeout)
- wdt_data->pretimeout = timeout;
- else
- wdt_data->pretimeout = 0;
- wdt_data->wdd.timeout = pretimeout + timeout;
- }
- static int kempld_wdt_start(struct watchdog_device *wdd)
- {
- struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
- struct kempld_device_data *pld = wdt_data->pld;
- u8 status;
- int ret;
- ret = kempld_wdt_set_timeout(wdd, wdd->timeout);
- if (ret)
- return ret;
- kempld_get_mutex(pld);
- status = kempld_read8(pld, KEMPLD_WDT_CFG);
- status |= KEMPLD_WDT_CFG_ENABLE;
- kempld_write8(pld, KEMPLD_WDT_CFG, status);
- status = kempld_read8(pld, KEMPLD_WDT_CFG);
- kempld_release_mutex(pld);
- /* Check if the watchdog was enabled */
- if (!(status & KEMPLD_WDT_CFG_ENABLE))
- return -EACCES;
- return 0;
- }
- static int kempld_wdt_stop(struct watchdog_device *wdd)
- {
- struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
- struct kempld_device_data *pld = wdt_data->pld;
- u8 status;
- kempld_get_mutex(pld);
- status = kempld_read8(pld, KEMPLD_WDT_CFG);
- status &= ~KEMPLD_WDT_CFG_ENABLE;
- kempld_write8(pld, KEMPLD_WDT_CFG, status);
- status = kempld_read8(pld, KEMPLD_WDT_CFG);
- kempld_release_mutex(pld);
- /* Check if the watchdog was disabled */
- if (status & KEMPLD_WDT_CFG_ENABLE)
- return -EACCES;
- return 0;
- }
- static int kempld_wdt_keepalive(struct watchdog_device *wdd)
- {
- struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
- struct kempld_device_data *pld = wdt_data->pld;
- kempld_get_mutex(pld);
- kempld_write8(pld, KEMPLD_WDT_KICK, 'K');
- kempld_release_mutex(pld);
- return 0;
- }
- static long kempld_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd,
- unsigned long arg)
- {
- struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
- void __user *argp = (void __user *)arg;
- int ret = -ENOIOCTLCMD;
- int __user *p = argp;
- int new_value;
- switch (cmd) {
- case WDIOC_SETPRETIMEOUT:
- if (get_user(new_value, p))
- return -EFAULT;
- ret = kempld_wdt_set_pretimeout(wdd, new_value);
- if (ret)
- return ret;
- ret = kempld_wdt_keepalive(wdd);
- break;
- case WDIOC_GETPRETIMEOUT:
- ret = put_user(wdt_data->pretimeout, (int __user *)arg);
- break;
- }
- return ret;
- }
- static int kempld_wdt_probe_stages(struct watchdog_device *wdd)
- {
- struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
- struct kempld_device_data *pld = wdt_data->pld;
- struct kempld_wdt_stage *pretimeout_stage;
- struct kempld_wdt_stage *timeout_stage;
- u8 index, data, data_orig;
- u32 mask;
- int i, j;
- pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
- timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
- pretimeout_stage->mask = 0;
- timeout_stage->mask = 0;
- for (i = 0; i < 3; i++) {
- index = KEMPLD_WDT_STAGE_TIMEOUT(i);
- mask = 0;
- kempld_get_mutex(pld);
- /* Probe each byte individually. */
- for (j = 0; j < 4; j++) {
- data_orig = kempld_read8(pld, index + j);
- kempld_write8(pld, index + j, 0x00);
- data = kempld_read8(pld, index + j);
- /* A failed write means this byte is reserved */
- if (data != 0x00)
- break;
- kempld_write8(pld, index + j, data_orig);
- mask |= 0xff << (j * 8);
- }
- kempld_release_mutex(pld);
- /* Assign available stages to timeout and pretimeout */
- if (!timeout_stage->mask) {
- timeout_stage->mask = mask;
- timeout_stage->id = i;
- } else {
- if (pld->feature_mask & KEMPLD_FEATURE_BIT_NMI) {
- pretimeout_stage->mask = timeout_stage->mask;
- timeout_stage->mask = mask;
- pretimeout_stage->id = timeout_stage->id;
- timeout_stage->id = i;
- }
- break;
- }
- }
- if (!timeout_stage->mask)
- return -ENODEV;
- return 0;
- }
- static const struct watchdog_info kempld_wdt_info = {
- .identity = "KEMPLD Watchdog",
- .options = WDIOF_SETTIMEOUT |
- WDIOF_KEEPALIVEPING |
- WDIOF_MAGICCLOSE |
- WDIOF_PRETIMEOUT
- };
- static const struct watchdog_ops kempld_wdt_ops = {
- .owner = THIS_MODULE,
- .start = kempld_wdt_start,
- .stop = kempld_wdt_stop,
- .ping = kempld_wdt_keepalive,
- .set_timeout = kempld_wdt_set_timeout,
- .ioctl = kempld_wdt_ioctl,
- };
- static int kempld_wdt_probe(struct platform_device *pdev)
- {
- struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
- struct kempld_wdt_data *wdt_data;
- struct device *dev = &pdev->dev;
- struct watchdog_device *wdd;
- u8 status;
- int ret = 0;
- wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL);
- if (!wdt_data)
- return -ENOMEM;
- wdt_data->pld = pld;
- wdd = &wdt_data->wdd;
- wdd->parent = dev;
- kempld_get_mutex(pld);
- status = kempld_read8(pld, KEMPLD_WDT_CFG);
- kempld_release_mutex(pld);
- /* Enable nowayout if watchdog is already locked */
- if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |
- KEMPLD_WDT_CFG_GLOBAL_LOCK)) {
- if (!nowayout)
- dev_warn(dev,
- "Forcing nowayout - watchdog lock enabled!\n");
- nowayout = true;
- }
- wdd->info = &kempld_wdt_info;
- wdd->ops = &kempld_wdt_ops;
- watchdog_set_drvdata(wdd, wdt_data);
- watchdog_set_nowayout(wdd, nowayout);
- ret = kempld_wdt_probe_stages(wdd);
- if (ret)
- return ret;
- kempld_wdt_set_timeout(wdd, timeout);
- kempld_wdt_set_pretimeout(wdd, pretimeout);
- /* Check if watchdog is already enabled */
- if (status & KEMPLD_WDT_CFG_ENABLE) {
- /* Get current watchdog settings */
- kempld_wdt_update_timeouts(wdt_data);
- dev_info(dev, "Watchdog was already enabled\n");
- }
- platform_set_drvdata(pdev, wdt_data);
- ret = watchdog_register_device(wdd);
- if (ret)
- return ret;
- dev_info(dev, "Watchdog registered with %ds timeout\n", wdd->timeout);
- return 0;
- }
- static void kempld_wdt_shutdown(struct platform_device *pdev)
- {
- struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
- kempld_wdt_stop(&wdt_data->wdd);
- }
- static int kempld_wdt_remove(struct platform_device *pdev)
- {
- struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
- struct watchdog_device *wdd = &wdt_data->wdd;
- int ret = 0;
- if (!nowayout)
- ret = kempld_wdt_stop(wdd);
- watchdog_unregister_device(wdd);
- return ret;
- }
- #ifdef CONFIG_PM
- /* Disable watchdog if it is active during suspend */
- static int kempld_wdt_suspend(struct platform_device *pdev,
- pm_message_t message)
- {
- struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
- struct kempld_device_data *pld = wdt_data->pld;
- struct watchdog_device *wdd = &wdt_data->wdd;
- kempld_get_mutex(pld);
- wdt_data->pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);
- kempld_release_mutex(pld);
- kempld_wdt_update_timeouts(wdt_data);
- if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)
- return kempld_wdt_stop(wdd);
- return 0;
- }
- /* Enable watchdog and configure it if necessary */
- static int kempld_wdt_resume(struct platform_device *pdev)
- {
- struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
- struct watchdog_device *wdd = &wdt_data->wdd;
- /*
- * If watchdog was stopped before suspend be sure it gets disabled
- * again, for the case BIOS has enabled it during resume
- */
- if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)
- return kempld_wdt_start(wdd);
- else
- return kempld_wdt_stop(wdd);
- }
- #else
- #define kempld_wdt_suspend NULL
- #define kempld_wdt_resume NULL
- #endif
- static struct platform_driver kempld_wdt_driver = {
- .driver = {
- .name = "kempld-wdt",
- },
- .probe = kempld_wdt_probe,
- .remove = kempld_wdt_remove,
- .shutdown = kempld_wdt_shutdown,
- .suspend = kempld_wdt_suspend,
- .resume = kempld_wdt_resume,
- };
- module_platform_driver(kempld_wdt_driver);
- MODULE_DESCRIPTION("KEM PLD Watchdog Driver");
- MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
- MODULE_LICENSE("GPL");
|