/* * GSC3280 SoC WATCHDOG Controller Driver * * Copyright (C) 2013 BLX IC Design Corp.,Ltd. * Author: Davied, apple_guet@126.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * the formulate is pclkOne = T*55555555/2(tor + 16) * * the watchdog default T = 9.66s,tor = 0x05, pclkOne = 0x08 * * torr pclkOne T(s) Set value * 0x04 0x08 4.83 0x48 * 0x05 0x08 9.66 0x58 * 0x06 0x08 19.33 0x68 * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gsc3280_wdt.h" #ifdef GSC3280_WDT_DEBUG #define DBG(msg...) do { \ printk(KERN_INFO msg); \ } while (0) #else #define DBG(msg...) do { } while(0) #endif static LIST_HEAD(wdt_device_list); static DEFINE_MUTEX(wdt_device_list_lock); static void gsc3280wdt_keepalive(struct gsc3280_wdt *wdt) { spin_lock(&wdt->wdt_lock); writel(WDT_FEED_VALUE, wdt->wdt_base + WDT_CRR); spin_unlock(&wdt->wdt_lock); DBG("feed watchdog\n"); } static void gsc3280wdt_stop(struct gsc3280_wdt *wdt) { unsigned int tmp_value = 0; spin_lock(&wdt->wdt_lock); tmp_value = readl(GSC3280_WDT_SYS_CFG); writel(tmp_value | GSC3280_WDT_SYS_CFG_PAUSE, GSC3280_WDT_SYS_CFG); spin_unlock(&wdt->wdt_lock); } static int gsc3280_watchdog_init(struct gsc3280_wdt *wdt) { unsigned int tmp_value = 0; DBG("Enter watchdog_init\n"); spin_lock(&wdt->wdt_lock); tmp_value = readl(GSC3280_WDT_SYS_CFG); writel((tmp_value & 0xf0) | wdt->pclkOne, GSC3280_WDT_SYS_CFG); tmp_value = readl(GSC3280_WDT_SYS_CFG); DBG("wdt_pclk = %x\n", tmp_value & 0x0f); writel(wdt->torr & 0xffff , wdt->wdt_base + WDT_TORR); //0x1d:reset directly, 0x1f: reset after interrupt writel(wdt->wdt_cr & 0x1d, wdt->wdt_base + WDT_CR); DBG("wdt_ccvr = %x\n", readl(wdt->wdt_base + WDT_CCVR)); DBG("wdt_cr = %x\n", readl(wdt->wdt_base + WDT_CR)); tmp_value = readl(GSC3280_WDT_SYS_CFG); writel(tmp_value & ~GSC3280_WDT_SYS_CFG_PAUSE, GSC3280_WDT_SYS_CFG); //start watchdog spin_unlock(&wdt->wdt_lock); return 0; } static void gsc3280_watchdog_enable(struct gsc3280_wdt *wdt) { spin_lock(&wdt->wdt_lock); //0x1d:reset directly, 0x1f: reset after interrupt writel(0x1d, wdt->wdt_base + WDT_CR); spin_unlock(&wdt->wdt_lock); } static void gsc3280wdt_start(struct gsc3280_wdt *wdt) { DBG("watchdog start\n"); wdt->wdt_cr = 0x1f; wdt->torr = 0x05; wdt->pclkOne = 0x08; gsc3280_watchdog_init(wdt); gsc3280_watchdog_enable(wdt); gsc3280wdt_keepalive(wdt); } static void gsc3280wdt_set_par(struct gsc3280_wdt *wdt) { unsigned int tmp_value = 0; spin_lock(&wdt->wdt_lock); tmp_value = readl(GSC3280_WDT_SYS_CFG); writel((tmp_value & 0xf0) | wdt->pclkOne, GSC3280_WDT_SYS_CFG); DBG("sys_wdt_cfg = %x\n", readl(GSC3280_WDT_SYS_CFG)); writel(wdt->torr, wdt->wdt_base + WDT_TORR); DBG("wdt_torr = %x\n", readl(wdt->wdt_base + WDT_TORR)); spin_unlock(&wdt->wdt_lock); } static int gsc3280wdt_read_par(struct gsc3280_wdt *wdt) { unsigned int cfg = 0, torr = 0; cfg = readl(GSC3280_WDT_SYS_CFG) & 0x000f; torr = readl(wdt->wdt_base + WDT_TORR); cfg |= (torr << 4) & 0xfffffff0; DBG("%x\n", cfg); return cfg; } static int gsc3280wdt_open(struct inode *inode, struct file *file) { int status = -ENXIO; struct gsc3280_wdt *wdt; mutex_lock(&wdt_device_list_lock); list_for_each_entry(wdt, &wdt_device_list, device_entry) { if(strcmp(wdt->name, GSC3280_WDT_NAME) == 0) { status = 0; break; } } if (status == 0) { file->private_data = wdt; } mutex_unlock(&wdt_device_list_lock); if (test_and_set_bit(0, &wdt->open_lock)) { DBG("ERROR:gsc3280 open err, busy\n"); return -EBUSY; } return nonseekable_open(inode, file); } static int gsc3280wdt_release(struct inode *inode, struct file *file) { struct gsc3280_wdt *wdt = file->private_data; gsc3280wdt_stop(wdt); clear_bit(0, &wdt->open_lock); DBG("gsc3280 release ok\n"); return 0; } static ssize_t gsc3280wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { //struct gsc3280_wdt *wdt = file->private_data; DBG("This is in write\n"); return 0; } static void ReadDebug(struct gsc3280_wdt *wdt) { unsigned int tmp_value = 0; DBG("read register\n"); tmp_value = readl((unsigned int *)(GSC3280_SYSCTL_BASEADDR + SYS_WDT_EN_OFFSET)); DBG("sys_wdt_en = %x\n", tmp_value & 0x04000000); DBG("sys_wdt_cfg = %x\n", readl(GSC3280_WDT_SYS_CFG)); DBG("ccvr = %x\n", readl(wdt->wdt_base + WDT_CCVR)); DBG("cr = %x\n", readl(wdt->wdt_base + WDT_CR)); DBG("torr = %x\n", readl(wdt->wdt_base + WDT_TORR)); printk(KERN_INFO "clk rate is %ld\n", clk_get_rate(wdt->clk)); } static long gsc3280wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int new_margin; void __user *argp = (void __user *)arg; int __user *p = argp; struct gsc3280_wdt *wdt; if (_IOC_TYPE(cmd) != WDT_IOC_MAGIC) return -ENOTTY; if (_IOC_NR(cmd) > WDT_IOC_MAXNR) return -ENOTTY; wdt = file->private_data; switch (cmd) { case WDT_START: gsc3280wdt_start(wdt); return 0; case WDT_KEEPALIVE: gsc3280wdt_keepalive(wdt); return 0; case WDT_PAUSE: gsc3280wdt_stop(wdt); return 0; case WDT_READ: ReadDebug(wdt); return 0; case WDT_SETTIMEOUT: if (get_user(new_margin, p)) return -EFAULT; wdt->pclkOne= new_margin & 0x000f; wdt->torr = (new_margin >> 4) & 0xffff; gsc3280wdt_set_par(wdt); gsc3280wdt_keepalive(wdt); return 0; case WDT_GETTIMEOUT: return put_user(gsc3280wdt_read_par(wdt), p); default: return -ENOTTY; } } static const struct file_operations gsc3280wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = gsc3280wdt_write, .unlocked_ioctl = gsc3280wdt_ioctl, .open = gsc3280wdt_open, .release = gsc3280wdt_release, }; static struct miscdevice gsc3280wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &gsc3280wdt_fops, }; static irqreturn_t gsc3280wdt_irq(int irqno, void *param) { #if 0 struct gsc3280_wdt *wdt = param; dev_info(wdt->dev, "watchdog timer expired (irq)\n"); gsc3280wdt_keepalive(wdt); #endif DBG("This is in irq handle\n"); return IRQ_HANDLED; } static int __devinit gsc3280wdt_probe(struct platform_device *pdev) { int ret = 0, size = 0; struct gsc3280_wdt *gsc = NULL; DBG("############\n"); printk(KERN_INFO "gsc3280 watchdog probe start\n"); gsc = kzalloc(sizeof(struct gsc3280_wdt), GFP_KERNEL); if (!gsc) { DBG("no memory error!!!!\n"); return -ENOMEM; } /* Initialize the driver data */ gsc->suspend_flg = 0; gsc->dev = &pdev->dev; spin_lock_init(&gsc->wdt_lock); INIT_LIST_HEAD(&gsc->device_entry); strlcpy(gsc->name, GSC3280_WDT_NAME, sizeof(gsc->name)); /* get the memory region for the watchdog timer */ gsc->wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (gsc->wdt_mem == NULL) { DBG("no memory resource specified!!!!\n"); ret = -ENOENT; goto free; } size = resource_size(gsc->wdt_mem); if (!request_mem_region(gsc->wdt_mem->start, size, pdev->name)) { DBG("failed to get memory region!!!!\n"); ret = -EBUSY; goto free; } gsc->wdt_base = ioremap(gsc->wdt_mem->start, size); if (gsc->wdt_base == NULL) { DBG("failed to ioremap() region!!!!\n"); ret = -EINVAL; goto err_req; } DBG("probe: mapped wdt_base=%p\n", gsc->wdt_base); gsc->wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (gsc->wdt_irq == NULL) { DBG("no irq resource specified!!!!\n"); ret = -ENOENT; goto err_map; } ret = request_irq(gsc->wdt_irq->start, gsc3280wdt_irq, 0, pdev->name, gsc); if (ret != 0) { DBG("failed to install irq (%d)!!!!\n", ret); goto err_map; } gsc->clk = clk_get(NULL, "wdt"); if (IS_ERR(gsc->clk)) { DBG("failed to find watchdog clock source!!!!\n"); ret = PTR_ERR(gsc->clk); goto err_irq; } DBG("clk rate is %ld\n", clk_get_rate(gsc->clk)); clk_enable(gsc->clk); ret = misc_register(&gsc3280wdt_miscdev); if(ret < 0) { DBG("misc register watchdog error!!!!\n"); goto err_clk; } #ifdef CONFIG_PM /* set pm runtime power state and register with power system */ pm_runtime_set_active(gsc->dev); pm_runtime_enable(gsc->dev); pm_runtime_resume(gsc->dev); //device_init_wakeup(gsc->dev, 1); #endif mutex_lock(&wdt_device_list_lock); list_add(&gsc->device_entry, &wdt_device_list); mutex_unlock(&wdt_device_list_lock); platform_set_drvdata(pdev, gsc); printk(KERN_INFO "gsc3280 watchdog probe success\n"); DBG("############\n"); return ret; err_clk: clk_disable(gsc->clk); clk_put(gsc->clk); err_irq: free_irq(gsc->wdt_irq->start, pdev); err_map: iounmap(gsc->wdt_base); err_req: release_mem_region(gsc->wdt_mem->start, size); gsc->wdt_mem = NULL; free: kfree(gsc); printk(KERN_INFO "!!!!!!gsc3280 watchdog probe err!!!!!!\n"); return ret; } static int __devexit gsc3280wdt_remove(struct platform_device *dev) { struct gsc3280_wdt *gsc = platform_get_drvdata(dev); misc_deregister(&gsc3280wdt_miscdev); clk_disable(gsc->clk); clk_put(gsc->clk); gsc->clk = NULL; free_irq(gsc->wdt_irq->start, dev); gsc->wdt_irq = NULL; iounmap(gsc->wdt_base); release_mem_region(gsc->wdt_mem->start, resource_size(gsc->wdt_mem)); gsc->wdt_mem = NULL; kfree(gsc); printk(KERN_INFO "gsc3280 remove ok.\n"); return 0; } static void gsc3280wdt_shutdown(struct platform_device *dev) { struct gsc3280_wdt *gsc = platform_get_drvdata(dev); gsc3280wdt_stop(gsc); printk(KERN_INFO "gsc3280 shutdown ok.\n"); } #ifdef CONFIG_PM static int gsc3280wdt_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct gsc3280_wdt *gsc = platform_get_drvdata(pdev); printk(KERN_INFO "gsc3280 wdt suspend\n"); if (gsc->suspend_flg == 1) { spin_lock(&gsc->wdt_lock); /* Save watchdog state, and turn it off. */ gsc->pclkOne = readl(GSC3280_WDT_SYS_CFG) & 0x0f; gsc->torr = readl(gsc->wdt_base + WDT_TORR) & 0x0000ffff; gsc->wdt_cr = readl(gsc->wdt_base + WDT_CR); spin_unlock(&gsc->wdt_lock); /* Note that WTCNT doesn't need to be saved. */ gsc3280wdt_stop(gsc); clk_disable(gsc->clk); } else { gsc->suspend_flg = 1; } return 0; } static int gsc3280wdt_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct gsc3280_wdt *gsc = platform_get_drvdata(pdev); printk(KERN_INFO "gsc3280 wdt resume\n"); clk_enable(gsc->clk); gsc3280_watchdog_init(gsc); return 0; } #else #define gsc3280wdt_suspend NULL #define gsc3280wdt_resume NULL #endif //end #ifdef CONFIG_PM static const struct dev_pm_ops gsc3280wdt_pm_ops = { .suspend = gsc3280wdt_suspend, .resume = gsc3280wdt_resume, .runtime_suspend = gsc3280wdt_suspend, .runtime_resume = gsc3280wdt_resume, }; static struct platform_driver gsc3280wdt_driver = { .probe = gsc3280wdt_probe, .remove = __devexit_p(gsc3280wdt_remove), .shutdown = gsc3280wdt_shutdown, .driver = { .owner = THIS_MODULE, .name = "gsc3280-watchdog", .pm = &gsc3280wdt_pm_ops, }, }; static int __init watchdog_init(void) { return platform_driver_register(&gsc3280wdt_driver); } static void __exit watchdog_exit(void) { platform_driver_unregister(&gsc3280wdt_driver); } module_init(watchdog_init); module_exit(watchdog_exit); MODULE_AUTHOR("Davied"); MODULE_DESCRIPTION("gsc3280 Watchdog Device Driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); MODULE_ALIAS("platform:gsc3280-wdt");