Merge tag 'mips_5.14_1' of git://git.kernel.org/pub/scm/linux/kernel/git/mips/linux
[linux-2.6-microblaze.git] / drivers / watchdog / pic32-wdt.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * PIC32 watchdog driver
4  *
5  * Joshua Henderson <joshua.henderson@microchip.com>
6  * Copyright (c) 2016, Microchip Technology Inc.
7  */
8 #include <linux/clk.h>
9 #include <linux/device.h>
10 #include <linux/err.h>
11 #include <linux/io.h>
12 #include <linux/kernel.h>
13 #include <linux/module.h>
14 #include <linux/of.h>
15 #include <linux/of_device.h>
16 #include <linux/platform_device.h>
17 #include <linux/pm.h>
18 #include <linux/watchdog.h>
19
20 #include <asm/mach-pic32/pic32.h>
21
22 /* Watchdog Timer Registers */
23 #define WDTCON_REG              0x00
24
25 /* Watchdog Timer Control Register fields */
26 #define WDTCON_WIN_EN           BIT(0)
27 #define WDTCON_RMCS_MASK        0x0003
28 #define WDTCON_RMCS_SHIFT       0x0006
29 #define WDTCON_RMPS_MASK        0x001F
30 #define WDTCON_RMPS_SHIFT       0x0008
31 #define WDTCON_ON               BIT(15)
32 #define WDTCON_CLR_KEY          0x5743
33
34 /* Reset Control Register fields for watchdog */
35 #define RESETCON_TIMEOUT_IDLE   BIT(2)
36 #define RESETCON_TIMEOUT_SLEEP  BIT(3)
37 #define RESETCON_WDT_TIMEOUT    BIT(4)
38
39 struct pic32_wdt {
40         void __iomem    *regs;
41         void __iomem    *rst_base;
42         struct clk      *clk;
43 };
44
45 static inline bool pic32_wdt_is_win_enabled(struct pic32_wdt *wdt)
46 {
47         return !!(readl(wdt->regs + WDTCON_REG) & WDTCON_WIN_EN);
48 }
49
50 static inline u32 pic32_wdt_get_post_scaler(struct pic32_wdt *wdt)
51 {
52         u32 v = readl(wdt->regs + WDTCON_REG);
53
54         return (v >> WDTCON_RMPS_SHIFT) & WDTCON_RMPS_MASK;
55 }
56
57 static inline u32 pic32_wdt_get_clk_id(struct pic32_wdt *wdt)
58 {
59         u32 v = readl(wdt->regs + WDTCON_REG);
60
61         return (v >> WDTCON_RMCS_SHIFT) & WDTCON_RMCS_MASK;
62 }
63
64 static int pic32_wdt_bootstatus(struct pic32_wdt *wdt)
65 {
66         u32 v = readl(wdt->rst_base);
67
68         writel(RESETCON_WDT_TIMEOUT, PIC32_CLR(wdt->rst_base));
69
70         return v & RESETCON_WDT_TIMEOUT;
71 }
72
73 static u32 pic32_wdt_get_timeout_secs(struct pic32_wdt *wdt, struct device *dev)
74 {
75         unsigned long rate;
76         u32 period, ps, terminal;
77
78         rate = clk_get_rate(wdt->clk);
79
80         dev_dbg(dev, "wdt: clk_id %d, clk_rate %lu (prescale)\n",
81                 pic32_wdt_get_clk_id(wdt), rate);
82
83         /* default, prescaler of 32 (i.e. div-by-32) is implicit. */
84         rate >>= 5;
85         if (!rate)
86                 return 0;
87
88         /* calculate terminal count from postscaler. */
89         ps = pic32_wdt_get_post_scaler(wdt);
90         terminal = BIT(ps);
91
92         /* find time taken (in secs) to reach terminal count */
93         period = terminal / rate;
94         dev_dbg(dev,
95                 "wdt: clk_rate %lu (postscale) / terminal %d, timeout %dsec\n",
96                 rate, terminal, period);
97
98         return period;
99 }
100
101 static void pic32_wdt_keepalive(struct pic32_wdt *wdt)
102 {
103         /* write key through single half-word */
104         writew(WDTCON_CLR_KEY, wdt->regs + WDTCON_REG + 2);
105 }
106
107 static int pic32_wdt_start(struct watchdog_device *wdd)
108 {
109         struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
110
111         writel(WDTCON_ON, PIC32_SET(wdt->regs + WDTCON_REG));
112         pic32_wdt_keepalive(wdt);
113
114         return 0;
115 }
116
117 static int pic32_wdt_stop(struct watchdog_device *wdd)
118 {
119         struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
120
121         writel(WDTCON_ON, PIC32_CLR(wdt->regs + WDTCON_REG));
122
123         /*
124          * Cannot touch registers in the CPU cycle following clearing the
125          * ON bit.
126          */
127         nop();
128
129         return 0;
130 }
131
132 static int pic32_wdt_ping(struct watchdog_device *wdd)
133 {
134         struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
135
136         pic32_wdt_keepalive(wdt);
137
138         return 0;
139 }
140
141 static const struct watchdog_ops pic32_wdt_fops = {
142         .owner          = THIS_MODULE,
143         .start          = pic32_wdt_start,
144         .stop           = pic32_wdt_stop,
145         .ping           = pic32_wdt_ping,
146 };
147
148 static const struct watchdog_info pic32_wdt_ident = {
149         .options = WDIOF_KEEPALIVEPING |
150                         WDIOF_MAGICCLOSE | WDIOF_CARDRESET,
151         .identity = "PIC32 Watchdog",
152 };
153
154 static struct watchdog_device pic32_wdd = {
155         .info           = &pic32_wdt_ident,
156         .ops            = &pic32_wdt_fops,
157 };
158
159 static const struct of_device_id pic32_wdt_dt_ids[] = {
160         { .compatible = "microchip,pic32mzda-wdt", },
161         { /* sentinel */ }
162 };
163 MODULE_DEVICE_TABLE(of, pic32_wdt_dt_ids);
164
165 static void pic32_clk_disable_unprepare(void *data)
166 {
167         clk_disable_unprepare(data);
168 }
169
170 static int pic32_wdt_drv_probe(struct platform_device *pdev)
171 {
172         struct device *dev = &pdev->dev;
173         int ret;
174         struct watchdog_device *wdd = &pic32_wdd;
175         struct pic32_wdt *wdt;
176
177         wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
178         if (!wdt)
179                 return -ENOMEM;
180
181         wdt->regs = devm_platform_ioremap_resource(pdev, 0);
182         if (IS_ERR(wdt->regs))
183                 return PTR_ERR(wdt->regs);
184
185         wdt->rst_base = devm_ioremap(dev, PIC32_BASE_RESET, 0x10);
186         if (!wdt->rst_base)
187                 return -ENOMEM;
188
189         wdt->clk = devm_clk_get(dev, NULL);
190         if (IS_ERR(wdt->clk)) {
191                 dev_err(dev, "clk not found\n");
192                 return PTR_ERR(wdt->clk);
193         }
194
195         ret = clk_prepare_enable(wdt->clk);
196         if (ret) {
197                 dev_err(dev, "clk enable failed\n");
198                 return ret;
199         }
200         ret = devm_add_action_or_reset(dev, pic32_clk_disable_unprepare,
201                                        wdt->clk);
202         if (ret)
203                 return ret;
204
205         if (pic32_wdt_is_win_enabled(wdt)) {
206                 dev_err(dev, "windowed-clear mode is not supported.\n");
207                 return -ENODEV;
208         }
209
210         wdd->timeout = pic32_wdt_get_timeout_secs(wdt, dev);
211         if (!wdd->timeout) {
212                 dev_err(dev, "failed to read watchdog register timeout\n");
213                 return -EINVAL;
214         }
215
216         dev_info(dev, "timeout %d\n", wdd->timeout);
217
218         wdd->bootstatus = pic32_wdt_bootstatus(wdt) ? WDIOF_CARDRESET : 0;
219
220         watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT);
221         watchdog_set_drvdata(wdd, wdt);
222
223         ret = devm_watchdog_register_device(dev, wdd);
224         if (ret)
225                 return ret;
226
227         platform_set_drvdata(pdev, wdd);
228
229         return 0;
230 }
231
232 static struct platform_driver pic32_wdt_driver = {
233         .probe          = pic32_wdt_drv_probe,
234         .driver         = {
235                 .name           = "pic32-wdt",
236                 .of_match_table = of_match_ptr(pic32_wdt_dt_ids),
237         }
238 };
239
240 module_platform_driver(pic32_wdt_driver);
241
242 MODULE_AUTHOR("Joshua Henderson <joshua.henderson@microchip.com>");
243 MODULE_DESCRIPTION("Microchip PIC32 Watchdog Timer");
244 MODULE_LICENSE("GPL");