aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/battery/sec_battery.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/battery/sec_battery.c')
-rw-r--r--drivers/battery/sec_battery.c2199
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");