Commit | Line | Data |
---|---|---|
8ce6796d BX |
1 | /* |
2 | * watchdog driver for ZTE's zx2967 family | |
3 | * | |
4 | * Copyright (C) 2017 ZTE Ltd. | |
5 | * | |
6 | * Author: Baoyou Xie <baoyou.xie@linaro.org> | |
7 | * | |
8 | * License terms: GNU General Public License (GPL) version 2 | |
9 | */ | |
10 | ||
11 | #include <linux/clk.h> | |
12 | #include <linux/io.h> | |
13 | #include <linux/mfd/syscon.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/of_address.h> | |
16 | #include <linux/platform_device.h> | |
17 | #include <linux/regmap.h> | |
18 | #include <linux/reset.h> | |
19 | #include <linux/watchdog.h> | |
20 | ||
21 | #define ZX2967_WDT_CFG_REG 0x4 | |
22 | #define ZX2967_WDT_LOAD_REG 0x8 | |
23 | #define ZX2967_WDT_REFRESH_REG 0x18 | |
24 | #define ZX2967_WDT_START_REG 0x1c | |
25 | ||
26 | #define ZX2967_WDT_REFRESH_MASK GENMASK(5, 0) | |
27 | ||
28 | #define ZX2967_WDT_CFG_DIV(n) ((((n) & 0xff) - 1) << 8) | |
29 | #define ZX2967_WDT_START_EN 0x1 | |
30 | ||
31 | /* | |
32 | * Hardware magic number. | |
33 | * When watchdog reg is written, the lowest 16 bits are valid, but | |
34 | * the highest 16 bits should be always this number. | |
35 | */ | |
36 | #define ZX2967_WDT_WRITEKEY (0x1234 << 16) | |
37 | #define ZX2967_WDT_VAL_MASK GENMASK(15, 0) | |
38 | ||
39 | #define ZX2967_WDT_DIV_DEFAULT 16 | |
40 | #define ZX2967_WDT_DEFAULT_TIMEOUT 32 | |
41 | #define ZX2967_WDT_MIN_TIMEOUT 1 | |
42 | #define ZX2967_WDT_MAX_TIMEOUT 524 | |
43 | #define ZX2967_WDT_MAX_COUNT 0xffff | |
44 | ||
45 | #define ZX2967_WDT_CLK_FREQ 0x8000 | |
46 | ||
47 | #define ZX2967_WDT_FLAG_REBOOT_MON BIT(0) | |
48 | ||
49 | struct zx2967_wdt { | |
50 | struct watchdog_device wdt_device; | |
51 | void __iomem *reg_base; | |
52 | struct clk *clock; | |
53 | }; | |
54 | ||
55 | static inline u32 zx2967_wdt_readl(struct zx2967_wdt *wdt, u16 reg) | |
56 | { | |
57 | return readl_relaxed(wdt->reg_base + reg); | |
58 | } | |
59 | ||
60 | static inline void zx2967_wdt_writel(struct zx2967_wdt *wdt, u16 reg, u32 val) | |
61 | { | |
62 | writel_relaxed(val | ZX2967_WDT_WRITEKEY, wdt->reg_base + reg); | |
63 | } | |
64 | ||
65 | static void zx2967_wdt_refresh(struct zx2967_wdt *wdt) | |
66 | { | |
67 | u32 val; | |
68 | ||
69 | val = zx2967_wdt_readl(wdt, ZX2967_WDT_REFRESH_REG); | |
70 | /* | |
71 | * Bit 4-5, 1 and 2: refresh config info | |
72 | * Bit 2-3, 1 and 2: refresh counter | |
73 | * Bit 0-1, 1 and 2: refresh int-value | |
74 | * we shift each group value between 1 and 2 to refresh all data. | |
75 | */ | |
76 | val ^= ZX2967_WDT_REFRESH_MASK; | |
77 | zx2967_wdt_writel(wdt, ZX2967_WDT_REFRESH_REG, | |
78 | val & ZX2967_WDT_VAL_MASK); | |
79 | } | |
80 | ||
81 | static int | |
82 | zx2967_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) | |
83 | { | |
84 | struct zx2967_wdt *wdt = watchdog_get_drvdata(wdd); | |
85 | unsigned int divisor = ZX2967_WDT_DIV_DEFAULT; | |
86 | u32 count; | |
87 | ||
88 | count = timeout * ZX2967_WDT_CLK_FREQ; | |
89 | if (count > divisor * ZX2967_WDT_MAX_COUNT) | |
90 | divisor = DIV_ROUND_UP(count, ZX2967_WDT_MAX_COUNT); | |
91 | count = DIV_ROUND_UP(count, divisor); | |
92 | zx2967_wdt_writel(wdt, ZX2967_WDT_CFG_REG, | |
93 | ZX2967_WDT_CFG_DIV(divisor) & ZX2967_WDT_VAL_MASK); | |
94 | zx2967_wdt_writel(wdt, ZX2967_WDT_LOAD_REG, | |
95 | count & ZX2967_WDT_VAL_MASK); | |
96 | zx2967_wdt_refresh(wdt); | |
97 | wdd->timeout = (count * divisor) / ZX2967_WDT_CLK_FREQ; | |
98 | ||
99 | return 0; | |
100 | } | |
101 | ||
102 | static void __zx2967_wdt_start(struct zx2967_wdt *wdt) | |
103 | { | |
104 | u32 val; | |
105 | ||
106 | val = zx2967_wdt_readl(wdt, ZX2967_WDT_START_REG); | |
107 | val |= ZX2967_WDT_START_EN; | |
108 | zx2967_wdt_writel(wdt, ZX2967_WDT_START_REG, | |
109 | val & ZX2967_WDT_VAL_MASK); | |
110 | } | |
111 | ||
112 | static void __zx2967_wdt_stop(struct zx2967_wdt *wdt) | |
113 | { | |
114 | u32 val; | |
115 | ||
116 | val = zx2967_wdt_readl(wdt, ZX2967_WDT_START_REG); | |
117 | val &= ~ZX2967_WDT_START_EN; | |
118 | zx2967_wdt_writel(wdt, ZX2967_WDT_START_REG, | |
119 | val & ZX2967_WDT_VAL_MASK); | |
120 | } | |
121 | ||
122 | static int zx2967_wdt_start(struct watchdog_device *wdd) | |
123 | { | |
124 | struct zx2967_wdt *wdt = watchdog_get_drvdata(wdd); | |
125 | ||
126 | zx2967_wdt_set_timeout(wdd, wdd->timeout); | |
127 | __zx2967_wdt_start(wdt); | |
128 | ||
129 | return 0; | |
130 | } | |
131 | ||
132 | static int zx2967_wdt_stop(struct watchdog_device *wdd) | |
133 | { | |
134 | struct zx2967_wdt *wdt = watchdog_get_drvdata(wdd); | |
135 | ||
136 | __zx2967_wdt_stop(wdt); | |
137 | ||
138 | return 0; | |
139 | } | |
140 | ||
141 | static int zx2967_wdt_keepalive(struct watchdog_device *wdd) | |
142 | { | |
143 | struct zx2967_wdt *wdt = watchdog_get_drvdata(wdd); | |
144 | ||
145 | zx2967_wdt_refresh(wdt); | |
146 | ||
147 | return 0; | |
148 | } | |
149 | ||
150 | #define ZX2967_WDT_OPTIONS \ | |
151 | (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) | |
152 | static const struct watchdog_info zx2967_wdt_ident = { | |
153 | .options = ZX2967_WDT_OPTIONS, | |
154 | .identity = "zx2967 watchdog", | |
155 | }; | |
156 | ||
935988c6 | 157 | static const struct watchdog_ops zx2967_wdt_ops = { |
8ce6796d BX |
158 | .owner = THIS_MODULE, |
159 | .start = zx2967_wdt_start, | |
160 | .stop = zx2967_wdt_stop, | |
161 | .ping = zx2967_wdt_keepalive, | |
162 | .set_timeout = zx2967_wdt_set_timeout, | |
163 | }; | |
164 | ||
165 | static void zx2967_wdt_reset_sysctrl(struct device *dev) | |
166 | { | |
167 | int ret; | |
168 | void __iomem *regmap; | |
169 | unsigned int offset, mask, config; | |
170 | struct of_phandle_args out_args; | |
171 | ||
172 | ret = of_parse_phandle_with_fixed_args(dev->of_node, | |
173 | "zte,wdt-reset-sysctrl", 3, 0, &out_args); | |
174 | if (ret) | |
175 | return; | |
176 | ||
177 | offset = out_args.args[0]; | |
178 | config = out_args.args[1]; | |
179 | mask = out_args.args[2]; | |
180 | ||
181 | regmap = syscon_node_to_regmap(out_args.np); | |
182 | if (IS_ERR(regmap)) { | |
183 | of_node_put(out_args.np); | |
184 | return; | |
185 | } | |
186 | ||
187 | regmap_update_bits(regmap, offset, mask, config); | |
188 | of_node_put(out_args.np); | |
189 | } | |
190 | ||
191 | static int zx2967_wdt_probe(struct platform_device *pdev) | |
192 | { | |
193 | struct device *dev = &pdev->dev; | |
194 | struct zx2967_wdt *wdt; | |
195 | struct resource *base; | |
196 | int ret; | |
197 | struct reset_control *rstc; | |
198 | ||
199 | wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); | |
200 | if (!wdt) | |
201 | return -ENOMEM; | |
202 | ||
203 | platform_set_drvdata(pdev, wdt); | |
204 | ||
205 | wdt->wdt_device.info = &zx2967_wdt_ident; | |
206 | wdt->wdt_device.ops = &zx2967_wdt_ops; | |
207 | wdt->wdt_device.timeout = ZX2967_WDT_DEFAULT_TIMEOUT; | |
208 | wdt->wdt_device.max_timeout = ZX2967_WDT_MAX_TIMEOUT; | |
209 | wdt->wdt_device.min_timeout = ZX2967_WDT_MIN_TIMEOUT; | |
210 | wdt->wdt_device.parent = &pdev->dev; | |
211 | ||
212 | base = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
213 | wdt->reg_base = devm_ioremap_resource(dev, base); | |
07441a7d | 214 | if (IS_ERR(wdt->reg_base)) |
8ce6796d | 215 | return PTR_ERR(wdt->reg_base); |
8ce6796d BX |
216 | |
217 | zx2967_wdt_reset_sysctrl(dev); | |
218 | ||
219 | wdt->clock = devm_clk_get(dev, NULL); | |
220 | if (IS_ERR(wdt->clock)) { | |
221 | dev_err(dev, "failed to find watchdog clock source\n"); | |
222 | return PTR_ERR(wdt->clock); | |
223 | } | |
224 | ||
225 | ret = clk_prepare_enable(wdt->clock); | |
226 | if (ret < 0) { | |
227 | dev_err(dev, "failed to enable clock\n"); | |
228 | return ret; | |
229 | } | |
230 | clk_set_rate(wdt->clock, ZX2967_WDT_CLK_FREQ); | |
231 | ||
232 | rstc = devm_reset_control_get(dev, NULL); | |
233 | if (IS_ERR(rstc)) { | |
234 | dev_err(dev, "failed to get rstc"); | |
235 | ret = PTR_ERR(rstc); | |
236 | goto err; | |
237 | } | |
238 | ||
239 | reset_control_assert(rstc); | |
240 | reset_control_deassert(rstc); | |
241 | ||
242 | watchdog_set_drvdata(&wdt->wdt_device, wdt); | |
243 | watchdog_init_timeout(&wdt->wdt_device, | |
244 | ZX2967_WDT_DEFAULT_TIMEOUT, dev); | |
245 | watchdog_set_nowayout(&wdt->wdt_device, WATCHDOG_NOWAYOUT); | |
246 | ||
247 | ret = watchdog_register_device(&wdt->wdt_device); | |
248 | if (ret) | |
249 | goto err; | |
250 | ||
251 | dev_info(dev, "watchdog enabled (timeout=%d sec, nowayout=%d)", | |
252 | wdt->wdt_device.timeout, WATCHDOG_NOWAYOUT); | |
253 | ||
254 | return 0; | |
255 | ||
256 | err: | |
257 | clk_disable_unprepare(wdt->clock); | |
258 | return ret; | |
259 | } | |
260 | ||
261 | static int zx2967_wdt_remove(struct platform_device *pdev) | |
262 | { | |
263 | struct zx2967_wdt *wdt = platform_get_drvdata(pdev); | |
264 | ||
265 | watchdog_unregister_device(&wdt->wdt_device); | |
266 | clk_disable_unprepare(wdt->clock); | |
267 | ||
268 | return 0; | |
269 | } | |
270 | ||
271 | static const struct of_device_id zx2967_wdt_match[] = { | |
272 | { .compatible = "zte,zx296718-wdt", }, | |
273 | {} | |
274 | }; | |
275 | MODULE_DEVICE_TABLE(of, zx2967_wdt_match); | |
276 | ||
277 | static struct platform_driver zx2967_wdt_driver = { | |
278 | .probe = zx2967_wdt_probe, | |
279 | .remove = zx2967_wdt_remove, | |
280 | .driver = { | |
281 | .name = "zx2967-wdt", | |
282 | .of_match_table = of_match_ptr(zx2967_wdt_match), | |
283 | }, | |
284 | }; | |
285 | module_platform_driver(zx2967_wdt_driver); | |
286 | ||
287 | MODULE_AUTHOR("Baoyou Xie <baoyou.xie@linaro.org>"); | |
288 | MODULE_DESCRIPTION("ZTE zx2967 Watchdog Device Driver"); | |
289 | MODULE_LICENSE("GPL v2"); |