1643 lines
46 KiB
C
1643 lines
46 KiB
C
/*
|
|
* Copyright (C) 2008-2013 Apple Inc. All rights reserved.
|
|
*
|
|
* This document is the property of Apple Inc.
|
|
* It is considered confidential and proprietary.
|
|
*
|
|
* This document may not be reproduced or transmitted in any form,
|
|
* in whole or in part, without the express written permission of
|
|
* Apple Inc.
|
|
*/
|
|
#include <debug.h>
|
|
#include <drivers/iic.h>
|
|
#include <drivers/power.h>
|
|
#include <drivers/usb/usb_public.h>
|
|
#include <drivers/gasgauge.h>
|
|
#include <drivers/charger.h>
|
|
#include <drivers/tristar.h>
|
|
#include <lib/paint.h>
|
|
#include <lib/syscfg.h>
|
|
#include <platform.h>
|
|
#include <platform/gpiodef.h>
|
|
#include <platform/miu.h>
|
|
#include <target.h>
|
|
#include <sys.h>
|
|
#include <sys/boot.h>
|
|
#include <sys/menu.h>
|
|
#include <sys/task.h>
|
|
#include <lib/nvram.h>
|
|
|
|
#define POWERCONFIG_CHARGE_TABLE 1
|
|
#include <target/powerconfig.h>
|
|
|
|
#ifndef NUM_DOCKS
|
|
#define NUM_DOCKS (1)
|
|
#endif
|
|
|
|
enum {
|
|
kPowerSupplyBattery = 1,
|
|
kPowerSupplyFirewire = 2,
|
|
kPowerSupplyUSBHost = 3,
|
|
kPowerSupplyUSBBrick = 4,
|
|
kPowerSupplyUSBCharger = 5,
|
|
kPowerSupplyExternal = 6,
|
|
kPowerSupplyNum
|
|
};
|
|
|
|
bool force_usb_power = false;
|
|
|
|
static unsigned int boot_battery_level = -1U;
|
|
static bool need_precharge;
|
|
static bool need_buttonwait;
|
|
static int power_supply_type[NUM_DOCKS];
|
|
static int usb_brick_id[NUM_DOCKS];
|
|
static uint32_t available_charge_current;
|
|
|
|
static int battery_delta_target;
|
|
|
|
static const char * kPowerSupplyNames[kPowerSupplyNum] = {
|
|
"error", "batt", "firewire", "usb host", "usb brick", "usb charger", "external" };
|
|
|
|
static bool
|
|
power_set_usb_brick_detect(int dock, int select)
|
|
{
|
|
#if WITH_HW_TRISTAR
|
|
return tristar_set_usb_brick_detect(select);
|
|
#elif WITH_HW_CHARGER
|
|
return charger_set_usb_brick_detect(dock, select);
|
|
#else
|
|
return platform_set_usb_brick_detect(select);
|
|
#endif
|
|
}
|
|
|
|
static unsigned
|
|
power_detect_usb_brick(int dock)
|
|
{
|
|
static const int dM = 0;
|
|
static const int dP = 1;
|
|
static const int dNum = 2;
|
|
|
|
uint32_t dataVoltage[dNum], dataID[dNum], ratio;
|
|
uint32_t brickID;
|
|
|
|
usb_brick_id[dock] = 0;
|
|
|
|
for (int idx = dM; idx < dNum; idx++)
|
|
{
|
|
uint32_t dMon = (idx == dP) ? kUSB_DP : kUSB_DM;
|
|
|
|
power_set_usb_brick_detect(dock, dMon);
|
|
spin(2 * 1000);
|
|
dataVoltage[idx] = pmu_read_brick_id_level();
|
|
|
|
// Thresholds based on N20 code, adjusted for 5V scale
|
|
if (dataVoltage[idx] <= 2220) {
|
|
dataID[idx] = 0;
|
|
} else if (dataVoltage[idx] <= 2890) {
|
|
dataID[idx] = 1;
|
|
} else {
|
|
dataID[idx] = 2;
|
|
}
|
|
|
|
power_set_usb_brick_detect(dock, kUSB_NONE);
|
|
}
|
|
|
|
ratio = (dataVoltage[dP] == 0) ? 0 : (dataVoltage[dM] * 1000 / dataVoltage[dP]);
|
|
|
|
// First apply legacy ratio-based detection for 1A and 2A bricks to ensure
|
|
// detection of all bricks correctly identified by previous products.
|
|
if ((ratio > 1290) && (ratio <= 1505))
|
|
{
|
|
// Ratio 1.34
|
|
brickID = 2;
|
|
}
|
|
else if ((ratio > 664) && (ratio <= 775))
|
|
{
|
|
// Ratio 0.75
|
|
brickID = 4;
|
|
}
|
|
else
|
|
{
|
|
// New (N20-based) brick detection method
|
|
brickID = 1 + (3 * dataID[dP]) + dataID[dM];
|
|
}
|
|
|
|
if (NUM_DOCKS > 1) dprintf(DEBUG_INFO, "dock %d ", dock);
|
|
dprintf(DEBUG_INFO, "usb voltage dP %d, dM %d, ratio %d id %d\n", dataVoltage[dP] , dataVoltage[dM], ratio, brickID);
|
|
|
|
if ((dataVoltage[dM] < 1000) || (dataVoltage[dP] < 1000))
|
|
{
|
|
if (power_set_usb_brick_detect(dock, kUSB_CP1)) {
|
|
uint32_t dv;
|
|
|
|
// must wait 40ms for a CDP to assert DM voltage source
|
|
task_sleep(40 * 1000);
|
|
|
|
dv = pmu_read_brick_id_level();
|
|
|
|
power_set_usb_brick_detect(dock, kUSB_NONE);
|
|
|
|
if (NUM_DOCKS > 1) dprintf(DEBUG_INFO, "dock %d ", dock);
|
|
dprintf(DEBUG_INFO, "usb charger detect voltage dM %d\n", dv);
|
|
|
|
// if we see 0.25 to 0.8V, it is a USB charger (at this point, we
|
|
// don't try to distinguish DCP from CDP)
|
|
if (dv > 250 && dv < 800) {
|
|
return kPowerSupplyUSBCharger;
|
|
}
|
|
}
|
|
|
|
return (kPowerSupplyUSBHost);
|
|
}
|
|
|
|
if ((ratio < 500) || (ratio > 2000))
|
|
return (kPowerSupplyUSBHost);
|
|
|
|
usb_brick_id[dock] = brickID;
|
|
return kPowerSupplyUSBBrick;
|
|
}
|
|
|
|
static void
|
|
power_determine_power_supply(void)
|
|
{
|
|
for (int dock = 0; dock < NUM_DOCKS; dock++) {
|
|
int powerSupplyType;
|
|
|
|
if (charger_has_external(dock))
|
|
{
|
|
powerSupplyType = kPowerSupplyExternal;
|
|
}
|
|
else if (charger_has_usb(dock))
|
|
{
|
|
if (usb_brick_id[dock] != 0) {
|
|
powerSupplyType = kPowerSupplyUSBBrick;
|
|
} else {
|
|
powerSupplyType = power_detect_usb_brick(dock);
|
|
}
|
|
}
|
|
else if (charger_has_firewire(dock)) {
|
|
powerSupplyType = kPowerSupplyFirewire;
|
|
}
|
|
else {
|
|
powerSupplyType = kPowerSupplyBattery;
|
|
usb_brick_id[dock] = 0;
|
|
}
|
|
|
|
if (NUM_DOCKS > 1) printf("dock %d ", dock);
|
|
printf("power supply type %s", kPowerSupplyNames[powerSupplyType] );
|
|
if (powerSupplyType == kPowerSupplyUSBBrick) {
|
|
printf(" id %d", usb_brick_id[dock] );
|
|
}
|
|
printf("\n");
|
|
|
|
if (powerSupplyType != power_supply_type[dock])
|
|
{
|
|
power_supply_type[dock] = powerSupplyType;
|
|
charger_clear_usb_state();
|
|
}
|
|
}
|
|
|
|
#ifdef GPIO_USB_MUX_SEL
|
|
if (NUM_DOCKS > 1)
|
|
{
|
|
bool mux = (power_supply_type[0] != kPowerSupplyUSBHost) && (power_supply_type[1] == kPowerSupplyUSBHost);
|
|
gpio_write(GPIO_USB_MUX_SEL, mux);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// will return charger_get_max_charge_current() unless we have an ATV table.
|
|
// not much of a limiting... I guess...
|
|
static UInt16
|
|
limit_precharge_current(bool charging)
|
|
{
|
|
UInt16 charge_current_limit;
|
|
|
|
charge_current_limit = 0;
|
|
for (int dock = 0; dock < NUM_DOCKS; dock++) {
|
|
charge_current_limit += charger_get_max_charge_current(dock);
|
|
}
|
|
|
|
if (!charging) {
|
|
// If the charge rate is too low to need limiting, don't bother. This avoids needing to
|
|
// talk to talk to the gas gauge or measure temperature when waking from sleep (7062950)
|
|
return charge_current_limit;
|
|
}
|
|
|
|
if (!power_has_batterypack())
|
|
return 0;
|
|
|
|
#if TARGET_USE_CHARGE_TABLE
|
|
// limit charge current in low temperature
|
|
unsigned int milliVolts = power_get_battery_level();
|
|
|
|
int centiCelsius;
|
|
#if WITH_HW_GASGAUGE
|
|
int error = gasgauge_read_temperature(¢iCelsius);
|
|
#else
|
|
int error = charger_read_battery_temperature(¢iCelsius);
|
|
#endif
|
|
if (!error) {
|
|
// Set charge current to max allowable charge current.
|
|
UInt16 limit = 0; // Default - suspend. If temp is higher than table can catch
|
|
const struct power_charge_limits *charge_table = pmu_charge_table;
|
|
static const size_t num_charge_limits = sizeof(pmu_charge_table)/sizeof(struct power_charge_limits);
|
|
|
|
#if WITH_HW_GASGAUGE && TARGET_USE_CHARGE_TABLE
|
|
static struct power_charge_limits gg_charge_table[num_charge_limits];
|
|
static bool chargetable_read = false;
|
|
if (!chargetable_read) {
|
|
chargetable_read = gasgauge_read_charge_table(gg_charge_table, num_charge_limits);
|
|
}
|
|
if (chargetable_read) {
|
|
charge_table = gg_charge_table;
|
|
}
|
|
#endif
|
|
|
|
// Retain last-used charge limit; only switch if no longer applicable
|
|
static const struct power_charge_limits *active_limit = NULL;
|
|
const struct power_charge_limits *tl = charge_table;
|
|
|
|
const struct power_charge_limits *new_limit = NULL;
|
|
while (tl < &charge_table[num_charge_limits]) {
|
|
if((milliVolts > tl->upperVoltageLimit) ||
|
|
(centiCelsius < tl->lowerTempLimit) ||
|
|
(centiCelsius > tl->upperTempLimit)) {
|
|
tl++;
|
|
continue;
|
|
}
|
|
new_limit = tl;
|
|
break;
|
|
}
|
|
|
|
// switch to the new limit unless the voltage range is the same and
|
|
// we are still within the temperature range of the old limit
|
|
if ((new_limit == NULL) || (active_limit == NULL)
|
|
|| (new_limit->upperVoltageLimit != active_limit->upperVoltageLimit)
|
|
|| (centiCelsius < active_limit->lowerTempLimit)
|
|
|| (centiCelsius > active_limit->upperTempLimit))
|
|
{
|
|
active_limit = new_limit;
|
|
}
|
|
|
|
if (active_limit != NULL) {
|
|
limit = active_limit->currentSetting;
|
|
}
|
|
|
|
if (charge_current_limit > limit) {
|
|
charge_current_limit = limit;
|
|
dprintf(DEBUG_INFO, "Precharge: Battery temp %dcC voltage %dmV, limiting charge current: %dmA\n", centiCelsius, milliVolts, charge_current_limit);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return charge_current_limit;
|
|
}
|
|
|
|
uint32_t power_get_available_charge_current()
|
|
{
|
|
return available_charge_current;
|
|
}
|
|
|
|
#if TARGET_HAS_SMARTPORT
|
|
// orion centric BUT it might work with bellatrix too
|
|
int smartport_get_data(int dev, uint16_t reg, uint8_t *byte)
|
|
{
|
|
const UInt8 addr[1]={ reg&0xff };
|
|
return iic_read(dev, 0xe0, addr, sizeof(addr), byte, 1, IIC_NORMAL);
|
|
}
|
|
|
|
//
|
|
int smartport_get_pwr_in_sel(int dev, uint8_t *sel)
|
|
{
|
|
int rc=smartport_get_data(dev, 0x16, sel);
|
|
if ( rc<0 ) return rc;
|
|
|
|
switch ( *sel&(0x3<<3) ) {
|
|
case 0x10: *sel=SMARTPORT_EXT_PWR_IN_SEL; break; // ext
|
|
case 0x08: *sel=SMARTPORT_ACC_PWR_IN_SEL; break; // acc
|
|
default: rc=-2; break; // not known
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#else
|
|
int smartport_get_data(int dev, uint16_t reg, uint8_t *byte) { return -1; };
|
|
int smartport_get_pwr_in_sel(int dev, uint8_t *sel) { return -1; };
|
|
#endif
|
|
|
|
static bool
|
|
power_set_charging(bool critical, bool configured, bool suspended,
|
|
bool pause)
|
|
{
|
|
dprintf(DEBUG_SPEW, "power_set_charging(critical=%d, configured=%d, suspended=%d, pause=%d)\n",
|
|
critical, configured, suspended, pause);
|
|
|
|
bool ok = false;
|
|
|
|
int max_input_current_limit_dock = 0;
|
|
uint32_t dock_input_current_limit[NUM_DOCKS]={ 0 };
|
|
|
|
for (int dock = 0; dock < NUM_DOCKS; dock++)
|
|
{
|
|
|
|
if ((power_supply_type[dock] == kPowerSupplyUSBHost) && !critical && suspended) {
|
|
dock_input_current_limit[dock] = 0;
|
|
}
|
|
else if (power_supply_type[dock] == kPowerSupplyUSBBrick) {
|
|
switch (usb_brick_id[dock]) {
|
|
case 1:
|
|
dock_input_current_limit[dock] = 500;
|
|
break;
|
|
|
|
case 2:
|
|
dock_input_current_limit[dock] = 1000;
|
|
break;
|
|
|
|
case 4:
|
|
dock_input_current_limit[dock] = 2100;
|
|
break;
|
|
|
|
case 5:
|
|
dock_input_current_limit[dock] = 2400;
|
|
break;
|
|
|
|
default:
|
|
// reserved brick IDs default to 1A
|
|
dock_input_current_limit[dock] = 1000;
|
|
break;
|
|
}
|
|
}
|
|
else if (power_supply_type[dock] == kPowerSupplyUSBCharger) {
|
|
// limit USB charger to 1A even though spec allows 1500mA (13332167)
|
|
dock_input_current_limit[dock] = 1000;
|
|
}
|
|
else if ((power_supply_type[dock] == kPowerSupplyUSBHost)
|
|
&& (force_usb_power || configured || critical)) {
|
|
dock_input_current_limit[dock] = 500;
|
|
}
|
|
else {
|
|
dock_input_current_limit[dock] = 100;
|
|
}
|
|
|
|
if (power_supply_type[dock] >= kPowerSupplyUSBHost) {
|
|
ok = true;
|
|
}
|
|
|
|
if (dock_input_current_limit[dock] > dock_input_current_limit[max_input_current_limit_dock]) {
|
|
max_input_current_limit_dock = dock;
|
|
}
|
|
|
|
}
|
|
|
|
available_charge_current=0;
|
|
|
|
uint32_t charge_current_limit = 0;
|
|
if ( !pause ) {
|
|
available_charge_current = dock_input_current_limit[max_input_current_limit_dock];
|
|
charge_current_limit = limit_precharge_current( available_charge_current > 100 ); // might not limit at all
|
|
|
|
#if TARGET_HAS_SMARTPORT
|
|
uint8_t sel;
|
|
int rc=smartport_get_pwr_in_sel(1, &sel);
|
|
if ( rc<0 ) {
|
|
} else if ( sel==SMARTPORT_ACC_PWR_IN_SEL ) { // accessory
|
|
available_charge_current=500; // <rdar://problem/21289146> J99a Orion power-in support for iBoot
|
|
}
|
|
#endif
|
|
}
|
|
// charger_set_charging will subtract from charge_current_limit, and we may need to
|
|
// spread the limit across multiple docks. So start with the one with the highest
|
|
// current limit, since it is most efficient to use the local power.
|
|
for (int i = 0; i < NUM_DOCKS; i++)
|
|
{
|
|
int dock = (i + max_input_current_limit_dock) % NUM_DOCKS; // start with highest current limit
|
|
|
|
// available_charge_current is the MAX unless I have SMARTPORT
|
|
uint32_t docklimit=dock_input_current_limit[dock];
|
|
if ( (docklimit>available_charge_current) && available_charge_current) docklimit=available_charge_current;
|
|
|
|
charger_set_charging(dock, docklimit, &charge_current_limit);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
static bool power_charge_done()
|
|
{
|
|
if (!power_has_batterypack())
|
|
return false;
|
|
|
|
#if WITH_HW_GASGAUGE
|
|
return gasgauge_full();
|
|
#else
|
|
for (int dock = 0; dock < NUM_DOCKS; dock++) {
|
|
if ((power_supply_type[dock] >= kPowerSupplyUSBHost) && !charger_charge_done(dock))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
bool
|
|
power_enable_charging(bool inflow, bool charging)
|
|
{
|
|
return power_set_charging(need_precharge, false, !inflow, !charging);
|
|
}
|
|
|
|
void
|
|
power_set_usb_state(bool configured, bool suspended)
|
|
{
|
|
charger_clear_usb_state();
|
|
power_set_charging(false, configured, suspended, false);
|
|
}
|
|
|
|
void power_set_usb_enabled(bool enabled)
|
|
{
|
|
#if WITH_HW_TRISTAR
|
|
if (enabled) {
|
|
// if there is a B139/B164 attached, provide it power (11991279)
|
|
uint8_t digital_id[6];
|
|
if (tristar_read_id(digital_id)) {
|
|
// if accessory could be a USB host (USB=2 or USB=3) and needs power for USB (PS bit), enable power
|
|
if ((digital_id[0] & (1 << 4)) && (digital_id[2] & (1 << 7))) {
|
|
tristar_enable_acc_pwr(true);
|
|
}
|
|
}
|
|
} else {
|
|
tristar_enable_acc_pwr(false);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// same as gasgauge_needs_precharge(), reset the charger when needed
|
|
// @return 1 when needs precharge, 0 when it doesn't
|
|
static int charger_needs_precharge(int debug_print_level, bool debug_show_target)
|
|
{
|
|
int state;
|
|
|
|
state=gasgauge_needs_precharge(debug_print_level, debug_show_target);
|
|
if ( state&GG_COMMS_WD ) {
|
|
dprintf(debug_print_level, "comms wd\n");
|
|
|
|
// TODO: recovery procedure?
|
|
|
|
} else if ( state&GG_NEEDS_RESET ) {
|
|
const int rc=gasgauge_reset_timer(debug_print_level, 8 ); // 8 seconds timeout
|
|
if ( rc!=0 ) dprintf(debug_print_level, "reset charge timer failed (%d)", rc);
|
|
}
|
|
|
|
charger_print_status();
|
|
|
|
return (state&GG_NEEDS_PRECHARGE)!=0;
|
|
}
|
|
|
|
/* power_init() is only called in iBSS and LLB - not in iBoot or iBEC. */
|
|
int
|
|
power_init(void)
|
|
{
|
|
u_int8_t data;
|
|
|
|
pmu_early_init();
|
|
|
|
if (!power_is_suspended()) {
|
|
#if WITH_HW_CHARGER
|
|
charger_early_init();
|
|
#endif
|
|
|
|
#if TARGET_POWER_NO_BATTERY
|
|
need_precharge = power_needs_precharge();
|
|
#else
|
|
/*
|
|
* Use two different heurestics to determine whether the unit has a working battery installed:
|
|
* - no_battery_power: if the voltage is very low, there may be no battery present (or it may be very drained)
|
|
* - no_battery_pack: if the pack temperature reads very low, there may be no NTC and hence no battery (or it may be very cold)
|
|
*
|
|
* In the case where no battery pack is present, we want to avoid either trying to talk
|
|
* to the gas gauge (which may not be present) or sitting in the battery trap (which would
|
|
* not be charging a real battery). There are two different types of no-battery situations, though:
|
|
* If nothing is connected to the battery terminals, both no_battery_power and no_battery_pack will be
|
|
* true. More likely (on a dev board or in the factory) we will have a 4V supply attached to the
|
|
* battery terminals, but NTC and gas gauge will still be disconnected, so no_battery_power will be
|
|
* false but no_battery_pack will be true.
|
|
*
|
|
* In the latter case, the battery voltage is always well above 3.6V, so only bypass the voltage check
|
|
* if both indications (no_battery_power and no_battery_pack) are set. Otherwise, we check
|
|
* for a low battery voltage. On a gas gauge-based device, we can therefore assume it's safe to
|
|
* check with the gas gauge even when no_battery_power is set, if the voltage is in this range (see
|
|
* below). On a device with no gas gauge, the voltage check is the only determination of whether
|
|
* is necessary, so we want to be conservative and only skip a battery voltage that would otherwise
|
|
* lead to precharge only if both no_battery_power and no_battery_pack are set.
|
|
*
|
|
* However, since both of these values can be true even when there is a battery, we try to avoid
|
|
* making any permanent decisions based on them that will affect the user's ability to use
|
|
* the unit later. We also want to avoid a poor user experience for a user that happens to have
|
|
* a very dead or very cold battery, although if they have both, there's little we can do
|
|
* to distinguish this from a dev board or factory situation.
|
|
*
|
|
* Note that a "very cold" battery that triggers no_battery_pack is always well below the point
|
|
* at which the battery is too cold to charge safely. So battery trap would not necessarily
|
|
* help the user recover anyway (although sitting in the trap can warm up the unit simply by
|
|
* having the CPU, screen, backlight on).
|
|
*/
|
|
bool no_battery_power, no_battery_pack;
|
|
|
|
no_battery_pack = !power_has_batterypack();
|
|
|
|
// read boot battery voltage with minimal current being drawn, charger disabled
|
|
power_set_charging(false, false, false, true);
|
|
dprintf(DEBUG_INFO, "battery voltage ");
|
|
|
|
if (!charger_read_battery_level(&boot_battery_level))
|
|
dprintf(DEBUG_INFO, "%d mV\n", boot_battery_level);
|
|
else
|
|
dprintf(DEBUG_INFO, "error\n");
|
|
power_set_charging(false, false, false, false);
|
|
|
|
no_battery_power = (boot_battery_level < NO_BATTERY_VOLTAGE);
|
|
need_precharge = (!no_battery_power || !no_battery_pack) && (boot_battery_level < MIN_BOOT_BATTERY_VOLTAGE);
|
|
|
|
#if WITH_HW_GASGAUGE
|
|
/*
|
|
* If no_battery_pack is set, need_precharge will still be set if
|
|
* NO_BATTERY_VOLTAGE < boot_battery_level < MIN_BOOT_BATTERY_VOLTAGE. This means
|
|
* while a very cold, very dead, battery can still bypass battery trap, a
|
|
* very cold, mostly dead, battery will not.
|
|
*
|
|
* If the voltage falls in this range, assume we have a real battery and talk to
|
|
* the gas gauge anyway; a fake battery from an external supply should always
|
|
* be well above MIN_BOOT_BATTERY_VOLTAGE (3.4-3.6V).
|
|
*
|
|
* We do not talk to the gas gauge if we're waking from sleep and it's unlikely
|
|
* the battery is low: it will delay wakeup by a measurable amount, and wiggling
|
|
* the SWI line will confuse the PMU when it looks for wakeup events.
|
|
*/
|
|
if (!no_battery_pack || need_precharge) {
|
|
gasgauge_init();
|
|
|
|
// iOS or charge trap will reset the charge timer if needed
|
|
need_precharge = ( gasgauge_needs_precharge(DEBUG_INFO, true)&GG_NEEDS_PRECHARGE) !=0 ;
|
|
}
|
|
#endif
|
|
|
|
if (power_get_nvram(kPowerNVRAMiBootDebugKey, &data) == 0 && (data & kPowerNVRAMiBootDebugBatteryTrap) != 0) {
|
|
#if TARGET_FORCE_DEBUG_PRECHARGE
|
|
need_precharge = true;
|
|
#elif !RELEASE_BUILD && defined(GPIO_RINGER_AB)
|
|
if (gpio_read(GPIO_RINGER_AB)) {
|
|
need_precharge = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
power_determine_power_supply();
|
|
power_set_charging(need_precharge || no_battery_power, false, false, no_battery_power && no_battery_pack);
|
|
#endif
|
|
|
|
// store boot voltage and brick ID for iBoot. Keep backwards compatible with old
|
|
// LLB that stored boot voltage in mV: mV truncated to cV with brick ID encoded in lowest
|
|
// order decimal digit, encoded in bits [12:0]. Bits [15:14] indicate which dock is
|
|
// in use; 1-indexed with zero meaning none. Reserved: bit 13 reserved, "brick ID" zero
|
|
UInt16 boot_battery_data = ((boot_battery_level / 10) * 10) & 0x1FFF;
|
|
#if NUM_DOCKS > 3
|
|
#error too many docks
|
|
#endif
|
|
for (int dock = 0; dock < NUM_DOCKS; dock++) {
|
|
if (power_supply_type[dock] == kPowerSupplyUSBBrick) {
|
|
boot_battery_data |= (dock+1) << 14;
|
|
boot_battery_data += usb_brick_id[dock] % 10;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// pass boot voltage between LLB and iBoot
|
|
power_set_nvram(kPowerNVRAMiBootBootFlags0Key, boot_battery_data & 0xFF);
|
|
power_set_nvram(kPowerNVRAMiBootBootFlags1Key, (boot_battery_data >> 8) & 0xFF);
|
|
|
|
// this allow charging via ISET
|
|
charger_clear_alternate_usb_current_limit();
|
|
}
|
|
|
|
power_get_nvram(kPowerNVRAMiBootStateKey, &data);
|
|
need_buttonwait = ((data & kPowerNVRAMiBootStateModeMask) == kPowerNVRAMiBootStateModeButtonwaitWithGraphics)
|
|
|| ((data & kPowerNVRAMiBootStateModeMask) == kPowerNVRAMiBootStateModeButtonwaitNoGraphics);
|
|
if (need_precharge)
|
|
data |= kPowerNVRAMiBootStatePrecharge;
|
|
else
|
|
data &= ~kPowerNVRAMiBootStatePrecharge;
|
|
power_set_nvram(kPowerNVRAMiBootStateKey, data);
|
|
|
|
dprintf(DEBUG_INFO, "need precharge %d set nvram %02x\n", need_precharge, data);
|
|
|
|
pmu_setup();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
power_late_init(void)
|
|
{
|
|
pmu_late_init();
|
|
#if WITH_HW_GASGAUGE
|
|
gasgauge_late_init();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
power_needs_precharge(void)
|
|
{
|
|
#if TARGET_POWER_NO_BATTERY
|
|
// since power_init() doesn't get called from regular iBoot, this is "the place" to set up
|
|
// iBoot properly (we call it from power_init if TARGET_POWER_NO_BATTERY).
|
|
// Make sure we have the right power supply type, and force USB power enabled, as if we were
|
|
// in precharge on a battery-powered device.
|
|
if (-1U == boot_battery_level) {
|
|
boot_battery_level = 0;
|
|
force_usb_power = true;
|
|
power_determine_power_supply();
|
|
power_set_charging(true, false, false, false);
|
|
}
|
|
|
|
return (false);
|
|
#elif PRODUCT_IBSS || PRODUCT_IBEC
|
|
return (false);
|
|
#elif PRODUCT_LLB
|
|
return (need_precharge);
|
|
#else
|
|
if (need_precharge) return (true);
|
|
|
|
u_int8_t data = 0;
|
|
power_get_nvram(kPowerNVRAMiBootStateKey, &data);
|
|
need_precharge = (0 != (kPowerNVRAMiBootStatePrecharge & data));
|
|
need_buttonwait = ((data & kPowerNVRAMiBootStateModeMask) == kPowerNVRAMiBootStateModeButtonwaitWithGraphics)
|
|
|| ((data & kPowerNVRAMiBootStateModeMask) == kPowerNVRAMiBootStateModeButtonwaitNoGraphics);
|
|
if (need_precharge || (-1U == boot_battery_level)) {
|
|
bool no_battery_power;
|
|
|
|
// see encoding table in power_init
|
|
power_get_nvram(kPowerNVRAMiBootBootFlags1Key, &data);
|
|
int dock = (data & 0xC0) >> 6;
|
|
boot_battery_level = ((data & 0x1F) << 8);
|
|
power_get_nvram(kPowerNVRAMiBootBootFlags0Key, &data);
|
|
if (dock > 0) usb_brick_id[dock-1] = ((boot_battery_level | data) % 10);
|
|
boot_battery_level = ((boot_battery_level | data) / 10) * 10;
|
|
|
|
printf("battery voltage %d mV\n", boot_battery_level);
|
|
|
|
if ((boot_battery_level + 50) > TARGET_BOOT_BATTERY_VOLTAGE)
|
|
battery_delta_target = 50; // charge at least 50 mV
|
|
else
|
|
battery_delta_target = TARGET_BOOT_BATTERY_VOLTAGE - boot_battery_level;
|
|
no_battery_power = (boot_battery_level < NO_BATTERY_VOLTAGE);
|
|
|
|
power_determine_power_supply();
|
|
power_set_charging(need_precharge || no_battery_power, false, false, no_battery_power && !power_has_batterypack());
|
|
}
|
|
|
|
return (need_precharge);
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
power_needs_thermal_trap(void)
|
|
{
|
|
u_int8_t data = 0;
|
|
power_get_nvram(kPowerNVRAMiBootStateKey, &data);
|
|
return data & kPowerNVRAMiBootStateThermalTrap;
|
|
}
|
|
|
|
void
|
|
power_cancel_buttonwait(void)
|
|
{
|
|
if (need_buttonwait) {
|
|
u_int8_t data = 0;
|
|
power_get_nvram(kPowerNVRAMiBootStateKey, &data);
|
|
data &= ~kPowerNVRAMiBootStateModeMask;
|
|
data |= kPowerNVRAMiBootStateModeNormalBoot;
|
|
power_set_nvram(kPowerNVRAMiBootStateKey, data);
|
|
|
|
need_buttonwait = false;
|
|
}
|
|
}
|
|
|
|
u_int32_t
|
|
power_get_boot_battery_level(void)
|
|
{
|
|
return (boot_battery_level);
|
|
}
|
|
|
|
void
|
|
power_get_usb_brick_id(uint32_t *id, size_t count)
|
|
{
|
|
for (unsigned dock = 0; dock < NUM_DOCKS && dock < count; dock++) {
|
|
if (power_supply_type[dock] == kPowerSupplyUSBBrick) {
|
|
id[dock] = usb_brick_id[dock];
|
|
} else {
|
|
id[dock] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
u_int32_t
|
|
power_get_battery_level(void)
|
|
{
|
|
uint32_t voltage;
|
|
|
|
#if WITH_HW_GASGAUGE
|
|
if (gasgauge_read_voltage(&voltage) != 0)
|
|
return 0;
|
|
#else
|
|
if (charger_read_battery_level(&voltage) != 0)
|
|
return 0;
|
|
#endif
|
|
|
|
return voltage;
|
|
}
|
|
|
|
bool
|
|
power_has_batterypack(void)
|
|
{
|
|
return charger_has_batterypack(0);
|
|
}
|
|
|
|
enum
|
|
{
|
|
kCheckBatteryInterval = 5 * 1000 * 1000, // 5s
|
|
kCheckPowerSupplyInterval = 100 * 1000, // 100ms
|
|
kChargingAnimationFrameInterval = 1 * 1000 * 1000, // 1s
|
|
kHomeButtonPoweronTimeout = 400 * 1000, // 400ms
|
|
kPrechargeDisplayDimTimeout = 8 * 1000 * 1000, // 8s
|
|
};
|
|
|
|
enum chargetrap_type_t
|
|
{
|
|
kChargetrapPrecharge = 1,
|
|
kChargetrapButtonwaitWithGraphics = 2,
|
|
kChargetrapButtonwaitNoGraphics = 3,
|
|
};
|
|
|
|
enum chargetrap_return_t
|
|
{
|
|
kChargetrapReturnSuccess = 0, // conditions of precharge were met
|
|
kChargetrapReturnNoCharger = 1, // no charger attached
|
|
kChargetrapReturnChargerRemoved = 2, // charger was attached, and then removed
|
|
};
|
|
|
|
// round up when charging (same as SpringBoard)...
|
|
static int
|
|
power_battery_level(unsigned int capacity, unsigned int total_capacity)
|
|
{
|
|
int level = (((IMAGE_TYPE_BATTERYFULL_N_TOTAL-1) * capacity) + (total_capacity-1)) / total_capacity;
|
|
|
|
// ... only show full battery if completely full...
|
|
if ((level == IMAGE_TYPE_BATTERYFULL_N_FULL) && (capacity < total_capacity)) level--;
|
|
|
|
// ... and never show the "empty" level...
|
|
if (level == 0) level++;
|
|
|
|
#if !TARGET_PRECHARGE_ALWAYS_DISPLAY_IDLE
|
|
// if iPhone, always show red battery until 20%
|
|
if ((level > IMAGE_TYPE_BATTERYFULL_N_RED) && ((5 * capacity) <= total_capacity)) level = IMAGE_TYPE_BATTERYFULL_N_RED;
|
|
#endif
|
|
|
|
return level;
|
|
}
|
|
|
|
static bool
|
|
power_paint_battery_with_capacity(bool precharge, int frameCount, unsigned int capacity, unsigned int total_capacity)
|
|
{
|
|
bool ok = true;
|
|
|
|
static int current_level = -1;
|
|
|
|
int level = power_battery_level(capacity, total_capacity);
|
|
|
|
dprintf(DEBUG_SPEW, "power_paint_battery: %d capacity %d/%d\n", level, capacity, total_capacity);
|
|
|
|
if ((level == current_level) && (frameCount > 0)) {
|
|
return true;
|
|
}
|
|
|
|
paint_set_picture(0);
|
|
|
|
bool batteryImage = true;
|
|
#if !WITH_HW_GASGAUGE
|
|
batteryImage = precharge || level == IMAGE_TYPE_BATTERYFULL_N_FULL;
|
|
#endif
|
|
if (!batteryImage) {
|
|
if (((frameCount / (kChargingAnimationFrameInterval / kCheckPowerSupplyInterval)) & 1) == 0) {
|
|
ok = !paint_set_picture_for_tag(IMAGE_TYPE_BATTERYCHARGING0);
|
|
} else {
|
|
ok = !paint_set_picture_for_tag(IMAGE_TYPE_BATTERYCHARGING1);
|
|
}
|
|
} else {
|
|
ok = !paint_set_picture_for_tag(IMAGE_TYPE_BATTERYLOW0);
|
|
if (ok) {
|
|
ok = !paint_set_picture_for_tag(IMAGE_TYPE_BATTERYLOW1);
|
|
if (ok && level >= IMAGE_TYPE_BATTERYFULL_N_START) {
|
|
ok = !paint_set_picture_for_tag_list(IMAGE_TYPE_BATTERYFULL, 0, 1 + level - IMAGE_TYPE_BATTERYFULL_N_START);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ok) {
|
|
// Default to yellow if there are no pictures.
|
|
paint_set_bgcolor(255, 255, 0);
|
|
paint_set_picture(0);
|
|
}
|
|
|
|
paint_update_image();
|
|
|
|
current_level = level;
|
|
|
|
return ok;
|
|
}
|
|
|
|
static bool
|
|
power_paint_battery(bool precharge, int frameCount)
|
|
{
|
|
unsigned int capacity, total_capacity;
|
|
|
|
if (precharge) {
|
|
// no need to ask gas gauge
|
|
capacity = 0;
|
|
total_capacity = 1;
|
|
} else {
|
|
#if WITH_HW_GASGAUGE
|
|
total_capacity=100;
|
|
|
|
if (gasgauge_read_soc(&capacity) != 0) {
|
|
// gas gauge error; leave screen as is
|
|
return true;
|
|
}
|
|
#else
|
|
total_capacity = 1;
|
|
capacity = power_charge_done() ? 0 : 1;
|
|
#endif
|
|
}
|
|
|
|
return power_paint_battery_with_capacity(precharge, frameCount, capacity, total_capacity);
|
|
}
|
|
|
|
|
|
static enum chargetrap_return_t
|
|
charger_precharge(enum chargetrap_type_t chargetrap_type)
|
|
{
|
|
bool ok;
|
|
int pollCount;
|
|
unsigned startBatteryLevel, newBatteryLevel, lastChargingBatteryLevel;
|
|
utime_t buttonpress_time = 0;
|
|
utime_t display_idle_time;
|
|
int frameCount = 0;
|
|
bool animate, color_map_state;
|
|
const char *chargetrap_label;
|
|
bool test_buttonwait_with_ringer = false;
|
|
uint8_t data;
|
|
|
|
if (chargetrap_type != kChargetrapButtonwaitNoGraphics) {
|
|
power_set_usb_enabled(true);
|
|
}
|
|
|
|
// check connection type before starting
|
|
power_determine_power_supply();
|
|
|
|
charger_clear_usb_state();
|
|
|
|
ok = power_set_charging(true, false, false, false);
|
|
if (!ok) {
|
|
|
|
#if WITH_HW_GASGAUGE
|
|
// if precharge and no charger, check gas gauge health once anyway
|
|
// if charger is attached, we do this later in a more methodical manner.
|
|
static bool gg_health_checked = false;
|
|
if (!gg_health_checked && (chargetrap_type == kChargetrapPrecharge)) {
|
|
gasgauge_check_health(boot_battery_level);
|
|
gg_health_checked = true;
|
|
}
|
|
#endif
|
|
|
|
return kChargetrapReturnNoCharger;
|
|
}
|
|
|
|
|
|
#if !RELEASE_BUILD || TARGET_FORCE_DEBUG_PRECHARGE
|
|
if (power_get_nvram(kPowerNVRAMiBootDebugKey, &data) == 0) {
|
|
test_buttonwait_with_ringer = (data & kPowerNVRAMiBootDebugBatteryTrap) != 0;
|
|
} else {
|
|
printf(" Error: cannot read kPowerNVRAMiBootDebugKey\n");
|
|
}
|
|
#endif
|
|
|
|
|
|
if ( test_buttonwait_with_ringer ) {
|
|
chargetrap_label = "debug precharge";
|
|
} else if (chargetrap_type == kChargetrapPrecharge) {
|
|
chargetrap_label = "low-battery precharge";
|
|
} else {
|
|
chargetrap_label = "power-off simulation";
|
|
}
|
|
|
|
|
|
charger_read_battery_level(&startBatteryLevel);
|
|
|
|
printf("\nStarting %s: ", chargetrap_label);
|
|
if (chargetrap_type == kChargetrapPrecharge) {
|
|
#if WITH_HW_GASGAUGE
|
|
charger_needs_precharge(DEBUG_CRITICAL, true); // print target and current level
|
|
#else
|
|
printf("target %d boot %d, now %d mV\n", startBatteryLevel + battery_delta_target, boot_battery_level, startBatteryLevel);
|
|
#endif
|
|
} else {
|
|
printf("waiting for power button or unplug (screen %s)\n",
|
|
(chargetrap_type == kChargetrapButtonwaitWithGraphics) ? "on" : "off");
|
|
}
|
|
|
|
lastChargingBatteryLevel = newBatteryLevel = startBatteryLevel;
|
|
pollCount = 0;
|
|
power_set_charging(true, false, false, false);
|
|
|
|
animate = (chargetrap_type != kChargetrapButtonwaitNoGraphics);
|
|
display_idle_time = system_time();
|
|
|
|
do {
|
|
bool powersupply_changed = false;
|
|
bool button_event = false;
|
|
bool other_wake_event = false;
|
|
|
|
if (need_buttonwait) {
|
|
bool button = platform_get_request_dfu1();
|
|
|
|
if (button && (buttonpress_time == 0))
|
|
buttonpress_time = system_time();
|
|
else if (!button)
|
|
buttonpress_time = 0;
|
|
|
|
if ((buttonpress_time != 0) && time_has_elapsed(buttonpress_time, kHomeButtonPoweronTimeout - kCheckPowerSupplyInterval)) {
|
|
printf("power button press detected: button wait cancelled\n");
|
|
need_buttonwait = false;
|
|
if ((chargetrap_type == kChargetrapButtonwaitWithGraphics) || (chargetrap_type == kChargetrapButtonwaitNoGraphics))
|
|
break;
|
|
}
|
|
}
|
|
|
|
// turn on the display if needed
|
|
if (animate && (frameCount == 0)) {
|
|
platform_init_display();
|
|
color_map_state = paint_color_map_enable(false);
|
|
ok = power_paint_battery(chargetrap_type == kChargetrapPrecharge, frameCount);
|
|
if (!ok) {
|
|
animate = false;
|
|
}
|
|
}
|
|
|
|
task_sleep(kCheckPowerSupplyInterval);
|
|
|
|
if (animate && ((++frameCount % (kChargingAnimationFrameInterval / kCheckPowerSupplyInterval)) == 0)) {
|
|
if (chargetrap_type == kChargetrapButtonwaitWithGraphics) {
|
|
power_paint_battery(false, frameCount);
|
|
}
|
|
|
|
// Turn off the screen after a timeout with no user activity.
|
|
// If the display is too large to sustain being on at 500mA, we always idle off, but otherwise
|
|
// if graphical button-wait is enabled we do not, even in precharge, because the graphical
|
|
// button-wait mode is defined as leaving the screen on at all times.
|
|
#if TARGET_PRECHARGE_ALWAYS_DISPLAY_IDLE
|
|
if (true) {
|
|
#else
|
|
if ((chargetrap_type == kChargetrapPrecharge) && !need_buttonwait) {
|
|
#endif
|
|
if (system_time() >= (display_idle_time + kPrechargeDisplayDimTimeout)) {
|
|
platform_quiesce_display();
|
|
animate = false;
|
|
frameCount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Read & clear interrupts */
|
|
pmu_check_events(&powersupply_changed, &button_event, &other_wake_event);
|
|
#if WITH_HW_CHARGER
|
|
powersupply_changed = false;
|
|
for (int dock = 0; dock < NUM_DOCKS; dock++) {
|
|
if (charger_check_usb_change(dock, power_supply_type[dock] >= kPowerSupplyUSBHost))
|
|
powersupply_changed = true;
|
|
}
|
|
#endif
|
|
|
|
if (++pollCount > (kCheckBatteryInterval / kCheckPowerSupplyInterval)) {
|
|
pollCount = 0;
|
|
dprintf(DEBUG_INFO, "%s: ", chargetrap_label);
|
|
|
|
if (charger_read_battery_level(&newBatteryLevel) != 0)
|
|
continue;
|
|
|
|
#if WITH_HW_GASGAUGE
|
|
need_precharge = charger_needs_precharge(DEBUG_INFO, (chargetrap_type == kChargetrapPrecharge));
|
|
#else
|
|
if (chargetrap_type == kChargetrapPrecharge) {
|
|
dprintf(DEBUG_INFO, "battery level %d mV (target %d mV)\n", newBatteryLevel, startBatteryLevel + battery_delta_target);
|
|
} else {
|
|
dprintf(DEBUG_INFO, "waiting for power button or unplug (battery level %d mV)\n", newBatteryLevel);
|
|
}
|
|
|
|
if (need_precharge
|
|
&& ((newBatteryLevel >= (startBatteryLevel + battery_delta_target))
|
|
|| (newBatteryLevel >= (ALWAYS_BOOT_BATTERY_VOLTAGE)))) {
|
|
need_precharge = false;
|
|
boot_battery_level += (newBatteryLevel - startBatteryLevel);
|
|
}
|
|
#endif
|
|
|
|
if (test_buttonwait_with_ringer) {
|
|
#if TARGET_FORCE_DEBUG_PRECHARGE
|
|
need_precharge = true;
|
|
#elif !RELEASE_BUILD && defined(GPIO_RINGER_AB)
|
|
if (gpio_read(GPIO_RINGER_AB)) {
|
|
need_precharge = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if ((chargetrap_type == kChargetrapPrecharge) && !need_precharge) {
|
|
break;
|
|
}
|
|
|
|
#if WITH_HW_GASGAUGE
|
|
if (need_precharge) {
|
|
gasgauge_check_health(newBatteryLevel);
|
|
}
|
|
#endif
|
|
|
|
// always reset the charge control in case the current limit has changed due to temperature or voltage
|
|
if (!power_set_charging(true, false, false, false)) {
|
|
powersupply_changed = true;
|
|
}
|
|
}
|
|
|
|
if (powersupply_changed)
|
|
{
|
|
power_determine_power_supply();
|
|
ok = power_set_charging(true, false, false, false);
|
|
if (!ok) {
|
|
boot_battery_level += (lastChargingBatteryLevel - startBatteryLevel);
|
|
battery_delta_target -= (lastChargingBatteryLevel - startBatteryLevel);
|
|
power_set_charging(true, false, false, false);
|
|
if (animate) paint_color_map_enable(color_map_state);
|
|
return kChargetrapReturnChargerRemoved;
|
|
}
|
|
}
|
|
else if ((chargetrap_type == kChargetrapButtonwaitNoGraphics) && other_wake_event)
|
|
{
|
|
// if we are simulating off (kChargetrapButtonwaitNoGraphics) and got an event
|
|
// that would have woken a real unit from standby (excluding buttons, which are special),
|
|
// boot. This ensures that, e.g., an accessory (7020690) or alarm will turn on the unit.
|
|
need_buttonwait = false;
|
|
break;
|
|
}
|
|
else if ((chargetrap_type != kChargetrapButtonwaitNoGraphics) && button_event)
|
|
{
|
|
// any button event will reset the display-off timer in precharge,
|
|
// and turn the screen back off if already dim.
|
|
display_idle_time = system_time();
|
|
animate = true; // next loop will re-activate the screen
|
|
}
|
|
|
|
lastChargingBatteryLevel = newBatteryLevel;
|
|
}
|
|
while (true);
|
|
|
|
power_set_charging(true, false, false, false);
|
|
|
|
data = 0;
|
|
power_get_nvram(kPowerNVRAMiBootStateKey, &data);
|
|
if (!need_precharge)
|
|
data &= ~kPowerNVRAMiBootStatePrecharge;
|
|
if (!need_buttonwait) {
|
|
data &= ~kPowerNVRAMiBootStateModeMask;
|
|
data |= kPowerNVRAMiBootStateModeNormalBoot;
|
|
}
|
|
power_set_nvram(kPowerNVRAMiBootStateKey, data);
|
|
|
|
if (animate) paint_color_map_enable(color_map_state);
|
|
|
|
return kChargetrapReturnSuccess;
|
|
}
|
|
|
|
bool
|
|
power_do_chargetrap(void)
|
|
{
|
|
enum chargetrap_return_t ret;
|
|
uint8_t buttonwait_type = 0;
|
|
unsigned int stage;
|
|
|
|
// There are three different types of charge traps. If more than one applies, show them in this order:
|
|
//
|
|
// 1. button wait without graphics, used to simulate being completely powered off (screen off)
|
|
// 2. precharge (aka battery trap), used when we don't have enough charge to boot (screen displaying low-battery icon)
|
|
// 3. button wait with graphics, used when "off" on certain SKUs (screen displaying charging icon)
|
|
//
|
|
// These are gated by need_buttonwait and power_needs_precharge(), which can be relied on to only ever go
|
|
// from true to false.
|
|
|
|
if (need_buttonwait) {
|
|
u_int8_t data = 0;
|
|
power_get_nvram(kPowerNVRAMiBootStateKey, &data);
|
|
|
|
buttonwait_type = (data & kPowerNVRAMiBootStateModeMask);
|
|
}
|
|
|
|
if (need_buttonwait && (buttonwait_type == kPowerNVRAMiBootStateModeButtonwaitNoGraphics)) {
|
|
ret = charger_precharge(kChargetrapButtonwaitNoGraphics);
|
|
if (ret != kChargetrapReturnSuccess) {
|
|
// this mode is one-time; the next boot should always be normal
|
|
power_cancel_buttonwait();
|
|
platform_poweroff();
|
|
}
|
|
}
|
|
|
|
stage = 0;
|
|
while (power_needs_precharge() && (stage < 5)) {
|
|
bool ok;
|
|
|
|
ret = charger_precharge(kChargetrapPrecharge);
|
|
if (ret != kChargetrapReturnNoCharger) {
|
|
stage = 0; // if any charging occurred, restart battery trap sequence
|
|
continue;
|
|
}
|
|
|
|
platform_init_display();
|
|
bool color_map_state = paint_color_map_enable(false);
|
|
paint_set_picture(0);
|
|
|
|
ok = !paint_set_picture_for_tag(IMAGE_TYPE_BATTERYLOW0);
|
|
if (ok && !(stage++ & 1)) ok = !paint_set_picture_for_tag(IMAGE_TYPE_BATTERYLOW1);
|
|
if (ok &&
|
|
((power_supply_type[0] == kPowerSupplyFirewire) ||
|
|
(paint_set_picture_for_tag(IMAGE_TYPE_GLYPHPLUGIN) == 0))) {
|
|
paint_set_bgcolor(0, 0, 0);
|
|
} else {
|
|
paint_set_picture(0);
|
|
}
|
|
paint_update_image();
|
|
|
|
task_sleep(1 * 1000 * 1000);
|
|
|
|
paint_color_map_enable(color_map_state);
|
|
}
|
|
|
|
if (power_needs_precharge()) {
|
|
platform_quiesce_display();
|
|
printf("need battery charge, no power source; shutting down\n");
|
|
power_shutdown();
|
|
}
|
|
|
|
if (need_buttonwait && (buttonwait_type == kPowerNVRAMiBootStateModeButtonwaitWithGraphics)) {
|
|
ret = charger_precharge(kChargetrapButtonwaitWithGraphics);
|
|
if (ret != kChargetrapReturnSuccess) {
|
|
platform_poweroff();
|
|
}
|
|
}
|
|
|
|
power_set_usb_enabled(false);
|
|
|
|
return (true);
|
|
}
|
|
|
|
static int
|
|
do_charge_cmd(int argc, struct cmd_arg *args)
|
|
{
|
|
unsigned battery_level;
|
|
bool enable = true;
|
|
|
|
if (!security_allow_modes(kSecurityModeDebugCmd | kSecurityModeHWAccess)) {
|
|
printf("Permission Denied\n");
|
|
return -1;
|
|
}
|
|
|
|
#if WITH_HW_USB
|
|
usb_quiesce();
|
|
#endif
|
|
|
|
if (argc >= 2) {
|
|
if (!strcmp("dis", args[1].str)) {
|
|
enable = false;
|
|
}
|
|
else if (!strcmp("ena", args[1].str)) {
|
|
}
|
|
else if (!strcmp("trap", args[1].str)) {
|
|
}
|
|
else if (!strcmp("restart", args[1].str)) {
|
|
#if WITH_HW_GASGAUGE
|
|
int result;
|
|
|
|
result=gasgauge_reset_timer( DEBUG_CRITICAL, 8 ); // 8 seconds timeout
|
|
if ( result==0 ) {
|
|
result=gasgauge_needs_precharge(DEBUG_INFO, false);
|
|
if ( result&GG_NEEDS_RESET ) printf("restart failed %#x\n", result);
|
|
} else {
|
|
printf("failed (%d)\n", result);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
else if (!strcmp("detect", args[1].str)) {
|
|
for (int dock = 0; dock < NUM_DOCKS; dock++) usb_brick_id[dock]=0;
|
|
power_determine_power_supply();
|
|
return 0;
|
|
}
|
|
else if (!strcmp("draw", args[1].str)) {
|
|
}
|
|
else if (!strcmp("status", args[1].str)) {
|
|
#if WITH_HW_GASGAUGE
|
|
gasgauge_print_status();
|
|
charger_print_status();
|
|
#endif
|
|
return 0;
|
|
}
|
|
else {
|
|
printf("usage:\n\t%s <command>\n", args[0].str);
|
|
printf("\t\tena Enable charger.\n");
|
|
printf("\t\tdis Disable charger.\n");
|
|
printf("\t\trestart Reset/Restart Charge timer.\n");
|
|
printf("\t\ttrap <delta> Enter trap with target voltage delta.\n");
|
|
printf("\t\tdraw <soc> Draw battery logo with specified SoC (percent)\n");
|
|
printf("\t\tdetect Force detect power brick\n");
|
|
printf("\t\tstatus Dump gas gauge and charger status\n");
|
|
}
|
|
}
|
|
|
|
power_determine_power_supply();
|
|
#if TARGET_HAS_SMARTPORT
|
|
uint8_t pwr_in_sel;
|
|
int rc=smartport_get_pwr_in_sel(1, &pwr_in_sel);
|
|
printf("smartport: pwr_in_sel=%x ", pwr_in_sel);
|
|
if ( rc<0 ) {
|
|
printf("error %d\n", rc);
|
|
} else {
|
|
printf("(%s)\n", (pwr_in_sel==SMARTPORT_ACC_PWR_IN_SEL)?"acc":"ext");
|
|
}
|
|
#endif
|
|
|
|
power_set_charging(false, enable, false, !enable);
|
|
|
|
/* read battery voltage */
|
|
printf("battery voltage ");
|
|
if (!charger_read_battery_level(&battery_level))
|
|
printf("%d mV\n", battery_level);
|
|
else
|
|
printf("error\n");
|
|
|
|
#if WITH_HW_GASGAUGE
|
|
gasgauge_print_status();
|
|
charger_print_status();
|
|
#endif
|
|
|
|
if ((argc >= 2) && !strcmp("trap", args[1].str)) {
|
|
boot_battery_level = battery_level;
|
|
battery_delta_target = args[2].u;
|
|
|
|
enum chargetrap_return_t ret = charger_precharge(kChargetrapPrecharge);
|
|
if (ret == kChargetrapReturnSuccess) printf("charger_precharge success\n");
|
|
else if (ret == kChargetrapReturnNoCharger) printf("charger_precharge no charger\n");
|
|
else if (ret == kChargetrapReturnChargerRemoved) printf("charger_precharge charger removed\n");
|
|
}
|
|
|
|
if ( strcmp("draw", args[1].str)==0 ) {
|
|
if ( argc==3 ) {
|
|
// charger draw <soc>
|
|
power_paint_battery_with_capacity(false, 0, args[2].u, 100);
|
|
} else if ( argc==4 ) {
|
|
// charger draw <precharge> <framecount>
|
|
power_paint_battery(args[2].u, args[3].u);
|
|
} else if ( argc>4 ) {
|
|
// charger draw <precharge> <framecount> <soc>
|
|
power_paint_battery_with_capacity(args[2].u, args[3].u, args[4].u, 100);
|
|
}
|
|
}
|
|
|
|
#if WITH_HW_USB
|
|
/* re-initializes usb as a result of the usb_quiesce() call */
|
|
usb_early_init();
|
|
#if WITH_PLATFORM_INIT_USB
|
|
if(platform_init_usb() != 0)
|
|
usb_quiesce();
|
|
#else
|
|
if(usb_init() != 0)
|
|
usb_quiesce();
|
|
#endif
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if WITH_RECOVERY_MODE
|
|
MENU_COMMAND_DEBUG(charge, do_charge_cmd, "Manage the charger chip", NULL);
|
|
#endif
|
|
|
|
static const char *nvram_property_names[kPowerNVRAMPropertyCount] = {
|
|
kPowerNVRAMiBootStateName, // kPowerNVRAMiBootStateKey
|
|
kPowerNVRAMiBootDebugName, // kPowerNVRAMiBootDebugKey
|
|
kPowerNVRAMiBootStageName, // kPowerNVRAMiBootStageKey
|
|
kPowerNVRAMiBootErrorCountName, // kPowerNVRAMiBootErrorCountKey
|
|
kPowerNVRAMiBootErrorStageName, // kPowerNVRAMiBootErrorStageKey
|
|
kPowerNVRAMiBootMemCalCAOffset0Name,
|
|
kPowerNVRAMiBootMemCalCAOffset1Name,
|
|
kPowerNVRAMiBootMemCalCAOffset2Name,
|
|
kPowerNVRAMiBootMemCalCAOffset3Name,
|
|
kPowerNVRAMiBootBootFlags0Name,
|
|
kPowerNVRAMiBootBootFlags1Name,
|
|
kPowerNVRAMiBootEnterDFUName, // kPowerNVRAMiBootEnterDFUKey
|
|
};
|
|
|
|
static int
|
|
do_power_nvram(int argc, struct cmd_arg *args)
|
|
{
|
|
uint32_t cnt;
|
|
uint8_t data;
|
|
|
|
if (!security_allow_modes(kSecurityModeDebugCmd)) {
|
|
printf("Permission Denied\n");
|
|
return -1;
|
|
}
|
|
|
|
if (argc < 2) {
|
|
goto usage;
|
|
}
|
|
|
|
if (!strcmp("list", args[1].str)) {
|
|
for (cnt = 0; cnt < kPowerNVRAMPropertyCount; cnt++) {
|
|
if (power_get_nvram(cnt, &data) == 0) {
|
|
printf("%x: [%s] 0x%02x\n", cnt, nvram_property_names[cnt], data);
|
|
}
|
|
}
|
|
|
|
} else if (!strcmp("set", args[1].str)) {
|
|
if (argc != 4) {
|
|
goto usage;
|
|
}
|
|
|
|
switch (args[2].u) {
|
|
case kPowerNVRAMiBootStateKey :
|
|
if (!security_allow_modes(kSecurityModeHWAccess)) {
|
|
printf("Permission Denied\n");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
power_set_nvram(args[2].u, args[3].u);
|
|
|
|
} else {
|
|
goto usage;
|
|
}
|
|
|
|
return 0;
|
|
|
|
usage:
|
|
printf("usage:\n\t%s <command>\n", args[0].str);
|
|
printf("\t\tlist List NVRAM Properties.\n");
|
|
printf("\t\tset <prop#> <value> Set NVRAM Property.\n");
|
|
return -1;
|
|
}
|
|
|
|
#if WITH_RECOVERY_MODE
|
|
MENU_COMMAND_DEBUG(powernvram, do_power_nvram, "Access Power NVRAM.", NULL);
|
|
#endif
|
|
|
|
void
|
|
power_suspend(void)
|
|
{
|
|
platform_quiesce_display();
|
|
|
|
#if WITH_HW_MIU
|
|
miu_suspend();
|
|
#endif
|
|
pmu_set_backlight_enable(0);
|
|
|
|
/* enter hibernate mode */
|
|
dprintf(DEBUG_INFO, "pmu suspend\n");
|
|
|
|
pmu_suspend();
|
|
}
|
|
|
|
void
|
|
power_shutdown(void)
|
|
{
|
|
platform_quiesce_display();
|
|
|
|
/* enter standby mode */
|
|
dprintf(DEBUG_INFO, "pmu shutdown\n");
|
|
|
|
#if WITH_HW_GASGAUGE
|
|
gasgauge_will_shutdown();
|
|
#endif
|
|
|
|
power_clr_events(1);
|
|
|
|
pmu_shutdown();
|
|
}
|
|
|
|
int
|
|
_power_backlight_enable_internal(uint32_t backlight_level)
|
|
{
|
|
if (backlight_level) {
|
|
if (power_needs_precharge() && (backlight_level > PRECHARGE_BACKLIGHT_LEVEL))
|
|
backlight_level = PRECHARGE_BACKLIGHT_LEVEL;
|
|
|
|
}
|
|
|
|
#if WITH_HW_LM3534
|
|
extern int lm3534_backlight_enable(uint32_t backlight_level);
|
|
|
|
if (lm3534_backlight_enable(backlight_level) != 0)
|
|
#endif
|
|
#if WITH_HW_LP8559
|
|
extern int lp8559_backlight_enable(uint32_t backlight_level);
|
|
|
|
if (lp8559_backlight_enable(backlight_level) != 0)
|
|
#endif
|
|
{
|
|
pmu_set_backlight_enable(backlight_level);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
power_clear_dark_boot_flag(void)
|
|
{
|
|
#if PRODUCT_IBOOT
|
|
const char* boot_cmd = env_get("boot-command");
|
|
|
|
if (env_get_bool("auto-boot", false) && boot_cmd && strncmp(boot_cmd, "upgrade", 7) == 0) {
|
|
// If this is iBoot and we are going to upgrade,
|
|
// we don't want to clear dark boot (because iBEC needs to know)
|
|
return;
|
|
}
|
|
#endif
|
|
env_unset("darkboot");
|
|
nvram_save();
|
|
}
|
|
|
|
|
|
static bool force_disable_dark_boot;
|
|
|
|
bool
|
|
power_is_dark_boot (void)
|
|
{
|
|
static bool dark_boot_init = false;
|
|
static bool is_dark_boot = false;
|
|
|
|
if (dark_boot_init)
|
|
return is_dark_boot && !force_disable_dark_boot;
|
|
else {
|
|
is_dark_boot = env_get_bool("darkboot", false);
|
|
dark_boot_init = true;
|
|
return is_dark_boot && !force_disable_dark_boot;
|
|
}
|
|
}
|
|
|
|
static uint32_t requested_backlight_level = 0;
|
|
|
|
void
|
|
power_dark_boot_checkpoint (void)
|
|
{
|
|
if (!power_is_dark_boot())
|
|
return;
|
|
// Check buttons and light up screen if needed
|
|
bool powersupply_changed = false;
|
|
bool button_event = false;
|
|
bool other_wake_event = false;
|
|
|
|
pmu_check_events(&powersupply_changed, &button_event, &other_wake_event);
|
|
|
|
if (button_event) {
|
|
dprintf(DEBUG_INFO, "dark boot: found button event\n");
|
|
power_disable_dark_boot();
|
|
return;
|
|
}
|
|
dprintf(DEBUG_INFO, "dark boot: checkpoint found no pending button presses -- staying dark.\n");
|
|
}
|
|
|
|
void
|
|
power_disable_dark_boot (void)
|
|
{
|
|
force_disable_dark_boot = true;
|
|
dprintf(DEBUG_INFO, "dark boot: Lighting up display... (remembered backlight_level: %u)\n", (unsigned int)requested_backlight_level);
|
|
_power_backlight_enable_internal(requested_backlight_level);
|
|
}
|
|
|
|
int
|
|
power_backlight_enable(uint32_t backlight_level)
|
|
{
|
|
#if !NO_DARK_BOOT
|
|
dprintf(DEBUG_INFO, "dark boot: Received backlight level request %u\n", (unsigned int)backlight_level);
|
|
requested_backlight_level = backlight_level;
|
|
|
|
if (power_is_dark_boot()) {
|
|
dprintf(DEBUG_INFO, "dark boot: This is a dark boot, remembering request but leaving backlight_level = 0\n");
|
|
_power_backlight_enable_internal(0);
|
|
} else {
|
|
return _power_backlight_enable_internal(backlight_level);
|
|
}
|
|
return 0;
|
|
#else
|
|
return _power_backlight_enable_internal(backlight_level);
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
do_backlight_cmd(int argc, struct cmd_arg *args)
|
|
{
|
|
if (argc != 2) {
|
|
printf("usage: backlight <level>\n");
|
|
return -1;
|
|
}
|
|
|
|
if (power_is_dark_boot()) {
|
|
printf("[disabling dark boot due to explicit backlight command]\n");
|
|
// This will momentarily set the backlight to a higher level then back to this level
|
|
power_disable_dark_boot();
|
|
}
|
|
|
|
return power_backlight_enable(args[1].n);
|
|
}
|
|
|
|
#if WITH_MENU
|
|
MENU_COMMAND_DEBUG(backlight, do_backlight_cmd, "set the backlight", NULL);
|
|
#endif
|
|
|
|
bool
|
|
power_has_usb(void)
|
|
{
|
|
for (int dock = 0; dock < NUM_DOCKS; dock++) {
|
|
if (charger_has_usb(dock)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
power_is_suspended(void)
|
|
{
|
|
bool suspended = false;
|
|
#if WITH_CLASSIC_SUSPEND_TO_RAM
|
|
#if PRODUCT_LLB
|
|
bool resume_lost = power_get_boot_flag() != kPowerBootFlagWarm;
|
|
#else
|
|
bool resume_lost = false;
|
|
#endif
|
|
u_int8_t data = 0;
|
|
power_get_nvram(kPowerNVRAMiBootStateKey, &data);
|
|
suspended = (data & kPowerNVRAMiBootStateModeMask) == kPowerNVRAMiBootStateModeSuspended;
|
|
if (suspended) {
|
|
if (resume_lost) {
|
|
data &= ~kPowerNVRAMiBootStateModeMask;
|
|
data |= kPowerNVRAMiBootStateModeNormalBoot;
|
|
power_set_nvram(kPowerNVRAMiBootStateKey, data);
|
|
suspended = false;
|
|
}
|
|
}
|
|
#endif // WITH_CLASSIC_SUSPEND_TO_RAM
|
|
return suspended;
|
|
}
|
|
|
|
void
|
|
power_will_resume(void)
|
|
{
|
|
#if WITH_CLASSIC_SUSPEND_TO_RAM
|
|
u_int8_t data = 0;
|
|
power_get_nvram(kPowerNVRAMiBootStateKey, &data);
|
|
data &= ~kPowerNVRAMiBootStateModeMask;
|
|
data |= kPowerNVRAMiBootStateModeResumed;
|
|
power_set_nvram(kPowerNVRAMiBootStateKey, data);
|
|
|
|
pmu_will_resume();
|
|
#endif // WITH_CLASSIC_SUSPEND_TO_RAM
|
|
}
|
|
|
|
bool
|
|
power_get_diags_dock(void)
|
|
{
|
|
#if WITH_HW_TRISTAR
|
|
// check the third byte of ID response for DI (diagnostics mode) bit
|
|
uint8_t digital_id[6];
|
|
if (!tristar_read_id(digital_id)) return false;
|
|
return digital_id[2] & (1 << 4);
|
|
#else
|
|
unsigned id;
|
|
return !power_read_dock_id(&id) && (id == 0x07);
|
|
#endif
|
|
}
|