/* * sec_adc.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. */ #include "include/sec_adc.h" #define DEBUG struct adc_list { const char *name; struct iio_channel *channel; bool is_used; int prev_value; }; static struct adc_list batt_adc_list[SEC_BAT_ADC_CHANNEL_NUM] = { {.name = "adc-cable"}, {.name = "adc-bat"}, {.name = "adc-temp"}, {.name = "adc-temp-amb"}, {.name = "adc-full"}, {.name = "adc-volt"}, {.name = "adc-chg-temp"}, {.name = "adc-in-bat"}, {.name = "adc-dischg"}, {.name = "adc-dischg-ntc"}, {.name = "adc-wpc-temp"}, {.name = "adc-slave-chg-temp"}, {.name = "adc-usb-temp"}, {.name = "adc-sub-bat"}, {.name = "adc-blkt-temp"}, }; #ifdef CONFIG_SEC_EXT_THERMAL_MONITOR static struct sec_battery_info *local_battery; #endif /* CONFIG_SEC_EXT_THERMAL_MONITOR */ static void sec_bat_adc_ap_init(struct platform_device *pdev, struct sec_battery_info *battery) { int i = 0; struct iio_channel *temp_adc; for (i = 0; i < SEC_BAT_ADC_CHANNEL_NUM; i++) { temp_adc = iio_channel_get(&pdev->dev, batt_adc_list[i].name); batt_adc_list[i].channel = temp_adc; batt_adc_list[i].is_used = !IS_ERR_OR_NULL(temp_adc); } for (i = 0; i < SEC_BAT_ADC_CHANNEL_NUM; i++) pr_info("%s %s - %s\n", __func__, batt_adc_list[i].name, batt_adc_list[i].is_used ? "used" : "not used"); } static int sec_bat_adc_ap_read(struct sec_battery_info *battery, int channel) { int data = -1; int ret = 0; int retry_cnt = RETRY_CNT; if (batt_adc_list[channel].is_used) { do { ret = (batt_adc_list[channel].is_used) ? iio_read_channel_processed(batt_adc_list[channel].channel, &data) : 0; retry_cnt--; } while ((retry_cnt > 0) && (data < 0)); } if (retry_cnt <= 0) { pr_err("%s: Error in ADC\n", __func__); data = batt_adc_list[channel].prev_value; } else batt_adc_list[channel].prev_value = data; return data; } static void sec_bat_adc_ap_exit(void) { int i = 0; for (i = 0; i < SEC_BAT_ADC_CHANNEL_NUM; i++) { if (batt_adc_list[i].is_used) { iio_channel_release(batt_adc_list[i].channel); } } } static void sec_bat_adc_none_init(struct platform_device *pdev) { } static int sec_bat_adc_none_read(int channel) { return 0; } static void sec_bat_adc_none_exit(void) { } static void sec_bat_adc_ic_init(struct platform_device *pdev) { } static int sec_bat_adc_ic_read(int channel) { return 0; } static void sec_bat_adc_ic_exit(void) { } static int adc_read_type(struct sec_battery_info *battery, int channel) { int adc = 0; switch (battery->pdata->temp_adc_type) { case SEC_BATTERY_ADC_TYPE_NONE: adc = sec_bat_adc_none_read(channel); break; case SEC_BATTERY_ADC_TYPE_AP: adc = sec_bat_adc_ap_read(battery, channel); break; case SEC_BATTERY_ADC_TYPE_IC: adc = sec_bat_adc_ic_read(channel); break; case SEC_BATTERY_ADC_TYPE_NUM: break; default: break; } pr_debug("[%s] [%d] ADC = %d\n", __func__, channel, adc); return adc; } static void adc_init_type(struct platform_device *pdev, struct sec_battery_info *battery) { switch (battery->pdata->temp_adc_type) { case SEC_BATTERY_ADC_TYPE_NONE: sec_bat_adc_none_init(pdev); break; case SEC_BATTERY_ADC_TYPE_AP: sec_bat_adc_ap_init(pdev, battery); break; case SEC_BATTERY_ADC_TYPE_IC: sec_bat_adc_ic_init(pdev); break; case SEC_BATTERY_ADC_TYPE_NUM: break; default: break; } } static void adc_exit_type(struct sec_battery_info *battery) { switch (battery->pdata->temp_adc_type) { case SEC_BATTERY_ADC_TYPE_NONE: sec_bat_adc_none_exit(); break; case SEC_BATTERY_ADC_TYPE_AP: sec_bat_adc_ap_exit(); break; case SEC_BATTERY_ADC_TYPE_IC: sec_bat_adc_ic_exit(); break; case SEC_BATTERY_ADC_TYPE_NUM: break; default: break; } } int sec_bat_get_adc_data(struct sec_battery_info *battery, int adc_ch, int count) { int adc_data = 0; int adc_max = 0; int adc_min = 0xFFFF; int adc_total = 0; int i = 0; if (count < 3) count = 3; for (i = 0; i < count; i++) { mutex_lock(&battery->adclock); #ifdef CONFIG_OF adc_data = adc_read_type(battery, adc_ch); #else adc_data = adc_read_type(battery->pdata, adc_ch); #endif mutex_unlock(&battery->adclock); 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); } 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 = SEC_BATTERY_CABLE_USB; int adc = 0; int i = 0; /* Do NOT check cable type when cable_switch_check() returns false * and keep current cable type */ if (battery->pdata->cable_switch_check && !battery->pdata->cable_switch_check()) return battery->cable_type; adc = sec_bat_get_adc_data(battery, SEC_BAT_ADC_CHANNEL_CABLE_CHECK, battery->pdata->adc_check_count); /* Do NOT check cable type when cable_switch_normal() returns false * and keep current cable type */ if (battery->pdata->cable_switch_normal && !battery->pdata->cable_switch_normal()) return battery->cable_type; for (i = 0; i < SEC_BATTERY_CABLE_MAX; i++) if ((adc > battery->pdata->cable_adc_value[i].min) && (adc < battery->pdata->cable_adc_value[i].max)) break; if (i >= SEC_BATTERY_CABLE_MAX) 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; } bool sec_bat_get_value_by_adc( struct sec_battery_info *battery, enum sec_battery_adc_channel channel, union power_supply_propval *value, int check_type) { int temp = 0; int temp_adc; int low = 0; int high = 0; int mid = 0; const sec_bat_adc_table_data_t *temp_adc_table = {0 , }; unsigned int temp_adc_table_size = 0; if (check_type == SEC_BATTERY_TEMP_CHECK_FAKE) { value->intval = 300; return true; } temp_adc = sec_bat_get_adc_data(battery, channel, battery->pdata->adc_check_count); if (temp_adc < 0) return false; switch (channel) { case SEC_BAT_ADC_CHANNEL_TEMP: temp_adc_table = battery->pdata->temp_adc_table; temp_adc_table_size = battery->pdata->temp_adc_table_size; battery->temp_adc = temp_adc; break; case 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; battery->temp_ambient_adc = temp_adc; break; case SEC_BAT_ADC_CHANNEL_USB_TEMP: temp_adc_table = battery->pdata->usb_temp_adc_table; temp_adc_table_size = battery->pdata->usb_temp_adc_table_size; battery->usb_temp_adc = temp_adc; break; case SEC_BAT_ADC_CHANNEL_CHG_TEMP: temp_adc_table = battery->pdata->chg_temp_adc_table; temp_adc_table_size = battery->pdata->chg_temp_adc_table_size; battery->chg_temp_adc = temp_adc; break; case SEC_BAT_ADC_CHANNEL_WPC_TEMP: temp_adc_table = battery->pdata->wpc_temp_adc_table; temp_adc_table_size = battery->pdata->wpc_temp_adc_table_size; battery->wpc_temp_adc = temp_adc; break; case SEC_BAT_ADC_CHANNEL_SLAVE_CHG_TEMP: temp_adc_table = battery->pdata->slave_chg_temp_adc_table; temp_adc_table_size = battery->pdata->slave_chg_temp_adc_table_size; battery->slave_chg_temp_adc = temp_adc; break; case SEC_BAT_ADC_CHANNEL_SUB_BAT_TEMP: temp_adc_table = battery->pdata->sub_bat_temp_adc_table; temp_adc_table_size = battery->pdata->sub_bat_temp_adc_table_size; battery->sub_bat_temp_adc = temp_adc; break; case SEC_BAT_ADC_CHANNEL_BLKT_TEMP: temp_adc_table = battery->pdata->blkt_temp_adc_table; temp_adc_table_size = battery->pdata->blkt_temp_adc_table_size; battery->blkt_temp_adc = temp_adc; break; default: dev_err(battery->dev, "%s: Invalid Property\n", __func__); return false; } if (temp_adc_table[0].adc >= temp_adc) { temp = temp_adc_table[0].data; goto temp_by_adc_goto; } else if (temp_adc_table[temp_adc_table_size-1].adc <= temp_adc) { temp = temp_adc_table[temp_adc_table_size-1].data; goto temp_by_adc_goto; } 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 { temp = temp_adc_table[mid].data; goto temp_by_adc_goto; } } temp = temp_adc_table[high].data; temp += ((temp_adc_table[low].data - temp_adc_table[high].data) * (temp_adc - temp_adc_table[high].adc)) / (temp_adc_table[low].adc - temp_adc_table[high].adc); temp_by_adc_goto: value->intval = temp; dev_dbg(battery->dev, "%s:[%d] Temp(%d), Temp-ADC(%d)\n", __func__,channel, temp, temp_adc); return true; } #ifdef CONFIG_SEC_EXT_THERMAL_MONITOR int sec_bat_convert_adc_to_temp(unsigned int adc_ch, int temp_adc) { enum sec_battery_adc_channel channel = 0; int temp = 25000; int low = 0; int high = 0; int mid = 0; const sec_bat_adc_table_data_t *temp_adc_table = {0 , }; unsigned int temp_adc_table_size = 0; if(!local_battery) { pr_info("%s: battery data is not ready yet\n", __func__); goto temp_to_adc_goto; } if (adc_ch == 0x4d) channel = SEC_BAT_ADC_CHANNEL_USB_TEMP; else if (adc_ch == 0x52) channel = SEC_BAT_ADC_CHANNEL_TEMP; else goto temp_to_adc_goto; switch (channel) { case SEC_BAT_ADC_CHANNEL_USB_TEMP: temp_adc_table = local_battery->pdata->usb_temp_adc_table; temp_adc_table_size = local_battery->pdata->usb_temp_adc_table_size; break; case SEC_BAT_ADC_CHANNEL_TEMP: temp_adc_table = local_battery->pdata->temp_adc_table; temp_adc_table_size = local_battery->pdata->temp_adc_table_size; break; default: dev_err(local_battery->dev, "%s: Invalid Property\n", __func__); goto temp_to_adc_goto; } if (temp_adc_table[0].adc >= temp_adc) { temp = temp_adc_table[0].data; goto temp_to_adc_goto; } else if (temp_adc_table[temp_adc_table_size-1].adc <= temp_adc) { temp = temp_adc_table[temp_adc_table_size-1].data; goto temp_to_adc_goto; } 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 { temp = temp_adc_table[mid].data; goto temp_to_adc_goto; } } temp = temp_adc_table[high].data; temp += ((temp_adc_table[low].data - temp_adc_table[high].data) * (temp_adc - temp_adc_table[high].adc)) / (temp_adc_table[low].adc - temp_adc_table[high].adc); temp_to_adc_goto: return (temp == 25000 ? temp : temp * 100); } EXPORT_SYMBOL(sec_bat_convert_adc_to_temp); int sec_bat_get_thr_voltage(unsigned int adc_ch, int temp) { enum sec_battery_adc_channel channel = 0; int volt = 0; int low = 0; int high = 0; int mid = 0; const sec_bat_adc_table_data_t *temp_adc_table = {0 , }; unsigned int temp_adc_table_size = 0; if(!local_battery) { pr_info("%s: battery data is not ready yet\n", __func__); goto get_thr_voltage_goto; } if (adc_ch == 0x4d) channel = SEC_BAT_ADC_CHANNEL_USB_TEMP; else if (adc_ch == 0x52) channel = SEC_BAT_ADC_CHANNEL_TEMP; else goto get_thr_voltage_goto; switch (channel) { case SEC_BAT_ADC_CHANNEL_USB_TEMP: temp_adc_table = local_battery->pdata->usb_temp_adc_table; temp_adc_table_size = local_battery->pdata->usb_temp_adc_table_size; break; case SEC_BAT_ADC_CHANNEL_TEMP: temp_adc_table = local_battery->pdata->temp_adc_table; temp_adc_table_size = local_battery->pdata->temp_adc_table_size; break; default: dev_err(local_battery->dev, "%s: Invalid Property\n", __func__); goto get_thr_voltage_goto; } if (temp > 900 || temp < -200) { dev_err(local_battery->dev, "%s: unsupported temperature\n", __func__); goto get_thr_voltage_goto; } high = temp_adc_table_size - 1; while (low <= high) { mid = (low + high) / 2; if (temp_adc_table[mid].data < temp) high = mid - 1; else if (temp_adc_table[mid].data > temp) low = mid + 1; else { volt = temp_adc_table[mid].adc / 1000; goto get_thr_voltage_goto; } } volt = temp_adc_table[mid].adc / 1000; get_thr_voltage_goto: return volt; } EXPORT_SYMBOL(sec_bat_get_thr_voltage); #endif /* CONFIG_SEC_EXT_THERMAL_MONITOR */ int sec_bat_get_inbat_vol_by_adc(struct sec_battery_info *battery) { int inbat = 0; int inbat_adc; int low = 0; int high = 0; int mid = 0; const sec_bat_adc_table_data_t *inbat_adc_table; unsigned int inbat_adc_table_size; if (!battery->pdata->inbat_adc_table) { dev_err(battery->dev, "%s: not designed to read in-bat voltage\n", __func__); return -1; } inbat_adc_table = battery->pdata->inbat_adc_table; inbat_adc_table_size = battery->pdata->inbat_adc_table_size; inbat_adc = sec_bat_get_adc_data(battery, SEC_BAT_ADC_CHANNEL_INBAT_VOLTAGE, battery->pdata->adc_check_count); if (inbat_adc <= 0) return inbat_adc; battery->inbat_adc = inbat_adc; if (inbat_adc_table[0].adc <= inbat_adc) { inbat = inbat_adc_table[0].data; goto inbat_by_adc_goto; } else if (inbat_adc_table[inbat_adc_table_size-1].adc >= inbat_adc) { inbat = inbat_adc_table[inbat_adc_table_size-1].data; goto inbat_by_adc_goto; } high = inbat_adc_table_size - 1; while (low <= high) { mid = (low + high) / 2; if (inbat_adc_table[mid].adc < inbat_adc) high = mid - 1; else if (inbat_adc_table[mid].adc > inbat_adc) low = mid + 1; else { inbat = inbat_adc_table[mid].data; goto inbat_by_adc_goto; } } inbat = inbat_adc_table[high].data; inbat += ((inbat_adc_table[low].data - inbat_adc_table[high].data) * (inbat_adc - inbat_adc_table[high].adc)) / (inbat_adc_table[low].adc - inbat_adc_table[high].adc); if (inbat < 0) inbat = 0; inbat_by_adc_goto: dev_info(battery->dev, "%s: inbat(%d), inbat-ADC(%d)\n", __func__, inbat, inbat_adc); return inbat; } bool sec_bat_check_vf_adc(struct sec_battery_info *battery) { int adc = 0; 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 { dev_info(battery->dev, "%s: adc (%d)\n", __func__, battery->check_adc_value); return false; } } #if defined(CONFIG_DIRECT_CHARGING) int sec_bat_get_direct_chg_temp_adc(struct sec_battery_info *battery, int adc_data, int count) { int temp = 0; int temp_adc; int low = 0; int high = 0; int mid = 0; const sec_bat_adc_table_data_t *temp_adc_table = {0 , }; unsigned int temp_adc_table_size = 0; temp_adc = adc_data; if (temp_adc < 0) return 0; temp_adc_table = battery->pdata->dchg_temp_adc_table; temp_adc_table_size = battery->pdata->dchg_temp_adc_table_size; battery->dchg_temp_adc = temp_adc; if (temp_adc_table[0].adc >= temp_adc) { temp = temp_adc_table[0].data; goto direct_chg_temp_goto; } else if (temp_adc_table[temp_adc_table_size-1].adc <= temp_adc) { temp = temp_adc_table[temp_adc_table_size-1].data; goto direct_chg_temp_goto; } 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 { temp = temp_adc_table[mid].data; goto direct_chg_temp_goto; } } temp = temp_adc_table[high].data; temp += ((temp_adc_table[low].data - temp_adc_table[high].data) * (temp_adc - temp_adc_table[high].adc)) / (temp_adc_table[low].adc - temp_adc_table[high].adc); direct_chg_temp_goto: dev_info(battery->dev, "%s: temp(%d), direct-chg-temp-ADC(%d)\n", __func__, temp, temp_adc); return temp; } #endif void adc_init(struct platform_device *pdev, struct sec_battery_info *battery) { adc_init_type(pdev, battery); #ifdef CONFIG_SEC_EXT_THERMAL_MONITOR local_battery = battery; #endif /* CONFIG_SEC_EXT_THERMAL_MONITOR */ } void adc_exit(struct sec_battery_info *battery) { adc_exit_type(battery); }