linux/drivers/watchdog/rti_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Watchdog driver for the K3 RTI module
   4 *
   5 * (c) Copyright 2019-2020 Texas Instruments Inc.
   6 * All rights reserved.
   7 */
   8
   9#include <linux/clk.h>
  10#include <linux/device.h>
  11#include <linux/err.h>
  12#include <linux/io.h>
  13#include <linux/kernel.h>
  14#include <linux/mod_devicetable.h>
  15#include <linux/module.h>
  16#include <linux/moduleparam.h>
  17#include <linux/platform_device.h>
  18#include <linux/pm_runtime.h>
  19#include <linux/types.h>
  20#include <linux/watchdog.h>
  21
  22#define DEFAULT_HEARTBEAT 60
  23
  24/* Max heartbeat is calculated at 32kHz source clock */
  25#define MAX_HEARTBEAT   1000
  26
  27/* Timer register set definition */
  28#define RTIDWDCTRL      0x90
  29#define RTIDWDPRLD      0x94
  30#define RTIWDSTATUS     0x98
  31#define RTIWDKEY        0x9c
  32#define RTIDWDCNTR      0xa0
  33#define RTIWWDRXCTRL    0xa4
  34#define RTIWWDSIZECTRL  0xa8
  35
  36#define RTIWWDRX_NMI    0xa
  37
  38#define RTIWWDSIZE_50P          0x50
  39#define RTIWWDSIZE_25P          0x500
  40#define RTIWWDSIZE_12P5         0x5000
  41#define RTIWWDSIZE_6P25         0x50000
  42#define RTIWWDSIZE_3P125        0x500000
  43
  44#define WDENABLE_KEY    0xa98559da
  45
  46#define WDKEY_SEQ0              0xe51a
  47#define WDKEY_SEQ1              0xa35c
  48
  49#define WDT_PRELOAD_SHIFT       13
  50
  51#define WDT_PRELOAD_MAX         0xfff
  52
  53#define DWDST                   BIT(1)
  54
  55static int heartbeat = DEFAULT_HEARTBEAT;
  56
  57/*
  58 * struct to hold data for each WDT device
  59 * @base - base io address of WD device
  60 * @freq - source clock frequency of WDT
  61 * @wdd  - hold watchdog device as is in WDT core
  62 */
  63struct rti_wdt_device {
  64        void __iomem            *base;
  65        unsigned long           freq;
  66        struct watchdog_device  wdd;
  67};
  68
  69static int rti_wdt_start(struct watchdog_device *wdd)
  70{
  71        u32 timer_margin;
  72        struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);
  73
  74        /* set timeout period */
  75        timer_margin = (u64)wdd->timeout * wdt->freq;
  76        timer_margin >>= WDT_PRELOAD_SHIFT;
  77        if (timer_margin > WDT_PRELOAD_MAX)
  78                timer_margin = WDT_PRELOAD_MAX;
  79        writel_relaxed(timer_margin, wdt->base + RTIDWDPRLD);
  80
  81        /*
  82         * RTI only supports a windowed mode, where the watchdog can only
  83         * be petted during the open window; not too early or not too late.
  84         * The HW configuration options only allow for the open window size
  85         * to be 50% or less than that; we obviouly want to configure the open
  86         * window as large as possible so we select the 50% option.
  87         */
  88        wdd->min_hw_heartbeat_ms = 500 * wdd->timeout;
  89
  90        /* Generate NMI when wdt expires */
  91        writel_relaxed(RTIWWDRX_NMI, wdt->base + RTIWWDRXCTRL);
  92
  93        /* Open window size 50%; this is the largest window size available */
  94        writel_relaxed(RTIWWDSIZE_50P, wdt->base + RTIWWDSIZECTRL);
  95
  96        readl_relaxed(wdt->base + RTIWWDSIZECTRL);
  97
  98        /* enable watchdog */
  99        writel_relaxed(WDENABLE_KEY, wdt->base + RTIDWDCTRL);
 100        return 0;
 101}
 102
 103static int rti_wdt_ping(struct watchdog_device *wdd)
 104{
 105        struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);
 106
 107        /* put watchdog in service state */
 108        writel_relaxed(WDKEY_SEQ0, wdt->base + RTIWDKEY);
 109        /* put watchdog in active state */
 110        writel_relaxed(WDKEY_SEQ1, wdt->base + RTIWDKEY);
 111
 112        return 0;
 113}
 114
 115static int rti_wdt_setup_hw_hb(struct watchdog_device *wdd, u32 wsize)
 116{
 117        /*
 118         * RTI only supports a windowed mode, where the watchdog can only
 119         * be petted during the open window; not too early or not too late.
 120         * The HW configuration options only allow for the open window size
 121         * to be 50% or less than that.
 122         */
 123        switch (wsize) {
 124        case RTIWWDSIZE_50P:
 125                /* 50% open window => 50% min heartbeat */
 126                wdd->min_hw_heartbeat_ms = 500 * heartbeat;
 127                break;
 128
 129        case RTIWWDSIZE_25P:
 130                /* 25% open window => 75% min heartbeat */
 131                wdd->min_hw_heartbeat_ms = 750 * heartbeat;
 132                break;
 133
 134        case RTIWWDSIZE_12P5:
 135                /* 12.5% open window => 87.5% min heartbeat */
 136                wdd->min_hw_heartbeat_ms = 875 * heartbeat;
 137                break;
 138
 139        case RTIWWDSIZE_6P25:
 140                /* 6.5% open window => 93.5% min heartbeat */
 141                wdd->min_hw_heartbeat_ms = 935 * heartbeat;
 142                break;
 143
 144        case RTIWWDSIZE_3P125:
 145                /* 3.125% open window => 96.9% min heartbeat */
 146                wdd->min_hw_heartbeat_ms = 969 * heartbeat;
 147                break;
 148
 149        default:
 150                return -EINVAL;
 151        }
 152
 153        return 0;
 154}
 155
 156static unsigned int rti_wdt_get_timeleft_ms(struct watchdog_device *wdd)
 157{
 158        u64 timer_counter;
 159        u32 val;
 160        struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);
 161
 162        /* if timeout has occurred then return 0 */
 163        val = readl_relaxed(wdt->base + RTIWDSTATUS);
 164        if (val & DWDST)
 165                return 0;
 166
 167        timer_counter = readl_relaxed(wdt->base + RTIDWDCNTR);
 168
 169        timer_counter *= 1000;
 170
 171        do_div(timer_counter, wdt->freq);
 172
 173        return timer_counter;
 174}
 175
 176static unsigned int rti_wdt_get_timeleft(struct watchdog_device *wdd)
 177{
 178        return rti_wdt_get_timeleft_ms(wdd) / 1000;
 179}
 180
 181static const struct watchdog_info rti_wdt_info = {
 182        .options = WDIOF_KEEPALIVEPING,
 183        .identity = "K3 RTI Watchdog",
 184};
 185
 186static const struct watchdog_ops rti_wdt_ops = {
 187        .owner          = THIS_MODULE,
 188        .start          = rti_wdt_start,
 189        .ping           = rti_wdt_ping,
 190        .get_timeleft   = rti_wdt_get_timeleft,
 191};
 192
 193static int rti_wdt_probe(struct platform_device *pdev)
 194{
 195        int ret = 0;
 196        struct device *dev = &pdev->dev;
 197        struct watchdog_device *wdd;
 198        struct rti_wdt_device *wdt;
 199        struct clk *clk;
 200        u32 last_ping = 0;
 201
 202        wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
 203        if (!wdt)
 204                return -ENOMEM;
 205
 206        clk = clk_get(dev, NULL);
 207        if (IS_ERR(clk))
 208                return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n");
 209
 210        wdt->freq = clk_get_rate(clk);
 211
 212        clk_put(clk);
 213
 214        if (!wdt->freq) {
 215                dev_err(dev, "Failed to get fck rate.\n");
 216                return -EINVAL;
 217        }
 218
 219        /*
 220         * If watchdog is running at 32k clock, it is not accurate.
 221         * Adjust frequency down in this case so that we don't pet
 222         * the watchdog too often.
 223         */
 224        if (wdt->freq < 32768)
 225                wdt->freq = wdt->freq * 9 / 10;
 226
 227        pm_runtime_enable(dev);
 228        ret = pm_runtime_get_sync(dev);
 229        if (ret) {
 230                pm_runtime_put_noidle(dev);
 231                pm_runtime_disable(&pdev->dev);
 232                return dev_err_probe(dev, ret, "runtime pm failed\n");
 233        }
 234
 235        platform_set_drvdata(pdev, wdt);
 236
 237        wdd = &wdt->wdd;
 238        wdd->info = &rti_wdt_info;
 239        wdd->ops = &rti_wdt_ops;
 240        wdd->min_timeout = 1;
 241        wdd->max_hw_heartbeat_ms = (WDT_PRELOAD_MAX << WDT_PRELOAD_SHIFT) /
 242                wdt->freq * 1000;
 243        wdd->parent = dev;
 244
 245        watchdog_set_drvdata(wdd, wdt);
 246        watchdog_set_nowayout(wdd, 1);
 247        watchdog_set_restart_priority(wdd, 128);
 248
 249        wdt->base = devm_platform_ioremap_resource(pdev, 0);
 250        if (IS_ERR(wdt->base)) {
 251                ret = PTR_ERR(wdt->base);
 252                goto err_iomap;
 253        }
 254
 255        if (readl(wdt->base + RTIDWDCTRL) == WDENABLE_KEY) {
 256                u32 time_left_ms;
 257                u64 heartbeat_ms;
 258                u32 wsize;
 259
 260                set_bit(WDOG_HW_RUNNING, &wdd->status);
 261                time_left_ms = rti_wdt_get_timeleft_ms(wdd);
 262                heartbeat_ms = readl(wdt->base + RTIDWDPRLD);
 263                heartbeat_ms <<= WDT_PRELOAD_SHIFT;
 264                heartbeat_ms *= 1000;
 265                do_div(heartbeat_ms, wdt->freq);
 266                if (heartbeat_ms != heartbeat * 1000)
 267                        dev_warn(dev, "watchdog already running, ignoring heartbeat config!\n");
 268
 269                heartbeat = heartbeat_ms;
 270                heartbeat /= 1000;
 271
 272                wsize = readl(wdt->base + RTIWWDSIZECTRL);
 273                ret = rti_wdt_setup_hw_hb(wdd, wsize);
 274                if (ret) {
 275                        dev_err(dev, "bad window size.\n");
 276                        goto err_iomap;
 277                }
 278
 279                last_ping = heartbeat_ms - time_left_ms;
 280                if (time_left_ms > heartbeat_ms) {
 281                        dev_warn(dev, "time_left > heartbeat? Assuming last ping just before now.\n");
 282                        last_ping = 0;
 283                }
 284        }
 285
 286        watchdog_init_timeout(wdd, heartbeat, dev);
 287
 288        ret = watchdog_register_device(wdd);
 289        if (ret) {
 290                dev_err(dev, "cannot register watchdog device\n");
 291                goto err_iomap;
 292        }
 293
 294        if (last_ping)
 295                watchdog_set_last_hw_keepalive(wdd, last_ping);
 296
 297        return 0;
 298
 299err_iomap:
 300        pm_runtime_put_sync(&pdev->dev);
 301        pm_runtime_disable(&pdev->dev);
 302
 303        return ret;
 304}
 305
 306static int rti_wdt_remove(struct platform_device *pdev)
 307{
 308        struct rti_wdt_device *wdt = platform_get_drvdata(pdev);
 309
 310        watchdog_unregister_device(&wdt->wdd);
 311        pm_runtime_put(&pdev->dev);
 312        pm_runtime_disable(&pdev->dev);
 313
 314        return 0;
 315}
 316
 317static const struct of_device_id rti_wdt_of_match[] = {
 318        { .compatible = "ti,j7-rti-wdt", },
 319        {},
 320};
 321MODULE_DEVICE_TABLE(of, rti_wdt_of_match);
 322
 323static struct platform_driver rti_wdt_driver = {
 324        .driver = {
 325                .name = "rti-wdt",
 326                .of_match_table = rti_wdt_of_match,
 327        },
 328        .probe = rti_wdt_probe,
 329        .remove = rti_wdt_remove,
 330};
 331
 332module_platform_driver(rti_wdt_driver);
 333
 334MODULE_AUTHOR("Tero Kristo <t-kristo@ti.com>");
 335MODULE_DESCRIPTION("K3 RTI Watchdog Driver");
 336
 337module_param(heartbeat, int, 0);
 338MODULE_PARM_DESC(heartbeat,
 339                 "Watchdog heartbeat period in seconds from 1 to "
 340                 __MODULE_STRING(MAX_HEARTBEAT) ", default "
 341                 __MODULE_STRING(DEFAULT_HEARTBEAT));
 342
 343MODULE_LICENSE("GPL");
 344MODULE_ALIAS("platform:rti-wdt");
 345