| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "healthd-flounder" |
| #include <healthd/healthd.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <cutils/klog.h> |
| #include <sys/types.h> |
| #include <sys/sysinfo.h> |
| |
| /* Nominal voltage for ENERGY_COUNTER computation */ |
| #define VOLTAGE_NOMINAL 3.7 |
| |
| #define POWER_SUPPLY_SUBSYSTEM "power_supply" |
| #define POWER_SUPPLY_SYSFS_PATH "/sys/class/" POWER_SUPPLY_SUBSYSTEM |
| |
| #define MAX17050_PATH POWER_SUPPLY_SYSFS_PATH "/battery" |
| #define CHARGE_COUNTER_EXT_PATH MAX17050_PATH "/charge_counter_ext" |
| |
| #define PALMAS_VOLTAGE_MONITOR_PATH POWER_SUPPLY_SYSFS_PATH "/palmas_voltage_monitor" |
| #define VOLTAGE_MONITOR_PATH PALMAS_VOLTAGE_MONITOR_PATH "/device/voltage_monitor" |
| |
| #define BATTERY_FULL 100 |
| #define BATTERY_LOW 15 |
| #define BATTERY_CRITICAL_LOW_MV (3000) |
| #define BATTERY_DEAD_MV (2800) |
| #define NORMAL_MAX_SOC_DEC (2) |
| #define CRITICAL_LOW_FORCE_SOC_DROP (6) |
| #define UPDATE_PERIOD_MINIMUM_S (55) |
| #define BATTERY_OVERTEMP_THRESHOLD (550) |
| #define BATTERY_UNDERTEMP_THRESHOLD (0) |
| |
| using namespace android; |
| static bool first_update_done; |
| static int lasttime_soc; |
| static unsigned int flounder_monitor_voltage; |
| static long last_update_time; |
| static bool force_decrease; |
| |
| static int read_sysfs(const char *path, char *buf, size_t size) { |
| char *cp = NULL; |
| |
| int fd = open(path, O_RDONLY, 0); |
| if (fd == -1) { |
| KLOG_ERROR(LOG_TAG, "Could not open '%s'\n", path); |
| return -1; |
| } |
| |
| ssize_t count = TEMP_FAILURE_RETRY(read(fd, buf, size)); |
| if (count > 0) |
| cp = (char *)memrchr(buf, '\n', count); |
| |
| if (cp) |
| *cp = '\0'; |
| else |
| buf[0] = '\0'; |
| |
| close(fd); |
| return count; |
| } |
| |
| static void write_sysfs(const char *path, char *s) |
| { |
| char buf[80]; |
| int len; |
| int fd = open(path, O_WRONLY); |
| |
| if (fd < 0) { |
| strerror_r(errno, buf, sizeof(buf)); |
| KLOG_ERROR(LOG_TAG, "Error opening %s: %s\n", path, buf); |
| return; |
| } |
| |
| len = write(fd, s, strlen(s)); |
| if (len < 0) { |
| strerror_r(errno, buf, sizeof(buf)); |
| KLOG_ERROR(LOG_TAG, "Error writing to %s: %s\n", path, buf); |
| } |
| |
| close(fd); |
| } |
| |
| static int64_t get_int64_field(const char *path) { |
| const int SIZE = 21; |
| char buf[SIZE]; |
| |
| int64_t value = 0; |
| if (read_sysfs(path, buf, SIZE) > 0) { |
| value = strtoll(buf, NULL, 0); |
| } |
| return value; |
| } |
| |
| static void flounder_status_check(struct BatteryProperties *props) |
| { |
| if (props->batteryStatus == BATTERY_STATUS_UNKNOWN) |
| props->batteryStatus = BATTERY_STATUS_DISCHARGING; |
| else if (props->batteryStatus == BATTERY_STATUS_FULL && |
| props->batteryLevel < BATTERY_FULL) |
| props->batteryStatus = BATTERY_STATUS_CHARGING; |
| } |
| |
| static void flounder_health_check(struct BatteryProperties *props) |
| { |
| if (props->batteryLevel >= BATTERY_FULL) |
| props->batteryHealth = BATTERY_HEALTH_GOOD; |
| else if (props->batteryLevel < BATTERY_LOW) |
| props->batteryHealth = BATTERY_HEALTH_DEAD; |
| else |
| props->batteryHealth = BATTERY_HEALTH_GOOD; |
| |
| if (props->batteryHealth == BATTERY_HEALTH_GOOD){ |
| if (props->batteryTemperature > BATTERY_OVERTEMP_THRESHOLD) |
| props->batteryHealth = BATTERY_HEALTH_OVERHEAT; |
| else if(props->batteryTemperature < BATTERY_UNDERTEMP_THRESHOLD) |
| props->batteryHealth = BATTERY_HEALTH_COLD; |
| } |
| } |
| |
| static void flounder_voltage_monitor_check(struct BatteryProperties *props) |
| { |
| unsigned int monitor_voltage = 0; |
| int vcell_mv; |
| char voltage[10]; |
| |
| if (props->batteryStatus != BATTERY_STATUS_CHARGING && |
| props->batteryStatus != BATTERY_STATUS_FULL && props->batteryLevel > 0) { |
| vcell_mv = props->batteryVoltage; |
| if (vcell_mv > BATTERY_CRITICAL_LOW_MV) |
| monitor_voltage = BATTERY_CRITICAL_LOW_MV; |
| else if (vcell_mv > BATTERY_DEAD_MV) |
| monitor_voltage = BATTERY_DEAD_MV; |
| } |
| |
| if (monitor_voltage != flounder_monitor_voltage) { |
| snprintf(voltage, sizeof(voltage), "%d", monitor_voltage); |
| write_sysfs(VOLTAGE_MONITOR_PATH, voltage); |
| flounder_monitor_voltage = monitor_voltage; |
| } |
| } |
| |
| static void flounder_soc_adjust(struct BatteryProperties *props) |
| { |
| int soc_decrease; |
| int soc, vcell_mv; |
| struct sysinfo info; |
| long uptime = 0; |
| int ret; |
| |
| ret = sysinfo(&info); |
| if (ret) { |
| KLOG_ERROR(LOG_TAG, "Fail to get sysinfo!!\n"); |
| uptime = last_update_time; |
| } else |
| uptime = info.uptime; |
| |
| if (!first_update_done) { |
| if (props->batteryLevel >= BATTERY_FULL) { |
| props->batteryLevel = BATTERY_FULL - 1; |
| lasttime_soc = BATTERY_FULL - 1; |
| } else { |
| lasttime_soc = props->batteryLevel; |
| } |
| last_update_time = uptime; |
| first_update_done = true; |
| } |
| |
| if (props->batteryStatus == BATTERY_STATUS_FULL) |
| soc = BATTERY_FULL; |
| else if (props->batteryLevel >= BATTERY_FULL && |
| lasttime_soc < BATTERY_FULL) |
| soc = BATTERY_FULL - 1; |
| else |
| soc = props->batteryLevel; |
| |
| if (props->batteryLevel > BATTERY_FULL) |
| props->batteryLevel = BATTERY_FULL; |
| else if (props->batteryLevel < 0) |
| props->batteryLevel = 0; |
| |
| vcell_mv = props->batteryVoltage; |
| if (props->batteryStatus == BATTERY_STATUS_DISCHARGING || |
| props->batteryStatus == BATTERY_STATUS_NOT_CHARGING || |
| props->batteryStatus == BATTERY_STATUS_UNKNOWN) { |
| if (vcell_mv >= BATTERY_CRITICAL_LOW_MV) { |
| force_decrease = false; |
| soc_decrease = lasttime_soc - soc; |
| if (soc_decrease < 0) { |
| soc = lasttime_soc; |
| goto done; |
| } |
| |
| if (uptime > last_update_time && |
| uptime - last_update_time <= UPDATE_PERIOD_MINIMUM_S) { |
| soc = lasttime_soc; |
| goto done; |
| } |
| |
| if (soc_decrease < 0) |
| soc_decrease = 0; |
| else if (soc_decrease > NORMAL_MAX_SOC_DEC) |
| soc_decrease = NORMAL_MAX_SOC_DEC; |
| |
| soc = lasttime_soc - soc_decrease; |
| } else if (vcell_mv < BATTERY_DEAD_MV) { |
| soc = 0; |
| } else { |
| if (force_decrease && |
| uptime > last_update_time && |
| uptime - last_update_time <= UPDATE_PERIOD_MINIMUM_S) { |
| soc = lasttime_soc; |
| goto done; |
| } |
| |
| soc_decrease = CRITICAL_LOW_FORCE_SOC_DROP; |
| if (lasttime_soc <= soc_decrease) |
| soc = 0; |
| else |
| soc = lasttime_soc - soc_decrease; |
| force_decrease = true; |
| } |
| } else { |
| force_decrease = false; |
| if (soc > lasttime_soc) |
| soc = lasttime_soc + 1; |
| } |
| last_update_time = uptime; |
| done: |
| props->batteryLevel = lasttime_soc = soc; |
| } |
| |
| static void flounder_bat_monitor(struct BatteryProperties *props) |
| { |
| flounder_soc_adjust(props); |
| flounder_health_check(props); |
| flounder_status_check(props); |
| flounder_voltage_monitor_check(props); |
| } |
| |
| int healthd_board_battery_update(struct BatteryProperties *props) |
| { |
| |
| flounder_bat_monitor(props); |
| |
| // return 0 to log periodic polled battery status to kernel log |
| return 0; |
| } |
| |
| static int flounder_energy_counter(int64_t *energy) |
| { |
| *energy = get_int64_field(CHARGE_COUNTER_EXT_PATH) * VOLTAGE_NOMINAL; |
| return 0; |
| } |
| |
| void healthd_board_init(struct healthd_config *config) |
| { |
| config->energyCounter = flounder_energy_counter; |
| } |