1 /* 2 * Watchdog driver for Renesas WDT watchdog 3 * 4 * Copyright (C) 2015-17 Wolfram Sang, Sang Engineering <wsa@sang-engineering.com> 5 * Copyright (C) 2015-17 Renesas Electronics Corporation 6 * 7 * This program is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License version 2 as published by 9 * the Free Software Foundation. 10 */ 11 #include <linux/bitops.h> 12 #include <linux/clk.h> 13 #include <linux/io.h> 14 #include <linux/kernel.h> 15 #include <linux/module.h> 16 #include <linux/of.h> 17 #include <linux/platform_device.h> 18 #include <linux/pm_runtime.h> 19 #include <linux/watchdog.h> 20 21 #define RWTCNT 0 22 #define RWTCSRA 4 23 #define RWTCSRA_WOVF BIT(4) 24 #define RWTCSRA_WRFLG BIT(5) 25 #define RWTCSRA_TME BIT(7) 26 #define RWTCSRB 8 27 28 #define RWDT_DEFAULT_TIMEOUT 60U 29 30 /* 31 * In probe, clk_rate is checked to be not more than 16 bit * biggest clock 32 * divider (12 bits). d is only a factor to fully utilize the WDT counter and 33 * will not exceed its 16 bits. Thus, no overflow, we stay below 32 bits. 34 */ 35 #define MUL_BY_CLKS_PER_SEC(p, d) \ 36 DIV_ROUND_UP((d) * (p)->clk_rate, clk_divs[(p)->cks]) 37 38 /* d is 16 bit, clk_divs 12 bit -> no 32 bit overflow */ 39 #define DIV_BY_CLKS_PER_SEC(p, d) ((d) * clk_divs[(p)->cks] / (p)->clk_rate) 40 41 static const unsigned int clk_divs[] = { 1, 4, 16, 32, 64, 128, 1024, 4096 }; 42 43 static bool nowayout = WATCHDOG_NOWAYOUT; 44 module_param(nowayout, bool, 0); 45 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 46 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 47 48 struct rwdt_priv { 49 void __iomem *base; 50 struct watchdog_device wdev; 51 unsigned long clk_rate; 52 u8 cks; 53 }; 54 55 static void rwdt_write(struct rwdt_priv *priv, u32 val, unsigned int reg) 56 { 57 if (reg == RWTCNT) 58 val |= 0x5a5a0000; 59 else 60 val |= 0xa5a5a500; 61 62 writel_relaxed(val, priv->base + reg); 63 } 64 65 static int rwdt_init_timeout(struct watchdog_device *wdev) 66 { 67 struct rwdt_priv *priv = watchdog_get_drvdata(wdev); 68 69 rwdt_write(priv, 65536 - MUL_BY_CLKS_PER_SEC(priv, wdev->timeout), RWTCNT); 70 71 return 0; 72 } 73 74 static int rwdt_start(struct watchdog_device *wdev) 75 { 76 struct rwdt_priv *priv = watchdog_get_drvdata(wdev); 77 78 pm_runtime_get_sync(wdev->parent); 79 80 rwdt_write(priv, 0, RWTCSRB); 81 rwdt_write(priv, priv->cks, RWTCSRA); 82 rwdt_init_timeout(wdev); 83 84 while (readb_relaxed(priv->base + RWTCSRA) & RWTCSRA_WRFLG) 85 cpu_relax(); 86 87 rwdt_write(priv, priv->cks | RWTCSRA_TME, RWTCSRA); 88 89 return 0; 90 } 91 92 static int rwdt_stop(struct watchdog_device *wdev) 93 { 94 struct rwdt_priv *priv = watchdog_get_drvdata(wdev); 95 96 rwdt_write(priv, priv->cks, RWTCSRA); 97 pm_runtime_put(wdev->parent); 98 99 return 0; 100 } 101 102 static unsigned int rwdt_get_timeleft(struct watchdog_device *wdev) 103 { 104 struct rwdt_priv *priv = watchdog_get_drvdata(wdev); 105 u16 val = readw_relaxed(priv->base + RWTCNT); 106 107 return DIV_BY_CLKS_PER_SEC(priv, 65536 - val); 108 } 109 110 static const struct watchdog_info rwdt_ident = { 111 .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT, 112 .identity = "Renesas WDT Watchdog", 113 }; 114 115 static const struct watchdog_ops rwdt_ops = { 116 .owner = THIS_MODULE, 117 .start = rwdt_start, 118 .stop = rwdt_stop, 119 .ping = rwdt_init_timeout, 120 .get_timeleft = rwdt_get_timeleft, 121 }; 122 123 static int rwdt_probe(struct platform_device *pdev) 124 { 125 struct rwdt_priv *priv; 126 struct resource *res; 127 struct clk *clk; 128 unsigned long clks_per_sec; 129 int ret, i; 130 131 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 132 if (!priv) 133 return -ENOMEM; 134 135 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 136 priv->base = devm_ioremap_resource(&pdev->dev, res); 137 if (IS_ERR(priv->base)) 138 return PTR_ERR(priv->base); 139 140 clk = devm_clk_get(&pdev->dev, NULL); 141 if (IS_ERR(clk)) 142 return PTR_ERR(clk); 143 144 pm_runtime_enable(&pdev->dev); 145 146 pm_runtime_get_sync(&pdev->dev); 147 priv->clk_rate = clk_get_rate(clk); 148 pm_runtime_put(&pdev->dev); 149 150 if (!priv->clk_rate) { 151 ret = -ENOENT; 152 goto out_pm_disable; 153 } 154 155 for (i = ARRAY_SIZE(clk_divs) - 1; i >= 0; i--) { 156 clks_per_sec = priv->clk_rate / clk_divs[i]; 157 if (clks_per_sec && clks_per_sec < 65536) { 158 priv->cks = i; 159 break; 160 } 161 } 162 163 if (i < 0) { 164 dev_err(&pdev->dev, "Can't find suitable clock divider\n"); 165 ret = -ERANGE; 166 goto out_pm_disable; 167 } 168 169 priv->wdev.info = &rwdt_ident, 170 priv->wdev.ops = &rwdt_ops, 171 priv->wdev.parent = &pdev->dev; 172 priv->wdev.min_timeout = 1; 173 priv->wdev.max_timeout = DIV_BY_CLKS_PER_SEC(priv, 65536); 174 priv->wdev.timeout = min(priv->wdev.max_timeout, RWDT_DEFAULT_TIMEOUT); 175 176 platform_set_drvdata(pdev, priv); 177 watchdog_set_drvdata(&priv->wdev, priv); 178 watchdog_set_nowayout(&priv->wdev, nowayout); 179 180 /* This overrides the default timeout only if DT configuration was found */ 181 ret = watchdog_init_timeout(&priv->wdev, 0, &pdev->dev); 182 if (ret) 183 dev_warn(&pdev->dev, "Specified timeout value invalid, using default\n"); 184 185 ret = watchdog_register_device(&priv->wdev); 186 if (ret < 0) 187 goto out_pm_disable; 188 189 return 0; 190 191 out_pm_disable: 192 pm_runtime_disable(&pdev->dev); 193 return ret; 194 } 195 196 static int rwdt_remove(struct platform_device *pdev) 197 { 198 struct rwdt_priv *priv = platform_get_drvdata(pdev); 199 200 watchdog_unregister_device(&priv->wdev); 201 pm_runtime_disable(&pdev->dev); 202 203 return 0; 204 } 205 206 /* 207 * This driver does also fit for R-Car Gen2 (r8a779[0-4]) WDT. However, for SMP 208 * to work there, one also needs a RESET (RST) driver which does not exist yet 209 * due to HW issues. This needs to be solved before adding compatibles here. 210 */ 211 static const struct of_device_id rwdt_ids[] = { 212 { .compatible = "renesas,rcar-gen3-wdt", }, 213 { /* sentinel */ } 214 }; 215 MODULE_DEVICE_TABLE(of, rwdt_ids); 216 217 static struct platform_driver rwdt_driver = { 218 .driver = { 219 .name = "renesas_wdt", 220 .of_match_table = rwdt_ids, 221 }, 222 .probe = rwdt_probe, 223 .remove = rwdt_remove, 224 }; 225 module_platform_driver(rwdt_driver); 226 227 MODULE_DESCRIPTION("Renesas WDT Watchdog Driver"); 228 MODULE_LICENSE("GPL v2"); 229 MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>"); 230