//=========================================================================//
//                                                                         //
//  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. //
//                                                                         //
//=========================================================================//

#ifndef _BUSINTERFACE_H
#define _BUSINTERFACE_H

#include "types.h"
#include "errcode.h"
#include "wait.h"
#include "globals.h"
#include "interfconv.h"

#include <QDebug>

//Some useful flags
#define SPIMODE_CPHA		0x01		// clock phase/edge
#define SPIMODE_CPOL		0x02		// clock polarity
#define SPIMODE_MASK		0x03

#define xMODE_RDONLY		0x04
#define xMODE_WRONLY		0x08

//#define I2CMODE_MASK		0x10

enum
{
	SPI_MODE_0 = (0 | 0),
	SPI_MODE_1 = (0 | SPIMODE_CPHA),
	SPI_MODE_2 = (SPIMODE_CPOL | 0),
	SPI_MODE_3 = (SPIMODE_CPOL | SPIMODE_CPHA)
};

#define SCLTIMEOUT      900     // enable SCL check and timing (for slaves that hold down the SCL line to slow the transfer)

class BusInterface
{
  public:
	BusInterface()
		: old_portno(-1),
		  usb_vp(0),
		  installed(-1),
		  cmd2cmd_delay(0),
		  shot_delay(5),
		  i2c_mode(false)
	{
	}

	virtual int Open(int port) = 0;
	virtual void Close() = 0;

	virtual int TestOpen(int port)
	{
		qDebug() << Q_FUNC_INFO << "(" << port << ") IN";

		int ret_val = TestSave(port);

		TestRestore();

		qDebug() << Q_FUNC_INFO << "=" << ret_val << " OUT";

		return ret_val;
	}
	virtual int TestPort(int port)
	{
		qDebug() << Q_FUNC_INFO << "(" << port << ") IN";

		return TestOpen(port);
	}
	virtual int TestSave(int port)
	{
		int ret_val;

		qDebug() << Q_FUNC_INFO << "(" << port << ") IN";

		old_portno = installed;

		Close();

		if ((ret_val = Open(port)) == OK)
		{
		}

		qDebug() << Q_FUNC_INFO << "=" << ret_val << " OUT";

		return ret_val;
	}
	virtual void TestRestore()
	{
		qDebug() << Q_FUNC_INFO << "IN *** Inst=" << installed;

		if (IsInstalled())
		{
			Close();
		}

		if (old_portno >= 0)
		{
			Open(old_portno);
			old_portno = -1;
		}

		qDebug() << Q_FUNC_INFO << "OUT";
	}

	virtual int SetPower(bool onoff)
	{
		return OK;
	}
	virtual void SetControlLine(int res = 1)
	{
	}

	virtual void SetDataOut(int sda = 1) = 0;
	virtual void SetInvDataOut(int sda = 1)
	{
		SetDataOut(!sda);
	}
	virtual void SetClock(int scl = 1) = 0;
	virtual int GetDataIn() = 0;
	virtual int GetClock() = 0;
	virtual void SetClockData() = 0;
	virtual void ClearClockData()
	{ }
	virtual int IsClockDataUP() = 0;
	virtual int IsClockDataDOWN() = 0;

	virtual bool CheckDataLines(int len = 1, int sda = -1, int scl = -1)
	{
		bool test = false;

		if (len > 0)
		{
			do {
				if (sda == 0 && scl == 0)
				{
					test = IsClockDataDOWN();
				}
				else if (sda > 0 && scl > 0)
				{
					test = IsClockDataUP();
				}
				else
				{
					bool test_sda = true, test_scl = true;

					if (sda > 0)
					{
						test_sda = GetDataIn();
					}
					else if (sda == 0)
					{
						test_sda = !GetDataIn();
					}

					if (scl > 0)
					{
						test_scl = GetClock();
					}
					else if (scl == 0)
					{
						test_scl = !GetClock();
					}

					test = (test_sda && test_scl);
				}
			} while (test && --len > 0);
		}

		return test;
	}

	int GetCmd2CmdDelay() const
	{
		return cmd2cmd_delay;
	}
	void SetCmd2CmdDelay(int delay)
	{
		if (delay >= 0)
		{
			cmd2cmd_delay = delay;
		}
	}

	bool IsInstalled() const
	{
		return (installed >= 0) ? true : false;
	}

	void SetUSBVidPid(VidPid vp)
	{
		usb_vp = vp;
	}
	VidPid GetUSBVid()
	{
		return usb_vp;
	}

	virtual void WaitMsec(unsigned int msec)
	{
		Flush();
		w.WaitMsec(msec);
	}

	virtual void WaitUsec(unsigned int usec)
	{
		Flush();
		w.WaitUsec(usec);
	}

	virtual void ShotDelay(int n = 1)
	{
		w.WaitUsec(shot_delay * n);
	}

	virtual int xferBit(int &err, int b, int mode = 0)
	{
		int ret = 0;

		if (!i2c_mode)	//(mode & I2CMODE_MASK) == 0)
		{
			switch (mode & SPIMODE_MASK)
			{
			case 3:
				SetClock(0);
				SetDataOut(b);
				ShotDelay();
				SetClock(1);
				if ((mode & xMODE_WRONLY) == 0)
				{
					ret = GetDataIn();
				}
				ShotDelay();
				break;
			case 2:
				SetDataOut(b);
				ShotDelay();
				SetClock(0);
				if ((mode & xMODE_WRONLY) == 0)
				{
					ret = GetDataIn();
				}
				ShotDelay();
				SetClock(1);
				break;
			case 1:
				SetClock(1);
				SetDataOut(b);
				ShotDelay();
				SetClock(0);
				if ((mode & xMODE_WRONLY) == 0)
				{
					ret = GetDataIn();
				}
				ShotDelay();
				break;
			case 0:
			default:
				SetDataOut(b);
				ShotDelay();
				SetClock(1);
				if ((mode & xMODE_WRONLY) == 0)
				{
					ret = GetDataIn();
				}
				ShotDelay();
				SetClock(0);
				break;
			}
		}
		else
		{
			//I2CBus
			SetDataOut(b);		// SDA must be high to receive data (low dominant)
			ShotDelay();		// tSU;DAT = 250 nsec (tLOW / 2 = 2 usec)
			SetClock(1);
#ifdef SCLTIMEOUT
			for (int k = SCLTIMEOUT; GetClock() == 0 && k > 0; k--)
			{
				WaitUsec(1);
			}
			if (GetClock() == 0)
			{
				return IICERR_SCLCONFLICT;
			}
#endif
			ShotDelay();		// tHIGH / 2 = 2 usec
			if ((mode & xMODE_WRONLY) == 0)
			{
				ret = GetDataIn();
			}
			ShotDelay();		// tHIGH / 2 = 2 usec
			SetClock(0);
			ShotDelay();		// tHD;DATA = 300 nsec (tLOW / 2 = 2 usec)
		}
		err = OK;
		return ret;
	}

	virtual uint8_t xferByte(int &err, uint8_t by, int mode = 0, int bpw = 8, bool lsb_first = false)
	{
		return (uint8_t)xferWord(err, by, mode, bpw, lsb_first);
	}

	virtual unsigned long xferWord(int &err, unsigned long word_out, int mode = 0, int bpw = 8, bool lsb_first = false)
	{
		uint32_t word_in = 0;
		uint32_t bitmask;
		err = OK;

		if (!i2c_mode)	//if ((mode & I2CMODE_MASK) == 0)
		{
			switch (mode & SPIMODE_MASK)
			{
			case 3:
			case 2:
				SetClock(1);
				break;
			case 1:
			case 0:
			default:
				SetClock(0);
				break;
			}
		}

		if (lsb_first)
		{
			bitmask = 1;
		}
		else
		{
			bitmask = 1 << (bpw - 1);
		}

		for (int k = 0; k < bpw; k++)
		{
			if (xferBit(err, word_out & bitmask, mode))
			{
				word_in |= bitmask;
			}

			if (lsb_first)
			{
				bitmask <<= 1;
			}
			else
			{
				bitmask >>= 1;
			}
		}
		SetDataOut(1);

		return word_in;
	}

	virtual void SetDelay(int delay)
	{
		if (delay >= 0)
		{
			shot_delay = delay;
		}
	}
	int GetDelay() const
	{
		return shot_delay;
	}

	void SetI2CMode(bool mode)
	{
		i2c_mode = mode;
	}
	bool GetI2CMode() const
	{
		return i2c_mode;
	}

	virtual void ConfigPins(int pinum_ctrl = -1, int pinum_datain = -1, int pinum_dataout = -1, int pinum_clock = -1, int pinum_clockin = -1, int pinum_poweron = -1, int pinum_enbus = -1, int pinnum_ctrlin = -1)
	{
		pins.ctrl = pinum_ctrl;
		pins.datain = pinum_datain;
		pins.dataout = pinum_dataout;
		pins.clock = pinum_clock;
		pins.ctrlin = pinnum_ctrlin;
		pins.clockin = pinum_clockin;
		pins.poweron = pinum_poweron;
		pins.enbus = pinum_enbus;
	}
	virtual void ConfigPins(InterfPins p)
	{
		ConfigPins(p.ctrl, p.datain, p.dataout, p.clock, p.clockin, p.poweron, p.enbus, p.ctrlin);
	}

  protected:
	void Install(int val)
	{
		installed = val;
	}

	void DeInstall()
	{
		installed = -1;
	}

	int GetInstalled() const
	{
		return installed;
	}

	virtual int Flush()
	{
		return OK;
	}

	int old_portno;             // TestSave() save the status here
	VidPid usb_vp;
	Wait w;
	InterfPins pins;

  private:
	int installed;              // -1 --> not installed, >= 0 number if the installed port
	int cmd2cmd_delay;			// <> 0 if a delay between commands is needed
	unsigned int shot_delay;	//delay unit to perform bus timing
	bool i2c_mode;
};

#endif
