/* -*- c++ -*-
 *
 * SOCLIB_LGPL_HEADER_BEGIN
 * 
 * This file is part of SoCLib, GNU LGPLv2.1.
 * 
 * SoCLib 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; version 2.1 of the License.
 * 
 * SoCLib 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 SoCLib; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * SOCLIB_LGPL_HEADER_END
 *
 * Copyright (c) UPMC, Lip6, Asim
 *         Nicolas Pouillon <nipo@ssji.net>, 2007
 */

#include <stdint.h>
#include "register.h"
#include "../include/vci_xdma.h"
#include "xdma.h"

namespace soclib { namespace caba {

#define tmpl(t) template<typename vci_param> t VciXDma<vci_param>

tmpl(bool)::on_write(int seg, typename vci_param::addr_t addr, typename vci_param::data_t data, int be)
{
    int cell = (int)addr / vci_param::B;

	switch ((enum SoclibXDmaRegisters)cell) {
    case DMA_CONF:
        m_conf = data;
        return true;
	case DMA_SRC:
		m_src = data;
		return true;
    case DMA_DST:
		m_dst = data;
		return true;
    case DMA_LEN:
		m_len = data;
		return true;
    case DMA_RESET:
        m_must_finish = true;
        m_irq_enabled = false;
        m_conf = 0;
		r_irq = false;
        return true;
    case DMA_IRQ_DISABLED:
		m_irq_enabled = !data;
		return true;
	};
	return false;
}

tmpl(void)::ended()
{
	if ( m_irq_enabled )
		r_irq = true;
	m_len = 0;
    m_must_finish = false;
	m_handling = false;
}

tmpl(bool)::on_read(int seg, typename vci_param::addr_t addr, typename vci_param::data_t &data)
{
    int cell = (int)addr / vci_param::B;

	switch ((enum SoclibXDmaRegisters)cell) {
    case DMA_CONF:
        data = m_conf;
        return true;
	case DMA_SRC:
		data = m_src;
		return true;
    case DMA_DST:
		data = m_dst;
		return true;
	case DMA_LEN:
		data = m_len;
		return true;
    default:
        return false;
	}
	return false;
}

tmpl(void)::read_done( req_t *req )
{
	if ( !req->failed() && !m_must_finish ) {
        if (!m_have_data && !(m_conf & 0x1))
        {
            m_have_data = true;
        }
		ssize_t remaining = (ssize_t)m_len-(size_t)m_offset;

		if ( remaining < m_size_block )
			m_size_block = remaining;
        
        uint32_t dest = m_dst;
        if (m_conf & 0x2)
            dest += m_offset;
            
		VciInitSimpleWriteReq<vci_param> *new_req =
			new VciInitSimpleWriteReq<vci_param>( dest, &m_data[0], m_size_block );
		new_req->setDone( this, ON_T(write_finish) );
		m_vci_init_fsm.doReq( new_req );
	} else {
		ended();
	}
	delete req;
}

tmpl(void)::write_finish( req_t *req )
{
	m_offset += m_size_block;
	if ( !req->failed() && !m_must_finish )
		next_req();
	else {
		ended();
	}
	delete req;
}

tmpl(void)::next_req()
{
	ssize_t remaining = (ssize_t)m_len-(size_t)m_offset;

	if ( remaining <= 0 ) {
		ended();
		return;
	}

	if ( remaining < m_size_block )
		m_size_block = remaining;

    if ( !m_have_data || (m_conf & 0x1))
    {
        VciInitSimpleReadReq<vci_param> *req =
            new VciInitSimpleReadReq<vci_param>(
                &m_data[0], m_src+m_offset, m_size_block );
        req->setDone( this, ON_T(read_done) );
        m_vci_init_fsm.doReq( req );
    }
    else
    {
        uint32_t dest = m_dst;
        if (m_conf & 0x2)
            dest += m_offset;
            
		VciInitSimpleWriteReq<vci_param> *new_req =
			new VciInitSimpleWriteReq<vci_param>( dest, &m_data[0], m_size_block );
		new_req->setDone( this, ON_T(write_finish) );
		m_vci_init_fsm.doReq( new_req );
    }
}

tmpl(void)::transition()
{
	if (!p_resetn) {
		m_vci_target_fsm.reset();
		m_vci_init_fsm.reset();
		r_irq = false;
		m_irq_enabled = false;
		m_len = 0;
		m_handling = false;
        m_must_finish = false;
        m_have_data = false;
		return;
	}

	if ( m_len && !m_handling ) {
		m_handling = true;
		m_offset = 0;
        m_have_data = false;

        if (!(m_conf&0x3))
        {
            m_size_block = m_conf>>2;
            if (m_size_block > m_data.size())
            {
                m_size_block = m_data.size();
            }
        }
        else
        {
            m_size_block = m_data.size();
        }

		next_req();
	}
    
    if( !m_handling )
        m_must_finish = false;

	m_vci_target_fsm.transition();
	m_vci_init_fsm.transition();
}

tmpl(void)::genMoore()
{
	m_vci_target_fsm.genMoore();
	m_vci_init_fsm.genMoore();

	p_irq = r_irq && m_irq_enabled;
}

tmpl(/**/)::VciXDma(
    sc_module_name name,
    const MappingTable &mt,
    const IntTab &srcid,
    const IntTab &tgtid,
	const size_t burst_size )
	: caba::BaseModule(name),
	  m_vci_target_fsm(p_vci_target, mt.getSegmentList(tgtid)),
	  m_vci_init_fsm(p_vci_initiator, mt.indexForId(srcid)),
	  m_len(0),
	  m_data(burst_size, (uint8_t)0),
      p_clk("clk"),
      p_resetn("resetn"),
      p_vci_target("vci_target"),
      p_vci_initiator("vci_initiator"),
      p_irq("irq")
{
	m_vci_target_fsm.on_read_write(on_read, on_write);

    assert(burst_size && "Useless XDMA with no buffer");
    assert(burst_size < (1<<vci_param::K) && "I will be unable to create requests that big");

	SC_METHOD(transition);
	dont_initialize();
	sensitive << p_clk.pos();

	SC_METHOD(genMoore);
	dont_initialize();
	sensitive << p_clk.neg();
}

}}

// Local Variables:
// tab-width: 4
// c-basic-offset: 4
// c-file-offsets:((innamespace . 0)(inline-open . 0))
// indent-tabs-mode: nil
// End:

// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4

