1/* 2 * Watchdog driver for Cortina Systems Gemini SoC 3 * 4 * Copyright (C) 2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 */ 10 11#include <linux/kernel.h> 12#include <linux/init.h> 13#include <linux/module.h> 14#include <linux/io.h> 15#include <linux/fs.h> 16#include <linux/uaccess.h> 17#include <linux/miscdevice.h> 18#include <linux/platform_device.h> 19#include <linux/watchdog.h> 20#include <linux/slab.h> 21 22#define GEMINI_WDCOUNTER 0x0 23#define GEMINI_WDLOAD 0x4 24#define GEMINI_WDRESTART 0x8 25 26#define WDRESTART_MAGIC 0x5AB9 27 28#define GEMINI_WDCR 0xC 29 30#define WDCR_CLOCK_5MHZ (1 << 4) 31#define WDCR_SYS_RST (1 << 1) 32#define WDCR_ENABLE (1 << 0) 33 34#define WDT_CLOCK 5000000 /* 5 MHz */ 35#define WDT_DEFAULT_TIMEOUT 13 36#define WDT_MAX_TIMEOUT (0xFFFFFFFF / WDT_CLOCK) 37 38/* status bits */ 39#define WDT_ACTIVE 0 40#define WDT_OK_TO_CLOSE 1 41 42static unsigned int timeout = WDT_DEFAULT_TIMEOUT; 43static int nowayout = WATCHDOG_NOWAYOUT; 44 45static DEFINE_SPINLOCK(gemini_wdt_lock); 46 47static struct platform_device *gemini_wdt_dev; 48 49struct gemini_wdt_struct { 50 struct resource *res; 51 struct device *dev; 52 void __iomem *base; 53 unsigned long status; 54}; 55 56static struct watchdog_info gemini_wdt_info = { 57 .identity = "Gemini watchdog", 58 .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | 59 WDIOF_SETTIMEOUT, 60}; 61 62/* Disable the watchdog. */ 63static void gemini_wdt_stop(struct gemini_wdt_struct *gemini_wdt) 64{ 65 spin_lock(&gemini_wdt_lock); 66 67 __raw_writel(0, gemini_wdt->base + GEMINI_WDCR); 68 69 clear_bit(WDT_ACTIVE, &gemini_wdt->status); 70 71 spin_unlock(&gemini_wdt_lock); 72} 73 74/* Service the watchdog */ 75static void gemini_wdt_service(struct gemini_wdt_struct *gemini_wdt) 76{ 77 __raw_writel(WDRESTART_MAGIC, gemini_wdt->base + GEMINI_WDRESTART); 78} 79 80/* Enable and reset the watchdog. */ 81static void gemini_wdt_start(struct gemini_wdt_struct *gemini_wdt) 82{ 83 spin_lock(&gemini_wdt_lock); 84 85 __raw_writel(timeout * WDT_CLOCK, gemini_wdt->base + GEMINI_WDLOAD); 86 87 gemini_wdt_service(gemini_wdt); 88 89 /* set clock before enabling */ 90 __raw_writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST, 91 gemini_wdt->base + GEMINI_WDCR); 92 93 __raw_writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST | WDCR_ENABLE, 94 gemini_wdt->base + GEMINI_WDCR); 95 96 set_bit(WDT_ACTIVE, &gemini_wdt->status); 97 98 spin_unlock(&gemini_wdt_lock); 99} 100 101/* Watchdog device is opened, and watchdog starts running. */ 102static int gemini_wdt_open(struct inode *inode, struct file *file) 103{ 104 struct gemini_wdt_struct *gemini_wdt = platform_get_drvdata(gemini_wdt_dev); 105 106 if (test_bit(WDT_ACTIVE, &gemini_wdt->status)) 107 return -EBUSY; 108 109 file->private_data = gemini_wdt; 110 111 gemini_wdt_start(gemini_wdt); 112 113 return nonseekable_open(inode, file); 114} 115 116/* Close the watchdog device. */ 117static int gemini_wdt_close(struct inode *inode, struct file *file) 118{ 119 struct gemini_wdt_struct *gemini_wdt = file->private_data; 120 121 /* Disable the watchdog if possible */ 122 if (test_bit(WDT_OK_TO_CLOSE, &gemini_wdt->status)) 123 gemini_wdt_stop(gemini_wdt); 124 else 125 dev_warn(gemini_wdt->dev, "Device closed unexpectedly - timer will not stop\n"); 126 127 return 0; 128} 129 130/* Handle commands from user-space. */ 131static long gemini_wdt_ioctl(struct file *file, unsigned int cmd, 132 unsigned long arg) 133{ 134 struct gemini_wdt_struct *gemini_wdt = file->private_data; 135 136 int value; 137 138 switch (cmd) { 139 case WDIOC_KEEPALIVE: 140 gemini_wdt_service(gemini_wdt); 141 return 0; 142 143 case WDIOC_GETSUPPORT: 144 return copy_to_user((struct watchdog_info *)arg, &gemini_wdt_info, 145 sizeof(gemini_wdt_info)) ? -EFAULT : 0; 146 147 case WDIOC_SETTIMEOUT: 148 if (get_user(value, (int *)arg)) 149 return -EFAULT; 150 151 if ((value < 1) || (value > WDT_MAX_TIMEOUT)) 152 return -EINVAL; 153 154 timeout = value; 155 156 /* restart wdt to use new timeout */ 157 gemini_wdt_stop(gemini_wdt); 158 gemini_wdt_start(gemini_wdt); 159 160 /* Fall through */ 161 case WDIOC_GETTIMEOUT: 162 return put_user(timeout, (int *)arg); 163 164 case WDIOC_GETTIMELEFT: 165 value = __raw_readl(gemini_wdt->base + GEMINI_WDCOUNTER); 166 return put_user(value / WDT_CLOCK, (int *)arg); 167 168 default: 169 return -ENOTTY; 170 } 171} 172 173/* Refresh the watchdog whenever device is written to. */ 174static ssize_t gemini_wdt_write(struct file *file, const char *data, 175 size_t len, loff_t *ppos) 176{ 177 struct gemini_wdt_struct *gemini_wdt = file->private_data; 178 179 if (len) { 180 if (!nowayout) { 181 size_t i; 182 183 clear_bit(WDT_OK_TO_CLOSE, &gemini_wdt->status); 184 for (i = 0; i != len; i++) { 185 char c; 186 187 if (get_user(c, data + i)) 188 return -EFAULT; 189 if (c == 'V') 190 set_bit(WDT_OK_TO_CLOSE, 191 &gemini_wdt->status); 192 } 193 } 194 gemini_wdt_service(gemini_wdt); 195 } 196 197 return len; 198} 199 200static const struct file_operations gemini_wdt_fops = { 201 .owner = THIS_MODULE, 202 .llseek = no_llseek, 203 .unlocked_ioctl = gemini_wdt_ioctl, 204 .open = gemini_wdt_open, 205 .release = gemini_wdt_close, 206 .write = gemini_wdt_write, 207}; 208 209static struct miscdevice gemini_wdt_miscdev = { 210 .minor = WATCHDOG_MINOR, 211 .name = "watchdog", 212 .fops = &gemini_wdt_fops, 213}; 214 215static void gemini_wdt_shutdown(struct platform_device *pdev) 216{ 217 struct gemini_wdt_struct *gemini_wdt = platform_get_drvdata(pdev); 218 219 gemini_wdt_stop(gemini_wdt); 220} 221 222static int gemini_wdt_probe(struct platform_device *pdev) 223{ 224 int ret; 225 int res_size; 226 struct resource *res; 227 void __iomem *base; 228 struct gemini_wdt_struct *gemini_wdt; 229 unsigned int reg; 230 231 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 232 if (!res) { 233 dev_err(&pdev->dev, "can't get device resources\n"); 234 return -ENODEV; 235 } 236 237 res_size = resource_size(res); 238 if (!request_mem_region(res->start, res_size, res->name)) { 239 dev_err(&pdev->dev, "can't allocate %d bytes at %d address\n", 240 res_size, res->start); 241 return -ENOMEM; 242 } 243 244 base = ioremap(res->start, res_size); 245 if (!base) { 246 dev_err(&pdev->dev, "ioremap failed\n"); 247 ret = -EIO; 248 goto fail0; 249 } 250 251 gemini_wdt = kzalloc(sizeof(struct gemini_wdt_struct), GFP_KERNEL); 252 if (!gemini_wdt) { 253 dev_err(&pdev->dev, "can't allocate interface\n"); 254 ret = -ENOMEM; 255 goto fail1; 256 } 257 258 /* Setup gemini_wdt driver structure */ 259 gemini_wdt->base = base; 260 gemini_wdt->res = res; 261 262 /* Set up platform driver data */ 263 platform_set_drvdata(pdev, gemini_wdt); 264 gemini_wdt_dev = pdev; 265 266 if (gemini_wdt_miscdev.parent) { 267 ret = -EBUSY; 268 goto fail2; 269 } 270 271 gemini_wdt_miscdev.parent = &pdev->dev; 272 273 reg = __raw_readw(gemini_wdt->base + GEMINI_WDCR); 274 if (reg & WDCR_ENABLE) { 275 /* Watchdog was enabled by the bootloader, disable it. */ 276 reg &= ~(WDCR_ENABLE); 277 __raw_writel(reg, gemini_wdt->base + GEMINI_WDCR); 278 } 279 280 ret = misc_register(&gemini_wdt_miscdev); 281 if (ret) 282 goto fail2; 283 284 return 0; 285 286fail2: 287 platform_set_drvdata(pdev, NULL); 288 kfree(gemini_wdt); 289fail1: 290 iounmap(base); 291fail0: 292 release_mem_region(res->start, res_size); 293 294 return ret; 295} 296 297static int gemini_wdt_remove(struct platform_device *pdev) 298{ 299 struct gemini_wdt_struct *gemini_wdt = platform_get_drvdata(pdev); 300 301 platform_set_drvdata(pdev, NULL); 302 misc_deregister(&gemini_wdt_miscdev); 303 gemini_wdt_dev = NULL; 304 iounmap(gemini_wdt->base); 305 release_mem_region(gemini_wdt->res->start, resource_size(gemini_wdt->res)); 306 307 kfree(gemini_wdt); 308 309 return 0; 310} 311 312#ifdef CONFIG_PM 313static int gemini_wdt_suspend(struct platform_device *pdev, pm_message_t message) 314{ 315 struct gemini_wdt_struct *gemini_wdt = platform_get_drvdata(pdev); 316 unsigned int reg; 317 318 reg = __raw_readw(gemini_wdt->base + GEMINI_WDCR); 319 reg &= ~(WDCR_WDENABLE); 320 __raw_writel(reg, gemini_wdt->base + GEMINI_WDCR); 321 322 return 0; 323} 324 325static int gemini_wdt_resume(struct platform_device *pdev) 326{ 327 struct gemini_wdt_struct *gemini_wdt = platform_get_drvdata(pdev); 328 unsigned int reg; 329 330 if (gemini_wdt->status) { 331 reg = __raw_readw(gemini_wdt->base + GEMINI_WDCR); 332 reg |= WDCR_WDENABLE; 333 __raw_writel(reg, gemini_wdt->base + GEMINI_WDCR); 334 } 335 336 return 0; 337} 338#else 339#define gemini_wdt_suspend NULL 340#define gemini_wdt_resume NULL 341#endif 342 343static struct platform_driver gemini_wdt_driver = { 344 .probe = gemini_wdt_probe, 345 .remove = gemini_wdt_remove, 346 .shutdown = gemini_wdt_shutdown, 347 .suspend = gemini_wdt_suspend, 348 .resume = gemini_wdt_resume, 349 .driver = { 350 .name = "gemini-wdt", 351 .owner = THIS_MODULE, 352 }, 353}; 354 355static int __init gemini_wdt_init(void) 356{ 357 return platform_driver_probe(&gemini_wdt_driver, gemini_wdt_probe); 358} 359 360static void __exit gemini_wdt_exit(void) 361{ 362 platform_driver_unregister(&gemini_wdt_driver); 363} 364 365module_init(gemini_wdt_init); 366module_exit(gemini_wdt_exit); 367 368module_param(timeout, uint, 0); 369MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); 370 371module_param(nowayout, int, 0); 372MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); 373 374MODULE_AUTHOR("Paulius Zaleckas"); 375MODULE_DESCRIPTION("Watchdog driver for Gemini"); 376MODULE_LICENSE("GPL"); 377MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 378MODULE_ALIAS("platform:gemini-wdt"); 379