/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id: wm8978.c 17847 2008-06-28 18:10:04Z bagder $
 *
 * Copyright (C) 2008 by Michael Sevakis
 *
 * Driver for WM8978 audio codec 
 *
 * 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 "config.h"
#include "system.h"
#include "audiohw.h"
#include "wmcodec.h"
#include "audio.h"
//#define LOGF_ENABLE
#include "logf.h"

/* #define to help adjust lower volume limit */
#define HW_VOL_MIN                0
#define HW_VOL_MUTE               0
#define HW_VOL_MAX               96
#define HW_VOL_ANA_MIN            0
#define HW_VOL_ANA_MAX           63
#define HW_VOL_DIG_MAX          255
#define HW_VOL_DIG_THRESHOLD    (HW_VOL_MAX - HW_VOL_ANA_MAX)
#define HW_VOL_DIG_MIN          (HW_VOL_DIG_MAX - 2*HW_VOL_DIG_THRESHOLD)

/* TODO: Define/refine an API for special hardware steps outside the
 * main codec driver such as special GPIO handling. */
extern void audiohw_enable_headphone_jack(bool enable);

const struct sound_settings_info audiohw_settings[] =
{
    [SOUND_VOLUME]        = {"dB", 0,  1, -90,   6, -25},
    [SOUND_BASS]          = {"dB", 0,  1, -12,  12,   0},
    [SOUND_TREBLE]        = {"dB", 0,  1, -12,  12,   0},
    [SOUND_BALANCE]       = {"%",  0,  1,-100, 100,   0},
    [SOUND_CHANNELS]      = {"",   0,  1,   0,   5,   0},
    [SOUND_STEREO_WIDTH]  = {"%",  0,  5,   0, 250, 100},
#ifdef HAVE_RECORDING
    [SOUND_LEFT_GAIN]     = {"dB", 1,  1,-128,  96,   0},
    [SOUND_RIGHT_GAIN]    = {"dB", 1,  1,-128,  96,   0},
#if 0
    [SOUND_MIC_GAIN]      = {"dB", 1,  1,-128, 108,  16},
#endif
#endif
#if 0
    [SOUND_BASS_CUTOFF]   = {"",   0,  1,   1,   4,   1},
    [SOUND_TREBLE_CUTOFF] = {"",   0,  1,   1,   4,   1},
#endif
};

static uint16_t wmc_regs[WMC_NUM_REGISTERS] =
{
    /* Initialized with post-reset default values - the 2-wire interface
     * cannot be read. Or-in additional bits desired for some registers. */
    [0 ... WMC_NUM_REGISTERS-1]   = 0x8000, /* To ID invalids in gaps */
    [WMC_SOFTWARE_RESET]          = 0x000,
    [WMC_POWER_MANAGEMENT1]       = 0x000,
    [WMC_POWER_MANAGEMENT2]       = 0x000,
    [WMC_POWER_MANAGEMENT3]       = 0x000,
    [WMC_AUDIO_INTERFACE]         = 0x050,
    [WMC_COMPANDING_CTRL]         = 0x000,
    [WMC_CLOCK_GEN_CTRL]          = 0x140,
    [WMC_ADDITIONAL_CTRL]         = 0x000,
    [WMC_GPIO]                    = 0x000,
    [WMC_JACK_DETECT_CONTROL1]    = 0x000,
    [WMC_DAC_CONTROL]             = 0x000,
    [WMC_LEFT_DAC_DIGITAL_VOL]    = 0x0ff | WMC_VU,
    [WMC_RIGHT_DAC_DIGITAL_VOL]   = 0x0ff | WMC_VU,
    [WMC_JACK_DETECT_CONTROL2]    = 0x000,
    [WMC_ADC_CONTROL]             = 0x100,
    [WMC_LEFT_ADC_DIGITAL_VOL]    = 0x0ff | WMC_VU,
    [WMC_RIGHT_ADC_DIGITAL_VOL]   = 0x0ff | WMC_VU,
    [WMC_EQ1_LOW_SHELF]           = 0x12c,
    [WMC_EQ2_PEAK1]               = 0x02c,
    [WMC_EQ3_PEAK2]               = 0x02c,
    [WMC_EQ4_PEAK3]               = 0x02c,
    [WMC_EQ5_HIGH_SHELF]          = 0x02c,
    [WMC_DAC_LIMITER1]            = 0x032,
    [WMC_DAC_LIMITER2]            = 0x000,
    [WMC_NOTCH_FILTER1]           = 0x000,
    [WMC_NOTCH_FILTER2]           = 0x000,
    [WMC_NOTCH_FILTER3]           = 0x000,
    [WMC_NOTCH_FILTER4]           = 0x000,
    [WMC_ALC_CONTROL1]            = 0x038,
    [WMC_ALC_CONTROL2]            = 0x00b,
    [WMC_ALC_CONTROL3]            = 0x032,
    [WMC_NOISE_GATE]              = 0x000,
    [WMC_PLL_N]                   = 0x008,
    [WMC_PLL_K1]                  = 0x00c,
    [WMC_PLL_K2]                  = 0x093,
    [WMC_PLL_K3]                  = 0x0e9,
    [WMC_3D_CONTROL]              = 0x000,
    [WMC_BEEP_CONTROL]            = 0x000,
    [WMC_INPUT_CTRL]              = 0x033,
    [WMC_LEFT_INP_PGA_GAIN_CTRL]  = 0x010,
    [WMC_RIGHT_INP_PGA_GAIN_CTRL] = 0x010,
    [WMC_LEFT_ADC_BOOST_CTRL]     = 0x100,
    [WMC_RIGHT_ADC_BOOST_CTRL]    = 0x100,
    [WMC_OUTPUT_CTRL]             = 0x002,
    [WMC_LEFT_MIXER_CTRL]         = 0x001,
    [WMC_RIGHT_MIXER_CTRL]        = 0x001,
    [WMC_LOUT1_HP_VOLUME_CTRL]    = 0x039 | WMC_VU | WMC_ZC,
    [WMC_ROUT1_HP_VOLUME_CTRL]    = 0x039 | WMC_VU | WMC_ZC,
    [WMC_LOUT2_SPK_VOLUME_CTRL]   = 0x039 | WMC_VU | WMC_ZC,
    [WMC_ROUT2_SPK_VOLUME_CTRL]   = 0x039 | WMC_VU | WMC_ZC,
    [WMC_OUT3_MIXER_CTRL]         = 0x001,
    [WMC_OUT4_MONO_MIXER_CTRL]    = 0x001,
};

struct
{
    int vol_l;
    int vol_r;
    bool ahw_mute;
} wmc_vol =
{
    HW_VOL_MUTE, HW_VOL_MUTE, false
};

static void wmc_write(unsigned int reg, unsigned int val)
{
    if (reg >= WMC_NUM_REGISTERS || (wmc_regs[reg] & 0x8000))
    {
        logf("wm8978 invalid register: %d", reg);
        return;
    }

    wmc_regs[reg] = val & ~0x8000;
    wmcodec_write(reg, val);
}

static void wmc_set(unsigned int reg, unsigned int bits)
{
    wmc_write(reg, wmc_regs[reg] | bits);
}

static void wmc_clear(unsigned int reg, unsigned int bits)
{
    wmc_write(reg, wmc_regs[reg] & ~bits);
}

static void wmc_write_masked(unsigned int reg, unsigned int bits,
                             unsigned int mask)
{
    wmc_write(reg, (wmc_regs[reg] & ~mask) | (bits & mask));
}

/* convert tenth of dB volume (-890..60) to master volume register value
 * (000000...111111) */
int tenthdb2master(int db)
{
    /* -90dB to +6dB 1dB steps (96 levels) 7bits */
    /* 1100000 ==  +6dB  (0x60,96)               */
    /* 1101010 ==   0dB  (0x5a,90)               */
    /* 1000001 == -57dB  (0x21,33,DAC)           */
    /* 0000001 == -89dB  (0x01,01)               */
    /* 0000000 == -90dB  (0x00,00,Mute)          */
    if (db <= VOLUME_MIN)
    {
        return 0x0;
    }
    else
    {
        return (db - VOLUME_MIN) / 10;
    }
}

void audiohw_preinit(void)
{
    /* 1. Turn on external power supplies. Wait for supply voltage to settle. */

    /* Step 1 should be completed already. Reset and return all registers to
     * defaults */
    wmcodec_write(WMC_SOFTWARE_RESET, 0xff);
    sleep(HZ/10);

    /* 2. Mute all analogue outputs */
    wmc_set(WMC_LOUT1_HP_VOLUME_CTRL, WMC_MUTE);
    wmc_set(WMC_ROUT1_HP_VOLUME_CTRL, WMC_MUTE);
    wmc_set(WMC_LOUT2_SPK_VOLUME_CTRL, WMC_MUTE);
    wmc_set(WMC_ROUT2_SPK_VOLUME_CTRL, WMC_MUTE);
    wmc_set(WMC_OUT3_MIXER_CTRL, WMC_MUTE);
    wmc_set(WMC_OUT4_MONO_MIXER_CTRL, WMC_MUTE);
    wmc_set(WMC_INPUT_CTRL, 0x000);

    /* 3. Set L/RMIXEN = 1 and DACENL/R = 1 in register R3. */
    wmc_write(WMC_POWER_MANAGEMENT3,
              WMC_RMIXEN | WMC_LMIXEN | WMC_DACENR | WMC_DACENL);

    /* 4. Set BUFIOEN = 1 and VMIDSEL[1:0] to required value in register
     *    R1. Wait for VMID supply to settle */
    wmc_write(WMC_POWER_MANAGEMENT1, WMC_BUFIOEN | WMC_VMIDSEL_300K);
    sleep(HZ/10);

    /* 5. Set BIASEN = 1 in register R1. */
    wmc_set(WMC_POWER_MANAGEMENT1, WMC_BIASEN);
}

void audiohw_postinit(void)
{
    sleep(HZ);

    /* 6. Set L/ROUTEN = 1 in register R2. */
    wmc_write(WMC_POWER_MANAGEMENT2, WMC_LOUT1EN | WMC_ROUT1EN);

    /* 7. Enable other mixers as required */

    /* 8. Enable other outputs as required */

    /* 9. Set remaining registers */
    wmc_write(WMC_AUDIO_INTERFACE, WMC_WL_16 | WMC_FMT_I2S
                                   | WMC_DACLRSWAP | WMC_ADCLRSWAP);
    wmc_write(WMC_DAC_CONTROL, WMC_DACOSR_128 | WMC_AMUTE);

    /* Specific to HW clocking */
    wmc_write(WMC_CLOCK_GEN_CTRL, WMC_MCLKDIV_1_5 | WMC_BCLKDIV_8 | WMC_MS);
    wmc_write(WMC_ADDITIONAL_CTRL, WMC_SR_48KHZ); /* 44.1 */

    /* Initialize to minimum volume */
    wmc_write_masked(WMC_LEFT_DAC_DIGITAL_VOL, HW_VOL_DIG_MIN, WMC_DVOL);
    wmc_write_masked(WMC_LOUT1_HP_VOLUME_CTRL, HW_VOL_ANA_MIN, WMC_AVOL);
    wmc_write_masked(WMC_RIGHT_DAC_DIGITAL_VOL, HW_VOL_DIG_MIN, WMC_DVOL);
    wmc_write_masked(WMC_ROUT1_HP_VOLUME_CTRL, HW_VOL_ANA_MIN, WMC_AVOL);

    /* ADC silenced */
    wmc_write_masked(WMC_LEFT_ADC_DIGITAL_VOL, 0x00, WMC_DVOL);
    wmc_write_masked(WMC_RIGHT_ADC_DIGITAL_VOL, 0x00, WMC_DVOL);

    audiohw_enable_headphone_jack(true);
}

void audiohw_set_headphone_vol(int vol_l, int vol_r)
{
    int prev_l = wmc_vol.vol_l;
    int prev_r = wmc_vol.vol_r;
    int dac_l, dac_r;

    wmc_vol.vol_l = vol_l;
    wmc_vol.vol_r = vol_r;

    /* When analogue volume falls below -57dB (0x00) start attenuating the
     * DAC volume */
    if (vol_l >= HW_VOL_DIG_THRESHOLD)
    {
        if (vol_l > HW_VOL_MAX)
            vol_l = HW_VOL_MAX;

        dac_l = HW_VOL_DIG_MAX;
        vol_l -= HW_VOL_DIG_THRESHOLD;
    }
    else
    {
        if (vol_l < HW_VOL_MIN)
            vol_l = HW_VOL_MIN;

        dac_l = 2*vol_l + HW_VOL_DIG_MIN;
        vol_l = HW_VOL_ANA_MIN;
    }

    if (vol_r >= HW_VOL_DIG_THRESHOLD)
    {
        if (vol_r > HW_VOL_MAX)
            vol_r = HW_VOL_MAX;

        dac_r = HW_VOL_DIG_MAX;
        vol_r -= HW_VOL_DIG_THRESHOLD;
    }
    else
    {
        if (vol_r < HW_VOL_MIN)
            vol_r = HW_VOL_MIN;

        dac_r = 2*vol_r + HW_VOL_DIG_MIN;
        vol_r = HW_VOL_ANA_MIN;
    }

    /* Have to write both channels always to have the latching work */
    wmc_write_masked(WMC_LEFT_DAC_DIGITAL_VOL, dac_l, WMC_DVOL);
    wmc_write_masked(WMC_LOUT1_HP_VOLUME_CTRL, vol_l, WMC_AVOL);
    wmc_write_masked(WMC_RIGHT_DAC_DIGITAL_VOL, dac_r, WMC_DVOL);
    wmc_write_masked(WMC_ROUT1_HP_VOLUME_CTRL, vol_r, WMC_AVOL);

    if (wmc_vol.vol_l > HW_VOL_MUTE)
    {
        /* Not muted and going up from mute level? */
        if (prev_l <= HW_VOL_MUTE && !wmc_vol.ahw_mute)
            wmc_clear(WMC_LOUT1_HP_VOLUME_CTRL, WMC_MUTE);
    }
    else
    {
        /* Going to mute level? */
        if (prev_l > HW_VOL_MUTE)
            wmc_set(WMC_LOUT1_HP_VOLUME_CTRL, WMC_MUTE);
    }

    if (wmc_vol.vol_r > HW_VOL_MUTE)
    {
        /* Not muted and going up from mute level? */
        if (prev_r <= HW_VOL_MIN && !wmc_vol.ahw_mute)
            wmc_clear(WMC_ROUT1_HP_VOLUME_CTRL, WMC_MUTE);
    }
    else
    {
        /* Going to mute level? */
        if (prev_r > HW_VOL_MUTE)
            wmc_set(WMC_ROUT1_HP_VOLUME_CTRL, WMC_MUTE);
    }
}

void audiohw_close(void)
{
    /* 1. Mute all analogue outputs */
    audiohw_mute(true);
    audiohw_enable_headphone_jack(false);

    /* 2. Disable power management register 1. R1 = 00 */
    wmc_write(WMC_POWER_MANAGEMENT1, 0x000);

    /* 3. Disable power management register 2. R2 = 00 */
    wmc_write(WMC_POWER_MANAGEMENT2, 0x000);

    /* 4. Disable power management register 3. R3 = 00 */
    wmc_write(WMC_POWER_MANAGEMENT3, 0x000);

    /* 5. Remove external power supplies. */
}

void audiohw_mute(bool mute)
{
    wmc_vol.ahw_mute = mute;

    /* No DAC mute here, please - take care of each enabled output. */
    if (mute)
    {
        wmc_set(WMC_LOUT1_HP_VOLUME_CTRL, WMC_MUTE);
        wmc_set(WMC_ROUT1_HP_VOLUME_CTRL, WMC_MUTE);
    }
    else
    {
        /* Unmute outputs not at mute level */
        if (wmc_vol.vol_l > HW_VOL_MUTE)
            wmc_clear(WMC_LOUT1_HP_VOLUME_CTRL, WMC_MUTE);

        if (wmc_vol.vol_r > HW_VOL_MUTE)
            wmc_clear(WMC_ROUT1_HP_VOLUME_CTRL, WMC_MUTE);
    }
}


#ifdef HAVE_RECORDING
/* TODO */
void audiohw_set_recvol(int left, int right, int type)
{
    (void)left; (void)right; (void)type;
}
#endif
