linux/drivers/watchdog/sl28cpld_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * sl28cpld watchdog driver
   4 *
   5 * Copyright 2020 Kontron Europe GmbH
   6 */
   7
   8#include <linux/kernel.h>
   9#include <linux/mod_devicetable.h>
  10#include <linux/module.h>
  11#include <linux/platform_device.h>
  12#include <linux/property.h>
  13#include <linux/regmap.h>
  14#include <linux/watchdog.h>
  15
  16/*
  17 * Watchdog timer block registers.
  18 */
  19#define WDT_CTRL                        0x00
  20#define  WDT_CTRL_EN                    BIT(0)
  21#define  WDT_CTRL_LOCK                  BIT(2)
  22#define  WDT_CTRL_ASSERT_SYS_RESET      BIT(6)
  23#define  WDT_CTRL_ASSERT_WDT_TIMEOUT    BIT(7)
  24#define WDT_TIMEOUT                     0x01
  25#define WDT_KICK                        0x02
  26#define  WDT_KICK_VALUE                 0x6b
  27#define WDT_COUNT                       0x03
  28
  29#define WDT_DEFAULT_TIMEOUT             10
  30
  31static bool nowayout = WATCHDOG_NOWAYOUT;
  32module_param(nowayout, bool, 0);
  33MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
  34                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  35
  36static int timeout;
  37module_param(timeout, int, 0);
  38MODULE_PARM_DESC(timeout, "Initial watchdog timeout in seconds");
  39
  40struct sl28cpld_wdt {
  41        struct watchdog_device wdd;
  42        struct regmap *regmap;
  43        u32 offset;
  44        bool assert_wdt_timeout;
  45};
  46
  47static int sl28cpld_wdt_ping(struct watchdog_device *wdd)
  48{
  49        struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
  50
  51        return regmap_write(wdt->regmap, wdt->offset + WDT_KICK,
  52                            WDT_KICK_VALUE);
  53}
  54
  55static int sl28cpld_wdt_start(struct watchdog_device *wdd)
  56{
  57        struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
  58        unsigned int val;
  59
  60        val = WDT_CTRL_EN | WDT_CTRL_ASSERT_SYS_RESET;
  61        if (wdt->assert_wdt_timeout)
  62                val |= WDT_CTRL_ASSERT_WDT_TIMEOUT;
  63        if (nowayout)
  64                val |= WDT_CTRL_LOCK;
  65
  66        return regmap_update_bits(wdt->regmap, wdt->offset + WDT_CTRL,
  67                                  val, val);
  68}
  69
  70static int sl28cpld_wdt_stop(struct watchdog_device *wdd)
  71{
  72        struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
  73
  74        return regmap_update_bits(wdt->regmap, wdt->offset + WDT_CTRL,
  75                                  WDT_CTRL_EN, 0);
  76}
  77
  78static unsigned int sl28cpld_wdt_get_timeleft(struct watchdog_device *wdd)
  79{
  80        struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
  81        unsigned int val;
  82        int ret;
  83
  84        ret = regmap_read(wdt->regmap, wdt->offset + WDT_COUNT, &val);
  85        if (ret)
  86                return 0;
  87
  88        return val;
  89}
  90
  91static int sl28cpld_wdt_set_timeout(struct watchdog_device *wdd,
  92                                    unsigned int timeout)
  93{
  94        struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
  95        int ret;
  96
  97        ret = regmap_write(wdt->regmap, wdt->offset + WDT_TIMEOUT, timeout);
  98        if (ret)
  99                return ret;
 100
 101        wdd->timeout = timeout;
 102
 103        return 0;
 104}
 105
 106static const struct watchdog_info sl28cpld_wdt_info = {
 107        .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
 108        .identity = "sl28cpld watchdog",
 109};
 110
 111static struct watchdog_ops sl28cpld_wdt_ops = {
 112        .owner = THIS_MODULE,
 113        .start = sl28cpld_wdt_start,
 114        .stop = sl28cpld_wdt_stop,
 115        .ping = sl28cpld_wdt_ping,
 116        .set_timeout = sl28cpld_wdt_set_timeout,
 117        .get_timeleft = sl28cpld_wdt_get_timeleft,
 118};
 119
 120static int sl28cpld_wdt_probe(struct platform_device *pdev)
 121{
 122        struct watchdog_device *wdd;
 123        struct sl28cpld_wdt *wdt;
 124        unsigned int status;
 125        unsigned int val;
 126        int ret;
 127
 128        if (!pdev->dev.parent)
 129                return -ENODEV;
 130
 131        wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
 132        if (!wdt)
 133                return -ENOMEM;
 134
 135        wdt->regmap = dev_get_regmap(pdev->dev.parent, NULL);
 136        if (!wdt->regmap)
 137                return -ENODEV;
 138
 139        ret = device_property_read_u32(&pdev->dev, "reg", &wdt->offset);
 140        if (ret)
 141                return -EINVAL;
 142
 143        wdt->assert_wdt_timeout = device_property_read_bool(&pdev->dev,
 144                                                            "kontron,assert-wdt-timeout-pin");
 145
 146        /* initialize struct watchdog_device */
 147        wdd = &wdt->wdd;
 148        wdd->parent = &pdev->dev;
 149        wdd->info = &sl28cpld_wdt_info;
 150        wdd->ops = &sl28cpld_wdt_ops;
 151        wdd->min_timeout = 1;
 152        wdd->max_timeout = 255;
 153
 154        watchdog_set_drvdata(wdd, wdt);
 155        watchdog_stop_on_reboot(wdd);
 156
 157        /*
 158         * Read the status early, in case of an error, we haven't modified the
 159         * hardware.
 160         */
 161        ret = regmap_read(wdt->regmap, wdt->offset + WDT_CTRL, &status);
 162        if (ret)
 163                return ret;
 164
 165        /*
 166         * Initial timeout value, may be overwritten by device tree or module
 167         * parameter in watchdog_init_timeout().
 168         *
 169         * Reading a zero here means that either the hardware has a default
 170         * value of zero (which is very unlikely and definitely a hardware
 171         * bug) or the bootloader set it to zero. In any case, we handle
 172         * this case gracefully and set out own timeout.
 173         */
 174        ret = regmap_read(wdt->regmap, wdt->offset + WDT_TIMEOUT, &val);
 175        if (ret)
 176                return ret;
 177
 178        if (val)
 179                wdd->timeout = val;
 180        else
 181                wdd->timeout = WDT_DEFAULT_TIMEOUT;
 182
 183        watchdog_init_timeout(wdd, timeout, &pdev->dev);
 184        sl28cpld_wdt_set_timeout(wdd, wdd->timeout);
 185
 186        /* if the watchdog is locked, we set nowayout */
 187        if (status & WDT_CTRL_LOCK)
 188                nowayout = true;
 189        watchdog_set_nowayout(wdd, nowayout);
 190
 191        /*
 192         * If watchdog is already running, keep it enabled, but make
 193         * sure its mode is set correctly.
 194         */
 195        if (status & WDT_CTRL_EN) {
 196                sl28cpld_wdt_start(wdd);
 197                set_bit(WDOG_HW_RUNNING, &wdd->status);
 198        }
 199
 200        ret = devm_watchdog_register_device(&pdev->dev, wdd);
 201        if (ret < 0) {
 202                dev_err(&pdev->dev, "failed to register watchdog device\n");
 203                return ret;
 204        }
 205
 206        dev_info(&pdev->dev, "initial timeout %d sec%s\n",
 207                 wdd->timeout, nowayout ? ", nowayout" : "");
 208
 209        return 0;
 210}
 211
 212static const struct of_device_id sl28cpld_wdt_of_match[] = {
 213        { .compatible = "kontron,sl28cpld-wdt" },
 214        {}
 215};
 216MODULE_DEVICE_TABLE(of, sl28cpld_wdt_of_match);
 217
 218static struct platform_driver sl28cpld_wdt_driver = {
 219        .probe = sl28cpld_wdt_probe,
 220        .driver = {
 221                .name = "sl28cpld-wdt",
 222                .of_match_table = sl28cpld_wdt_of_match,
 223        },
 224};
 225module_platform_driver(sl28cpld_wdt_driver);
 226
 227MODULE_DESCRIPTION("sl28cpld Watchdog Driver");
 228MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
 229MODULE_LICENSE("GPL");
 230