/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 */
/*
 * Copyright (c) 2010, Intel Corporation.
 * All rights reserved.
 */

#include <sys/time.h>
#include <sys/psm.h>
#include <sys/psm_common.h>
#include <sys/apic.h>
#include <sys/pit.h>
#include <sys/x86_archext.h>
#include <sys/archsystm.h>
#include <sys/machsystm.h>
#include <sys/cpuvar.h>
#include <sys/clock.h>
#include <sys/apic_timer.h>

/*
 * preferred apic timer mode, allow tuning from the /etc/system file.
 */
int		apic_timer_preferred_mode = APIC_TIMER_MODE_DEADLINE;

int		apic_oneshot = 0;
uint_t		apic_hertz_count;
uint_t		apic_nsec_per_intr = 0;
uint64_t	apic_ticks_per_SFnsecs;		/* # of ticks in SF nsecs */

static int		apic_min_timer_ticks = 1; /* minimum timer tick */
static hrtime_t		apic_nsec_max;

static void	periodic_timer_enable(void);
static void	periodic_timer_disable(void);
static void	periodic_timer_reprogram(hrtime_t);
static void	oneshot_timer_enable(void);
static void	oneshot_timer_disable(void);
static void	oneshot_timer_reprogram(hrtime_t);
static void	deadline_timer_enable(void);
static void	deadline_timer_disable(void);
static void	deadline_timer_reprogram(hrtime_t);

extern int	apic_clkvect;
extern uint32_t	apic_divide_reg_init;

/*
 * apic timer data structure
 */
typedef struct apic_timer {
	int	mode;
	void	(*apic_timer_enable_ops)(void);
	void	(*apic_timer_disable_ops)(void);
	void	(*apic_timer_reprogram_ops)(hrtime_t);
} apic_timer_t;

static apic_timer_t	apic_timer;

/*
 * apic timer initialization
 *
 * For the one-shot mode request case, the function returns the
 * resolution (in nanoseconds) for the hardware timer interrupt.
 * If one-shot mode capability is not available, the return value
 * will be 0.
 */
int
apic_timer_init(int hertz)
{
	uint_t		apic_ticks = 0;
	uint_t		pit_ticks;
	int		ret, timer_mode;
	uint16_t	pit_ticks_adj;
	static int	firsttime = 1;

	if (firsttime) {
		/* first time calibrate on CPU0 only */

		apic_reg_ops->apic_write(APIC_DIVIDE_REG, apic_divide_reg_init);
		apic_reg_ops->apic_write(APIC_INIT_COUNT, APIC_MAXVAL);
		apic_ticks = apic_calibrate(apicadr, &pit_ticks_adj);

		/* total number of PIT ticks corresponding to apic_ticks */
		pit_ticks = APIC_TIME_COUNT + pit_ticks_adj;

		/*
		 * Determine the number of nanoseconds per APIC clock tick
		 * and then determine how many APIC ticks to interrupt at the
		 * desired frequency
		 * apic_ticks / (pitticks / PIT_HZ) = apic_ticks_per_s
		 * (apic_ticks * PIT_HZ) / pitticks = apic_ticks_per_s
		 * apic_ticks_per_ns = (apic_ticks * PIT_HZ) / (pitticks * 10^9)
		 * pic_ticks_per_SFns =
		 * (SF * apic_ticks * PIT_HZ) / (pitticks * 10^9)
		 */
		apic_ticks_per_SFnsecs = ((SF * apic_ticks * PIT_HZ) /
		    ((uint64_t)pit_ticks * NANOSEC));

		/* the interval timer initial count is 32 bit max */
		apic_nsec_max = APIC_TICKS_TO_NSECS(APIC_MAXVAL);
		firsttime = 0;
	}

	if (hertz == 0) {
		/* requested one_shot */

		/*
		 * return 0 if TSC is not supported.
		 */
		if (!tsc_gethrtime_enable)
			return (0);
		/*
		 * return 0 if one_shot is not preferred.
		 * here, APIC_TIMER_DEADLINE is also an one_shot mode.
		 */
		if ((apic_timer_preferred_mode != APIC_TIMER_MODE_ONESHOT) &&
		    (apic_timer_preferred_mode != APIC_TIMER_MODE_DEADLINE))
			return (0);

		apic_oneshot = 1;
		ret = (int)APIC_TICKS_TO_NSECS(1);
		if ((apic_timer_preferred_mode == APIC_TIMER_MODE_DEADLINE) &&
		    cpuid_deadline_tsc_supported()) {
			timer_mode = APIC_TIMER_MODE_DEADLINE;
		} else {
			timer_mode = APIC_TIMER_MODE_ONESHOT;
		}
	} else {
		/* periodic */
		apic_nsec_per_intr = NANOSEC / hertz;
		apic_hertz_count = APIC_NSECS_TO_TICKS(apic_nsec_per_intr);

		/* program the local APIC to interrupt at the given frequency */
		apic_reg_ops->apic_write(APIC_INIT_COUNT, apic_hertz_count);
		apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
		    (apic_clkvect + APIC_BASE_VECT) | AV_PERIODIC);
		apic_oneshot = 0;
		timer_mode = APIC_TIMER_MODE_PERIODIC;
		ret = NANOSEC / hertz;
	}

	/*
	 * initialize apic_timer data structure, install the timer ops
	 */
	apic_timer.mode = timer_mode;
	switch (timer_mode) {
	default:
		/* FALLTHROUGH */
	case APIC_TIMER_MODE_ONESHOT:
		apic_timer.apic_timer_enable_ops = oneshot_timer_enable;
		apic_timer.apic_timer_disable_ops = oneshot_timer_disable;
		apic_timer.apic_timer_reprogram_ops = oneshot_timer_reprogram;
		break;

	case APIC_TIMER_MODE_PERIODIC:
		apic_timer.apic_timer_enable_ops = periodic_timer_enable;
		apic_timer.apic_timer_disable_ops = periodic_timer_disable;
		apic_timer.apic_timer_reprogram_ops = periodic_timer_reprogram;
		break;

	case APIC_TIMER_MODE_DEADLINE:
		apic_timer.apic_timer_enable_ops = deadline_timer_enable;
		apic_timer.apic_timer_disable_ops = deadline_timer_disable;
		apic_timer.apic_timer_reprogram_ops = deadline_timer_reprogram;
		break;
	}

	return (ret);
}

/*
 * periodic timer mode ops
 */
/* periodic timer enable */
static void
periodic_timer_enable(void)
{
	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
	    (apic_clkvect + APIC_BASE_VECT) | AV_PERIODIC);
}

/* periodic timer disable */
static void
periodic_timer_disable(void)
{
	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
	    (apic_clkvect + APIC_BASE_VECT) | AV_MASK);
}

/* periodic timer reprogram */
static void
periodic_timer_reprogram(hrtime_t time)
{
	uint_t	ticks;
	/* time is the interval for periodic mode */
	ticks = APIC_NSECS_TO_TICKS(time);

	if (ticks < apic_min_timer_ticks)
		ticks = apic_min_timer_ticks;

	apic_reg_ops->apic_write(APIC_INIT_COUNT, ticks);
}

/*
 * oneshot timer mode ops
 */
/* oneshot timer enable */
static void
oneshot_timer_enable(void)
{
	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
	    (apic_clkvect + APIC_BASE_VECT));
}

/* oneshot timer disable */
static void
oneshot_timer_disable(void)
{
	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
	    (apic_clkvect + APIC_BASE_VECT) | AV_MASK);
}

/* oneshot timer reprogram */
static void
oneshot_timer_reprogram(hrtime_t time)
{
	hrtime_t	now;
	int64_t		delta;
	uint_t		ticks;

	now = gethrtime();
	delta = time - now;

	if (delta <= 0) {
		/*
		 * requested to generate an interrupt in the past
		 * generate an interrupt as soon as possible
		 */
		ticks = apic_min_timer_ticks;
	} else if (delta > apic_nsec_max) {
		/*
		 * requested to generate an interrupt at a time
		 * further than what we are capable of. Set to max
		 * the hardware can handle
		 */
		ticks = APIC_MAXVAL;
#ifdef DEBUG
		cmn_err(CE_CONT, "apic_timer_reprogram, request at"
		    "  %lld  too far in future, current time"
		    "  %lld \n", time, now);
#endif
	} else {
		ticks = APIC_NSECS_TO_TICKS(delta);
	}

	if (ticks < apic_min_timer_ticks)
		ticks = apic_min_timer_ticks;

	apic_reg_ops->apic_write(APIC_INIT_COUNT, ticks);
}

/*
 * deadline timer mode ops
 */
/* deadline timer enable */
static void
deadline_timer_enable(void)
{
	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
	    (apic_clkvect + APIC_BASE_VECT) | AV_DEADLINE);
}

/* deadline timer disable */
static void
deadline_timer_disable(void)
{
	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
	    (apic_clkvect + APIC_BASE_VECT) | AV_MASK);
}

/* deadline timer reprogram */
static void
deadline_timer_reprogram(hrtime_t time)
{
	uint64_t	ticks;

	if (time <= 0) {
		/*
		 * generate an immediate interrupt
		 */
		ticks = (uint64_t)tsc_read();
	} else {
		ticks = unscalehrtime(time);
	}

	wrmsr(IA32_DEADLINE_TSC_MSR, ticks);
}

/*
 * This function will reprogram the timer.
 *
 * When in oneshot mode the argument is the absolute time in future to
 * generate the interrupt at.
 *
 * When in periodic mode, the argument is the interval at which the
 * interrupts should be generated. There is no need to support the periodic
 * mode timer change at this time.
 */
void
apic_timer_reprogram(hrtime_t time)
{
	/*
	 * we should be Called from high PIL context (CBE_HIGH_PIL),
	 * so kpreempt is disabled.
	 */
	apic_timer.apic_timer_reprogram_ops(time);
}

/*
 * This function will enable timer interrupts.
 */
void
apic_timer_enable(void)
{
	/*
	 * we should be Called from high PIL context (CBE_HIGH_PIL),
	 * so kpreempt is disabled.
	 */
	apic_timer.apic_timer_enable_ops();
}

/*
 * This function will disable timer interrupts.
 */
void
apic_timer_disable(void)
{
	/*
	 * we should be Called from high PIL context (CBE_HIGH_PIL),
	 * so kpreempt is disabled.
	 */
	apic_timer.apic_timer_disable_ops();
}

/*
 * Set timer far into the future and return timer
 * current count in nanoseconds.
 */
hrtime_t
apic_timer_stop_count(void)
{
	hrtime_t	ns_val;
	int		enable_val, count_val;

	/*
	 * Should be called with interrupts disabled.
	 */
	ASSERT(!interrupts_enabled());

	enable_val = apic_reg_ops->apic_read(APIC_LOCAL_TIMER);
	if ((enable_val & AV_MASK) == AV_MASK)
		return ((hrtime_t)-1);	/* timer is disabled */

	count_val = apic_reg_ops->apic_read(APIC_CURR_COUNT);
	ns_val = APIC_TICKS_TO_NSECS(count_val);

	apic_reg_ops->apic_write(APIC_INIT_COUNT, APIC_MAXVAL);

	return (ns_val);
}

/*
 * Reprogram timer after Deep C-State.
 */
void
apic_timer_restart(hrtime_t time)
{
	apic_timer_reprogram(time);
}
