diff options
Diffstat (limited to 'drivers/battery/sec_battery.c')
-rw-r--r-- | drivers/battery/sec_battery.c | 2199 |
1 files changed, 2199 insertions, 0 deletions
diff --git a/drivers/battery/sec_battery.c b/drivers/battery/sec_battery.c new file mode 100644 index 0000000..3be97cd --- /dev/null +++ b/drivers/battery/sec_battery.c @@ -0,0 +1,2199 @@ +/* + * sec_battery.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2012 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 <linux/battery/sec_battery.h> + +char *sec_bat_charging_mode_str[] = { + "None", + "Normal", + "Additional", + "Re-Charging" +}; + +char *sec_bat_status_str[] = { + "Unknown", + "Charging", + "Discharging", + "Not-charging", + "Full" +}; + +char *sec_bat_health_str[] = { + "Unknown", + "Good", + "Overheat", + "Dead", + "OverVoltage", + "UnspecFailure", + "Cold", + "UnderVoltage" +}; + +static int sec_bat_set_charge( + struct sec_battery_info *battery, + bool enable) +{ + union power_supply_propval val; + ktime_t current_time; + struct timespec ts; + + current_time = alarm_get_elapsed_realtime(); + ts = ktime_to_timespec(current_time); + + if ((battery->cable_type != POWER_SUPPLY_TYPE_BATTERY) && + (battery->health != POWER_SUPPLY_HEALTH_GOOD)) { + dev_info(battery->dev, + "%s: Battery is NOT good!\n", __func__); + return -EPERM; + } + + if (enable) { + val.intval = battery->cable_type; + /*Reset charging start time only in initial charging start */ + if (battery->charging_start_time == 0) { + battery->charging_start_time = ts.tv_sec; + battery->charging_next_time = + battery->pdata->charging_reset_time; + } + } else { + val.intval = POWER_SUPPLY_TYPE_BATTERY; + battery->charging_start_time = 0; + battery->charging_passed_time = 0; + battery->charging_next_time = 0; + battery->full_check_cnt = 0; + } + + battery->temp_high_cnt = 0; + battery->temp_low_cnt = 0; + battery->temp_recover_cnt = 0; + + psy_do_property("sec-charger", set, + POWER_SUPPLY_PROP_ONLINE, val); + + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_ONLINE, val); + + return 0; +} + +static int sec_bat_get_adc_data(struct sec_battery_info *battery, + int adc_ch, int count) +{ + int adc_data; + int adc_max; + int adc_min; + int adc_total; + int i; + + adc_data = 0; + adc_max = 0; + adc_min = 0; + adc_total = 0; + + for (i = 0; i < count; i++) { + mutex_lock(&battery->adclock); + adc_data = adc_read(battery->pdata, adc_ch); + mutex_unlock(&battery->adclock); + + if (adc_data < 0) + goto err; + + if (i != 0) { + if (adc_data > adc_max) + adc_max = adc_data; + else if (adc_data < adc_min) + adc_min = adc_data; + } else { + adc_max = adc_data; + adc_min = adc_data; + } + adc_total += adc_data; + } + + return (adc_total - adc_max - adc_min) / (count - 2); +err: + return adc_data; +} + +static unsigned long calculate_average_adc( + struct sec_battery_info *battery, + int channel, int adc) +{ + unsigned int cnt = 0; + int total_adc = 0; + int average_adc = 0; + int index = 0; + + cnt = battery->adc_sample[channel].cnt; + total_adc = battery->adc_sample[channel].total_adc; + + if (adc < 0) { + dev_err(battery->dev, + "%s : Invalid ADC : %d\n", __func__, adc); + adc = battery->adc_sample[channel].average_adc; + } + + if (cnt < ADC_SAMPLE_COUNT) { + battery->adc_sample[channel].adc_arr[cnt] = adc; + battery->adc_sample[channel].index = cnt; + battery->adc_sample[channel].cnt = ++cnt; + + total_adc += adc; + average_adc = total_adc / cnt; + } else { + index = battery->adc_sample[channel].index; + if (++index >= ADC_SAMPLE_COUNT) + index = 0; + + total_adc = total_adc - + battery->adc_sample[channel].adc_arr[index] + adc; + average_adc = total_adc / ADC_SAMPLE_COUNT; + + battery->adc_sample[channel].adc_arr[index] = adc; + battery->adc_sample[channel].index = index; + } + + battery->adc_sample[channel].total_adc = total_adc; + battery->adc_sample[channel].average_adc = average_adc; + + return average_adc; +} + +static int sec_bat_get_adc_value( + struct sec_battery_info *battery, int channel) +{ + int adc; + + adc = sec_bat_get_adc_data(battery, channel, + battery->pdata->adc_check_count); + + if (adc < 0) { + dev_err(battery->dev, + "%s: Error in ADC\n", __func__); + return adc; + } + + return adc; +} + +static int sec_bat_get_charger_type_adc + (struct sec_battery_info *battery) +{ + /* It is true something valid is + connected to the device for charging. + By default this something is considered to be USB.*/ + int result = POWER_SUPPLY_TYPE_USB; + + int adc = 0; + int i; + + battery->pdata->cable_switch_check(); + + adc = sec_bat_get_adc_value(battery, + SEC_BAT_ADC_CHANNEL_CABLE_CHECK); + + battery->pdata->cable_switch_normal(); + + for (i = 0; i < SEC_SIZEOF_POWER_SUPPLY_TYPE; i++) + if ((adc > battery->pdata->cable_adc_value[i].min) && + (adc < battery->pdata->cable_adc_value[i].max)) + break; + if (i >= SEC_SIZEOF_POWER_SUPPLY_TYPE) + dev_err(battery->dev, + "%s : default USB\n", __func__); + else + result = i; + + dev_dbg(battery->dev, "%s : result(%d), adc(%d)\n", + __func__, result, adc); + + return result; +} + +static bool sec_bat_check_vf_adc(struct sec_battery_info *battery) +{ + int adc; + + adc = sec_bat_get_adc_data(battery, + SEC_BAT_ADC_CHANNEL_BAT_CHECK, + battery->pdata->adc_check_count); + + if (adc < 0) { + dev_err(battery->dev, "%s: VF ADC error\n", __func__); + adc = battery->check_adc_value; + } else + battery->check_adc_value = adc; + + if ((battery->check_adc_value < battery->pdata->check_adc_max) && + (battery->check_adc_value > battery->pdata->check_adc_min)) + return true; + else + return false; +} + +static bool sec_bat_check_by_psy(struct sec_battery_info *battery) +{ + char *psy_name; + union power_supply_propval value; + bool ret; + ret = true; + + switch (battery->pdata->battery_check_type) { + case SEC_BATTERY_CHECK_PMIC: + psy_name = battery->pdata->pmic_name; + break; + case SEC_BATTERY_CHECK_FUELGAUGE: + psy_name = "sec-fuelgauge"; + break; + case SEC_BATTERY_CHECK_CHARGER: + psy_name = "sec-charger"; + break; + default: + dev_err(battery->dev, + "%s: Invalid Battery Check Type\n", __func__); + ret = false; + goto battery_check_error; + break; + } + + psy_do_property(psy_name, get, + POWER_SUPPLY_PROP_PRESENT, value); + ret = (bool)value.intval; + +battery_check_error: + return ret; +} + +static bool sec_bat_check(struct sec_battery_info *battery) +{ + bool ret; + ret = true; + + switch (battery->pdata->battery_check_type) { + case SEC_BATTERY_CHECK_ADC: + ret = sec_bat_check_vf_adc(battery); + break; + case SEC_BATTERY_CHECK_CALLBACK: + ret = battery->pdata->check_battery_callback(); + break; + case SEC_BATTERY_CHECK_PMIC: + case SEC_BATTERY_CHECK_FUELGAUGE: + case SEC_BATTERY_CHECK_CHARGER: + ret = sec_bat_check_by_psy(battery); + break; + case SEC_BATTERY_CHECK_INT: + ret = battery->present; + break; + case SEC_BATTERY_CHECK_NONE: + dev_dbg(battery->dev, "%s: No Check\n", __func__); + default: + break; + } + + return ret; +} + +static bool sec_bat_battery_cable_check(struct sec_battery_info *battery) +{ + if (!sec_bat_check(battery)) { + if (battery->check_count < battery->pdata->check_count) + battery->check_count++; + else { + dev_err(battery->dev, + "%s: Battery Disconnected\n", __func__); + battery->present = false; + battery->health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + battery->pdata->check_battery_result_callback(); + return false; + } + } else + battery->check_count = 0; + + battery->present = true; + + dev_dbg(battery->dev, "%s: Battery Connected\n", __func__); + + if (battery->pdata->cable_check_type & + SEC_BATTERY_CABLE_CHECK_POLLING) { + /* check cable status */ + wake_lock(&battery->cable_wake_lock); + queue_work(battery->monitor_wqueue, + &battery->cable_work); + } + return true; +}; + +static bool sec_bat_ovp_uvlo_by_psy(struct sec_battery_info *battery) +{ + char *psy_name; + union power_supply_propval value; + bool ret; + ret = true; + + switch (battery->pdata->ovp_uvlo_check_type) { + case SEC_BATTERY_OVP_UVLO_PMICPOLLING: + psy_name = battery->pdata->pmic_name; + break; + case SEC_BATTERY_OVP_UVLO_CHGPOLLING: + psy_name = "sec-charger"; + break; + default: + dev_err(battery->dev, + "%s: Invalid OVP/UVLO Check Type\n", __func__); + ret = false; + goto ovp_uvlo_check_error; + break; + } + + psy_do_property(psy_name, get, + POWER_SUPPLY_PROP_HEALTH, value); + + if (value.intval != POWER_SUPPLY_HEALTH_GOOD) { + battery->health = value.intval; + ret = false; + } + +ovp_uvlo_check_error: + return ret; +} + +static bool sec_bat_ovp_uvlo(struct sec_battery_info *battery) +{ + bool ret; + ret = true; + + switch (battery->pdata->ovp_uvlo_check_type) { + case SEC_BATTERY_OVP_UVLO_CALLBACK: + ret = battery->pdata->ovp_uvlo_callback(); + break; + case SEC_BATTERY_OVP_UVLO_PMICPOLLING: + case SEC_BATTERY_OVP_UVLO_CHGPOLLING: + ret = sec_bat_ovp_uvlo_by_psy(battery); + break; + case SEC_BATTERY_OVP_UVLO_PMICINT: + case SEC_BATTERY_OVP_UVLO_CHGINT: + /* nothing for interrupt check */ + default: + break; + } + + return ret; +} + +static bool sec_bat_check_recharge(struct sec_battery_info *battery) +{ + if ((battery->status == POWER_SUPPLY_STATUS_FULL || + (battery->status == POWER_SUPPLY_STATUS_CHARGING && + (battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_NOTIMEFULL))) && + (battery->charging_mode == SEC_BATTERY_CHARGING_NONE)) { + if ((battery->pdata->recharge_condition_type & + SEC_BATTERY_RECHARGE_CONDITION_SOC) && + (battery->capacity <= + battery->pdata->recharge_condition_soc)) { + dev_info(battery->dev, + "%s: Re-charging by SOC (%d)\n", + __func__, battery->capacity); + return true; + } + + if ((battery->pdata->recharge_condition_type & + SEC_BATTERY_RECHARGE_CONDITION_AVGVCELL) && + (battery->voltage_avg <= + battery->pdata->recharge_condition_avgvcell)) { + dev_info(battery->dev, + "%s: Re-charging by average VCELL (%d)\n", + __func__, battery->voltage_avg); + return true; + } + + if ((battery->pdata->recharge_condition_type & + SEC_BATTERY_RECHARGE_CONDITION_VCELL) && + (battery->voltage_now <= + battery->pdata->recharge_condition_vcell)) { + dev_info(battery->dev, + "%s: Re-charging by VCELL (%d)\n", + __func__, battery->voltage_now); + return true; + } + } + + return false; +} + +static bool sec_bat_ovp_uvlo_result(struct sec_battery_info *battery) +{ + bool ret; + + ret = true; + + dev_err(battery->dev, + "%s: Abnormal Charging Voltage\n", __func__); + battery->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + ret = battery->pdata->ovp_uvlo_result_callback(battery->health); + + return ret; +} + +static bool sec_bat_voltage_check(struct sec_battery_info *battery) +{ + if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { + dev_dbg(battery->dev, + "%s: Charging Disabled\n", __func__); + return true; + } + + /* OVP/UVLO check */ + if (sec_bat_ovp_uvlo(battery)) + dev_dbg(battery->dev, + "%s: Normal Charging Voltage\n", __func__); + else + sec_bat_ovp_uvlo_result(battery); + + /* Re-Charging check */ + if (sec_bat_check_recharge(battery)) { + battery->charging_mode = SEC_BATTERY_CHARGING_RECHARGING; + sec_bat_set_charge(battery, true); + } + + return true; +}; + +static bool sec_bat_get_temperature_by_adc( + struct sec_battery_info *battery, + enum power_supply_property psp, + union power_supply_propval *value) +{ + int temp = 0; + int temp_adc; + int low = 0; + int high = 0; + int mid = 0; + int channel; + sec_bat_adc_table_data_t *temp_adc_table; + unsigned int temp_adc_table_size; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP: + channel = SEC_BAT_ADC_CHANNEL_TEMP; + temp_adc_table = battery->pdata->temp_adc_table; + temp_adc_table_size = + battery->pdata->temp_adc_table_size; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + channel = SEC_BAT_ADC_CHANNEL_TEMP_AMBIENT; + temp_adc_table = battery->pdata->temp_amb_adc_table; + temp_adc_table_size = + battery->pdata->temp_amb_adc_table_size; + break; + default: + dev_err(battery->dev, + "%s: Invalid Property\n", __func__); + return false; + } + + temp_adc = sec_bat_get_adc_value(battery, channel); + if (temp_adc < 0) + return true; + battery->temp_adc = temp_adc; + + high = temp_adc_table_size - 1; + + while (low <= high) { + mid = (low + high) / 2; + if (temp_adc_table[mid].adc > temp_adc) + high = mid - 1; + else if (temp_adc_table[mid].adc < temp_adc) + low = mid + 1; + else + break; + } + temp = temp_adc_table[mid].temperature; + + temp += + ((temp_adc_table[mid+1].temperature - temp_adc_table[mid].temperature) * + (temp_adc - temp_adc_table[mid].adc)) / + (temp_adc_table[mid+1].adc - temp_adc_table[mid].adc); + + value->intval = temp; + + dev_dbg(battery->dev, + "%s: Temp(%d), Temp-ADC(%d)\n", + __func__, temp, temp_adc); + + return true; +} + +static bool sec_bat_temperature( + struct sec_battery_info *battery) +{ + bool ret; + ret = true; + + if (battery->pdata->event_check && battery->event) { + battery->temp_high_threshold = + battery->pdata->temp_high_threshold_event; + battery->temp_high_recovery = + battery->pdata->temp_high_recovery_event; + battery->temp_low_recovery = + battery->pdata->temp_low_recovery_event; + battery->temp_low_threshold = + battery->pdata->temp_low_threshold_event; + } else if (battery->pdata->is_lpm()) { + battery->temp_high_threshold = + battery->pdata->temp_high_threshold_lpm; + battery->temp_high_recovery = + battery->pdata->temp_high_recovery_lpm; + battery->temp_low_recovery = + battery->pdata->temp_low_recovery_lpm; + battery->temp_low_threshold = + battery->pdata->temp_low_threshold_lpm; + } else { + battery->temp_high_threshold = + battery->pdata->temp_high_threshold_normal; + battery->temp_high_recovery = + battery->pdata->temp_high_recovery_normal; + battery->temp_low_recovery = + battery->pdata->temp_low_recovery_normal; + battery->temp_low_threshold = + battery->pdata->temp_low_threshold_normal; + } + + dev_info(battery->dev, + "%s: HT(%d), HR(%d), LT(%d), LR(%d)\n", + __func__, battery->temp_high_threshold, + battery->temp_high_recovery, + battery->temp_low_threshold, + battery->temp_low_recovery); + return ret; +} + +static bool sec_bat_temperature_check( + struct sec_battery_info *battery) +{ + int temp_value; + + if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { + dev_dbg(battery->dev, + "%s: Charging Disabled\n", __func__); + return true; + } + + sec_bat_temperature(battery); + + switch (battery->pdata->temp_check_type) { + case SEC_BATTERY_TEMP_CHECK_ADC: + temp_value = battery->temp_adc; + break; + case SEC_BATTERY_TEMP_CHECK_TEMP: + temp_value = battery->temperature; + break; + default: + dev_err(battery->dev, + "%s: Invalid Temp Check Type\n", __func__); + return true; + } + + if (temp_value >= battery->temp_high_threshold) { + if (battery->health != POWER_SUPPLY_HEALTH_OVERHEAT) { + if (battery->temp_high_cnt < + battery->pdata->temp_check_count) + battery->temp_high_cnt++; + dev_dbg(battery->dev, + "%s: high count = %d\n", + __func__, battery->temp_high_cnt); + } + } else if ((temp_value <= battery->temp_high_recovery) && + (temp_value >= battery->temp_low_recovery)) { + if (battery->health == POWER_SUPPLY_HEALTH_OVERHEAT || + battery->health == POWER_SUPPLY_HEALTH_COLD) { + if (battery->temp_recover_cnt < + battery->pdata->temp_check_count) + battery->temp_recover_cnt++; + dev_dbg(battery->dev, + "%s: recovery count = %d\n", + __func__, battery->temp_recover_cnt); + } + } else if (temp_value <= battery->temp_low_threshold) { + if (battery->health != POWER_SUPPLY_HEALTH_COLD) { + if (battery->temp_low_cnt < + battery->pdata->temp_check_count) + battery->temp_low_cnt++; + dev_dbg(battery->dev, + "%s: low count = %d\n", + __func__, battery->temp_low_cnt); + } + } else { + battery->temp_high_cnt = 0; + battery->temp_low_cnt = 0; + battery->temp_recover_cnt = 0; + } + + if (battery->temp_high_cnt >= + battery->pdata->temp_check_count) + battery->health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (battery->temp_low_cnt >= + battery->pdata->temp_check_count) + battery->health = POWER_SUPPLY_HEALTH_COLD; + else if (battery->temp_recover_cnt >= + battery->pdata->temp_check_count) + battery->health = POWER_SUPPLY_HEALTH_GOOD; + + if ((battery->health == POWER_SUPPLY_HEALTH_OVERHEAT) || + (battery->health == POWER_SUPPLY_HEALTH_COLD)) { + dev_info(battery->dev, + "%s: Insafe Temperature\n", __func__); + battery->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + /* change charging current to battery (default 0mA) */ + sec_bat_set_charge(battery, false); + return false; + } else { + dev_dbg(battery->dev, + "%s: Safe Temperature\n", __func__); + /* if recovered from not charging */ + if ((battery->health == POWER_SUPPLY_HEALTH_GOOD) && + (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING)) { + if (battery->charging_mode == + SEC_BATTERY_CHARGING_RECHARGING) + battery->status = POWER_SUPPLY_STATUS_FULL; + else /* Normal Charging */ + battery->status = POWER_SUPPLY_STATUS_CHARGING; + /* turn on charger by cable type */ + sec_bat_set_charge(battery, true); + } + return true; + } +}; + +static void sec_bat_event_expired_timer_func(unsigned long param) +{ + struct sec_battery_info *battery = + (struct sec_battery_info *)param; + + battery->event &= (~battery->event_wait); + dev_info(battery->dev, + "%s: event expired (0x%x)\n", __func__, battery->event); +} + +static void sec_bat_event_set( + struct sec_battery_info *battery, int event, int enable) +{ + if (!battery->pdata->event_check) + return; + + /* ignore duplicated deactivation of same event + * only if the event is one last event + */ + if (!enable && (battery->event == battery->event_wait)) { + dev_info(battery->dev, + "%s: ignore duplicated deactivation of same event\n", + __func__); + return; + } + + del_timer_sync(&battery->event_expired_timer); + battery->event &= (~battery->event_wait); + + if (enable) { + battery->event_wait = 0; + battery->event |= event; + + dev_info(battery->dev, + "%s: event set (0x%x)\n", __func__, battery->event); + } else { + if (battery->event == 0) { + dev_dbg(battery->dev, + "%s: nothing to clear\n", __func__); + return; /* nothing to clear */ + } + battery->event_wait = event; + mod_timer(&battery->event_expired_timer, + jiffies + (battery->pdata->event_waiting_time * HZ)); + dev_info(battery->dev, + "%s: start timer (curr 0x%x, wait 0x%x)\n", + __func__, battery->event, battery->event_wait); + } +} + +static bool sec_bat_time_management( + struct sec_battery_info *battery) +{ + unsigned long charging_time; + ktime_t current_time; + struct timespec ts; + + current_time = alarm_get_elapsed_realtime(); + ts = ktime_to_timespec(current_time); + + if (battery->charging_start_time == 0) { + dev_dbg(battery->dev, + "%s: Charging Disabled\n", __func__); + return true; + } + + if (ts.tv_sec >= battery->charging_start_time) + charging_time = ts.tv_sec - battery->charging_start_time; + else + charging_time = 0xFFFFFFFF - battery->charging_start_time + + ts.tv_sec; + + battery->charging_passed_time = charging_time; + + dev_info(battery->dev, + "%s: Charging Time : %ld secs\n", __func__, + battery->charging_passed_time); + + switch (battery->status) { + case POWER_SUPPLY_STATUS_FULL: + if (battery->charging_mode == SEC_BATTERY_CHARGING_RECHARGING && + (charging_time > + battery->pdata->recharging_total_time)) { + dev_dbg(battery->dev, + "%s: Recharging Timer Expired\n", __func__); + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + if (sec_bat_set_charge(battery, false)) { + dev_err(battery->dev, + "%s: Fail to Set Charger\n", __func__); + return true; + } + + return false; + } + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (battery->charging_mode == SEC_BATTERY_CHARGING_NORMAL && + (charging_time > + battery->pdata->charging_total_time)) { + dev_dbg(battery->dev, + "%s: Charging Timer Expired\n", __func__); + if (!(battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_NOTIMEFULL)) + battery->status = POWER_SUPPLY_STATUS_FULL; + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + if (sec_bat_set_charge(battery, false)) { + dev_err(battery->dev, + "%s: Fail to Set Charger\n", __func__); + return true; + } + + return false; + } + if (battery->pdata->charging_reset_time) { + if (charging_time > battery->charging_next_time) { + /*reset current in charging status */ + battery->charging_next_time = + battery->charging_passed_time + + (battery->pdata->charging_reset_time * + HZ); + + dev_dbg(battery->dev, + "%s: Reset charging current\n", + __func__); + if (sec_bat_set_charge(battery, true)) { + dev_err(battery->dev, + "%s: Fail to Set Charger\n", + __func__); + return true; + } + } + } + break; + default: + dev_err(battery->dev, + "%s: Undefine Battery Status\n", __func__); + return true; + } + + return true; +} + +static bool sec_bat_check_fullcharged( + struct sec_battery_info *battery) +{ + int current_adc; + bool ret; + int err; + + ret = false; + + if (battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_SOC) { + if (battery->capacity < + battery->pdata->full_condition_soc) { + dev_dbg(battery->dev, + "%s: Not enough SOC (%d%%)\n", + __func__, battery->capacity); + goto not_full_charged; + } + } + + if (battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_AVGVCELL) { + if (battery->voltage_avg < + battery->pdata->full_condition_avgvcell) { + dev_dbg(battery->dev, + "%s: Not enough AVGVCELL (%dmV)\n", + __func__, battery->voltage_avg); + goto not_full_charged; + } + } + + if (battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_OCV) { + if (battery->voltage_ocv < + battery->pdata->full_condition_ocv) { + dev_dbg(battery->dev, + "%s: Not enough OCV (%dmV)\n", + __func__, battery->voltage_ocv); + goto not_full_charged; + } + } + + switch (battery->pdata->full_check_type) { + case SEC_BATTERY_FULLCHARGED_ADC_DUAL: + if (battery->charging_mode == + SEC_BATTERY_CHARGING_2ND) { + current_adc = + sec_bat_get_adc_value(battery, + SEC_BAT_ADC_CHANNEL_FULL_CHECK); + + dev_dbg(battery->dev, + "%s: Current ADC (%d)\n", + __func__, current_adc); + + if (current_adc < 0) + break; + battery->current_adc = current_adc; + + if (battery->current_adc < + battery->pdata->full_check_adc_2nd) { + battery->full_check_cnt++; + dev_dbg(battery->dev, + "%s: Full Check 2nd (%d)\n", + __func__, battery->full_check_cnt); + } else + battery->full_check_cnt = 0; + break; + } + case SEC_BATTERY_FULLCHARGED_ADC: + current_adc = + sec_bat_get_adc_value(battery, + SEC_BAT_ADC_CHANNEL_FULL_CHECK); + + dev_dbg(battery->dev, + "%s: Current ADC (%d)\n", + __func__, current_adc); + + if (current_adc < 0) + break; + battery->current_adc = current_adc; + + if (battery->current_adc < + battery->pdata->full_check_adc_1st) { + battery->full_check_cnt++; + dev_dbg(battery->dev, + "%s: Full Check ADC (%d)\n", + __func__, battery->full_check_cnt); + } else + battery->full_check_cnt = 0; + break; + + case SEC_BATTERY_FULLCHARGED_FG_CURRENT_DUAL: + if (battery->charging_mode == + SEC_BATTERY_CHARGING_2ND) { + if (battery->current_avg < + battery->pdata->charging_current[ + battery->cable_type].full_check_current_2nd) { + battery->full_check_cnt++; + dev_dbg(battery->dev, + "%s: Full Check Current 2nd (%d)\n", + __func__, battery->full_check_cnt); + } else + battery->full_check_cnt = 0; + break; + } + case SEC_BATTERY_FULLCHARGED_FG_CURRENT: + if (battery->current_avg < + battery->pdata->charging_current[ + battery->cable_type].full_check_current_1st) { + battery->full_check_cnt++; + dev_dbg(battery->dev, + "%s: Full Check Current (%d)\n", + __func__, battery->full_check_cnt); + } else + battery->full_check_cnt = 0; + break; + + case SEC_BATTERY_FULLCHARGED_CHGGPIO: + err = gpio_request( + battery->pdata->chg_gpio_full_check, + "GPIO_CHG_FULL"); + if (err) { + dev_err(battery->dev, + "%s: Error in Request of GPIO\n", __func__); + break; + } + if (!(gpio_get_value_cansleep( + battery->pdata->chg_gpio_full_check) ^ + !battery->pdata->chg_polarity_full_check)) { + battery->full_check_cnt++; + dev_dbg(battery->dev, + "%s: Full Check GPIO (%d)\n", + __func__, battery->full_check_cnt); + } else + battery->full_check_cnt = 0; + gpio_free(battery->pdata->chg_gpio_full_check); + break; + + case SEC_BATTERY_FULLCHARGED_CHGINT: + break; + + default: + dev_err(battery->dev, + "%s: Invalid Full Check\n", __func__); + break; + } + + if (battery->full_check_cnt > + battery->pdata->full_check_count) { + battery->full_check_cnt = 0; + ret = true; + } + +not_full_charged: + return ret; +} + +static void sec_bat_do_fullcharged( + struct sec_battery_info *battery) +{ + union power_supply_propval value; + + if (((battery->pdata->full_check_type == + SEC_BATTERY_FULLCHARGED_ADC_DUAL) || + (battery->pdata->full_check_type == + SEC_BATTERY_FULLCHARGED_FG_CURRENT_DUAL)) && + (battery->charging_mode == + SEC_BATTERY_CHARGING_NORMAL)) { + battery->charging_mode = SEC_BATTERY_CHARGING_2ND; + } else { + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + sec_bat_set_charge(battery, false); + + value.intval = POWER_SUPPLY_STATUS_FULL; + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_STATUS, value); + } + + /* platform can NOT get information of battery + * because wakeup time is too short to check uevent + * To make sure that target is wakeup if full-charged, + * activated wake lock in a few seconds + */ + wake_lock_timeout(&battery->vbus_wake_lock, HZ * 10); + battery->status = POWER_SUPPLY_STATUS_FULL; +} + +static bool sec_bat_fullcharged_check( + struct sec_battery_info *battery) +{ + if (battery->charging_mode == SEC_BATTERY_CHARGING_NONE) { + dev_dbg(battery->dev, + "%s: No Need to Check Full-Charged\n", __func__); + return true; + } + + if (sec_bat_check_fullcharged(battery)) + sec_bat_do_fullcharged(battery); + + dev_info(battery->dev, + "%s: Charging Mode : %s\n", __func__, + sec_bat_charging_mode_str[battery->charging_mode]); + + return true; +}; + +static void sec_bat_get_battery_info( + struct sec_battery_info *battery) +{ + union power_supply_propval value; + + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + battery->voltage_now = value.intval; + + value.intval = SEC_BATTEY_VOLTAGE_AVERAGE; + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + battery->voltage_avg = value.intval; + + value.intval = SEC_BATTEY_VOLTAGE_OCV; + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + battery->voltage_ocv = value.intval; + + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + battery->current_now = value.intval; + + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_CURRENT_AVG, value); + battery->current_avg = value.intval; + + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_CAPACITY, value); + battery->capacity = value.intval; + + switch (battery->pdata->thermal_source) { + case SEC_BATTERY_THERMAL_SOURCE_FG: + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_TEMP, value); + battery->temperature = value.intval; + + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_TEMP_AMBIENT, value); + battery->temper_amb = value.intval; + break; + case SEC_BATTERY_THERMAL_SOURCE_CALLBACK: + battery->pdata->get_temperature_callback( + POWER_SUPPLY_PROP_TEMP, &value); + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_TEMP, value); + battery->temperature = value.intval; + + battery->pdata->get_temperature_callback( + POWER_SUPPLY_PROP_TEMP_AMBIENT, &value); + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_TEMP_AMBIENT, value); + battery->temper_amb = value.intval; + break; + case SEC_BATTERY_THERMAL_SOURCE_ADC: + sec_bat_get_temperature_by_adc(battery, + POWER_SUPPLY_PROP_TEMP, &value); + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_TEMP, value); + battery->temperature = value.intval; + + sec_bat_get_temperature_by_adc(battery, + POWER_SUPPLY_PROP_TEMP_AMBIENT, &value); + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_TEMP_AMBIENT, value); + battery->temper_amb = value.intval; + break; + default: + break; + } + + dev_info(battery->dev, + "%s: %s, SOC(%d%%)\n", __func__, + battery->present ? "Connected" : "Disconnected", + battery->capacity); + dev_info(battery->dev, + "%s: Vnow(%dmV), Vavg(%dmV), Vocv(%dmV)\n", + __func__, battery->voltage_now, + battery->voltage_avg, battery->voltage_ocv); + dev_info(battery->dev, + "%s: Inow(%dmA), Iavg(%dmA), Iadc(%d)\n", + __func__, battery->current_now, + battery->current_avg, battery->current_adc); + dev_info(battery->dev, + "%s: Tbat(%d.%d), Tamb(%d.%d)\n", + __func__, battery->temperature / 10, + battery->temperature % 10, + battery->temper_amb / 10, + battery->temper_amb % 10); +}; + +static void sec_bat_polling_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of( + work, struct sec_battery_info, polling_work.work); + + wake_lock(&battery->monitor_wake_lock); + queue_work(battery->monitor_wqueue, &battery->monitor_work); + dev_dbg(battery->dev, "%s: Activated\n", __func__); +} + +static void sec_bat_program_alarm( + struct sec_battery_info *battery, int seconds) +{ + ktime_t low_interval = ktime_set(seconds, 0); + ktime_t slack = ktime_set(10, 0); + ktime_t next; + + next = ktime_add(battery->last_poll_time, low_interval); + alarm_start_range(&battery->polling_alarm, + next, ktime_add(next, slack)); +} + +static void sec_bat_alarm(struct alarm *alarm) +{ + struct sec_battery_info *battery = container_of(alarm, + struct sec_battery_info, polling_alarm); + + /* In wake up, monitor work will be queued in complete function + * To avoid duplicated queuing of monitor work, + * do NOT queue monitor work in wake up by polling alarm + */ + if (!battery->polling_in_sleep) { + wake_lock(&battery->monitor_wake_lock); + queue_work(battery->monitor_wqueue, &battery->monitor_work); + dev_dbg(battery->dev, "%s: Activated\n", __func__); + } +} + + +static unsigned int sec_bat_get_polling_time( + struct sec_battery_info *battery) +{ + if (battery->status == + POWER_SUPPLY_STATUS_FULL) + battery->polling_time = + battery->pdata->polling_time[ + POWER_SUPPLY_STATUS_CHARGING]; + else + battery->polling_time = + battery->pdata->polling_time[ + battery->status]; + + battery->polling_short = true; + + switch (battery->status) { + case POWER_SUPPLY_STATUS_CHARGING: + if (battery->polling_in_sleep) + battery->polling_short = false; + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + if (battery->polling_in_sleep) + battery->polling_time = + battery->pdata->polling_time[ + SEC_BATTERY_POLLING_TIME_SLEEP]; + else + battery->polling_time = + battery->pdata->polling_time[ + battery->status]; + battery->polling_short = false; + break; + case POWER_SUPPLY_STATUS_FULL: + if (battery->polling_in_sleep) { + if (battery->charging_mode == + SEC_BATTERY_CHARGING_NONE) + battery->polling_time = + battery->pdata->polling_time[ + SEC_BATTERY_POLLING_TIME_SLEEP]; + battery->polling_short = false; + } else { + if (battery->charging_mode == + SEC_BATTERY_CHARGING_NONE) + battery->polling_short = false; + } + break; + } + + if (battery->polling_short) + return battery->pdata->polling_time[ + SEC_BATTERY_POLLING_TIME_BASIC]; + else + return battery->polling_time; +} + +static bool sec_bat_is_short_polling( + struct sec_battery_info *battery) +{ + if (battery->polling_short && + ((battery->polling_time / + battery->pdata->polling_time[ + SEC_BATTERY_POLLING_TIME_BASIC]) + > battery->polling_count)) + return true; + + return false; +} + +static void sec_bat_update_polling_count( + struct sec_battery_info *battery) +{ + if (sec_bat_is_short_polling(battery)) + battery->polling_count++; + else + battery->polling_count = 1; /* initial value = 1 */ +} + +static void sec_bat_set_polling( + struct sec_battery_info *battery) +{ + unsigned long flags; + unsigned int polling_time_temp; + + polling_time_temp = sec_bat_get_polling_time(battery); + + dev_dbg(battery->dev, + "%s: Status:%s, Sleep:%s, Charging:%s, Short Poll:%s\n", + __func__, sec_bat_status_str[battery->status], + battery->polling_in_sleep ? "Yes" : "No", + (battery->charging_mode == + SEC_BATTERY_CHARGING_NONE) ? "No" : "Yes", + battery->polling_short ? "Yes" : "No"); + dev_dbg(battery->dev, + "%s: Polling time %d/%d sec.\n", __func__, + battery->polling_short ? + (polling_time_temp * battery->polling_count) : + polling_time_temp, battery->polling_time); + + /* To sync with log above, + * change polling count after log is displayed + */ + sec_bat_update_polling_count(battery); + + switch (battery->pdata->polling_type) { + case SEC_BATTERY_MONITOR_WORKQUEUE: + if (battery->pdata->monitor_initial_count) { + battery->pdata->monitor_initial_count--; + schedule_delayed_work(&battery->polling_work, HZ); + } else + schedule_delayed_work(&battery->polling_work, + polling_time_temp * HZ); + break; + case SEC_BATTERY_MONITOR_ALARM: + battery->last_poll_time = alarm_get_elapsed_realtime(); + local_irq_save(flags); + if (battery->pdata->monitor_initial_count) { + battery->pdata->monitor_initial_count--; + sec_bat_program_alarm(battery, 1); + } else + sec_bat_program_alarm(battery, polling_time_temp); + local_irq_restore(flags); + break; + case SEC_BATTERY_MONITOR_TIMER: + break; + default: + break; + } +} + +static void sec_bat_monitor_work( + struct work_struct *work) +{ + struct sec_battery_info *battery = + container_of(work, struct sec_battery_info, + monitor_work); + + sec_bat_get_battery_info(battery); + + /* 0. test mode */ + if (battery->test_activated) + goto continue_monitor; + + /* 1. battery check */ + if (!sec_bat_battery_cable_check(battery)) + goto continue_monitor; + + /* 2. voltage check */ + if (!sec_bat_voltage_check(battery)) + goto continue_monitor; + + if (sec_bat_is_short_polling(battery)) + goto continue_monitor; + + /* monitor every stage in first time after wakeup */ + if (battery->polling_in_sleep) + battery->polling_in_sleep = false; + + /* 3. time management */ + if (!sec_bat_time_management(battery)) + goto continue_monitor; + + /* 4. temperature check */ + if (!sec_bat_temperature_check(battery)) + goto continue_monitor; + + /* 5. full charging check */ + sec_bat_fullcharged_check(battery); + +continue_monitor: + dev_info(battery->dev, + "%s: Status(%s), Health(%s)\n", __func__, + sec_bat_status_str[battery->status], + sec_bat_health_str[battery->health]); + + battery->test_activated = false; + power_supply_changed(&battery->psy_bat); + + sec_bat_set_polling(battery); + + wake_unlock(&battery->monitor_wake_lock); + + return; +} + +static void sec_bat_cable_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, cable_work); + int cable_type; + + switch (battery->pdata->cable_source_type) { + case SEC_BATTERY_CABLE_SOURCE_CALLBACK: + cable_type = + battery->pdata->check_cable_callback(); + break; + case SEC_BATTERY_CABLE_SOURCE_ADC: + if (gpio_get_value_cansleep( + battery->pdata->bat_gpio_ta_nconnected) ^ + battery->pdata->bat_polarity_ta_nconnected) + cable_type = POWER_SUPPLY_TYPE_BATTERY; + else + cable_type = + sec_bat_get_charger_type_adc(battery); + break; + case SEC_BATTERY_CABLE_SOURCE_EXTERNAL: + dev_dbg(battery->dev, + "%s: Cable Type Defined (%d)\n", + __func__, battery->cable_type); + default: + /* make cable status changed forcefully */ + cable_type = SEC_SIZEOF_POWER_SUPPLY_TYPE; + break; + } + + if (battery->cable_type == cable_type) { + dev_dbg(battery->dev, + "%s: No need to change cable status\n", __func__); + goto end_of_cable_work; + } else { + if (battery->pdata->cable_source_type != + SEC_BATTERY_CABLE_SOURCE_EXTERNAL) { + if (cable_type < POWER_SUPPLY_TYPE_BATTERY || + cable_type >= SEC_SIZEOF_POWER_SUPPLY_TYPE) { + dev_err(battery->dev, + "%s: Invalid cable type\n", __func__); + goto end_of_cable_work; + } else + battery->cable_type = cable_type; + } + dev_dbg(battery->dev, "%s: Cable Changed (%d)\n", + __func__, battery->cable_type); + } + + if (battery->cable_type == POWER_SUPPLY_TYPE_BATTERY) { + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->status = POWER_SUPPLY_STATUS_DISCHARGING; + battery->health = POWER_SUPPLY_HEALTH_GOOD; + + if (sec_bat_set_charge(battery, false)) + goto end_of_cable_work; + + wake_lock_timeout(&battery->vbus_wake_lock, HZ * 5); + } else { + battery->charging_mode = SEC_BATTERY_CHARGING_NORMAL; + battery->status = POWER_SUPPLY_STATUS_CHARGING; + + if (sec_bat_set_charge(battery, true)) + goto end_of_cable_work; + + /* No need for wakelock in Alarm */ + if (battery->pdata->polling_type != SEC_BATTERY_MONITOR_ALARM) + wake_lock(&battery->vbus_wake_lock); + } + + battery->polling_count = 1; /* initial value = 1 */ + + battery->pdata->check_cable_result_callback(battery->cable_type); + + power_supply_changed(&battery->psy_ac); + power_supply_changed(&battery->psy_usb); + +end_of_cable_work: + wake_unlock(&battery->cable_wake_lock); +} + +ssize_t sec_bat_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = + container_of(psy, struct sec_battery_info, psy_bat); + const ptrdiff_t offset = attr - sec_battery_attrs; + int i = 0; + + switch (offset) { + case BATT_RESET_SOC: + break; + case BATT_READ_RAW_SOC: + break; + case BATT_READ_ADJ_SOC: + break; + case BATT_TYPE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + battery->pdata->vendor); + break; + case BATT_VFOCV: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->voltage_ocv); + break; + case BATT_VOL_ADC: + break; + case BATT_VOL_ADC_CAL: + break; + case BATT_VOL_AVER: + break; + case BATT_VOL_ADC_AVER: + break; + case BATT_TEMP_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->temp_adc); + break; + case BATT_TEMP_AVER: + break; + case BATT_TEMP_ADC_AVER: + break; + case BATT_VF_ADC: + break; + + case BATT_LP_CHARGING: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->pdata->is_lpm() ? 1 : 0); + break; + case SIOP_ACTIVATED: + break; + case BATT_CHARGING_SOURCE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->cable_type); + break; + case FG_REG_DUMP: + break; + case FG_RESET_CAP: + break; + case AUTH: + break; + case CHG_CURRENT_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->current_adc); + break; + case WC_ADC: + break; + case WC_STATUS: + break; + + case BATT_EVENT_2G_CALL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_2G_CALL) ? 1 : 0); + break; + case BATT_EVENT_3G_CALL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_3G_CALL) ? 1 : 0); + break; + case BATT_EVENT_MUSIC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_MUSIC) ? 1 : 0); + break; + case BATT_EVENT_VIDEO: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_VIDEO) ? 1 : 0); + break; + case BATT_EVENT_BROWSER: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_BROWSER) ? 1 : 0); + break; + case BATT_EVENT_HOTSPOT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_HOTSPOT) ? 1 : 0); + break; + case BATT_EVENT_CAMERA: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_CAMERA) ? 1 : 0); + break; + case BATT_EVENT_CAMCORDER: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_CAMCORDER) ? 1 : 0); + break; + case BATT_EVENT_DATA_CALL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_DATA_CALL) ? 1 : 0); + break; + case BATT_EVENT_WIFI: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_WIFI) ? 1 : 0); + break; + case BATT_EVENT_WIBRO: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_WIBRO) ? 1 : 0); + break; + case BATT_EVENT_LTE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_LTE) ? 1 : 0); + break; + case BATT_EVENT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->event); + break; + default: + i = -EINVAL; + } + + return i; +} + +ssize_t sec_bat_store_attrs( + struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = + container_of(psy, struct sec_battery_info, psy_bat); + const ptrdiff_t offset = attr - sec_battery_attrs; + int ret = 0; + int x = 0; + + switch (offset) { + case BATT_RESET_SOC: + /* Do NOT reset fuel gauge in charging mode */ + if (!battery->pdata->check_cable_callback()) { + union power_supply_propval value; + + value.intval = + SEC_FUELGAUGE_CAPACITY_TYPE_RESET; + psy_do_property("sec-fuelgauge", set, + POWER_SUPPLY_PROP_CAPACITY, value); + + /* update battery info */ + sec_bat_get_battery_info(battery); + } + ret = count; + break; + case BATT_READ_RAW_SOC: + break; + case BATT_READ_ADJ_SOC: + break; + case BATT_TYPE: + break; + case BATT_VFOCV: + break; + case BATT_VOL_ADC: + break; + case BATT_VOL_ADC_CAL: + break; + case BATT_VOL_AVER: + break; + case BATT_VOL_ADC_AVER: + break; + case BATT_TEMP_ADC: + break; + case BATT_TEMP_AVER: + break; + case BATT_TEMP_ADC_AVER: + break; + case BATT_VF_ADC: + break; + + case BATT_LP_CHARGING: + break; + case SIOP_ACTIVATED: + break; + case BATT_CHARGING_SOURCE: + break; + case FG_REG_DUMP: + break; + case FG_RESET_CAP: + break; + case AUTH: + break; + case CHG_CURRENT_ADC: + break; + case WC_ADC: + break; + case WC_STATUS: + break; + + case BATT_EVENT_2G_CALL: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_2G_CALL, x); + ret = count; + } + break; + case BATT_EVENT_3G_CALL: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_3G_CALL, x); + ret = count; + } + break; + case BATT_EVENT_MUSIC: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_MUSIC, x); + ret = count; + } + break; + case BATT_EVENT_VIDEO: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_VIDEO, x); + ret = count; + } + break; + case BATT_EVENT_BROWSER: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_BROWSER, x); + ret = count; + } + break; + case BATT_EVENT_HOTSPOT: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_HOTSPOT, x); + ret = count; + } + break; + case BATT_EVENT_CAMERA: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_CAMERA, x); + ret = count; + } + break; + case BATT_EVENT_CAMCORDER: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_CAMCORDER, x); + ret = count; + } + break; + case BATT_EVENT_DATA_CALL: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_DATA_CALL, x); + ret = count; + } + break; + case BATT_EVENT_WIFI: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_WIFI, x); + ret = count; + } + break; + case BATT_EVENT_WIBRO: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_WIBRO, x); + ret = count; + } + break; + case BATT_EVENT_LTE: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_LTE, x); + ret = count; + } + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int sec_bat_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(sec_battery_attrs); i++) { + rc = device_create_file(dev, &sec_battery_attrs[i]); + if (rc) + goto create_attrs_failed; + } + goto create_attrs_succeed; + +create_attrs_failed: + while (i--) + device_remove_file(dev, &sec_battery_attrs[i]); +create_attrs_succeed: + return rc; +} + +static int sec_bat_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_battery_info *battery = + container_of(psy, struct sec_battery_info, psy_bat); + + dev_dbg(battery->dev, + "%s: (%d,%d)\n", __func__, psp, val->intval); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if ((battery->pdata->full_check_type == + SEC_BATTERY_FULLCHARGED_CHGINT) && + (val->intval == POWER_SUPPLY_STATUS_FULL)) + sec_bat_do_fullcharged(battery); + battery->status = val->intval; + break; + case POWER_SUPPLY_PROP_HEALTH: + battery->health = val->intval; + switch (battery->health) { + /* OVP/UVLO from Interrupt */ + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + case POWER_SUPPLY_HEALTH_UNDERVOLTAGE: + sec_bat_ovp_uvlo_result(battery); + break; + } + break; + case POWER_SUPPLY_PROP_ONLINE: + /* cable is attached or detached */ + if (battery->pdata->cable_source_type == + SEC_BATTERY_CABLE_SOURCE_EXTERNAL) { + /* skip cable work if cable is NOT changed */ + if (battery->cable_type != val->intval) + battery->cable_type = val->intval; + else { + dev_dbg(battery->dev, + "%s: Cable is NOT Changed(%d)\n", + __func__, battery->cable_type); + /* Do NOT activate cable work for NOT changed */ + break; + } + } + wake_lock(&battery->cable_wake_lock); + queue_work(battery->monitor_wqueue, &battery->cable_work); + break; + case POWER_SUPPLY_PROP_CAPACITY: + battery->capacity = val->intval; + power_supply_changed(&battery->psy_bat); + break; + default: + return -EINVAL; + } + + battery->test_activated = true; + return 0; +} + +static int sec_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_battery_info *battery = + container_of(psy, struct sec_battery_info, psy_bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (battery->pdata->cable_check_type & + SEC_BATTERY_CABLE_CHECK_NOUSBCHARGE) { + switch (battery->cable_type) { + case POWER_SUPPLY_TYPE_USB: + case POWER_SUPPLY_TYPE_USB_DCP: + case POWER_SUPPLY_TYPE_USB_CDP: + case POWER_SUPPLY_TYPE_USB_ACA: + val->intval = + POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + } + val->intval = battery->status; + break; + /* charging mode (differ from power supply) */ + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = battery->charging_mode; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = battery->health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = battery->present; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = battery->cable_type; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = battery->pdata->technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + /* voltage value should be in uV */ + val->intval = battery->voltage_now * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* voltage value should be in uV */ + val->intval = battery->voltage_avg * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = battery->current_now; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = battery->current_avg; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = battery->capacity; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = battery->temperature; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + val->intval = battery->temper_amb; + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_battery_info *battery = + container_of(psy, struct sec_battery_info, psy_usb); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + /* Set enable=1 only if the USB charger is connected */ + val->intval = (battery->cable_type == POWER_SUPPLY_TYPE_USB); + + return 0; +} + +static int sec_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_battery_info *battery = + container_of(psy, struct sec_battery_info, psy_ac); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + /* Set enable=1 only if the AC charger is connected */ + val->intval = (battery->cable_type == POWER_SUPPLY_TYPE_MAINS); + + return 0; +} + +static irqreturn_t sec_bat_irq_thread(int irq, void *irq_data) +{ + struct sec_battery_info *battery = irq_data; + + if (battery->pdata->cable_check_type & + SEC_BATTERY_CABLE_CHECK_INT) { + wake_lock(&battery->cable_wake_lock); + queue_work(battery->monitor_wqueue, &battery->cable_work); + } + + if (battery->pdata->battery_check_type == + SEC_BATTERY_CHECK_INT) { + battery->present = battery->pdata->check_battery_callback(); + + wake_lock(&battery->monitor_wake_lock); + queue_work(battery->monitor_wqueue, &battery->monitor_work); + } + + return IRQ_HANDLED; +} + +static int __devinit sec_battery_probe(struct platform_device *pdev) +{ + sec_battery_platform_data_t *pdata = dev_get_platdata(&pdev->dev); + struct sec_battery_info *battery; + int ret = 0; + int i; + + dev_dbg(&pdev->dev, + "%s: SEC Battery Driver Loading\n", __func__); + + battery = kzalloc(sizeof(*battery), GFP_KERNEL); + if (!battery) + return -ENOMEM; + + platform_set_drvdata(pdev, battery); + + battery->dev = &pdev->dev; + battery->pdata = pdata; + + mutex_init(&battery->adclock); + dev_dbg(battery->dev, "%s: ADC init\n", __func__); + for (i = 0; i < SEC_BAT_ADC_CHANNEL_FULL_CHECK; i++) + adc_init(pdev, pdata, i); + + wake_lock_init(&battery->monitor_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-monitor"); + wake_lock_init(&battery->cable_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-cable"); + wake_lock_init(&battery->vbus_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-vbus"); + + /* initialization of battery info */ + battery->status = POWER_SUPPLY_STATUS_DISCHARGING; + battery->health = POWER_SUPPLY_HEALTH_GOOD; + battery->present = true; + + battery->polling_count = 1; /* initial value = 1 */ + battery->polling_time = pdata->polling_time[ + SEC_BATTERY_POLLING_TIME_DISCHARGING]; + battery->polling_in_sleep = false; + battery->polling_short = false; + + battery->check_count = 0; + battery->check_adc_count = 0; + battery->check_adc_value = 0; + + battery->charging_start_time = 0; + battery->charging_passed_time = 0; + battery->charging_next_time = 0; + + setup_timer(&battery->event_expired_timer, + sec_bat_event_expired_timer_func, (unsigned long)battery); + + battery->temp_high_threshold = + pdata->temp_high_threshold_normal; + battery->temp_high_recovery = + pdata->temp_high_recovery_normal; + battery->temp_low_recovery = + pdata->temp_low_recovery_normal; + battery->temp_low_threshold = + pdata->temp_low_threshold_normal; + + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->cable_type = POWER_SUPPLY_TYPE_BATTERY; + battery->test_activated = false; + + battery->psy_bat.name = "battery", + battery->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY, + battery->psy_bat.properties = sec_battery_props, + battery->psy_bat.num_properties = ARRAY_SIZE(sec_battery_props), + battery->psy_bat.get_property = sec_bat_get_property, + battery->psy_bat.set_property = sec_bat_set_property, + battery->psy_usb.name = "usb", + battery->psy_usb.type = POWER_SUPPLY_TYPE_USB, + battery->psy_usb.supplied_to = supply_list, + battery->psy_usb.num_supplicants = ARRAY_SIZE(supply_list), + battery->psy_usb.properties = sec_power_props, + battery->psy_usb.num_properties = ARRAY_SIZE(sec_power_props), + battery->psy_usb.get_property = sec_usb_get_property, + battery->psy_ac.name = "ac", + battery->psy_ac.type = POWER_SUPPLY_TYPE_MAINS, + battery->psy_ac.supplied_to = supply_list, + battery->psy_ac.num_supplicants = ARRAY_SIZE(supply_list), + battery->psy_ac.properties = sec_power_props, + battery->psy_ac.num_properties = ARRAY_SIZE(sec_power_props), + battery->psy_ac.get_property = sec_ac_get_property; + + /* init power supplier framework */ + ret = power_supply_register(&pdev->dev, &battery->psy_bat); + if (ret) { + dev_err(battery->dev, + "%s: Failed to Register psy_bat\n", __func__); + goto err_wake_lock; + } + + ret = power_supply_register(&pdev->dev, &battery->psy_usb); + if (ret) { + dev_err(battery->dev, + "%s: Failed to Register psy_usb\n", __func__); + goto err_supply_unreg_bat; + } + + ret = power_supply_register(&pdev->dev, &battery->psy_ac); + if (ret) { + dev_err(battery->dev, + "%s: Failed to Register psy_ac\n", __func__); + goto err_supply_unreg_usb; + } + + if (!battery->pdata->bat_gpio_init()) { + dev_err(battery->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_supply_unreg_ac; + } + + /* create work queue */ + battery->monitor_wqueue = + create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!battery->monitor_wqueue) { + dev_err(battery->dev, + "%s: Fail to Create Workqueue\n", __func__); + goto err_supply_unreg_ac; + } + INIT_WORK(&battery->monitor_work, sec_bat_monitor_work); + INIT_WORK(&battery->cable_work, sec_bat_cable_work); + + if (battery->pdata->bat_irq) { + ret = request_threaded_irq(battery->pdata->bat_irq, + NULL, sec_bat_irq_thread, + battery->pdata->bat_irq_attr, + "battery-irq", battery); + if (ret) { + dev_err(battery->dev, + "%s: Failed to Reqeust IRQ\n", __func__); + return ret; + } + + ret = enable_irq_wake(battery->pdata->bat_irq); + if (ret < 0) + dev_err(battery->dev, + "%s: Failed to Enable Wakeup Source(%d)\n", + __func__, ret); + } + + ret = sec_bat_create_attrs(battery->psy_bat.dev); + if (ret) { + dev_err(battery->dev, + "%s : Failed to create_attrs\n", __func__); + goto err_supply_unreg_ac; + } + + switch (pdata->polling_type) { + case SEC_BATTERY_MONITOR_WORKQUEUE: + INIT_DELAYED_WORK_DEFERRABLE(&battery->polling_work, + sec_bat_polling_work); + break; + case SEC_BATTERY_MONITOR_ALARM: + battery->last_poll_time = alarm_get_elapsed_realtime(); + alarm_init(&battery->polling_alarm, + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + sec_bat_alarm); + break; + default: + break; + } + + wake_lock(&battery->monitor_wake_lock); + queue_work(battery->monitor_wqueue, &battery->monitor_work); + + pdata->initial_check(); + + dev_dbg(battery->dev, + "%s: SEC Battery Driver Loaded\n", __func__); + return 0; + +err_supply_unreg_ac: + power_supply_unregister(&battery->psy_ac); +err_supply_unreg_usb: + power_supply_unregister(&battery->psy_usb); +err_supply_unreg_bat: + power_supply_unregister(&battery->psy_bat); +err_wake_lock: + wake_lock_destroy(&battery->monitor_wake_lock); + wake_lock_destroy(&battery->cable_wake_lock); + wake_lock_destroy(&battery->vbus_wake_lock); + mutex_destroy(&battery->adclock); + kfree(battery); + + return ret; +} + +static int __devexit sec_battery_remove(struct platform_device *pdev) +{ + struct sec_battery_info *battery = platform_get_drvdata(pdev); + int i; + + switch (battery->pdata->polling_type) { + case SEC_BATTERY_MONITOR_WORKQUEUE: + cancel_delayed_work(&battery->polling_work); + break; + case SEC_BATTERY_MONITOR_ALARM: + alarm_cancel(&battery->polling_alarm); + break; + default: + break; + } + + flush_workqueue(battery->monitor_wqueue); + destroy_workqueue(battery->monitor_wqueue); + wake_lock_destroy(&battery->monitor_wake_lock); + wake_lock_destroy(&battery->cable_wake_lock); + wake_lock_destroy(&battery->vbus_wake_lock); + + mutex_destroy(&battery->adclock); + for (i = 0; i < SEC_BAT_ADC_CHANNEL_FULL_CHECK; i++) + adc_exit(battery->pdata, i); + + power_supply_unregister(&battery->psy_ac); + power_supply_unregister(&battery->psy_usb); + power_supply_unregister(&battery->psy_bat); + + kfree(battery); + + return 0; +} + +static int sec_battery_prepare(struct device *dev) +{ + struct sec_battery_info *battery + = dev_get_drvdata(dev); + + cancel_work_sync(&battery->monitor_work); + + battery->polling_in_sleep = true; + + sec_bat_set_polling(battery); + + if (battery->pdata->polling_type == + SEC_BATTERY_MONITOR_WORKQUEUE) + cancel_delayed_work(&battery->polling_work); + + return 0; +} + +static int sec_battery_suspend(struct device *dev) +{ + return 0; +} + +static int sec_battery_resume(struct device *dev) +{ + return 0; +} + +static void sec_battery_complete(struct device *dev) +{ + struct sec_battery_info *battery + = dev_get_drvdata(dev); + + wake_lock(&battery->monitor_wake_lock); + queue_work(battery->monitor_wqueue, + &battery->monitor_work); + + return; +} + +static void sec_battery_shutdown(struct device *dev) +{ +} + +static const struct dev_pm_ops sec_battery_pm_ops = { + .prepare = sec_battery_prepare, + .suspend = sec_battery_suspend, + .resume = sec_battery_resume, + .complete = sec_battery_complete, +}; + +static struct platform_driver sec_battery_driver = { + .driver = { + .name = "sec-battery", + .owner = THIS_MODULE, + .pm = &sec_battery_pm_ops, + .shutdown = sec_battery_shutdown, + }, + .probe = sec_battery_probe, + .remove = __devexit_p(sec_battery_remove), +}; + +static int __init sec_battery_init(void) +{ + return platform_driver_register(&sec_battery_driver); +} + +static void __exit sec_battery_exit(void) +{ + platform_driver_unregister(&sec_battery_driver); +} + +late_initcall(sec_battery_init); +module_exit(sec_battery_exit); + +MODULE_DESCRIPTION("Samsung Battery Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); |