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        /*
 121         * Prevent pings from occurring late in system poweroff/reboot sequence
 122         * and possibly locking out restart handler from accessing i2c bus.
 123         */
 124        if (system_state > SYSTEM_RUNNING)
 125                return 0;
 126
 127        ret = da9062_reset_watchdog_timer(wdt);
 128        if (ret)
 129                dev_err(wdt->hw->dev, "Failed to ping the watchdog (err = %d)\n",
 130                        ret);
 131
 132        return ret;
 133}
 134
 135static int da9062_wdt_set_timeout(struct watchdog_device *wdd,
 136                                  unsigned int timeout)
 137{
 138        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
 139        unsigned int selector;
 140        int ret;
 141
 142        selector = da9062_wdt_timeout_to_sel(timeout);
 143        ret = da9062_wdt_update_timeout_register(wdt, selector);
 144        if (ret)
 145                dev_err(wdt->hw->dev, "Failed to set watchdog timeout (err = %d)\n",
 146                        ret);
 147        else
 148                wdd->timeout = wdt_timeout[selector];
 149
 150        return ret;
 151}
 152
 153static int da9062_wdt_restart(struct watchdog_device *wdd, unsigned long action,
 154                              void *data)
 155{
 156        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
 157        struct i2c_client *client = to_i2c_client(wdt->hw->dev);
 158        int ret;
 159
 160        /* Don't use regmap because it is not atomic safe */
 161        ret = i2c_smbus_write_byte_data(client, DA9062AA_CONTROL_F,
 162                                        DA9062AA_SHUTDOWN_MASK);
 163        if (ret < 0)
 164                dev_alert(wdt->hw->dev, "Failed to shutdown (err = %d)\n",
 165                          ret);
 166
 167        /* wait for reset to assert... */
 168        mdelay(500);
 169
 170        return ret;
 171}
 172
 173static const struct watchdog_info da9062_watchdog_info = {
 174        .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
 175        .identity = "DA9062 WDT",
 176};
 177
 178static const struct watchdog_ops da9062_watchdog_ops = {
 179        .owner = THIS_MODULE,
 180        .start = da9062_wdt_start,
 181        .stop = da9062_wdt_stop,
 182        .ping = da9062_wdt_ping,
 183        .set_timeout = da9062_wdt_set_timeout,
 184        .restart = da9062_wdt_restart,
 185};
 186
 187static const struct of_device_id da9062_compatible_id_table[] = {
 188        { .compatible = "dlg,da9062-watchdog", },
 189        { },
 190};
 191
 192MODULE_DEVICE_TABLE(of, da9062_compatible_id_table);
 193
 194static int da9062_wdt_probe(struct platform_device *pdev)
 195{
 196        struct device *dev = &pdev->dev;
 197        unsigned int timeout;
 198        struct da9062 *chip;
 199        struct da9062_watchdog *wdt;
 200
 201        chip = dev_get_drvdata(dev->parent);
 202        if (!chip)
 203                return -EINVAL;
 204
 205        wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
 206        if (!wdt)
 207                return -ENOMEM;
 208
 209        wdt->use_sw_pm = device_property_present(dev, "dlg,use-sw-pm");
 210
 211        wdt->hw = chip;
 212
 213        wdt->wdtdev.info = &da9062_watchdog_info;
 214        wdt->wdtdev.ops = &da9062_watchdog_ops;
 215        wdt->wdtdev.min_timeout = DA9062_WDT_MIN_TIMEOUT;
 216        wdt->wdtdev.max_timeout = DA9062_WDT_MAX_TIMEOUT;
 217        wdt->wdtdev.min_hw_heartbeat_ms = DA9062_RESET_PROTECTION_MS;
 218        wdt->wdtdev.timeout = DA9062_WDG_DEFAULT_TIMEOUT;
 219        wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
 220        wdt->wdtdev.parent = dev;
 221
 222        watchdog_set_restart_priority(&wdt->wdtdev, 128);
 223
 224        watchdog_set_drvdata(&wdt->wdtdev, wdt);
 225        dev_set_drvdata(dev, &wdt->wdtdev);
 226
 227        timeout = da9062_wdt_read_timeout(wdt);
 228        if (timeout)
 229                wdt->wdtdev.timeout = timeout;
 230
 231        /* Set timeout from DT value if available */
 232        watchdog_init_timeout(&wdt->wdtdev, 0, dev);
 233
 234        if (timeout) {
 235                da9062_wdt_set_timeout(&wdt->wdtdev, wdt->wdtdev.timeout);
 236                set_bit(WDOG_HW_RUNNING, &wdt->wdtdev.status);
 237        }
 238
 239        return devm_watchdog_register_device(dev, &wdt->wdtdev);
 240}
 241
 242static int __maybe_unused da9062_wdt_suspend(struct device *dev)
 243{
 244        struct watchdog_device *wdd = dev_get_drvdata(dev);
 245        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
 246
 247        if (!wdt->use_sw_pm)
 248                return 0;
 249
 250        if (watchdog_active(wdd))
 251                return da9062_wdt_stop(wdd);
 252
 253        return 0;
 254}
 255
 256static int __maybe_unused da9062_wdt_resume(struct device *dev)
 257{
 258        struct watchdog_device *wdd = dev_get_drvdata(dev);
 259        struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
 260
 261        if (!wdt->use_sw_pm)
 262                return 0;
 263
 264        if (watchdog_active(wdd))
 265                return da9062_wdt_start(wdd);
 266
 267        return 0;
 268}
 269
 270static SIMPLE_DEV_PM_OPS(da9062_wdt_pm_ops,
 271                         da9062_wdt_suspend, da9062_wdt_resume);
 272
 273static struct platform_driver da9062_wdt_driver = {
 274        .probe = da9062_wdt_probe,
 275        .driver = {
 276                .name = "da9062-watchdog",
 277                .pm = &da9062_wdt_pm_ops,
 278                .of_match_table = da9062_compatible_id_table,
 279        },
 280};
 281module_platform_driver(da9062_wdt_driver);
 282
 283MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>");
 284MODULE_DESCRIPTION("WDT device driver for Dialog DA9062 and DA9061");
 285MODULE_LICENSE("GPL");
 286MODULE_ALIAS("platform:da9062-watchdog");
 287