/*
    cx25840 audio functions

    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 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, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "compat.h"
#include <linux/i2c.h>

#include "decoder.h"

#include "cx25840-driver.h"
#include "cx25840-audio.h"
#include "cx25840-registers.h"

int cx25840_set_audio_input(struct i2c_client *client,
			    struct decoder_state *state, int audio_input)
{
	if (audio_input <= 4) {
		CX25840_DEBUG("set audio input (%d)\n", audio_input);

		CX25840_SET_START_MICROCNTL(0x00);

		// Mute everything to prevent the PFFT!
		CX25840_SET_AC97_MUTE_EN(0x01);
		CX25840_SET_PAR_MUTE_EN(0x01);
		CX25840_SET_SA_MUTE_EN(0x01);
		CX25840_SET_SRC1_MUTE_EN(0x01);
		//CX25840_SET_SOFT1_MUTE_EN(0x01);

		switch (audio_input) {
		case 0:	// TV audio
			// 70
			CX25840_SET_PATH1_SEL_CTL(0x00);
			CX25840_SET_PATH1_AVC_RMS_CON(0x07);

			// 38
			CX25840_SET_PATH1_AVC_AT(0x03);
			CX25840_SET_PATH1_AVC_STEREO(0x01);
			CX25840_SET_PATH1_AVC_CR(0x00);

			// 06
			CX25840_SET_PATH1_AVC_CG(0x00);
			CX25840_SET_PATH1_AVC_RT(0x06);

			// When the microcontroller detects the
			// audio format, it will unmute the lines       
			CX25840_SET_START_MICROCNTL(0x01);
			break;

		case 1:	// external line-in (???)
		case 2:	// external line-in (external)
		case 3:	// external line-in (internal)
		case 4:	// FM Radio
			// 12
			CX25840_SET_PATH1_SEL_CTL(0x02);
			CX25840_SET_PATH1_AVC_RMS_CON(0x01);

			// 10
			CX25840_SET_PATH1_AVC_AT(0x01);
			CX25840_SET_PATH1_AVC_STEREO(0x00);
			CX25840_SET_PATH1_AVC_CR(0x00);

			// 01
			CX25840_SET_PATH1_AVC_CG(0x00);
			CX25840_SET_PATH1_AVC_RT(0x01);

			// 01
			CX25840_SET_AC97_MUTE_EN(0x00);
			CX25840_SET_PAR_MUTE_EN(0x00);
			CX25840_SET_SA_MUTE_EN(0x00);
			CX25840_SET_SRC1_MUTE_EN(0x00);
			//CX25840_SET_SOFT1_MUTE_EN(0x01);
			break;
		}

		state->audio_input = audio_input;
		return 0;
	}

	CX25840_DEBUG("Invalid audio input selection %d\n", audio_input);
	return -EINVAL;
}

int cx25840_set_audio_rate(struct i2c_client *client,
			   struct decoder_state *state, int audio)
{
	if ((state->audio_input > 4) || (state->audio_input < 0)) {
		CX25840_DEBUG("Invalid audio input selected %d\n", state->audio_input);
		return -EINVAL;
	}
	// assert soft reset
	CX25840_SET_SOFT_RESET(0x01);

	// common for all inputs and rates
	CX25840_SET_VID_PLL_INT(0x0f);
	CX25840_SET_VID_PLL_POST(0x04);
	CX25840_SET_SA_MCLK_SEL(0x01);
	CX25840_SET_SA_MCLK_DIV(0x10);

	switch (state->audio_input) {
	case 0:		// TV audio
		switch (audio) {
		case DECODER_AUDIO_32_KHZ:
			// pll_ctl1 = 0x1006040f;
			CX25840_SET_AUX_PLL_INT(0x06);
			CX25840_SET_AUX_PLL_POST(0x10);

			// aux_pll_frac = 0x01bb39ee;
			CX25840_SET_AUX_PLL_FRAC1(0xee);
			CX25840_SET_AUX_PLL_FRAC2(0x39);
			CX25840_SET_AUX_PLL_FRAC3(0xbb);
			CX25840_SET_AUX_PLL_FRAC4(0x01);

			// src3_ctl = 0x0801f77f;
			CX25840_SET_SRC3_PHASE_INC(0x1f77f);
			CX25840_SET_SRC3_FIFO_RD_TH(0x08);

			// src4_ctl = 0x0801f77f;
			CX25840_SET_SRC4_PHASE_INC(0x1f77f);
			CX25840_SET_SRC4_FIFO_RD_TH(0x08);

			// src6_ctl = 0x0801f77f;
			CX25840_SET_SRC6_PHASE_INC(0x1f77f);
			CX25840_SET_SRC6_FIFO_RD_TH(0x08);
			break;
		case DECODER_AUDIO_441_KHZ:
			// pll_ctl1 = 0x1009040f;
			CX25840_SET_AUX_PLL_INT(0x09);
			CX25840_SET_AUX_PLL_POST(0x10);

			// aux_pll_frac = 0x00ec6bd6;
			CX25840_SET_AUX_PLL_FRAC1(0xd6);
			CX25840_SET_AUX_PLL_FRAC2(0x6b);
			CX25840_SET_AUX_PLL_FRAC3(0xec);
			CX25840_SET_AUX_PLL_FRAC4(0x00);

			// src3_ctl = 0x08016d59;
			CX25840_SET_SRC3_PHASE_INC(0x16d59);
			CX25840_SET_SRC3_FIFO_RD_TH(0x08);

			// src4_ctl = 0x0801f77f;
			CX25840_SET_SRC4_PHASE_INC(0x16d59);
			CX25840_SET_SRC4_FIFO_RD_TH(0x08);

			// src6_ctl = 0x0801f77f;
			CX25840_SET_SRC6_PHASE_INC(0x16d59);
			CX25840_SET_SRC6_FIFO_RD_TH(0x08);
			break;
		case DECODER_AUDIO_48_KHZ:
			// pll_ctl1 = 0x100a040f;
			CX25840_SET_AUX_PLL_INT(0x0a);
			CX25840_SET_AUX_PLL_POST(0x10);

			// aux_pll_frac = 0x0098d6e5;
			CX25840_SET_AUX_PLL_FRAC1(0xe5);
			CX25840_SET_AUX_PLL_FRAC2(0xd6);
			CX25840_SET_AUX_PLL_FRAC3(0x98);
			CX25840_SET_AUX_PLL_FRAC4(0x00);

			// src3_ctl = 0x08014faa;
			CX25840_SET_SRC3_PHASE_INC(0x14faa);
			CX25840_SET_SRC3_FIFO_RD_TH(0x08);

			// src4_ctl = 0x0801f77f;
			CX25840_SET_SRC4_PHASE_INC(0x14faa);
			CX25840_SET_SRC4_FIFO_RD_TH(0x08);

			// src6_ctl = 0x0801f77f;
			CX25840_SET_SRC6_PHASE_INC(0x14faa);
			CX25840_SET_SRC6_FIFO_RD_TH(0x08);
			break;
		}
		break;

	case 1:		// external line in (???)
	case 2:		// external line in (external)
	case 3:		// external line in (internal)
	case 4:		// FM radio
		switch (audio) {
		case DECODER_AUDIO_32_KHZ:
			// pll_ctl1 = 0x1e08040f;
			CX25840_SET_AUX_PLL_INT(0x08);
			CX25840_SET_AUX_PLL_POST(0x1e);

			// aux_pll_frac = 0x012a0869;
			CX25840_SET_AUX_PLL_FRAC1(0x69);
			CX25840_SET_AUX_PLL_FRAC2(0x08);
			CX25840_SET_AUX_PLL_FRAC3(0x2a);
			CX25840_SET_AUX_PLL_FRAC4(0x01);

			// src1_ctl = 0x08010000;
			CX25840_SET_SRC1_PHASE_INC(0x10000);
			CX25840_SET_SRC1_FIFO_RD_TH(0x08);

			// src3_ctl = 0x08020000;
			CX25840_SET_SRC3_PHASE_INC(0x20000);
			CX25840_SET_SRC3_FIFO_RD_TH(0x08);

			// src4_ctl = 0x08020000;
			CX25840_SET_SRC4_PHASE_INC(0x20000);
			CX25840_SET_SRC4_FIFO_RD_TH(0x08);

			// src6_ctl = 0x08020000;
			CX25840_SET_SRC6_PHASE_INC(0x20000);
			CX25840_SET_SRC6_FIFO_RD_TH(0x08);

			CX25840_SET_SA_MCLK_DIV(0x14);
			break;
		case DECODER_AUDIO_441_KHZ:
			// pll_ctl1 = 0x1809040f;
			CX25840_SET_AUX_PLL_INT(0x09);
			CX25840_SET_AUX_PLL_POST(0x18);

			// aux_pll_frac = 0x00ec6bd6;
			CX25840_SET_AUX_PLL_FRAC1(0xd6);
			CX25840_SET_AUX_PLL_FRAC2(0x6b);
			CX25840_SET_AUX_PLL_FRAC3(0xec);
			CX25840_SET_AUX_PLL_FRAC4(0x00);

			// src1_ctl = 0x080160cd;
			CX25840_SET_SRC1_PHASE_INC(0x160cd);
			CX25840_SET_SRC1_FIFO_RD_TH(0x08);

			// src3_ctl = 0x08017385;
			CX25840_SET_SRC3_PHASE_INC(0x17385);
			CX25840_SET_SRC3_FIFO_RD_TH(0x08);

			// src4_ctl = 0x08017385;
			CX25840_SET_SRC4_PHASE_INC(0x17385);
			CX25840_SET_SRC4_FIFO_RD_TH(0x08);

			// src6_ctl = 0x08017385;
			CX25840_SET_SRC6_PHASE_INC(0x17385);
			CX25840_SET_SRC6_FIFO_RD_TH(0x08);
			break;
		case DECODER_AUDIO_48_KHZ:
			// pll_ctl1 = 0x180a040f;
			CX25840_SET_AUX_PLL_INT(0x0a);
			CX25840_SET_AUX_PLL_POST(0x18);

			// aux_pll_frac = 0x0098d6e5;
			CX25840_SET_AUX_PLL_FRAC1(0xe5);
			CX25840_SET_AUX_PLL_FRAC2(0xd6);
			CX25840_SET_AUX_PLL_FRAC3(0x98);
			CX25840_SET_AUX_PLL_FRAC4(0x00);

			// src1_ctl = 0x08018000;
			CX25840_SET_SRC1_PHASE_INC(0x18000);
			CX25840_SET_SRC1_FIFO_RD_TH(0x08);

			// src3_ctl = 0x08015555;
			CX25840_SET_SRC3_PHASE_INC(0x15555);
			CX25840_SET_SRC3_FIFO_RD_TH(0x08);

			// src4_ctl = 0x08015555;
			CX25840_SET_SRC4_PHASE_INC(0x15555);
			CX25840_SET_SRC4_FIFO_RD_TH(0x08);

			// src6_ctl = 0x08015555;
			CX25840_SET_SRC6_PHASE_INC(0x15555);
			CX25840_SET_SRC6_FIFO_RD_TH(0x08);
			break;
		}
		break;
	}

	// deassert soft reset  
	CX25840_SET_SOFT_RESET(0x00);

	state->audio = audio;

	return 0;
}

void cx25840_get_v4l_audio(struct i2c_client *client, struct video_audio *va)
{
	/* MUTABLE isn't included, although it is possible if
	 * we stop the microcontroller and mute all inputs */
	va->flags = VIDEO_AUDIO_TREBLE | VIDEO_AUDIO_BASS;
	
	/* Volume runs +18dB to -96dB in 1/2dB steps
	 * change to fit the msp3400 -114dB to +12dB range */
	va->step = 228;
	va->volume = 228 - cx25840_read_setting(client, PATH1_VOLUME);
	va->volume = (va->volume / 2) + 23; 
	va->volume = va->volume << 9;
	/* bass is 49 steps +12dB to -12dB */
	va->bass = cx25840_read_setting(client, PATH1_EQ_BASS_VOL);
	va->bass = (((48 - va->bass) * 0xffff) + 47) / 48;
	/* treble is 49 steps +12dB to -12dB */
	va->treble = cx25840_read_setting(client, PATH1_EQ_TREBLE_VOL);
	va->treble = (((48 - va->treble) * 0xffff) + 47) / 48;
	/* balance is 7 bit, 0 to -96dB */
	va->balance = cx25840_read_setting(client, PATH1_BAL_LEVEL);
	if (cx25840_read_setting(client, PATH1_BAL_LEFT) == 0)
		va->balance = 0x80 - va->balance;
	else
		va->balance = 0x80 + va->balance;
	va->balance = va->balance << 8;
}

void cx25840_set_v4l_audio(struct i2c_client *client, struct video_audio *va)
{
	// First convert the volume to msp3400 values (0-127)
	int volume = va->volume >> 9;
	int balance = va->balance >> 8;
	// now scale it up to cx25840 values
	// -114dB to -96dB maps to 0
	// this should be 19, but in my testing that was 4dB too loud
	if (volume <= 23)
		volume = 0;
	else 
		volume -= 23;

	CX25840_SET_PATH1_VOLUME(228 - (volume * 2));
	CX25840_SET_PATH1_EQ_BASS_VOL(48 - (va->bass * 48 / 0xffff));
	CX25840_SET_PATH1_EQ_TREBLE_VOL(48 - (va->treble * 48 / 0xffff));
	if (balance > 0x80) {
		CX25840_SET_PATH1_BAL_LEFT(0x01);
		CX25840_SET_PATH1_BAL_LEVEL(balance & 0x7f);
	} else {
		CX25840_SET_PATH1_BAL_LEFT(0x00);
		CX25840_SET_PATH1_BAL_LEVEL(0x80 - balance);
	}
}

