/*++ 
 * linux/drivers/input/rmtctl/wmt-rmtctl.c
 * WonderMedia input remote control driver
 *
 * Copyright c 2010  WonderMedia  Technologies, Inc.
 *
 * 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 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 General Public License for more details. 
 *
 * You should have received a copy of the GNU General Public License 
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * WonderMedia Technologies, Inc.
 * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C
--*/
  
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/version.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <asm/irq.h>
#include <mach/hardware.h>
#include <mach/wmt_pmc.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
#include <linux/gpio.h>

#include "wmt-rmtctl.h"

enum CIR_CODEC_TYPE {
	CIR_CODEC_NEC = 0,
	CIR_CODEC_TOSHIBA = 1,
	CIR_CODEC_PHILIPS_RC6 = 2,
	CIR_CODEC_MAX,
};

struct cir_param {
	enum CIR_CODEC_TYPE codec;
	unsigned int param[7];
	unsigned int repeat_timeout;
};

struct cir_vendor_info {
	char *vendor_name;
	enum CIR_CODEC_TYPE codec;
	
	unsigned int vendor_code;
	unsigned int wakeup_code;
	unsigned int key_codes[258]; // usb-keyboard standard
};

#define RMTCTL_DEBUG 0
#define REL_DELTA    20
#define WMT_RMTCTL_VENDOR_ENV "wmt.io.rmtctl.vendorcode"
struct rmtctl_led{
    int gpio;
    int active;
    int on;
    int enable;
    struct timer_list timer;
};

struct rmtctl_rel {
	int on;
	unsigned int code;
	struct timer_list timer;
	struct input_dev *input;
};

struct rmtctl_priv {
	enum CIR_CODEC_TYPE codec;
	int vendor_index;
    int table_index;
    unsigned int saved_vcode;
	unsigned int vendor_code;
	unsigned int scan_code;

	struct input_dev *idev;
	struct timer_list timer;
	struct delayed_work delaywork;

    struct rmtctl_led led;//led control
    
    struct rmtctl_rel rel_dev;//remote cursor removing
};

static struct cir_param rmtctl_params[] = {
	// NEC : |9ms carrier wave| + |4.5ms interval|
	[CIR_CODEC_NEC] = {
		.codec = CIR_CODEC_NEC,
		.param = { 0x10a, 0x8e, 0x42, 0x55, 0x9, 0x13, 0x13 },
		.repeat_timeout = 17965000,
	},

	// TOSHIBA : |4.5ms carrier wave| + |4.5ms interval|
	[CIR_CODEC_TOSHIBA] = {
		.codec = CIR_CODEC_TOSHIBA,
		.param = { 0x8e, 0x8e, 0x42, 0x55, 0x9, 0x13, 0x13 },
		.repeat_timeout = 17965000,
	},

	[CIR_CODEC_PHILIPS_RC6] = {
		.codec = CIR_CODEC_PHILIPS_RC6,
		.param = { 0x7, 0x15, 0x23, 0x31, 0x40, 0x4C, 0x5B },
		.repeat_timeout = 17965000,
	},
};

static struct cir_vendor_info rmtctl_vendors[] = {

//Add by Flourise Kevin for FuRuiYing IR keypad 2014.12.07 Start
    {
		.vendor_name = "FRY-01",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x00FF,
		.wakeup_code = 0x0F,
		.key_codes = {
			//No.1 Line
			[0x0F] = KEY_POWER,				/* power down */
			[0x52] = KEY_HOME,				/* Home */
			[0x10] = KEY_MENU,				/* menu */
			[0x58] = KEY_MUTE,				/* mute */

			//No.2 Line
			[0x5E] = KEY_KATAKANA,		 /* cursor */ 
			[0x4D] = KEY_UP,			 /* up */
			[0x11] = KEY_PREVIOUSSONG,   /*previous song*/
			[0x5C] = KEY_VOLUMEUP,		 /*volume Up*/		
            
            //No.3 Line	
			[0x57] = KEY_LEFT,			/*left*/
			[0x5B] = KEY_ENTER,		    /* OK */
			[0x5F] = KEY_RIGHT,			/*Right*/	
			[0x54] = KEY_VOLUMEDOWN,	/*volume down*/

			//No.4 Line
			[0x56] = KEY_TAB,			/*table key*/
			[0x5A] = KEY_DOWN,			/*Down*/
			[0x12] = KEY_NEXTSONG,		/*next song*/
			[0x53] = KEY_BACK,			/* back */	
			
			//No.5 Line
			[0x17] = KEY_1,
			[0x1B] = KEY_2,
			[0x1F] = KEY_3,
			[0x16] = KEY_4,		

			//No.6 Line
			[0x1A] = KEY_5,
			[0x1E] = KEY_6,
			[0x15] = KEY_7,
			[0x19] = KEY_8,						

			//No.7 Line
			[0x1D] = KEY_9,
			[0x18] = KEY_0,
			[0x14] = KEY_DOT, 			/*dot //small point*/ 
			[0x1c] = KEY_BACKSPACE,     /* backspace */

			//No.8 Line
			[0x13] = KEY_PAUSE, /*vod */      
			[0x50] = KEY_PLAY, /*live */      
			[0x59] = KEY_PAUSE, /*pause */      			
			[0x55] = KEY_PLAY, /*play */      


		},
	},	
	//Add by Flourise Kevin for FuRuiYing IR keypad 2014.12.07 End
	

	//Add by Flourise Kevin for FuRuiYing IR keypad 2014.11.29 Start
    {
		.vendor_name = "Ly-01",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x00FF,
		.wakeup_code = 0x18,
		.key_codes = {
			[0x18] = KEY_POWER,			/* power down */
			[0xF2] = KEY_SWITCHVIDEOMODE, /*Cycle between available vide outputs (Monitor/LCD/TV-out/etc) */      

			[0x54] = KEY_1,
			[0x48] = KEY_2,
			[0x07] = KEY_3,
			[0x50] = KEY_4,
			[0x12] = KEY_5,
			[0x11] = KEY_6,
			[0x4c] = KEY_7,
			[0x0e] = KEY_8,
			[0x0d] = KEY_9,
//			[0x41] = KEY_SW_INPUT_TYPE, /*switch input type*/ 
			[0x0c] = KEY_0,
			[0x4b] = KEY_BACKSPACE,     /* backspace */

			[0x06] = KEY_HOME,			/* Home */
			[0x40] = KEY_BACK,			/* back */

			[0x46] = KEY_UP,			/* up */

			[0x47] = KEY_LEFT,			/*left*/
			[0x55] = KEY_ENTER,		     /* OK */
			[0x15] = KEY_RIGHT,			/*Right*/						
	
			[0x16] = KEY_DOWN,			/*Down*/

			[0x04] = KEY_MENU,			 /* menu */
            [0x17] = KEY_KATAKANA,		 /* cursor */  
			
			

			[0x14] = KEY_VOLUMEDOWN,	 /*volume down*/
			[0x08] = KEY_MUTE,			  /* mute */
            [0x10] = KEY_VOLUMEUP,		 /*volume Up*/
			
			
//			[0x56] = KEY_TV,			 /*start TV*/
//			[0x57] = KEY_QQ,			 /*start QQ*/
			[0x1f] = KEY_PREVIOUSSONG,   /*previous song*/
			[0x5b] = KEY_NEXTSONG,		/*next song*/
		},
	},	
	//Add by Flourise Kevin for FuRuiYing IR keypad 2014.11.29 End
	
	
	
	// SRC1804 0
	{
		.vendor_name = "SRC1804",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x02fd,
		.wakeup_code = 0x57,
		.key_codes = {
			[0x57] = KEY_POWER,      /* power down */
			[0x56] = KEY_VOLUMEDOWN, /* vol- */
			[0x14] = KEY_VOLUMEUP,   /* vol+ */
			[0x53] = KEY_HOME,       /* home */
			[0x11] = KEY_MENU,       /* menu */
			[0x10] = KEY_BACK,       /* back */
			[0x4b] = KEY_ZOOMOUT,    /* zoom out */
			[0x08] = KEY_ZOOMIN,     /* zoom in */
			[0x0d] = KEY_UP,         /* up */
			[0x4e] = KEY_LEFT,       /* left */
			[0x19] = KEY_ENTER,      /* OK */
			[0x0c] = KEY_RIGHT,      /* right */
			[0x4f] = KEY_DOWN,       /* down */
			[0x09] = KEY_PAGEUP,     /* page up */
			[0x47] = KEY_PREVIOUSSONG, /* rewind */
			[0x05] = KEY_PAGEDOWN,   /* page down */
			[0x04] = KEY_NEXTSONG    /* forward */
		},
	},

	// IH8950 1
	{
		.vendor_name = "IH8950",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x0909,
		.wakeup_code = 0xdc,
		.key_codes = {
			[0xdc] = KEY_POWER,	     /* power down */
			[0x81] = KEY_VOLUMEDOWN, /* vol- */
			[0x80] = KEY_VOLUMEUP,	 /* vol+ */
			[0x82] = KEY_HOME,		 /* home */
			[0xc5] = KEY_BACK,		 /* back */
			[0xca] = KEY_UP,		 /* up */
			[0x99] = KEY_LEFT,		 /* left */
			[0xce] = KEY_ENTER, 	 /* OK */
			[0xc1] = KEY_RIGHT, 	 /* right */
			[0xd2] = KEY_DOWN,		 /* down */
			[0x9c] = KEY_MUTE,
			[0x95] = KEY_PLAYPAUSE,
			[0x88] = KEY_MENU,
		},
	},

	// sunday 2
	{
		.vendor_name = "sunday",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x02fd,
		.wakeup_code = 0x1a,
		.key_codes = {
			[0x1a] = KEY_POWER,	     /* power down */
			[0x16] = KEY_VOLUMEDOWN, /* vol- */
			[0x44] = KEY_VOLUMEUP,	 /* vol+ */
			[0x59] = KEY_HOME,		 /* home */
			[0x1b] = KEY_BACK,		 /* back */
			[0x06] = KEY_UP,		 /* up */
			[0x5d] = KEY_LEFT,		 /* left */
			[0x1e] = KEY_ENTER, 	 /* OK */
			[0x5c] = KEY_RIGHT, 	 /* right */
			[0x1f] = KEY_DOWN,		 /* down */
			[0x55] = KEY_PLAYPAUSE,
			[0x54] = KEY_REWIND,
			[0x17] = KEY_FASTFORWARD,
			[0x58] = KEY_AGAIN,      /* recent app */
		},
	},

	/* F1 - F12 */
	// KT-9211 3
	{
		.vendor_name = "KT-9211",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x02fd,
		.wakeup_code = 0x57,
		.key_codes = {
			[0x57] = KEY_POWER,      /* power down */
			[0x56] = KEY_VOLUMEDOWN, /* volume- */
			[0x15] = KEY_MUTE,       /* mute */ 
			[0x14] = KEY_VOLUMEUP,   /* volume+ */
			[0x0d] = KEY_UP,         /* up */
			[0x4e] = KEY_LEFT,       /* left */
			[0x19] = KEY_ENTER,      /* OK */
			[0x0c] = KEY_RIGHT,      /* right */
			[0x4f] = KEY_DOWN,       /* down */
			[0x53] = KEY_HOME,       /* home */ 
			[0x09] = KEY_F5,		 /* my photo in default */ 
			[0x11] = KEY_F12,        /* setting apk in default */ 
			[0x47] = KEY_F3,         /* My Music in default */
			[0x10] = KEY_BACK,       /* Back */ 
			[0x08] = KEY_F4,		 /* my video in default */ 
			[0x17] = KEY_F1,         /* web browser in default  */ 

			//Following items are configured manually.
			[0x05] = KEY_F10,       /* file browser in default  */ 
			[0x04] = KEY_F2,        /* camera in default */ 
			[0x4b] = KEY_F11,       /* calendar in default */ 
			[0x16] = KEY_SCREEN,    /* calculator in default */ 
			[0x18] = KEY_F9,        /* recorder in default */ 
		},
	},

	// KT-8830 4
	{
		.vendor_name = "KT-8830",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x866b,
		.wakeup_code = 0x1c,
		.key_codes = {
			[0x1c] = KEY_POWER,
			[0x14] = KEY_MUTE,
			[0x09] = KEY_1,
			[0x1d] = KEY_2,
			[0x1f] = KEY_3,
			[0x0d] = KEY_4,
			[0x19] = KEY_5,
			[0x1b] = KEY_6,
			[0x11] = KEY_7,
			[0x15] = KEY_8,
			[0x17] = KEY_9,
			[0x12] = KEY_0,
			[0x16] = KEY_VOLUMEDOWN,
			[0x04] = KEY_VOLUMEUP,
			[0x40] = KEY_HOME,
			[0x4c] = KEY_BACK,
			[0x00] = KEY_F12, // setup
			[0x13] = KEY_MENU,
			[0x03] = KEY_UP,
			[0x0e] = KEY_LEFT,
			[0x1a] = KEY_RIGHT,
			[0x02] = KEY_DOWN,
			[0x07] = KEY_ENTER,
			[0x47] = KEY_F4, // video
			[0x10] = KEY_F3, // music
			[0x0f] = KEY_FASTFORWARD,
			[0x43] = KEY_REWIND,
			[0x18] = KEY_F1, // browser
			[0x0b] = KEY_YEN,  // multi-functions
			[0x4e] = KEY_PLAYPAUSE,
		},
	},

	// Haier-OTT 5
	{
		.vendor_name = "Haier-OTT",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0xb34c,
		.wakeup_code = 0xdc,
		.key_codes = {
			[0xdc] = KEY_POWER,
			[0x9c] = KEY_MUTE,
			[0xca] = KEY_UP,
			[0x99] = KEY_LEFT,
			[0xc1] = KEY_RIGHT,
			[0xd2] = KEY_DOWN,
			[0xce] = KEY_ENTER,
			[0x80] = KEY_VOLUMEDOWN,
			[0x81] = KEY_VOLUMEUP,
			[0xc5] = KEY_HOME,
			[0x95] = KEY_BACK,
			[0x88] = KEY_MENU,
			[0x82] = KEY_F12,        /* setting apk in default */ 
		},
	},

	// GPRC-11933 6
	{
		.vendor_name = "GPRC-11933",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x4040,
		.wakeup_code = 0x4D,
		.key_codes = {
			[0x4D] = KEY_POWER,      /* power down */
            [0x53] = KEY_PLAYPAUSE,      
            [0x5B] = KEY_F3,         /* My Music in default */
            [0x57] = KEY_F1,         /* web browser in default */ 
            [0x54] = KEY_F10,        /* file browser in default */ 
            
			[0x17] = KEY_VOLUMEDOWN, /* volume- */
			[0x43] = KEY_MUTE,       /* mute */ 
			[0x18] = KEY_VOLUMEUP,   /* volume+ */
			[0x1F] = KEY_PREVIOUSSONG, /* rewind */
			[0x1E] = KEY_NEXTSONG,    /* forward */
			
			[0x0B] = KEY_UP,         /* up */
			[0x10] = KEY_LEFT,       /* left */
			[0x0D] = KEY_ENTER,      /* OK */
			[0x11] = KEY_RIGHT,      /* right */
			[0x0E] = KEY_DOWN,       /* down */
			[0x1A] = KEY_HOME,       /* home */ 
			[0x42] = KEY_BACK,       /* Back */ 			

            [0x45] = KEY_F12,        /* setting apk in default */ 
            [0x47] = KEY_KATAKANA,        /* multi-functions */
			[0x01] = KEY_1,
			[0x02] = KEY_2,
			[0x03] = KEY_3,
			[0x04] = KEY_4,
			[0x05] = KEY_5,
			[0x06] = KEY_6,
			[0x07] = KEY_7,
			[0x08] = KEY_8,
			[0x09] = KEY_9,
			[0x00] = KEY_0,		
			[0x44] = KEY_MENU,
			[0x0C] = KEY_BACKSPACE,
		},
	},

	// TS-Y118 7
	{
		.vendor_name = "TS-Y118",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0xb34c,
		.wakeup_code = 0xdc,
		.key_codes = {
			[0xdc] = KEY_POWER,
			[0x9c] = KEY_MUTE,
			[0x8d] = KEY_F12, // apk-settings
			[0x88] = KEY_HOME,
			[0xca] = KEY_UP,
			[0xd2] = KEY_DOWN,
			[0x99] = KEY_LEFT,
			[0xc1] = KEY_RIGHT,
			[0xce] = KEY_ENTER,
			[0x95] = KEY_PLAYPAUSE,
			[0xc5] = KEY_BACK,
			[0x80] = KEY_VOLUMEUP,
			[0x81] = KEY_VOLUMEDOWN,
			[0xdd] = KEY_PAGEUP,
			[0x8c] = KEY_PAGEDOWN,
			[0x92] = KEY_1,
			[0x93] = KEY_2,
			[0xcc] = KEY_3,
			[0x8e] = KEY_4,
			[0x8f] = KEY_5,
			[0xc8] = KEY_6,
			[0x8a] = KEY_7,
			[0x8b] = KEY_8,
			[0xc4] = KEY_9,
			[0x87] = KEY_0,
		},
	},

	// Hisense 8
	{
		.vendor_name = "Hisense",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x00ff,
		.wakeup_code = 0x14,
		.key_codes = {
			[0x14] = KEY_POWER,
			[0x1c] = KEY_MUTE,
			[0x40] = KEY_PAGEUP,
			[0x44] = KEY_PAGEDOWN,
			[0x0b] = KEY_VOLUMEUP,
			[0x58] = KEY_VOLUMEDOWN,
			[0x01] = KEY_MENU,
			[0x03] = KEY_UP,
			[0x02] = KEY_DOWN,
			[0x0e] = KEY_LEFT,
			[0x1a] = KEY_RIGHT,
			[0x07] = KEY_ENTER,
			[0x48] = KEY_HOME,
			[0x5c] = KEY_BACK,
			[0x09] = KEY_1,
			[0x1d] = KEY_2,
			[0x1f] = KEY_3,
			[0x0d] = KEY_4,
			[0x19] = KEY_5,
			[0x1b] = KEY_6,
			[0x11] = KEY_7,
			[0x15] = KEY_8,
			[0x17] = KEY_9,
			[0x12] = KEY_0,
			[0x06] = KEY_DOT,
			[0x16] = KEY_DELETE,
			[0x0c] = KEY_ZOOMIN,
			[0x4c] = KEY_F11,
			[0x13] = KEY_YEN,
		},
	}, 

	// Mountain 9
	{
		.vendor_name = "TV BOX Mountain",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x00df,
		.wakeup_code = 0x1c,
		.key_codes = {
			[0x1c] = KEY_POWER,      /* power down */
			[0x08] = KEY_MUTE,		 /* volume mute */
			[0x1a] = KEY_UP,         /* up */
			[0x47] = KEY_LEFT,       /* left */
			[0x06] = KEY_ENTER,      /* OK */
			[0x07] = KEY_RIGHT,      /* right */
			[0x48] = KEY_DOWN,       /* down */
			[0x4f] = KEY_VOLUMEDOWN, /* vol- */
			[0x4b] = KEY_VOLUMEUP,   /* vol+ */
			[0x0a] = KEY_BACK,       /* back */
			[0x03] = KEY_HOME,       /* home */
			[0x42] = KEY_KATAKANA,         /* TV */
			[0x55] = KEY_MENU,       /* menu */
		},
	},

    // GPRC-11933 10
	{
		.vendor_name = "GPRC-11933A",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x00FF,
		.wakeup_code = 0x57,
		.key_codes = {
			[0x57] = KEY_POWER,      /* power down */
            [0x5B] = KEY_MUTE,       /* mute */ 

            [0x16] = KEY_F3,         /* My Musicin default */ 
            [0x5A] = KEY_F1,         /* web browser in default */
            [0x52] = KEY_PLAYPAUSE,
            [0x50] = KEY_KATAKANA,   /* cursor */

            [0x0F] = KEY_PREVIOUSSONG, /* rewind */
			[0x4C] = KEY_NEXTSONG,    /* forward */
            [0x58] = KEY_VOLUMEDOWN,        /* volume- */                  
            [0x1B] = KEY_VOLUMEUP,         /* volume+ */
                                 
            [0x4F] = KEY_F12,        /* setting apk in default */  
            [0x1A] = KEY_MENU,
            
			[0x43] = KEY_UP,         /* up */
			
			[0x06] = KEY_LEFT,       /* left */
			[0x02] = KEY_ENTER,      /* OK */
			[0x0E] = KEY_RIGHT,      /* right */
			
			[0x0A] = KEY_DOWN,       /* down */

            [0x4E] = KEY_HOME,       /* home */
            [0x4D] = KEY_BACK,       /* back */
            
			[0x10] = KEY_1,
			[0x11] = KEY_2,
			[0x12] = KEY_3,
			[0x13] = KEY_4,
			[0x14] = KEY_5,
			[0x15] = KEY_6,
			[0x17] = KEY_7,
			[0x18] = KEY_8,
			[0x19] = KEY_9,
			[0x1D] = KEY_0,	

            [0x1C] = KEY_F1,			
			[0x1E] = KEY_BACKSPACE,
		},
	},  
	
    //China Telecom White 11
    {
		.vendor_name = "TS-Y118A",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0xb34c,
		.wakeup_code = 0xdc,
		.key_codes = {
			[0xdc] = KEY_POWER,
                
            [0x98] =KEY_AUDIO,    
			[0x9c] = KEY_MUTE,
			
			[0x8d] = KEY_SETUP, 
			[0xd6] = KEY_LAST, 

            [0xcd] = KEY_RED,
			[0x91] = KEY_GREEN,
			[0x83] = KEY_YELLOW,
			[0xc3] = KEY_BLUE,

            [0x88] = KEY_HOMEPAGE,
			[0xca] = KEY_UP,
			[0x82] = KEY_HOME,
			
			[0x99] = KEY_LEFT,
			[0xce] = KEY_SELECT,
			[0xc1] = KEY_RIGHT,						
			
			[0x95] = KEY_PLAYPAUSE,
			[0xd2] = KEY_DOWN,
			[0xc5] = KEY_BACK,

            [0x80] = KEY_VOLUMEUP,
			[0x81] = KEY_VOLUMEDOWN,
			
			[0xdd] = KEY_PAGEUP,
			[0x8c] = KEY_PAGEDOWN,

            [0x85] = KEY_CHANNELUP,
			[0x86] = KEY_CHANNELDOWN,
			
			[0x92] = KEY_1,
			[0x93] = KEY_2,
			[0xcc] = KEY_3,
			[0x8e] = KEY_4,
			[0x8f] = KEY_5,
			[0xc8] = KEY_6,
			[0x8a] = KEY_7,
			[0x8b] = KEY_8,
			[0xc4] = KEY_9,
			[0x87] = KEY_0,
			
			[0xda] = KEY_NUMERIC_STAR,
			[0xd0] = KEY_NUMERIC_POUND,
		},
	},	

    //China Telecom Black 12
    {
		.vendor_name = "TS-Y118B",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0xb24d,
		.wakeup_code = 0xdc,
		.key_codes = {
			[0xdc] = KEY_POWER,
                
            [0x98] =KEY_AUDIO,    
			[0x9c] = KEY_MUTE,
			
			[0x8d] = KEY_SETUP, 
			[0xd6] = KEY_LAST, 

            [0xcd] = KEY_RED,
			[0x91] = KEY_GREEN,
			[0x83] = KEY_YELLOW,
			[0xc3] = KEY_BLUE,

            [0x88] = KEY_HOMEPAGE,
			[0xca] = KEY_UP,
			[0x82] = KEY_HOME,
			
			[0x99] = KEY_LEFT,
			[0xce] = KEY_SELECT,
			[0xc1] = KEY_RIGHT,						
			
			[0x95] = KEY_PLAYPAUSE,
			[0xd2] = KEY_DOWN,
			[0xc5] = KEY_BACK,

            [0x80] = KEY_VOLUMEUP,
			[0x81] = KEY_VOLUMEDOWN,
			
			[0xdd] = KEY_PAGEUP,
			[0x8c] = KEY_PAGEDOWN,

            [0x85] = KEY_CHANNELUP,
			[0x86] = KEY_CHANNELDOWN,
			
			[0x92] = KEY_1,
			[0x93] = KEY_2,
			[0xcc] = KEY_3,
			[0x8e] = KEY_4,
			[0x8f] = KEY_5,
			[0xc8] = KEY_6,
			[0x8a] = KEY_7,
			[0x8b] = KEY_8,
			[0xc4] = KEY_9,
			[0x87] = KEY_0,
			
			[0xda] = KEY_NUMERIC_STAR,
			[0xd0] = KEY_NUMERIC_POUND,		
		},
	},	       
	
	// Jensen 13
	{
		.vendor_name = "Jensen",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x00ff,
		.wakeup_code = 0x46,
		.key_codes = {
			[0x08] = KEY_RESERVED,

			[0x5a] = KEY_VOLUMEUP,
			[0x4a] = KEY_VOLUMEDOWN,

			[0x19] = KEY_UP,
			[0x1c] = KEY_DOWN,
			[0x0c] = KEY_LEFT,
			[0x5e] = KEY_RIGHT,
			[0x18] = KEY_ENTER,

			[0x0d] = KEY_BACK,
			[0x45] = KEY_1,
			[0x46] = KEY_POWER,
			[0x47] = KEY_3,
			[0x44] = KEY_4,
			[0x40] = KEY_F1,
			[0x43] = KEY_6,
			[0x07] = KEY_7,
			[0x15] = KEY_BACK,
			[0x09] = KEY_9,
			[0x16] = KEY_0,
			
			[0x42] = KEY_PREVIOUSSONG, /* rewind */
			[0x52] = KEY_NEXTSONG,    /* forward */

		},
	},      
	// Foxconn 14
	{
		.vendor_name = "CIR-9F",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x009f,
		.wakeup_code = 0x57,
		.key_codes = {
			[0x57] = KEY_POWER,      /* power */
			[0x5d] = KEY_VOLUMEDOWN, /* vol- */
			[0xff] = KEY_VOLUMEUP,   /* vol+ */
			[0x47] = KEY_HOME,       /* home */
			[0x16] = KEY_MENU,       /* menu */
			[0x4f] = KEY_BACK,       /* back */
			[0x43] = KEY_UP,         /* up */
			[0x06] = KEY_LEFT,       /* left */
			[0x02] = KEY_ENTER,      /* PLAYPAUSE */    
			//[0x02] = KEY_PLAYPAUSE,      /* PLAYPAUSE */    
			[0x0e] = KEY_RIGHT,      /* right */
			[0x0a] = KEY_DOWN,       /* down */
			[0x0b] = KEY_REWIND,     /* rewind data is 0x36*/
			[0x0f] = KEY_FASTFORWARD, /* forward */

			[0x5b] = KEY_ENTER /* OK - notify  */
		},
	},   
            
	// Philips 15
	{
		.vendor_name = "PHILIPS",
		.codec = CIR_CODEC_PHILIPS_RC6,
		.vendor_code = 60,
		.wakeup_code = 12,
		.key_codes = {
			[12] = KEY_POWER,    
			[146] = KEY_HOME, 
			[84] = KEY_MENU,  
			[92] = KEY_ENTER,  
			[88] = KEY_UP,  
			[89] = KEY_DOWN,  
			[90] = KEY_LEFT,  
			[91] = KEY_RIGHT, 
			[83] = BTN_DEAD,     
			[131] = KEY_BACK,      
		},
	},

    //Ronsheng 16
	{
		.vendor_name = "Ronsheng",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x00FF,
		.wakeup_code = 0x18,
		.key_codes = {
			[0x18] = KEY_POWER,      /* power down */            

            [0x56] = KEY_F3,         /* My Musicin default */ 
            [0x57] = KEY_F1,         /* web browser in default */            

            [0x1f] = KEY_PREVIOUSSONG, /* rewind */
			[0x5b] = KEY_NEXTSONG,    /* forward */
			
            [0x14] = KEY_VOLUMEDOWN,        /* volume- */   
            [0x08] = KEY_MUTE,              /* mute */ 
            [0x10] = KEY_VOLUMEUP,         /* volume+ */
                                             
            [0x17] = KEY_KATAKANA,   /* cursor */
            [0x04] = KEY_MENU,
            
			[0x46] = KEY_UP,         /* up */		
			[0x47] = KEY_LEFT,       /* left */
			[0x55] = KEY_ENTER,      /* OK */
			[0x15] = KEY_RIGHT,      /* right */			
			[0x16] = KEY_DOWN,       /* down */
			
            [0x06] = KEY_HOME,       /* home */
            [0x40] = KEY_BACK,       /* back */
            
			[0x54] = KEY_1,
			[0x48] = KEY_2,
			[0x07] = KEY_3,
			[0x50] = KEY_4,
			[0x12] = KEY_5,
			[0x11] = KEY_6,
			[0x4c] = KEY_7,
			[0x0e] = KEY_8,
			[0x0d] = KEY_9,
			[0x0c] = KEY_0,	
			[0x41] = KEY_RESERVED,
			[0x4b] = KEY_BACKSPACE,	
			
		},
	}, 
	
	// GPRC-11933 17
	{
		.vendor_name = "GPRC-11933",
		.codec = CIR_CODEC_NEC,
		.vendor_code = 0x4040,
		.wakeup_code = 0x4D,
		.key_codes = {
			[0x4D] = KEY_POWER,      /* power down */
            [0x53] = KEY_F1,      
            [0x5B] = KEY_F2,         
            [0x57] = KEY_F3,          
            [0x54] = KEY_F4,         
            
			[0x17] = KEY_VOLUMEDOWN, /* volume- */
			[0x43] = KEY_MUTE,       /* mute */ 
			[0x18] = KEY_VOLUMEUP,   /* volume+ */

			[0x1F] = KEY_F12, 		/* setting */
			[0x1E] = KEY_F5,   
			
			[0x0B] = KEY_UP,         /* up */
			[0x10] = KEY_LEFT,       /* left */
			[0x0D] = KEY_ENTER,      /* OK */
			[0x11] = KEY_RIGHT,      /* right */
			[0x0E] = KEY_DOWN,       /* down */
			[0x1A] = KEY_HOME,       /* home */ 
			[0x47] = KEY_BACK,       /* Back */ 			

            [0x45] = KEY_MENU,        /* MENU */ 
            [0x42] = KEY_KATAKANA,        /* multi-functions */
			[0x01] = KEY_1,
			[0x02] = KEY_2,
			[0x03] = KEY_3,
			[0x04] = KEY_4,
			[0x05] = KEY_5,
			[0x06] = KEY_6,
			[0x07] = KEY_7,
			[0x08] = KEY_8,
			[0x09] = KEY_9,
			[0x00] = KEY_0,		
			[0x44] = KEY_AGAIN,
			[0x0C] = KEY_BACKSPACE,
		},
	},

	
};

extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen);
extern int wmt_setsyspara(char *varname, char *varval);

static int rmtctl_report_rel(struct rmtctl_rel *rel_dev,const unsigned int code )
{
	switch (code) {                    
	case KEY_UP:
		input_report_rel(rel_dev->input, REL_X, 0);
		input_report_rel(rel_dev->input, REL_Y, -REL_DELTA);
		input_sync(rel_dev->input);
		break;
	case KEY_DOWN:
		input_report_rel(rel_dev->input, REL_X, 0);
		input_report_rel(rel_dev->input, REL_Y, REL_DELTA);
		input_sync(rel_dev->input);
		break;
	case KEY_LEFT:
		input_report_rel(rel_dev->input, REL_X, -REL_DELTA);
		input_report_rel(rel_dev->input, REL_Y, 0);
		input_sync(rel_dev->input);
		break;
	case KEY_RIGHT:
		input_report_rel(rel_dev->input, REL_X, REL_DELTA);
		input_report_rel(rel_dev->input, REL_Y, 0);
		input_sync(rel_dev->input);
		break;
	default:
		break;
	}
	return 0;
}

static void rmtctl_report_event(unsigned int *code, int repeat)
{
	static unsigned int last_code = KEY_RESERVED;
	int ret;
	struct rmtctl_priv *priv = container_of(code, struct rmtctl_priv, scan_code);

	ret = del_timer(&priv->timer);

	if (!repeat) {
		// new code coming
		if (ret == 1) {
			// del_timer() of active timer returns 1 means that active timer has been stoped by new key,
			// so report last key up event
			if (RMTCTL_DEBUG)
				printk("[%d] up reported caused by new key[%d]\n", last_code, *code);

			if (priv->rel_dev.on) {
				switch (*code) {
				case KEY_UP:
				case KEY_DOWN:
				case KEY_LEFT:
				case KEY_RIGHT:
					del_timer(&priv->rel_dev.timer); //stop report mouse
					break;
				case KEY_ENTER:
					input_report_key(priv->rel_dev.input, BTN_LEFT, 0);
					input_sync(priv->rel_dev.input);
					del_timer(&priv->rel_dev.timer); //stop report mouse
					break;
				default:
					input_report_key(priv->idev, last_code, 0);
					input_sync(priv->idev);
					break;
				}
			}
			else {
				input_report_key(priv->idev, last_code, 0);
				input_sync(priv->idev);
			}

			if (*code == KEY_KATAKANA) {
				if (priv->rel_dev.on == 0) {
					input_report_rel(priv->rel_dev.input, REL_X, 1);
					input_report_rel(priv->rel_dev.input, REL_Y, 1);
					input_sync(priv->rel_dev.input);
					priv->rel_dev.on = 1;
				}
				else
					priv->rel_dev.on = 0;
			}

		}

		if (RMTCTL_DEBUG)
			printk("[%d] down\n", *code);
		
		// report key down event, mod_timer() to report key up event later for measure key repeat.

		if (priv->rel_dev.on) {
			switch (*code) {
			case KEY_UP:
			case KEY_DOWN:
			case KEY_LEFT:
			case KEY_RIGHT:
				rmtctl_report_rel(&priv->rel_dev,*code);
				break;
			case KEY_ENTER:
				input_report_key(priv->rel_dev.input, BTN_LEFT, 1);
				input_sync(priv->rel_dev.input);
				break;
			default:
				input_event(priv->idev, EV_KEY, *code, 1);
				input_sync(priv->idev);
				break;
			}
		}
		else {
			input_event(priv->idev, EV_KEY, *code, 1);
			input_sync(priv->idev);
		}

        if(priv->led.enable){
            priv->led.on = 0;
            gpio_direction_output(priv->led.gpio, !priv->led.active);
            mod_timer(&priv->led.timer, jiffies+HZ/20);
        }
        
		priv->timer.data = (unsigned long)code;
		mod_timer(&priv->timer, jiffies + 250*HZ/1000);
	}
	else {
		// report key up event after report repeat event according to usb-keyboard standard
		priv->timer.data = (unsigned long)code;
		mod_timer(&priv->timer, jiffies + 250*HZ/1000);
		
		// detect 'repeat' flag, report repeat event
		if (*code != KEY_POWER && *code != KEY_END) {
			if (RMTCTL_DEBUG)
				printk("[%d] repeat\n", *code);

			if (priv->rel_dev.on) {
				switch (*code) {
				case KEY_UP:
				case KEY_DOWN:
				case KEY_LEFT:
				case KEY_RIGHT:
				case KEY_ENTER:
					priv->rel_dev.code = *code;
					mod_timer(&priv->rel_dev.timer, jiffies+HZ/100); //ready to repeat report mouse event
					break;
				default:
					input_event(priv->idev, EV_KEY, *code, 2);
					input_sync(priv->idev);
					break;
				}
			}
			else {
				input_event(priv->idev, EV_KEY, *code, 2);
				input_sync(priv->idev);
			}
		}
	}

	last_code = *code;
}

/** no hw repeat detect, so we measure repeat timeout event by timer */
static void rmtctl_report_event_without_hwrepeat(unsigned int *code)
{
	static unsigned int last_code = KEY_RESERVED;
	static unsigned long last_jiffies = 0;
	int repeat = (jiffies_to_msecs(jiffies - last_jiffies) <250 && *code==last_code) ? 1 : 0;

	last_code = *code;
	last_jiffies = jiffies;

	rmtctl_report_event(code, repeat);
}

static void rmtctl_timer_handler(unsigned long data)
{
	struct rmtctl_priv *priv = container_of((void *)data, struct rmtctl_priv, scan_code);
	unsigned int code = *(unsigned int *)data;
	
	// report key up event
	if (RMTCTL_DEBUG)
		printk("[%d] up reported by timer\n", code);

	if (priv->rel_dev.on) {
		switch (code) {
		case KEY_UP:
		case KEY_DOWN:
		case KEY_LEFT:
		case KEY_RIGHT:
			del_timer(&priv->rel_dev.timer);
			break;
		case KEY_ENTER:
			input_report_key(priv->rel_dev.input, BTN_LEFT, 0);
			input_sync(priv->rel_dev.input);
			break;
		default:
			input_report_key(priv->idev, code, 0);
    		input_sync(priv->idev);
			break;
		}
	}
	else {
		input_report_key(priv->idev, code, 0);
		input_sync(priv->idev);
	}

	if (code == KEY_KATAKANA) {
		if (priv->rel_dev.on == 0) {
			input_report_rel(priv->rel_dev.input, REL_X, 1);
			input_report_rel(priv->rel_dev.input, REL_Y, 1);
			input_sync(priv->rel_dev.input);
			priv->rel_dev.on = 1;
		} 
		else
			priv->rel_dev.on = 0;
	}

    if(priv->led.enable){
        del_timer_sync(&priv->led.timer);
        gpio_direction_output(priv->led.gpio,priv->led.active);
    }
}

static irqreturn_t rmtctl_interrupt(int irq, void *dev_id)
{
	unsigned int status, ir_data, ir_code,vendor, repeat;
	unsigned char *key;
	int i;
    
	struct rmtctl_priv *priv = (struct rmtctl_priv *)dev_id;

	/* get IR status. */
	status = REG32_VAL(IRSTS);

	/* check 'IR received data' flag. */
	if ((status & 0x1) == 0x0) {
		printk("IR IRQ was triggered without data received. (0x%x)\n",
			status);
		return IRQ_NONE;
	}

	/* read IR data. */
	ir_data = REG32_VAL(IRDATA(0)) ;
	key = (char *) &ir_data;

	/* clear INT status*/
	REG32_VAL(IRSTS)=0x1 ;

	if (RMTCTL_DEBUG){
		printk("ir_data = 0x%08x, status = 0x%x \n", ir_data, status);
	}

	if(priv->codec == CIR_CODEC_PHILIPS_RC6){
		vendor = key[1];
		ir_code = key[0];
        //trailer = key[2] & 0x01;
	}
	else{//NEC 
    	/* get vendor ID. */
		vendor = (key[0] << 8) | (key[1]);

		/* check if key is valid. Key[3] is XORed t o key[2]. */
		if (key[2] & key[3]) {
			printk("Invalid IR key received. (0x%x, 0x%x)\n", key[2], key[3]);
			return IRQ_NONE;
		}
	
		/* keycode mapping. */
		ir_code = key[2];
	}
	
	if ( priv->vendor_index >= 0 ) {
		if (vendor == rmtctl_vendors[priv->vendor_index].vendor_code && 
				rmtctl_vendors[priv->vendor_index].codec == priv->codec) {
			priv->table_index = priv->vendor_index;
			priv->scan_code = rmtctl_vendors[priv->vendor_index].key_codes[ir_code];
		}
        else{
            if(vendor != priv->vendor_code){
          		for (i = 0; i < ARRAY_SIZE(rmtctl_vendors); i++) {
        			if (vendor == rmtctl_vendors[i].vendor_code && 
                        rmtctl_vendors[i].codec == priv->codec) {
                        priv->table_index = i;
        				priv->scan_code = rmtctl_vendors[i].key_codes[ir_code];
        				break;
        			}
        		}

                if(i==ARRAY_SIZE(rmtctl_vendors))
                    return IRQ_HANDLED;
            }
            else{
                priv->scan_code = rmtctl_vendors[priv->table_index].key_codes[ir_code];
            }
        }
	}
	else {
        if(vendor != priv->vendor_code){
    		for (i = 0; i < ARRAY_SIZE(rmtctl_vendors); i++) {
    			if (vendor == rmtctl_vendors[i].vendor_code && 
                    rmtctl_vendors[i].codec == priv->codec) {
                    priv->table_index = i;
    				priv->scan_code = rmtctl_vendors[i].key_codes[ir_code];
    				break;
    			}
    		}
            
            if(i==ARRAY_SIZE(rmtctl_vendors))
                    return IRQ_HANDLED;
        }
        else{
            priv->scan_code = rmtctl_vendors[priv->table_index].key_codes[ir_code];
        }
	}
    
    //switch to a new remote controller shoud reset cursor mode
    if (priv->vendor_code != vendor) 
        priv->rel_dev.on = 0;
	
		
	if ((status & 0x2) || (priv->scan_code == KEY_RESERVED)) {
		/* ignore repeated or reserved keys. */
	}
	else if (priv->codec == CIR_CODEC_NEC) {
        /* check 'IR code repeat' flag. */
	    repeat = status & 0x10;
		rmtctl_report_event(&priv->scan_code, repeat);
 	}
    else if(priv->codec == CIR_CODEC_PHILIPS_RC6){                      
        //repeat = !(trailer^key[2]);
        //trailer = key[2];
        //printk("RCV philips rc6,repeat=%d\n",repeat);

        rmtctl_report_event_without_hwrepeat(&priv->scan_code);                
    }
	else {
		rmtctl_report_event_without_hwrepeat(&priv->scan_code);
	}

	if (priv->vendor_code != vendor) {
		priv->vendor_code = vendor;        
		/* save new vendor code to env
         *1.remote controller configed index is active, no need to save it's vendor code 
         *2.current vendor code is saved, no need to save again
		 */
		if(priv->table_index != priv->vendor_index && vendor != priv->saved_vcode){
            priv->saved_vcode = vendor;
		    //schedule_delayed_work(&priv->delaywork, msecs_to_jiffies(20));
        }
	}

	return IRQ_HANDLED;
}

static void rmtctl_hw_suspend(unsigned long data)
{
	unsigned int wakeup_code;
	int i;
	struct rmtctl_priv *priv = (struct rmtctl_priv *)data;

	if (priv->vendor_index >= 0 && priv->vendor_index == priv->table_index) {
		i = priv->vendor_index;
	}
	else {
		for (i = 0; i < ARRAY_SIZE(rmtctl_vendors); i++)
			if (priv->vendor_code == rmtctl_vendors[i].vendor_code &&
                priv->codec == rmtctl_vendors[i].codec)
				break;

		if (i == ARRAY_SIZE(rmtctl_vendors))
			goto out;
	}
	
	if(priv->codec == CIR_CODEC_PHILIPS_RC6){
        wakeup_code = 
		    ((unsigned char)(rmtctl_vendors[i].vendor_code & 0xff) << 8) |
		    (unsigned char)(rmtctl_vendors[i].wakeup_code) ;
        REG32_VAL(WAKEUP_CMD2(0)) = 0x10000| wakeup_code;
    }else{
	    wakeup_code = 
		    ((unsigned char)(~rmtctl_vendors[i].wakeup_code) << 24) | 
		    ((unsigned char)(rmtctl_vendors[i].wakeup_code) << 16 ) |
		    ((unsigned char)(rmtctl_vendors[i].vendor_code & 0xff) << 8) |
		    ((unsigned char)((rmtctl_vendors[i].vendor_code) >> 8) << 0 );

    }
	REG32_VAL(WAKEUP_CMD1(0)) = wakeup_code;
	REG32_VAL(WAKEUP_CMD1(1)) = 0x0;
	REG32_VAL(WAKEUP_CMD1(2)) = 0x0;
	REG32_VAL(WAKEUP_CMD1(3)) = 0x0;
	REG32_VAL(WAKEUP_CMD1(4)) = 0x0;

out:
	REG32_VAL(WAKEUP_CTRL) = 0x101;
}

static void rmtctl_hw_init(unsigned long data)
{
	unsigned int st;
	int i, retries = 0;
	struct rmtctl_priv *priv = (struct rmtctl_priv *)data;

	/*setting shared pin*/
	GPIO_CTRL_GP62_WAKEUP_SUS_BYTE_VAL  &= 0xFFFD;
	PULL_EN_GP62_WAKEUP_SUS_BYTE_VAL 	|= 0x02;
	PULL_CTRL_GP62_WAKEUP_SUS_BYTE_VAL 	|= 0x02;

	/* turn off CIR SW reset. */
	REG32_VAL(IRSWRST) = 1;
	REG32_VAL(IRSWRST) = 0;

	for (i = 0; i < ARRAY_SIZE(rmtctl_params[priv->codec].param); i++)
		REG32_VAL(PARAMETER(i)) = rmtctl_params[priv->codec].param[i];

	if (priv->codec == CIR_CODEC_NEC || priv->codec == CIR_CODEC_TOSHIBA) {
        printk("NEC\n");
		REG32_VAL(NEC_REPEAT_TIME_OUT_CTRL) = 0x1;
		REG32_VAL(NEC_REPEAT_TIME_OUT_COUNT) = rmtctl_params[priv->codec].repeat_timeout;
		REG32_VAL(IRCTL) = 0X100; //NEC repeat key
	}
    else if(priv->codec == CIR_CODEC_PHILIPS_RC6){
        printk("PHILIPS RC6\n");
        REG32_VAL(IRCTL) =0x40; //PHILIPS RC6
        REG32_VAL(INT_MASK_COUNT) = 40*1000000*1/4; 
        //REG32_VAL(IRCTL_2) =0xFE;//Enable PHILIPS RC6 mode 0,Mask mode1-7 ?
    }else{
		REG32_VAL(IRCTL) = 0; //NEC repeat key
		REG32_VAL(INT_MASK_COUNT) = 28*1000000*1/4; //0x47868C0/4;//count for 1 sec 0x47868C0
	}

	REG32_VAL(IRCTL) |= (0x1<<25);
	REG32_VAL(INT_MASK_CTRL) = 0x1;

	/*  IR_EN */
	REG32_VAL(IRCTL) |= 0x1;
    while(retries++ < 100 && !(REG32_VAL(IRCTL)&0x01)){
        REG32_VAL(IRCTL) |= 0x1;
        udelay(5);
    }

	/* read CIR status to clear IR interrupt. */
	st = REG32_VAL(IRSTS);
}

static void rmtctl_refresh_vendorcode(struct work_struct *work)
{
	unsigned char data[64];
	struct rmtctl_priv *priv = container_of(work, struct rmtctl_priv, delaywork.work);
	sprintf(data, "0x%x", priv->vendor_code);
	wmt_setsyspara(WMT_RMTCTL_VENDOR_ENV, data);
}

static void rel_handler(unsigned long data)
{
	struct rmtctl_rel *rel = (struct rmtctl_rel *)data;
    struct rmtctl_priv *priv = container_of(rel, struct rmtctl_priv, rel_dev);
    
	rmtctl_report_rel(&priv->rel_dev, rel->code);
	mod_timer(&rel->timer, jiffies+HZ/15); // report rate 15pps
}

static void rmtctl_rel_init(struct rmtctl_rel *rel)
{
	struct input_dev *input;

	input = input_allocate_device();
	input->name = "rmtctl-rel";

	/* register the device as mouse */
	set_bit(EV_REL, input->evbit);
	set_bit(REL_X, input->relbit);
	set_bit(REL_Y, input->relbit);

	/* register device's buttons and keys */
	set_bit(EV_KEY, input->evbit);
	set_bit(BTN_LEFT, input->keybit);
	set_bit(BTN_RIGHT, input->keybit);

	input_register_device(input);
	rel->input = input;

	init_timer(&rel->timer);
	rel->timer.data = (unsigned long)rel;
	rel->timer.function = rel_handler;
}

void led_handler(unsigned long data)
{
    struct rmtctl_led *led = (struct rmtctl_led*)data;

    gpio_direction_output(led->gpio, led->on++%2);
    mod_timer(&led->timer, jiffies+HZ/20);
    return;
}

static void rmtctl_led_init(struct rmtctl_led *led)
{    
	init_timer(&led->timer);
	led->timer.data = (unsigned long)led;
	led->timer.function = led_handler; 

    gpio_direction_output(led->gpio, led->active);

    return;
}

static int rmtctl_probe(struct platform_device *dev)
{
	int i,ivendor, ikeycode;
	char buf[64];
	int varlen = sizeof(buf);
	struct rmtctl_priv *priv;

	priv = kzalloc(sizeof(struct rmtctl_priv), GFP_KERNEL);
	if (priv == NULL)
		return -ENOMEM;
	platform_set_drvdata(dev, priv);

	// get codec type of cir device
	if (wmt_getsyspara("wmt.io.rmtctl", buf, &varlen) == 0) {
		sscanf(buf, "%d", &priv->codec);
		if (priv->codec < CIR_CODEC_NEC || priv->codec >= CIR_CODEC_MAX){
			kfree(priv);
			return -EINVAL;
		}
	}
	else {
		kfree(priv);
		return -EINVAL;
	}

	// using assigned cir device?
	if (wmt_getsyspara("wmt.io.rmtctl.index", buf, &varlen) == 0) {
		sscanf(buf, "%d", &priv->vendor_index);
		if (priv->vendor_index >= ARRAY_SIZE(rmtctl_vendors) ||
			rmtctl_vendors[priv->vendor_index].codec != priv->codec)
			priv->vendor_index = -1;
        
        priv->table_index = priv->vendor_index;
	}
	else
		priv->vendor_index = -1;

	// get last vendor code saved in uboot environment
#if 0
	if (wmt_getsyspara(WMT_RMTCTL_VENDOR_ENV, buf, &varlen) == 0){
		sscanf(buf, "0x%x", &priv->vendor_code);
        priv->saved_vcode = priv->vendor_code;
        for (i = 0; i < ARRAY_SIZE(rmtctl_vendors); i++) {
			if (priv->vendor_code == rmtctl_vendors[i].vendor_code && 
                rmtctl_vendors[i].codec == priv->codec) {
                priv->table_index = i;
				break;
			}
        }
	}else
#endif	
		priv->vendor_code = 0xffff;

    if (wmt_getsyspara("wmt.io.rmtctl.led", buf, &varlen) == 0) {
		sscanf(buf, "%d:%d", &priv->led.gpio, &priv->led.active);        
        priv->led.enable = 1;        
	}
	
	/* register an input device. */
	if ((priv->idev = input_allocate_device()) == NULL)
		return -ENOMEM;

	set_bit(EV_KEY, priv->idev->evbit);

	for (ivendor = 0; ivendor < ARRAY_SIZE(rmtctl_vendors); ivendor++) {
		if (priv->codec == rmtctl_vendors[ivendor].codec) {
			for (ikeycode = 0; ikeycode < ARRAY_SIZE(rmtctl_vendors[ivendor].key_codes); ikeycode++) {
				if (rmtctl_vendors[ivendor].key_codes[ikeycode]) {
					set_bit(rmtctl_vendors[ivendor].key_codes[ikeycode], priv->idev->keybit);
				}
			}
		}
	}

	priv->idev->name = "rmtctl";
	priv->idev->phys = "rmtctl";
	input_register_device(priv->idev);
	rmtctl_rel_init(&priv->rel_dev);
    if(priv->led.enable){
        gpio_request(priv->led.gpio,"rmtctl-led");
        rmtctl_led_init(&priv->led);
    }
	INIT_DELAYED_WORK(&priv->delaywork, rmtctl_refresh_vendorcode);
    
	init_timer(&priv->timer);
	priv->timer.data = (unsigned long)priv->idev;
	priv->timer.function = rmtctl_timer_handler;

    /* Register an ISR */
	request_irq(IRQ_CIR, rmtctl_interrupt, IRQF_SHARED, "rmtctl", priv);

	/* Initial H/W */
	rmtctl_hw_init((unsigned long)priv);

	if (RMTCTL_DEBUG)
		printk("WonderMedia rmtctl driver v0.98 initialized: ok\n");

	return 0;
}

static int rmtctl_remove(struct platform_device *dev)
{
	struct rmtctl_priv *priv = platform_get_drvdata(dev);
	
	if (RMTCTL_DEBUG)
		printk("rmtctl_remove\n");

	del_timer_sync(&priv->timer);
	cancel_delayed_work_sync(&priv->delaywork);	
    del_timer_sync(&priv->rel_dev.timer);
    input_unregister_device(priv->rel_dev.input);
	input_free_device(priv->rel_dev.input);
    if(priv->led.enable){
        del_timer_sync(&priv->led.timer);
        gpio_direction_output(priv->led.gpio, priv->led.active);
        gpio_free(priv->led.gpio);
    }

	free_irq(IRQ_CIR, priv);
	input_unregister_device(priv->idev);
	input_free_device(priv->idev);

	kfree(priv);

	return 0;
}

#ifdef CONFIG_PM
static int rmtctl_suspend(struct platform_device *dev, pm_message_t state)
{
	struct rmtctl_priv *priv = platform_get_drvdata(dev);
	
	del_timer_sync(&priv->timer);
	cancel_delayed_work_sync(&priv->delaywork);
    del_timer_sync(&priv->rel_dev.timer);
    if(priv->led.enable){
        priv->led.on = 0;
        del_timer(&priv->led.timer);
        gpio_direction_output(priv->led.gpio, priv->led.active);
    }
	
	/* Nothing to suspend? */
	rmtctl_hw_init((unsigned long)priv);
	rmtctl_hw_suspend((unsigned long)priv);
	
	disable_irq(IRQ_CIR);

	/* Enable rmt wakeup */
	PMWE_VAL |= (1 << WKS_CIR);
	
	return 0;
}

static int rmtctl_resume(struct platform_device *dev)
{
	volatile  unsigned int regval;
	int i =0 ;
	struct rmtctl_priv *priv = platform_get_drvdata(dev);

	/* Initial H/W */
	REG32_VAL(WAKEUP_CTRL) &=~ BIT0;

	for (i=0;i<10;i++)
	{
		regval = REG32_VAL(WAKEUP_STS) ;

		if (regval & BIT0){
			REG32_VAL(WAKEUP_STS) |= BIT4;

		}else{
			break;
		}
		msleep_interruptible(5);
	}
	
	regval = REG32_VAL(WAKEUP_STS) ;
	if (regval & BIT0)
		printk("CIR resume NG  WAKEUP_STS 0x%08x \n",regval);

	rmtctl_hw_init((unsigned long)priv);
    if(priv->led.enable)
        rmtctl_led_init(&priv->led);
	enable_irq(IRQ_CIR);
	return 0;
}
#else
#define rmtctl_suspend NULL
#define rmtctl_resume NULL
#endif

static struct platform_driver  rmtctl_driver = {
	.driver.name = "wmt-rmtctl", 
	//.bus = &platform_bus_type,
	//.probe = rmtctl_probe,
	.remove = rmtctl_remove,
	.suspend = rmtctl_suspend,
	.resume = rmtctl_resume
};

static void rmtctl_release(struct device *dev)
{
	/* Nothing to release? */
}

static u64 rmtctl_dmamask = 0xffffffff;

static struct platform_device rmtctl_device = {
	.name = "wmt-rmtctl",
	.id = 0,
	.dev = { 
		.release = rmtctl_release,
		.dma_mask = &rmtctl_dmamask,
	},
	.num_resources = 0,
	.resource = NULL,
};

static int __init rmtctl_init(void)
{
	int ret;

	if (platform_device_register(&rmtctl_device))
		return -1;
	ret = platform_driver_probe(&rmtctl_driver, rmtctl_probe);

	return ret;
}

static void __exit rmtctl_exit(void)
{
	platform_driver_unregister(&rmtctl_driver);
	platform_device_unregister(&rmtctl_device);
}

module_init(rmtctl_init);
module_exit(rmtctl_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("WonderMedia Technologies, Inc.");
MODULE_DESCRIPTION("WMT [Remoter] driver");