|
| 1 | +/* Copyright 2018 The Chromium OS Authors. All rights reserved. |
| 2 | + * Use of this source code is governed by a BSD-style license that can be |
| 3 | + * found in the LICENSE file. |
| 4 | + */ |
| 5 | + |
| 6 | +/* mt8183 chipset power control module for Chrome EC */ |
| 7 | + |
| 8 | +#include "charge_state.h" |
| 9 | +#include "chipset.h" |
| 10 | +#include "common.h" |
| 11 | +#include "console.h" |
| 12 | +#include "ec_commands.h" |
| 13 | +#include "gpio.h" |
| 14 | +#include "hooks.h" |
| 15 | +#include "lid_switch.h" |
| 16 | +#include "power.h" |
| 17 | +#include "power_button.h" |
| 18 | +#include "system.h" |
| 19 | +#include "task.h" |
| 20 | +#include "timer.h" |
| 21 | +#include "util.h" |
| 22 | + |
| 23 | +/* Console output macros */ |
| 24 | +#define CPUTS(outstr) cputs(CC_CHIPSET, outstr) |
| 25 | +#define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ## args) |
| 26 | + |
| 27 | +/* Input state flags */ |
| 28 | +#define IN_PGOOD_PMIC POWER_SIGNAL_MASK(PMIC_PWR_GOOD) |
| 29 | +#define IN_SUSPEND_DEASSERTED POWER_SIGNAL_MASK(AP_IN_S3_L) |
| 30 | + |
| 31 | +/* Rails required for S3 and S0 */ |
| 32 | +#define IN_PGOOD_S0 (IN_PGOOD_PMIC) |
| 33 | +#define IN_PGOOD_S3 (IN_PGOOD_PMIC) |
| 34 | + |
| 35 | +/* All inputs in the right state for S0 */ |
| 36 | +#define IN_ALL_S0 (IN_PGOOD_S0 | IN_SUSPEND_DEASSERTED) |
| 37 | + |
| 38 | +/* Long power key press to force shutdown in S0 */ |
| 39 | +#define FORCED_SHUTDOWN_DELAY (8 * SECOND) |
| 40 | + |
| 41 | +#define CHARGER_INITIALIZED_DELAY_MS 100 |
| 42 | +#define CHARGER_INITIALIZED_TRIES 40 |
| 43 | + |
| 44 | +#define PMIC_EN_PULSE_MS 50 |
| 45 | + |
| 46 | +/* Data structure for a GPIO operation for power sequencing */ |
| 47 | +struct power_seq_op { |
| 48 | + /* enum gpio_signal in 8 bits */ |
| 49 | + uint8_t signal; |
| 50 | + uint8_t level; |
| 51 | + /* Number of milliseconds to delay after setting signal to level */ |
| 52 | + uint8_t delay; |
| 53 | +}; |
| 54 | +BUILD_ASSERT(GPIO_COUNT < 256); |
| 55 | + |
| 56 | +/* |
| 57 | + * This is the power sequence for POWER_S5S3. |
| 58 | + * The entries in the table are handled sequentially from the top |
| 59 | + * to the bottom. |
| 60 | + */ |
| 61 | + |
| 62 | +static const struct power_seq_op s5s3_power_seq[] = { |
| 63 | + { GPIO_PP3300_S3_EN, 1, 2 }, |
| 64 | + { GPIO_PP1800_S3_EN, 1, 2 }, |
| 65 | + /* Turn on AP. */ |
| 66 | + { GPIO_AP_SYS_RST_L, 1, 2 }, |
| 67 | +}; |
| 68 | + |
| 69 | +/* The power sequence for POWER_S3S0 */ |
| 70 | +static const struct power_seq_op s3s0_power_seq[] = { |
| 71 | + { GPIO_PP3300_S0_EN, 1, 0 }, |
| 72 | + { GPIO_PP1800_S0_EN, 1, 0 }, |
| 73 | +}; |
| 74 | + |
| 75 | +/* The power sequence for POWER_S0S3 */ |
| 76 | +static const struct power_seq_op s0s3_power_seq[] = { |
| 77 | + { GPIO_PP3300_S0_EN, 0, 0 }, |
| 78 | + { GPIO_PP1800_S0_EN, 0, 0 }, |
| 79 | +}; |
| 80 | + |
| 81 | +/* The power sequence for POWER_S3S5 */ |
| 82 | +static const struct power_seq_op s3s5_power_seq[] = { |
| 83 | + /* Turn off AP. */ |
| 84 | + { GPIO_AP_SYS_RST_L, 0, 0 }, |
| 85 | + { GPIO_PP1800_S3_EN, 0, 2 }, |
| 86 | + { GPIO_PP3300_S3_EN, 0, 2 }, |
| 87 | + /* Pulse watchdog to PMIC (there may be a 1.6ms debounce) */ |
| 88 | + { GPIO_PMIC_WATCHDOG_L, 0, 3 }, |
| 89 | + { GPIO_PMIC_WATCHDOG_L, 1, 0 }, |
| 90 | +}; |
| 91 | + |
| 92 | +static int forcing_shutdown; |
| 93 | + |
| 94 | +void chipset_force_shutdown(void) |
| 95 | +{ |
| 96 | + CPRINTS("%s()", __func__); |
| 97 | + |
| 98 | + /* |
| 99 | + * Force power off. This condition will reset once the state machine |
| 100 | + * transitions to G3. |
| 101 | + */ |
| 102 | + forcing_shutdown = 1; |
| 103 | + task_wake(TASK_ID_CHIPSET); |
| 104 | +} |
| 105 | +DECLARE_DEFERRED(chipset_force_shutdown); |
| 106 | + |
| 107 | +/* If chipset needs to be reset, EC also reboots to RO. */ |
| 108 | +void chipset_reset(void) |
| 109 | +{ |
| 110 | + CPRINTS("%s", __func__); |
| 111 | + |
| 112 | + cflush(); |
| 113 | + system_reset(SYSTEM_RESET_HARD); |
| 114 | + |
| 115 | + /* This should not be reachable. */ |
| 116 | + while (1) |
| 117 | + ; |
| 118 | +} |
| 119 | + |
| 120 | +enum power_state power_chipset_init(void) |
| 121 | +{ |
| 122 | + if (system_jumped_to_this_image()) { |
| 123 | + if ((power_get_signals() & IN_ALL_S0) == IN_ALL_S0) { |
| 124 | + disable_sleep(SLEEP_MASK_AP_RUN); |
| 125 | + CPRINTS("already in S0"); |
| 126 | + return POWER_S0; |
| 127 | + } |
| 128 | + } else if (!(system_get_reset_flags() & RESET_FLAG_AP_OFF)) { |
| 129 | + /* Auto-power on */ |
| 130 | + chipset_exit_hard_off(); |
| 131 | + /* |
| 132 | + * TODO(b:109850749): If we see that PMIC power good is up, |
| 133 | + * we could probably jump straight to S5 and power on the AP. |
| 134 | + */ |
| 135 | + } |
| 136 | + |
| 137 | + return POWER_G3; |
| 138 | +} |
| 139 | + |
| 140 | +/** |
| 141 | + * Step through the power sequence table and do corresponding GPIO operations. |
| 142 | + * |
| 143 | + * @param power_seq_ops The pointer to the power sequence table. |
| 144 | + * @param op_count The number of entries of power_seq_ops. |
| 145 | + */ |
| 146 | +static void power_seq_run(const struct power_seq_op *power_seq_ops, |
| 147 | + int op_count) |
| 148 | +{ |
| 149 | + int i; |
| 150 | + |
| 151 | + for (i = 0; i < op_count; i++) { |
| 152 | + gpio_set_level(power_seq_ops[i].signal, |
| 153 | + power_seq_ops[i].level); |
| 154 | + if (!power_seq_ops[i].delay) |
| 155 | + continue; |
| 156 | + msleep(power_seq_ops[i].delay); |
| 157 | + } |
| 158 | +} |
| 159 | + |
| 160 | +enum power_state power_handle_state(enum power_state state) |
| 161 | +{ |
| 162 | + switch (state) { |
| 163 | + case POWER_G3: |
| 164 | + break; |
| 165 | + |
| 166 | + case POWER_S5: |
| 167 | + if (forcing_shutdown) { |
| 168 | + /* |
| 169 | + * While PMIC is still not off, press power+home button. |
| 170 | + * This should not happen if PMIC is configured |
| 171 | + * properly, and shuts down upon receiving WATCHDOG. |
| 172 | + */ |
| 173 | + if (power_has_signals(IN_PGOOD_PMIC)) { |
| 174 | + gpio_set_level(GPIO_PMIC_EN_ODL, 0); |
| 175 | + return POWER_S5; |
| 176 | + } |
| 177 | + |
| 178 | + gpio_set_level(GPIO_PMIC_EN_ODL, 1); |
| 179 | + return POWER_S5G3; |
| 180 | + } else { |
| 181 | + return POWER_S5S3; |
| 182 | + } |
| 183 | + break; |
| 184 | + |
| 185 | + case POWER_S3: |
| 186 | + if (!power_has_signals(IN_PGOOD_S3) || forcing_shutdown) |
| 187 | + return POWER_S3S5; |
| 188 | + else if (power_get_signals() & IN_SUSPEND_DEASSERTED) |
| 189 | + return POWER_S3S0; |
| 190 | + break; |
| 191 | + |
| 192 | + case POWER_S0: |
| 193 | + if (!power_has_signals(IN_PGOOD_S0) || |
| 194 | + forcing_shutdown || |
| 195 | + !(power_get_signals() & IN_SUSPEND_DEASSERTED)) |
| 196 | + return POWER_S0S3; |
| 197 | + |
| 198 | + break; |
| 199 | + |
| 200 | + case POWER_G3S5: |
| 201 | + forcing_shutdown = 0; |
| 202 | + |
| 203 | + /* If PMIC is off, switch it on by pulsing PMIC enable. */ |
| 204 | + if (!power_has_signals(IN_PGOOD_PMIC)) { |
| 205 | + gpio_set_level(GPIO_PMIC_EN_ODL, 1); |
| 206 | + msleep(PMIC_EN_PULSE_MS); |
| 207 | + gpio_set_level(GPIO_PMIC_EN_ODL, 0); |
| 208 | + } |
| 209 | + |
| 210 | + /* If EC is in RW, reboot to RO. */ |
| 211 | + if (system_get_image_copy() != SYSTEM_IMAGE_RO) { |
| 212 | + /* |
| 213 | + * TODO(b:109850749): How quickly does the EC come back |
| 214 | + * up? Would IN_PGOOD_PMIC be ready by the time we are |
| 215 | + * back? According to PMIC spec, it should take ~158 ms |
| 216 | + * after debounce (32 ms), minus PMIC_EN_PULSE_MS above. |
| 217 | + * It would be good to avoid another _EN pulse above. |
| 218 | + */ |
| 219 | + chipset_reset(); |
| 220 | + } |
| 221 | + |
| 222 | + /* Wait for PMIC to bring up rails. */ |
| 223 | + if (power_wait_signals(IN_PGOOD_PMIC)) |
| 224 | + return POWER_G3; |
| 225 | + |
| 226 | + /* Power up to next state */ |
| 227 | + return POWER_S5; |
| 228 | + |
| 229 | + case POWER_S5S3: |
| 230 | + /* Enable S3 power supplies, release AP reset. */ |
| 231 | + power_seq_run(s5s3_power_seq, ARRAY_SIZE(s5s3_power_seq)); |
| 232 | + |
| 233 | + /* Call hooks now that rails are up */ |
| 234 | + hook_notify(HOOK_CHIPSET_STARTUP); |
| 235 | + |
| 236 | + /* Power up to next state */ |
| 237 | + return POWER_S3; |
| 238 | + |
| 239 | + case POWER_S3S0: |
| 240 | + power_seq_run(s3s0_power_seq, ARRAY_SIZE(s3s0_power_seq)); |
| 241 | + |
| 242 | + if (power_wait_signals(IN_PGOOD_S0)) { |
| 243 | + chipset_force_shutdown(); |
| 244 | + return POWER_S0S3; |
| 245 | + } |
| 246 | + |
| 247 | + /* Call hooks now that rails are up */ |
| 248 | + hook_notify(HOOK_CHIPSET_RESUME); |
| 249 | + |
| 250 | + /* |
| 251 | + * Disable idle task deep sleep. This means that the low |
| 252 | + * power idle task will not go into deep sleep while in S0. |
| 253 | + */ |
| 254 | + disable_sleep(SLEEP_MASK_AP_RUN); |
| 255 | + |
| 256 | + /* Power up to next state */ |
| 257 | + return POWER_S0; |
| 258 | + |
| 259 | + case POWER_S0S3: |
| 260 | + /* Call hooks before we remove power rails */ |
| 261 | + hook_notify(HOOK_CHIPSET_SUSPEND); |
| 262 | + |
| 263 | + /* |
| 264 | + * TODO(b:109850749): Check if we need some delay here to |
| 265 | + * "debounce" entering suspend (rk3399 uses 20ms delay). |
| 266 | + */ |
| 267 | + |
| 268 | + power_seq_run(s0s3_power_seq, ARRAY_SIZE(s0s3_power_seq)); |
| 269 | + |
| 270 | + /* |
| 271 | + * Enable idle task deep sleep. Allow the low power idle task |
| 272 | + * to go into deep sleep in S3 or lower. |
| 273 | + */ |
| 274 | + enable_sleep(SLEEP_MASK_AP_RUN); |
| 275 | + |
| 276 | + /* |
| 277 | + * In case the power button is held awaiting power-off timeout, |
| 278 | + * power off immediately now that we're entering S3. |
| 279 | + */ |
| 280 | + if (power_button_is_pressed()) { |
| 281 | + forcing_shutdown = 1; |
| 282 | + hook_call_deferred(&chipset_force_shutdown_data, -1); |
| 283 | + } |
| 284 | + |
| 285 | + return POWER_S3; |
| 286 | + |
| 287 | + case POWER_S3S5: |
| 288 | + /* Call hooks before we remove power rails */ |
| 289 | + hook_notify(HOOK_CHIPSET_SHUTDOWN); |
| 290 | + |
| 291 | + power_seq_run(s3s5_power_seq, ARRAY_SIZE(s3s5_power_seq)); |
| 292 | + |
| 293 | + /* Start shutting down */ |
| 294 | + return POWER_S5; |
| 295 | + |
| 296 | + case POWER_S5G3: |
| 297 | + return POWER_G3; |
| 298 | + } |
| 299 | + |
| 300 | + return state; |
| 301 | +} |
| 302 | + |
| 303 | +static void power_button_changed(void) |
| 304 | +{ |
| 305 | + if (power_button_is_pressed()) { |
| 306 | + if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) |
| 307 | + /* Power up from off */ |
| 308 | + chipset_exit_hard_off(); |
| 309 | + |
| 310 | + /* Delayed power down from S0/S3, cancel on PB release */ |
| 311 | + hook_call_deferred(&chipset_force_shutdown_data, |
| 312 | + FORCED_SHUTDOWN_DELAY); |
| 313 | + } else { |
| 314 | + /* Power button released, cancel deferred shutdown */ |
| 315 | + hook_call_deferred(&chipset_force_shutdown_data, -1); |
| 316 | + } |
| 317 | +} |
| 318 | +DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, power_button_changed, HOOK_PRIO_DEFAULT); |
| 319 | + |
| 320 | +#ifdef CONFIG_LID_SWITCH |
| 321 | +static void lid_changed(void) |
| 322 | +{ |
| 323 | + /* Power-up from off on lid open */ |
| 324 | + if (lid_is_open() && chipset_in_state(CHIPSET_STATE_ANY_OFF)) |
| 325 | + chipset_exit_hard_off(); |
| 326 | +} |
| 327 | +DECLARE_HOOK(HOOK_LID_CHANGE, lid_changed, HOOK_PRIO_DEFAULT); |
| 328 | +#endif |
0 commit comments