/*
 * Realtek Semiconductor Corp.
 *
 * arch/rlx/rlxocp0/irq.c
 *   Interrupt and exception initialization for RLX OCP Platform
 *
 * Tony Wu (tonywu@realtek.com.tw)
 * Nov. 7, 2006
 */
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kernel_stat.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/timex.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <linux/irq.h>

#include <asm/bitops.h>
#include <asm/bootinfo.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/irq_cpu.h>
#include <asm/irq_vec.h>
#include <asm/system.h>

#include <asm/rlxregs.h>
#include <asm/rlxbsp.h>
#include <net/rtl/rtl_types.h>
#include "bspchip.h"

static struct irqaction irq_cascade = { 
  .handler = no_action,
  .mask = CPU_MASK_NONE,
  .name = "cascade",
};

static void bsp_ictl_irq_mask(unsigned int irq)
{
	REG32(BSP_GIMR) &= ~(1 << (irq - BSP_IRQ_ICTL_BASE));
}

static void bsp_ictl_irq_unmask(unsigned int irq)
{
	REG32(BSP_GIMR) |= (1 << (irq - BSP_IRQ_ICTL_BASE));
}

static struct irq_chip bsp_ictl_irq = {
    .typename = "ICTL",
    .ack = bsp_ictl_irq_mask,
    .mask = bsp_ictl_irq_mask,
    .mask_ack = bsp_ictl_irq_mask,
    .unmask = bsp_ictl_irq_unmask,
};

static void bsp_ictl_irq_dispatch(void)
{
	volatile unsigned int pending;

	pending = REG32(BSP_GIMR) & REG32(BSP_GISR);

	if (pending & BSP_UART0_IP)
		do_IRQ(BSP_UART0_IRQ);
	else if (pending & BSP_TC1_IP)
		do_IRQ(BSP_TC1_IRQ);
	else if (pending & BSP_GPIO_ABCD_IP)
		do_IRQ(BSP_GPIO_ABCD_IRQ);
	else {
		REG32(BSP_GIMR) &= (~pending);
		REG32(BSP_GISR) = REG32(BSP_GISR);
		printk("Unknown Interrupt0:%x\n", pending);
		#if defined(CONFIG_RTK_VOIP) || defined(CONFIG_RTL_819X)
		spurious_interrupt(SPURIOS_INT_CASCADE);
		#else
		spurious_interrupt();
		#endif
	}
}

void bsp_irq_dispatch(void)
{
	volatile unsigned int pending;
	pending = read_c0_cause() & read_c0_status();
	
	if (pending & CAUSEF_IP2)
		bsp_ictl_irq_dispatch();
	else if (pending & CAUSEF_IP0)
		do_IRQ(0);
	else if (pending & CAUSEF_IP1)
		do_IRQ(1);
	else {
#if defined(CONFIG_RTK_VOIP) || defined(CONFIG_RTL_819X)
	spurious_interrupt(SPURIOS_INT_CPU);
#else
	spurious_interrupt();
#endif
	}
}

static void __init bsp_ictl_irq_init(unsigned int irq_base)
{
    int i;

    for (i=0; i < BSP_IRQ_ICTL_NUM; i++) 
        set_irq_chip_and_handler(irq_base + i, &bsp_ictl_irq, handle_level_irq);

    setup_irq(BSP_ICTL_IRQ, &irq_cascade);
}

void __init bsp_irq_init(void)
{
	//unsigned int	status;
	//volatile unsigned int status;
	/* disable ict interrupt */
	REG32(BSP_GIMR) = 0;

	/* initialize IRQ action handlers */
	rlx_cpu_irq_init(BSP_IRQ_CPU_BASE);
	rlx_vec_irq_init(BSP_IRQ_LOPI_BASE);
	bsp_ictl_irq_init(BSP_IRQ_ICTL_BASE);

	/* Set IRR */
	REG32(BSP_IRR0) = BSP_IRR0_SETTING;
	REG32(BSP_IRR1) = BSP_IRR1_SETTING;
	REG32(BSP_IRR2) = BSP_IRR2_SETTING;
	REG32(BSP_IRR3) = BSP_IRR3_SETTING;  

	//status = read_c0_status();
	//status = (status&(~ST0_IM))|(CAUSEF_IP2|CAUSEF_IP3|CAUSEF_IP4|CAUSEF_IP5|CAUSEF_IP6);
	//write_c0_status(status);
}

#if defined(CONFIG_RTL_8196C) && defined(CONFIG_ARCH_SUSPEND_POSSIBLE)//michaelxxx 
   #define CONFIG_RTL819X_SUSPEND_CHECK_INTERRUPT 
    
   #ifdef CONFIG_RTL819X_SUSPEND_CHECK_INTERRUPT 
   #include <linux/proc_fs.h> 
   #include <linux/kernel_stat.h> 
   #include <asm/uaccess.h> 
   //#define INT_HIGH_WATER_MARK 1850 //for window size = 1, based on LAN->WAN test result 
   //#define INT_LOW_WATER_MARK  1150 
   //#define INT_HIGH_WATER_MARK 9190 //for window size = 5, based on LAN->WAN test result 
   //#define INT_LOW_WATER_MARK  5500 
   #define INT_HIGH_WATER_MARK 3200  //for window size = 5, based on WLAN->WAN test result 
   #define INT_LOW_WATER_MARK  2200 
   #define INT_WINDOW_SIZE_MAX 10 
   static int suspend_check_enable = 1; 
   static int suspend_check_high_water_mark = INT_HIGH_WATER_MARK; 
   static int suspend_check_low_water_mark = INT_LOW_WATER_MARK; 
   static int suspend_check_win_size = 5; 
   static struct timer_list suspend_check_timer; 
   static int index=0; 
   static int eth_int_count[INT_WINDOW_SIZE_MAX]; 
   static int wlan_int_count[INT_WINDOW_SIZE_MAX]; 
   int cpu_can_suspend = 1; 
   int cpu_can_suspend_check_init = 0; 
    
   static int read_proc_suspend_check(char *page, char **start, off_t off, 
           int count, int *eof, void *data) 
   { 
       int len; 
    
       len = sprintf(page, "enable=%d, winsize=%d(%d), high=%d, low=%d, suspend=%d\n", 
                   suspend_check_enable, suspend_check_win_size, INT_WINDOW_SIZE_MAX, 
                   suspend_check_high_water_mark, suspend_check_low_water_mark, cpu_can_suspend); 
    
       if (len <= off+count) 
           *eof = 1; 
       *start = page + off; 
       len -= off; 
       if (len > count) 
           len = count; 
       if (len < 0) 
           len = 0; 
       return len; 
   } 
    
   static int write_proc_suspend_check(struct file *file, const char *buffer, 
                 unsigned long count, void *data) 
   { 
           char tmp[128]; 
    
           if (buffer && !copy_from_user(tmp, buffer, 128)) { 
                   sscanf(tmp, "%d %d %d %d", 
                           &suspend_check_enable, &suspend_check_win_size, 
                           &suspend_check_high_water_mark, &suspend_check_low_water_mark); 
                   if (suspend_check_win_size >= INT_WINDOW_SIZE_MAX) 
                           suspend_check_win_size = INT_WINDOW_SIZE_MAX - 1; 
                   if (suspend_check_enable) { 
                           mod_timer(&suspend_check_timer, jiffies + 100); 
                   } 
                   else { 
                           del_timer(&suspend_check_timer); 
                   } 
           } 
           return count; 
   } 
    
   static void suspend_check_timer_fn(unsigned long arg) 
   { 
           int count, j; 
    
           index++; 
           if (INT_WINDOW_SIZE_MAX <= index) 
                   index = 0; 
           eth_int_count[index] = kstat_irqs(BSP_SWCORE_IRQ); 
           wlan_int_count[index] = kstat_irqs(BSP_PCIE_IRQ); 
           j = index - suspend_check_win_size; 
           if (j < 0) 
                   j += INT_WINDOW_SIZE_MAX; 
           count = (eth_int_count[index] - eth_int_count[j]) + 
                   (wlan_int_count[index]- wlan_int_count[j]); //unit: number of interrupt occurred 
    
           if (cpu_can_suspend) { 
                   if (count > suspend_check_high_water_mark) { 
                           cpu_can_suspend = 0; 
                           //printk("\n<<<RTL8196C LEAVE SLEEP>>>\n"); /* for Debug Only*/ 
                   } 
           } 
           else { 
                   if (count < suspend_check_low_water_mark) { 
                           cpu_can_suspend = 1; 
                           //printk("\n<<<RTL8196C ENTER SLEEP>>>\n"); /* for Debug Only*/ 
                   } 
           } 
   #if 0 /* for Debug Only*/ 
           printk("###index=%d, count=%d (%d+%d) suspend=%d###\n",index, count, 
                   (eth_int_count[index] - eth_int_count[j]), 
                   (wlan_int_count[index]- wlan_int_count[j]), 
                   cpu_can_suspend); 
   #endif 
           mod_timer(&suspend_check_timer, jiffies + 100); 
   } 
    
   void suspend_check_interrupt_init(void) 
   { 
           struct proc_dir_entry *res; 
           int i; 
    
           res = create_proc_entry("suspend_check", 0, NULL); 
           if (res) { 
                   res->read_proc = read_proc_suspend_check; 
                   res->write_proc = write_proc_suspend_check; 
           } 
           else { 
                   printk("unable to create /proc/suspend_check\n"); 
           } 
    
           for (i=0; i<INT_WINDOW_SIZE_MAX; i++) { 
                   wlan_int_count[i] = 0; 
                   eth_int_count[i] = 0; 
           } 
           init_timer(&suspend_check_timer); 
           suspend_check_timer.data = 0; 
           suspend_check_timer.function = suspend_check_timer_fn; 
           suspend_check_timer.expires = jiffies + 100; /* in jiffies */ 
           add_timer(&suspend_check_timer); 
   	}
   #endif // CONFIG_RTL819X_SUSPEND_CHECK_INTERRUPT 
   #endif //CONFIG_RTL8196C