blob: eeb2a4cf804a92e506dd43724bdc6440e52d8be7 [file] [log] [blame]
/* 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 "common.h"
#include "device_event.h"
#include "hooks.h"
#include "host_command.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)
static int dropped_event;
static void pchg_queue_event(struct pchg *ctx, enum pchg_event event)
{
mutex_lock(&ctx->mtx);
if (queue_add_unit(&ctx->events, &event) == 0) {
dropped_event++;
CPRINTS("ERR: Queue is full");
}
mutex_unlock(&ctx->mtx);
}
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;
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_INITIALIZED] = "INITIALIZED",
[PCHG_EVENT_ENABLED] = "ENABLED",
[PCHG_EVENT_DISABLED] = "DISABLED",
[PCHG_EVENT_DEVICE_DETECTED] = "DEVICE_DETECTED",
[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_CHARGE_ERROR] = "CHARGE_ERROR",
[PCHG_EVENT_INITIALIZE] = "INITIALIZE",
[PCHG_EVENT_ENABLE] = "ENABLE",
[PCHG_EVENT_DISABLE] = "DISABLE",
};
if (event >= sizeof(event_names))
return "UNDEF";
return event_names[event];
}
static enum pchg_state pchg_state_reset(struct pchg *ctx)
{
enum pchg_state state = PCHG_STATE_RESET;
int rv;
switch (ctx->event) {
case PCHG_EVENT_INITIALIZE:
rv = ctx->cfg->drv->init(ctx);
if (rv == EC_SUCCESS) {
pchg_queue_event(ctx, PCHG_EVENT_ENABLE);
state = PCHG_STATE_INITIALIZED;
} else if (rv != EC_SUCCESS_IN_PROGRESS) {
CPRINTS("ERR: Failed to initialize");
}
break;
case PCHG_EVENT_INITIALIZED:
pchg_queue_event(ctx, PCHG_EVENT_ENABLE);
state = PCHG_STATE_INITIALIZED;
break;
default:
break;
}
return state;
}
static enum pchg_state pchg_state_initialized(struct pchg *ctx)
{
enum pchg_state state = PCHG_STATE_INITIALIZED;
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 state;
switch (ctx->event) {
case PCHG_EVENT_ENABLE:
rv = ctx->cfg->drv->enable(ctx, true);
if (rv == EC_SUCCESS)
state = PCHG_STATE_ENABLED;
else if (rv != EC_SUCCESS_IN_PROGRESS)
CPRINTS("ERR: Failed to enable");
break;
case PCHG_EVENT_ENABLED:
state = PCHG_STATE_ENABLED;
break;
default:
break;
}
return state;
}
static enum pchg_state pchg_state_enabled(struct pchg *ctx)
{
enum pchg_state state = PCHG_STATE_ENABLED;
int rv;
switch (ctx->event) {
case PCHG_EVENT_DISABLE:
ctx->error |= PCHG_ERROR_HOST;
rv = ctx->cfg->drv->enable(ctx, false);
if (rv == EC_SUCCESS)
state = PCHG_STATE_INITIALIZED;
else if (rv != EC_SUCCESS_IN_PROGRESS)
CPRINTS("ERR: Failed to disable");
break;
case PCHG_EVENT_DISABLED:
state = PCHG_STATE_INITIALIZED;
break;
case PCHG_EVENT_DEVICE_DETECTED:
state = PCHG_STATE_DETECTED;
break;
case PCHG_EVENT_CHARGE_STARTED:
state = PCHG_STATE_CHARGING;
break;
default:
break;
}
return state;
}
static enum pchg_state pchg_state_detected(struct pchg *ctx)
{
enum pchg_state state = PCHG_STATE_DETECTED;
int rv;
switch (ctx->event) {
case PCHG_EVENT_DISABLE:
ctx->error |= PCHG_ERROR_HOST;
rv = ctx->cfg->drv->enable(ctx, false);
if (rv == EC_SUCCESS)
state = PCHG_STATE_INITIALIZED;
else if (rv != EC_SUCCESS_IN_PROGRESS)
CPRINTS("ERR: Failed to disable");
break;
case PCHG_EVENT_DISABLED:
state = PCHG_STATE_INITIALIZED;
break;
case PCHG_EVENT_CHARGE_STARTED:
state = PCHG_STATE_CHARGING;
break;
case PCHG_EVENT_DEVICE_LOST:
state = PCHG_STATE_ENABLED;
break;
case PCHG_EVENT_CHARGE_ERROR:
state = PCHG_STATE_INITIALIZED;
break;
default:
break;
}
return state;
}
static enum pchg_state pchg_state_charging(struct pchg *ctx)
{
enum pchg_state state = PCHG_STATE_CHARGING;
int rv;
switch (ctx->event) {
case PCHG_EVENT_DISABLE:
ctx->error |= PCHG_ERROR_HOST;
rv = ctx->cfg->drv->enable(ctx, false);
if (rv == EC_SUCCESS)
state = PCHG_STATE_INITIALIZED;
else if (rv != EC_SUCCESS_IN_PROGRESS)
CPRINTS("ERR: Failed to disable");
break;
case PCHG_EVENT_DISABLED:
state = PCHG_STATE_INITIALIZED;
break;
case PCHG_EVENT_CHARGE_UPDATE:
CPRINTS("Battery %d%%", ctx->battery_percent);
break;
case PCHG_EVENT_DEVICE_LOST:
state = PCHG_STATE_ENABLED;
break;
case PCHG_EVENT_CHARGE_ERROR:
state = PCHG_STATE_INITIALIZED;
break;
case PCHG_EVENT_CHARGE_ENDED:
case PCHG_EVENT_CHARGE_STOPPED:
state = PCHG_STATE_DETECTED;
break;
default:
break;
}
return state;
}
static void pchg_run(struct pchg *ctx)
{
enum pchg_state previous_state = ctx->state;
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;
}
mutex_unlock(&ctx->mtx);
CPRINTS("P%d Run in %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: get_event (%d)", rv);
ctx->event = PCHG_EVENT_NONE;
return;
}
CPRINTS("(IRQ:EVENT_%s)", _text_event(ctx->event));
}
switch (ctx->state) {
case PCHG_STATE_RESET:
ctx->state = pchg_state_reset(ctx);
break;
case PCHG_STATE_INITIALIZED:
ctx->state = pchg_state_initialized(ctx);
break;
case PCHG_STATE_ENABLED:
ctx->state = pchg_state_enabled(ctx);
break;
case PCHG_STATE_DETECTED:
ctx->state = pchg_state_detected(ctx);
break;
case PCHG_STATE_CHARGING:
ctx->state = pchg_state_charging(ctx);
break;
default:
CPRINTS("ERR: Unknown state (%d)", ctx->state);
break;
}
if (previous_state != ctx->state)
CPRINTS("->%s", _text_state(ctx->state));
ctx->event = PCHG_EVENT_NONE;
CPRINTS("Done");
}
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;
}
}
}
void pchg_task(void *u)
{
struct pchg *ctx;
int p;
/* TODO: i2c is wedged for a while after reset. investigate. */
msleep(500);
for (p = 0; p < pchg_count; p++) {
ctx = &pchgs[p];
ctx->state = PCHG_STATE_RESET;
queue_init(&ctx->events);
pchg_queue_event(ctx, PCHG_EVENT_INITIALIZE);
gpio_enable_interrupt(ctx->cfg->irq_pin);
}
while (true) {
/* Process pending events for all ports. */
for (p = 0; p < pchg_count; p++) {
ctx = &pchgs[p];
do {
if (atomic_clear(&ctx->irq))
pchg_queue_event(ctx, PCHG_EVENT_IRQ);
pchg_run(ctx);
} while (queue_count(&ctx->events));
}
/*
* Send one host event for all ports. Currently PCHG supports
* only WLC but in the future other types (e.g. WPC Qi) should
* send different device events.
*/
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));
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];
r->state = ctx->state;
r->battery_percentage = ctx->battery_percent;
r->error = ctx->error;
args->response_size = sizeof(*r);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_PCHG, hc_pchg, EC_VER_MASK(0));
static int cc_pchg(int argc, char **argv)
{
int port;
char *end;
struct pchg *ctx;
if (argc < 2 || 3 < argc)
return EC_ERROR_PARAM_COUNT;
port = strtoi(argv[1], &end, 0);
if (*end || port < 0 || port >= pchg_count)
return EC_ERROR_PARAM2;
ctx = &pchgs[port];
if (argc == 2) {
ccprintf("P%d %s %s\n", port,
_text_state(ctx->state), _text_event(ctx->event));
return EC_SUCCESS;
}
if (!strcasecmp(argv[2], "init")) {
ctx->state = PCHG_STATE_RESET;
pchg_queue_event(ctx, PCHG_EVENT_INITIALIZE);
} 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_PARAM1;
}
task_wake(TASK_ID_PCHG);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(pchg, cc_pchg,
"<port> [init/enable/disable]"
"\n\t<port>"
"\n\t<port> init"
"\n\t<port> enable"
"\n\t<port> disable",
"Control peripheral chargers");