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