/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright © 2008 Rafaël Carré
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "config.h"
#include "system.h"
#include "kernel.h"
#include "panic.h"
#include "timer.h"

#if INCREASED_SCROLLWHEEL_POLLING
#include "button-target.h"
/* The scrollwheel is polled every 5 ms (the tick tasks only every 10) */
static int poll_scrollwheel = 0;

static inline void do_scrollwheel(void)
{
    if (!poll_scrollwheel)
        call_tick_tasks();      /* Run through the list of tick tasks
                                 * (that includes reading the scrollwheel) */
    else
    {
        if (!button_hold())
            get_scrollwheel(); /* Read the scrollwheel */
    }

    poll_scrollwheel ^= 1;
}
#else
static inline void do_scrollwheel(void)
{
    call_tick_tasks();      /* Run through the list of tick tasks */
}
#endif

#if defined(SANSA_C200V2)
#include "backlight-target.h"

static int timer2_cycles_per_tick = 0;
static int timer2_cycles_pwmon = 0;
static int timer2_cycles_pwmoff = 0;
static int timer2_pwm_state = 0;
static int timer2_pwm_on = 0;

void _set_timer2_pwm_ratio(int ratio)
{
    int cycles = timer2_cycles_per_tick;

    /*
     * Rather arbitrary limits, but since the CPU
     * needs some to time in the interrupt handler
     * there sure is some limit.
     * More specifically, if the cycles needed to do
     * the pwm handling are more than the reloaded counter needs
     * to reach 0 again it will reload to the old value most
     * likely leading to a (slight) slowdown in tick rate.
     */

    if (ratio < 10) {
        /*
         * Permanent off, reduce interrupt rate to save power
         */
        TIMER2_BGLOAD = cycles;
        timer2_pwm_on = 0;
        _backlight_pwm(0);
        return;
    }

    if (ratio > 990) {
        /*
         * Permanent on, reduce interrupt rate to save power
         */
        TIMER2_BGLOAD = cycles;
        timer2_pwm_on = 0;
        _backlight_pwm(1);
        return;
    }

    timer2_cycles_pwmon = cycles*ratio/1000;
    timer2_cycles_pwmoff = cycles*(1000-ratio)/1000;

    if (timer2_pwm_on == 0) {
        timer2_pwm_state = 0;
        timer2_pwm_on = 1;
        TIMER2_BGLOAD = timer2_cycles_pwmoff;
    }
}

static void set_timer2_cycles_per_tick(int cycles)
{
    timer2_cycles_per_tick = cycles;
}

static inline void do_sw_pwm(void)
{
    if (!timer2_pwm_on) {
        do_scrollwheel();  /* Handle scrollwheel and tick tasks */
        return;
    }

    timer2_pwm_state ^= 1;
    if (timer2_pwm_state) {
        TIMER2_BGLOAD = timer2_cycles_pwmoff;
        _backlight_pwm(1);
        /*
         * Always do scrollwheel and tick tasks during the longer cycle for safety,
         * since the short cycle can be quite short.
         * (minimum: 1us if ratio is 10 or 990 or 0.5us with scrollwheel,
         *  or just about 6000 clock cycles at 60MHz)
         */
        if (timer2_cycles_pwmon > timer2_cycles_pwmoff)
            do_scrollwheel();  /* Handle scrollwheel and tick tasks */
    } else {
        TIMER2_BGLOAD = timer2_cycles_pwmon;
        _backlight_pwm(0);
        if (!(timer2_cycles_pwmon > timer2_cycles_pwmoff))
            do_scrollwheel();  /* Handle scrollwheel and tick tasks */
    }
}
#else
static inline void do_sw_pwm(void)
{
    do_scrollwheel();  /* Handle scrollwheel and tick tasks */
}

static void set_timer2_cycles_per_tick(int cycles)
{
    (void)cycles;
}
#endif


void INT_TIMER2(void)
{
    /*
     * Timer is stacked as follows:
     * Lowest layer: Software PWM (if configured)
     *   Alternates timer2 reload value to implement
     *   software pwm at 100Hz (no scrollwheel)
     *   or 200Hz (scrollwheel) with variable pulse width 1% to 99%
     * Middle layer: Scrollwheel handling (if configured, 200Hz)
     *   Alternate between polling scrollwheel and running tick
     *   tasks (includes scrollwheel polling).
     * Top layer: Run tick tasks at 100Hz
     */
    do_sw_pwm();

    TIMER2_INTCLR = 0;  /* clear interrupt */
}

void tick_start(unsigned int interval_in_ms)
{
    int cycles = KERNEL_TIMER_FREQ / 1000 * interval_in_ms;

    bitset32(&CGU_PERI, CGU_TIMER2_CLOCK_ENABLE);    /* enable peripheral */
    VIC_INT_ENABLE = INTERRUPT_TIMER2;     /* enable interrupt */

    set_timer2_cycles_per_tick(cycles);
    TIMER2_LOAD = TIMER2_BGLOAD = cycles;   /* timer period */

    /* /!\ bit 4 (reserved) must not be modified
     * periodic mode, interrupt enabled, no prescale, 32 bits counter */
    TIMER2_CONTROL = (TIMER2_CONTROL & (1<<4)) |
                     TIMER_ENABLE |
                     TIMER_PERIODIC |
                     TIMER_INT_ENABLE |
                     TIMER_32_BIT;
}
