| /* SPDX-License-Identifier: GPL-2.0 */ |
| /* |
| * Copyright 2021 Google, LLC |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/kernel.h> |
| #include <linux/printk.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <misc/gvotable.h> |
| #include "gbms_power_supply.h" |
| #include "google_bms.h" |
| #include "google_psy.h" |
| |
| #define MAX(x, y) ((x) < (y) ? (y) : (x)) |
| #define DUAL_FG_DELAY_INIT_MS 500 |
| #define DUAL_FG_WORK_PERIOD_MS 10000 |
| #define DUAL_BATT_TEMP_VOTER "daul_batt_temp" |
| |
| #define DUAL_BATT_VFLIP_OFFSET 50000 |
| #define DUAL_BATT_VFLIP_OFFSET_IDX 0 |
| |
| struct dual_fg_drv { |
| struct device *device; |
| struct power_supply *psy; |
| |
| const char *first_fg_psy_name; |
| const char *second_fg_psy_name; |
| |
| struct power_supply *first_fg_psy; |
| struct power_supply *second_fg_psy; |
| |
| struct mutex fg_lock; |
| |
| struct delayed_work init_work; |
| struct delayed_work gdbatt_work; |
| struct gvotable_election *fcc_votable; |
| |
| struct gbms_chg_profile chg_profile; |
| |
| struct notifier_block fg_nb; |
| |
| u32 battery_capacity; |
| int cc_max; |
| |
| int base_charge_full; |
| int flip_charge_full; |
| |
| bool init_complete; |
| bool resume_complete; |
| bool cable_in; |
| |
| u32 vflip_offset; |
| }; |
| |
| static int gdbatt_resume_check(struct dual_fg_drv *dual_fg_drv) { |
| int ret = 0; |
| |
| pm_runtime_get_sync(dual_fg_drv->device); |
| if (!dual_fg_drv->init_complete || !dual_fg_drv->resume_complete) |
| ret = -EAGAIN; |
| pm_runtime_put_sync(dual_fg_drv->device); |
| |
| return ret; |
| } |
| |
| static enum power_supply_property gdbatt_fg_props[] = { |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_CAPACITY, /* replace with _RAW */ |
| POWER_SUPPLY_PROP_CHARGE_COUNTER, |
| POWER_SUPPLY_PROP_CHARGE_FULL, |
| POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, /* used from gbattery */ |
| POWER_SUPPLY_PROP_CURRENT_AVG, /* candidate for tier switch */ |
| POWER_SUPPLY_PROP_CURRENT_NOW, |
| POWER_SUPPLY_PROP_CYCLE_COUNT, |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_TEMP, |
| POWER_SUPPLY_PROP_VOLTAGE_AVG, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, |
| POWER_SUPPLY_PROP_VOLTAGE_OCV, |
| POWER_SUPPLY_PROP_TECHNOLOGY, |
| POWER_SUPPLY_PROP_SERIAL_NUMBER, |
| }; |
| |
| static int gdbatt_get_temp(struct power_supply *fg_psy, int *temp) |
| { |
| int err = 0; |
| union power_supply_propval val; |
| |
| if (!fg_psy) |
| return -EINVAL; |
| |
| err = power_supply_get_property(fg_psy, |
| POWER_SUPPLY_PROP_TEMP, &val); |
| if (err == 0) |
| *temp = val.intval; |
| |
| return err; |
| } |
| |
| static int gdbatt_select_temp_idx(struct gbms_chg_profile *profile, int temp) |
| { |
| if (temp < profile->temp_limits[0] || |
| temp > profile->temp_limits[profile->temp_nb_limits - 1]) |
| return -1; |
| else |
| return gbms_msc_temp_idx(profile, temp); |
| } |
| |
| static int gdbatt_select_voltage_idx(struct gbms_chg_profile *profile, int vbatt) |
| { |
| int vbatt_idx = 0; |
| |
| while (vbatt_idx < profile->volt_nb_limits - 1 && |
| vbatt > profile->volt_limits[vbatt_idx]) |
| vbatt_idx++; |
| |
| return vbatt_idx; |
| } |
| |
| static void gdbatt_select_cc_max(struct dual_fg_drv *dual_fg_drv) |
| { |
| struct gbms_chg_profile *profile = &dual_fg_drv->chg_profile; |
| int base_temp, flip_temp, base_vbatt, flip_vbatt; |
| int base_temp_idx, flip_temp_idx, base_vbatt_idx, flip_vbatt_idx; |
| int base_cc_max, flip_cc_max, cc_max; |
| struct power_supply *base_psy = dual_fg_drv->first_fg_psy; |
| struct power_supply *flip_psy = dual_fg_drv->second_fg_psy; |
| int ret = 0; |
| |
| if (!dual_fg_drv->cable_in) |
| goto check_done; |
| |
| ret = gdbatt_get_temp(base_psy, &base_temp); |
| if (ret < 0) |
| goto check_done; |
| |
| ret = gdbatt_get_temp(flip_psy, &flip_temp); |
| if (ret < 0) |
| goto check_done; |
| |
| base_vbatt = GPSY_GET_PROP(base_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW); |
| if (base_vbatt < 0) |
| goto check_done; |
| |
| flip_vbatt = GPSY_GET_PROP(flip_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW); |
| if (flip_vbatt < 0) |
| goto check_done; |
| |
| base_temp_idx = gdbatt_select_temp_idx(profile, base_temp); |
| flip_temp_idx = gdbatt_select_temp_idx(profile, flip_temp); |
| |
| base_vbatt_idx = gdbatt_select_voltage_idx(profile, base_vbatt); |
| flip_vbatt_idx = gdbatt_select_voltage_idx(profile, |
| flip_vbatt - dual_fg_drv->vflip_offset); |
| |
| /* only apply offset in allowed idx */ |
| if (flip_vbatt_idx > DUAL_BATT_VFLIP_OFFSET_IDX) |
| flip_vbatt_idx = gdbatt_select_voltage_idx(profile, flip_vbatt); |
| |
| base_cc_max = GBMS_CCCM_LIMITS(profile, base_temp_idx, base_vbatt_idx); |
| flip_cc_max = GBMS_CCCM_LIMITS(profile, flip_temp_idx, flip_vbatt_idx); |
| cc_max = (base_cc_max <= flip_cc_max) ? base_cc_max : flip_cc_max; |
| if (cc_max == dual_fg_drv->cc_max) |
| goto check_done; |
| |
| if (!dual_fg_drv->fcc_votable) |
| dual_fg_drv->fcc_votable = |
| gvotable_election_get_handle(VOTABLE_MSC_FCC); |
| if (dual_fg_drv->fcc_votable) { |
| pr_info("temp:%d/%d(%d/%d), vbatt:%d/%d(%d/%d), cc_max:%d/%d(%d)\n", |
| base_temp, flip_temp, base_temp_idx, flip_temp_idx, base_vbatt, |
| flip_vbatt, base_vbatt_idx, flip_vbatt_idx, base_cc_max, |
| flip_cc_max, cc_max); |
| gvotable_cast_int_vote(dual_fg_drv->fcc_votable, |
| DUAL_BATT_TEMP_VOTER, cc_max, true); |
| dual_fg_drv->cc_max = cc_max; |
| power_supply_changed(dual_fg_drv->psy); |
| } |
| |
| check_done: |
| pr_debug("check done. cable_in=%d (%d)\n", dual_fg_drv->cable_in, ret); |
| } |
| |
| static int gdbatt_get_capacity(struct dual_fg_drv *dual_fg_drv, int base_soc, int flip_soc) |
| { |
| const int base_full = dual_fg_drv->base_charge_full / 1000; |
| const int flip_full = dual_fg_drv->flip_charge_full / 1000; |
| const int full_sum = base_full + flip_full; |
| |
| if (!base_full || !flip_full) |
| return (base_soc + flip_soc) / 2; |
| |
| return (base_soc * base_full + flip_soc * flip_full) / full_sum; |
| } |
| |
| static void google_dual_batt_work(struct work_struct *work) |
| { |
| struct dual_fg_drv *dual_fg_drv = container_of(work, struct dual_fg_drv, |
| gdbatt_work.work); |
| struct power_supply *base_psy = dual_fg_drv->first_fg_psy; |
| struct power_supply *flip_psy = dual_fg_drv->second_fg_psy; |
| int base_data, flip_data; |
| |
| mutex_lock(&dual_fg_drv->fg_lock); |
| |
| if (!dual_fg_drv->init_complete) |
| goto error_done; |
| |
| if (!base_psy || !flip_psy) |
| goto error_done; |
| |
| gdbatt_select_cc_max(dual_fg_drv); |
| |
| base_data = GPSY_GET_PROP(base_psy, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN); |
| flip_data = GPSY_GET_PROP(flip_psy, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN); |
| |
| if (base_data <= 0 || flip_data <= 0) |
| goto error_done; |
| |
| dual_fg_drv->base_charge_full = base_data; |
| dual_fg_drv->flip_charge_full = flip_data; |
| |
| error_done: |
| mod_delayed_work(system_wq, &dual_fg_drv->gdbatt_work, |
| msecs_to_jiffies(DUAL_FG_WORK_PERIOD_MS)); |
| |
| mutex_unlock(&dual_fg_drv->fg_lock); |
| } |
| |
| static int gdbatt_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct dual_fg_drv *dual_fg_drv = (struct dual_fg_drv *) |
| power_supply_get_drvdata(psy); |
| int err = 0; |
| union power_supply_propval fg_1; |
| union power_supply_propval fg_2; |
| |
| err = gdbatt_resume_check(dual_fg_drv); |
| if (err != 0) |
| return err; |
| |
| if (!dual_fg_drv->first_fg_psy && !dual_fg_drv->second_fg_psy) |
| return -EAGAIN; |
| |
| if (!dual_fg_drv->first_fg_psy || !dual_fg_drv->second_fg_psy) |
| goto single_fg; |
| |
| mutex_lock(&dual_fg_drv->fg_lock); |
| |
| err = power_supply_get_property(dual_fg_drv->first_fg_psy, psp, &fg_1); |
| if (err != 0) { |
| pr_debug("error %d reading first fg prop %d\n", err, psp); |
| mutex_unlock(&dual_fg_drv->fg_lock); |
| return err; |
| } |
| |
| err = power_supply_get_property(dual_fg_drv->second_fg_psy, psp, &fg_2); |
| if (err != 0) { |
| pr_debug("error %d reading second fg prop %d\n", err, psp); |
| mutex_unlock(&dual_fg_drv->fg_lock); |
| return err; |
| } |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_CHARGE_COUNTER: |
| case POWER_SUPPLY_PROP_CHARGE_FULL: |
| case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: |
| case POWER_SUPPLY_PROP_CURRENT_AVG: |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| val->intval = fg_1.intval + fg_2.intval; |
| break; |
| case POWER_SUPPLY_PROP_TEMP: |
| val->intval = MAX(fg_1.intval, fg_2.intval); |
| break; |
| case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: |
| case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: |
| val->intval = MAX(fg_1.intval, fg_2.intval); |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| val->intval = (fg_1.intval + fg_2.intval)/2; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_AVG: |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: |
| case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: |
| case POWER_SUPPLY_PROP_VOLTAGE_OCV: |
| val->intval = (fg_1.intval + fg_2.intval)/2; |
| break; |
| case GBMS_PROP_CAPACITY_RAW: |
| case POWER_SUPPLY_PROP_CAPACITY: |
| val->intval = gdbatt_get_capacity(dual_fg_drv, fg_1.intval, fg_2.intval); |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| /* larger one is bad. TODO: confirm its priority */ |
| val->intval = MAX(fg_1.intval, fg_2.intval); |
| break; |
| case POWER_SUPPLY_PROP_STATUS: |
| case POWER_SUPPLY_PROP_CYCLE_COUNT: |
| case POWER_SUPPLY_PROP_TECHNOLOGY: |
| val->intval = fg_1.intval; |
| if (fg_1.intval != fg_2.intval) |
| pr_debug("case %d not align: %d/%d", |
| psp, fg_1.intval, fg_2.intval); |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| val->intval = fg_1.intval && fg_2.intval; |
| if (fg_1.intval != fg_2.intval) |
| pr_debug("PRESENT different: %d/%d", fg_1.intval, fg_2.intval); |
| break; |
| case POWER_SUPPLY_PROP_SERIAL_NUMBER: |
| /* TODO: need hash SN */ |
| val->strval = fg_1.strval; |
| break; |
| /* support bhi */ |
| case GBMS_PROP_HEALTH_ACT_IMPEDANCE: |
| case GBMS_PROP_HEALTH_IMPEDANCE: |
| case GBMS_PROP_RESISTANCE: |
| case GBMS_PROP_RESISTANCE_RAW: |
| case GBMS_PROP_RESISTANCE_AVG: |
| case GBMS_PROP_BATTERY_AGE: |
| case GBMS_PROP_CHARGE_FULL_ESTIMATE: |
| case GBMS_PROP_CAPACITY_FADE_RATE: |
| val->intval = fg_1.intval; |
| break; |
| default: |
| pr_debug("getting unsupported property: %d\n", psp); |
| break; |
| } |
| |
| mutex_unlock(&dual_fg_drv->fg_lock); |
| |
| return 0; |
| |
| single_fg: |
| mutex_lock(&dual_fg_drv->fg_lock); |
| if (dual_fg_drv->first_fg_psy) |
| err = power_supply_get_property(dual_fg_drv->first_fg_psy, psp, val); |
| else if (dual_fg_drv->second_fg_psy) |
| err = power_supply_get_property(dual_fg_drv->second_fg_psy, psp, val); |
| mutex_unlock(&dual_fg_drv->fg_lock); |
| |
| if (err < 0) |
| pr_debug("error %d reading single prop %d\n", err, psp); |
| |
| return 0; |
| } |
| |
| static int gdbatt_set_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct dual_fg_drv *dual_fg_drv = (struct dual_fg_drv *) |
| power_supply_get_drvdata(psy); |
| int ret = 0; |
| |
| ret = gdbatt_resume_check(dual_fg_drv); |
| if (ret != 0) |
| return ret; |
| |
| switch (psp) { |
| case GBMS_PROP_BATT_CE_CTRL: |
| if (dual_fg_drv->first_fg_psy) { |
| ret = GPSY_SET_PROP(dual_fg_drv->first_fg_psy, psp, val->intval); |
| if (ret < 0) |
| pr_err("Cannot set the first BATT_CE_CTRL, ret=%d\n", ret); |
| } |
| if (dual_fg_drv->second_fg_psy) { |
| ret = GPSY_SET_PROP(dual_fg_drv->second_fg_psy, psp, val->intval); |
| if (ret < 0) |
| pr_err("Cannot set the second BATT_CE_CTRL, ret=%d\n", ret); |
| } |
| dual_fg_drv->cable_in = !!val->intval; |
| break; |
| case GBMS_PROP_HEALTH_ACT_IMPEDANCE: |
| /* TODO: discuss with BattEng to decide save data */ |
| /* if (dual_fg_drv->first_fg_psy) { |
| ret = GPSY_SET_PROP(dual_fg_drv->first_fg_psy, psp, val->intval); |
| if (ret < 0) |
| pr_err("Cannot set the first HEALTH_ACT_IMPEDANCE, ret=%d\n", ret); |
| } */ |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| if (ret < 0) { |
| pr_debug("gdbatt: set_prop cannot write psp=%d\n", psp); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int gdbatt_property_is_writeable(struct power_supply *psy, |
| enum power_supply_property psp) |
| { |
| switch (psp) { |
| case GBMS_PROP_BATT_CE_CTRL: |
| return 1; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static struct power_supply_desc gdbatt_psy_desc = { |
| .name = "dualbatt", |
| .type = POWER_SUPPLY_TYPE_BATTERY, |
| .get_property = gdbatt_get_property, |
| .set_property = gdbatt_set_property, |
| .property_is_writeable = gdbatt_property_is_writeable, |
| .properties = gdbatt_fg_props, |
| .num_properties = ARRAY_SIZE(gdbatt_fg_props), |
| }; |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| static int gdbatt_init_chg_profile(struct dual_fg_drv *dual_fg_drv) |
| { |
| struct device_node *node = of_find_node_by_name(NULL, "google,battery"); |
| struct gbms_chg_profile *profile = &dual_fg_drv->chg_profile; |
| int ret = 0; |
| |
| if (profile->cccm_limits) |
| return 0; |
| |
| ret = gbms_init_chg_profile(profile, node); |
| if (ret < 0) |
| return -EINVAL; |
| |
| ret = of_property_read_u32(node, "google,chg-battery-capacity", |
| &dual_fg_drv->battery_capacity); |
| if (ret < 0) |
| pr_warn("battery not present, no default capacity, zero charge table\n"); |
| |
| gbms_init_chg_table(profile, node, dual_fg_drv->battery_capacity); |
| |
| return ret; |
| } |
| |
| static int psy_changed(struct notifier_block *nb, |
| unsigned long action, void *data) |
| { |
| struct power_supply *psy = data; |
| struct dual_fg_drv *dual_fg_drv = container_of(nb, struct dual_fg_drv, fg_nb); |
| |
| if ((action != PSY_EVENT_PROP_CHANGED) || (psy == NULL) || (psy->desc == NULL) || |
| (psy->desc->name == NULL)) |
| return NOTIFY_OK; |
| |
| pr_debug("name=%s evt=%lu\n", psy->desc->name, action); |
| |
| if (action == PSY_EVENT_PROP_CHANGED) { |
| bool is_first_fg = (dual_fg_drv->first_fg_psy_name != NULL) && |
| !strcmp(psy->desc->name, dual_fg_drv->first_fg_psy_name); |
| bool is_second_fg = (dual_fg_drv->second_fg_psy_name != NULL) && |
| !strcmp(psy->desc->name, dual_fg_drv->second_fg_psy_name); |
| |
| if (is_first_fg || is_second_fg) |
| power_supply_changed(dual_fg_drv->psy); |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static void google_dual_batt_gauge_init_work(struct work_struct *work) |
| { |
| struct dual_fg_drv *dual_fg_drv = container_of(work, struct dual_fg_drv, |
| init_work.work); |
| struct power_supply *first_fg_psy = dual_fg_drv->first_fg_psy; |
| struct power_supply *second_fg_psy = dual_fg_drv->second_fg_psy; |
| union power_supply_propval val; |
| int err = 0; |
| |
| if (!dual_fg_drv->first_fg_psy && dual_fg_drv->first_fg_psy_name) { |
| first_fg_psy = power_supply_get_by_name(dual_fg_drv->first_fg_psy_name); |
| if (!first_fg_psy) { |
| dev_info(dual_fg_drv->device, |
| "failed to get \"%s\" power supply, retrying...\n", |
| dual_fg_drv->first_fg_psy_name); |
| goto retry_init_work; |
| } |
| |
| dual_fg_drv->first_fg_psy = first_fg_psy; |
| |
| /* Don't use it if battery not present */ |
| err = power_supply_get_property(first_fg_psy, |
| POWER_SUPPLY_PROP_PRESENT, &val); |
| if (err == -EAGAIN) |
| goto retry_init_work; |
| if (err == 0 && val.intval == 0) { |
| dev_info(dual_fg_drv->device, "First battery not PRESENT\n"); |
| dual_fg_drv->first_fg_psy_name = NULL; |
| dual_fg_drv->first_fg_psy = NULL; |
| } |
| } |
| |
| if (!dual_fg_drv->second_fg_psy && dual_fg_drv->second_fg_psy_name) { |
| second_fg_psy = power_supply_get_by_name(dual_fg_drv->second_fg_psy_name); |
| if (!second_fg_psy) { |
| pr_info("failed to get \"%s\" power supply, retrying...\n", |
| dual_fg_drv->second_fg_psy_name); |
| goto retry_init_work; |
| } |
| |
| dual_fg_drv->second_fg_psy = second_fg_psy; |
| |
| /* Don't use it if battery not present */ |
| err = power_supply_get_property(second_fg_psy, |
| POWER_SUPPLY_PROP_PRESENT, &val); |
| if (err == -EAGAIN) |
| goto retry_init_work; |
| if (err == 0 && val.intval == 0) { |
| dev_info(dual_fg_drv->device, "Second battery not PRESENT\n"); |
| dual_fg_drv->second_fg_psy_name = NULL; |
| dual_fg_drv->second_fg_psy = NULL; |
| } |
| } |
| |
| dual_fg_drv->cc_max = -1; |
| err = gdbatt_init_chg_profile(dual_fg_drv); |
| if (err < 0) |
| dev_info(dual_fg_drv->device,"fail to init chg profile (%d)\n", err); |
| |
| dual_fg_drv->fg_nb.notifier_call = psy_changed; |
| err = power_supply_reg_notifier(&dual_fg_drv->fg_nb); |
| if (err < 0) |
| pr_err("cannot register power supply notifer (%d)\n", err); |
| |
| dual_fg_drv->init_complete = true; |
| dual_fg_drv->resume_complete = true; |
| mod_delayed_work(system_wq, &dual_fg_drv->gdbatt_work, 0); |
| dev_info(dual_fg_drv->device, "google_dual_batt_gauge_init_work done\n"); |
| |
| return; |
| |
| retry_init_work: |
| schedule_delayed_work(&dual_fg_drv->init_work, |
| msecs_to_jiffies(DUAL_FG_DELAY_INIT_MS)); |
| } |
| |
| static int google_dual_batt_gauge_probe(struct platform_device *pdev) |
| { |
| const char *first_fg_psy_name; |
| const char *second_fg_psy_name; |
| struct dual_fg_drv *dual_fg_drv; |
| struct power_supply_config psy_cfg = {}; |
| int ret; |
| |
| dual_fg_drv = devm_kzalloc(&pdev->dev, sizeof(*dual_fg_drv), GFP_KERNEL); |
| if (!dual_fg_drv) |
| return -ENOMEM; |
| |
| dual_fg_drv->device = &pdev->dev; |
| |
| ret = of_property_read_string(pdev->dev.of_node, "google,first-fg-psy-name", |
| &first_fg_psy_name); |
| if (ret == 0) { |
| pr_info("google,first-fg-psy-name=%s\n", first_fg_psy_name); |
| dual_fg_drv->first_fg_psy_name = devm_kstrdup(&pdev->dev, |
| first_fg_psy_name, GFP_KERNEL); |
| if (!dual_fg_drv->first_fg_psy_name) |
| return -ENOMEM; |
| } |
| |
| ret = of_property_read_string(pdev->dev.of_node, "google,second-fg-psy-name", |
| &second_fg_psy_name); |
| if (ret == 0) { |
| pr_info("google,second-fg-psy-name=%s\n", second_fg_psy_name); |
| dual_fg_drv->second_fg_psy_name = devm_kstrdup(&pdev->dev, second_fg_psy_name, |
| GFP_KERNEL); |
| if (!dual_fg_drv->second_fg_psy_name) |
| return -ENOMEM; |
| } |
| |
| if (!dual_fg_drv->first_fg_psy_name && !dual_fg_drv->second_fg_psy_name) { |
| pr_err("no dual gauge setting\n"); |
| return -EINVAL; |
| } |
| |
| ret = of_property_read_u32(pdev->dev.of_node, "google,vflip-offset", |
| &dual_fg_drv->vflip_offset); |
| if (ret < 0) { |
| pr_debug("Couldn't set vflip_offset (%d)\n", ret); |
| dual_fg_drv->vflip_offset = DUAL_BATT_VFLIP_OFFSET; |
| } |
| |
| INIT_DELAYED_WORK(&dual_fg_drv->init_work, google_dual_batt_gauge_init_work); |
| INIT_DELAYED_WORK(&dual_fg_drv->gdbatt_work, google_dual_batt_work); |
| mutex_init(&dual_fg_drv->fg_lock); |
| platform_set_drvdata(pdev, dual_fg_drv); |
| |
| psy_cfg.drv_data = dual_fg_drv; |
| psy_cfg.of_node = pdev->dev.of_node; |
| |
| if (of_property_read_bool(pdev->dev.of_node, "google,psy-type-unknown")) |
| gdbatt_psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN; |
| |
| dual_fg_drv->psy = devm_power_supply_register(dual_fg_drv->device, |
| &gdbatt_psy_desc, &psy_cfg); |
| if (IS_ERR(dual_fg_drv->psy)) { |
| ret = PTR_ERR(dual_fg_drv->psy); |
| if (ret == -EPROBE_DEFER) |
| return -EPROBE_DEFER; |
| |
| /* TODO: fail with -ENODEV */ |
| dev_err(dual_fg_drv->device, |
| "Couldn't register as power supply, ret=%d\n", ret); |
| } |
| |
| schedule_delayed_work(&dual_fg_drv->init_work, |
| msecs_to_jiffies(DUAL_FG_DELAY_INIT_MS)); |
| |
| pr_info("google_dual_batt_gauge_probe done\n"); |
| |
| return 0; |
| } |
| |
| static int google_dual_batt_gauge_remove(struct platform_device *pdev) |
| { |
| struct dual_fg_drv *dual_fg_drv = platform_get_drvdata(pdev); |
| |
| gbms_free_chg_profile(&dual_fg_drv->chg_profile); |
| |
| return 0; |
| } |
| |
| static int __maybe_unused google_dual_batt_pm_suspend(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct dual_fg_drv *dual_fg_drv = platform_get_drvdata(pdev); |
| |
| pm_runtime_get_sync(dual_fg_drv->device); |
| dual_fg_drv->resume_complete = false; |
| pm_runtime_put_sync(dual_fg_drv->device); |
| |
| return 0; |
| } |
| |
| static int __maybe_unused google_dual_batt_pm_resume(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct dual_fg_drv *dual_fg_drv = platform_get_drvdata(pdev); |
| |
| pm_runtime_get_sync(dual_fg_drv->device); |
| dual_fg_drv->resume_complete = true; |
| pm_runtime_put_sync(dual_fg_drv->device); |
| |
| return 0; |
| } |
| |
| static SIMPLE_DEV_PM_OPS(google_dual_batt_pm_ops, |
| google_dual_batt_pm_suspend, |
| google_dual_batt_pm_resume); |
| |
| static const struct of_device_id google_dual_batt_gauge_of_match[] = { |
| {.compatible = "google,dual_batt_gauge"}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, google_dual_batt_gauge_of_match); |
| |
| static struct platform_driver google_dual_batt_gauge_driver = { |
| .driver = { |
| .name = "google,dual_batt_gauge", |
| .owner = THIS_MODULE, |
| .of_match_table = google_dual_batt_gauge_of_match, |
| .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
| .pm = &google_dual_batt_pm_ops, |
| }, |
| .probe = google_dual_batt_gauge_probe, |
| .remove = google_dual_batt_gauge_remove, |
| }; |
| |
| module_platform_driver(google_dual_batt_gauge_driver); |
| |
| MODULE_DESCRIPTION("Google Dual Gauge Driver"); |
| MODULE_AUTHOR("Jenny Ho <[email protected]>"); |
| MODULE_LICENSE("GPL"); |