| /* Copyright (c) 2013 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. |
| * |
| * Battery pack vendor provided charging profile |
| */ |
| |
| #include "battery.h" |
| #include "battery_smart.h" |
| #include "gpio.h" |
| #include "host_command.h" |
| #include "util.h" |
| #include "console.h" |
| |
| /* Console output macros */ |
| #define CPRINTS(format, args...) cprints(CC_CHARGER, format, ## args) |
| |
| /* These 2 defines are for cut_off command for 3S battery */ |
| #define SB_SHIP_MODE_ADDR 0x3a |
| #define SB_SHIP_MODE_DATA 0xc574 |
| |
| static struct battery_info *battery_info; |
| static int support_cut_off; |
| |
| struct battery_device { |
| char manuf[9]; |
| char device[9]; |
| int design_mv; |
| struct battery_info *battery_info; |
| int support_cut_off; |
| }; |
| |
| /* |
| * Used for the case that battery cannot be detected, such as the pre-charge |
| * case. In this case, we need to provide the battery with the enough voltage |
| * (usually the highest voltage among batteries, but the smallest precharge |
| * current). This should be as conservative as possible. |
| */ |
| static struct battery_info info_precharge = { |
| |
| .voltage_max = 12900, /* the max voltage among batteries */ |
| .voltage_normal = 11400, |
| .voltage_min = 9000, |
| |
| /* Pre-charge values. */ |
| .precharge_current = 256, /* mA, the min current among batteries */ |
| |
| .start_charging_min_c = 0, |
| .start_charging_max_c = 50, |
| .charging_min_c = 0, |
| .charging_max_c = 60, |
| .discharging_min_c = 0, |
| .discharging_max_c = 75, |
| }; |
| |
| static struct battery_info info_2s = { |
| /* |
| * Design voltage |
| * max = 8.4V |
| * normal = 7.4V |
| * min = 6.0V |
| */ |
| .voltage_max = 8400, |
| .voltage_normal = 7400, |
| .voltage_min = 6000, |
| |
| /* Pre-charge current: I <= 0.01C */ |
| .precharge_current = 64, /* mA */ |
| |
| /* |
| * Operational temperature range |
| * 0 <= T_charge <= 50 deg C |
| * -20 <= T_discharge <= 60 deg C |
| */ |
| .start_charging_min_c = 0, |
| .start_charging_max_c = 50, |
| .charging_min_c = 0, |
| .charging_max_c = 50, |
| .discharging_min_c = -20, |
| .discharging_max_c = 60, |
| }; |
| |
| static struct battery_info info_3s = { |
| |
| .voltage_max = 12600, |
| .voltage_normal = 11100, /* Average of max & min */ |
| .voltage_min = 9000, |
| |
| /* Pre-charge values. */ |
| .precharge_current = 392, /* mA */ |
| |
| .start_charging_min_c = 0, |
| .start_charging_max_c = 60, |
| .charging_min_c = 0, |
| .charging_max_c = 60, |
| .discharging_min_c = 0, |
| .discharging_max_c = 50, |
| }; |
| |
| static struct battery_info info_3s_LGC = { |
| |
| .voltage_max = 12900, |
| .voltage_normal = 11400, /* Average of max & min */ |
| .voltage_min = 9000, |
| |
| /* Pre-charge values. */ |
| .precharge_current = 256, /* mA */ |
| |
| .start_charging_min_c = 0, |
| .start_charging_max_c = 50, |
| .charging_min_c = 0, |
| .charging_max_c = 60, |
| .discharging_min_c = 0, |
| .discharging_max_c = 75, |
| }; |
| |
| static struct battery_info info_4s_LGC = { |
| |
| .voltage_max = 17200, |
| .voltage_normal = 15200, /* Average of max & min */ |
| .voltage_min = 12000, |
| |
| /* Pre-charge values. */ |
| .precharge_current = 256, /* mA */ |
| |
| .start_charging_min_c = 0, |
| .start_charging_max_c = 50, |
| .charging_min_c = 0, |
| .charging_max_c = 60, |
| .discharging_min_c = 0, |
| .discharging_max_c = 75, |
| }; |
| |
| static struct battery_device support_batteries[] = { |
| { |
| .manuf = "NVT", |
| .device = "ARROW", |
| .design_mv = 7400, |
| .battery_info = &info_2s, |
| .support_cut_off = 0, |
| }, |
| { |
| .manuf = "SANYO", |
| .device = "AP13J3K", |
| .design_mv = 11250, |
| .battery_info = &info_3s, |
| .support_cut_off = 1, |
| }, |
| { |
| .manuf = "SONYCorp", |
| .device = "AP13J4K", |
| .design_mv = 11400, |
| .battery_info = &info_3s, |
| .support_cut_off = 1, |
| }, |
| { |
| .manuf = "LGC", |
| .device = "AC14B8K", |
| .design_mv = 15200, |
| .battery_info = &info_4s_LGC, |
| .support_cut_off = 1, |
| }, |
| { |
| .manuf = "LGC", |
| .device = "AC14B18J", |
| .design_mv = 11400, |
| .battery_info = &info_3s_LGC, |
| .support_cut_off = 1, |
| }, |
| }; |
| |
| #ifdef CONFIG_BATTERY_OVERRIDE_PARAMS |
| /* |
| * The following parameters are for 2S battery. |
| * There is no corresponding params for 3S battery. |
| */ |
| enum { |
| TEMP_RANGE_10, |
| TEMP_RANGE_23, |
| TEMP_RANGE_35, |
| TEMP_RANGE_45, |
| TEMP_RANGE_50, |
| TEMP_RANGE_MAX |
| }; |
| |
| enum { |
| VOLT_RANGE_7200, |
| VOLT_RANGE_8000, |
| VOLT_RANGE_8400, |
| VOLT_RANGE_MAX |
| }; |
| |
| /* |
| * Vendor provided charging method |
| * temp : < 7.2V, 7.2V ~ 8.0V, 8.0V ~ 8.4V |
| * - 0 ~ 10 : 0.8A 1.6A 0.8A |
| * - 10 ~ 23 : 1.6A 4.0A 1.6A |
| * - 23 ~ 35 : 4.0A 4.0A 4.0A |
| * - 35 ~ 45 : 1.6A 4.0A 1.6A |
| * - 45 ~ 50 : 0.8A 1.6A 0.8A |
| */ |
| static const int const current_limit[TEMP_RANGE_MAX][VOLT_RANGE_MAX] = { |
| { 800, 1600, 800}, |
| {1600, 4000, 1600}, |
| {4000, 4000, 4000}, |
| {1600, 4000, 1600}, |
| { 800, 1600, 800}, |
| }; |
| |
| static inline void limit_value(int *val, int limit) |
| { |
| if (*val > limit) |
| *val = limit; |
| } |
| |
| void battery_override_params(struct batt_params *batt) |
| { |
| int *desired_current = &batt->desired_current; |
| int temp_range, volt_range; |
| int bat_temp_c = DECI_KELVIN_TO_CELSIUS(batt->temperature); |
| |
| if (battery_info == NULL) |
| return; |
| |
| /* Return if the battery is not a 2S battery */ |
| if (battery_info->voltage_max != info_2s.voltage_max) |
| return; |
| |
| /* Limit charging voltage */ |
| if (batt->desired_voltage > battery_info->voltage_max) |
| batt->desired_voltage = battery_info->voltage_max; |
| |
| /* Don't charge if outside of allowable temperature range */ |
| if (bat_temp_c >= battery_info->charging_max_c || |
| bat_temp_c < battery_info->charging_min_c) { |
| batt->desired_voltage = 0; |
| batt->desired_current = 0; |
| batt->flags &= ~BATT_FLAG_WANT_CHARGE; |
| return; |
| } |
| |
| if (bat_temp_c <= 10) |
| temp_range = TEMP_RANGE_10; |
| else if (bat_temp_c <= 23) |
| temp_range = TEMP_RANGE_23; |
| else if (bat_temp_c <= 35) |
| temp_range = TEMP_RANGE_35; |
| else if (bat_temp_c <= 45) |
| temp_range = TEMP_RANGE_45; |
| else |
| temp_range = TEMP_RANGE_50; |
| |
| if (batt->voltage < 7200) |
| volt_range = VOLT_RANGE_7200; |
| else if (batt->voltage < 8000) |
| volt_range = VOLT_RANGE_8000; |
| else |
| volt_range = VOLT_RANGE_8400; |
| |
| limit_value(desired_current, current_limit[temp_range][volt_range]); |
| |
| /* If battery wants current, give it at least the precharge current */ |
| if (*desired_current > 0 && |
| *desired_current < battery_info->precharge_current) |
| *desired_current = battery_info->precharge_current; |
| } |
| #endif /* CONFIG_BATTERY_OVERRIDE_PARAMS */ |
| |
| const struct battery_info *battery_get_info(void) |
| { |
| int i; |
| char manuf[9]; |
| char device[9]; |
| int design_mv; |
| |
| if (battery_manufacturer_name(manuf, sizeof(manuf))) { |
| CPRINTS("Failed to get MANUF name"); |
| return &info_precharge; |
| } |
| |
| if (battery_device_name(device, sizeof(device))) { |
| CPRINTS("Failed to get DEVICE name"); |
| return &info_precharge; |
| } |
| if (battery_design_voltage((int *)&design_mv)) { |
| CPRINTS("Failed to get DESIGN_VOLTAGE"); |
| return &info_precharge; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(support_batteries); ++i) { |
| if ((strcasecmp(support_batteries[i].manuf, manuf) == 0) && |
| (strcasecmp(support_batteries[i].device, device) == 0) && |
| (support_batteries[i].design_mv == design_mv)) { |
| CPRINTS("battery Manuf:%s, Device=%s, design=%u", |
| manuf, device, design_mv); |
| support_cut_off = support_batteries[i].support_cut_off; |
| battery_info = support_batteries[i].battery_info; |
| return battery_info; |
| } |
| } |
| |
| CPRINTS("un-recognized battery Manuf:%s, Device:%s", |
| manuf, device); |
| return &info_precharge; |
| } |
| |
| int board_cut_off_battery(void) |
| { |
| if (support_cut_off) |
| return sb_write(SB_SHIP_MODE_ADDR, SB_SHIP_MODE_DATA); |
| else |
| return EC_RES_INVALID_COMMAND; |
| } |