OSHW-DEIMOS/SOFTWARE/A64-TERES/linux-a64/drivers/power/axp_power/axp81x-sply.c
Dimitar Gamishev f9b0e7a283 linux
2017-10-13 14:07:04 +03:00

518 lines
15 KiB
C
Executable File

/*
* Battery charger driver for allwinnertech AXP81X
*
* Copyright (C) 2014 ALLWINNERTECH.
* Ming Li <liming@allwinnertech.com>
*
* 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.
*/
#include <linux/kthread.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/mfd/axp-mfd.h>
#include <asm/div64.h>
#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#endif
#include "axp-cfg.h"
#include "axp81x-sply.h"
struct axp_charger *axp_charger;
#ifdef CONFIG_HAS_EARLYSUSPEND
static struct early_suspend axp_early_suspend;
#endif
static enum power_supply_property axp_battery_props[] = {
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_TEMP,
};
static enum power_supply_property axp_ac_props[] = {
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
};
static enum power_supply_property axp_usb_props[] = {
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
};
static void axp_battery_check_status(struct axp_charger *charger,
union power_supply_propval *val)
{
if (charger->bat_det){
if (charger->ext_valid){
if( charger->rest_vol == 100)
val->intval = POWER_SUPPLY_STATUS_FULL;
else if(charger->charge_on)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
}else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
}else
val->intval = POWER_SUPPLY_STATUS_FULL;
}
static void axp_battery_check_health(struct axp_charger *charger,
union power_supply_propval *val)
{
if (charger->fault & AXP81X_FAULT_LOG_BATINACT)
val->intval = POWER_SUPPLY_HEALTH_DEAD;
else if (charger->fault & AXP81X_FAULT_LOG_OVER_TEMP)
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (charger->fault & AXP81X_FAULT_LOG_COLD)
val->intval = POWER_SUPPLY_HEALTH_COLD;
else
val->intval = POWER_SUPPLY_HEALTH_GOOD;
}
static s32 axp_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct axp_charger *charger;
s32 ret = 0;
charger = container_of(psy, struct axp_charger, batt);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
axp_battery_check_status(charger, val);
break;
case POWER_SUPPLY_PROP_HEALTH:
axp_battery_check_health(charger, val);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = charger->battery_info->technology;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = charger->battery_info->voltage_max_design;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = charger->battery_info->voltage_min_design;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
//val->intval = charger->ocv * 1000;
val->intval = charger->vbat * 1000;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = charger->ibat * 1000;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = charger->batt.name;
break;
case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
val->intval = charger->battery_info->energy_full_design;
// DBG_PSY_MSG("POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:%d\n",val->intval);
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = charger->rest_vol;
break;
case POWER_SUPPLY_PROP_ONLINE:
{
/* in order to get hardware state, we must update charger state now.
* by sunny at 2012-12-23 11:06:15.
*/
axp_charger_update_state(charger);
val->intval = charger->bat_current_direction;
if (charger->bat_temp > 50 || -5 < charger->bat_temp)
val->intval = 0;
break;
}
case POWER_SUPPLY_PROP_PRESENT:
val->intval = charger->bat_det;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = charger->bat_temp * 10;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static s32 axp_ac_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct axp_charger *charger;
s32 ret = 0;
charger = container_of(psy, struct axp_charger, ac);
switch(psp){
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = charger->ac.name;break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = (charger->ac_valid || charger->usb_adapter_valid);break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = (charger->ac_valid || charger->usb_adapter_valid);break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = charger->vac * 1000;break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = charger->iac * 1000;break;
default:
ret = -EINVAL;break;
}
return ret;
}
static s32 axp_usb_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct axp_charger *charger;
s32 ret = 0;
charger = container_of(psy, struct axp_charger, usb);
switch(psp){
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = charger->usb.name;break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = charger->usb_valid;break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = charger->usb_valid;break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = charger->vusb * 1000;break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = charger->iusb * 1000;break;
default:
ret = -EINVAL;break;
}
return ret;
}
static char *supply_list[] = {
"battery",
};
static void axp_battery_setup_psy(struct axp_charger *charger)
{
struct power_supply *batt = &charger->batt;
struct power_supply *ac = &charger->ac;
struct power_supply *usb = &charger->usb;
struct power_supply_info *info = charger->battery_info;
batt->name = "battery";
batt->use_for_apm = info->use_for_apm;
batt->type = POWER_SUPPLY_TYPE_BATTERY;
batt->get_property = axp_battery_get_property;
batt->properties = axp_battery_props;
batt->num_properties = ARRAY_SIZE(axp_battery_props);
ac->name = "ac";
ac->type = POWER_SUPPLY_TYPE_MAINS;
ac->get_property = axp_ac_get_property;
ac->supplied_to = supply_list,
ac->num_supplicants = ARRAY_SIZE(supply_list),
ac->properties = axp_ac_props;
ac->num_properties = ARRAY_SIZE(axp_ac_props);
usb->name = "usb";
usb->type = POWER_SUPPLY_TYPE_USB;
usb->get_property = axp_usb_get_property;
usb->supplied_to = supply_list,
usb->num_supplicants = ARRAY_SIZE(supply_list),
usb->properties = axp_usb_props;
usb->num_properties = ARRAY_SIZE(axp_usb_props);
};
#if defined CONFIG_HAS_EARLYSUSPEND
static void axp_earlysuspend(struct early_suspend *h)
{
DBG_PSY_MSG(DEBUG_SPLY, "======early suspend=======\n");
}
static void axp_lateresume(struct early_suspend *h)
{
s32 value;
value = axp_powerkey_get();
if (0 != value)
axp_powerkey_set(0);
DBG_PSY_MSG(DEBUG_SPLY, "======late resume=======\n");
}
#endif
static void axp_charging_monitor(struct work_struct *work)
{
struct axp_charger *charger;
u8 temp_val[4];
static s32 pre_rest_vol = 0;
static s32 pre_bat_curr_dir = 0;
u64 power_sply = 0;
charger = container_of(work, struct axp_charger, work.work);
axp_charger_update_state(charger);
axp_charger_update(charger, &axp81x_config);
if (axp_debug & DEBUG_SPLY) {
DBG_PSY_MSG(DEBUG_SPLY, "charger->ic_temp = %d\n",charger->ic_temp);
DBG_PSY_MSG(DEBUG_SPLY, "charger->bat_temp = %d\n",charger->bat_temp);
DBG_PSY_MSG(DEBUG_SPLY, "charger->vbat = %d\n",charger->vbat);
DBG_PSY_MSG(DEBUG_SPLY, "charger->ibat = %d\n",charger->ibat);
DBG_PSY_MSG(DEBUG_SPLY, "charger->ocv = %d\n",charger->ocv);
DBG_PSY_MSG(DEBUG_SPLY, "charger->disvbat = %d\n",charger->disvbat);
DBG_PSY_MSG(DEBUG_SPLY, "charger->disibat = %d\n",charger->disibat);
power_sply = charger->disvbat * charger->disibat;
if (0 != power_sply)
power_sply = power_sply/1000;
DBG_PSY_MSG(DEBUG_SPLY, "power_sply = %lld mW\n",power_sply);
axp_reads(charger->master,0xba,2,temp_val);
DBG_PSY_MSG(DEBUG_SPLY, "Axp Rdc = %d\n",(((temp_val[0] & 0x1f) <<8) + temp_val[1])*10742/10000);
axp_reads(charger->master,0xe0,2,temp_val);
DBG_PSY_MSG(DEBUG_SPLY, "Axp batt_max_cap = %d\n",(((temp_val[0] & 0x7f) <<8) + temp_val[1])*1456/1000);
axp_reads(charger->master,0xe2,2,temp_val);
DBG_PSY_MSG(DEBUG_SPLY, "Axp coulumb_counter = %d\n",(((temp_val[0] & 0x7f) <<8) + temp_val[1])*1456/1000);
axp_read(charger->master,0xb8,temp_val);
DBG_PSY_MSG(DEBUG_SPLY, "Axp REG_B8 = %x\n",temp_val[0]);
DBG_PSY_MSG(DEBUG_SPLY, "charger->is_on = %d\n",charger->is_on);
DBG_PSY_MSG(DEBUG_SPLY, "charger->bat_current_direction = %d\n",charger->bat_current_direction);
DBG_PSY_MSG(DEBUG_SPLY, "charger->charge_on = %d\n",charger->charge_on);
DBG_PSY_MSG(DEBUG_SPLY, "charger->ext_valid = %d\n",charger->ext_valid);
}
axp_battery_update_vol(charger);
/* if battery volume changed, inform uevent */
if((charger->rest_vol - pre_rest_vol) || (charger->bat_current_direction != pre_bat_curr_dir)){
axp_reads(charger->master,0xe2,2,temp_val);
axp_reads(charger->master,0xe4,2,(temp_val+2));
DBG_PSY_MSG(DEBUG_SPLY, "battery vol change: %d->%d \n", pre_rest_vol, charger->rest_vol);
DBG_PSY_MSG(DEBUG_SPLY, "for test %d %d %d %d %d %d\n",charger->vbat,charger->ocv,charger->ibat,
(temp_val[2] & 0x7f),(temp_val[3] & 0x7f),(((temp_val[0] & 0x7f) <<8) + temp_val[1])*1456/1000);
pre_rest_vol = charger->rest_vol;
pre_bat_curr_dir = charger->bat_current_direction;
power_supply_changed(&charger->batt);
}
/* reschedule for the next time */
schedule_delayed_work(&charger->work, charger->interval);
}
static s32 axp_battery_probe(struct platform_device *pdev)
{
struct axp_charger *charger;
struct axp_supply_init_data *pdata = pdev->dev.platform_data;
s32 ret;
if (pdata == NULL)
return -EINVAL;
if (pdata->chgcur > AXP81X_CHARGE_CURRENT_MAX ||
pdata->chgvol < AXP81X_CHARGE_VOLTAGE_LEVEL0 ||
pdata->chgvol > AXP81X_CHARGE_VOLTAGE_LEVEL3){
printk("charger milliamp is too high or target voltage is over range\n");
return -EINVAL;
}
if (pdata->chgpretime < AXP81X_CHARGE_PRETIME_MIN || pdata->chgpretime >AXP81X_CHARGE_PRETIME_MAX ||
pdata->chgcsttime < AXP81X_CHARGE_FASTTIME_MIN || pdata->chgcsttime > AXP81X_CHARGE_FASTTIME_MAX){
printk("prechaging time or constant current charging time is over range\n");
return -EINVAL;
}
charger = kzalloc(sizeof(*charger), GFP_KERNEL);
if (charger == NULL)
return -ENOMEM;
spin_lock_init(&charger->charger_lock);
spin_lock(&charger->charger_lock);
charger->master = pdev->dev.parent;
charger->chgcur = pdata->chgcur;
charger->chgvol = pdata->chgvol;
charger->chgend = pdata->chgend;
charger->sample_time = pdata->sample_time;
charger->chgen = pdata->chgen;
charger->chgpretime = pdata->chgpretime;
charger->chgcsttime = pdata->chgcsttime;
charger->battery_info = pdata->battery_info;
charger->disvbat = 0;
charger->disibat = 0;
spin_unlock(&charger->charger_lock);
axp_charger = charger;
ret = axp81x_init(charger);
if (ret) {
goto err_ps_register;
}
axp_battery_setup_psy(charger);
ret = power_supply_register(&pdev->dev, &charger->batt);
if (ret)
goto err_ps_register;
ret = power_supply_register(&pdev->dev, &charger->ac);
if (ret){
power_supply_unregister(&charger->batt);
goto err_ps_register;
}
ret = power_supply_register(&pdev->dev, &charger->usb);
if (ret){
power_supply_unregister(&charger->ac);
power_supply_unregister(&charger->batt);
goto err_ps_register;
}
platform_set_drvdata(pdev, charger);
axp_charger_update_state(charger);
axp_battery_update_vol(charger);
spin_lock(&charger->charger_lock);
charger->interval = msecs_to_jiffies(10 * 1000);
spin_unlock(&charger->charger_lock);
INIT_DELAYED_WORK(&charger->work, axp_charging_monitor);
schedule_delayed_work(&charger->work, charger->interval);
axp_chg_init(charger);
ret = axp_irq_init(charger, pdev);
if(ret){
printk("cat notaxp_charger_create_attrs!!!===\n ");
return ret;
}
axp_usbac_checkst(charger);
#ifdef CONFIG_HAS_EARLYSUSPEND
axp_early_suspend.suspend = axp_earlysuspend;
axp_early_suspend.resume = axp_lateresume;
axp_early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 2;
register_early_suspend(&axp_early_suspend);
#endif
class_register(&axppower_class);
return ret;
err_ps_register:
axp81x_exit(charger);
cancel_delayed_work_sync(&charger->work);
kfree(charger);
return ret;
}
static s32 axp_battery_remove(struct platform_device *dev)
{
struct axp_charger *charger = platform_get_drvdata(dev);
class_unregister(&axppower_class);
#ifdef CONFIG_HAS_EARLYSUSPEND
unregister_early_suspend(&axp_early_suspend);
#endif
axp_irq_exit(charger);
axp_chg_exit(charger);
cancel_delayed_work_sync(&charger->work);
power_supply_unregister(&charger->usb);
power_supply_unregister(&charger->ac);
power_supply_unregister(&charger->batt);
kfree(charger);
return 0;
}
static s32 axp81x_suspend(struct platform_device *dev, pm_message_t state)
{
struct axp_charger *charger = platform_get_drvdata(dev);
/*s32 ret;
ret = axp_disable_irq(charger, 6);
if (ret < 0)
return ret;*/
schedule_delayed_work(&charger->usbwork, 0);
flush_delayed_work(&charger->usbwork);
cancel_delayed_work_sync(&charger->work);
cancel_delayed_work_sync(&charger->usbwork);
axp81x_chg_current_limit(axp81x_config.pmu_suspend_chgcur);
return 0;
}
static s32 axp81x_resume(struct platform_device *dev)
{
struct axp_charger *charger = platform_get_drvdata(dev);
s32 pre_rest_vol;
/*axp_enable_irq(charger);*/
pre_rest_vol = charger->rest_vol;
axp_charger_update_state(charger);
axp_charger_update(charger, &axp81x_config);
axp_battery_update_vol(charger);
if(charger->rest_vol - pre_rest_vol){
printk("battery vol change: %d->%d \n", pre_rest_vol, charger->rest_vol);
pre_rest_vol = charger->rest_vol;
axp_write(charger->master,AXP81X_DATA_BUFFER1,charger->rest_vol | 0x80);
}
axp81x_chg_current_limit(axp81x_config.pmu_runtime_chgcur);
power_supply_changed(&charger->batt);
power_supply_changed(&charger->ac);
power_supply_changed(&charger->usb);
schedule_delayed_work(&charger->work, charger->interval);
schedule_delayed_work(&charger->usbwork, msecs_to_jiffies(7 * 1000));
return 0;
}
static void axp81x_shutdown(struct platform_device *dev)
{
struct axp_charger *charger = platform_get_drvdata(dev);
cancel_delayed_work_sync(&charger->work);
axp81x_chg_current_limit(axp81x_config.pmu_shutdown_chgcur);
return;
}
static struct platform_driver axp_battery_driver = {
.driver = {
.name = "axp81x-supplyer",
.owner = THIS_MODULE,
},
.probe = axp_battery_probe,
.remove = axp_battery_remove,
.suspend = axp81x_suspend,
.resume = axp81x_resume,
.shutdown = axp81x_shutdown,
};
static s32 axp_battery_init(void)
{
s32 ret =0;
ret = platform_driver_register(&axp_battery_driver);
return ret;
}
static void axp_battery_exit(void)
{
platform_driver_unregister(&axp_battery_driver);
}
device_initcall(axp_battery_init);
module_exit(axp_battery_exit);
MODULE_DESCRIPTION("AXP81X battery driver");
MODULE_AUTHOR("Ming Li");
MODULE_LICENSE("GPL");