/*
 * Copyright (C) 2020 iosabi
 *
 * This file is subject to the terms and conditions of the GNU Lesser General
 * Public License v2.1. See the file LICENSE in the top level directory for more
 * details.
 */

/**
 * @ingroup     drivers_si1133
 * @{
 *
 * @file
 * @brief       Device driver implementation for the SI1133 UV/IR/Ambient light
 *              sensor with I2C interface.
 *
 * @author      iosabi <iosabi@protonmail.com>
 *
 * @}
 */

#include <math.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

#include "xtimer.h"

#include "periph/i2c.h"

#include "si1133.h"
#include "si1133_internals.h"

#define ENABLE_DEBUG 0
#include "debug.h"

/**
 * @brief The command execution timeout in blocking mode.
 *
 * When forcing a measurement (SI1133_CMD_FORCE) the command is not done until
 * all the measurements from all the channels are taken. This may take some time
 * depending on the configuration. After the expected sampling time is over we
 * wait up to this command timeout value for the command to be ready.
 */
#define SI1133_COMMAND_TIMEOUT_USEC 10000

/**
 * @brief Return the expected sampling time in us for a FORCE command.
 */
static uint32_t _si1133_force_time_us(si1133_t *dev)
{
    /* Sample time measured in 512 clocks of the 21 MHz, or about 24.4 us. */
    uint32_t sample_time = 0;

    for (uint32_t i = 0; i < dev->num_channels; i++) {
        /* A forced sample consists of (1 << sw_gain) measurements where each
         * one has a total 155 us (about 7 * 24.4 us) of processing time, plus
         * two ADC samples. Each ADC sample requires a t_adstart time (48.8 us
         * or 2 * 24.4 us) plus the time configured by the sample_time_log. */
        sample_time += (2 * (2 + (1u << dev->channel[i].sample_time_log)) + 7)
                       << dev->channel[i].sw_gain;
    }
    /* The resulting sample_time value in microseconds is sample_time * 512 / 21
     * the sample_time is already a 24-bit number so we split the logic to
     * fit in 32-bit arithmetic using the fact that 512 == 24 * 21 + 8. */
    return sample_time * 24 + sample_time * 8 / 21;
}

/**
 * @brief Run a command with no arguments.
 *
 * For commands CMD_PARAM_SET or CMD_PARAM_QUERY use @ref _si1133_set_param
 * and @ref _si1133_get_param respectively instead.
 */
static int _si1133_run_command(si1133_t *dev, uint8_t command)
{
    DEBUG("[si1133] run_command: 0x%.2x, cmd_counter=%d\n",
          (unsigned)command, dev->cmd_counter);
    int ret;
    ret = i2c_write_reg(dev->i2c_dev, dev->address,
                        SI1133_REG_COMMAND, command, 0 /* flags */);
    if (ret) {
        DEBUG("[si1133] write command I2C error: %s\n", strerror(-ret));
        return SI1133_ERR_I2C;
    }

    if (command == SI1133_CMD_FORCE) {
        /* Wait for the expected force acquisition time. */
        xtimer_usleep(_si1133_force_time_us(dev));
    }

    if (command == SI1133_CMD_RESET_SW) {
        /* Reset command sets the command counter of 0x0f. */
        dev->cmd_counter = 0x0f;
        /* Reset command puts the device in "Initialization Mode" which requires
         * us to wait until the device is ready. */
        xtimer_msleep(SI1133_STARTUP_TIME_MS);
    }
    else if (command == SI1133_CMD_RESET_CMD_CTR) {
        /* The reset cmd_counter command, well, resets it to 0. */
        dev->cmd_counter = 0;
    }
    else {
        /* Increment the expected 4-bit command counter value. */
        dev->cmd_counter = (dev->cmd_counter + 1) & SI1133_RESP0_COUNTER_MASK;
    }

    uint8_t new_cmd_ctr;
    xtimer_ticks32_t start_time;
    bool retry = false;
    while (1) {
        ret = i2c_read_reg(dev->i2c_dev, dev->address, SI1133_REG_RESPONSE0,
                           &new_cmd_ctr, 0 /* flags */);
        if (ret) {
            DEBUG("[si1133] read RESPONSE0 I2C error: %s\n", strerror(-ret));
            return SI1133_ERR_I2C;
        }
        if (new_cmd_ctr & SI1133_RESP0_CMD_ERR_MASK) {
            DEBUG("[si1133] Command 0x%.2x returned error %d\n",
                  (unsigned)command, new_cmd_ctr & SI1133_RESP0_COUNTER_MASK);
            /* Error code 2 is "ADC or accumulation overflow", while error code
             * 3 is output buffer overflow which can only occur in BURST mode.
             * However, in FORCE mode after the first overflow we can get an
             * error code 3 if we change the settings in between the overflow
             * and a new FORCE command, probably due to a silicon bug when
             * handling sw_gain overflows since it is not possible to write more
             * than 18 bytes of output in FORCE mode while the output buffer is
             * 26 bytes long. */
            if ((new_cmd_ctr & SI1133_RESP0_COUNTER_MASK) >= 2) {
                return SI1133_ERR_OVERFLOW;
            }
            return SI1133_ERR_LOGIC;
        }
        /* The reset command is done when the RUNNING flag is clear, the other
         * commands are done when the command value is set to the expected
         * one. */
        if ((command == SI1133_CMD_RESET_SW)
            ? !(new_cmd_ctr & SI1133_RESP0_RUNNING_MASK)
            : (dev->cmd_counter ==
               (new_cmd_ctr & SI1133_RESP0_COUNTER_MASK))) {
            break;
        }
        /* The command didn't yet finish in this case so it should be in running
         * state and we need to retry the loop with a timeout. This avoids
         * calling xtimer for commands that are immediate. */
        if (retry) {
            if (xtimer_usec_from_ticks(xtimer_diff(xtimer_now(), start_time))
                > SI1133_COMMAND_TIMEOUT_USEC) {
                DEBUG("[si1133] Command 0x%.2x timeout.\n", (unsigned)command);
                return SI1133_ERR_LOGIC;
            }
        }
        else {
            retry = true;
            start_time = xtimer_now();
        }
    }
    if (retry) {
        DEBUG("[si1133] Command overtime: %" PRIu32 " us.\n",
              xtimer_usec_from_ticks(xtimer_diff(xtimer_now(), start_time)));
    }
    return SI1133_OK;
}

static int _si1133_set_param(si1133_t *dev, uint8_t param, uint8_t value)
{
    int ret;

    ret = i2c_write_reg(dev->i2c_dev, dev->address,
                        SI1133_REG_HOSTIN0, value, 0 /* flags */);
    if (ret) {
        DEBUG("[si1133] write HOSTIN0 I2C error: %s\n", strerror(-ret));
        return SI1133_ERR_I2C;
    }
    ret = _si1133_run_command(dev, param | SI1133_CMD_PARAM_SET);
    if (ret) {
        return ret;
    }

    uint8_t resp1;
    ret = i2c_read_reg(dev->i2c_dev, dev->address,
                       SI1133_REG_RESPONSE1, &resp1, 0);
    if (ret) {
        DEBUG("[si1133] read RESPONSE1 I2C error: %s\n", strerror(-ret));
        return SI1133_ERR_I2C;
    }
    if (resp1 != value) {
        DEBUG("[si1133] Expected to read back value 0x%.2" PRIu8
              " when setting param 0x%.2" PRIu8 " but got 0x%.2" PRIu8 "\n",
              value, param, resp1);
        return SI1133_ERR_LOGIC;
    }
    return SI1133_OK;
}

/**
 * @brief Reset the device.
 */
static int _si1133_reset(si1133_t *dev)
{
    DEBUG("[si1133] reset()\n");
    dev->num_channels = 0;
    for (uint32_t i = 0; i < SI1133_NUM_CHANNELS; i++) {
        /* Initialize the config with invalid values to force the first config
         * call to update it. */
        dev->channel[i].sw_gain = 0xff;
    }
    int ret = _si1133_run_command(dev, SI1133_CMD_RESET_SW);
    if (ret) {
        return ret;
    }

    return _si1133_run_command(dev, SI1133_CMD_RESET_CMD_CTR);
}

/**
 * @brief Configure a single channel with the passed parameters.
 */
static si1133_ret_code_t _si1133_configure_channel(
    si1133_t *dev, uint32_t channel_id, const si1133_channel_t *channel)
{
    if (channel->sample_time_log > 14 || channel->sw_gain > 7) {
        return SI1133_ERR_PARAMS;
    }

    if (!memcmp(channel, &dev->channel[channel_id], sizeof(si1133_channel_t))) {
        /* Avoid the I2C roundtrip if the channel configuration didn't
         * change. */
        return SI1133_OK;
    }

    /* Generate the channel configuration. */
    si1133_channel_params_t config;
#define SI1133_SENS_CASE(x)                                       \
    case SI1133_SENS_ ## x:                                       \
        config.adcconfig =                                        \
            SI1133_ADCMUX_ ## x << SI1133_ADCCONFIG_ADCMUX_SHIFT; \
        break;

    switch (channel->sensor) {
        SI1133_SENS_CASE(SMALL_IR)
        SI1133_SENS_CASE(MEDIUM_IR)
        SI1133_SENS_CASE(LARGE_IR)
        SI1133_SENS_CASE(WHITE)
        SI1133_SENS_CASE(LARGE_WHITE)
        SI1133_SENS_CASE(UV)
        SI1133_SENS_CASE(DEEP_UV)
    default:
        return SI1133_ERR_PARAMS;
    }
    /* Use normal decimation (1) except in the extremes where we must use
     * the other decimation values. */
    const uint8_t hw_gain = channel->sample_time_log == 0
                            ? 0
                            : (channel->sample_time_log <= 11
                               ? channel->sample_time_log - 1
                               : 11);
    /* Select decimation. A value of "0" in this field is the "normal"
     * decimation, which corresponds to 1 in our "decimation" equation.
     * The values in this hardware field are offset by 3. */
    const uint8_t decimation =
        (channel->sample_time_log - hw_gain + 3) & 3;
    config.adcconfig |= decimation << SI1133_ADCCONFIG_DECIM_RATE_SHIFT;
    /* HSIG = 0, SW_GAIN and HW_GAIN as configured. */
    config.adcsens = (hw_gain << SI1133_ADCSENS_HW_GAIN_SHIFT) |
                     (channel->sw_gain << SI1133_ADCSENS_SW_GAIN_SHIFT);
    /* 24-bit output, no output shift, no threshold. */
    config.adcpost = SI1133_ADCPOST_24BIT_OUT_MASK;
    /* No counter, this will only be used in Force mode. */
    config.measconfig = 0;

    DEBUG("[si1133] config: %.2x %.2x %.2x %.2x\n",
          ((uint8_t *)&config)[0], ((uint8_t *)&config)[1],
          ((uint8_t *)&config)[2], ((uint8_t *)&config)[3]);

    for (uint8_t i = 0; i < sizeof(config); i++) {
        uint8_t param = SI1133_PARAM_ADCCONFIG0 + sizeof(config) * channel_id +
                        i;
        si1133_ret_code_t ret =
            _si1133_set_param(dev, param, ((uint8_t *)&config)[i]);
        if (ret) {
            return ret;
        }

    }
    memcpy(&(dev->channel[channel_id]), channel, sizeof(si1133_channel_t));
    return SI1133_OK;
}

static int _si1133_read_values(si1133_t *dev, int32_t *values,
                               uint32_t num_channels)
{
    /* We can read all registers in a single I2C burst. */
    uint8_t data[3 * num_channels];

    /* We only request 24-bit values from the device so they are all 3 byte
     * long. */
    int ret = i2c_read_regs(dev->i2c_dev, dev->address, SI1133_REG_HOSTOUTx,
                            data, 3 * num_channels, 0 /* flags */);

    if (ret) {
        return SI1133_ERR_I2C;
    }
    uint8_t *offset = data;
    for (uint8_t i = 0; i < num_channels; i++) {
        /* Sign-extend the first 8-bit value before shifting. */
        int32_t value = ((int32_t)(int8_t)*(offset++)) << 16;
        value |= *(offset++) << 8u;
        value |= *(offset++);
        values[i] = value;
    }
    return SI1133_OK;
}

si1133_ret_code_t si1133_init(si1133_t *dev, const si1133_params_t *params)
{
    dev->i2c_dev = params->i2c_dev;
    dev->address = params->address;

    /* After leaving "Off Mode" the SI1133 enters an "Initialization Mode" for
     * a period of time in which it can't be reached over I2C. After this time
     * the device will be in Standby Mode. */
    xtimer_msleep(SI1133_STARTUP_TIME_MS);

    i2c_acquire(params->i2c_dev);

    /* check sensor ID */
    uint8_t partid = 0;
    int ret = i2c_read_reg(params->i2c_dev, params->address, SI1133_REG_PART_ID,
                           &partid, 0);
    if (ret != 0) {
        DEBUG("[si1133] i2c communication error: %s.\n", strerror(-ret));
        i2c_release(params->i2c_dev);
        return SI1133_ERR_I2C;
    }
    if (partid != SI1133_ID) {
        DEBUG("[si1133] Invalid part id: 0x%.2u\n", (unsigned)partid);
        i2c_release(params->i2c_dev);
        return SI1133_ERR_NODEV;
    }

#if ENABLE_DEBUG
    uint8_t rev_id, hw_id;
    i2c_read_reg(params->i2c_dev, params->address, SI1133_REG_REV_ID, &rev_id,
                 0 /* flags */);
    i2c_read_reg(params->i2c_dev, params->address, SI1133_REG_HW_ID, &hw_id,
                 0 /* flags */);
    DEBUG("[si1133] impl code: %u, silicon HW rev: %u, rev: %u.%u\n",
          hw_id & 0x1f, hw_id >> 5, rev_id >> 4, rev_id & 0x0f);
#endif  /* ENABLE_DEBUG */

    /* We don't know the state in which the device is at this point so we need
     * to perform a reset, unfortunately this requires another start-up wait. */
    ret = _si1133_reset(dev);
    if (ret) {
        i2c_release(dev->i2c_dev);
        return ret;
    }

    i2c_release(dev->i2c_dev);
    return SI1133_OK;
}

si1133_ret_code_t si1133_configure_channels(si1133_t *dev,
                                            const si1133_channel_t *channels,
                                            uint32_t num_channels)
{
    DEBUG("[si1133] configure_channels(num=%" PRIu32 ")\n", num_channels);
    if (num_channels > SI1133_NUM_CHANNELS) {
        return SI1133_ERR_PARAMS;
    }

    i2c_acquire(dev->i2c_dev);
    si1133_ret_code_t ret;
    for (uint32_t i = 0; i < num_channels; i++) {
        ret = _si1133_configure_channel(dev, i, channels + i);
        if (ret) {
            dev->num_channels = 0;
            i2c_release(dev->i2c_dev);
            return ret;
        }
    }
    /* CHAN_LIST is a bit mask of channels used. */
    ret = _si1133_set_param(dev, SI1133_PARAM_CHAN_LIST,
                            (1u << num_channels) - 1);
    if (ret) {
        num_channels = 0;
    }
    dev->num_channels = num_channels;
    i2c_release(dev->i2c_dev);
    DEBUG("[si1133] Sample Time %" PRIu32 " us\n", _si1133_force_time_us(dev));
    return ret;
}

si1133_ret_code_t si1133_easy_configure(si1133_t *dev,
                                        si1133_sensor_t sensor_mask,
                                        uint8_t sample_time_log,
                                        uint8_t sw_gain)
{
    DEBUG("[si1133] easy_configure(0x%.2x)\n", (unsigned)sensor_mask);
    i2c_acquire(dev->i2c_dev);

    si1133_ret_code_t ret;
    uint8_t num_channels = 0;
    for (uint8_t mask = sensor_mask; mask;
         num_channels++, mask = mask & (mask - 1)) {
        if (num_channels >= SI1133_NUM_CHANNELS) {
            dev->num_channels = 0;
            i2c_release(dev->i2c_dev);
            return SI1133_ERR_PARAMS;
        }

        si1133_channel_t channel;
        channel.sensor = mask ^ (mask & (mask - 1));
        channel.sample_time_log = sample_time_log;
        channel.sw_gain = sw_gain;
        ret = _si1133_configure_channel(dev, num_channels, &channel);
        if (ret) {
            dev->num_channels = 0;
            i2c_release(dev->i2c_dev);
            return ret;
        }
    }

    /* CHAN_LIST is a bit mask of channels used. */
    ret = _si1133_set_param(dev, SI1133_PARAM_CHAN_LIST,
                            (1u << num_channels) - 1);
    if (ret) {
        num_channels = 0;
    }
    dev->num_channels = num_channels;
    i2c_release(dev->i2c_dev);
    DEBUG("[si1133] Sample Time %" PRIu32 " us\n", _si1133_force_time_us(dev));
    return ret;
}

si1133_ret_code_t si1133_capture_sensors(si1133_t *dev, int32_t *values,
                                         uint32_t num_channels)
{
    if (!dev->num_channels) {
        /* Must be configured before calling capture_sensors. */
        return SI1133_ERR_PARAMS;
    }

    i2c_acquire(dev->i2c_dev);

    si1133_ret_code_t force_ret;
    force_ret = _si1133_run_command(dev, SI1133_CMD_FORCE);
    if (force_ret != SI1133_OK && force_ret != SI1133_ERR_OVERFLOW) {
        i2c_release(dev->i2c_dev);
        DEBUG("[si1133] force read command error: %d\n", force_ret);
        return force_ret;
    }
    si1133_ret_code_t ret = SI1133_OK;
    if (force_ret == SI1133_ERR_OVERFLOW) {
        /* We need to reset the overflow condition with a RESET_CMD_CTR */
        ret = _si1133_run_command(dev, SI1133_CMD_RESET_CMD_CTR);
    }
    if (ret == SI1133_OK) {
        ret = _si1133_read_values(dev, values, num_channels);
    }
    i2c_release(dev->i2c_dev);
    /* If there was an error reading the I2C values then return that error,
     * otherwise we want to return the CMD_FORCE return value because there
     * could be an overflow non-fatal error to report. */
    return ret ? ret : force_ret;
}