/*
 * Copyright (c) 2026, Chloe M.
 * Provided under the BSD-3 clause.
 *
 * Description: HPET driver
 * Author:      Chloe M.
 */

#include <machine/hpet.h>
#include <machine/hpetreg.h>
#include <drivers/acpi/tables.h>
#include <drivers/acpi/acpi.h>
#include <hal/mmio.h>
#include <mm/vmm.h>
#include <ke/knot.h>
#include <ex/trace.h>

#define DTRACE(Fmt, ...) \
    TRACE("[ HPET ]: " Fmt, ##__VA_ARGS__)

static VOID *MMIOBase = NULL;

/*
 * Write a 64-bit value to an HPET register
 *
 * @Register: Register to write to
 * @Value:    Value to write
 */
static VOID
HpetWriteq(UCHAR Register, UQUAD Value)
{
    VOID *RegBase;

    if (MMIOBase == NULL) {
        return;
    }

    RegBase = PTR_OFFSET(MMIOBase, Register);
    MMIOWrite64(MMIOBase, Value);
}

/*
 * Read a 64-bit value from an HPET register
 *
 * @Register:  Register to read from
 */
static UQUAD
HpetReadq(UCHAR Register)
{
    VOID *RegBase;

    if (MMIOBase == NULL) {
        return 0;
    }

    RegBase = PTR_OFFSET(MMIOBase, Register);
    return MMIORead64(RegBase);
}

VOID
MdHpetInit(VOID)
{
    ACPI_HPET *Hpet;
    UQUAD GeneralCap;
    ULONG ClkPeriod;
    UCHAR RevId;
    const ACPI_GAS *Gas;

    Hpet = AcpiQuery("HPET");
    if (Hpet == NULL) {
        KeKnot(
            KNOT_UNBOUND_RSRC,
            "could not detect HPET on this platform\n"
        );
    }

    /* Some informational logging */
    DTRACE("detected hpet with pci vendor id : %x\n", Hpet->PciVendorId);
    DTRACE("counter size : %d\n", Hpet->CounterSize);
    DTRACE("minimum tick : %d\n", Hpet->MinimumTick);

    /* Obtain the MMIO base address */
    Gas = &Hpet->Gas;
    MMIOBase = PMA_TO_VMA(Gas->Address);

    GeneralCap = HpetReadq(HPET_GENERAL_CAP);
    ClkPeriod = (GeneralCap >> HPET_PERIOD_SHIFT) & HPET_PERIOD_MASK;
    RevId = GeneralCap & 0xFF;
    DTRACE("counter clk period : %d\n", ClkPeriod);

    /*
     * Verify that the clock period and the revision ID are not
     * garbage values.
     */
    if (ClkPeriod > HPET_MAX_CLK_PERIOD)
        KeKnot(KNOT_MISC, "hpet: bad counter clk period\n");
    if (RevId == 0)
        KeKnot(KNOT_MISC, "hpet: bad revision id, must not be zero\n");
}
