/****************************************************************************
 * drivers/sensors/adxl372.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>

#if defined(CONFIG_SPI) && defined(CONFIG_SENSORS_ADXL372) \
    && defined(CONFIG_SPI_EXCHANGE)

#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <string.h>

#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/mutex.h>
#include <nuttx/sensors/adxl372.h>

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

#define ADXL372_INITIAL_CR_SIZE 7

/****************************************************************************
 * Private structure definitions
 ****************************************************************************/

struct sensor_data_s
{
  int16_t x_gyr;                       /* Measurement result for x axis */
  int16_t y_gyr;                       /* Measurement result for y axis */
  int16_t z_gyr;                       /* Measurement result for z axis */
};

struct adxl372_dev_s
{
  FAR struct adxl372_dev_s *flink;     /* Supports a singly linked list of
                                        * drivers */
  FAR struct spi_dev_s *spi;           /* Pointer to the SPI instance */
  FAR struct adxl372_config_s *config; /* Pointer to the configuration of the
                                        * ADXL372 sensor */
  mutex_t devicelock;                  /* Manages exclusive access to this
                                        * device */
  struct sensor_data_s data;           /* The data as measured by the sensor */
  uint8_t seek_address;                /* Current device address. */
  uint8_t readonly;                    /* 0 = writing to the device in enabled */
};

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

static uint8_t  adxl372_read_register(FAR struct adxl372_dev_s *dev,
                                     uint8_t reg_addr);
static void     adxl372_read_registerblk(FAR struct adxl372_dev_s *dev,
                                    uint8_t reg_addr,
                                    FAR uint8_t *reg_data,
                                    uint8_t xfercnt);
static void     adxl372_write_register(FAR struct adxl372_dev_s *dev,
                                    uint8_t reg_addr,
                                    uint8_t reg_data);
static void     adxl372_write_registerblk(FAR struct adxl372_dev_s *dev,
                                    uint8_t reg_addr,
                                    FAR uint8_t *reg_data,
                                    uint8_t xfercnt);
static void     adxl372_reset(FAR struct adxl372_dev_s *dev);

static int      adxl372_open(FAR struct file *filep);
static int      adxl372_close(FAR struct file *filep);
static ssize_t  adxl372_read(FAR struct file *, FAR char *, size_t);
static ssize_t  adxl372_write(FAR struct file *filep,
                              FAR const char *buffer, size_t buflen);
static off_t    adxl372_seek(FAR struct file *filep, off_t offset,
                             int whence);
static int      adxl372_ioctl(FAR struct file *filep, int cmd,
                              unsigned long arg);

static int      adxl372_dvr_open(FAR void *instance, int32_t arg);
static int      adxl372_dvr_close(FAR void *instance, int32_t arg);
static ssize_t  adxl372_dvr_read(FAR void *instance,
                                 FAR char *buffer, size_t buflen);
static ssize_t  adxl372_dvr_write(FAR void *instance,
                                  FAR const char *buffer, size_t buflen);
static off_t    adxl372_dvr_seek(FAR void *instance, off_t offset,
                                 int whence);
static int      adxl372_dvr_ioctl(FAR void *instance, int cmd,
                                  unsigned long arg);
static void     adxl372_dvr_exchange(FAR void *instance,
                                     FAR const void *txbuffer,
                                     FAR void *rxbuffer, size_t nwords);

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

static const struct file_operations g_adxl372_fops =
{
  adxl372_open,    /* open */
  adxl372_close,   /* close */
  adxl372_read,    /* read */
  adxl372_write,   /* write */
  adxl372_seek,    /* seek */
  adxl372_ioctl,   /* ioctl */
};

static const struct adxl372_dvr_entry_vector_s g_adxl372_dops =
{
  /* Standard sensor cluster driver entry-vector */

  {
    .driver_open    = adxl372_dvr_open,
    .driver_close   = adxl372_dvr_close,
    .driver_read    = adxl372_dvr_read,
    .driver_write   = adxl372_dvr_write,
    .driver_seek    = adxl372_dvr_seek,
    .driver_ioctl   = adxl372_dvr_ioctl,
    .driver_suspend = 0,
    .driver_resume  = 0,
    },

  /* adxl372 extensions follow */

  .driver_spiexc = adxl372_dvr_exchange,
};

/****************************************************************************
 * Private data storage
 ****************************************************************************/

/* Single linked list to store instances of drivers */

static struct adxl372_dev_s *g_adxl372_list = NULL;

/* Default accelerometer initialization sequence */

/* Configure ADXL372 to read live data (not using FIFO).
 * 1. Set to standby mode. The below can't be set while running.
 * 2. Configure the FIFO to be bypassed.
 * 3. Configure interrupts as disabled, because ADXL372 irpts are used.
 * 4. Configure the Output Data Rate (ODR) as 1600 Hz.
 * 5. Configure normal mode (vs low noise) and 800Hz bandwidth.
 * 6. Set to operational mode; 370ms filter settle; LPF=enb; HPF=dis;
 */

static struct adxl372_reg_pair_s g_initial_adxl372_cr_values[] =
{
  /* Set to standby mode */

  {
    .addr  = ADXL372_POWER_CTL,
    .value = 0
  },
  {
    .addr  = ADXL372_FIFO_CTL,
    .value = ADXL372_FIFO_BYPASSED
  },

  /* Interrupts disabled. */

  {
    .addr  = ADXL372_INT1_MAP,
    .value = 0
  },
  {
    .addr  = ADXL372_TIMING,
    .value = ADXL372_TIMING_ODR1600
  },
  {
    .addr  = ADXL372_MEASURE,
    .value = ADXL372_MEAS_BW800
  },
  {
    .addr  = ADXL372_POWER_CTL,
    .value = ADXL372_POWER_HPF_DISABLE | ADXL372_POWER_MODE_MEASURE
  }
};

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

/****************************************************************************
 * Name: adxl372_read_register
 ****************************************************************************/

static uint8_t adxl372_read_register(FAR struct adxl372_dev_s *dev,
                                     uint8_t reg_addr)
{
  uint8_t reg_data;

  /* Lock the SPI bus so that only one device can access it
   * at the same time
   */

  SPI_LOCK(dev->spi, true);

  SPI_SETFREQUENCY(dev->spi, ADXL372_SPI_FREQUENCY);
  SPI_SETMODE(dev->spi, ADXL372_SPI_MODE);

  /* Set CS to low to select the ADXL372 */

  SPI_SELECT(dev->spi, dev->config->spi_devid, true);

  /* Transmit the register address from where we want to read. */

  SPI_SEND(dev->spi, (reg_addr << 1) | ADXL372_READ);

  /* Write an idle byte while receiving the requested data */

  reg_data = (uint8_t) (SPI_SEND(dev->spi, 0xff));

  /* Set CS to high to deselect the ADXL372 */

  SPI_SELECT(dev->spi, dev->config->spi_devid, false);

  /* Unlock the SPI bus */

  SPI_LOCK(dev->spi, false);

  return reg_data;
}

/****************************************************************************
 * Name: adxl372_read_registerblk
 ****************************************************************************/

static void adxl372_read_registerblk(FAR struct adxl372_dev_s *dev,
                                     uint8_t reg_addr,
                                     FAR uint8_t *reg_data,
                                     uint8_t xfercnt)
{
  /* Lock the SPI bus so that only one device can access it
   * at the same time
   */

  SPI_LOCK(dev->spi, true);

  SPI_SETFREQUENCY(dev->spi, ADXL372_SPI_FREQUENCY);
  SPI_SETMODE(dev->spi, ADXL372_SPI_MODE);

  /* Set CS to low to select the ADXL372 */

  SPI_SELECT(dev->spi, dev->config->spi_devid, true);

  /* Transmit the register address from where we want to start reading */

  SPI_SEND(dev->spi, (reg_addr << 1) | ADXL372_READ);

  /* Write idle bytes while receiving the requested data */

  while (0 != xfercnt--)
    {
      *reg_data++ = (uint8_t)SPI_SEND(dev->spi, 0xff);
    }

  /* Set CS to high to deselect the ADXL372 */

  SPI_SELECT(dev->spi, dev->config->spi_devid, false);

  /* Unlock the SPI bus */

  SPI_LOCK(dev->spi, false);
}

/****************************************************************************
 * Name: adxl372_write_register
 ****************************************************************************/

static void adxl372_write_register(FAR struct adxl372_dev_s *dev,
                                   uint8_t reg_addr, uint8_t reg_data)
{
  /* Lock the SPI bus so that only one device can access it
   * at the same time
   */

  SPI_LOCK(dev->spi, true);

  SPI_SETFREQUENCY(dev->spi, ADXL372_SPI_FREQUENCY);
  SPI_SETMODE(dev->spi, ADXL372_SPI_MODE);

  /* Set CS to low to select the ADXL372 */

  SPI_SELECT(dev->spi, dev->config->spi_devid, true);

  /* Transmit the register address to where we want to write */

  SPI_SEND(dev->spi, (reg_addr << 1) | ADXL372_WRITE);

  /* Transmit the content which should be written into the register */

  SPI_SEND(dev->spi, reg_data);

  /* Set CS to high to deselect the ADXL372 */

  SPI_SELECT(dev->spi, dev->config->spi_devid, false);

  /* Unlock the SPI bus */

  SPI_LOCK(dev->spi, false);
}

/****************************************************************************
 * Name: adxl372_write_registerblk
 ****************************************************************************/

static void adxl372_write_registerblk(FAR struct adxl372_dev_s *dev,
                                      uint8_t reg_addr,
                                      FAR uint8_t *reg_data,
                                      uint8_t xfercnt)
{
  /* Lock the SPI bus so that only one device can access it
   * at the same time
   */

  SPI_LOCK(dev->spi, true);

  SPI_SETFREQUENCY(dev->spi, ADXL372_SPI_FREQUENCY);
  SPI_SETMODE(dev->spi, ADXL372_SPI_MODE);

  /* Set CS to low which selects the ADXL372 */

  SPI_SELECT(dev->spi, dev->config->spi_devid, true);

  /* Transmit the register address to where we want to start writing */

  SPI_SEND(dev->spi, (reg_addr << 1) | ADXL372_WRITE);

  /* Transmit the content which should be written in the register block */

  while (0 != xfercnt--)
    {
      SPI_SEND(dev->spi, *reg_data++);
    }

  /* Set CS to high to deselect the ADXL372 */

  SPI_SELECT(dev->spi, dev->config->spi_devid, false);

  /* Unlock the SPI bus */

  SPI_LOCK(dev->spi, false);
}

/****************************************************************************
 * Name: adxl372_reset
 *
 * Description:
 *   ADXL Accelerometer Reset
 *   1. Make sure that a reset is not in progress.
 *   2. Write ADXL372_RESET_VALUE (0x52) to ADXL372_RESET register.
 *   3. Wait for the reset to finish.
 *
 ****************************************************************************/

static void adxl372_reset(FAR struct adxl372_dev_s *dev)
{
  uint wdcnt = 10;

  /* Wait for boot to finish (15 ms error timeout) */

  up_mdelay(5);
  while (wdcnt > 0 && (0 != adxl372_read_register(dev, ADXL372_RESET)))
    {
      up_mdelay(1);
    }

  /* Reset ADXL372 Accelerometer. Write only. Begin a boot. */

  adxl372_write_register(dev, ADXL372_RESET, ADXL372_RESET_VALUE);

  /* Wait for boot to finish (15 ms error timeout) */

  up_mdelay(5);
  wdcnt = 10;
  while (wdcnt > 0 && (0 != adxl372_read_register(dev, ADXL372_RESET)))
    {
      up_mdelay(1);
    }
}

/****************************************************************************
 * Name: adxl372_read_id
 *
 * Description:
 *
 *   Read the ADXL372 Accelerometer's ID Registers.
 *   There are 4 ID Registers...
 *
 *     Manufacturer should be ADXL372_DEVID_AD_VALUE (0xAD).
 *     Family should be ADXL372_DEVID_MST_VALUE (0x1D).
 *     Part ID should be ADXL372_PARTID_VALUE (0xFA, Octal 372)
 *     Revision is returned, but not expected to be checked.
 *     All of the above are returned as an uint32_t. Should be 0xAD1DFAxx.
 *
 ****************************************************************************/

static uint32_t adxl372_read_id(FAR struct adxl372_dev_s *dev)
{
  union
  {
    uint32_t adxl_devid32;
    uint8_t  adxl_devid[4];
  } un;

  un.adxl_devid[3] = adxl372_read_register(dev, ADXL372_DEVID_AD);
  un.adxl_devid[2] = adxl372_read_register(dev, ADXL372_DEVID_MST);
  un.adxl_devid[1] = adxl372_read_register(dev, ADXL372_PARTID);
  un.adxl_devid[0] = adxl372_read_register(dev, ADXL372_REVID);
  return un.adxl_devid32;
}

/****************************************************************************
 * Name: adxl372_dvr_open
 ****************************************************************************/

static int adxl372_dvr_open(FAR void *instance, int32_t arg)
{
  FAR struct adxl372_dev_s *priv = (FAR struct adxl372_dev_s *)instance;
  FAR struct adxl372_reg_pair_s *initp;
  uint32_t pnpid;
  int sz;
  int ret;
  int i;

#ifdef CONFIG_DEBUG_SENSORS_INFO
  uint8_t reg_content;
#endif

  sninfo("adxl372_open: entered...\n");

  DEBUGASSERT(priv != NULL);
  UNUSED(arg);

  ret = nxmutex_trylock(&priv->devicelock);
  if (ret < 0)
    {
      sninfo("INFO: ADXL372 Accelerometer is already opened.\n");
      return -EBUSY;
    }

  /* Read the ID registers */

  pnpid = adxl372_read_id(priv);
  priv->readonly = false;

  sninfo("ADXL372_ID = 0x%08x\n", pnpid);

  if ((pnpid & 0xffffff00) != (ADXL372_DEVID_AD_VALUE << 24 |
                               ADXL372_DEVID_MST_VALUE << 16 |
                               ADXL372_PARTID_VALUE << 8))
    {
      snwarn("ERROR: Invalid ADXL372_ID = 0x%08x\n", pnpid);

      priv->readonly = true;
    }
  else /* ID matches */
    {
      adxl372_reset(priv);   /* Perform a sensor reset */

      /* Choose the initialization sequence */

      if (priv->config->initial_cr_values_size == 0 ||
          priv->config->initial_cr_values == NULL)
        {
          initp = g_initial_adxl372_cr_values;      /* Default values */
          sz = ADXL372_INITIAL_CR_SIZE;
        }
      else
        {
          initp = priv->config->initial_cr_values;  /* User supplied values */
          sz = priv->config->initial_cr_values_size;
        }

      /* Apply the initialization sequence */

      for (i = 0; i < sz; i++)
        {
          adxl372_write_register(priv, initp[i].addr, initp[i].value);
        }

#ifdef CONFIG_DEBUG_SENSORS_INFO
      /* Read back the content of all control registers for debug purposes */

      reg_content = adxl372_read_register(priv, ADXL372_FIFO_CTL);
      sninfo("ADXL372_FIFO_CTL = 0x%02x\n", reg_content);

      reg_content = adxl372_read_register(priv, ADXL372_INT1_MAP);
      sninfo("ADXL372_INT1_MAP = 0x%02x\n", reg_content);

      reg_content = adxl372_read_register(priv, ADXL372_TIMING);
      sninfo("ADXL372_TIMING = 0x%02x\n", reg_content);

      reg_content = adxl372_read_register(priv, ADXL372_MEASURE);
      sninfo("ADXL372_MEASURE = 0x%02x\n", reg_content);

      reg_content = adxl372_read_register(priv, ADXL372_POWER_CTL);
      sninfo("ADXL372_POWER_CTL = 0x%02x\n", reg_content);
#endif
    }

  priv->seek_address = (uint8_t) ADXL372_XDATA_H;
  return OK;
}

/****************************************************************************
 * Name: adxl372_dvr_close
 ****************************************************************************/

static int adxl372_dvr_close(FAR void *instance, int32_t arg)
{
  FAR struct adxl372_dev_s *priv = (FAR struct adxl372_dev_s *)instance;

  DEBUGASSERT(priv != NULL);
  UNUSED(arg);

  /* Perform a reset to place the sensor in standby mode. */

  adxl372_reset(priv);

  /* Release the sensor */

  nxmutex_unlock(&priv->devicelock);
  return OK;
}

/****************************************************************************
 * Name: adxl372_dvr_read
 ****************************************************************************/

static ssize_t adxl372_dvr_read(FAR void *instance, FAR char *buffer,
                                size_t buflen)
{
  FAR struct adxl372_dev_s *priv = (FAR struct adxl372_dev_s *)instance;
  union
  {
    int16_t d16;
    char    d8[2];
  } un;

  FAR char *p1;
  FAR char *p2;
  int i;

  DEBUGASSERT(priv != NULL);

  adxl372_read_registerblk(priv, priv->seek_address, (FAR uint8_t *)buffer,
                          buflen);

  /* Permute accelerometer data out fields */

  if (priv->seek_address == ADXL372_XDATA_H && buflen >= 6)
    {
      p1 = p2 = buffer;
      for (i = 0; i < 3; i++)
        {
          un.d8[1] = *p1++;
          un.d8[0] = *p1++;
          un.d16   = un.d16 >> 4;
          *p2++    = un.d8[0];
          *p2++    = un.d8[1];
        }
    }

  return buflen;
}

/****************************************************************************
 * Name: adxl372_dvr_write
 ****************************************************************************/

static ssize_t adxl372_dvr_write(FAR void *instance,
                                 FAR const char *buffer, size_t buflen)
{
  FAR struct adxl372_dev_s *priv = (FAR struct adxl372_dev_s *)instance;

  DEBUGASSERT(priv != NULL);

  if (priv->readonly)
    {
      return -EROFS;
    }

  adxl372_write_registerblk(priv, priv->seek_address, (FAR uint8_t *)buffer,
                            buflen);

  return buflen;
}

/****************************************************************************
 * Name: adxl372_dvr_seek
 ****************************************************************************/

static off_t adxl372_dvr_seek(FAR void *instance, off_t offset,
                              int whence)
{
  off_t reg;
  FAR struct adxl372_dev_s *priv = (FAR struct adxl372_dev_s *)instance;

  DEBUGASSERT(priv != NULL);

  switch (whence)
    {
      case SEEK_CUR:  /* Incremental seek */
        reg = priv->seek_address + offset;
        if (0 > reg || reg > ADXL372_LAST)
          {
            return -EINVAL;
          }

        priv->seek_address = reg;
        break;

      case SEEK_END:  /* Seek to the 1st X-data register */
        priv->seek_address = ADXL372_XDATA_H;
        break;

      case SEEK_SET:  /* Seek to designated address */
        if (0 > offset || offset > ADXL372_LAST)
          {
            return -EINVAL;
          }

        priv->seek_address = offset;
        break;

      default:        /* invalid whence */
        return -EINVAL;
    }

  return priv->seek_address;
}

/****************************************************************************
 * Name: adxl372_dvr_ioctl
 ****************************************************************************/

static int adxl372_dvr_ioctl(FAR void *instance, int cmd,
                             unsigned long arg)
{
  int ret = OK;

  switch (cmd)
    {
      /* Command was not recognized */

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

  return ret;
}

/****************************************************************************
 * Name: adxl372_dvr_exchange (with SPI DMA capability)
 *
 * Description:
 *   Exchange a block of data on SPI using DMA
 *
 * Input Parameters:
 *   instance - Pointer to struct adxl372_dev_s.
 *   txbuffer - A pointer to the buffer of data to be sent
 *   rxbuffer - A pointer to a buffer in which to receive data
 *   nwords   - the length of data to be exchanged in units of words.
 *              The wordsize is determined by the number of bits-per-word
 *              selected for the SPI interface.  If nbits <= 8, the data is
 *              packed into uint8_t's; if nbits >8, the data is packed into
 *              uint16_t's
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void adxl372_dvr_exchange(FAR void *instance,
                                 FAR const void *txbuffer,
                                 FAR void *rxbuffer, size_t nwords)
{
  FAR struct adxl372_dev_s *priv = (FAR struct adxl372_dev_s *)instance;
  FAR struct spi_dev_s *spi = priv->spi;

  /* Lock the SPI bus so that only one device can access it
   * at the same time
   */

  SPI_LOCK(spi, true);

  SPI_SETFREQUENCY(spi, ADXL372_SPI_FREQUENCY);
  SPI_SETMODE(spi, ADXL372_SPI_MODE);

  /* Set CS to low which selects the ADXL372 */

  SPI_SELECT(spi, priv->config->spi_devid, true);

  /* Perform an SPI exchange block operation. */

  SPI_EXCHANGE(spi, txbuffer, rxbuffer, nwords);

  /* Set CS to high to deselect the ADXL372 */

  SPI_SELECT(spi, priv->config->spi_devid, false);

  /* Unlock the SPI bus */

  SPI_LOCK(spi, false);
}

/****************************************************************************
 * Name: adxl372_open
 ****************************************************************************/

static int adxl372_open(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct adxl372_dev_s *priv = inode->i_private;
  int ret;

  ret = adxl372_dvr_open((FAR void *)priv, 0);
  return ret;
}

/****************************************************************************
 * Name: adxl372_close
 ****************************************************************************/

static int adxl372_close(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct adxl372_dev_s *priv = inode->i_private;
  int ret;

  ret = adxl372_dvr_close((FAR void *)priv, 0);
  return ret;
}

/****************************************************************************
 * Name: adxl372_read
 ****************************************************************************/

static ssize_t adxl372_read(FAR struct file *filep, FAR char *buffer,
                            size_t buflen)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct adxl372_dev_s *priv = inode->i_private;

  return adxl372_dvr_read(priv, buffer, buflen);
}

/****************************************************************************
 * Name: adxl372_write
 ****************************************************************************/

static ssize_t adxl372_write(FAR struct file *filep, FAR const char *buffer,
                             size_t buflen)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct adxl372_dev_s *priv = inode->i_private;

  return adxl372_dvr_write(priv, buffer, buflen);
}

/****************************************************************************
 * Name: adxl372_seek
 ****************************************************************************/

static off_t adxl372_seek(FAR struct file *filep, off_t offset, int whence)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct adxl372_dev_s *priv = inode->i_private;

  return adxl372_dvr_seek(priv, offset, whence);
}

/****************************************************************************
 * Name: adxl372_ioctl
 ****************************************************************************/

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

  return adxl372_dvr_ioctl(priv, cmd, arg);
}

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

/****************************************************************************
 * Name: adxl372_register
 *
 * Description:
 *   Register the ADXL372 character device as 'devpath'
 *
 * Input Parameters:
 *   devpath  - The full path to the driver to register. E.g., "/dev/acl0"
 *   spi      - An instance of the SPI interface to use to communicate with
 *              ADXL372
 *   config   - Configuration for the ADXL372 accelerometer driver.  For
 *              details see description above.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno value on failure.
 *
 ****************************************************************************/

int adxl372_register(FAR const char *devpath,
                     FAR struct spi_dev_s *spi,
                     FAR struct adxl372_config_s *config)
{
  FAR struct adxl372_dev_s *priv;
  int ret;

  /* Sanity check */

  DEBUGASSERT(spi != NULL);
  DEBUGASSERT(config != NULL);

  /* Initialize the ADXL372 accelerometer device structure. */

  priv = kmm_malloc(sizeof(struct adxl372_dev_s));
  if (priv == NULL)
    {
      snerr("ERROR: Failed to allocate accelerometer instance\n");
      return -ENOMEM;
    }

  priv->spi           = spi;
  priv->config        = config;
  config->leaf_handle = NULL;
  config->sc_ops      = NULL;

  /* Initialize sensor and sensor data access mutex */

  nxmutex_init(&priv->devicelock);

  /* Register the character driver */

  ret = register_driver(devpath, &g_adxl372_fops, 0666, priv);
  if (ret < 0)
    {
      snerr("ERROR: Failed to register accelerometer driver: %d\n", ret);

      nxmutex_destroy(&priv->devicelock);
      kmm_free(priv);
      return ret;
    }

  /* Since we support multiple ADXL372 devices, we will need to add this new
   * instance to a list of device instances so that it can be found by the
   * interrupt handler based on the received IRQ number.
   */

  priv->flink         = g_adxl372_list;
  g_adxl372_list      = priv;
  config->leaf_handle = (FAR void *)priv;
  config->sc_ops      = &g_adxl372_dops;

  return OK;
}

#endif /* CONFIG_SPI && CONFIG_SENSORS_ADXL372 && CONFIG_SPI_EXCHANGE */