/* Copyright 2019 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.
 */

/*
 * AOZ1380 USB-C Power Path Controller
 *
 * This is a basic TCPM controlled PPC driver.  It could easily be
 * renamed and repurposed to be generic, if there are other TCPM
 * controlled PPC chips that are similar to the AOZ1380
 */

#include "atomic.h"
#include "common.h"
#include "console.h"
#include "aoz1380.h"
#include "hooks.h"
#include "system.h"
#include "tcpm/tcpm.h"
#include "usb_pd.h"
#include "usb_pd_tcpc.h"
#include "usbc_ppc.h"

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

static uint32_t irq_pending; /* Bitmask of ports signaling an interrupt. */

#define AOZ1380_FLAGS_SOURCE_ENABLED    BIT(0)
#define AOZ1380_FLAGS_SINK_ENABLED      BIT(1)
#define AOZ1380_FLAGS_INT_ON_DISCONNECT BIT(2)
static uint32_t flags[CONFIG_USB_PD_PORT_MAX_COUNT];

#define AOZ1380_SET_FLAG(port, flag) atomic_or(&flags[port], (flag))
#define AOZ1380_CLR_FLAG(port, flag) atomic_clear_bits(&flags[port], (flag))

static int aoz1380_init(int port)
{
	flags[port] = 0;

	if (tcpm_get_snk_ctrl(port))
		AOZ1380_SET_FLAG(port, AOZ1380_FLAGS_SINK_ENABLED);

	if (tcpm_get_src_ctrl(port))
		AOZ1380_SET_FLAG(port, AOZ1380_FLAGS_SOURCE_ENABLED);

	return EC_SUCCESS;
}

static int aoz1380_vbus_sink_enable(int port, int enable)
{
	int rv;

	rv = tcpm_set_snk_ctrl(port, enable);
	if (rv)
		return rv;

	/*
	 * On enable, we want to indicate connection as a SINK.
	 * On disable, clear SINK and that we have interrupted.
	 */
	if (enable)
		AOZ1380_SET_FLAG(port, AOZ1380_FLAGS_SINK_ENABLED);
	else
		AOZ1380_CLR_FLAG(port, (AOZ1380_FLAGS_SINK_ENABLED |
					AOZ1380_FLAGS_INT_ON_DISCONNECT));

	return EC_SUCCESS;
}

static int aoz1380_vbus_source_enable(int port, int enable)
{
	int rv;

	rv = tcpm_set_src_ctrl(port, enable);
	if (rv)
		return rv;

	/*
	 * On enable, we want to indicate connection as a SOURCE.
	 * On disable, clear SOURCE and that we have interrupted.
	 */
	if (enable)
		AOZ1380_SET_FLAG(port, AOZ1380_FLAGS_SOURCE_ENABLED);
	else
		AOZ1380_CLR_FLAG(port, (AOZ1380_FLAGS_SOURCE_ENABLED |
					AOZ1380_FLAGS_INT_ON_DISCONNECT));

	return EC_SUCCESS;
}

static int aoz1380_is_sourcing_vbus(int port)
{
	return flags[port] & AOZ1380_FLAGS_SOURCE_ENABLED;
}

static int aoz1380_set_vbus_source_current_limit(int port,
						 enum tcpc_rp_value rp)
{
	return board_aoz1380_set_vbus_source_current_limit(port, rp);
}

/*
 * AOZ1380 Interrupt Handler
 *
 * This device only has a single over current/temperature interrupt.
 * TODO(b/141939343) Determine how to clear the interrupt
 * TODO(b/142076004) Test this to verify we shut off vbus current
 * TODO(b/147359722) Verify correct fault functionality
 */
static void aoz1380_handle_interrupt(int port)
{
	/*
	 * We can get a false positive on disconnect that we
	 * had an over current/temperature event when we are no
	 * longer connected as sink or source.  Ignore it if
	 * that is the case.
	 */
	if (flags[port] != 0) {
		/*
		 * This is a over current/temperature condition
		 */
		ppc_prints("Vbus overcurrent/temperature", port);
		pd_handle_overcurrent(port);
	} else {
		/*
		 * Just in case there is a condition that we will
		 * continue an interrupt storm, track that we have
		 * already been here once and will take the other
		 * path if we do this again before setting the
		 * sink/source as enabled or disabled again.
		 */
		AOZ1380_SET_FLAG(port, AOZ1380_FLAGS_INT_ON_DISCONNECT);
	}
}

static void aoz1380_irq_deferred(void)
{
	int i;
	uint32_t pending = atomic_clear(&irq_pending);

	for (i = 0; i < board_get_usb_pd_port_count(); i++)
		if (BIT(i) & pending)
			aoz1380_handle_interrupt(i);
}
DECLARE_DEFERRED(aoz1380_irq_deferred);

void aoz1380_interrupt(int port)
{
	atomic_or(&irq_pending, BIT(port));
	hook_call_deferred(&aoz1380_irq_deferred_data, 0);
}

const struct ppc_drv aoz1380_drv = {
	.init = &aoz1380_init,
	.is_sourcing_vbus = &aoz1380_is_sourcing_vbus,
	.vbus_sink_enable = &aoz1380_vbus_sink_enable,
	.vbus_source_enable = &aoz1380_vbus_source_enable,
	.set_vbus_source_current_limit =
		&aoz1380_set_vbus_source_current_limit,
};
