Commit | Line | Data |
---|---|---|
22e1b8f6 CC |
1 | /* |
2 | * Meson Watchdog Driver | |
3 | * | |
4 | * Copyright (c) 2014 Carlo Caione | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU General Public License | |
8 | * as published by the Free Software Foundation; either version | |
9 | * 2 of the License, or (at your option) any later version. | |
10 | */ | |
11 | ||
12 | #include <linux/clk.h> | |
13 | #include <linux/delay.h> | |
14 | #include <linux/err.h> | |
15 | #include <linux/init.h> | |
16 | #include <linux/io.h> | |
17 | #include <linux/kernel.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/moduleparam.h> | |
22e1b8f6 CC |
20 | #include <linux/of.h> |
21 | #include <linux/platform_device.h> | |
22e1b8f6 CC |
22 | #include <linux/types.h> |
23 | #include <linux/watchdog.h> | |
24 | ||
25 | #define DRV_NAME "meson_wdt" | |
26 | ||
27 | #define MESON_WDT_TC 0x00 | |
28 | #define MESON_WDT_TC_EN BIT(22) | |
29 | #define MESON_WDT_TC_TM_MASK 0x3fffff | |
30 | #define MESON_WDT_DC_RESET (3 << 24) | |
31 | ||
32 | #define MESON_WDT_RESET 0x04 | |
33 | ||
34 | #define MESON_WDT_TIMEOUT 30 | |
35 | #define MESON_WDT_MIN_TIMEOUT 1 | |
36 | #define MESON_WDT_MAX_TIMEOUT (MESON_WDT_TC_TM_MASK / 100000) | |
37 | ||
38 | #define MESON_SEC_TO_TC(s) ((s) * 100000) | |
39 | ||
40 | static bool nowayout = WATCHDOG_NOWAYOUT; | |
41 | static unsigned int timeout = MESON_WDT_TIMEOUT; | |
42 | ||
43 | struct meson_wdt_dev { | |
44 | struct watchdog_device wdt_dev; | |
45 | void __iomem *wdt_base; | |
22e1b8f6 CC |
46 | }; |
47 | ||
1b6fd59a | 48 | static int meson_wdt_restart(struct watchdog_device *wdt_dev) |
22e1b8f6 | 49 | { |
1b6fd59a | 50 | struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); |
06980b24 | 51 | u32 tc_reboot = MESON_WDT_DC_RESET | MESON_WDT_TC_EN; |
22e1b8f6 CC |
52 | |
53 | while (1) { | |
54 | writel(tc_reboot, meson_wdt->wdt_base + MESON_WDT_TC); | |
55 | mdelay(5); | |
56 | } | |
57 | ||
1b6fd59a | 58 | return 0; |
22e1b8f6 CC |
59 | } |
60 | ||
61 | static int meson_wdt_ping(struct watchdog_device *wdt_dev) | |
62 | { | |
63 | struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); | |
64 | ||
65 | writel(0, meson_wdt->wdt_base + MESON_WDT_RESET); | |
66 | ||
67 | return 0; | |
68 | } | |
69 | ||
70 | static void meson_wdt_change_timeout(struct watchdog_device *wdt_dev, | |
71 | unsigned int timeout) | |
72 | { | |
73 | struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); | |
74 | u32 reg; | |
75 | ||
76 | reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); | |
77 | reg &= ~MESON_WDT_TC_TM_MASK; | |
78 | reg |= MESON_SEC_TO_TC(timeout); | |
79 | writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); | |
80 | } | |
81 | ||
82 | static int meson_wdt_set_timeout(struct watchdog_device *wdt_dev, | |
83 | unsigned int timeout) | |
84 | { | |
85 | wdt_dev->timeout = timeout; | |
86 | ||
87 | meson_wdt_change_timeout(wdt_dev, timeout); | |
88 | meson_wdt_ping(wdt_dev); | |
89 | ||
90 | return 0; | |
91 | } | |
92 | ||
93 | static int meson_wdt_stop(struct watchdog_device *wdt_dev) | |
94 | { | |
95 | struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); | |
96 | u32 reg; | |
97 | ||
98 | reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); | |
99 | reg &= ~MESON_WDT_TC_EN; | |
100 | writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); | |
101 | ||
102 | return 0; | |
103 | } | |
104 | ||
105 | static int meson_wdt_start(struct watchdog_device *wdt_dev) | |
106 | { | |
107 | struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); | |
108 | u32 reg; | |
109 | ||
110 | meson_wdt_change_timeout(wdt_dev, meson_wdt->wdt_dev.timeout); | |
111 | meson_wdt_ping(wdt_dev); | |
112 | ||
113 | reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); | |
114 | reg |= MESON_WDT_TC_EN; | |
115 | writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); | |
116 | ||
117 | return 0; | |
118 | } | |
119 | ||
120 | static const struct watchdog_info meson_wdt_info = { | |
121 | .identity = DRV_NAME, | |
122 | .options = WDIOF_SETTIMEOUT | | |
123 | WDIOF_KEEPALIVEPING | | |
124 | WDIOF_MAGICCLOSE, | |
125 | }; | |
126 | ||
127 | static const struct watchdog_ops meson_wdt_ops = { | |
128 | .owner = THIS_MODULE, | |
129 | .start = meson_wdt_start, | |
130 | .stop = meson_wdt_stop, | |
131 | .ping = meson_wdt_ping, | |
132 | .set_timeout = meson_wdt_set_timeout, | |
1b6fd59a | 133 | .restart = meson_wdt_restart, |
22e1b8f6 CC |
134 | }; |
135 | ||
136 | static int meson_wdt_probe(struct platform_device *pdev) | |
137 | { | |
138 | struct resource *res; | |
139 | struct meson_wdt_dev *meson_wdt; | |
140 | int err; | |
141 | ||
142 | meson_wdt = devm_kzalloc(&pdev->dev, sizeof(*meson_wdt), GFP_KERNEL); | |
143 | if (!meson_wdt) | |
144 | return -ENOMEM; | |
145 | ||
146 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
147 | meson_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); | |
148 | if (IS_ERR(meson_wdt->wdt_base)) | |
149 | return PTR_ERR(meson_wdt->wdt_base); | |
150 | ||
151 | meson_wdt->wdt_dev.parent = &pdev->dev; | |
152 | meson_wdt->wdt_dev.info = &meson_wdt_info; | |
153 | meson_wdt->wdt_dev.ops = &meson_wdt_ops; | |
154 | meson_wdt->wdt_dev.timeout = MESON_WDT_TIMEOUT; | |
155 | meson_wdt->wdt_dev.max_timeout = MESON_WDT_MAX_TIMEOUT; | |
156 | meson_wdt->wdt_dev.min_timeout = MESON_WDT_MIN_TIMEOUT; | |
157 | ||
158 | watchdog_set_drvdata(&meson_wdt->wdt_dev, meson_wdt); | |
159 | ||
160 | watchdog_init_timeout(&meson_wdt->wdt_dev, timeout, &pdev->dev); | |
161 | watchdog_set_nowayout(&meson_wdt->wdt_dev, nowayout); | |
1b6fd59a | 162 | watchdog_set_restart_priority(&meson_wdt->wdt_dev, 128); |
22e1b8f6 CC |
163 | |
164 | meson_wdt_stop(&meson_wdt->wdt_dev); | |
165 | ||
166 | err = watchdog_register_device(&meson_wdt->wdt_dev); | |
167 | if (err) | |
168 | return err; | |
169 | ||
170 | platform_set_drvdata(pdev, meson_wdt); | |
171 | ||
22e1b8f6 CC |
172 | dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", |
173 | meson_wdt->wdt_dev.timeout, nowayout); | |
174 | ||
175 | return 0; | |
176 | } | |
177 | ||
178 | static int meson_wdt_remove(struct platform_device *pdev) | |
179 | { | |
180 | struct meson_wdt_dev *meson_wdt = platform_get_drvdata(pdev); | |
181 | ||
22e1b8f6 CC |
182 | watchdog_unregister_device(&meson_wdt->wdt_dev); |
183 | ||
184 | return 0; | |
185 | } | |
186 | ||
187 | static void meson_wdt_shutdown(struct platform_device *pdev) | |
188 | { | |
189 | struct meson_wdt_dev *meson_wdt = platform_get_drvdata(pdev); | |
190 | ||
191 | meson_wdt_stop(&meson_wdt->wdt_dev); | |
192 | } | |
193 | ||
194 | static const struct of_device_id meson_wdt_dt_ids[] = { | |
195 | { .compatible = "amlogic,meson6-wdt" }, | |
196 | { /* sentinel */ } | |
197 | }; | |
198 | MODULE_DEVICE_TABLE(of, meson_wdt_dt_ids); | |
199 | ||
200 | static struct platform_driver meson_wdt_driver = { | |
201 | .probe = meson_wdt_probe, | |
202 | .remove = meson_wdt_remove, | |
203 | .shutdown = meson_wdt_shutdown, | |
204 | .driver = { | |
22e1b8f6 CC |
205 | .name = DRV_NAME, |
206 | .of_match_table = meson_wdt_dt_ids, | |
207 | }, | |
208 | }; | |
209 | ||
210 | module_platform_driver(meson_wdt_driver); | |
211 | ||
212 | module_param(timeout, uint, 0); | |
213 | MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); | |
214 | ||
215 | module_param(nowayout, bool, 0); | |
216 | MODULE_PARM_DESC(nowayout, | |
217 | "Watchdog cannot be stopped once started (default=" | |
218 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
219 | ||
220 | MODULE_LICENSE("GPL"); | |
221 | MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); | |
222 | MODULE_DESCRIPTION("Meson Watchdog Timer Driver"); |