/*
  Copyright (c) 2015 Arduino LLC.  All right reserved.
  Copyright (c) 2015 Atmel Corporation/Thibaut VIARD.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library 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 Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "board_driver_i2c.h"

#ifdef CONFIGURE_PMIC

/*- Definitions -------------------------------------------------------------*/
#define I2C_SERCOM            SERCOM2
#define I2C_SERCOM_PMUX       PORT_PMUX_PMUXE_D_Val
#define I2C_SERCOM_GCLK_ID    SERCOM0_GCLK_ID_CORE
#define I2C_SERCOM_CLK_GEN    0
#define I2C_SERCOM_APBCMASK   PM_APBCMASK_SERCOM0

#define T_RISE                215e-9 // Depends on the board, actually

enum
{
  I2C_TRANSFER_WRITE = 0,
  I2C_TRANSFER_READ  = 1,
};

enum
{
  I2C_PINS_SDA = (1 << 0),
  I2C_PINS_SCL = (1 << 1),
};

#define F_CPU   48000000

static inline void pin_set_peripheral_function(uint32_t pinmux)
{
    /* the variable pinmux consist of two components:
        31:16 is a pad, wich includes:
            31:21 : port information 0->PORTA, 1->PORTB
            20:16 : pin 0-31
        15:00 pin multiplex information
        there are defines for pinmux like: PINMUX_PA09D_SERCOM2_PAD1 
    */
    uint16_t pad = pinmux >> 16;    // get pad (port+pin)
    uint8_t port = pad >> 5;        // get port
    uint8_t pin  = pad & 0x1F;      // get number of pin - no port information anymore
    
    PORT->Group[port].PINCFG[pin].bit.PMUXEN =1;
    
    /* each pinmux register is for two pins! with pin/2 you can get the index of the needed pinmux register
       the p mux resiter is 8Bit   (7:4 odd pin; 3:0 evan bit)  */
    // reset pinmux values.                             VV shift if pin is odd (if evan:  (4*(pin & 1))==0  )
    PORT->Group[port].PMUX[pin/2].reg &= ~( 0xF << (4*(pin & 1)) );
                    //          
    // set new values
    PORT->Group[port].PMUX[pin/2].reg |=  ( (uint8_t)( (pinmux&0xFFFF) <<(4*(pin&1)) ) ); 
}

//-----------------------------------------------------------------------------
void i2c_init(int freq)
{
  int baud = ((float)F_CPU / freq - (float)F_CPU * T_RISE - 10.0) / 2.0;

  if (baud < 0)
    baud = 0;
  else if (baud > 255)
    baud = 255;

  PM->APBCMASK.reg |= I2C_SERCOM_APBCMASK;

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(I2C_SERCOM_GCLK_ID) |
      GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(I2C_SERCOM_CLK_GEN);

  I2C_SERCOM->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_SWRST;
  while (I2C_SERCOM->I2CM.CTRLA.reg & SERCOM_I2CM_CTRLA_SWRST);

  I2C_SERCOM->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN;
  while (I2C_SERCOM->I2CM.SYNCBUSY.reg);

  I2C_SERCOM->I2CM.BAUD.reg = SERCOM_I2CM_BAUD_BAUD(baud);
  while (I2C_SERCOM->I2CM.SYNCBUSY.reg);

  I2C_SERCOM->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_ENABLE |
      SERCOM_I2CM_CTRLA_MODE_I2C_MASTER |
      SERCOM_I2CM_CTRLA_SDAHOLD(3);
  while (I2C_SERCOM->I2CM.SYNCBUSY.reg);

  I2C_SERCOM->I2CM.STATUS.reg |= SERCOM_I2CM_STATUS_BUSSTATE(1);

  pin_set_peripheral_function(PINMUX_PA08C_SERCOM0_PAD0);
  pin_set_peripheral_function(PINMUX_PA09C_SERCOM0_PAD1);
}

//-----------------------------------------------------------------------------
bool i2c_start(int addr)
{
  I2C_SERCOM->I2CM.ADDR.reg = addr;

  while (0 == (I2C_SERCOM->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_MB) &&
         0 == (I2C_SERCOM->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_SB));

  if (I2C_SERCOM->I2CM.STATUS.reg & SERCOM_I2CM_STATUS_RXNACK)
  {
    I2C_SERCOM->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3);
    return false;
  }

  return true;
}

//-----------------------------------------------------------------------------
bool i2c_stop(void)
{
  if ((I2C_SERCOM->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_MB) ||
      (I2C_SERCOM->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_SB))
  {
    I2C_SERCOM->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3);
  }

  return true;
}

//-----------------------------------------------------------------------------
bool i2c_read_byte(uint8_t *byte, bool last)
{
  while (0 == (I2C_SERCOM->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_SB));

  if (last)
    I2C_SERCOM->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT | SERCOM_I2CM_CTRLB_CMD(3);
  else
    I2C_SERCOM->I2CM.CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT;

  *byte = I2C_SERCOM->I2CM.DATA.reg;

  return true;
}

//-----------------------------------------------------------------------------
bool i2c_write_byte(uint8_t byte)
{
  I2C_SERCOM->I2CM.DATA.reg = byte;

  while (0 == (I2C_SERCOM->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_MB));

  if (I2C_SERCOM->I2CM.STATUS.reg & SERCOM_I2CM_STATUS_RXNACK)
  {
    I2C_SERCOM->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3);
    return false;
  }

  return true;
}

//-----------------------------------------------------------------------------
bool i2c_busy(int addr)
{
  bool busy;

  I2C_SERCOM->I2CM.ADDR.reg = addr | I2C_TRANSFER_WRITE;

  while (0 == (I2C_SERCOM->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_MB));

  busy = (0 != (I2C_SERCOM->I2CM.STATUS.reg & SERCOM_I2CM_STATUS_RXNACK));

  I2C_SERCOM->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3);

  return busy;
}

uint8_t readRegister(uint8_t startAddress) {

    uint8_t DATA = 0;
    i2c_start(PMIC_ADDRESS);
    i2c_write_byte(startAddress);
    i2c_stop();

    i2c_start(PMIC_ADDRESS);
    i2c_read_byte(&DATA, true);
    return DATA;
}


/*******************************************************************************
 * Function Name  :
 * Description    :
 * Input          :
 * Return         :
 *******************************************************************************/
void writeRegister(uint8_t address, uint8_t DATA) {

    i2c_start(PMIC_ADDRESS);
    i2c_write_byte(address);
    i2c_write_byte(DATA);
    i2c_stop();
}

bool disableWatchdog(void) {

    uint8_t DATA = readRegister(CHARGE_TIMER_CONTROL_REGISTER);
    writeRegister(CHARGE_TIMER_CONTROL_REGISTER, (DATA & 0b11001110));
    return 1;
}

bool setInputVoltageLimit(uint16_t voltage) {

    uint8_t DATA = readRegister(INPUT_SOURCE_REGISTER);
    uint8_t mask = DATA & 0b10000111;

    switch(voltage) {

        case 3880:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00000000));
        break;

        case 3960:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00001000));
        break;

        case 4040:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00010000));
        break;

        case 4120:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00011000));
        break;

        case 4200:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00100000));
        break;

        case 4280:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00101000));
        break;

        case 4360:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00110000));
        break;

        case 4440:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00111000));
        break;

        case 4520:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b01000000));
        break;

        case 4600:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b01001000));
        break;

        case 4680:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b01010000));
        break;

        case 4760:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b01011000));
        break;

        case 4840:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b01100000));
        break;

        case 4920:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b01101000));
        break;

        case 5000:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b01110000));
        break;

        case 5080:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b01111000));
        break;

        default:
        return 0; // return error since the value passed didn't match
    }

    return 1; // value was written successfully
}

bool setInputCurrentLimit(uint16_t current) {


    uint8_t DATA = readRegister(INPUT_SOURCE_REGISTER);
    uint8_t mask = DATA & 0b11111000;

    switch (current) {

        case 100:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00000000));
        break;

        case 150:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00000001));
        break;

        case 500:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00000010));
        break;

        case 900:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00000011));
        break;

        case 1200:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00000100));
        break;

        case 1500:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00000101));
        break;

        case 2000:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00000110));
        break;

        case 3000:
        writeRegister(INPUT_SOURCE_REGISTER, (mask | 0b00000111));
        break;

        default:
        return 0; // return error since the value passed didn't match
    }

    return 1; // value was written successfully
}

bool setChargeCurrent(bool bit7, bool bit6, bool bit5, bool bit4, bool bit3, bool bit2) {

    uint8_t current = 0;
    if (bit7) current = current | 0b10000000;
    if (bit6) current = current | 0b01000000;
    if (bit5) current = current | 0b00100000;
    if (bit4) current = current | 0b00010000;
    if (bit3) current = current | 0b00001000;
    if (bit2) current = current | 0b00000100;

    uint8_t DATA = readRegister(CHARGE_CURRENT_CONTROL_REGISTER);
    uint8_t mask = DATA & 0b00000001;
    writeRegister(CHARGE_CURRENT_CONTROL_REGISTER, current | mask);
    return 1;
}

bool setChargeVoltage(uint16_t voltage) {

    uint8_t DATA = readRegister(CHARGE_VOLTAGE_CONTROL_REGISTER);
    uint8_t mask = DATA & 0b000000011;

    switch (voltage) {

        case 4112:
        writeRegister(CHARGE_VOLTAGE_CONTROL_REGISTER, (mask | 0b10011000));
        break;

        case 4208:
        writeRegister(CHARGE_VOLTAGE_CONTROL_REGISTER, (mask | 0b10110000));
        break;

        default:
        return 0; // return error since the value passed didn't match
    }

    return 1; // value was written successfully
}

void apply_pmic_newdefaults()
{
  disableWatchdog();
  //disableDPDM();
  setInputVoltageLimit(4360); // default
  setInputCurrentLimit(900);     // 900mA
  setChargeCurrent(0,0,0,0,0,0); // 512mA
  setChargeVoltage(4112);        // 4.112V termination voltage
}

void configure_pmic() {
  i2c_init(100000);
  apply_pmic_newdefaults();
}

#endif