/*	$NetBSD$	*/

/*
 * TI Dual-mode timers
 */

/*-
 * Copyright (c) 2012, 2017 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Taylor R. Campbell.
 *
 * 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD$");

#include <sys/types.h>
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/kernel.h>
#include <sys/timetc.h>

#include <dev/fdt/fdtvar.h>

#include <arm/ti/ti_dmtimerreg.h>
#include <arm/ti/ti_dmtimervar.h>

#include "ioconf.h"

typedef uint8_t dmt_reg_t;		/* register offset */
typedef uint16_t dmt_timer_reg_t;	/* register offset/posting metadata */

static int	ti_dmtimer_match(device_t, cfdata_t, void *);
static void	ti_dmtimer_attach(device_t, device_t, void *);

static int	ti_dmtimer_init(void);
static void	ti_dmtimer_initclocks(void);

static unsigned int
		dmt_tc_get_timecount(struct timecounter *);
static int	dmt_hardintr(void *);
static int	dmt_statintr(void *);
static void	dmt_intr_establish(struct ti_dmtimer_softc *, int,
		    unsigned int, int (*)(void *));
static void	dmt_setup_intr(struct ti_dmtimer_softc *, unsigned int);
static void	dmt_setup_timecounter(struct ti_dmtimer_softc *);
static void	dmt_reset(struct ti_dmtimer_softc *);
static void	dmt_start_autoreload(struct ti_dmtimer_softc *, unsigned int);
static void	dmt_enable(struct ti_dmtimer_softc *);
static void	dmt_intr_enable(struct ti_dmtimer_softc *, uint32_t);
static void	dmt_intr_ack(struct ti_dmtimer_softc *, uint32_t);
static dmt_reg_t
		dmt_timer_base(struct ti_dmtimer_softc *);
static uint32_t	dmt_timer_read_4(struct ti_dmtimer_softc *, dmt_timer_reg_t);
static void	dmt_timer_write_4(struct ti_dmtimer_softc *, dmt_timer_reg_t,
		    uint32_t);
static void	dmt_timer_write_post_wait(struct ti_dmtimer_softc *,
		    dmt_timer_reg_t);
static uint32_t	dmt_read_4(struct ti_dmtimer_softc *, dmt_reg_t);
static void	dmt_write_4(struct ti_dmtimer_softc *, dmt_reg_t, uint32_t);

CFATTACH_DECL_NEW(ti_dmtimer, sizeof(struct ti_dmtimer_softc),
    ti_dmtimer_match, ti_dmtimer_attach, NULL, NULL);

static const char *const compatible[] = {
	/* XXX possibly also omap3430-timer but not sure */
	"ti,am335x-timer",
	"ti,am335x-timer-1ms",
	NULL,
};

static const char *const compatible_v1[] = {
	/* XXX possibly also omap3430-timer but not sure */
	"ti,am335x-timer1ms",
	NULL,
};

static const char *const compatible_v2[] = {
	"ti,am335x-timer",
	NULL,
};

static const struct timecounter dmt_timecounter_template = {
	.tc_get_timecount	= dmt_tc_get_timecount,
	.tc_counter_mask	= 0xffffffff, /* XXXMAGIC Make sense?  */
	.tc_frequency		= -1,
	.tc_name		= "ti_dmtimerN",
	.tc_quality		= 100, /* XXXMAGIC?  */
	.tc_priv		= NULL,
};

static ONCE_DECL(ti_dmtimer_init_once);

static struct ti_dmtimer_softc *hardclock_sc = NULL;
static struct ti_dmtimer_softc *statclock_sc = NULL;

static int
ti_dmtimer_match(device_t parent, cfdata_t match, void *aux)
{
	struct fdt_attach_args *faa = aux;

	return of_match_compatible(faa->faa_phandle, compatible);
}

static void
ti_dmtimer_attach(device_t parent, device_t self, void *aux)
{
	struct ti_dmtimer_softc *sc = device_private(self);
	struct fdt_attach_args *faa = aux;
	bus_addr_t addr;
	bus_size_t size;

	/* Prepare to initialize the system clocks with TI dmtimers.  */
	if (RUN_ONCE(&ti_dmtimer_init_once, ti_dmtimer_init))
		panic("ti dmtimer setup failed");

	/* Record the bus and device parameters.  */
	sc->sc_dev = self;
	sc->sc_phandle = faa->faa_phandle;
	sc->sc_iot = faa->faa_bst;
	if (of_match_compatible(faa->faa_phandle, compatible_v1) != 0)
		sc->sc_version = 1;
	else if (of_match_compatible(faa->faa_phandle, compatible_v2) != 0)
		sc->sc_version = 2;
	else
		panic("%s: incompatible?", device_xname(self));

	/* Find and map the device registers.  */
	if (fdtbus_get_reg(sc->sc_phandle, 0, &addr, &size) != 0) {
		aprint_error(": couldn't get registers\n");
		return;
	}
	if (bus_space_map(sc->sc_iot, addr, size, 0, &sc->sc_ioh) != 0)
		panic("%s: unable to map bus space", device_xname(self));

	/* Find the clock so we can enable it later.  */
	sc->sc_clk = ti_hwmod_get_clk(sc->sc_phandle);

	/* Set up the timecounter, in case we want to use it.  */
	sc->sc_tc = dmt_timecounter_template;
	sc->sc_tc.tc_name = device_xname(self);
	sc->sc_tc.tc_frequency = clk_get_rate(sc->sc_clk);

	/* Decide how to use this timer, and report it.  */
	aprint_normal(": TI dual-mode timer v%u", sc->sc_version);
	if (sc->sc_version == 2 && hardclock_sc == NULL) {
		aprint_normal(", configuring as hardclock");
		hardclock_sc = sc;
	} else if (sc->sc_version == 2 && statclock_sc == NULL) {
		aprint_normal(", configuring as statclock");
		statclock_sc = sc;
	} else {
		aprint_normal(", configuring as timecounter");
	}
	aprint_normal("\n");
	aprint_naive("\n");
}

/*
 * ti_dmtimer_init()
 *
 *	One-time initialization to register the TI dmtimer clock
 *	initialization routine, which will run after we have scanned
 *	devices.
 */
static int
ti_dmtimer_init(void)
{

	arm_fdt_timer_register(ti_dmtimer_initclocks);
	return 0;
}

/*
 * ti_dmtimer_initclocks()
 *
 *	Initialize the system clocks based on the dmtimer devices we
 *	have attached so far: hardclock, statclock, timecounters.
 */
static void
ti_dmtimer_initclocks(void)
{
	struct ti_dmtimer_softc *sc;
	unsigned int i = 0;

	/* Establish the hardclock interrupt handler and kick it off.  */
	if (hardclock_sc == NULL)
		panic("ti dmtimer hardclock not initialized");
	dmt_intr_establish(hardclock_sc, IPL_CLOCK, &dmt_hardintr);
	dmt_enable(hardclock_sc);
	dmt_setup_intr(hardclock_sc, hz);

	/* Establish the statclock interrupt handler and kick it off.  */
	if (statclock_sc == NULL)
		panic("ti dmtimer statclock not initialized");
	dmt_intr_establish(statclock_sc, IPL_HIGH, &dmt_statintr);
	dmt_enable(statclock_sc);
	dmt_setup_intr(statclock_sc, stathz);

	/* Set up all other dmtimers as timecounters.  */
	/* XXX Why all?  Is that of any use?  */
	while ((sc = device_lookup_private(&ti_dmtimer_cd, i++)) != NULL) {
		if (sc == hardclock_sc)
			continue;
		if (sc == statclock_sc)
			continue;
		dmt_enable(sc);
		dmt_setup_timecounter(sc);
		tc_init(&sc->sc_tc);
	}
}

#if 0				/* XXX setstatclockrate */
/*
 * setstatclockrate(rate)
 *
 *	Set the statclock to sample at the specified frequency.
 */
void
setstatclockrate(int rate)
{
	struct ti_dmtimer_softc *sc = statclock_sc;

	if (rate < 0)
		panic("I can't run the statclock backward!");
	if (sc == NULL)
		panic("There is no statclock timer!\n");

	dmt_setup_intr(sc, rate);
}
#endif

/*
 * dmt_tc_get_timecount(tc)
 *
 *	Get the current counter value of the dmtimer behind the
 *	timecounter sc.
 */
static unsigned int
dmt_tc_get_timecount(struct timecounter *tc)
{
	struct ti_dmtimer_softc *sc = container_of(tc, struct ti_dmtimer_softc,
	    sc_tc);

	return dmt_timer_read_4(sc, TI_DMTIMER_TIMER_COUNTER);
}

/*
 * dmt_hardintr(frame)
 *
 *	Process a hardclock interrupt for the dmtimer of hardclock_sc:
 *	acknowledge the interrupt and call hardclock.
 */
static int
dmt_hardintr(void *frame)
{
	struct ti_dmtimer_softc *sc = hardclock_sc;

	KASSERT(sc != NULL);
	dmt_intr_ack(sc, TI_DMTIMER_INTR_ALL);
	hardclock(frame);

	return 1;
}

/*
 * dmt_statintr(frame)
 *
 *	Process a statclock interrupt for the dmtimer of statclock_sc:
 *	acknowledge the interrupt and call statclock.
 */
static int
dmt_statintr(void *frame)
{
	struct ti_dmtimer_softc *sc = statclock_sc;

	KASSERT(sc != NULL);
	dmt_intr_ack(sc, TI_DMTIMER_INTR_ALL);
	statclock(frame);

	return 1;
}

/*
 * dmt_intr_establish(sc, ipl, func)
 *
 *	Establish func as the interrupt handler for the dmtimer of sc,
 *	to run at ipl.
 */
static void
dmtr_intr_establish(struct ti_dmtimer_softc *sc, int ipl, int (*func)(void *))
{
	char intrstr[128];

	/* Get the fdtbus interrupt string, or panic if we can't.  */
	if (!fdtbus_intr_str(sc->sc_phandle, 0, intrstr, sizeof intrstr))
		panic("%s: failed to decode interrupt",
		    device_xname(sc->sc_dev));

	/*
	 * Establish the fdtbus interrupt handler, or panic if we can't.
	 *
	 * Null argument means func gets the interrupt frame.  For
	 * whatever reason it's not an option to pass an argument (such
	 * as sc) and the interrupt frame both, which is why we have
	 * the global hardclock_sc and statclock_sc.
	 */
	if (fdtbus_intr_establish(sc->sc_phandle, 0, ipl, FDT_INTR_MPSAFE,
		func, NULL) == NULL)
		panic("%s: failed to establish timer interrupt: %s",
		    device_xname(sc->sc_dev), intrstr);
}

/*
 * dmt_setup_intr(sc, freq)
 *
 *	Set up the dmtimer of sc to trigger interrupts at the specified
 *	frequency.  Resets the device to do so.
 */
static void
dmt_setup_intr(struct ti_dmtimer_softc *sc, unsigned int freq)
{
	unsigned int clkrate = clk_get_rate(sc->sc_clk);
	unsigned int ticks = clkrate/freq;
	unsigned int initcnt = 0xffffffff - (ticks - 1); /* = 2^32 - ticks */

	/*
	 * XXXPOWER Should do something clever with prescaling and
	 * clock selection to save power.
	 */

	dmt_reset(sc);
	dmt_start_autoreload(sc, initcnt);
	dmt_intr_enable(sc, TI_DMTIMER_INTR_OVERFLOW);
}

/*
 * dmt_setup_timecounter(sc)
 *
 *	Set up the dmtimer of sc to just count time without triggering
 *	any interrupts.  Resets the device to do so.
 */
static void
dmt_setup_timecounter(struct ti_dmtimer_softc *sc)
{

	/*
	 * XXXPOWER On reset, the timer uses the system clock.  For
	 * low-power operation, we can configure timers to use less
	 * frequent clocks, but there's currently no abstraction for
	 * doing this.
	 */

	dmt_reset(sc);
	dmt_start_autoreload(sc, 0);
}

/*
 * dmt_enable(sc)
 *
 *	Enable the clock that the dmtimer of sc runs on, if it was not
 *	already enabled.  (XXX Is it harmful or costly to repeat a call
 *	to clk_enable?)
 */
static void
dmt_enable(struct ti_dmtimer_softc *sc)
{

	if (!sc->sc_enabled) {
		clk_enable(sc->sc_clk);
		sc->sc_enabled = 1;
	}
}

/*
 * dmt_reset(sc)
 *
 *	Initiate a reset the dmtimer of sc and wait for the reset to
 *	complete.  Record whether it is in posted-write mode in
 *	sc->sc_posted.
 */
static void
dmt_reset(struct ti_dmtimer_softc *sc)
{
	uint32_t reset_mask, sync_int_ctrl;
	unsigned int tries = 1000; /* XXXMAGIC */

	/* Decide which reset mask to use by the core version.  */
	if (sc->sc_version == 1)
		reset_mask = TI_DMTIMER_V1_OCP_CFG_SOFTRESET_MASK;
	else
		reset_mask = TI_DMTIMER_V2_OCP_CFG_SOFTRESET_MASK;

	/* Write the reset mask and wait for it to post.  */
	dmt_write_4(sc, TI_DMTIMER_OCP_CFG, reset_mask);
	while (dmt_read_4(sc, TI_DMTIMER_OCP_CFG) & reset_mask) {
		if (--tries == 0)
			panic("unable to reset dmtimer %p", sc);
		DELAY(10);	/* XXXMAGIC */
	}

	/*
	 * Posted mode is enabled on reset on the OMAP35x but disabled
	 * on reset on the AM335x, so handle both cases.
	 *
	 * XXXPOWER Does enabling this reduce power consumption?
	 */
	sync_int_ctrl = dmt_timer_read_4(sc, TI_DMTIMER_TIMER_SYNC_INT_CTRL);
	sc->sc_posted = sync_int_ctrl & TI_DMTIMER_TIMER_SYNC_INT_CTRL_POSTED;
}

/*
 * dmt_start_autoreload(sc, initcnt)
 *
 *	Set up the dmtimer of sc to count up from an initial 32-bit
 *	counter value of initcnt, and, on overflow, to automatically
 *	restart at initcnt again; then start the dmtimer running.
 */
static void
dmt_start_autoreload(struct ti_dmtimer_softc *sc, unsigned int initcnt)
{

	dmt_timer_write_4(sc, TI_DMTIMER_TIMER_LOAD, initcnt);
	dmt_timer_write_4(sc, TI_DMTIMER_TIMER_COUNTER, initcnt);
	dmt_timer_write_4(sc, TI_DMTIMER_TIMER_CTRL,
	    TI_DMTIMER_TIMER_CTRL_START |
	    TI_DMTIMER_TIMER_CTRL_AUTORELOAD);
}

/*
 * dmt_intr_enable(sc, intr)
 *
 *	Enable the specified interrupts from the dmtimer of sc.
 */
static void
dmt_intr_enable(struct ti_dmtimer_softc *sc, uint32_t intr)
{

	if (sc->sc_version == 1) {
		dmt_write_4(sc, TI_DMTIMER_V1_INTR_ENABLE, intr);
	} else {
		dmt_write_4(sc, TI_DMTIMER_V2_INTR_ENABLE_CLEAR,
		    TI_DMTIMER_INTR_ALL & ~intr);
		dmt_write_4(sc, TI_DMTIMER_V2_INTR_ENABLE_SET, intr);
	}
}

/*
 * dmt_intr_ack(sc, intr)
 *
 *	Acknowledge receipt of the specified interrupts from the
 *	dmtimer of sc.
 */
static void
dmt_intr_ack(struct ti_dmtimer_softc *sc, uint32_t intr)
{

	if (sc->sc_version == 1)
		dmt_write_4(sc, TI_DMTIMER_V1_INTR_STATUS, intr);
	else
		dmt_write_4(sc, TI_DMTIMER_V2_INTR_STATUS, intr);
}

/*
 * dmt_timer_base(sc)
 *
 *	Return the register offset where the timer configuration
 *	registers start in the dmtimer's register map.  v2 cores have
 *	more interrupt configuration registers, so the timer
 *	configuration registers got pushed forward by a few words.
 */
static dmt_reg_t
dmt_timer_base(struct ti_dmtimer_softc *sc)
{

	if (sc->sc_version == 1)
		return TI_DMTIMER_V1_TIMER_REGS;
	else
		return TI_DMTIMER_V2_TIMER_REGS;
}

/*
 * dmt_timer_read_4(sc, reg)
 *
 *	Wait for any pending writes to the timer register
 *	(T_DMTIMER_TIMER_*) reg to post, and then read from it.
 */
static uint32_t
dmt_timer_read_4(struct ti_dmtimer_softc *sc, dmt_timer_reg_t reg)
{

	dmt_timer_write_post_wait(sc, reg);
	return dmt_read_4(sc, dmt_timer_base(sc) + (reg & 0xff));
}

/*
 * dmt_timer_write_4(sc, reg, value)
 *
 *	Wait for any prior writes to the timer register
 *	(T_DMTIMER_TIMER_*) reg to post, and then write value to it.
 */
static void
dmt_timer_write_4(struct ti_dmtimer_softc *sc, dmt_timer_reg_t reg,
    uint32_t value)
{

	dmt_timer_write_post_wait(sc, reg);
	dmt_write_4(sc, dmt_timer_base(sc) + (reg & 0xff), value);
}

/*
 * dmt_timer_write_post_wait(sc, reg)
 *
 *	If sc is in posted-write mode, and reg requires waiting for
 *	writes to post before subsequent use, wait for any pending
 *	write to reg to post.
 */
static void
dmt_timer_write_post_wait(struct ti_dmtimer_softc *sc, dmt_timer_reg_t reg)
{

	/*
	 * Make sure we can read the TWPS (TI_DMTIMER_TIMER_WRITE_POST)
	 * register with vanilla dmt_read_4.
	 */
	CTASSERT(TI_DMTIMER_TIMER_WRITE_POST ==
	    TI_DMTIMER_REG_POSTED_INDEX(TI_DMTIMER_TIMER_WRITE_POST));

	if (sc->sc_posted && TI_DMTIMER_REG_POSTED_P(reg)) {
		const uint32_t mask = TI_DMTIMER_REG_POSTED_MASK(reg);
		const dmt_reg_t write_post_reg = dmt_timer_base(sc) +
		    TI_DMTIMER_TIMER_WRITE_POST;
		unsigned int tries = 1000; /* XXXMAGIC */

		while (dmt_read_4(sc, write_post_reg) & mask) {
			if (--tries == 0)
				panic("dmtimer %p failed to post write", sc);
			DELAY(10);
		}
	}
}

/*
 * dmt_read_4(sc, reg)
 *
 *	Read the register reg from the dmtimer of sc.
 */
static uint32_t
dmt_read_4(struct ti_dmtimer_softc *sc, dmt_reg_t reg)
{

	return bus_space_read_4(sc->sc_iot, sc->sc_ioh, reg);
}


/*
 * dmt_write_4(sc, reg, value)
 *
 *	Write value to the register reg from the dmtimer of sc.
 */
static void
dmt_write_4(struct ti_dmtimer_softc *sc, dmt_reg_t reg, uint32_t value)
{

	bus_space_write_4(sc->sc_iot, sc->sc_ioh, reg, value);
}