summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Boichat <drinkcat@chromium.org>2018-06-07 16:47:26 +0800
committerchrome-bot <chrome-bot@chromium.org>2018-06-14 05:38:39 -0700
commit24153748b61b9f771c3b4e4646b9f54b4e4e2a18 (patch)
tree62f82a18f8226104d10d49872eed0c8f23e7f773
parentb4f69d8a0cf5c4bc55880befe7cd995b1ddd2926 (diff)
downloadchrome-ec-24153748b61b9f771c3b4e4646b9f54b4e4e2a18.tar.gz
power/mt8183: Power sequencing logic for MT8183
MT8183 uses a power sequencing inspired from RK3399, with fewer signals. We only have 1 signal from PMIC (PMIC_PWR_GOOD), active in S0/S3, and 1 signal from AP (AP_IN_S3_L), active in S3/S5. One particularity of this design is that we need to reboot the EC to RO on every single cold boot/reboot. For the forced transition to S5, we assert the WATCHDOG signal to PMIC to shut it down, which should usually work, if the PMIC was configured properly by AP. If not, we also assert power+home key (PMIC_EN_ODL) until the PMIC shuts down for good. BRANCH=none BUG=b:109850749 TEST=make BOARD=kukui -j Change-Id: Ibcde8b937d7f4cecb0f470b9a7e0809fc24efae6 Signed-off-by: Nicolas Boichat <drinkcat@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/1092402 Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
-rw-r--r--include/config.h1
-rw-r--r--power/build.mk1
-rw-r--r--power/mt8183.c328
3 files changed, 330 insertions, 0 deletions
diff --git a/include/config.h b/include/config.h
index bb6b8bf8be..b51cbfc5b8 100644
--- a/include/config.h
+++ b/include/config.h
@@ -765,6 +765,7 @@
#undef CONFIG_CHIPSET_ECDRIVEN /* Dummy power module */
#undef CONFIG_CHIPSET_GEMINILAKE /* Intel Geminilake (x86) */
#undef CONFIG_CHIPSET_MT817X /* MediaTek MT817x */
+#undef CONFIG_CHIPSET_MT8183 /* MediaTek MT8183 */
#undef CONFIG_CHIPSET_RK3288 /* Rockchip rk3288 */
#undef CONFIG_CHIPSET_RK3399 /* Rockchip rk3399 */
#undef CONFIG_CHIPSET_SKYLAKE /* Intel Skylake (x86) */
diff --git a/power/build.mk b/power/build.mk
index a5cbddaa06..a15b6cf49e 100644
--- a/power/build.mk
+++ b/power/build.mk
@@ -11,6 +11,7 @@ power-$(CONFIG_CHIPSET_BRASWELL)+=braswell.o
power-$(CONFIG_CHIPSET_CANNONLAKE)+=cannonlake.o intel_x86.o
power-$(CONFIG_CHIPSET_ECDRIVEN)+=ec_driven.o
power-$(CONFIG_CHIPSET_MT817X)+=mt817x.o
+power-$(CONFIG_CHIPSET_MT8183)+=mt8183.o
power-$(CONFIG_CHIPSET_RK3288)+=rk3288.o
power-$(CONFIG_CHIPSET_RK3399)+=rk3399.o
power-$(CONFIG_CHIPSET_SDM845)+=sdm845.o
diff --git a/power/mt8183.c b/power/mt8183.c
new file mode 100644
index 0000000000..e598c5f62f
--- /dev/null
+++ b/power/mt8183.c
@@ -0,0 +1,328 @@
+/* Copyright 2018 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.
+ */
+
+/* mt8183 chipset power control module for Chrome EC */
+
+#include "charge_state.h"
+#include "chipset.h"
+#include "common.h"
+#include "console.h"
+#include "ec_commands.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "lid_switch.h"
+#include "power.h"
+#include "power_button.h"
+#include "system.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_CHIPSET, outstr)
+#define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ## args)
+
+/* Input state flags */
+#define IN_PGOOD_PMIC POWER_SIGNAL_MASK(PMIC_PWR_GOOD)
+#define IN_SUSPEND_DEASSERTED POWER_SIGNAL_MASK(AP_IN_S3_L)
+
+/* Rails required for S3 and S0 */
+#define IN_PGOOD_S0 (IN_PGOOD_PMIC)
+#define IN_PGOOD_S3 (IN_PGOOD_PMIC)
+
+/* All inputs in the right state for S0 */
+#define IN_ALL_S0 (IN_PGOOD_S0 | IN_SUSPEND_DEASSERTED)
+
+/* Long power key press to force shutdown in S0 */
+#define FORCED_SHUTDOWN_DELAY (8 * SECOND)
+
+#define CHARGER_INITIALIZED_DELAY_MS 100
+#define CHARGER_INITIALIZED_TRIES 40
+
+#define PMIC_EN_PULSE_MS 50
+
+/* Data structure for a GPIO operation for power sequencing */
+struct power_seq_op {
+ /* enum gpio_signal in 8 bits */
+ uint8_t signal;
+ uint8_t level;
+ /* Number of milliseconds to delay after setting signal to level */
+ uint8_t delay;
+};
+BUILD_ASSERT(GPIO_COUNT < 256);
+
+/*
+ * This is the power sequence for POWER_S5S3.
+ * The entries in the table are handled sequentially from the top
+ * to the bottom.
+ */
+
+static const struct power_seq_op s5s3_power_seq[] = {
+ { GPIO_PP3300_S3_EN, 1, 2 },
+ { GPIO_PP1800_S3_EN, 1, 2 },
+ /* Turn on AP. */
+ { GPIO_AP_SYS_RST_L, 1, 2 },
+};
+
+/* The power sequence for POWER_S3S0 */
+static const struct power_seq_op s3s0_power_seq[] = {
+ { GPIO_PP3300_S0_EN, 1, 0 },
+ { GPIO_PP1800_S0_EN, 1, 0 },
+};
+
+/* The power sequence for POWER_S0S3 */
+static const struct power_seq_op s0s3_power_seq[] = {
+ { GPIO_PP3300_S0_EN, 0, 0 },
+ { GPIO_PP1800_S0_EN, 0, 0 },
+};
+
+/* The power sequence for POWER_S3S5 */
+static const struct power_seq_op s3s5_power_seq[] = {
+ /* Turn off AP. */
+ { GPIO_AP_SYS_RST_L, 0, 0 },
+ { GPIO_PP1800_S3_EN, 0, 2 },
+ { GPIO_PP3300_S3_EN, 0, 2 },
+ /* Pulse watchdog to PMIC (there may be a 1.6ms debounce) */
+ { GPIO_PMIC_WATCHDOG_L, 0, 3 },
+ { GPIO_PMIC_WATCHDOG_L, 1, 0 },
+};
+
+static int forcing_shutdown;
+
+void chipset_force_shutdown(void)
+{
+ CPRINTS("%s()", __func__);
+
+ /*
+ * Force power off. This condition will reset once the state machine
+ * transitions to G3.
+ */
+ forcing_shutdown = 1;
+ task_wake(TASK_ID_CHIPSET);
+}
+DECLARE_DEFERRED(chipset_force_shutdown);
+
+/* If chipset needs to be reset, EC also reboots to RO. */
+void chipset_reset(void)
+{
+ CPRINTS("%s", __func__);
+
+ cflush();
+ system_reset(SYSTEM_RESET_HARD);
+
+ /* This should not be reachable. */
+ while (1)
+ ;
+}
+
+enum power_state power_chipset_init(void)
+{
+ if (system_jumped_to_this_image()) {
+ if ((power_get_signals() & IN_ALL_S0) == IN_ALL_S0) {
+ disable_sleep(SLEEP_MASK_AP_RUN);
+ CPRINTS("already in S0");
+ return POWER_S0;
+ }
+ } else if (!(system_get_reset_flags() & RESET_FLAG_AP_OFF)) {
+ /* Auto-power on */
+ chipset_exit_hard_off();
+ /*
+ * TODO(b:109850749): If we see that PMIC power good is up,
+ * we could probably jump straight to S5 and power on the AP.
+ */
+ }
+
+ return POWER_G3;
+}
+
+/**
+ * Step through the power sequence table and do corresponding GPIO operations.
+ *
+ * @param power_seq_ops The pointer to the power sequence table.
+ * @param op_count The number of entries of power_seq_ops.
+ */
+static void power_seq_run(const struct power_seq_op *power_seq_ops,
+ int op_count)
+{
+ int i;
+
+ for (i = 0; i < op_count; i++) {
+ gpio_set_level(power_seq_ops[i].signal,
+ power_seq_ops[i].level);
+ if (!power_seq_ops[i].delay)
+ continue;
+ msleep(power_seq_ops[i].delay);
+ }
+}
+
+enum power_state power_handle_state(enum power_state state)
+{
+ switch (state) {
+ case POWER_G3:
+ break;
+
+ case POWER_S5:
+ if (forcing_shutdown) {
+ /*
+ * While PMIC is still not off, press power+home button.
+ * This should not happen if PMIC is configured
+ * properly, and shuts down upon receiving WATCHDOG.
+ */
+ if (power_has_signals(IN_PGOOD_PMIC)) {
+ gpio_set_level(GPIO_PMIC_EN_ODL, 0);
+ return POWER_S5;
+ }
+
+ gpio_set_level(GPIO_PMIC_EN_ODL, 1);
+ return POWER_S5G3;
+ } else {
+ return POWER_S5S3;
+ }
+ break;
+
+ case POWER_S3:
+ if (!power_has_signals(IN_PGOOD_S3) || forcing_shutdown)
+ return POWER_S3S5;
+ else if (power_get_signals() & IN_SUSPEND_DEASSERTED)
+ return POWER_S3S0;
+ break;
+
+ case POWER_S0:
+ if (!power_has_signals(IN_PGOOD_S0) ||
+ forcing_shutdown ||
+ !(power_get_signals() & IN_SUSPEND_DEASSERTED))
+ return POWER_S0S3;
+
+ break;
+
+ case POWER_G3S5:
+ forcing_shutdown = 0;
+
+ /* If PMIC is off, switch it on by pulsing PMIC enable. */
+ if (!power_has_signals(IN_PGOOD_PMIC)) {
+ gpio_set_level(GPIO_PMIC_EN_ODL, 1);
+ msleep(PMIC_EN_PULSE_MS);
+ gpio_set_level(GPIO_PMIC_EN_ODL, 0);
+ }
+
+ /* If EC is in RW, reboot to RO. */
+ if (system_get_image_copy() != SYSTEM_IMAGE_RO) {
+ /*
+ * TODO(b:109850749): How quickly does the EC come back
+ * up? Would IN_PGOOD_PMIC be ready by the time we are
+ * back? According to PMIC spec, it should take ~158 ms
+ * after debounce (32 ms), minus PMIC_EN_PULSE_MS above.
+ * It would be good to avoid another _EN pulse above.
+ */
+ chipset_reset();
+ }
+
+ /* Wait for PMIC to bring up rails. */
+ if (power_wait_signals(IN_PGOOD_PMIC))
+ return POWER_G3;
+
+ /* Power up to next state */
+ return POWER_S5;
+
+ case POWER_S5S3:
+ /* Enable S3 power supplies, release AP reset. */
+ power_seq_run(s5s3_power_seq, ARRAY_SIZE(s5s3_power_seq));
+
+ /* Call hooks now that rails are up */
+ hook_notify(HOOK_CHIPSET_STARTUP);
+
+ /* Power up to next state */
+ return POWER_S3;
+
+ case POWER_S3S0:
+ power_seq_run(s3s0_power_seq, ARRAY_SIZE(s3s0_power_seq));
+
+ if (power_wait_signals(IN_PGOOD_S0)) {
+ chipset_force_shutdown();
+ return POWER_S0S3;
+ }
+
+ /* Call hooks now that rails are up */
+ hook_notify(HOOK_CHIPSET_RESUME);
+
+ /*
+ * Disable idle task deep sleep. This means that the low
+ * power idle task will not go into deep sleep while in S0.
+ */
+ disable_sleep(SLEEP_MASK_AP_RUN);
+
+ /* Power up to next state */
+ return POWER_S0;
+
+ case POWER_S0S3:
+ /* Call hooks before we remove power rails */
+ hook_notify(HOOK_CHIPSET_SUSPEND);
+
+ /*
+ * TODO(b:109850749): Check if we need some delay here to
+ * "debounce" entering suspend (rk3399 uses 20ms delay).
+ */
+
+ power_seq_run(s0s3_power_seq, ARRAY_SIZE(s0s3_power_seq));
+
+ /*
+ * Enable idle task deep sleep. Allow the low power idle task
+ * to go into deep sleep in S3 or lower.
+ */
+ enable_sleep(SLEEP_MASK_AP_RUN);
+
+ /*
+ * In case the power button is held awaiting power-off timeout,
+ * power off immediately now that we're entering S3.
+ */
+ if (power_button_is_pressed()) {
+ forcing_shutdown = 1;
+ hook_call_deferred(&chipset_force_shutdown_data, -1);
+ }
+
+ return POWER_S3;
+
+ case POWER_S3S5:
+ /* Call hooks before we remove power rails */
+ hook_notify(HOOK_CHIPSET_SHUTDOWN);
+
+ power_seq_run(s3s5_power_seq, ARRAY_SIZE(s3s5_power_seq));
+
+ /* Start shutting down */
+ return POWER_S5;
+
+ case POWER_S5G3:
+ return POWER_G3;
+ }
+
+ return state;
+}
+
+static void power_button_changed(void)
+{
+ if (power_button_is_pressed()) {
+ if (chipset_in_state(CHIPSET_STATE_ANY_OFF))
+ /* Power up from off */
+ chipset_exit_hard_off();
+
+ /* Delayed power down from S0/S3, cancel on PB release */
+ hook_call_deferred(&chipset_force_shutdown_data,
+ FORCED_SHUTDOWN_DELAY);
+ } else {
+ /* Power button released, cancel deferred shutdown */
+ hook_call_deferred(&chipset_force_shutdown_data, -1);
+ }
+}
+DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, power_button_changed, HOOK_PRIO_DEFAULT);
+
+#ifdef CONFIG_LID_SWITCH
+static void lid_changed(void)
+{
+ /* Power-up from off on lid open */
+ if (lid_is_open() && chipset_in_state(CHIPSET_STATE_ANY_OFF))
+ chipset_exit_hard_off();
+}
+DECLARE_HOOK(HOOK_LID_CHANGE, lid_changed, HOOK_PRIO_DEFAULT);
+#endif