1 /* $NetBSD: dwc_wdt.c,v 1.1 2023/04/16 16:51:38 jmcneill Exp $ */
2 
3 /*-
4  * Copyright (c) 2018, 2023 Jared McNeill <jmcneill@invisible.ca>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: dwc_wdt.c,v 1.1 2023/04/16 16:51:38 jmcneill Exp $");
31 
32 #include <sys/param.h>
33 #include <sys/bus.h>
34 #include <sys/device.h>
35 #include <sys/intr.h>
36 #include <sys/systm.h>
37 #include <sys/mutex.h>
38 #include <sys/wdog.h>
39 
40 #include <dev/sysmon/sysmonvar.h>
41 #include <dev/ic/dwc_wdt_var.h>
42 
43 #define   WDT_CR                                  0x00
44 #define    WDT_CR_RST_PULSE_LENGTH      __BITS(4,2)
45 #define    WDT_CR_RESP_MODE             __BIT(1)
46 #define    WDT_CR_WDT_EN                          __BIT(0)
47 
48 #define   WDT_TORR                      0x04
49 #define    WDT_TORR_TIMEOUT_PERIOD      __BITS(3,0)
50 
51 #define   WDT_CCVR                      0x08
52 
53 #define   WDT_CRR                                 0x0c
54 #define    WDT_CRR_CNT_RESTART                    __BITS(7,0)
55 #define     WDT_CRR_CNT_RESTART_MAGIC   0x76
56 
57 #define   WDT_STAT                      0x10
58 #define    WDT_STAT_WDT_STATUS                    __BIT(0)
59 
60 #define   WDT_EOI                                 0x14
61 #define    WDT_EOI_WDT_INT_CLR                    __BIT(0)
62 
63 static const uint32_t wdt_torr[] = {
64           0x0000ffff,
65           0x0001ffff,
66           0x0003ffff,
67           0x0007ffff,
68           0x000fffff,
69           0x001fffff,
70           0x003fffff,
71           0x007fffff,
72           0x00ffffff,
73           0x01ffffff,
74           0x03ffffff,
75           0x07ffffff,
76           0x0fffffff,
77           0x1fffffff,
78           0x3fffffff,
79           0x7fffffff,
80 };
81 
82 #define   DWCWDT_PERIOD_DEFAULT                   15
83 
84 #define RD4(sc, reg) \
85           bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
86 #define WR4(sc, reg, val) \
87           bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
88 
89 static int
dwcwdt_map_period(struct dwcwdt_softc * sc,u_int period,u_int * aperiod)90 dwcwdt_map_period(struct dwcwdt_softc *sc, u_int period,
91     u_int *aperiod)
92 {
93           int i;
94 
95           if (period == 0)
96                     return -1;
97 
98           for (i = 0; i < __arraycount(wdt_torr); i++) {
99                     const u_int ms = (u_int)((((uint64_t)wdt_torr[i] + 1) * 1000) / sc->sc_clkrate);
100                     if (ms >= period * 1000) {
101                               *aperiod = ms / 1000;
102                               return i;
103                     }
104           }
105 
106           return -1;
107 }
108 
109 static int
dwcwdt_tickle(struct sysmon_wdog * smw)110 dwcwdt_tickle(struct sysmon_wdog *smw)
111 {
112           struct dwcwdt_softc * const sc = smw->smw_cookie;
113           const uint32_t crr =
114               __SHIFTIN(WDT_CRR_CNT_RESTART_MAGIC, WDT_CRR_CNT_RESTART);
115 
116           WR4(sc, WDT_CRR, crr);
117 
118           return 0;
119 }
120 
121 static int
dwcwdt_setmode(struct sysmon_wdog * smw)122 dwcwdt_setmode(struct sysmon_wdog *smw)
123 {
124           struct dwcwdt_softc * const sc = smw->smw_cookie;
125           uint32_t cr, torr;
126           int intv;
127 
128           if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
129                     /* Watchdog can only be disarmed by a reset */
130                     return EIO;
131           }
132 
133           if (smw->smw_period == WDOG_PERIOD_DEFAULT)
134                     smw->smw_period = DWCWDT_PERIOD_DEFAULT;
135 
136           intv = dwcwdt_map_period(sc, smw->smw_period,
137               &sc->sc_smw.smw_period);
138           if (intv == -1)
139                     return EINVAL;
140 
141           torr = __SHIFTIN(intv, WDT_TORR_TIMEOUT_PERIOD);
142           WR4(sc, WDT_TORR, torr);
143           dwcwdt_tickle(smw);
144           cr = RD4(sc, WDT_CR);
145           cr &= ~WDT_CR_RESP_MODE;
146           cr |= WDT_CR_WDT_EN;
147           WR4(sc, WDT_CR, cr);
148 
149           return 0;
150 }
151 
152 void
dwcwdt_init(struct dwcwdt_softc * sc)153 dwcwdt_init(struct dwcwdt_softc *sc)
154 {
155           if (sc->sc_clkrate == 0) {
156                     aprint_error_dev(sc->sc_dev, "clock rate not specified\n");
157                     return;
158           }
159 
160           sc->sc_smw.smw_name = device_xname(sc->sc_dev);
161           sc->sc_smw.smw_cookie = sc;
162           sc->sc_smw.smw_period = DWCWDT_PERIOD_DEFAULT;
163           sc->sc_smw.smw_setmode = dwcwdt_setmode;
164           sc->sc_smw.smw_tickle = dwcwdt_tickle;
165 
166           aprint_normal_dev(sc->sc_dev,
167               "default watchdog period is %u seconds\n",
168               sc->sc_smw.smw_period);
169 
170           if (sysmon_wdog_register(&sc->sc_smw) != 0) {
171                     aprint_error_dev(sc->sc_dev, "couldn't register with sysmon\n");
172           }
173 }
174