1*6e54367aSthorpej /* $NetBSD: sunxi_rtc.c,v 1.10 2021/01/27 03:10:20 thorpej Exp $ */
21c0653e6Sjmcneill
31c0653e6Sjmcneill /*-
41c0653e6Sjmcneill * Copyright (c) 2014-2017 Jared McNeill <jmcneill@invisible.ca>
51c0653e6Sjmcneill * All rights reserved.
61c0653e6Sjmcneill *
71c0653e6Sjmcneill * Redistribution and use in source and binary forms, with or without
81c0653e6Sjmcneill * modification, are permitted provided that the following conditions
91c0653e6Sjmcneill * are met:
101c0653e6Sjmcneill * 1. Redistributions of source code must retain the above copyright
111c0653e6Sjmcneill * notice, this list of conditions and the following disclaimer.
121c0653e6Sjmcneill * 2. Redistributions in binary form must reproduce the above copyright
131c0653e6Sjmcneill * notice, this list of conditions and the following disclaimer in the
141c0653e6Sjmcneill * documentation and/or other materials provided with the distribution.
151c0653e6Sjmcneill *
161c0653e6Sjmcneill * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
171c0653e6Sjmcneill * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
181c0653e6Sjmcneill * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
191c0653e6Sjmcneill * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
201c0653e6Sjmcneill * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
211c0653e6Sjmcneill * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
221c0653e6Sjmcneill * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
231c0653e6Sjmcneill * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
241c0653e6Sjmcneill * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
251c0653e6Sjmcneill * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
261c0653e6Sjmcneill * SUCH DAMAGE.
271c0653e6Sjmcneill */
281c0653e6Sjmcneill
291c0653e6Sjmcneill #include <sys/cdefs.h>
30*6e54367aSthorpej __KERNEL_RCSID(0, "$NetBSD: sunxi_rtc.c,v 1.10 2021/01/27 03:10:20 thorpej Exp $");
311c0653e6Sjmcneill
321c0653e6Sjmcneill #include <sys/param.h>
331c0653e6Sjmcneill #include <sys/bus.h>
341c0653e6Sjmcneill #include <sys/device.h>
351c0653e6Sjmcneill #include <sys/intr.h>
361c0653e6Sjmcneill #include <sys/systm.h>
371c0653e6Sjmcneill #include <sys/mutex.h>
381c0653e6Sjmcneill
391c0653e6Sjmcneill #include <dev/clock_subr.h>
40a8bc2d90Sthorpej #include <dev/clk/clk_backend.h>
411c0653e6Sjmcneill
421c0653e6Sjmcneill #include <dev/fdt/fdtvar.h>
431c0653e6Sjmcneill
44a62bda20Sjmcneill #define SUN4I_RTC_YY_MM_DD_REG 0x04
45a62bda20Sjmcneill #define SUN4I_RTC_LEAP __BIT(22)
46a62bda20Sjmcneill #define SUN4I_RTC_YEAR __BITS(21,16)
47a62bda20Sjmcneill #define SUN4I_RTC_MONTH __BITS(11,8)
48a62bda20Sjmcneill #define SUN4I_RTC_DAY __BITS(4,0)
49a62bda20Sjmcneill #define SUN4I_RTC_HH_MM_SS_REG 0x08
50a62bda20Sjmcneill #define SUN4I_RTC_WK_NO __BITS(31,29)
51a62bda20Sjmcneill #define SUN4I_RTC_HOUR __BITS(20,16)
52a62bda20Sjmcneill #define SUN4I_RTC_MINUTE __BITS(13,8)
53a62bda20Sjmcneill #define SUN4I_RTC_SECOND __BITS(5,0)
54a62bda20Sjmcneill #define SUN4I_RTC_BASE_YEAR 2010
55a62bda20Sjmcneill
568864ea80Sjmcneill #define SUN7I_RTC_YY_MM_DD_REG 0x04
578864ea80Sjmcneill #define SUN7I_RTC_LEAP __BIT(24)
588864ea80Sjmcneill #define SUN7I_RTC_YEAR __BITS(23,16)
598864ea80Sjmcneill #define SUN7I_RTC_MONTH __BITS(11,8)
608864ea80Sjmcneill #define SUN7I_RTC_DAY __BITS(4,0)
618864ea80Sjmcneill #define SUN7I_RTC_HH_MM_SS_REG 0x08
628864ea80Sjmcneill #define SUN7I_RTC_WK_NO __BITS(31,29)
638864ea80Sjmcneill #define SUN7I_RTC_HOUR __BITS(20,16)
648864ea80Sjmcneill #define SUN7I_RTC_MINUTE __BITS(13,8)
658864ea80Sjmcneill #define SUN7I_RTC_SECOND __BITS(5,0)
66a62bda20Sjmcneill #define SUN7I_RTC_BASE_YEAR 1970
678864ea80Sjmcneill
68a8bc2d90Sthorpej #define SUN6I_LOSC_CTRL_REG 0x00
69a8bc2d90Sthorpej #define SUN6I_LOSC_CTRL_KEY (0x16aa << 16)
70a8bc2d90Sthorpej #define SUN6I_LOSC_CTRL_AUTO_SWT_BYPASS __BIT(15)
71a8bc2d90Sthorpej #define SUN6I_LOSC_CTRL_ALM_DHMS_ACC __BIT(9)
72a8bc2d90Sthorpej #define SUN6I_LOSC_CTRL_RTC_HMS_ACC __BIT(8)
73a8bc2d90Sthorpej #define SUN6I_LOSC_CTRL_RTC_YMD_ACC __BIT(7)
74a8bc2d90Sthorpej #define SUN6I_LOSC_CTRL_EXT_LOSC_EN __BIT(4)
75a8bc2d90Sthorpej #define SUN6I_LOSC_CTRL_EXT_OSC __BIT(0)
76a8bc2d90Sthorpej
77a8bc2d90Sthorpej #define SUN6I_INTOSC_CLK_PRESCAL_REG 0x08
78a8bc2d90Sthorpej #define SUN6I_INTOSC_CLK_PRESCAL __BITS(0,4)
79a8bc2d90Sthorpej
808864ea80Sjmcneill #define SUN6I_RTC_YY_MM_DD_REG 0x10
818864ea80Sjmcneill #define SUN6I_RTC_LEAP __BIT(22)
828864ea80Sjmcneill #define SUN6I_RTC_YEAR __BITS(21,16)
838864ea80Sjmcneill #define SUN6I_RTC_MONTH __BITS(11,8)
848864ea80Sjmcneill #define SUN6I_RTC_DAY __BITS(4,0)
858864ea80Sjmcneill #define SUN6I_RTC_HH_MM_SS_REG 0x14
868864ea80Sjmcneill #define SUN6I_RTC_WK_NO __BITS(31,29)
878864ea80Sjmcneill #define SUN6I_RTC_HOUR __BITS(20,16)
888864ea80Sjmcneill #define SUN6I_RTC_MINUTE __BITS(13,8)
898864ea80Sjmcneill #define SUN6I_RTC_SECOND __BITS(5,0)
90a62bda20Sjmcneill #define SUN6I_RTC_BASE_YEAR 2000
911c0653e6Sjmcneill
92a8bc2d90Sthorpej #define SUN6I_RTC_LOSC_OUT_GATING_REG 0x60
93a8bc2d90Sthorpej #define SUN6I_RTC_LOSC_OUT_EN __BIT(0)
94a8bc2d90Sthorpej
95a62bda20Sjmcneill struct sunxi_rtc_config {
96a62bda20Sjmcneill bus_size_t yy_mm_dd_reg;
97a62bda20Sjmcneill uint32_t leap, year, month, day;
98a62bda20Sjmcneill bus_size_t hh_mm_ss_reg;
99a62bda20Sjmcneill uint32_t wk_no, hour, minute, second;
100a62bda20Sjmcneill u_int base_year;
101a8bc2d90Sthorpej
102a8bc2d90Sthorpej u_int iosc_rate;
103a8bc2d90Sthorpej u_int fixed_prescaler;
104a8bc2d90Sthorpej uint32_t ext_losc_en;
105a8bc2d90Sthorpej uint32_t auto_swt_bypass;
106a8bc2d90Sthorpej u_int flags;
107a62bda20Sjmcneill };
1081c0653e6Sjmcneill
109a8bc2d90Sthorpej #define SUNXI_RTC_F_HAS_VAR_PRESCALER __BIT(0)
110a8bc2d90Sthorpej
111a62bda20Sjmcneill static const struct sunxi_rtc_config sun4i_rtc_config = {
112a62bda20Sjmcneill .yy_mm_dd_reg = SUN4I_RTC_YY_MM_DD_REG,
113a62bda20Sjmcneill .leap = SUN4I_RTC_LEAP,
114a62bda20Sjmcneill .year = SUN4I_RTC_YEAR,
115a62bda20Sjmcneill .month = SUN4I_RTC_MONTH,
116a62bda20Sjmcneill .day = SUN4I_RTC_DAY,
117a62bda20Sjmcneill .hh_mm_ss_reg = SUN4I_RTC_HH_MM_SS_REG,
118a62bda20Sjmcneill .wk_no = SUN4I_RTC_WK_NO,
119a62bda20Sjmcneill .hour = SUN4I_RTC_HOUR,
120a62bda20Sjmcneill .minute = SUN4I_RTC_MINUTE,
121a62bda20Sjmcneill .second = SUN4I_RTC_SECOND,
122a62bda20Sjmcneill .base_year = SUN4I_RTC_BASE_YEAR,
123a62bda20Sjmcneill };
124a62bda20Sjmcneill
125a8bc2d90Sthorpej static const struct sunxi_rtc_config sun6i_a31_rtc_config = {
126a62bda20Sjmcneill .yy_mm_dd_reg = SUN6I_RTC_YY_MM_DD_REG,
127a62bda20Sjmcneill .leap = SUN6I_RTC_LEAP,
128a62bda20Sjmcneill .year = SUN6I_RTC_YEAR,
129a62bda20Sjmcneill .month = SUN6I_RTC_MONTH,
130a62bda20Sjmcneill .day = SUN6I_RTC_DAY,
131a62bda20Sjmcneill .hh_mm_ss_reg = SUN6I_RTC_HH_MM_SS_REG,
132a62bda20Sjmcneill .wk_no = SUN6I_RTC_WK_NO,
133a62bda20Sjmcneill .hour = SUN6I_RTC_HOUR,
134a62bda20Sjmcneill .minute = SUN6I_RTC_MINUTE,
135a62bda20Sjmcneill .second = SUN6I_RTC_SECOND,
136a62bda20Sjmcneill .base_year = SUN6I_RTC_BASE_YEAR,
137a8bc2d90Sthorpej
138a8bc2d90Sthorpej .iosc_rate = 667000,
139a8bc2d90Sthorpej .flags = SUNXI_RTC_F_HAS_VAR_PRESCALER,
140a62bda20Sjmcneill };
141a62bda20Sjmcneill
142a62bda20Sjmcneill static const struct sunxi_rtc_config sun7i_rtc_config = {
143a62bda20Sjmcneill .yy_mm_dd_reg = SUN7I_RTC_YY_MM_DD_REG,
144a62bda20Sjmcneill .leap = SUN7I_RTC_LEAP,
145a62bda20Sjmcneill .year = SUN7I_RTC_YEAR,
146a62bda20Sjmcneill .month = SUN7I_RTC_MONTH,
147a62bda20Sjmcneill .day = SUN7I_RTC_DAY,
148a62bda20Sjmcneill .hh_mm_ss_reg = SUN7I_RTC_HH_MM_SS_REG,
149a62bda20Sjmcneill .wk_no = SUN7I_RTC_WK_NO,
150a62bda20Sjmcneill .hour = SUN7I_RTC_HOUR,
151a62bda20Sjmcneill .minute = SUN7I_RTC_MINUTE,
152a62bda20Sjmcneill .second = SUN7I_RTC_SECOND,
153a62bda20Sjmcneill .base_year = SUN7I_RTC_BASE_YEAR,
1548864ea80Sjmcneill };
1558864ea80Sjmcneill
156a8bc2d90Sthorpej static const struct sunxi_rtc_config sun8i_a23_rtc_config = {
157a8bc2d90Sthorpej .yy_mm_dd_reg = SUN6I_RTC_YY_MM_DD_REG,
158a8bc2d90Sthorpej .leap = SUN6I_RTC_LEAP,
159a8bc2d90Sthorpej .year = SUN6I_RTC_YEAR,
160a8bc2d90Sthorpej .month = SUN6I_RTC_MONTH,
161a8bc2d90Sthorpej .day = SUN6I_RTC_DAY,
162a8bc2d90Sthorpej .hh_mm_ss_reg = SUN6I_RTC_HH_MM_SS_REG,
163a8bc2d90Sthorpej .wk_no = SUN6I_RTC_WK_NO,
164a8bc2d90Sthorpej .hour = SUN6I_RTC_HOUR,
165a8bc2d90Sthorpej .minute = SUN6I_RTC_MINUTE,
166a8bc2d90Sthorpej .second = SUN6I_RTC_SECOND,
167a8bc2d90Sthorpej .base_year = SUN6I_RTC_BASE_YEAR,
168a8bc2d90Sthorpej
169a8bc2d90Sthorpej .iosc_rate = 667000,
170a8bc2d90Sthorpej .flags = SUNXI_RTC_F_HAS_VAR_PRESCALER,
171a8bc2d90Sthorpej };
172a8bc2d90Sthorpej
173a8bc2d90Sthorpej static const struct sunxi_rtc_config sun8i_r40_rtc_config = {
174a8bc2d90Sthorpej .yy_mm_dd_reg = SUN6I_RTC_YY_MM_DD_REG,
175a8bc2d90Sthorpej .leap = SUN6I_RTC_LEAP,
176a8bc2d90Sthorpej .year = SUN6I_RTC_YEAR,
177a8bc2d90Sthorpej .month = SUN6I_RTC_MONTH,
178a8bc2d90Sthorpej .day = SUN6I_RTC_DAY,
179a8bc2d90Sthorpej .hh_mm_ss_reg = SUN6I_RTC_HH_MM_SS_REG,
180a8bc2d90Sthorpej .wk_no = SUN6I_RTC_WK_NO,
181a8bc2d90Sthorpej .hour = SUN6I_RTC_HOUR,
182a8bc2d90Sthorpej .minute = SUN6I_RTC_MINUTE,
183a8bc2d90Sthorpej .second = SUN6I_RTC_SECOND,
184a8bc2d90Sthorpej .base_year = SUN6I_RTC_BASE_YEAR,
185a8bc2d90Sthorpej
186a8bc2d90Sthorpej .iosc_rate = 16000000,
187a8bc2d90Sthorpej .fixed_prescaler = 512,
188a8bc2d90Sthorpej };
189a8bc2d90Sthorpej
190a8bc2d90Sthorpej static const struct sunxi_rtc_config sun8i_v3_rtc_config = {
191a8bc2d90Sthorpej .yy_mm_dd_reg = SUN6I_RTC_YY_MM_DD_REG,
192a8bc2d90Sthorpej .leap = SUN6I_RTC_LEAP,
193a8bc2d90Sthorpej .year = SUN6I_RTC_YEAR,
194a8bc2d90Sthorpej .month = SUN6I_RTC_MONTH,
195a8bc2d90Sthorpej .day = SUN6I_RTC_DAY,
196a8bc2d90Sthorpej .hh_mm_ss_reg = SUN6I_RTC_HH_MM_SS_REG,
197a8bc2d90Sthorpej .wk_no = SUN6I_RTC_WK_NO,
198a8bc2d90Sthorpej .hour = SUN6I_RTC_HOUR,
199a8bc2d90Sthorpej .minute = SUN6I_RTC_MINUTE,
200a8bc2d90Sthorpej .second = SUN6I_RTC_SECOND,
201a8bc2d90Sthorpej .base_year = SUN6I_RTC_BASE_YEAR,
202a8bc2d90Sthorpej
203a8bc2d90Sthorpej .iosc_rate = 32000,
204a8bc2d90Sthorpej };
205a8bc2d90Sthorpej
206a8bc2d90Sthorpej static const struct sunxi_rtc_config sun8i_h3_rtc_config = {
207a8bc2d90Sthorpej .yy_mm_dd_reg = SUN6I_RTC_YY_MM_DD_REG,
208a8bc2d90Sthorpej .leap = SUN6I_RTC_LEAP,
209a8bc2d90Sthorpej .year = SUN6I_RTC_YEAR,
210a8bc2d90Sthorpej .month = SUN6I_RTC_MONTH,
211a8bc2d90Sthorpej .day = SUN6I_RTC_DAY,
212a8bc2d90Sthorpej .hh_mm_ss_reg = SUN6I_RTC_HH_MM_SS_REG,
213a8bc2d90Sthorpej .wk_no = SUN6I_RTC_WK_NO,
214a8bc2d90Sthorpej .hour = SUN6I_RTC_HOUR,
215a8bc2d90Sthorpej .minute = SUN6I_RTC_MINUTE,
216a8bc2d90Sthorpej .second = SUN6I_RTC_SECOND,
217a8bc2d90Sthorpej .base_year = SUN6I_RTC_BASE_YEAR,
218a8bc2d90Sthorpej
219a8bc2d90Sthorpej .iosc_rate = 16000000,
220a8bc2d90Sthorpej .fixed_prescaler = 32,
221a8bc2d90Sthorpej .flags = SUNXI_RTC_F_HAS_VAR_PRESCALER,
222a8bc2d90Sthorpej };
223a8bc2d90Sthorpej
224a8bc2d90Sthorpej static const struct sunxi_rtc_config sun50i_h6_rtc_config = {
225a8bc2d90Sthorpej .yy_mm_dd_reg = SUN6I_RTC_YY_MM_DD_REG,
226a8bc2d90Sthorpej .leap = SUN6I_RTC_LEAP,
227a8bc2d90Sthorpej .year = SUN6I_RTC_YEAR,
228a8bc2d90Sthorpej .month = SUN6I_RTC_MONTH,
229a8bc2d90Sthorpej .day = SUN6I_RTC_DAY,
230a8bc2d90Sthorpej .hh_mm_ss_reg = SUN6I_RTC_HH_MM_SS_REG,
231a8bc2d90Sthorpej .wk_no = SUN6I_RTC_WK_NO,
232a8bc2d90Sthorpej .hour = SUN6I_RTC_HOUR,
233a8bc2d90Sthorpej .minute = SUN6I_RTC_MINUTE,
234a8bc2d90Sthorpej .second = SUN6I_RTC_SECOND,
235a8bc2d90Sthorpej .base_year = SUN6I_RTC_BASE_YEAR,
236a8bc2d90Sthorpej
237a8bc2d90Sthorpej .iosc_rate = 16000000,
238a8bc2d90Sthorpej .fixed_prescaler = 32,
239a8bc2d90Sthorpej .auto_swt_bypass = SUN6I_LOSC_CTRL_AUTO_SWT_BYPASS,
240a8bc2d90Sthorpej .ext_losc_en = SUN6I_LOSC_CTRL_EXT_LOSC_EN,
241a8bc2d90Sthorpej .flags = SUNXI_RTC_F_HAS_VAR_PRESCALER,
242a8bc2d90Sthorpej };
243a8bc2d90Sthorpej
244646c0f59Sthorpej static const struct device_compatible_entry compat_data[] = {
245646c0f59Sthorpej { .compat = "allwinner,sun4i-a10-rtc",
246646c0f59Sthorpej .data = &sun4i_rtc_config },
247646c0f59Sthorpej { .compat = "allwinner,sun6i-a31-rtc",
248646c0f59Sthorpej .data = &sun6i_a31_rtc_config },
249646c0f59Sthorpej { .compat = "allwinner,sun7i-a20-rtc",
250646c0f59Sthorpej .data = &sun7i_rtc_config },
251646c0f59Sthorpej { .compat = "allwinner,sun8i-a23-rtc",
252646c0f59Sthorpej .data = &sun8i_a23_rtc_config },
253646c0f59Sthorpej { .compat = "allwinner,sun8i-r40-rtc",
254646c0f59Sthorpej .data = &sun8i_r40_rtc_config },
255646c0f59Sthorpej { .compat = "allwinner,sun8i-v3-rtc",
256646c0f59Sthorpej .data = &sun8i_v3_rtc_config },
257646c0f59Sthorpej { .compat = "allwinner,sun8i-h3-rtc",
258646c0f59Sthorpej .data = &sun8i_h3_rtc_config },
259646c0f59Sthorpej { .compat = "allwinner,sun50i-h5-rtc",
260646c0f59Sthorpej .data = &sun8i_h3_rtc_config },
261646c0f59Sthorpej { .compat = "allwinner,sun50i-h6-rtc",
262646c0f59Sthorpej .data = &sun50i_h6_rtc_config },
263646c0f59Sthorpej
264ec189949Sthorpej DEVICE_COMPAT_EOL
2651c0653e6Sjmcneill };
2661c0653e6Sjmcneill
267a8bc2d90Sthorpej #define SUNXI_RTC_CLK_LOSC 0
268a8bc2d90Sthorpej #define SUNXI_RTC_CLK_LOSC_GATE 1
269a8bc2d90Sthorpej #define SUNXI_RTC_CLK_IOSC 2
270a8bc2d90Sthorpej #define SUNXI_RTC_NCLKS 3
271a8bc2d90Sthorpej
2721c0653e6Sjmcneill struct sunxi_rtc_softc {
2731c0653e6Sjmcneill device_t sc_dev;
2741c0653e6Sjmcneill bus_space_tag_t sc_bst;
2751c0653e6Sjmcneill bus_space_handle_t sc_bsh;
2761c0653e6Sjmcneill struct todr_chip_handle sc_todr;
277a62bda20Sjmcneill const struct sunxi_rtc_config *sc_conf;
278a8bc2d90Sthorpej
279a8bc2d90Sthorpej int sc_phandle;
280a8bc2d90Sthorpej
281a8bc2d90Sthorpej struct clk *sc_parent_clk; /* external oscillator */
282a8bc2d90Sthorpej
283a8bc2d90Sthorpej /*
284a8bc2d90Sthorpej * We export up to 3 clocks:
285a8bc2d90Sthorpej * [0] The local oscillator output
286a8bc2d90Sthorpej * [1] Gated version of [0]
287a8bc2d90Sthorpej * [2] The internal oscillator
288a8bc2d90Sthorpej *
289a8bc2d90Sthorpej * The local oscillator is driven either by the internal
290a8bc2d90Sthorpej * oscillator (less precise) or an external oscillator.
291a8bc2d90Sthorpej *
292a8bc2d90Sthorpej * Note that these are the order they appear in the device
293a8bc2d90Sthorpej * tree "clock-output-names" property for our node. Not
294a8bc2d90Sthorpej * all flavors of the Allwinner SoCs export all of these
295a8bc2d90Sthorpej * clocks, so we export only those that appear in the
296a8bc2d90Sthorpej * "clock-output-names" property.
297a8bc2d90Sthorpej */
298a8bc2d90Sthorpej const char *sc_clk_names[SUNXI_RTC_NCLKS];
299a8bc2d90Sthorpej struct clk sc_clks[SUNXI_RTC_NCLKS];
300a8bc2d90Sthorpej kmutex_t sc_clk_mutex;
301a8bc2d90Sthorpej struct clk_domain sc_clkdom;
3021c0653e6Sjmcneill };
3031c0653e6Sjmcneill
3041c0653e6Sjmcneill #define RTC_READ(sc, reg) \
3051c0653e6Sjmcneill bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
3061c0653e6Sjmcneill #define RTC_WRITE(sc, reg, val) \
3071c0653e6Sjmcneill bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
3081c0653e6Sjmcneill
3091c0653e6Sjmcneill static int sunxi_rtc_match(device_t, cfdata_t, void *);
3101c0653e6Sjmcneill static void sunxi_rtc_attach(device_t, device_t, void *);
3111c0653e6Sjmcneill
312a62bda20Sjmcneill static int sunxi_rtc_gettime(todr_chip_handle_t, struct clock_ymdhms *);
313a62bda20Sjmcneill static int sunxi_rtc_settime(todr_chip_handle_t, struct clock_ymdhms *);
3141c0653e6Sjmcneill
315a8bc2d90Sthorpej static struct clk *
316a8bc2d90Sthorpej sunxi_rtc_clk_get(void *, const char *);
317a8bc2d90Sthorpej static u_int sunxi_rtc_clk_get_rate(void *, struct clk *);
318a8bc2d90Sthorpej static int sunxi_rtc_clk_enable(void *, struct clk *);
319a8bc2d90Sthorpej static int sunxi_rtc_clk_disable(void *, struct clk *);
320a8bc2d90Sthorpej static int sunxi_rtc_clk_set_parent(void *, struct clk *, struct clk *);
321a8bc2d90Sthorpej static struct clk *
322a8bc2d90Sthorpej sunxi_rtc_clk_get_parent(void *, struct clk *);
323a8bc2d90Sthorpej
324a8bc2d90Sthorpej static const struct clk_funcs sunxi_rtc_clk_funcs = {
325a8bc2d90Sthorpej .get = sunxi_rtc_clk_get,
326a8bc2d90Sthorpej .get_rate = sunxi_rtc_clk_get_rate,
327a8bc2d90Sthorpej .enable = sunxi_rtc_clk_enable,
328a8bc2d90Sthorpej .disable = sunxi_rtc_clk_disable,
329a8bc2d90Sthorpej .set_parent = sunxi_rtc_clk_set_parent,
330a8bc2d90Sthorpej .get_parent = sunxi_rtc_clk_get_parent,
331a8bc2d90Sthorpej };
332a8bc2d90Sthorpej
333a8bc2d90Sthorpej static struct clk *
sunxi_rtc_clock_decode(device_t dev,int cc_phandle,const void * data,size_t len)334a8bc2d90Sthorpej sunxi_rtc_clock_decode(device_t dev, int cc_phandle, const void *data,
335a8bc2d90Sthorpej size_t len)
336a8bc2d90Sthorpej {
337a8bc2d90Sthorpej struct sunxi_rtc_softc * const sc = device_private(dev);
338a8bc2d90Sthorpej
339a8bc2d90Sthorpej if (len != 4)
340a8bc2d90Sthorpej return NULL;
341a8bc2d90Sthorpej
342a8bc2d90Sthorpej const u_int clock_id = be32dec(data);
343a8bc2d90Sthorpej if (clock_id >= SUNXI_RTC_NCLKS)
344a8bc2d90Sthorpej return NULL;
345a8bc2d90Sthorpej
346a8bc2d90Sthorpej if (sc->sc_clk_names[clock_id] == NULL)
347a8bc2d90Sthorpej return NULL;
348a8bc2d90Sthorpej
349a8bc2d90Sthorpej return &sc->sc_clks[clock_id];
350a8bc2d90Sthorpej }
351a8bc2d90Sthorpej
352a8bc2d90Sthorpej static const struct fdtbus_clock_controller_func sunxi_rtc_fdtclock_funcs = {
353a8bc2d90Sthorpej .decode = sunxi_rtc_clock_decode,
354a8bc2d90Sthorpej };
355a8bc2d90Sthorpej
3561c0653e6Sjmcneill CFATTACH_DECL_NEW(sunxi_rtc, sizeof(struct sunxi_rtc_softc),
3571c0653e6Sjmcneill sunxi_rtc_match, sunxi_rtc_attach, NULL, NULL);
3581c0653e6Sjmcneill
3591c0653e6Sjmcneill static int
sunxi_rtc_match(device_t parent,cfdata_t cf,void * aux)3601c0653e6Sjmcneill sunxi_rtc_match(device_t parent, cfdata_t cf, void *aux)
3611c0653e6Sjmcneill {
3621c0653e6Sjmcneill struct fdt_attach_args * const faa = aux;
3631c0653e6Sjmcneill
364*6e54367aSthorpej return of_compatible_match(faa->faa_phandle, compat_data);
3651c0653e6Sjmcneill }
3661c0653e6Sjmcneill
3671c0653e6Sjmcneill static void
sunxi_rtc_attach(device_t parent,device_t self,void * aux)3681c0653e6Sjmcneill sunxi_rtc_attach(device_t parent, device_t self, void *aux)
3691c0653e6Sjmcneill {
3701c0653e6Sjmcneill struct sunxi_rtc_softc * const sc = device_private(self);
3711c0653e6Sjmcneill struct fdt_attach_args * const faa = aux;
3721c0653e6Sjmcneill const int phandle = faa->faa_phandle;
3731c0653e6Sjmcneill bus_addr_t addr;
3741c0653e6Sjmcneill bus_size_t size;
3751c0653e6Sjmcneill
376a8bc2d90Sthorpej sc->sc_phandle = phandle;
377a8bc2d90Sthorpej
3781c0653e6Sjmcneill if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
3791c0653e6Sjmcneill aprint_error(": couldn't get registers\n");
3801c0653e6Sjmcneill return;
3811c0653e6Sjmcneill }
3821c0653e6Sjmcneill
3831c0653e6Sjmcneill sc->sc_dev = self;
3841c0653e6Sjmcneill sc->sc_bst = faa->faa_bst;
3851c0653e6Sjmcneill if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
3861c0653e6Sjmcneill aprint_error(": couldn't map registers\n");
3871c0653e6Sjmcneill return;
3881c0653e6Sjmcneill }
389*6e54367aSthorpej sc->sc_conf = of_compatible_lookup(phandle, compat_data)->data;
3901c0653e6Sjmcneill
3911c0653e6Sjmcneill aprint_naive("\n");
3921c0653e6Sjmcneill aprint_normal(": RTC\n");
3931c0653e6Sjmcneill
394a8bc2d90Sthorpej mutex_init(&sc->sc_clk_mutex, MUTEX_DEFAULT, IPL_HIGH);
395a8bc2d90Sthorpej
3961c0653e6Sjmcneill sc->sc_todr.cookie = sc;
397a62bda20Sjmcneill sc->sc_todr.todr_gettime_ymdhms = sunxi_rtc_gettime;
398a62bda20Sjmcneill sc->sc_todr.todr_settime_ymdhms = sunxi_rtc_settime;
3991c0653e6Sjmcneill
4001c0653e6Sjmcneill fdtbus_todr_attach(self, phandle, &sc->sc_todr);
401a8bc2d90Sthorpej
402a8bc2d90Sthorpej sc->sc_parent_clk = fdtbus_clock_get_index(phandle, 0);
403a8bc2d90Sthorpej
404a8bc2d90Sthorpej if (sc->sc_parent_clk == NULL || sc->sc_conf->iosc_rate == 0)
405a8bc2d90Sthorpej return;
406a8bc2d90Sthorpej
407a8bc2d90Sthorpej uint32_t reg = SUN6I_LOSC_CTRL_KEY;
408a8bc2d90Sthorpej if (sc->sc_conf->auto_swt_bypass) {
409a8bc2d90Sthorpej /*
410a8bc2d90Sthorpej * Disable auto-switching to the internal oscillator
411a8bc2d90Sthorpej * if the external oscillator disappears.
412a8bc2d90Sthorpej */
413a8bc2d90Sthorpej reg |= sc->sc_conf->auto_swt_bypass;
414a8bc2d90Sthorpej RTC_WRITE(sc, SUN6I_LOSC_CTRL_REG, reg);
415a8bc2d90Sthorpej }
416a8bc2d90Sthorpej
417a8bc2d90Sthorpej /* Switch to the external oscillator by default. */
418a8bc2d90Sthorpej reg |= SUN6I_LOSC_CTRL_EXT_OSC | sc->sc_conf->ext_losc_en;
419a8bc2d90Sthorpej RTC_WRITE(sc, SUN6I_LOSC_CTRL_REG, reg);
420a8bc2d90Sthorpej
421a8bc2d90Sthorpej sc->sc_clkdom.name = device_xname(sc->sc_dev);
422a8bc2d90Sthorpej sc->sc_clkdom.funcs = &sunxi_rtc_clk_funcs;
423a8bc2d90Sthorpej sc->sc_clkdom.priv = sc;
424a8bc2d90Sthorpej
425a8bc2d90Sthorpej unsigned int i;
426a8bc2d90Sthorpej for (i = 0; i < SUNXI_RTC_NCLKS; i++) {
427a8bc2d90Sthorpej sc->sc_clk_names[i] = fdtbus_get_string_index(phandle,
428a8bc2d90Sthorpej "clock-output-names", i);
429a8bc2d90Sthorpej if (sc->sc_clk_names[i] == NULL)
430a8bc2d90Sthorpej break;
431a8bc2d90Sthorpej sc->sc_clks[i].domain = &sc->sc_clkdom;
432a8bc2d90Sthorpej sc->sc_clks[i].name = sc->sc_clk_names[i];
433a8bc2d90Sthorpej clk_attach(&sc->sc_clks[i]);
434a8bc2d90Sthorpej }
435a8bc2d90Sthorpej
436a8bc2d90Sthorpej fdtbus_register_clock_controller(sc->sc_dev, sc->sc_phandle,
437a8bc2d90Sthorpej &sunxi_rtc_fdtclock_funcs);
4381c0653e6Sjmcneill }
4391c0653e6Sjmcneill
4401c0653e6Sjmcneill static int
sunxi_rtc_gettime(todr_chip_handle_t tch,struct clock_ymdhms * dt)441a62bda20Sjmcneill sunxi_rtc_gettime(todr_chip_handle_t tch, struct clock_ymdhms *dt)
4421c0653e6Sjmcneill {
4431c0653e6Sjmcneill struct sunxi_rtc_softc *sc = tch->cookie;
444a62bda20Sjmcneill const struct sunxi_rtc_config *conf = sc->sc_conf;
4451c0653e6Sjmcneill
446a62bda20Sjmcneill const uint32_t yymmdd = RTC_READ(sc, conf->yy_mm_dd_reg);
447a62bda20Sjmcneill const uint32_t hhmmss = RTC_READ(sc, conf->hh_mm_ss_reg);
4481c0653e6Sjmcneill
449a62bda20Sjmcneill dt->dt_year = __SHIFTOUT(yymmdd, conf->year) + conf->base_year;
450a62bda20Sjmcneill dt->dt_mon = __SHIFTOUT(yymmdd, conf->month);
451a62bda20Sjmcneill dt->dt_day = __SHIFTOUT(yymmdd, conf->day);
452a62bda20Sjmcneill dt->dt_wday = __SHIFTOUT(hhmmss, conf->wk_no);
453a62bda20Sjmcneill dt->dt_hour = __SHIFTOUT(hhmmss, conf->hour);
454a62bda20Sjmcneill dt->dt_min = __SHIFTOUT(hhmmss, conf->minute);
455a62bda20Sjmcneill dt->dt_sec = __SHIFTOUT(hhmmss, conf->second);
4561c0653e6Sjmcneill
4571c0653e6Sjmcneill return 0;
4581c0653e6Sjmcneill }
4591c0653e6Sjmcneill
4601c0653e6Sjmcneill static int
sunxi_rtc_settime(todr_chip_handle_t tch,struct clock_ymdhms * dt)461a62bda20Sjmcneill sunxi_rtc_settime(todr_chip_handle_t tch, struct clock_ymdhms *dt)
4621c0653e6Sjmcneill {
4631c0653e6Sjmcneill struct sunxi_rtc_softc *sc = tch->cookie;
464a62bda20Sjmcneill const struct sunxi_rtc_config *conf = sc->sc_conf;
4651c0653e6Sjmcneill uint32_t yymmdd, hhmmss, maxyear;
4661c0653e6Sjmcneill
4671c0653e6Sjmcneill /*
4681c0653e6Sjmcneill * Sanity check the date before writing it back
4691c0653e6Sjmcneill */
470a62bda20Sjmcneill if (dt->dt_year < conf->base_year) {
4712c613b24Schristos aprint_normal_dev(sc->sc_dev, "year pre the epoch: %" PRIu64
4722c613b24Schristos ", not writing back time\n", dt->dt_year);
4731c0653e6Sjmcneill return EIO;
4741c0653e6Sjmcneill }
475a62bda20Sjmcneill maxyear = __SHIFTOUT(0xffffffff, conf->year) + conf->base_year;
4761c0653e6Sjmcneill if (dt->dt_year > maxyear) {
4771c0653e6Sjmcneill aprint_normal_dev(sc->sc_dev, "year exceeds available field:"
4782c613b24Schristos " %" PRIu64 ", not writing back time\n", dt->dt_year);
4791c0653e6Sjmcneill return EIO;
4801c0653e6Sjmcneill }
4811c0653e6Sjmcneill
482a62bda20Sjmcneill yymmdd = __SHIFTIN(dt->dt_year - conf->base_year, conf->year);
483a62bda20Sjmcneill yymmdd |= __SHIFTIN(dt->dt_mon, conf->month);
484a62bda20Sjmcneill yymmdd |= __SHIFTIN(dt->dt_day, conf->day);
4851c0653e6Sjmcneill
486a62bda20Sjmcneill hhmmss = __SHIFTIN(dt->dt_wday, conf->wk_no);
487a62bda20Sjmcneill hhmmss |= __SHIFTIN(dt->dt_hour, conf->hour);
488a62bda20Sjmcneill hhmmss |= __SHIFTIN(dt->dt_min, conf->minute);
489a62bda20Sjmcneill hhmmss |= __SHIFTIN(dt->dt_sec, conf->second);
4901c0653e6Sjmcneill
491a62bda20Sjmcneill RTC_WRITE(sc, conf->yy_mm_dd_reg, yymmdd);
492a62bda20Sjmcneill RTC_WRITE(sc, conf->hh_mm_ss_reg, hhmmss);
4931c0653e6Sjmcneill
4941c0653e6Sjmcneill return 0;
4951c0653e6Sjmcneill }
496a8bc2d90Sthorpej
497a8bc2d90Sthorpej static struct clk *
sunxi_rtc_clk_get(void * priv,const char * name)498a8bc2d90Sthorpej sunxi_rtc_clk_get(void *priv, const char *name)
499a8bc2d90Sthorpej {
500a8bc2d90Sthorpej struct sunxi_rtc_softc * const sc = priv;
501a8bc2d90Sthorpej u_int i;
502a8bc2d90Sthorpej
503a8bc2d90Sthorpej for (i = 0; i < SUNXI_RTC_NCLKS; i++) {
504a8bc2d90Sthorpej if (sc->sc_clk_names[i] != NULL &&
505a8bc2d90Sthorpej strcmp(sc->sc_clk_names[i], name) == 0) {
506a8bc2d90Sthorpej return &sc->sc_clks[i];
507a8bc2d90Sthorpej }
508a8bc2d90Sthorpej }
509a8bc2d90Sthorpej
510a8bc2d90Sthorpej return NULL;
511a8bc2d90Sthorpej }
512a8bc2d90Sthorpej
513a8bc2d90Sthorpej static u_int
sunxi_rtc_clk_get_rate(void * priv,struct clk * clk)514a8bc2d90Sthorpej sunxi_rtc_clk_get_rate(void *priv, struct clk *clk)
515a8bc2d90Sthorpej {
516a8bc2d90Sthorpej struct sunxi_rtc_softc * const sc = priv;
517a8bc2d90Sthorpej
518a8bc2d90Sthorpej if (clk == &sc->sc_clks[SUNXI_RTC_CLK_IOSC]) {
519a8bc2d90Sthorpej KASSERT(sc->sc_clk_names[SUNXI_RTC_CLK_IOSC] != NULL);
520a8bc2d90Sthorpej KASSERT(sc->sc_conf->iosc_rate != 0);
521a8bc2d90Sthorpej return sc->sc_conf->iosc_rate;
522a8bc2d90Sthorpej }
523a8bc2d90Sthorpej
524a8bc2d90Sthorpej KASSERT(sc->sc_parent_clk != NULL);
525a8bc2d90Sthorpej u_int parent_rate = clk_get_rate(sc->sc_parent_clk);
526a8bc2d90Sthorpej uint32_t prescaler = 0;
527a8bc2d90Sthorpej
528a8bc2d90Sthorpej if (RTC_READ(sc, SUN6I_LOSC_CTRL_REG) & SUN6I_LOSC_CTRL_EXT_OSC)
529a8bc2d90Sthorpej return parent_rate;
530a8bc2d90Sthorpej
531a8bc2d90Sthorpej if (sc->sc_conf->fixed_prescaler)
532a8bc2d90Sthorpej parent_rate /= sc->sc_conf->fixed_prescaler;
533a8bc2d90Sthorpej
534a8bc2d90Sthorpej if (sc->sc_conf->flags & SUNXI_RTC_F_HAS_VAR_PRESCALER) {
535a8bc2d90Sthorpej prescaler =
536a8bc2d90Sthorpej __SHIFTOUT(RTC_READ(sc, SUN6I_INTOSC_CLK_PRESCAL_REG),
537a8bc2d90Sthorpej SUN6I_INTOSC_CLK_PRESCAL);
538a8bc2d90Sthorpej }
539a8bc2d90Sthorpej
540a8bc2d90Sthorpej return parent_rate / (prescaler + 1);
541a8bc2d90Sthorpej }
542a8bc2d90Sthorpej
543a8bc2d90Sthorpej static int
sunxi_rtc_clk_enable(void * priv,struct clk * clk)544a8bc2d90Sthorpej sunxi_rtc_clk_enable(void *priv, struct clk *clk)
545a8bc2d90Sthorpej {
546a8bc2d90Sthorpej struct sunxi_rtc_softc * const sc = priv;
547a8bc2d90Sthorpej
548a8bc2d90Sthorpej if (clk != &sc->sc_clks[SUNXI_RTC_CLK_LOSC_GATE])
549a8bc2d90Sthorpej return 0;
550a8bc2d90Sthorpej
551a8bc2d90Sthorpej mutex_enter(&sc->sc_clk_mutex);
552a8bc2d90Sthorpej uint32_t reg = RTC_READ(sc, SUN6I_RTC_LOSC_OUT_GATING_REG);
553a8bc2d90Sthorpej if ((reg & SUN6I_RTC_LOSC_OUT_EN) == 0) {
554a8bc2d90Sthorpej reg |= SUN6I_RTC_LOSC_OUT_EN;
555a8bc2d90Sthorpej RTC_WRITE(sc, SUN6I_RTC_LOSC_OUT_GATING_REG, reg);
556a8bc2d90Sthorpej }
557a8bc2d90Sthorpej mutex_exit(&sc->sc_clk_mutex);
558a8bc2d90Sthorpej
559a8bc2d90Sthorpej return 0;
560a8bc2d90Sthorpej }
561a8bc2d90Sthorpej
562a8bc2d90Sthorpej static int
sunxi_rtc_clk_disable(void * priv,struct clk * clk)563a8bc2d90Sthorpej sunxi_rtc_clk_disable(void *priv, struct clk *clk)
564a8bc2d90Sthorpej {
565a8bc2d90Sthorpej struct sunxi_rtc_softc * const sc = priv;
566a8bc2d90Sthorpej
567a8bc2d90Sthorpej if (clk != &sc->sc_clks[SUNXI_RTC_CLK_LOSC_GATE])
568a8bc2d90Sthorpej return EINVAL;
569a8bc2d90Sthorpej
570a8bc2d90Sthorpej mutex_enter(&sc->sc_clk_mutex);
571a8bc2d90Sthorpej uint32_t reg = RTC_READ(sc, SUN6I_RTC_LOSC_OUT_GATING_REG);
572a8bc2d90Sthorpej if (reg & SUN6I_RTC_LOSC_OUT_EN) {
573a8bc2d90Sthorpej reg &= ~SUN6I_RTC_LOSC_OUT_EN;
574a8bc2d90Sthorpej RTC_WRITE(sc, SUN6I_RTC_LOSC_OUT_GATING_REG, reg);
575a8bc2d90Sthorpej }
576a8bc2d90Sthorpej mutex_exit(&sc->sc_clk_mutex);
577a8bc2d90Sthorpej
578a8bc2d90Sthorpej return 0;
579a8bc2d90Sthorpej }
580a8bc2d90Sthorpej
581a8bc2d90Sthorpej static int
sunxi_rtc_clk_set_parent(void * priv,struct clk * clk,struct clk * parent_clk)582a8bc2d90Sthorpej sunxi_rtc_clk_set_parent(void *priv, struct clk *clk, struct clk *parent_clk)
583a8bc2d90Sthorpej {
584a8bc2d90Sthorpej struct sunxi_rtc_softc * const sc = priv;
585a8bc2d90Sthorpej
586a8bc2d90Sthorpej if (clk == &sc->sc_clks[SUNXI_RTC_CLK_IOSC])
587a8bc2d90Sthorpej return EINVAL;
588a8bc2d90Sthorpej
589a8bc2d90Sthorpej if (parent_clk != sc->sc_parent_clk &&
590a8bc2d90Sthorpej parent_clk != &sc->sc_clks[SUNXI_RTC_CLK_IOSC])
591a8bc2d90Sthorpej return EINVAL;
592a8bc2d90Sthorpej
593a8bc2d90Sthorpej mutex_enter(&sc->sc_clk_mutex);
594a8bc2d90Sthorpej uint32_t reg = RTC_READ(sc, SUN6I_LOSC_CTRL_REG);
595a8bc2d90Sthorpej if (parent_clk == sc->sc_parent_clk)
596a8bc2d90Sthorpej reg |= SUN6I_LOSC_CTRL_EXT_OSC | sc->sc_conf->ext_losc_en;
597a8bc2d90Sthorpej else
598a8bc2d90Sthorpej reg &= ~(SUN6I_LOSC_CTRL_EXT_OSC | sc->sc_conf->ext_losc_en);
599a8bc2d90Sthorpej RTC_WRITE(sc, SUN6I_LOSC_CTRL_REG, reg | SUN6I_LOSC_CTRL_KEY);
600a8bc2d90Sthorpej mutex_exit(&sc->sc_clk_mutex);
601a8bc2d90Sthorpej
602a8bc2d90Sthorpej return 0;
603a8bc2d90Sthorpej }
604a8bc2d90Sthorpej
605a8bc2d90Sthorpej static struct clk *
sunxi_rtc_clk_get_parent(void * priv,struct clk * clk)606a8bc2d90Sthorpej sunxi_rtc_clk_get_parent(void *priv, struct clk *clk)
607a8bc2d90Sthorpej {
608a8bc2d90Sthorpej struct sunxi_rtc_softc * const sc = priv;
609a8bc2d90Sthorpej uint32_t reg;
610a8bc2d90Sthorpej
611a8bc2d90Sthorpej if (clk == &sc->sc_clks[SUNXI_RTC_CLK_IOSC])
612a8bc2d90Sthorpej return NULL;
613a8bc2d90Sthorpej
614a8bc2d90Sthorpej reg = RTC_READ(sc, SUN6I_LOSC_CTRL_REG);
615a8bc2d90Sthorpej if (reg & SUN6I_LOSC_CTRL_EXT_OSC)
616a8bc2d90Sthorpej return sc->sc_parent_clk;
617a8bc2d90Sthorpej
618a8bc2d90Sthorpej /*
619a8bc2d90Sthorpej * We switch to the external oscillator at attach time becacuse
620a8bc2d90Sthorpej * it's higher quality than the internal one. If we haven't
621a8bc2d90Sthorpej * exported the internal oscillator to the clock tree, then
622a8bc2d90Sthorpej * we shouldn't get here.
623a8bc2d90Sthorpej */
624a8bc2d90Sthorpej KASSERT(sc->sc_clk_names[SUNXI_RTC_CLK_IOSC] != NULL);
625a8bc2d90Sthorpej return &sc->sc_clks[SUNXI_RTC_CLK_IOSC];
626a8bc2d90Sthorpej }
627