/*
* Copyright (C) 2013 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-manta"
#include <errno.h>
#include <fcntl.h>
#include <healthd.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <batteryservice/BatteryService.h>
#include <cutils/klog.h>
#include <sys/stat.h>
#include <sys/types.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 DS2784_PATH POWER_SUPPLY_SYSFS_PATH "/ds2784-fuelgauge"
#define CHARGE_COUNTER_EXT_PATH DS2784_PATH "/charge_counter_ext"
using namespace android;
#define TEMP_HIGH_THRESHOLD 600 /* 60C */
#define TEMP_HIGH_RECOVERY 420 /* 42C */
#define TEMP_LOW_RECOVERY 0 /* 0C */
#define TEMP_LOW_THRESHOLD -50 /* -5C */
#define FULL_CHARGING_TIME (12 * 60 * 60)
#define RECHARGING_TIME (2 * 60 * 60)
#define RECHARGING_VOLTAGE (4250)
static bool manta_bat_recharging;
static time_t manta_bat_charging_start_time;
static unsigned int manta_bat_health = BATTERY_HEALTH_GOOD;
static unsigned int manta_bat_charging_status = BATTERY_STATUS_DISCHARGING;
static bool manta_bat_batterypresent;
static int charge_enabled_fd;
static void manta_bat_check_temp(struct BatteryProperties *props)
{
if (props->chargerAcOnline == false &&
props->chargerUsbOnline == false)
return;
if (props->batteryTemperature >= TEMP_HIGH_THRESHOLD) {
if (manta_bat_health != BATTERY_HEALTH_OVERHEAT &&
manta_bat_health != BATTERY_HEALTH_UNSPECIFIED_FAILURE) {
KLOG_INFO(LOG_TAG,
"battery overheat (%d), charging unavailable\n",
props->batteryTemperature);
manta_bat_health = BATTERY_HEALTH_OVERHEAT;
}
} else if (props->batteryTemperature <= TEMP_HIGH_RECOVERY &&
props->batteryTemperature >= TEMP_LOW_RECOVERY) {
if (manta_bat_health == BATTERY_HEALTH_OVERHEAT ||
manta_bat_health == BATTERY_HEALTH_COLD) {
KLOG_INFO(LOG_TAG,
"battery recovery (%d), charging available\n",
props->batteryTemperature);
manta_bat_health = BATTERY_HEALTH_GOOD;
}
} else if (props->batteryTemperature <= TEMP_LOW_THRESHOLD) {
if (manta_bat_health != BATTERY_HEALTH_COLD &&
manta_bat_health != BATTERY_HEALTH_UNSPECIFIED_FAILURE) {
KLOG_INFO(LOG_TAG,
"battery cold (%d), charging unavailable\n",
props->batteryTemperature);
manta_bat_health = BATTERY_HEALTH_COLD;
}
}
}
static void manta_bat_set_charge_time(bool enable)
{
if (enable && !manta_bat_charging_start_time)
time(&manta_bat_charging_start_time);
else if (!enable)
manta_bat_charging_start_time = 0;
}
static void manta_bat_enable_charging(bool enable)
{
int ret;
char val[20];
if (enable && (manta_bat_health != BATTERY_HEALTH_GOOD)) {
manta_bat_charging_status = BATTERY_STATUS_NOT_CHARGING;
return;
}
if (charge_enabled_fd < 0)
goto err;
snprintf(val, sizeof(val), "%d", enable);
ret = write(charge_enabled_fd, val, strlen(val));
if (ret == -1) {
KLOG_ERROR(LOG_TAG, "charge_enabled write error; errno=%d\n", errno);
goto err;
}
manta_bat_set_charge_time(enable);
KLOG_INFO(LOG_TAG, "battery charge enable=%d\n", enable);
return;
err:
if (enable)
manta_bat_charging_status = BATTERY_STATUS_NOT_CHARGING;
}
static bool manta_bat_charge_timeout(time_t timeout)
{
if (!manta_bat_charging_start_time)
return false;
return time(NULL) >= manta_bat_charging_start_time + timeout;
}
static void manta_bat_set_full(void)
{
KLOG_INFO(LOG_TAG, "battery full\n");
manta_bat_charging_status = BATTERY_STATUS_FULL;
manta_bat_enable_charging(false);
manta_bat_recharging = false;
}
static void manta_bat_charging_timer(struct BatteryProperties *props)
{
if (!manta_bat_charging_start_time &&
manta_bat_charging_status == BATTERY_STATUS_CHARGING) {
KLOG_WARNING("battery charging timer not started, starting\n");
manta_bat_enable_charging(true);
manta_bat_recharging = true;
} else if (!manta_bat_charging_start_time) {
return;
}
if (manta_bat_charge_timeout(manta_bat_recharging ?
RECHARGING_TIME : FULL_CHARGING_TIME)) {
KLOG_INFO(LOG_TAG, "battery charging timer expired\n");
if (props->batteryVoltage > RECHARGING_VOLTAGE &&
props->batteryLevel == 100) {
manta_bat_set_full();
} else {
manta_bat_enable_charging(false);
manta_bat_recharging = false;
manta_bat_charging_start_time = 0;
}
}
}
static void manta_bat_check_charge_source_changed(
struct BatteryProperties *props)
{
if (props->chargerUsbOnline || props->chargerAcOnline) {
if (manta_bat_charging_status == BATTERY_STATUS_CHARGING ||
(manta_bat_charging_status == BATTERY_STATUS_FULL &&
manta_bat_recharging))
return;
/*
* If charging status indicates a charger was already
* connected prior to this and the status is something
* other than charging ("full" or "not-charging"), leave
* the status alone.
*/
if (manta_bat_charging_status == BATTERY_STATUS_DISCHARGING ||
manta_bat_charging_status == BATTERY_STATUS_UNKNOWN)
manta_bat_charging_status = BATTERY_STATUS_CHARGING;
/*
* Don't re-enable charging if the battery is full and we
* are not actively re-charging it, or if "not-charging"
* status is set.
*/
if (!(manta_bat_charging_status == BATTERY_STATUS_FULL
&& !manta_bat_recharging) &&
manta_bat_charging_status != BATTERY_STATUS_NOT_CHARGING)
manta_bat_enable_charging(true);
} else if (manta_bat_charging_status == BATTERY_STATUS_CHARGING ||
manta_bat_charging_status == BATTERY_STATUS_NOT_CHARGING ||
manta_bat_charging_status == BATTERY_STATUS_FULL) {
manta_bat_charging_status = BATTERY_STATUS_DISCHARGING;
manta_bat_enable_charging(false);
manta_bat_health = BATTERY_HEALTH_GOOD;
manta_bat_recharging = false;
manta_bat_charging_start_time = 0;
}
}
static void manta_bat_monitor(struct BatteryProperties *props)
{
unsigned int old_bat_health = manta_bat_health;
if (manta_bat_batterypresent) {
manta_bat_check_temp(props);
} else {
props->batteryTemperature = 42; /* 4.2C */
props->batteryVoltage = 4342; /* 4342mV */
props->batteryLevel = 42; /* 42% */
}
if (props->batteryStatus == BATTERY_STATUS_FULL &&
(manta_bat_charging_status == BATTERY_STATUS_CHARGING ||
manta_bat_recharging)) {
manta_bat_set_full();
}
manta_bat_check_charge_source_changed(props);
switch (manta_bat_charging_status) {
case BATTERY_STATUS_FULL:
if (props->batteryVoltage < RECHARGING_VOLTAGE &&
!manta_bat_recharging) {
KLOG_INFO(LOG_TAG, "start recharging, v=%d\n",
props->batteryVoltage);
manta_bat_recharging = true;
manta_bat_enable_charging(true);
}
break;
case BATTERY_STATUS_DISCHARGING:
break;
case BATTERY_STATUS_CHARGING:
switch (manta_bat_health) {
case BATTERY_HEALTH_OVERHEAT:
case BATTERY_HEALTH_COLD:
case BATTERY_HEALTH_OVER_VOLTAGE:
case BATTERY_HEALTH_DEAD:
case BATTERY_HEALTH_UNSPECIFIED_FAILURE:
manta_bat_charging_status = BATTERY_STATUS_NOT_CHARGING;
manta_bat_enable_charging(false);
KLOG_INFO(LOG_TAG, "Not charging, health=%d\n",
manta_bat_health);
break;
default:
break;
}
break;
case BATTERY_STATUS_NOT_CHARGING:
if (old_bat_health != BATTERY_HEALTH_GOOD &&
manta_bat_health == BATTERY_HEALTH_GOOD) {
KLOG_INFO(LOG_TAG, "battery health recovered\n");
if (props->chargerUsbOnline || props->chargerAcOnline) {
manta_bat_enable_charging(true);
manta_bat_charging_status = BATTERY_STATUS_CHARGING;
} else {
manta_bat_charging_status =
BATTERY_STATUS_DISCHARGING;
}
}
break;
default:
break;
}
manta_bat_charging_timer(props);
// set health and status according to our state, hardcode invariants
props->batteryHealth = manta_bat_health;
props->batteryStatus = manta_bat_charging_status;
props->batteryTechnology = "Li-ion";
props->batteryPresent = manta_bat_batterypresent;
}
int healthd_board_battery_update(struct BatteryProperties *props)
{
manta_bat_monitor(props);
// return 0 to log periodic polled battery status to kernel log
return 0;
}
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 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 int manta_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)
{
charge_enabled_fd = open(POWER_SUPPLY_SYSFS_PATH
"/manta-battery/charge_enabled", O_WRONLY);
if (charge_enabled_fd == -1)
KLOG_ERROR(LOG_TAG, "open manta-battery/charge_enabled failed"
"; errno=%d\n", errno);
config->batteryCurrentNowPath = DS2784_PATH "/current_now";
if (access(config->batteryCurrentNowPath.string(), R_OK) == 0) {
manta_bat_batterypresent = true;
} else {
KLOG_INFO(LOG_TAG, "Missing battery, using fake battery data\n");
config->batteryCurrentNowPath.clear();
}
config->energyCounter = manta_energy_counter;
}