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_read_timeout(struct da9062_watchdog *wdt)
  39{
  40        unsigned int val;
  41
  42        regmap_read(wdt->hw->regmap, DA9062AA_CONTROL_D, &val);
  43
  44        return wdt_timeout[val & DA9062AA_TWDSCALE_MASK];
  45}
  46
  47static unsigned int da9062_wdt_timeout_to_sel(unsigned int secs)
  48{
  49        unsigned int i;
  50
  51        for (i = DA9062_TWDSCALE_MIN; i <= DA9062_TWDSCALE_MAX; i++) {
  52                if (wdt_timeout[i] >= secs)
  53                        return i;
  54        }
  55
  56        return DA9062_TWDSCALE_MAX;
  57}
  58
  59static int da9062_reset_watchdog_timer(struct da9062_watchdog *wdt)
  60{
  61        return regmap_update_bits(wdt->hw->regmap, DA9062AA_CONTROL_F,
  62                                  DA9062AA_WATCHDOG_MASK,
  63                                  DA9062AA_WATCHDOG_MASK);
  64}
  65
  66static int da9062_wdt_update_timeout_register(struct da9062_watchdog *wdt,
  67                                              unsigned int regval)
  68{
  69        struct da9062 *chip = wdt->hw;
  70
  71        regmap_update_bits(chip->regmap,
  72                                  DA9062AA_CONTROL_D,
  73                                  DA9062AA_TWDSCALE_MASK,
  74                                  DA9062_TWDSCALE_DISABLE);
  75
  76        usleep_range(150, 300);
  77
  78        return regmap_update_bits(chip->regmap,
  79                                  DA9062AA_CONTROL_D,
  80                                  DA9062AA_TWDSCALE_MASK,
  81                                  regval);
  82}
  83
  84static int da9062_wdt_start(struct watchdog_device *wdd)
  85{
  86        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
  87        unsigned int selector;
  88        int ret;
  89
  90        selector = da9062_wdt_timeout_to_sel(wdt->wdtdev.timeout);
  91        ret = da9062_wdt_update_timeout_register(wdt, selector);
  92        if (ret)
  93                dev_err(wdt->hw->dev, "Watchdog failed to start (err = %d)\n",
  94                        ret);
  95
  96        return ret;
  97}
  98
  99static int da9062_wdt_stop(struct watchdog_device *wdd)
 100{
 101        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
 102        int ret;
 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        struct i2c_client *client = to_i2c_client(wdt->hw->dev);
 151        int ret;
 152
 153        /* Don't use regmap because it is not atomic safe */
 154        ret = i2c_smbus_write_byte_data(client, DA9062AA_CONTROL_F,
 155                                        DA9062AA_SHUTDOWN_MASK);
 156        if (ret < 0)
 157                dev_alert(wdt->hw->dev, "Failed to shutdown (err = %d)\n",
 158                          ret);
 159
 160        /* wait for reset to assert... */
 161        mdelay(500);
 162
 163        return ret;
 164}
 165
 166static const struct watchdog_info da9062_watchdog_info = {
 167        .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
 168        .identity = "DA9062 WDT",
 169};
 170
 171static const struct watchdog_ops da9062_watchdog_ops = {
 172        .owner = THIS_MODULE,
 173        .start = da9062_wdt_start,
 174        .stop = da9062_wdt_stop,
 175        .ping = da9062_wdt_ping,
 176        .set_timeout = da9062_wdt_set_timeout,
 177        .restart = da9062_wdt_restart,
 178};
 179
 180static const struct of_device_id da9062_compatible_id_table[] = {
 181        { .compatible = "dlg,da9062-watchdog", },
 182        { },
 183};
 184
 185MODULE_DEVICE_TABLE(of, da9062_compatible_id_table);
 186
 187static int da9062_wdt_probe(struct platform_device *pdev)
 188{
 189        struct device *dev = &pdev->dev;
 190        unsigned int timeout;
 191        struct da9062 *chip;
 192        struct da9062_watchdog *wdt;
 193
 194        chip = dev_get_drvdata(dev->parent);
 195        if (!chip)
 196                return -EINVAL;
 197
 198        wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
 199        if (!wdt)
 200                return -ENOMEM;
 201
 202        wdt->use_sw_pm = device_property_present(dev, "dlg,use-sw-pm");
 203
 204        wdt->hw = chip;
 205
 206        wdt->wdtdev.info = &da9062_watchdog_info;
 207        wdt->wdtdev.ops = &da9062_watchdog_ops;
 208        wdt->wdtdev.min_timeout = DA9062_WDT_MIN_TIMEOUT;
 209        wdt->wdtdev.max_timeout = DA9062_WDT_MAX_TIMEOUT;
 210        wdt->wdtdev.min_hw_heartbeat_ms = DA9062_RESET_PROTECTION_MS;
 211        wdt->wdtdev.timeout = DA9062_WDG_DEFAULT_TIMEOUT;
 212        wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
 213        wdt->wdtdev.parent = dev;
 214
 215        watchdog_set_restart_priority(&wdt->wdtdev, 128);
 216
 217        watchdog_set_drvdata(&wdt->wdtdev, wdt);
 218        dev_set_drvdata(dev, &wdt->wdtdev);
 219
 220        timeout = da9062_wdt_read_timeout(wdt);
 221        if (timeout)
 222                wdt->wdtdev.timeout = timeout;
 223
 224        /* Set timeout from DT value if available */
 225        watchdog_init_timeout(&wdt->wdtdev, 0, dev);
 226
 227        if (timeout) {
 228                da9062_wdt_set_timeout(&wdt->wdtdev, wdt->wdtdev.timeout);
 229                set_bit(WDOG_HW_RUNNING, &wdt->wdtdev.status);
 230        }
 231
 232        return devm_watchdog_register_device(dev, &wdt->wdtdev);
 233}
 234
 235static int __maybe_unused da9062_wdt_suspend(struct device *dev)
 236{
 237        struct watchdog_device *wdd = dev_get_drvdata(dev);
 238        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
 239
 240        if (!wdt->use_sw_pm)
 241                return 0;
 242
 243        if (watchdog_active(wdd))
 244                return da9062_wdt_stop(wdd);
 245
 246        return 0;
 247}
 248
 249static int __maybe_unused da9062_wdt_resume(struct device *dev)
 250{
 251        struct watchdog_device *wdd = dev_get_drvdata(dev);
 252        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
 253
 254        if (!wdt->use_sw_pm)
 255                return 0;
 256
 257        if (watchdog_active(wdd))
 258                return da9062_wdt_start(wdd);
 259
 260        return 0;
 261}
 262
 263static SIMPLE_DEV_PM_OPS(da9062_wdt_pm_ops,
 264                         da9062_wdt_suspend, da9062_wdt_resume);
 265
 266static struct platform_driver da9062_wdt_driver = {
 267        .probe = da9062_wdt_probe,
 268        .driver = {
 269                .name = "da9062-watchdog",
 270                .pm = &da9062_wdt_pm_ops,
 271                .of_match_table = da9062_compatible_id_table,
 272        },
 273};
 274module_platform_driver(da9062_wdt_driver);
 275
 276MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>");
 277MODULE_DESCRIPTION("WDT device driver for Dialog DA9062 and DA9061");
 278MODULE_LICENSE("GPL");
 279MODULE_ALIAS("platform:da9062-watchdog");
 280