/****************************************************************************
 * drivers/i2c/i2c_slave_driver.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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 <fcntl.h>
#include <poll.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>

#include <nuttx/fs/fs.h>
#include <nuttx/spinlock.h>
#include <nuttx/kmalloc.h>
#include <nuttx/i2c/i2c_slave.h>

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

#define DEVNAME_FMT    "/dev/i2cslv%d"
#define DEVNAME_FMTLEN (11 + 3 + 1)

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

static int i2c_slave_open(FAR struct file *filep);
static int i2c_slave_close(FAR struct file *filep);
static ssize_t i2c_slave_read(FAR struct file *filep, FAR char *buffer,
                              size_t buflen);
static ssize_t i2c_slave_write(FAR struct file *filep,
                               FAR const char *buffer, size_t buflen);
static int i2c_slave_poll(FAR struct file *filep, FAR struct pollfd *fds,
                          bool setup);

#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int i2c_slave_unlink(FAR struct inode *inode);
#endif

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

struct i2c_slave_driver_s
{
  /* Slave private data */

  FAR struct i2c_slave_s *dev;

  /* Slave send buffer */

  uint8_t write_buffer[CONFIG_I2C_SLAVE_WRITEBUFSIZE];

  /* Slave receive buffer */

  uint8_t read_buffer[CONFIG_I2C_SLAVE_READBUFSIZE];

  /* Slave receive buffer length */

  size_t read_length;

  /* Read buffer index */

  size_t read_index;

  /* Wait for transfer to complete */

  sem_t wait;

  /* I2C Slave write flag */

  bool writeable;

  /* Mutual exclusion */

  spinlock_t lock;

  /* The poll waiter */

  FAR struct pollfd *fds[CONFIG_I2C_SLAVE_NPOLLWAITERS];

  /* Number of open references */

  int16_t crefs;

  /* I2C Slave address */

  int addr;

  /* The number of address bits provided (7 or 10) */

  int nbits;

#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  bool unlinked; /* Indicates if the driver has been unlinked */
#endif
};

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

static const struct file_operations g_i2cslavefops =
{
  i2c_slave_open,     /* open */
  i2c_slave_close,    /* close */
  i2c_slave_read,     /* read */
  i2c_slave_write,    /* write */
  NULL,               /* seek */
  NULL,               /* ioctl */
  NULL,               /* mmap */
  NULL,               /* truncate */
  i2c_slave_poll,     /* poll */
  NULL,               /* readv */
  NULL                /* writev */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  , i2c_slave_unlink  /* unlink */
#endif
};

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

/****************************************************************************
 * Name: i2c_slave_open
 *
 * Description:
 *   This function is called whenever the I2C Slave device is opened.
 *
 * Input Parameters:
 *   filep  - File structure instance
 *
 * Returned Value:
 *   Zero (OK) is returned on success. A negated errno value is returned on
 *   any failure to indicate the nature of the failure.
 *
 ****************************************************************************/

static int i2c_slave_open(FAR struct file *filep)
{
  FAR struct i2c_slave_driver_s *priv;
  irqstate_t flags;
  int ret;

  DEBUGASSERT(filep->f_inode->i_private != NULL);

  /* Get our private data structure */

  priv = filep->f_inode->i_private;

  /* Get exclusive access to the I2C Slave driver state structure */

  flags = spin_lock_irqsave(&priv->lock);

  /* I2c slave initialize */

  if (priv->dev->ops->setup != NULL && priv->crefs == 0)
    {
      ret = I2CS_SETUP(priv->dev);
      if (ret < 0)
        {
          goto out;
        }
    }

  /* Set i2c slave address */

  ret = I2CS_SETOWNADDRESS(priv->dev, priv->addr, priv->nbits);
  if (ret < 0)
    {
      if (priv->dev->ops->shutdown != NULL)
        {
          ret = I2CS_SHUTDOWN(priv->dev);
        }
    }

  /* Increment the count of open references on the driver */

  priv->crefs++;
  DEBUGASSERT(priv->crefs > 0);

out:
  spin_unlock_irqrestore(&priv->lock, flags);
  return ret;
}

/****************************************************************************
 * Name: i2c_slave_close
 *
 * Description:
 *   This routine is called when the I2C Slave device is closed.
 *
 * Input Parameters:
 *   filep  - File structure instance
 *
 * Returned Value:
 *   Zero (OK) is returned on success; A negated errno value is returned on
 *   any failure to indicate the nature of the failure.
 *
 ****************************************************************************/

static int i2c_slave_close(FAR struct file *filep)
{
  FAR struct i2c_slave_driver_s *priv;
  irqstate_t flags;
  int ret = OK;

  DEBUGASSERT(filep->f_inode->i_private != NULL);

  /* Get our private data structure */

  priv = filep->f_inode->i_private;

  /* Get exclusive access to the I2C slave driver state structure */

  flags = spin_lock_irqsave(&priv->lock);

  /* I2c slave uninitialize */

  if (priv->dev->ops->shutdown != NULL && priv->crefs == 1)
    {
      ret = I2CS_SHUTDOWN(priv->dev);
      if (ret < 0)
        {
          goto out;
        }
    }

  /* Decrement the count of open references on the driver */

  DEBUGASSERT(priv->crefs > 0);
  priv->crefs--;
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  if (priv->crefs <= 0 && priv->unlinked)
    {
      spin_unlock_irqrestore(&priv->lock, flags);
      kmm_free(priv);
      filep->f_inode->i_private = NULL;
      return OK;
    }
#endif

out:
  spin_unlock_irqrestore(&priv->lock, flags);
  return ret;
}

/****************************************************************************
 * Name: i2c_slave_read
 *
 * Description:
 *   This routine is called when the application requires to read the data
 *   received by the I2C Slave device.
 *
 * Input Parameters:
 *   filep  - File structure instance
 *   buffer - User-provided to save the data
 *   buflen - The maximum size of the user-provided buffer
 *
 * Returned Value:
 *   The positive non-zero number of bytes read on success, 0 on if an
 *   end-of-file condition, or a negated errno value on any failure.
 *
 ****************************************************************************/

static ssize_t i2c_slave_read(FAR struct file *filep, FAR char *buffer,
                              size_t buflen)
{
  FAR struct i2c_slave_driver_s *priv;
  irqstate_t flags;
  int ret;

  DEBUGASSERT(filep->f_inode->i_private != NULL);

  /* Get our private data structure */

  priv = filep->f_inode->i_private;

  /* Get exclusive access to the I2C Slave driver state structure */

  flags = spin_lock_irqsave(&priv->lock);

  while (priv->read_length == 0)
    {
      spin_unlock_irqrestore(&priv->lock, flags);
      if (filep->f_oflags & O_NONBLOCK)
        {
          return -EAGAIN;
        }

      ret = nxsem_wait(&priv->wait);
      if (ret < 0)
        {
          return ret;
        }

      flags = spin_lock_irqsave(&priv->lock);
    }

  buflen = MIN(buflen, priv->read_length);
  memcpy(buffer, priv->read_buffer + priv->read_index, buflen);
  priv->read_index += buflen;
  priv->read_length -= buflen;
  spin_unlock_irqrestore(&priv->lock, flags);
  return buflen;
}

/****************************************************************************
 * Name: i2c_slave_write
 *
 * Description:
 *   This routine is called when the application needs to enqueue data to be
 *   transferred at the next leading clock edge of the I2C Slave controller.
 *
 * Input Parameters:
 *   filep  - Instance of struct file to use with the write
 *   buffer - Data to write
 *   buflen - Length of data to write in bytes
 *
 * Returned Value:
 *   On success, the number of bytes written are returned (zero indicates
 *   nothing was written). On any failure, a negated errno value is returned.
 *
 ****************************************************************************/

static ssize_t i2c_slave_write(FAR struct file *filep,
                               FAR const char *buffer, size_t buflen)
{
  FAR struct i2c_slave_driver_s *priv;
  size_t write_bytes;
  irqstate_t flags;
  int ret;

  DEBUGASSERT(filep->f_inode->i_private != NULL);

  /* Get our private data structure */

  priv = filep->f_inode->i_private;

  /* Get exclusive access to the I2C Slave driver state structure */

  flags = spin_lock_irqsave(&priv->lock);

  write_bytes = MIN(buflen, CONFIG_I2C_SLAVE_WRITEBUFSIZE);
  memcpy(priv->write_buffer, buffer, write_bytes);
  ret = I2CS_WRITE(priv->dev, priv->write_buffer, write_bytes);
  if (ret >= 0)
    {
      priv->writeable = false;
    }

  spin_unlock_irqrestore(&priv->lock, flags);
  return ret < 0 ? ret : write_bytes;
}

/****************************************************************************
 * Name: i2c_slave_poll
 ****************************************************************************/

static int i2c_slave_poll(FAR struct file *filep, FAR struct pollfd *fds,
                          bool setup)
{
  FAR struct i2c_slave_driver_s *priv;
  irqstate_t flags;
  int ret = OK;
  int i;

  DEBUGASSERT(filep->f_inode->i_private != NULL);

  /* Get our private data structure */

  priv = filep->f_inode->i_private;

  /* Get exclusive access to the I2C Slave driver state structure */

  flags = spin_lock_irqsave(&priv->lock);
  if (setup)
    {
      pollevent_t eventset = 0;

      for (i = 0; i < CONFIG_I2C_SLAVE_NPOLLWAITERS; i++)
        {
          if (!priv->fds[i])
            {
              priv->fds[i] = fds;
              fds->priv    = &priv->fds[i];
              break;
            }
        }

      if (i == CONFIG_I2C_SLAVE_NPOLLWAITERS)
        {
          ret = -EBUSY;
          goto out;
        }

      if (priv->read_length > 0)
        {
          eventset |= POLLIN;
        }

      if (priv->writeable)
        {
          eventset |= POLLOUT;
        }

      poll_notify(priv->fds, CONFIG_I2C_SLAVE_NPOLLWAITERS, eventset);
    }
  else if (fds->priv != NULL)
    {
      FAR struct pollfd **slot = fds->priv;
      *slot     = NULL;
      fds->priv = NULL;
    }

out:
  spin_unlock_irqrestore(&priv->lock, flags);
  return ret;
}

/****************************************************************************
 * Name: i2c_slave_unlink
 *
 * Description:
 *   This routine is called when the I2C Slave device is unlinked.
 *
 * Input Parameters:
 *   inode  - The inode associated with the I2C Slave device
 *
 * Returned Value:
 *   Zero is returned on success; a negated value is returned on any failure.
 *
 ****************************************************************************/

#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int i2c_slave_unlink(FAR struct inode *inode)
{
  FAR struct i2c_slave_driver_s *priv;
  irqstate_t flags;

  DEBUGASSERT(inode->i_private != NULL);

  /* Get our private data structure */

  priv = inode->i_private;

  /* Get exclusive access to the I2C Slave driver state structure */

  flags = spin_lock_irqsave(&priv->lock);

  /* Are there open references to the driver data structure? */

  if (priv->crefs <= 0)
    {
      spin_unlock_irqrestore(&priv->lock, flags);
      kmm_free(priv);
      inode->i_private = NULL;
      return OK;
    }

  /* No... just mark the driver as unlinked and free the resources when the
   * last client closes their reference to the driver.
   */

  priv->unlinked = true;
  spin_unlock_irqrestore(&priv->lock, flags);
  return OK;
}
#endif

static int i2c_slave_callback(FAR void *arg, i2c_slave_complete_t status,
                              size_t length)
{
  FAR struct i2c_slave_driver_s *priv = arg;
  pollevent_t events;
  irqstate_t flags;
  int semcount;

  /* Get exclusive access to the I2C Slave driver state structure */

  flags = spin_lock_irqsave(&priv->lock);

  if (status == I2CS_RX_COMPLETE)
    {
      events = POLLIN;
      priv->read_index = 0;
      priv->read_length = length;

      if (nxsem_get_value(&priv->wait, &semcount) >= 0)
        {
          while (semcount++ <= 0)
            {
              nxsem_post(&priv->wait);
            }
        }
    }
  else
    {
      events = POLLOUT;
      priv->writeable = true;
    }

  spin_unlock_irqrestore(&priv->lock, flags);
  poll_notify(priv->fds, CONFIG_I2C_SLAVE_NPOLLWAITERS, events);
  return OK;
}

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

/****************************************************************************
 * Name: i2c_slave_register
 *
 * Description:
 *   Register the I2C Slave character device driver as 'devpath'.
 *
 * Input Parameters:
 *   dev   - An instance of the I2C Slave interface to use to communicate
 *           with the I2C Slave device
 *   bus   - The I2C Slave bus number. This will be used as the I2C device
 *           minor number. The I2C Slave character device will be
 *           registered as /dev/i2cslvN where N is the minor number
 *   addr  - I2C Slave address
 *   nbits - The number of address bits provided (7 or 10)
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno value on failure.
 *
 ****************************************************************************/

int i2c_slave_register(FAR struct i2c_slave_s *dev, int bus, int addr,
                       int nbits)
{
  FAR struct i2c_slave_driver_s *priv;
  char devname[DEVNAME_FMTLEN];
  int ret;

  /* Sanity check */

  DEBUGASSERT(dev != NULL && (unsigned int)bus < 1000);

  priv = kmm_zalloc(sizeof(struct i2c_slave_driver_s));
  if (priv == NULL)
    {
      return -ENOMEM;
    }

  snprintf(devname, sizeof(devname), DEVNAME_FMT, bus);
  ret = register_driver(devname, &g_i2cslavefops, 0666, priv);
  if (ret < 0)
    {
      kmm_free(priv);
      return ret;
    }

  nxsem_init(&priv->wait, 0, 0);
  spin_lock_init(&priv->lock);
  priv->dev = dev;
  priv->addr = addr;
  priv->nbits = nbits;
  priv->writeable = true;

  ret = I2CS_READ(priv->dev, priv->read_buffer,
                  CONFIG_I2C_SLAVE_READBUFSIZE);
  if (ret < 0)
    {
      goto out;
    }

  ret = I2CS_REGISTERCALLBACK(priv->dev, i2c_slave_callback, priv);
  if (ret >= 0)
    {
      return OK;
    }

out:
  nxsem_destroy(&priv->wait);
  unregister_driver(devname);
  kmm_free(priv);
  return ret;
}