/****************************************************************************
 * arch/arm/src/lpc31xx/lpc31_i2c.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/arch.h>
#include <nuttx/wdog.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/i2c/i2c_master.h>

#include <nuttx/irq.h>
#include <arch/board/board.h>

#include "chip.h"
#include "arm_internal.h"
#include "lpc31_i2c.h"
#include "lpc31_evntrtr.h"
#include "lpc31_syscreg.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define I2C_TIMEOUT  ((20 * CLK_TCK) / 1000)      /* 20 mS */

/****************************************************************************
 * Private Data
 ****************************************************************************/

struct lpc31_i2cdev_s
{
    struct i2c_master_s dev;      /* Generic I2C device */
    unsigned int      base;       /* Base address of registers */
    uint16_t          clkid;      /* Clock for this device */
    uint16_t          rstid;      /* Reset for this device */
    uint16_t          irqid;      /* IRQ for this device */

    mutex_t           lock;       /* Only one thread can access at a time */
    sem_t             wait;       /* Place to wait for state machine completion */
    volatile uint8_t  state;      /* State of state machine */
    struct wdog_s     timeout;    /* Watchdog to timeout when bus hung */
    uint32_t          frequency;  /* Current I2C frequency */

    struct i2c_msg_s *msgs;       /* remaining transfers - first one is in progress */
    unsigned int      nmsg;       /* number of transfer remaining */

    uint16_t          header[3];  /* I2C address header */
    uint16_t          hdrcnt;     /* number of bytes of header */
    uint16_t          wrcnt;      /* number of bytes sent to tx fifo */
    uint16_t          rdcnt;      /* number of bytes read from rx fifo */
};

#define I2C_STATE_DONE      0
#define I2C_STATE_START     1
#define I2C_STATE_HEADER    2
#define I2C_STATE_TRANSFER  3

static struct lpc31_i2cdev_s i2cdevices[2] =
{
  {
    .lock = NXMUTEX_INITIALIZER,
    .wait = SEM_INITIALIZER(0),
  },
  {
    .lock = NXMUTEX_INITIALIZER,
    .wait = SEM_INITIALIZER(0),
  },
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int  i2c_interrupt(int irq, void *context, void *arg);
static void i2c_progress(struct lpc31_i2cdev_s *priv);
static void i2c_timeout(wdparm_t arg);
static void i2c_hwreset(struct lpc31_i2cdev_s *priv);
static void i2c_setfrequency(struct lpc31_i2cdev_s *priv,
                             uint32_t frequency);
static int  i2c_transfer(struct i2c_master_s *dev,
                         struct i2c_msg_s *msgs, int count);
#ifdef CONFIG_I2C_RESET
static int  i2c_reset(struct i2c_master_s *dev);
#endif

/****************************************************************************
 * Private Data
 ****************************************************************************/

struct i2c_ops_s lpc31_i2c_ops =
{
  .transfer = i2c_transfer
#ifdef CONFIG_I2C_RESET
  , .reset  = i2c_reset
#endif
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: i2c_setfrequency
 *
 * Description:
 *   Set the frequency for the next transfer
 *
 ****************************************************************************/

static void i2c_setfrequency(struct lpc31_i2cdev_s *priv, uint32_t frequency)
{
  if (frequency != priv->frequency)
    {
      uint32_t freq = lpc31_clkfreq(priv->clkid, DOMAINID_AHB0APB1);

      if (freq > 100000)
        {
          /* asymmetric per 400Khz I2C spec */

          putreg32(((47 * freq) / (83 + 47)) / frequency,
                   priv->base + LPC31_I2C_CLKHI_OFFSET);
          putreg32(((83 * freq) / (83 + 47)) / frequency,
                   priv->base + LPC31_I2C_CLKLO_OFFSET);
        }
      else
        {
          /* 50/50 mark space ratio */

          putreg32(((50 * freq) / 100) / frequency,
                   priv->base + LPC31_I2C_CLKLO_OFFSET);
          putreg32(((50 * freq) / 100) / frequency,
                   priv->base + LPC31_I2C_CLKHI_OFFSET);
        }

      priv->frequency = frequency;
    }
}

/****************************************************************************
 * Name: i2c_interrupt
 *
 * Description:
 *   The I2C Interrupt Handler
 *
 ****************************************************************************/

static int i2c_interrupt(int irq, void *context, void *arg)
{
  struct lpc31_i2cdev_s *priv = (struct lpc31_i2cdev_s *)arg;

  DEBUGASSERT(priv != NULL);
  i2c_progress(priv);
  return OK;
}

/****************************************************************************
 * Name: i2c_progress
 *
 * Description:
 *   Progress any remaining I2C transfers
 *
 ****************************************************************************/

static void i2c_progress(struct lpc31_i2cdev_s *priv)
{
  struct i2c_msg_s *msg;
  uint32_t stat;
  uint32_t ctrl;

  stat = getreg32(priv->base + LPC31_I2C_STAT_OFFSET);

  /* Were there arbitration problems? */

  if ((stat & I2C_STAT_AFI) != 0)
    {
      /* Perform a soft reset */

      i2c_hwreset(priv);

      /* FIXME: automatic retry? */

      priv->state = I2C_STATE_DONE;
      nxsem_post(&priv->wait);
      return;
    }

  while (priv->nmsg > 0)
    {
      ctrl = I2C_CTRL_NAIE | I2C_CTRL_AFIE | I2C_CTRL_TDIE;
      msg  = priv->msgs;

      switch (priv->state)
        {
        case I2C_STATE_START:
          if ((msg->flags & I2C_M_TEN) != 0)
            {
              priv->header[0] = I2C_TX_START | 0xf0 |
                                ((msg->addr & 0x300) >> 7);
              priv->header[1] = msg->addr & 0xff;
              priv->hdrcnt = 2;
              if (msg->flags & I2C_M_READ)
                {
                  priv->header[2] = priv->header[0] | 1;
                  priv->hdrcnt++;
                }
            }
          else
            {
              priv->header[0] = I2C_TX_START | (msg->addr << 1) |
                                (msg->flags & I2C_M_READ);
              priv->hdrcnt = 1;
            }

          putreg32(ctrl, priv->base + LPC31_I2C_CTRL_OFFSET);

          priv->state = I2C_STATE_HEADER;
          priv->wrcnt = 0;

          /* DROP THROUGH */

        case I2C_STATE_HEADER:
          while ((priv->wrcnt != priv->hdrcnt) && (stat & I2C_STAT_TFF) == 0)
            {
              putreg32(priv->header[priv->wrcnt],
                       priv->base + LPC31_I2C_TX_OFFSET);
              priv->wrcnt++;

              stat = getreg32(priv->base + LPC31_I2C_STAT_OFFSET);
            }

          if (priv->wrcnt < priv->hdrcnt)
            {
              /* Enable Tx FIFO Not Full Interrupt */

              putreg32(ctrl | I2C_CTRL_TFFIE,
                       priv->base + LPC31_I2C_CTRL_OFFSET);
              goto out;
            }

          priv->state = I2C_STATE_TRANSFER;
          priv->wrcnt = 0;
          priv->rdcnt = 0;

          /* DROP THROUGH */

        case I2C_STATE_TRANSFER:
          if (msg->flags & I2C_M_READ)
            {
              while ((priv->rdcnt != msg->length) &&
                     (stat & I2C_STAT_RFE) == 0)
                {
                  msg->buffer[priv->rdcnt] =
                    getreg32(priv->base + LPC31_I2C_RX_OFFSET);
                  priv->rdcnt++;

                  stat = getreg32(priv->base + LPC31_I2C_STAT_OFFSET);
                }

              if (priv->rdcnt < msg->length)
                {
                  /* Not all data received,
                   * fill the Tx FIFO with more dummies
                   */

                  while ((priv->wrcnt != msg->length) &&
                         (stat & I2C_STAT_TFF) == 0)
                    {
                      if (priv->wrcnt + 1 == msg->length && priv->nmsg == 1)
                        {
                          putreg32(I2C_TX_STOP,
                                   priv->base + LPC31_I2C_TX_OFFSET);
                        }
                      else
                        {
                          putreg32(0, priv->base + LPC31_I2C_TX_OFFSET);
                        }

                      priv->wrcnt++;

                      stat = getreg32(priv->base + LPC31_I2C_STAT_OFFSET);
                    }

                  if (priv->wrcnt < msg->length)
                    {
                      /* Enable Tx FIFO not full and
                       * Rx Fifo Avail Interrupts
                       */

                      putreg32(ctrl | I2C_CTRL_TFFIE | I2C_CTRL_RFDAIE,
                               priv->base + LPC31_I2C_CTRL_OFFSET);
                    }
                  else
                    {
                      /* Enable Rx Fifo Avail Interrupts */

                      putreg32(ctrl | I2C_CTRL_RFDAIE,
                               priv->base + LPC31_I2C_CTRL_OFFSET);
                    }

                  goto out;
                }
            }
          else    /* WRITE */
            {
              while (!!(priv->wrcnt != msg->length) && (stat & I2C_STAT_TFF))
                {
                  if ((priv->wrcnt + 1) == msg->length && priv->nmsg == 1)
                    {
                      putreg32(I2C_TX_STOP | msg->buffer[priv->wrcnt],
                               priv->base + LPC31_I2C_TX_OFFSET);
                    }
                  else
                    {
                      putreg32(msg->buffer[priv->wrcnt],
                               priv->base + LPC31_I2C_TX_OFFSET);
                    }

                  priv->wrcnt++;

                  stat = getreg32(priv->base + LPC31_I2C_STAT_OFFSET);
                }

              if (priv->wrcnt < msg->length)
                {
                  /* Enable Tx Fifo not full Interrupt */

                  putreg32(ctrl | I2C_CTRL_TFFIE,
                           priv->base + LPC31_I2C_CTRL_OFFSET);
                  goto out;
                }
            }

          /* Transfer completed, move onto the next one */

          priv->state = I2C_STATE_START;

          if (--priv->nmsg == 0)
            {
              /* Final transfer, wait for Transmit Done Interrupt */

              putreg32(ctrl, priv->base + LPC31_I2C_CTRL_OFFSET);
              goto out;
            }

          priv->msgs++;
          break;
        }
    }

out:
  if (stat & I2C_STAT_TDI)
    {
      putreg32(I2C_STAT_TDI, priv->base + LPC31_I2C_STAT_OFFSET);

      /* You'd expect the NAI bit to be set when no acknowledge was
       * received - but it gets cleared whenever a write it done to
       * the TXFIFO - so we've gone and cleared it while priming the
       * rest of the transfer!
       */

      if ((stat = getreg32(priv->base + LPC31_I2C_TXFL_OFFSET)) != 0)
        {
          if (priv->nmsg == 0)
            {
              priv->nmsg++;
            }

          i2c_hwreset(priv);
        }

      priv->state = I2C_STATE_DONE;
      nxsem_post(&priv->wait);
    }
}

/****************************************************************************
 * Name: i2c_timeout
 *
 * Description:
 *   Watchdog timer for timeout of I2C operation
 *
 ****************************************************************************/

static void i2c_timeout(wdparm_t arg)
{
  struct lpc31_i2cdev_s *priv = (struct lpc31_i2cdev_s *)arg;

  irqstate_t flags = enter_critical_section();

  if (priv->state != I2C_STATE_DONE)
    {
      /* If there's data remaining in the TXFIFO, then ensure at least
       * one transfer has failed to complete.
       */

      if (getreg32(priv->base + LPC31_I2C_TXFL_OFFSET) != 0)
        {
          if (priv->nmsg == 0)
            {
              priv->nmsg++;
            }
        }

      /* Soft reset the USB controller */

      i2c_hwreset(priv);

      /* Mark the transfer as finished */

      priv->state = I2C_STATE_DONE;
      nxsem_post(&priv->wait);
    }

  leave_critical_section(flags);
}

/****************************************************************************
 * Name: i2c_hwreset
 *
 * Description:
 *   Perform a soft reset of the I2C controller
 *
 ****************************************************************************/

static void i2c_hwreset(struct lpc31_i2cdev_s *priv)
{
  putreg32(I2C_CTRL_RESET, priv->base + LPC31_I2C_CTRL_OFFSET);

  /* Wait for Reset to complete */

  while (!!(getreg32(priv->base + LPC31_I2C_CTRL_OFFSET) & I2C_CTRL_RESET))
      ;
}

/****************************************************************************
 * Name: i2c_transfer
 *
 * Description:
 *   Perform a sequence of I2C transfers
 *
 ****************************************************************************/

static int i2c_transfer(struct i2c_master_s *dev,
                        struct i2c_msg_s *msgs, int count)
{
  struct lpc31_i2cdev_s *priv = (struct lpc31_i2cdev_s *)dev;
  irqstate_t flags;
  int ret;

  /* Get exclusive access to the I2C bus */

  nxmutex_lock(&priv->lock);
  flags = enter_critical_section();

  /* Set up for the transfer */

  priv->state = I2C_STATE_START;
  priv->msgs  = msgs;
  priv->nmsg  = count;

  /* Configure the I2C frequency.
   * REVISIT: Note that the frequency is set only on the first message.
   * This could be extended to support different transfer frequencies for
   * each message segment.
   */

  i2c_setfrequency(priv, msgs->frequency);

  /* Start the transfer */

  i2c_progress(priv);

  /* Start a watchdog to timeout the transfer if the bus is locked up... */

  wd_start(&priv->timeout, I2C_TIMEOUT, i2c_timeout, (wdparm_t)priv);

  /* Wait for the transfer to complete */

  while (priv->state != I2C_STATE_DONE)
    {
      nxsem_wait(&priv->wait);
    }

  wd_cancel(&priv->timeout);
  ret = count - priv->nmsg;

  leave_critical_section(flags);
  nxmutex_unlock(&priv->lock);
  return ret;
}

/****************************************************************************
 * Name: i2c_reset
 *
 * Description:
 *   Perform an I2C bus reset in an attempt to break loose stuck I2C devices.
 *
 * Input Parameters:
 *   dev   - Device-specific state data
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno value on failure.
 *
 ****************************************************************************/

#ifdef CONFIG_I2C_RESET
static int i2c_reset(struct i2c_master_s *dev)
{
  return OK;
}
#endif /* CONFIG_I2C_RESET */

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: lpc31_i2cbus_initialize
 *
 * Description:
 *   Initialise an I2C device
 *
 ****************************************************************************/

struct i2c_master_s *lpc31_i2cbus_initialize(int port)
{
  struct lpc31_i2cdev_s *priv = &i2cdevices[port];

  priv->base  = (port == 0) ? LPC31_I2C0_VBASE : LPC31_I2C1_VBASE;
  priv->clkid = (port == 0) ? CLKID_I2C0PCLK   : CLKID_I2C1PCLK;
  priv->rstid = (port == 0) ? RESETID_I2C0RST  : RESETID_I2C1RST;
  priv->irqid = (port == 0) ? LPC31_IRQ_I2C0   : LPC31_IRQ_I2C1;

  /* Enable I2C system clocks */

  lpc31_enableclock(priv->clkid);

  /* Reset I2C blocks */

  lpc31_softreset(priv->rstid);

  /* Soft reset the device */

  i2c_hwreset(priv);

  /* Attach Interrupt Handler */

  irq_attach(priv->irqid, i2c_interrupt, priv);

  /* Enable Interrupt Handler */

  up_enable_irq(priv->irqid);

  /* Install our operations */

  priv->dev.ops = &lpc31_i2c_ops;
  return &priv->dev;
}

/****************************************************************************
 * Name: lpc31_i2cbus_uninitialize
 *
 * Description:
 *   Uninitialise an I2C device
 *
 ****************************************************************************/

int lpc31_i2cbus_uninitialize(struct i2c_master_s *dev)
{
  struct lpc31_i2cdev_s *priv = (struct lpc31_i2cdev_s *)dev;

  /* Disable All Interrupts, soft reset the device */

  i2c_hwreset(priv);

  /* Detach Interrupt Handler */

  irq_detach(priv->irqid);

  /* Reset I2C blocks */

  lpc31_softreset(priv->rstid);

  /* Disable I2C system clocks */

  lpc31_disableclock(priv->clkid);
  return OK;
}
