summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/build.mk1
-rw-r--r--common/peripheral_charger.c373
-rw-r--r--include/config.h5
-rw-r--r--include/console_channel.inc3
-rw-r--r--include/peripheral_charger.h184
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 */