/**
 * usb3803.c
 *
 * Low level control of the USB3803 USB hub over I2C.
 * 
 * Copyright (c) 2022 Bigscreen, Inc.
 */

#include <asf.h>
#include "stdbool.h"
#include "i2c.h"
#include "usb3803.h"

bool hub1_is_initialized = false;
bool hub2_is_initialized = false;

static uint8_t set_usb3803_ilock(I2C_Handle* hi2c, uint8_t hub_i2c_addr) {
	I2C_Transaction_Request i2ctxn;
	uint8_t temp_byte;
	uint8_t retval = pdPASS;
	
	i2ctxn.dev_addr = hub_i2c_addr;
	i2ctxn.cb = NULL;
	i2ctxn.internal_addr = USB3803_SP_ILOCK;
	i2ctxn.data = &temp_byte;
	i2ctxn.len = 1;
	i2ctxn.shift = I2C_Unshifted;
	i2ctxn.ttype = I2C_INT_ADDR_READ;
	
	// read...
	retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME);
	if(retval != pdPASS) {return retval;}
	// modify...
	temp_byte |= USB3803_ILOCK_CONFIG;
	// and write.
	i2ctxn.ttype = I2C_INT_ADDR_WRITE;
	return i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME);
}

static uint8_t set_usb3803_register_settings(I2C_Handle* hi2c, uint8_t hub_i2c_addr) {
    I2C_Transaction_Request i2ctxn;
    uint8_t temp_byte;
    uint8_t retval = pdPASS;
	
	i2ctxn.dev_addr = hub_i2c_addr;
	i2ctxn.cb = NULL;
	i2ctxn.data = &temp_byte;
	i2ctxn.len = 1;
	i2ctxn.shift = I2C_Unshifted;
	
	// Set the power mode. Self-powered, 0mA draw from bus
	i2ctxn.internal_addr = USB3803_CFG1;
	i2ctxn.ttype = I2C_INT_ADDR_READ;
	retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME);
	temp_byte |= USB3803_CFG1_SELFBUS; // Self-powered
	i2ctxn.ttype = I2C_INT_ADDR_WRITE;
	if(retval == pdPASS) {
		retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME);
	}
	// Set max power to zero everywhere
	temp_byte = 0;
	i2ctxn.internal_addr = USB3803_MAXPS;
	if(retval == pdPASS) {
		retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME);
	}
	i2ctxn.internal_addr = USB3803_MAXPB;
	if(retval == pdPASS) {
		retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME);
	}
	i2ctxn.internal_addr = USB3803_HCMCS;
	if(retval == pdPASS) {
		retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME);
	}
	i2ctxn.internal_addr = USB3803_HCMCB;
	if(retval == pdPASS) {
		retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME);
	}

#if 0
	// Increase the upstream signaling current
	i2ctxn.internal_addr = USB3803_BOOST_UP3;
	temp_byte = USB3803_BOOST_15_PCT << 4;
	if(retval == pdPASS) {
		retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME);
	}
#endif
	
	return retval;
}

static uint8_t clear_usb3803_ilock(I2C_Handle* hi2c, uint8_t hub_i2c_addr) {
	I2C_Transaction_Request i2ctxn;
	uint8_t temp_byte;
	uint8_t retval = pdPASS;
	
	i2ctxn.dev_addr = hub_i2c_addr;
	i2ctxn.cb = NULL;
	i2ctxn.internal_addr = USB3803_SP_ILOCK;
	i2ctxn.data = &temp_byte;
	i2ctxn.len = 1;
	i2ctxn.shift = I2C_Unshifted;
	i2ctxn.ttype = I2C_INT_ADDR_READ;
	
	// read...
	retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME);
	if(retval != pdPASS) {return retval;}
	// modify...
	temp_byte &= (~USB3803_ILOCK_CONFIG);
	// and write.
	i2ctxn.ttype = I2C_INT_ADDR_WRITE;
	return i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME);
}

/* Performs initialization of the USB3803 hubs
 * returns pdFAIL if something went wrong (error in I2C)
 * or pdPASS if all is well.
 * This function only requires the I2C_Handle of the I2C
 * bus connected to the hub as an argument.
 */
uint8_t usb3803_init(I2C_Handle* hi2c) {
    uint8_t retval_hub1 = pdPASS;
	uint8_t retval_hub2 = pdPASS;

    ioport_set_pin_level(PIN_USBHUB_RESET, true); // RESET_N released, hub starts up
    
	// Now we have to wait at max 4ms for the hub to respond to I2C commands
	// but if we wait longer than 400ms, it will go straight to connection
	// and we will have missed our window to make changes to the USB descriptors
	// That's a generous window of time, but let's be sure to observe the
	// minimum time as well
	
	vTaskDelay(10); // 10ms is safely past the startup time and well before the end of connection delay
	
	i2c_lock(hi2c);
	// Set the hub's ILOCK.config bit to pause startup
	// Read-modify-write to prevent changing any other settings	
	retval_hub1 = set_usb3803_ilock(hi2c, USB3803_I2C_ADDR);
	// Do the same for the other hub
	retval_hub2 = set_usb3803_ilock(hi2c, USB3803_2ND_I2C_ADDR);
	
	if(pdPASS == retval_hub1) {
		retval_hub1 = set_usb3803_register_settings(hi2c, USB3803_I2C_ADDR);
	}
	
	if(pdPASS == retval_hub2) {
		retval_hub2 = set_usb3803_register_settings(hi2c, USB3803_2ND_I2C_ADDR);
	}
	
	if(pdPASS == retval_hub1) {
		retval_hub1 = clear_usb3803_ilock(hi2c, USB3803_I2C_ADDR);
	}
	
	if(pdPASS == retval_hub2) {
		retval_hub2 = clear_usb3803_ilock(hi2c, USB3803_2ND_I2C_ADDR);
	}	
	
	i2c_unlock(hi2c);
	
	if(pdPASS == retval_hub1) {
		hub1_is_initialized = true;
	}
	
	if(pdPASS == retval_hub2) {
		hub2_is_initialized = true;
	}
	
    return retval_hub1;
}

bool usb3803_is_initialized(HUB_Select_T hubsel) {
	if(hubsel == USB_HUB_1)
	{
		return hub1_is_initialized;
	} else {
		return hub2_is_initialized;
	}
}