sunxi_timer.c revision 1.1
1/* $NetBSD: sunxi_timer.c,v 1.1 2017/08/25 00:07:03 jmcneill Exp $ */ 2 3/*- 4 * Copyright (c) 2017 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: sunxi_timer.c,v 1.1 2017/08/25 00:07:03 jmcneill Exp $"); 31 32#include <sys/param.h> 33#include <sys/kernel.h> 34#include <sys/bus.h> 35#include <sys/device.h> 36#include <sys/intr.h> 37#include <sys/systm.h> 38#include <sys/timetc.h> 39 40#include <arm/locore.h> 41 42#include <dev/fdt/fdtvar.h> 43 44#include <arm/fdt/arm_fdtvar.h> 45 46#define TMR_IRQ_EN_REG 0x00 47#define TMR_IRQ_EN(n) __BIT(n) 48#define TMR_IRQ_STAS_REG 0x04 49#define TMR_IRQ_STAS_PEND(n) __BIT(n) 50#define TMR0_CTRL_REG 0x10 51#define TMR0_CTRL_MODE __BIT(7) 52#define TMR0_CTRL_CLK_PRESCALE __BITS(6,4) 53#define TMR0_CTRL_CLK_SRC __BITS(3,2) 54#define TMR0_CTRL_CLK_SRC_OSC24M 1 55#define TMR0_CTRL_CLK_SRC_PLL6_6 2 56#define TMR0_CTRL_RELOAD __BIT(1) 57#define TMR0_CTRL_EN __BIT(0) 58#define TMR0_INTV_VALUE_REG 0x14 59#define TMR0_CURNT_VALUE_REG 0x18 60 61static const char * const compatible[] = { 62 "allwinner,sun4i-a10-timer", 63 NULL 64}; 65 66struct sunxi_timer_softc { 67 device_t sc_dev; 68 bus_space_tag_t sc_bst; 69 bus_space_handle_t sc_bsh; 70 int sc_phandle; 71 struct clk *sc_clk; 72 73 struct timecounter sc_tc; 74}; 75 76#define TIMER_READ(sc, reg) \ 77 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) 78#define TIMER_WRITE(sc, reg, val) \ 79 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) 80 81static struct sunxi_timer_softc *timer_softc; 82 83static int 84sunxi_timer_intr(void *arg) 85{ 86 struct sunxi_timer_softc * const sc = timer_softc; 87 struct clockframe *frame = arg; 88 uint32_t stas; 89 90 stas = TIMER_READ(sc, TMR_IRQ_STAS_REG); 91 if (stas == 0) 92 return 0; 93 TIMER_WRITE(sc, TMR_IRQ_STAS_REG, stas); 94 95 if ((stas & TMR_IRQ_STAS_PEND(0)) != 0) 96 hardclock(frame); 97 98 return 1; 99} 100 101static void 102sunxi_timer_cpu_initclocks(void) 103{ 104 struct sunxi_timer_softc * const sc = timer_softc; 105 char intrstr[128]; 106 void *ih; 107 108 KASSERT(sc != NULL); 109 110 if (!fdtbus_intr_str(sc->sc_phandle, 0, intrstr, sizeof(intrstr))) 111 panic("%s: failed to decode interrupt", __func__); 112 113 ih = fdtbus_intr_establish(sc->sc_phandle, 0, IPL_CLOCK, 114 FDT_INTR_MPSAFE, sunxi_timer_intr, NULL); 115 if (ih == NULL) 116 panic("%s: failed to establish timer interrupt", __func__); 117 118 aprint_normal_dev(sc->sc_dev, "interrupting on %s\n", intrstr); 119 120 /* Enable Timer 0 IRQ */ 121 const uint32_t irq_en = TIMER_READ(sc, TMR_IRQ_EN_REG); 122 TIMER_WRITE(sc, TMR_IRQ_EN_REG, irq_en | TMR_IRQ_EN(0)); 123} 124 125static u_int 126sunxi_timer_get_timecount(struct timecounter *tc) 127{ 128 struct sunxi_timer_softc * const sc = tc->tc_priv; 129 130 return ~TIMER_READ(sc, TMR0_CURNT_VALUE_REG); 131} 132 133static int 134sunxi_timer_match(device_t parent, cfdata_t cf, void *aux) 135{ 136 struct fdt_attach_args * const faa = aux; 137 138 return of_match_compatible(faa->faa_phandle, compatible); 139} 140 141static void 142sunxi_timer_attach(device_t parent, device_t self, void *aux) 143{ 144 struct sunxi_timer_softc * const sc = device_private(self); 145 struct fdt_attach_args * const faa = aux; 146 struct timecounter *tc = &sc->sc_tc; 147 const int phandle = faa->faa_phandle; 148 bus_addr_t addr; 149 bus_size_t size; 150 151 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 152 aprint_error(": couldn't get registers\n"); 153 return; 154 } 155 156 if ((sc->sc_clk = fdtbus_clock_get_index(phandle, 0)) == NULL) { 157 aprint_error(": couldn't get clock\n"); 158 return; 159 } 160 161 sc->sc_dev = self; 162 sc->sc_phandle = phandle; 163 sc->sc_bst = faa->faa_bst; 164 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { 165 aprint_error(": couldn't map registers\n"); 166 return; 167 } 168 169 aprint_naive("\n"); 170 aprint_normal(": Timer\n"); 171 172 const u_int rate = clk_get_rate(sc->sc_clk); 173 174 /* Disable IRQs and all timers */ 175 TIMER_WRITE(sc, TMR_IRQ_EN_REG, 0); 176 TIMER_WRITE(sc, TMR_IRQ_STAS_REG, TIMER_READ(sc, TMR_IRQ_STAS_REG)); 177 /* Enable Timer 0 */ 178 TIMER_WRITE(sc, TMR0_INTV_VALUE_REG, rate / hz); 179 TIMER_WRITE(sc, TMR0_CTRL_REG, 180 __SHIFTIN(TMR0_CTRL_CLK_SRC_OSC24M, TMR0_CTRL_CLK_SRC) | 181 TMR0_CTRL_RELOAD | TMR0_CTRL_EN); 182 183 /* Timecounter setup */ 184 tc->tc_get_timecount = sunxi_timer_get_timecount; 185 tc->tc_counter_mask = ~0u, 186 tc->tc_frequency = clk_get_rate(sc->sc_clk); 187 tc->tc_name = device_xname(self); 188 tc->tc_quality = 100; 189 tc->tc_priv = sc; 190 tc_init(tc); 191 192 /* Use this as the OS timer in UP configurations */ 193 if (!arm_has_mpext_p) { 194 timer_softc = sc; 195 arm_fdt_timer_register(sunxi_timer_cpu_initclocks); 196 } 197} 198 199CFATTACH_DECL_NEW(sunxi_timer, sizeof(struct sunxi_timer_softc), 200 sunxi_timer_match, sunxi_timer_attach, NULL, NULL); 201