/****************************************************************************
 * drivers/sensors/hts221.c
 *
 *   Copyright (C) 2014 Haltian Ltd. All rights reserved.
 *
 * 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 <sys/types.h>
#include <debug.h>
#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>

#include <nuttx/arch.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/irq.h>
#include <nuttx/kmalloc.h>
#include <nuttx/signal.h>
#include <nuttx/random.h>

#include <nuttx/sensors/hts221.h>

/****************************************************************************
 * Pre-Processor Definitions
 ****************************************************************************/

#ifdef CONFIG_HTS221_DEBUG
#  define hts221_dbg(x, ...)    _info(x, ##__VA_ARGS__)
#else
#  define hts221_dbg(x, ...)    sninfo(x, ##__VA_ARGS__)
#endif

#ifndef CONFIG_HTS221_I2C_FREQUENCY
#  define CONFIG_HTS221_I2C_FREQUENCY 400000
#endif

#define HTS221_WHO_AM_I             0x0f
#define HTS221_AV_CONF              0x10
#define HTS221_CTRL_REG1            0x20
#define HTS221_CTRL_REG2            0x21
#define HTS221_CTRL_REG3            0x22
#define HTS221_STATUS_REG           0x27
#define HTS221_HUM_OUT_L            0x28
#define HTS221_HUM_OUT_H            0x29
#define HTS221_TEMP_OUT_L           0x2a
#define HTS221_TEMP_OUT_H           0x2b

/* Calibration registers */

#define HTS221_CALIB_H0_RH_X2       0x30
#define HTS221_CALIB_H1_RH_X2       0x31
#define HTS221_CALIB_T0_DEGC_X8     0x32
#define HTS221_CALIB_T1_DEGC_X8     0x33
#define HTS221_CALIB_T1_T0_MSB      0x35
#define HTS221_CALIB_H0T0_OUT_L     0x36
#define HTS221_CALIB_H0T0_OUT_H     0x37
#define HTS221_CALIB_H1T0_OUT_L     0x3a
#define HTS221_CALIB_H1T0_OUT_H     0x3b
#define HTS221_CALIB_T0_OUT_L       0x3c
#define HTS221_CALIB_T0_OUT_H       0x3d
#define HTS221_CALIB_T1_OUT_L       0x3e
#define HTS221_CALIB_T1_OUT_H       0x3f

/* HTS221_CTRL_REG1 */

#define HTS221_CTRL_REG1_PD         (1 << 7)
#define HTS221_CTRL_REG1_BDU        (1 << 2)

/* HTS221_CTRL_REG2 */

#define HTS221_CTRL_REG2_BOOT       (1 << 7)
#define HTS221_CTRL_REG2_ONE_SHOT   (1 << 0)

/* HTS221_CTRL_REG3 */

#define HTS221_CTRL_REG3_DRDY_L_H   (1 << 7)
#define HTS221_CTRL_REG3_PP_OD      (1 << 6)
#define HTS221_CTRL_REG3_DRDY_EN    (1 << 2)

/* HTS221_STATUS_REG */

#define HTS221_STATUS_REG_H_DA      (1 << 1)
#define HTS221_STATUS_REG_T_DA      (1 << 0)

#define HTS221_I2C_RETRIES          10

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

static int hts221_open(FAR struct file *filep);
static int hts221_close(FAR struct file *filep);
static ssize_t hts221_read(FAR struct file *filep, FAR char *buffer,
                           size_t buflen);
static ssize_t hts221_write(FAR struct file *filep, FAR const char *buffer,
                            size_t buflen);
static int hts221_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
static int hts221_poll(FAR struct file *filep, FAR struct pollfd *fds,
                       bool setup);

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

struct hts221_dev_s
{
  struct i2c_master_s *i2c;
  uint8_t addr;
  hts221_config_t *config;
  sem_t devsem;
  volatile bool int_pending;
  struct pollfd *fds[CONFIG_HTS221_NPOLLWAITERS];
  struct
  {
    int16_t t0_out;
    int16_t t1_out;
    int16_t h0_t0_out;
    int16_t h1_t0_out;
    unsigned int t0_x8:10;
    unsigned int t1_x8:10;
    uint8_t h0_x2;
    uint8_t h1_x2;
  } calib;
};

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

static const struct file_operations g_humidityops =
{
  hts221_open,   /* open */
  hts221_close,  /* close */
  hts221_read,   /* read */
  hts221_write,  /* write */
  NULL,          /* seek */
  hts221_ioctl,  /* ioctl */
  hts221_poll    /* poll */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  , NULL         /* unlink */
#endif
};

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

static int hts221_do_transfer(FAR struct hts221_dev_s *priv,
                              FAR struct i2c_msg_s *msgv,
                              size_t nmsg)
{
  int ret = -EIO;
  int retries;

  for (retries = 0; retries < HTS221_I2C_RETRIES; retries++)
    {
      ret = I2C_TRANSFER(priv->i2c, msgv, nmsg);
      if (ret >= 0)
        {
          return 0;
        }
      else
        {
          /* Some error. Try to reset I2C bus and keep trying. */

#ifdef CONFIG_I2C_RESET
          if (retries == HTS221_I2C_RETRIES - 1)
            {
              break;
            }

          ret = I2C_RESET(priv->i2c);
          if (ret < 0)
            {
              hts221_dbg("I2C_RESET failed: %d\n", ret);
              return ret;
            }
#endif
        }
    }

  hts221_dbg("xfer failed: %d\n", ret);
  return ret;
}

static int32_t hts221_write_reg8(FAR struct hts221_dev_s *priv,
                                 const uint8_t *command)
{
  struct i2c_msg_s msgv[2] =
  {
    {
      .frequency = CONFIG_HTS221_I2C_FREQUENCY,
      .addr      = priv->addr,
      .flags     = 0,
      .buffer    = (FAR void *)&command[0],
      .length    = 1
    },
    {
      .frequency = CONFIG_HTS221_I2C_FREQUENCY,
      .addr      = priv->addr,
      .flags     = I2C_M_NOSTART,
      .buffer    = (FAR void *)&command[1],
      .length    = 1
    }
  };

  return hts221_do_transfer(priv, msgv, 2);
}

static int hts221_read_reg(FAR struct hts221_dev_s *priv,
                           FAR const uint8_t *command, FAR uint8_t *value)
{
  struct i2c_msg_s msgv[2] =
  {
    {
      .frequency = CONFIG_HTS221_I2C_FREQUENCY,
      .addr      = priv->addr,
      .flags     = 0,
      .buffer    = (FAR void *)command,
      .length    = 1
    },
    {
      .frequency = CONFIG_HTS221_I2C_FREQUENCY,
      .addr      = priv->addr,
      .flags     = I2C_M_READ,
      .buffer    = value,
      .length    = 1
    }
  };

  return hts221_do_transfer(priv, msgv, 2);
}

static int hts221_get_id(FAR struct hts221_dev_s *priv, uint8_t *value)
{
  int ret = OK;
  uint8_t cmd = HTS221_WHO_AM_I;

  ret = hts221_read_reg(priv, &cmd, value);

  hts221_dbg("Who am I request: 0x%02X\n", *value);

  return ret;
}

static int hts221_cfgr_resolution(FAR struct hts221_dev_s *priv,
                                  FAR hts221_settings_t *settings)
{
  int ret;
  uint8_t value;
  const uint8_t addr = HTS221_AV_CONF;
  uint8_t regval = 0;
  uint8_t cmd[2] =
  {
    0
  };

  const uint8_t mask = 0x3f;

  ret = hts221_read_reg(priv, &addr, &regval);
  hts221_dbg("Default resolution: 0x%02X\n", regval);
  if (ret < 0)
    {
      return ERROR;
    }

  value   = (uint8_t)settings->humid_resol |
            ((uint8_t)settings->temp_resol << 3);
  regval &= ~mask;
  cmd[0]  = addr;
  cmd[1]  = regval | value;
  hts221_dbg("New resolution: 0x%02X\n", cmd[1]);

  ret = hts221_write_reg8(priv, cmd);

  hts221_dbg("Resolution changed: temp=%d humid=%d ret=%d\n",
               settings->temp_resol, settings->humid_resol, ret);

  return ret;
}

static int hts221_config_ctrl_reg3(FAR struct hts221_dev_s *priv,
                                   FAR hts221_settings_t *settings)
{
  int ret = OK;
  uint8_t regval = 0;
  uint8_t addr = HTS221_CTRL_REG3;
  const uint8_t mask = 0xc4;
  uint8_t data_to_write[2] =
  {
    0
  };

  ret = hts221_read_reg(priv, &addr, &regval);
  hts221_dbg("CTRL_REG%d: 0x%02X\n", 3, regval);
  if (ret < 0)
    {
      return ERROR;
    }

  regval &= ~mask;
  regval |= (uint8_t)(settings->is_high_edge ?
                      0 : HTS221_CTRL_REG3_DRDY_L_H);
  regval |= (uint8_t)(settings->is_open_drain ? HTS221_CTRL_REG3_PP_OD : 0);
  regval |= (uint8_t)(settings->is_data_rdy ? HTS221_CTRL_REG3_DRDY_EN : 0);

  data_to_write[0] = addr;
  data_to_write[1] = regval;

  ret = hts221_write_reg8(priv, data_to_write);
  if (ret >= 0)
    {
      ret = hts221_read_reg(priv, &addr, &regval);
      if (ret >= 0)
        {
          hts221_dbg("wrote 0x%02X => CTRL_REG%d: 0x%02X\n",
                     data_to_write[1], 3, regval);
        }
    }

  return ret;
}

static int hts221_config_ctrl_reg2(FAR struct hts221_dev_s *priv,
                                   FAR hts221_settings_t *settings)
{
  int ret = OK;
  uint8_t regval = 0;
  uint8_t addr = HTS221_CTRL_REG2;
  const uint8_t mask = 0x80;
  uint8_t data_to_write[2] =
  {
    0
  };

  int retries = 5;

  if (!settings->is_boot)
    {
      return OK;
    }

  ret = hts221_read_reg(priv, &addr, &regval);
  hts221_dbg("CTRL_REG%d: 0x%02X\n", 2, regval);

  if (ret < 0)
    {
      return ERROR;
    }

  regval &= ~mask;
  regval |= HTS221_CTRL_REG2_BOOT;

  data_to_write[0] = addr;
  data_to_write[1] = regval;

  ret = hts221_write_reg8(priv, data_to_write);
  if (ret >= 0)
    {
      /* Wait until boot bit is cleared. */

      do
        {
          ret = hts221_read_reg(priv, &addr, &regval);
          if (ret >= 0)
            {
              hts221_dbg("wrote 0x%02X => CTRL_REG%d: 0x%02X\n",
                         data_to_write[1], 2, regval);
            }
          else
            {
              break;
            }

          if ((regval & HTS221_CTRL_REG2_BOOT) == 0)
            {
              /* After boot bit is cleared, wait additional 5 msec as
               * recommended in HTS221 application note.
               */

              up_mdelay(5);
              break;
            }

          nxsig_usleep(10 * 1000);
          retries--;
        }
      while (retries);

      if (ret >= 0 && (regval & HTS221_CTRL_REG2_BOOT) != 0)
        {
          ret = -ETIMEDOUT;
        }
    }

  return ret;
}

static int hts221_config_ctrl_reg1(FAR struct hts221_dev_s *priv,
                                   FAR hts221_settings_t *settings)
{
  int ret = OK;
  uint8_t regval = 0;
  uint8_t addr = HTS221_CTRL_REG1;
  const uint8_t mask = 0x87;
  uint8_t data_to_write[2] =
  {
    0
  };

  ret = hts221_read_reg(priv, &addr, &regval);
  hts221_dbg("CTRL_REG%d: 0x%02X\n", 1, regval);
  if (ret < 0)
    {
      return ERROR;
    }

  regval &= ~mask;
  regval |= (uint8_t)(settings->odr & 0xff);
  regval |= (uint8_t)(settings->is_bdu ? HTS221_CTRL_REG1_BDU : 0);

  data_to_write[0] = addr;
  data_to_write[1] = regval;

  ret = hts221_write_reg8(priv, data_to_write);
  if (ret >= 0)
    {
      ret = hts221_read_reg(priv, &addr, &regval);
      if (ret >= 0)
        {
          hts221_dbg("wrote 0x%02X => CTRL_REG%d: 0x%02X\n",
                     data_to_write[1], 1, regval);
        }
    }

  return ret;
}

static int hts221_power_on_off(FAR struct hts221_dev_s *priv, bool on)
{
  int ret = OK;
  uint8_t regval = 0;
  uint8_t addr = HTS221_CTRL_REG1;
  uint8_t data_to_write[2];

  ret = hts221_read_reg(priv, &addr, &regval);
  hts221_dbg("CTRL_REG%d: 0x%02X\n", 1, regval);
  if (ret < 0)
    {
      return ret;
    }

  if (on)
    {
      regval |= HTS221_CTRL_REG1_PD;
    }
  else
    {
      regval &= ~HTS221_CTRL_REG1_PD;
    }

  data_to_write[0] = addr;
  data_to_write[1] = regval;

  ret = hts221_write_reg8(priv, data_to_write);
  if (ret >= 0)
    {
      ret = hts221_read_reg(priv, &addr, &regval);
      if (ret >= 0)
        {
          hts221_dbg("wrote 0x%02X => CTRL_REG%d: 0x%02X\n",
                     data_to_write[1], 1, regval);
        }
    }

  return ret;
}

static int hts221_config(FAR struct hts221_dev_s *priv,
                         FAR hts221_settings_t *cfgr)
{
  int ret = OK;

  ret = hts221_config_ctrl_reg2(priv, cfgr); /* Performs sensor reset. */
  if (ret < 0)
    {
      return ERROR;
    }

  ret = hts221_cfgr_resolution(priv, cfgr);
  if (ret < 0)
    {
      return ERROR;
    }

  ret = hts221_config_ctrl_reg3(priv, cfgr);
  if (ret < 0)
    {
      return ERROR;
    }

  ret = hts221_config_ctrl_reg1(priv, cfgr);
  if (ret < 0)
    {
      return ERROR;
    }

  return ret;
}

static int hts221_start_conversion(FAR struct hts221_dev_s *priv)
{
  int ret;
  uint8_t addr = HTS221_CTRL_REG2;
  uint8_t data_to_write[2];

  ret = hts221_power_on_off(priv, true);
  if (ret < 0)
    {
      return ERROR;
    }

  data_to_write[0] = addr;
  data_to_write[1] = (uint8_t) HTS221_CTRL_REG2_ONE_SHOT;

  ret = hts221_write_reg8(priv, data_to_write);
  if (ret < 0)
    {
      hts221_dbg("Cannot start conversion\n");
      ret = ERROR;
    }

  return ret;
}

static int hts221_check_status(FAR struct hts221_dev_s *priv,
                               FAR hts221_status_t *status)
{
  int ret = OK;
  uint8_t addr = HTS221_STATUS_REG;
  const uint8_t humid_mask = 0x02;
  const uint8_t temp_mask = 0x01;
  uint8_t regval = 0;

  ret = hts221_read_reg(priv, &addr, &regval);
  if (ret < 0)
    {
      return ERROR;
    }

  status->is_humid_ready = ((regval & humid_mask) ? true : false);
  status->is_temp_ready = ((regval & temp_mask) ? true : false);

  return ret;
}

static int hts221_read_raw_data(FAR struct hts221_dev_s *priv,
                                FAR hts221_raw_data_t *data)
{
  int ret = OK;
  uint8_t addr_humid_low = HTS221_HUM_OUT_L;
  uint8_t addr_humid_high = HTS221_HUM_OUT_H;
  uint8_t addr_temp_low = HTS221_TEMP_OUT_L;
  uint8_t addr_temp_high = HTS221_TEMP_OUT_H;
  irqstate_t flags;

  ret = hts221_read_reg(priv, &addr_humid_low, &data->humid_low_bits);
  if (ret < 0)
    {
      return ERROR;
    }

  ret = hts221_read_reg(priv, &addr_humid_high, &data->humid_high_bits);
  if (ret < 0)
    {
      return ERROR;
    }

  ret = hts221_read_reg(priv, &addr_temp_low, &data->temp_low_bits);
  if (ret < 0)
    {
      return ERROR;
    }

  ret = hts221_read_reg(priv, &addr_temp_high, &data->temp_high_bits);
  if (ret < 0)
    {
      return ERROR;
    }

  /* Add low-order bytes to entropy pool. */

  add_sensor_randomness(((uint32_t)data->humid_low_bits << 8) |
                        data->temp_low_bits);

  flags = enter_critical_section();
  priv->int_pending = false;
  leave_critical_section(flags);

  hts221_dbg("Humid: 0x%02X, 0x%02X Temper: 0x%02X 0x%02X\n",
               data->humid_high_bits, data->humid_low_bits,
               data->temp_high_bits, data->temp_low_bits);

  return ret;
}

static int hts221_load_calibration_data(FAR struct hts221_dev_s *priv)
{
  int ret;
  uint8_t addr;
  uint8_t t0_degc_x8   = 0;
  uint8_t t1_degc_x8   = 0;
  uint8_t t1_t0_msb    = 0;
  uint8_t t0_out_lsb   = 0;
  uint8_t t0_out_msb   = 0;
  uint8_t t1_out_lsb   = 0;
  uint8_t t1_out_msb   = 0;
  uint8_t h0_rh_x2     = 0;
  uint8_t h1_rh_x2     = 0;
  uint8_t h0t0_out_lsb = 0;
  uint8_t h0t0_out_msb = 0;
  uint8_t h1t0_out_lsb = 0;
  uint8_t h1t0_out_msb = 0;

  addr = HTS221_CALIB_T0_DEGC_X8;
  ret = hts221_read_reg(priv, &addr, &t0_degc_x8);
  if (ret < 0)
    {
      return ret;
    }

  addr = HTS221_CALIB_T1_DEGC_X8;
  ret = hts221_read_reg(priv, &addr, &t1_degc_x8);
  if (ret < 0)
    {
      return ret;
    }

  addr = HTS221_CALIB_T1_T0_MSB;
  ret = hts221_read_reg(priv, &addr, &t1_t0_msb);
  if (ret < 0)
    {
      return ret;
    }

  addr = HTS221_CALIB_T0_OUT_L;
  ret = hts221_read_reg(priv, &addr, &t0_out_lsb);
  if (ret < 0)
    {
      return ret;
    }

  addr = HTS221_CALIB_T0_OUT_H;
  ret = hts221_read_reg(priv, &addr, &t0_out_msb);
  if (ret < 0)
    {
      return ret;
    }

  addr = HTS221_CALIB_T1_OUT_L;
  ret = hts221_read_reg(priv, &addr, &t1_out_lsb);
  if (ret < 0)
    {
      return ret;
    }

  addr = HTS221_CALIB_T1_OUT_H;
  ret = hts221_read_reg(priv, &addr, &t1_out_msb);
  if (ret < 0)
    {
      return ret;
    }

  addr = HTS221_CALIB_H0_RH_X2;
  ret = hts221_read_reg(priv, &addr, &h0_rh_x2);
  if (ret < 0)
    {
      return ret;
    }

  addr = HTS221_CALIB_H1_RH_X2;
  ret = hts221_read_reg(priv, &addr, &h1_rh_x2);
  if (ret < 0)
    {
      return ret;
    }

  addr = HTS221_CALIB_H0T0_OUT_L;
  ret = hts221_read_reg(priv, &addr, &h0t0_out_lsb);
  if (ret < 0)
    {
      return ret;
    }

  addr = HTS221_CALIB_H0T0_OUT_H;
  ret = hts221_read_reg(priv, &addr, &h0t0_out_msb);
  if (ret < 0)
    {
      return ret;
    }

  addr = HTS221_CALIB_H1T0_OUT_L;
  ret = hts221_read_reg(priv, &addr, &h1t0_out_lsb);
  if (ret < 0)
    {
      return ret;
    }

  addr = HTS221_CALIB_H1T0_OUT_H;
  ret = hts221_read_reg(priv, &addr, &h1t0_out_msb);
  if (ret < 0)
    {
      return ret;
    }

  priv->calib.t0_x8     = t0_degc_x8 | ((t1_t0_msb & 0x3) << 8);
  priv->calib.t1_x8     = t1_degc_x8 | ((t1_t0_msb & (0x3 << 2)) << (8 - 2));
  priv->calib.t0_out    = (uint16_t) (t0_out_lsb | (t0_out_msb << 8));
  priv->calib.t1_out    = (uint16_t) (t1_out_lsb | (t1_out_msb << 8));
  priv->calib.h0_x2     = h0_rh_x2;
  priv->calib.h1_x2     = h1_rh_x2;
  priv->calib.h0_t0_out = (uint16_t) (h0t0_out_lsb | (h0t0_out_msb << 8));
  priv->calib.h1_t0_out = (uint16_t) (h1t0_out_lsb | (h1t0_out_msb << 8));

  hts221_dbg("calib.t0_x8: %d\n", priv->calib.t0_x8);
  hts221_dbg("calib.t1_x8: %d\n", priv->calib.t1_x8);
  hts221_dbg("calib.t0_out: %d\n", priv->calib.t0_out);
  hts221_dbg("calib.t1_out: %d\n", priv->calib.t1_out);
  hts221_dbg("calib.h0_x2: %d\n", priv->calib.h0_x2);
  hts221_dbg("calib.h1_x2: %d\n", priv->calib.h1_x2);
  hts221_dbg("calib.h0_t0_out: %d\n", priv->calib.h0_t0_out);
  hts221_dbg("calib.h1_t0_out: %d\n", priv->calib.h1_t0_out);

  /* As calibration coefficients are unique to each sensor device,
   * they are a good candidate to be added to entropy pool.
   */

  up_rngaddentropy(RND_SRC_HW, (uint32_t *)&priv->calib,
                   sizeof(priv->calib) / sizeof(uint32_t));

  return OK;
}

static int hts221_calculate_temperature(FAR struct hts221_dev_s *priv,
                                        FAR int *temperature,
                                        FAR hts221_raw_data_t *raw_data)
{
  int16_t t_out = (raw_data->temp_high_bits << 8) | raw_data->temp_low_bits;
  int x0 = priv->calib.t0_out;
  int x1 = priv->calib.t1_out;
  int y0 = priv->calib.t0_x8;
  int y1 = priv->calib.t1_x8;
  int x = t_out;
  int64_t y;
  int x1_x0_diff;

  x1_x0_diff = x1 - x0;

  y = (y0 * x1_x0_diff + (y1 - y0) * (x - x0));
  y *= HTS221_TEMPERATURE_PRECISION;
  y /= x1_x0_diff * 8;

  *temperature = (int)y;

  hts221_dbg("Interpolation data temper: %d\n", *temperature);

  return OK;
}

static int hts221_calculate_humidity(FAR struct hts221_dev_s *priv,
                                     FAR unsigned int *humidity,
                                     FAR hts221_raw_data_t *raw_data)
{
  int16_t h_out;
  int x0 = priv->calib.h0_t0_out;
  int x1 = priv->calib.h1_t0_out;
  int y0 = priv->calib.h0_x2;
  int y1 = priv->calib.h1_x2;
  int x;
  int64_t y;
  int x1_x0_diff;

  h_out = (raw_data->humid_high_bits << 8) | raw_data->humid_low_bits;
  x = h_out;
  x1_x0_diff = x1 - x0;

  y  = (y0 * x1_x0_diff + (y1 - y0) * (x - x0));
  y *= HTS221_HUMIDITY_PRECISION;
  y /= x1_x0_diff * 2;

  *humidity = (int)y;

  hts221_dbg("Interpolation data humidity: %d\n", *humidity);

  return OK;
}

static int hts221_read_convert_data(FAR struct hts221_dev_s *priv,
                                    FAR hts221_conv_data_t *data)
{
  int ret = OK;
  hts221_raw_data_t raw_data;

  ret = hts221_read_raw_data(priv, &raw_data);
  if (ret < 0)
    {
      return ERROR;
    }

  ret = hts221_calculate_temperature(priv, &data->temperature, &raw_data);
  if (ret < 0)
    {
      return ERROR;
    }

  hts221_dbg("Temperature calculated\n");

  ret = hts221_calculate_humidity(priv, &data->humidity, &raw_data);
  if (ret < 0)
    {
      return ERROR;
    }

  hts221_dbg("Humidity calculated\n");
  return ret;
}

#ifdef CONFIG_HTS221_DEBUG
static int hts221_dump_registers(FAR struct hts221_dev_s *priv)
{
  int ret = OK;
  uint8_t av_addr = HTS221_AV_CONF;
  uint8_t ctrl_reg1_addr = HTS221_CTRL_REG1;
  uint8_t ctrl_reg2_addr = HTS221_CTRL_REG2;
  uint8_t ctrl_reg3_addr = HTS221_CTRL_REG3;
  uint8_t regval = 0;

  ret = hts221_read_reg(priv, &av_addr, &regval);
  if (ret < 0)
    {
      return ERROR;
    }

  hts221_dbg("AV_CONF_REG: 0x%02X\n", regval);

  ret = hts221_read_reg(priv, &ctrl_reg1_addr, &regval);
  if (ret < 0)
    {
      return ERROR;
    }

  hts221_dbg("CTRL_REG_1: 0x%02X\n", regval);

  ret = hts221_read_reg(priv, &ctrl_reg2_addr, &regval);
  if (ret < 0)
    {
      return ERROR;
    }

  hts221_dbg("CTRL_REG_2: 0x%02X\n", regval);

  ret = hts221_read_reg(priv, &ctrl_reg3_addr, &regval);
  if (ret < 0)
    {
      return ERROR;
    }

  hts221_dbg("CTRL_REG_3: 0x%02X\n", regval);
  return ret;
}
#endif

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

  /* Get exclusive access */

  ret = nxsem_wait_uninterruptible(&priv->devsem);
  if (ret < 0)
    {
      return ret;
    }

  priv->config->set_power(priv->config, true);
  priv->config->irq_enable(priv->config, true);

  nxsem_post(&priv->devsem);
  hts221_dbg("Sensor is powered on\n");
  return OK;
}

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

  /* Get exclusive access */

  ret = nxsem_wait_uninterruptible(&priv->devsem);
  if (ret < 0)
    {
      return ret;
    }

  priv->config->irq_enable(priv->config, false);
  ret = hts221_power_on_off(priv, false);
  priv->config->set_power(priv->config, false);

  nxsem_post(&priv->devsem);
  hts221_dbg("CLOSED\n");
  return ret;
}

static ssize_t hts221_read(FAR struct file *filep, FAR char *buffer,
                           size_t buflen)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct hts221_dev_s *priv = inode->i_private;
  hts221_conv_data_t data;
  ssize_t length = 0;
  int ret;

  /* Get exclusive access */

  ret = nxsem_wait_uninterruptible(&priv->devsem);
  if (ret < 0)
    {
      return (ssize_t)ret;
    }

  ret = hts221_read_convert_data(priv, &data);
  if (ret < 0)
    {
      hts221_dbg("cannot read data: %d\n", ret);
    }
  else
    {
      /* This interface is mainly intended for easy debugging in nsh. */

      length = snprintf(buffer, buflen, "%d %u\n",
                        data.temperature, data.humidity);
      if (length > buflen)
        {
          length = buflen;
        }
    }

  nxsem_post(&priv->devsem);
  return length;
}

static ssize_t hts221_write(FAR struct file *filep, FAR const char *buffer,
                            size_t buflen)
{
  ssize_t length = 0;
  return length;
}

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

  /* Get exclusive access */

  ret = nxsem_wait_uninterruptible(&priv->devsem);
  if (ret < 0)
    {
      return ret;
    }

  switch (cmd)
    {
    case SNIOC_GET_DEV_ID:
      ret = hts221_get_id(priv, (FAR uint8_t *) arg);
      break;

    case SNIOC_CFGR:
      ret = hts221_config(priv, (FAR hts221_settings_t *) arg);
      break;

    case SNIOC_START_CONVERSION:
      ret = hts221_start_conversion(priv);
      break;

    case SNIOC_CHECK_STATUS_REG:
      ret = hts221_check_status(priv, (FAR hts221_status_t *) arg);
      break;

    case SNIOC_READ_RAW_DATA:
      ret = hts221_read_raw_data(priv, (FAR hts221_raw_data_t *) arg);
      break;

#ifdef CONFIG_HTS221_DEBUG
    case SNIOC_DUMP_REGS:
      ret = hts221_dump_registers(priv);
      break;
#endif

    case SNIOC_READ_CONVERT_DATA:
      ret = hts221_read_convert_data(priv, (FAR hts221_conv_data_t *) arg);
      break;

    default:
      ret = -ENOTTY;
      break;
    }

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

static bool hts221_sample(FAR struct hts221_dev_s *priv)
{
  int ret;
  hts221_status_t status =
    {
      .is_humid_ready = false,
      .is_temp_ready = false
    };

  ret = hts221_check_status(priv, &status);
  if (ret < 0)
    {
      return false;
    }

  return status.is_humid_ready || status.is_temp_ready;
}

static void hts221_notify(FAR struct hts221_dev_s *priv)
{
  DEBUGASSERT(priv != NULL);

  int i;

  /* If there are threads waiting on poll() for 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.
   */

  for (i = 0; i < CONFIG_HTS221_NPOLLWAITERS; i++)
    {
      FAR struct pollfd *fds = priv->fds[i];
      if (fds)
        {
          fds->revents |= POLLIN;
          hts221_dbg("Report events: %02x\n", fds->revents);
          nxsem_post(fds->sem);
        }
    }
}

static int hts221_poll(FAR struct file *filep, FAR struct pollfd *fds,
                       bool setup)
{
  FAR struct inode *inode;
  FAR struct hts221_dev_s *priv;
  irqstate_t flags;
  int ret = OK;
  int i;

  DEBUGASSERT(filep && fds);
  inode = filep->f_inode;

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

  /* Get exclusive access */

  ret = nxsem_wait_uninterruptible(&priv->devsem);
  if (ret < 0)
    {
      return ret;
    }

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

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

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

      for (i = 0; i < CONFIG_HTS221_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_HTS221_NPOLLWAITERS)
        {
          fds->priv = NULL;
          ret = -EBUSY;
          goto out;
        }

      flags = enter_critical_section();
      if (priv->int_pending || hts221_sample(priv))
        {
          hts221_notify(priv);
        }

      leave_critical_section(flags);
    }
  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;
    }

out:
  nxsem_post(&priv->devsem);
  return ret;
}

static int hts221_int_handler(int irq, FAR void *context, FAR void *arg)
{
  FAR struct hts221_dev_s *priv = (FAR struct hts221_dev_s *)arg;

  DEBUGASSERT(priv != NULL);

  priv->int_pending = true;
  hts221_dbg("Hts221 interrupt\n");
  hts221_notify(priv);

  return OK;
}

int hts221_register(FAR const char *devpath, FAR struct i2c_master_s *i2c,
                    uint8_t addr, FAR hts221_config_t *config)
{
  int ret = 0;
  FAR struct hts221_dev_s *priv;

  priv = (struct hts221_dev_s *)kmm_zalloc(sizeof(struct hts221_dev_s));

  if (!priv)
    {
      hts221_dbg("Memory cannot be allocated for hts221 sensor");
      return -ENOMEM;
    }

  priv->addr   = addr;
  priv->i2c    = i2c;
  priv->config = config;
  nxsem_init(&priv->devsem, 0, 1);

  priv->config->set_power(priv->config, true);

  ret = hts221_load_calibration_data(priv);
  if (ret < 0)
    {
      kmm_free(priv);
      hts221_dbg("Cannot calibrate hts221 sensor\n");
      return -EAGAIN;
    }

  ret = register_driver(devpath, &g_humidityops, 0666, priv);

  hts221_dbg("Registered with %d\n", ret);

  if (ret < 0)
    {
      kmm_free(priv);
      hts221_dbg("Error occurred during the driver registering\n");
      return ret;
    }

  if (priv->config->irq_clear)
    {
      priv->config->irq_clear(priv->config);
    }

  priv->config->irq_attach(priv->config, hts221_int_handler, priv);
  priv->config->irq_enable(priv->config, false);
  priv->config->set_power(priv->config, false);
  return OK;
}