linux/drivers/watchdog/da9062_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Watchdog device driver for DA9062 and DA9061 PMICs
   4 * Copyright (C) 2015  Dialog Semiconductor Ltd.
   5 *
   6 */
   7
   8#include <linux/kernel.h>
   9#include <linux/module.h>
  10#include <linux/watchdog.h>
  11#include <linux/platform_device.h>
  12#include <linux/uaccess.h>
  13#include <linux/slab.h>
  14#include <linux/delay.h>
  15#include <linux/jiffies.h>
  16#include <linux/mfd/da9062/registers.h>
  17#include <linux/mfd/da9062/core.h>
  18#include <linux/regmap.h>
  19#include <linux/of.h>
  20
  21static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 };
  22#define DA9062_TWDSCALE_DISABLE         0
  23#define DA9062_TWDSCALE_MIN             1
  24#define DA9062_TWDSCALE_MAX             (ARRAY_SIZE(wdt_timeout) - 1)
  25#define DA9062_WDT_MIN_TIMEOUT          wdt_timeout[DA9062_TWDSCALE_MIN]
  26#define DA9062_WDT_MAX_TIMEOUT          wdt_timeout[DA9062_TWDSCALE_MAX]
  27#define DA9062_WDG_DEFAULT_TIMEOUT      wdt_timeout[DA9062_TWDSCALE_MAX-1]
  28#define DA9062_RESET_PROTECTION_MS      300
  29
  30struct da9062_watchdog {
  31        struct da9062 *hw;
  32        struct watchdog_device wdtdev;
  33};
  34
  35static unsigned int da9062_wdt_timeout_to_sel(unsigned int secs)
  36{
  37        unsigned int i;
  38
  39        for (i = DA9062_TWDSCALE_MIN; i <= DA9062_TWDSCALE_MAX; i++) {
  40                if (wdt_timeout[i] >= secs)
  41                        return i;
  42        }
  43
  44        return DA9062_TWDSCALE_MAX;
  45}
  46
  47static int da9062_reset_watchdog_timer(struct da9062_watchdog *wdt)
  48{
  49        return regmap_update_bits(wdt->hw->regmap, DA9062AA_CONTROL_F,
  50                                  DA9062AA_WATCHDOG_MASK,
  51                                  DA9062AA_WATCHDOG_MASK);
  52}
  53
  54static int da9062_wdt_update_timeout_register(struct da9062_watchdog *wdt,
  55                                              unsigned int regval)
  56{
  57        struct da9062 *chip = wdt->hw;
  58        int ret;
  59
  60        ret = da9062_reset_watchdog_timer(wdt);
  61        if (ret)
  62                return ret;
  63
  64        regmap_update_bits(chip->regmap,
  65                                  DA9062AA_CONTROL_D,
  66                                  DA9062AA_TWDSCALE_MASK,
  67                                  DA9062_TWDSCALE_DISABLE);
  68
  69        usleep_range(150, 300);
  70
  71        return regmap_update_bits(chip->regmap,
  72                                  DA9062AA_CONTROL_D,
  73                                  DA9062AA_TWDSCALE_MASK,
  74                                  regval);
  75}
  76
  77static int da9062_wdt_start(struct watchdog_device *wdd)
  78{
  79        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
  80        unsigned int selector;
  81        int ret;
  82
  83        selector = da9062_wdt_timeout_to_sel(wdt->wdtdev.timeout);
  84        ret = da9062_wdt_update_timeout_register(wdt, selector);
  85        if (ret)
  86                dev_err(wdt->hw->dev, "Watchdog failed to start (err = %d)\n",
  87                        ret);
  88
  89        return ret;
  90}
  91
  92static int da9062_wdt_stop(struct watchdog_device *wdd)
  93{
  94        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
  95        int ret;
  96
  97        ret = da9062_reset_watchdog_timer(wdt);
  98        if (ret) {
  99                dev_err(wdt->hw->dev, "Failed to ping the watchdog (err = %d)\n",
 100                        ret);
 101                return ret;
 102        }
 103
 104        ret = regmap_update_bits(wdt->hw->regmap,
 105                                 DA9062AA_CONTROL_D,
 106                                 DA9062AA_TWDSCALE_MASK,
 107                                 DA9062_TWDSCALE_DISABLE);
 108        if (ret)
 109                dev_err(wdt->hw->dev, "Watchdog failed to stop (err = %d)\n",
 110                        ret);
 111
 112        return ret;
 113}
 114
 115static int da9062_wdt_ping(struct watchdog_device *wdd)
 116{
 117        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
 118        int ret;
 119
 120        ret = da9062_reset_watchdog_timer(wdt);
 121        if (ret)
 122                dev_err(wdt->hw->dev, "Failed to ping the watchdog (err = %d)\n",
 123                        ret);
 124
 125        return ret;
 126}
 127
 128static int da9062_wdt_set_timeout(struct watchdog_device *wdd,
 129                                  unsigned int timeout)
 130{
 131        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
 132        unsigned int selector;
 133        int ret;
 134
 135        selector = da9062_wdt_timeout_to_sel(timeout);
 136        ret = da9062_wdt_update_timeout_register(wdt, selector);
 137        if (ret)
 138                dev_err(wdt->hw->dev, "Failed to set watchdog timeout (err = %d)\n",
 139                        ret);
 140        else
 141                wdd->timeout = wdt_timeout[selector];
 142
 143        return ret;
 144}
 145
 146static int da9062_wdt_restart(struct watchdog_device *wdd, unsigned long action,
 147                              void *data)
 148{
 149        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
 150        int ret;
 151
 152        ret = regmap_write(wdt->hw->regmap,
 153                           DA9062AA_CONTROL_F,
 154                           DA9062AA_SHUTDOWN_MASK);
 155        if (ret)
 156                dev_alert(wdt->hw->dev, "Failed to shutdown (err = %d)\n",
 157                          ret);
 158
 159        /* wait for reset to assert... */
 160        mdelay(500);
 161
 162        return ret;
 163}
 164
 165static const struct watchdog_info da9062_watchdog_info = {
 166        .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
 167        .identity = "DA9062 WDT",
 168};
 169
 170static const struct watchdog_ops da9062_watchdog_ops = {
 171        .owner = THIS_MODULE,
 172        .start = da9062_wdt_start,
 173        .stop = da9062_wdt_stop,
 174        .ping = da9062_wdt_ping,
 175        .set_timeout = da9062_wdt_set_timeout,
 176        .restart = da9062_wdt_restart,
 177};
 178
 179static const struct of_device_id da9062_compatible_id_table[] = {
 180        { .compatible = "dlg,da9062-watchdog", },
 181        { },
 182};
 183
 184MODULE_DEVICE_TABLE(of, da9062_compatible_id_table);
 185
 186static int da9062_wdt_probe(struct platform_device *pdev)
 187{
 188        struct device *dev = &pdev->dev;
 189        int ret;
 190        struct da9062 *chip;
 191        struct da9062_watchdog *wdt;
 192
 193        chip = dev_get_drvdata(dev->parent);
 194        if (!chip)
 195                return -EINVAL;
 196
 197        wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
 198        if (!wdt)
 199                return -ENOMEM;
 200
 201        wdt->hw = chip;
 202
 203        wdt->wdtdev.info = &da9062_watchdog_info;
 204        wdt->wdtdev.ops = &da9062_watchdog_ops;
 205        wdt->wdtdev.min_timeout = DA9062_WDT_MIN_TIMEOUT;
 206        wdt->wdtdev.max_timeout = DA9062_WDT_MAX_TIMEOUT;
 207        wdt->wdtdev.min_hw_heartbeat_ms = DA9062_RESET_PROTECTION_MS;
 208        wdt->wdtdev.timeout = DA9062_WDG_DEFAULT_TIMEOUT;
 209        wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
 210        wdt->wdtdev.parent = dev;
 211
 212        watchdog_set_restart_priority(&wdt->wdtdev, 128);
 213
 214        watchdog_set_drvdata(&wdt->wdtdev, wdt);
 215
 216        ret = devm_watchdog_register_device(dev, &wdt->wdtdev);
 217        if (ret < 0) {
 218                dev_err(wdt->hw->dev,
 219                        "watchdog registration failed (%d)\n", ret);
 220                return ret;
 221        }
 222
 223        return da9062_wdt_ping(&wdt->wdtdev);
 224}
 225
 226static struct platform_driver da9062_wdt_driver = {
 227        .probe = da9062_wdt_probe,
 228        .driver = {
 229                .name = "da9062-watchdog",
 230                .of_match_table = da9062_compatible_id_table,
 231        },
 232};
 233module_platform_driver(da9062_wdt_driver);
 234
 235MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>");
 236MODULE_DESCRIPTION("WDT device driver for Dialog DA9062 and DA9061");
 237MODULE_LICENSE("GPL");
 238MODULE_ALIAS("platform:da9062-watchdog");
 239