/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2021 Aidan MacDonald
 *
 * 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 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "audiohw.h"
#include "sound.h"
#include "panic.h"
#include "pcm_sampr.h"
#include "pcm_sw_volume.h"
#include "system.h"
#include "i2c-async.h"

/* sample rates supported by the hardware */
#define CAPS (SAMPR_CAP_192 | SAMPR_CAP_176 | \
              SAMPR_CAP_96  | SAMPR_CAP_88  | SAMPR_CAP_64 | \
              SAMPR_CAP_48  | SAMPR_CAP_44  | SAMPR_CAP_32 | \
              SAMPR_CAP_24  | SAMPR_CAP_22  | SAMPR_CAP_16 | \
              SAMPR_CAP_12  | SAMPR_CAP_11  | SAMPR_CAP_8)

/* future proofing */
#if (HW_SAMPR_CAPS & ~CAPS) != 0
# error "incorrect HW_SAMPR_CAPS"
#endif

#ifndef HAVE_SW_VOLUME_CONTROL
# error "AK4376 requires HAVE_SW_VOLUME_CONTROL!"
#endif

/* NOTE: At present, only the FiiO M3K uses this driver so the handling of
 * the clock / audio interface is limited to I2S slave, 16-bit samples, with
 * DAC master clock provided directly on the MCLK input pin, fitting the
 * clock setup of the M3K.
 *
 * Feel free to expand upon this if another target ever needs this driver.
 */

/* Converts HW_FREQ_XX constants to register values */
static const uint8_t ak4376_fsel_to_hw[] = {
    HW_HAVE_192_(AK4376_FS_192,)
    HW_HAVE_176_(AK4376_FS_176,)
    HW_HAVE_96_(AK4376_FS_96,)
    HW_HAVE_88_(AK4376_FS_88,)
    HW_HAVE_64_(AK4376_FS_64,)
    HW_HAVE_48_(AK4376_FS_48,)
    HW_HAVE_44_(AK4376_FS_44,)
    HW_HAVE_32_(AK4376_FS_32,)
    HW_HAVE_24_(AK4376_FS_24,)
    HW_HAVE_22_(AK4376_FS_22,)
    HW_HAVE_16_(AK4376_FS_16,)
    HW_HAVE_12_(AK4376_FS_12,)
    HW_HAVE_11_(AK4376_FS_11,)
    HW_HAVE_8_(AK4376_FS_8,)
};

static int ak4376_regs[AK4376_NUM_REGS];

void ak4376_open(void)
{
    /* Initialize DAC state */
    for(int i = 0; i < AK4376_NUM_REGS; ++i)
        ak4376_regs[i] = -1;

    /* Initial reset after power-on */
    ak4376_set_pdn_pin(0);
    mdelay(1);
    ak4376_set_pdn_pin(1);
    mdelay(1);

    static const uint8_t init_config[] = {
        /* Ensure HPRHZ, HPLHZ are 0 */
        AK4376_REG_OUTPUT_MODE,  0x00,
        /* Mute all volume controls */
        AK4376_REG_MIXER,        AK4376_MIX_LCH | (AK4376_MIX_RCH << 4),
        AK4376_REG_LCH_VOLUME,   0x80,
        AK4376_REG_RCH_VOLUME,   0x00,
        AK4376_REG_AMP_VOLUME,   0x00,
        /* Clock source = MCLK, divider = 1 */
        AK4376_REG_DAC_CLK_SRC,  0x00,
        AK4376_REG_DAC_CLK_DIV,  0x00,
        /* I2S slave mode, 16-bit samples */
        AK4376_REG_AUDIO_IF_FMT, 0x03,
        /* Recommended by datasheet */
        AK4376_REG_ADJUST1,      0x20,
        AK4376_REG_ADJUST2,      0x05,
        /* Power controls */
        AK4376_REG_PWR2,         0x33,
        AK4376_REG_PWR3,         0x01,
        AK4376_REG_PWR4,         0x03,
    };

    /* Write initial configuration prior to power-up */
    for(size_t i = 0; i < ARRAYLEN(init_config); i += 2)
        ak4376_write(init_config[i], init_config[i+1]);
}

void ak4376_close(void)
{
    /* Shut off power */
    ak4376_write(AK4376_REG_PWR3, 0x00);
    ak4376_write(AK4376_REG_PWR4, 0x00);
    ak4376_write(AK4376_REG_PWR2, 0x00);

    /* PDN pin low */
    ak4376_set_pdn_pin(0);
}

void ak4376_write(int reg, int value)
{
    /* Ensure value is sensible and differs from the last set value */
    if((value & 0xff) == value && ak4376_regs[reg] != value) {
        int r = i2c_reg_write1(AK4376_BUS, AK4376_ADDR, reg, value);
        if(r == I2C_STATUS_OK)
            ak4376_regs[reg] = value;
        else
            ak4376_regs[reg] = -1;
    }
}

int ak4376_read(int reg)
{
    /* Only read from I2C if we don't already know the value */
    if(ak4376_regs[reg] < 0)
        ak4376_regs[reg] = i2c_reg_read1(AK4376_BUS, AK4376_ADDR, reg);

    return ak4376_regs[reg];
}

static int round_step_up(int x, int step)
{
    int rem = x % step;
    if(rem > 0)
        rem -= step;
    return x - rem;
}

static void calc_volumes(int vol, int* dig, int* sw)
{
    /* Apply digital volume */
    *dig = round_step_up(vol, AK4376_DIG_VOLUME_STEP);
    *dig = MIN(*dig, AK4376_DIG_VOLUME_MAX);
    *dig = MAX(*dig, AK4376_DIG_VOLUME_MIN);
    vol -= *dig;

    /* Seems that this is the allowable range for software volume */
    *sw = MIN(vol, 60);
    *sw = MAX(*sw, -730);
    vol -= *sw;
}

static int dig_vol_to_hw(int vol)
{
    if(vol < AK4376_DIG_VOLUME_MIN) return 0;
    if(vol > AK4376_DIG_VOLUME_MAX) return 31;
    return (vol - AK4376_DIG_VOLUME_MIN) / AK4376_DIG_VOLUME_STEP + 1;
}

static int amp_vol_to_hw(int vol)
{
    if(vol < AK4376_AMP_VOLUME_MIN) return 0;
    if(vol > AK4376_AMP_VOLUME_MAX) return 14;
    return (vol - AK4376_AMP_VOLUME_MIN) / AK4376_AMP_VOLUME_STEP + 1;
}

void ak4376_set_volume(int vol_l, int vol_r)
{
    int amp;
    int dig_l, sw_l;
    int dig_r, sw_r;

    if(vol_l <= AK4376_MIN_VOLUME && vol_r <= AK4376_MIN_VOLUME) {
        /* Special case for full mute */
        amp = AK4376_AMP_VOLUME_MUTE;
        dig_l = dig_r = AK4376_DIG_VOLUME_MUTE;
        sw_l = sw_r = PCM_MUTE_LEVEL;
    } else {
        /* Amp is a mono control -- calculate based on the loudest channel.
         * The quieter channel then gets reduced more by digital controls. */
        amp = round_step_up(MAX(vol_l, vol_r), AK4376_AMP_VOLUME_STEP);
        amp = MIN(amp, AK4376_AMP_VOLUME_MAX);
        amp = MAX(amp, AK4376_AMP_VOLUME_MIN);

        /* Other controls are stereo */
        calc_volumes(vol_l - amp, &dig_l, &sw_l);
        calc_volumes(vol_r - amp, &dig_r, &sw_r);
    }

    ak4376_write(AK4376_REG_LCH_VOLUME, dig_vol_to_hw(dig_l) | (1 << 7));
    ak4376_write(AK4376_REG_RCH_VOLUME, dig_vol_to_hw(dig_r));
    ak4376_write(AK4376_REG_AMP_VOLUME, amp_vol_to_hw(amp));
    pcm_set_master_volume(sw_l, sw_r);
}

void ak4376_set_filter_roll_off(int val)
{
    int reg = ak4376_read(AK4376_REG_FILTER);
    reg &= ~0xc0;
    reg |= (val & 3) << 6;
    ak4376_write(AK4376_REG_FILTER, reg);
}

void ak4376_set_freqmode(int fsel, int mult, int power_mode)
{
    /* Calculate clock mode for frequency. Multipliers of 32/64 are only
     * for rates >= 256 KHz which are not supported by Rockbox, so they
     * are commented out -- but they're in the correct place. */
    int clock_mode = ak4376_fsel_to_hw[fsel];
    switch(mult) {
    /* case 32: */
    case 256:
        break;
    /* case 64: */
    case 512:
        clock_mode |= 0x20;
        break;
    case 1024:
        clock_mode |= 0x40;
        break;
    case 128:
        clock_mode |= 0x60;
        break;
    default:
        panicf("ak4376: bad master clock multiple %d", mult);
        return;
    }

    /* Handle the DSMLP bit in the MODE_CTRL register */
    int mode_ctrl = 0x00;
    if(power_mode == SOUND_LOW_POWER || hw_freq_sampr[fsel] <= SAMPR_12)
        mode_ctrl |= 0x40;

    /* Handle the LPMODE bit */
    int pwr3 = power_mode == SOUND_LOW_POWER ? 0x11 : 0x01;

    /* The datasheet says the HP amp must be powered down before changing
     * the operating mode of the DAC or HP amp. I'm assuming this means
     * the amp must be shut down when changing DSMLP or LPMODE. */
    int cur_mode_ctrl = ak4376_read(AK4376_REG_MODE_CTRL);
    int cur_pwr3 = ak4376_read(AK4376_REG_PWR3);
    bool disable_amp = mode_ctrl != cur_mode_ctrl || pwr3 != cur_pwr3;

    /* Program the new settings */
    if(disable_amp)
        ak4376_write(AK4376_REG_PWR4, 0x00);

    ak4376_write(AK4376_REG_CLOCK_MODE, clock_mode);
    ak4376_write(AK4376_REG_MODE_CTRL, mode_ctrl);
    ak4376_write(AK4376_REG_PWR3, pwr3);

    if(disable_amp) {
        ak4376_write(AK4376_REG_PWR4, 0x03);
        mdelay(26);
    }
}
