/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Driver for WM8751 audio codec
 *
 * Based on code from the ipodlinux project - http://ipodlinux.org/
 * Adapted for Rockbox in December 2005
 *
 * Original file: linux/arch/armnommu/mach-ipod/audio.c
 *
 * Copyright (c) 2003-2005 Bernard Leach (leachbj@bouncycastle.org)
 *
 * 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 "kernel.h"
#include "wmcodec.h"
#include "audio.h"
#include "audiohw.h"
#include "system.h"
#include "sound.h"

static uint16_t wmcodec_regs[WM_NUM_REGS] =
{
    [0 ... WM_NUM_REGS-1] = 0x200, /* set invalid data in gaps */
#ifdef HAVE_WM8750
    [LINVOL]              = 0x097,
    [RINVOL]              = 0x097,
#endif
    [LOUT1]               = 0x079,
    [ROUT1]               = 0x079,
    [DACCTRL]             = 0x008,
    [AINTFCE]             = 0x00a,
    [CLOCKING]            = 0x000,
    [LEFTGAIN]            = 0x0ff,
    [RIGHTGAIN]           = 0x0ff,
    [BASSCTRL]            = 0x00f,
    [TREBCTRL]            = 0x00f,
/*  [RESET] */
#ifdef HAVE_WM8750
    [ENHANCE_3D]          = 0x000,
    [ALC1]                = 0x07b,
    [ALC2]                = 0x000,
    [ALC3]                = 0x032,
    [NGAT]                = 0x000,
    [LADCVOL]             = 0x0c3,
    [RADCVOL]             = 0x0c3,
#endif
    [ADDITIONAL1]         = 0x0c0,
    [ADDITIONAL2]         = 0x000,
    [PWRMGMT1]            = 0x000,
    [PWRMGMT2]            = 0x000,
    [ADDITIONAL3]         = 0x000,
#ifdef HAVE_WM8750
    [ADCIM]               = 0x000,
    [ADCL]                = 0x000,
    [ADCR]                = 0x000,
#endif
    [LEFTMIX1]            = 0x050,
    [LEFTMIX2]            = 0x050,
    [RIGHTMIX1]           = 0x050,
    [RIGHTMIX2]           = 0x050,
    [MONOMIX1]            = 0x050,
    [MONOMIX2]            = 0x050,
    [LOUT2]               = 0x079,
    [ROUT2]               = 0x079,
    [MONOOUT]             = 0x079
};

/* global prescaler vars */
static int prescalertone = 0;
static int prescaler3d = 0;

static void wmcodec_set_reg(unsigned int reg, unsigned int val)
{
    if (reg >= WM_NUM_REGS || (wmcodec_regs[reg] & 0x200))
        /* invalid register */
        return;

    wmcodec_regs[reg] = val & 0x1ff;
    wmcodec_write(reg, wmcodec_regs[reg]);
}

static void wmcodec_set_bits(unsigned int reg, unsigned int bits)
{
    wmcodec_set_reg(reg, wmcodec_regs[reg] | bits);
}

static void wmcodec_clear_bits(unsigned int reg, unsigned int bits)
{
    wmcodec_set_reg(reg, wmcodec_regs[reg] & ~bits);
}

static void wmcodec_set_masked(unsigned int reg, unsigned int val, 
                          unsigned int mask )
{
    wmcodec_set_reg(reg, (wmcodec_regs[reg] & ~mask) | val);
}

/* convert tenth of dB volume (-730..60) to master volume register value */
static int vol_tenthdb2hw(int db)
{
    /* +6 to -73dB 1dB steps (plus mute == 80levels) 7bits */
    /* 1111111 ==  +6dB  (0x7f)                            */
    /* 1111001 ==   0dB  (0x79)                            */
    /* 0110000 == -73dB  (0x30)                            */
    /* 0101111..0000000 == mute  (<= 0x2f)                 */
    if (db <= -740)
        return 0x0;
    else
        return (db / 10) + 73 + 0x30;
}

static int tone_tenthdb2hw(int value)
{
    /* -6.0db..+0db..+9.0db step 1.5db - translate -60..+0..+90 step 15
        to 10..6..0 step -1.
    */
    value = 10 - (value + 60) / 15;

    if (value == 6)
        value = 0xf; /* 0db -> off */

    return value;
}

#ifdef AUDIOHW_HAVE_BASS_CUTOFF
void audiohw_set_bass_cutoff(int val)
{
     if ( val == 130 )
         wmcodec_clear_bits(BASSCTRL, BASSCTRL_BC);
     else
         wmcodec_set_bits(BASSCTRL, BASSCTRL_BC);

}
#endif

#ifdef AUDIOHW_HAVE_TREBLE_CUTOFF
void audiohw_set_treble_cutoff(int val)
{
    if ( val == 8 )
        wmcodec_clear_bits(TREBCTRL, TREBCTRL_TC);
    else
        wmcodec_set_bits(TREBCTRL, TREBCTRL_TC); 
}
#endif

static void audiohw_mute(bool mute)
{
    /* Mute:   Set DACMU = 1 to soft-mute the audio DACs. */
    /* Unmute: Set DACMU = 0 to soft-un-mute the audio DACs. */
    if (mute)
        wmcodec_set_bits(DACCTRL, DACCTRL_DACMU);
    else
        wmcodec_clear_bits(DACCTRL, DACCTRL_DACMU);
}

/* Reset and power up the WM8751 */
void audiohw_preinit(void)
{
#if defined(MROBE_100)
    /* controls headphone ouput */
    GPIOL_ENABLE     |= 0x10;
    GPIOL_OUTPUT_EN  |= 0x10;
    GPIOL_OUTPUT_VAL |= 0x10; /* disable */
#elif defined(MPIO_HD200)
    /* control headphone output
     * disabled on startup
     */
    and_l(~(1<<25), &GPIO1_OUT);
    or_l((1<<25), &GPIO1_ENABLE);
    or_l((1<<25), &GPIO1_FUNCTION);
#elif defined(MPIO_HD300)
    and_l(~(1<<5), &GPIO1_OUT);
    or_l((1<<5), &GPIO1_ENABLE);
    or_l((1<<5), &GPIO1_FUNCTION);
#endif

    /*
     * 1. Switch on power supplies.
     *    By default the WM8751 is in Standby Mode, the DAC is
     *    digitally muted and the Audio Interface, Line outputs
     *    and Headphone outputs are all OFF (DACMU = 1 Power
     *    Management registers 1 and 2 are all zeros).
     */

    wmcodec_write(RESET, RESET_RESET);    /*Reset*/

     /* 2. Enable Vmid and VREF. */
    wmcodec_set_bits(PWRMGMT1, PWRMGMT1_VREF | PWRMGMT1_VMIDSEL_5K);

#ifdef CODEC_SLAVE
    wmcodec_set_bits(AINTFCE,AINTFCE_WL_16 | AINTFCE_FORMAT_I2S);
#else
    /* BCLKINV=0(Dont invert BCLK) MS=1(Enable Master) LRSWAP=0 LRP=0 */
    /* IWL=00(16 bit) FORMAT=10(I2S format) */
    wmcodec_set_bits(AINTFCE, AINTFCE_MS | AINTFCE_WL_16 |
                  AINTFCE_FORMAT_I2S);
#endif
    /* Set default samplerate */

    audiohw_set_frequency(HW_FREQ_DEFAULT);
}

/* Enable DACs and audio output after a short delay */
void audiohw_postinit(void)
{
    /* From app notes: allow Vref to stabilize to reduce clicks */
    sleep(HZ);

#ifdef AUDIOHW_HAVE_DEPTH_3D
    wmcodec_set_bits(ENHANCE_3D, ENHANCE_3D_MODE3D_PLAYBACK);
#endif

     /* 3. Enable DACs as required. */
    wmcodec_set_bits(PWRMGMT2, PWRMGMT2_DACL | PWRMGMT2_DACR);

     /* 4. Enable line and / or headphone output buffers as required. */
#if defined(GIGABEAT_F)
    /* headphones + line-out */
    wmcodec_set_bits(PWRMGMT2, PWRMGMT2_LOUT1 | PWRMGMT2_ROUT1 | 
                     PWRMGMT2_LOUT2 | PWRMGMT2_ROUT2);
#else
    /* headphones */
    wmcodec_set_bits(PWRMGMT2, PWRMGMT2_LOUT1 | PWRMGMT2_ROUT1);
#endif

    /* Full -0dB on the DACS */
    wmcodec_set_bits(LEFTGAIN, 0xff);
    wmcodec_set_bits(RIGHTGAIN, RIGHTGAIN_RDVU | 0xff);

    /* Enable Thermal shutdown, Timeout when zero-crossing in use,
     * set analog bias for 3.3V, monomix to DACR
     */
    wmcodec_set_reg(ADDITIONAL1, ADDITIONAL1_TSDEN | ADDITIONAL1_TOEN |
                    ADDITIONAL1_DMONOMIX_LLRR | ADDITIONAL1_VSEL_DEFAULT);


    /* Enable zero-crossing in out stage */
    wmcodec_set_bits(LOUT1, LOUT1_LO1ZC);
    wmcodec_set_bits(ROUT1, ROUT1_RO1ZC);
    wmcodec_set_bits(LOUT2, LOUT2_LO2ZC);
    wmcodec_set_bits(ROUT2, ROUT2_RO2ZC);

    /* route signal from DAC to mixers */
    wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LD2LO);
    wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RD2RO);

#ifdef TOSHIBA_GIGABEAT_F
#ifdef HAVE_HARDWARE_BEEP
    /* Single-ended mono input */
    wmcodec_set_reg(MONOMIX1, 0x00);

    /* Route mono input to both outputs at 0dB */
    wmcodec_set_reg(LEFTMIX2, LEFTMIX2_MI2LO | LEFTMIX2_MI2LOVOL(2));
    wmcodec_set_reg(RIGHTMIX1, RIGHTMIX1_MI2RO | RIGHTMIX1_MI2ROVOL(2));
#endif
#endif

    /* lower power consumption */
    wmcodec_set_masked(PWRMGMT1, PWRMGMT1_VMIDSEL_50K,
                       PWRMGMT1_VMIDSEL_MASK);

    audiohw_mute(false);

#if defined(MROBE_100)
    /* enable headphone output */
    sleep(1);
    GPIOL_OUTPUT_VAL &= ~0x10;
    GPIOL_OUTPUT_EN  |=  0x10;
#elif defined(MPIO_HD200)
   /* enable headphone output */
   or_l((1<<25), &GPIO1_OUT);
#elif defined(MPIO_HD300)
   or_l((1<<5), &GPIO1_OUT);
#endif
}

void audiohw_set_volume(int vol_l, int vol_r)
{
    vol_l = vol_tenthdb2hw(vol_l);
    vol_r = vol_tenthdb2hw(vol_r);   
    wmcodec_set_masked(LOUT1, LOUT1_LOUT1VOL(vol_l),
                       LOUT1_LOUT1VOL_MASK);
    wmcodec_set_masked(ROUT1, ROUT1_RO1VU | ROUT1_ROUT1VOL(vol_r),
                       ROUT1_ROUT1VOL_MASK);
}

#ifdef AUDIOHW_HAVE_LINEOUT
void audiohw_set_lineout_volume(int vol_l, int vol_r)
{
    vol_l = vol_tenthdb2hw(vol_l);
    vol_r = vol_tenthdb2hw(vol_r);
    wmcodec_set_masked(LOUT2, LOUT2_LOUT2VOL(vol_l),
                       LOUT2_LOUT2VOL_MASK);
    wmcodec_set_masked(ROUT2, ROUT2_RO2VU | ROUT2_ROUT2VOL(vol_r),
                       ROUT2_ROUT2VOL_MASK);
}
#endif /* AUDIOHW_HAVE_LINEOUT */

void audiohw_set_bass(int value)
{
    value = tone_tenthdb2hw(value);
    wmcodec_set_masked(BASSCTRL, BASSCTRL_BASS(value),
                       BASSCTRL_BASS_MASK);
}

void audiohw_set_treble(int value)
{
    value = tone_tenthdb2hw(value);
    wmcodec_set_masked(TREBCTRL, TREBCTRL_TREB(value),
                       TREBCTRL_TREB_MASK);
}

static void sync_prescaler(void)
{
    int prescaler;
    prescaler = prescalertone + prescaler3d;

    /* attenuate in 0.5dB steps (0dB - -127dB) */
    wmcodec_set_reg(LEFTGAIN, 0xff - (prescaler & LEFTGAIN_LDACVOL));
    wmcodec_set_reg(RIGHTGAIN, RIGHTGAIN_RDVU |
                    (0xff - (prescaler & RIGHTGAIN_RDACVOL)));
}

void audiohw_set_prescaler(int value)
{
    prescalertone = 3 * value / 15; /* value in tdB */
    sync_prescaler();
}

/* Nice shutdown of WM8751 codec */
void audiohw_close(void)
{
    /* 1. Set DACMU = 1 to soft-mute the audio DACs. */
    audiohw_mute(true);

#if defined(MPIO_HD200)
    /* disable headphone out */
    and_l(~(1<<25), &GPIO1_OUT);
#elif defined(MPIO_HD300)
    and_l(~(1<<5), &GPIO1_OUT);
#endif

    /* 2. Disable all output buffers. */
    wmcodec_set_reg(PWRMGMT2, 0x0);

    /* 3. Switch off the power supplies. */
    wmcodec_set_reg(PWRMGMT1, 0x0);
}

/* According to datasheet of WM8750
 * clocking setup is needed in both slave and master mode
 */
void audiohw_set_frequency(int fsel)
{
    (void)fsel;
    static const unsigned char srctrl_table[HW_NUM_FREQ] =
    {
        HW_HAVE_11_([HW_FREQ_11] = CODEC_SRCTRL_11025HZ,)
        HW_HAVE_22_([HW_FREQ_22] = CODEC_SRCTRL_22050HZ,)
        HW_HAVE_44_([HW_FREQ_44] = CODEC_SRCTRL_44100HZ,)
        HW_HAVE_88_([HW_FREQ_88] = CODEC_SRCTRL_88200HZ,)
    };

    if ((unsigned)fsel >= HW_NUM_FREQ)
        fsel = HW_FREQ_DEFAULT;

    wmcodec_set_reg(CLOCKING, srctrl_table[fsel]);
}

#ifdef HAVE_WM8750
#ifdef AUDIOHW_HAVE_DEPTH_3D
/* Set the depth of the 3D effect
 * value in 0 .. 15, corresponds to 0% .. 100% in 6.66% steps */
void audiohw_set_depth_3d(int val)
{
    if (val > 0)
    {
        wmcodec_set_bits(ENHANCE_3D, ENHANCE_3D_3DEN);
        wmcodec_set_masked(ENHANCE_3D, ENHANCE_3D_DEPTH(val),
                           ENHANCE_3D_DEPTH_MASK);
    }
    else
    {
        wmcodec_clear_bits(ENHANCE_3D, ENHANCE_3D_3DEN);
    }

    /* -4 dB @ full setting
     * this gives approximately constant volume on setting change
     * and prevents clipping (at least on my HD300)
     */
    prescaler3d = 8*val / 15;
    sync_prescaler();
}
#endif

#ifdef HAVE_RECORDING
#if 0
static void audiohw_set_ngat(int ngath, int type, bool enable)
{
    /* This function controls Noise gate function
     * of the codec. This can only run in conjunction
     * with ALC
     */

    if(enable)
        wmcodec_set_reg(NGAT, NGAT_NGG(type) |
                        NGAT_NGTH(ngath) | NGAT_NGAT);
    else
        wmcodec_clear_bits(NGAT, NGAT_NGAT);
}


static void audiohw_set_alc(unsigned char level,  /* signal level at ADC */
                            bool zc,              /* zero cross detection */
                            unsigned char hold,   /* hold time */
                            unsigned char decay,  /* decay time */
                            unsigned char attack, /* attack time */
                            bool enable)          /* hw function on/off */
{
    if(enable)
    {
        wmcodec_set_reg(ALC1, ALC1_ALCSEL_STEREO | ALC1_MAXGAIN(0x07) |
                        ALC1_ALCL(level));
        wmcodec_set_reg(ALC2, zc?ALC2_ALCZC:0 | ALC2_HLD(hold));
        wmcodec_set_reg(ALC3, ALC3_DCY(decay) | ALC3_ATK(attack));
    }
    else
    {
        wmcodec_set_masked(ALC1, ALC1_ALCSEL_DISABLED, ALC1_ALCSEL_MASK);
    }
}
#endif

void audiohw_set_recsrc(int source, bool recording)
{
    /* INPUT1 - FM radio
     * INPUT2 - Line-in
     * INPUT3 - MIC
     *
     * if recording == false we use analog bypass from input
     * turn off ADC, PGA to save power
     * turn on output buffer(s)
     * 
     * if recording == true we route input signal to PGA
     * and monitoring picks up signal after PGA and ADC
     * turn on ADC, PGA, DAC, output buffer(s)
     */
    
    switch(source)
    {
    case AUDIO_SRC_PLAYBACK:
        audiohw_mute(true);

        /* mute PGA, disable all audio paths but DAC and output stage*/
        wmcodec_set_bits(LINVOL, LINVOL_LINMUTE); /* Mute */
        wmcodec_set_bits(RINVOL, RINVOL_RINMUTE); /* Mute */

        wmcodec_clear_bits(PWRMGMT2, PWRMGMT2_OUT3 | PWRMGMT2_MOUT |
                           PWRMGMT2_ROUT2 | PWRMGMT2_LOUT2);

        /* route DAC signal to output mixer, disable INPUT routing */
        wmcodec_clear_bits(LEFTMIX1, LEFTMIX1_LI2LO);
        wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LD2LO);
        wmcodec_clear_bits(RIGHTMIX2, RIGHTMIX2_RI2RO);
        wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RD2RO);

        /* unmute DAC */
        audiohw_mute(false);
        break;

    case AUDIO_SRC_FMRADIO:
        if(recording)
        {
            audiohw_mute(true);
            /* Turn on PGA and ADC */
            wmcodec_set_bits(PWRMGMT1, PWRMGMT1_AINL | PWRMGMT1_AINR | 
                             PWRMGMT1_ADCL | PWRMGMT1_ADCR);

            /* Setup input source for PGA as INPUT1 */
            wmcodec_set_masked(ADCL, ADCL_LINSEL_LINPUT1, ADCL_LINSEL_MASK);
            wmcodec_set_masked(ADCR, ADCR_RINSEL_RINPUT1, ADCR_RINSEL_MASK);

           /* turn off ALC and NGAT as OF do */
           /*
           audiohw_set_alc(0x00, false, 0x00, 0x00, 0x00, false);
           audiohw_set_ngat(0x00, 0x00, false);
           */

            /* setup output digital data
             * default is LADC -> LDATA, RADC -> RDATA
             * so we don't touch this
             */

            /* skip: power up DAC and output stage as its never powered down
            wmcodec_set_bits(PWRMGMT2, PWRMGMT2_DACL | PWRMGMT2_DACR |
                          PWRMGMT2_LOUT1 | PWRMGMT2_ROUT1);
            */

            /* route DAC signal to output mixer, disable INPUT routing */
            wmcodec_clear_bits(LEFTMIX1, LEFTMIX1_LI2LO);
            wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LD2LO);
            wmcodec_clear_bits(RIGHTMIX2, RIGHTMIX2_RI2RO);
            wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RD2RO);

            audiohw_mute(false);
        }
        else
        {
            audiohw_mute(true);

            /* turn off ADC, PGA */
            wmcodec_clear_bits(PWRMGMT1, PWRMGMT1_AINL | PWRMGMT1_AINR |
                               PWRMGMT1_ADCL | PWRMGMT1_ADCR);

            /* setup monitor mode by routing input signal to outmix 
             * at 0dB volume
             */
            wmcodec_set_masked(LEFTMIX1, LEFTMIX1_LI2LOVOL(0x20),
                               LEFTMIX1_LI2LOVOL_MASK);
            wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LI2LO |
                             LEFTMIX1_LMIXSEL_LINPUT1 |
                             LEFTMIX1_LD2LO);
            wmcodec_set_masked(RIGHTMIX2, RIGHTMIX2_RI2ROVOL(0x20),
                               RIGHTMIX2_RI2ROVOL_MASK);
            wmcodec_set_bits(RIGHTMIX1, RIGHTMIX1_RMIXSEL_RINPUT1);
            wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RI2RO |
                             RIGHTMIX2_RD2RO);

            audiohw_mute(false);
        }
        break;

#if (INPUT_SRC_CAPS & SRC_CAP_LINEIN)
    case AUDIO_SRC_LINEIN:
        audiohw_mute(true);

        /* Turn on PGA, ADC */
        wmcodec_set_bits(PWRMGMT1, PWRMGMT1_AINL | PWRMGMT1_AINR |
                         PWRMGMT1_ADCL | PWRMGMT1_ADCR);

        /* Setup input source for PGA as INPUT2 
         * MICBOOST disabled
         */
        wmcodec_set_masked(ADCL, ADCL_LINSEL_LINPUT2, ADCL_LINSEL_MASK);
        wmcodec_set_masked(ADCR, ADCR_RINSEL_RINPUT2, ADCR_RINSEL_MASK);

        /* setup ALC and NGAT as OF do */
        /* level, zc, hold, decay, attack, enable */
        /*  audiohw_set_alc(0x0b, true, 0x00, 0x03, 0x02, true); */
        /* ngath, type, enable */
        /* audiohw_set_ngat(0x08, 0x02, true); */

        /* setup output digital data
         * default is LADC -> LDATA, RADC -> RDATA
         * so we don't touch this
         */

        /* route DAC signal to output mixer, disable INPUT routing */
        wmcodec_clear_bits(LEFTMIX1, LEFTMIX1_LI2LO);
        wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LD2LO);
        wmcodec_clear_bits(RIGHTMIX2, RIGHTMIX2_RI2RO);
        wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RD2RO);

#if !defined(MPIO_HD300)
        /* HD300 uses the same socket for headphones and line-in */
        audiohw_mute(false);
#endif
        break;
#endif
#if (INPUT_SRC_CAPS & SRC_CAP_MIC)
    case AUDIO_SRC_MIC:
        audiohw_mute(true);

        /* Turn on PGA and ADC */
        wmcodec_set_bits(PWRMGMT1, PWRMGMT1_AINL | PWRMGMT1_AINR |
                         PWRMGMT1_ADCL | PWRMGMT1_ADCR);

        /* Setup input source for PGA as INPUT3 
         * MICBOOST disabled
         */
        wmcodec_set_masked(ADCL, ADCL_LINSEL_LINPUT3, ADCL_LINSEL_MASK);
        wmcodec_set_masked(ADCR, ADCR_RINSEL_RINPUT3, ADCR_RINSEL_MASK);

        /* setup ALC and NGAT as OF do */
        /* level, zc, hold, decay, attack, enable */
        /* audiohw_set_alc(0x0f, false, 0x00, 0x05, 0x02, true); */
        /* ngath, type, enable */
        /* audiohw_set_ngat(0x1f, 0x00, true); */

        /* setup output digital data
         * default is LADC -> LDATA, RADC -> RDATA
         * so we don't touch this
         */

        /* route DAC signal to output mixer, disable INPUT routing */
        wmcodec_clear_bits(LEFTMIX1, LEFTMIX1_LI2LO);
        wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LD2LO);
        wmcodec_clear_bits(RIGHTMIX2, RIGHTMIX2_RI2RO);
        wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RD2RO);

        audiohw_mute(false);
        break;
#endif
    } /* switch(source) */
}

static int digital_gain2hw(int value)
{
    /* -9700 to 3000 => 0 ... 195 ... 255 */
    return (2 * value) / 100 + 195;
}

static int pga_gain2hw(int value)
{
    /* -1725 to 3000 => 0 ... 23 ... 63 */
    return ((4 * value) / 300) + 23;
}

/* volume in hundredth of dB in -1725 .. 6000 */
void audiohw_set_recvol(int vol_l, int vol_r, int type)
{
    int d_vol_l = 0;
    int d_vol_r = 0;

    if (vol_l > 3000)
    {
        d_vol_l = vol_l - 3000;
        vol_l = 3000;
    }

    if (vol_r > 3000)
    {
        d_vol_r = vol_r - 3000;
        vol_r = 3000;
    }

    /* PGA left gain */
    wmcodec_set_reg(LINVOL, LINVOL_LIZC | LINVOL_LINVOL(pga_gain2hw(vol_l)));

    /* digital left gain set */
    wmcodec_set_reg(LADCVOL, digital_gain2hw(d_vol_l));

    if (type == AUDIO_GAIN_MIC)
    {
        /* PGA right gain = PGA left gain*/
        wmcodec_set_reg(RINVOL, RINVOL_RIVU | RINVOL_RIZC |
                                RINVOL_RINVOL(pga_gain2hw(vol_l)));

        /* digital right gain = digital left gain*/
        wmcodec_set_reg(RADCVOL, RADCVOL_RAVU | digital_gain2hw(d_vol_l));
    }
    else
    {
        /* PGA right gain */
        wmcodec_set_reg(RINVOL, RINVOL_RIVU | RINVOL_RIZC |
                                RINVOL_RINVOL(pga_gain2hw(vol_r)));

        /* digital right gain */
        wmcodec_set_reg(RADCVOL, RADCVOL_RAVU | digital_gain2hw(d_vol_r));
    }
}
#endif /* HAVE_RECORDING */
#endif /* HAVE_WM8750 */
