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