/*
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <AP_HAL/AP_HAL.h>

#if CONFIG_HAL_BOARD_SUBTYPE == HAL_BOARD_SUBTYPE_LINUX_BEBOP
#include "CameraSensor_Mt9v117.h"

#include <utility>

#include "GPIO.h"

/* Cam sensor register definitions */
#define CHIP_ID                 0x0
#define MT9V117_CHIP_ID             0x2282
#define COMMAND_REGISTER        0x0040
#define     HOST_COMMAND_OK (1 << 15)
#define     HOST_COMMAND_2  (1 << 2)
#define     HOST_COMMAND_1  (1 << 1)
#define     HOST_COMMAND_0  (1 << 0)
#define PAD_SLEW                0x0030
#define RESET_AND_MISC_CONTROL  0x001a
#define     RESET_SOC_I2C   (1 << 0)
#define ACCESS_CTL_STAT         0x0982
#define PHYSICAL_ADDRESS_ACCESS 0x098a
#define LOGICAL_ADDRESS_ACCESS  0x098e
#define AE_TRACK_JUMP_DIVISOR   0xa812
#define CAM_AET_SKIP_FRAMES     0xc868

#define AE_RULE_VAR     9
#define     AE_RULE_ALGO_OFFSET                         4
#define         AE_RULE_ALGO_AVERAGE    0
#define         AE_RULE_ALGO_WEIGHTED   1
#define AE_TRACK_VAR    10
#define AWB_VAR         11
#define     AWB_PIXEL_THRESHOLD_COUNT_OFFSET            64
#define LOW_LIGHT_VAR   15
#define CAM_CTRL_VAR    18
#define     CAM_SENSOR_CFG_Y_ADDR_START_OFFSET          0
#define     CAM_SENSOR_CFG_X_ADDR_START_OFFSET          2
#define     CAM_SENSOR_CFG_Y_ADDR_END_OFFSET            4
#define     CAM_SENSOR_CFG_X_ADDR_END_OFFSET            6
#define     CAM_SENSOR_CFG_FRAME_LENGTH_LINES_OFFSET    14
#define     CAM_SENSOR_CFG_CPIPE_LAST_ROW_OFFSET        20
#define     CAM_SENSOR_CFG_FDPERIOD_60HZ                22
#define     CAM_SENSOR_CFG_FDPERIOD_50HZ                24
#define     CAM_SENSOR_CFG_MAX_FDZONE_60_OFFSET         26
#define     CAM_SENSOR_CFG_MAX_FDZONE_50_OFFSET         28
#define     CAM_SENSOR_CFG_TARGET_FDZONE_60_OFFSET      30
#define     CAM_SENSOR_CFG_TARGET_FDZONE_50_OFFSET      32
#define     CAM_SENSOR_CONTROL_READ_MODE_OFFSET         40
#define         CAM_SENSOR_CONTROL_Y_SKIP_EN                (1 << 2)
#define         CAM_SENSOR_CONTROL_VERT_FLIP_EN             (1 << 1)
#define         CAM_SENSOR_CONTROL_HORZ_MIRROR_EN           (1 << 0)
#define     CAM_FLICKER_PERIOD_OFFSET                   62
#define         CAM_FLICKER_PERIOD_60HZ 0
#define         CAM_FLICKER_PERIOD_50HZ 1
#define     CAM_CROP_WINDOW_XOFFSET_OFFSET              72
#define     CAM_CROP_WINDOW_YOFFSET_OFFSET              74
#define     CAM_CROP_WINDOW_WIDTH_OFFSET                76
#define     CAM_CROP_WINDOW_HEIGHT_OFFSET               78
#define     CAM_CROP_MODE_OFFSET                        80
#define     CAM_OUTPUT_WIDTH_OFFSET                     84
#define     CAM_OUTPUT_HEIGHT_OFFSET                    86
#define     CAM_OUTPUT_FORMAT_OFFSET                    88
#define         CAM_OUTPUT_FORMAT_RGB_565                   (0 << 12)
#define         CAM_OUTPUT_FORMAT_RGB_555                   (1 << 12)
#define         CAM_OUTPUT_FORMAT_RGB_444X                  (2 << 12)
#define         CAM_OUTPUT_FORMAT_RGB_X444                  (3 << 12)
#define         CAM_OUTPUT_FORMAT_BAYER_10                  (0 << 10)
#define         CAM_OUTPUT_FORMAT_YUV                       (0 << 8)
#define         CAM_OUTPUT_FORMAT_RGB                       (1 << 8)
#define         CAM_OUTPUT_FORMAT_BAYER                     (2 << 8)
#define         CAM_OUTPUT_FORMAT_BT656_ENABLE              (1 << 3)
#define         CAM_OUTPUT_FORMAT_MONO_ENABLE               (1 << 2)
#define         CAM_OUTPUT_FORMAT_SWAP_BYTES                (1 << 1)
#define         CAM_OUTPUT_FORMAT_SWAP_RED_BLUE             (1 << 0)
#define     CAM_STAT_AWB_HG_WINDOW_XSTART_OFFSET        236
#define     CAM_STAT_AWB_HG_WINDOW_YSTART_OFFSET        238
#define     CAM_STAT_AWB_HG_WINDOW_XEND_OFFSET          240
#define     CAM_STAT_AWB_HG_WINDOW_YEND_OFFSET          242
#define     CAM_STAT_AE_INITIAL_WINDOW_XSTART_OFFSET    244
#define     CAM_STAT_AE_INITIAL_WINDOW_YSTART_OFFSET    246
#define     CAM_STAT_AE_INITIAL_WINDOW_XEND_OFFSET      248
#define     CAM_STAT_AE_INITIAL_WINDOW_YEND_OFFSET      250
#define     CAM_LL_START_GAIN_METRIC_OFFSET             278
#define     CAM_LL_STOP_GAIN_METRIC_OFFSET              280
#define SYSMGR_VAR      23
#define     SYSMGR_NEXT_STATE_OFFSET                    0
#define PATCHLDR_VAR    24
#define     PATCHLDR_LOADER_ADDRESS_OFFSET              0
#define     PATCHLDR_PATCH_ID_OFFSET                    2
#define     PATCHLDR_FIRMWARE_ID_OFFSET                   4

extern const AP_HAL::HAL& hal;

using namespace Linux;

CameraSensor_Mt9v117::CameraSensor_Mt9v117(const char *device_path,
                                           AP_HAL::OwnPtr<AP_HAL::I2CDevice> dev,
                                           enum mt9v117_res res,
                                           uint16_t nrst_gpio, uint32_t clock_freq)
    : CameraSensor(device_path)
    , _dev(std::move(dev))
    , _nrst_gpio(nrst_gpio)
    , _clock_freq(clock_freq)
{
    if (!_dev) {
        AP_HAL::panic("Could not find I2C bus for CameraSensor_Mt9v117");
    }

    switch (res) {
    case MT9V117_QVGA:
        _init_sensor();
        _configure_sensor_qvga();
        break;
    default:
        AP_HAL::panic("mt9v117: unsupported resolution\n");
        break;
    }

    _itu656_enable();
    _config_change();
}

uint8_t CameraSensor_Mt9v117::_read_reg8(uint16_t reg)
{
    uint8_t buf[2];
    buf[0] = (uint8_t) (reg >> 8);
    buf[1] = (uint8_t) (reg & 0xFF);

    if (!_dev->transfer(buf, 2, buf, 1)) {
        hal.console->printf("mt9v117: error reading 0x%2x\n", reg);
        return 0;
    }

    return buf[0];
}

void CameraSensor_Mt9v117::_write_reg8(uint16_t reg, uint8_t val)
{
    uint8_t buf[3];
    buf[0] = (uint8_t) (reg >> 8);
    buf[1] = (uint8_t) (reg & 0xFF);
    buf[2] = val;

    if (!_dev->transfer(buf, 3, nullptr, 0)) {
        hal.console->printf("mt9v117: error writing 0x%2x\n", reg);
    }
}

uint16_t CameraSensor_Mt9v117::_read_reg16(uint16_t reg)
{
    uint8_t buf[2];
    buf[0] = (uint8_t) (reg >> 8);
    buf[1] = (uint8_t) (reg & 0xFF);

    if (!_dev->transfer(buf, 2, buf, 2)) {
        hal.console->printf("mt9v117: error reading 0x%4x\n", reg);
        return 0;
    }

    return (buf[0] << 8 | buf[1]);
}

void CameraSensor_Mt9v117::_write_reg16(uint16_t reg, uint16_t val)
{
    uint8_t buf[4];
    buf[0] = (uint8_t) (reg >> 8);
    buf[1] = (uint8_t) (reg & 0xFF);
    buf[2] = (uint8_t) (val >> 8);
    buf[3] = (uint8_t) (val & 0xFF);

    if (!_dev->transfer(buf, 4, nullptr, 0)) {
        hal.console->printf("mt9v117: error writing 0x%4x\n", reg);
    }
}

void CameraSensor_Mt9v117::_write_reg32(uint16_t reg, uint32_t val)
{
    uint8_t buf[6];
    buf[0] = (uint8_t) (reg >> 8);
    buf[1] = (uint8_t) (reg & 0xFF);
    buf[2] = (uint8_t) (val >> 24);
    buf[3] = (uint8_t) ((val >> 16) & 0xFF);
    buf[4] = (uint8_t) ((val >> 8) & 0xFF);
    buf[5] = (uint8_t) (val & 0xFF);

    if (!_dev->transfer(buf, 6, nullptr, 0)) {
        hal.console->printf("mt9v117: error writing 0x%8x\n", reg);
    }
}

inline uint16_t CameraSensor_Mt9v117::_var2reg(uint16_t var,
                                               uint16_t offset)
{
    return (0x8000 | (var << 10) | offset);
}

uint16_t CameraSensor_Mt9v117::_read_var16(uint16_t var, uint16_t offset)
{
    uint16_t reg = _var2reg(var, offset);
    return _read_reg16(reg);
}

void CameraSensor_Mt9v117::_write_var16(uint16_t var,
                                        uint16_t offset,
                                        uint16_t value)
{
    uint16_t reg = _var2reg(var, offset);
    _write_reg16(reg, value);
}

uint8_t CameraSensor_Mt9v117::_read_var8(uint16_t var, uint16_t offset)
{
    uint16_t reg = _var2reg(var, offset);
    return _read_reg8(reg);
}

void CameraSensor_Mt9v117::_write_var8(uint16_t var,
                                       uint16_t offset,
                                       uint8_t value)
{
    uint16_t reg = _var2reg(var, offset);
    return _write_reg8(reg, value);
}

void CameraSensor_Mt9v117::_write_var32(uint16_t var,
                                        uint16_t offset,
                                        uint32_t value)
{
    uint16_t reg = _var2reg(var, offset);
    return _write_reg32(reg, value);
}

void CameraSensor_Mt9v117::_config_change()
{
    uint16_t cmd_status;
    /* timeout 100ms delay 10ms */
    int timeout = 10;

    _write_var8(SYSMGR_VAR, SYSMGR_NEXT_STATE_OFFSET, 0x28);

    _write_reg16(COMMAND_REGISTER, HOST_COMMAND_OK | HOST_COMMAND_1);

    do {
        hal.scheduler->delay(10);
        cmd_status = _read_reg16(COMMAND_REGISTER);
        timeout--;
    } while (((cmd_status & HOST_COMMAND_1) != 0) &&
              (timeout > 0));

    if (timeout == 0) {
        hal.console->printf("mt9v117:"
                            "timeout waiting or command to complete\n");
    }

    if ((cmd_status & HOST_COMMAND_OK) == 0) {
        hal.console->printf("mt9v117:config change failed\n");
    }
}

void CameraSensor_Mt9v117::_itu656_enable()
{
    _write_var16(CAM_CTRL_VAR, CAM_OUTPUT_FORMAT_OFFSET,
                 _read_var16(CAM_CTRL_VAR, CAM_OUTPUT_FORMAT_OFFSET) |
                 CAM_OUTPUT_FORMAT_BT656_ENABLE);
}

void CameraSensor_Mt9v117::_soft_reset()
{
    _write_reg16(RESET_AND_MISC_CONTROL, RESET_SOC_I2C);
    _write_reg16(RESET_AND_MISC_CONTROL, 0);
    /* sleep 50ms after soft reset */
    hal.scheduler->delay(50);
}

void CameraSensor_Mt9v117::_apply_patch()
{
    uint16_t cmd_status;
    /* timeout 100ms delay 10ms */
    int timeout = 10;

    /* Errata item 2 */
    _write_reg16(0x301a, 0x10d0);
    _write_reg16(0x31c0, 0x1404);
    _write_reg16(0x3ed8, 0x879c);
    _write_reg16(0x3042, 0x20e1);
    _write_reg16(0x30d4, 0x8020);
    _write_reg16(0x30c0, 0x0026);
    _write_reg16(0x301a, 0x10d4);

    /* Errata item 6 */
    _write_var16(AE_TRACK_VAR, 0x0002, 0x00d3);
    _write_var16(CAM_CTRL_VAR, 0x0078, 0x00a0);
    _write_var16(CAM_CTRL_VAR, 0x0076, 0x0140);

    /* Errata item 8 */
    _write_var16(LOW_LIGHT_VAR, 0x0004, 0x00fc);
    _write_var16(LOW_LIGHT_VAR, 0x0038, 0x007f);
    _write_var16(LOW_LIGHT_VAR, 0x003a, 0x007f);
    _write_var16(LOW_LIGHT_VAR, 0x003c, 0x007f);
    _write_var16(LOW_LIGHT_VAR, 0x0004, 0x00f4);

    /* Patch 0403; Critical; Sensor optimization */
    _write_reg16(ACCESS_CTL_STAT, 0x0001);
    _write_reg16(PHYSICAL_ADDRESS_ACCESS, 0x7000);

    /* write patch */
    for (unsigned int i = 0; i < MT9V117_PATCH_LINE_NUM; i++) {
        _dev->transfer(_patch_lines[i].data, _patch_lines[i].size, nullptr, 0);
    }

    _write_reg16(LOGICAL_ADDRESS_ACCESS, 0x0000);

    _write_var16(PATCHLDR_VAR, PATCHLDR_LOADER_ADDRESS_OFFSET, 0x05d8);
    _write_var16(PATCHLDR_VAR, PATCHLDR_PATCH_ID_OFFSET, 0x0403);
    _write_var32(PATCHLDR_VAR, PATCHLDR_FIRMWARE_ID_OFFSET, 0x00430104);

    _write_reg16(COMMAND_REGISTER, HOST_COMMAND_OK | HOST_COMMAND_0);

    do {
        hal.scheduler->delay(10);
        cmd_status = _read_reg16(COMMAND_REGISTER);
        timeout--;
    } while (((cmd_status & HOST_COMMAND_0) != 0) &&
             (timeout > 0));

    if ((cmd_status & HOST_COMMAND_OK) == 0) {
        hal.console->printf("mt9v117:patch apply failed\n");
    }
}

void CameraSensor_Mt9v117::_set_basic_settings()
{
    _write_var32(AWB_VAR, AWB_PIXEL_THRESHOLD_COUNT_OFFSET, 50000);
    _write_var16(AE_RULE_VAR, AE_RULE_ALGO_OFFSET, AE_RULE_ALGO_AVERAGE);

    /* Set pixclk pad slew to 6 and data out pad slew to 1 */
    _write_reg16(PAD_SLEW, _read_reg16(PAD_SLEW) | 0x0600 | 0x0001);
}

void CameraSensor_Mt9v117::_configure_sensor_qvga()
{
    _write_var16(CAM_CTRL_VAR, CAM_SENSOR_CFG_X_ADDR_START_OFFSET, 16);
    _write_var16(CAM_CTRL_VAR, CAM_SENSOR_CFG_X_ADDR_END_OFFSET, 663);
    _write_var16(CAM_CTRL_VAR, CAM_SENSOR_CFG_Y_ADDR_START_OFFSET, 8);
    _write_var16(CAM_CTRL_VAR, CAM_SENSOR_CFG_Y_ADDR_END_OFFSET,  501);
    _write_var16(CAM_CTRL_VAR, CAM_SENSOR_CFG_CPIPE_LAST_ROW_OFFSET, 243);
    _write_var16(CAM_CTRL_VAR, CAM_SENSOR_CFG_FRAME_LENGTH_LINES_OFFSET, 283);
    _write_var16(CAM_CTRL_VAR, CAM_SENSOR_CONTROL_READ_MODE_OFFSET,
                 CAM_SENSOR_CONTROL_Y_SKIP_EN);
    _write_var16(CAM_CTRL_VAR, CAM_SENSOR_CFG_MAX_FDZONE_60_OFFSET, 1);
    _write_var16(CAM_CTRL_VAR, CAM_SENSOR_CFG_TARGET_FDZONE_60_OFFSET, 1);

    _write_reg8(AE_TRACK_JUMP_DIVISOR, 0x03);
    _write_reg8(CAM_AET_SKIP_FRAMES, 0x02);

    _write_var16(CAM_CTRL_VAR, CAM_OUTPUT_WIDTH_OFFSET, 320);
    _write_var16(CAM_CTRL_VAR, CAM_OUTPUT_HEIGHT_OFFSET, 240);

    /* Set gain metric for 111.2 fps
     * The final fps depends on the input clock
     * (89.2fps on bebop) so a modification may be needed here */
    _write_var16(CAM_CTRL_VAR, CAM_LL_START_GAIN_METRIC_OFFSET, 0x03e8);
    _write_var16(CAM_CTRL_VAR, CAM_LL_STOP_GAIN_METRIC_OFFSET, 0x1770);

    /* set crop window */
    _write_var16(CAM_CTRL_VAR, CAM_CROP_WINDOW_XOFFSET_OFFSET, 0);
    _write_var16(CAM_CTRL_VAR, CAM_CROP_WINDOW_YOFFSET_OFFSET, 0);
    _write_var16(CAM_CTRL_VAR, CAM_CROP_WINDOW_WIDTH_OFFSET, 640);
    _write_var16(CAM_CTRL_VAR, CAM_CROP_WINDOW_HEIGHT_OFFSET, 240);

    /* Enable auto-stats mode */
    _write_var8(CAM_CTRL_VAR, CAM_CROP_MODE_OFFSET, 3);
    _write_var16(CAM_CTRL_VAR, CAM_STAT_AWB_HG_WINDOW_XEND_OFFSET, 319);
    _write_var16(CAM_CTRL_VAR, CAM_STAT_AWB_HG_WINDOW_YEND_OFFSET, 239);
    _write_var16(CAM_CTRL_VAR, CAM_STAT_AE_INITIAL_WINDOW_XSTART_OFFSET, 2);
    _write_var16(CAM_CTRL_VAR, CAM_STAT_AE_INITIAL_WINDOW_YSTART_OFFSET, 2);
    _write_var16(CAM_CTRL_VAR, CAM_STAT_AE_INITIAL_WINDOW_XEND_OFFSET, 65);
    _write_var16(CAM_CTRL_VAR, CAM_STAT_AE_INITIAL_WINDOW_YEND_OFFSET, 49);
}

void CameraSensor_Mt9v117::_init_sensor()
{
    AP_HAL::DigitalSource *gpio_source;
    uint16_t id;

    if (_nrst_gpio != 0xFFFF) {
        gpio_source = hal.gpio->channel(_nrst_gpio);
        gpio_source->mode(HAL_GPIO_OUTPUT);
        gpio_source->write(1);
        uint32_t delay = 3.5f + (35.0f - 3.5f) *
                        (54000000.0f - (float)_clock_freq) /
                        (54000000.0f - 6000000.0f);
        hal.scheduler->delay(delay);
    }

    id = _read_reg16(CHIP_ID);
    if (id != MT9V117_CHIP_ID) {
        AP_HAL::panic("Mt9v117: bad chip id 0x%04x\n", id);
    }
    _soft_reset();
    _apply_patch();
    _set_basic_settings();
}

#endif