blob: 198528a620c4097c79e5975be7a295e687527848 [file] [log] [blame]
/*
* Copyright(c) 2013, LGE Inc. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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) "%s: " fmt, __func__
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/of_device.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/qpnp/qpnp-adc.h>
#include <linux/suspend.h>
struct batt_tm_data {
struct device *dev;
struct power_supply tm_psy;
struct power_supply *batt_psy;
struct power_supply *ac_psy;
struct qpnp_adc_tm_btm_param adc_param;
struct delayed_work tm_work;
struct wake_lock tm_wake_lock;
struct tm_ctrl_data *warm_cfg;
struct tm_ctrl_data *cold_cfg;
struct notifier_block pm_notifier;
int warm_cfg_size;
int cold_cfg_size;
int batt_vreg_uv;
int low_batt_vreg_mv;
int low_current_ma;
int chg_online;
int tm_noti_stat;
};
struct tm_ctrl_data {
int thr;
int action;
int next_cool_thr;
int next_warm_thr;
int health;
};
enum {
CHG_ENABLE,
CHG_DECREASE,
CHG_STOP,
CHG_NONE,
};
static int power_supply_set_max_voltage(struct power_supply *psy, int limit)
{
const union power_supply_propval ret = {limit,};
if (psy->set_property)
return psy->set_property(psy, POWER_SUPPLY_PROP_VOLTAGE_MAX,
&ret);
return -ENXIO;
}
static int power_supply_set_batt_health(struct power_supply *psy, int health)
{
const union power_supply_propval ret = {health,};
if (psy->set_property)
return psy->set_property(psy, POWER_SUPPLY_PROP_HEALTH,
&ret);
return -ENXIO;
}
static int power_supply_set_chg_enable(struct power_supply *psy, int enable)
{
const union power_supply_propval ret = {enable,};
if (psy->set_property)
return psy->set_property(psy,
POWER_SUPPLY_PROP_CHARGING_ENABLED, &ret);
return -ENXIO;
}
static void batt_tm_worker(struct work_struct *work)
{
struct batt_tm_data *batt_tm =
container_of(work, struct batt_tm_data, tm_work.work);
int temp;
int i;
int tm_action;
int batt_health;
union power_supply_propval ret = {0,};
int rc;
if (batt_tm->tm_noti_stat >= ADC_TM_STATE_NUM) {
pr_err("invalid notification %d\n", batt_tm->tm_noti_stat);
goto out;
}
batt_tm->batt_psy->get_property(batt_tm->batt_psy,
POWER_SUPPLY_PROP_TEMP, &ret);
temp = ret.intval;
pr_debug("temp = %d state = %s\n", temp,
batt_tm->tm_noti_stat == ADC_TM_WARM_STATE ? "warm" : "cool");
if (batt_tm->tm_noti_stat == ADC_TM_WARM_STATE) {
i = batt_tm->warm_cfg_size - 1;
while ((batt_tm->adc_param.high_temp
< batt_tm->warm_cfg[i].thr) && (i > 0))
i--;
batt_tm->adc_param.low_temp =
batt_tm->warm_cfg[i].next_cool_thr;
batt_tm->adc_param.high_temp =
batt_tm->warm_cfg[i].next_warm_thr;
tm_action = batt_tm->warm_cfg[i].action;
batt_health = batt_tm->warm_cfg[i].health;
} else {
i = 0;
while ((batt_tm->adc_param.low_temp > batt_tm->cold_cfg[i].thr)
&& (i < batt_tm->cold_cfg_size-1))
i++;
batt_tm->adc_param.low_temp =
batt_tm->cold_cfg[i].next_cool_thr;
batt_tm->adc_param.high_temp =
batt_tm->cold_cfg[i].next_warm_thr;
tm_action = batt_tm->cold_cfg[i].action;
batt_health = batt_tm->cold_cfg[i].health;
}
power_supply_set_batt_health(batt_tm->ac_psy, batt_health);
if (!batt_tm->chg_online)
tm_action = CHG_NONE;
switch (tm_action) {
case CHG_ENABLE:
pr_info("Enable charging. vbatt_max = %d\n",
batt_tm->batt_vreg_uv/1000);
power_supply_set_chg_enable(batt_tm->ac_psy, true);
power_supply_set_max_voltage(batt_tm->ac_psy,
batt_tm->batt_vreg_uv);
break;
case CHG_DECREASE:
pr_info("Decrease current to %d, vbatt_max to %d\n",
batt_tm->low_current_ma,
batt_tm->low_batt_vreg_mv);
power_supply_set_current_limit(batt_tm->ac_psy,
batt_tm->low_current_ma * 1000);
power_supply_set_max_voltage(batt_tm->ac_psy,
batt_tm->low_batt_vreg_mv * 1000);
break;
case CHG_STOP:
pr_info("Stop charging !!\n");
power_supply_set_chg_enable(batt_tm->ac_psy, false);
break;
case CHG_NONE:
pr_info("No charger.\n");
break;
default:
break;
}
pr_info("set new threshold : low_temp = %d high_temp = %d\n",
batt_tm->adc_param.low_temp,
batt_tm->adc_param.high_temp);
rc = qpnp_adc_tm_channel_measure(&batt_tm->adc_param);
if (rc)
pr_err("request ADC error\n");
out:
wake_unlock(&batt_tm->tm_wake_lock);
}
static void batt_tm_ctrl_notification(enum qpnp_tm_state state, void *ctx)
{
struct batt_tm_data *batt_tm = ctx;
wake_lock(&batt_tm->tm_wake_lock);
batt_tm->tm_noti_stat = state;
schedule_delayed_work(&batt_tm->tm_work,
msecs_to_jiffies(2000));
}
static int batt_tm_notification_start(struct batt_tm_data *batt_tm)
{
int rc = 0;
union power_supply_propval ret = {0,};
rc = qpnp_adc_tm_is_ready();
if (rc) {
pr_err("qpnp_adc is not ready");
return rc;
}
if (batt_tm->ac_psy && batt_tm->chg_online) {
batt_tm->ac_psy->get_property(batt_tm->ac_psy,
POWER_SUPPLY_PROP_VOLTAGE_MAX, &ret);
batt_tm->batt_vreg_uv = ret.intval;
power_supply_set_chg_enable(batt_tm->ac_psy, true);
power_supply_set_max_voltage(batt_tm->ac_psy,
batt_tm->batt_vreg_uv);
}
batt_tm->adc_param.low_temp =
batt_tm->warm_cfg[0].next_cool_thr;
batt_tm->adc_param.high_temp =
batt_tm->warm_cfg[0].next_warm_thr;
batt_tm->adc_param.state_request =
ADC_TM_HIGH_LOW_THR_ENABLE;
batt_tm->adc_param.timer_interval = ADC_MEAS1_INTERVAL_8S;
batt_tm->adc_param.btm_ctx = batt_tm;
batt_tm->adc_param.threshold_notification =
batt_tm_ctrl_notification;
batt_tm->adc_param.channel = LR_MUX1_BATT_THERM;
rc = qpnp_adc_tm_channel_measure(&batt_tm->adc_param);
if (rc)
pr_err("request ADC error %d\n", rc);
return rc;
}
static int batt_tm_notification_end(struct batt_tm_data *batt_tm)
{
int ret;
batt_tm->adc_param.state_request =
ADC_TM_HIGH_LOW_THR_DISABLE;
batt_tm->adc_param.threshold_notification =
batt_tm_ctrl_notification;
batt_tm->adc_param.channel = LR_MUX1_BATT_THERM;
ret = qpnp_adc_tm_channel_measure(&batt_tm->adc_param);
if (ret)
pr_err("request ADC error %d\n", ret);
cancel_delayed_work_sync(&batt_tm->tm_work);
if (wake_lock_active(&batt_tm->tm_wake_lock))
wake_unlock(&batt_tm->tm_wake_lock);
return ret;
}
static void batt_tm_external_power_changed(struct power_supply *psy)
{
struct batt_tm_data *batt_tm = container_of(psy,
struct batt_tm_data, tm_psy);
int chg_online;
chg_online = power_supply_is_system_supplied();
if (batt_tm->chg_online ^ chg_online) {
batt_tm_notification_end(batt_tm);
power_supply_set_batt_health(batt_tm->ac_psy,
POWER_SUPPLY_HEALTH_GOOD);
batt_tm->chg_online = chg_online;
batt_tm_notification_start(batt_tm);
}
}
static enum power_supply_property power_props_batt_therm[] = {
POWER_SUPPLY_PROP_CURRENT_NOW,
};
static int batt_tm_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct batt_tm_data *batt_tm = container_of(psy, struct batt_tm_data,
tm_psy);
switch (psp) {
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = batt_tm->low_current_ma;
break;
default:
return -EINVAL;
}
return 0;
}
static int batt_tm_psy_init(struct batt_tm_data *batt_tm)
{
int ret = 0;
batt_tm->batt_psy = power_supply_get_by_name("battery");
if (!batt_tm->batt_psy) {
pr_err("battery supply not found\n");
return -EPROBE_DEFER;
}
batt_tm->ac_psy = power_supply_get_by_name("ac");
if (!batt_tm->ac_psy) {
pr_err("ac supply not found\n");
return -EPROBE_DEFER;
}
batt_tm->tm_psy.name = "batt_therm";
batt_tm->tm_psy.type = POWER_SUPPLY_TYPE_BMS;
batt_tm->tm_psy.properties = power_props_batt_therm;
batt_tm->tm_psy.num_properties = ARRAY_SIZE(power_props_batt_therm);
batt_tm->tm_psy.get_property = batt_tm_get_property;
batt_tm->tm_psy.external_power_changed =
batt_tm_external_power_changed;
ret = power_supply_register(batt_tm->dev, &batt_tm->tm_psy);
if (ret) {
pr_err("failed to register power_supply. ret=%d.\n", ret);
return ret;
}
return 0;
}
#define KELVIN_TO_CELSIUS(x) ((x)-2730)
static int batt_tm_parse_dt(struct device_node *np,
struct batt_tm_data *batt_tm)
{
int *buf;
int ret;
int i;
ret = of_property_read_u32(np, "tm,low-vbatt-mv",
&batt_tm->low_batt_vreg_mv);
if (ret) {
pr_err("failed to get tm,low-vbatt-mv\n");
goto error;
}
ret = of_property_read_u32(np, "tm,low-current-ma",
&batt_tm->low_current_ma);
if (ret) {
pr_err("failed to get tm,low-current-ma\n");
goto error;
}
ret = of_property_read_u32(np, "tm,warm-cfg-size",
&batt_tm->warm_cfg_size);
if (ret) {
pr_err("failed to get tm,warm-cfg-size\n");
goto error;
}
batt_tm->warm_cfg = kzalloc(sizeof(struct tm_ctrl_data) *
batt_tm->warm_cfg_size, GFP_KERNEL);
if (!batt_tm->warm_cfg) {
pr_err("Unable to allocate memory\n");
ret = -ENOMEM;
goto error;
}
buf = kzalloc(sizeof(int32_t) * batt_tm->warm_cfg_size * 5,
GFP_KERNEL);
if (!buf) {
pr_err("Unable to allocate memory\n");
ret = -ENOMEM;
goto alloc_fail0;
}
ret = of_property_read_u32_array(np, "tm,warm-cfg",
buf, batt_tm->warm_cfg_size * 5);
if (ret) {
pr_err("failed to get tm,warm-cfg\n");
kfree(buf);
goto alloc_fail0;
}
for(i = 0; i < batt_tm->warm_cfg_size; i++) {
batt_tm->warm_cfg[i].thr = KELVIN_TO_CELSIUS(buf[i*5]);
batt_tm->warm_cfg[i].action = buf[i*5+1];
batt_tm->warm_cfg[i].next_cool_thr =
KELVIN_TO_CELSIUS(buf[i*5+2]);
batt_tm->warm_cfg[i].next_warm_thr =
KELVIN_TO_CELSIUS(buf[i*5+3]);
batt_tm->warm_cfg[i].health = buf[i*5+4];
}
kfree(buf);
ret = of_property_read_u32(np, "tm,cold-cfg-size",
&batt_tm->cold_cfg_size);
if (ret) {
pr_err("failed to get tm,cold-cfg-size\n");
goto alloc_fail0;
}
batt_tm->cold_cfg = kzalloc(sizeof(struct tm_ctrl_data) *
batt_tm->cold_cfg_size, GFP_KERNEL);
if (!batt_tm->cold_cfg) {
pr_err("Unable to allocate memory\n");
ret = -ENOMEM;
goto alloc_fail0;
}
buf = kzalloc(sizeof(int32_t) * batt_tm->cold_cfg_size * 5,
GFP_KERNEL);
if (!buf) {
pr_err("Unable to allocate memory\n");
ret = -ENOMEM;
goto alloc_fail1;
}
ret = of_property_read_u32_array(np, "tm,cold-cfg",
buf, batt_tm->cold_cfg_size * 5);
if (ret) {
pr_err("failed to get tm,cold-cfg\n");
kfree(buf);
goto alloc_fail1;
}
for(i = 0; i < batt_tm->cold_cfg_size; i++) {
batt_tm->cold_cfg[i].thr = KELVIN_TO_CELSIUS(buf[i*5]);
batt_tm->cold_cfg[i].action = buf[i*5+1];
batt_tm->cold_cfg[i].next_cool_thr =
KELVIN_TO_CELSIUS(buf[i*5+2]);
batt_tm->cold_cfg[i].next_warm_thr =
KELVIN_TO_CELSIUS(buf[i*5+3]);
batt_tm->cold_cfg[i].health = buf[i*5+4];
}
kfree(buf);
return 0;
alloc_fail1:
kfree(batt_tm->cold_cfg);
alloc_fail0:
kfree(batt_tm->warm_cfg);
error:
return ret;
}
static int batt_tm_pm_notifier(struct notifier_block *notifier,
unsigned long pm_event, void *unused)
{
struct batt_tm_data *batt_tm = container_of(notifier,
struct batt_tm_data, pm_notifier);
switch (pm_event) {
case PM_SUSPEND_PREPARE:
if (!batt_tm->chg_online)
batt_tm_notification_end(batt_tm);
break;
case PM_POST_SUSPEND:
if (!batt_tm->chg_online)
batt_tm_notification_start(batt_tm);
break;
default:
break;
}
return NOTIFY_DONE;
}
static int batt_tm_ctrl_probe(struct platform_device *pdev)
{
struct batt_tm_data *batt_tm;
struct device_node *dev_node = pdev->dev.of_node;
int ret = 0;
batt_tm = kzalloc(sizeof(struct batt_tm_data), GFP_KERNEL);
if (!batt_tm) {
pr_err("falied to alloc memory\n");
return -ENOMEM;
}
if (dev_node) {
ret = batt_tm_parse_dt(dev_node, batt_tm);
if (ret) {
pr_err("failed to parse dt\n");
goto err_parse_dt;
}
}
batt_tm->dev = &pdev->dev;
wake_lock_init(&batt_tm->tm_wake_lock,
WAKE_LOCK_SUSPEND, "batt_tm_wake_lock");
INIT_DELAYED_WORK(&batt_tm->tm_work, batt_tm_worker);
ret = batt_tm_psy_init(batt_tm);
if (ret) {
pr_err("failed to init psy\n");
goto err_psy_init;
}
batt_tm->chg_online = power_supply_is_system_supplied();
ret = batt_tm_notification_start(batt_tm);
if (ret) {
pr_err("failed to init adc tm\n");
goto err_tm_init;
}
batt_tm->pm_notifier.notifier_call = batt_tm_pm_notifier;
ret = register_pm_notifier(&batt_tm->pm_notifier);
if (ret) {
pr_err("failed to register pm notifier\n");
goto err_tm_init;
}
platform_set_drvdata(pdev, batt_tm);
pr_info("probe success\n");
return 0;
err_tm_init:
power_supply_unregister(&batt_tm->tm_psy);
err_psy_init:
if(batt_tm->warm_cfg)
kfree(batt_tm->warm_cfg);
if(batt_tm->cold_cfg)
kfree(batt_tm->cold_cfg);
wake_lock_destroy(&batt_tm->tm_wake_lock);
err_parse_dt:
kfree(batt_tm);
return ret;
}
static int batt_tm_ctrl_remove(struct platform_device *pdev)
{
struct batt_tm_data *batt_tm = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
unregister_pm_notifier(&batt_tm->pm_notifier);
power_supply_unregister(&batt_tm->tm_psy);
wake_lock_destroy(&batt_tm->tm_wake_lock);
if(batt_tm->warm_cfg)
kfree(batt_tm->warm_cfg);
if(batt_tm->cold_cfg)
kfree(batt_tm->cold_cfg);
kfree(batt_tm);
return 0;
}
static struct of_device_id batt_tm_match[] = {
{.compatible = "battery_tm_ctrl", },
{}
};
static struct platform_driver batt_tm_driver = {
.probe = batt_tm_ctrl_probe,
.remove = batt_tm_ctrl_remove,
.driver = {
.name = "batt_tm_ctrl",
.owner = THIS_MODULE,
.of_match_table = batt_tm_match,
},
};
static int __init batt_tm_ctrl_init(void)
{
return platform_driver_register(&batt_tm_driver);
}
late_initcall(batt_tm_ctrl_init);
static void __exit batt_tm_ctrl_exit(void)
{
platform_driver_unregister(&batt_tm_driver);
}
module_exit(batt_tm_ctrl_exit);
MODULE_DESCRIPTION("Battery Temperature Control Driver");
MODULE_AUTHOR("ChoongRyeol Lee <choongryeol.lee@lge.com>");
MODULE_LICENSE("GPL");