blob: 2736150c9bcc7eae0b66aee5110d40a9b6309a0c [file] [log] [blame]
/*
* 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");