blob: a108e3beca26fb6c9976910c67903225eefd5118 [file] [log] [blame]
/*
*Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
*
*Redistribution and use in source and binary forms, with or without
*modification, are permitted provided that the following conditions are
*met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
*THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
*MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
*ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
*CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
*SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
*BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
*WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
*OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
*IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <cutils/klog.h>
#include <batteryservice/BatteryService.h>
#include <cutils/android_reboot.h>
#include <cutils/properties.h>
#include <healthd/healthd.h>
#include "minui/minui.h"
#include "healthd_msm.h"
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
#define HVDCP_CHARGER "USB_HVDCP"
#define HVDCP_BLINK_TYPE 2
#define RED_LED_PATH "/sys/class/leds/red/brightness"
#define GREEN_LED_PATH "/sys/class/leds/green/brightness"
#define BLUE_LED_PATH "/sys/class/leds/blue/brightness"
#define RED_LED_BLINK_PATH "/sys/class/leds/red/blink"
#define GREEN_LED_BLINK_PATH "/sys/class/leds/green/blink"
#define BACKLIGHT_PATH "/sys/class/backlight/panel0-backlight/brightness"
#define CHARGING_ENABLED_PATH "/sys/class/power_supply/battery/charging_enabled"
#define CHARGER_TYPE_PATH "/sys/class/power_supply/usb/type"
#define BMS_READY_PATH "/sys/class/power_supply/bms/soc_reporting_ready"
#define BMS_BATT_INFO_PATH "/sys/class/power_supply/bms/battery_info"
#define BMS_BATT_INFO_ID_PATH "/sys/class/power_supply/bms/battery_info_id"
#define BMS_BATT_RES_ID_PATH "/sys/class/power_supply/bms/resistance_id"
#define PERSIST_BATT_INFO_PATH "/persist/bms/batt_info.txt"
#define CHGR_TAG "charger"
#define HEALTHD_TAG "healthd_msm"
#define LOGE(tag, x...) do { KLOG_ERROR(tag, x); } while (0)
#define LOGW(tag, x...) do { KLOG_WARNING(tag, x); } while (0)
#define LOGV(tag, x...) do { KLOG_DEBUG(tag, x); } while (0)
enum {
RED_LED = 0x01 << 0,
GREEN_LED = 0x01 << 1,
BLUE_LED = 0x01 << 2,
};
enum batt_info_params {
BATT_INFO_NOTIFY = 0,
BATT_INFO_SOC,
BATT_INFO_RES_ID,
BATT_INFO_VOLTAGE,
BATT_INFO_TEMP,
BATT_INFO_FCC,
BATT_INFO_MAX,
};
struct led_ctl {
int color;
const char *path;
};
struct led_ctl leds[3] =
{{RED_LED, RED_LED_PATH},
{GREEN_LED, GREEN_LED_PATH},
{BLUE_LED, BLUE_LED_PATH}};
#define HVDCP_COLOR_MAP (RED_LED | GREEN_LED)
struct soc_led_color_mapping {
int soc;
int color;
};
struct soc_led_color_mapping soc_leds[3] = {
{15, RED_LED},
{90, RED_LED | GREEN_LED},
{100, GREEN_LED},
};
static int batt_info_cached[BATT_INFO_MAX];
static bool healthd_msm_err_log_once;
static int8_t healthd_msm_log_en;
static int8_t healthd_msm_store_params;
static int write_file_int(char const* path, int value)
{
int fd;
char buffer[20];
int rc = -1, bytes;
fd = open(path, O_WRONLY);
if (fd >= 0) {
bytes = snprintf(buffer, sizeof(buffer), "%d\n", value);
rc = write(fd, buffer, bytes);
close(fd);
}
return rc > 0 ? 0 : -1;
}
static int set_tricolor_led(int on, int color)
{
int rc, i;
char buffer[10];
for (i = 0; i < (int)ARRAY_SIZE(leds); i++) {
if ((color & leds[i].color) && (access(leds[i].path, R_OK | W_OK) == 0)) {
rc = write_file_int(leds[i].path, on ? 255 : 0);
if (rc < 0)
return rc;
}
}
return 0;
}
static bool is_hvdcp_inserted()
{
bool hvdcp = false;
char buff[12] = "\0";
int fd, cnt;
fd = open(CHARGER_TYPE_PATH, O_RDONLY);
if (fd >= 0) {
cnt = read(fd, buff, (sizeof(buff) - 1));
if (cnt > 0 && !strncmp(buff, HVDCP_CHARGER, 9))
hvdcp = true;
close(fd);
}
return hvdcp;
}
static int get_blink_led_for_hvdcp(void)
{
int ret, rc = 0, bytes;
int red_blink_fd = -1, green_blink_fd = -1, type_fd = -1;
char buf[20];
type_fd = open(CHARGER_TYPE_PATH, O_RDONLY);
if (type_fd < 0) {
LOGE(CHGR_TAG, "Could not open USB type node\n");
return rc;
} else {
close(type_fd);
ret = write_file_int(GREEN_LED_BLINK_PATH, 0);
if (ret < 0) {
LOGE(CHGR_TAG, "Fail to write: %s\n", GREEN_LED_BLINK_PATH);
} else {
rc |= GREEN_LED;
}
ret = write_file_int(RED_LED_BLINK_PATH, 0);
if (ret < 0) {
LOGE(CHGR_TAG, "Fail to write: %s\n", RED_LED_BLINK_PATH);
} else {
rc |= RED_LED;
}
}
return rc;
}
#if QTI_BSP
#define STR_LEN 8
void healthd_board_mode_charger_draw_battery(
struct android::BatteryProperties *batt_prop)
{
char cap_str[STR_LEN];
int x, y;
int str_len_px;
static int char_height = -1, char_width = -1;
if (char_height == -1 && char_width == -1)
gr_font_size(&char_width, &char_height);
snprintf(cap_str, (STR_LEN - 1), "%d%%", batt_prop->batteryLevel);
str_len_px = gr_measure(cap_str);
x = (gr_fb_width() - str_len_px) / 2;
y = (gr_fb_height() + char_height) / 2;
gr_color(0xa4, 0xc6, 0x39, 255);
gr_text(x, y, cap_str, 0);
}
#endif
void healthd_board_mode_charger_battery_update(
struct android::BatteryProperties *batt_prop)
{
static int blink_for_hvdcp = -1;
static int old_color = 0;
int i, color, soc, rc;
bool blink = false;
if (blink_for_hvdcp == -1)
blink_for_hvdcp = get_blink_led_for_hvdcp();
if ((blink_for_hvdcp > 0) && is_hvdcp_inserted())
blink = true;
soc = batt_prop->batteryLevel;
for (i = 0; i < ((int)ARRAY_SIZE(soc_leds) - 1); i++) {
if (soc < soc_leds[i].soc)
break;
}
color = soc_leds[i].color;
if (old_color != color) {
if ((color == HVDCP_COLOR_MAP) && blink) {
if (blink_for_hvdcp & RED_LED) {
rc = write_file_int(RED_LED_BLINK_PATH, HVDCP_BLINK_TYPE);
if (rc < 0) {
LOGE(CHGR_TAG, "Fail to write: %s\n", RED_LED_BLINK_PATH);
return;
}
}
if (blink_for_hvdcp & GREEN_LED) {
rc = write_file_int(GREEN_LED_BLINK_PATH, HVDCP_BLINK_TYPE);
if (rc < 0) {
LOGE(CHGR_TAG, "Fail to write: %s\n", GREEN_LED_BLINK_PATH);
return;
}
}
} else {
rc = set_tricolor_led(0, old_color);
if (rc < 0)
LOGE(CHGR_TAG, "Error in setting old_color on tricolor_led\n");
rc = set_tricolor_led(1, color);
if (rc < 0)
LOGE(CHGR_TAG, "Error in setting color on tricolor_led\n");
if (!rc) {
old_color = color;
LOGV(CHGR_TAG, "soc = %d, set led color 0x%x\n", soc, soc_leds[i].color);
}
}
}
}
#define BACKLIGHT_ON_LEVEL 100
#define BACKLIGHT_OFF_LEVEL 0
void healthd_board_mode_charger_set_backlight(bool en)
{
int rc;
if (access(BACKLIGHT_PATH, R_OK | W_OK) != 0)
{
LOGW(CHGR_TAG, "Backlight control not support\n");
return;
}
rc = write_file_int(BACKLIGHT_PATH, en ? BACKLIGHT_ON_LEVEL :
BACKLIGHT_OFF_LEVEL);
if (rc < 0) {
LOGE(CHGR_TAG, "Could not write to backlight node : %s\n", strerror(errno));
return;
}
LOGV(CHGR_TAG, "set backlight status to %d\n", en);
}
static inline void get_healthd_props()
{
healthd_msm_log_en = property_get_bool("persist.healthd_msm.log_en", 1);
healthd_msm_store_params =
property_get_bool("persist.healthd_msm.store_params", 0);
}
#define WAIT_BMS_READY_TIMES_MAX 200
#define WAIT_BMS_READY_INTERVAL_USEC 200000
void healthd_board_mode_charger_init()
{
int ret;
char buff[8] = "\0";
int charging_enabled = 0;
int bms_ready = 0;
int wait_count = 0;
int fd;
/* check the charging is enabled or not */
fd = open(CHARGING_ENABLED_PATH, O_RDONLY);
if (fd < 0)
return;
ret = read(fd, buff, (sizeof(buff) - 1));
close(fd);
if (ret > 0) {
buff[ret] = '\0';
sscanf(buff, "%d\n", &charging_enabled);
LOGW(CHGR_TAG, "android charging is %s\n",
!!charging_enabled ? "enabled" : "disabled");
/* if charging is disabled, reboot and exit power off charging */
if (!charging_enabled)
android_reboot(ANDROID_RB_RESTART, 0, 0);
}
fd = open(BMS_READY_PATH, O_RDONLY);
if (fd < 0)
return;
while (1) {
ret = read(fd, buff, (sizeof(buff) - 1));
if (ret > 0) {
buff[ret] = '\0';
sscanf(buff, "%d\n", &bms_ready);
} else {
LOGE(CHGR_TAG, "read soc-ready failed, ret=%d\n", ret);
break;
}
if ((bms_ready > 0) || (wait_count++ > WAIT_BMS_READY_TIMES_MAX))
break;
usleep(WAIT_BMS_READY_INTERVAL_USEC);
lseek(fd, 0, SEEK_SET);
}
close(fd);
LOGV(CHGR_TAG, "Checking BMS SoC ready done %d!\n", bms_ready);
}
static void healthd_batt_info_notify()
{
int rc, fd, id = 0;
int bms_ready = 0;
int wait_count = 0;
char buff[100] = "";
int batt_info[BATT_INFO_MAX];
char *ptr, *tmp, *temp_str;
char path_str[50] = "";
bool notify_bms = false;
if (!healthd_msm_store_params) {
return;
}
fd = open(PERSIST_BATT_INFO_PATH, O_RDONLY);
if (fd < 0) {
LOGW(HEALTHD_TAG, "Error in opening batt_info.txt, fd=%d\n", fd);
fd = creat(PERSIST_BATT_INFO_PATH, S_IRWXU);
if (fd < 0) {
LOGE(HEALTHD_TAG, "Couldn't create file, fd=%d errno=%s\n", fd,
strerror(errno));
goto out;
}
LOGV(HEALTHD_TAG, "Created file %s\n", PERSIST_BATT_INFO_PATH);
close(fd);
goto out;
} else {
LOGV(HEALTHD_TAG, "opened %s\n", PERSIST_BATT_INFO_PATH);
}
rc = read(fd, buff, (sizeof(buff) - 1));
if (rc < 0) {
LOGE(HEALTHD_TAG, "Error in reading fd %d, rc=%d\n", fd, rc);
close(fd);
goto out;
}
close(fd);
buff[rc] = '\0';
temp_str = strtok_r(buff, ":", &ptr);
id = 1;
while (temp_str != NULL && id < BATT_INFO_MAX) {
batt_info[id++] = (int)strtol(temp_str, &tmp, 10);
temp_str = strtok_r(NULL, ":", &ptr);
}
if (id < BATT_INFO_MAX) {
LOGE(HEALTHD_TAG, "Read %d batt_info parameters\n", id);
goto out;
}
/* Send batt_info parameters to FG driver */
for (id = 1; id < BATT_INFO_MAX; id++) {
snprintf(path_str, sizeof(path_str), "%s", BMS_BATT_INFO_ID_PATH);
rc = write_file_int(path_str, id);
if (rc < 0) {
LOGE(HEALTHD_TAG, "Error in writing batt_info_id %d, rc=%d\n", id,
rc);
goto out;
}
snprintf(path_str, sizeof(path_str), "%s", BMS_BATT_INFO_PATH);
rc = write_file_int(path_str, batt_info[id]);
if (rc < 0) {
LOGE(HEALTHD_TAG, "Error in writing batt_info %d, rc=%d\n",
batt_info[id], rc);
goto out;
}
}
notify_bms = true;
out:
fd = open(BMS_READY_PATH, O_RDONLY);
if (fd < 0) {
LOGE(HEALTHD_TAG, "Couldn't open %s\n", BMS_READY_PATH);
return;
}
/* Wait for soc_reporting_ready */
wait_count = 0;
memset(buff, 0, sizeof(buff));
while (1) {
rc = read(fd, buff, 1);
if (rc > 0) {
sscanf(buff, "%d\n", &bms_ready);
} else {
LOGE(HEALTHD_TAG, "read soc-ready failed, rc=%d\n", rc);
break;
}
if ((bms_ready > 0) || (wait_count++ > WAIT_BMS_READY_TIMES_MAX))
break;
usleep(WAIT_BMS_READY_INTERVAL_USEC);
lseek(fd, 0, SEEK_SET);
}
close(fd);
if (!bms_ready)
notify_bms = false;
if (!notify_bms) {
LOGE(HEALTHD_TAG, "Not notifying BMS\n");
return;
}
/* Notify FG driver */
snprintf(path_str, sizeof(path_str), "%s", BMS_BATT_INFO_ID_PATH);
rc = write_file_int(path_str, BATT_INFO_NOTIFY);
if (rc < 0) {
LOGE(HEALTHD_TAG, "Error in writing batt_info_id, rc=%d\n", rc);
return;
}
snprintf(path_str, sizeof(path_str), "%s", BMS_BATT_INFO_PATH);
rc = write_file_int(path_str, INT_MAX - 1);
if (rc < 0)
LOGE(HEALTHD_TAG, "Error in writing batt_info, rc=%d\n", rc);
}
void healthd_board_init(struct healthd_config*)
{
// use defaults
get_healthd_props();
power_off_alarm_init();
healthd_batt_info_notify();
}
static void healthd_store_batt_props(const struct android::BatteryProperties* props)
{
char buff[100];
int fd, rc, len, batteryId = 0;
if (!healthd_msm_store_params) {
return;
}
if (!props->batteryPresent) {
return;
}
if (props->batteryLevel == 0 || props->batteryVoltage == 0) {
return;
}
memset(buff, 0, sizeof(buff));
fd = open(BMS_BATT_RES_ID_PATH, O_RDONLY);
if (fd < 0) {
if (!healthd_msm_err_log_once) {
LOGE(HEALTHD_TAG, "Couldn't open %s\n", BMS_BATT_RES_ID_PATH);
healthd_msm_err_log_once = true;
}
} else {
rc = read(fd, buff, 6);
if (rc > 0) {
sscanf(buff, "%d\n", &batteryId);
batteryId /= 1000;
} else if (!healthd_msm_err_log_once) {
LOGE(HEALTHD_TAG, "reading batt_res_id failed, rc=%d\n", rc);
healthd_msm_err_log_once = true;
}
}
if (fd >= 0)
close(fd);
if (props->batteryLevel == batt_info_cached[BATT_INFO_SOC] &&
props->batteryVoltage == batt_info_cached[BATT_INFO_VOLTAGE] &&
props->batteryTemperature == batt_info_cached[BATT_INFO_TEMP] &&
props->batteryFullCharge == batt_info_cached[BATT_INFO_FCC] &&
batteryId == batt_info_cached[BATT_INFO_RES_ID])
return;
fd = open(PERSIST_BATT_INFO_PATH, O_RDWR | O_TRUNC);
if (fd < 0) {
/*
* Print the error just only once as this function can be called as
* long as the system is running and logs should not flood the console.
*/
if (!healthd_msm_err_log_once) {
LOGE(HEALTHD_TAG, "Error in opening batt_info.txt, fd=%d\n", fd);
healthd_msm_err_log_once = true;
}
return;
}
len = snprintf(buff, sizeof(buff), "%d:%d:%d:%d:%d", props->batteryLevel,
batteryId, props->batteryVoltage,
props->batteryTemperature, props->batteryFullCharge);
if (len < 0) {
if (!healthd_msm_err_log_once) {
LOGE(HEALTHD_TAG, "Error in printing to buff, len=%d\n", len);
healthd_msm_err_log_once = true;
}
goto out;
}
buff[len] = '\0';
rc = write(fd, buff, sizeof(buff));
if (rc < 0) {
if (!healthd_msm_err_log_once) {
LOGE(HEALTHD_TAG, "Error in writing to batt_info.txt, rc=%d\n", rc);
healthd_msm_err_log_once = true;
}
goto out;
}
batt_info_cached[BATT_INFO_SOC] = props->batteryLevel;
batt_info_cached[BATT_INFO_RES_ID] = batteryId;
batt_info_cached[BATT_INFO_VOLTAGE] = props->batteryVoltage;
batt_info_cached[BATT_INFO_TEMP] = props->batteryTemperature;
batt_info_cached[BATT_INFO_FCC] = props->batteryFullCharge;
out:
if (fd >= 0)
close(fd);
}
int healthd_board_battery_update(struct android::BatteryProperties* props)
{
// return 0 to log periodic polled battery status to kernel log
healthd_store_batt_props(props);
if (healthd_msm_log_en)
return 0;
return 1;
}