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