/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* 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, using version 2 of the License.             */
/*                                                                           */
/* 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, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/
/**
   @file   phStbKey_6keysDirectGPIO.c
   @brief  Frontpanel keyboard device driver.

   @b  Component:  phStbKey.

   Set your editor for 4 space indentation.
*/
/**
Rev Date       Author      Comments
--------------------------------------------------------------------------------
  3 20090121   batelaan    Update author/license.
*/

#define __PHSTBKEY_C__

#include <asm/io.h>
#include <asm/mach-pnx833x/irq.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/device.h>
#include <linux/major.h>

#include <tmosal.h>
#include "tmdlPio.h"

#include "phStbKey.h"

/******************************************************************************
* LOCAL MACROS                                                                *
*******************************************************************************/

/**** Module Setup ****/
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION(PHSTBKEY_MODULE_DESCRIPTION);
MODULE_AUTHOR("NXP B.V.");

//  ----------------------- PHSTBKEY MODULE Debug Level   ------------------------
static int debug = 0;
module_param(debug, int, 0);
MODULE_PARM_DESC(debug, "Debug level (0-1)");


//  ----------------------- PHSTBKEY Kernel Debug --------------------------------
#ifdef __PHSTBKEY_C__
#undef  dprintk
#define dprintk(num, format, args...)                                           \
    do {                                                                        \
        if (debug >= num) printk(format, ##args);                               \
    } while (0)
#endif

/******************************************************************************
* LOCAL TYPEDEFS                                                              *
*******************************************************************************/

/******************************************************************************
* STATIC FUNCTION PROTOTYPES                                                  *
*******************************************************************************/

/******************************************************************************
* EXPORTED DATA                                                               *
*******************************************************************************/

/******************************************************************************
* LOCAL DATA                                                                  *
*******************************************************************************/

static unsigned int keyList[] = { 
    KEY_LEFT, 
    KEY_MENU, 
    KEY_KP2, 
    KEY_OK, 
    KEY_UP, 
    KEY_DOWN, 
    KEY_RIGHT, 
    KEY_KP7, 
    KEY_KP8,
    KEY_POWER
};

#define NUM_KEYS sizeof(keyList)/sizeof(unsigned int)
#define PSU_KEY  (9)

#define PHSTBKEY_EVENT_ID 1

#define PHSTBKEY_TASK_DELAY ((HZ) / 100)
#define PHSTBKEY_TIMEOUT 60

//#define PSU_PIN   (41)
//#define PSU_MASK  ((tmdlPioLines_t)1<<PSU_PIN)

#define FPN_OK_PIN  (17)
#define FPN_MENU_PIN  (19)
#define FPN_UP_PIN  (38)
#define FPN_OK_MASK ((tmdlPioLines_t)1<<FPN_OK_PIN)
#define FPN_MENU_MASK ((tmdlPioLines_t)1<<FPN_MENU_PIN)
#define FPN_UP_MASK ((tmdlPioLines_t)1<<FPN_UP_PIN)

#define FPN_RIGHT_PIN  (41)
#define FPN_LEFT_PIN  (42)
#define FPN_DOWN_PIN  (43)
#define FPN_RIGHT_MASK ((tmdlPioLines_t)1<<FPN_RIGHT_PIN)
#define FPN_LEFT_MASK ((tmdlPioLines_t)1<<FPN_LEFT_PIN)
#define FPN_DOWN_MASK ((tmdlPioLines_t)1<<FPN_DOWN_PIN)

static tmInstance_t         phStbKey_pioInstance = (tmInstance_t)Null;
static const tmdlPioLines_t FpnInput[6] = { FPN_OK_MASK, FPN_MENU_MASK, FPN_UP_MASK, FPN_RIGHT_MASK, FPN_LEFT_MASK, FPN_DOWN_MASK };
static int                  phStbKeyInitialised = 0;
static struct               timer_list timer;
static struct input_dev     *phStbKey_input_dev;

static int                  key_sendrelease = 0;

/******************************************************************************
* FUNCTION IMPLEMENTATION                                                     *
*******************************************************************************/

/*=============================================================================
| File/Device functions                                                       |
==============================================================================*/

/*=============================================================================
| Interrupt Handler                                                           |
==============================================================================*/

/*=============================================================================
| Module functions                                                            |
==============================================================================*/

static void setupOutput(int which)
{
    tmErrorCode_t error;

    error = tmdlPioWrite(phStbKey_pioInstance, (tmdlPioLines_t)(FPN_OK_MASK|FPN_MENU_MASK|FPN_UP_MASK|FPN_RIGHT_MASK|FPN_LEFT_MASK|FPN_DOWN_MASK), FpnInput[which]);
    if (error != TM_OK) 
    {
        dprintk(1, "Error returned from tmdlPioWrite %x\n", error);
    }
}

static int checkOutput(void)
{
    tmErrorCode_t error;
    tmdlPioLines_t bitPattern;

    error = tmdlPioRead(phStbKey_pioInstance, (tmdlPioLines_t)(FPN_OK_MASK|FPN_MENU_MASK|FPN_UP_MASK|FPN_RIGHT_MASK|FPN_LEFT_MASK|FPN_DOWN_MASK), &bitPattern);
    if (error == TM_OK) 
    {
        if (!(bitPattern & FPN_OK_MASK))
        {
        //	printk("FPN_OK_MASK\n");
            return 1;
        }
        if (!(bitPattern & FPN_MENU_MASK))
        {
        //	printk("FPN_MENU_MASK\n");
            return 3;
        }
        if (!(bitPattern & FPN_UP_MASK))
        {
        //	printk("FPN_UP_MASK\n");
            return 4;
        }
        if (!(bitPattern & FPN_RIGHT_MASK))
        {
        //	printk("FPN_RIGHT_MASK\n");
            return 6;
        }
        if (!(bitPattern & FPN_LEFT_MASK))
        {
        //	printk("FPN_LEFT_MASK\n");
            return 0;
        }
        if (!(bitPattern & FPN_DOWN_MASK))
        {
        //	printk("FPN_DOWN_MASK\n");
            return 5;
        }

    }
    else
    {
        dprintk(1, "Error returned from tmdlPioRead %x\n", error);
    }
    return -1;
}



static void phStbKey_timer (unsigned long data)
{
   static int timeout = 0;
   static int lastKey = -1;
   static int savedKey = -1;
   static bool pressed = false;
   unsigned long newTimeout = jiffies + PHSTBKEY_TASK_DELAY;
   int thisKey;

   if (timeout)
   {
       timeout--;
   }

   thisKey = checkOutput();
   
   if (key_sendrelease)
   {
       if (timeout == 0) 
       {
           if (thisKey >= 0) 
           {
               input_report_key( phStbKey_input_dev, keyList[thisKey], 1 );
               input_report_key( phStbKey_input_dev, keyList[thisKey], 0 );
               timeout = PHSTBKEY_TIMEOUT;
           }
       }
   }
   else
   {
       if (timeout == 0)
       {
           /* Check if a key has already been pressed */
           if (pressed)
           {
               /* A different key OR no key at all was pressed */
               if (((thisKey >= 0) && (thisKey != lastKey))||
                   (savedKey == -1))
               {
                   /* Key released */
                   input_report_key( phStbKey_input_dev, keyList[lastKey], 0 );
               //      printk(" Key released \n");
                   lastKey = -1;
                   pressed = false;
               }
               else
               {
                   /* Repeated key press - dealt with by auto-repeat */
                   timeout = PHSTBKEY_TIMEOUT;
               }
           }

           if ((thisKey >= 0) && (thisKey != lastKey))
           {
               /* New (initial) key press */
          //     printk("thiskey=%d\n", thisKey);
               input_report_key( phStbKey_input_dev, keyList[thisKey], 1 );
            //     printk(" New (initial) key press \n");
               timeout = PHSTBKEY_TIMEOUT;
               lastKey = thisKey;
               pressed = true;
           }
           else
           {  
               /* No key pressed - Do nothing more ! */
           }

           savedKey = -1;
       }
       else
       {
           /* Avoid key bounce - by waiting before checking keys */
           if (timeout < (PHSTBKEY_TIMEOUT / 2))
           {
               if (thisKey >= 0)
               {
                   /* Check to see if the same key is still being pressed */
                   if (thisKey == lastKey)
                   {
                       savedKey = thisKey;
                   }
                   else
                   {
                       /* A different key is being pressed */
                       if (pressed)
                       {
                           /* Release previously pressed key */
                           input_report_key( phStbKey_input_dev, keyList[lastKey], 0 );
                       }

                       /* New (initial) key press */
                       input_report_key( phStbKey_input_dev, keyList[thisKey], 1 );
                       lastKey = thisKey;
                       savedKey = -1;
                       timeout = PHSTBKEY_TIMEOUT;
                       pressed = true;
                   }
               }
           }
       }
   }
   mod_timer(&timer, newTimeout);
}


static int phStbKey_open( struct input_dev * pDev )
{
    tmdlPioInstanceSetup_t pioSetup;
    tmdlPioIcfgCmd_t       pioCfgCmd;
    tmdlPioSetModeMask_t   pioCfgData;
    tmErrorCode_t error;

    if (phStbKeyInitialised)
    {
        return -1;
    }

    error = tmdlPioOpen( &phStbKey_pioInstance );
    if ( error == TM_OK )
    {
        pioSetup.lineSelect = (FPN_OK_MASK|FPN_MENU_MASK|FPN_UP_MASK) | (FPN_RIGHT_MASK|FPN_LEFT_MASK|FPN_DOWN_MASK);
        
        error = tmdlPioInstanceSetup( phStbKey_pioInstance, &pioSetup );
        if ( error == TM_OK )
        {
            pioCfgCmd = tmdlPioCmdSetMode;
            pioCfgData.lineSelInput  = (FPN_OK_MASK|FPN_MENU_MASK|FPN_UP_MASK|FPN_RIGHT_MASK|FPN_LEFT_MASK|FPN_DOWN_MASK);
            pioCfgData.lineSelPeriph = 0;
            
            error = tmdlPioInstanceConfig( phStbKey_pioInstance, pioCfgCmd, (pVoid)&pioCfgData  );
            if (error != TM_OK) 
            {
                dprintk(1, "Error returned from tmdlPioInstanceConfig %x\n", error);
            }
        }
        else
        {
            dprintk(1, "Error returned from tmdlPioInstanceSetup %x\n", error);
        }
    }
    else
    {
        dprintk(1, "Error returned from tmdlPioOpen %x\n", error);
    }

    setupOutput(0);

    /* create timer task to modify GPIO outputs */
    init_timer(&timer);
    timer.expires = jiffies + PHSTBKEY_TASK_DELAY;
    timer.data = 0;
    timer.function = phStbKey_timer;
    add_timer(&timer);

    phStbKeyInitialised = 1;
    return 0;
}

static void phStbKey_close( struct input_dev * pDev )
{
    if (phStbKeyInitialised)
    {
        del_timer(&timer);
        tmdlPioClose(phStbKey_pioInstance);
        phStbKeyInitialised = 0;
    }

}

/** Module initialisation. */
static int __init phStbKey_Init( void )
{
    int error;
    unsigned int index;

    // intialize structure
    phStbKey_input_dev = input_allocate_device();

    phStbKey_input_dev->name = "STB225 Front Panel Keyboard";
    phStbKey_input_dev->phys = "";

    phStbKey_input_dev->id.bustype = BUS_HOST;
    phStbKey_input_dev->id.vendor  = 0x0001;
    phStbKey_input_dev->id.product = 0x0001;
    phStbKey_input_dev->id.version = 0x0001;

    set_bit(EV_KEY, phStbKey_input_dev->evbit);
    set_bit(EV_REP, phStbKey_input_dev->evbit);

    // set keys
    for(index=0; index<NUM_KEYS; index++)
    {
       set_bit( keyList[index], phStbKey_input_dev->keybit );
    }

    // Set functions
    phStbKey_input_dev->open  = phStbKey_open;
    phStbKey_input_dev->close = phStbKey_close;

    error = input_register_device( phStbKey_input_dev );
    if (error) {
      printk (KERN_ERR "Could not register device %s (%s-%s)\n", PHSTBKEY_MODULE_DESCRIPTION, __DATE__, __TIME__);
      input_free_device(phStbKey_input_dev);
      return error;
    }
    
    /* Set up the repeat delay to be slightly longer than the scan loop */
    phStbKey_input_dev->rep[REP_DELAY]  = (PHSTBKEY_TIMEOUT + 5) * 10;
    /* Set up the repeat period to be the same as the scan loop */
    phStbKey_input_dev->rep[REP_PERIOD] = PHSTBKEY_TIMEOUT * 10;


    class_device_create(&input_class, NULL,
                         MKDEV(INPUT_MAJOR, PHSTBKEY_EVENT_ID), 
                         phStbKey_input_dev->cdev.dev, 
                         "keyboard");

    printk ("%s (%s-%s)\n", PHSTBKEY_MODULE_DESCRIPTION, __DATE__, __TIME__);
    return 0;
}
module_init(phStbKey_Init);

/** Module deinitialisation */
static void __exit phStbKey_Exit( void )
{	
    class_device_destroy(&input_class,MKDEV(INPUT_MAJOR, PHSTBKEY_EVENT_ID));
    input_unregister_device( phStbKey_input_dev );
}
module_exit(phStbKey_Exit);

module_param(key_sendrelease, int, 0644);
MODULE_PARM_DESC(key_sendrelease, "Send key release after each key press [default: 0]");
