linux/drivers/watchdog/visconti_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright (c) 2020 TOSHIBA CORPORATION
   4 * Copyright (c) 2020 Toshiba Electronic Devices & Storage Corporation
   5 * Copyright (c) 2020 Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp>
   6 */
   7
   8#include <linux/clk.h>
   9#include <linux/io.h>
  10#include <linux/kernel.h>
  11#include <linux/module.h>
  12#include <linux/of.h>
  13#include <linux/platform_device.h>
  14#include <linux/watchdog.h>
  15
  16#define WDT_CNT                 0x00
  17#define WDT_MIN                 0x04
  18#define WDT_MAX                 0x08
  19#define WDT_CTL                 0x0c
  20#define WDT_CMD                 0x10
  21#define WDT_CMD_CLEAR           0x4352
  22#define WDT_CMD_START_STOP      0x5354
  23#define WDT_DIV                 0x30
  24
  25#define VISCONTI_WDT_FREQ       2000000 /* 2MHz */
  26#define WDT_DEFAULT_TIMEOUT     10U /* in seconds */
  27
  28static bool nowayout = WATCHDOG_NOWAYOUT;
  29module_param(nowayout, bool, 0);
  30MODULE_PARM_DESC(
  31        nowayout,
  32        "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT)")");
  33
  34struct visconti_wdt_priv {
  35        struct watchdog_device wdev;
  36        void __iomem *base;
  37        u32 div;
  38};
  39
  40static int visconti_wdt_start(struct watchdog_device *wdev)
  41{
  42        struct visconti_wdt_priv *priv = watchdog_get_drvdata(wdev);
  43        u32 timeout = wdev->timeout * VISCONTI_WDT_FREQ;
  44
  45        writel(priv->div, priv->base + WDT_DIV);
  46        writel(0, priv->base + WDT_MIN);
  47        writel(timeout, priv->base + WDT_MAX);
  48        writel(0, priv->base + WDT_CTL);
  49        writel(WDT_CMD_START_STOP, priv->base + WDT_CMD);
  50
  51        return 0;
  52}
  53
  54static int visconti_wdt_stop(struct watchdog_device *wdev)
  55{
  56        struct visconti_wdt_priv *priv = watchdog_get_drvdata(wdev);
  57
  58        writel(1, priv->base + WDT_CTL);
  59        writel(WDT_CMD_START_STOP, priv->base + WDT_CMD);
  60
  61        return 0;
  62}
  63
  64static int visconti_wdt_ping(struct watchdog_device *wdd)
  65{
  66        struct visconti_wdt_priv *priv = watchdog_get_drvdata(wdd);
  67
  68        writel(WDT_CMD_CLEAR, priv->base + WDT_CMD);
  69
  70        return 0;
  71}
  72
  73static unsigned int visconti_wdt_get_timeleft(struct watchdog_device *wdev)
  74{
  75        struct visconti_wdt_priv *priv = watchdog_get_drvdata(wdev);
  76        u32 timeout = wdev->timeout * VISCONTI_WDT_FREQ;
  77        u32 cnt = readl(priv->base + WDT_CNT);
  78
  79        if (timeout <= cnt)
  80                return 0;
  81        timeout -= cnt;
  82
  83        return timeout / VISCONTI_WDT_FREQ;
  84}
  85
  86static int visconti_wdt_set_timeout(struct watchdog_device *wdev, unsigned int timeout)
  87{
  88        u32 val;
  89        struct visconti_wdt_priv *priv = watchdog_get_drvdata(wdev);
  90
  91        wdev->timeout = timeout;
  92        val = wdev->timeout * VISCONTI_WDT_FREQ;
  93
  94        /* Clear counter before setting timeout because WDT expires */
  95        writel(WDT_CMD_CLEAR, priv->base + WDT_CMD);
  96        writel(val, priv->base + WDT_MAX);
  97
  98        return 0;
  99}
 100
 101static const struct watchdog_info visconti_wdt_info = {
 102        .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
 103        .identity = "Visconti Watchdog",
 104};
 105
 106static const struct watchdog_ops visconti_wdt_ops = {
 107        .owner          = THIS_MODULE,
 108        .start          = visconti_wdt_start,
 109        .stop           = visconti_wdt_stop,
 110        .ping           = visconti_wdt_ping,
 111        .get_timeleft   = visconti_wdt_get_timeleft,
 112        .set_timeout    = visconti_wdt_set_timeout,
 113};
 114
 115static void visconti_clk_disable_unprepare(void *data)
 116{
 117        clk_disable_unprepare(data);
 118}
 119
 120static int visconti_wdt_probe(struct platform_device *pdev)
 121{
 122        struct watchdog_device *wdev;
 123        struct visconti_wdt_priv *priv;
 124        struct device *dev = &pdev->dev;
 125        struct clk *clk;
 126        int ret;
 127        unsigned long clk_freq;
 128
 129        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 130        if (!priv)
 131                return -ENOMEM;
 132
 133        priv->base = devm_platform_ioremap_resource(pdev, 0);
 134        if (IS_ERR(priv->base))
 135                return PTR_ERR(priv->base);
 136
 137        clk = devm_clk_get(dev, NULL);
 138        if (IS_ERR(clk))
 139                return dev_err_probe(dev, PTR_ERR(clk), "Could not get clock\n");
 140
 141        ret = clk_prepare_enable(clk);
 142        if (ret) {
 143                dev_err(dev, "Could not enable clock\n");
 144                return ret;
 145        }
 146
 147        ret = devm_add_action_or_reset(dev, visconti_clk_disable_unprepare, clk);
 148        if (ret)
 149                return ret;
 150
 151        clk_freq = clk_get_rate(clk);
 152        if (!clk_freq)
 153                return -EINVAL;
 154
 155        priv->div = clk_freq / VISCONTI_WDT_FREQ;
 156
 157        /* Initialize struct watchdog_device. */
 158        wdev = &priv->wdev;
 159        wdev->info = &visconti_wdt_info;
 160        wdev->ops = &visconti_wdt_ops;
 161        wdev->parent = dev;
 162        wdev->min_timeout = 1;
 163        wdev->max_timeout = 0xffffffff / VISCONTI_WDT_FREQ;
 164        wdev->timeout = min(wdev->max_timeout, WDT_DEFAULT_TIMEOUT);
 165
 166        watchdog_set_drvdata(wdev, priv);
 167        watchdog_set_nowayout(wdev, nowayout);
 168        watchdog_stop_on_unregister(wdev);
 169
 170        /* This overrides the default timeout only if DT configuration was found */
 171        ret = watchdog_init_timeout(wdev, 0, dev);
 172        if (ret)
 173                dev_warn(dev, "Specified timeout value invalid, using default\n");
 174
 175        return devm_watchdog_register_device(dev, wdev);
 176}
 177
 178static const struct of_device_id visconti_wdt_of_match[] = {
 179        { .compatible = "toshiba,visconti-wdt", },
 180        {}
 181};
 182MODULE_DEVICE_TABLE(of, visconti_wdt_of_match);
 183
 184static struct platform_driver visconti_wdt_driver = {
 185        .driver = {
 186                        .name = "visconti_wdt",
 187                        .of_match_table = visconti_wdt_of_match,
 188                },
 189        .probe = visconti_wdt_probe,
 190};
 191module_platform_driver(visconti_wdt_driver);
 192
 193MODULE_DESCRIPTION("TOSHIBA Visconti Watchdog Driver");
 194MODULE_AUTHOR("Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp");
 195MODULE_LICENSE("GPL v2");
 196