/*
 * @author  xamidev <xamidev@riseup.net>
 * @brief   Programmable Interval Timer init and enabling
 * @license GPL-3.0-only
 */

#include <stdint.h>
#include <io/serial/serial.h>
#include <kernel.h>
#include <config.h>

/*
For now, the timer module will be using the PIC.
Even though it's quite old, it's still supported by newer CPUs
and it will be precise enough for what we'll do. Also it's easier
to implement than ACPI etc. (we may upgrade to ACPI when we're
interested in multi-core functionnality like SMP)
*/

volatile uint64_t ticks = 0;

extern struct init_status init;

/*
 * pic_remap - Remap the Programmable Interrupt Controller
 *
 * By default, interrupts are mapped at the wrong place.
 * This function remaps interrupt numbers so interrupts
 * don't conflict with each other. 
 */
void pic_remap()
{
    uint8_t master_mask = inb(0x21);
    uint8_t slave_mask  = inb(0xA1);

    // ICW1: start initialization
    outb(0x20, 0x11);
    outb(0xA0, 0x11);

    // ICW2: vector offsets
    outb(0x21, 0x20); // Master PIC -> 0x20
    outb(0xA1, 0x28); // Slave PIC  -> 0x28

    // ICW3: tell Master about Slave at IRQ2 (0000 0100)
    outb(0x21, 0x04);
    // ICW3: tell Slave its cascade identity (0000 0010)
    outb(0xA1, 0x02);

    // ICW4: 8086 mode
    outb(0x21, 0x01);
    outb(0xA1, 0x01);

    // Restore saved masks
    outb(0x21, master_mask);
    outb(0xA1, slave_mask);
}

/*
 * pic_enable - Enable the Programmable Interrupt Controller
 *
 * This function enables IRQ0 and IRQ1, which correspond to
 * the timer and keyboard interrupts, respectively.
 */
void pic_enable()
{
    // Enabling IRQ0 (unmasking it) but not the others
    uint8_t mask = inb(0x21);
    mask &= ~(1 << 0);  // Set IRQ0 (timer, clear bit 0)
    //mask &= ~(1 << 1); // Set IRQ1 (PS/2 Keyboard, clear bit 1)
    outb(0x21, mask);
}

/*
 * pit_init - Initialization of the Programmable Interval Timer
 *
 * The PIT is the simplest timer we can get working on x86.
 * It has a base frequency of 1.193182 MHz.
 * A custom frequency can be set using TIMER_FREQUENCY macro.
 */
void pit_init()
{
    uint32_t frequency = TIMER_FREQUENCY;
    uint32_t divisor = 1193182 / frequency;

    // Set PIT to mode 3, channel 0
    outb(0x43, 0x36); // 0x36

    // Send divisor (low byte, then high byte)
    outb(0x40, divisor & 0xFF);
    outb(0x40, (divisor >> 8) & 0xFF);
}

/*
 * timer_wait - Wait for X ticks
 *
 * By default, the timer frequency is 1000Hz, meaning
 * ticks are equal to milliseconds.
 */
void timer_wait(uint64_t wait_ticks)
{
    uint64_t then = ticks + wait_ticks;
    while (ticks < then) {
        asm("hlt");
    };
}

/*
 * timer_init - Initialization of the timer
 *
 * This function wakes the PIT.
 */
void timer_init()
{
    // Remapping the PIC, because at startup it conflicts with
    // the reserved IRQs we have for faults/exceptions etc.
    // so we move its IRQ0 to something not reserved (32)
    pic_remap();
    pic_enable();
    pit_init();
    DEBUG("PIT initialized");
    init.timer = true;
}