1*f865c352SPaul Cercueil /* 2*f865c352SPaul Cercueil * Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net> 3*f865c352SPaul Cercueil * JZ4740 Watchdog driver 4*f865c352SPaul Cercueil * 5*f865c352SPaul Cercueil * This program is free software; you can redistribute it and/or modify it 6*f865c352SPaul Cercueil * under the terms of the GNU General Public License as published by the 7*f865c352SPaul Cercueil * Free Software Foundation; either version 2 of the License, or (at your 8*f865c352SPaul Cercueil * option) any later version. 9*f865c352SPaul Cercueil * 10*f865c352SPaul Cercueil * You should have received a copy of the GNU General Public License along 11*f865c352SPaul Cercueil * with this program; if not, write to the Free Software Foundation, Inc., 12*f865c352SPaul Cercueil * 675 Mass Ave, Cambridge, MA 02139, USA. 13*f865c352SPaul Cercueil * 14*f865c352SPaul Cercueil */ 15*f865c352SPaul Cercueil 16*f865c352SPaul Cercueil #include <linux/module.h> 17*f865c352SPaul Cercueil #include <linux/moduleparam.h> 18*f865c352SPaul Cercueil #include <linux/types.h> 19*f865c352SPaul Cercueil #include <linux/kernel.h> 20*f865c352SPaul Cercueil #include <linux/fs.h> 21*f865c352SPaul Cercueil #include <linux/miscdevice.h> 22*f865c352SPaul Cercueil #include <linux/watchdog.h> 23*f865c352SPaul Cercueil #include <linux/init.h> 24*f865c352SPaul Cercueil #include <linux/bitops.h> 25*f865c352SPaul Cercueil #include <linux/platform_device.h> 26*f865c352SPaul Cercueil #include <linux/spinlock.h> 27*f865c352SPaul Cercueil #include <linux/uaccess.h> 28*f865c352SPaul Cercueil #include <linux/io.h> 29*f865c352SPaul Cercueil #include <linux/device.h> 30*f865c352SPaul Cercueil #include <linux/clk.h> 31*f865c352SPaul Cercueil #include <linux/slab.h> 32*f865c352SPaul Cercueil 33*f865c352SPaul Cercueil #include <asm/mach-jz4740/timer.h> 34*f865c352SPaul Cercueil 35*f865c352SPaul Cercueil #define JZ_REG_WDT_TIMER_DATA 0x0 36*f865c352SPaul Cercueil #define JZ_REG_WDT_COUNTER_ENABLE 0x4 37*f865c352SPaul Cercueil #define JZ_REG_WDT_TIMER_COUNTER 0x8 38*f865c352SPaul Cercueil #define JZ_REG_WDT_TIMER_CONTROL 0xC 39*f865c352SPaul Cercueil 40*f865c352SPaul Cercueil #define JZ_WDT_CLOCK_PCLK 0x1 41*f865c352SPaul Cercueil #define JZ_WDT_CLOCK_RTC 0x2 42*f865c352SPaul Cercueil #define JZ_WDT_CLOCK_EXT 0x4 43*f865c352SPaul Cercueil 44*f865c352SPaul Cercueil #define WDT_IN_USE 0 45*f865c352SPaul Cercueil #define WDT_OK_TO_CLOSE 1 46*f865c352SPaul Cercueil 47*f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_SHIFT 3 48*f865c352SPaul Cercueil 49*f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_1 (0 << JZ_WDT_CLOCK_DIV_SHIFT) 50*f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_4 (1 << JZ_WDT_CLOCK_DIV_SHIFT) 51*f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_16 (2 << JZ_WDT_CLOCK_DIV_SHIFT) 52*f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_64 (3 << JZ_WDT_CLOCK_DIV_SHIFT) 53*f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_256 (4 << JZ_WDT_CLOCK_DIV_SHIFT) 54*f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_1024 (5 << JZ_WDT_CLOCK_DIV_SHIFT) 55*f865c352SPaul Cercueil 56*f865c352SPaul Cercueil #define DEFAULT_HEARTBEAT 5 57*f865c352SPaul Cercueil #define MAX_HEARTBEAT 2048 58*f865c352SPaul Cercueil 59*f865c352SPaul Cercueil static struct { 60*f865c352SPaul Cercueil void __iomem *base; 61*f865c352SPaul Cercueil struct resource *mem; 62*f865c352SPaul Cercueil struct clk *rtc_clk; 63*f865c352SPaul Cercueil unsigned long status; 64*f865c352SPaul Cercueil } jz4740_wdt; 65*f865c352SPaul Cercueil 66*f865c352SPaul Cercueil static int heartbeat = DEFAULT_HEARTBEAT; 67*f865c352SPaul Cercueil 68*f865c352SPaul Cercueil 69*f865c352SPaul Cercueil static void jz4740_wdt_service(void) 70*f865c352SPaul Cercueil { 71*f865c352SPaul Cercueil writew(0x0, jz4740_wdt.base + JZ_REG_WDT_TIMER_COUNTER); 72*f865c352SPaul Cercueil } 73*f865c352SPaul Cercueil 74*f865c352SPaul Cercueil static void jz4740_wdt_set_heartbeat(int new_heartbeat) 75*f865c352SPaul Cercueil { 76*f865c352SPaul Cercueil unsigned int rtc_clk_rate; 77*f865c352SPaul Cercueil unsigned int timeout_value; 78*f865c352SPaul Cercueil unsigned short clock_div = JZ_WDT_CLOCK_DIV_1; 79*f865c352SPaul Cercueil 80*f865c352SPaul Cercueil heartbeat = new_heartbeat; 81*f865c352SPaul Cercueil 82*f865c352SPaul Cercueil rtc_clk_rate = clk_get_rate(jz4740_wdt.rtc_clk); 83*f865c352SPaul Cercueil 84*f865c352SPaul Cercueil timeout_value = rtc_clk_rate * heartbeat; 85*f865c352SPaul Cercueil while (timeout_value > 0xffff) { 86*f865c352SPaul Cercueil if (clock_div == JZ_WDT_CLOCK_DIV_1024) { 87*f865c352SPaul Cercueil /* Requested timeout too high; 88*f865c352SPaul Cercueil * use highest possible value. */ 89*f865c352SPaul Cercueil timeout_value = 0xffff; 90*f865c352SPaul Cercueil break; 91*f865c352SPaul Cercueil } 92*f865c352SPaul Cercueil timeout_value >>= 2; 93*f865c352SPaul Cercueil clock_div += (1 << JZ_WDT_CLOCK_DIV_SHIFT); 94*f865c352SPaul Cercueil } 95*f865c352SPaul Cercueil 96*f865c352SPaul Cercueil writeb(0x0, jz4740_wdt.base + JZ_REG_WDT_COUNTER_ENABLE); 97*f865c352SPaul Cercueil writew(clock_div, jz4740_wdt.base + JZ_REG_WDT_TIMER_CONTROL); 98*f865c352SPaul Cercueil 99*f865c352SPaul Cercueil writew((u16)timeout_value, jz4740_wdt.base + JZ_REG_WDT_TIMER_DATA); 100*f865c352SPaul Cercueil writew(0x0, jz4740_wdt.base + JZ_REG_WDT_TIMER_COUNTER); 101*f865c352SPaul Cercueil writew(clock_div | JZ_WDT_CLOCK_RTC, 102*f865c352SPaul Cercueil jz4740_wdt.base + JZ_REG_WDT_TIMER_CONTROL); 103*f865c352SPaul Cercueil 104*f865c352SPaul Cercueil writeb(0x1, jz4740_wdt.base + JZ_REG_WDT_COUNTER_ENABLE); 105*f865c352SPaul Cercueil } 106*f865c352SPaul Cercueil 107*f865c352SPaul Cercueil static void jz4740_wdt_enable(void) 108*f865c352SPaul Cercueil { 109*f865c352SPaul Cercueil jz4740_timer_enable_watchdog(); 110*f865c352SPaul Cercueil jz4740_wdt_set_heartbeat(heartbeat); 111*f865c352SPaul Cercueil } 112*f865c352SPaul Cercueil 113*f865c352SPaul Cercueil static void jz4740_wdt_disable(void) 114*f865c352SPaul Cercueil { 115*f865c352SPaul Cercueil jz4740_timer_disable_watchdog(); 116*f865c352SPaul Cercueil writeb(0x0, jz4740_wdt.base + JZ_REG_WDT_COUNTER_ENABLE); 117*f865c352SPaul Cercueil } 118*f865c352SPaul Cercueil 119*f865c352SPaul Cercueil static int jz4740_wdt_open(struct inode *inode, struct file *file) 120*f865c352SPaul Cercueil { 121*f865c352SPaul Cercueil if (test_and_set_bit(WDT_IN_USE, &jz4740_wdt.status)) 122*f865c352SPaul Cercueil return -EBUSY; 123*f865c352SPaul Cercueil 124*f865c352SPaul Cercueil jz4740_wdt_enable(); 125*f865c352SPaul Cercueil 126*f865c352SPaul Cercueil return nonseekable_open(inode, file); 127*f865c352SPaul Cercueil } 128*f865c352SPaul Cercueil 129*f865c352SPaul Cercueil static ssize_t jz4740_wdt_write(struct file *file, const char *data, 130*f865c352SPaul Cercueil size_t len, loff_t *ppos) 131*f865c352SPaul Cercueil { 132*f865c352SPaul Cercueil if (len) { 133*f865c352SPaul Cercueil if (data[len-1] == 'V') 134*f865c352SPaul Cercueil set_bit(WDT_OK_TO_CLOSE, &jz4740_wdt.status); 135*f865c352SPaul Cercueil else 136*f865c352SPaul Cercueil clear_bit(WDT_OK_TO_CLOSE, &jz4740_wdt.status); 137*f865c352SPaul Cercueil 138*f865c352SPaul Cercueil jz4740_wdt_service(); 139*f865c352SPaul Cercueil } 140*f865c352SPaul Cercueil 141*f865c352SPaul Cercueil return len; 142*f865c352SPaul Cercueil } 143*f865c352SPaul Cercueil 144*f865c352SPaul Cercueil static const struct watchdog_info ident = { 145*f865c352SPaul Cercueil .options = WDIOF_KEEPALIVEPING, 146*f865c352SPaul Cercueil .identity = "jz4740 Watchdog", 147*f865c352SPaul Cercueil }; 148*f865c352SPaul Cercueil 149*f865c352SPaul Cercueil static long jz4740_wdt_ioctl(struct file *file, 150*f865c352SPaul Cercueil unsigned int cmd, unsigned long arg) 151*f865c352SPaul Cercueil { 152*f865c352SPaul Cercueil int ret = -ENOTTY; 153*f865c352SPaul Cercueil int heartbeat_seconds; 154*f865c352SPaul Cercueil 155*f865c352SPaul Cercueil switch (cmd) { 156*f865c352SPaul Cercueil case WDIOC_GETSUPPORT: 157*f865c352SPaul Cercueil ret = copy_to_user((struct watchdog_info *)arg, &ident, 158*f865c352SPaul Cercueil sizeof(ident)) ? -EFAULT : 0; 159*f865c352SPaul Cercueil break; 160*f865c352SPaul Cercueil 161*f865c352SPaul Cercueil case WDIOC_GETSTATUS: 162*f865c352SPaul Cercueil case WDIOC_GETBOOTSTATUS: 163*f865c352SPaul Cercueil ret = put_user(0, (int *)arg); 164*f865c352SPaul Cercueil break; 165*f865c352SPaul Cercueil 166*f865c352SPaul Cercueil case WDIOC_KEEPALIVE: 167*f865c352SPaul Cercueil jz4740_wdt_service(); 168*f865c352SPaul Cercueil return 0; 169*f865c352SPaul Cercueil 170*f865c352SPaul Cercueil case WDIOC_SETTIMEOUT: 171*f865c352SPaul Cercueil if (get_user(heartbeat_seconds, (int __user *)arg)) 172*f865c352SPaul Cercueil return -EFAULT; 173*f865c352SPaul Cercueil 174*f865c352SPaul Cercueil jz4740_wdt_set_heartbeat(heartbeat_seconds); 175*f865c352SPaul Cercueil return 0; 176*f865c352SPaul Cercueil 177*f865c352SPaul Cercueil case WDIOC_GETTIMEOUT: 178*f865c352SPaul Cercueil return put_user(heartbeat, (int *)arg); 179*f865c352SPaul Cercueil 180*f865c352SPaul Cercueil default: 181*f865c352SPaul Cercueil break; 182*f865c352SPaul Cercueil } 183*f865c352SPaul Cercueil 184*f865c352SPaul Cercueil return ret; 185*f865c352SPaul Cercueil } 186*f865c352SPaul Cercueil 187*f865c352SPaul Cercueil static int jz4740_wdt_release(struct inode *inode, struct file *file) 188*f865c352SPaul Cercueil { 189*f865c352SPaul Cercueil jz4740_wdt_service(); 190*f865c352SPaul Cercueil 191*f865c352SPaul Cercueil if (test_and_clear_bit(WDT_OK_TO_CLOSE, &jz4740_wdt.status)) 192*f865c352SPaul Cercueil jz4740_wdt_disable(); 193*f865c352SPaul Cercueil 194*f865c352SPaul Cercueil clear_bit(WDT_IN_USE, &jz4740_wdt.status); 195*f865c352SPaul Cercueil return 0; 196*f865c352SPaul Cercueil } 197*f865c352SPaul Cercueil 198*f865c352SPaul Cercueil static const struct file_operations jz4740_wdt_fops = { 199*f865c352SPaul Cercueil .owner = THIS_MODULE, 200*f865c352SPaul Cercueil .llseek = no_llseek, 201*f865c352SPaul Cercueil .write = jz4740_wdt_write, 202*f865c352SPaul Cercueil .unlocked_ioctl = jz4740_wdt_ioctl, 203*f865c352SPaul Cercueil .open = jz4740_wdt_open, 204*f865c352SPaul Cercueil .release = jz4740_wdt_release, 205*f865c352SPaul Cercueil }; 206*f865c352SPaul Cercueil 207*f865c352SPaul Cercueil static struct miscdevice jz4740_wdt_miscdev = { 208*f865c352SPaul Cercueil .minor = WATCHDOG_MINOR, 209*f865c352SPaul Cercueil .name = "watchdog", 210*f865c352SPaul Cercueil .fops = &jz4740_wdt_fops, 211*f865c352SPaul Cercueil }; 212*f865c352SPaul Cercueil 213*f865c352SPaul Cercueil static int __devinit jz4740_wdt_probe(struct platform_device *pdev) 214*f865c352SPaul Cercueil { 215*f865c352SPaul Cercueil int ret = 0, size; 216*f865c352SPaul Cercueil struct resource *res; 217*f865c352SPaul Cercueil struct device *dev = &pdev->dev; 218*f865c352SPaul Cercueil 219*f865c352SPaul Cercueil res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 220*f865c352SPaul Cercueil if (res == NULL) { 221*f865c352SPaul Cercueil dev_err(dev, "failed to get memory region resource\n"); 222*f865c352SPaul Cercueil return -ENXIO; 223*f865c352SPaul Cercueil } 224*f865c352SPaul Cercueil 225*f865c352SPaul Cercueil size = resource_size(res); 226*f865c352SPaul Cercueil jz4740_wdt.mem = request_mem_region(res->start, size, pdev->name); 227*f865c352SPaul Cercueil if (jz4740_wdt.mem == NULL) { 228*f865c352SPaul Cercueil dev_err(dev, "failed to get memory region\n"); 229*f865c352SPaul Cercueil return -EBUSY; 230*f865c352SPaul Cercueil } 231*f865c352SPaul Cercueil 232*f865c352SPaul Cercueil jz4740_wdt.base = ioremap_nocache(res->start, size); 233*f865c352SPaul Cercueil if (jz4740_wdt.base == NULL) { 234*f865c352SPaul Cercueil dev_err(dev, "failed to map memory region\n"); 235*f865c352SPaul Cercueil ret = -EBUSY; 236*f865c352SPaul Cercueil goto err_release_region; 237*f865c352SPaul Cercueil } 238*f865c352SPaul Cercueil 239*f865c352SPaul Cercueil jz4740_wdt.rtc_clk = clk_get(NULL, "rtc"); 240*f865c352SPaul Cercueil if (IS_ERR(jz4740_wdt.rtc_clk)) { 241*f865c352SPaul Cercueil dev_err(dev, "cannot find RTC clock\n"); 242*f865c352SPaul Cercueil ret = PTR_ERR(jz4740_wdt.rtc_clk); 243*f865c352SPaul Cercueil goto err_iounmap; 244*f865c352SPaul Cercueil } 245*f865c352SPaul Cercueil 246*f865c352SPaul Cercueil ret = misc_register(&jz4740_wdt_miscdev); 247*f865c352SPaul Cercueil if (ret < 0) { 248*f865c352SPaul Cercueil dev_err(dev, "cannot register misc device\n"); 249*f865c352SPaul Cercueil goto err_disable_clk; 250*f865c352SPaul Cercueil } 251*f865c352SPaul Cercueil 252*f865c352SPaul Cercueil return 0; 253*f865c352SPaul Cercueil 254*f865c352SPaul Cercueil err_disable_clk: 255*f865c352SPaul Cercueil clk_put(jz4740_wdt.rtc_clk); 256*f865c352SPaul Cercueil err_iounmap: 257*f865c352SPaul Cercueil iounmap(jz4740_wdt.base); 258*f865c352SPaul Cercueil err_release_region: 259*f865c352SPaul Cercueil release_mem_region(jz4740_wdt.mem->start, 260*f865c352SPaul Cercueil resource_size(jz4740_wdt.mem)); 261*f865c352SPaul Cercueil return ret; 262*f865c352SPaul Cercueil } 263*f865c352SPaul Cercueil 264*f865c352SPaul Cercueil 265*f865c352SPaul Cercueil static int __devexit jz4740_wdt_remove(struct platform_device *pdev) 266*f865c352SPaul Cercueil { 267*f865c352SPaul Cercueil jz4740_wdt_disable(); 268*f865c352SPaul Cercueil misc_deregister(&jz4740_wdt_miscdev); 269*f865c352SPaul Cercueil clk_put(jz4740_wdt.rtc_clk); 270*f865c352SPaul Cercueil 271*f865c352SPaul Cercueil iounmap(jz4740_wdt.base); 272*f865c352SPaul Cercueil jz4740_wdt.base = NULL; 273*f865c352SPaul Cercueil 274*f865c352SPaul Cercueil release_mem_region(jz4740_wdt.mem->start, 275*f865c352SPaul Cercueil resource_size(jz4740_wdt.mem)); 276*f865c352SPaul Cercueil jz4740_wdt.mem = NULL; 277*f865c352SPaul Cercueil 278*f865c352SPaul Cercueil return 0; 279*f865c352SPaul Cercueil } 280*f865c352SPaul Cercueil 281*f865c352SPaul Cercueil 282*f865c352SPaul Cercueil static struct platform_driver jz4740_wdt_driver = { 283*f865c352SPaul Cercueil .probe = jz4740_wdt_probe, 284*f865c352SPaul Cercueil .remove = __devexit_p(jz4740_wdt_remove), 285*f865c352SPaul Cercueil .driver = { 286*f865c352SPaul Cercueil .name = "jz4740-wdt", 287*f865c352SPaul Cercueil .owner = THIS_MODULE, 288*f865c352SPaul Cercueil }, 289*f865c352SPaul Cercueil }; 290*f865c352SPaul Cercueil 291*f865c352SPaul Cercueil 292*f865c352SPaul Cercueil static int __init jz4740_wdt_init(void) 293*f865c352SPaul Cercueil { 294*f865c352SPaul Cercueil return platform_driver_register(&jz4740_wdt_driver); 295*f865c352SPaul Cercueil } 296*f865c352SPaul Cercueil module_init(jz4740_wdt_init); 297*f865c352SPaul Cercueil 298*f865c352SPaul Cercueil static void __exit jz4740_wdt_exit(void) 299*f865c352SPaul Cercueil { 300*f865c352SPaul Cercueil platform_driver_unregister(&jz4740_wdt_driver); 301*f865c352SPaul Cercueil } 302*f865c352SPaul Cercueil module_exit(jz4740_wdt_exit); 303*f865c352SPaul Cercueil 304*f865c352SPaul Cercueil MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 305*f865c352SPaul Cercueil MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 306*f865c352SPaul Cercueil 307*f865c352SPaul Cercueil module_param(heartbeat, int, 0); 308*f865c352SPaul Cercueil MODULE_PARM_DESC(heartbeat, 309*f865c352SPaul Cercueil "Watchdog heartbeat period in seconds from 1 to " 310*f865c352SPaul Cercueil __MODULE_STRING(MAX_HEARTBEAT) ", default " 311*f865c352SPaul Cercueil __MODULE_STRING(DEFAULT_HEARTBEAT)); 312*f865c352SPaul Cercueil 313*f865c352SPaul Cercueil MODULE_LICENSE("GPL"); 314*f865c352SPaul Cercueil MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 315*f865c352SPaul Cercueil MODULE_ALIAS("platform:jz4740-wdt"); 316