/*
 * Copyright (C) 2016 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

#define INCLUDE
#include "arch_i2c_slave.c"
#undef INCLUDE

#include "glue.h"

#include "chip_st_lis302dl.h"

#define CHIP_(x) chip_st_lis302dl_ ## x

struct cpssp {
	unsigned int state_power;
	unsigned int device_addr;

	struct sig_std_logic *port_sda;

	int is_address_phase;
	uint8_t counter;

#define STATE

#define NAME		i2c_slave
#define NAME_(x)	i2c_slave_ ## x
#include "arch_i2c_slave.c"
#undef NAME_
#undef NAME

#undef STATE
};

static uint8_t
CHIP_(reg_read)(struct cpssp *cpssp, uint8_t addr)
{
	return 0; /* FIXME */
}

static void
CHIP_(reg_write)(struct cpssp *cpssp, uint8_t addr, uint8_t val)
{
	/* FIXME */
}

static int
CHIP_(ack_addr)(struct cpssp *cpssp, unsigned char addr)
{
	if ((addr & 0xfe) == (0x38 | (cpssp->device_addr << 1))) {
		/* Selected */
		return 1;
	}

	return 0;
}

static void
CHIP_(read_byte)(struct cpssp *cpssp, unsigned char *valp)
{
	if (cpssp->is_address_phase) {
		*valp = cpssp->counter;
		cpssp->is_address_phase = 0;

	} else {
		assert(cpssp->counter < 256);

		*valp = CHIP_(reg_read)(cpssp, cpssp->counter);

		cpssp->counter = (cpssp->counter + 1) & (256 - 1);
		assert(cpssp->counter < 256);
	}
}

static int
CHIP_(write_byte)(struct cpssp *cpssp, unsigned char val)
{
	if (cpssp->is_address_phase) {
		cpssp->counter = val;
		assert(cpssp->counter < 256);
		cpssp->is_address_phase = 0;

	} else {
		assert(cpssp->counter < 256);

		CHIP_(reg_write)(cpssp, cpssp->counter, val);

		cpssp->counter = (cpssp->counter + 1) & (256 - 1);
		assert(cpssp->counter < 256);
	}

	return 0;
}

static void
CHIP_(stop_transaction)(struct cpssp *cpssp)
{
	cpssp->is_address_phase = 1;
}

static int
i2c_slave_ack_addr(struct cpssp *cpssp, uint8_t val)
{
	return CHIP_(ack_addr)(cpssp, val);
}

static void
i2c_slave_read_byte(struct cpssp *cpssp, uint8_t *valp)
{
	CHIP_(read_byte)(cpssp, valp);
}

static void
i2c_slave_write_byte(struct cpssp *cpssp, uint8_t val)
{
	CHIP_(write_byte)(cpssp, val);
}

static void
i2c_slave_stop_transaction(struct cpssp *cpssp)
{
	CHIP_(stop_transaction)(cpssp);
}

static void
i2c_slave_sda_out(struct cpssp *cpssp, unsigned int val)
{
	switch (val) {
	case SIG_STD_LOGIC_Z: val = 1; break;
	case SIG_STD_LOGIC_1: val = 1; break;
	case SIG_STD_LOGIC_0: val = 0; break;
	default: assert(0);
	}

	sig_std_logic_set(cpssp->port_sda, cpssp, val);
}

#define BEHAVIOR

#define NAME		i2c_slave
#define NAME_(x)	i2c_slave_ ## x
#include "arch_i2c_slave.c"
#undef NAME_
#undef NAME

#undef BEHAVIOR

static void
CHIP_(scl_in_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = _cpssp;

	i2c_slave_scl_in(cpssp, val);
}

static void
CHIP_(sda_in_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = _cpssp;

	i2c_slave_sda_in(cpssp, val);
}

static void
CHIP_(sdo_in_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = _cpssp;

	cpssp->device_addr = val;
}

static void
CHIP_(vdd_in_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = _cpssp;

	cpssp->state_power = val;
	if (val) {
		/* Power-on reset. */
		cpssp->is_address_phase = 1;
	}
}

void *
CHIP_(create)(
	const char *name,
	struct sig_manage *manage,
	struct sig_std_logic *port_gnd0,
	struct sig_std_logic *port_gnd1,
	struct sig_std_logic *port_gnd2,
	struct sig_std_logic *port_gnd3,
	struct sig_std_logic *port_R1,
	struct sig_std_logic *port_vdd_io,
	struct sig_std_logic *port_vdd,
	struct sig_std_logic *port_R0,
	struct sig_std_logic *port_sda,
	struct sig_std_logic *port_scl,
	struct sig_std_logic *port_int2,
	struct sig_std_logic *port_int1,
	struct sig_std_logic *port_cs,
	struct sig_std_logic *port_sdo
)
{
	static const struct sig_std_logic_funcs vdd_funcs = {
		.boolean_or_set = CHIP_(vdd_in_set),
	};
	static const struct sig_std_logic_funcs scl_funcs = {
		.std_logic_set = CHIP_(scl_in_set),
	};
	static const struct sig_std_logic_funcs sda_funcs = {
		.std_logic_set = CHIP_(sda_in_set),
	};
	static const struct sig_std_logic_funcs sdo_funcs = {
		.boolean_or_set = CHIP_(sdo_in_set),
	};
	struct cpssp *cpssp;

	cpssp = malloc(sizeof(*cpssp));
	assert(cpssp);

	cpssp->device_addr = 0;

	/* FIXME */
	i2c_slave_create(cpssp);

	cpssp->state_power = 0;
	sig_std_logic_connect_in(port_vdd, cpssp, &vdd_funcs);
	sig_std_logic_connect_in(port_scl, cpssp, &scl_funcs);
	sig_std_logic_connect_in(port_sda, cpssp, &sda_funcs);
	sig_std_logic_connect_in(port_sdo, cpssp, &sdo_funcs);

	return cpssp;
}

void
CHIP_(destroy)(void *_cpssp)
{
	struct cpssp *cpssp = _cpssp;

	/* FIXME */
	i2c_slave_destroy(cpssp);

	free(cpssp);
}

void
CHIP_(suspend)(void *_cpssp, FILE *fp)
{
	struct cpssp *cpssp = _cpssp;

	generic_suspend(cpssp, sizeof(*cpssp), fp);
}

void
CHIP_(resume)(void *_cpssp, FILE *fp)
{
	struct cpssp *cpssp = _cpssp;

	generic_resume(cpssp, sizeof(*cpssp), fp);
}
