/*
 * timer.c
 *
 * Copyright (C) 2013 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <timer.h>
#include <thread.h>

volatile qword_t total_ticks = 0;

void timer_irq(registers_t *regs, byte_t irq_num)
{
    total_ticks++;

    if (scheduler_enabled)
    {
        scheduler(regs);
        get_current_thread()->running_ticks++;
    }
}

sysret_t syscall_get_milliseconds(void)
{
    return total_ticks;
}

sysret_t syscall_get_nanoseconds(void)
{
    critical_t critical;
    enter_critical(&critical);

    qword_t result = total_ticks * 1000000ULL;

    cpu_write_port_byte(TIMER_CMD_PORT, 0x00);
    word_t count = cpu_read_port_byte(TIMER_CHANNEL_PORT(0));
    count |= cpu_read_port_byte(TIMER_CHANNEL_PORT(0)) << 8;
    count = -count;

    result += (count * 1000ULL) / (qword_t)TIMER_BASE_FREQUENCY;

    leave_critical(&critical);
    return result;
}

void timer_init(void)
{
    dword_t value = TIMER_BASE_FREQUENCY / TIMER_FREQUENCY;
    dword_t remainder = TIMER_BASE_FREQUENCY - value * TIMER_FREQUENCY;

    if ((remainder * 2) > TIMER_FREQUENCY) value++;
    if (value == 0) value = 1;
    if (value >= 65536) value = 0;

    cpu_write_port_byte(TIMER_CMD_PORT, TIMER_RATE_GENERATOR(0));
    cpu_write_port_byte(TIMER_CHANNEL_PORT(0), value & 0xFF);
    cpu_write_port_byte(TIMER_CHANNEL_PORT(0), (value >> 8) & 0xFF);
    register_irq_handler(TIMER_IRQ, &timer_irq, FALSE);
}
