/****************************************************************************
 * drivers/timers/oneshot.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 <fcntl.h>
#include <assert.h>
#include <debug.h>

#include <nuttx/kmalloc.h>
#include <nuttx/signal.h>
#include <nuttx/fs/fs.h>
#include <nuttx/semaphore.h>
#include <nuttx/timers/oneshot.h>

#ifdef CONFIG_ONESHOT

/****************************************************************************
 * Private Type Definitions
 ****************************************************************************/

/* This structure describes the state of the upper half driver */

struct oneshot_dev_s
{
  FAR struct oneshot_lowerhalf_s *od_lower;    /* Lower-half driver state */
  sem_t od_exclsem;                            /* Supports mutual exclusion */

  /* Oneshot timer expiration notification information */

  struct sigevent od_event;                    /* Signal info */
  struct sigwork_s od_work;                    /* Signal work */
  pid_t od_pid;                                /* PID to be notified */
};

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

static int     oneshot_open(FAR struct file *filep);
static int     oneshot_close(FAR struct file *filep);
static ssize_t oneshot_read(FAR struct file *filep, FAR char *buffer,
                 size_t buflen);
static ssize_t oneshot_write(FAR struct file *filep, FAR const char *buffer,
                 size_t buflen);
static int     oneshot_ioctl(FAR struct file *filep, int cmd,
                 unsigned long arg);

static void    oneshot_callback(FAR struct oneshot_lowerhalf_s *lower,
                 FAR void *arg);

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

static const struct file_operations g_oneshot_ops =
{
  oneshot_open,  /* open */
  oneshot_close, /* close */
  oneshot_read,  /* read */
  oneshot_write, /* write */
  NULL,          /* seek */
  oneshot_ioctl, /* ioctl */
  NULL           /* poll */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  , NULL         /* unlink */
#endif
};

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

/****************************************************************************
 * Name: oneshot_callback
 ****************************************************************************/

static void oneshot_callback(FAR struct oneshot_lowerhalf_s *lower,
                             FAR void *arg)
{
  FAR struct oneshot_dev_s *priv = (FAR struct oneshot_dev_s *)arg;

  DEBUGASSERT(priv != NULL);

  /* Signal the waiter.. if there is one */

  nxsig_notification(priv->od_pid, &priv->od_event,
                     SI_QUEUE, &priv->od_work);
}

/****************************************************************************
 * Name: oneshot_open
 *
 * Description:
 *   This function is called whenever the PWM device is opened.
 *
 ****************************************************************************/

static int oneshot_open(FAR struct file *filep)
{
  tmrinfo("Opening...\n");
  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
  return OK;
}

/****************************************************************************
 * Name: oneshot_close
 *
 * Description:
 *   This function is called when the PWM device is closed.
 *
 ****************************************************************************/

static int oneshot_close(FAR struct file *filep)
{
  tmrinfo("Closing...\n");
  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
  return OK;
}

/****************************************************************************
 * Name: oneshot_read
 *
 * Description:
 *   A dummy read method.  This is provided only to satsify the VFS layer.
 *
 ****************************************************************************/

static ssize_t oneshot_read(FAR struct file *filep, FAR char *buffer,
                            size_t buflen)
{
  /* Return zero -- usually meaning end-of-file */

  tmrinfo("buflen=%ld\n", (unsigned long)buflen);
  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
  return 0;
}

/****************************************************************************
 * Name: oneshot_write
 *
 * Description:
 *   A dummy write method.  This is provided only to satsify the VFS layer.
 *
 ****************************************************************************/

static ssize_t oneshot_write(FAR struct file *filep, FAR const char *buffer,
                             size_t buflen)
{
  /* Return a failure */

  tmrinfo("buflen=%ld\n", (unsigned long)buflen);
  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
  return -EPERM;
}

/****************************************************************************
 * Name: oneshot_ioctl
 *
 * Description:
 *   The standard ioctl method.  This is where ALL of the PWM work is done.
 *
 ****************************************************************************/

static int oneshot_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct inode *inode;
  FAR struct oneshot_dev_s *priv;
  int ret;

  tmrinfo("cmd=%d arg=%08lx\n", cmd, (unsigned long)arg);

  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
  inode = filep->f_inode;
  priv  = (FAR struct oneshot_dev_s *)inode->i_private;
  DEBUGASSERT(priv != NULL);

  /* Get exclusive access to the device structures */

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

  /* Handle oneshot timer ioctl commands */

  switch (cmd)
    {
      /* OSIOC_MAXDELAY - Return the maximum delay that can be supported
       *                  by this timer.
       *                  Argument: A reference to a struct timespec in
       *                  which the maximum time will be returned.
       */

      case OSIOC_MAXDELAY:
        {
          FAR struct timespec *ts = (FAR struct timespec *)((uintptr_t)arg);
          DEBUGASSERT(ts != NULL);

          ret = ONESHOT_MAX_DELAY(priv->od_lower, ts);
        }
        break;

      /* OSIOC_START - Start the oneshot timer
       *               Argument: A reference to struct oneshot_start_s
       */

      case OSIOC_START:
        {
          FAR struct oneshot_start_s *start;
          pid_t pid;

          start = (FAR struct oneshot_start_s *)((uintptr_t)arg);
          DEBUGASSERT(start != NULL);

          /* Save signaling information */

          priv->od_event = start->event;

          pid = start->pid;
          if (pid == 0)
            {
              pid = getpid();
            }

          priv->od_pid = pid;

          /* Start the oneshot timer */

          ret = ONESHOT_START(priv->od_lower, oneshot_callback, priv,
                              &start->ts);
        }
        break;

      /* OSIOC_CANCEL - Stop the timer
       *                Argument: A reference to a struct timespec in
       *                which the time remaining will be returned.
       */

      case OSIOC_CANCEL:
        {
          FAR struct timespec *ts = (FAR struct timespec *)((uintptr_t)arg);

          /* Cancel the oneshot timer */

          ret = ONESHOT_CANCEL(priv->od_lower, ts);
          nxsig_cancel_notification(&priv->od_work);
        }
        break;

      /* OSIOC_CURRENT - Get the current time
       *                 Argument: A reference to a struct timespec in
       *                 which the current time will be returned.
       */

      case OSIOC_CURRENT:
        {
          FAR struct timespec *ts = (FAR struct timespec *)((uintptr_t)arg);

          /* Get the current time */

          ret = ONESHOT_CURRENT(priv->od_lower, ts);
        }
        break;

      default:
        {
          tmrerr("ERROR: Unrecognized cmd: %d arg: %ld\n", cmd, arg);
          ret = -ENOTTY;
        }
        break;
    }

  nxsem_post(&priv->od_exclsem);
  return ret;
}

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

/****************************************************************************
 * Name: oneshot_register
 *
 * Description:
 *   Register the oneshot device as 'devpath'
 *
 * Input Parameters:
 *   devpath - The full path to the driver to register. E.g., "/dev/oneshot0"
 *   lower - An instance of the lower half interface
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno value on failure.  The following
 *   possible error values may be returned (most are returned by
 *   register_driver()):
 *
 *   EINVAL - 'path' is invalid for this operation
 *   EEXIST - An inode already exists at 'path'
 *   ENOMEM - Failed to allocate in-memory resources for the operation
 *
 ****************************************************************************/

int oneshot_register(FAR const char *devname,
                     FAR struct oneshot_lowerhalf_s *lower)
{
  FAR struct oneshot_dev_s *priv;
  int ret;

  tmrinfo("devname=%s lower=%p\n", devname, lower);
  DEBUGASSERT(devname != NULL && lower != NULL);

  /* Allocate a new oneshot timer driver instance */

  priv = (FAR struct oneshot_dev_s *)
    kmm_zalloc(sizeof(struct oneshot_dev_s));

  if (!priv)
    {
      tmrerr("ERROR: Failed to allocate device structure\n");
      return -ENOMEM;
    }

  /* Initialize the new oneshot timer driver instance */

  priv->od_lower = lower;
  nxsem_init(&priv->od_exclsem, 0, 1);

  /* And register the oneshot timer driver */

  ret = register_driver(devname, &g_oneshot_ops, 0666, priv);
  if (ret < 0)
    {
      tmrerr("ERROR: register_driver failed: %d\n", ret);
      nxsem_destroy(&priv->od_exclsem);
      kmm_free(priv);
    }

  return ret;
}

#endif /* CONFIG_ONESHOT */