blob: 9d3a5b97845bd5a8a15c5e6d4a407ab08cc5dcdd [file] [log] [blame]
Sylver Bruneau22ac9232008-06-26 10:47:45 +02001/*
Nicolas Pitre3b937a7db2009-06-01 13:56:02 -04002 * drivers/watchdog/orion_wdt.c
Sylver Bruneau22ac9232008-06-26 10:47:45 +02003 *
Nicolas Pitre3b937a7db2009-06-01 13:56:02 -04004 * Watchdog driver for Orion/Kirkwood processors
Sylver Bruneau22ac9232008-06-26 10:47:45 +02005 *
6 * Author: Sylver Bruneau <sylver.bruneau@googlemail.com>
7 *
8 * This file is licensed under the terms of the GNU General Public
9 * License version 2. This program is licensed "as is" without any
10 * warranty of any kind, whether express or implied.
11 */
12
Joe Perches27c766a2012-02-15 15:06:19 -080013#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
14
Sylver Bruneau22ac9232008-06-26 10:47:45 +020015#include <linux/module.h>
16#include <linux/moduleparam.h>
17#include <linux/types.h>
18#include <linux/kernel.h>
Thomas Reitmayr9e058d42009-02-24 14:59:22 -080019#include <linux/platform_device.h>
Sylver Bruneau22ac9232008-06-26 10:47:45 +020020#include <linux/watchdog.h>
21#include <linux/init.h>
Ezequiel Garciae97662e2014-02-10 20:00:24 -030022#include <linux/interrupt.h>
Sylver Bruneau22ac9232008-06-26 10:47:45 +020023#include <linux/io.h>
Andrew Lunn4f04be62012-03-04 16:57:31 +010024#include <linux/clk.h>
Axel Lin0dd6e482012-03-26 11:14:29 +080025#include <linux/err.h>
Andrew Lunn1e7bad02012-06-10 15:20:06 +020026#include <linux/of.h>
Sylver Bruneau22ac9232008-06-26 10:47:45 +020027
Ezequiel Garcia868eb612014-02-10 20:00:25 -030028/* RSTOUT mask register physical address for Orion5x, Kirkwood and Dove */
29#define ORION_RSTOUT_MASK_OFFSET 0x20108
30
31/* Internal registers can be configured at any 1 MiB aligned address */
32#define INTERNAL_REGS_MASK ~(SZ_1M - 1)
33
Sylver Bruneau22ac9232008-06-26 10:47:45 +020034/*
35 * Watchdog timer block registers.
36 */
Jason Coopera855a7c2012-03-15 00:33:26 +000037#define TIMER_CTRL 0x0000
Axel Lin0dd6e482012-03-26 11:14:29 +080038#define WDT_EN 0x0010
Jason Coopera855a7c2012-03-15 00:33:26 +000039#define WDT_VAL 0x0024
Sylver Bruneau22ac9232008-06-26 10:47:45 +020040
Thomas Reitmayr9e058d42009-02-24 14:59:22 -080041#define WDT_MAX_CYCLE_COUNT 0xffffffff
Sylver Bruneau22ac9232008-06-26 10:47:45 +020042
Russell Kingfa142ff2013-06-18 17:19:32 +010043#define WDT_RESET_OUT_EN BIT(1)
Russell Kingfa142ff2013-06-18 17:19:32 +010044
Wim Van Sebroeck86a1e182012-03-05 16:51:11 +010045static bool nowayout = WATCHDOG_NOWAYOUT;
Thomas Reitmayr9e058d42009-02-24 14:59:22 -080046static int heartbeat = -1; /* module parameter (seconds) */
Ezequiel Garciab89a9c42014-02-10 20:00:27 -030047
48struct orion_watchdog {
49 struct watchdog_device wdt;
50 void __iomem *reg;
51 void __iomem *rstout;
52 unsigned long clk_rate;
53 struct clk *clk;
54};
Sylver Bruneau22ac9232008-06-26 10:47:45 +020055
Axel Lin0dd6e482012-03-26 11:14:29 +080056static int orion_wdt_ping(struct watchdog_device *wdt_dev)
Thomas Reitmayrdf6707b2009-02-20 19:44:59 +010057{
Ezequiel Garciab89a9c42014-02-10 20:00:27 -030058 struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
Thomas Reitmayrdf6707b2009-02-20 19:44:59 +010059 /* Reload watchdog duration */
Ezequiel Garciab89a9c42014-02-10 20:00:27 -030060 writel(dev->clk_rate * wdt_dev->timeout, dev->reg + WDT_VAL);
Axel Lin0dd6e482012-03-26 11:14:29 +080061 return 0;
Thomas Reitmayrdf6707b2009-02-20 19:44:59 +010062}
63
Axel Lin0dd6e482012-03-26 11:14:29 +080064static int orion_wdt_start(struct watchdog_device *wdt_dev)
Sylver Bruneau22ac9232008-06-26 10:47:45 +020065{
Ezequiel Garciab89a9c42014-02-10 20:00:27 -030066 struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
67
Sylver Bruneau22ac9232008-06-26 10:47:45 +020068 /* Set watchdog duration */
Ezequiel Garciab89a9c42014-02-10 20:00:27 -030069 writel(dev->clk_rate * wdt_dev->timeout, dev->reg + WDT_VAL);
Sylver Bruneau22ac9232008-06-26 10:47:45 +020070
Sylver Bruneau22ac9232008-06-26 10:47:45 +020071 /* Enable watchdog timer */
Ezequiel Garciab89a9c42014-02-10 20:00:27 -030072 atomic_io_modify(dev->reg + TIMER_CTRL, WDT_EN, WDT_EN);
Sylver Bruneau22ac9232008-06-26 10:47:45 +020073
74 /* Enable reset on watchdog */
Ezequiel Garciab89a9c42014-02-10 20:00:27 -030075 atomic_io_modify(dev->rstout, WDT_RESET_OUT_EN, WDT_RESET_OUT_EN);
76
Axel Lin0dd6e482012-03-26 11:14:29 +080077 return 0;
Sylver Bruneau22ac9232008-06-26 10:47:45 +020078}
79
Axel Lin0dd6e482012-03-26 11:14:29 +080080static int orion_wdt_stop(struct watchdog_device *wdt_dev)
Sylver Bruneau22ac9232008-06-26 10:47:45 +020081{
Ezequiel Garciab89a9c42014-02-10 20:00:27 -030082 struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
83
Sylver Bruneau22ac9232008-06-26 10:47:45 +020084 /* Disable reset on watchdog */
Ezequiel Garciab89a9c42014-02-10 20:00:27 -030085 atomic_io_modify(dev->rstout, WDT_RESET_OUT_EN, 0);
Sylver Bruneau22ac9232008-06-26 10:47:45 +020086
87 /* Disable watchdog timer */
Ezequiel Garciab89a9c42014-02-10 20:00:27 -030088 atomic_io_modify(dev->reg + TIMER_CTRL, WDT_EN, 0);
89
Axel Lin0dd6e482012-03-26 11:14:29 +080090 return 0;
Wim Van Sebroeck6d0f0df2008-10-02 09:32:45 +000091}
92
Ezequiel Garciab89a9c42014-02-10 20:00:27 -030093static int orion_wdt_enabled(struct orion_watchdog *dev)
Ezequiel Garciad9d0c532014-02-10 20:00:23 -030094{
95 bool enabled, running;
96
Ezequiel Garciab89a9c42014-02-10 20:00:27 -030097 enabled = readl(dev->rstout) & WDT_RESET_OUT_EN;
98 running = readl(dev->reg + TIMER_CTRL) & WDT_EN;
Ezequiel Garciad9d0c532014-02-10 20:00:23 -030099
100 return enabled && running;
101}
102
Axel Lin0dd6e482012-03-26 11:14:29 +0800103static unsigned int orion_wdt_get_timeleft(struct watchdog_device *wdt_dev)
Wim Van Sebroeck6d0f0df2008-10-02 09:32:45 +0000104{
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300105 struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
106 return readl(dev->reg + WDT_VAL) / dev->clk_rate;
Axel Lin0dd6e482012-03-26 11:14:29 +0800107}
108
109static int orion_wdt_set_timeout(struct watchdog_device *wdt_dev,
110 unsigned int timeout)
111{
112 wdt_dev->timeout = timeout;
Wim Van Sebroeck6d0f0df2008-10-02 09:32:45 +0000113 return 0;
Sylver Bruneau22ac9232008-06-26 10:47:45 +0200114}
115
Axel Lin0dd6e482012-03-26 11:14:29 +0800116static const struct watchdog_info orion_wdt_info = {
117 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
118 .identity = "Orion Watchdog",
Sylver Bruneau22ac9232008-06-26 10:47:45 +0200119};
120
Axel Lin0dd6e482012-03-26 11:14:29 +0800121static const struct watchdog_ops orion_wdt_ops = {
122 .owner = THIS_MODULE,
123 .start = orion_wdt_start,
124 .stop = orion_wdt_stop,
125 .ping = orion_wdt_ping,
126 .set_timeout = orion_wdt_set_timeout,
127 .get_timeleft = orion_wdt_get_timeleft,
Sylver Bruneau22ac9232008-06-26 10:47:45 +0200128};
129
Ezequiel Garciae97662e2014-02-10 20:00:24 -0300130static irqreturn_t orion_wdt_irq(int irq, void *devid)
131{
132 panic("Watchdog Timeout");
133 return IRQ_HANDLED;
134}
135
Ezequiel Garcia868eb612014-02-10 20:00:25 -0300136/*
137 * The original devicetree binding for this driver specified only
138 * one memory resource, so in order to keep DT backwards compatibility
139 * we try to fallback to a hardcoded register address, if the resource
140 * is missing from the devicetree.
141 */
142static void __iomem *orion_wdt_ioremap_rstout(struct platform_device *pdev,
143 phys_addr_t internal_regs)
144{
145 struct resource *res;
146 phys_addr_t rstout;
147
148 res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
149 if (res)
150 return devm_ioremap(&pdev->dev, res->start,
151 resource_size(res));
152
153 /* This workaround works only for "orion-wdt", DT-enabled */
154 if (!of_device_is_compatible(pdev->dev.of_node, "marvell,orion-wdt"))
155 return NULL;
156
157 rstout = internal_regs + ORION_RSTOUT_MASK_OFFSET;
158
159 WARN(1, FW_BUG "falling back to harcoded RSTOUT reg 0x%x\n", rstout);
160 return devm_ioremap(&pdev->dev, rstout, 0x4);
161}
162
Bill Pemberton2d991a12012-11-19 13:21:41 -0500163static int orion_wdt_probe(struct platform_device *pdev)
Thomas Reitmayr9e058d42009-02-24 14:59:22 -0800164{
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300165 struct orion_watchdog *dev;
166 unsigned int wdt_max_duration; /* (seconds) */
Jason Coopera855a7c2012-03-15 00:33:26 +0000167 struct resource *res;
Ezequiel Garciae97662e2014-02-10 20:00:24 -0300168 int ret, irq;
Thomas Reitmayr9e058d42009-02-24 14:59:22 -0800169
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300170 dev = devm_kzalloc(&pdev->dev, sizeof(struct orion_watchdog),
171 GFP_KERNEL);
172 if (!dev)
173 return -ENOMEM;
174
175 dev->wdt.info = &orion_wdt_info;
176 dev->wdt.ops = &orion_wdt_ops;
177 dev->wdt.min_timeout = 1;
178
179 dev->clk = devm_clk_get(&pdev->dev, NULL);
180 if (IS_ERR(dev->clk)) {
Axel Lin0dd6e482012-03-26 11:14:29 +0800181 dev_err(&pdev->dev, "Orion Watchdog missing clock\n");
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300182 return PTR_ERR(dev->clk);
Thomas Reitmayr9e058d42009-02-24 14:59:22 -0800183 }
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300184 ret = clk_prepare_enable(dev->clk);
Ezequiel Garciabb02c662014-02-10 20:00:20 -0300185 if (ret)
186 return ret;
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300187 dev->clk_rate = clk_get_rate(dev->clk);
Thomas Reitmayr9e058d42009-02-24 14:59:22 -0800188
Jason Coopera855a7c2012-03-15 00:33:26 +0000189 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
Ezequiel Garciabb02c662014-02-10 20:00:20 -0300190 if (!res) {
191 ret = -ENODEV;
192 goto disable_clk;
193 }
194
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300195 dev->reg = devm_ioremap(&pdev->dev, res->start,
196 resource_size(res));
197 if (!dev->reg) {
Ezequiel Garciabb02c662014-02-10 20:00:20 -0300198 ret = -ENOMEM;
199 goto disable_clk;
200 }
Thomas Reitmayr9e058d42009-02-24 14:59:22 -0800201
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300202 dev->rstout = orion_wdt_ioremap_rstout(pdev, res->start &
203 INTERNAL_REGS_MASK);
204 if (!dev->rstout) {
Ezequiel Garcia868eb612014-02-10 20:00:25 -0300205 ret = -ENODEV;
206 goto disable_clk;
207 }
208
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300209 wdt_max_duration = WDT_MAX_CYCLE_COUNT / dev->clk_rate;
Axel Lin0dd6e482012-03-26 11:14:29 +0800210
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300211 dev->wdt.timeout = wdt_max_duration;
212 dev->wdt.max_timeout = wdt_max_duration;
213 watchdog_init_timeout(&dev->wdt, heartbeat, &pdev->dev);
214
215 platform_set_drvdata(pdev, &dev->wdt);
216 watchdog_set_drvdata(&dev->wdt, dev);
Axel Lin0dd6e482012-03-26 11:14:29 +0800217
Ezequiel Garciad9d0c532014-02-10 20:00:23 -0300218 /*
219 * Let's make sure the watchdog is fully stopped, unless it's
220 * explicitly enabled. This may be the case if the module was
221 * removed and re-insterted, or if the bootloader explicitly
222 * set a running watchdog before booting the kernel.
223 */
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300224 if (!orion_wdt_enabled(dev))
225 orion_wdt_stop(&dev->wdt);
Ezequiel Garciad9d0c532014-02-10 20:00:23 -0300226
Ezequiel Garciae97662e2014-02-10 20:00:24 -0300227 /* Request the IRQ only after the watchdog is disabled */
228 irq = platform_get_irq(pdev, 0);
229 if (irq > 0) {
230 /*
231 * Not all supported platforms specify an interrupt for the
232 * watchdog, so let's make it optional.
233 */
234 ret = devm_request_irq(&pdev->dev, irq, orion_wdt_irq, 0,
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300235 pdev->name, dev);
Ezequiel Garciae97662e2014-02-10 20:00:24 -0300236 if (ret < 0) {
237 dev_err(&pdev->dev, "failed to request IRQ\n");
238 goto disable_clk;
239 }
240 }
241
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300242 watchdog_set_nowayout(&dev->wdt, nowayout);
243 ret = watchdog_register_device(&dev->wdt);
Ezequiel Garciabb02c662014-02-10 20:00:20 -0300244 if (ret)
245 goto disable_clk;
Thomas Reitmayr9e058d42009-02-24 14:59:22 -0800246
Joe Perches27c766a2012-02-15 15:06:19 -0800247 pr_info("Initial timeout %d sec%s\n",
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300248 dev->wdt.timeout, nowayout ? ", nowayout" : "");
Thomas Reitmayr9e058d42009-02-24 14:59:22 -0800249 return 0;
Ezequiel Garciabb02c662014-02-10 20:00:20 -0300250
251disable_clk:
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300252 clk_disable_unprepare(dev->clk);
Ezequiel Garciabb02c662014-02-10 20:00:20 -0300253 return ret;
Thomas Reitmayr9e058d42009-02-24 14:59:22 -0800254}
255
Bill Pemberton4b12b892012-11-19 13:26:24 -0500256static int orion_wdt_remove(struct platform_device *pdev)
Sylver Bruneau22ac9232008-06-26 10:47:45 +0200257{
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300258 struct watchdog_device *wdt_dev = platform_get_drvdata(pdev);
259 struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
260
261 watchdog_unregister_device(wdt_dev);
262 clk_disable_unprepare(dev->clk);
Axel Lin0dd6e482012-03-26 11:14:29 +0800263 return 0;
Sylver Bruneau22ac9232008-06-26 10:47:45 +0200264}
265
Nicolas Pitre3b937a7db2009-06-01 13:56:02 -0400266static void orion_wdt_shutdown(struct platform_device *pdev)
Thomas Reitmayrdf6707b2009-02-20 19:44:59 +0100267{
Ezequiel Garciab89a9c42014-02-10 20:00:27 -0300268 struct watchdog_device *wdt_dev = platform_get_drvdata(pdev);
269 orion_wdt_stop(wdt_dev);
Thomas Reitmayrdf6707b2009-02-20 19:44:59 +0100270}
271
Bill Pemberton1d131362012-11-19 13:24:05 -0500272static const struct of_device_id orion_wdt_of_match_table[] = {
Andrew Lunn1e7bad02012-06-10 15:20:06 +0200273 { .compatible = "marvell,orion-wdt", },
274 {},
275};
276MODULE_DEVICE_TABLE(of, orion_wdt_of_match_table);
277
Nicolas Pitre3b937a7db2009-06-01 13:56:02 -0400278static struct platform_driver orion_wdt_driver = {
279 .probe = orion_wdt_probe,
Bill Pemberton82268712012-11-19 13:21:12 -0500280 .remove = orion_wdt_remove,
Nicolas Pitre3b937a7db2009-06-01 13:56:02 -0400281 .shutdown = orion_wdt_shutdown,
Thomas Reitmayr9e058d42009-02-24 14:59:22 -0800282 .driver = {
283 .owner = THIS_MODULE,
Nicolas Pitre3b937a7db2009-06-01 13:56:02 -0400284 .name = "orion_wdt",
Sachin Kamat85eee812013-09-30 10:12:51 +0530285 .of_match_table = orion_wdt_of_match_table,
Thomas Reitmayr9e058d42009-02-24 14:59:22 -0800286 },
287};
288
Axel Linb8ec6112011-11-29 13:56:27 +0800289module_platform_driver(orion_wdt_driver);
Sylver Bruneau22ac9232008-06-26 10:47:45 +0200290
291MODULE_AUTHOR("Sylver Bruneau <sylver.bruneau@googlemail.com>");
Nicolas Pitre3b937a7db2009-06-01 13:56:02 -0400292MODULE_DESCRIPTION("Orion Processor Watchdog");
Sylver Bruneau22ac9232008-06-26 10:47:45 +0200293
294module_param(heartbeat, int, 0);
Thomas Reitmayrdf6707b2009-02-20 19:44:59 +0100295MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds");
Sylver Bruneau22ac9232008-06-26 10:47:45 +0200296
Wim Van Sebroeck86a1e182012-03-05 16:51:11 +0100297module_param(nowayout, bool, 0);
Thomas Reitmayrdf6707b2009-02-20 19:44:59 +0100298MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
299 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
Sylver Bruneau22ac9232008-06-26 10:47:45 +0200300
301MODULE_LICENSE("GPL");
Lubomir Rintelf3ea7332013-01-04 15:06:28 +0100302MODULE_ALIAS("platform:orion_wdt");