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