1*9f806850SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2e51c288eSKevin Strasser /* 3e51c288eSKevin Strasser * Kontron PLD watchdog driver 4e51c288eSKevin Strasser * 5e51c288eSKevin Strasser * Copyright (c) 2010-2013 Kontron Europe GmbH 6e51c288eSKevin Strasser * Author: Michael Brunner <michael.brunner@kontron.com> 7e51c288eSKevin Strasser * 8e51c288eSKevin Strasser * Note: From the PLD watchdog point of view timeout and pretimeout are 9e51c288eSKevin Strasser * defined differently than in the kernel. 10e51c288eSKevin Strasser * First the pretimeout stage runs out before the timeout stage gets 11e51c288eSKevin Strasser * active. 12e51c288eSKevin Strasser * 13e51c288eSKevin Strasser * Kernel/API: P-----| pretimeout 14e51c288eSKevin Strasser * |-----------------------T timeout 15e51c288eSKevin Strasser * Watchdog: |-----------------P pretimeout_stage 16e51c288eSKevin Strasser * |-----T timeout_stage 17e51c288eSKevin Strasser */ 18e51c288eSKevin Strasser 19e51c288eSKevin Strasser #include <linux/module.h> 20e51c288eSKevin Strasser #include <linux/moduleparam.h> 21e51c288eSKevin Strasser #include <linux/uaccess.h> 22e51c288eSKevin Strasser #include <linux/watchdog.h> 23e51c288eSKevin Strasser #include <linux/platform_device.h> 24e51c288eSKevin Strasser #include <linux/mfd/kempld.h> 25e51c288eSKevin Strasser 26e51c288eSKevin Strasser #define KEMPLD_WDT_STAGE_TIMEOUT(x) (0x1b + (x) * 4) 27e51c288eSKevin Strasser #define KEMPLD_WDT_STAGE_CFG(x) (0x18 + (x)) 28e51c288eSKevin Strasser #define STAGE_CFG_GET_PRESCALER(x) (((x) & 0x30) >> 4) 294c4e4566SJingoo Han #define STAGE_CFG_SET_PRESCALER(x) (((x) & 0x3) << 4) 30e51c288eSKevin Strasser #define STAGE_CFG_PRESCALER_MASK 0x30 31e51c288eSKevin Strasser #define STAGE_CFG_ACTION_MASK 0x7 32e51c288eSKevin Strasser #define STAGE_CFG_ASSERT (1 << 3) 33e51c288eSKevin Strasser 34e51c288eSKevin Strasser #define KEMPLD_WDT_MAX_STAGES 2 35e51c288eSKevin Strasser #define KEMPLD_WDT_KICK 0x16 36e51c288eSKevin Strasser #define KEMPLD_WDT_CFG 0x17 37e51c288eSKevin Strasser #define KEMPLD_WDT_CFG_ENABLE 0x10 38e51c288eSKevin Strasser #define KEMPLD_WDT_CFG_ENABLE_LOCK 0x8 39e51c288eSKevin Strasser #define KEMPLD_WDT_CFG_GLOBAL_LOCK 0x80 40e51c288eSKevin Strasser 41e51c288eSKevin Strasser enum { 42e51c288eSKevin Strasser ACTION_NONE = 0, 43e51c288eSKevin Strasser ACTION_RESET, 44e51c288eSKevin Strasser ACTION_NMI, 45e51c288eSKevin Strasser ACTION_SMI, 46e51c288eSKevin Strasser ACTION_SCI, 47e51c288eSKevin Strasser ACTION_DELAY, 48e51c288eSKevin Strasser }; 49e51c288eSKevin Strasser 50e51c288eSKevin Strasser enum { 51e51c288eSKevin Strasser STAGE_TIMEOUT = 0, 52e51c288eSKevin Strasser STAGE_PRETIMEOUT, 53e51c288eSKevin Strasser }; 54e51c288eSKevin Strasser 55e51c288eSKevin Strasser enum { 56e51c288eSKevin Strasser PRESCALER_21 = 0, 57e51c288eSKevin Strasser PRESCALER_17, 58e51c288eSKevin Strasser PRESCALER_12, 59e51c288eSKevin Strasser }; 60e51c288eSKevin Strasser 61b3970bdeSJingoo Han static const u32 kempld_prescaler[] = { 62e51c288eSKevin Strasser [PRESCALER_21] = (1 << 21) - 1, 63e51c288eSKevin Strasser [PRESCALER_17] = (1 << 17) - 1, 64e51c288eSKevin Strasser [PRESCALER_12] = (1 << 12) - 1, 65e51c288eSKevin Strasser 0, 66e51c288eSKevin Strasser }; 67e51c288eSKevin Strasser 68e51c288eSKevin Strasser struct kempld_wdt_stage { 69e51c288eSKevin Strasser unsigned int id; 70e51c288eSKevin Strasser u32 mask; 71e51c288eSKevin Strasser }; 72e51c288eSKevin Strasser 73e51c288eSKevin Strasser struct kempld_wdt_data { 74e51c288eSKevin Strasser struct kempld_device_data *pld; 75e51c288eSKevin Strasser struct watchdog_device wdd; 76e51c288eSKevin Strasser unsigned int pretimeout; 77e51c288eSKevin Strasser struct kempld_wdt_stage stage[KEMPLD_WDT_MAX_STAGES]; 78e51c288eSKevin Strasser #ifdef CONFIG_PM 79e51c288eSKevin Strasser u8 pm_status_store; 80e51c288eSKevin Strasser #endif 81e51c288eSKevin Strasser }; 82e51c288eSKevin Strasser 83e51c288eSKevin Strasser #define DEFAULT_TIMEOUT 30 /* seconds */ 84e51c288eSKevin Strasser #define DEFAULT_PRETIMEOUT 0 85e51c288eSKevin Strasser 86e51c288eSKevin Strasser static unsigned int timeout = DEFAULT_TIMEOUT; 87e51c288eSKevin Strasser module_param(timeout, uint, 0); 88e51c288eSKevin Strasser MODULE_PARM_DESC(timeout, 89e51c288eSKevin Strasser "Watchdog timeout in seconds. (>=0, default=" 90e51c288eSKevin Strasser __MODULE_STRING(DEFAULT_TIMEOUT) ")"); 91e51c288eSKevin Strasser 92e51c288eSKevin Strasser static unsigned int pretimeout = DEFAULT_PRETIMEOUT; 93e51c288eSKevin Strasser module_param(pretimeout, uint, 0); 94e51c288eSKevin Strasser MODULE_PARM_DESC(pretimeout, 95e51c288eSKevin Strasser "Watchdog pretimeout in seconds. (>=0, default=" 96e51c288eSKevin Strasser __MODULE_STRING(DEFAULT_PRETIMEOUT) ")"); 97e51c288eSKevin Strasser 98e51c288eSKevin Strasser static bool nowayout = WATCHDOG_NOWAYOUT; 99e51c288eSKevin Strasser module_param(nowayout, bool, 0); 100e51c288eSKevin Strasser MODULE_PARM_DESC(nowayout, 101e51c288eSKevin Strasser "Watchdog cannot be stopped once started (default=" 102e51c288eSKevin Strasser __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 103e51c288eSKevin Strasser 104e51c288eSKevin Strasser static int kempld_wdt_set_stage_action(struct kempld_wdt_data *wdt_data, 105e51c288eSKevin Strasser struct kempld_wdt_stage *stage, 106e51c288eSKevin Strasser u8 action) 107e51c288eSKevin Strasser { 108e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld; 109e51c288eSKevin Strasser u8 stage_cfg; 110e51c288eSKevin Strasser 111e51c288eSKevin Strasser if (!stage || !stage->mask) 112e51c288eSKevin Strasser return -EINVAL; 113e51c288eSKevin Strasser 114e51c288eSKevin Strasser kempld_get_mutex(pld); 115e51c288eSKevin Strasser stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id)); 116e51c288eSKevin Strasser stage_cfg &= ~STAGE_CFG_ACTION_MASK; 117e51c288eSKevin Strasser stage_cfg |= (action & STAGE_CFG_ACTION_MASK); 118e51c288eSKevin Strasser 119e51c288eSKevin Strasser if (action == ACTION_RESET) 120e51c288eSKevin Strasser stage_cfg |= STAGE_CFG_ASSERT; 121e51c288eSKevin Strasser else 122e51c288eSKevin Strasser stage_cfg &= ~STAGE_CFG_ASSERT; 123e51c288eSKevin Strasser 124e51c288eSKevin Strasser kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg); 125e51c288eSKevin Strasser kempld_release_mutex(pld); 126e51c288eSKevin Strasser 127e51c288eSKevin Strasser return 0; 128e51c288eSKevin Strasser } 129e51c288eSKevin Strasser 130e51c288eSKevin Strasser static int kempld_wdt_set_stage_timeout(struct kempld_wdt_data *wdt_data, 131e51c288eSKevin Strasser struct kempld_wdt_stage *stage, 132e51c288eSKevin Strasser unsigned int timeout) 133e51c288eSKevin Strasser { 134e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld; 1353736d4ebSArnd Bergmann u32 prescaler; 136e51c288eSKevin Strasser u64 stage_timeout64; 137e51c288eSKevin Strasser u32 stage_timeout; 138e51c288eSKevin Strasser u32 remainder; 139e51c288eSKevin Strasser u8 stage_cfg; 140e51c288eSKevin Strasser 1413736d4ebSArnd Bergmann prescaler = kempld_prescaler[PRESCALER_21]; 1423736d4ebSArnd Bergmann 143e51c288eSKevin Strasser if (!stage) 144e51c288eSKevin Strasser return -EINVAL; 145e51c288eSKevin Strasser 146e51c288eSKevin Strasser stage_timeout64 = (u64)timeout * pld->pld_clock; 147e51c288eSKevin Strasser remainder = do_div(stage_timeout64, prescaler); 148e51c288eSKevin Strasser if (remainder) 149e51c288eSKevin Strasser stage_timeout64++; 150e51c288eSKevin Strasser 151e51c288eSKevin Strasser if (stage_timeout64 > stage->mask) 152e51c288eSKevin Strasser return -EINVAL; 153e51c288eSKevin Strasser 154e51c288eSKevin Strasser stage_timeout = stage_timeout64 & stage->mask; 155e51c288eSKevin Strasser 156e51c288eSKevin Strasser kempld_get_mutex(pld); 157e51c288eSKevin Strasser stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id)); 158e51c288eSKevin Strasser stage_cfg &= ~STAGE_CFG_PRESCALER_MASK; 159a9e0436bSgundberg stage_cfg |= STAGE_CFG_SET_PRESCALER(PRESCALER_21); 160e51c288eSKevin Strasser kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg); 161e51c288eSKevin Strasser kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id), 162e51c288eSKevin Strasser stage_timeout); 163e51c288eSKevin Strasser kempld_release_mutex(pld); 164e51c288eSKevin Strasser 165e51c288eSKevin Strasser return 0; 166e51c288eSKevin Strasser } 167e51c288eSKevin Strasser 168e51c288eSKevin Strasser /* 169e51c288eSKevin Strasser * kempld_get_mutex must be called prior to calling this function. 170e51c288eSKevin Strasser */ 171e51c288eSKevin Strasser static unsigned int kempld_wdt_get_timeout(struct kempld_wdt_data *wdt_data, 172e51c288eSKevin Strasser struct kempld_wdt_stage *stage) 173e51c288eSKevin Strasser { 174e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld; 175e51c288eSKevin Strasser unsigned int timeout; 176e51c288eSKevin Strasser u64 stage_timeout; 177e51c288eSKevin Strasser u32 prescaler; 178e51c288eSKevin Strasser u32 remainder; 179e51c288eSKevin Strasser u8 stage_cfg; 180e51c288eSKevin Strasser 181e51c288eSKevin Strasser if (!stage->mask) 182e51c288eSKevin Strasser return 0; 183e51c288eSKevin Strasser 184e51c288eSKevin Strasser stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id)); 185e51c288eSKevin Strasser stage_timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id)); 186e51c288eSKevin Strasser prescaler = kempld_prescaler[STAGE_CFG_GET_PRESCALER(stage_cfg)]; 187e51c288eSKevin Strasser 188e51c288eSKevin Strasser stage_timeout = (stage_timeout & stage->mask) * prescaler; 189e51c288eSKevin Strasser remainder = do_div(stage_timeout, pld->pld_clock); 190e51c288eSKevin Strasser if (remainder) 191e51c288eSKevin Strasser stage_timeout++; 192e51c288eSKevin Strasser 193e51c288eSKevin Strasser timeout = stage_timeout; 194e51c288eSKevin Strasser WARN_ON_ONCE(timeout != stage_timeout); 195e51c288eSKevin Strasser 196e51c288eSKevin Strasser return timeout; 197e51c288eSKevin Strasser } 198e51c288eSKevin Strasser 199e51c288eSKevin Strasser static int kempld_wdt_set_timeout(struct watchdog_device *wdd, 200e51c288eSKevin Strasser unsigned int timeout) 201e51c288eSKevin Strasser { 202e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); 203e51c288eSKevin Strasser struct kempld_wdt_stage *pretimeout_stage; 204e51c288eSKevin Strasser struct kempld_wdt_stage *timeout_stage; 205e51c288eSKevin Strasser int ret; 206e51c288eSKevin Strasser 207e51c288eSKevin Strasser timeout_stage = &wdt_data->stage[STAGE_TIMEOUT]; 208e51c288eSKevin Strasser pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT]; 209e51c288eSKevin Strasser 210e51c288eSKevin Strasser if (pretimeout_stage->mask && wdt_data->pretimeout > 0) 211e51c288eSKevin Strasser timeout = wdt_data->pretimeout; 212e51c288eSKevin Strasser 213e51c288eSKevin Strasser ret = kempld_wdt_set_stage_action(wdt_data, timeout_stage, 214e51c288eSKevin Strasser ACTION_RESET); 215e51c288eSKevin Strasser if (ret) 216e51c288eSKevin Strasser return ret; 217e51c288eSKevin Strasser ret = kempld_wdt_set_stage_timeout(wdt_data, timeout_stage, 218e51c288eSKevin Strasser timeout); 219e51c288eSKevin Strasser if (ret) 220e51c288eSKevin Strasser return ret; 221e51c288eSKevin Strasser 222e51c288eSKevin Strasser wdd->timeout = timeout; 223e51c288eSKevin Strasser return 0; 224e51c288eSKevin Strasser } 225e51c288eSKevin Strasser 226e51c288eSKevin Strasser static int kempld_wdt_set_pretimeout(struct watchdog_device *wdd, 227e51c288eSKevin Strasser unsigned int pretimeout) 228e51c288eSKevin Strasser { 229e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); 230e51c288eSKevin Strasser struct kempld_wdt_stage *pretimeout_stage; 231e51c288eSKevin Strasser u8 action = ACTION_NONE; 232e51c288eSKevin Strasser int ret; 233e51c288eSKevin Strasser 234e51c288eSKevin Strasser pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT]; 235e51c288eSKevin Strasser 236e51c288eSKevin Strasser if (!pretimeout_stage->mask) 237e51c288eSKevin Strasser return -ENXIO; 238e51c288eSKevin Strasser 239e51c288eSKevin Strasser if (pretimeout > wdd->timeout) 240e51c288eSKevin Strasser return -EINVAL; 241e51c288eSKevin Strasser 242e51c288eSKevin Strasser if (pretimeout > 0) 243e51c288eSKevin Strasser action = ACTION_NMI; 244e51c288eSKevin Strasser 245e51c288eSKevin Strasser ret = kempld_wdt_set_stage_action(wdt_data, pretimeout_stage, 246e51c288eSKevin Strasser action); 247e51c288eSKevin Strasser if (ret) 248e51c288eSKevin Strasser return ret; 249e51c288eSKevin Strasser ret = kempld_wdt_set_stage_timeout(wdt_data, pretimeout_stage, 250e51c288eSKevin Strasser wdd->timeout - pretimeout); 251e51c288eSKevin Strasser if (ret) 252e51c288eSKevin Strasser return ret; 253e51c288eSKevin Strasser 254e51c288eSKevin Strasser wdt_data->pretimeout = pretimeout; 255e51c288eSKevin Strasser return 0; 256e51c288eSKevin Strasser } 257e51c288eSKevin Strasser 258e51c288eSKevin Strasser static void kempld_wdt_update_timeouts(struct kempld_wdt_data *wdt_data) 259e51c288eSKevin Strasser { 260e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld; 261e51c288eSKevin Strasser struct kempld_wdt_stage *pretimeout_stage; 262e51c288eSKevin Strasser struct kempld_wdt_stage *timeout_stage; 263e51c288eSKevin Strasser unsigned int pretimeout, timeout; 264e51c288eSKevin Strasser 265e51c288eSKevin Strasser pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT]; 266e51c288eSKevin Strasser timeout_stage = &wdt_data->stage[STAGE_TIMEOUT]; 267e51c288eSKevin Strasser 268e51c288eSKevin Strasser kempld_get_mutex(pld); 269e51c288eSKevin Strasser pretimeout = kempld_wdt_get_timeout(wdt_data, pretimeout_stage); 270e51c288eSKevin Strasser timeout = kempld_wdt_get_timeout(wdt_data, timeout_stage); 271e51c288eSKevin Strasser kempld_release_mutex(pld); 272e51c288eSKevin Strasser 273e51c288eSKevin Strasser if (pretimeout) 274e51c288eSKevin Strasser wdt_data->pretimeout = timeout; 275e51c288eSKevin Strasser else 276e51c288eSKevin Strasser wdt_data->pretimeout = 0; 277e51c288eSKevin Strasser 278e51c288eSKevin Strasser wdt_data->wdd.timeout = pretimeout + timeout; 279e51c288eSKevin Strasser } 280e51c288eSKevin Strasser 281e51c288eSKevin Strasser static int kempld_wdt_start(struct watchdog_device *wdd) 282e51c288eSKevin Strasser { 283e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); 284e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld; 285e51c288eSKevin Strasser u8 status; 286e51c288eSKevin Strasser int ret; 287e51c288eSKevin Strasser 288e51c288eSKevin Strasser ret = kempld_wdt_set_timeout(wdd, wdd->timeout); 289e51c288eSKevin Strasser if (ret) 290e51c288eSKevin Strasser return ret; 291e51c288eSKevin Strasser 292e51c288eSKevin Strasser kempld_get_mutex(pld); 293e51c288eSKevin Strasser status = kempld_read8(pld, KEMPLD_WDT_CFG); 294e51c288eSKevin Strasser status |= KEMPLD_WDT_CFG_ENABLE; 295e51c288eSKevin Strasser kempld_write8(pld, KEMPLD_WDT_CFG, status); 296e51c288eSKevin Strasser status = kempld_read8(pld, KEMPLD_WDT_CFG); 297e51c288eSKevin Strasser kempld_release_mutex(pld); 298e51c288eSKevin Strasser 299e51c288eSKevin Strasser /* Check if the watchdog was enabled */ 300e51c288eSKevin Strasser if (!(status & KEMPLD_WDT_CFG_ENABLE)) 301e51c288eSKevin Strasser return -EACCES; 302e51c288eSKevin Strasser 303e51c288eSKevin Strasser return 0; 304e51c288eSKevin Strasser } 305e51c288eSKevin Strasser 306e51c288eSKevin Strasser static int kempld_wdt_stop(struct watchdog_device *wdd) 307e51c288eSKevin Strasser { 308e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); 309e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld; 310e51c288eSKevin Strasser u8 status; 311e51c288eSKevin Strasser 312e51c288eSKevin Strasser kempld_get_mutex(pld); 313e51c288eSKevin Strasser status = kempld_read8(pld, KEMPLD_WDT_CFG); 314e51c288eSKevin Strasser status &= ~KEMPLD_WDT_CFG_ENABLE; 315e51c288eSKevin Strasser kempld_write8(pld, KEMPLD_WDT_CFG, status); 316e51c288eSKevin Strasser status = kempld_read8(pld, KEMPLD_WDT_CFG); 317e51c288eSKevin Strasser kempld_release_mutex(pld); 318e51c288eSKevin Strasser 319e51c288eSKevin Strasser /* Check if the watchdog was disabled */ 320e51c288eSKevin Strasser if (status & KEMPLD_WDT_CFG_ENABLE) 321e51c288eSKevin Strasser return -EACCES; 322e51c288eSKevin Strasser 323e51c288eSKevin Strasser return 0; 324e51c288eSKevin Strasser } 325e51c288eSKevin Strasser 326e51c288eSKevin Strasser static int kempld_wdt_keepalive(struct watchdog_device *wdd) 327e51c288eSKevin Strasser { 328e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); 329e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld; 330e51c288eSKevin Strasser 331e51c288eSKevin Strasser kempld_get_mutex(pld); 332e51c288eSKevin Strasser kempld_write8(pld, KEMPLD_WDT_KICK, 'K'); 333e51c288eSKevin Strasser kempld_release_mutex(pld); 334e51c288eSKevin Strasser 335e51c288eSKevin Strasser return 0; 336e51c288eSKevin Strasser } 337e51c288eSKevin Strasser 338e51c288eSKevin Strasser static long kempld_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd, 339e51c288eSKevin Strasser unsigned long arg) 340e51c288eSKevin Strasser { 341e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); 342e51c288eSKevin Strasser void __user *argp = (void __user *)arg; 343e51c288eSKevin Strasser int ret = -ENOIOCTLCMD; 344e51c288eSKevin Strasser int __user *p = argp; 345e51c288eSKevin Strasser int new_value; 346e51c288eSKevin Strasser 347e51c288eSKevin Strasser switch (cmd) { 348e51c288eSKevin Strasser case WDIOC_SETPRETIMEOUT: 349e51c288eSKevin Strasser if (get_user(new_value, p)) 350e51c288eSKevin Strasser return -EFAULT; 351e51c288eSKevin Strasser ret = kempld_wdt_set_pretimeout(wdd, new_value); 352e51c288eSKevin Strasser if (ret) 353e51c288eSKevin Strasser return ret; 354e51c288eSKevin Strasser ret = kempld_wdt_keepalive(wdd); 355e51c288eSKevin Strasser break; 356e51c288eSKevin Strasser case WDIOC_GETPRETIMEOUT: 357b3970bdeSJingoo Han ret = put_user(wdt_data->pretimeout, (int __user *)arg); 358e51c288eSKevin Strasser break; 359e51c288eSKevin Strasser } 360e51c288eSKevin Strasser 361e51c288eSKevin Strasser return ret; 362e51c288eSKevin Strasser } 363e51c288eSKevin Strasser 364e51c288eSKevin Strasser static int kempld_wdt_probe_stages(struct watchdog_device *wdd) 365e51c288eSKevin Strasser { 366e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); 367e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld; 368e51c288eSKevin Strasser struct kempld_wdt_stage *pretimeout_stage; 369e51c288eSKevin Strasser struct kempld_wdt_stage *timeout_stage; 370e51c288eSKevin Strasser u8 index, data, data_orig; 371e51c288eSKevin Strasser u32 mask; 372e51c288eSKevin Strasser int i, j; 373e51c288eSKevin Strasser 374e51c288eSKevin Strasser pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT]; 375e51c288eSKevin Strasser timeout_stage = &wdt_data->stage[STAGE_TIMEOUT]; 376e51c288eSKevin Strasser 377e51c288eSKevin Strasser pretimeout_stage->mask = 0; 378e51c288eSKevin Strasser timeout_stage->mask = 0; 379e51c288eSKevin Strasser 380e51c288eSKevin Strasser for (i = 0; i < 3; i++) { 381e51c288eSKevin Strasser index = KEMPLD_WDT_STAGE_TIMEOUT(i); 382e51c288eSKevin Strasser mask = 0; 383e51c288eSKevin Strasser 384e51c288eSKevin Strasser kempld_get_mutex(pld); 385e51c288eSKevin Strasser /* Probe each byte individually. */ 386e51c288eSKevin Strasser for (j = 0; j < 4; j++) { 387e51c288eSKevin Strasser data_orig = kempld_read8(pld, index + j); 388e51c288eSKevin Strasser kempld_write8(pld, index + j, 0x00); 389e51c288eSKevin Strasser data = kempld_read8(pld, index + j); 390e51c288eSKevin Strasser /* A failed write means this byte is reserved */ 391e51c288eSKevin Strasser if (data != 0x00) 392e51c288eSKevin Strasser break; 393e51c288eSKevin Strasser kempld_write8(pld, index + j, data_orig); 394e51c288eSKevin Strasser mask |= 0xff << (j * 8); 395e51c288eSKevin Strasser } 396e51c288eSKevin Strasser kempld_release_mutex(pld); 397e51c288eSKevin Strasser 398e51c288eSKevin Strasser /* Assign available stages to timeout and pretimeout */ 399e51c288eSKevin Strasser if (!timeout_stage->mask) { 400e51c288eSKevin Strasser timeout_stage->mask = mask; 401e51c288eSKevin Strasser timeout_stage->id = i; 402e51c288eSKevin Strasser } else { 403e51c288eSKevin Strasser if (pld->feature_mask & KEMPLD_FEATURE_BIT_NMI) { 404e51c288eSKevin Strasser pretimeout_stage->mask = timeout_stage->mask; 405e51c288eSKevin Strasser timeout_stage->mask = mask; 406e51c288eSKevin Strasser pretimeout_stage->id = timeout_stage->id; 407e51c288eSKevin Strasser timeout_stage->id = i; 408e51c288eSKevin Strasser } 409e51c288eSKevin Strasser break; 410e51c288eSKevin Strasser } 411e51c288eSKevin Strasser } 412e51c288eSKevin Strasser 413e51c288eSKevin Strasser if (!timeout_stage->mask) 414e51c288eSKevin Strasser return -ENODEV; 415e51c288eSKevin Strasser 416e51c288eSKevin Strasser return 0; 417e51c288eSKevin Strasser } 418e51c288eSKevin Strasser 4196c368932SBhumika Goyal static const struct watchdog_info kempld_wdt_info = { 420e51c288eSKevin Strasser .identity = "KEMPLD Watchdog", 421e51c288eSKevin Strasser .options = WDIOF_SETTIMEOUT | 422e51c288eSKevin Strasser WDIOF_KEEPALIVEPING | 423e51c288eSKevin Strasser WDIOF_MAGICCLOSE | 424e51c288eSKevin Strasser WDIOF_PRETIMEOUT 425e51c288eSKevin Strasser }; 426e51c288eSKevin Strasser 42785f15cfcSJulia Lawall static const struct watchdog_ops kempld_wdt_ops = { 428e51c288eSKevin Strasser .owner = THIS_MODULE, 429e51c288eSKevin Strasser .start = kempld_wdt_start, 430e51c288eSKevin Strasser .stop = kempld_wdt_stop, 431e51c288eSKevin Strasser .ping = kempld_wdt_keepalive, 432e51c288eSKevin Strasser .set_timeout = kempld_wdt_set_timeout, 433e51c288eSKevin Strasser .ioctl = kempld_wdt_ioctl, 434e51c288eSKevin Strasser }; 435e51c288eSKevin Strasser 436e51c288eSKevin Strasser static int kempld_wdt_probe(struct platform_device *pdev) 437e51c288eSKevin Strasser { 438e51c288eSKevin Strasser struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent); 439e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data; 440e51c288eSKevin Strasser struct device *dev = &pdev->dev; 441e51c288eSKevin Strasser struct watchdog_device *wdd; 442e51c288eSKevin Strasser u8 status; 443e51c288eSKevin Strasser int ret = 0; 444e51c288eSKevin Strasser 445e51c288eSKevin Strasser wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL); 446e51c288eSKevin Strasser if (!wdt_data) 447e51c288eSKevin Strasser return -ENOMEM; 448e51c288eSKevin Strasser 449e51c288eSKevin Strasser wdt_data->pld = pld; 450e51c288eSKevin Strasser wdd = &wdt_data->wdd; 451e51c288eSKevin Strasser wdd->parent = dev; 452e51c288eSKevin Strasser 453e51c288eSKevin Strasser kempld_get_mutex(pld); 454e51c288eSKevin Strasser status = kempld_read8(pld, KEMPLD_WDT_CFG); 455e51c288eSKevin Strasser kempld_release_mutex(pld); 456e51c288eSKevin Strasser 457e51c288eSKevin Strasser /* Enable nowayout if watchdog is already locked */ 458e51c288eSKevin Strasser if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK | 459e51c288eSKevin Strasser KEMPLD_WDT_CFG_GLOBAL_LOCK)) { 460e51c288eSKevin Strasser if (!nowayout) 461e51c288eSKevin Strasser dev_warn(dev, 462e51c288eSKevin Strasser "Forcing nowayout - watchdog lock enabled!\n"); 463e51c288eSKevin Strasser nowayout = true; 464e51c288eSKevin Strasser } 465e51c288eSKevin Strasser 466e51c288eSKevin Strasser wdd->info = &kempld_wdt_info; 467e51c288eSKevin Strasser wdd->ops = &kempld_wdt_ops; 468e51c288eSKevin Strasser 469e51c288eSKevin Strasser watchdog_set_drvdata(wdd, wdt_data); 470e51c288eSKevin Strasser watchdog_set_nowayout(wdd, nowayout); 471e51c288eSKevin Strasser 472e51c288eSKevin Strasser ret = kempld_wdt_probe_stages(wdd); 473e51c288eSKevin Strasser if (ret) 474e51c288eSKevin Strasser return ret; 475e51c288eSKevin Strasser 476e51c288eSKevin Strasser kempld_wdt_set_timeout(wdd, timeout); 477e51c288eSKevin Strasser kempld_wdt_set_pretimeout(wdd, pretimeout); 478e51c288eSKevin Strasser 479e51c288eSKevin Strasser /* Check if watchdog is already enabled */ 480e51c288eSKevin Strasser if (status & KEMPLD_WDT_CFG_ENABLE) { 481e51c288eSKevin Strasser /* Get current watchdog settings */ 482e51c288eSKevin Strasser kempld_wdt_update_timeouts(wdt_data); 483e51c288eSKevin Strasser dev_info(dev, "Watchdog was already enabled\n"); 484e51c288eSKevin Strasser } 485e51c288eSKevin Strasser 486e51c288eSKevin Strasser platform_set_drvdata(pdev, wdt_data); 4874689ba97SGuenter Roeck watchdog_stop_on_reboot(wdd); 4884689ba97SGuenter Roeck watchdog_stop_on_unregister(wdd); 4894689ba97SGuenter Roeck ret = devm_watchdog_register_device(dev, wdd); 490e51c288eSKevin Strasser if (ret) 491e51c288eSKevin Strasser return ret; 492e51c288eSKevin Strasser 493e51c288eSKevin Strasser dev_info(dev, "Watchdog registered with %ds timeout\n", wdd->timeout); 494e51c288eSKevin Strasser 495e51c288eSKevin Strasser return 0; 496e51c288eSKevin Strasser } 497e51c288eSKevin Strasser 498e51c288eSKevin Strasser #ifdef CONFIG_PM 499e51c288eSKevin Strasser /* Disable watchdog if it is active during suspend */ 500e51c288eSKevin Strasser static int kempld_wdt_suspend(struct platform_device *pdev, 501e51c288eSKevin Strasser pm_message_t message) 502e51c288eSKevin Strasser { 503e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev); 504e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld; 505e51c288eSKevin Strasser struct watchdog_device *wdd = &wdt_data->wdd; 506e51c288eSKevin Strasser 507e51c288eSKevin Strasser kempld_get_mutex(pld); 508e51c288eSKevin Strasser wdt_data->pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG); 509e51c288eSKevin Strasser kempld_release_mutex(pld); 510e51c288eSKevin Strasser 511e51c288eSKevin Strasser kempld_wdt_update_timeouts(wdt_data); 512e51c288eSKevin Strasser 513e51c288eSKevin Strasser if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE) 514e51c288eSKevin Strasser return kempld_wdt_stop(wdd); 515e51c288eSKevin Strasser 516e51c288eSKevin Strasser return 0; 517e51c288eSKevin Strasser } 518e51c288eSKevin Strasser 519e51c288eSKevin Strasser /* Enable watchdog and configure it if necessary */ 520e51c288eSKevin Strasser static int kempld_wdt_resume(struct platform_device *pdev) 521e51c288eSKevin Strasser { 522e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev); 523e51c288eSKevin Strasser struct watchdog_device *wdd = &wdt_data->wdd; 524e51c288eSKevin Strasser 525e51c288eSKevin Strasser /* 526e51c288eSKevin Strasser * If watchdog was stopped before suspend be sure it gets disabled 527e51c288eSKevin Strasser * again, for the case BIOS has enabled it during resume 528e51c288eSKevin Strasser */ 529e51c288eSKevin Strasser if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE) 530e51c288eSKevin Strasser return kempld_wdt_start(wdd); 531e51c288eSKevin Strasser else 532e51c288eSKevin Strasser return kempld_wdt_stop(wdd); 533e51c288eSKevin Strasser } 534e51c288eSKevin Strasser #else 535e51c288eSKevin Strasser #define kempld_wdt_suspend NULL 536e51c288eSKevin Strasser #define kempld_wdt_resume NULL 537e51c288eSKevin Strasser #endif 538e51c288eSKevin Strasser 539e51c288eSKevin Strasser static struct platform_driver kempld_wdt_driver = { 540e51c288eSKevin Strasser .driver = { 541e51c288eSKevin Strasser .name = "kempld-wdt", 542e51c288eSKevin Strasser }, 543e51c288eSKevin Strasser .probe = kempld_wdt_probe, 544e51c288eSKevin Strasser .suspend = kempld_wdt_suspend, 545e51c288eSKevin Strasser .resume = kempld_wdt_resume, 546e51c288eSKevin Strasser }; 547e51c288eSKevin Strasser 548e51c288eSKevin Strasser module_platform_driver(kempld_wdt_driver); 549e51c288eSKevin Strasser 550e51c288eSKevin Strasser MODULE_DESCRIPTION("KEM PLD Watchdog Driver"); 551e51c288eSKevin Strasser MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>"); 552e51c288eSKevin Strasser MODULE_LICENSE("GPL"); 553