1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Driver for the Renesas Window Watchdog Timer (WWDT) 4 * 5 * The WWDT can only be setup once after boot. Because we cannot know if this 6 * already happened in early boot stages, it is mandated that the firmware 7 * configures the watchdog. Linux then adapts according to the given setup. 8 * Note that this watchdog reports in the default configuration an overflow to 9 * the Error Control Module which then decides further actions. Or the WWDT is 10 * configured to generate an interrupt. 11 */ 12 13 #include <linux/bitfield.h> 14 #include <linux/clk.h> 15 #include <linux/interrupt.h> 16 #include <linux/io.h> 17 #include <linux/kernel.h> 18 #include <linux/module.h> 19 #include <linux/of.h> 20 #include <linux/platform_device.h> 21 #include <linux/watchdog.h> 22 23 #define WDTA0WDTE 0x00 24 #define WDTA0RUN BIT(7) 25 #define WDTA0_KEY 0x2c 26 27 #define WDTA0MD 0x0c 28 #define WDTA0OVF(x) FIELD_GET(GENMASK(6, 4), x) 29 #define WDTA0WIE BIT(3) 30 #define WDTA0ERM BIT(2) 31 #define WDTA0WS(x) FIELD_GET(GENMASK(1, 0), x) 32 33 struct wwdt_priv { 34 void __iomem *base; 35 struct watchdog_device wdev; 36 }; 37 38 static int wwdt_start(struct watchdog_device *wdev) 39 { 40 struct wwdt_priv *priv = container_of(wdev, struct wwdt_priv, wdev); 41 42 writeb(WDTA0RUN | WDTA0_KEY, priv->base + WDTA0WDTE); 43 return 0; 44 } 45 46 static const struct watchdog_info wwdt_ident = { 47 .options = WDIOF_KEEPALIVEPING | WDIOF_ALARMONLY, 48 .identity = "Renesas Window Watchdog", 49 }; 50 51 static const struct watchdog_ops wwdt_ops = { 52 .owner = THIS_MODULE, 53 .start = wwdt_start, 54 }; 55 56 static irqreturn_t wwdt_error_irq(int irq, void *dev_id) 57 { 58 struct device *dev = dev_id; 59 60 dev_warn(dev, "Watchdog timed out\n"); 61 return IRQ_HANDLED; 62 } 63 64 static irqreturn_t wwdt_pretimeout_irq(int irq, void *dev_id) 65 { 66 struct watchdog_device *wdev = dev_id; 67 68 watchdog_notify_pretimeout(wdev); 69 return IRQ_HANDLED; 70 } 71 72 static int wwdt_probe(struct platform_device *pdev) 73 { 74 struct device *dev = &pdev->dev; 75 struct wwdt_priv *priv; 76 struct watchdog_device *wdev; 77 struct clk *clk; 78 unsigned long rate; 79 unsigned int interval, window_size; 80 int ret; 81 u8 val; 82 83 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 84 if (!priv) 85 return -ENOMEM; 86 87 priv->base = devm_platform_ioremap_resource(pdev, 0); 88 if (IS_ERR(priv->base)) 89 return PTR_ERR(priv->base); 90 91 clk = devm_clk_get(dev, "cnt"); 92 if (IS_ERR(clk)) 93 return PTR_ERR(clk); 94 95 rate = clk_get_rate(clk); 96 if (!rate) 97 return -EINVAL; 98 99 wdev = &priv->wdev; 100 101 val = readb(priv->base + WDTA0WDTE); 102 if (val & WDTA0RUN) 103 set_bit(WDOG_HW_RUNNING, &wdev->status); 104 105 val = readb(priv->base + WDTA0MD); 106 interval = 1 << (9 + WDTA0OVF(val)); 107 /* size of the closed(!) window per mille */ 108 window_size = 250 * (3 - WDTA0WS(val)); 109 110 wdev->info = &wwdt_ident; 111 wdev->ops = &wwdt_ops; 112 wdev->parent = dev; 113 wdev->min_hw_heartbeat_ms = window_size * interval / rate; 114 wdev->max_hw_heartbeat_ms = 1000 * interval / rate; 115 wdev->timeout = DIV_ROUND_UP(wdev->max_hw_heartbeat_ms, 1000); 116 watchdog_set_nowayout(wdev, true); 117 118 if (!(val & WDTA0ERM)) { 119 ret = platform_get_irq_byname(pdev, "error"); 120 if (ret < 0) 121 return ret; 122 123 ret = devm_request_threaded_irq(dev, ret, NULL, wwdt_error_irq, 124 IRQF_ONESHOT, NULL, dev); 125 if (ret < 0) 126 return ret; 127 } 128 129 if (val & WDTA0WIE) { 130 ret = platform_get_irq_byname(pdev, "pretimeout"); 131 if (ret < 0) 132 return ret; 133 134 ret = devm_request_threaded_irq(dev, ret, NULL, wwdt_pretimeout_irq, 135 IRQF_ONESHOT, NULL, wdev); 136 if (ret < 0) 137 return ret; 138 } 139 140 devm_watchdog_register_device(dev, wdev); 141 142 return 0; 143 } 144 145 static const struct of_device_id renesas_wwdt_ids[] = { 146 { .compatible = "renesas,rcar-gen3-wwdt", }, 147 { .compatible = "renesas,rcar-gen4-wwdt", }, 148 { /* sentinel */ } 149 }; 150 MODULE_DEVICE_TABLE(of, renesas_wwdt_ids); 151 152 static struct platform_driver renesas_wwdt_driver = { 153 .driver = { 154 .name = "renesas_wwdt", 155 .of_match_table = renesas_wwdt_ids, 156 }, 157 .probe = wwdt_probe, 158 }; 159 module_platform_driver(renesas_wwdt_driver); 160 161 MODULE_DESCRIPTION("Renesas Window Watchdog (WWDT) Driver"); 162 MODULE_LICENSE("GPL"); 163 MODULE_AUTHOR("Wolfram Sang <wsa+renesas@sang-engineering.com>"); 164