/*
 * Copyright (C) 2010 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 "config.h"
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

#include "system.h"
#include "conv_gen.h"
#include "glue-storage.h"
#include "glue-shm.h"
#include "glue-suspend.h"
#include "umutil.h"

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

#include "chip_at_24c164.h"

#define COMP_(x) chip_at_24c164_ ## x

struct cpssp {
	unsigned int state_power;
	struct sig_i2c_bus *port_ser;

	struct media *media;

	uint8_t device_addr;
	unsigned int counter;
	bool is_address_phase;

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

static bool
COMP_(ack_addr)(struct cpssp *cpssp, unsigned char addr)
{
	if (((addr >> 4) & 0x7) == cpssp->device_addr) {
		/* Selected */
		cpssp->counter &= ~0xff00;
		cpssp->counter |= ((addr >> 1) & 0x7) << 8;
		assert(cpssp->counter < 2048);

		return true;
	}

	return false;
}

static void
COMP_(read_byte)(struct cpssp *cpssp, unsigned char *valp)
{
	int ret;

	if (cpssp->is_address_phase) {
		*valp = cpssp->counter & 0xff;
		cpssp->is_address_phase = false;

	} else {
		assert(cpssp->counter < 2048);
		ret = storage_read(cpssp->media, valp, 1, cpssp->counter);
		assert(ret == 1);
		cpssp->counter = (cpssp->counter + 1) & (2048 - 1);
		assert(cpssp->counter < 2048);
	}
}

static bool
COMP_(write_byte)(struct cpssp *cpssp, unsigned char val)
{
	int ret;

	if (cpssp->is_address_phase) {
		cpssp->counter &= ~0x00ff;
		cpssp->counter |= val << 0;
		assert(cpssp->counter < 2048);
		cpssp->is_address_phase = false;

	} else {
		ret = storage_write(cpssp->media, &val, 1, cpssp->counter);
		assert(ret == 1);
		cpssp->counter
			= (cpssp->counter & 0xfff0)
			| ((cpssp->counter + 1) & 0x000f);
		assert(cpssp->counter < 2048);
	}
	return true;
}

static void
COMP_(stop_transaction)(void *_cpssp)
{
	struct cpssp *cpssp = _cpssp;

	cpssp->is_address_phase = true;
}

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

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

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

static void
i2c_slave_stop_transaction(struct cpssp *cpssp)
{
	COMP_(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_i2c_bus_set_data(cpssp->port_ser, 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
COMP_(data_event)(void *_cpssp, bool val)
{
	struct cpssp *cpssp = _cpssp;

	i2c_slave_sda_in(cpssp, val ? SIG_STD_LOGIC_1 : SIG_STD_LOGIC_0);
}

static void
COMP_(clk_event)(void *_cpssp, bool val)
{
	struct cpssp *cpssp = _cpssp;

	i2c_slave_scl_in(cpssp, val ? SIG_STD_LOGIC_1 : SIG_STD_LOGIC_0);
}

static void
COMP_(a0_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = _cpssp;

	cpssp->device_addr &= ~(1 << 0);
	cpssp->device_addr |= val << 0;
}

static void
COMP_(a1_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = _cpssp;

	cpssp->device_addr &= ~(1 << 1);
	cpssp->device_addr |= val << 1;
}

static void
COMP_(a2_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = _cpssp;

	cpssp->device_addr &= ~(1 << 2);
	cpssp->device_addr |= val << 2;
}

static void
COMP_(power_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = _cpssp;

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

void *
COMP_(create)(
	const char *name,
	const char *content,
	struct sig_manage *manage,
	struct sig_std_logic *port_power,
	struct sig_i2c_bus *port_ser,
	struct sig_std_logic *port_a0,
	struct sig_std_logic *port_a1,
	struct sig_std_logic *port_a2
)
{
	static const struct sig_std_logic_funcs power_funcs = {
		.boolean_or_set = COMP_(power_set),
	};
	static const struct sig_i2c_bus_funcs ser_funcs = {
		.data_event = COMP_(data_event),
		.clk_event = COMP_(clk_event),
	};
	static const struct sig_std_logic_funcs a0_funcs = {
		.boolean_or_set = COMP_(a0_set),
	};
	static const struct sig_std_logic_funcs a1_funcs = {
		.boolean_or_set = COMP_(a1_set),
	};
	static const struct sig_std_logic_funcs a2_funcs = {
		.boolean_or_set = COMP_(a2_set),
	};
	struct cpssp *cpssp;

	system_name_push(name);

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

	cpssp->device_addr = 0;

	cpssp->media = storage_create(system_path(), 2048,
			buildpath(ROMDIR, content),
			conv_gen_open, conv_gen_close, conv_gen_read);
	assert(cpssp->media);

	i2c_slave_create(cpssp);

	/* Call */
	cpssp->port_ser = port_ser;
	sig_i2c_bus_connect_raw(port_ser, cpssp, &ser_funcs);

	/* Out */

	/* In */
	cpssp->state_power = 0;
	sig_std_logic_connect_in(port_power, cpssp, &power_funcs);

	sig_std_logic_connect_in(port_a0, cpssp, &a0_funcs);
	sig_std_logic_connect_in(port_a1, cpssp, &a1_funcs);
	sig_std_logic_connect_in(port_a2, cpssp, &a2_funcs);

	system_name_pop();

	return cpssp;
}

void
COMP_(destroy)(void *_cpssp)
{
	struct cpssp *cpssp = _cpssp;
	int ret;

	i2c_slave_destroy(cpssp);

	ret = storage_destroy(cpssp->media);

	shm_free(cpssp);
}

void
COMP_(suspend)(void *_cpssp, FILE *fp)
{
	struct cpssp *cpssp = _cpssp;
	
	generic_suspend(cpssp, sizeof(*cpssp), fp);
}

void
COMP_(resume)(void *_cpssp, FILE *fp)
{
	struct cpssp *cpssp = _cpssp;
	
	struct media *savemedia = cpssp->media;
	
	generic_resume(cpssp, sizeof(*cpssp), fp);
	
	cpssp->media = savemedia;
}
