/****************************************************************************
 * arch/arm/src/sama5/sam_tsd.c
 *
 *   Copyright (C) 2013 Gregory Nutt. All rights reserved.
 *   Authors: Gregory Nutt <gnutt@nuttx.org>
 *
 * References:
 *
 *   SAMA5D3 Series Data Sheet
 *   Atmel NoOS sample code.
 *
 * The Atmel sample code has a BSD compatible license that requires this
 * copyright notice:
 *
 *   Copyright (c) 2011, Atmel Corporation
 *
 * 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 names NuttX nor Atmel 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 OWNER 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.
 *
 ****************************************************************************/

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

#include <nuttx/config.h>

#include <sys/types.h>

#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <semaphore.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>

#include <nuttx/arch.h>
#include <nuttx/wdog.h>
#include <nuttx/wqueue.h>
#include <nuttx/clock.h>
#include <nuttx/input/touchscreen.h>

#include <arch/board/board.h>

#include "up_arch.h"
#include "chip/sam_adc.h"
#include "sam_adc.h"
#include "sam_tsd.h"

#if defined(CONFIG_SAMA5_ADC) && defined(CONFIG_SAMA5_TSD)

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* Driver support ***********************************************************/
/* This format is used to construct the /dev/input[n] device driver path.  It
 * defined here so that it will be used consistently in all places.
 */

#define DEV_FORMAT          "/dev/input%d"
#define DEV_NAMELEN         16

/* Poll the pen position while the pen is down at this rate (50MS): */

#define TSD_WDOG_DELAY      MSEC2TICK(50)

/* This is a value for the threshold that guantees a big difference on the
 * first pendown (but can't overflow).
 */

#define INVALID_THRESHOLD 0x1000

/* Data read bit definitions */

#ifdef CONFIG_SAMA5_TSD_4WIRE
#  define TSD_ALLREADY      (ADC_INT_XRDY | ADC_INT_YRDY | ADC_INT_PRDY)
#else
#  define TSD_ALLREADY      (ADC_INT_XRDY | ADC_INT_YRDY)
#endif

/* Pen sample state bit sets */

#ifdef CONFIG_SAMA5_TSD_4WIRE
#  define TSD_PENUP_VALID   (TOUCH_UP | TOUCH_ID_VALID | TOUCH_POS_VALID | \
                             TOUCH_PRESSURE_VALID)
#  define TSD_PENUP_INVALID (TOUCH_UP | TOUCH_ID_VALID)
#  define TSD_PENDOWN       (TOUCH_DOWN | TOUCH_ID_VALID | TOUCH_POS_VALID | \
                             TOUCH_PRESSURE_VALID)
#  define TSD_PENMOVE       (TOUCH_MOVE | TOUCH_ID_VALID | TOUCH_POS_VALID | \
                             TOUCH_PRESSURE_VALID)
#else
#  define TSD_PENUP_VALID   (TOUCH_UP | TOUCH_ID_VALID | TOUCH_POS_VALID)
#  define TSD_PENUP_INVALID (TOUCH_UP | TOUCH_ID_VALID)
#  define TSD_PENDOWN       (TOUCH_DOWN | TOUCH_ID_VALID | TOUCH_POS_VALID)
#  define TSD_PENMOVE       (TOUCH_MOVE | TOUCH_ID_VALID | TOUCH_POS_VALID)
#endif

/* Ever-present MIN and MAX macros */

#ifndef MIN
#  define MIN(a,b) (((a) < (b)) ? (a) : (b))
#endif

#ifndef MAX
#  define MAX(a,b) (((a) > (b)) ? (a) : (b))
#endif

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

/* This describes the state of one contact */

enum sam_contact_e
{
  CONTACT_NONE = 0,             /* No contact */
  CONTACT_DOWN,                 /* First contact */
  CONTACT_MOVE,                 /* Same contact, possibly different position */
  CONTACT_UP,                   /* Contact lost */
};

/* This structure describes the results of one touchscreen sample */

struct sam_sample_s
{
  uint8_t id;                   /* Sampled touch point ID */
  uint8_t contact;              /* Contact state (see enum sam_contact_e) */
  bool valid;                   /* True: x,y,p contain valid, sampled data */
  uint16_t x;                   /* Measured X position */
  uint16_t y;                   /* Measured Y position */
#ifdef CONFIG_SAMA5_TSD_4WIRE
  uint16_t p;                   /* Measured pressure */
#endif
};

/* This structure describes the state of one touchscreen driver instance */

struct sam_tsd_s
{
  uint8_t nwaiters;             /* Number of threads waiting for touchscreen data */
  uint8_t id;                   /* Current touch point ID */
  uint8_t valid;                /* Data ready bit set */
  uint8_t crefs;                /* The number of times the device has been opened */
  volatile bool penchange;      /* An unreported event is buffered */
  uint32_t threshx;             /* Thresholding X value */
  uint32_t threshy;             /* Thresholding Y value */
  sem_t waitsem;                /* Used to wait for the availability of data */

  struct sam_adc_s *adc;        /* ADC device handle */
  struct work_s work;           /* Supports the interrupt handling "bottom half" */
  struct sam_sample_s sample;   /* Last sampled touch point data */
  WDOG_ID wdog;                 /* Poll the position while the pen is down */

  /* The following is a list if poll structures of threads waiting for
   * driver events. The 'struct pollfd' reference for each open is also
   * retained in the f_priv field of the 'struct file'.
   */

#ifndef CONFIG_DISABLE_POLL
  struct pollfd *fds[CONFIG_SAMA5_TSD_NPOLLWAITERS];
#endif
};

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

/* Interrupt bottom half logic and data sampling */

static void sam_tsd_notify(struct sam_tsd_s *priv);
static int  sam_tsd_sample(struct sam_tsd_s *priv, struct sam_sample_s *sample);
static int  sam_tsd_waitsample(struct sam_tsd_s *priv,
              struct sam_sample_s *sample);
static void sam_tsd_bottomhalf(void *arg);
static int  sam_tsd_schedule(struct sam_tsd_s *priv);
static void sam_tsd_expiry(int argc, uint32_t arg1, ...);

/* Character driver methods */

static int  sam_tsd_open(struct file *filep);
static int  sam_tsd_close(struct file *filep);
static ssize_t sam_tsd_read(struct file *filep, char *buffer, size_t len);
static int  sam_tsd_ioctl(struct file *filep, int cmd, unsigned long arg);
#ifndef CONFIG_DISABLE_POLL
static int  sam_tsd_poll(struct file *filep, struct pollfd *fds, bool setup);
#endif

/* Initialization and configuration */

static void sam_tsd_startuptime(struct sam_tsd_s *priv, uint32_t time);
static void sam_tsd_tracking(struct sam_tsd_s *priv, uint32_t time);
static void sam_tsd_trigperiod(struct sam_tsd_s *priv, uint32_t period);
static void sam_tsd_debounce(struct sam_tsd_s *priv, uint32_t time);
static void sam_tsd_initialize(struct sam_tsd_s *priv);
static void sam_tsd_uninitialize(struct sam_tsd_s *priv);

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

/* This the vtable that supports the character driver interface */

static const struct file_operations g_tsdops =
{
  sam_tsd_open,    /* open */
  sam_tsd_close,   /* close */
  sam_tsd_read,    /* read */
  0,               /* write */
  0,               /* seek */
  sam_tsd_ioctl    /* ioctl */
#ifndef CONFIG_DISABLE_POLL
  , sam_tsd_poll   /* poll */
#endif
};

/* The driver state structure is pre-allocated. */

static struct sam_tsd_s g_tsd;

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

/****************************************************************************
 * Name: sam_tsd_notify
 ****************************************************************************/

static void sam_tsd_notify(struct sam_tsd_s *priv)
{
#ifndef CONFIG_DISABLE_POLL
  int i;
#endif

  /* If there are threads waiting for read data, then signal one of them
   * that the read data is available.
   */

  if (priv->nwaiters > 0)
    {
      /* After posting this semaphore, we need to exit because the touchscreen
       * is no longer available.
       */

      sem_post(&priv->waitsem);
    }

  /* If there are threads waiting on poll() for touchscreen data to become available,
   * then wake them up now.  NOTE: we wake up all waiting threads because we
   * do not know that they are going to do.  If they all try to read the data,
   * then some make end up blocking after all.
   */

#ifndef CONFIG_DISABLE_POLL
  for (i = 0; i < CONFIG_SAMA5_TSD_NPOLLWAITERS; i++)
    {
      struct pollfd *fds = priv->fds[i];
      if (fds)
        {
          fds->revents |= POLLIN;
          ivdbg("Report events: %02x\n", fds->revents);
          sem_post(fds->sem);
        }
    }
#endif
}

/****************************************************************************
 * Name: sam_tsd_sample
 ****************************************************************************/

static int sam_tsd_sample(struct sam_tsd_s *priv, struct sam_sample_s *sample)
{
  irqstate_t flags;
  int ret = -EAGAIN;

  /* Interrupts me be disabled when this is called to (1) prevent posting
   * of semaphores from interrupt handlers, and (2) to prevent sampled data
   * from changing until it has been reported.
   */

  flags = irqsave();

  /* Is there new touchscreen sample data available? */

  if (priv->penchange)
    {
      /* Yes.. the state has changed in some way.  Return a copy of the
       * sampled data.
       */

      memcpy(sample, &priv->sample, sizeof(struct sam_sample_s));

      /* Now manage state transitions */

      if (sample->contact == CONTACT_UP)
        {
          /* Next.. no contact.  Increment the ID so that next contact ID
           * will be unique.  X/Y positions are no longer valid.
           */

          priv->sample.contact = CONTACT_NONE;
          priv->sample.valid   = false;
          priv->id++;
        }
      else if (sample->contact == CONTACT_DOWN)
        {
          /* First report -- next report will be a movement */

          priv->sample.contact = CONTACT_MOVE;
        }

      priv->penchange = false;
      ret = OK;
    }

  irqrestore(flags);
  return ret;
}

/****************************************************************************
 * Name: sam_tsd_waitsample
 ****************************************************************************/

static int sam_tsd_waitsample(struct sam_tsd_s *priv, struct sam_sample_s *sample)
{
  irqstate_t flags;
  int ret;

  /* Interrupts me be disabled when this is called to (1) prevent posting
   * of semaphores from interrupt handlers, and (2) to prevent sampled data
   * from changing until it has been reported.
   *
   * In addition, we will also disable pre-emption to prevent other threads
   * from getting control while we muck with the semaphores.
   */

  sched_lock();
  flags = irqsave();

  /* Now release the semaphore that manages mutually exclusive access to
   * the device structure.  This may cause other tasks to become ready to
   * run, but they cannot run yet because pre-emption is disabled.
   */

  sam_adc_unlock(priv->adc);

  /* Try to get the a sample... if we cannot, then wait on the semaphore
   * that is posted when new sample data is available.
   */

  while (sam_tsd_sample(priv, sample) < 0)
    {
      /* Wait for a sample data */

      ivdbg("Waiting..\n");
      priv->nwaiters++;
      ret = sem_wait(&priv->waitsem);
      priv->nwaiters--;

      if (ret < 0)
        {
          /* If we are awakened by a signal, then we need to return
           * the failure now.
           */

          idbg("ERROR: sem_wait: %d\n", errno);
          DEBUGASSERT(errno == EINTR);
          ret = -EINTR;
          goto errout;
        }
    }

  ivdbg("Sampled\n");

  /* Re-acquire the semaphore that manages mutually exclusive access to
   * the device structure.  We may have to wait here.  But we have our sample.
   * Interrupts and pre-emption will be re-enabled while we wait.
   */

  sam_adc_lock(priv->adc);

errout:
  /* Then re-enable interrupts.  We might get interrupt here and there
   * could be a new sample.  But no new threads will run because we still
   * have pre-emption disabled.
   */

  irqrestore(flags);

  /* Restore pre-emption.  We might get suspended here but that is okay
   * because we already have our sample.  Note:  this means that if there
   * were two threads reading from the touchscreen ADC for some reason, the
   * data might be read out of order.
   */

  sched_unlock();
  return ret;
}

/****************************************************************************
 * Name: sam_tsd_setaverage
 *
 * Description:
 *   The ADC hardware can filter the touchscreen samples by averaging.  The
 *   function selects (or de-selects) that filtering.
 *
 * Input Parameters
 *   priv - The touchscreen private data structure
 *   tsav - The new (shifted) value of the TSAV field of the ADC TSMR regsiter.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void sam_tsd_setaverage(struct sam_tsd_s *priv, uint32_t tsav)
{
  uint32_t regval;
  uint32_t minfreq;
  uint32_t tsfreq;

  /* Get the current value of the TSMR register */

  regval = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);

  /* Get the unshifted TSAVE value and compare it to the touchscreen
   * frequency
   *
   *   minfreq = 0: No filtering
   *   minfreq = 1: Averages 2 ADC conversions
   *   minfreq = 2: Averages 4 ADC conversions
   *   minfreq = 3: Averages 8 ADC conversions
   */

  minfreq = (tsav >> ADC_TSMR_TSAV_SHIFT);
  if (minfreq)
    {
      /* TSFREQ: Defines the Touchscreen Frequency compared to the Trigger
       * Frequency.  --> TSFREQ must be greater or equal to TSAV. <--
       */

      tsfreq = (regval & ADC_TSMR_TSFREQ_MASK) >> ADC_TSMR_TSFREQ_SHIFT;
      if (minfreq > tsfreq)
        {
          /* Set TSFREQ = TSAV */

          regval &= ~ADC_TSMR_TSFREQ_MASK;
          regval |=  ADC_TSMR_TSFREQ(minfreq);
        }
    }

  /* Save the new filter value */

  regval &= ~ADC_TSMR_TSAV_MASK;
  regval |= tsav;
  sam_adc_putreg(priv->adc, SAM_ADC_TSMR, regval);
}

/****************************************************************************
 * Name: sam_tsd_bottomhalf
 *
 * Description:
 *   This function executes on the worker thread.  It is scheduled by
 *   sam_tsd_interrupt whenever any interesting, enabled TSD event occurs.
 *   All TSD interrupts are disabled when this function runs.  sam_tsd_bottomhalf
 *   will re-enable TSD interrupts when it completes processing all pending
 *   TSD events.
 *
 * Input Parameters
 *   arg - The touchscreen private data structure cast to (void *)
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void sam_tsd_bottomhalf(void *arg)
{
  struct sam_tsd_s *priv = (struct sam_tsd_s *)arg;
  uint32_t pending;
  uint32_t ier;
  uint32_t regval;
  uint32_t xraw;
  uint32_t xscale;
  uint32_t x;
  uint32_t xdiff;
  uint32_t yraw;
  uint32_t yscale;
  uint32_t y;
  uint32_t ydiff;
  uint32_t z1;
  uint32_t z2;
  uint32_t pressr;
  uint32_t p;
  bool pendown;

  ASSERT(priv != NULL);

  /* Get the set of pending ADC interrupts and pen status */

  pending = sam_adc_getreg(priv->adc, SAM_ADC_ISR);

  /* Get exclusive access to the driver data structure */

  sam_adc_lock(priv->adc);

  /* Check the pen state */

  pendown = ((pending & ADC_SR_PENS) != 0);

  /* Handle the change from pen down to pen up */

  ivdbg("pending: %08x pendown: %d contact: %d\n",
        pending, pendown, priv->sample.contact);

  if (!pendown)
    {
      /* The pen is up.. reset thresholding variables. */

      priv->threshx = INVALID_THRESHOLD;
      priv->threshy = INVALID_THRESHOLD;

      /* We will enable only the ADC_INT_PEN interrupt on exit.  We don't
       * want to hear anything from the touchscreen until the next touch.
       */

      ier = ADC_INT_PEN;

      /* Ignore the interrupt if the pen was already up (CONTACT_NONE == pen up
       * and already reported; CONTACT_UP == pen up, but not reported)
       */

      if (priv->sample.contact == CONTACT_NONE ||
          priv->sample.contact == CONTACT_UP)

        {
          goto ignored;
        }

      /* The pen is up.  NOTE: We know from a previous test, that this is a
       * loss of contact condition.  This will be changed to CONTACT_NONE
       * after the loss of contact is sampled.
       */

       priv->sample.contact = CONTACT_UP;

      /* Stop periodic trigger & enable pen */

      sam_tsd_setaverage(priv, ADC_TSMR_TSAV_NOFILTER);
      sam_tsd_debounce(priv, BOARD_TSD_DEBOUNCE);

      regval  = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
      regval &= ~ADC_TRGR_TRGMOD_MASK;
      regval |= ADC_TRGR_TRGMOD_PEN;
      sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);
    }

  /* It is a pen down event.  If the last loss-of-contact event has not been
   * processed yet, then we have to ignore the pen down event (or else it will
   * look like a drag event)
   */

  else if (priv->sample.contact == CONTACT_UP)
    {
      /* If we have not yet processed the last pen up event, then we
       * cannot handle this pen down event. We will have to discard it.  That
       * should be okay because we will set the timer to to sample again
       * a little later.  NOTE that pen interrupts are not re-enabled in
       * this case; we rely on the timer expiry to get us going again.
       */

      wd_start(priv->wdog, TSD_WDOG_DELAY, sam_tsd_expiry, 1, (uint32_t)priv);
      ier = 0;
      goto ignored;
    }
  else
    {
      /* The pen is down and the driver has accepted the last sample values. */

      /* While the pen is down we want interrupts on all data ready and pen
       * release events.
       */

      ier = ADC_TSD_RELEASEINTS;

      /* Check if all of the date that we need is available.  If not, just
       * re-enable interrupts and wait until it is.
       */

      if ((pending & TSD_ALLREADY) != TSD_ALLREADY)
        {
          /* But don't enable interrupts for the data that we already have */

          ier &= ~(pending & TSD_ALLREADY);
          goto ignored;
        }

      /* Sample positional values.  Get raw X and Y position data */

      regval = sam_adc_getreg(priv->adc, SAM_ADC_XPOSR);
      xraw   = (regval & ADC_XPOSR_XPOS_MASK) >> ADC_XPOSR_XPOS_SHIFT;
      xscale = (regval & ADC_XPOSR_XSCALE_MASK) >> ADC_XPOSR_XSCALE_SHIFT;

      regval = sam_adc_getreg(priv->adc, SAM_ADC_YPOSR);
      yraw   = (regval & ADC_YPOSR_YPOS_MASK) >> ADC_YPOSR_YPOS_SHIFT;
      yscale = (regval & ADC_YPOSR_YSCALE_MASK) >> ADC_YPOSR_YSCALE_SHIFT;

#ifdef CONFIG_SAMA5_TSD_4WIRE
      /* Read the PRESSR register now, but don't do anything until we
       * decide if we are going to use this measurement.
       */

      pressr = sam_adc_getreg(priv->adc, SAM_ADC_PRESSR);
#endif
      /* Discard any bad readings.  This check may not be necessary. */

      if (xraw == 0 || xraw >= xscale || yraw == 0 || yraw > yscale)
        {
          idbg("Discarding: x %d:%d y %d:%d\n", xraw, xscale);
          goto ignored;
        }

      /* Scale the X/Y measurements.  The scale value is the maximum
       * value that the sample can attain.  It should be close to 4095.
       * Scaling:
       *
       *   scaled = raw * 4095 / scale
       *          = ((raw << 12) - raw) / scale
       */

#ifdef CONFIG_SAMA5_TSD_SWAPXY
      x  = ((yraw << 12) - yraw) / yscale;
      y  = ((xraw << 12) - xraw) / xscale;
#else
      x  = ((xraw << 12) - xraw) / xscale;
      y  = ((yraw << 12) - yraw) / yscale;
#endif

      /* Perform a thresholding operation so that the results will be
       * more stable.  If the difference from the last sample is small,
       * then ignore the event. REVISIT:  Should a large change in
       * pressure also generate a event?
       */

      xdiff = x > priv->threshx ? (x - priv->threshx) : (priv->threshx - x);
      ydiff = y > priv->threshy ? (y - priv->threshy) : (priv->threshy - y);

      /* Continue to sample the position while the pen is down */

      wd_start(priv->wdog, TSD_WDOG_DELAY, sam_tsd_expiry, 1, (uint32_t)priv);

      /* Check the thresholds.  Bail if (1) this is not the first
       * measurement and (2) there is no significant difference from
       * the last measurement.
       */

      if (priv->sample.contact == CONTACT_MOVE &&
          xdiff < CONFIG_SAMA5_TSD_THRESHX &&
          ydiff < CONFIG_SAMA5_TSD_THRESHY)
        {
          /* Little or no change in either direction ... don't report
           * anything.
           */

          goto ignored;
        }

      /* When we see a big difference, snap to the new x/y thresholds */

      priv->threshx  = x;
      priv->threshy  = y;

      /* Update the x/y position in the sample data */

      priv->sample.x = MIN(x, UINT16_MAX);
      priv->sample.y = MIN(y, UINT16_MAX);

#ifdef CONFIG_SAMA5_TSD_4WIRE
      /* Scale the pressure and update the pressure in the sample data.
       *
       * The method to measure the pressure (Rp) applied to the
       * touchscreen is based on the known resistance of the X-Panel
       * resistance (Rxp). Three conversions (Xpos, Z1, Z2) are
       * necessary to determine the value of Rp (Zaxis resistance).
       *
       *   Rp = Rxp * (Xraw / 1024) * [(Z2 / Z1) - 1]
       */

      z2 = (pressr & ADC_PRESSR_Z2_MASK) >> ADC_PRESSR_Z2_SHIFT;
      z1 = (pressr & ADC_PRESSR_Z1_MASK) >> ADC_PRESSR_Z1_SHIFT;
      p  = CONFIG_SAMA_TSD_RXP * xraw * (z2 - z1) / z1;

      priv->sample.p = MIN(p, UINT16_MAX);
#endif

      /* The X/Y positional data is now valid */

      priv->sample.valid = true;

      /* If this is the first (acknowledged) pen down report, then
       * report this as the first contact.  If contact == CONTACT_DOWN,
       * it will be set to set to CONTACT_MOVE after the contact is
       * first sampled.
       */

      if (priv->sample.contact != CONTACT_MOVE)
        {
          /* First contact.  Handle transitions from pen UP to pen DOWN */

          priv->sample.contact = CONTACT_DOWN;

          /* Configure for periodic trigger */

          sam_tsd_setaverage(priv, ADC_TSMR_TSAV_8CONV);
          sam_tsd_debounce(priv, 300);         /* 300ns */

          regval  = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
          regval &= ~ADC_TRGR_TRGMOD_MASK;
          regval |= ADC_TRGR_TRGMOD_PERIOD;
          sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);
        }
    }

  /* Indicate the availability of new sample data for this ID */

  priv->sample.id = priv->id;
  priv->penchange = true;

  /* Notify any waiters that new touchscreen data is available */

  sam_tsd_notify(priv);

  /* Exit, re-enabling touchscreen interrupts */

ignored:
  /* Re-enable touchscreen interrupts as appropriate. */

  sam_adc_putreg(priv->adc, SAM_ADC_IER, ier);

  /* Release our lock on the state structure */

  sam_adc_unlock(priv->adc);
}

/****************************************************************************
 * Name: sam_tsd_schedule
 ****************************************************************************/

static int sam_tsd_schedule(struct sam_tsd_s *priv)
{
  int ret;

  /* Disable the watchdog timer.  It will be re-enabled in the worker thread
   * while the pen remains down.
   */

  wd_cancel(priv->wdog);

  /* Disable further touchscreen interrupts.  Touchscreen interrupts will be
   * re-enabled after the worker thread executes.
   */

  sam_adc_putreg(priv->adc, SAM_ADC_IDR, ADC_TSD_ALLINTS);

  /* Transfer processing to the worker thread.  Since touchscreen ADC interrupts are
   * disabled while the work is pending, no special action should be required
   * to protected the work queue.
   */

  DEBUGASSERT(priv->work.worker == NULL);
  ret = work_queue(HPWORK, &priv->work, sam_tsd_bottomhalf, priv, 0);
  if (ret != 0)
    {
      illdbg("Failed to queue work: %d\n", ret);
    }

  return OK;
}

/****************************************************************************
 * Name: sam_tsd_expiry
 *
 * Description:
 *   While the pen is pressed, pen position is periodically polled via a
 *   watchdog timer.  This function handles that timer expiration.
 *
 ****************************************************************************/

static void sam_tsd_expiry(int argc, uint32_t arg1, ...)
{
  struct sam_tsd_s *priv = (struct sam_tsd_s *)((uintptr_t)arg1);

  /* Schedule touchscreen work */

  (void)sam_tsd_schedule(priv);
}

/****************************************************************************
 * Name: sam_tsd_open
 ****************************************************************************/

static int sam_tsd_open(struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct sam_tsd_s *priv = inode->i_private;
  uint8_t tmp;
  int ret;

  ivdbg("crefs: %d\n", priv->crefs);

  /* Get exclusive access to the device structures */

  sam_adc_lock(priv->adc);

  /* Increment the count of references to the device.  If this the first
   * time that the driver has been opened for this device, then initialize
   * the device.
   */

  tmp = priv->crefs + 1;
  if (tmp == 0)
    {
      /* More than 255 opens; uint8_t overflows to zero */

      ret = -EAGAIN;
    }
  else
    {
      /* Save the new open count */

      priv->crefs = tmp;

      /* Initialize the touchscreen when it is first opened */

      if (tmp == 1)
        {
          sam_tsd_initialize(priv);
        }

      /* Successfully opened */

      ret = OK;
    }

  sam_adc_unlock(priv->adc);
  return ret;
}

/****************************************************************************
 * Name: sam_tsd_close
 ****************************************************************************/

static int sam_tsd_close(struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct sam_tsd_s *priv = inode->i_private;

  ivdbg("crefs: %d\n", priv->crefs);

  /* Get exclusive access to the ADC device */

  sam_adc_lock(priv->adc);

  /* Decrement the references to the driver */

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

  /* If this was the last reference to the driver, then un-initialize the
   * TSD now.
   */

  if (priv->crefs == 0)
    {
      sam_tsd_uninitialize(priv);
    }

  sam_adc_unlock(priv->adc);
  return OK;
}

/****************************************************************************
 * Name: sam_tsd_read
 ****************************************************************************/

static ssize_t sam_tsd_read(struct file *filep, char *buffer, size_t len)
{
  struct inode *inode;
  struct sam_tsd_s *priv;
  struct touch_sample_s *report;
  struct sam_sample_s sample;
  int ret;

  ivdbg("buffer:%p len:%d\n", buffer, len);
  DEBUGASSERT(filep);
  inode = filep->f_inode;

  DEBUGASSERT(inode && inode->i_private);
  priv  = (struct sam_tsd_s *)inode->i_private;

  /* Verify that the caller has provided a buffer large enough to receive
   * the touch data.
   */

  if (len < SIZEOF_TOUCH_SAMPLE_S(1))
    {
      /* We could provide logic to break up a touch report into segments and
       * handle smaller reads... but why?
       */

      idbg("ERROR: Unsupported read size: %d\n", len);
      return -ENOSYS;
    }

  /* Get exclusive access to the driver data structure */

  sam_adc_lock(priv->adc);

  /* Try to read sample data. */

  ret = sam_tsd_sample(priv, &sample);
  if (ret < 0)
    {
      /* Sample data is not available now.  We would ave to wait to get
       * receive sample data.  If the user has specified the O_NONBLOCK
       * option, then just return an error.
       */

      ivdbg("Sample data is not available\n");
      if (filep->f_oflags & O_NONBLOCK)
        {
          ret = -EAGAIN;
          goto errout;
        }

      /* Wait for sample data */

      ret = sam_tsd_waitsample(priv, &sample);
      if (ret < 0)
        {
          /* We might have been awakened by a signal */

          idbg("ERROR: sam_tsd_waitsample: %d\n", ret);
          goto errout;
        }
    }

  /* In any event, we now have sampled touchscreen data that we can report
   * to the caller.
   */

  report = (struct touch_sample_s *)buffer;
  memset(report, 0, SIZEOF_TOUCH_SAMPLE_S(1));
  report->npoints           = 1;
  report->point[0].id       = sample.id;
  report->point[0].x        = sample.x;
  report->point[0].y        = sample.y;
#ifdef CONFIG_SAMA5_TSD_4WIRE
  report->point[0].pressure = sample.p;
#endif

  /* Report the appropriate flags */

  if (sample.contact == CONTACT_UP)
    {
      /* Pen is now up.  Is the positional data valid?  This is important
       * to know because the release will be sent to the window based on
       * its last positional data.
       */

      if (sample.valid)
        {
          report->point[0].flags  = TSD_PENUP_VALID;
        }
      else
        {
          report->point[0].flags  = TSD_PENUP_INVALID;
        }
    }
  else if (sample.contact == CONTACT_DOWN)
    {
      /* First contact */

      report->point[0].flags  = TSD_PENDOWN;
    }
  else /* if (sample->contact == CONTACT_MOVE) */
    {
      /* Movement of the same contact */

      report->point[0].flags  = TSD_PENMOVE;
    }

  ivdbg("  id:      %d\n", report->point[0].id);
  ivdbg("  flags:   %02x\n", report->point[0].flags);
  ivdbg("  x:       %d\n", report->point[0].x);
  ivdbg("  y:       %d\n", report->point[0].y);

  ret = SIZEOF_TOUCH_SAMPLE_S(1);

errout:
  sam_adc_unlock(priv->adc);
  ivdbg("Returning: %d\n", ret);
  return (ssize_t)ret;
}

/****************************************************************************
 * Name:sam_tsd_ioctl
 ****************************************************************************/

static int sam_tsd_ioctl(struct file *filep, int cmd, unsigned long arg)
{
  struct inode *inode;
  struct sam_tsd_s *priv;
  int ret;

  ivdbg("cmd: %d arg: %ld\n", cmd, arg);
  DEBUGASSERT(filep);
  inode = filep->f_inode;

  DEBUGASSERT(inode && inode->i_private);
  priv  = (struct sam_tsd_s *)inode->i_private;

  /* Get exclusive access to the driver data structure */

  sam_adc_lock(priv->adc);

  /* Process the IOCTL by command */

  switch (cmd)
    {
      default:
        ret = -ENOTTY;
        break;
    }

  sam_adc_unlock(priv->adc);
  return ret;
}

/****************************************************************************
 * Name: sam_tsd_poll
 ****************************************************************************/

#ifndef CONFIG_DISABLE_POLL
static int sam_tsd_poll(struct file *filep, struct pollfd *fds, bool setup)
{
  struct inode *inode;
  struct sam_tsd_s *priv;
  int ret = OK;
  int i;

  ivdbg("setup: %d\n", (int)setup);
  DEBUGASSERT(filep && fds);
  inode = filep->f_inode;

  DEBUGASSERT(inode && inode->i_private);
  priv  = (struct sam_tsd_s *)inode->i_private;

  /* Get exclusive access to the ADC hardware */

  sam_adc_lock(priv->adc);

  /* Are we setting up the poll?  Or tearing it down? */

  if (setup)
    {
      /* Ignore waits that do not include POLLIN */

      if ((fds->events & POLLIN) == 0)
        {
          ret = -EDEADLK;
          goto errout;
        }

      /* This is a request to set up the poll.  Find an available
       * slot for the poll structure reference
       */

      for (i = 0; i < CONFIG_SAMA5_TSD_NPOLLWAITERS; i++)
        {
          /* Find an available slot */

          if (!priv->fds[i])
            {
              /* Bind the poll structure and this slot */

              priv->fds[i] = fds;
              fds->priv    = &priv->fds[i];
              break;
            }
        }

      if (i >= CONFIG_SAMA5_TSD_NPOLLWAITERS)
        {
          fds->priv    = NULL;
          ret          = -EBUSY;
          goto errout;
        }

      /* Should we immediately notify on any of the requested events? */

      if (priv->penchange)
        {
          sam_tsd_notify(priv);
        }
    }
  else if (fds->priv)
    {
      /* This is a request to tear down the poll. */

      struct pollfd **slot = (struct pollfd **)fds->priv;
      DEBUGASSERT(slot != NULL);

      /* Remove all memory of the poll setup */

      *slot                = NULL;
      fds->priv            = NULL;
    }

errout:
  sam_adc_unlock(priv->adc);
  return ret;
}
#endif

/****************************************************************************
 * Initialization and Configuration
 ****************************************************************************/

/****************************************************************************
 * Name: sam_tsd_startuptime
 *
 * Description:
 *   Set the STARTUP field of the ADC MR register.
 *
 * Input Parameters:
 *   priv - A reference to the touchscreen device structure
 *   time - The new startup time in nanoseconds
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void sam_tsd_startuptime(struct sam_tsd_s *priv, uint32_t time)
{
  uint32_t startup;
  uint32_t regval;

  /* Formula for STARTUP is:
   *
   * STARTUP = (time x ADCCLK) / (1000000) - 1
   *
   * Division multiplied by 10 for higher precision.
   */

  startup = (time * BOARD_ADCCLK_FREQUENCY) / 100000;

  if (startup % 10)
    {
      /* Handle partial values by not decrementing startup.  This is
       * basically a 'ceil' operation.
       */

      startup /= 10;
    }
  else
    {
      /* The final value needs to be decrement by one */

      startup /= 10;
      if (startup)
        {
          startup --;
        }
    }

  /* Then set the STARTUP value in the ADC MR register. */

  regval  = sam_adc_getreg(priv->adc, SAM_ADC_MR);
  regval &= ~ADC_MR_STARTUP_MASK;

  if (startup > 896)
    {
      regval = ADC_MR_STARTUP_960;
    }
  else if (startup > 832)
    {
      regval = ADC_MR_STARTUP_896;
    }
  else if (startup > 768)
    {
      regval = ADC_MR_STARTUP_832;
    }
  else if (startup > 704)
    {
      regval = ADC_MR_STARTUP_768;
    }
  else if (startup > 640)
    {
      regval = ADC_MR_STARTUP_704;
    }
  else if (startup > 576)
    {
      regval = ADC_MR_STARTUP_640;
    }
  else if (startup > 512)
    {
      regval = ADC_MR_STARTUP_576;
    }
  else if (startup > 112)
    {
      regval = ADC_MR_STARTUP_512;
    }
  else if (startup > 96)
    {
      regval = ADC_MR_STARTUP_112;
    }
  else if (startup > 80)
    {
      regval = ADC_MR_STARTUP_96;
    }
  else if (startup > 64)
    {
      regval = ADC_MR_STARTUP_80;
    }
  else if (startup > 24)
    {
      regval = ADC_MR_STARTUP_64;
    }
  else if (startup > 16)
    {
      regval = ADC_MR_STARTUP_24;
    }
  else if (startup > 8)
    {
      regval = ADC_MR_STARTUP_16;
    }
  else if (startup > 0)
    {
      regval = ADC_MR_STARTUP_8;
    }
  else
    {
      regval = ADC_MR_STARTUP_0;
    }

  sam_adc_putreg(priv->adc, SAM_ADC_MR, regval);
}

/****************************************************************************
 * Name: sam_tsd_tracking
 *
 * Description:
 *   Set the TRACKTIM field of the ADC MR register.
 *
 *     TrackingTime = (TRACKTIM + 1) * ADCClock periods.
 *     TRACKTIM     = (TrackingTime * ADCCLK) / (1000000000) - 1
 *
 * Input Parameters:
 *   priv - A reference to the touchscreen device structure
 *   time - The new tracking time in nanoseconds
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void sam_tsd_tracking(struct sam_tsd_s *priv, uint32_t time)
{
  uint32_t tracktim;
  uint32_t regval;

  /* Formula for SHTIM is:
   *
   *  TRACKTIM     = (TrackingTime * ADCCLK) / (1000000000) - 1
   *
   * Since 1 billion is close to the maximum value for an integer, we first
   * divide ADCCLK by 1000 to avoid an overflow
   */

  tracktim = (time * (BOARD_ADCCLK_FREQUENCY / 1000)) / 100000;
  if (tracktim % 10)
    {
      /* Handle partial values by not decrementing tracktim.  This is
       * basically a 'ceil' operation.
       */

      tracktim /= 10;
    }
  else
    {
      /* The final value needs to be decrement by one */

      tracktim /= 10;
      if (tracktim)
        {
          tracktim --;
        }
    }

  /* Set the neew TRACKTIM field value int he ADC MR register */

  regval  = sam_adc_getreg(priv->adc, SAM_ADC_MR);
  regval &= ~ADC_MR_TRACKTIM_MASK;
  regval |= ADC_MR_TRACKTIM(tracktim);
  sam_adc_putreg(priv->adc, SAM_ADC_MR, regval);
}

/****************************************************************************
 * Name: sam_tsd_trigperiod
 *
 * Description:
 *   Set the TGPER field of the TRGR register in order to define a periodic
 *   trigger perioc.
 *
 *     Trigger Period = (TRGPER+1) / ADCCLK
 *
 * Input Parameters:
 *   priv - A reference to the touchscreen device structure
 *   time - The new trigger period in nanoseconds
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void sam_tsd_trigperiod(struct sam_tsd_s *priv, uint32_t period)
{
  uint32_t trigper;
  uint32_t regval;
  uint32_t div;

  /* Divide trigger period avoid overflows.  Division by ten is awkard, but
   * appropriate here because times are specified in decimal with lots of
   * zeroes.
   */

  div = 100000000;
  while (period >= 10 && div >= 10)
    {
      period /= 10;
      div    /= 10;
    }

  /* Calculate and adjust the scaled trigger period:
   *
   *   Trigger Period = (TRGPER+1) / ADCCLK
   */

  trigper = (period * BOARD_ADCCLK_FREQUENCY) / div;
  if ((trigper % 10) != 0)
    {
      /* Handle partial values by not decrementing trigper.  This is
       * basically a 'ceil' operation.
       */

      trigper /= 10;
    }
  else
    {
      /* The final value needs to be decrement by one */

      trigper /= 10;
      if (trigper > 0)
        {
          trigper--;
        }
    }

  /* Set the calculated trigger period in the TRGR register */

  regval  = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
  regval &= ~ADC_TRGR_TRGPER_MASK;
  regval |=  ADC_TRGR_TRGPER(trigper);
  sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);
}

/****************************************************************************
 * Name: sam_tsd_debounce
 *
 * Description:
 *   Set the Pen Detect Debouncing Period (PENBC) in the touchscreen mode
 *   register.
 *
 *     Debouncing period = 2 ** PENDBC ADCClock periods.
 *
 * Input Parameters:
 *   priv - A reference to the touchscreen device structure
 *   time - The new debounce time in nanoseconds
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void sam_tsd_debounce(struct sam_tsd_s *priv, uint32_t time)
{
  uint32_t candidate;
  uint32_t target;
  uint32_t regval;
  uint32_t pendbc;
  uint32_t div;
  uint32_t clk;

  /* Make sure that a valid debounce time was provided */

  DEBUGASSERT(time > 0);

  /* Divide time and ADCCLK to avoid overflows.  Division by ten is awkard,
   * but appropriate here because times are specified in decimal with lots of
   * zeroes.
   */

  div = 1000000000;
  while (div > 1 && (time % 10) == 0)
    {
      time /= 10;
      div  /= 10;
    }

  clk = BOARD_ADCCLK_FREQUENCY;
  while (div > 1 && (clk % 10) == 0)
    {
      clk  /= 10;
      div  /= 10;
    }

  /* Compute PENDBC */

  target    = time * clk / div;
  candidate = 1;
  pendbc    = 0;

  while (candidate < target)
    {
      pendbc++;
      candidate *= 2;
    }

  DEBUGASSERT(pendbc > 0);

  /* Update the TSMR with the new PENBC value */

  regval = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);
  regval &= ~ADC_TSMR_PENDBC_MASK;
  regval |= ADC_TSMR_PENDBC(pendbc);
  sam_adc_putreg(priv->adc, SAM_ADC_TSMR, regval);
}

/****************************************************************************
 * Name: sam_tsd_initialize
 *
 * Description:
 *   Initialize the touchscreen for normal operation.  This function is
 *   called from sam_tsd_open() the first time that the driver is opened.
 *
 * Input Parameters:
 *   priv - A reference to the touchscreen device structure
 *
 * Returned Value:
 *   Zero is returned on success.  Otherwise, a negated errno value is
 *   returned to indicate the nature of the failure.
 *
 ****************************************************************************/

static void sam_tsd_initialize(struct sam_tsd_s *priv)
{
  uint32_t regval;

  /* Disable touch trigger */

  regval  = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
  regval &= ~ADC_TRGR_TRGMOD_MASK;
  regval |= ADC_TRGR_TRGMOD_NOTRIG;
  sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);

  /* Setup timing */

  sam_tsd_startuptime(priv, BOARD_TSD_STARTUP);
  sam_tsd_tracking(priv, BOARD_TSD_TRACKTIM);
  sam_tsd_trigperiod(priv, 20000000); /*  20ms */

  /* Setup the touchscreen mode */

  regval  = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);
  regval &= ~ADC_TSMR_TSMODE_MASK;

#if defined(CONFIG_SAMA5_TSD_5WIRE)
  regval |= ADC_TSMR_TSMODE_5WIRE;
#elif defined(CONFIG_SAMA5_TSD_4WIRENPM)
  regval |= ADC_TSMR_TSMODE_4WIRENPM;
#else /* if defined(CONFIG_SAMA5_TSD_4WIRE) */
  regval |= ADC_TSMR_TSMODE_4WIRE;
#endif

  sam_adc_putreg(priv->adc, SAM_ADC_TSMR, regval);

  /* Disable averaging */

  sam_tsd_setaverage(priv, ADC_TSMR_TSAV_NOFILTER);

  /* Select 4-wire w/pressure, 4-wire w/o pressure, or 5 wire modes */

  regval  = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);
  regval &= ~ADC_TSMR_TSMODE_MASK;

#if defined(CONFIG_SAMA5_TSD_5WIRE)
  regval |= ADC_TSMR_TSMODE_5WIRE;
#elif defined(CONFIG_SAMA5_TSD_4WIRENPM)
  regval |= ADC_TSMR_TSMODE_4WIRENPM;
#else /* if defined(CONFIG_SAMA5_TSD_4WIRE) */
  regval |= ADC_TSMR_TSMODE_4WIRE;
#endif

  /* Disable all TSD-related interrupts */

  sam_adc_putreg(priv->adc, SAM_ADC_IDR, ADC_TSD_ALLINTS);

  /* Clear any pending TSD interrupts */

  sam_adc_getreg(priv->adc, SAM_ADC_ISR);

  /* Read and discard any samples */

  sam_adc_getreg(priv->adc, SAM_ADC_XPOSR);
  sam_adc_getreg(priv->adc, SAM_ADC_YPOSR);
#ifdef CONFIG_SAMA5_TSD_4WIRE
  sam_adc_getreg(priv->adc, SAM_ADC_PRESSR);
#endif

  /* Enable pen contact detection */

  regval |= ADC_TSMR_PENDET;
  sam_adc_putreg(priv->adc, SAM_ADC_TSMR, regval);

  /* Set up pen debounce time */

  sam_tsd_debounce(priv, BOARD_TSD_DEBOUNCE);

  /* Configure pen interrupt generation */

  regval  = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
  regval &= ~ADC_TRGR_TRGMOD_MASK;
  regval |= ADC_TRGR_TRGMOD_PEN;
  sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);

  sam_adc_putreg(priv->adc, SAM_ADC_IER, ADC_INT_PEN);
}

/****************************************************************************
 * Name: sam_tsd_uninitialize
 *
 * Description:
 *   Uninitialize the touchscreen.  This function is called from
 *   sam_tsd_close() when the final driver instance is closed.
 *
 * Input Parameters:
 *   priv - A reference to the touchscreen device structure
 *
 * Returned Value:
 *   Zero is returned on success.  Otherwise, a negated errno value is
 *   returned to indicate the nature of the failure.
 *
 ****************************************************************************/

static void sam_tsd_uninitialize(struct sam_tsd_s *priv)
{
  uint32_t regval;

  /* Disable the watchdog timer.  It will be re-enabled in the worker thread
   * while the pen remains down.
   */

  wd_cancel(priv->wdog);

  /* Disable further touchscreen interrupts.  Touchscreen interrupts will be
   * re-enabled after the worker thread executes.
   */

  sam_adc_putreg(priv->adc, SAM_ADC_IDR, ADC_TSD_ALLINTS);

  /* Disable touch trigger */

  regval  = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
  regval &= ~ADC_TRGR_TRGMOD_MASK;
  regval |= ADC_TRGR_TRGMOD_NOTRIG;
  sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);

  /* Disable the touchscreen mode */

  regval  = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);
  regval &= ~ADC_TSMR_TSMODE_MASK;
  regval |= ADC_TSMR_TSMODE_NONE;
  sam_adc_putreg(priv->adc, SAM_ADC_TSMR, regval);

  /* No touch and no valid sample data */

  priv->sample.contact = CONTACT_NONE;
  priv->sample.valid = false;
}

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

/****************************************************************************
 * Name: sam_tsd_register
 *
 * Description:
 *   Configure the SAMA5 touchscreen.  This will register the driver as
 *   /dev/inputN where N is the minor device number
 *
 * Input Parameters:
 *   adc   - An opaque reference to the ADC device structure
 *   minor - The input device minor number
 *
 * Returned Value:
 *   Zero is returned on success.  Otherwise, a negated errno value is
 *   returned to indicate the nature of the failure.
 *
 ****************************************************************************/

int sam_tsd_register(struct sam_adc_s *adc, int minor)
{
  struct sam_tsd_s *priv = &g_tsd;
  char devname[DEV_NAMELEN];
  int ret;

  ivdbg("minor: %d\n", minor);

  /* Debug-only sanity checks */

  DEBUGASSERT(adc && minor >= 0 && minor < 100);

  /* Initialize the touchscreen device driver instance */

  memset(priv, 0, sizeof(struct sam_tsd_s));
  priv->adc     = adc;               /* Save the ADC device handle */
  priv->wdog    = wd_create();       /* Create a watchdog timer */
  priv->threshx = INVALID_THRESHOLD; /* Initialize thresholding logic */
  priv->threshy = INVALID_THRESHOLD; /* Initialize thresholding logic */

  sem_init(&priv->waitsem, 0, 0);    /* Initialize pen event wait semaphore */

  /* Register the device as an input device */

  (void)snprintf(devname, DEV_NAMELEN, DEV_FORMAT, minor);
  ivdbg("Registering %s\n", devname);

  ret = register_driver(devname, &g_tsdops, 0666, priv);
  if (ret < 0)
    {
      idbg("ERROR: register_driver() failed: %d\n", ret);
      goto errout_with_priv;
    }

  /* And return success.  The hardware will be initialized as soon as the
   * touchscreen driver is opened for the first time.
   */

  return OK;

errout_with_priv:
  sem_destroy(&priv->waitsem);
  return ret;
}

/****************************************************************************
 * Name: sam_tsd_interrupt
 *
 * Description:
 *   Handles ADC interrupts associated with touchscreen channels
 *
 * Input parmeters:
 *   pending - Current set of pending interrupts being handled
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void sam_tsd_interrupt(uint32_t pending)
{
  struct sam_tsd_s *priv = &g_tsd;
  int ret;

  /* Are there pending TSD interrupts? */

  if ((pending & ADC_TSD_ALLINTS) != 0)
    {
      /* Schedule sampling to occur by the interrupt bottom half on the
       * worker thread.
       */

      ret = sam_tsd_schedule(priv);
      if (ret < 0)
        {
          idbg("ERROR: sam_tsd_schedule failed: %d\n", ret);
        }
    }
}

#endif /* CONFIG_SAMA5_ADC && CONFIG_SAMA5_TSD */
