/****************************************************************************
 * arch/arm/src/tiva/common/tiva_qencoder.c
 *
 *   Copyright (C) 2016 Young Mu. All rights reserved.
 *   Author: Young Mu <young.mu@aliyun.com>
 *
 * The basic structure of this driver derives in spirit (if nothing more)
 * from the NuttX STM32 QEI driver which has:
 *
 *   Copyright (C) 2012 Gregory Nutt. All rights reserved.
 *   Authors: Gregory Nutt <gnutt@nuttx.org>
 *            Diego Sanchez <dsanchez@nx-engineering.com>
 *
 * 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 name NuttX 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 <stdio.h>
#include <string.h>
#include <debug.h>

#include <nuttx/sensors/qencoder.h>

#include "arm_internal.h"
#include "tiva_gpio.h"
#include "tiva_qencoder.h"
#include "tiva_enablepwr.h"
#include "tiva_enableclks.h"

#include "hardware/tiva_qencoder.h"
#include "hardware/tiva_pinmap.h"
#include "hardware/tiva_memorymap.h"

#include <arch/board/board.h>

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

struct tiva_qe_s
{
  const struct qe_ops_s *ops;
  uint8_t id;
  uintptr_t base;
  uint32_t idx;
  uint32_t pha;
  uint32_t phb;
  uint32_t maxpos;
};

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

static inline void tiva_qe_putreg(struct tiva_qe_s *qe, unsigned int offset,
                                  uint32_t regval);
static inline uint32_t tiva_qe_getreg(struct tiva_qe_s *qe,
                                      unsigned int offset);

static int tiva_qe_setup(struct qe_lowerhalf_s *lower);
static int tiva_qe_shutdown(struct qe_lowerhalf_s *lower);
static int tiva_qe_position(struct qe_lowerhalf_s *lower,
                            int32_t *pos);
static int tiva_qe_reset(struct qe_lowerhalf_s *lower);
static int tiva_qe_ioctl(struct qe_lowerhalf_s *lower, int cmd,
                         unsigned long arg);

static int tiva_qe_direction(struct tiva_qe_s *qe, unsigned long *dir);
static int tiva_qe_velocity(struct tiva_qe_s *qe, unsigned long *vel);
static int tiva_qe_resetatppr(struct tiva_qe_s *qe, unsigned long ppr);
static int tiva_qe_resetatmaxpos(struct tiva_qe_s *qe,
                                 unsigned long offs);
static int tiva_qe_resetatindex(struct tiva_qe_s *qe);

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

static const struct qe_ops_s g_qe_ops =
{
  .setup     = tiva_qe_setup,
  .shutdown  = tiva_qe_shutdown,
  .position  = tiva_qe_position,
  .setposmax = NULL,            /* not supported yet */
  .reset     = tiva_qe_reset,
  .setindex  = NULL,            /* not supported yet */
  .ioctl     = tiva_qe_ioctl,
};

#ifdef CONFIG_TIVA_QEI0
static struct tiva_qe_s g_qe0 =
{
  .ops      = &g_qe_ops,
  .id       = 0,
  .base     = TIVA_QEI0_BASE,
  .idx      = GPIO_QEI0_IDX,
  .pha      = GPIO_QEI0_PHA,
  .phb      = GPIO_QEI0_PHB,
  .maxpos   = 0,
};
#endif

#ifdef CONFIG_TIVA_QEI1
static struct tiva_qe_s g_qe1 =
{
  .ops      = &g_qe_ops,
  .id       = 1,
  .base     = TIVA_QEI1_BASE,
  .idx      = GPIO_QEI1_IDX,
  .pha      = GPIO_QEI1_PHA,
  .phb      = GPIO_QEI1_PHB,
  .maxpos   = 0,
};
#endif

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

/****************************************************************************
 * Name: tiva_qe_getreg
 *
 * Description:
 *   Get a 32-bit register value by offset
 *
 ****************************************************************************/

static inline uint32_t tiva_qe_getreg(struct tiva_qe_s *qe,
                                      unsigned int offset)
{
  uintptr_t regaddr = qe->base + offset;
  return getreg32(regaddr);
}

/****************************************************************************
 * Name: tiva_qe_putreg
 *
 * Description:
 *  Put a 32-bit register value by offset
 *
 ****************************************************************************/

static inline void tiva_qe_putreg(struct tiva_qe_s *qe, unsigned int offset,
                                  uint32_t regval)
{
  uintptr_t regaddr = qe->base + offset;
  putreg32(regval, regaddr);
}

/****************************************************************************
 * Name: qe_setup
 *
 * Description:
 *   This method is called when the driver is opened.  The lower half driver
 *   will be configured and initialized the device so that it is ready for
 *   use.  It will not, however, output pulses until the start method is
 *   called.
 *
 * Input Parameters:
 *   lower - A reference to the lower half QEI driver state structure
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int tiva_qe_setup(struct qe_lowerhalf_s *lower)
{
  uint32_t ctlreg = 0;
  struct tiva_qe_s *qe = (struct tiva_qe_s *)lower;
  int ret;

  sninfo("setup QEI %d\n", qe->id);

  /* Enable GPIO port, GPIO pin type and GPIO alternate function */

  ret = tiva_configgpio(qe->idx);
  if (ret < 0)
    {
      snerr("ERROR: tiva_configgpio failed (%x)\n", qe->idx);
      return -1;
    }

  ret = tiva_configgpio(qe->pha);
  if (ret < 0)
    {
      snerr("ERROR: tiva_configgpio failed (%x)\n", qe->pha);
      return -1;
    }

  ret = tiva_configgpio(qe->phb);
  if (ret < 0)
    {
      snerr("ERROR: tiva_configgpio failed (%x)\n", qe->phb);
      return -1;
    }

  /* Get initial value of register */

  ctlreg = tiva_qe_getreg(qe, TIVA_QEI_CTL_OFFSET);

  /* Turn off the bits we're about to control */

  ctlreg &= ~(uint32_t)((1 << TIVA_QEI_CTL_RESMODE) |
                        (1 << TIVA_QEI_CTL_CAPMODE) |
                        (1 << TIVA_QEI_CTL_VELEN) |
                        (1 << TIVA_QEI_CTL_SIGMODE) |
                        (1 << TIVA_QEI_CTL_SWAP));

  /* Set reset mode (default as MAXPOS) */

  ctlreg |= RESMODE_BY_MAXPOS << TIVA_QEI_CTL_RESMODE;

  /* Set capture mode (default as PHA_AND_PHB) */

  ctlreg |= CAPMODE_PHA_AND_PHB << TIVA_QEI_CTL_CAPMODE;

  /* Enable velocity capture */

  ctlreg |= VELEN_ENABLE << TIVA_QEI_CTL_VELEN;

  /* Set signal mode (default as quadrature) */

  ctlreg |= SIGMODE_QUADRATURE << TIVA_QEI_CTL_SIGMODE;

  /* Set swap mode (default as no swap) */

  ctlreg |= SWAP_NO_SWAP << TIVA_QEI_CTL_SWAP;

  /* Program the register */

  tiva_qe_putreg(qe, TIVA_QEI_CTL_OFFSET, ctlreg);

  /* Set default maxpos value to entire uint32_t range */

  qe->maxpos = UINT32_MAX;
  tiva_qe_putreg(qe, TIVA_QEI_MAXPOS_OFFSET, qe->maxpos);

  /* Reset the position */

  tiva_qe_putreg(qe, TIVA_QEI_POS_OFFSET, 0);

  /* Set prediv (1) */

  ctlreg = tiva_qe_getreg(qe, TIVA_QEI_CTL_OFFSET);
  ctlreg |= VELDIV_1 << TIVA_QEI_CTL_VELDIV;
  tiva_qe_putreg(qe, TIVA_QEI_CTL_OFFSET, ctlreg);

  /* Set period load (10ms for TM4C1294NCPDT) */

  tiva_qe_putreg(qe, TIVA_QEI_LOAD_OFFSET, 1200000);

  /* Enable the QEI */

  ctlreg = tiva_qe_getreg(qe, TIVA_QEI_CTL_OFFSET);
  ctlreg |= QEI_ENABLE << TIVA_QEI_CTL_ENABLE;
  tiva_qe_putreg(qe, TIVA_QEI_CTL_OFFSET, ctlreg);

  return OK;
}

/****************************************************************************
 * Name: qe_shutdown
 *
 * Description:
 *   This method is called when the driver is closed.  The lower half driver
 *   stop data collection, free any resources, disable the timer hardware,
 *   and put the system into the lowest possible power usage state.
 *
 * Input Parameters:
 *   lower - A reference to the lower half QEI driver state structure
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int tiva_qe_shutdown(struct qe_lowerhalf_s *lower)
{
  struct tiva_qe_s *qe = (struct tiva_qe_s *)lower;

  sninfo("shutdown QEI %d\n", qe->id);

  /* Disable the QEI */

  tiva_qe_putreg(qe, TIVA_SYSCON_SRQEI_OFFSET, SYSCON_SRQEI(qe->id));

  return OK;
}

/****************************************************************************
 * Name: tiva_qe_reset
 *
 * Description:
 *   Reset the position measurement to zero.
 *
 * Input Parameters:
 *   lower - A reference to the lower half QEI driver state structure
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int tiva_qe_reset(struct qe_lowerhalf_s *lower)
{
  struct tiva_qe_s *qe = (struct tiva_qe_s *)lower;

  sninfo("reset QEI %d\n", qe->id);

  tiva_qe_putreg(qe, TIVA_QEI_POS_OFFSET, 0);

  return OK;
}

/****************************************************************************
 * Name: tiva_qe_position
 *
 * Description:
 *   Return the position measured by QEI.
 *
 * Input Parameters:
 *   lower - A reference to the lower half QEI driver state structure
 *   pos - pointer to the position returned
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int tiva_qe_position(struct qe_lowerhalf_s *lower,
                            int32_t *pos)
{
  struct tiva_qe_s *qe = (struct tiva_qe_s *)lower;

  sninfo("get position of QEI %d\n", qe->id);

  *pos = (int32_t) tiva_qe_getreg(qe, TIVA_QEI_POS_OFFSET);

  return OK;
}

/****************************************************************************
 * Name: tiva_qe_ioctl
 *
 * Description:
 *   Lower-half logic may support platform-specific ioctl commands
 *
 * Input Parameters:
 *   lower - A reference to the lower half QEI driver state structure
 *   cmd - The ioctl command
 *   arg - The argument accompanying the ioctl command
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int tiva_qe_ioctl(struct qe_lowerhalf_s *lower, int cmd,
                         unsigned long arg)
{
  struct tiva_qe_s *qe = (struct tiva_qe_s *)lower;

  sninfo("ioctl QEI %d\n", qe->id);

  switch (cmd)
    {
    case QEIOC_DIRECTION:
      tiva_qe_direction(qe, (unsigned long *)arg);
      break;

    case QEIOC_VELOCITY:
      tiva_qe_velocity(qe, (unsigned long *)arg);
      break;

    case QEIOC_RESETATPPR:
      tiva_qe_resetatppr(qe, arg);
      break;

    case QEIOC_RESETATMAXPOS:
      tiva_qe_resetatmaxpos(qe, arg);
      break;

    case QEIOC_RESETATINDEX:
      tiva_qe_resetatindex(qe);
      break;

    default:
      snerr("ERROR: invalid cmd %x\n", cmd);
      break;
    }

  return OK;
}

/****************************************************************************
 * Name: tiva_qe_direction
 *
 * Description:
 *   Return the direction measured by QEI.
 *
 * Input Parameters:
 *   qe - A reference to the TIVA QEI structure
 *   dir - pointer to the direction returned
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int tiva_qe_direction(struct tiva_qe_s *qe, unsigned long *dir)
{
  sninfo("get direction of QEI %d\n", qe->id);

  uint32_t statreg;
  statreg = tiva_qe_getreg(qe, TIVA_QEI_STAT_OFFSET);

  int32_t dirbit;
  dirbit =
    (statreg & (1 << TIVA_QEI_STAT_DIRECTION)) == DIRECTION_FORWARD ? 1 : -1;

  *dir = dirbit;

  return OK;
}

/****************************************************************************
 * Name: tiva_qe_direction
 *
 * Description:
 *   Return the velocity (A/B pulses per second) measured by QEI.
 *
 * Input Parameters:
 *   qe - A reference to the TIVA QEI structure
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int tiva_qe_velocity(struct tiva_qe_s *qe, unsigned long *vel)
{
  sninfo("get direction of QEI %d\n", qe->id);

  *vel = (int32_t) tiva_qe_getreg(qe, TIVA_QEI_SPEED_OFFSET) * 100 / 4;

  return OK;
}

/****************************************************************************
 * Name: tiva_qe_resetatppr
 *
 * Description:
 *   Set reset mode as MAXPOS and set maxpos value to number of pulses
 *   per round of encoder. Note that this function multiplies the given
 *   ppr by 4 because capture mode is PHA_AND_PHB, meaning we count 4
 *   edges per "pulse."
 *
 * Input Parameters:
 *   qe - A reference to the TIVA QEI structure
 *   ppr - pulses per round of encoder
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int tiva_qe_resetatppr(struct tiva_qe_s *qe, unsigned long ppr)
{
  /* maxpos is 4 times of ppr since we set capture mode as PHA_AND_PHB */

  return tiva_qe_resetatmaxpos(qe, ppr * 4);
}

/****************************************************************************
 * Name: tiva_qe_resetatmaxpos
 *
 * Description:
 *   Set reset mode as MAXPOS and set maxpos value as given.
 *
 * Input Parameters:
 *   qe - A reference to the TIVA QEI structure
 *   maxpos - Maximum position count at which QEI resets. To get the full
 *            range, give UINT32_MAX.
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int tiva_qe_resetatmaxpos(struct tiva_qe_s *qe,
                                 unsigned long maxpos)
{
  sninfo("set maxpos reset mode and maxpos value of QEI %d\n", qe->id);

  struct qe_lowerhalf_s *lower;
  uint32_t ctlreg = 0;

  /* Disable the QEI */

  lower = (struct qe_lowerhalf_s *)qe;
  tiva_qe_shutdown(lower);

  qe->maxpos = maxpos;

  /* Set reset mode as MAXPOS */

  ctlreg = tiva_qe_getreg(qe, TIVA_QEI_CTL_OFFSET);
  ctlreg &= ~(uint32_t)(1 << TIVA_QEI_CTL_RESMODE);
  ctlreg |= RESMODE_BY_MAXPOS << TIVA_QEI_CTL_RESMODE;
  tiva_qe_putreg(qe, TIVA_QEI_CTL_OFFSET, ctlreg);

  /* Set maxpos value */

  tiva_qe_putreg(qe, TIVA_QEI_MAXPOS_OFFSET, qe->maxpos);

  /* Enable the QEI */

  ctlreg = tiva_qe_getreg(qe, TIVA_QEI_CTL_OFFSET);
  ctlreg |= QEI_ENABLE << TIVA_QEI_CTL_ENABLE;
  tiva_qe_putreg(qe, TIVA_QEI_CTL_OFFSET, ctlreg);

  return OK;
}

/****************************************************************************
 * Name: tiva_qe_resetatindex
 *
 * Description:
 *   Set reset mode as INDEX
 *
 * Input Parameters:
 *   qe - A reference to the TIVA QEI structure
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int tiva_qe_resetatindex(struct tiva_qe_s *qe)
{
  sninfo("set index reset mode of QEI %d\n", qe->id);

  struct qe_lowerhalf_s *lower;
  uint32_t ctlreg = 0;

  /* Disable the QEI */

  lower = (struct qe_lowerhalf_s *)qe;
  tiva_qe_shutdown(lower);

  /* Set reset mode as INDEX */

  ctlreg = tiva_qe_getreg(qe, TIVA_QEI_CTL_OFFSET);
  ctlreg &= ~(uint32_t)(1 << TIVA_QEI_CTL_RESMODE);
  ctlreg |= RESMODE_BY_INDEX_PULSE << TIVA_QEI_CTL_RESMODE;
  tiva_qe_putreg(qe, TIVA_QEI_CTL_OFFSET, ctlreg);

  /* Enable the QEI */

  ctlreg = tiva_qe_getreg(qe, TIVA_QEI_CTL_OFFSET);
  ctlreg |= QEI_ENABLE << TIVA_QEI_CTL_ENABLE;
  tiva_qe_putreg(qe, TIVA_QEI_CTL_OFFSET, ctlreg);

  return OK;
}

/****************************************************************************
 * Name: tiva_qei_initialize
 *
 * Description:
 *   Enable power and clock for quadrature encoder interface. This function
 *   must be called from board-specific logic.
 *
 * Input Parameters:
 *   id - A number identifying certain QEI.
 *
 * Returned Value:
 *   On success, a pointer to the lower half QEI driver is returned.
 *   NULL is returned on any failure.
 *
 ****************************************************************************/

struct qe_lowerhalf_s *tiva_qei_initialize(int id)
{
  struct tiva_qe_s *qe;
  struct qe_lowerhalf_s *lower;

  switch (id)
    {
#ifdef CONFIG_TIVA_QEI0
    case 0:
      qe = &g_qe0;
      break;
#endif

#ifdef CONFIG_TIVA_QEI1
    case 1:
      qe = &g_qe1;
      break;
#endif

    default:
      snerr("ERROR: invalid QEI %d\n", id);
      return NULL;
    }

  /* Enable QEI clock */

  tiva_qei_enablepwr(qe->id);
  tiva_qei_enableclk(qe->id);

  /* Make sure that the QEI enable bit has been cleared */

  lower = (struct qe_lowerhalf_s *)qe;
  tiva_qe_shutdown(lower);

  return lower;
}