linux/drivers/watchdog/jz4740_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 *  Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net>
   4 *  JZ4740 Watchdog driver
   5 */
   6
   7#include <linux/mfd/ingenic-tcu.h>
   8#include <linux/mfd/syscon.h>
   9#include <linux/module.h>
  10#include <linux/moduleparam.h>
  11#include <linux/types.h>
  12#include <linux/kernel.h>
  13#include <linux/watchdog.h>
  14#include <linux/platform_device.h>
  15#include <linux/io.h>
  16#include <linux/device.h>
  17#include <linux/clk.h>
  18#include <linux/slab.h>
  19#include <linux/err.h>
  20#include <linux/of.h>
  21#include <linux/regmap.h>
  22
  23#define DEFAULT_HEARTBEAT 5
  24#define MAX_HEARTBEAT     2048
  25
  26static bool nowayout = WATCHDOG_NOWAYOUT;
  27module_param(nowayout, bool, 0);
  28MODULE_PARM_DESC(nowayout,
  29                 "Watchdog cannot be stopped once started (default="
  30                 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  31
  32static unsigned int heartbeat = DEFAULT_HEARTBEAT;
  33module_param(heartbeat, uint, 0);
  34MODULE_PARM_DESC(heartbeat,
  35                "Watchdog heartbeat period in seconds from 1 to "
  36                __MODULE_STRING(MAX_HEARTBEAT) ", default "
  37                __MODULE_STRING(DEFAULT_HEARTBEAT));
  38
  39struct jz4740_wdt_drvdata {
  40        struct watchdog_device wdt;
  41        struct regmap *map;
  42        struct clk *clk;
  43        unsigned long clk_rate;
  44};
  45
  46static int jz4740_wdt_ping(struct watchdog_device *wdt_dev)
  47{
  48        struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
  49
  50        regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0);
  51
  52        return 0;
  53}
  54
  55static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev,
  56                                    unsigned int new_timeout)
  57{
  58        struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
  59        u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout);
  60        unsigned int tcer;
  61
  62        regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer);
  63        regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0);
  64
  65        regmap_write(drvdata->map, TCU_REG_WDT_TDR, timeout_value);
  66        regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0);
  67
  68        if (tcer & TCU_WDT_TCER_TCEN)
  69                regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN);
  70
  71        wdt_dev->timeout = new_timeout;
  72        return 0;
  73}
  74
  75static int jz4740_wdt_start(struct watchdog_device *wdt_dev)
  76{
  77        struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
  78        unsigned int tcer;
  79        int ret;
  80
  81        ret = clk_prepare_enable(drvdata->clk);
  82        if (ret)
  83                return ret;
  84
  85        regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer);
  86
  87        jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
  88
  89        /* Start watchdog if it wasn't started already */
  90        if (!(tcer & TCU_WDT_TCER_TCEN))
  91                regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN);
  92
  93        return 0;
  94}
  95
  96static int jz4740_wdt_stop(struct watchdog_device *wdt_dev)
  97{
  98        struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
  99
 100        regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0);
 101        clk_disable_unprepare(drvdata->clk);
 102
 103        return 0;
 104}
 105
 106static int jz4740_wdt_restart(struct watchdog_device *wdt_dev,
 107                              unsigned long action, void *data)
 108{
 109        wdt_dev->timeout = 0;
 110        jz4740_wdt_start(wdt_dev);
 111        return 0;
 112}
 113
 114static const struct watchdog_info jz4740_wdt_info = {
 115        .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
 116        .identity = "jz4740 Watchdog",
 117};
 118
 119static const struct watchdog_ops jz4740_wdt_ops = {
 120        .owner = THIS_MODULE,
 121        .start = jz4740_wdt_start,
 122        .stop = jz4740_wdt_stop,
 123        .ping = jz4740_wdt_ping,
 124        .set_timeout = jz4740_wdt_set_timeout,
 125        .restart = jz4740_wdt_restart,
 126};
 127
 128#ifdef CONFIG_OF
 129static const struct of_device_id jz4740_wdt_of_matches[] = {
 130        { .compatible = "ingenic,jz4740-watchdog", },
 131        { .compatible = "ingenic,jz4780-watchdog", },
 132        { /* sentinel */ }
 133};
 134MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches);
 135#endif
 136
 137static int jz4740_wdt_probe(struct platform_device *pdev)
 138{
 139        struct device *dev = &pdev->dev;
 140        struct jz4740_wdt_drvdata *drvdata;
 141        struct watchdog_device *jz4740_wdt;
 142        long rate;
 143        int ret;
 144
 145        drvdata = devm_kzalloc(dev, sizeof(struct jz4740_wdt_drvdata),
 146                               GFP_KERNEL);
 147        if (!drvdata)
 148                return -ENOMEM;
 149
 150        drvdata->clk = devm_clk_get(&pdev->dev, "wdt");
 151        if (IS_ERR(drvdata->clk)) {
 152                dev_err(&pdev->dev, "cannot find WDT clock\n");
 153                return PTR_ERR(drvdata->clk);
 154        }
 155
 156        /* Set smallest clock possible */
 157        rate = clk_round_rate(drvdata->clk, 1);
 158        if (rate < 0)
 159                return rate;
 160
 161        ret = clk_set_rate(drvdata->clk, rate);
 162        if (ret)
 163                return ret;
 164
 165        drvdata->clk_rate = rate;
 166        jz4740_wdt = &drvdata->wdt;
 167        jz4740_wdt->info = &jz4740_wdt_info;
 168        jz4740_wdt->ops = &jz4740_wdt_ops;
 169        jz4740_wdt->min_timeout = 1;
 170        jz4740_wdt->max_timeout = 0xffff / rate;
 171        jz4740_wdt->timeout = clamp(heartbeat,
 172                                    jz4740_wdt->min_timeout,
 173                                    jz4740_wdt->max_timeout);
 174        jz4740_wdt->parent = dev;
 175        watchdog_set_nowayout(jz4740_wdt, nowayout);
 176        watchdog_set_drvdata(jz4740_wdt, drvdata);
 177
 178        drvdata->map = device_node_to_regmap(dev->parent->of_node);
 179        if (IS_ERR(drvdata->map)) {
 180                dev_err(dev, "regmap not found\n");
 181                return PTR_ERR(drvdata->map);
 182        }
 183
 184        return devm_watchdog_register_device(dev, &drvdata->wdt);
 185}
 186
 187static struct platform_driver jz4740_wdt_driver = {
 188        .probe = jz4740_wdt_probe,
 189        .driver = {
 190                .name = "jz4740-wdt",
 191                .of_match_table = of_match_ptr(jz4740_wdt_of_matches),
 192        },
 193};
 194
 195module_platform_driver(jz4740_wdt_driver);
 196
 197MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>");
 198MODULE_DESCRIPTION("jz4740 Watchdog Driver");
 199MODULE_LICENSE("GPL");
 200MODULE_ALIAS("platform:jz4740-wdt");
 201