/*
 * LP5521 LED chip driver.
 *
 * Copyright (C) 2010 Nokia Corporation
 * Copyright (C) 2012 Texas Instruments
 *
 * Contact: Samu Onkalo <samu.p.onkalo@nokia.com>
 *          Milo(Woogyom) Kim <milo.kim@ti.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * 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.	
*/
#include "SRI_Global.h"
#include "Parse.h"
#include <stdio.h>
#include <string.h>
#include "Board.h"
#include "math.h"
#include "LP5521.h"
#include "i2c.h"

inline void lp5521_wait_opmode_done(void)
{
	/* operation mode change needs to be longer than 153 us */
	usleep(200);
}

inline void lp5521_wait_enable_done(void)
{
	/* it takes more 488 us to update ENABLE register */
	usleep(1000);
}

void lp5521_set_led_current(struct lp55xx_led *led, uint8_t led_current)
{
	led->led_current = led_current;
	lp55xx_write(LP5521_REG_LED_CURRENT_BASE + led->chan_nr,
		led_current);
}

void lp5521_load_engine(struct lp55xx_chip *chip)
{
	enum lp55xx_engine_index idx = chip->engine_idx;
	static const uint8_t mask[] =
	{
		[LP55XX_ENGINE_1] = LP5521_MODE_R_M,
		[LP55XX_ENGINE_2] = LP5521_MODE_G_M,
		[LP55XX_ENGINE_3] = LP5521_MODE_B_M,
	};

	static const uint8_t val[] =
	{
		[LP55XX_ENGINE_1] = LP5521_LOAD_R,
		[LP55XX_ENGINE_2] = LP5521_LOAD_G,
		[LP55XX_ENGINE_3] = LP5521_LOAD_B,
	};

	lp55xx_update_bits(LP5521_REG_OP_MODE, mask[idx], val[idx]);

	lp5521_wait_opmode_done();
}

void lp5521_stop_all_engines(struct lp55xx_chip *chip)
{
	lp55xx_write(LP5521_REG_OP_MODE, 0);
	lp5521_wait_opmode_done();
}

void lp5521_stop_engine(struct lp55xx_chip *chip)
{
	enum lp55xx_engine_index idx = chip->engine_idx;
	static const uint8_t mask[] =
	{
		[LP55XX_ENGINE_1] = LP5521_MODE_R_M,
		[LP55XX_ENGINE_2] = LP5521_MODE_G_M,
		[LP55XX_ENGINE_3] = LP5521_MODE_B_M,
	};

	lp55xx_update_bits(LP5521_REG_OP_MODE, mask[idx], 0);

	lp5521_wait_opmode_done();
}

void lp5521_run_engine(struct lp55xx_chip *chip, bool start)
{
	int ret;
	uint8_t mode;
	uint8_t exec;

	/* stop engine */
	if (!start)
	{
		lp5521_stop_engine(chip);
		lp55xx_write(LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
		lp5521_wait_opmode_done();
		return;
	}

	/*
	 * To run the engine,
	 * operation mode and enable register should updated at the same time
	 */

	ret = lp55xx_read(LP5521_REG_OP_MODE, &mode);
	if (ret)
		return;

	ret = lp55xx_read(LP5521_REG_ENABLE, &exec);
	if (ret)
		return;

	/* change operation mode to RUN only when each engine is loading */
	if (LP5521_R_IS_LOADING(mode))
	{
		mode = (mode & ~LP5521_MODE_R_M) | LP5521_RUN_R;
		exec = (exec & ~LP5521_EXEC_R_M) | LP5521_RUN_R;
	}

	if (LP5521_G_IS_LOADING(mode))
	{
		mode = (mode & ~LP5521_MODE_G_M) | LP5521_RUN_G;
		exec = (exec & ~LP5521_EXEC_G_M) | LP5521_RUN_G;
	}

	if (LP5521_B_IS_LOADING(mode))
	{
		mode = (mode & ~LP5521_MODE_B_M) | LP5521_RUN_B;
		exec = (exec & ~LP5521_EXEC_B_M) | LP5521_RUN_B;
	}

	lp55xx_write(LP5521_REG_OP_MODE, mode);
	lp5521_wait_opmode_done();

	lp55xx_update_bits(LP5521_REG_ENABLE, LP5521_EXEC_M, exec);
	lp5521_wait_enable_done();
}

int lp5521_update_program_memory(struct lp55xx_chip *chip,
					const uint8_t *data, size_t size)
{
	enum lp55xx_engine_index idx = chip->engine_idx;
	uint8_t pattern[LP5521_PROGRAM_LENGTH] = {0};
	static const uint8_t addr[] = {
		[LP55XX_ENGINE_1] = LP5521_REG_R_PROG_MEM,
		[LP55XX_ENGINE_2] = LP5521_REG_G_PROG_MEM,
		[LP55XX_ENGINE_3] = LP5521_REG_B_PROG_MEM,
	};
	unsigned cmd;
	char c[3];
	int nrchars;
	int ret;
	int offset = 0;
	int i = 0;

	while ((offset < size - 1) && (i < LP5521_PROGRAM_LENGTH))
	{
		/* separate sscanfs because length is working only for %s */
		ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
		if (ret != 1)
			goto err;

		ret = sscanf(c, "%2x", &cmd);
		if (ret != 1)
			goto err;

		pattern[i] = (uint8_t)cmd;
		offset += nrchars;
		i++;
	}

	/* Each instruction is 16bit long. Check that length is even */
	if (i % 2)
		goto err;

	for (i = 0; i < LP5521_PROGRAM_LENGTH; i++) {
		ret = lp55xx_write(addr[idx] + i, pattern[i]);
	    return (ret);
	}
	return size;

err:
	return -1;
}

void lp5521_firmware_loaded(struct lp55xx_chip *chip)
{
	/*
	 * Program memory sequence
	 *  1) set engine mode to "LOAD"
	 *  2) write firmware data into program memory
	 */
	lp5521_load_engine(chip);
//	lp5521_update_program_memory(chip, fw->data, fw->size);
}

int lp5521_init(void)
{
	int ret;
	uint8_t val;

	/* Bring LP5521 Chip Enable High */
    GPIO_setDio(MINIRAMAN_LED_EN);
    usleep(2000);

    /* This locks up because the device cannot ACK
     * During the reset process. It is unnecessary
     * because Power On Reset has already occurred.
     */

 //   lp55xx_reset_device();

     /* 1ms appears to be enough for reset. */
   // usleep(2000);

	/*
	 * Make sure that the chip is reset by reading back the r channel
	 * current reg. This is dummy read is required on some platforms -
	 * otherwise further access to the R G B channels in the
	 * LP5521_REG_ENABLE register will not have any effect - strange!
	 */
	lp55xx_read(LP5521_REG_R_CURRENT, &val);

	if (val != LP5521_REG_R_CURR_DEFAULT)
	{
		ret = -1;
		return ret;
	}
	usleep(2000);

	/* Set all PWMs to direct control mode */
	ret = lp55xx_write(LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);

	/* Update configuration for the clock setting */
	val = LP5521_DEFAULT_CFG;
	val |= LP5521_CLK_INT;

	ret = lp55xx_write(LP5521_REG_CONFIG, val);
	if (ret == false) return (-1);

	/* Initialize all channels PWM to zero -> leds off */
	lp55xx_write(LP5521_REG_R_PWM, 0x00);
	lp55xx_write(LP5521_REG_G_PWM, 0x00);
	lp55xx_write(LP5521_REG_B_PWM, 0x00);

	/* Set engines are set to run state when OP_MODE enables engines */
	ret = lp55xx_write(LP5521_REG_ENABLE, LP5521_ENABLE_RUN_PROGRAM);
	if (ret == false) return (-1);

	lp5521_wait_enable_done();

	return 0;
}

int lp5521_led_brightness(struct lp55xx_led *led)
{
	int ret;

	ret = lp55xx_write(LP5521_REG_LED_PWM_BASE + led->chan_nr,
		led->brightness);

	return ret;
}

void lp55xx_reset_device(void)
{
    /* no error checking here because no ACK from the device after reset */
    lp55xx_write(LP5521_REG_RESET, LP5521_RESET);
}


int lp55xx_write(uint8_t reg, uint8_t val)
{
    bool i2c_return;

    i2c_txBuffer[0] = reg;
    i2c_txBuffer[1] = val;

    i2cTransaction.slaveAddress = LP5521_ADDRESS;
    i2cTransaction.writeBuf = i2c_txBuffer;
    i2cTransaction.writeCount = 2;
    i2cTransaction.readBuf = i2c_rxBuffer;
    i2cTransaction.readCount = 0;
    i2c_return = I2C_transfer(i2c, &i2cTransaction);
    return(i2c_return);
}


int lp55xx_read(uint8_t reg, uint8_t *val)
{
    bool i2c_return;

    i2c_txBuffer[0] = reg;

    i2cTransaction.slaveAddress = LP5521_ADDRESS;
    i2cTransaction.writeBuf = i2c_txBuffer;
    i2cTransaction.writeCount = 1;
    i2cTransaction.readBuf = i2c_rxBuffer;
    i2cTransaction.readCount = 1;
    i2c_return = I2C_transfer(i2c, &i2cTransaction);
    *val = i2c_rxBuffer[0];
    return (i2c_return);
}


int lp55xx_update_bits(uint8_t reg, uint8_t mask, uint8_t val)
{
    uint8_t tmp;
    bool i2c_return;

    i2c_txBuffer[0] = reg;

    i2cTransaction.slaveAddress = LP5521_ADDRESS;
    i2cTransaction.writeBuf = i2c_txBuffer;
    i2cTransaction.writeCount = 1;
    i2cTransaction.readBuf = i2c_rxBuffer;
    i2cTransaction.readCount = 1;
    i2c_return = I2C_transfer(i2c, &i2cTransaction);
    if (i2c_return == false) return (i2c_return);
    tmp = i2c_rxBuffer[0];
    tmp &= ~mask;
    tmp |= val & mask;

    i2c_txBuffer[0] = reg;
    i2c_txBuffer[1] = tmp;

    i2cTransaction.slaveAddress = LP5521_ADDRESS;
    i2cTransaction.writeBuf = i2c_txBuffer;
    i2cTransaction.writeCount = 2;
    i2cTransaction.readBuf = i2c_rxBuffer;
    i2cTransaction.readCount = 0;
    i2c_return = I2C_transfer(i2c, &i2cTransaction);
    return(i2c_return);
}

/************************************ Done ***********************************************/



