root/sys/arch/arm/ep93xx/epclk.c
/*      $NetBSD: epclk.c,v 1.23 2021/11/21 08:25:26 skrll Exp $ */

/*
 * Copyright (c) 2004 Jesse Off
 * 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``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 FOUNDATION OR CONTRIBUTORS
 * 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.
 */

/*
 * Driver for the ep93xx clock tick.
 *
 * We use the 64Hz RTC interrupt as its the only thing that allows for timekeeping
 * of a second (crystal error only).  There are two general purpose timers
 * on the ep93xx, but they run at a frequency that makes a perfect integer
 * number of ticks per second impossible.  Note that there was an errata with
 * the ep93xx processor and many early boards (including the Cirrus eval board) have
 * a broken crystal oscillator input that may make this 64Hz unreliable.  However,
 * not all boards are susceptible, the Technologic Systems TS-7200 is a notable
 * exception that is immune to this errata.   --joff
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: epclk.c,v 1.23 2021/11/21 08:25:26 skrll Exp $");

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

#include <sys/bus.h>
#include <machine/intr.h>

#include <arm/cpufunc.h>

#include <arm/ep93xx/epsocvar.h>
#include <arm/ep93xx/epclkreg.h>
#include <arm/ep93xx/ep93xxreg.h>
#include <arm/ep93xx/ep93xxvar.h>
#include <dev/clock_subr.h>

#include "opt_hz.h"

#define TIMER_FREQ      983040

static int      epclk_match(device_t, cfdata_t, void *);
static void     epclk_attach(device_t, device_t, void *);
static u_int    epclk_get_timecount(struct timecounter *);

void            rtcinit(void);

/* callback functions for intr_functions */
static int      epclk_intr(void* arg);

struct epclk_softc {
        bus_addr_t              sc_baseaddr;
        bus_space_tag_t         sc_iot;
        bus_space_handle_t      sc_ioh;
#if defined(HZ) && (HZ == 64)
        bus_space_handle_t      sc_teoi_ioh;
#endif
        int                     sc_intr;
};

static struct timecounter epclk_timecounter = {
        .tc_get_timecount = epclk_get_timecount,
        .tc_counter_mask = ~0u,
        .tc_frequency = TIMER_FREQ,
        .tc_name = "epclk",
        .tc_quality = 100,
};

static struct epclk_softc *epclk_sc = NULL;

CFATTACH_DECL_NEW(epclk, sizeof(struct epclk_softc),
    epclk_match, epclk_attach, NULL, NULL);

/* This is a quick ARM way to multiply by 983040/1000000 (w/o overflow) */
#define US_TO_TIMER4VAL(x, y) { \
        uint32_t hi, lo, scalar = 4222124650UL; \
        __asm volatile ( \
                "umull %0, %1, %2, %3;" \
                : "=&r"(lo), "=&r"(hi) \
                : "r"((x)), "r"(scalar) \
        ); \
        (y) = hi; \
}

#define TIMER4VAL()     (*(volatile uint32_t *)(EP93XX_APB_VBASE + \
        EP93XX_APB_TIMERS + EP93XX_TIMERS_Timer4ValueLow))

static int
epclk_match(device_t parent, cfdata_t match, void *aux)
{

        return 2;
}

static void
epclk_attach(device_t parent, device_t self, void *aux)
{
        struct epclk_softc              *sc;
        struct epsoc_attach_args        *sa;
        bool first_run;

        printf("\n");

        sc = device_private(self);
        sa = aux;
        sc->sc_iot = sa->sa_iot;
        sc->sc_baseaddr = sa->sa_addr;
        sc->sc_intr = sa->sa_intr;

        if (epclk_sc == NULL) {
                first_run = true;
                epclk_sc = sc;
        } else
                first_run = false;

        if (bus_space_map(sa->sa_iot, sa->sa_addr, sa->sa_size,
                0, &sc->sc_ioh))
                panic("%s: Cannot map registers", device_xname(self));
#if defined(HZ) && (HZ == 64)
        if (bus_space_map(sa->sa_iot, EP93XX_APB_HWBASE + EP93XX_APB_SYSCON +
                EP93XX_SYSCON_TEOI, 4, 0, &sc->sc_teoi_ioh))
                panic("%s: Cannot map registers", device_xname(self));
#endif

        /* clear and start the debug timer (Timer4) */
        bus_space_write_4(sc->sc_iot, sc->sc_ioh, EP93XX_TIMERS_Timer4Enable, 0);
        bus_space_write_4(sc->sc_iot, sc->sc_ioh, EP93XX_TIMERS_Timer4Enable, 0x100);

        if (first_run)
                tc_init(&epclk_timecounter);
}

/*
 * epclk_intr:
 *
 *      Handle the hardclock interrupt.
 */
static int
epclk_intr(void *arg)
{
        struct epclk_softc*     sc;

        sc = epclk_sc;

#if defined(HZ) && (HZ == 64)
        bus_space_write_4(sc->sc_iot, sc->sc_teoi_ioh, 0, 1);
#else
        bus_space_write_4(sc->sc_iot, sc->sc_ioh, EP93XX_TIMERS_Timer1Clear, 1);
#endif
        hardclock((struct clockframe*) arg);
        return (1);
}

/*
 * setstatclockrate:
 *
 *      Set the rate of the statistics clock.
 *
 *      We assume that hz is either stathz or profhz, and that neither
 *      will change after being set by cpu_initclocks().  We could
 *      recalculate the intervals here, but that would be a pain.
 */
void
setstatclockrate(int newhz)
{

        /* use hardclock */
}

/*
 * cpu_initclocks:
 *
 *      Initialize the clock and get them going.
 */
void
cpu_initclocks(void)
{
        struct epclk_softc*     sc;

        sc = epclk_sc;
        stathz = profhz = 0;

#if defined(HZ) && (HZ == 64)
        if (hz != 64) panic("HZ must be 64!");

        /* clear 64Hz interrupt status */
        bus_space_write_4(sc->sc_iot, sc->sc_teoi_ioh, 0, 1);
#else
#define CLOCK_SOURCE_RATE       14745600UL
#define CLOCK_TICK_DIV          29
#define CLOCK_TICK_RATE \
        (((CLOCK_SOURCE_RATE+(CLOCK_TICK_DIV*hz-1))/(CLOCK_TICK_DIV*hz))*hz)
#define LATCH   ((CLOCK_TICK_RATE + hz/2) / hz)
        /* setup and start the 16bit timer (Timer1) */
        bus_space_write_4(sc->sc_iot, sc->sc_ioh,
                          EP93XX_TIMERS_Timer1Control,
                          (TimerControl_MODE)|(TimerControl_CLKSEL));
        bus_space_write_4(sc->sc_iot, sc->sc_ioh,
                          EP93XX_TIMERS_Timer1Load, LATCH-1);
        bus_space_write_4(sc->sc_iot, sc->sc_ioh,
                          EP93XX_TIMERS_Timer1Control,
                          (TimerControl_ENABLE)|(TimerControl_MODE)|(TimerControl_CLKSEL));
#endif

        ep93xx_intr_establish(sc->sc_intr, IPL_CLOCK, epclk_intr, NULL);
}

/*
 * delay:
 *
 *      Delay for at least N microseconds.
 */
void
delay(unsigned int n)
{
        unsigned int cur_tick, initial_tick;
        int remaining;

#ifdef DEBUG
        if (epclk_sc == NULL) {
                printf("delay: called before start epclk\n");
                return;
        }
#endif

        /*
         * Read the counter first, so that the rest of the setup overhead is
         * counted.
         */
        initial_tick = TIMER4VAL();

        US_TO_TIMER4VAL(n, remaining);

        while (remaining > 0) {
                cur_tick = TIMER4VAL();
                if (cur_tick >= initial_tick)
                        remaining -= cur_tick - initial_tick;
                else
                        remaining -= UINT_MAX - initial_tick + cur_tick + 1;
                initial_tick = cur_tick;
        }
}

static u_int
epclk_get_timecount(struct timecounter *tc)
{
        return TIMER4VAL();
}