blob: 8eeac935760f91afcd85a8df7a0be7b81abdd52d [file] [log] [blame]
Samuel Holland65c982b2021-08-22 13:53:27 -05001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Derived from linux/drivers/watchdog/sunxi_wdt.c:
4 * Copyright (C) 2013 Carlo Caione
5 * Copyright (C) 2012 Henrik Nordstrom
6 */
7
8#include <dm.h>
9#include <wdt.h>
10#include <asm/io.h>
11#include <linux/delay.h>
Igor Prusovc3421ea2023-11-09 20:10:04 +030012#include <linux/time.h>
Samuel Holland65c982b2021-08-22 13:53:27 -050013
14#define WDT_MAX_TIMEOUT 16
15#define WDT_TIMEOUT_MASK 0xf
16
17#define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1))
18
19#define WDT_MODE_EN BIT(0)
20
21struct sunxi_wdt_reg {
22 u8 wdt_ctrl;
23 u8 wdt_cfg;
24 u8 wdt_mode;
25 u8 wdt_timeout_shift;
26 u8 wdt_reset_mask;
27 u8 wdt_reset_val;
28 u32 wdt_key_val;
29};
30
31struct sunxi_wdt_priv {
32 void __iomem *base;
33 const struct sunxi_wdt_reg *regs;
34};
35
36/* Map of timeout in seconds to register value */
37static const u8 wdt_timeout_map[1 + WDT_MAX_TIMEOUT] = {
38 [0] = 0x0,
39 [1] = 0x1,
40 [2] = 0x2,
41 [3] = 0x3,
42 [4] = 0x4,
43 [5] = 0x5,
44 [6] = 0x6,
45 [7] = 0x7,
46 [8] = 0x7,
47 [9] = 0x8,
48 [10] = 0x8,
49 [11] = 0x9,
50 [12] = 0x9,
51 [13] = 0xa,
52 [14] = 0xa,
53 [15] = 0xb,
54 [16] = 0xb,
55};
56
57static int sunxi_wdt_reset(struct udevice *dev)
58{
59 struct sunxi_wdt_priv *priv = dev_get_priv(dev);
60 const struct sunxi_wdt_reg *regs = priv->regs;
61 void __iomem *base = priv->base;
62
63 writel(WDT_CTRL_RELOAD, base + regs->wdt_ctrl);
64
65 return 0;
66}
67
68static int sunxi_wdt_start(struct udevice *dev, u64 timeout, ulong flags)
69{
70 struct sunxi_wdt_priv *priv = dev_get_priv(dev);
71 const struct sunxi_wdt_reg *regs = priv->regs;
72 void __iomem *base = priv->base;
73 u32 val;
74
75 timeout /= MSEC_PER_SEC;
76 if (timeout > WDT_MAX_TIMEOUT)
77 timeout = WDT_MAX_TIMEOUT;
78
79 /* Set system reset function */
80 val = readl(base + regs->wdt_cfg);
81 val &= ~regs->wdt_reset_mask;
82 val |= regs->wdt_reset_val;
83 val |= regs->wdt_key_val;
84 writel(val, base + regs->wdt_cfg);
85
86 /* Set timeout and enable watchdog */
87 val = readl(base + regs->wdt_mode);
88 val &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
89 val |= wdt_timeout_map[timeout] << regs->wdt_timeout_shift;
90 val |= WDT_MODE_EN;
91 val |= regs->wdt_key_val;
92 writel(val, base + regs->wdt_mode);
93
94 return sunxi_wdt_reset(dev);
95}
96
97static int sunxi_wdt_stop(struct udevice *dev)
98{
99 struct sunxi_wdt_priv *priv = dev_get_priv(dev);
100 const struct sunxi_wdt_reg *regs = priv->regs;
101 void __iomem *base = priv->base;
102
103 writel(regs->wdt_key_val, base + regs->wdt_mode);
104
105 return 0;
106}
107
108static int sunxi_wdt_expire_now(struct udevice *dev, ulong flags)
109{
110 int ret;
111
112 ret = sunxi_wdt_start(dev, 0, flags);
113 if (ret)
114 return ret;
115
116 mdelay(500);
117
118 return 0;
119}
120
121static const struct wdt_ops sunxi_wdt_ops = {
122 .reset = sunxi_wdt_reset,
123 .start = sunxi_wdt_start,
124 .stop = sunxi_wdt_stop,
125 .expire_now = sunxi_wdt_expire_now,
126};
127
128static const struct sunxi_wdt_reg sun4i_wdt_reg = {
129 .wdt_ctrl = 0x00,
130 .wdt_cfg = 0x04,
131 .wdt_mode = 0x04,
132 .wdt_timeout_shift = 3,
133 .wdt_reset_mask = 0x2,
134 .wdt_reset_val = 0x2,
135};
136
137static const struct sunxi_wdt_reg sun6i_wdt_reg = {
138 .wdt_ctrl = 0x10,
139 .wdt_cfg = 0x14,
140 .wdt_mode = 0x18,
141 .wdt_timeout_shift = 4,
142 .wdt_reset_mask = 0x3,
143 .wdt_reset_val = 0x1,
144};
145
146static const struct sunxi_wdt_reg sun20i_wdt_reg = {
147 .wdt_ctrl = 0x10,
148 .wdt_cfg = 0x14,
149 .wdt_mode = 0x18,
150 .wdt_timeout_shift = 4,
151 .wdt_reset_mask = 0x03,
152 .wdt_reset_val = 0x01,
153 .wdt_key_val = 0x16aa0000,
154};
155
156static const struct udevice_id sunxi_wdt_ids[] = {
157 { .compatible = "allwinner,sun4i-a10-wdt", .data = (ulong)&sun4i_wdt_reg },
158 { .compatible = "allwinner,sun6i-a31-wdt", .data = (ulong)&sun6i_wdt_reg },
159 { .compatible = "allwinner,sun20i-d1-wdt", .data = (ulong)&sun20i_wdt_reg },
160 { /* sentinel */ }
161};
162
163static int sunxi_wdt_probe(struct udevice *dev)
164{
165 struct sunxi_wdt_priv *priv = dev_get_priv(dev);
166
167 priv->base = dev_remap_addr(dev);
168 if (!priv->base)
169 return -EINVAL;
170
171 priv->regs = (void *)dev_get_driver_data(dev);
172 if (!priv->regs)
173 return -EINVAL;
174
175 sunxi_wdt_stop(dev);
176
177 return 0;
178}
179
180U_BOOT_DRIVER(sunxi_wdt) = {
181 .name = "sunxi_wdt",
182 .id = UCLASS_WDT,
183 .of_match = sunxi_wdt_ids,
184 .probe = sunxi_wdt_probe,
185 .priv_auto = sizeof(struct sunxi_wdt_priv),
186 .ops = &sunxi_wdt_ops,
187};