/*
 * Copyright (c) 2019 Intel Corporation
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/init.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/sys_clock.h>
#include <zephyr/spinlock.h>
#include <zephyr/drivers/interrupt_controller/loapic.h>
#include <zephyr/irq.h>

BUILD_ASSERT(!IS_ENABLED(CONFIG_TICKLESS_KERNEL), "this is a tickfull driver");

/*
 * Overview:
 *
 * This driver enables the local APIC as the Zephyr system timer. It supports
 * legacy ("tickful") mode only. The driver will work with any APIC that has
 * the ARAT "always running APIC timer" feature (CPUID 0x06, EAX bit 2).
 *
 * Configuration:
 *
 * CONFIG_APIC_TIMER=y			enables this timer driver.
 * CONFIG_APIC_TIMER_IRQ=<irq>		which IRQ to configure for the timer.
 * CONFIG_APIC_TIMER_IRQ_PRIORITY=<p>	priority for IRQ_CONNECT()
 *
 * CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=<hz> must contain the frequency seen
 *     by the local APIC timer block (before it gets to the timer divider).
 */

/* These should be merged into include/drivers/interrupt_controller/loapic.h. */

#define DCR_DIVIDER_MASK	0x0000000F	/* divider bits */
#define DCR_DIVIDER		0x0000000B	/* divide by 1 */
#define LVT_MODE_MASK		0x00060000	/* timer mode bits */
#define LVT_MODE		0x00020000	/* periodic mode */

#if defined(CONFIG_TEST)
const int32_t z_sys_timer_irq_for_test = CONFIG_APIC_TIMER_IRQ;
#endif

#define CYCLES_PER_TICK \
	(CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / CONFIG_SYS_CLOCK_TICKS_PER_SEC)

BUILD_ASSERT(CYCLES_PER_TICK >= 1, "APIC timer: bad CYCLES_PER_TICK");

static volatile uint64_t total_cycles;

static void isr(const void *arg)
{
	ARG_UNUSED(arg);

	total_cycles += CYCLES_PER_TICK;
	sys_clock_announce(1);
}

uint32_t sys_clock_elapsed(void)
{
	return 0U;
}

uint64_t sys_clock_cycle_get_64(void)
{
	uint32_t ccr_1st, ccr_2nd;
	uint64_t cycles;

       /*
	* We may race with CCR reaching 0 and reloading, and the interrupt
	* handler updating total_cycles. Let's make sure we sample everything
	* away from this roll-over transition by ensuring consecutive CCR
	* values are descending so we're sure the enclosed (volatile)
	* total_cycles sample and CCR value are coherent with each other.
	*/
	do {
		ccr_1st = x86_read_loapic(LOAPIC_TIMER_CCR);
		cycles = total_cycles;
		ccr_2nd = x86_read_loapic(LOAPIC_TIMER_CCR);
	} while (ccr_2nd > ccr_1st);

	return cycles + (CYCLES_PER_TICK - ccr_2nd);
}

uint32_t sys_clock_cycle_get_32(void)
{
	return (uint32_t)sys_clock_cycle_get_64();
}

static int sys_clock_driver_init(void)
{
	uint32_t val;


	val = x86_read_loapic(LOAPIC_TIMER_CONFIG);	/* set divider */
	val &= ~DCR_DIVIDER_MASK;
	val |= DCR_DIVIDER;
	x86_write_loapic(LOAPIC_TIMER_CONFIG, val);

	val = x86_read_loapic(LOAPIC_TIMER);		/* set timer mode */
	val &= ~LVT_MODE_MASK;
	val |= LVT_MODE;
	x86_write_loapic(LOAPIC_TIMER, val);

	/* remember, wiring up the interrupt will mess with the LVT, too */

	IRQ_CONNECT(CONFIG_APIC_TIMER_IRQ,
		CONFIG_APIC_TIMER_IRQ_PRIORITY,
		isr, 0, 0);

	x86_write_loapic(LOAPIC_TIMER_ICR, CYCLES_PER_TICK);
	irq_enable(CONFIG_APIC_TIMER_IRQ);

	return 0;
}

SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2,
	 CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
