/***************************************************************************
 *   Copyright (C) 2021 by Federico Amedeo Izzo IU2NUO,                    *
 *                         Niccolò Izzo IU2KIN                             *
 *                         Frederik Saraci IU2NRO                          *
 *                         Silvano Seva IU2KWO                             *
 *                                                                         *
 *   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 <interfaces/gpio.h>
#include <interfaces/delays.h>
#include <hwconfig.h>
#include "AT1846S.h"

void AT1846S::init()
{
    i2c_writeReg16(0x30, 0x0001);   // Soft reset
    delayMs(160);

    i2c_writeReg16(0x30, 0x0004);   // Set pdn_reg (power down pin)

    i2c_writeReg16(0x04, 0x0FD0);   // Set clk_mode to 25.6MHz/26MHz
    i2c_writeReg16(0x0A, 0x7C20);   // Set 0x0A to its default value
    i2c_writeReg16(0x13, 0xA100);
    i2c_writeReg16(0x1F, 0x1001);   // Set gpio0 to ctcss_out/css_int/css_cmp
                                    // and gpio6 to sq, sq&ctcss/cdcss when sq_out_set=1
    i2c_writeReg16(0x31, 0x0031);
    i2c_writeReg16(0x33, 0x44A5);
    i2c_writeReg16(0x34, 0x2B89);
    i2c_writeReg16(0x41, 0x4122);   // Set voice_gain_tx (voice digital gain) to 0x22
    i2c_writeReg16(0x42, 0x1052);
    i2c_writeReg16(0x43, 0x0100);
    i2c_writeReg16(0x44, 0x07FF);   // Set gain_tx (voice digital gain after tx ADC downsample) to 0x7
    i2c_writeReg16(0x59, 0x0B90);   // Set c_dev (CTCSS/CDCSS TX FM deviation) to 0x10
                                    // and xmitter_dev (voice/subaudio TX FM deviation) to 0x2E
    i2c_writeReg16(0x47, 0x7F2F);
    i2c_writeReg16(0x4F, 0x2C62);
    i2c_writeReg16(0x53, 0x0094);
    i2c_writeReg16(0x54, 0x2A3C);
    i2c_writeReg16(0x55, 0x0081);
    i2c_writeReg16(0x56, 0x0B02);
    i2c_writeReg16(0x57, 0x1C00);
    i2c_writeReg16(0x58, 0x9CDD);   // Set ctcss_lpfil_bw to 250Hz bandwidth
                                    // and bypass ctcss_highpass_filter
                                    // and bypass ctcss_lowpass_filter
                                    // and enable void_lowpass_filter
                                    // and bypass voice_highpass_filter
                                    // and bypass pre/de-emphasis
                                    // and bypass vox_highpass_filter
                                    // and bypass vox_lowpass_filter
                                    // and enable rssi_lpfil_bw
    i2c_writeReg16(0x5A, 0x06DB);
    i2c_writeReg16(0x63, 0x16AD);
    i2c_writeReg16(0x67, 0x0628);   // Set DTMF C0 697Hz to ???
    i2c_writeReg16(0x68, 0x05E5);   // Set DTMF C1 770Hz to 13MHz and 26MHz
    i2c_writeReg16(0x69, 0x0555);   // Set DTMF C2 852Hz to ???
    i2c_writeReg16(0x6A, 0x04B8);   // Set DTMF C3 941Hz to ???
    i2c_writeReg16(0x6B, 0x02FE);   // Set DTMF C4 1209Hz to 13MHz and 26MHz
    i2c_writeReg16(0x6C, 0x01DD);   // Set DTMF C5 1336Hz
    i2c_writeReg16(0x6D, 0x00B1);   // Set DTMF C6 1477Hz
    i2c_writeReg16(0x6E, 0x0F82);   // Set DTMF C7 1633Hz
    i2c_writeReg16(0x6F, 0x017A);   // Set DTMF C0 2nd harmonic
    i2c_writeReg16(0x70, 0x004C);   // Set DTMF C1 2nd harmonic
    i2c_writeReg16(0x71, 0x0F1D);   // Set DTMF C2 2nd harmonic
    i2c_writeReg16(0x72, 0x0D91);   // Set DTMF C3 2nd harmonic
    i2c_writeReg16(0x73, 0x0A3E);   // Set DTMF C4 2nd harmonic
    i2c_writeReg16(0x74, 0x090F);   // Set DTMF C5 2nd harmonic
    i2c_writeReg16(0x75, 0x0833);   // Set DTMF C6 2nd harmonic
    i2c_writeReg16(0x76, 0x0806);   // Set DTMF C7 2nd harmonic

    i2c_writeReg16(0x30, 0x40A4);   // Set pdn_pin (power down enable)
                                    // and set rx_on
                                    // and set mute when rxno
                                    // and set xtal_mode to 26MHz/13MHz
    delayMs(160);

    i2c_writeReg16(0x30, 0x40A6);   // Start calibration
    delayMs(160);
    i2c_writeReg16(0x30, 0x4006);   // Stop calibration
    delayMs(160);

    i2c_writeReg16(0x40, 0x0031);
}

void AT1846S::setBandwidth(const AT1846S_BW band)
{
    if(band == AT1846S_BW::_25)
    {
        // 25kHz bandwidth
        i2c_writeReg16(0x15, 0x1F00);
        i2c_writeReg16(0x32, 0x7564);
        i2c_writeReg16(0x3A, 0x04C3);
        i2c_writeReg16(0x3C, 0x1B34);
        i2c_writeReg16(0x3F, 0x29D1);
        i2c_writeReg16(0x48, 0x1F3C);
        i2c_writeReg16(0x60, 0x0F17);
        i2c_writeReg16(0x62, 0x3263);
        i2c_writeReg16(0x65, 0x248A);
        i2c_writeReg16(0x66, 0xFFAE);
        i2c_writeReg16(0x7F, 0x0001);
        i2c_writeReg16(0x06, 0x0024);
        i2c_writeReg16(0x07, 0x0214);
        i2c_writeReg16(0x08, 0x0224);
        i2c_writeReg16(0x09, 0x0314);
        i2c_writeReg16(0x0A, 0x0324);
        i2c_writeReg16(0x0B, 0x0344);
        i2c_writeReg16(0x0C, 0x0384);
        i2c_writeReg16(0x0D, 0x1384);
        i2c_writeReg16(0x0E, 0x1B84);
        i2c_writeReg16(0x0F, 0x3F84);
        i2c_writeReg16(0x12, 0xE0EB);
        i2c_writeReg16(0x7F, 0x0000);
        maskSetRegister(0x30, 0x3000, 0x3000);
    }
    else
    {
        // 12.5kHz bandwidth
        i2c_writeReg16(0x15, 0x1100);
        i2c_writeReg16(0x32, 0x4495);
        i2c_writeReg16(0x3A, 0x00C3);
        i2c_writeReg16(0x3F, 0x29D1);
        i2c_writeReg16(0x3C, 0x1B34);
        i2c_writeReg16(0x48, 0x19B1);
        i2c_writeReg16(0x60, 0x0F17);
        i2c_writeReg16(0x62, 0x1425);
        i2c_writeReg16(0x65, 0x2494);
        i2c_writeReg16(0x66, 0xEB2E);
        i2c_writeReg16(0x7F, 0x0001);
        i2c_writeReg16(0x06, 0x0014);
        i2c_writeReg16(0x07, 0x020C);
        i2c_writeReg16(0x08, 0x0214);
        i2c_writeReg16(0x09, 0x030C);
        i2c_writeReg16(0x0A, 0x0314);
        i2c_writeReg16(0x0B, 0x0324);
        i2c_writeReg16(0x0C, 0x0344);
        i2c_writeReg16(0x0D, 0x1344);
        i2c_writeReg16(0x0E, 0x1B44);
        i2c_writeReg16(0x0F, 0x3F44);
        i2c_writeReg16(0x12, 0xE0EB);
        i2c_writeReg16(0x7F, 0x0000);
        maskSetRegister(0x30, 0x3000, 0x0000);
    }

    reloadConfig();
}

void AT1846S::setOpMode(const AT1846S_OpMode mode)
{
    if(mode == AT1846S_OpMode::DMR)
    {
        //
        // TODO: values copy-pasted from GD77 driver, they seems to work well
        // at least with M17
        //
        i2c_writeReg16(0x3A, 0x00C2);
        i2c_writeReg16(0x33, 0x45F5);
        i2c_writeReg16(0x41, 0x4731);
        i2c_writeReg16(0x42, 0x1036);
        i2c_writeReg16(0x43, 0x00BB);
        i2c_writeReg16(0x58, 0xBCFD);
        i2c_writeReg16(0x44, 0x06CC);
        i2c_writeReg16(0x40, 0x0031);
    }
    else
    {
        // FM mode
        i2c_writeReg16(0x58, 0x9C1D);
        i2c_writeReg16(0x40, 0x0030);
    }

    reloadConfig();
}

/*
 * Implementation of AT1846S I2C interface.
 *
 * On MD-UV3x0 the I2C interface towars the AT1846S is not connected to any
 * hardware peripheral and has to be implemented in software by bit-banging.
 */

void _i2c_start();
void _i2c_stop();
void _i2c_write(uint8_t val);
uint8_t _i2c_read(bool ack);

static constexpr uint8_t devAddr = 0x5C;

void AT1846S::i2c_init()
{
    gpio_setMode(I2C_SDA, INPUT);
    gpio_setMode(I2C_SCL, OUTPUT);
    gpio_clearPin(I2C_SCL);
}

void AT1846S::i2c_writeReg16(uint8_t reg, uint16_t value)
{
    /*
     * Beware of endianness!
     * When writing an AT1846S register, bits 15:8 must be sent first, followed
     * by bits 7:0.
     */
    uint8_t valHi = (value >> 8) & 0xFF;
    uint8_t valLo = value & 0xFF;

    _i2c_start();
    _i2c_write(devAddr);
    _i2c_write(reg);
    _i2c_write(valHi);
    _i2c_write(valLo);
    _i2c_stop();
}

uint16_t AT1846S::i2c_readReg16(uint8_t reg)
{
    _i2c_start();
    _i2c_write(devAddr);
    _i2c_write(reg);
    _i2c_start();
    _i2c_write(devAddr | 0x01);
    uint8_t valHi = _i2c_read(true);
    uint8_t valLo = _i2c_read(false);
    _i2c_stop();

    return (valHi << 8) | valLo;
}

/*
 * Software I2C routine
 */

void _i2c_start()
{
    gpio_setMode(I2C_SDA, OUTPUT);

    /*
     * Lines commented to keep SCL high when idle
     *
    gpio_clearPin(I2C_SCL);
    delayUs(2);
    */

    gpio_setPin(I2C_SDA);
    delayUs(2);

    gpio_setPin(I2C_SCL);
    delayUs(2);

    gpio_clearPin(I2C_SDA);
    delayUs(2);

    gpio_clearPin(I2C_SCL);
    delayUs(6);
}

void _i2c_stop()
{
    gpio_setMode(I2C_SDA, OUTPUT);

    gpio_clearPin(I2C_SCL);
    delayUs(2);

    gpio_clearPin(I2C_SDA);
    delayUs(2);

    gpio_setPin(I2C_SCL);
    delayUs(2);

    gpio_setPin(I2C_SDA);
    delayUs(2);

    /*
     * Lines commented to keep SCL high when idle
     *
    gpio_clearPin(I2C_SCL);
    delayUs(2);
    */
}

void _i2c_write(uint8_t val)
{
    gpio_setMode(I2C_SDA, OUTPUT);

    for(uint8_t i = 0; i < 8; i++)
    {
        gpio_clearPin(I2C_SCL);
        delayUs(1);

        if(val & 0x80)
        {
            gpio_setPin(I2C_SDA);
        }
        else
        {
            gpio_clearPin(I2C_SDA);
        }

        val <<= 1;
        delayUs(1);
        gpio_setPin(I2C_SCL);
        delayUs(2);
    }

    /* Ensure SCL is low before releasing SDA */
    gpio_clearPin(I2C_SCL);

    /* Clock cycle for slave ACK/NACK */
    gpio_setMode(I2C_SDA, INPUT_PULL_UP);
    delayUs(2);
    gpio_setPin(I2C_SCL);
    delayUs(2);
    gpio_clearPin(I2C_SCL);
    delayUs(1);

    /* Asserting SDA pin allows to fastly bring the line to idle state */
    gpio_setPin(I2C_SDA);
    gpio_setMode(I2C_SDA, OUTPUT);
    delayUs(6);
}

uint8_t _i2c_read(bool ack)
{
    gpio_setMode(I2C_SDA, INPUT_PULL_UP);
    gpio_clearPin(I2C_SCL);

    uint8_t value = 0;
    for(uint8_t i = 0; i < 8; i++)
    {
        delayUs(2);
        gpio_setPin(I2C_SCL);
        delayUs(2);

        value <<= 1;
        value |= gpio_readPin(I2C_SDA);

        gpio_clearPin(I2C_SCL);
    }

    /*
     * Set ACK/NACK state BEFORE putting SDA gpio to output mode.
     * This avoids spurious spikes which can be interpreted as NACKs
     */
    gpio_clearPin(I2C_SDA);
    gpio_setMode(I2C_SDA, OUTPUT);
    delayUs(2);
    if(!ack) gpio_setPin(I2C_SDA);

    /* Clock cycle for ACK/NACK */
    delayUs(2);
    gpio_setPin(I2C_SCL);
    delayUs(2);
    gpio_clearPin(I2C_SCL);
    delayUs(2);

    return value;
}
