/****************************************************************************
 * drivers/sensors/ads1242.c
 * Character driver for the MCP3426 Differential Input 16 Bit Delta/Sigma ADC
 *
 *   Copyright (C) 2015 DS-Automotion GmbH. All rights reserved.
 *   Author: Alexander Entinger <a.entinger@ds-automotion.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 <nuttx/config.h>

#include <stdlib.h>
#include <stdbool.h>
#include <fixedmath.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/spi/spi.h>

#include <nuttx/analog/ads1242.h>

#if defined(CONFIG_SPI) && defined(CONFIG_ADC_ADS1242)

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

struct ads1242_dev_s
{
  FAR struct spi_dev_s *spi;  /* Pointer to the SPI instance */
  uint32_t osc_period_us;     /* Period of the oscillator attached to the ADS1242 in us */
};

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

/* SPI Helpers */

static void    ads1242_reset(FAR struct ads1242_dev_s *dev);
static void    ads1242_performSelfGainCalibration(
                 FAR struct ads1242_dev_s *dev);
static void    ads1242_performSelfOffsetCalibration(
                 FAR struct ads1242_dev_s *dev);
static void    ads1242_performSystemOffsetCalibration(
                 FAR struct ads1242_dev_s *dev);
static void    ads1242_read_conversion_result(FAR struct ads1242_dev_s *dev,
                 FAR uint32_t *conversion_result);

static void    ads1242_write_reg(FAR struct ads1242_dev_s *dev,
                 uint8_t const reg_addr, uint8_t const reg_value);
static void    ads1242_read_reg(FAR struct ads1242_dev_s *dev,
                 uint8_t const reg_addr, FAR uint8_t *reg_value);

static void    ads1242_set_gain(FAR struct ads1242_dev_s *dev,
                 ADS1242_GAIN_SELECTION const gain_selection);
static void    ads1242_set_positive_input(FAR struct ads1242_dev_s *dev,
                 ADS1242_POSITIVE_INPUT_SELECTION const pos_in_sel);
static void    ads1242_set_negative_input(FAR struct ads1242_dev_s *dev,
                 ADS1242_NEGATIVE_INPUT_SELECTION const neg_in_sel);
static bool    ads1242_is_data_ready(FAR struct ads1242_dev_s *dev);

#if defined(CONFIG_DEBUG) && defined(CONFIG_DEBUG_VERBOSE)
static void    ads1242_print_regs(FAR struct ads1242_dev_s *dev, char const *msg);
#endif /* CONFIG_DEBUG && CONFIG_DEBUG_VERBOSE */

/* Character driver methods */

static int     ads1242_open(FAR struct file *filep);
static int     ads1242_close(FAR struct file *filep);
static ssize_t ads1242_read(FAR struct file *, FAR char *, size_t);
static ssize_t ads1242_write(FAR struct file *filep, FAR const char *buffer,
                 size_t buflen);
static int     ads1242_ioctl(FAR struct file *filep, int cmd,
                 unsigned long arg);

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

static const struct file_operations g_ads1242_fops =
{
  ads1242_open,
  ads1242_close,
  ads1242_read,
  ads1242_write,
  NULL,
  ads1242_ioctl
#ifndef CONFIG_DISABLE_POLL
  , NULL
#endif
};

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

/****************************************************************************
 * Name: ads1242_reset
 ****************************************************************************/

static void ads1242_reset(FAR struct ads1242_dev_s *dev)
{
  SPI_SELECT(dev->spi, 0, true);        /* Set nADC_SPI_CS to low which selects the ADS1242 */

  SPI_SEND(dev->spi, ADS1242_CMD_RESET);/* Issue reset command */

  SPI_SELECT(dev->spi, 0, false);       /* Set nADC_SPI_CS to high which deselects the ADS1242 */

  up_mdelay(100);                       /* Wait a little so the device has time to perform a proper reset */
}

/****************************************************************************
 * Name: ads1242_performSelfGainCalibration
 ****************************************************************************/

static void ads1242_performSelfGainCalibration(FAR struct ads1242_dev_s *dev)
{
  SPI_SELECT(dev->spi, 0, true);

  SPI_SEND(dev->spi, ADS1242_CMD_SELF_GAIN_CALIB);

  SPI_SELECT(dev->spi, 0, false);
}

/****************************************************************************
 * Name: ads1242_performSelfOffsetCalibration
 ****************************************************************************/

static void ads1242_performSelfOffsetCalibration(FAR struct ads1242_dev_s *dev)
{
  SPI_SELECT(dev->spi, 0, true);

  SPI_SEND(dev->spi, ADS1242_CMD_SELF_OFFSET_CALIB);

  SPI_SELECT(dev->spi, 0, false);
}

/****************************************************************************
 * Name: ads1242_performSystemOffsetCalibration
 ****************************************************************************/

static void
ads1242_performSystemOffsetCalibration(FAR struct ads1242_dev_s *dev)
{
  SPI_SELECT(dev->spi, 0, true);

  SPI_SEND(dev->spi, ADS1242_CMD_SYSTEM_OFFSET_CALIB);

  SPI_SELECT(dev->spi, 0, false);
}

/****************************************************************************
 * Name: ads1242_read_conversion_result
 ****************************************************************************/

static void ads1242_read_conversion_result(FAR struct ads1242_dev_s *dev,
                                           FAR uint32_t *conversion_result)
{
  SPI_SELECT(dev->spi, 0, true);

  SPI_SEND(dev->spi, ADS1242_CMD_READ_DATA);

  /* Delay between last SCLK edge for DIN and first SCLK edge for DOUT:
   * RDATA, RDATAC, RREG, WREG: Min 50 x tOSC Periods
   */

  up_udelay(50 * dev->osc_period_us);

  *conversion_result = 0;

  /* 1st Byte = MSB
   * 2nd Byte = Mid-Byte
   * 3rd Byte = LSB
   */

   *conversion_result |= ((uint32_t)(SPI_SEND(dev->spi, 0xFF))) << 16;
   *conversion_result |= ((uint32_t)(SPI_SEND(dev->spi, 0xFF))) << 8;
   *conversion_result |= ((uint32_t)(SPI_SEND(dev->spi, 0xFF))) << 0;

  SPI_SELECT(dev->spi, 0, false);
}

/****************************************************************************
 * Name: ads1242_write_reg
 *
 * Description:
 *    Write to the registers starting with the register address specified as
 *    part of the instruction. The number of registers that will be written
 *    is one plus the value of the second byte.
 *
 ****************************************************************************/

static void ads1242_write_reg(FAR struct ads1242_dev_s *dev,
                              uint8_t const reg_addr, uint8_t const reg_value)
{
  SPI_SELECT(dev->spi, 0, true);
  SPI_SEND(dev->spi, ADS1242_CMD_WRITE_REGISTER | reg_addr);
  SPI_SEND(dev->spi, 0x00); /* Write 1 Byte */
  SPI_SEND(dev->spi, reg_value);
  SPI_SELECT(dev->spi, 0, false);
}

/****************************************************************************
 * Name: ads1242_read_reg
 *
 * Description:
 *   Output the data from up to 16 registers starting with the register
 *   address specified as part of the instruction.  The number of registers
 *   read will be one plus the second byte count. If the count exceeds the
 *   remaining registers, the addresses wrap back to the beginning.
 *
 ****************************************************************************/

static void ads1242_read_reg(FAR struct ads1242_dev_s *dev,
                             uint8_t const reg_addr, FAR uint8_t *reg_value)
{
  SPI_SELECT(dev->spi, 0, true);
  SPI_SEND(dev->spi, ADS1242_CMD_READ_REGISTER | reg_addr);
  SPI_SEND(dev->spi, 0x00);  /* Read 1 Byte */

  /* Delay between last SCLK edge for DIN and first SCLK edge for DOUT:
   * RDATA, RDATAC, RREG, WREG: Min 50 x tOSC Periods
   */

  up_udelay(50 * dev->osc_period_us);

  *reg_value = SPI_SEND(dev->spi, 0xFF);

  SPI_SELECT(dev->spi, 0, false);
}

/****************************************************************************
 * Name: ads1242_set_gain
 ****************************************************************************/

static void ads1242_set_gain(FAR struct ads1242_dev_s *dev,
                             ADS1242_GAIN_SELECTION const gain_selection)
{
  uint8_t setup_reg_value = 0;

  ads1242_read_reg(dev, ADS1242_REG_SETUP, &setup_reg_value);
  setup_reg_value &= ~(ADS1242_REG_SETUP_BIT_PGA2 |
                       ADS1242_REG_SETUP_BIT_PGA1 |
                       ADS1242_REG_SETUP_BIT_PGA0);
  setup_reg_value |= (uint8_t)(gain_selection);
  ads1242_write_reg(dev, ADS1242_REG_SETUP, setup_reg_value);

#if defined(CONFIG_DEBUG) && defined(CONFIG_DEBUG_VERBOSE)
  ads1242_print_regs(dev, "ads1242_set_gain");
#endif

  /* It is necessary to perform a offset calibration after setting the gain */

  ads1242_performSelfOffsetCalibration(dev);
}

/****************************************************************************
 * Name: ads1242_set_positive_input
 ****************************************************************************/

static void ads1242_set_positive_input(FAR struct ads1242_dev_s *dev,
                                       ADS1242_POSITIVE_INPUT_SELECTION const pos_in_sel)
{
  uint8_t mux_reg_value = 0;

  ads1242_read_reg(dev, ADS1242_REG_MUX, &mux_reg_value);
  mux_reg_value &= ~(ADS1242_REG_MUX_BIT_PSEL3 | ADS1242_REG_MUX_BIT_PSEL2 |
                     ADS1242_REG_MUX_BIT_PSEL1 | ADS1242_REG_MUX_BIT_PSEL0);
  mux_reg_value |= (uint8_t)(pos_in_sel);
  ads1242_write_reg(dev, ADS1242_REG_MUX, mux_reg_value);

#if defined(CONFIG_DEBUG) && defined(CONFIG_DEBUG_VERBOSE)
  ads1242_print_regs(dev, "ads1242_set_positive_input");
#endif
}

/****************************************************************************
 * Name: ads1242_set_negative_input
 ****************************************************************************/

static void ads1242_set_negative_input(FAR struct ads1242_dev_s *dev,
                                       ADS1242_NEGATIVE_INPUT_SELECTION const neg_in_sel)
{
  uint8_t mux_reg_value = 0;

  ads1242_read_reg(dev, ADS1242_REG_MUX, &mux_reg_value);
  mux_reg_value &= ~(ADS1242_REG_MUX_BIT_NSEL3 | ADS1242_REG_MUX_BIT_NSEL2 |
                     ADS1242_REG_MUX_BIT_NSEL1 | ADS1242_REG_MUX_BIT_NSEL0);
  mux_reg_value |= (uint8_t)(neg_in_sel);
  ads1242_write_reg(dev, ADS1242_REG_MUX, mux_reg_value);

#if defined(CONFIG_DEBUG) && defined(CONFIG_DEBUG_VERBOSE)
  ads1242_print_regs(dev, "ads1242_set_negative_input");
#endif
}

/****************************************************************************
 * Name: ads1242_set_negative_input
 ****************************************************************************/

static bool ads1242_is_data_ready(FAR struct ads1242_dev_s *dev)
{
  uint8_t acr_reg_value = 0xFF;

  ads1242_read_reg(dev, ADS1242_REG_ACR, &acr_reg_value);
  return (acr_reg_value & ADS1242_REG_ACR_BIT_nDRDY) == 0;
}

/****************************************************************************
 * Name: ads1242_print_regs
 ****************************************************************************/

#if defined(CONFIG_DEBUG) && defined(CONFIG_DEBUG_VERBOSE)
static void ads1242_print_regs(FAR struct ads1242_dev_s *dev, char const *msg)
{
 uint8_t setup_reg_value = 0;
 uint8_t mux_reg_value   = 0;
 uint8_t acr_reg_value   = 0;

 dbg("%s\n", msg);

 ads1242_read_reg(dev, ADS1242_REG_SETUP, &setup_reg_value);
 ads1242_read_reg(dev, ADS1242_REG_MUX, &mux_reg_value);
 ads1242_read_reg(dev, ADS1242_REG_ACR, &acr_reg_value);

 dbg("SETUP  %02X\n", setup_reg_value);
 dbg("MUX    %02X\n", mux_reg_value);
 dbg("ACR    %02X\n", acr_reg_value);
}
#endif /* CONFIG_DEBUG && CONFIG_DEBUG_VERBOSE */

/****************************************************************************
 * Name: ads1242_open
 ****************************************************************************/

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

  ads1242_reset(priv);
  up_mdelay(100);

  ads1242_performSelfGainCalibration(priv);
  up_mdelay(100);

  /* SPEED = 1 -> fMod = fOsc / 256 (fMod = Modulator Clock Speed)
   * BUFEN = 1 -> Internal input buffer enabled -> results in a very high
   *              impedance input for the ADC ~ 5 GOhm
   */

  ads1242_write_reg(priv, ADS1242_REG_ACR,
                    ADS1242_REG_ACR_BIT_SPEED | ADS1242_REG_ACR_BIT_BUFEN);

  ads1242_performSelfOffsetCalibration(priv);
  up_mdelay(100);

#if defined(CONFIG_DEBUG) && defined(CONFIG_DEBUG_VERBOSE)
  ads1242_print_regs(priv, "ads1242_open");
#endif

  return OK;
}
/****************************************************************************
 * Name: ads1242_close
 ****************************************************************************/

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

  ads1242_reset(priv);
  up_mdelay(100);

  return OK;
}

/****************************************************************************
 * Name: ads1242_read
 ****************************************************************************/

static ssize_t ads1242_read(FAR struct file *filep,
                            FAR char *buffer, size_t buflen)
{
  return -ENOSYS;
}

/****************************************************************************
 * Name: ads1242_write
 ****************************************************************************/

static ssize_t ads1242_write(FAR struct file *filep,
                             FAR const char *buffer, size_t buflen)
{
  return -ENOSYS;
}

/****************************************************************************
 * Name: ads1242_ioctl
 ****************************************************************************/

static int ads1242_ioctl (FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct ads1242_dev_s *priv = inode->i_private;

  int ret = OK;

  switch (cmd)
    {
    /* Read the result of an analog conversion */

    case ANIOC_ADS2142_READ:
      {
          FAR uint32_t *data = (FAR uint32_t *)((uintptr_t)arg);
          ads1242_read_conversion_result(priv, data);
      }
      break;

      /* Set the gain of the ADC */

    case ANIOC_ADS2142_SET_GAIN:
      {
        ads1242_set_gain(priv, (ADS1242_GAIN_SELECTION)(arg));
      }
      break;

      /* Set the positive input of the ADC */

    case ANIOC_ADS2142_SET_POSITIVE_INPUT:
      {
        ads1242_set_positive_input(priv,
                                   (ADS1242_POSITIVE_INPUT_SELECTION)(arg));
      }
      break;

      /* Set the negative input of the ADC */

    case ANIOC_ADS2142_SET_NEGATIVE_INPUT:
      {
        ads1242_set_negative_input(priv,
                                   (ADS1242_NEGATIVE_INPUT_SELECTION)(arg));
      }
      break;

      /* Check if data is ready to be read */

    case ANIOC_ADS2142_IS_DATA_READY:
      {
        FAR bool *is_data_ready = (FAR bool *)((uintptr_t)arg);
        *is_data_ready = ads1242_is_data_ready(priv);
      }
      break;

      /* Perform a system offset calibration - Note: Zero input signal must
       * be applied.
       */

    case ANIOC_ADS2142_DO_SYSTEM_OFFSET_CALIB:
      {
        ads1242_performSystemOffsetCalibration(priv);
      }
      break;

      /* Command was not recognized */

    default:
      dbg ("Unrecognized cmd: %d\n", cmd);
      ret = -ENOTTY;
      break;
    }

  return ret;
}

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

/****************************************************************************
 * Name: ads1242_register
 *
 * Description:
 *   Register the ADS1242 character device as 'devpath'
 *
 * Input Parameters:
 *   devpath - The full path to the driver to register. E.g., "/dev/ads1242"
 *   spi - An instance of the SPI interface to use to communicate with ADS1242
 *   osc_freq_hz - The frequency of the ADS1242 oscillator in Hz. Required for
 *   calculating the minimum delay periods when accessing the device via SPI.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno value on failure.
 *
 ****************************************************************************/

int ads1242_register(FAR const char *devpath, FAR struct spi_dev_s *spi,
                     uint32_t const osc_freq_hz)
{
  FAR struct ads1242_dev_s *priv;
  int ret;

  /* Sanity check */

  DEBUGASSERT(spi != NULL);

  /* Initialize the ADS1242 device structure */

  priv = (FAR struct ads1242_dev_s *)kmm_malloc(sizeof(struct ads1242_dev_s));
  if (priv == NULL)
    {
      dbg ("Failed to allocate instance\n");
      return -ENOMEM;
    }

  priv->spi = spi;

  float const osc_period_us = (1000.0 * 1000.0) / ((float)(osc_freq_hz));
  priv->osc_period_us = (uint32_t)(osc_period_us);

  /* Register the character driver */

  ret = register_driver(devpath, &g_ads1242_fops, 0666, priv);
  if (ret < 0)
    {
      dbg ("Failed to register driver: %d\n", ret);
      kmm_free(priv);
    }

  /* setup SPI frequency */

  SPI_SETFREQUENCY(spi, ADS1242_SPI_FREQUENCY);

  /* Setup SPI mode */

  SPI_SETMODE(spi, ADS1242_SPI_MODE);

  return ret;
}

#endif /* CONFIG_SPI && CONFIG_ADC_ADS1242 */
