/* Copyright 2020 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
/* CCD Measure SBU */

#include "adc.h"
#include "console.h"
#include "gpio.h"
#include "hooks.h"
#include "ioexpanders.h"
#include "timer.h"

#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ## args)

/*
 * Define voltage thresholds for SBU USB detection.
 *
 * Max observed USB low across sampled systems: 666mV
 * Min observed USB high across sampled systems: 3026mV
 */
#define GND_MAX_MV      700
#define USB_HIGH_MV     2500
#define SBU_DIRECT      0
#define SBU_FLIP        1

#define MODE_SBU_DISCONNECT     0
#define MODE_SBU_CONNECT        1
#define MODE_SBU_FLIP           2
#define MODE_SBU_OTHER          3

static void ccd_measure_sbu(void);
DECLARE_DEFERRED(ccd_measure_sbu);
static void ccd_measure_sbu(void)
{
	int sbu1;
	int sbu2;
	int mux_en;
	static int count /* = 0 */;
	static int last /* = 0 */;
	static int polarity /* = 0 */;

	/* Read sbu voltage levels */
	sbu1 = adc_read_channel(ADC_SBU1_DET);
	sbu2 = adc_read_channel(ADC_SBU2_DET);
	mux_en = gpio_get_level(GPIO_SBU_MUX_EN);

	/*
	 * While SBU_MUX is disabled (SuzyQ unplugged), we'll poll the SBU lines
	 * to check if an idling, unconfigured USB device is present.
	 * USB FS pulls one line high for connect request.
	 * If so, and it persists for 500ms, we'll enable the SuzyQ in that
	 * orientation.
	 */
	if ((!mux_en) && (sbu1 > USB_HIGH_MV) && (sbu2 < GND_MAX_MV)) {
		/* Check flip connection polarity. */
		if (last != MODE_SBU_FLIP) {
			last = MODE_SBU_FLIP;
			polarity = SBU_FLIP;
			count = 0;
		} else {
			count++;
		}
	} else if ((!mux_en) && (sbu2 > USB_HIGH_MV) && (sbu1 < GND_MAX_MV)) {
		/* Check direct connection polarity. */
		if (last != MODE_SBU_CONNECT) {
			last = MODE_SBU_CONNECT;
			polarity = SBU_DIRECT;
			count = 0;
		} else {
			count++;
		}
		/*
		 * If SuzyQ is enabled, we'll poll for a persistent no-signal
		 * for 500ms. Since USB is differential, we should never see
		 * GND/GND while the device is connected.
		 * If disconnected, electrically remove SuzyQ.
		 */
	} else if ((mux_en) && (sbu1 < GND_MAX_MV) && (sbu2 < GND_MAX_MV)) {
		/* Check for SBU disconnect if connected. */
		if (last != MODE_SBU_DISCONNECT) {
			last = MODE_SBU_DISCONNECT;
			count = 0;
		} else {
			count++;
		}
	} else {
		/* Didn't find anything, reset state. */
		last = MODE_SBU_OTHER;
		count = 0;
	}

	/*
	 * We have seen a new state continuously for 500ms.
	 * Let's update the mux to enable/disable SuzyQ appropriately.
	 */
	if (count > 5) {
		if (mux_en) {
			/* Disable mux as it's disconnected now. */
			gpio_set_level(GPIO_SBU_MUX_EN, 0);
			msleep(10);
			CPRINTS("CCD: disconnected.");
		} else {
			/* SBU flip = polarity */
			sbu_flip_sel(polarity);
			gpio_set_level(GPIO_SBU_MUX_EN, 1);
			msleep(10);
			CPRINTS("CCD: connected %s",
				polarity ? "flip" : "noflip");
		}
	}

	/* Measure every 100ms, forever. */
	hook_call_deferred(&ccd_measure_sbu_data, 100 * MSEC);
}

void ccd_enable(int enable)
{
	if (enable) {
		hook_call_deferred(&ccd_measure_sbu_data, 0);
	} else {
		gpio_set_level(GPIO_SBU_MUX_EN, 0);
		hook_call_deferred(&ccd_measure_sbu_data, -1);
	}
}

void start_ccd_meas_sbu_cycle(void)
{
	hook_call_deferred(&ccd_measure_sbu_data, 1000 * MSEC);
}
