diff options
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | common/peripheral_charger.c | 373 | ||||
-rw-r--r-- | include/config.h | 5 | ||||
-rw-r--r-- | include/console_channel.inc | 3 | ||||
-rw-r--r-- | include/peripheral_charger.h | 184 |
5 files changed, 566 insertions, 0 deletions
diff --git a/common/build.mk b/common/build.mk index 839207a1a8..ab43c992e0 100644 --- a/common/build.mk +++ b/common/build.mk @@ -110,6 +110,7 @@ common-$(CONFIG_OCPC)+=ocpc.o common-$(CONFIG_ONEWIRE)+=onewire.o common-$(CONFIG_ORIENTATION_SENSOR)+=motion_orientation.o common-$(CONFIG_PECI_COMMON)+=peci.o +common-$(CONFIG_PERIPHERAL_CHARGER)+=peripheral_charger.o common-$(CONFIG_POWER_BUTTON)+=power_button.o common-$(CONFIG_POWER_BUTTON_X86)+=power_button_x86.o common-$(CONFIG_PSTORE)+=pstore_commands.o diff --git a/common/peripheral_charger.c b/common/peripheral_charger.c new file mode 100644 index 0000000000..dee6203aa5 --- /dev/null +++ b/common/peripheral_charger.c @@ -0,0 +1,373 @@ +/* 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 "hooks.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[] = { + [PCHG_STATE_RESET] = "RESET", + [PCHG_STATE_INITIALIZED] = "INITIALIZED", + [PCHG_STATE_ENABLED] = "ENABLED", + [PCHG_STATE_DETECTED] = "DETECTED", + [PCHG_STATE_CHARGING] = "CHARGING", + }; + + 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)); + } + task_wait_event(-1); + } +} + +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"); diff --git a/include/config.h b/include/config.h index 23fe474ff4..abec105bc0 100644 --- a/include/config.h +++ b/include/config.h @@ -3046,6 +3046,11 @@ */ #undef CONFIG_PECI_TJMAX +/* + * Enable peripheral charge manager (e.g. NFC/WLC, WPC Qi) + */ +#undef CONFIG_PERIPHERAL_CHARGER + /*****************************************************************************/ /* PMU config */ diff --git a/include/console_channel.inc b/include/console_channel.inc index 8d7cd66a01..01658a0eaa 100644 --- a/include/console_channel.inc +++ b/include/console_channel.inc @@ -75,6 +75,9 @@ CONSOLE_CHANNEL(CC_PORT80, "port80") #ifdef CONFIG_PS2 CONSOLE_CHANNEL(CC_PS2, "ps2") #endif +#ifdef CONFIG_PERIPHERAL_CHARGER +CONSOLE_CHANNEL(CC_PCHG, "pchg") +#endif #if defined(CONFIG_PWM) || defined(CONFIG_FANS) CONSOLE_CHANNEL(CC_PWM, "pwm") #endif diff --git a/include/peripheral_charger.h b/include/peripheral_charger.h new file mode 100644 index 0000000000..99fd4e3bb0 --- /dev/null +++ b/include/peripheral_charger.h @@ -0,0 +1,184 @@ +/* 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. + */ + +#ifndef __CROS_EC_PERIPHERAL_CHARGER_H +#define __CROS_EC_PERIPHERAL_CHARGER_H + +#include "common.h" +#include "gpio.h" +#include "queue.h" +#include "stdbool.h" +#include "task.h" + +/* + * Peripheral charge manager + * + * Peripheral charge manager (PCHG) is a state machine (SM), which manages + * charge ports to charge peripheral devices. Events can be generated + * externally (by a charger chip) or internally (by a host command or the SM + * itself). Events are queued and handled first-come-first-serve basis. + * + * Peripheral charger drivers should implement struct pchg_drv. Each operation + * can be synchronous or asynchronous depending on the chip. If a function + * works synchronously, it should return EC_SUCCESS. That'll make the SM + * immediately queue the next event (if applicable) and transition to the next + * state. If a function works asynchronously, it should return + * EC_SUCCESS_IN_PROGRESS. That'll make the SM stay in the same state. The SM + * is expected to receive IRQ for further information about the operation, + * which may or may not make the SM transition to the next state. + * + * Roughly speaking the SM looks as follows: + * + * +---------------+ + * | RESET | + * +-------+-------+ + * | + * | INITIALIZED + * v + * +-------+-------+ + * | INITIALIZED |<--------------+ + * +------+-+------+ | + * | ^ | + * ENABLED | | DISABLED | + * v | | + * +------+--------+ | + * +------------->+ ENABLED | | + * | +-------+-------+ | + * | | | + * | | DEVICE_DETECTED | + * | v | + * | +-------+-------+ | + * +--------------+ DETECTED +---------------+ + * | DEVICE_LOST +------+-+------+ ERROR | + * | | ^ | + * | CHARGE_STARTED | | CHARGE_ENDED | + * | | | CHARGE_STOPPED | + * | v | | + * | +------+-+------+ | + * +--------------+ CHARGING +---------------+ + * DEVICE_LOST +---------------+ ERROR + * + */ + +/* Size of event queue. Use it to initialize struct pchg.events. */ +#define PCHG_EVENT_QUEUE_SIZE 8 + +enum pchg_event { + /* No event */ + PCHG_EVENT_NONE = 0, + + /* IRQ is pending. */ + PCHG_EVENT_IRQ, + + /* External Events */ + PCHG_EVENT_INITIALIZED, + PCHG_EVENT_ENABLED, + PCHG_EVENT_DISABLED, + PCHG_EVENT_DEVICE_DETECTED, + PCHG_EVENT_DEVICE_LOST, + PCHG_EVENT_CHARGE_STARTED, + PCHG_EVENT_CHARGE_UPDATE, + PCHG_EVENT_CHARGE_ENDED, + PCHG_EVENT_CHARGE_STOPPED, + PCHG_EVENT_CHARGE_ERROR, + + /* Internal (a.k.a. Host) Events */ + PCHG_EVENT_INITIALIZE, + PCHG_EVENT_ENABLE, + PCHG_EVENT_DISABLE, +}; + +enum pchg_state { + /* Charger is reset and not initialized. */ + PCHG_STATE_RESET = 0, + /* Charger is initialized or disabled. */ + PCHG_STATE_INITIALIZED, + /* Charger is enabled and ready to detect a device. */ + PCHG_STATE_ENABLED, + /* Device is detected in proximity. */ + PCHG_STATE_DETECTED, + /* Device is being charged. */ + PCHG_STATE_CHARGING, +}; + +enum pchg_error { + PCHG_ERROR_NONE = 0, + /* Error initiated by host. */ + PCHG_ERROR_HOST = BIT(0), + PCHG_ERROR_OVER_TEMPERATURE = BIT(1), + PCHG_ERROR_OVER_CURRENT = BIT(2), + PCHG_ERROR_FOREIGN_OBJECT = BIT(3), +}; + +/** + * Data struct describing the configuration of a peripheral charging port. + */ +struct pchg_config { + /* Charger driver */ + const struct pchg_drv *drv; + /* I2C port number */ + const int i2c_port; + /* GPIO pin used for IRQ */ + const enum gpio_signal irq_pin; +}; + +/** + * Data struct describing the status of a peripheral charging port. It provides + * the state machine and a charger driver with a context to work on. + */ +struct pchg { + /* Static configuration */ + const struct pchg_config * const cfg; + /* Current state of the port */ + enum pchg_state state; + /* Event queue */ + struct queue const events; + /* Event queue mutex */ + struct mutex mtx; + /* 1:Pending IRQ 0:No pending IRQ */ + uint32_t irq; + /* Event currently being handled */ + enum pchg_event event; + /* Error (enum pchg_error). Port is disabled until it's cleared. */ + uint32_t error; + /* Battery percentage (0% ~ 100%) of the connected peripheral device */ + uint8_t battery_percent; +}; + +/** + * Peripheral charger driver + */ +struct pchg_drv { + /* Initialize the charger. */ + int (*init)(struct pchg *ctx); + /* Enable/disable the charger. */ + int (*enable)(struct pchg *ctx, bool enable); + /* Get event info. */ + int (*get_event)(struct pchg *ctx); +}; + +/** + * Array storing configs and states of all the peripheral charging ports. + * Should be defined in board.c. + */ +extern struct pchg pchgs[]; +extern const int pchg_count; + +/* Utility macro converting port config to port number. */ +#define PCHG_CTX_TO_PORT(ctx) ((ctx) - &pchgs[0]) + +/** + * Interrupt handler for a peripheral charger. + * + * @param signal + */ +void pchg_irq(enum gpio_signal signal); + +/** + * Task running a state machine for charging peripheral devices. + */ +void pchg_task(void *u); + +#endif /* __CROS_EC_PERIPHERAL_CHARGER_H */ |