1 /* $NetBSD: mvsoctmr.c,v 1.10 2013/10/14 04:17:59 kiyohara Exp $ */ 2 /* 3 * Copyright (c) 2007, 2008, 2010 KIYOHARA Takashi 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 19 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 23 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 24 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 * POSSIBILITY OF SUCH DAMAGE. 26 */ 27 #include <sys/cdefs.h> 28 __KERNEL_RCSID(0, "$NetBSD: mvsoctmr.c,v 1.10 2013/10/14 04:17:59 kiyohara Exp $"); 29 30 #include "opt_ddb.h" 31 #include "opt_mvsoc.h" 32 33 #include <sys/param.h> 34 #include <sys/atomic.h> 35 #include <sys/bus.h> 36 #include <sys/device.h> 37 #include <sys/errno.h> 38 #include <sys/kernel.h> 39 #include <sys/time.h> 40 #include <sys/timetc.h> 41 #include <sys/systm.h> 42 #include <sys/wdog.h> 43 44 #include <machine/intr.h> 45 46 #include <arm/cpufunc.h> 47 48 #include <arm/marvell/mvsocreg.h> 49 #include <arm/marvell/mvsocvar.h> 50 #include <arm/marvell/mvsoctmrreg.h> 51 52 #include <dev/marvell/marvellreg.h> 53 #include <dev/marvell/marvellvar.h> 54 55 #include <dev/sysmon/sysmonvar.h> 56 57 #ifdef DDB 58 #include <machine/db_machdep.h> 59 #include <ddb/db_extern.h> 60 #endif 61 62 63 struct mvsoctmr_softc { 64 device_t sc_dev; 65 66 struct sysmon_wdog sc_wdog; 67 uint32_t sc_wdog_period; 68 uint32_t sc_wdog_armed; 69 70 bus_space_tag_t sc_iot; 71 bus_space_handle_t sc_ioh; 72 int sc_irq; 73 74 #define TMR_FLAGS_ARMADAXP (1 << 0) 75 int sc_flags; 76 }; 77 78 79 static int mvsoctmr_match(device_t, struct cfdata *, void *); 80 static void mvsoctmr_attach(device_t, device_t, void *); 81 82 static int clockhandler(void *); 83 84 static u_int mvsoctmr_get_timecount(struct timecounter *); 85 86 static void mvsoctmr_cntl(struct mvsoctmr_softc *, int, u_int, int, int); 87 88 static int mvsoctmr_wdog_tickle(struct sysmon_wdog *); 89 static int mvsoctmr_wdog_setmode(struct sysmon_wdog *); 90 91 #ifdef DDB 92 static void mvsoctmr_wdog_ddb_trap(int); 93 #endif 94 95 #define MVSOC_WDOG_MAX_PERIOD (0xffffffff / mvTclk) 96 97 static struct mvsoctmr_softc *mvsoctmr_sc; 98 static struct timecounter mvsoctmr_timecounter = { 99 mvsoctmr_get_timecount, /* get_timecount */ 100 0, /* no poll_pps */ 101 ~0u, /* counter_mask */ 102 0, /* frequency (set by cpu_initclocks()) */ 103 "mvsoctmr", /* name */ 104 100, /* quality */ 105 NULL, /* prev */ 106 NULL, /* next */ 107 }; 108 109 CFATTACH_DECL_NEW(mvsoctmr, sizeof(struct mvsoctmr_softc), 110 mvsoctmr_match, mvsoctmr_attach, NULL, NULL); 111 112 113 /* ARGSUSED */ 114 static int 115 mvsoctmr_match(device_t parent, struct cfdata *match, void *aux) 116 { 117 struct marvell_attach_args *mva = aux; 118 119 if (strcmp(mva->mva_name, match->cf_name) != 0) 120 return 0; 121 if (mva->mva_offset == MVA_OFFSET_DEFAULT || 122 mva->mva_irq == MVA_IRQ_DEFAULT) 123 return 0; 124 125 mva->mva_size = MVSOCTMR_SIZE; 126 return 1; 127 } 128 129 /* ARGSUSED */ 130 static void 131 mvsoctmr_attach(device_t parent, device_t self, void *aux) 132 { 133 struct mvsoctmr_softc *sc = device_private(self); 134 struct marvell_attach_args *mva = aux; 135 uint32_t rstoutn; 136 137 aprint_naive("\n"); 138 aprint_normal(": Marvell SoC Timer\n"); 139 140 if (mvsoctmr_sc == NULL) 141 mvsoctmr_sc = sc; 142 143 sc->sc_dev = self; 144 sc->sc_iot = mva->mva_iot; 145 if (bus_space_subregion(mva->mva_iot, mva->mva_ioh, 146 mva->mva_offset, mva->mva_size, &sc->sc_ioh)) 147 panic("%s: Cannot map registers", device_xname(self)); 148 sc->sc_irq = mva->mva_irq; 149 150 switch (mva->mva_model) { 151 case MARVELL_ARMADAXP_MV78130: 152 case MARVELL_ARMADAXP_MV78160: 153 case MARVELL_ARMADAXP_MV78230: 154 case MARVELL_ARMADAXP_MV78260: 155 case MARVELL_ARMADAXP_MV78460: 156 sc->sc_flags = TMR_FLAGS_ARMADAXP; 157 break; 158 } 159 160 mvsoctmr_timecounter.tc_name = device_xname(self); 161 mvsoctmr_cntl(sc, MVSOCTMR_TIMER1, 0xffffffff, 1, 1); 162 163 /* 164 * stop watchdog timer, enable watchdog timer resets 165 */ 166 mvsoctmr_cntl(sc, MVSOCTMR_WATCHDOG, 0xffffffff, 0, 0); 167 write_mlmbreg(MVSOC_MLMB_MLMBICR, 168 ~(1<<MVSOC_MLMB_MLMBI_CPUWDTIMERINTREQ)); 169 rstoutn = read_mlmbreg(MVSOC_MLMB_RSTOUTNMASKR); 170 write_mlmbreg(MVSOC_MLMB_RSTOUTNMASKR, 171 rstoutn | MVSOC_MLMB_RSTOUTNMASKR_WDRSTOUTEN); 172 173 #ifdef DDB 174 db_trap_callback = mvsoctmr_wdog_ddb_trap; 175 #endif 176 177 sc->sc_wdog.smw_name = device_xname(self); 178 sc->sc_wdog.smw_cookie = sc; 179 sc->sc_wdog.smw_setmode = mvsoctmr_wdog_setmode; 180 sc->sc_wdog.smw_tickle = mvsoctmr_wdog_tickle; 181 sc->sc_wdog.smw_period = MVSOC_WDOG_MAX_PERIOD; 182 183 if (sysmon_wdog_register(&sc->sc_wdog) != 0) 184 aprint_error_dev(self, 185 "unable to register watchdog with sysmon\n"); 186 } 187 188 /* 189 * clockhandler: 190 * 191 * Handle the hardclock interrupt. 192 */ 193 static int 194 clockhandler(void *arg) 195 { 196 struct clockframe *frame = arg; 197 198 #if defined(ARMADAXP) 199 KASSERT(mvsoctmr_sc != NULL); 200 201 if (mvsoctmr_sc->sc_flags & TMR_FLAGS_ARMADAXP) 202 /* Acknowledge all timers-related interrupts */ 203 bus_space_write_4(mvsoctmr_sc->sc_iot, mvsoctmr_sc->sc_ioh, 204 MVSOCTMR_TESR, 0x0); 205 #endif 206 207 hardclock(frame); 208 209 return 1; 210 } 211 212 /* 213 * setstatclockrate: 214 * 215 * Set the rate of the statistics clock. 216 */ 217 /* ARGSUSED */ 218 void 219 setstatclockrate(int newhz) 220 { 221 } 222 223 /* 224 * cpu_initclocks: 225 * 226 * Initialize the clock and get them going. 227 */ 228 void 229 cpu_initclocks(void) 230 { 231 struct mvsoctmr_softc *sc; 232 void *clock_ih; 233 const int en = 1, autoen = 1; 234 uint32_t timer0_tval; 235 236 sc = mvsoctmr_sc; 237 if (sc == NULL) 238 panic("cpu_initclocks: mvsoctmr not found"); 239 240 mvsoctmr_timecounter.tc_priv = sc; 241 242 if (sc->sc_flags & TMR_FLAGS_ARMADAXP) 243 /* We set global timer and counter to 25 MHz mode */ 244 mvsoctmr_timecounter.tc_frequency = 25000000; 245 else 246 mvsoctmr_timecounter.tc_frequency = mvTclk; 247 248 timer0_tval = (mvsoctmr_timecounter.tc_frequency * 2) / (u_long) hz; 249 timer0_tval = (timer0_tval / 2) + (timer0_tval & 1); 250 251 mvsoctmr_cntl(sc, MVSOCTMR_TIMER0, timer0_tval, en, autoen); 252 mvsoctmr_cntl(sc, MVSOCTMR_TIMER1, 0xffffffff, en, autoen); 253 254 if (sc->sc_flags & TMR_FLAGS_ARMADAXP) { 255 /* 256 * Establishing timer interrupts is slightly different for 257 * Armada XP than for other supported SoCs from Marvell. 258 * Timer interrupt is no different from any other interrupt 259 * in Armada XP, so we use generic marvell_intr_establish(). 260 */ 261 clock_ih = marvell_intr_establish(sc->sc_irq, IPL_CLOCK, 262 clockhandler, NULL); 263 } else 264 clock_ih = mvsoc_bridge_intr_establish( 265 MVSOC_MLMB_MLMBI_CPUTIMER0INTREQ, IPL_CLOCK, clockhandler, 266 NULL); 267 if (clock_ih == NULL) 268 panic("cpu_initclocks: unable to register timer interrupt"); 269 270 tc_init(&mvsoctmr_timecounter); 271 } 272 273 void 274 delay(unsigned int n) 275 { 276 struct mvsoctmr_softc *sc; 277 unsigned int cur_tick, initial_tick; 278 int remaining; 279 280 sc = mvsoctmr_sc; 281 #ifdef DEBUG 282 if (sc == NULL) { 283 printf("%s: called before start mvsoctmr\n", __func__); 284 return; 285 } 286 #endif 287 288 /* 289 * Read the counter first, so that the rest of the setup overhead is 290 * counted. 291 */ 292 initial_tick = bus_space_read_4(sc->sc_iot, sc->sc_ioh, 293 MVSOCTMR_TIMER(MVSOCTMR_TIMER1)); 294 295 if (n <= UINT_MAX / mvTclk) { 296 /* 297 * For unsigned arithmetic, division can be replaced with 298 * multiplication with the inverse and a shift. 299 */ 300 remaining = n * mvTclk / 1000000; 301 } else { 302 /* 303 * This is a very long delay. 304 * Being slow here doesn't matter. 305 */ 306 remaining = (unsigned long long) n * mvTclk / 1000000; 307 } 308 309 while (remaining > 0) { 310 cur_tick = bus_space_read_4(sc->sc_iot, sc->sc_ioh, 311 MVSOCTMR_TIMER(MVSOCTMR_TIMER1)); 312 if (cur_tick > initial_tick) 313 remaining -= 0xffffffff - cur_tick + initial_tick; 314 else 315 remaining -= (initial_tick - cur_tick); 316 initial_tick = cur_tick; 317 } 318 } 319 320 static u_int 321 mvsoctmr_get_timecount(struct timecounter *tc) 322 { 323 struct mvsoctmr_softc *sc = tc->tc_priv; 324 325 return 0xffffffff - bus_space_read_4(sc->sc_iot, sc->sc_ioh, 326 MVSOCTMR_TIMER(MVSOCTMR_TIMER1)); 327 } 328 329 static void 330 mvsoctmr_cntl(struct mvsoctmr_softc *sc, int num, u_int ticks, int en, 331 int autoen) 332 { 333 uint32_t ctrl; 334 335 bus_space_write_4(sc->sc_iot, sc->sc_ioh, MVSOCTMR_RELOAD(num), ticks); 336 337 bus_space_write_4(sc->sc_iot, sc->sc_ioh, MVSOCTMR_TIMER(num), ticks); 338 339 ctrl = bus_space_read_4(sc->sc_iot, sc->sc_ioh, MVSOCTMR_CTCR); 340 if (en) 341 ctrl |= MVSOCTMR_CTCR_CPUTIMEREN(num); 342 else 343 ctrl &= ~MVSOCTMR_CTCR_CPUTIMEREN(num); 344 if (autoen) 345 ctrl |= MVSOCTMR_CTCR_CPUTIMERAUTO(num); 346 else 347 ctrl &= ~MVSOCTMR_CTCR_CPUTIMERAUTO(num); 348 if (sc->sc_flags & TMR_FLAGS_ARMADAXP) 349 /* Set timer and counter to 25MHz mode */ 350 ctrl |= MVSOCTMR_CTCR_25MHZEN(num); 351 bus_space_write_4(sc->sc_iot, sc->sc_ioh, MVSOCTMR_CTCR, ctrl); 352 } 353 354 static int 355 mvsoctmr_wdog_setmode(struct sysmon_wdog *smw) 356 { 357 struct mvsoctmr_softc *sc = smw->smw_cookie; 358 359 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 360 sc->sc_wdog_armed = 0; 361 mvsoctmr_cntl(sc, MVSOCTMR_WATCHDOG, 0xffffffff, 0, 0); 362 } else { 363 sc->sc_wdog_armed = 1; 364 if (smw->smw_period == WDOG_PERIOD_DEFAULT) 365 smw->smw_period = MVSOC_WDOG_MAX_PERIOD; 366 else if (smw->smw_period > MVSOC_WDOG_MAX_PERIOD || 367 smw->smw_period <= 0) 368 return (EOPNOTSUPP); 369 sc->sc_wdog_period = smw->smw_period * mvTclk; 370 mvsoctmr_cntl(sc, MVSOCTMR_WATCHDOG, sc->sc_wdog_period, 1, 0); 371 } 372 373 return (0); 374 } 375 376 static int 377 mvsoctmr_wdog_tickle(struct sysmon_wdog *smw) 378 { 379 struct mvsoctmr_softc *sc = smw->smw_cookie; 380 381 mvsoctmr_cntl(sc, MVSOCTMR_WATCHDOG, sc->sc_wdog_period, 1, 0); 382 383 return (0); 384 } 385 386 #ifdef DDB 387 static void 388 mvsoctmr_wdog_ddb_trap(int enter) 389 { 390 struct mvsoctmr_softc *sc = mvsoctmr_sc; 391 392 if (sc == NULL) 393 return; 394 395 if (sc->sc_wdog_armed) { 396 if (enter) 397 mvsoctmr_cntl(sc, MVSOCTMR_WATCHDOG, 0xffffffff, 0, 0); 398 else 399 mvsoctmr_cntl(sc, MVSOCTMR_WATCHDOG, 400 sc->sc_wdog_period, 1, 0); 401 } 402 } 403 #endif 404