/*****************************************************************************/
/* 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 name   : i2c_client.c

Description : I2C Client Driver for the TDHL5 ISDB-T Tuner Driver

Date          Modification                              Name
----          ------------                              ----
01 Jun 2007   Created                                   Anastacio Cavalcante - Lucas Cordeiro
23 Jul 2007   Changed driver name to TDHL5              Lucas Cordeiro
25 Jul 2007   Added Doxygen comments                    Lucas Cordeiro
******************************************************************************************/
/******************************** Kernel Includes *******************************/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/jiffies.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <asm/semaphore.h>

#include "../inc/tdhl5.h"
#include "../inc/i2c_client.h"
/********************************************************************************/

#define I2C_NAME(s) (s)->name

/******************************** Probing classes *******************************/
//static unsigned short normal_i2c_range[] = { 0x00, 0x7f, I2C_CLIENT_END };
static unsigned short normal_i2c[] = {0x10, 0x18, I2C_CLIENT_END };
I2C_CLIENT_INSMOD;
/********************************************************************************/

/*************************** Structures and variables ***************************/
#if 0
static struct miscdevice sm_MiscDev = {
     .minor =  MISC_DYNAMIC_MINOR,
     .name  =  "sm",
     .fops  =  &sm_fops
};
#endif
static int    sm_present = 0;
static int sm_attachAdapter(struct i2c_adapter *adapter);
static int sm_detachClient(struct i2c_client *client);

static struct driver_data sm_data;
//static struct i2c_driver  sm_driver;

static struct i2c_client *sm_client = NULL;
static struct i2c_driver sm_driver = {
    .driver = {
       .name = "SM",
    },
    //.id = I2C_DRIVERID_SM,
    .attach_adapter = sm_attachAdapter,
    .detach_client = sm_detachClient,
};

static struct i2c_adapter sm_adapter;
/********************************************************************************/

/*   T D H N 2   I N T E R F A C E S   */

/********************************Lock functions********************************/

/**
 * @brief Function used to lock the I2C bus to a given device
 *
 * @param[in] *access 	device access info
 *
 * @retval This function returns 0 if the device is locked successfully. 
 *		   Otherwise, it returns -1.
 */
s32 sm_lock (access_info * access)
{
	if(down_interruptible(&sm_data.sema))
		return -1;

	if (sm_data.lock == 0)
	{
		sm_data.lock = 1;
		sm_data.access.id = jiffies%101101 + 1;
		sm_data.access.owner = access->owner;
		access->id = sm_data.access.id;
		up(&sm_data.sema);

		return 0;
	}
	up(&sm_data.sema);

	return -1;
}

/**
 * @brief Function used to unlock the I2C bus
 *
 * @param[in] *access 	device access info
 *
 * @retval This function returns 0 if the device is unlocked successfully. 
 *		   Otherwise, it returns -1.
 */
s32 sm_unlock(access_info * access)
{
	if(down_interruptible(&sm_data.sema))
		return -1;

	if ((sm_data.lock == 1) && (access->id == sm_data.access.id) && (access->owner == sm_data.access.owner))
	{
		sm_data.lock = 0;
		sm_data.access.id = jiffies + 1;
		sm_data.access.owner = (struct module *) 0xFFFFFFFF;
		access->id = 0;
		up(&sm_data.sema);

		return 0;
	}
	up(&sm_data.sema);

	return -1;
}

//  ----------------------- sm I2C Detection Function (Detect) ------------
static int sm_detectClient (struct i2c_adapter *adapter, int address, int kind)
{
    int result;
#if 1
    printk(
        KERN_INFO
        "sm.c: detecting sm client on address 0x%x\n",
        address << 1);
#endif
    /* Check if the adapter supports the needed features */
    if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
    {
        return 0;
    }

    /* Create the I2C client data structure */
    sm_client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
    if (sm_client == 0)
    {
        return -ENOMEM;
    }
    memset(sm_client, 0, sizeof(struct i2c_client));
    sm_client->addr = address;
    sm_client->adapter = adapter;
    sm_client->driver = &sm_driver;
    snprintf(I2C_NAME(sm_client), sizeof(I2C_NAME(sm_client)) - 1, "sm");

    /* Attach the device to the I2C adapter */
    result = i2c_attach_client(sm_client);
    if (result) {
        printk(
            KERN_INFO
            "sm.c: Failed to attach to client on adapter %s (0x%x) - 0x%02X\n",
            I2C_NAME(adapter), adapter->id, result);
        kfree(sm_client);
        return result;
    }
#if 0
    result = misc_register( &sm_MiscDev );
    if (result < 0)
    {
        printk(KERN_WARNING "%s: Can't register misc device (minor %d)!\n", THIS_MODULE->name, sm_MiscDev.minor);
        return result;
    }
#endif
    sm_present = 1;
    printk(
        KERN_INFO
        "sm.c: found device sm on adapter %s (0x%x) - 0x%02X\n",
        I2C_NAME(adapter), adapter->id, result);

    return 0;
}

//  ----------------------- saa1064 I2C Detection Function (Attach)     --------
static int sm_attachAdapter (struct i2c_adapter *adapter)
{
    return i2c_probe(adapter, &addr_data, &sm_detectClient);
}

//  ----------------------- saa1064 I2C Detection Function (Detach)     --------
static int sm_detachClient (struct i2c_client *client)
{
    int err;

    err = i2c_detach_client(client);
    if (err)
    {
        return err;
    }

    kfree(client);

    return 0;
}

/**************************** Communication Functions ***************************/

/**
 * @brief This function sends one byte to the I2C bus.
 *
 * @param[in] reg 		register address
 * @param[in] value	 	register value
 * @param[in] *access 	device access info
 *
 * @retval This function returns 0 if the bytes is sent successfully. 
 *		   Otherwise, it returns -1.
 */
s32 sm_write_value(u8 reg, u8 value, access_info *access)
{
	if ((sm_data.lock != 0) && (access->id == sm_data.access.id) && (access->owner == sm_data.access.owner) && (sm_data.chip_detected == 1))
	{
		return ((i2c_smbus_write_byte_data(sm_data.sm_client, reg, value) == -1) ? -1 : 0);
	}
	
	return -1;
}

/**
 * @brief This function retrievs the TDHL5 registers value.
 *
 * @param[in] 	reg 		register address
 * @param[out]	data	 	register value
 * @param[in]	*access 		device access info
 *
 * @retval This function returns the register value. Otherwise, it returns -1.
 */
s32 sm_read_value(u8 reg, s32 *data, access_info *access)
{
	if ((sm_data.lock != 0) && (access->id == sm_data.access.id) && (access->owner == sm_data.access.owner) && (sm_data.chip_detected == 1))
	{
		*data = i2c_smbus_read_byte_data(sm_data.sm_client, reg);
		
		return ((*data == -1) ? -1 : 0);
	}
	
	return -1;
}

/**
 * @brief This function sends a set of bytes to the I2C bus.
 *
 * @param[in] 	length 		register value length
 * @param[in]	data	 	register value
 * @param[in]	*access 		device access info
 *
 * @retval This function returns 0 if the bytes are sent successfully. Otherwise, 
 *         it returns -1.
 */
s32 sm_write_pll(u8 length, u8 *data, access_info *access)
{
	if ((sm_data.lock != 0) && (access->id == sm_data.access.id) && (access->owner == sm_data.access.owner) && (sm_data.chip_detected == 1))
	{
		return ((i2c_master_send(sm_data.sm_client, data , length) == -1) ? -1 : 0);
	}
	
	return -1;
}




/******************************* Driver Methods *******************************/
/**
 * @brief This function is used to create the /proc file.
 *
 * @param[in] 	*param 		parameter
 * @param[in]	**start	 	start
 * @param[in]	offset 		off_t
 * @param[in]	len	 		data length
 * @param[in]	*eof		end of file
 * @param[in]	*data		data to be sent via i2c bus
 *
 * @retval This function returns 0 if the bytes are sent successfully. Otherwise, 
 *         it returns -1.
 */
s32 sm_read_proc(char *param, char **start, off_t offset, s32 len, s32 *eof, void *data)
{
	s32 comp = 0;
	
	comp+=sprintf(param+comp, "TDHL5->i2c_client.c:%s(%d), Chip detected=%s\nChip Address=%.2Xh\n", __FUNCTION__, __LINE__, (sm_data.chip_detected == 1) ? "OK" : "NOK", (sm_data.chip_detected == 1) ? sm_data.sm_client->addr : 0);
	
	*eof = 1;
	return comp;
}

/******************************* Commom functions *******************************/

/**
 * @brief This function looks for the devices that are connected to the i2c bus
 *
 * @param[in] *adapter	i2c_adapter
 *
 * @retval None
 */
s32 sm_attach_adapter(struct i2c_adapter *adapter){

	debug(KERN_WARNING "TDHL5->i2c_client.c:%s(%d), Searching device...\n", __FUNCTION__, __LINE__);

	return i2c_probe(adapter, &addr_data, sm_detect_client);
}

/**
 * @brief This function responsible for detecting a device connected to the
 *		  i2c bus.
 *
 * @param[in] *adapter	i2c_adapter
 * @param[in] address	device address in the i2c bus
 * @param[in] kind		kind
 *
 * @retval This function returns 0 if the device is detected. Otherwise, it
 *		    returns -1.
 */
s32 sm_detect_client(struct i2c_adapter *adapter, s32 address, s32 kind)
{
	s32 err;
	struct i2c_client *new_client;
	const char *client_name = "TDHL5";
	
	if (sm_data.chip_detected == 1)
		return -1;
	
	if (!i2c_check_functionality(adapter,I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_I2C))
		return -1;
	
	new_client = (struct i2c_client *)kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
	if (!new_client)
		return -ENOMEM;

	debug(KERN_WARNING "TDHL5->i2c_client.c:%s(%d), Device detected on Address:Ox%.2X\n", __FUNCTION__, __LINE__, address);
	
	sm_data.sm_client = new_client;
	
	memset(new_client, 0, sizeof(struct i2c_client));

	new_client->addr = address;
	new_client->adapter = adapter;
	new_client->driver = &sm_driver;
	new_client->flags = 0;
	
	strncpy(new_client->name,client_name,I2C_NAME_SIZE);

	err = i2c_attach_client(new_client);

	if (err)
	{
		kfree(new_client);
		sm_data.sm_client = NULL;
	}

	sm_data.chip_detected = 1;

	return err;
}

/**
 * @brief This function detaches a given device from the I2C bus.
 *
 * @param[in] *client		information abou the i2c client 
 *
 * @retval This function returns 0 if the device is successfully detached. Otherwise,
 *		    it returns -1.
 */
s32 sm_detach_client(struct i2c_client *client){

	s32 err;

	err = i2c_detach_client(client);
	if (err){

		debug(KERN_WARNING "TDHL5->i2c_client.c:%s(%d), Client deregistration failed, client not detached.\n", __FUNCTION__, __LINE__);
		
		return err;
	}
	
	kfree(client);
	
	sm_data.sm_client = NULL;
	sm_data.chip_detected = 0;
	
	return 0;
}


/**************************** Init and Exit methods *****************************/

/**
 * @brief This method is called to initialize the i2c client
 *
 * @retval None.
 */
s32 sm_init(void)
{
	s32 resp;

	debug(KERN_WARNING "TDHL5->i2c_client.c:%s(%d), Loading I2C Client...\n", __FUNCTION__, __LINE__);
	
	strncpy(sm_adapter.name, "TDHL5 I2C Client", DRIVER_NAME_LEN);
	sm_adapter.owner = THIS_MODULE;

	//sm_driver.flags          = I2C_DF_NOTIFY;
	sm_driver.attach_adapter = sm_attach_adapter;
	sm_driver.detach_client  = sm_detach_client;
	
	sm_data.lock          = 0;
	sm_data.chip_detected = 0;
	sm_data.sm_client     = NULL;
	sm_data.access.id     = jiffies + 1;
	sm_data.access.owner  = NULL;
	
	init_MUTEX(&sm_data.sema);
	
	resp = i2c_add_driver(&sm_driver);
#if 0
	if (resp){
		debug(KERN_WARNING "TDHL5->i2c_client.c:%s(%d), Driver registration failed, module not inserted.\n", __FUNCTION__, __LINE__);
		return resp;
	}

	create_proc_read_entry("i2csminfo", 0, NULL, sm_read_proc, NULL);
#endif
	return 0;
}

/**
 * @brief This method is called when unload the i2c client driver
 *
 * @retval None.
 */
void sm_exit(void)
{
	remove_proc_entry("i2csminfo", NULL);

	debug(KERN_WARNING "TDHL5->i2c_client.c:%s(%d), I2C Client is being unloaded!!!\n", __FUNCTION__, __LINE__);

	i2c_del_driver(&sm_driver);
#if 0
	if (i2c_del_driver(&sm_driver))
		debug(KERN_WARNING "TDHL5->i2c_client.c:%s(%d), Driver unregistration failed, module not removed.\n", __FUNCTION__, __LINE__);
#endif
}
