/*
 * board selector support for TCA9535 used in Bitmine's CoinCraft Desk
 *
 * Copyright 2014 Zefir Kurtisi <zefir.kurtisi@gmail.com>
 *
 * 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 version 3 of the License, or (at your option)
 * any later version.  See COPYING for more details.
 */


#include "miner.h"

#include "A1-board-selector.h"
#include "i2c-context.h"


static struct board_selector ccr_selector;

static struct i2c_ctx *U1_tca9548;
static struct i2c_ctx *U3_tca9535;
static struct i2c_ctx *U4_tca9535;
static uint8_t active_chain;
static pthread_mutex_t lock;

struct chain_mapping {
	uint8_t chain_id;
	uint8_t U1;
	uint8_t U3p0;
	uint8_t U3p1;
};

static const struct chain_mapping chain_mapping[CCR_MAX_CHAINS] = {
	{  0, 0x01, 0x01, 0x00, },
	{  1, 0x01, 0x00, 0x80, },
	{  2, 0x02, 0x02, 0x00, },
	{  3, 0x02, 0x00, 0x40, },
	{  4, 0x04, 0x04, 0x00, },
	{  5, 0x04, 0x00, 0x20, },
	{  6, 0x08, 0x08, 0x00, },
	{  7, 0x08, 0x00, 0x10, },
	{  8, 0x10, 0x10, 0x00, },
	{  9, 0x10, 0x00, 0x08, },
	{ 10, 0x20, 0x20, 0x00, },
	{ 11, 0x20, 0x00, 0x04, },
	{ 12, 0x40, 0x40, 0x00, },
	{ 13, 0x40, 0x00, 0x02, },
	{ 14, 0x80, 0x80, 0x00, },
	{ 15, 0x80, 0x00, 0x01, },
};

static void ccr_unlock(void)
{
	mutex_unlock(&lock);
}

static void ccr_exit(void)
{
	if (U1_tca9548 != NULL)
		U1_tca9548->exit(U1_tca9548);
	if (U3_tca9535 != NULL)
		U3_tca9535->exit(U3_tca9535);
	if (U4_tca9535 != NULL)
		U4_tca9535->exit(U4_tca9535);
}


extern struct board_selector *ccr_board_selector_init(void)
{
	mutex_init(&lock);
	applog(LOG_INFO, "ccr_board_selector_init()");

	/* detect all i2c slaves */
	U1_tca9548 = i2c_slave_open(I2C_BUS, 0x70);
	U3_tca9535 = i2c_slave_open(I2C_BUS, 0x23);
	U4_tca9535 = i2c_slave_open(I2C_BUS, 0x22);
	if (U1_tca9548 == NULL || U3_tca9535 == NULL || U4_tca9535 == NULL)
		goto fail;

			/* init I2C multiplexer */
	bool res =	U1_tca9548->write(U1_tca9548, 0x00, 0x00) &&
			/* init reset selector */
			U3_tca9535->write(U3_tca9535, 0x06, 0x00) &&
			U3_tca9535->write(U3_tca9535, 0x07, 0x00) &&
			U3_tca9535->write(U3_tca9535, 0x02, 0x00) &&
			U3_tca9535->write(U3_tca9535, 0x03, 0x00) &&
			/* init chain selector */
			U4_tca9535->write(U4_tca9535, 0x06, 0x00) &&
			U4_tca9535->write(U4_tca9535, 0x07, 0x00) &&
			U4_tca9535->write(U4_tca9535, 0x02, 0x00) &&
			U4_tca9535->write(U4_tca9535, 0x03, 0x00);

	if (!res)
		goto fail;

	return &ccr_selector;

fail:
	ccr_exit();
	return NULL;
}

static bool ccr_select(uint8_t chain)
{
	if (chain >= CCR_MAX_CHAINS)
		return false;

	mutex_lock(&lock);
	if (active_chain == chain)
		return true;

	active_chain = chain;
	const struct chain_mapping *cm = &chain_mapping[chain];

	if (!U1_tca9548->write(U1_tca9548, cm->U1, cm->U1))
		return false;

	if (!U4_tca9535->write(U4_tca9535, 0x02, cm->U3p0) ||
	    !U4_tca9535->write(U4_tca9535, 0x03, cm->U3p1))
		return false;

	/* sanity check: ensure i2c command has been written before we leave */
	uint8_t tmp;
	if (!U4_tca9535->read(U4_tca9535, 0x02, &tmp) || tmp != cm->U3p0) {
		applog(LOG_ERR, "ccr_select: wrote 0x%02x, read 0x%02x",
		       cm->U3p0, tmp);
	}
	applog(LOG_DEBUG, "selected chain %d", chain);
	return true;
}

static bool __ccr_board_selector_reset(uint8_t p0, uint8_t p1)
{
	if (!U3_tca9535->write(U3_tca9535, 0x02, p0) ||
	    !U3_tca9535->write(U3_tca9535, 0x03, p1))
		return false;
	cgsleep_ms(RESET_LOW_TIME_MS);
	if (!U3_tca9535->write(U3_tca9535, 0x02, 0x00) ||
	    !U3_tca9535->write(U3_tca9535, 0x03, 0x00))
		return false;
	cgsleep_ms(RESET_HI_TIME_MS);
	return true;
}
// we assume we are already holding the mutex
static bool ccr_reset(void)
{
	const struct chain_mapping *cm = &chain_mapping[active_chain];
	applog(LOG_DEBUG, "resetting chain %d", cm->chain_id);
	bool retval = __ccr_board_selector_reset(cm->U3p0, cm->U3p1);
	return retval;
}

static bool ccr_reset_all(void)
{
	mutex_lock(&lock);
	bool retval = __ccr_board_selector_reset(0xff, 0xff);
	mutex_unlock(&lock);
	return retval;
}

static uint8_t ccr_get_temp(uint8_t sensor_id)
{
	if ((active_chain & 1) != 0 || sensor_id != 0)
		return 0;

	struct i2c_ctx *U7 = i2c_slave_open(I2C_BUS, 0x4c);
	if (U7 == NULL)
		return 0;

	uint8_t retval = 0;
	if (!U7->read(U7, 0, &retval))
		retval = 0;
	U7->exit(U7);
	return retval;
}

static struct board_selector ccr_selector = {
	.select = ccr_select,
	.release = ccr_unlock,
	.exit = ccr_exit,
	.reset = ccr_reset,
	.reset_all = ccr_reset_all,
	.get_temp = ccr_get_temp,
};