/*****************************************************************************/
/* Copyright (c) 2009 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_DirectGPIO.c
   @brief  Frontpanel keyboard device driver - use gpio directly

   @b  Component:  phStbKey.

   Set your editor for 4 space indentation.
*/
/**
Rev Date       Author      Comments
--------------------------------------------------------------------------------
  1 20090110   qianjiang       Original
  6 20090319   feeling         clean & merge
  7 20090406   ocourson        Updates after review
*/
#define __PHSTBKEYGPIO_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 <linux/miscdevice.h>
#include "phStbKey.h"

#define PHSTBKEYGPIO_EVENT_ID 1

#define SCAN_INTERVAL   ((80/*ms*/*HZ) / 1000)
#define DEVNAME "phStbSwKeyboard"
#define PHSTBKEYPROTOCOL_MODULE_DESCRIPTION   "STB225 Frontpanel Key Protocol driver"
#define SCANMAP_LIST(x) while(gMapList[x].pioNo != PHSTBKEY_END_OF_LIST && \
            gMapList[x].translated_key != PHSTBKEY_END_OF_LIST && \
            x < PHSTBKEY_MAX_ENTRIES)
#define MINI_REPEAT_DELAY   200

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

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

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

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

/* internal global variables */
static struct input_dev     *phStbKey_gpio_input_dev;
typedef enum
{
    KEY_PRESSED,
    KEY_RELEASED
}keyStatus;

/* internal functions prototype  */
static void Timer_Func(unsigned long param);
static int phStbKeyProtocol_Ioctl( struct inode   *inode,
                                   struct file    *filp,
                                   unsigned int    cmd,
                                   unsigned long   arg );
static void ScanFunc(struct work_struct * work);

/* internal global variables */
static phStbKeyMapList   gMapList;
static struct timer_list gTimerList;
static tmdlPioLines_t    gPioLines[PHSTBKEY_MAX_ENTRIES];
static keyStatus         gKeyStatus[PHSTBKEY_MAX_ENTRIES];
static UInt32            gRepeatDelay = MINI_REPEAT_DELAY; //as default
static Bool              gbMapListSetupDone = False;
static struct input_dev* gpInputDevice = NULL;

static struct file_operations gphStbKeyProtocol_Fops = {
    .ioctl =  phStbKeyProtocol_Ioctl,
};

static struct miscdevice gphStbKeyProtocol_MiscDev = {
    .minor =    MISC_DYNAMIC_MINOR,
    .name  =    DEVNAME,
    .fops  =   &gphStbKeyProtocol_Fops
};

DECLARE_MUTEX(gKeyGpioMutex);
static struct workqueue_struct *gpKeyGpioWorkQueue = NULL;
DECLARE_WORK(gScanWork, ScanFunc);
static tmInstance_t         phStbKey_pioInstance = (tmInstance_t)Null;

//define possible keys here,
static UInt32 gPossibleKeys[]={
    KEY_POWER,       // Power
    KEY_TEXT,        // Text
    KEY_SUBTITLE,    // Subtitle
    KEY_INFO,        // Info
    KEY_EPG,         // Guide
    KEY_SCREEN,      // TV format
    KEY_EXIT,        // Exit
    KEY_MENU,        // Menu
    KEY_UP,          // Up
    KEY_DOWN,        // Down
    KEY_LEFT,        // Left
    KEY_RIGHT,       // Right
    KEY_OK,          // OK
    KEY_MUTE,        // Mute
    KEY_CHANNELUP,   // Prog+
    KEY_CHANNELDOWN, // Prog-
    KEY_VOLUMEUP,    // Vol+
    KEY_VOLUMEDOWN,  // Vol-
    KEY_RED,         // Red
    KEY_GREEN,       // Green
    KEY_YELLOW,      // Yellow
    KEY_BLUE,        // Blue
    KEY_FAVORITES,   // Favorites
    KEY_PREVIOUS     // Previous
};

/** Module initialisation. */
static int __init phStbKey_gpio_Init( void )
{
    int error;

    // intialize structure
    phStbKey_gpio_input_dev = input_allocate_device();

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

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

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

    error = input_register_device( phStbKey_gpio_input_dev );
    if (error) 
    {
        input_free_device(phStbKey_gpio_input_dev);
        return error;
    }
    
    class_device_create(&input_class, NULL,
                        MKDEV(INPUT_MAJOR, PHSTBKEYGPIO_EVENT_ID), 
                        phStbKey_gpio_input_dev->cdev.dev, 
                        "keyboard_gpio");

    phStbKeyProtocol_Init(phStbKey_gpio_input_dev);
    return 0;
}
module_init(phStbKey_gpio_Init);

/** Module deinitialisation */
static void __exit phStbKey_gpio_Exit( void )
{   
    phStbKeyProtocol_Exit();
    class_device_destroy(&input_class,MKDEV(INPUT_MAJOR, PHSTBKEYGPIO_EVENT_ID));
    input_unregister_device( phStbKey_gpio_input_dev );
    input_free_device(phStbKey_gpio_input_dev);
}
module_exit(phStbKey_gpio_Exit);

static int SetupPioResources(void)
{
    tmdlPioInstanceSetup_t pioSetup;
    tmdlPioSetModeMask_t   pioCfgData;
    tmErrorCode_t error;
    int i;

    if(phStbKey_pioInstance != (tmInstance_t)Null) 
    {
        tmdlPioClose(phStbKey_pioInstance);
    }

    if(tmdlPioOpen( &phStbKey_pioInstance ) != TM_OK)
    {
        phStbKey_pioInstance = (tmInstance_t)Null;
        return -EIO;
    }

    i = 0;
    pioSetup.lineSelect = 0;
    SCANMAP_LIST(i) //scan map list
    {
        gPioLines[i] = ((tmdlPioLines_t)1<<gMapList[i].pioNo);
        gKeyStatus[i] = KEY_RELEASED;
        pioSetup.lineSelect |= gPioLines[i];
        i ++;
    }
    error = tmdlPioInstanceSetup( phStbKey_pioInstance, &pioSetup );
    if(error != TM_OK)
    {
        dprintk(1, "phStbKey: tmdlPioInstanceSetup failed\n");
        return -EIO;
    }

    i = 0;
    pioCfgData.lineSelInput  = 0;
    SCANMAP_LIST(i) //scan map list
    {
        pioCfgData.lineSelInput |= gPioLines[i];
        i++;
    }

    pioCfgData.lineSelOutput = 0;
    pioCfgData.lineSelPeriph = 0;
            
    error = tmdlPioInstanceConfig( phStbKey_pioInstance, tmdlPioCmdSetMode, (pVoid)&pioCfgData);
    if (error != TM_OK) 
        return -EIO;

    return 0;
}

/* scan gpio function */
static void ScanFunc(struct work_struct * work)
{ 
    tmdlPioLines_t bitPattern;
    tmErrorCode_t error;
    tmdlPioLines_t pressedPattern;
    int i;
    
    dprintk(2, "ScanFunc\n");

    if(down_trylock(&gKeyGpioMutex) != 0) 
        return; //not going to sleep here

    if(gbMapListSetupDone == False || phStbKey_pioInstance == (tmInstance_t)Null)
    {
        up(&gKeyGpioMutex);
    }

    i = 0;
    SCANMAP_LIST(i) //scan map list
    {
        dprintk(2, "SCANMAP_LIST\n");
        error = tmdlPioRead(phStbKey_pioInstance, gPioLines[i], &bitPattern);
        if(error != TM_OK) 
            continue;

        if(gMapList[i].pressedPioLevel != 0) 
            pressedPattern = 1;
        else 
            pressedPattern = 0;

        pressedPattern = ((tmdlPioLines_t)pressedPattern<<gMapList[i].pioNo);
        if((bitPattern & gPioLines[i]) == pressedPattern) //pressed
        {
            dprintk(2, "pressedPattern\n");
            if(gKeyStatus[i] == KEY_RELEASED)
            {
                gKeyStatus[i] = KEY_PRESSED;
                input_report_key( gpInputDevice, gMapList[i].translated_key, 1 );
            }
        }
        else
        {
            if(gKeyStatus[i] == KEY_PRESSED)
            {
                gKeyStatus[i] = KEY_RELEASED;
                input_report_key( gpInputDevice, gMapList[i].translated_key, 0 );
            }
        }
        i ++;
    }
    up(&gKeyGpioMutex);
}

/* Timer_Func */
static void Timer_Func(unsigned long param)
{
    queue_work(gpKeyGpioWorkQueue, &gScanWork); //because tmdlPio operation may cause sleep, so not doing scan in timer 
    gTimerList.expires = jiffies + SCAN_INTERVAL;
    add_timer(&gTimerList);
}

static int phStbKeyProtocol_Ioctl( struct inode  *inode,
                                   struct file   *filp,
                                   unsigned int   cmd,
                                   unsigned long  arg )
{
    int ret = 0;
    int i;

    switch(cmd)
    {
        case PHSTBKEY_IOCTL_REPEAT_DELAY:
            ret = get_user(gRepeatDelay, (int __user *)arg);
            if(gRepeatDelay < MINI_REPEAT_DELAY) gRepeatDelay = MINI_REPEAT_DELAY;
            gpInputDevice->rep[REP_PERIOD] = gRepeatDelay;
            break;
            
        case PHSTBKEY_IOCTL_SET_KEYS_MAP:
            /* stop timer at first */
            del_timer_sync(&gTimerList);
            /* */
            if(down_interruptible(&gKeyGpioMutex) != 0) return -ERESTART;
                gbMapListSetupDone = False;
                
            if(copy_from_user(gMapList, (unsigned char __user *)arg, sizeof(phStbKeyMapList)) != 0)
            {
                up(&gKeyGpioMutex); 
                return -EFAULT;
            }
            //-------------- set gMapList -------------------
            //check parameters
            i = 0;
            SCANMAP_LIST(i) //scan map list
            {
                if(gMapList[i].pioNo >= 54 || gMapList[i].translated_key >= KEY_MAX) //max 54 pios for pnx83xx
                {
                    ret = -EINVAL;
                    break;
                }
                i ++;
            }
            //set pio resources 
            ret = SetupPioResources();
            //set keybit
            if(ret == 0)
            {
                i = 0;
                SCANMAP_LIST(i) //scan map list
                {
                    set_bit( gMapList[i].translated_key, gpInputDevice->keybit );   
                    i ++;
                }
                gpInputDevice->rep[REP_PERIOD] = gRepeatDelay;
                gbMapListSetupDone = True;
            }
    
            up(&gKeyGpioMutex); 
    
            /* start timer && set gbMapListSetupDone as True */ 
            if(ret == 0)
            {
                gTimerList.data = 0;
                gTimerList.expires = jiffies + SCAN_INTERVAL;
                gTimerList.function = Timer_Func;
                add_timer(&gTimerList);
            }
            break;
    
        default:
            ret = -ENOTTY;
            break;
    } //end switch

    return ret;
}

static int phStbKeyGpio_open( struct input_dev * pDev )
{
    return 0;
}

static void phStbKeyGpio_close( struct input_dev * pDev )
{
    del_timer_sync(&gTimerList);
}

/** Module initialisation. */
int phStbKeyProtocol_Init( struct input_dev * pInput_dev )
{
    int ret = 0;
    int i;
    
    gbMapListSetupDone = False;

    /* Register a misc device called "phStbKeyProtocol". */
    ret = misc_register( &gphStbKeyProtocol_MiscDev );
    if (ret < 0)
        dprintk(1, "can't register misc device (minor %d)!\n", gphStbKeyProtocol_MiscDev.minor );
    else
        dprintk(1, "%s (%s-%s)\n", PHSTBKEYPROTOCOL_MODULE_DESCRIPTION, __DATE__, __TIME__);

    /* init timer */
    init_timer(&gTimerList);
    
    /* get input device */
    gpInputDevice = pInput_dev;
    gpInputDevice->open = phStbKeyGpio_open;
    gpInputDevice->close = phStbKeyGpio_close;

    /* creat work queue */
    gpKeyGpioWorkQueue = create_singlethread_workqueue("stbKeyGpio");

    /* set keybit to enable directfb keys report */
    for(i = 0; (i*4) < sizeof(gPossibleKeys); i++)
    {
        set_bit( gPossibleKeys[i], gpInputDevice->keybit ); 
    }
    return 0;
}

/** Module deinitialisation */
void phStbKeyProtocol_Exit( void )
{   
    del_timer_sync(&gTimerList);
    destroy_workqueue(gpKeyGpioWorkQueue);

    if(phStbKey_pioInstance != (tmInstance_t)Null) 
    {
        tmdlPioClose(phStbKey_pioInstance);
        phStbKey_pioInstance = (tmInstance_t)Null;
    }
    misc_deregister( &gphStbKeyProtocol_MiscDev );
}

EXPORT_SYMBOL(phStbKeyProtocol_Init);
EXPORT_SYMBOL(phStbKeyProtocol_Exit);
