/*
 * Copyright (C) 2014 Intel Corporation
 * Copyright 2014 Google Inc.  All rights reserved.
 *
 * 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 <libpayload.h>

#include "base/container_of.h"
#include "drivers/bus/i2s/i2s.h"
#include "drivers/bus/i2s/baytrail/baytrail.h"
#include "drivers/bus/i2s/baytrail/baytrail-regs.h"

/* ssacd LUT */
static const uint8_t
ssp_ssacd[SSP_FRM_SIZE][SSP_BPS_SIZE][SSP_TS_SIZE] = {
	[SSP_FRM_UNDEFINED][SSP_BPS_8][SSP_TS_1] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_UNDEFINED][SSP_BPS_8][SSP_TS_2] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_UNDEFINED][SSP_BPS_8][SSP_TS_4] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_UNDEFINED][SSP_BPS_8][SSP_TS_8] = SSP_SSACD_NOT_AVAILABLE,

	[SSP_FRM_UNDEFINED][SSP_BPS_16][SSP_TS_1] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_UNDEFINED][SSP_BPS_16][SSP_TS_2] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_UNDEFINED][SSP_BPS_16][SSP_TS_4] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_UNDEFINED][SSP_BPS_16][SSP_TS_8] = SSP_SSACD_NOT_AVAILABLE,

	[SSP_FRM_UNDEFINED][SSP_BPS_32][SSP_TS_1] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_UNDEFINED][SSP_BPS_32][SSP_TS_2] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_UNDEFINED][SSP_BPS_32][SSP_TS_4] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_UNDEFINED][SSP_BPS_32][SSP_TS_8] = SSP_SSACD_NOT_AVAILABLE,

	[SSP_FRM_48K][SSP_BPS_8][SSP_TS_1] =
		SSP_PLL_FREQ_12_235 | SSP_SYSCLK_DIV_8,
	[SSP_FRM_48K][SSP_BPS_8][SSP_TS_2] =
		SSP_PLL_FREQ_12_235 | SSP_SYSCLK_DIV_4,
	[SSP_FRM_48K][SSP_BPS_8][SSP_TS_4] =
		SSP_PLL_FREQ_12_235 | SSP_SYSCLK_DIV_2,
	[SSP_FRM_48K][SSP_BPS_8][SSP_TS_8] =
		SSP_PLL_FREQ_12_235 | SSP_SYSCLK_DIV_1,

	[SSP_FRM_48K][SSP_BPS_16][SSP_TS_1] =
		SSP_PLL_FREQ_12_235 | SSP_SYSCLK_DIV_4,
	[SSP_FRM_48K][SSP_BPS_16][SSP_TS_2] =
		SSP_PLL_FREQ_12_235 | SSP_SYSCLK_DIV_2,
	[SSP_FRM_48K][SSP_BPS_16][SSP_TS_4] =
		SSP_PLL_FREQ_12_235 | SSP_SYSCLK_DIV_1,
	[SSP_FRM_48K][SSP_BPS_16][SSP_TS_8] =
		SSP_PLL_FREQ_12_235 | SSP_SYSCLK_DIV_2 | SSP_SYSCLK_DIV4_BYPASS,

	[SSP_FRM_48K][SSP_BPS_32][SSP_TS_1] =
		SSP_PLL_FREQ_12_235 | SSP_SYSCLK_DIV_2,
	[SSP_FRM_48K][SSP_BPS_32][SSP_TS_2] =
		SSP_PLL_FREQ_12_235 | SSP_SYSCLK_DIV_1,
	[SSP_FRM_48K][SSP_BPS_32][SSP_TS_4] =
		SSP_PLL_FREQ_12_235 | SSP_SYSCLK_DIV_2 | SSP_SYSCLK_DIV4_BYPASS,
	[SSP_FRM_48K][SSP_BPS_32][SSP_TS_8] =
		SSP_PLL_FREQ_12_235 | SSP_SYSCLK_DIV_1 | SSP_SYSCLK_DIV4_BYPASS,

	[SSP_FRM_44K][SSP_BPS_8][SSP_TS_1] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_8,
	[SSP_FRM_44K][SSP_BPS_8][SSP_TS_2] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_4,
	[SSP_FRM_44K][SSP_BPS_8][SSP_TS_4] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_2,
	[SSP_FRM_44K][SSP_BPS_8][SSP_TS_8] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_1,

	[SSP_FRM_44K][SSP_BPS_16][SSP_TS_1] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_4,
	[SSP_FRM_44K][SSP_BPS_16][SSP_TS_2] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_2,
	[SSP_FRM_44K][SSP_BPS_16][SSP_TS_4] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_1,
	[SSP_FRM_44K][SSP_BPS_16][SSP_TS_8] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_2 | SSP_SYSCLK_DIV4_BYPASS,

	[SSP_FRM_44K][SSP_BPS_32][SSP_TS_1] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_2,
	[SSP_FRM_44K][SSP_BPS_32][SSP_TS_2] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_1,
	[SSP_FRM_44K][SSP_BPS_32][SSP_TS_4] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_2 | SSP_SYSCLK_DIV4_BYPASS,
	[SSP_FRM_44K][SSP_BPS_32][SSP_TS_8] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_1 | SSP_SYSCLK_DIV4_BYPASS,

	[SSP_FRM_22K][SSP_BPS_8][SSP_TS_1] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_8,
	[SSP_FRM_22K][SSP_BPS_8][SSP_TS_2] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_4,
	[SSP_FRM_22K][SSP_BPS_8][SSP_TS_4] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_2,
	[SSP_FRM_22K][SSP_BPS_8][SSP_TS_8] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_1,

	[SSP_FRM_22K][SSP_BPS_16][SSP_TS_1] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_4,
	[SSP_FRM_22K][SSP_BPS_16][SSP_TS_2] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_2,
	[SSP_FRM_22K][SSP_BPS_16][SSP_TS_4] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_1,
	[SSP_FRM_22K][SSP_BPS_16][SSP_TS_8] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_2 | SSP_SYSCLK_DIV4_BYPASS,

	[SSP_FRM_22K][SSP_BPS_32][SSP_TS_1] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_2,
	[SSP_FRM_22K][SSP_BPS_32][SSP_TS_2] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_1,
	[SSP_FRM_22K][SSP_BPS_32][SSP_TS_4] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_2 | SSP_SYSCLK_DIV4_BYPASS,
	[SSP_FRM_22K][SSP_BPS_32][SSP_TS_8] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_1 | SSP_SYSCLK_DIV4_BYPASS,

	[SSP_FRM_16K][SSP_BPS_8][SSP_TS_1] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_16K][SSP_BPS_8][SSP_TS_2] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_32,
	[SSP_FRM_16K][SSP_BPS_8][SSP_TS_4] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_16,
	[SSP_FRM_16K][SSP_BPS_8][SSP_TS_8] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_8,

	[SSP_FRM_16K][SSP_BPS_16][SSP_TS_1] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_32,
	[SSP_FRM_16K][SSP_BPS_16][SSP_TS_2] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_16,
	[SSP_FRM_16K][SSP_BPS_16][SSP_TS_4] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_8,
	[SSP_FRM_16K][SSP_BPS_16][SSP_TS_8] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_4,

	[SSP_FRM_16K][SSP_BPS_32][SSP_TS_1] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_16,
	[SSP_FRM_16K][SSP_BPS_32][SSP_TS_2] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_8,
	[SSP_FRM_16K][SSP_BPS_32][SSP_TS_4] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_4,
	[SSP_FRM_16K][SSP_BPS_32][SSP_TS_8] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_2,

	[SSP_FRM_11K][SSP_BPS_8][SSP_TS_1] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_16,
	[SSP_FRM_11K][SSP_BPS_8][SSP_TS_2] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_8,
	[SSP_FRM_11K][SSP_BPS_8][SSP_TS_4] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_4,
	[SSP_FRM_11K][SSP_BPS_8][SSP_TS_8] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_2,

	[SSP_FRM_11K][SSP_BPS_16][SSP_TS_1] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_8,
	[SSP_FRM_11K][SSP_BPS_16][SSP_TS_2] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_4,
	[SSP_FRM_11K][SSP_BPS_16][SSP_TS_4] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_2,
	[SSP_FRM_11K][SSP_BPS_16][SSP_TS_8] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_1,

	[SSP_FRM_11K][SSP_BPS_32][SSP_TS_1] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_4,
	[SSP_FRM_11K][SSP_BPS_32][SSP_TS_2] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_2,
	[SSP_FRM_11K][SSP_BPS_32][SSP_TS_4] =
		SSP_PLL_FREQ_05_622 | SSP_SYSCLK_DIV_1,
	[SSP_FRM_11K][SSP_BPS_32][SSP_TS_8] =
		SSP_PLL_FREQ_11_345 | SSP_SYSCLK_DIV_1,

	[SSP_FRM_8K][SSP_BPS_8][SSP_TS_1] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_8K][SSP_BPS_8][SSP_TS_2] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_8K][SSP_BPS_8][SSP_TS_4] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_32,
	[SSP_FRM_8K][SSP_BPS_8][SSP_TS_8] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_16,

	[SSP_FRM_8K][SSP_BPS_16][SSP_TS_1] = SSP_SSACD_NOT_AVAILABLE,
	[SSP_FRM_8K][SSP_BPS_16][SSP_TS_2] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_32,
	[SSP_FRM_8K][SSP_BPS_16][SSP_TS_4] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_16,
	[SSP_FRM_8K][SSP_BPS_16][SSP_TS_8] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_8,

	[SSP_FRM_8K][SSP_BPS_32][SSP_TS_1] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_32,
	[SSP_FRM_8K][SSP_BPS_32][SSP_TS_2] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_16,
	[SSP_FRM_8K][SSP_BPS_32][SSP_TS_4] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_8,
	[SSP_FRM_8K][SSP_BPS_32][SSP_TS_8] =
		SSP_PLL_FREQ_32_842 | SSP_SYSCLK_DIV_4,
};

enum {
	LPE_SSP_FIFO_SIZE = 16,
};

/*
 * i2s_enable -  enable SSP device
 * @regs: pointer to registers
 * @settings: config settings
 * @streamdir: RX/TX direction
 *
 * Writes SSP register to enable the device
 */
static void i2s_enable(BytI2sRegs *regs,
		       const struct BytI2sSettings *settings,
		       int streamdir)
{
	uint32_t status;
	uint32_t sssr;
	uint32_t saved_SSCR3 = 0;
	uint64_t start;
	int i;

	status = read_SSCR0(regs);
	if (status & 0x80) {
		sssr = read_SSSR(regs);
		if (GET_SSSR_val(sssr, ROR)) {
			if (settings->frame_rate_divider_control ==
			    FRAME_RATE_CONTROL_STEREO) {
				saved_SSCR3 = read_SSCR3(regs);
				clear_SSCR3_reg(regs, I2S_TX_EN);
				clear_SSCR3_reg(regs, I2S_RX_EN);
			}
			clear_SSCR0_reg(regs, SSE);
			start = timer_us(0);
			while (read_SSCR0(regs) & 0x80 &&
			       timer_us(start) < 10000);
			set_SSCR0_reg(regs, SSE);

			if (settings->frame_rate_divider_control ==
			    FRAME_RATE_CONTROL_STEREO) {
				if (SSPSCLK_SLAVE_MODE ==
					settings->sspslclk_direction) {
					start = timer_us(0);
					while (read_SSSR(regs) & 0x400000 &&
					       timer_us(start) < 10000);
				}
				write_SSCR3(saved_SSCR3, regs);
			}
		}
	} else {
		set_SSCR0_reg(regs, SSE);

		if (SSPSCLK_MASTER_MODE == settings->sspslclk_direction &&
		    SSP_DATA_RX == streamdir &&
		    settings->frame_rate_divider_control !=
		    FRAME_RATE_CONTROL_STEREO) {
			for (i = 0; i < 8; ++i)
				write_SSDR(0x1FBEEFF8, regs);
		} else if (SSPSCLK_SLAVE_MODE == settings->sspslclk_direction
			  && settings->frame_rate_divider_control ==
			  FRAME_RATE_CONTROL_STEREO)
			while (read_SSSR(regs) & 0x400000);
	}
}

/**
 * i2s_disable -  disable SSP device
 * @regs: pointer to registers
 *
 * Writes SSP register to disable the device
 */
static void i2s_disable(BytI2sRegs *regs)
{
	clear_SSCR3_reg(regs, I2S_TX_EN);
	clear_SSCR3_reg(regs, I2S_RX_EN);
	clear_SSCR0_reg(regs, SSE);
}

/*
 * calculate_sspsp_psp - Calculate sspsp register
 * @settings: config settings
 *
 * Calculate sspsp register.
 */
static uint32_t calculate_sspsp_psp(const BytI2sSettings *settings)
{
	uint32_t sspsp;
	sspsp = SSPSP_reg(FSRT, NEXT_FRMS_ASS_AFTER_END_OF_T4) |
		SSPSP_reg(ETDS, settings->ssp_end_transfer_state) |
		SSPSP_reg(SCMODE, settings->ssp_serial_clk_mode) |
		SSPSP_reg(DMYSTOP, settings->ssp_psp_T4) |
		SSPSP_reg(SFRMDLY, settings->ssp_psp_T5) |
		SSPSP_reg(SFRMWDTH, settings->ssp_psp_T6) |
		SSPSP_reg(SFRMP, settings->ssp_frmsync_pol_bit);
	return sspsp;
}

/*
 * calculate_ssacd - Calculate ssacd register
 * @settings: config settings
 * @bps: bits per sample
 * @frame_rate_divider_new: calculated frame rate divider
 *
 * Calculate ssacd register
 *
 */
static uint32_t calculate_ssacd(const BytI2sSettings *settings, int bps,
	uint8_t *frame_rate_divider_new)
{
	uint8_t calculated_ssacd;
	BytSspTimeslot timeslots = 0;
	BytSspBitsPerSample bps_idx;
	BytSspFrmFreq freq = settings->master_mode_standard_freq;
	static uint8_t frame_rate_divider[SSP_TS_SIZE] = {
		[SSP_TS_1] = 1,
		[SSP_TS_2] = 2,
		[SSP_TS_4] = 4,
		[SSP_TS_8] = 8
	};

	switch (settings->frame_rate_divider_control) {
	case FRAME_RATE_CONTROL_STEREO:
		timeslots = SSP_TS_2;
		break;
	case FRAME_RATE_CONTROL_TDM8:
		timeslots = SSP_TS_8;
		break;
	};

	if (bps == 8)
		bps_idx = SSP_BPS_8;
	else if (bps == 16)
		bps_idx = SSP_BPS_16;
	else if (bps == 32)
		bps_idx = SSP_BPS_32;
	else {
		printf("Master mode bit per sample=%d unsupported\n", bps);
		return 0xFFFF;
	}

	while (timeslots < SSP_TS_8 &&
	       ssp_ssacd[freq][bps_idx][timeslots] == SSP_SSACD_NOT_AVAILABLE)
		timeslots++;

	calculated_ssacd = ssp_ssacd[freq][bps_idx][timeslots];
	if (calculated_ssacd == SSP_SSACD_NOT_AVAILABLE) {
		printf("Master mode unexpected error freq=%d,"
		       "bitPerSamp=%d,calculated timeslot=%d, original"
		       "timeslot=%d\n", freq, bps_idx, timeslots,
		       settings->frame_rate_divider_control);
		return 0xFFFF;
	}

	*frame_rate_divider_new = frame_rate_divider[timeslots];

	return calculated_ssacd;
}


/*
 * calculate_sscr0_psp: Calculate sscr0 register
 * @settings: config settings
 * @bps: bits per sample
 *
 * Calculate sscr0 register
 */
static uint32_t calculate_sscr0_psp(const BytI2sSettings *settings, int bps)
{
	uint32_t sscr0;

	if (bps > 16)
		sscr0 = SSCR0_reg(DSS, SSCR0_DataSize(bps - 16)) |
			SSCR0_reg(EDSS, 1);
	else
		sscr0 = SSCR0_reg(DSS, SSCR0_DataSize(bps)) |
			SSCR0_reg(EDSS, 0);

	sscr0 |= SSCR0_reg(MOD, settings->mode) |
		 SSCR0_reg(FRF, PSP_FORMAT) |
		 SSCR0_reg(RIM, SSP_FIFO_INT_DISABLE) |
		 SSCR0_reg(TIM, SSP_FIFO_INT_DISABLE);
	return sscr0;
}

/**
 * calculate_sscr1_psp - Calculate sscr1 register.
 * @settings: config settings
 *
 * Calculate sscr1 register.
 */
static uint32_t calculate_sscr1_psp(const BytI2sSettings *settings)
{
	uint32_t sscr1;
	BytSspSspsfrmDirection i2s_frmdir_fix;

	i2s_frmdir_fix = settings->sspsfrm_direction;

	if (settings->frame_rate_divider_control ==
	    FRAME_RATE_CONTROL_STEREO)
		i2s_frmdir_fix = SSPSFRM_SLAVE_MODE;

	sscr1 = SSCR1_reg(SFRMDIR, i2s_frmdir_fix) |
		SSCR1_reg(SCLKDIR, settings->sspslclk_direction) |
		SSCR1_reg(EBCEI, 1) |
		SSCR1_reg(TTELP, TXD_TRISTATE_LAST_PHASE_OFF) |
		SSCR1_reg(TTE, TXD_TRISTATE_OFF) |
		SSCR1_reg(TRAIL, settings->ssp_trailing_byte_mode) |
		SSCR1_reg(TINTE, settings->ssp_rx_timeout_interrupt_status) |
		SSCR1_reg(LBM, settings->ssp_loopback_mode_status) |
		SSCR1_reg(RWOT, RX_AND_TX_MODE) |
		SSCR1_reg(RFT, SSCR1_RxTresh(settings->ssp_rx_fifo_threshold)) |
		SSCR1_reg(TFT, SSCR1_TxTresh(settings->ssp_tx_fifo_threshold));
	return sscr1;
}

/*
 * calculate_sscr2 - Calculate sscr2 register.
 * @settings: config settings
 * @clk_frame_mode: clock frame mode
 *
 * Calculate sscr2 register.
 *
 *
 */
static uint32_t calculate_sscr2(const BytI2sSettings *settings,
	uint16_t clk_frm_mode)


{
	uint32_t sscr2 = 0;
	if (settings->frame_rate_divider_control ==
	    FRAME_RATE_CONTROL_TDM8) {
		sscr2 |= (1 << SSCR2_UNDERRUN_FIX_1_SHIFT);
		if (clk_frm_mode == SSP_IN_SLAVE_MODE)
			sscr2 |= (1 << SSCR2_SLV_EXT_CLK_RUN_EN_SHIFT);
	}

	return sscr2;
}

/*
 * calculate_sscr3 - Calculate sscr3 register
 * @settings: config settings
 * @clk_frm_mode: clock frame mode
 *
 * Calculate sscr3 register
 */
static uint32_t calculate_sscr3(const BytI2sSettings *settings,
	uint16_t clk_frm_mode)
{
	uint32_t sscr3 = 0;

	sscr3 = ((1 << SSCR3_STRETCH_RX_SHIFT) | (1 << SSCR3_STRETCH_TX_SHIFT));

	if (settings->frame_rate_divider_control ==
	    FRAME_RATE_CONTROL_STEREO) {
		sscr3 |= (1 << SSCR3_I2S_RX_SS_FIX_EN_SHIFT) |
			 (1 << SSCR3_I2S_TX_SS_FIX_EN_SHIFT) |
			 (1 << SSCR3_I2S_MODE_EN_SHIFT) | 0x20004;
		if (clk_frm_mode == SSP_IN_MASTER_MODE) {
			sscr3 |= (1 << SSCR3_MST_CLK_EN_SHIFT) |
				 (1 << SSCR3_FRM_MST_EN_SHIFT);
		}

	}

	return sscr3;
}

/*
 * set_ssp_i2s_hw - Configure SSP driver according to settings.
 * @regs: pointer to registers
 * @shim: pointer to shim registers
 * @settings: config settings
 * @bps: bits per sample
 *
 * Configure SSP driver according to settings.
 */
static void set_ssp_i2s_hw(BytI2sRegs *regs, BytI2sRegs *shim,
	const BytI2sSettings *settings, int bps)
{
	uint32_t sscr0 = 0;
	uint32_t sscr1 = 0;
	uint32_t sscr2 = 0;
	uint32_t sscr3 = 0;
	uint32_t sscr4 = 0;
	uint32_t sscr5 = 0;
	uint32_t sstsa = 0;
	uint32_t ssrsa = 0;
	uint32_t sspsp = 0;
	uint32_t sssr = 0;
	uint32_t ssacd = 0;

	uint32_t ssp_divc_l = 0;
	uint32_t ssp_divc_h = 0;

	uint8_t frame_rate_divider;

	uint16_t clk_frm_mode = 0xFF;

	if (settings->sspsfrm_direction == SSPSFRM_MASTER_MODE &&
	    settings->sspslclk_direction == SSPSCLK_MASTER_MODE) {
		clk_frm_mode = SSP_IN_MASTER_MODE;
	} else if (settings->sspsfrm_direction == SSPSFRM_SLAVE_MODE &&
		 settings->sspslclk_direction == SSPSCLK_SLAVE_MODE) {
		clk_frm_mode = SSP_IN_SLAVE_MODE;
	} else {
		printf("Unsupported I2S PCM1 configuration\n");
		return;
	}
	frame_rate_divider = settings->frame_rate_divider_control;

	sscr0 = calculate_sscr0_psp(settings, bps);
	sscr1 = calculate_sscr1_psp(settings);

	if (settings->mode == SSP_IN_NETWORK_MODE)
		sscr0 |= SSCR0_reg(FRDC, SSCR0_SlotsPerFrm(frame_rate_divider));
	else if (settings->mode != SSP_IN_NORMAL_MODE) {
		printf("Unsupported mode\n");
		return;
	}

	sspsp = calculate_sspsp_psp(settings);
	sstsa = SSTSA_reg(TTSA, settings->ssp_active_tx_slots_map);
	ssrsa = SSRSA_reg(RTSA, settings->ssp_active_rx_slots_map);

	if (clk_frm_mode == SSP_IN_MASTER_MODE) {
		switch (settings->master_mode_clk_selection) {
		case SSP_MASTER_CLOCK_UNDEFINED:
		case SSP_ONCHIP_CLOCK:
			/* Use M/N divider directly to generate clock */
			break;
		case SSP_NETWORK_CLOCK:
			sscr0 |= SSCR0_reg(NCS, 1);
			break;
		case SSP_EXTERNAL_CLOCK:
			sscr0 |= SSCR0_reg(ECS, 1);
			break;
		case SSP_ONCHIP_AUDIO_CLOCK:
			ssacd = calculate_ssacd( settings, bps,
				&frame_rate_divider);
			if (ssacd == 0xFFFF) {
				printf("Error during SSACD calculation\n");
				return;
			}
			sscr0 |= SSCR0_reg(ACS, 1);
			break;
		}

		if (frame_rate_divider == FRAME_RATE_CONTROL_STEREO) {
			sscr5 = settings->ssp_psp_T6;
			sscr4 = (sscr5 + 1) *  frame_rate_divider;
			sscr4 = SSCR4_reg(TOT_FRM_PRD, sscr4);
			sscr5 = SSCR5_reg(FRM_ASRT_WIDTH, sscr5);
		}

		sspsp |= SSPSP_reg(STRTDLY, settings->ssp_psp_T1) |
			 SSPSP_reg(DMYSTRT, settings->ssp_psp_T2);

	} else
		sscr1 |= SSCR1_reg(SCFR, SLAVE_SSPCLK_ON_ALWAYS);

	if (frame_rate_divider == FRAME_RATE_CONTROL_STEREO) {
		sspsp &= ~SSPSP_reg(SFRMWDTH, -1);
		sspsp |= SSPSP_reg(SFRMWDTH, 0x1) |
			 SSPSP_reg(STRTDLY, settings->ssp_psp_T1) |
			 SSPSP_reg(DMYSTRT, settings->ssp_psp_T2);
	}

	ssp_divc_h |= LPE_SSP0_DIVC_H_reg(DIV_BYPASS,
					  settings->ssp_divider_bypass) |
		      LPE_SSP0_DIVC_H_reg(DIV_EN,
					  settings->ssp_divider_enable) |
		      LPE_SSP0_DIVC_H_reg(DIV_UPDATE,
					  settings->ssp_divider_update) |
		      LPE_SSP0_DIVC_H_reg(DIV_M, settings->m_value);

	ssp_divc_l |= LPE_SSP0_DIVC_L_reg(DIV_N, settings->n_value);
	sscr2 |= calculate_sscr2(settings, clk_frm_mode);
	sscr3 |= calculate_sscr3(settings, clk_frm_mode);

	sssr = (SSSR_BCE_MASK << SSSR_BCE_SHIFT) |
	       (SSSR_TUR_MASK << SSSR_TUR_SHIFT) |
	       (SSSR_TINT_MASK << SSSR_TINT_SHIFT) |
	       (SSSR_PINT_MASK << SSSR_PINT_SHIFT) |
	       (SSSR_ROR_MASK << SSSR_ROR_SHIFT);

	i2s_disable(regs);

	/* first set CR1 without interrupt and service enables */
	write_SSCR1(sscr1, regs);
	write_SSCR2(sscr2, regs);
	write_SSCR3(sscr3, regs);
	if (frame_rate_divider == FRAME_RATE_CONTROL_STEREO &&
	    clk_frm_mode == SSP_IN_MASTER_MODE) {
		write_SSCR4(sscr4, regs);
		write_SSCR5(sscr5, regs);
	}

	write_SSPSP(sspsp, regs);
	write_SSTSA(sstsa, regs);
	write_SSRSA(ssrsa, regs);

	/* Clear status */
	write_SSSR(sssr, regs);
	write_SSCR0(sscr0, regs);

	/* set the time out for the reception */
	write_SSTO(0, regs);

	write_LPE_SSP2_DIVC_L(ssp_divc_l, shim);
	write_LPE_SSP2_DIVC_H(ssp_divc_h, shim);
}

/*
 * byt_i2s_init - Initialize I2s.
 * @bus: i2s config structure
 *
 * Initialize I2s.
 */
static int byt_i2s_init(BytI2s *bus)
{

	i2s_disable(bus->regs);
	set_ssp_i2s_hw(bus->regs,
		       bus->shim,
		       bus->settings,
		       bus->bits_per_sample);
	return 0;
}

/*
 * byt_i2s_send - Send audio samples to I2s controller.
 * @me: I2sOps structure
 * @data: Audio samples
 * @length: Number of samples
 *
 * Send audio samples to I2s controller.
 */
static int byt_i2s_send(I2sOps *me, unsigned int *data, unsigned int length)
{
	int i;
	uint64_t start;
	BytI2s *bus = container_of(me, BytI2s, ops);
	struct BytI2sRegs *i2s_reg = bus->regs;

	if (!bus->initialized) {
		if (byt_i2s_init(bus))
			return -1;
		bus->initialized = 1;
	}

	if (length < LPE_SSP_FIFO_SIZE) {
		printf("%s : Invalid data size\n", __func__);
		return -1;
	}

	i2s_enable(bus->regs, bus->settings, SSP_DATA_TX);
	for (i = 0; i < LPE_SSP_FIFO_SIZE; i++)
		writel(*data++, &i2s_reg->ssdr);

	length -= LPE_SSP_FIFO_SIZE;
	set_SSCR3_reg(i2s_reg, I2S_TX_EN);

	while (length > 0) {
		start = timer_us(0);
		if (readl(&i2s_reg->sssr) & 0x4) {
			writel(*data++, &i2s_reg->ssdr);
			length--;
		} else {
			if (timer_us(start) > 100000) {
				i2s_disable(bus->regs);
				printf("I2S Transfer Timeout\n" );
				return -1;
			}
		}
	}

	i2s_disable(bus->regs);
	return 0;
}

/*
 * new_byt_i2s - Allocate new I2s data structures.
 * @mmio: memory-mapped base address for controller
 * @mode: Implementation-specific driver mode
 * @bps: Bits per sample
 * @channels: Number of channels
 * @clock_frequenct: I2c frequency
 * @sampling_rate: Audio sample rate
 *
 * Allocate new I2s data structures.
 */
BytI2s *new_byt_i2s(uintptr_t mmio, const BytI2sSettings *settings,
		    int bps, int channels, uint32_t clock_freq,
		    uint32_t sampling_rate)
{
	BytI2s *bus = xzalloc(sizeof(*bus));

	bus->ops.send = &byt_i2s_send;
	bus->regs = (BytI2sRegs *)(mmio + BYT_SSP2_START_ADDRESS);
	bus->shim = (BytI2sRegs *)(mmio + BYT_SSP2_SHIM_START_ADDRESS);
	bus->settings = settings;
	bus->bits_per_sample = bps;
	bus->channels = channels;
	bus->clock_freq = clock_freq;
	bus->sampling_rate = sampling_rate;
	return bus;
}
