/*
        ifd_phoenix.c
        This module provides IFD handling functions for Smartmouse/Phoenix reader.
*/

#include "../globals.h"

#ifdef CARDREADER_PHOENIX
#include "../oscam-time.h"
#include "icc_async.h"
#include "ifd_db2com.h"
#include "ifd_phoenix.h"
#include "io_serial.h"

#define OK 0
#define ERROR 1

#define GPIO_PIN (1 << (reader->detect - 4))

static inline int reader_use_gpio(struct s_reader *reader)
{
	return reader->use_gpio && reader->detect > 4;
}

static void set_gpio(struct s_reader *reader, int32_t level)
{
	int ret = 0;

	ret |= read(reader->gpio_outen, &reader->gpio, sizeof(reader->gpio));
	reader->gpio |= GPIO_PIN;
	ret |= write(reader->gpio_outen, &reader->gpio, sizeof(reader->gpio));

	ret |= read(reader->gpio_out, &reader->gpio, sizeof(reader->gpio));
	if(level > 0)
		{ reader->gpio |= GPIO_PIN; }
	else
		{ reader->gpio &= ~GPIO_PIN; }
	ret |= write(reader->gpio_out, &reader->gpio, sizeof(reader->gpio));

	rdr_log_dbg(reader, D_IFD, "%s level: %d ret: %d", __func__, level, ret);
}

static void set_gpio_input(struct s_reader *reader)
{
	int ret = 0;
	ret |= read(reader->gpio_outen, &reader->gpio, sizeof(reader->gpio));
	reader->gpio &= ~GPIO_PIN;
	ret |= write(reader->gpio_outen, &reader->gpio, sizeof(reader->gpio));
	rdr_log_dbg(reader, D_IFD, "%s ret:%d", __func__, ret);
}

static int32_t get_gpio(struct s_reader *reader)
{
	int ret = 0;
	set_gpio_input(reader);
	ret = read(reader->gpio_in, &reader->gpio, sizeof(reader->gpio));
	rdr_log_dbg(reader, D_IFD, "%s ok:%d ret:%d", __func__, reader->gpio & GPIO_PIN, ret);
	if(reader->gpio & GPIO_PIN)
		{ return OK; }
	else
		{ return ERROR; }
}

int32_t Phoenix_Init(struct s_reader *reader)
{
	// First set card in reset state, to not change any parameters while communication ongoing
	IO_Serial_RTS_Set(reader);

	const struct s_cardreader *crdr_ops = reader->crdr;
	if (!crdr_ops) return ERROR;

	if(crdr_ops->flush) { IO_Serial_Flush(reader); }

	// define reader->gpio number used for card detect and reset. ref to globals.h
	if(reader_use_gpio(reader))
	{
		reader->gpio_outen = open("/dev/gpio/outen", O_RDWR);
		reader->gpio_out   = open("/dev/gpio/out",   O_RDWR);
		reader->gpio_in    = open("/dev/gpio/in",    O_RDWR);
		rdr_log_dbg(reader, D_IFD, "init gpio_outen:%d gpio_out:%d gpio_in:%d",
					   reader->gpio_outen, reader->gpio_out, reader->gpio_in);
		set_gpio_input(reader);
	}

	rdr_log_dbg(reader, D_IFD, "Initializing reader type=%d", reader->typ);

	/* Default serial port settings */
	if(reader->atr[0] == 0)
	{
		if(IO_Serial_SetParams(reader, DEFAULT_BAUDRATE, 8, PARITY_EVEN, 2, NULL, NULL)) { return ERROR; }
		if(crdr_ops->flush) { IO_Serial_Flush(reader); }
	}
	return OK;
}

int32_t Phoenix_GetStatus(struct s_reader *reader, int32_t *status)
{
	// detect card via defined reader->gpio
	if(reader_use_gpio(reader))
	{
		*status = !get_gpio(reader);
		return OK;
	}
	else
	{
		return IO_Serial_GetStatus(reader, status);
	}
}

int32_t Phoenix_Reset(struct s_reader *reader, ATR *atr)
{
	rdr_log_dbg(reader, D_IFD, "Resetting card");
	int32_t ret;
	int32_t i;
	unsigned char buf[ATR_MAX_SIZE];
	int32_t parity[3] = {PARITY_EVEN, PARITY_ODD, PARITY_NONE};

	call(IO_Serial_SetBaudrate(reader, DEFAULT_BAUDRATE));

	const struct s_cardreader *crdr_ops = reader->crdr;
	if (!crdr_ops) return ERROR;

	for(i = 0; i < 3; i++)
	{
		if(crdr_ops->flush) { IO_Serial_Flush(reader); }
		if(crdr_ops->set_parity) { IO_Serial_SetParity(reader, parity[i]); }

		ret = ERROR;

		IO_Serial_Ioctl_Lock(reader, 1);
		if(reader_use_gpio(reader))
			{ set_gpio(reader, 0); }
		else
			{ IO_Serial_RTS_Set(reader); }

		cs_sleepms(50);

		// felix: set card reset hi (inactive)
		if(reader_use_gpio(reader))
			{ set_gpio_input(reader); }
		else
			{ IO_Serial_RTS_Clr(reader); }
		cs_sleepms(50);
		IO_Serial_Ioctl_Lock(reader, 0);

		int32_t n = 0;
		while(n < ATR_MAX_SIZE && !IO_Serial_Read(reader, 0, ATR_TIMEOUT, 1, buf + n))
			{ n++; }
		if(n == 0)
			{ continue; }
		if(ATR_InitFromArray(atr, buf, n) != ERROR)
			{ ret = OK; }
		// Succesfully retrieve ATR
		if(ret == OK)
			{ break; }
	}

	return ret;
}

int32_t Phoenix_Close(struct s_reader *reader)
{
	rdr_log_dbg(reader, D_IFD, "Closing phoenix device %s", reader->device);
	if(reader_use_gpio(reader))
	{
		if(reader->gpio_outen > -1)
			{ close(reader->gpio_outen); }
		if(reader->gpio_out > -1)
			{ close(reader->gpio_out); }
		if(reader->gpio_in > -1)
			{ close(reader->gpio_in); }
	}
	IO_Serial_Close(reader);
	return OK;
}

/*
int32_t Phoenix_FastReset (struct s_reader * reader, int32_t delay)
{
    IO_Serial_Ioctl_Lock(reader, 1);
    if (reader_use_gpio(reader))
        set_gpio(reader, 0);
    else
        IO_Serial_RTS_Set(reader);

    cs_sleepms(delay);

    // set card reset hi (inactive)
    if (reader_use_gpio(reader))
        set_gpio_input(reader);
    else
        IO_Serial_RTS_Clr(reader);

    IO_Serial_Ioctl_Lock(reader, 0);

    cs_sleepms(50);

    IO_Serial_Flush(reader);
    return 0;

}
*/
static int32_t mouse_init(struct s_reader *reader)
{
	const struct s_cardreader *crdr_ops = reader->crdr;
	if (!crdr_ops) return ERROR;

	if(detect_db2com_reader(reader))
	{
		reader->crdr = crdr_ops = &cardreader_db2com;
		return crdr_ops->reader_init(reader);
	}

	reader->handle = open(reader->device,  O_RDWR | O_NOCTTY | O_NONBLOCK);
	if(reader->handle < 0)
	{
		rdr_log(reader, "ERROR: Opening device %s (errno=%d %s)",
				reader->device, errno, strerror(errno));
		return ERROR;
	}
	if(Phoenix_Init(reader))
	{
		rdr_log(reader, "ERROR: Phoenix_Init returns error");
		Phoenix_Close(reader);
		return ERROR;
	}
	return OK;
}

const struct s_cardreader cardreader_mouse =
{
	.desc          = "mouse",
	.typ           = R_MOUSE,
	.flush         = 1,
	.read_written  = 1,
	.need_inverse  = 1,
	.reader_init   = mouse_init,
	.get_status    = Phoenix_GetStatus,
	.activate      = Phoenix_Reset,
	.transmit      = IO_Serial_Transmit,
	.receive       = IO_Serial_Receive,
	.close         = Phoenix_Close,
	.set_parity    = IO_Serial_SetParity,
	.set_baudrate  = IO_Serial_SetBaudrate,
};

#endif
