/*
 * Copyright (C) 2015-2016, Intel Corporation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "contiki.h"
#include "i2c.h"

#include "i2c-registers.h"
#include "paging.h"
#include "shared-isr.h"
#include "syscalls.h"

#define I2C_CLOCK_SPEED 25 /* kHz */
#define I2C_FIFO_DEPTH  16

#define I2C_STD_HCNT (I2C_CLOCK_SPEED * 4)
#define I2C_STD_LCNT (I2C_CLOCK_SPEED * 5)
#define I2C_FS_HCNT  (I2C_CLOCK_SPEED)
#define I2C_FS_LCNT  (I2C_CLOCK_SPEED)

#define I2C_FS_SPKLEN_LCNT_OFFSET 8
#define I2C_FS_SPKLEN_HCNT_OFFSET 6

#define I2C_POLLING_TIMEOUT (CLOCK_SECOND / 10)

#define I2C_IRQ 9

#if X86_CONF_PROT_DOMAINS == X86_CONF_PROT_DOMAINS__PAGING
#define MMIO_SZ MIN_PAGE_SIZE
#else
#define MMIO_SZ (QUARKX1000_IC_HIGHEST + 4)
#endif

typedef enum {
  I2C_DIRECTION_READ,
  I2C_DIRECTION_WRITE
} I2C_DIRECTION;

PROT_DOMAINS_ALLOC(pci_driver_t, drv);

struct quarkX1000_i2c_config {
  QUARKX1000_I2C_SPEED speed;
  QUARKX1000_I2C_ADDR_MODE addressing_mode;

  quarkX1000_i2c_callback cb_rx;
  quarkX1000_i2c_callback cb_tx;
  quarkX1000_i2c_callback cb_err;
};

struct i2c_internal_data {
  struct quarkX1000_i2c_config config;

  I2C_DIRECTION direction;

  uint8_t rx_len;
  uint8_t *rx_buffer;
  uint8_t tx_len;
  uint8_t *tx_buffer;
  uint8_t rx_tx_len;

  uint32_t hcnt;
  uint32_t lcnt;
};

static struct i2c_internal_data device;

static int inited = 0;

void quarkX1000_i2c_mmin(uint32_t offset, uint32_t *res);
SYSCALLS_DEFINE_SINGLETON(quarkX1000_i2c_mmin, drv,
                          uint32_t offset, uint32_t *res)
{
  uint32_t *loc_res;

  PROT_DOMAINS_VALIDATE_PTR(loc_res, res, sizeof(*res));
  if(QUARKX1000_IC_HIGHEST < offset) {
    halt();
  }

  prot_domains_enable_mmio();
  PCI_MMIO_READL(drv, *loc_res, offset);
  prot_domains_disable_mmio();
}

static inline uint32_t
read(uint32_t offset)
{
  uint32_t res;
  quarkX1000_i2c_mmin(offset, &res);

  return res;
}

void quarkX1000_i2c_mmout(uint32_t offset, uint32_t val);
SYSCALLS_DEFINE_SINGLETON(quarkX1000_i2c_mmout, drv,
                          uint32_t offset, uint32_t val)
{
  if(QUARKX1000_IC_HIGHEST < offset) {
    halt();
  }

  prot_domains_enable_mmio();
  PCI_MMIO_WRITEL(drv, offset, val);
  prot_domains_disable_mmio();
}

static inline void
write(uint32_t offset, uint32_t val)
{
  quarkX1000_i2c_mmout(offset, val);
}

static uint32_t
get_value(uint32_t offset, uint32_t mask, uint32_t shift)
{
  uint32_t register_value = read(offset);

  register_value &= ~(0xFFFFFFFF - mask);

  return register_value >> shift;
}

static void
set_value(uint32_t offset, uint32_t mask, uint32_t shift, uint32_t value)
{
  uint32_t register_value = read(offset);

  register_value &= ~mask;
  register_value |= value << shift;

  write(offset, register_value);
}

static void
i2c_data_read(void)
{
  uint8_t i, rx_cnt;

  if (device.rx_len == 0)
    return;

  rx_cnt = get_value(QUARKX1000_IC_RXFLR,
    QUARKX1000_IC_RXFLR_MASK, QUARKX1000_IC_RXFLR_SHIFT);

  if (rx_cnt > device.rx_len)
    rx_cnt = device.rx_len;

  for (i = 0; i < rx_cnt; i++) {
    device.rx_buffer[i] = get_value(QUARKX1000_IC_DATA_CMD,
      QUARKX1000_IC_DATA_CMD_DAT_MASK, QUARKX1000_IC_DATA_CMD_DAT_SHIFT);
  }

  device.rx_buffer += i;
  device.rx_len -= i;
}

static void
i2c_data_send(void)
{
  uint32_t data = 0;
  uint8_t i, tx_cnt;

  if (device.rx_tx_len == 0)
    return;

  tx_cnt = I2C_FIFO_DEPTH - get_value(QUARKX1000_IC_TXFLR,
    QUARKX1000_IC_TXFLR_MASK, QUARKX1000_IC_TXFLR_SHIFT);

  if (tx_cnt > device.rx_tx_len)
    tx_cnt = device.rx_tx_len;

  for (i = 0; i < tx_cnt; i++) {
    if (device.tx_len > 0) {
      data = device.tx_buffer[i];

      if (device.tx_len == 1)
        data |= (device.rx_len > 0) ? QUARKX1000_IC_DATA_CMD_RESTART_MASK : QUARKX1000_IC_DATA_CMD_STOP_MASK;

      device.tx_len -= 1;
    } else {
      data = QUARKX1000_IC_DATA_CMD_CMD_MASK;

      if (device.rx_tx_len == 1)
        data |= QUARKX1000_IC_DATA_CMD_STOP_MASK;
    }

    write(QUARKX1000_IC_DATA_CMD, data);
    device.rx_tx_len -= 1;
  }

  device.tx_buffer += i;
}

static bool
i2c_isr(void)
{
  bool handled = false;

  if (read(QUARKX1000_IC_INTR_STAT) & QUARKX1000_IC_INTR_STAT_STOP_DET_MASK) {
    i2c_data_read();

    write(QUARKX1000_IC_INTR_MASK, 0);
    read(QUARKX1000_IC_CLR_INTR);

    if (device.direction == I2C_DIRECTION_WRITE) {
      if (device.config.cb_tx)
        device.config.cb_tx();
    } else {
      if (device.config.cb_rx)
        device.config.cb_rx();
    }

    handled = true;
  }

  if (read(QUARKX1000_IC_INTR_STAT) & QUARKX1000_IC_INTR_STAT_TX_EMPTY_MASK) {
    i2c_data_send();
    if (device.rx_tx_len <= 0) {
      set_value(QUARKX1000_IC_INTR_MASK,
        QUARKX1000_IC_INTR_STAT_TX_EMPTY_MASK, QUARKX1000_IC_INTR_STAT_TX_EMPTY_SHIFT, 0);
      set_value(QUARKX1000_IC_INTR_MASK,
        QUARKX1000_IC_INTR_STAT_STOP_DET_MASK, QUARKX1000_IC_INTR_STAT_STOP_DET_SHIFT, 1);
    }

    handled = true;
  }

  if(read(QUARKX1000_IC_INTR_STAT) & QUARKX1000_IC_INTR_STAT_RX_FULL_MASK) {
    i2c_data_read();

    handled = true;
  }

  if (read(QUARKX1000_IC_INTR_STAT) & (QUARKX1000_IC_INTR_STAT_TX_ABRT_MASK
    | QUARKX1000_IC_INTR_STAT_TX_OVER_MASK | QUARKX1000_IC_INTR_STAT_RX_OVER_MASK
    | QUARKX1000_IC_INTR_STAT_RX_UNDER_MASK)) {
    write(QUARKX1000_IC_INTR_MASK, 0);
    read(QUARKX1000_IC_CLR_INTR);

    if (device.config.cb_err)
      device.config.cb_err();

    handled = true;
  }

  return handled;
}

void
quarkX1000_i2c_configure(QUARKX1000_I2C_SPEED speed,
                         QUARKX1000_I2C_ADDR_MODE addressing_mode)
{
  uint32_t hcnt, lcnt;
  uint8_t ic_fs_spklen;

  device.config.speed = speed;
  device.config.addressing_mode = addressing_mode;

  if (device.config.speed == QUARKX1000_I2C_SPEED_STANDARD) {
    lcnt = I2C_STD_LCNT;
    hcnt = I2C_STD_HCNT;
  } else {
    lcnt = I2C_FS_LCNT;
    hcnt = I2C_FS_HCNT;
  }

  ic_fs_spklen = get_value(QUARKX1000_IC_FS_SPKLEN,
    QUARKX1000_IC_FS_SPKLEN_MASK, QUARKX1000_IC_FS_SPKLEN_SHIFT);

  /* We adjust the Low Count and High Count based on the Spike Suppression Limit */
  device.lcnt = (lcnt < (ic_fs_spklen + I2C_FS_SPKLEN_LCNT_OFFSET)) ? ic_fs_spklen + I2C_FS_SPKLEN_LCNT_OFFSET : lcnt;
  device.hcnt = (hcnt < (ic_fs_spklen + I2C_FS_SPKLEN_HCNT_OFFSET)) ? ic_fs_spklen + I2C_FS_SPKLEN_HCNT_OFFSET : hcnt;

  /* Clear interrupts. */
  read(QUARKX1000_IC_CLR_INTR);
}

void
quarkX1000_i2c_set_callbacks(quarkX1000_i2c_callback rx,
                             quarkX1000_i2c_callback tx,
                             quarkX1000_i2c_callback err)
{
  device.config.cb_rx = rx;
  device.config.cb_tx = tx;
  device.config.cb_err = err;
}

static int
i2c_setup(void)
{
  /* Clear all values */
  write(QUARKX1000_IC_CON, 0);

  /* Clear interrupts */
  read(QUARKX1000_IC_CLR_INTR);

  /* Quark X1000 SoC I2C only supports master mode. */
  set_value(QUARKX1000_IC_CON,
    QUARKX1000_IC_CON_MASTER_MODE_MASK, QUARKX1000_IC_CON_MASTER_MODE_SHIFT, 1);

  /* Set restart enable */
  set_value(QUARKX1000_IC_CON,
    QUARKX1000_IC_CON_RESTART_EN_MASK, QUARKX1000_IC_CON_RESTART_EN_SHIFT, 1);

  /* Set addressing mode */
  if (device.config.addressing_mode == QUARKX1000_I2C_ADDR_MODE_10BIT) {
    set_value(QUARKX1000_IC_CON,
      QUARKX1000_IC_CON_10BITADDR_MASTER_MASK, QUARKX1000_IC_CON_10BITADDR_MASTER_SHIFT, 1);
  }

  if (device.config.speed == QUARKX1000_I2C_SPEED_STANDARD) {
    set_value(QUARKX1000_IC_SS_SCL_LCNT,
      QUARKX1000_IC_SS_SCL_LCNT_MASK, QUARKX1000_IC_SS_SCL_LCNT_SHIFT, device.lcnt);
    set_value(QUARKX1000_IC_SS_SCL_HCNT,
      QUARKX1000_IC_SS_SCL_HCNT_MASK, QUARKX1000_IC_SS_SCL_HCNT_SHIFT, device.hcnt);
    set_value(QUARKX1000_IC_CON,
      QUARKX1000_IC_CON_SPEED_MASK, QUARKX1000_IC_CON_SPEED_SHIFT, 0x1);
  } else {
    set_value(QUARKX1000_IC_FS_SCL_LCNT,
      QUARKX1000_IC_FS_SCL_LCNT_MASK, QUARKX1000_IC_FS_SCL_LCNT_SHIFT, device.lcnt);
    set_value(QUARKX1000_IC_FS_SCL_HCNT,
      QUARKX1000_IC_FS_SCL_HCNT_MASK, QUARKX1000_IC_FS_SCL_HCNT_SHIFT, device.hcnt);
    set_value(QUARKX1000_IC_CON,
      QUARKX1000_IC_CON_SPEED_MASK, QUARKX1000_IC_CON_SPEED_SHIFT, 0x2);
  }

  return 0;
}

static void
i2c_operation_setup(uint8_t *write_buf, uint8_t write_len,
  uint8_t *read_buf,  uint8_t read_len, uint16_t addr)
{
  device.rx_len = read_len;
  device.rx_buffer = read_buf;
  device.tx_len = write_len;
  device.tx_buffer = write_buf;
  device.rx_tx_len = device.rx_len + device.tx_len;

  /* Disable controller */
  set_value(QUARKX1000_IC_ENABLE,
    QUARKX1000_IC_ENABLE_MASK, QUARKX1000_IC_ENABLE_SHIFT, 0);

  i2c_setup();

  /* Disable interrupts */
  write(QUARKX1000_IC_INTR_MASK, 0);

  /* Clear interrupts */
  read(QUARKX1000_IC_CLR_INTR);

  /* Set address of target slave */
  set_value(QUARKX1000_IC_TAR,
    QUARKX1000_IC_TAR_MASK, QUARKX1000_IC_TAR_SHIFT, addr);
}

/* This is an interrupt based operation */
static int
i2c_operation(uint8_t *write_buf, uint8_t write_len,
  uint8_t *read_buf,  uint8_t read_len, uint16_t addr)
{
  if (read(QUARKX1000_IC_STATUS) & QUARKX1000_IC_STATUS_ACTIVITY_MASK)
    return -1;

  i2c_operation_setup(write_buf, write_len, read_buf, read_len, addr);

  /* Enable master TX and RX interrupts */
  set_value(QUARKX1000_IC_INTR_MASK,
    QUARKX1000_IC_INTR_STAT_TX_OVER_MASK, QUARKX1000_IC_INTR_STAT_TX_OVER_SHIFT, 1);
  set_value(QUARKX1000_IC_INTR_MASK,
    QUARKX1000_IC_INTR_STAT_TX_EMPTY_MASK, QUARKX1000_IC_INTR_STAT_TX_EMPTY_SHIFT, 1);
  set_value(QUARKX1000_IC_INTR_MASK,
    QUARKX1000_IC_INTR_STAT_TX_ABRT_MASK, QUARKX1000_IC_INTR_STAT_TX_ABRT_SHIFT, 1);
  set_value(QUARKX1000_IC_INTR_MASK,
    QUARKX1000_IC_INTR_STAT_RX_UNDER_MASK, QUARKX1000_IC_INTR_STAT_RX_UNDER_SHIFT, 1);
  set_value(QUARKX1000_IC_INTR_MASK,
    QUARKX1000_IC_INTR_STAT_RX_OVER_MASK, QUARKX1000_IC_INTR_STAT_RX_OVER_SHIFT, 1);
  set_value(QUARKX1000_IC_INTR_MASK,
    QUARKX1000_IC_INTR_STAT_RX_FULL_MASK, QUARKX1000_IC_INTR_STAT_RX_FULL_SHIFT, 1);
  set_value(QUARKX1000_IC_INTR_MASK,
    QUARKX1000_IC_INTR_STAT_STOP_DET_MASK, QUARKX1000_IC_INTR_STAT_STOP_DET_SHIFT, 1);

  /* Enable controller */
  set_value(QUARKX1000_IC_ENABLE,
    QUARKX1000_IC_ENABLE_MASK, QUARKX1000_IC_ENABLE_SHIFT, 1);

  return 0;
}

/* This is an interrupt based write */
int
quarkX1000_i2c_write(uint8_t *buf, uint8_t len, uint16_t addr)
{
  device.direction = I2C_DIRECTION_WRITE;
  return i2c_operation(buf, len, 0, 0, addr);
}

/* This is an interrupt based read */
int
quarkX1000_i2c_read(uint8_t *buf, uint8_t len, uint16_t addr)
{
  device.direction = I2C_DIRECTION_READ;
  return i2c_operation(0, 0, buf, len, addr);
}

static int
i2c_polling_operation(uint8_t *write_buf, uint8_t write_len,
  uint8_t *read_buf,  uint8_t read_len, uint16_t addr)
{
  uint32_t start_time, intr_mask_stat;

  if (!(read(QUARKX1000_IC_CON) & QUARKX1000_IC_CON_MASTER_MODE_MASK))
    return -1;

  /* Wait i2c idle */
  start_time = clock_seconds();
  while (read(QUARKX1000_IC_STATUS) & QUARKX1000_IC_STATUS_ACTIVITY_MASK) {
    if ((clock_seconds() - start_time) > I2C_POLLING_TIMEOUT) {
      return -1;
    }
  }

  /* Get interrupt mask to restore in the end of polling operation */
  intr_mask_stat = read(QUARKX1000_IC_INTR_MASK);

  i2c_operation_setup(write_buf, write_len, read_buf, read_len, addr);

  /* Enable controller */
  set_value(QUARKX1000_IC_ENABLE,
    QUARKX1000_IC_ENABLE_MASK, QUARKX1000_IC_ENABLE_SHIFT, 1);

  /* Transmit */
  if (device.tx_len != 0) {
    while (device.tx_len > 0) {
      start_time = clock_seconds();
      while (!(read(QUARKX1000_IC_STATUS) & QUARKX1000_IC_STATUS_TFNF_MASK)) {
        if ((clock_seconds() - start_time) > I2C_POLLING_TIMEOUT) {
          set_value(QUARKX1000_IC_ENABLE,
            QUARKX1000_IC_ENABLE_MASK, QUARKX1000_IC_ENABLE_SHIFT, 0);
          return -1;
        }
      }
      i2c_data_send();
    }

    start_time = clock_seconds();
    while (!(read(QUARKX1000_IC_STATUS) & QUARKX1000_IC_STATUS_TFE_MASK)) {
      if ((clock_seconds() - start_time) > I2C_POLLING_TIMEOUT) {
        set_value(QUARKX1000_IC_ENABLE,
          QUARKX1000_IC_ENABLE_MASK, QUARKX1000_IC_ENABLE_SHIFT, 0);
        return -1;
      }
    }
  }

  i2c_data_send();

  /* Receive */
  if (device.rx_len != 0) {
    while (device.rx_len > 0) {
      start_time = clock_seconds();
      while (!(read(QUARKX1000_IC_STATUS) & QUARKX1000_IC_STATUS_RFNE_MASK)) {
        if ((clock_seconds() - start_time) > I2C_POLLING_TIMEOUT) {
          set_value(QUARKX1000_IC_ENABLE,
            QUARKX1000_IC_ENABLE_MASK, QUARKX1000_IC_ENABLE_SHIFT, 0);
          return -1;
        }
      }
      i2c_data_read();
    }
  }

  /* Stop Det */
  start_time = clock_seconds();
  while (!(read(QUARKX1000_IC_RAW_INTR_STAT) & QUARKX1000_IC_INTR_STAT_STOP_DET_MASK)) {
    if ((clock_seconds() - start_time) > I2C_POLLING_TIMEOUT) {
      set_value(QUARKX1000_IC_ENABLE,
        QUARKX1000_IC_ENABLE_MASK, QUARKX1000_IC_ENABLE_SHIFT, 0);
      return -1;
    }
  }
  read(QUARKX1000_IC_CLR_STOP_DET);

  /* Wait i2c idle */
  start_time = clock_seconds();
  while (read(QUARKX1000_IC_STATUS) & QUARKX1000_IC_STATUS_ACTIVITY_MASK) {
    if ((clock_seconds() - start_time) > I2C_POLLING_TIMEOUT) {
      set_value(QUARKX1000_IC_ENABLE,
        QUARKX1000_IC_ENABLE_MASK, QUARKX1000_IC_ENABLE_SHIFT, 0);
      return -1;
    }
  }

  /* Disable controller */
  set_value(QUARKX1000_IC_ENABLE,
    QUARKX1000_IC_ENABLE_MASK, QUARKX1000_IC_ENABLE_SHIFT, 0);

  /* Restore interrupt mask */
  write(QUARKX1000_IC_INTR_MASK, intr_mask_stat);

  return 0;
}

int
quarkX1000_i2c_polling_write(uint8_t *buf, uint8_t len, uint16_t addr)
{
  device.direction = I2C_DIRECTION_WRITE;
  return i2c_polling_operation(buf, len, 0, 0, addr);
}

int
quarkX1000_i2c_polling_read(uint8_t *buf, uint8_t len, uint16_t addr)
{
  device.direction = I2C_DIRECTION_READ;
  return i2c_polling_operation(0, 0, buf, len ,addr);
}

int
quarkX1000_i2c_is_available(void)
{
  return inited;
}

DEFINE_SHARED_IRQ(I2C_IRQ, IRQAGENT3, INTC, PIRQC, i2c_isr);

int
quarkX1000_i2c_init(void)
{
  pci_config_addr_t pci_addr;

  pci_addr.raw = 0;
  pci_addr.bus = 0;
  pci_addr.dev = 21;
  pci_addr.func = 2;
  pci_addr.reg_off = PCI_CONFIG_REG_BAR0;

  pci_command_enable(pci_addr, PCI_CMD_1_MEM_SPACE_EN);

  PROT_DOMAINS_INIT_ID(drv);
  pci_init(&drv, pci_addr, MMIO_SZ, 0, 0);
  SYSCALLS_INIT(quarkX1000_i2c_mmin);
  SYSCALLS_AUTHZ(quarkX1000_i2c_mmin, drv);
  SYSCALLS_INIT(quarkX1000_i2c_mmout);
  SYSCALLS_AUTHZ(quarkX1000_i2c_mmout, drv);

  inited = 1;

  return 0;
}