/****************************************************************************
 * drivers/i2c/i2c_bitbang.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 <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/spinlock.h>
#include <nuttx/semaphore.h>
#include <nuttx/kmalloc.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/i2c/i2c_bitbang.h>

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

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct i2c_bitbang_dev_s
{
  struct i2c_master_s i2c;
  struct i2c_bitbang_lower_dev_s *lower;

#ifndef CONFIG_I2C_BITBANG_NO_DELAY
  int32_t delay;
#endif
};

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

static int i2c_bitbang_transfer(FAR struct i2c_master_s *dev,
                                FAR struct i2c_msg_s *msgs, int count);

static int i2c_bitbang_set_scl(FAR struct i2c_bitbang_dev_s *dev,
                               bool high, bool nodelay);
static void i2c_bitbang_set_sda(FAR struct i2c_bitbang_dev_s *dev,
                                bool high);

static int i2c_bitbang_wait_ack(FAR struct i2c_bitbang_dev_s *dev);
static void i2c_bitbang_send(FAR struct i2c_bitbang_dev_s *dev,
                             uint8_t data);

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

static const struct i2c_ops_s g_i2c_ops =
{
  i2c_bitbang_transfer  /* transfer */
#ifdef CONFIG_I2C_RESET
  , NULL                /* reset */
#endif
};

/****************************************************************************
 * Inline Functions
 ****************************************************************************/

inline static bool i2c_bitbang_get_sda(FAR struct i2c_bitbang_dev_s *dev)
{
  return dev->lower->ops->get_sda(dev->lower);
}

#ifdef CONFIG_I2C_BITBANG_CLOCK_STRETCHING
inline static bool i2c_bitbang_get_scl(FAR struct i2c_bitbang_dev_s *dev)
{
  return dev->lower->ops->get_scl(dev->lower);
}
#endif

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

/****************************************************************************
 * Name: i2c_bitbang_transfer
 ****************************************************************************/

static int i2c_bitbang_transfer(FAR struct i2c_master_s *dev,
                                FAR struct i2c_msg_s *msgs, int count)
{
  FAR struct i2c_bitbang_dev_s *priv = (FAR struct i2c_bitbang_dev_s *)dev;
  int ret = OK;
  int i;
  irqstate_t flags;

  /* Lock to enforce timings */

  flags = spin_lock_irqsave(NULL);

  for (i = 0; i < count; i++)
    {
      uint8_t addr;
      FAR struct i2c_msg_s *msg = &msgs[i];

#ifndef CONFIG_I2C_BITBANG_NO_DELAY
      /* Compute delay from frequency */

      priv->delay = (USEC_PER_SEC / (2 * msg->frequency)) -
                    CONFIG_I2C_BITBANG_GPIO_OVERHEAD;

      if (priv->delay < 0)
        {
          priv->delay = 0;
        }
#endif

      /* If this is the start of transfer or we're changing sending direction
       * from last transfer, send START
       */

      if (i == 0 ||
          (msgs[i - 1].flags & I2C_M_READ) != (msgs[i].flags & I2C_M_READ))
        {
          /* Send start bit */

          i2c_bitbang_set_scl(priv, true, false);
          i2c_bitbang_set_sda(priv, false);
          i2c_bitbang_set_scl(priv, false, false);

          /* Send the address */

          addr = (msg->flags & I2C_M_READ ? I2C_READADDR8(msg->addr) :
                                            I2C_WRITEADDR8(msg->addr));

          i2c_bitbang_send(priv, addr);

          /* Wait for ACK */

          ret = i2c_bitbang_wait_ack(priv);

          if (ret < 0)
            {
              goto out;
            }
        }

      i2c_bitbang_set_scl(priv, false, false);

      if (msg->flags & I2C_M_READ)
        {
          int j;
          int k;

          for (j = 0; j < msg->length; j++)
            {
              uint8_t data = 0;

              i2c_bitbang_set_sda(priv, true);

              msg->buffer[j] = 0;

              for (k = 0; k < 8; k++)
                {
                  i2c_bitbang_set_scl(priv, true, false);
                  data |= (i2c_bitbang_get_sda(priv) & 1) << (7 - k);
                  i2c_bitbang_set_scl(priv, false, false);
                }

              msg->buffer[j] = data;

              if (j < msg->length - 1)
                {
                  /* Send ACK */

                  i2c_bitbang_set_sda(priv, false);
                  i2c_bitbang_set_scl(priv, true, false);
                  i2c_bitbang_set_scl(priv, false, false);
                }
              else
                {
                  /* On the last byte send NAK */

                  i2c_bitbang_set_sda(priv, true);
                  i2c_bitbang_set_scl(priv, true, false);
                  i2c_bitbang_set_scl(priv, false, false);
                }
            }
        }
      else
        {
          int j;

          for (j = 0; j < msg->length; j++)
            {
              /* Send the data */

              i2c_bitbang_send(priv, msg->buffer[j]);

              ret = i2c_bitbang_wait_ack(priv);

              if (ret < 0)
                {
                  goto out;
                }

              i2c_bitbang_set_scl(priv, false, false);
            }
        }

      if (!(msg->flags & I2C_M_NOSTOP))
        {
          /* Send stop */

          i2c_bitbang_set_sda(priv, false);
          i2c_bitbang_set_scl(priv, true, true);
          i2c_bitbang_set_sda(priv, true);
        }
    }

out:

  /* Ensure lines are released */

  i2c_bitbang_set_scl(priv, true, false);
  i2c_bitbang_set_sda(priv, true);

  spin_unlock_irqrestore(NULL, flags);

  return ret;
}

/****************************************************************************
 * Name: i2c_bitbang_wait_ack
 ****************************************************************************/

static int i2c_bitbang_wait_ack(FAR struct i2c_bitbang_dev_s *priv)
{
  int ret = OK;
  int i;

  /* Wait for ACK */

  i2c_bitbang_set_sda(priv, true);
  i2c_bitbang_set_scl(priv, true, true);

  for (i = 0; i2c_bitbang_get_sda(priv) &&
              i < CONFIG_I2C_BITBANG_TIMEOUT; i++)
    {
      up_udelay(1);
    }

  if (i == CONFIG_I2C_BITBANG_TIMEOUT)
    {
      ret = -EIO;
    }
#ifndef CONFIG_I2C_BITBANG_NO_DELAY
  else
    {
      int remaining = priv->delay - i;

      if (remaining > 0)
        {
          up_udelay(remaining);
        }
    }
#endif

  return ret;
}

/****************************************************************************
 * Name: i2c_bitbang_send
 ****************************************************************************/

static void i2c_bitbang_send(FAR struct i2c_bitbang_dev_s *priv,
                             uint8_t data)
{
  uint8_t bit = 1u << 7;

  while (bit)
    {
      i2c_bitbang_set_sda(priv, !!(data & bit));
      i2c_bitbang_set_scl(priv, true, false);
      i2c_bitbang_set_scl(priv, false, false);

      bit >>= 1;
    }
}

/****************************************************************************
 * Name: i2c_bitbang_set_sda
 ****************************************************************************/

static void i2c_bitbang_set_sda(FAR struct i2c_bitbang_dev_s *dev, bool high)
{
  dev->lower->ops->set_sda(dev->lower, high);
}

/****************************************************************************
 * Name: i2c_bitbang_set_scl
 ****************************************************************************/

static int i2c_bitbang_set_scl(FAR struct i2c_bitbang_dev_s *dev, bool high,
                               bool nodelay)
{
  dev->lower->ops->set_scl(dev->lower, high);

#ifndef CONFIG_I2C_BITBANG_NO_DELAY
  if (!nodelay && dev->delay)
    {
      up_udelay(dev->delay);
    }
#endif

#ifdef CONFIG_I2C_BITBANG_CLOCK_STRETCHING
  /* Allow for clock stretching */

  if (high)
    {
      int i;

      for (i = 0; !i2c_bitbang_get_scl(dev) &&
                  i < CONFIG_I2C_BITBANG_TIMEOUT; i++)
        {
          up_udelay(1);

          if (i == CONFIG_I2C_BITBANG_TIMEOUT)
            {
              return -ETIMEDOUT;
            }
        }
    }
#endif

  return OK;
}

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

/****************************************************************************
 * Name: i2c_bitbang_initialize
 *
 * Description:
 *   Initialize a bitbang I2C device instance
 *
 * Input Parameters:
 *   lower  - Lower half of driver
 *
 * Returned Value:
 *   Pointer to a the I2C instance
 *
 ****************************************************************************/

FAR struct i2c_master_s *i2c_bitbang_initialize(
    FAR struct i2c_bitbang_lower_dev_s *lower)
{
  FAR struct i2c_bitbang_dev_s *dev;

  DEBUGASSERT(lower && lower->ops);

  dev = kmm_zalloc(sizeof(*dev));

  if (!dev)
    {
      return NULL;
    }

  dev->i2c.ops = &g_i2c_ops;
  dev->lower = lower;
  dev->lower->ops->initialize(dev->lower);

  return &dev->i2c;
}