| /* |
| * drivers/watchdog/zx29_wdt.c |
| * |
| * Copyright (C) 2015 ZTE-TSP |
| * |
| * 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. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/types.h> |
| #include <linux/timer.h> |
| #include <linux/miscdevice.h> |
| #include <linux/watchdog.h> |
| #include <linux/init.h> |
| #include <linux/platform_device.h> |
| #include <linux/interrupt.h> |
| #include <linux/clk.h> |
| #include <linux/uaccess.h> |
| #include <linux/io.h> |
| #include <linux/cpufreq.h> |
| #include <linux/slab.h> |
| #include <linux/err.h> |
| |
| #include <mach/board.h> |
| #include "zx29_wdt.h" |
| |
| /***************************************************************************** |
| ** | --> | --> | |
| ** 15s(value load) 5s(feed in irq) 0s(reset if no feed) |
| ***************************************************************************** |
| **/ |
| |
| #define CONFIG_ZX29_WATCHDOG_ATBOOT (0) |
| #define CONFIG_ZX29_WATCHDOG_DEFAULT_TIME (15) |
| #define CONFIG_ZX29_WATCHDOG_DEADLINE_TIME (5) |
| |
| static bool nowayout = WATCHDOG_NOWAYOUT; /* 1-disagree to close wdt 0-agree to close wdt */ |
| static int tmr_margin = CONFIG_ZX29_WATCHDOG_DEFAULT_TIME; /* max during to feed dog, unit is second */ |
| static int tmr_atboot = CONFIG_ZX29_WATCHDOG_ATBOOT; /* 0 - no enable wdt when boot */ |
| static int debug; |
| |
| module_param(tmr_margin, int, 0); |
| module_param(tmr_atboot, int, 0); |
| module_param(nowayout, bool, 0); |
| module_param(debug, int, 0); |
| |
| MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default=" |
| __MODULE_STRING(CONFIG_ZX29_WATCHDOG_DEFAULT_TIME) ")"); |
| MODULE_PARM_DESC(tmr_atboot, |
| "Watchdog is started at boot time if set to 1, default=" |
| __MODULE_STRING(CONFIG_ZX29_WATCHDOG_ATBOOT)); |
| MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" |
| __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); |
| MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (default 0)"); |
| |
| static struct device *wdt_dev; /* platform device attached to */ |
| static struct clk *wdt_clock; |
| static struct clk *wdt_apb_clock; |
| static void __iomem *wdt_base; |
| static unsigned int wdt_count; |
| static DEFINE_SPINLOCK(wdt_lock); |
| static struct resource *wdt_irq; |
| |
| /* watchdog control routines */ |
| |
| #define DBG(fmt, ...) \ |
| do { \ |
| if (debug) \ |
| pr_info(fmt, ##__VA_ARGS__); \ |
| } while (0) |
| |
| /* functions */ |
| static unsigned int feed_count = 0; |
| static int zx29_wdt_keepalive(struct watchdog_device *wdd) |
| { |
| spin_lock(&wdt_lock); |
| __wdt_set_load(wdt_base, wdt_count); |
| feed_count ++; |
| spin_unlock(&wdt_lock); |
| |
| return 0; |
| } |
| |
| static int zx29_wdt_stop(struct watchdog_device *wdd) |
| { |
| spin_lock(&wdt_lock); |
| __wdt_stop(wdt_base); |
| spin_unlock(&wdt_lock); |
| |
| return 0; |
| } |
| |
| static int zx29_wdt_start(struct watchdog_device *wdd) |
| { |
| spin_lock(&wdt_lock); |
| __wdt_start(wdt_base); |
| spin_unlock(&wdt_lock); |
| |
| return 0; |
| } |
| |
| /* |
| * timeout -- unit(s) |
| * |
| */ |
| static int zx29_wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeout) |
| { |
| wdt_count = timeout*WDT_TIME_1S; |
| zx29_wdt_keepalive(wdd); |
| |
| spin_lock(&wdt_lock); |
| __wdt_set_int_value(wdt_base, CONFIG_ZX29_WATCHDOG_DEADLINE_TIME*WDT_TIME_1S); |
| spin_unlock(&wdt_lock); |
| |
| return 0; |
| } |
| |
| #define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) |
| |
| static const struct watchdog_info zx29_wdt_ident = { |
| .options = OPTIONS, |
| .firmware_version = 0, |
| .identity = "ZX29 Watchdog", |
| }; |
| |
| static struct watchdog_ops zx29_wdt_ops = { |
| .owner = THIS_MODULE, |
| .start = zx29_wdt_start, |
| .stop = zx29_wdt_stop, |
| .ping = zx29_wdt_keepalive, |
| .set_timeout = zx29_wdt_set_heartbeat, |
| }; |
| |
| static struct watchdog_device zx29_wdd = { |
| .info = &zx29_wdt_ident, |
| .ops = &zx29_wdt_ops, |
| }; |
| |
| /* interrupt handler code */ |
| static irqreturn_t zx29_wdt_irq(int irqno, void *param) |
| { |
| dev_info(wdt_dev, "watchdog timer expired (irq)\n"); |
| |
| zx29_wdt_keepalive(&zx29_wdd); |
| return IRQ_HANDLED; |
| } |
| |
| static int __devinit wdt_init_clk(struct platform_device *pdev) |
| { |
| int ret; |
| |
| wdt_apb_clock = clk_get(&pdev->dev, "apb_clk"); |
| if (IS_ERR(wdt_apb_clock)) { |
| dev_err(&pdev->dev, "failed to find watchdog apb clock source\n"); |
| ret = PTR_ERR(wdt_apb_clock); |
| return ret; |
| } |
| clk_enable(wdt_apb_clock); |
| |
| wdt_clock = clk_get(&pdev->dev, "work_clk"); |
| if (IS_ERR(wdt_clock)) { |
| dev_err(&pdev->dev, "failed to find watchdog clock source\n"); |
| ret = PTR_ERR(wdt_clock); |
| goto err; |
| } |
| clk_enable(wdt_clock); |
| clk_set_rate(wdt_clock, WDT_SOURCE_CLOCK_RATE); |
| |
| /* 32768/32 = 1kHz */ |
| __wdt_set_prescale(wdt_base, 31); |
| |
| return 0; |
| |
| err: |
| clk_disable(wdt_apb_clock); |
| clk_put(wdt_apb_clock); |
| wdt_apb_clock = NULL; |
| |
| return ret; |
| } |
| |
| static int __devinit zx29_wdt_probe(struct platform_device *pdev) |
| { |
| struct device *dev; |
| int started = 0; |
| int ret; |
| struct resource *wdt_mem; |
| |
| DBG("[WDT]%s: probe=%p\n", __func__, pdev); |
| |
| dev = &pdev->dev; |
| wdt_dev = &pdev->dev; |
| |
| wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (wdt_mem == NULL) { |
| dev_err(dev, "no memory resource specified\n"); |
| return -ENOENT; |
| } |
| wdt_base = (void __iomem *)wdt_mem->start; |
| DBG("[WDT]probe: wdt_base=%p\n", wdt_base); |
| |
| wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); |
| if (wdt_irq == NULL) { |
| dev_err(dev, "no irq resource specified\n"); |
| return -ENOENT; |
| } |
| |
| ret = wdt_init_clk(pdev); |
| if(ret) |
| return ret; |
| |
| started = zx29_wdt_set_heartbeat(&zx29_wdd, tmr_margin); |
| if (started) { |
| dev_info(dev, |
| "tmr_margin value out of range, ( %d ) used\n", |
| tmr_margin); |
| } |
| |
| ret = request_irq(wdt_irq->start, zx29_wdt_irq, 0, pdev->name, pdev); |
| if (ret != 0) { |
| dev_err(dev, "failed to install irq (%d)\n", ret); |
| goto err_clk; |
| } |
| |
| watchdog_set_nowayout(&zx29_wdd, nowayout); |
| |
| ret = watchdog_register_device(&zx29_wdd); |
| if (ret) { |
| dev_err(dev, "cannot register watchdog (%d)\n", ret); |
| goto err_irq; |
| } |
| |
| if (tmr_atboot && started == 0) { |
| dev_info(dev, "starting watchdog timer\n"); |
| zx29_wdt_start(&zx29_wdd); |
| __wdt_enable_reset(); |
| } else if (!tmr_atboot) { |
| zx29_wdt_stop(&zx29_wdd); |
| } |
| |
| pr_info("[WDT]Watchdog Timer init OK.\n"); |
| |
| return 0; |
| |
| err_irq: |
| free_irq(wdt_irq->start, pdev); |
| |
| err_clk: |
| clk_disable(wdt_clock); |
| clk_put(wdt_clock); |
| wdt_clock = NULL; |
| |
| return ret; |
| } |
| |
| static int __devexit zx29_wdt_remove(struct platform_device *dev) |
| { |
| watchdog_unregister_device(&zx29_wdd); |
| |
| free_irq(wdt_irq->start, dev); |
| |
| clk_disable(wdt_clock); |
| clk_put(wdt_clock); |
| wdt_clock = NULL; |
| |
| clk_disable(wdt_apb_clock); |
| clk_put(wdt_apb_clock); |
| wdt_apb_clock = NULL; |
| |
| return 0; |
| } |
| |
| static void zx29_wdt_shutdown(struct platform_device *dev) |
| { |
| zx29_wdt_stop(&zx29_wdd); |
| __wdt_disable_reset(); |
| } |
| |
| #ifdef CONFIG_PM |
| static zx29_wdt_context wdt_context; |
| static int zx29_wdt_suspend(struct platform_device *dev, pm_message_t state) |
| { |
| spin_lock(&wdt_lock); |
| __wdt_save_context(wdt_base, (u32 *)&wdt_context); |
| spin_unlock(&wdt_lock); |
| |
| return 0; |
| } |
| |
| static int zx29_wdt_resume(struct platform_device *dev) |
| { |
| spin_lock(&wdt_lock); |
| __wdt_restore_context(wdt_base, (u32 *)&wdt_context); |
| spin_unlock(&wdt_lock); |
| |
| return 0; |
| } |
| |
| #else |
| #define zx29_wdt_suspend NULL |
| #define zx29_wdt_resume NULL |
| #endif /* CONFIG_PM */ |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id zx29_wdt_match[] = { |
| { .compatible = "zte, zx29_ap_wdt" }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, zx29_wdt_match); |
| #else |
| #define zx29_wdt_match NULL |
| #endif |
| |
| static struct platform_driver zx29_wdt_driver = { |
| .probe = zx29_wdt_probe, |
| .remove = __devexit_p(zx29_wdt_remove), |
| .shutdown = zx29_wdt_shutdown, |
| .suspend = zx29_wdt_suspend, |
| .resume = zx29_wdt_resume, |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = "zx29_ap_wdt", |
| .of_match_table = zx29_wdt_match, |
| }, |
| }; |
| |
| |
| static int __init watchdog_init(void) |
| { |
| return platform_driver_register(&zx29_wdt_driver); |
| } |
| |
| static void __exit watchdog_exit(void) |
| { |
| platform_driver_unregister(&zx29_wdt_driver); |
| } |
| |
| module_init(watchdog_init); |
| module_exit(watchdog_exit); |
| |