linux/drivers/watchdog/pm8916_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2#include <linux/bitops.h>
   3#include <linux/interrupt.h>
   4#include <linux/kernel.h>
   5#include <linux/module.h>
   6#include <linux/of.h>
   7#include <linux/property.h>
   8#include <linux/platform_device.h>
   9#include <linux/regmap.h>
  10#include <linux/watchdog.h>
  11
  12#define PON_INT_RT_STS                  0x10
  13#define PMIC_WD_BARK_STS_BIT            BIT(6)
  14
  15#define PON_PMIC_WD_RESET_S1_TIMER      0x54
  16#define PON_PMIC_WD_RESET_S2_TIMER      0x55
  17
  18#define PON_PMIC_WD_RESET_S2_CTL        0x56
  19#define RESET_TYPE_WARM                 0x01
  20#define RESET_TYPE_SHUTDOWN             0x04
  21#define RESET_TYPE_HARD                 0x07
  22
  23#define PON_PMIC_WD_RESET_S2_CTL2       0x57
  24#define S2_RESET_EN_BIT                 BIT(7)
  25
  26#define PON_PMIC_WD_RESET_PET           0x58
  27#define WATCHDOG_PET_BIT                BIT(0)
  28
  29#define PM8916_WDT_DEFAULT_TIMEOUT      32
  30#define PM8916_WDT_MIN_TIMEOUT          1
  31#define PM8916_WDT_MAX_TIMEOUT          127
  32
  33struct pm8916_wdt {
  34        struct regmap *regmap;
  35        struct watchdog_device wdev;
  36        u32 baseaddr;
  37};
  38
  39static int pm8916_wdt_start(struct watchdog_device *wdev)
  40{
  41        struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
  42
  43        return regmap_update_bits(wdt->regmap,
  44                                  wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,
  45                                  S2_RESET_EN_BIT, S2_RESET_EN_BIT);
  46}
  47
  48static int pm8916_wdt_stop(struct watchdog_device *wdev)
  49{
  50        struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
  51
  52        return regmap_update_bits(wdt->regmap,
  53                                  wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,
  54                                  S2_RESET_EN_BIT, 0);
  55}
  56
  57static int pm8916_wdt_ping(struct watchdog_device *wdev)
  58{
  59        struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
  60
  61        return regmap_update_bits(wdt->regmap,
  62                                  wdt->baseaddr + PON_PMIC_WD_RESET_PET,
  63                                  WATCHDOG_PET_BIT, WATCHDOG_PET_BIT);
  64}
  65
  66static int pm8916_wdt_configure_timers(struct watchdog_device *wdev)
  67{
  68        struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
  69        int err;
  70
  71        err = regmap_write(wdt->regmap,
  72                           wdt->baseaddr + PON_PMIC_WD_RESET_S1_TIMER,
  73                           wdev->timeout - wdev->pretimeout);
  74        if (err)
  75                return err;
  76
  77        return regmap_write(wdt->regmap,
  78                            wdt->baseaddr + PON_PMIC_WD_RESET_S2_TIMER,
  79                            wdev->pretimeout);
  80}
  81
  82static int pm8916_wdt_set_timeout(struct watchdog_device *wdev,
  83                                  unsigned int timeout)
  84{
  85        wdev->timeout = timeout;
  86
  87        return pm8916_wdt_configure_timers(wdev);
  88}
  89
  90static int pm8916_wdt_set_pretimeout(struct watchdog_device *wdev,
  91                                     unsigned int pretimeout)
  92{
  93        wdev->pretimeout = pretimeout;
  94
  95        return pm8916_wdt_configure_timers(wdev);
  96}
  97
  98static irqreturn_t pm8916_wdt_isr(int irq, void *arg)
  99{
 100        struct pm8916_wdt *wdt = arg;
 101        int err, sts;
 102
 103        err = regmap_read(wdt->regmap, wdt->baseaddr + PON_INT_RT_STS, &sts);
 104        if (err)
 105                return IRQ_HANDLED;
 106
 107        if (sts & PMIC_WD_BARK_STS_BIT)
 108                watchdog_notify_pretimeout(&wdt->wdev);
 109
 110        return IRQ_HANDLED;
 111}
 112
 113static const struct watchdog_info pm8916_wdt_ident = {
 114        .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
 115        .identity = "QCOM PM8916 PON WDT",
 116};
 117
 118static const struct watchdog_info pm8916_wdt_pt_ident = {
 119        .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |
 120                   WDIOF_PRETIMEOUT,
 121        .identity = "QCOM PM8916 PON WDT",
 122};
 123
 124static const struct watchdog_ops pm8916_wdt_ops = {
 125        .owner = THIS_MODULE,
 126        .start = pm8916_wdt_start,
 127        .stop = pm8916_wdt_stop,
 128        .ping = pm8916_wdt_ping,
 129        .set_timeout = pm8916_wdt_set_timeout,
 130        .set_pretimeout = pm8916_wdt_set_pretimeout,
 131};
 132
 133static int pm8916_wdt_probe(struct platform_device *pdev)
 134{
 135        struct device *dev = &pdev->dev;
 136        struct pm8916_wdt *wdt;
 137        struct device *parent;
 138        int err, irq;
 139
 140        wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
 141        if (!wdt)
 142                return -ENOMEM;
 143
 144        parent = dev->parent;
 145
 146        /*
 147         * The pm8916-pon-wdt is a child of the pon device, which is a child
 148         * of the pm8916 mfd device. We want access to the pm8916 registers.
 149         * Retrieve regmap from pm8916 (parent->parent) and base address
 150         * from pm8916-pon (pon).
 151         */
 152        wdt->regmap = dev_get_regmap(parent->parent, NULL);
 153        if (!wdt->regmap) {
 154                dev_err(dev, "failed to locate regmap\n");
 155                return -ENODEV;
 156        }
 157
 158        err = device_property_read_u32(parent, "reg", &wdt->baseaddr);
 159        if (err) {
 160                dev_err(dev, "failed to get pm8916-pon address\n");
 161                return err;
 162        }
 163
 164        irq = platform_get_irq(pdev, 0);
 165        if (irq > 0) {
 166                err = devm_request_irq(dev, irq, pm8916_wdt_isr, 0,
 167                                       "pm8916_wdt", wdt);
 168                if (err)
 169                        return err;
 170
 171                wdt->wdev.info = &pm8916_wdt_pt_ident;
 172        } else {
 173                if (irq == -EPROBE_DEFER)
 174                        return -EPROBE_DEFER;
 175
 176                wdt->wdev.info = &pm8916_wdt_ident;
 177        }
 178
 179        /* Configure watchdog to hard-reset mode */
 180        err = regmap_write(wdt->regmap,
 181                           wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL,
 182                           RESET_TYPE_HARD);
 183        if (err) {
 184                dev_err(dev, "failed configure watchdog\n");
 185                return err;
 186        }
 187
 188        wdt->wdev.ops = &pm8916_wdt_ops,
 189        wdt->wdev.parent = dev;
 190        wdt->wdev.min_timeout = PM8916_WDT_MIN_TIMEOUT;
 191        wdt->wdev.max_timeout = PM8916_WDT_MAX_TIMEOUT;
 192        wdt->wdev.timeout = PM8916_WDT_DEFAULT_TIMEOUT;
 193        wdt->wdev.pretimeout = 0;
 194        watchdog_set_drvdata(&wdt->wdev, wdt);
 195        platform_set_drvdata(pdev, wdt);
 196
 197        watchdog_init_timeout(&wdt->wdev, 0, dev);
 198        pm8916_wdt_configure_timers(&wdt->wdev);
 199
 200        return devm_watchdog_register_device(dev, &wdt->wdev);
 201}
 202
 203static int __maybe_unused pm8916_wdt_suspend(struct device *dev)
 204{
 205        struct pm8916_wdt *wdt = dev_get_drvdata(dev);
 206
 207        if (watchdog_active(&wdt->wdev))
 208                return pm8916_wdt_stop(&wdt->wdev);
 209
 210        return 0;
 211}
 212
 213static int __maybe_unused pm8916_wdt_resume(struct device *dev)
 214{
 215        struct pm8916_wdt *wdt = dev_get_drvdata(dev);
 216
 217        if (watchdog_active(&wdt->wdev))
 218                return pm8916_wdt_start(&wdt->wdev);
 219
 220        return 0;
 221}
 222
 223static SIMPLE_DEV_PM_OPS(pm8916_wdt_pm_ops, pm8916_wdt_suspend,
 224                         pm8916_wdt_resume);
 225
 226static const struct of_device_id pm8916_wdt_id_table[] = {
 227        { .compatible = "qcom,pm8916-wdt" },
 228        { }
 229};
 230MODULE_DEVICE_TABLE(of, pm8916_wdt_id_table);
 231
 232static struct platform_driver pm8916_wdt_driver = {
 233        .probe = pm8916_wdt_probe,
 234        .driver = {
 235                .name = "pm8916-wdt",
 236                .of_match_table = of_match_ptr(pm8916_wdt_id_table),
 237                .pm = &pm8916_wdt_pm_ops,
 238        },
 239};
 240module_platform_driver(pm8916_wdt_driver);
 241
 242MODULE_AUTHOR("Loic Poulain <loic.poulain@linaro.org>");
 243MODULE_DESCRIPTION("Qualcomm pm8916 watchdog driver");
 244MODULE_LICENSE("GPL v2");
 245