1 /*-
2 * Copyright (c) 2017 Ian Lepore <ian@freebsd.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27 #include <sys/cdefs.h>
28 /*
29 * Driver for imx6 Secure Non-Volatile Storage system, which really means "all
30 * the stuff that's powered by a battery when main power is off". This includes
31 * realtime clock, tamper monitor, and power-management functions. Currently
32 * this driver provides only realtime clock support.
33 */
34
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/bus.h>
38 #include <sys/clock.h>
39 #include <sys/kernel.h>
40 #include <sys/module.h>
41 #include <machine/bus.h>
42
43 #include <dev/ofw/ofw_bus_subr.h>
44
45 #include "clock_if.h"
46 #include "syscon_if.h"
47
48 #define SNVS_LPCR 0x38 /* Control register */
49 #define LPCR_LPCALB_VAL_SHIFT 10 /* Calibration shift */
50 #define LPCR_LPCALB_VAL_MASK 0x1f /* Calibration mask */
51 #define LPCR_LPCALB_EN (1u << 8) /* Calibration enable */
52 #define LPCR_SRTC_ENV (1u << 0) /* RTC enabled/valid */
53
54 #define SNVS_LPSRTCMR 0x50 /* Counter MSB */
55 #define SNVS_LPSRTCLR 0x54 /* Counter LSB */
56
57 #define RTC_RESOLUTION_US (1000000 / 32768) /* 32khz clock */
58
59 /*
60 * The RTC is a 47-bit counter clocked at 32KHz and organized as a 32.15
61 * fixed-point binary value. Shifting by SBT_LSB bits translates between
62 * counter and sbintime values.
63 */
64 #define RTC_BITS 47
65 #define SBT_BITS 64
66 #define SBT_LSB (SBT_BITS - RTC_BITS)
67
68 struct snvs_softc {
69 device_t dev;
70 struct syscon *syscon;
71 uint32_t lpcr;
72 };
73
74 static struct ofw_compat_data compat_data[] = {
75 {"fsl,sec-v4.0-mon-rtc-lp", true},
76 {NULL, false}
77 };
78
79 static inline uint32_t
RD4(struct snvs_softc * sc,bus_size_t offset)80 RD4(struct snvs_softc *sc, bus_size_t offset)
81 {
82
83 return (SYSCON_READ_4(sc->syscon, offset));
84 }
85
86 static inline void
WR4(struct snvs_softc * sc,bus_size_t offset,uint32_t value)87 WR4(struct snvs_softc *sc, bus_size_t offset, uint32_t value)
88 {
89
90 SYSCON_WRITE_4(sc->syscon, offset, value);
91 }
92
93 static void
snvs_rtc_enable(struct snvs_softc * sc,bool enable)94 snvs_rtc_enable(struct snvs_softc *sc, bool enable)
95 {
96 uint32_t enbit;
97
98 if (enable)
99 sc->lpcr |= LPCR_SRTC_ENV;
100 else
101 sc->lpcr &= ~LPCR_SRTC_ENV;
102 WR4(sc, SNVS_LPCR, sc->lpcr);
103
104 /* Wait for the hardware to achieve the requested state. */
105 enbit = sc->lpcr & LPCR_SRTC_ENV;
106 while ((RD4(sc, SNVS_LPCR) & LPCR_SRTC_ENV) != enbit)
107 continue;
108 }
109
110 static int
snvs_gettime(device_t dev,struct timespec * ts)111 snvs_gettime(device_t dev, struct timespec *ts)
112 {
113 struct snvs_softc *sc;
114 sbintime_t counter1, counter2;
115
116 sc = device_get_softc(dev);
117
118 /* If the clock is not enabled and valid, we can't help. */
119 if (!(RD4(sc, SNVS_LPCR) & LPCR_SRTC_ENV)) {
120 return (EINVAL);
121 }
122
123 /*
124 * The counter is clocked asynchronously to cpu accesses; read and
125 * assemble the pieces of the counter until we get the same value twice.
126 * The counter is 47 bits, organized as a 32.15 binary fixed-point
127 * value. If we shift it up to the high order part of a 64-bit word it
128 * turns into an sbintime.
129 */
130 do {
131 counter1 = (uint64_t)RD4(sc, SNVS_LPSRTCMR) << (SBT_LSB + 32);
132 counter1 |= (uint64_t)RD4(sc, SNVS_LPSRTCLR) << (SBT_LSB);
133 counter2 = (uint64_t)RD4(sc, SNVS_LPSRTCMR) << (SBT_LSB + 32);
134 counter2 |= (uint64_t)RD4(sc, SNVS_LPSRTCLR) << (SBT_LSB);
135 } while (counter1 != counter2);
136
137 *ts = sbttots(counter1);
138
139 clock_dbgprint_ts(sc->dev, CLOCK_DBG_READ, ts);
140
141 return (0);
142 }
143
144 static int
snvs_settime(device_t dev,struct timespec * ts)145 snvs_settime(device_t dev, struct timespec *ts)
146 {
147 struct snvs_softc *sc;
148 sbintime_t sbt;
149
150 sc = device_get_softc(dev);
151
152 /*
153 * The hardware format is the same as sbt (with fewer fractional bits),
154 * so first convert the time to sbt. It takes two clock cycles for the
155 * counter to start after setting the enable bit, so add two SBT_LSBs to
156 * what we're about to set.
157 */
158 sbt = tstosbt(*ts);
159 sbt += 2 << SBT_LSB;
160 snvs_rtc_enable(sc, false);
161 WR4(sc, SNVS_LPSRTCMR, (uint32_t)(sbt >> (SBT_LSB + 32)));
162 WR4(sc, SNVS_LPSRTCLR, (uint32_t)(sbt >> (SBT_LSB)));
163 snvs_rtc_enable(sc, true);
164
165 clock_dbgprint_ts(sc->dev, CLOCK_DBG_WRITE, ts);
166
167 return (0);
168 }
169
170 static int
snvs_probe(device_t dev)171 snvs_probe(device_t dev)
172 {
173
174 if (!ofw_bus_status_okay(dev))
175 return (ENXIO);
176
177 if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
178 return (ENXIO);
179
180 device_set_desc(dev, "i.MX6 SNVS RTC");
181 return (BUS_PROBE_DEFAULT);
182 }
183
184 static int
snvs_attach(device_t dev)185 snvs_attach(device_t dev)
186 {
187 struct snvs_softc *sc;
188
189 sc = device_get_softc(dev);
190 sc->dev = dev;
191
192 if (syscon_get_handle_default(sc->dev, &sc->syscon) != 0) {
193 device_printf(sc->dev, "Cannot get syscon handle\n");
194 return (ENXIO);
195 }
196
197 clock_register(sc->dev, RTC_RESOLUTION_US);
198
199 return (0);
200 }
201
202 static int
snvs_detach(device_t dev)203 snvs_detach(device_t dev)
204 {
205 struct snvs_softc *sc;
206
207 sc = device_get_softc(dev);
208 clock_unregister(sc->dev);
209 return (0);
210 }
211
212 static device_method_t snvs_methods[] = {
213 DEVMETHOD(device_probe, snvs_probe),
214 DEVMETHOD(device_attach, snvs_attach),
215 DEVMETHOD(device_detach, snvs_detach),
216
217 /* clock_if methods */
218 DEVMETHOD(clock_gettime, snvs_gettime),
219 DEVMETHOD(clock_settime, snvs_settime),
220
221 DEVMETHOD_END
222 };
223
224 static driver_t snvs_driver = {
225 "snvs",
226 snvs_methods,
227 sizeof(struct snvs_softc),
228 };
229
230 DRIVER_MODULE(snvs, simplebus, snvs_driver, 0, 0);
231 SIMPLEBUS_PNP_INFO(compat_data);
232