/* * sec_dual_battery.c * Samsung Mobile Charger Driver * * Copyright (C) 2018 Samsung Electronics * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #define DEBUG #include "include/sec_battery.h" #include "include/sec_dual_battery.h" static enum power_supply_property sec_dual_battery_props[] = { }; static int sec_dual_check_eoc_status(struct sec_dual_battery_info *battery) { union power_supply_propval value; /* check out main battery's eoc status */ value.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(battery->pdata->main_limiter_name, get, (enum power_supply_property)POWER_SUPPLY_EXT_PROP_BAT_VOLTAGE, value); battery->main_voltage_avg = value.intval; value.intval = SEC_BATTERY_CURRENT_MA; psy_do_property(battery->pdata->main_limiter_name, get, (enum power_supply_property)POWER_SUPPLY_EXT_PROP_CHG_CURRENT, value); battery->main_current_avg = value.intval; pr_info("%s %d %d %d %d \n", __func__, battery->main_voltage_avg, battery->pdata->main_full_condition_vcell, battery->main_current_avg, battery->pdata->main_full_condition_eoc); if(battery->main_voltage_avg >= battery->pdata->main_full_condition_vcell && battery->main_current_avg <= battery->pdata->main_full_condition_eoc && !(battery->full_total_status & SEC_DUAL_BATTERY_MAIN_CONDITION_DONE)) { pr_info("%s Main Batt eoc condtion is done \n", __func__); battery->full_total_status |= SEC_DUAL_BATTERY_MAIN_CONDITION_DONE; /* main supplement mode enable */ value.intval = 1; psy_do_property(battery->pdata->main_limiter_name, set, POWER_SUPPLY_PROP_CHARGE_FULL, value); } /* check out sub battery's eoc status */ value.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(battery->pdata->sub_limiter_name, get, (enum power_supply_property)POWER_SUPPLY_EXT_PROP_BAT_VOLTAGE, value); battery->sub_voltage_avg = value.intval; value.intval = SEC_BATTERY_CURRENT_MA; psy_do_property(battery->pdata->sub_limiter_name, get, (enum power_supply_property)POWER_SUPPLY_EXT_PROP_CHG_CURRENT, value); battery->sub_current_avg = value.intval; pr_info("%s %d %d %d %d \n", __func__, battery->sub_voltage_avg, battery->pdata->sub_full_condition_vcell, battery->sub_current_avg, battery->pdata->sub_full_condition_eoc); if(battery->sub_voltage_avg >= battery->pdata->sub_full_condition_vcell && battery->sub_current_avg <= battery->pdata->sub_full_condition_eoc && !(battery->full_total_status & SEC_DUAL_BATTERY_SUB_CONDITION_DONE)) { pr_info("%s Sub Batt eoc condtion is done \n", __func__); battery->full_total_status |= SEC_DUAL_BATTERY_SUB_CONDITION_DONE; /* main supplement mode enable */ value.intval = 1; psy_do_property(battery->pdata->sub_limiter_name, set, POWER_SUPPLY_PROP_CHARGE_FULL, value); } pr_info("%s full satus = 0x%x \n", __func__, battery->full_total_status); if(battery->full_total_status == (SEC_DUAL_BATTERY_MAIN_CONDITION_DONE | SEC_DUAL_BATTERY_SUB_CONDITION_DONE)) return POWER_SUPPLY_STATUS_FULL; else return POWER_SUPPLY_STATUS_CHARGING; } #if 0 static int sec_dual_check_eoc_current(struct sec_dual_battery_info *battery) { union power_supply_propval value; psy_do_property(battery->pdata->main_limiter_name, get, POWER_SUPPLY_EXT_PROP_CHG_CURRENT, value); battery->main_current_avg = value.intval; if(battery->main_current_avg <= battery->pdata->main_full_condition_eoc) { battery->full_current_status |= SEC_DUAL_BATTERY_MAIN_CONDITION_DONE; } psy_do_property(battery->pdata->sub_limiter_name, get, POWER_SUPPLY_EXT_PROP_CHG_CURRENT, value); battery->sub_current_avg = value.intval; if(battery->sub_current_avg <= battery->pdata->sub_full_condition_eoc) { battery->full_current_status |= SEC_DUAL_BATTERY_MAIN_CONDITION_DONE; } if(battery->full_current_status == (SEC_DUAL_BATTERY_MAIN_CONDITION_DONE | SEC_DUAL_BATTERY_SUB_CONDITION_DONE)) return POWER_SUPPLY_STATUS_FULL; else return POWER_SUPPLY_STATUS_CHARGING; } #endif static int sec_dual_battery_current_avg(struct sec_dual_battery_info *battery, int bat_type, int mode) { union power_supply_propval value; int ichg = 0, idis = 0; if(bat_type == SEC_DUAL_BATTERY_MAIN) { value.intval = SEC_BATTERY_CURRENT_MA; psy_do_property(battery->pdata->main_limiter_name, get, (enum power_supply_property)POWER_SUPPLY_EXT_PROP_CHG_CURRENT, value); ichg = value.intval; value.intval = SEC_BATTERY_CURRENT_MA; psy_do_property(battery->pdata->main_limiter_name, get, (enum power_supply_property)POWER_SUPPLY_EXT_PROP_DISCHG_CURRENT, value); idis = value.intval; } else { value.intval = SEC_BATTERY_CURRENT_MA; psy_do_property(battery->pdata->sub_limiter_name, get, (enum power_supply_property)POWER_SUPPLY_EXT_PROP_CHG_CURRENT, value); ichg = value.intval; value.intval = SEC_BATTERY_CURRENT_MA; psy_do_property(battery->pdata->sub_limiter_name, get, (enum power_supply_property)POWER_SUPPLY_EXT_PROP_DISCHG_CURRENT, value); idis = value.intval; } pr_info("%s: ichg=%d, idis=%d\n", __func__, ichg, idis); if((ichg == 0) && (idis == 0)) return 0; else { if(ichg != 0) return ichg; else return idis * (-1); } } static int sec_dual_battery_voltage_avg(struct sec_dual_battery_info *battery, int bat_type, int mode) { union power_supply_propval value; int vbat = 0; if(bat_type == SEC_DUAL_BATTERY_MAIN) { value.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(battery->pdata->main_limiter_name, get, (enum power_supply_property)POWER_SUPPLY_EXT_PROP_BAT_VOLTAGE, value); vbat = value.intval; } else { value.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(battery->pdata->sub_limiter_name, get, (enum power_supply_property)POWER_SUPPLY_EXT_PROP_BAT_VOLTAGE, value); vbat = value.intval; } pr_info("%s: vbat=%d\n", __func__, vbat); return vbat; } static int sec_dual_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct sec_dual_battery_info *battery = power_supply_get_drvdata(psy); enum power_supply_ext_property ext_psp = (enum power_supply_ext_property)psp; union power_supply_propval value; value.intval = val->intval; switch (psp) { case POWER_SUPPLY_PROP_STATUS: val->intval = sec_dual_check_eoc_status(battery); break; case POWER_SUPPLY_PROP_VOLTAGE_AVG: if(value.intval == SEC_DUAL_BATTERY_MAIN) val->intval = sec_dual_battery_voltage_avg(battery, SEC_DUAL_BATTERY_MAIN, SEC_BATTERY_VOLTAGE_MV); else val->intval = sec_dual_battery_voltage_avg(battery, SEC_DUAL_BATTERY_SUB, SEC_BATTERY_VOLTAGE_MV); break; case POWER_SUPPLY_PROP_CURRENT_AVG: if(value.intval == SEC_DUAL_BATTERY_MAIN) val->intval = sec_dual_battery_current_avg(battery, SEC_DUAL_BATTERY_MAIN, SEC_BATTERY_CURRENT_MA); else val->intval = sec_dual_battery_current_avg(battery, SEC_DUAL_BATTERY_SUB, SEC_BATTERY_CURRENT_MA); break; case POWER_SUPPLY_PROP_MAX ... POWER_SUPPLY_EXT_PROP_MAX: switch (ext_psp) { case POWER_SUPPLY_EXT_PROP_DUAL_BAT_DET: if (value.intval == SEC_DUAL_BATTERY_MAIN) { if (battery->pdata->main_bat_con_det_gpio) { val->intval = !gpio_get_value(battery->pdata->main_bat_con_det_gpio); pr_info("%s : main det(%d) = %d \n", __func__, battery->pdata->main_bat_con_det_gpio, (int)value.intval); } else val->intval = -1; } else if (value.intval == SEC_DUAL_BATTERY_SUB) { if (battery->pdata->sub_bat_con_det_gpio) { val->intval = !gpio_get_value(battery->pdata->sub_bat_con_det_gpio); pr_info("%s : sub det(%d) = %d \n", __func__, battery->pdata->sub_bat_con_det_gpio, (int)value.intval); } else val->intval = -1; } break; default: return -EINVAL; } break; default: return -EINVAL; } return 0; } static int sec_dual_battery_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct sec_dual_battery_info *battery = power_supply_get_drvdata(psy); enum power_supply_ext_property ext_psp = (enum power_supply_ext_property)psp; union power_supply_propval value; //value.intval = val->intval; switch (psp) { case POWER_SUPPLY_PROP_CHARGING_ENABLED: break; case POWER_SUPPLY_PROP_CHARGE_FULL: if(val->intval == 0) { /* disable main/sub supplement mode */ value.intval = 0; psy_do_property(battery->pdata->main_limiter_name, set, POWER_SUPPLY_PROP_CHARGE_FULL, value); psy_do_property(battery->pdata->sub_limiter_name, set, POWER_SUPPLY_PROP_CHARGE_FULL, value); battery->full_total_status = SEC_DUAL_BATTERY_NONE; } //else { //value.intval = 1; /* enable main/sub supplement mode */ //psy_do_property(battery->pdata->main_limiter_name, set, // POWER_SUPPLY_PROP_CHARGE_FULL, value); //psy_do_property(battery->pdata->sub_limiter_name, set, // POWER_SUPPLY_PROP_CHARGE_FULL, value); //} break; case POWER_SUPPLY_PROP_ENERGY_NOW: /* SET PWR OFF MODE 2*/ if(val->intval == SEC_DUAL_BATTERY_MAIN) { value.intval = 1; psy_do_property(battery->pdata->main_limiter_name, set, POWER_SUPPLY_PROP_ENERGY_NOW, value); } else { value.intval = 1; psy_do_property(battery->pdata->sub_limiter_name, set, POWER_SUPPLY_PROP_ENERGY_NOW, value); } break; case POWER_SUPPLY_PROP_MAX ... POWER_SUPPLY_EXT_PROP_MAX: switch (ext_psp) { default: return -EINVAL; } break; default: return -EINVAL; } return 0; } #ifdef CONFIG_OF static int sec_dual_battery_parse_dt(struct device *dev, struct sec_dual_battery_info *battery) { struct device_node *np = dev->of_node; struct sec_dual_battery_platform_data *pdata = battery->pdata; int ret = 0; //int len; //const u32 *p; if (!np) { pr_err("%s: np NULL\n", __func__); return 1; } else { ret = of_property_read_string(np, "battery,main_current_limiter", (char const **)&battery->pdata->main_limiter_name); if (ret) pr_err("%s: main_current_limiter is Empty\n", __func__); ret = of_property_read_string(np, "battery,sub_current_limiter", (char const **)&battery->pdata->sub_limiter_name); if (ret) pr_err("%s: sub_current_limiter is Empty\n", __func__); ret = of_property_read_u32(np, "battery,main_full_condition_vcell", &pdata->main_full_condition_vcell); if (ret < 0) { pr_info("%s : main_full_condition_vcell is empty\n", __func__); pdata->main_full_condition_vcell = 4250; } ret = of_property_read_u32(np, "battery,sub_full_condition_vcell", &pdata->sub_full_condition_vcell); if (ret < 0) { pr_info("%s : sub_full_condition_vcell is empty\n", __func__); pdata->sub_full_condition_vcell = 4250; } ret = of_property_read_u32(np, "battery,main_full_condition_eoc", &pdata->main_full_condition_eoc); if (ret < 0) { pr_info("%s : main_full_condition_eoc is empty\n", __func__); pdata->main_full_condition_eoc = 100; } ret = of_property_read_u32(np, "battery,sub_full_condition_eoc", &pdata->sub_full_condition_eoc); if (ret < 0) { pr_info("%s : sub_full_condition_eoc is empty\n", __func__); pdata->sub_full_condition_eoc = 100; } /* MAIN_BATTERY_CON_DET */ ret = pdata->main_bat_con_det_gpio = of_get_named_gpio(np, "battery,main_bat_con_det_gpio", 0); if (ret < 0) { pr_info("%s : can't get main_bat_con_det_gpio\n", __func__); } /* SUB_BATTERY_CON_DET */ ret = pdata->sub_bat_con_det_gpio = of_get_named_gpio(np, "battery,sub_bat_con_det_gpio", 0); if (ret < 0) { pr_info("%s : can't get sub_bat_con_det_gpio\n", __func__); } } return 0; } #endif static const struct power_supply_desc sec_dual_battery_power_supply_desc = { .name = "sec-dual-battery", .type = POWER_SUPPLY_TYPE_UNKNOWN, .properties = sec_dual_battery_props, .num_properties = ARRAY_SIZE(sec_dual_battery_props), .get_property = sec_dual_battery_get_property, .set_property = sec_dual_battery_set_property, }; static int sec_dual_battery_probe(struct platform_device *pdev) { struct sec_dual_battery_info *battery; struct sec_dual_battery_platform_data *pdata = NULL; struct power_supply_config dual_battery_cfg = {}; int ret = 0; dev_info(&pdev->dev, "%s: SEC Dual Battery Driver Loading\n", __func__); battery = kzalloc(sizeof(*battery), GFP_KERNEL); if (!battery) return -ENOMEM; if (pdev->dev.of_node) { pdata = devm_kzalloc(&pdev->dev, sizeof(struct sec_dual_battery_platform_data), GFP_KERNEL); if (!pdata) { dev_err(&pdev->dev, "Failed to allocate memory\n"); ret = -ENOMEM; goto err_battery_free; } battery->pdata = pdata; if (sec_dual_battery_parse_dt(&pdev->dev, battery)) { dev_err(&pdev->dev, "%s: Failed to get sec-dual-battery dt\n", __func__); ret = -EINVAL; goto err_battery_free; } } else { pdata = dev_get_platdata(&pdev->dev); battery->pdata = pdata; } platform_set_drvdata(pdev, battery); battery->dev = &pdev->dev; dual_battery_cfg.drv_data = battery; battery->psy_bat = power_supply_register(&pdev->dev, &sec_dual_battery_power_supply_desc, &dual_battery_cfg); if (!battery->psy_bat) { dev_err(battery->dev, "%s: Failed to Register psy_bat\n", __func__); goto err_pdata_free; } dev_info(battery->dev, "%s: SEC Dual Battery Driver Loaded\n", __func__); return 0; err_pdata_free: kfree(pdata); err_battery_free: kfree(battery); return ret; } static int sec_dual_battery_remove(struct platform_device *pdev) { struct sec_dual_battery_info *battery = platform_get_drvdata(pdev); power_supply_unregister(battery->psy_bat); dev_dbg(battery->dev, "%s: End\n", __func__); kfree(battery->pdata); kfree(battery); return 0; } static int sec_dual_battery_suspend(struct device *dev) { return 0; } static int sec_dual_battery_resume(struct device *dev) { return 0; } static void sec_dual_battery_shutdown(struct platform_device *pdev) { } #ifdef CONFIG_OF static struct of_device_id sec_dual_battery_dt_ids[] = { { .compatible = "samsung,sec-dual-battery" }, { } }; MODULE_DEVICE_TABLE(of, sec_dual_battery_dt_ids); #endif /* CONFIG_OF */ static const struct dev_pm_ops sec_dual_battery_pm_ops = { .suspend = sec_dual_battery_suspend, .resume = sec_dual_battery_resume, }; static struct platform_driver sec_dual_battery_driver = { .driver = { .name = "sec-dual-battery", .owner = THIS_MODULE, .pm = &sec_dual_battery_pm_ops, #ifdef CONFIG_OF .of_match_table = sec_dual_battery_dt_ids, #endif }, .probe = sec_dual_battery_probe, .remove = sec_dual_battery_remove, .shutdown = sec_dual_battery_shutdown, }; static int __init sec_dual_battery_init(void) { pr_info("%s: \n", __func__); return platform_driver_register(&sec_dual_battery_driver); } static void __exit sec_dual_battery_exit(void) { platform_driver_unregister(&sec_dual_battery_driver); } device_initcall_sync(sec_dual_battery_init); module_exit(sec_dual_battery_exit); MODULE_DESCRIPTION("Samsung Dual Battery Driver"); MODULE_AUTHOR("Samsung Electronics"); MODULE_LICENSE("GPL");