1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net> 4 * JZ4740 Watchdog driver 5 */ 6 7 #include <linux/module.h> 8 #include <linux/moduleparam.h> 9 #include <linux/types.h> 10 #include <linux/kernel.h> 11 #include <linux/watchdog.h> 12 #include <linux/platform_device.h> 13 #include <linux/io.h> 14 #include <linux/device.h> 15 #include <linux/clk.h> 16 #include <linux/slab.h> 17 #include <linux/err.h> 18 #include <linux/of.h> 19 20 #include <asm/mach-jz4740/timer.h> 21 22 #define JZ_REG_WDT_TIMER_DATA 0x0 23 #define JZ_REG_WDT_COUNTER_ENABLE 0x4 24 #define JZ_REG_WDT_TIMER_COUNTER 0x8 25 #define JZ_REG_WDT_TIMER_CONTROL 0xC 26 27 #define JZ_WDT_CLOCK_PCLK 0x1 28 #define JZ_WDT_CLOCK_RTC 0x2 29 #define JZ_WDT_CLOCK_EXT 0x4 30 31 #define JZ_WDT_CLOCK_DIV_SHIFT 3 32 33 #define JZ_WDT_CLOCK_DIV_1 (0 << JZ_WDT_CLOCK_DIV_SHIFT) 34 #define JZ_WDT_CLOCK_DIV_4 (1 << JZ_WDT_CLOCK_DIV_SHIFT) 35 #define JZ_WDT_CLOCK_DIV_16 (2 << JZ_WDT_CLOCK_DIV_SHIFT) 36 #define JZ_WDT_CLOCK_DIV_64 (3 << JZ_WDT_CLOCK_DIV_SHIFT) 37 #define JZ_WDT_CLOCK_DIV_256 (4 << JZ_WDT_CLOCK_DIV_SHIFT) 38 #define JZ_WDT_CLOCK_DIV_1024 (5 << JZ_WDT_CLOCK_DIV_SHIFT) 39 40 #define DEFAULT_HEARTBEAT 5 41 #define MAX_HEARTBEAT 2048 42 43 static bool nowayout = WATCHDOG_NOWAYOUT; 44 module_param(nowayout, bool, 0); 45 MODULE_PARM_DESC(nowayout, 46 "Watchdog cannot be stopped once started (default=" 47 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 48 49 static unsigned int heartbeat = DEFAULT_HEARTBEAT; 50 module_param(heartbeat, uint, 0); 51 MODULE_PARM_DESC(heartbeat, 52 "Watchdog heartbeat period in seconds from 1 to " 53 __MODULE_STRING(MAX_HEARTBEAT) ", default " 54 __MODULE_STRING(DEFAULT_HEARTBEAT)); 55 56 struct jz4740_wdt_drvdata { 57 struct watchdog_device wdt; 58 void __iomem *base; 59 struct clk *rtc_clk; 60 }; 61 62 static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) 63 { 64 struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 65 66 writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); 67 return 0; 68 } 69 70 static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, 71 unsigned int new_timeout) 72 { 73 struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 74 unsigned int rtc_clk_rate; 75 unsigned int timeout_value; 76 unsigned short clock_div = JZ_WDT_CLOCK_DIV_1; 77 78 rtc_clk_rate = clk_get_rate(drvdata->rtc_clk); 79 80 timeout_value = rtc_clk_rate * new_timeout; 81 while (timeout_value > 0xffff) { 82 if (clock_div == JZ_WDT_CLOCK_DIV_1024) { 83 /* Requested timeout too high; 84 * use highest possible value. */ 85 timeout_value = 0xffff; 86 break; 87 } 88 timeout_value >>= 2; 89 clock_div += (1 << JZ_WDT_CLOCK_DIV_SHIFT); 90 } 91 92 writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 93 writew(clock_div, drvdata->base + JZ_REG_WDT_TIMER_CONTROL); 94 95 writew((u16)timeout_value, drvdata->base + JZ_REG_WDT_TIMER_DATA); 96 writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); 97 writew(clock_div | JZ_WDT_CLOCK_RTC, 98 drvdata->base + JZ_REG_WDT_TIMER_CONTROL); 99 100 writeb(0x1, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 101 102 wdt_dev->timeout = new_timeout; 103 return 0; 104 } 105 106 static int jz4740_wdt_start(struct watchdog_device *wdt_dev) 107 { 108 jz4740_timer_enable_watchdog(); 109 jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); 110 111 return 0; 112 } 113 114 static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) 115 { 116 struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 117 118 writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 119 jz4740_timer_disable_watchdog(); 120 121 return 0; 122 } 123 124 static int jz4740_wdt_restart(struct watchdog_device *wdt_dev, 125 unsigned long action, void *data) 126 { 127 wdt_dev->timeout = 0; 128 jz4740_wdt_start(wdt_dev); 129 return 0; 130 } 131 132 static const struct watchdog_info jz4740_wdt_info = { 133 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 134 .identity = "jz4740 Watchdog", 135 }; 136 137 static const struct watchdog_ops jz4740_wdt_ops = { 138 .owner = THIS_MODULE, 139 .start = jz4740_wdt_start, 140 .stop = jz4740_wdt_stop, 141 .ping = jz4740_wdt_ping, 142 .set_timeout = jz4740_wdt_set_timeout, 143 .restart = jz4740_wdt_restart, 144 }; 145 146 #ifdef CONFIG_OF 147 static const struct of_device_id jz4740_wdt_of_matches[] = { 148 { .compatible = "ingenic,jz4740-watchdog", }, 149 { .compatible = "ingenic,jz4780-watchdog", }, 150 { /* sentinel */ } 151 }; 152 MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches); 153 #endif 154 155 static int jz4740_wdt_probe(struct platform_device *pdev) 156 { 157 struct device *dev = &pdev->dev; 158 struct jz4740_wdt_drvdata *drvdata; 159 struct watchdog_device *jz4740_wdt; 160 int ret; 161 162 drvdata = devm_kzalloc(dev, sizeof(struct jz4740_wdt_drvdata), 163 GFP_KERNEL); 164 if (!drvdata) 165 return -ENOMEM; 166 167 if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) 168 heartbeat = DEFAULT_HEARTBEAT; 169 170 jz4740_wdt = &drvdata->wdt; 171 jz4740_wdt->info = &jz4740_wdt_info; 172 jz4740_wdt->ops = &jz4740_wdt_ops; 173 jz4740_wdt->timeout = heartbeat; 174 jz4740_wdt->min_timeout = 1; 175 jz4740_wdt->max_timeout = MAX_HEARTBEAT; 176 jz4740_wdt->parent = dev; 177 watchdog_set_nowayout(jz4740_wdt, nowayout); 178 watchdog_set_drvdata(jz4740_wdt, drvdata); 179 180 drvdata->base = devm_platform_ioremap_resource(pdev, 0); 181 if (IS_ERR(drvdata->base)) 182 return PTR_ERR(drvdata->base); 183 184 drvdata->rtc_clk = devm_clk_get(dev, "rtc"); 185 if (IS_ERR(drvdata->rtc_clk)) { 186 dev_err(dev, "cannot find RTC clock\n"); 187 return PTR_ERR(drvdata->rtc_clk); 188 } 189 190 return devm_watchdog_register_device(dev, &drvdata->wdt); 191 } 192 193 static struct platform_driver jz4740_wdt_driver = { 194 .probe = jz4740_wdt_probe, 195 .driver = { 196 .name = "jz4740-wdt", 197 .of_match_table = of_match_ptr(jz4740_wdt_of_matches), 198 }, 199 }; 200 201 module_platform_driver(jz4740_wdt_driver); 202 203 MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 204 MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 205 MODULE_LICENSE("GPL"); 206 MODULE_ALIAS("platform:jz4740-wdt"); 207