//=========================================================================//
//                                                                         //
//  PonyProg - Serial Device Programmer                                    //
//                                                                         //
//  Copyright (C) 1997-2025   Claudio Lanconelli                           //
//                                                                         //
//  https://github.com/lancos/ponyprog                                        //
//                                                                         //
//-------------------------------------------------------------------------//
//                                                                         //
// 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 version2 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 (see LICENSE);     if not, write to the         //
// Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. //
//                                                                         //
//=========================================================================//

#include "types.h"
#include "imbus.h"
#include "errcode.h"

#include <QDebug>
#include <QtCore>

#include "e2cmdw.h"

#ifdef Q_OS_LINUX
#  include <unistd.h>
#else
#  ifdef        __BORLANDC__
#    define     __inline__
#  else // _MICROSOFT_ VC++
#    define     __inline__ __inline
#    define _export
#  endif
#endif

#define IMADDR_ADDR(x)          ( (x) ? 132 : 128 )
#define IMADDR_READ(x)          ( (x) ? 133 : 129 )
#define IMADDR_WRITE(x)         ( (x) ? 135 : 131 )

// Costruttore
IMBus::IMBus(BusInterface *ptr)
	: BusIO(ptr)
{
	qDebug() << Q_FUNC_INFO;

	StatusLocation = 526;   //526 for NVM3060, 14 for MDA2062
	SecondaryAddress = false;
	Data16_mode = true;
	ProgDelay = 30;
}

// Distruttore
IMBus::~IMBus()
{
	qDebug() << Q_FUNC_INFO;
}

void IMBus::SetStatusLocation(int val)
{
	if (val >= 0)
	{
		StatusLocation = val;
	}
}

void IMBus::UseSecondaryAddress(bool val)
{
	SecondaryAddress = val;
}

void IMBus::SetDataMode(int val)
{
	if (val == 16)
	{
		Data16_mode = true;
	}
	else if (val == 8)
	{
		Data16_mode = false;
	}
}

void IMBus::SetProgDelay(int val)
{
	ProgDelay = val;
}

void IMBus::SetDelay()
{
	int val = E2Profile::GetIMBusSpeed();
	int n;

	switch (val)
	{
	case TURBO:
		n = 1;         // as fast as your PC can
		break;

	case FAST:
		n = 4;
		break;

	case SLOW:
		n = 30;
		break;

	case VERYSLOW:
		n = 100;
		break;

	case ULTRASLOW:
		n = 500;
		break;

	default:
		n = 10;         //Default (< 100KHz)
		break;
	}

	Q_CHECK_PTR(busI);
	busI->SetDelay(n);

	qDebug() << Q_FUNC_INFO << "=" << n;
}

int IMBus::SendDataBit(int b)
{
	//clearCLK();             //set clock low
	//bitDI(b);
	//ShotDelay();
	//setCLK();               //device latch data bit now!
	//ShotDelay();

	int err = OK;
	setCLK();
	busI->xferBit(err, b, SPI_MODE_3 | xMODE_WRONLY);

	return err;
}

// return negative number in case of error, otherwise received bit (0 or 1)
int IMBus::RecDataBit()
{
	//clearCLK();                             //the eeprom set data now
	//ShotDelay();
	//setCLK();
	//b = getDO();
	//ShotDelay();

	int err = OK;
	setCLK();
	int rv = busI->xferBit(err, 1, SPI_MODE_3 | xMODE_RDONLY);
	if (err == OK)
	{
		return rv;
	}
	else
	{
		return err;
	}
}

int IMBus::IdentPulse()
{
	clearIdent();
	ShotDelay(6);
	setIdent();
	ShotDelay(2);

	return OK;
}

//byte operations
int IMBus::SendAddrWord(int wo)
{
	int err = OK;

	setCLK();
	clearIdent();

	//From LSB to MSB
	//for (int k = 0; k < 8; k++)
	//	SendDataBit(wo & (1 << k));
	busI->xferByte(err, wo, SPI_MODE_3 | xMODE_WRONLY, 8, true);

	setCLK();
	setDI();
	setIdent();
	ShotDelay(2);	//1.5 usec

	return err;
}

int IMBus::SendDataWord(long wo, int wlen)
{
	int err = OK;

	setCLK();
	setIdent();

	//From LSB to MSB
	//for (int k = 0; k < wlen; k++)
	//	SendDataBit(wo & (1 << k));
	busI->xferWord(err, wo, SPI_MODE_3 | xMODE_WRONLY, wlen, true);

	setCLK();
	setDI();

	return err;
}

int IMBus::RecDataWord(int wlen)
{
	int err = OK;

	setCLK();
	setDI();
	setIdent();

	//int val = 0;
	//for (int k = 0; k < wlen; k++)
	//	if (RecDataBit())
	//		val |= 1 << k;
	int rv = busI->xferWord(err, 0xffff, SPI_MODE_3 | xMODE_RDONLY, wlen, true);
	if (err == OK)
	{
		return rv;
	}
	else
	{
		return err;
	}
}

int IMBus::WaitReadyAfterWrite(int addr, int delay, long timeout)
{
	int rval = OK;

	if (delay > 0)
	{
		WaitMsec(delay);
	}
	else
	{
		uint8_t val = 0xff;

		while ((val & (1 << 1)) && timeout--)
		{
			//Read status location at address 526

			//Send address code
			SendAddrWord(IMADDR_ADDR(SecondaryAddress));
			SendDataWord(StatusLocation, Data16_mode ? 16 : 8);
			IdentPulse();

			//Receive data
			SendAddrWord(IMADDR_READ(SecondaryAddress));
			val = (uint8_t)RecDataWord(Data16_mode ? 16 : 8);
			IdentPulse();
		}

		if (timeout == 0)
		{
			rval = E2P_TIMEOUT;
		}
	}

	return rval;
}

int IMBus::Reset(void)
{
	qDebug() << "IMBus::Reset()";

	SetDelay();

	setCLK();               //clock = 1
	setDI();                //data = 1
	setIdent();             //Ident = 1

	WaitMsec(50);

	return OK;
}

long IMBus::Read(int addr, uint8_t *data, long length, int page_size)
{
	qDebug() << "IMBus::Read(" << (Qt::hex) << addr << ", " << data << ", " << (Qt::dec) <<  length << ")";

	ReadStart();
	long len;

	for (len = 0; len < length; len++, addr++)
	{
		//Send address code
		SendAddrWord(IMADDR_ADDR(SecondaryAddress));
		SendDataWord(addr, Data16_mode ? 16 : 8);
		IdentPulse();

		//Receive data
		SendAddrWord(IMADDR_READ(SecondaryAddress));
		*data++ = (uint8_t)RecDataWord(Data16_mode ? 16 : 8);
		IdentPulse();

		if ((len % 4) == 0)
		{
			if (ReadProgress(len * 100 / length))
			{
				break;
			}
		}
	}

	ReadEnd();

	qDebug() << "IMBus::Read() = " << len;

	return len;
}

long IMBus::Write(int addr, uint8_t const *data, long length, int page_size)
{
	long len;
	uint8_t bval;
	int loop_timeout;

	WriteStart();

	for (len = 0; len < length; len++, addr++, data++)
	{
		bval = ~ *data;         //be sure are different to write at least once

		for (loop_timeout = 10; bval != *data && loop_timeout > 0; loop_timeout--)
		{
			//Erase command first
			//Send address code
			SendAddrWord(IMADDR_ADDR(SecondaryAddress));
			SendDataWord(addr, Data16_mode ? 16 : 8);
			IdentPulse();

			//Erase location
			SendAddrWord(IMADDR_WRITE(SecondaryAddress));
			SendDataWord(0xffff, Data16_mode ? 16 : 8);
			IdentPulse();

			if (WaitReadyAfterWrite(addr, ProgDelay) != OK)
			{
				return E2P_TIMEOUT;
			}

			//Write command
			if (ProgDelay <= 0)             //Ripristina l'indirizzo corretto (solo se ` attivo il polling)
			{
				//Send address code
				SendAddrWord(IMADDR_ADDR(SecondaryAddress));
				SendDataWord(addr, Data16_mode ? 16 : 8);
				IdentPulse();
			}

			//Program location
			SendAddrWord(IMADDR_WRITE(SecondaryAddress));
			SendDataWord(*data, Data16_mode ? 16 : 8);
			IdentPulse();

			if (WaitReadyAfterWrite(addr, ProgDelay) != OK)
			{
				return E2P_TIMEOUT;
			}

			//Verify while write
			//Send address code
			SendAddrWord(IMADDR_ADDR(SecondaryAddress));
			SendDataWord(addr, Data16_mode ? 16 : 8);
			IdentPulse();

			//Receive data
			SendAddrWord(IMADDR_READ(SecondaryAddress));
			bval = (uint8_t)RecDataWord(Data16_mode ? 16 : 8);
			IdentPulse();
		} //for

		if (loop_timeout == 0)
		{
			return E2ERR_WRITEFAILED;
		}

		if (WriteProgress(len * 100 / length))
		{
			break;
		}
	}

	WriteEnd();

	return len;
}
