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

#include "atomic.h"
#include "chipset.h"
#include "common.h"
#include "device_event.h"
#include "hooks.h"
#include "host_command.h"
#include "mkbp_event.h"
#include "peripheral_charger.h"
#include "queue.h"
#include "stdbool.h"
#include "task.h"
#include "timer.h"
#include "util.h"

/* Peripheral Charge Manager */

#define CPRINTS(fmt, args...) cprints(CC_PCHG, "PCHG: " fmt, ##args)

/* Currently only used for FW update. */
static uint32_t pchg_host_events;

static void pchg_queue_event(struct pchg *ctx, enum pchg_event event)
{
	mutex_lock(&ctx->mtx);
	if (queue_add_unit(&ctx->events, &event) == 0) {
		ctx->dropped_event_count++;
		CPRINTS("ERR: Queue is full");
	}
	mutex_unlock(&ctx->mtx);
}

static void _send_host_event(const struct pchg *ctx, uint32_t event)
{
	int port = PCHG_CTX_TO_PORT(ctx);

	atomic_or(&pchg_host_events, event | port << EC_MKBP_PCHG_PORT_SHIFT);
	mkbp_send_event(EC_MKBP_EVENT_PCHG);
}

static const char *_text_state(enum pchg_state state)
{
	/* TODO: Use "S%d" for normal build. */
	static const char * const state_names[] = EC_PCHG_STATE_TEXT;
	BUILD_ASSERT(ARRAY_SIZE(state_names) == PCHG_STATE_COUNT);

	if (state >= sizeof(state_names))
		return "UNDEF";

	return state_names[state];
}

static const char *_text_event(enum pchg_event event)
{
	/* TODO: Use "S%d" for normal build. */
	static const char * const event_names[] = {
		[PCHG_EVENT_NONE] = "NONE",
		[PCHG_EVENT_IRQ] = "IRQ",
		[PCHG_EVENT_RESET] = "RESET",
		[PCHG_EVENT_INITIALIZED] = "INITIALIZED",
		[PCHG_EVENT_ENABLED] = "ENABLED",
		[PCHG_EVENT_DISABLED] = "DISABLED",
		[PCHG_EVENT_DEVICE_DETECTED] = "DEVICE_DETECTED",
		[PCHG_EVENT_DEVICE_CONNECTED] = "DEVICE_CONNECTED",
		[PCHG_EVENT_DEVICE_LOST] = "DEVICE_LOST",
		[PCHG_EVENT_CHARGE_STARTED] = "CHARGE_STARTED",
		[PCHG_EVENT_CHARGE_UPDATE] = "CHARGE_UPDATE",
		[PCHG_EVENT_CHARGE_ENDED] = "CHARGE_ENDED",
		[PCHG_EVENT_CHARGE_STOPPED] = "CHARGE_STOPPED",
		[PCHG_EVENT_UPDATE_OPENED] = "UPDATE_OPENED",
		[PCHG_EVENT_UPDATE_CLOSED] = "UPDATE_CLOSED",
		[PCHG_EVENT_UPDATE_WRITTEN] = "UPDATE_WRITTEN",
		[PCHG_EVENT_IN_NORMAL] = "IN_NORMAL",
		[PCHG_EVENT_CHARGE_ERROR] = "CHARGE_ERROR",
		[PCHG_EVENT_UPDATE_ERROR] = "UPDATE_ERROR",
		[PCHG_EVENT_OTHER_ERROR] = "OTHER_ERROR",
		[PCHG_EVENT_ENABLE] = "ENABLE",
		[PCHG_EVENT_DISABLE] = "DISABLE",
		[PCHG_EVENT_UPDATE_OPEN] = "UPDATE_OPEN",
		[PCHG_EVENT_UPDATE_WRITE] = "UPDATE_WRITE",
		[PCHG_EVENT_UPDATE_CLOSE] = "UPDATE_CLOSE",
	};
	BUILD_ASSERT(ARRAY_SIZE(event_names) == PCHG_EVENT_COUNT);

	if (event >= sizeof(event_names))
		return "UNDEF";

	return event_names[event];
}

static void _clear_port(struct pchg *ctx)
{
	mutex_lock(&ctx->mtx);
	queue_init(&ctx->events);
	mutex_unlock(&ctx->mtx);
	atomic_clear(&ctx->irq);
	ctx->battery_percent = 0;
	ctx->error = 0;
	ctx->update.data_ready = 0;
}

static enum pchg_state pchg_reset(struct pchg *ctx)
{
	enum pchg_state state = PCHG_STATE_RESET;
	int rv;

	/*
	 * In case we get asynchronous reset, clear port though it's redundant
	 * for a synchronous reset.
	 */
	_clear_port(ctx);

	if (ctx->mode == PCHG_MODE_NORMAL) {
		rv = ctx->cfg->drv->init(ctx);
		if (rv == EC_SUCCESS) {
			state = PCHG_STATE_INITIALIZED;
			pchg_queue_event(ctx, PCHG_EVENT_ENABLE);
		} else if (rv != EC_SUCCESS_IN_PROGRESS) {
			CPRINTS("ERR: Failed to reset to normal mode");
		}
	} else {
		state = PCHG_STATE_DOWNLOAD;
		pchg_queue_event(ctx, PCHG_EVENT_UPDATE_OPEN);
	}

	return state;
}

static void pchg_state_reset(struct pchg *ctx)
{
	switch (ctx->event) {
	case PCHG_EVENT_RESET:
		ctx->state = pchg_reset(ctx);
		break;
	case PCHG_EVENT_IN_NORMAL:
		ctx->state = PCHG_STATE_INITIALIZED;
		pchg_queue_event(ctx, PCHG_EVENT_ENABLE);
		break;
	default:
		break;
	}
}

static void pchg_state_initialized(struct pchg *ctx)
{
	int rv;

	if (ctx->event == PCHG_EVENT_ENABLE)
		ctx->error &= ~PCHG_ERROR_HOST;

	/* Spin in INITIALIZED until error condition is cleared. */
	if (ctx->error)
		return;

	switch (ctx->event) {
	case PCHG_EVENT_RESET:
		ctx->state = pchg_reset(ctx);
		break;
	case PCHG_EVENT_ENABLE:
		rv = ctx->cfg->drv->enable(ctx, true);
		if (rv == EC_SUCCESS)
			ctx->state = PCHG_STATE_ENABLED;
		else if (rv != EC_SUCCESS_IN_PROGRESS)
			CPRINTS("ERR: Failed to enable");
		break;
	case PCHG_EVENT_ENABLED:
		ctx->state = PCHG_STATE_ENABLED;
		break;
	default:
		break;
	}
}

static void pchg_state_enabled(struct pchg *ctx)
{
	int rv;

	switch (ctx->event) {
	case PCHG_EVENT_RESET:
		ctx->state = pchg_reset(ctx);
		break;
	case PCHG_EVENT_DISABLE:
		ctx->error |= PCHG_ERROR_HOST;
		rv = ctx->cfg->drv->enable(ctx, false);
		if (rv == EC_SUCCESS)
			ctx->state = PCHG_STATE_INITIALIZED;
		else if (rv != EC_SUCCESS_IN_PROGRESS)
			CPRINTS("ERR: Failed to disable");
		break;
	case PCHG_EVENT_DISABLED:
		ctx->state = PCHG_STATE_INITIALIZED;
		break;
	case PCHG_EVENT_DEVICE_DETECTED:
		ctx->state = PCHG_STATE_DETECTED;
		break;
	case PCHG_EVENT_DEVICE_CONNECTED:
		/*
		 * Proactively query SOC in case charging info won't be sent
		 * because device is already charged.
		 */
		ctx->cfg->drv->get_soc(ctx);
		ctx->state = PCHG_STATE_CONNECTED;
		break;
	default:
		break;
	}
}

static void pchg_state_detected(struct pchg *ctx)
{
	int rv;

	switch (ctx->event) {
	case PCHG_EVENT_RESET:
		ctx->state = pchg_reset(ctx);
		break;
	case PCHG_EVENT_DISABLE:
		ctx->error |= PCHG_ERROR_HOST;
		rv = ctx->cfg->drv->enable(ctx, false);
		if (rv == EC_SUCCESS)
			ctx->state = PCHG_STATE_INITIALIZED;
		else if (rv != EC_SUCCESS_IN_PROGRESS)
			CPRINTS("ERR: Failed to disable");
		break;
	case PCHG_EVENT_DISABLED:
		ctx->state = PCHG_STATE_INITIALIZED;
		break;
	case PCHG_EVENT_DEVICE_CONNECTED:
		/*
		 * Proactively query SOC in case charging info won't be sent
		 * because device is already charged.
		 */
		ctx->cfg->drv->get_soc(ctx);
		ctx->state = PCHG_STATE_CONNECTED;
		break;
	case PCHG_EVENT_DEVICE_LOST:
		ctx->battery_percent = 0;
		ctx->state = PCHG_STATE_ENABLED;
		break;
	default:
		break;
	}
}

static void pchg_state_connected(struct pchg *ctx)
{
	int rv;

	switch (ctx->event) {
	case PCHG_EVENT_RESET:
		ctx->state = pchg_reset(ctx);
		break;
	case PCHG_EVENT_DISABLE:
		ctx->error |= PCHG_ERROR_HOST;
		rv = ctx->cfg->drv->enable(ctx, false);
		if (rv == EC_SUCCESS)
			ctx->state = PCHG_STATE_INITIALIZED;
		else if (rv != EC_SUCCESS_IN_PROGRESS)
			CPRINTS("ERR: Failed to disable");
		break;
	case PCHG_EVENT_DISABLED:
		ctx->state = PCHG_STATE_INITIALIZED;
		break;
	case PCHG_EVENT_CHARGE_STARTED:
		ctx->state = PCHG_STATE_CHARGING;
		break;
	case PCHG_EVENT_DEVICE_LOST:
		ctx->battery_percent = 0;
		ctx->state = PCHG_STATE_ENABLED;
		break;
	case PCHG_EVENT_CHARGE_ERROR:
		ctx->state = PCHG_STATE_INITIALIZED;
		break;
	default:
		break;
	}
}

static void pchg_state_charging(struct pchg *ctx)
{
	int rv;

	switch (ctx->event) {
	case PCHG_EVENT_RESET:
		ctx->state = pchg_reset(ctx);
		break;
	case PCHG_EVENT_DISABLE:
		ctx->error |= PCHG_ERROR_HOST;
		rv = ctx->cfg->drv->enable(ctx, false);
		if (rv == EC_SUCCESS)
			ctx->state = PCHG_STATE_INITIALIZED;
		else if (rv != EC_SUCCESS_IN_PROGRESS)
			CPRINTS("ERR: Failed to disable");
		break;
	case PCHG_EVENT_DISABLED:
		ctx->state = PCHG_STATE_INITIALIZED;
		break;
	case PCHG_EVENT_CHARGE_UPDATE:
		break;
	case PCHG_EVENT_DEVICE_LOST:
		ctx->battery_percent = 0;
		ctx->state = PCHG_STATE_ENABLED;
		break;
	case PCHG_EVENT_CHARGE_ERROR:
		ctx->state = PCHG_STATE_INITIALIZED;
		break;
	case PCHG_EVENT_CHARGE_ENDED:
	case PCHG_EVENT_CHARGE_STOPPED:
		ctx->state = PCHG_STATE_CONNECTED;
		break;
	default:
		break;
	}
}

static void pchg_state_download(struct pchg *ctx)
{
	int rv;

	switch (ctx->event) {
	case PCHG_EVENT_RESET:
		ctx->state = pchg_reset(ctx);
		break;
	case PCHG_EVENT_UPDATE_OPEN:
		rv = ctx->cfg->drv->update_open(ctx);
		if (rv == EC_SUCCESS) {
			ctx->state = PCHG_STATE_DOWNLOADING;
		} else if (rv != EC_SUCCESS_IN_PROGRESS) {
			_send_host_event(ctx, EC_MKBP_PCHG_UPDATE_ERROR);
			CPRINTS("ERR: Failed to open");
		}
		break;
	case PCHG_EVENT_UPDATE_OPENED:
		ctx->state = PCHG_STATE_DOWNLOADING;
		_send_host_event(ctx, EC_MKBP_PCHG_UPDATE_OPENED);
		break;
	case PCHG_EVENT_UPDATE_ERROR:
		_send_host_event(ctx, EC_MKBP_PCHG_UPDATE_ERROR);
		break;
	default:
		break;
	}
}

static void pchg_state_downloading(struct pchg *ctx)
{
	int rv;

	switch (ctx->event) {
	case PCHG_EVENT_RESET:
		ctx->state = pchg_reset(ctx);
		break;
	case PCHG_EVENT_UPDATE_WRITE:
		if (ctx->update.data_ready == 0)
			break;
		rv = ctx->cfg->drv->update_write(ctx);
		if (rv != EC_SUCCESS && rv != EC_SUCCESS_IN_PROGRESS) {
			_send_host_event(ctx, EC_MKBP_PCHG_UPDATE_ERROR);
			CPRINTS("ERR: Failed to write");
		}
		break;
	case PCHG_EVENT_UPDATE_WRITTEN:
		ctx->update.data_ready = 0;
		_send_host_event(ctx, EC_MKBP_PCHG_WRITE_COMPLETE);
		break;
	case PCHG_EVENT_UPDATE_CLOSE:
		rv = ctx->cfg->drv->update_close(ctx);
		if (rv == EC_SUCCESS) {
			ctx->state = PCHG_STATE_DOWNLOAD;
		} else if (rv != EC_SUCCESS_IN_PROGRESS) {
			_send_host_event(ctx, EC_MKBP_PCHG_UPDATE_ERROR);
			CPRINTS("ERR: Failed to close");
		}
		break;
	case PCHG_EVENT_UPDATE_CLOSED:
		ctx->state = PCHG_STATE_DOWNLOAD;
		_send_host_event(ctx, EC_MKBP_PCHG_UPDATE_CLOSED);
		break;
	case PCHG_EVENT_UPDATE_ERROR:
		CPRINTS("ERR: Failed to update");
		_send_host_event(ctx, EC_MKBP_PCHG_UPDATE_ERROR);
		break;
	default:
		break;
	}
}

static int pchg_run(struct pchg *ctx)
{
	enum pchg_state previous_state = ctx->state;
	uint8_t previous_battery = ctx->battery_percent;
	int port = PCHG_CTX_TO_PORT(ctx);
	int rv;

	mutex_lock(&ctx->mtx);
	if (!queue_remove_unit(&ctx->events, &ctx->event)) {
		mutex_unlock(&ctx->mtx);
		CPRINTS("P%d No event in queue", port);
		return 0;
	}
	mutex_unlock(&ctx->mtx);

	CPRINTS("P%d Run in STATE_%s for EVENT_%s", port,
		_text_state(ctx->state), _text_event(ctx->event));

	if (ctx->event == PCHG_EVENT_IRQ) {
		rv = ctx->cfg->drv->get_event(ctx);
		if (rv) {
			CPRINTS("ERR: Failed to get event (%d)", rv);
			return 0;
		}
		CPRINTS("  EVENT_%s", _text_event(ctx->event));
	}

	if (ctx->event == PCHG_EVENT_NONE)
		return 0;

	switch (ctx->state) {
	case PCHG_STATE_RESET:
		pchg_state_reset(ctx);
		break;
	case PCHG_STATE_INITIALIZED:
		pchg_state_initialized(ctx);
		break;
	case PCHG_STATE_ENABLED:
		pchg_state_enabled(ctx);
		break;
	case PCHG_STATE_DETECTED:
		pchg_state_detected(ctx);
		break;
	case PCHG_STATE_CONNECTED:
		pchg_state_connected(ctx);
		break;
	case PCHG_STATE_CHARGING:
		pchg_state_charging(ctx);
		break;
	case PCHG_STATE_DOWNLOAD:
		pchg_state_download(ctx);
		break;
	case PCHG_STATE_DOWNLOADING:
		pchg_state_downloading(ctx);
		break;
	default:
		CPRINTS("ERR: Unknown state (%d)", ctx->state);
		return 0;
	}

	if (previous_state != ctx->state)
		CPRINTS("->STATE_%s", _text_state(ctx->state));

	if (ctx->battery_percent != previous_battery)
		CPRINTS("Battery %u%%", ctx->battery_percent);

	/*
	 * Notify the host of
	 * - [S0] Charge update with SoC change and all other events.
	 * - [S3/S0IX] Device attach or detach (for wake-up)
	 * - [S5/G3] No events.
	 */
	if (chipset_in_state(CHIPSET_STATE_ANY_OFF))
		return 0;

	if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND))
		return (ctx->event == PCHG_EVENT_DEVICE_DETECTED)
			|| (ctx->event == PCHG_EVENT_DEVICE_LOST);

	if (ctx->event == PCHG_EVENT_CHARGE_UPDATE)
		return ctx->battery_percent != previous_battery;

	return ctx->event != PCHG_EVENT_NONE;
}

void pchg_irq(enum gpio_signal signal)
{
	struct pchg *ctx;
	int i;

	for (i = 0; i < pchg_count; i++) {
		ctx = &pchgs[i];
		if (signal == ctx->cfg->irq_pin) {
			ctx->irq = 1;
			task_wake(TASK_ID_PCHG);
			return;
		}
	}
}


static void pchg_suspend_complete(void)
{
	CPRINTS("%s", __func__);
	device_enable_event(EC_DEVICE_EVENT_WLC);
}
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND_COMPLETE, pchg_suspend_complete,
	     HOOK_PRIO_DEFAULT);

static void pchg_startup(void)
{
	struct pchg *ctx;
	int p;

	CPRINTS("%s", __func__);

	for (p = 0; p < pchg_count; p++) {
		ctx = &pchgs[p];
		_clear_port(ctx);
		ctx->mode = PCHG_MODE_NORMAL;
		ctx->cfg->drv->reset(ctx);
		gpio_enable_interrupt(ctx->cfg->irq_pin);
	}

	task_wake(TASK_ID_PCHG);
}
DECLARE_HOOK(HOOK_CHIPSET_STARTUP, pchg_startup, HOOK_PRIO_DEFAULT);

static void pchg_shutdown(void)
{
	struct pchg *ctx;
	int p;

	CPRINTS("%s", __func__);

	for (p = 0; p < pchg_count; p++) {
		ctx = &pchgs[0];
		gpio_disable_interrupt(ctx->cfg->irq_pin);
	}
}
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, pchg_shutdown, HOOK_PRIO_DEFAULT);

void pchg_task(void *u)
{
	struct pchg *ctx;
	int p;

	if (chipset_in_state(CHIPSET_STATE_ON))
		/* We are here after power-on (because of late sysjump). */
		pchg_startup();

	while (true) {
		/* Process pending events for all ports. */
		int rv = 0;

		for (p = 0; p < pchg_count; p++) {
			ctx = &pchgs[p];
			do {
				if (atomic_clear(&ctx->irq))
					pchg_queue_event(ctx, PCHG_EVENT_IRQ);
				rv |= pchg_run(ctx);
			} while (queue_count(&ctx->events));
		}

		/* Send one host event for all ports. */
		if (rv)
			device_set_single_event(EC_DEVICE_EVENT_WLC);

		task_wait_event(-1);
	}
}

static enum ec_status hc_pchg_count(struct host_cmd_handler_args *args)
{
	struct ec_response_pchg_count *r = args->response;

	r->port_count = pchg_count;
	args->response_size = sizeof(*r);

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_PCHG_COUNT, hc_pchg_count, EC_VER_MASK(0));

#define HCPRINTS(fmt, args...) cprints(CC_PCHG, "HC:PCHG: " fmt, ##args)

static enum ec_status hc_pchg(struct host_cmd_handler_args *args)
{
	const struct ec_params_pchg *p = args->params;
	struct ec_response_pchg *r = args->response;
	int port = p->port;
	struct pchg *ctx;

	if (port >= pchg_count)
		return EC_RES_INVALID_PARAM;

	ctx = &pchgs[port];

	if (ctx->state == PCHG_STATE_CONNECTED
			&& ctx->battery_percent >= ctx->cfg->full_percent)
		r->state = PCHG_STATE_FULL;
	else
		r->state = ctx->state;

	r->battery_percentage = ctx->battery_percent;
	r->error = ctx->error;
	r->fw_version = ctx->fw_version;
	r->dropped_event_count = ctx->dropped_event_count;

	args->response_size = sizeof(*r);

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_PCHG, hc_pchg, EC_VER_MASK(1));

int pchg_get_next_event(uint8_t *out)
{
	uint32_t events = atomic_clear(&pchg_host_events);

	memcpy(out, &events, sizeof(events));

	return sizeof(events);
}
DECLARE_EVENT_SOURCE(EC_MKBP_EVENT_PCHG, pchg_get_next_event);

static enum ec_status hc_pchg_update(struct host_cmd_handler_args *args)
{
	const struct ec_params_pchg_update *p = args->params;
	struct ec_response_pchg_update *r = args->response;
	int port = p->port;
	struct pchg *ctx;

	if (port >= pchg_count)
		return EC_RES_INVALID_PARAM;

	ctx = &pchgs[port];

	switch (p->cmd) {
	case EC_PCHG_UPDATE_CMD_RESET_TO_NORMAL:
		HCPRINTS("Resetting to normal mode");

		gpio_disable_interrupt(ctx->cfg->irq_pin);
		_clear_port(ctx);
		ctx->mode = PCHG_MODE_NORMAL;
		ctx->cfg->drv->reset(ctx);
		gpio_enable_interrupt(ctx->cfg->irq_pin);
		break;

	case EC_PCHG_UPDATE_CMD_OPEN:
		HCPRINTS("Resetting to download mode");

		gpio_disable_interrupt(ctx->cfg->irq_pin);
		_clear_port(ctx);
		ctx->mode = PCHG_MODE_DOWNLOAD;
		ctx->cfg->drv->reset(ctx);
		gpio_enable_interrupt(ctx->cfg->irq_pin);

		ctx->update.version = p->version;
		r->block_size = ctx->cfg->block_size;
		args->response_size = sizeof(*r);
		break;

	case EC_PCHG_UPDATE_CMD_WRITE:
		if (ctx->state != PCHG_STATE_DOWNLOADING)
			return EC_RES_ERROR;
		if (p->size > sizeof(ctx->update.data))
			return EC_RES_OVERFLOW;
		if (ctx->update.data_ready)
			return EC_RES_BUSY;

		HCPRINTS("Writing %u bytes to 0x%x", p->size, p->addr);
		ctx->update.addr = p->addr;
		ctx->update.size = p->size;
		memcpy(ctx->update.data, p->data, p->size);
		pchg_queue_event(ctx, PCHG_EVENT_UPDATE_WRITE);
		ctx->update.data_ready = 1;
		break;

	case EC_PCHG_UPDATE_CMD_CLOSE:
		if (ctx->state != PCHG_STATE_DOWNLOADING)
			return EC_RES_ERROR;
		if (ctx->update.data_ready)
			return EC_RES_BUSY;

		HCPRINTS("Closing update session (crc=0x%x)", p->crc32);
		ctx->update.crc32 = p->crc32;
		pchg_queue_event(ctx, PCHG_EVENT_UPDATE_CLOSE);
		break;
	default:
		return EC_RES_INVALID_PARAM;
	}

	task_wake(TASK_ID_PCHG);

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_PCHG_UPDATE, hc_pchg_update, EC_VER_MASK(0));

static int cc_pchg(int argc, char **argv)
{
	int port;
	char *end;
	struct pchg *ctx;

	if (argc < 2 || 4 < argc)
		return EC_ERROR_PARAM_COUNT;

	port = strtoi(argv[1], &end, 0);
	if (*end || port < 0 || port >= pchg_count)
		return EC_ERROR_PARAM1;
	ctx = &pchgs[port];

	if (argc == 2) {
		ccprintf("P%d STATE_%s EVENT_%s SOC=%d%%\n",
			 port, _text_state(ctx->state), _text_event(ctx->event),
			 ctx->battery_percent);
		ccprintf("error=0x%x dropped=%u fw_version=0x%x\n",
			 ctx->error, ctx->dropped_event_count, ctx->fw_version);
		return EC_SUCCESS;
	}

	if (!strcasecmp(argv[2], "reset")) {
		if (argc == 3)
			ctx->mode = PCHG_MODE_NORMAL;
		else if (!strcasecmp(argv[3], "download"))
			ctx->mode = PCHG_MODE_DOWNLOAD;
		else
			return EC_ERROR_PARAM3;
		gpio_disable_interrupt(ctx->cfg->irq_pin);
		_clear_port(ctx);
		ctx->cfg->drv->reset(ctx);
		gpio_enable_interrupt(ctx->cfg->irq_pin);
	} else if (!strcasecmp(argv[2], "enable")) {
		pchg_queue_event(ctx, PCHG_EVENT_ENABLE);
	} else if (!strcasecmp(argv[2], "disable")) {
		pchg_queue_event(ctx, PCHG_EVENT_DISABLE);
	} else {
		return EC_ERROR_PARAM2;
	}

	task_wake(TASK_ID_PCHG);

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(pchg, cc_pchg,
			"\n\t<port>"
			"\n\t<port> reset [download]"
			"\n\t<port> enable"
			"\n\t<port> disable",
			"Control peripheral chargers");
