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 resource *wdt_mem;
 198        struct watchdog_device *wdd;
 199        struct rti_wdt_device *wdt;
 200        struct clk *clk;
 201        u32 last_ping = 0;
 202
 203        wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
 204        if (!wdt)
 205                return -ENOMEM;
 206
 207        clk = clk_get(dev, NULL);
 208        if (IS_ERR(clk))
 209                return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n");
 210
 211        wdt->freq = clk_get_rate(clk);
 212
 213        clk_put(clk);
 214
 215        if (!wdt->freq) {
 216                dev_err(dev, "Failed to get fck rate.\n");
 217                return -EINVAL;
 218        }
 219
 220        /*
 221         * If watchdog is running at 32k clock, it is not accurate.
 222         * Adjust frequency down in this case so that we don't pet
 223         * the watchdog too often.
 224         */
 225        if (wdt->freq < 32768)
 226                wdt->freq = wdt->freq * 9 / 10;
 227
 228        pm_runtime_enable(dev);
 229        ret = pm_runtime_get_sync(dev);
 230        if (ret) {
 231                pm_runtime_put_noidle(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_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 250        wdt->base = devm_ioremap_resource(dev, wdt_mem);
 251        if (IS_ERR(wdt->base)) {
 252                ret = PTR_ERR(wdt->base);
 253                goto err_iomap;
 254        }
 255
 256        if (readl(wdt->base + RTIDWDCTRL) == WDENABLE_KEY) {
 257                u32 time_left_ms;
 258                u64 heartbeat_ms;
 259                u32 wsize;
 260
 261                set_bit(WDOG_HW_RUNNING, &wdd->status);
 262                time_left_ms = rti_wdt_get_timeleft_ms(wdd);
 263                heartbeat_ms = readl(wdt->base + RTIDWDPRLD);
 264                heartbeat_ms <<= WDT_PRELOAD_SHIFT;
 265                heartbeat_ms *= 1000;
 266                do_div(heartbeat_ms, wdt->freq);
 267                if (heartbeat_ms != heartbeat * 1000)
 268                        dev_warn(dev, "watchdog already running, ignoring heartbeat config!\n");
 269
 270                heartbeat = heartbeat_ms;
 271                heartbeat /= 1000;
 272
 273                wsize = readl(wdt->base + RTIWWDSIZECTRL);
 274                ret = rti_wdt_setup_hw_hb(wdd, wsize);
 275                if (ret) {
 276                        dev_err(dev, "bad window size.\n");
 277                        goto err_iomap;
 278                }
 279
 280                last_ping = heartbeat_ms - time_left_ms;
 281                if (time_left_ms > heartbeat_ms) {
 282                        dev_warn(dev, "time_left > heartbeat? Assuming last ping just before now.\n");
 283                        last_ping = 0;
 284                }
 285        }
 286
 287        watchdog_init_timeout(wdd, heartbeat, dev);
 288
 289        ret = watchdog_register_device(wdd);
 290        if (ret) {
 291                dev_err(dev, "cannot register watchdog device\n");
 292                goto err_iomap;
 293        }
 294
 295        if (last_ping)
 296                watchdog_set_last_hw_keepalive(wdd, last_ping);
 297
 298        return 0;
 299
 300err_iomap:
 301        pm_runtime_put_sync(&pdev->dev);
 302        pm_runtime_disable(&pdev->dev);
 303
 304        return ret;
 305}
 306
 307static int rti_wdt_remove(struct platform_device *pdev)
 308{
 309        struct rti_wdt_device *wdt = platform_get_drvdata(pdev);
 310
 311        watchdog_unregister_device(&wdt->wdd);
 312        pm_runtime_put(&pdev->dev);
 313        pm_runtime_disable(&pdev->dev);
 314
 315        return 0;
 316}
 317
 318static const struct of_device_id rti_wdt_of_match[] = {
 319        { .compatible = "ti,j7-rti-wdt", },
 320        {},
 321};
 322MODULE_DEVICE_TABLE(of, rti_wdt_of_match);
 323
 324static struct platform_driver rti_wdt_driver = {
 325        .driver = {
 326                .name = "rti-wdt",
 327                .of_match_table = rti_wdt_of_match,
 328        },
 329        .probe = rti_wdt_probe,
 330        .remove = rti_wdt_remove,
 331};
 332
 333module_platform_driver(rti_wdt_driver);
 334
 335MODULE_AUTHOR("Tero Kristo <t-kristo@ti.com>");
 336MODULE_DESCRIPTION("K3 RTI Watchdog Driver");
 337
 338module_param(heartbeat, int, 0);
 339MODULE_PARM_DESC(heartbeat,
 340                 "Watchdog heartbeat period in seconds from 1 to "
 341                 __MODULE_STRING(MAX_HEARTBEAT) ", default "
 342                 __MODULE_STRING(DEFAULT_HEARTBEAT));
 343
 344MODULE_LICENSE("GPL");
 345MODULE_ALIAS("platform:rti-wdt");
 346