root/sys/arch/powerpc/powerpc/clock.c
/*      $NetBSD: clock.c,v 1.19 2024/10/19 12:34:09 jmcneill Exp $      */
/*      $OpenBSD: clock.c,v 1.3 1997/10/13 13:42:53 pefo Exp $  */

/*
 * Copyright (C) 1995, 1996 Wolfgang Solfrank.
 * Copyright (C) 1995, 1996 TooLs GmbH.
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by TooLs GmbH.
 * 4. The name of TooLs GmbH may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``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 TOOLS GMBH 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: clock.c,v 1.19 2024/10/19 12:34:09 jmcneill Exp $");

#ifdef _KERNEL_OPT
#include "opt_ppcarch.h"
#endif

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/timetc.h>

#include <uvm/uvm_extern.h>

#include <powerpc/psl.h>
#include <powerpc/spr.h>
#if defined (PPC_OEA) || defined(PPC_OEA64) || defined (PPC_OEA64_BRIDGE)
#include <powerpc/oea/spr.h>
#elif defined (PPC_BOOKE)
#include <powerpc/booke/spr.h>
#elif defined (PPC_IBM4XX)
#include <powerpc/ibm4xx/spr.h>
#else
#error unknown powerpc variant
#endif

void decr_intr(struct clockframe *);
void init_powerpc_tc(void);
static u_int get_powerpc_timecount(struct timecounter *);
#ifdef PPC_OEA601
static u_int get_601_timecount(struct timecounter *);
#endif

uint32_t ticks_per_sec;
uint32_t ticks_per_msec;
uint32_t ns_per_tick;
uint32_t ticks_per_intr = 0;

#ifdef PPC_OEA601
static struct timecounter powerpc_601_timecounter = {
        .tc_get_timecount = get_601_timecount,
        .tc_poll_pps = 0,
        .tc_counter_mask = 0x7fffffff,
        .tc_frequency = 0,
        .tc_name = "rtc",
        .tc_quality = 100,
        .tc_priv = NULL,
        .tc_next = NULL
};
#endif

static struct timecounter powerpc_timecounter = {
        .tc_get_timecount = get_powerpc_timecount,
        .tc_poll_pps = 0,
        .tc_counter_mask = 0x7fffffff,
        .tc_frequency = 0,
        .tc_name = "mftb",
        .tc_quality = 100,
        .tc_priv = NULL,
        .tc_next = NULL
};

/*
 * Start the real-time and statistics clocks. Leave stathz 0 since there
 * are no other timers available.
 */
void
cpu_initclocks(void)
{
        struct cpu_info * const ci = curcpu();
        uint32_t msr;

        ticks_per_intr = ticks_per_sec / hz;
        cpu_timebase = ticks_per_sec;
#ifdef PPC_OEA601
        if ((mfpvr() >> 16) == MPC601)
                ci->ci_lasttb = rtc_nanosecs();
        else
#endif
                __asm volatile ("mftb %0" : "=r"(ci->ci_lasttb));
        __asm volatile ("mtdec %0" :: "r"(ticks_per_intr));
        init_powerpc_tc();

        /*
         * Now allow all hardware interrupts including hardclock(9).
         */
        __asm volatile ("mfmsr %0; ori %0,%0,%1; mtmsr %0"
            : "=r"(msr) : "K"(PSL_EE|PSL_RI));
}

/*
 * We assume newhz is either stathz or profhz, and that neither will
 * change after being set up above.  Could recalculate intervals here
 * but that would be a drag.
 */
void
setstatclockrate(int arg)
{

        /* Nothing we can do */
}

void
decr_intr(struct clockframe *cfp)
{
        struct cpu_info * const ci = curcpu();
        const register_t msr = mfmsr();
        int pri;
        u_long tb;
        long ticks;
        int nticks;

        /* Check whether we are initialized */
        if (!ticks_per_intr)
                return;

        /*
         * Based on the actual time delay since the last decrementer reload,
         * we arrange for earlier interrupt next time.
         */
        __asm ("mfdec %0" : "=r"(ticks));
        for (nticks = 0; ticks < 0; nticks++)
                ticks += ticks_per_intr;
        __asm volatile ("mtdec %0" :: "r"(ticks));

        ci->ci_data.cpu_nintr++;
        ci->ci_ev_clock.ev_count++;

        pri = splclock();
        if (pri >= IPL_CLOCK) {
                ci->ci_tickspending += nticks;
        } else {
                nticks += ci->ci_tickspending;
                ci->ci_tickspending = 0;

                /*
                 * lasttb is used during microtime. Set it to the virtual
                 * start of this tick interval.
                 */
#ifdef PPC_OEA601
                if ((mfpvr() >> 16) == MPC601)
                        tb = rtc_nanosecs();
                else
#endif
                        __asm volatile ("mftb %0" : "=r"(tb));

                ci->ci_lasttb = tb + ticks - ticks_per_intr;
                ci->ci_idepth++;
                mtmsr(msr | PSL_EE);
                /*
                 * Do standard timer interrupt stuff.
                 * Do softclock stuff only on the last iteration.
                 */
                while (--nticks > 0)
                        hardclock(cfp);
                hardclock(cfp);
                mtmsr(msr);
                ci->ci_idepth--;
        }
        mtmsr(msr | PSL_EE);
        splx(pri);
        mtmsr(msr);
}

/*
 * Wait for about n microseconds (at least!).
 */
void
delay(unsigned int n)
{
#ifdef _ARCH_PPC64
        uint64_t tb, scratch;
#else
        uint64_t tb;
        uint32_t tbh, tbl, scratch;

#ifdef PPC_OEA601
        if ((mfpvr() >> 16) == MPC601) {
                u_int32_t rtc[2];

                mfrtc(rtc);
                while (n >= 1000000) {
                        rtc[0]++;
                        n -= 1000000;
                }
                rtc[1] += (n * 1000);
                if (rtc[1] >= 1000000000) {
                        rtc[0]++;
                        rtc[1] -= 1000000000;
                }
                __asm volatile ("1: mfspr %0,%3; cmplw %0,%1; blt 1b; bgt 2f;"
                    "mfspr %0,%4; cmplw %0,%2; blt 1b; 2:"
                    : "=&r"(scratch)
                    : "r"(rtc[0]), "r"(rtc[1]), "n"(SPR_RTCU_R), "n"(SPR_RTCL_R)
                    : "cr0");
        } else
#endif /* PPC_OEA601 */
#endif /* !_ARCH_PPC64 */
        {
                tb = mftb();
                if (ticks_per_msec != 0 && n >= 1000) {
                        tb += (n / 1000ULL) * ticks_per_msec;
                        n = n % 1000;
                }
                tb += (n * 1000ULL + ns_per_tick - 1) / ns_per_tick;
#ifdef _ARCH_PPC64
                __asm volatile ("1: mftb %0; cmpld %0,%1; blt 1b;"
                              : "=&r"(scratch) : "r"(tb)
                              : "cr0");
#else
                tbh = tb >> 32;
                tbl = tb;
                __asm volatile ("1: mftbu %0; cmplw %0,%1; blt 1b; bgt 2f;"
                              "mftb %0; cmplw %0,%2; blt 1b; 2:"
                              : "=&r"(scratch) : "r"(tbh), "r"(tbl)
                              : "cr0");
#endif
        }
}

static u_int
get_powerpc_timecount(struct timecounter *tc)
{
        u_long tb;
        int msr, scratch;
        
        __asm volatile ("mfmsr %0; andi. %1,%0,%2; mtmsr %1"
                      : "=r"(msr), "=r"(scratch) : "K"((u_short)~PSL_EE));

        tb = (u_int)(mftb() & 0x7fffffff);
        mtmsr(msr);

        return tb;
}

#ifdef PPC_OEA601
static u_int
get_601_timecount(struct timecounter *tc)
{
        u_long tb;
        int msr, scratch;
        
        __asm volatile ("mfmsr %0; andi. %1,%0,%2; mtmsr %1"
                      : "=r"(msr), "=r"(scratch) : "K"((u_short)~PSL_EE));

        tb = rtc_nanosecs();
        mtmsr(msr);

        return tb;
}
#endif

void
init_powerpc_tc(void)
{
        struct timecounter *tc;

#ifdef PPC_OEA601
        if ((mfpvr() >> 16) == MPC601) {
                tc = &powerpc_601_timecounter;
        } else
#endif
                tc = &powerpc_timecounter;

        tc->tc_frequency = ticks_per_sec;
        tc_init(tc);
}