watchdog: Add a driver for the sunxi watchdog
[platform/kernel/u-boot.git] / drivers / watchdog / sunxi_wdt.c
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
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
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
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
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
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 };