linux/drivers/watchdog/da9063_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Watchdog driver for DA9063 PMICs.
   4 *
   5 * Copyright(c) 2012 Dialog Semiconductor Ltd.
   6 *
   7 * Author: Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>
   8 *
   9 */
  10
  11#include <linux/kernel.h>
  12#include <linux/module.h>
  13#include <linux/watchdog.h>
  14#include <linux/platform_device.h>
  15#include <linux/uaccess.h>
  16#include <linux/slab.h>
  17#include <linux/delay.h>
  18#include <linux/mfd/da9063/registers.h>
  19#include <linux/mfd/da9063/core.h>
  20#include <linux/regmap.h>
  21
  22/*
  23 * Watchdog selector to timeout in seconds.
  24 *   0: WDT disabled;
  25 *   others: timeout = 2048 ms * 2^(TWDSCALE-1).
  26 */
  27static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 };
  28#define DA9063_TWDSCALE_DISABLE         0
  29#define DA9063_TWDSCALE_MIN             1
  30#define DA9063_TWDSCALE_MAX             (ARRAY_SIZE(wdt_timeout) - 1)
  31#define DA9063_WDT_MIN_TIMEOUT          wdt_timeout[DA9063_TWDSCALE_MIN]
  32#define DA9063_WDT_MAX_TIMEOUT          wdt_timeout[DA9063_TWDSCALE_MAX]
  33#define DA9063_WDG_TIMEOUT              wdt_timeout[3]
  34#define DA9063_RESET_PROTECTION_MS      256
  35
  36static unsigned int da9063_wdt_timeout_to_sel(unsigned int secs)
  37{
  38        unsigned int i;
  39
  40        for (i = DA9063_TWDSCALE_MIN; i <= DA9063_TWDSCALE_MAX; i++) {
  41                if (wdt_timeout[i] >= secs)
  42                        return i;
  43        }
  44
  45        return DA9063_TWDSCALE_MAX;
  46}
  47
  48/*
  49 * Return 0 if watchdog is disabled, else non zero.
  50 */
  51static unsigned int da9063_wdt_is_running(struct da9063 *da9063)
  52{
  53        unsigned int val;
  54
  55        regmap_read(da9063->regmap, DA9063_REG_CONTROL_D, &val);
  56
  57        return val & DA9063_TWDSCALE_MASK;
  58}
  59
  60static int da9063_wdt_disable_timer(struct da9063 *da9063)
  61{
  62        return regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D,
  63                                  DA9063_TWDSCALE_MASK,
  64                                  DA9063_TWDSCALE_DISABLE);
  65}
  66
  67static int
  68da9063_wdt_update_timeout(struct da9063 *da9063, unsigned int timeout)
  69{
  70        unsigned int regval;
  71        int ret;
  72
  73        /*
  74         * The watchdog triggers a reboot if a timeout value is already
  75         * programmed because the timeout value combines two functions
  76         * in one: indicating the counter limit and starting the watchdog.
  77         * The watchdog must be disabled to be able to change the timeout
  78         * value if the watchdog is already running. Then we can set the
  79         * new timeout value which enables the watchdog again.
  80         */
  81        ret = da9063_wdt_disable_timer(da9063);
  82        if (ret)
  83                return ret;
  84
  85        usleep_range(150, 300);
  86        regval = da9063_wdt_timeout_to_sel(timeout);
  87
  88        return regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D,
  89                                  DA9063_TWDSCALE_MASK, regval);
  90}
  91
  92static int da9063_wdt_start(struct watchdog_device *wdd)
  93{
  94        struct da9063 *da9063 = watchdog_get_drvdata(wdd);
  95        int ret;
  96
  97        ret = da9063_wdt_update_timeout(da9063, wdd->timeout);
  98        if (ret)
  99                dev_err(da9063->dev, "Watchdog failed to start (err = %d)\n",
 100                        ret);
 101
 102        return ret;
 103}
 104
 105static int da9063_wdt_stop(struct watchdog_device *wdd)
 106{
 107        struct da9063 *da9063 = watchdog_get_drvdata(wdd);
 108        int ret;
 109
 110        ret = da9063_wdt_disable_timer(da9063);
 111        if (ret)
 112                dev_alert(da9063->dev, "Watchdog failed to stop (err = %d)\n",
 113                          ret);
 114
 115        return ret;
 116}
 117
 118static int da9063_wdt_ping(struct watchdog_device *wdd)
 119{
 120        struct da9063 *da9063 = watchdog_get_drvdata(wdd);
 121        int ret;
 122
 123        ret = regmap_write(da9063->regmap, DA9063_REG_CONTROL_F,
 124                           DA9063_WATCHDOG);
 125        if (ret)
 126                dev_alert(da9063->dev, "Failed to ping the watchdog (err = %d)\n",
 127                          ret);
 128
 129        return ret;
 130}
 131
 132static int da9063_wdt_set_timeout(struct watchdog_device *wdd,
 133                                  unsigned int timeout)
 134{
 135        struct da9063 *da9063 = watchdog_get_drvdata(wdd);
 136        int ret = 0;
 137
 138        /*
 139         * There are two cases when a set_timeout() will be called:
 140         * 1. The watchdog is off and someone wants to set the timeout for the
 141         *    further use.
 142         * 2. The watchdog is already running and a new timeout value should be
 143         *    set.
 144         *
 145         * The watchdog can't store a timeout value not equal zero without
 146         * enabling the watchdog, so the timeout must be buffered by the driver.
 147         */
 148        if (watchdog_active(wdd))
 149                ret = da9063_wdt_update_timeout(da9063, timeout);
 150
 151        if (ret)
 152                dev_err(da9063->dev, "Failed to set watchdog timeout (err = %d)\n",
 153                        ret);
 154        else
 155                wdd->timeout = wdt_timeout[da9063_wdt_timeout_to_sel(timeout)];
 156
 157        return ret;
 158}
 159
 160static int da9063_wdt_restart(struct watchdog_device *wdd, unsigned long action,
 161                              void *data)
 162{
 163        struct da9063 *da9063 = watchdog_get_drvdata(wdd);
 164        int ret;
 165
 166        ret = regmap_write(da9063->regmap, DA9063_REG_CONTROL_F,
 167                           DA9063_SHUTDOWN);
 168        if (ret)
 169                dev_alert(da9063->dev, "Failed to shutdown (err = %d)\n",
 170                          ret);
 171
 172        return ret;
 173}
 174
 175static const struct watchdog_info da9063_watchdog_info = {
 176        .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
 177        .identity = "DA9063 Watchdog",
 178};
 179
 180static const struct watchdog_ops da9063_watchdog_ops = {
 181        .owner = THIS_MODULE,
 182        .start = da9063_wdt_start,
 183        .stop = da9063_wdt_stop,
 184        .ping = da9063_wdt_ping,
 185        .set_timeout = da9063_wdt_set_timeout,
 186        .restart = da9063_wdt_restart,
 187};
 188
 189static int da9063_wdt_probe(struct platform_device *pdev)
 190{
 191        struct da9063 *da9063;
 192        struct watchdog_device *wdd;
 193
 194        if (!pdev->dev.parent)
 195                return -EINVAL;
 196
 197        da9063 = dev_get_drvdata(pdev->dev.parent);
 198        if (!da9063)
 199                return -EINVAL;
 200
 201        wdd = devm_kzalloc(&pdev->dev, sizeof(*wdd), GFP_KERNEL);
 202        if (!wdd)
 203                return -ENOMEM;
 204
 205        wdd->info = &da9063_watchdog_info;
 206        wdd->ops = &da9063_watchdog_ops;
 207        wdd->min_timeout = DA9063_WDT_MIN_TIMEOUT;
 208        wdd->max_timeout = DA9063_WDT_MAX_TIMEOUT;
 209        wdd->min_hw_heartbeat_ms = DA9063_RESET_PROTECTION_MS;
 210        wdd->timeout = DA9063_WDG_TIMEOUT;
 211        wdd->parent = &pdev->dev;
 212
 213        wdd->status = WATCHDOG_NOWAYOUT_INIT_STATUS;
 214
 215        watchdog_set_restart_priority(wdd, 128);
 216
 217        watchdog_set_drvdata(wdd, da9063);
 218
 219        /* Change the timeout to the default value if the watchdog is running */
 220        if (da9063_wdt_is_running(da9063)) {
 221                da9063_wdt_update_timeout(da9063, DA9063_WDG_TIMEOUT);
 222                set_bit(WDOG_HW_RUNNING, &wdd->status);
 223        }
 224
 225        return devm_watchdog_register_device(&pdev->dev, wdd);
 226}
 227
 228static struct platform_driver da9063_wdt_driver = {
 229        .probe = da9063_wdt_probe,
 230        .driver = {
 231                .name = DA9063_DRVNAME_WATCHDOG,
 232        },
 233};
 234module_platform_driver(da9063_wdt_driver);
 235
 236MODULE_AUTHOR("Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>");
 237MODULE_DESCRIPTION("Watchdog driver for Dialog DA9063");
 238MODULE_LICENSE("GPL");
 239MODULE_ALIAS("platform:" DA9063_DRVNAME_WATCHDOG);
 240