/* $NetBSD: pwmclock.c,v 1.12 2020/05/29 12:30:41 rin Exp $ */
/*
* Copyright (c) 2011 Michael Lorenz
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
void pwmclock_set_speed(struct pwmclock_softc *, int);
static int pwmclock_cpuspeed_temp(SYSCTLFN_ARGS);
static int pwmclock_cpuspeed_cur(SYSCTLFN_ARGS);
static int pwmclock_cpuspeed_available(SYSCTLFN_ARGS);
/*
* Establish a hook so on shutdown we can set the CPU clock back to
* full speed. This is necessary because PMON doesn't change the
* clock scale register on a warm boot, the MIPS clock code gets
* confused if we're too slow and the loongson-specific bits run
* too late in the boot process
*/
sc->sc_shutdown_cookie = shutdownhook_establish(pwmclock_shutdown, sc);
/* ok, let's see how far the cycle counter gets between interrupts */
DPRINTF("calibrating CPU timer...\n");
for (clk = 1; clk < 8; clk++) {
/* just in case the interrupt handler runs again after this */
sc->sc_step_wanted = 7;
/* set the clock to full speed */
REGVAL(LS2F_CHIPCFG0) =
(REGVAL(LS2F_CHIPCFG0) & ~LS2FCFG_FREQSCALE_MASK) | 7;
}
void
pwmclock_set_speed(struct pwmclock_softc *sc, int speed)
{
/*
* the PWM interrupt handler
* we don't have a CPU clock independent, high resolution counter so we're
* stuck with a PWM that can't count and a CP0 counter that slows down or
* speeds up with the actual CPU speed. In order to still get halfway
* accurate time we do the following:
* - only change CPU speed in the timer interrupt
* - each timer interrupt we measure how many CP0 cycles passed since last
* time, adjust for CPU speed since we can be sure it didn't change, use
* that to update a separate counter
* - when reading the time counter we take the number of CP0 ticks since
* the last timer interrupt, scale it to CPU clock, return that plus the
* interrupt updated counter mentioned above to get something close to
* CP0 running at full speed
* - when changing CPU speed do it as close to taking the time from CP0 as
* possible to keep the period of time we spend with CP0 running at the
* wrong frequency as short as possible - hopefully short enough to stay
* insignificant compared to other noise since switching speeds isn't
* going to happen all that often
*/
/* is it us? */
reg = bus_space_read_4(sc->sc_memt, sc->sc_regh, SM502_PWM1);
if ((reg & SM502_PWM_INTR_PENDING) == 0)
return 0;
/* yes, it's us, so clear the interrupt */
bus_space_write_4(sc->sc_memt, sc->sc_regh, SM502_PWM1, sc->sc_reg);
/*
* this looks kinda funny but what we want here is this:
* - reading the counter and changing the CPU clock should be as
* close together as possible in order to remain halfway accurate
* - we need to use the previous sc_step in order to scale the
* interval passed since the last clock interrupt correctly, so
* we only change sc_step after doing that
*/
if (sc->sc_step_wanted != sc->sc_step) {