/*--------------------------------------------------------------------------*\

    FILE....: DSPFIFO.CPP
    TYPE....: C++ Functions
    AUTHOR..: David Rowe
    DATE....: 19/11/97
    AUTHOR..: Ron Lee
    DATE....: 5/3/07

    Functions used to read and write to First In First Out (FIFO) queues in
    the DSP, thus facilitating PC to DSP data transfer.  The word length of
    the FIFOs is 16 bits.

    Modified 7/10/98 to support mark 2 kernel mode device driver that
    buffers DSP fifo in PC fifo.


        Voicetronix Voice Processing Board (VPB) Software
         Copyright (C) 1999-2008 Voicetronix www.voicetronix.com.au

         This library is free software; you can redistribute it and/or
         modify it under the terms of the GNU Lesser General Public
         License as published by the Free Software Foundation; either
         version 2.1 of the License, or (at your option) any later version.

         This library 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
         Lesser General Public License for more details.

         You should have received a copy of the GNU Lesser General Public
         License along with this library; if not, write to the Free Software
         Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
         MA  02110-1301  USA

\*---------------------------------------------------------------------------*/

#include "dspfifo.h"
#include "vpbapi.h"

#if _DEBUG
 #include <cstdio>
#endif


V4PCIDspFifo::V4PCIDspFifo(Hip *hip, unsigned short board, unsigned short fifo_addr)
    : m_hip( hip )
    , m_board( board )
    , m_adspfifo( fifo_addr )
    , m_start( 0 )
    , m_end( 0 )
    , m_wr( 0 )
    , m_rd( 0 )
{ //{{{
	switch( m_hip->GetDeviceDriverModel() )
	{
	    case 0:
	    case 2:
		pthread_mutex_init(&m_mutex,NULL);

		//XXX Nasty trickery here.  This initialises 5 packed members!
		//pthread_mutex_lock(&m_mutex);
		m_hip->ReadDspSram(m_board, m_adspfifo, FIFO_SIZE, &m_start);
		//pthread_mutex_unlock(&m_mutex);

		CheckDspFifo();
		break;

	    default: throw VpbException("V4PCIDspFifo: unsupported driver model");
	}
} //}}}

V4PCIDspFifo::~V4PCIDspFifo()
{ //{{{
	pthread_mutex_destroy(&m_mutex);
} //}}}

void V4PCIDspFifo::CheckDspFifo()
{ //{{{
    #if _DEBUG
	m_hip->ReadDspSram(m_board, m_adspfifo + PSTART, 1, &m_start);
	m_hip->ReadDspSram(m_board, m_adspfifo + PEND,   1, &m_end);

	if( m_end < m_start || m_wr < m_start || m_wr > m_end
			    || m_rd < m_start || m_rd > m_end )
	{
		printf("\n");
		printf("pstart = 0x%x\n", m_start);
		printf("pend   = 0x%x\n", m_end);
		printf("pwr    = 0x%x\n", m_wr);
		printf("prd    = 0x%x\n", m_rd);

		// read again - just to be sure
		m_hip->ReadDspSram(m_board, m_adspfifo + PWR,    1, &m_wr);
		m_hip->ReadDspSram(m_board, m_adspfifo + PRD,    1, &m_rd);
		m_hip->ReadDspSram(m_board, m_adspfifo + PSTART, 1, &m_start);
		m_hip->ReadDspSram(m_board, m_adspfifo + PEND,   1, &m_end);
		printf("\n");
		printf("pstart = 0x%x\n", m_start);
		printf("pend   = 0x%x\n", m_end);
		printf("pwr    = 0x%x\n", m_wr);
		printf("prd    = 0x%x\n", m_rd);

		//coff_check_dsp_firmware(hip, 0, "/etc/vpb/vpbmain.out");
		throw VpbException("CheckDspFifo: failed");
	}
    #endif
} //}}}

Fifo::Status V4PCIDspFifo::Write(uint16_t *buf, size_t len)
{ //{{{
	pthread_mutex_lock(&m_mutex);

	// update PC's copy of DSP FIFO state variables 
	m_hip->ReadDspSram(m_board, m_adspfifo + PWR, 1, &m_wr);
	m_hip->ReadDspSram(m_board, m_adspfifo + PRD, 1, &m_rd);

	CheckDspFifo();

	// determine if there is enough room for buf 
	size_t words_free = (m_rd > m_wr) ? m_rd - m_wr - 1
					  : m_size - (m_wr - m_rd) - 1;

	if(words_free < len) {
		pthread_mutex_unlock(&m_mutex);
		return Fifo::FULL;
	}

	// If buf overlaps end of linear array split into two block moves 
	if((m_wr + len) > m_end) {
		size_t copy_first = m_end - m_wr;

		m_hip->WriteDspSram(m_board, m_wr, copy_first, buf);
		m_hip->WriteDspSram(m_board, m_start, len - copy_first, buf + copy_first);
	} else  m_hip->WriteDspSram(m_board, m_wr, len, buf);

	// increment pwr and wrap around if required 
	size_t new_pwr = m_wr + len;
	m_wr = (new_pwr < m_end) ? new_pwr : m_start + (new_pwr - m_end);

	// copy pwr back to DSP 
	m_hip->WriteDspSram(m_board, m_adspfifo + PWR, 1, &m_wr);
	CheckDspFifo();
	pthread_mutex_unlock(&m_mutex);

	return Fifo::OK;
} //}}}

Fifo::Status V4PCIDspFifo::Read(uint16_t *buf, size_t len)
{ //{{{
	pthread_mutex_lock(&m_mutex);

	// update PC's copy of DSP FIFO state variables 
	m_hip->ReadDspSram(m_board, m_adspfifo + PWR, 1, &m_wr);
	m_hip->ReadDspSram(m_board, m_adspfifo + PRD, 1, &m_rd);

	CheckDspFifo();

	// determine if there is data in FIFO to fill buf 
	size_t words_used = (m_rd > m_wr) ? m_size - (m_rd - m_wr)
					  : m_wr - m_rd;
	if(words_used < len) {
		pthread_mutex_unlock(&m_mutex);
		return Fifo::EMPTY;
	}

	// If buf overlaps end of linear array split into two block moves 
	if((m_rd + len) > m_end) {
		size_t copy_first = m_end - m_rd;

		m_hip->ReadDspSram(m_board, m_rd, copy_first, buf);
		m_hip->ReadDspSram(m_board, m_start, len - copy_first, buf + copy_first);
	} else  m_hip->ReadDspSram(m_board, m_rd, len, buf);

	// increment prd and wrap around if required 
	size_t new_prd = m_rd + len;
	m_rd = (new_prd < m_end) ? new_prd : m_start + (new_prd - m_end);

	// copy prd back to DSP 
	m_hip->WriteDspSram(m_board, m_adspfifo + PRD, 1, &m_rd);
	CheckDspFifo();
	pthread_mutex_unlock(&m_mutex);

	return Fifo::OK;
} //}}}

size_t V4PCIDspFifo::HowFull()
{ //{{{
	pthread_mutex_lock(&m_mutex);

	m_hip->ReadDspSram(m_board, m_adspfifo + PWR, 1, &m_wr);
	m_hip->ReadDspSram(m_board, m_adspfifo + PRD, 1, &m_rd);
	CheckDspFifo();

	size_t words = (m_rd > m_wr) ? m_size - (m_rd - m_wr) : m_wr - m_rd;

	pthread_mutex_unlock(&m_mutex);
	return words;
} //}}}

size_t V4PCIDspFifo::HowEmpty()
{ //{{{
	return m_size - 1 - HowFull();
} //}}}

void V4PCIDspFifo::Flush()
{ //{{{
	pthread_mutex_lock(&m_mutex);

	m_wr = m_rd = m_start;
	m_hip->WriteDspSram(m_board, m_adspfifo + PWR, 1, &m_wr);
	m_hip->WriteDspSram(m_board, m_adspfifo + PRD, 1, &m_rd);
	CheckDspFifo();

	pthread_mutex_unlock(&m_mutex);
} //}}}

