int charger_set_current(int current) { const struct charger_info *info = charger_get_info(); if (current > 0 && current < info->current_min) current = info->current_min; if (current > info->current_max) current = info->current_max; if (mock_current != current) ccprintf("Charger set current: %d\n", current); mock_current = current; return EC_SUCCESS; }
static void charge_init(void) { struct charge_state_context *ctx = &task_ctx; ctx->prev.state = PWR_STATE_INIT; ctx->curr.state = PWR_STATE_INIT; ctx->trickle_charging_time.val = 0; ctx->battery = battery_get_info(); ctx->charger = charger_get_info(); /* Assume the battery is responsive until proven otherwise */ ctx->battery_responsive = 1; /* Set up LPC direct memmap */ ctx->memmap_batt_volt = (uint32_t *)host_get_memmap(EC_MEMMAP_BATT_VOLT); ctx->memmap_batt_rate = (uint32_t *)host_get_memmap(EC_MEMMAP_BATT_RATE); ctx->memmap_batt_cap = (uint32_t *)host_get_memmap(EC_MEMMAP_BATT_CAP); ctx->memmap_batt_flags = host_get_memmap(EC_MEMMAP_BATT_FLAG); }
/* Main loop */ void charger_task(void) { int sleep_usec; int need_static = 1; const struct charger_info * const info = charger_get_info(); /* Get the battery-specific values */ batt_info = battery_get_info(); prev_ac = prev_charge = -1; chg_ctl_mode = CHARGE_CONTROL_NORMAL; shutdown_warning_time.val = 0UL; battery_seems_to_be_dead = 0; /* * If system is not locked and we don't have a battery to live on, * then use max input current limit so that we can pull as much power * as needed. */ battery_get_params(&curr.batt); prev_bp = curr.batt.is_present; curr.desired_input_current = get_desired_input_current(prev_bp, info); while (1) { #ifdef CONFIG_SB_FIRMWARE_UPDATE if (sb_fw_update_in_progress()) { task_wait_event(CHARGE_MAX_SLEEP_USEC); continue; } #endif /* Let's see what's going on... */ curr.ts = get_time(); sleep_usec = 0; problems_exist = 0; curr.ac = extpower_is_present(); if (curr.ac != prev_ac) { if (curr.ac) { /* * Some chargers are unpowered when the AC is * off, so we'll reinitialize it when AC * comes back and set the input current limit. * Try again if it fails. */ int rv = charger_post_init(); if (rv != EC_SUCCESS) { problem(PR_POST_INIT, rv); } else { if (curr.desired_input_current != CHARGE_CURRENT_UNINITIALIZED) rv = charger_set_input_current( curr.desired_input_current); if (rv != EC_SUCCESS) problem(PR_SET_INPUT_CURR, rv); else prev_ac = curr.ac; } } else { /* Some things are only meaningful on AC */ chg_ctl_mode = CHARGE_CONTROL_NORMAL; battery_seems_to_be_dead = 0; prev_ac = curr.ac; } } charger_get_params(&curr.chg); battery_get_params(&curr.batt); if (prev_bp != curr.batt.is_present) { prev_bp = curr.batt.is_present; /* Update battery info due to change of battery */ batt_info = battery_get_info(); need_static = 1; curr.desired_input_current = get_desired_input_current(prev_bp, info); if (curr.desired_input_current != CHARGE_CURRENT_UNINITIALIZED) charger_set_input_current( curr.desired_input_current); hook_notify(HOOK_BATTERY_SOC_CHANGE); } /* * TODO(crosbug.com/p/27527). Sometimes the battery thinks its * temperature is 6280C, which seems a bit high. Let's ignore * anything above the boiling point of tungsten until this bug * is fixed. If the battery is really that warm, we probably * have more urgent problems. */ if (curr.batt.temperature > CELSIUS_TO_DECI_KELVIN(5660)) { CPRINTS("ignoring ridiculous batt.temp of %dC", DECI_KELVIN_TO_CELSIUS(curr.batt.temperature)); curr.batt.flags |= BATT_FLAG_BAD_TEMPERATURE; } /* If the battery thinks it's above 100%, don't believe it */ if (curr.batt.state_of_charge > 100) { CPRINTS("ignoring ridiculous batt.soc of %d%%", curr.batt.state_of_charge); curr.batt.flags |= BATT_FLAG_BAD_STATE_OF_CHARGE; } /* * Now decide what we want to do about it. We'll normally just * pass along whatever the battery wants to the charger. Note * that if battery_get_params() can't get valid values from the * battery it uses (0, 0), which is probably safer than blindly * applying power to a battery we can't talk to. */ curr.requested_voltage = curr.batt.desired_voltage; curr.requested_current = curr.batt.desired_current; /* If we *know* there's no battery, wait for one to appear. */ if (curr.batt.is_present == BP_NO) { ASSERT(curr.ac); /* How are we running? */ curr.state = ST_IDLE; curr.batt_is_charging = 0; battery_was_removed = 1; goto wait_for_it; } /* * If we had trouble talking to the battery or the charger, we * should probably do nothing for a bit, and if it doesn't get * better then flag it as an error. */ if (curr.chg.flags & CHG_FLAG_BAD_ANY) problem(PR_CHG_FLAGS, curr.chg.flags); if (curr.batt.flags & BATT_FLAG_BAD_ANY) problem(PR_BATT_FLAGS, curr.batt.flags); /* * If AC is present, check if input current is sufficient to * actually charge battery. */ curr.batt_is_charging = curr.ac && (curr.batt.current >= 0); /* Don't let the battery hurt itself. */ shutdown_on_critical_battery(); if (!curr.ac) { curr.state = ST_DISCHARGE; goto wait_for_it; } /* Okay, we're on AC and we should have a battery. */ /* Used for factory tests. */ if (chg_ctl_mode != CHARGE_CONTROL_NORMAL) { curr.state = ST_IDLE; goto wait_for_it; } /* If the battery is not responsive, try to wake it up. */ if (!(curr.batt.flags & BATT_FLAG_RESPONSIVE)) { if (battery_seems_to_be_dead || battery_is_cut_off()) { /* It's dead, do nothing */ curr.state = ST_IDLE; curr.requested_voltage = 0; curr.requested_current = 0; } else if (curr.state == ST_PRECHARGE && (get_time().val > precharge_start_time.val + PRECHARGE_TIMEOUT_US)) { /* We've tried long enough, give up */ CPRINTS("battery seems to be dead"); battery_seems_to_be_dead = 1; curr.state = ST_IDLE; curr.requested_voltage = 0; curr.requested_current = 0; } else { /* See if we can wake it up */ if (curr.state != ST_PRECHARGE) { CPRINTS("try to wake battery"); precharge_start_time = get_time(); need_static = 1; } curr.state = ST_PRECHARGE; curr.requested_voltage = batt_info->voltage_max; curr.requested_current = batt_info->precharge_current; } goto wait_for_it; } else { /* The battery is responding. Yay. Try to use it. */ #ifdef CONFIG_BATTERY_REQUESTS_NIL_WHEN_DEAD /* * TODO (crosbug.com/p/29467): remove this workaround * for dead battery that requests no voltage/current */ if (curr.requested_voltage == 0 && curr.requested_current == 0 && curr.batt.state_of_charge == 0) { /* Battery is dead, give precharge current */ curr.requested_voltage = batt_info->voltage_max; curr.requested_current = batt_info->precharge_current; } else #endif #ifdef CONFIG_BATTERY_REVIVE_DISCONNECT battery_seems_to_be_disconnected = 0; if (curr.requested_voltage == 0 && curr.requested_current == 0 && battery_get_disconnect_state() == BATTERY_DISCONNECTED) { /* * Battery is in disconnect state. Apply a * current to kick it out of this state. */ CPRINTS("found battery in disconnect state"); curr.requested_voltage = batt_info->voltage_max; curr.requested_current = batt_info->precharge_current; battery_seems_to_be_disconnected = 1; } else #endif if (curr.state == ST_PRECHARGE || battery_seems_to_be_dead || battery_was_removed) { CPRINTS("battery woke up"); /* Update the battery-specific values */ batt_info = battery_get_info(); need_static = 1; } battery_seems_to_be_dead = battery_was_removed = 0; curr.state = ST_CHARGE; } /* * TODO(crosbug.com/p/27643): Quit trying if charging too long * without getting full (CONFIG_CHARGER_TIMEOUT_HOURS). */ wait_for_it: #ifdef CONFIG_CHARGER_PROFILE_OVERRIDE sleep_usec = charger_profile_override(&curr); if (sleep_usec < 0) problem(PR_CUSTOM, sleep_usec); #endif /* Keep the AP informed */ if (need_static) need_static = update_static_battery_info(); /* Wait on the dynamic info until the static info is good. */ if (!need_static) update_dynamic_battery_info(); notify_host_of_low_battery(); /* And the EC console */ is_full = calc_is_full(); if ((!(curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE) && curr.batt.state_of_charge != prev_charge) || (is_full != prev_full)) { show_charging_progress(); prev_charge = curr.batt.state_of_charge; hook_notify(HOOK_BATTERY_SOC_CHANGE); } prev_full = is_full; /* Turn charger off if it's not needed */ if (curr.state == ST_IDLE || curr.state == ST_DISCHARGE) { curr.requested_voltage = 0; curr.requested_current = 0; } /* Apply external limits */ if (curr.requested_current > user_current_limit) curr.requested_current = user_current_limit; /* Round to valid values */ curr.requested_voltage = charger_closest_voltage(curr.requested_voltage); curr.requested_current = charger_closest_current(curr.requested_current); /* Charger only accpets request when AC is on. */ if (curr.ac) { /* * Some batteries would wake up after cut-off if we keep * charging it. Thus, we only charge when AC is on and * battery is not cut off yet. */ if (battery_is_cut_off()) charge_request(0, 0); /* * As a safety feature, some chargers will stop * charging if we don't communicate with it frequently * enough. In manual mode, we'll just tell it what it * knows. */ else if (manual_mode) { charge_request(curr.chg.voltage, curr.chg.current); } else { charge_request(curr.requested_voltage, curr.requested_current); } } else { charge_request( charger_closest_voltage( curr.batt.voltage + info->voltage_step), -1); } /* How long to sleep? */ if (problems_exist) /* If there are errors, don't wait very long. */ sleep_usec = CHARGE_POLL_PERIOD_SHORT; else if (sleep_usec <= 0) { /* default values depend on the state */ if (curr.state == ST_IDLE || curr.state == ST_DISCHARGE) { /* If AP is off, we can sleep a long time */ if (chipset_in_state(CHIPSET_STATE_ANY_OFF | CHIPSET_STATE_SUSPEND)) sleep_usec = CHARGE_POLL_PERIOD_VERY_LONG; else /* Discharging, not too urgent */ sleep_usec = CHARGE_POLL_PERIOD_LONG; } else { /* Charging, so pay closer attention */ sleep_usec = CHARGE_POLL_PERIOD_CHARGE; } } /* Adjust for time spent in this loop */ sleep_usec -= (int)(get_time().val - curr.ts.val); if (sleep_usec < CHARGE_MIN_SLEEP_USEC) sleep_usec = CHARGE_MIN_SLEEP_USEC; else if (sleep_usec > CHARGE_MAX_SLEEP_USEC) sleep_usec = CHARGE_MAX_SLEEP_USEC; task_wait_event(sleep_usec); } }
/* * Ask the charger for some voltage and current. If either value is 0, * charging is disabled; otherwise it's enabled. Negative values are ignored. */ static int charge_request(int voltage, int current) { int r1 = EC_SUCCESS, r2 = EC_SUCCESS, r3 = EC_SUCCESS; static int __bss_slow prev_volt, prev_curr; if (!voltage || !current) { #ifdef CONFIG_CHARGER_NARROW_VDC current = 0; /* With NVDC charger, keep VSYS voltage higher than battery */ voltage = charger_closest_voltage( curr.batt.voltage + charger_get_info()->voltage_step); /* If the battery is full, request the max voltage. */ if (is_full) voltage = battery_get_info()->voltage_max; /* And handle dead battery case */ voltage = MAX(voltage, battery_get_info()->voltage_min); #else voltage = current = 0; #endif } if (curr.ac) { if (prev_volt != voltage || prev_curr != current) CPRINTS("%s(%dmV, %dmA)", __func__, voltage, current); } /* * Set current before voltage so that if we are just starting * to charge, we allow some time (i2c delay) for charging circuit to * start at a voltage just above battery voltage before jumping * up. This helps avoid large current spikes when connecting * battery. */ if (current >= 0) r2 = charger_set_current(current); if (r2 != EC_SUCCESS) problem(PR_SET_CURRENT, r2); if (voltage >= 0) r1 = charger_set_voltage(voltage); if (r1 != EC_SUCCESS) problem(PR_SET_VOLTAGE, r1); /* * Set the charge inhibit bit when possible as it appears to save * power in some cases (e.g. Nyan with BQ24735). */ #if defined(CONFIG_CHARGER_BD99955) || defined(CONFIG_CHARGER_BD99956) /* Charger auto exits from battery learn mode if charge inhibited */ if (current > 0 || chg_ctl_mode == CHARGE_CONTROL_DISCHARGE) #else if (voltage > 0 || current > 0) #endif r3 = charger_set_mode(0); else r3 = charger_set_mode(CHARGE_FLAG_INHIBIT_CHARGE); if (r3 != EC_SUCCESS) problem(PR_SET_MODE, r3); /* * Only update if the request worked, so we'll keep trying on failures. */ if (!r1 && !r2) { prev_volt = voltage; prev_curr = current; } return r1 ? r1 : r2; }
/* * This can override the smart battery's charging profile. To make a change, * modify one or more of requested_voltage, requested_current, or state. * Leave everything else unchanged. * * Return the next poll period in usec, or zero to use the default (which is * state dependent). */ int charger_profile_override(struct charge_state_data *curr) { /* temp in 0.1 deg C */ int temp_c; const struct charger_info *info; /* keep track of last temperature range for hysteresis */ static enum { TEMP_LOW, TEMP_NORMAL, TEMP_HIGH } temp_range = TEMP_NORMAL, prev_temp_range = TEMP_NORMAL; /* charging voltage to use at high temp */ static int high_temp_charging_voltage; /* custom profile phase at normal temp */ static int normal_temp_phase; /* battery voltage and current and previous voltage and current */ int batt_voltage, batt_current; static int prev_batt_voltage, prev_batt_current; /* * Determine temperature range: * Low: Battery is <15C * Normal: Battery is 15-45C * High: Battery is >45C * * Add 0.2 degrees of hysteresis. * If temp reading was bad use last range. */ if (!(curr->batt.flags & BATT_FLAG_BAD_TEMPERATURE)) { temp_c = curr->batt.temperature - 2731; if (temp_c < 149) temp_range = TEMP_LOW; else if (temp_c > 151 && temp_c < 449) temp_range = TEMP_NORMAL; else if (temp_c > 451) temp_range = TEMP_HIGH; } /* * Treat voltage and current as a pair, if either is bad fall back to * previous reading. */ if (curr->batt.flags & (BATT_FLAG_BAD_VOLTAGE | BATT_FLAG_BAD_CURRENT)) { batt_voltage = prev_batt_voltage; batt_current = prev_batt_current; } else { batt_voltage = prev_batt_voltage = curr->batt.voltage; batt_current = prev_batt_current = curr->batt.current; } /* * If we are not charging or we aren't using fast charging profiles, * then do not override desired current and voltage and reset some * fast charging profile static variables. */ if (curr->state != ST_CHARGE || !fast_charging_allowed) { prev_temp_range = TEMP_NORMAL; normal_temp_phase = 0; return 0; } /* * Okay, impose our custom will: * Normal temp: * Phase 0: CC at 9515mA @ 8.3V * CV at 8.3V until current drops to 4759mA * Phase 1: CC at 4759mA @ 8.7V * CV at 8.7V * * Low temp: * CC at 2854mA @ 8.7V * CV at 8.7V * * High temp: * If battery voltage < 8.3V then: * CC at 6660mA @ 8.3V * CV at 8.3V (when battery is hot we don't go to fully charged) * else: * CV at just above battery voltage which will essentially * terminate the charge and allow battery to cool. * Note that if we ever request a voltage below the present battery * voltage, then we will stop the BQ switching, which will power off * the INA and we won't be able to charge again until AC is * disconnected. see crbug.com/p/35491. */ switch (temp_range) { case TEMP_LOW: curr->requested_current = 2854; curr->requested_voltage = 8700; break; case TEMP_NORMAL: if (normal_temp_phase == 0) { curr->requested_current = 9515; curr->requested_voltage = 8300; if (batt_current <= 4759 && batt_voltage >= 8200) normal_temp_phase = 1; } if (normal_temp_phase == 1) { curr->requested_current = 4759; curr->requested_voltage = 8700; } break; case TEMP_HIGH: /* * First time TEMP_HIGH is used, get the closest voltage * just above the battery voltage. If it is above 8.3V, we * will use that as the target, otherwise we will use 8.3V. */ if (prev_temp_range != TEMP_HIGH) { info = charger_get_info(); high_temp_charging_voltage = MAX(8300, charger_closest_voltage(batt_voltage + info->voltage_step)); } curr->requested_current = 6660; curr->requested_voltage = high_temp_charging_voltage; break; } prev_temp_range = temp_range; return 0; }