google_battery: expose battery health to sysfs nodes
- Battery cycle count
- Date of manufacturing of the battery
- Date of first use of the battery (since activation)
Bug: 251427008
Signed-off-by: Jack Wu <[email protected]>
Change-Id: Icc9423021356d40df347e3ae5a81c10729a7f1ed
(cherry picked from commit ce25b383c23975a3469bfe6ea550dc432734945d)
diff --git a/google_battery.c b/google_battery.c
index dd3e5a8..dfbd151 100644
--- a/google_battery.c
+++ b/google_battery.c
@@ -27,6 +27,7 @@
#include <linux/platform_device.h>
#include <linux/thermal.h>
#include <linux/slab.h>
+#include <linux/rtc.h>
#include "gbms_power_supply.h"
#include "google_bms.h"
#include "google_psy.h"
@@ -306,6 +307,13 @@
[BHI_ALGO_MIX_N_MATCH] = {90, 10, 5},
};
+struct bm_date {
+ u8 bm_y;
+ u8 bm_m;
+ u8 bm_d;
+ u8 reserve;
+};
+
struct bhi_data
{
/* context */
@@ -325,6 +333,10 @@
int swell_cumulative; /* from swell data */
int ccbin_index; /* from SOC residency */
+ /* battery manufacture date */
+ struct bm_date bm_date; /* from eeprom SN */
+ u8 act_date[BATT_EEPROM_TAG_XYMD_LEN];
+
};
struct health_data
@@ -3243,6 +3255,79 @@
/* BHI -------------------------------------------------------------------- */
+/*
+ * Format of XYMD:
+ * XYMD[0]: YEAR (1 CHARACTER, NUMERIC; LAST DIGIT OF YEAR)
+ * XYMD[1]: Month(1 Character ASCII,Alphanumeric;1-9 For Han-Sept,A=Oct,B=Nov,C=Dec)
+ * XYMD[2]: Day(1 Character ASCII,Alphanumeric;1-9 For 1st-9th, A=10th,B=11th,...X=31st;
+ * skip the letter “I” and the letter”O”)
+ */
+static inline int date_to_xymd(u8 val)
+{
+ if (val >= 23)
+ return (val - 23) + 0x50; /* 0x50 = 'P', 23th */
+ else if (val >= 18)
+ return (val - 18) + 0x4a; /* 0x4a = 'J', 18th*/
+ else if (val >= 10)
+ return (val - 10) + 0x41; /* 0x41 = 'A', 10th*/
+ else
+ return (val + 0x30);
+}
+
+static inline int xymd_to_date(u8 val)
+{
+ if (val >= 0x50)
+ return (val - 0x50) + 23; /* 0x50 = 'P', 23th */
+ else if (val >= 0x4a)
+ return (val - 0x4a) + 18; /* 0x4a = 'J', 18th*/
+ else if (val >= 0x41)
+ return (val - 0x41) + 10; /* 0x41 = 'A', 10th*/
+ else
+ return (val - 0x30);
+}
+
+static int batt_get_manufacture_date(struct bhi_data *bhi_data)
+{
+ struct bm_date *date = &bhi_data->bm_date;
+ u8 data[BATT_EEPROM_TAG_XYMD_LEN];
+ int ret = 0;
+
+ ret = gbms_storage_read(GBMS_TAG_MYMD, data, sizeof(data));
+ if (ret < 0)
+ return ret;
+
+ /* format: YYMMDD */
+ date->bm_y = xymd_to_date(data[0]) + 20;
+ date->bm_m = xymd_to_date(data[1]);
+ date->bm_d = xymd_to_date(data[2]);
+ pr_debug("%s: battery manufacture date: 20%d-%d-%d\n",
+ __func__, date->bm_y, date->bm_m, date->bm_d);
+
+ return 0;
+}
+
+static int batt_get_activation_date(struct bhi_data *bhi_data)
+{
+ int ret;
+
+ ret = gbms_storage_read(GBMS_TAG_AYMD, &bhi_data->act_date,
+ sizeof(bhi_data->act_date));
+ if (ret < 0)
+ return ret;
+
+ if (bhi_data->act_date[0] == 0xff) {
+ /*
+ * TODO: set a default value
+ * might to set by dev_act_date_show() from user space
+ */
+ bhi_data->act_date[0] = 0x30; /* 0x30 = '0', 2020 */
+ bhi_data->act_date[1] = 0x43; /* 0x43 = 'C', 12th */
+ bhi_data->act_date[2] = 0x31; /* 0x31 = '1', 1st */
+ }
+
+ return 0;
+}
+
#define ONE_YEAR_HRS (24 * 365)
#define BHI_INDI_CAP_DEFAULT 85
static int bhi_individual_conditions_index(const struct health_data *health_data)
@@ -6553,6 +6638,86 @@
static const DEVICE_ATTR_RW(health_indi_cap);
+static ssize_t health_cycle_count_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = container_of(dev, struct power_supply, dev);
+ struct batt_drv *batt_drv = power_supply_get_drvdata(psy);
+ struct bhi_data *bhi_data = &batt_drv->health_data.bhi_data;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", bhi_data->cycle_count);
+}
+
+static const DEVICE_ATTR_RO(health_cycle_count);
+
+static ssize_t dev_date_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = container_of(dev, struct power_supply, dev);
+ struct batt_drv *batt_drv = power_supply_get_drvdata(psy);
+ struct bm_date *date = &batt_drv->health_data.bhi_data.bm_date;
+ struct rtc_time tm;
+
+ tm.tm_year = date->bm_y + 100; // base is 1900
+ tm.tm_mon = date->bm_m - 1; // 0 is Jan ... 11 is Dec
+ tm.tm_mday = date->bm_d; // 1st ... 31th
+
+ return scnprintf(buf, PAGE_SIZE, "%lld\n", rtc_tm_to_time64(&tm));
+}
+
+static const DEVICE_ATTR_RO(dev_date);
+
+static ssize_t dev_act_date_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct power_supply *psy = container_of(dev, struct power_supply, dev);
+ struct batt_drv *batt_drv = power_supply_get_drvdata(psy);
+ struct bhi_data *bhi_data = &batt_drv->health_data.bhi_data;
+ int value, ret;
+
+ ret = kstrtoint(buf, 0, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value >= 0) {
+ /* reset: 0, set: YYMMDD */
+ bhi_data->act_date[0] = (!value) ? 0xff : date_to_xymd((value >> 16) & 0xff - 20);
+ bhi_data->act_date[1] = (!value) ? 0xff : date_to_xymd((value >> 8) & 0xff);
+ bhi_data->act_date[2] = (!value) ? 0xff : date_to_xymd(value & 0xff);
+
+ ret = gbms_storage_write(GBMS_TAG_AYMD, &bhi_data->act_date,
+ sizeof(bhi_data->act_date));
+ if (ret < 0)
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static ssize_t dev_act_date_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = container_of(dev, struct power_supply, dev);
+ struct batt_drv *batt_drv = power_supply_get_drvdata(psy);
+ struct bhi_data *bhi_data = &batt_drv->health_data.bhi_data;
+ struct bm_date date;
+ struct rtc_time tm;
+
+ /* format: YYMMDD */
+ date.bm_y = xymd_to_date(bhi_data->act_date[0]) + 20;
+ date.bm_m = xymd_to_date(bhi_data->act_date[1]);
+ date.bm_d = xymd_to_date(bhi_data->act_date[2]);
+
+ tm.tm_year = date.bm_y + 100; // base is 1900
+ tm.tm_mon = date.bm_m - 1; // 0 is Jan ... 11 is Dec
+ tm.tm_mday = date.bm_d; // 1st ... 31th
+
+ return scnprintf(buf, PAGE_SIZE, "%lld\n", rtc_tm_to_time64(&tm));
+}
+
+static const DEVICE_ATTR_RW(dev_act_date);
+
/* CSI --------------------------------------------------------------------- */
static ssize_t charging_speed_store(struct device *dev,
@@ -6964,6 +7129,15 @@
ret = device_create_file(&batt_drv->psy->dev, &dev_attr_health_indi_cap);
if (ret)
dev_err(&batt_drv->psy->dev, "Failed to create health individual capacity\n");
+ ret = device_create_file(&batt_drv->psy->dev, &dev_attr_health_cycle_count);
+ if (ret)
+ dev_err(&batt_drv->psy->dev, "Failed to create health cycle count\n");
+ ret = device_create_file(&batt_drv->psy->dev, &dev_attr_dev_date);
+ if (ret)
+ dev_err(&batt_drv->psy->dev, "Failed to create dev date\n");
+ ret = device_create_file(&batt_drv->psy->dev, &dev_attr_dev_act_date);
+ if (ret)
+ dev_err(&batt_drv->psy->dev, "Failed to create dev activation date\n");
/* csi */
ret = device_create_file(&batt_drv->psy->dev, &dev_attr_charging_speed);
@@ -7659,6 +7833,13 @@
notify_psy_changed = true;
}
+ /* update the date of first use of the battery */
+ if (!batt_drv->health_data.bhi_data.act_date[0]) {
+ ret = batt_get_activation_date(&batt_drv->health_data.bhi_data);
+ if (ret < 0)
+ pr_warn("cannot get battery activation date, ret=%d\n", ret);;
+ }
+
/* slow down the updates at full */
if (full && batt_drv->chg_done)
update_interval *= UPDATE_INTERVAL_AT_FULL_FACTOR;
@@ -8982,6 +9163,11 @@
batt_drv->power_metrics.polling_rate = 30;
batt_drv->power_metrics.interval = 120;
+ /* Date of manufacturing of the battery */
+ ret = batt_get_manufacture_date(&batt_drv->health_data.bhi_data);
+ if (ret < 0)
+ pr_warn("cannot get battery manufacture date, ret=%d\n", ret);
+
return 0;
}