diff options
| author | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
| commit | 8e4bff4cab0ddac6060645b0715210484d02ff40 (patch) | |
| tree | 3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/battery | |
Diffstat (limited to 'drivers/battery')
| -rwxr-xr-x | drivers/battery/Kconfig | 412 | ||||
| -rw-r--r-- | drivers/battery/Makefile | 32 | ||||
| -rwxr-xr-x | drivers/battery/sec_adc.c | 180 | ||||
| -rwxr-xr-x | drivers/battery/sec_battery.c | 3955 | ||||
| -rwxr-xr-x | drivers/battery/sec_charger.c | 745 | ||||
| -rw-r--r-- | drivers/battery/sec_dt_init.c | 1154 | ||||
| -rwxr-xr-x | drivers/battery/sec_fuelgauge.c | 781 | ||||
| -rw-r--r-- | drivers/battery/sm5414_charger.c | 666 | ||||
| -rw-r--r-- | drivers/battery/sm5701_charger.c | 805 | ||||
| -rwxr-xr-x | drivers/battery/smb328_charger.c | 808 | ||||
| -rwxr-xr-x | drivers/battery/smb358_charger.c | 2022 | ||||
| -rwxr-xr-x | drivers/battery/sprd27x3_charger4samsung.c | 960 | ||||
| -rwxr-xr-x | drivers/battery/sprd27x3_fuelgauge4samsung.c | 357 | ||||
| -rw-r--r-- | drivers/battery/sprd_battery.h | 133 |
14 files changed, 13010 insertions, 0 deletions
diff --git a/drivers/battery/Kconfig b/drivers/battery/Kconfig new file mode 100755 index 00000000..de52f796 --- /dev/null +++ b/drivers/battery/Kconfig @@ -0,0 +1,412 @@ + +config BATTERY_SAMSUNG + tristate "samsung battery driver" + help + Say Y to include support for samsung battery driver + This battery driver integrated all battery-related functions + To see battery-related functions, + refer to sec_charging_common.h + +config SAMSUNG_LPM_MODE + bool "Off charging mode support in sec battery driver" + default n + help + Say Y to include support for sec off charging support + This value defiend at bootloader. + Before enable this feature, + implement power off charging in the bootloader. + +config SAMSUNG_BATTERY_ENG_TEST + bool "enable ENG mode for battery test" + default n + help + Say Y to include support for battery test + enable this feature only ENG mode + this featuren must disabled user binary + stability test etc.. + +config STBC_SAMSUNG + tristate "sec stbc driver" + default n + help + Say Y to include support + for fuelgauge driver with mfd. + This driver source code implemented + fuelgauge driver with mfd. + +# Fuel Gauge + +config FUELGAUGE_MFD + tristate "use mfd driver for fuelgauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for fuelgauge driver with mfd. + This driver source code implemented + fuelgauge driver with mfd. + +config FUELGAUGE_DUMMY + tristate "dummy fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for dummy fuel gauge driver. + This driver source code implemented + skeleton source code for fuel gauge functions. + +config FUELGAUGE_ADC + tristate "ADC fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for ADC gauge driver. + This driver source code can be used for + fuel-gauge that is made of ADC. + +config FUELGAUGE_MAX17042 + tristate "MAX17042 fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for MAXIM MAX17042 fuel gauge driver. + This fuel-gauge can be used in voltage-tracking mode + or coulomb-counting mode. + +config FUELGAUGE_MAX17042_VOLTAGE_TRACKING + tristate "use MAX17042 fuel gauge only as voltage tracking" + default n + depends on FUELGAUGE_MAX17042 + help + Say Y to use MAX17042 fuel gauge + only as voltage tracking. + This mode is for target that consumes low current + like smart-phone. + +config FUELGAUGE_MAX17042_COULOMB_COUNTING + tristate "use MAX17042 fuel gauge as coulomb counting (including voltage tracking)" + default n + depends on FUELGAUGE_MAX17042 + help + Say Y to use MAX17042 fuel gauge + as coulomb counting (including voltage tracking). + This mode is for target that consumes high current + like tablet. + +config FUELGAUGE_MAX17048 + tristate "MAX17048 fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for MAXIM MAX17048 fuel gauge driver. + This fuel-gauge can be used + only in voltage-tracking mode. + +config FUELGAUGE_MAX17050 + tristate "MAX17050 fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for MAXIM MAX17047 or MAX17050 fuel gauge driver. + This fuel-gauge can be used in voltage-tracking mode + or coulomb-counting mode. + +config FUELGAUGE_MAX17050_VOLTAGE_TRACKING + tristate "use MAX17050 fuel gauge only as voltage tracking" + default n + depends on FUELGAUGE_MAX17050 + help + Say Y to use MAX17050 fuel gauge + only as voltage tracking. + This mode is for target that consumes low current + like smart-phone. + +config FUELGAUGE_MAX17050_COULOMB_COUNTING + tristate "use MAX17050 fuel gauge as coulomb counting (including voltage tracking)" + default n + depends on FUELGAUGE_MAX17050 + help + Say Y to use MAX17050 fuel gauge + as coulomb counting (including voltage tracking). + This mode is for target that consumes high current + like tablet. + +config FUELGAUGE_STC3115 + tristate "STC3115 fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for STC3115 fuel gauge driver. + +config FUELGAUGE_STC3117 + tristate "STC3117 fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for STC3117 fuel gauge driver. + +config FUELGAUGE_PM8917 + tristate "PM8917 fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for PM8917 fuel gauge driver. + This driver source code implemented + all functions for PM8917 fuel gauge. + +config FUELGAUGE_STC3115 + tristate "STC3115 fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for STC3115 fuel gauge driver. + This driver source code implemented + all functions for STC3115 fuel gauge. + +config FUELGAUGE_SPRD2713 + tristate "SPRD2713 fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for SPRD2713 fuel gauge driver. + This driver source code implemented + all functions for SPRD2713 fuel gauge. + +# Charger + +config CHARGER_MFD + tristate "use mfd driver for charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for charger driver with mfd. + This driver source code implemented + charger driver with mfd. + +config CHARGER_SPRD2713 + tristate "sprd2713 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for Summit sprd2713 charger driver. + This driver source code implemented + all functions for sprd2713 charger. + +config CHARGER_DUMMY + tristate "dummy charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for dummy charger driver. + This driver source code implemented + skeleton source code for charger functions. + +config CHARGER_MAX8903 + tristate "MAX8903 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for MAXIM MAX8903 charger driver. + This driver source code implemented + all functions for MAX8903 charger. + +config CHARGER_SMB328 + tristate "SMB328 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for Summit SMB328 charger driver. + This driver source code implemented + all functions for SMB328 charger. + +config CHARGER_SMB347 + tristate "SMB347 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for Summit SMB347 charger driver. + This driver source code implemented + all functions for SMB347 charger. + +config CHARGER_SMB358 + tristate "SMB358 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for SMB358 charger driver. + This driver source code implemented + all functions for SMB358 charger. + +config CHARGER_BQ24157 + tristate "BQ24157 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for TI BQ24157 charger driver. + This driver source code implemented + all functions for BQ24157 charger. + +config CHARGER_BQ24190 + tristate "BQ24190 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for TI BQ24190 charger driver. + This driver source code implemented + all functions for BQ24190 charger. + +config CHARGER_BQ24191 + tristate "BQ24191 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for TI BQ24191 charger driver. + This driver source code implemented + all functions for BQ24191 charger. + +config CHARGER_BQ24260 + tristate "BQ24260 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for TI BQ24260 charger driver. + This driver source code implemented + all functions for BQ24260 charger. + +config CHARGER_NCP1851 + tristate "NCP1851 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for NCP1851 charger driver. + This driver source code implemented + all functions for NCP1851 charger. + +config CHARGER_TSU8111 + tristate "TSU8111 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for TSU8111 charger driver. + This driver source code implemented + all functions for TSU8111 charger. + +config CHARGER_PM8917 + tristate "PM8917 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for PM8917 charger driver. + This driver source code implemented + all functions for PM8917 charger. + +config CHARGER_MAX14577 + tristate "MAX14577 charger driver" + default n + depends on BATTERY_SAMSUNG + depends on CHARGER_MFD + help + Say Y to include support + for MAX14577 charger driver. + This driver source code implemented + all functions for MAX14577 charger. + +config CHARGER_SM5701 + tristate "SM5701 charger driver" + depends on MFD_SM5701 && I2C + default n + help + Say Y to include support + for SiliconMitus SM5701 charger driver.. + +config CHARGER_SM5414 + tristate "SM5414 charger driver" + default n + help + Say Y to include support + for SM5414 charger driver. + This driver source code implemented + all functions for SMB5414 charger. + +config CHARGER_SM5418 + tristate "SM5418 charger driver" + default n + help + Say Y to include support + for SM5418 charger driver. + This driver source code implemented + all functions for SMB5418 charger. + +# IF PMIC + +config CHARGER_MAX77693 + tristate "MAX77693 battery charger support" + depends on MFD_MAX77693 && I2C + help + Say Y here to enable support for the MAX77693 charger + MAX77693 incluse muic, pmic, haptic, led, + flash driver. + You have to define MFD_MAX77693 + +config CHARGER_MAX77803 + tristate "MAX77803 battery charger support" + depends on MFD_MAX77693 && I2C + help + Say Y here to enable support for the MAX77803 charger + MAX77803 incluse muic, pmic, haptic, led, + flash driver. + You have to define MFD_MAX77693 + +config CHARGER_MAX77XXX + tristate "MAX77693 or MAX77803 battery charger support" + depends on MFD_MAX77693 && I2C + help + Say Y here to enable support for the MAX77803 charger + MAX77803 incluse muic, pmic, haptic, led, + flash driver. + You have to define MFD_MAX77693 + +config FUELGAUGE_SPRD4SAMSUNG27X3 + tristate "SPRD27X3 fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for SPRD27X3 fuel gauge driver. + This driver source code implemented + all functions for SPRD27X3 fuel gauge. + +config CHARGER_SPRD4SAMSUNG27X3 + tristate "sprd27X3 charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y to include support + for Summit sprd27X3 charger driver. + This driver source code implemented + all functions for sprd27X3 charger. + diff --git a/drivers/battery/Makefile b/drivers/battery/Makefile new file mode 100644 index 00000000..2aa6be0a --- /dev/null +++ b/drivers/battery/Makefile @@ -0,0 +1,32 @@ +obj-$(CONFIG_BATTERY_SAMSUNG) += sec_battery.o +obj-$(CONFIG_STBC_SAMSUNG) += stbc_charger.o\ + stbc_fuelgauge.o +obj-$(CONFIG_OF) += sec_adc.o \ + sec_dt_init.o + +obj-$(CONFIG_FUELGAUGE_ADC) += adc_fuelgauge.o +obj-$(CONFIG_FUELGAUGE_MAX17042) += max17042_fuelgauge.o +obj-$(CONFIG_FUELGAUGE_MAX17048) += max17048_fuelgauge.o +obj-$(CONFIG_FUELGAUGE_MAX17050) += max17050_fuelgauge.o +obj-$(CONFIG_FUELGAUGE_STC3115) += stc3115_fuelgauge.o +obj-$(CONFIG_FUELGAUGE_STC3117) += stc3117_fuelgauge.o sec_fuelgauge.o + +obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o +obj-$(CONFIG_CHARGER_SMB328) += smb328_charger.o +obj-$(CONFIG_CHARGER_SMB347) += smb347_charger.o +obj-$(CONFIG_CHARGER_SMB358) += smb358_charger.o +obj-$(CONFIG_CHARGER_BQ24157) += bq24157_charger.o +obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o +obj-$(CONFIG_CHARGER_BQ24191) += bq24190_charger.o +obj-$(CONFIG_CHARGER_BQ24260) += bq24260_charger.o +obj-$(CONFIG_CHARGER_NCP1851) += ncp1851_charger.o +obj-$(CONFIG_CHARGER_TSU8111) += tsu8111_charger.o +obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o +# For IF PMIC, sec_charger.o should be erased +obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o + +obj-$(CONFIG_CHARGER_SM5701) += sm5701_charger.o sec_dt_init.o +obj-$(CONFIG_FUELGAUGE_SPRD4SAMSUNG27X3) += sprd27x3_fuelgauge4samsung.o sec_fuelgauge.o ../power/sprd_2713_fgu.o +obj-$(CONFIG_CHARGER_SPRD4SAMSUNG27X3) += sprd27x3_charger4samsung.o sec_charger.o +obj-$(CONFIG_CHARGER_SM5414) += sm5414_charger.o sec_charger.o sec_dt_init.o +obj-$(CONFIG_CHARGER_SMB328) += smb328_charger.o diff --git a/drivers/battery/sec_adc.c b/drivers/battery/sec_adc.c new file mode 100755 index 00000000..c9f845f3 --- /dev/null +++ b/drivers/battery/sec_adc.c @@ -0,0 +1,180 @@ +/* + * 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 <linux/battery/sec_adc.h> +#include <soc/sprd/adc.h> + +#define ADC_CHANNEL_TEMP 1 + +static struct iio_channel *temp_adc; + +static void sec_bat_adc_ap_init(struct platform_device *pdev) +{ + /* + temp_adc = iio_channel_get_all(&pdev->dev); + */ + return true; +} + +static int sec_bat_adc_ap_read(int channel) +{ + int data; + int ret = 0; + + data = sci_adc_get_value(ADC_CHANNEL_TEMP, false); + pr_info("read channel [%d]\n", data); + /* + int data = -1; + int ret = 0; + + switch (channel) + { + case SEC_BAT_ADC_CHANNEL_CABLE_CHECK: + case SEC_BAT_ADC_CHANNEL_BAT_CHECK: + break; + case SEC_BAT_ADC_CHANNEL_TEMP: + case SEC_BAT_ADC_CHANNEL_TEMP_AMBIENT: + data = sci_adc_get_value(ADC_CHANNEL_1,0); + if (ret < 0) + pr_info("read channel error[%d]\n", ret); + else + pr_debug("TEMP ADC(%d)\n", data); + break; + case SEC_BAT_ADC_CHANNEL_FULL_CHECK: + case SEC_BAT_ADC_CHANNEL_VOLTAGE_NOW: + case SEC_BAT_ADC_CHANNEL_NUM: + break; + default: + break; + } + */ + return data; +} + +static void sec_bat_adc_ap_exit(void) +{ + /* + iio_channel_release(temp_adc); + */ + return true; +} + +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(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; + } + 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); + 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 adc_read(struct sec_battery_info *battery, int channel) +{ + int adc = 0; + + adc = adc_read_type(battery, channel); + + dev_dbg(battery->dev, "[%s]adc = %d\n", __func__, adc); + + return adc; +} + +void adc_init(struct platform_device *pdev, struct sec_battery_info *battery) +{ + adc_init_type(pdev, battery); +} + +void adc_exit(struct sec_battery_info *battery) +{ + adc_exit_type(battery); +} + diff --git a/drivers/battery/sec_battery.c b/drivers/battery/sec_battery.c new file mode 100755 index 00000000..83b21e6d --- /dev/null +++ b/drivers/battery/sec_battery.c @@ -0,0 +1,3955 @@ +/* + * 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. + */ + + +#include <linux/battery/sec_battery.h> +#if defined(CONFIG_MACH_CORSICA_VE) || defined(CONFIG_MACH_VIVALTO) +extern int get_hw_rev(); +#endif +//extern int sprdbat_creat_caliberate_attr(struct device *dev); + +extern void sec_bat_initial_check(void); + +static struct device_attribute sec_battery_attrs[] = { + SEC_BATTERY_ATTR(batt_reset_soc), + SEC_BATTERY_ATTR(batt_read_raw_soc), + SEC_BATTERY_ATTR(batt_read_adj_soc), + SEC_BATTERY_ATTR(batt_type), + SEC_BATTERY_ATTR(batt_vfocv), + SEC_BATTERY_ATTR(batt_vol_adc), + SEC_BATTERY_ATTR(batt_vol_adc_cal), + SEC_BATTERY_ATTR(batt_vol_aver), + SEC_BATTERY_ATTR(batt_vol_adc_aver), + SEC_BATTERY_ATTR(batt_current_ua_now), + SEC_BATTERY_ATTR(batt_current_ua_avg), + SEC_BATTERY_ATTR(batt_temp), + SEC_BATTERY_ATTR(batt_temp_adc), + SEC_BATTERY_ATTR(batt_temp_aver), + SEC_BATTERY_ATTR(batt_temp_adc_aver), + SEC_BATTERY_ATTR(batt_vf_adc), + SEC_BATTERY_ATTR(batt_slate_mode), + + SEC_BATTERY_ATTR(batt_lp_charging), + SEC_BATTERY_ATTR(siop_activated), + SEC_BATTERY_ATTR(siop_level), + SEC_BATTERY_ATTR(batt_charging_source), + SEC_BATTERY_ATTR(fg_reg_dump), + SEC_BATTERY_ATTR(fg_reset_cap), + SEC_BATTERY_ATTR(fg_capacity), + SEC_BATTERY_ATTR(auth), + SEC_BATTERY_ATTR(chg_current_adc), + SEC_BATTERY_ATTR(wc_adc), + SEC_BATTERY_ATTR(wc_status), + SEC_BATTERY_ATTR(wc_enable), + SEC_BATTERY_ATTR(factory_mode), + SEC_BATTERY_ATTR(update), + SEC_BATTERY_ATTR(test_mode), + + SEC_BATTERY_ATTR(call), + SEC_BATTERY_ATTR(2g_call), + SEC_BATTERY_ATTR(talk_gsm), + SEC_BATTERY_ATTR(3g_call), + SEC_BATTERY_ATTR(talk_wcdma), + SEC_BATTERY_ATTR(music), + SEC_BATTERY_ATTR(video), + SEC_BATTERY_ATTR(browser), + SEC_BATTERY_ATTR(hotspot), + SEC_BATTERY_ATTR(camera), + SEC_BATTERY_ATTR(camcorger), + SEC_BATTERY_ATTR(data_call), + SEC_BATTERY_ATTR(wifi), + SEC_BATTERY_ATTR(wibro), + SEC_BATTERY_ATTR(lte), + SEC_BATTERY_ATTR(lcd), + SEC_BATTERY_ATTR(gps), + SEC_BATTERY_ATTR(event), +#if defined(CONFIG_SAMSUNG_BATTERY_ENG_TEST) + SEC_BATTERY_ATTR(test_charge_current), + SEC_BATTERY_ATTR(set_stability_test), +#endif +}; + +static enum power_supply_property sec_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_AMBIENT, +}; + +static enum power_supply_property sec_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property sec_ps_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *supply_list[] = { + "battery", +}; + +char *sec_bat_charging_mode_str[] = { + "None", + "Normal", + "Additional", + "Re-Charging", + "ABS" +}; + +char *sec_bat_status_str[] = { + "Unknown", + "Charging", + "Discharging", + "Not-charging", + "Full" +}; + +char *sec_bat_health_str[] = { + "Unknown", + "Good", + "Overheat", + "Warm", + "Dead", + "OverVoltage", + "UnspecFailure", + "Cold", + "Cool", + "Watchdog timer expier", + "Safety timer expier", + "UnderVoltage", + "Overheatlimit" +}; + +static int sec_bat_set_charge( + struct sec_battery_info *battery, + bool enable) +{ + union power_supply_propval val; + + struct timespec ts; + ktime_t current_time; + + val.intval = battery->status; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_STATUS, val); +#if defined(ANDROID_ALARM_ACTIVATED) + current_time = alarm_get_elapsed_realtime(); + ts = ktime_to_timespec(current_time); +#else + current_time = ktime_get_boottime(); + ts = ktime_to_timespec(current_time); +#endif + + 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 ? ts.tv_sec : 1; + 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->charging_fullcharged_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(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_ONLINE, val); + + psy_do_property(battery->pdata->fuelgauge_name, 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); +#ifdef CONFIG_OF + adc_data = adc_read(battery, adc_ch); +#else + adc_data = adc_read(battery->pdata, adc_ch); +#endif + 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; + + /* 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_value(battery, + SEC_BAT_ADC_CHANNEL_CABLE_CHECK); + + /* 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_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 = battery->pdata->fuelgauge_name; + break; + case SEC_BATTERY_CHECK_CHARGER: + psy_name = battery->pdata->charger_name; + 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; + + if (battery->pdata->check_jig_status) { + if (battery->factory_mode + || battery->pdata->check_jig_status()) { + dev_dbg(battery->dev, "%s: No need to check in factory mode\n", + __func__); + return ret; + } + } else if (battery->factory_mode) { + dev_dbg(battery->dev, "%s: No need to check in factory mode\n", + __func__); + return ret; + } + + if (battery->health != POWER_SUPPLY_HEALTH_GOOD && + battery->health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + dev_dbg(battery->dev, "%s: No need to check\n", __func__); + return ret; + } + + switch (battery->pdata->battery_check_type) { + case SEC_BATTERY_CHECK_ADC: + ret = sec_bat_check_vf_adc(battery); + break; + case SEC_BATTERY_CHECK_INT: + case SEC_BATTERY_CHECK_CALLBACK: + if(battery->cable_type == POWER_SUPPLY_TYPE_BATTERY) { + ret = battery->present; + } else { + if (battery->pdata->check_battery_callback) + ret = battery->pdata->check_battery_callback(battery); + } + 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_NONE: + dev_info(battery->dev, "%s: No Check\n", __func__); + default: + break; + } + + return ret; +} + +static bool sec_bat_get_cable_type( + struct sec_battery_info *battery, + int cable_source_type) +{ + bool ret; + int cable_type; + + ret = false; + cable_type = battery->cable_type; + + if (cable_source_type & SEC_BATTERY_CABLE_SOURCE_CALLBACK) { + if (battery->pdata->check_cable_callback) + cable_type = + battery->pdata->check_cable_callback(battery); + } + + if (cable_source_type & 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); + } + + if (battery->cable_type == cable_type) { + dev_dbg(battery->dev, + "%s: No need to change cable status\n", __func__); + } else { + if (cable_type < POWER_SUPPLY_TYPE_BATTERY || + cable_type >= SEC_SIZEOF_POWER_SUPPLY_TYPE) { + dev_err(battery->dev, + "%s: Invalid cable type\n", __func__); + } else { + battery->cable_type = cable_type; + if (battery->pdata->check_cable_result_callback) + battery->pdata->check_cable_result_callback( + battery->cable_type); + + ret = true; + + dev_dbg(battery->dev, "%s: Cable Changed (%d)\n", + __func__, battery->cable_type); + } + } + + 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; + + if (battery->status != + POWER_SUPPLY_STATUS_DISCHARGING) { + battery->status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + sec_bat_set_charge(battery, false); + } + + if (battery->pdata->check_battery_result_callback) + battery->pdata-> + check_battery_result_callback(); + return false; + } + } else + battery->check_count = 0; + + battery->present = true; + + if (battery->health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + battery->health = POWER_SUPPLY_HEALTH_GOOD; + + if (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING) { + battery->status = POWER_SUPPLY_STATUS_CHARGING; + + sec_bat_set_charge(battery, true); + } + } + + dev_info(battery->dev, "%s: Battery Connected\n", __func__); + + if (battery->pdata->cable_check_type & + SEC_BATTERY_CABLE_CHECK_POLLING) { + if (sec_bat_get_cable_type(battery, + battery->pdata->cable_source_type)) { + wake_lock(&battery->cable_wake_lock); + queue_work(battery->monitor_wqueue, + &battery->cable_work); + } + } + return true; +}; + +static int sec_bat_ovp_uvlo_by_psy(struct sec_battery_info *battery) +{ + char *psy_name; + union power_supply_propval value; + + value.intval = POWER_SUPPLY_HEALTH_GOOD; + + 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 = battery->pdata->charger_name; + break; + default: + dev_err(battery->dev, + "%s: Invalid OVP/UVLO Check Type\n", __func__); + goto ovp_uvlo_check_error; + break; + } + + psy_do_property(psy_name, get, + POWER_SUPPLY_PROP_HEALTH, value); + +ovp_uvlo_check_error: + return value.intval; +} + +static bool sec_bat_ovp_uvlo_result( + struct sec_battery_info *battery, int health) +{ + if (battery->health != health) { + battery->health = health; + switch (health) { + case POWER_SUPPLY_HEALTH_GOOD: + dev_info(battery->dev, "%s: Safe voltage\n", __func__); + dev_info(battery->dev, "%s: is_recharging : %d\n", __func__, battery->is_recharging); + if (battery->is_recharging) + battery->status = + POWER_SUPPLY_STATUS_FULL; + else /* Normal Charging */ + battery->status = + POWER_SUPPLY_STATUS_CHARGING; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + case POWER_SUPPLY_HEALTH_UNDERVOLTAGE: + dev_info(battery->dev, + "%s: Unsafe voltage (%d)\n", + __func__, health); + battery->status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + /* Take the wakelock during 10 seconds + when over-voltage status is detected */ + wake_lock_timeout(&battery->vbus_wake_lock, HZ * 10); + break; + } + power_supply_changed(&battery->psy_bat); + return true; + } + + return false; +} + +static bool sec_bat_ovp_uvlo(struct sec_battery_info *battery) +{ + int health; + + if (battery->pdata->check_jig_status) { + if (battery->factory_mode + || battery->pdata->check_jig_status()) { + dev_dbg(battery->dev, + "%s: No need to check in factory mode\n", + __func__); + return false; + } + } else if (battery->factory_mode) { + dev_dbg(battery->dev, "%s: No need to check in factory mode\n", + __func__); + return false; + } + + if (battery->health != POWER_SUPPLY_HEALTH_GOOD && + battery->health != POWER_SUPPLY_HEALTH_OVERVOLTAGE && + battery->health != POWER_SUPPLY_HEALTH_UNDERVOLTAGE) { + dev_dbg(battery->dev, "%s: No need to check\n", __func__); + return false; + } + + health = battery->health; + + switch (battery->pdata->ovp_uvlo_check_type) { + case SEC_BATTERY_OVP_UVLO_CALLBACK: + if (battery->pdata->ovp_uvlo_callback) + health = battery->pdata->ovp_uvlo_callback(); + break; + case SEC_BATTERY_OVP_UVLO_PMICPOLLING: + case SEC_BATTERY_OVP_UVLO_CHGPOLLING: + health = 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 sec_bat_ovp_uvlo_result(battery, health); +} + +static bool sec_bat_check_recharge(struct sec_battery_info *battery) +{ + if ((battery->status == POWER_SUPPLY_STATUS_CHARGING) && + (battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_NOTIMEFULL) && + (battery->charging_mode == SEC_BATTERY_CHARGING_NONE)) { + dev_info(battery->dev, + "%s: Re-charging by NOTIMEFULL (%d)\n", + __func__, battery->capacity); + goto check_recharge_check_count; + } + + if (battery->status == POWER_SUPPLY_STATUS_FULL && + 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); + goto check_recharge_check_count; + } + + 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); + goto check_recharge_check_count; + } + + 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); + goto check_recharge_check_count; + } + } + + battery->recharge_check_cnt = 0; + return false; + +check_recharge_check_count: + if (battery->recharge_check_cnt < + battery->pdata->recharge_check_count) + battery->recharge_check_cnt++; + dev_dbg(battery->dev, + "%s: recharge count = %d\n", + __func__, battery->recharge_check_cnt); + + if (battery->recharge_check_cnt >= + battery->pdata->recharge_check_count) + return true; + else + return false; +} + +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)) { + if (battery->pdata->ovp_uvlo_result_callback) + battery->pdata-> + ovp_uvlo_result_callback(battery->health); + return false; + } + + /* Re-Charging check */ + if (sec_bat_check_recharge(battery)) { + if (battery->pdata->full_check_type != + SEC_BATTERY_FULLCHARGED_NONE) + battery->charging_mode = SEC_BATTERY_CHARGING_1ST; + else + battery->charging_mode = SEC_BATTERY_CHARGING_2ND; + battery->is_recharging = true; + sec_bat_set_charge(battery, true); + return false; + } + + 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; + const 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; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP: + battery->temp_adc = temp_adc; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + battery->temp_ambient_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 += (((int)temp_adc_table[low].data - (int)temp_adc_table[high].data) * + ((int)temp_adc - (int)temp_adc_table[high].adc)) / + ((int)temp_adc_table[low].adc - (int)temp_adc_table[high].adc); + +temp_by_adc_goto: + value->intval = temp; + + dev_info(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->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; + bool health_changed; + + /* set health_changed true as default + * this value will be changed after temperature check + */ + health_changed = true; + + if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { + dev_dbg(battery->dev, + "%s: Charging Disabled\n", __func__); + return true; + } + + if (battery->health != POWER_SUPPLY_HEALTH_GOOD && + battery->health != POWER_SUPPLY_HEALTH_OVERHEAT && + battery->health != POWER_SUPPLY_HEALTH_COLD) { + dev_dbg(battery->dev, "%s: No need to check\n", __func__); + return false; + } + + 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; + else + health_changed = false; + + if (health_changed) { + if ((battery->health == POWER_SUPPLY_HEALTH_OVERHEAT) || + (battery->health == POWER_SUPPLY_HEALTH_COLD)) { + dev_info(battery->dev, + "%s: Unsafe Temperature\n", __func__); + battery->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + /* change charging current to battery (default 0mA) */ + sec_bat_set_charge(battery, false); + } else { + dev_info(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->is_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 false; + } + return true; +}; + +static void sec_bat_event_program_alarm( + struct sec_battery_info *battery, int seconds) +{ +#if defined(ANDROID_ALARM_ACTIVATED) + ktime_t low_interval = ktime_set(seconds - 10, 0); + ktime_t slack = ktime_set(20, 0); + ktime_t next; + + next = ktime_add(battery->last_event_time, low_interval); + + alarm_start_range(&battery->event_termination_alarm, + next, ktime_add(next, slack)); +#else + alarm_start(&battery->event_termination_alarm, + ktime_add(battery->last_event_time, ktime_set(seconds - 10, 0))); +#endif +} + +#if defined(ANDROID_ALARM_ACTIVATED) +static void sec_bat_event_expired_timer_func(struct alarm *alarm) +#else +static enum alarmtimer_restart sec_bat_event_expired_timer_func( + struct alarm *alarm, ktime_t now) +#endif +{ + struct sec_battery_info *battery = + container_of(alarm, struct sec_battery_info, + event_termination_alarm); + + battery->event &= (~battery->event_wait); + dev_info(battery->dev, + "%s: event expired (0x%x)\n", __func__, battery->event); + +#if !defined(ANDROID_ALARM_ACTIVATED) + return ALARMTIMER_NORESTART; +#endif +} + +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; + } + + alarm_cancel(&battery->event_termination_alarm); + 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; +#if defined(ANDROID_ALARM_ACTIVATED) + battery->last_event_time = alarm_get_elapsed_realtime(); +#else + battery->last_event_time = ktime_get_boottime(); +#endif + sec_bat_event_program_alarm(battery, + battery->pdata->event_waiting_time); + dev_info(battery->dev, + "%s: start timer (curr 0x%x, wait 0x%x)\n", + __func__, battery->event, battery->event_wait); + } +} + +static bool sec_bat_check_fullcharged_condition( + struct sec_battery_info *battery) +{ + int full_check_type; + + if (battery->charging_mode == SEC_BATTERY_CHARGING_1ST) + full_check_type = battery->pdata->full_check_type; + else + full_check_type = battery->pdata->full_check_type_2nd; + + switch (full_check_type) { + case SEC_BATTERY_FULLCHARGED_ADC: + case SEC_BATTERY_FULLCHARGED_FG_CURRENT: + case SEC_BATTERY_FULLCHARGED_SOC: + case SEC_BATTERY_FULLCHARGED_CHGGPIO: + case SEC_BATTERY_FULLCHARGED_CHGPSY: + break; + + /* If these is NOT full check type or NONE full check type, + * it is full-charged + */ + case SEC_BATTERY_FULLCHARGED_CHGINT: + case SEC_BATTERY_FULLCHARGED_TIME: + case SEC_BATTERY_FULLCHARGED_NONE: + default: + return true; + break; + } + + 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); + return false; + } + } + + if (battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_VCELL) { + if (battery->voltage_now < + battery->pdata->full_condition_vcell) { + dev_dbg(battery->dev, + "%s: Not enough VCELL (%dmV)\n", + __func__, battery->voltage_now); + return false; + } + } + + 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); + return false; + } + } + + 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); + return false; + } + } + + return true; +} + +static void sec_bat_do_test_function( + struct sec_battery_info *battery) +{ + union power_supply_propval value; + + switch (battery->test_mode) { + case 1: + if (battery->status == POWER_SUPPLY_STATUS_CHARGING) { + sec_bat_set_charge(battery, false); + battery->status = + POWER_SUPPLY_STATUS_DISCHARGING; + } + break; + case 2: + if(battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { + sec_bat_set_charge(battery, true); + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_STATUS, value); + battery->status = value.intval; + } + battery->test_mode = 0; + break; + case 3: // clear temp block + battery->health = POWER_SUPPLY_HEALTH_GOOD; + battery->status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case 4: + if(battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { + sec_bat_set_charge(battery, true); + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_STATUS, value); + battery->status = value.intval; + } + break; + default: + pr_info("%s: error test: unknown state\n", __func__); + break; + } +} + +static bool sec_bat_time_management( + struct sec_battery_info *battery) +{ + unsigned long charging_time; + struct timespec ts; +#if defined(ANDROID_ALARM_ACTIVATED) + ktime_t current_time; + + current_time = alarm_get_elapsed_realtime(); + ts = ktime_to_timespec(current_time); +#else + get_monotonic_boottime(&ts); +#endif + + 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_dbg(battery->dev, + "%s: Charging Time : %ld secs\n", __func__, + battery->charging_passed_time); + + switch (battery->status) { + case POWER_SUPPLY_STATUS_FULL: + if (battery->is_recharging && (charging_time > + battery->pdata->recharging_total_time)) { + dev_info(battery->dev, + "%s: Recharging Timer Expired\n", __func__); + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->is_recharging = false; + 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->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_NOTIMEFULL) && + (battery->is_recharging && (charging_time > + battery->pdata->recharging_total_time))) { + dev_info(battery->dev, + "%s: Recharging Timer Expired\n", __func__); + if (battery->capacity >= 100) + battery->status = POWER_SUPPLY_STATUS_FULL; + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->is_recharging = false; + if (sec_bat_set_charge(battery, false)) { + dev_err(battery->dev, + "%s: Fail to Set Charger\n", __func__); + return true; + } + return false; + } else if (!battery->is_recharging && + (charging_time > battery->pdata->charging_total_time)) { + dev_info(battery->dev, + "%s: Charging Timer Expired\n", __func__); + if (battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_NOTIMEFULL) { + if (battery->capacity >= 100) + battery->status = + POWER_SUPPLY_STATUS_FULL; + } else + 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); + + 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) +{ + union power_supply_propval value; + int current_adc; + int full_check_type; + bool ret; + int err; + + ret = false; + + if (!sec_bat_check_fullcharged_condition(battery)) + goto not_full_charged; + + if (battery->charging_mode == SEC_BATTERY_CHARGING_1ST) + full_check_type = battery->pdata->full_check_type; + else + full_check_type = battery->pdata->full_check_type_2nd; + + switch (full_check_type) { + 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->charging_mode == + SEC_BATTERY_CHARGING_1ST ? + battery->pdata->charging_current[ + battery->cable_type].full_check_current_1st : + battery->pdata->charging_current[ + battery->cable_type].full_check_current_2nd)) { + 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: + if ((battery->current_now > 20 && battery->current_now < + battery->pdata->charging_current[ + battery->cable_type].full_check_current_1st) && + (battery->current_avg > 20 && battery->current_avg < + (battery->charging_mode == + SEC_BATTERY_CHARGING_1ST ? + battery->pdata->charging_current[ + battery->cable_type].full_check_current_1st : + battery->pdata->charging_current[ + battery->cable_type].full_check_current_2nd))) { + 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_TIME: + if ((battery->charging_mode == + SEC_BATTERY_CHARGING_2ND ? + (battery->charging_passed_time - + battery->charging_fullcharged_time) : + battery->charging_passed_time) > + (battery->charging_mode == + SEC_BATTERY_CHARGING_1ST ? + battery->pdata->charging_current[ + battery->cable_type].full_check_current_1st : + battery->pdata->charging_current[ + battery->cable_type].full_check_current_2nd)) { + battery->full_check_cnt++; + dev_dbg(battery->dev, + "%s: Full Check Time (%d)\n", + __func__, + battery->full_check_cnt); + } else + battery->full_check_cnt = 0; + break; + + case SEC_BATTERY_FULLCHARGED_SOC: + if (battery->capacity <= + (battery->charging_mode == + SEC_BATTERY_CHARGING_1ST ? + battery->pdata->charging_current[ + battery->cable_type].full_check_current_1st : + battery->pdata->charging_current[ + battery->cable_type].full_check_current_2nd)) { + battery->full_check_cnt++; + dev_dbg(battery->dev, + "%s: Full Check SOC (%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: + case SEC_BATTERY_FULLCHARGED_CHGPSY: + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_STATUS, value); + + if (value.intval == POWER_SUPPLY_STATUS_FULL) { + battery->full_check_cnt++; + dev_info(battery->dev, + "%s: Full Check Charger (%d)\n", + __func__, battery->full_check_cnt); + } else { + battery->full_check_cnt = 0; + } + break; + + /* If these is NOT full check type or NONE full check type, + * it is full-charged + */ + case SEC_BATTERY_FULLCHARGED_NONE: + battery->full_check_cnt = 0; + ret = true; + 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; + + /* To let charger/fuel gauge know the full status, + * set status before calling sec_bat_set_charge() + */ + battery->status = POWER_SUPPLY_STATUS_FULL; + + if (battery->charging_mode == SEC_BATTERY_CHARGING_1ST) { + battery->charging_mode = SEC_BATTERY_CHARGING_2ND; + battery->charging_fullcharged_time = + battery->charging_passed_time; + sec_bat_set_charge(battery, true); + } else { + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->is_recharging = false; + sec_bat_set_charge(battery, false); + + value.intval = POWER_SUPPLY_STATUS_FULL; + psy_do_property(battery->pdata->fuelgauge_name, 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 + */ + if (battery->pdata->polling_type == SEC_BATTERY_MONITOR_ALARM) + wake_lock_timeout(&battery->vbus_wake_lock, HZ * 10); +} + +static bool sec_bat_fullcharged_check( + struct sec_battery_info *battery) +{ + if ((battery->charging_mode == SEC_BATTERY_CHARGING_NONE) || + (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING)) { + 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__, + battery->is_recharging ? + sec_bat_charging_mode_str[SEC_BATTERY_CHARGING_RECHARGING] : + sec_bat_charging_mode_str[battery->charging_mode]); + + return true; +} + +#if defined(CONFIG_TOUCHSCREEN_IST30XXA) +int battery_temp; +#endif + +static void sec_bat_get_battery_info( + struct sec_battery_info *battery) +{ + union power_supply_propval value; + + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + battery->voltage_now = value.intval; + + value.intval = SEC_BATTEY_VOLTAGE_AVERAGE; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + battery->voltage_avg = value.intval; + + value.intval = SEC_BATTEY_VOLTAGE_OCV; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + battery->voltage_ocv = value.intval; + + value.intval = SEC_BATTEY_CURRENT_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + battery->current_now = value.intval; + + value.intval = SEC_BATTEY_CURRENT_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_AVG, value); + battery->current_avg = value.intval; + + /* input current limit in charger */ + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_CURRENT_MAX, value); + battery->current_max = value.intval; + + /* To get SOC value (NOT raw SOC), need to reset value */ + value.intval = 0; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + battery->capacity = value.intval; +#if defined(CONFIG_MACH_CORSICA_VE) + if (get_hw_rev() < 0x2) { + dev_info(battery->dev, + "%s: get_hw_rev (%d)\n", + __func__, get_hw_rev()); + battery->capacity = 50; + battery->voltage_ocv = 4000; + battery->voltage_avg = 4000; + battery->voltage_now = 4000; + } +#endif + + switch (battery->pdata->thermal_source) { + case SEC_BATTERY_THERMAL_SOURCE_FG: + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_TEMP, value); + battery->temperature = value.intval; + + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_TEMP_AMBIENT, value); + battery->temper_amb = value.intval; + break; + case SEC_BATTERY_THERMAL_SOURCE_CALLBACK: + if (battery->pdata->get_temperature_callback) { + battery->pdata->get_temperature_callback( + POWER_SUPPLY_PROP_TEMP, &value); + battery->temperature = value.intval; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_TEMP, value); + + battery->pdata->get_temperature_callback( + POWER_SUPPLY_PROP_TEMP_AMBIENT, &value); + battery->temper_amb = value.intval; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_TEMP_AMBIENT, value); + } + break; + case SEC_BATTERY_THERMAL_SOURCE_ADC: + sec_bat_get_temperature_by_adc(battery, + POWER_SUPPLY_PROP_TEMP, &value); + battery->temperature = value.intval; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_TEMP, value); + + sec_bat_get_temperature_by_adc(battery, + POWER_SUPPLY_PROP_TEMP_AMBIENT, &value); + battery->temper_amb = value.intval; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_TEMP_AMBIENT, value); + break; + default: + break; + } + +#if defined(CONFIG_TOUCHSCREEN_IST30XXA) + battery_temp = battery->temperature; +#endif + + dev_info(battery->dev, + "%s:Vnow(%dmV),Inow(%dmA),Imax(%dmA),SOC(%d%%),Tbat(%d)\n", + __func__, + battery->voltage_now, battery->current_now, + battery->current_max, battery->capacity, battery->temperature); + dev_dbg(battery->dev, + "%s,Vavg(%dmV),Vocv(%dmV),Tamb(%d)," + "Iavg(%dmA),Iadc(%d)\n", + battery->present ? "Connected" : "Disconnected", + battery->voltage_avg, battery->voltage_ocv, + battery->temper_amb, + battery->current_avg, battery->current_adc); +} + +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_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + dev_dbg(battery->dev, "%s: Activated\n", __func__); +} + +static void sec_bat_program_alarm( + struct sec_battery_info *battery, int seconds) +{ +#if defined(ANDROID_ALARM_ACTIVATED) + 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)); +#else + alarm_start(&battery->polling_alarm, + ktime_add(battery->last_poll_time, ktime_set(seconds, 0))); +#endif +} + + +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->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_NOSLEEPINFULL) && + 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) +{ + /* Change the full and short monitoring sequence + * Originally, full monitoring was the last time of polling_count + * But change full monitoring to first time + * because temperature check is too late + */ + if (!battery->polling_short || battery->polling_count == 1) + return false; + else + return true; +} + +static void sec_bat_update_polling_count( + struct sec_battery_info *battery) +{ + /* do NOT change polling count in sleep + * even though it is short polling + * to keep polling count along sleep/wakeup + */ + if (battery->polling_short && battery->polling_in_sleep) + return; + + if (battery->polling_short && + ((battery->polling_time / + battery->pdata->polling_time[ + SEC_BATTERY_POLLING_TIME_BASIC]) + > battery->polling_count)) + battery->polling_count++; + else + battery->polling_count = 1; /* initial value = 1 */ +} + +static void sec_bat_set_polling( + struct sec_battery_info *battery) +{ + unsigned int polling_time_temp; + + dev_dbg(battery->dev, "%s: Start\n", __func__); + + 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 + * Do NOT update polling count in initial monitor + */ + if (!battery->pdata->monitor_initial_count) + sec_bat_update_polling_count(battery); + else + dev_dbg(battery->dev, + "%s: Initial monitor %d times left.\n", __func__, + battery->pdata->monitor_initial_count); + + 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: +#if defined(ANDROID_ALARM_ACTIVATED) + battery->last_poll_time = alarm_get_elapsed_realtime(); +#else + battery->last_poll_time = ktime_get_boottime(); +#endif + + 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); + break; + case SEC_BATTERY_MONITOR_TIMER: + break; + default: + break; + } + dev_dbg(battery->dev, "%s: End\n", __func__); +} + +static void sec_bat_monitor_work( + struct work_struct *work) +{ + struct sec_battery_info *battery = + container_of(work, struct sec_battery_info, + monitor_work.work); + + dev_dbg(battery->dev, "%s: Start\n", __func__); + +/*#if !defined(ANDROID_ALARM_ACTIVATED) + alarm_cancel(&battery->polling_alarm); +#endif *//* ANDROID_ALARM_ACTIVATED */ + + /* monitor once after wakeup */ + if (battery->polling_in_sleep) + battery->polling_in_sleep = false; + + sec_bat_get_battery_info(battery); + + /* 0. test mode */ + if (battery->test_mode) { + dev_dbg(battery->dev, "%s: Test Mode\n", __func__); + sec_bat_do_test_function(battery); + if (battery->test_mode != 0) + 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; + + /* monitor short routine in initial monitor */ + if (battery->pdata->monitor_initial_count || + sec_bat_is_short_polling(battery)) + goto continue_monitor; + + /* 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); + + /* 6. additional check */ + if (battery->pdata->monitor_additional_check) + battery->pdata->monitor_additional_check(); + +continue_monitor: + dev_info(battery->dev, + "%s: Status(%s), mode(%s), Health(%s), Cable(%d), siop_level(%d)\n", + __func__, + sec_bat_status_str[battery->status], + sec_bat_charging_mode_str[battery->charging_mode], + sec_bat_health_str[battery->health], + battery->cable_type, battery->siop_level); + +#if defined(CONFIG_SAMSUNG_BATTERY_ENG_TEST) + dev_info(battery->dev, + "%s: battery->stability_test(%d), battery->eng_not_full_status(%d)\n", + __func__, battery->stability_test, battery->eng_not_full_status); +#endif + power_supply_changed(&battery->psy_bat); + + sec_bat_set_polling(battery); + + if (battery->capacity <= 0) + wake_lock_timeout(&battery->monitor_wake_lock, HZ * 5); + else + wake_unlock(&battery->monitor_wake_lock); + + dev_dbg(battery->dev, "%s: End\n", __func__); + + return; +} + +#if defined(ANDROID_ALARM_ACTIVATED) +static void sec_bat_alarm(struct alarm *alarm) +#else +static enum alarmtimer_restart sec_bat_alarm( + struct alarm *alarm, ktime_t now) + +#endif +{ + struct sec_battery_info *battery = container_of(alarm, + struct sec_battery_info, polling_alarm); + + dev_dbg(battery->dev, + "%s\n", __func__); + + /* 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_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + dev_dbg(battery->dev, "%s: Activated\n", __func__); + } +#if !defined(ANDROID_ALARM_ACTIVATED) + return ALARMTIMER_NORESTART; +#endif +} + + +static void sec_bat_cable_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, cable_work); + union power_supply_propval val; + int wl_cur, wr_cur, current_cable_type; + + dev_info(battery->dev, "%s: Start\n", __func__); + + wl_cur = battery->pdata->charging_current[ + POWER_SUPPLY_TYPE_WIRELESS].input_current_limit; + wr_cur = battery->pdata->charging_current[ + battery->wire_status].input_current_limit; + if (battery->wc_status && battery->wc_enable && + (wl_cur > wr_cur)) + current_cable_type = POWER_SUPPLY_TYPE_WIRELESS; + else + current_cable_type = battery->wire_status; + + if (current_cable_type == battery->cable_type) { + dev_info(battery->dev, + "%s: Cable is NOT Changed(%d)\n", + __func__, battery->cable_type); + /* Do NOT activate cable work for NOT changed */ + goto end_of_cable_work; + } + + battery->cable_type = current_cable_type; + if (battery->pdata->check_cable_result_callback) + battery->pdata->check_cable_result_callback( + battery->cable_type); + /* platform can NOT get information of cable connection + * because wakeup time is too short to check uevent + * To make sure that target is wakeup + * if cable is connected and disconnected, + * activated wake lock in a few seconds + */ + wake_lock_timeout(&battery->vbus_wake_lock, HZ * 10); + + if (battery->cable_type == POWER_SUPPLY_TYPE_BATTERY || + ((battery->pdata->cable_check_type & + SEC_BATTERY_CABLE_CHECK_NOINCOMPATIBLECHARGE) && + battery->cable_type == POWER_SUPPLY_TYPE_UNKNOWN)) { + if (battery->status == POWER_SUPPLY_STATUS_FULL) { + val.intval = POWER_SUPPLY_TYPE_BATTERY; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_CHARGE_FULL, val); + /* To get SOC value (NOT raw SOC), need to reset value */ + val.intval = 0; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CAPACITY, val); + battery->capacity = val.intval; + } + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->is_recharging = false; + battery->status = POWER_SUPPLY_STATUS_DISCHARGING; + battery->health = POWER_SUPPLY_HEALTH_GOOD; + + if (sec_bat_set_charge(battery, false)) + goto end_of_cable_work; + } else { + /* Do NOT display the charging icon when OTG is enabled */ + if (battery->cable_type == POWER_SUPPLY_TYPE_OTG) { + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->status = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + if (battery->pdata->full_check_type != + SEC_BATTERY_FULLCHARGED_NONE) + battery->charging_mode = + SEC_BATTERY_CHARGING_1ST; + else + battery->charging_mode = + SEC_BATTERY_CHARGING_2ND; + battery->status = POWER_SUPPLY_STATUS_CHARGING; + } + + if (sec_bat_set_charge(battery, true)) + goto end_of_cable_work; + +#if defined(ANDROID_ALARM_ACTIVATED) + /* No need for wakelock in Alarm */ + if (battery->pdata->polling_type != SEC_BATTERY_MONITOR_ALARM) + wake_lock(&battery->vbus_wake_lock); +#endif + } + + /* polling time should be reset when cable is changed + * polling_in_sleep should be reset also + * before polling time is re-calculated + * to prevent from counting 1 for events + * right after cable is connected + */ + battery->polling_in_sleep = false; + sec_bat_get_polling_time(battery); + + dev_info(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_info(battery->dev, + "%s: Polling time is reset to %d sec.\n", __func__, + battery->polling_time); + + battery->polling_count = 1; /* initial value = 1 */ + + wake_lock(&battery->monitor_wake_lock); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, + msecs_to_jiffies(500)); +end_of_cable_work: + wake_unlock(&battery->cable_wake_lock); + dev_dbg(battery->dev, "%s: End\n", __func__); +} + +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; + union power_supply_propval value; + int i = 0; + + switch (offset) { + case BATT_RESET_SOC: + break; + case BATT_READ_RAW_SOC: + { + union power_supply_propval value; + + value.intval = + SEC_FUELGAUGE_CAPACITY_TYPE_RAW; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + 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_CURRENT_UA_NOW: + { + union power_supply_propval value; + + value.intval = SEC_BATTEY_CURRENT_UA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_CURRENT_UA_AVG: + { + union power_supply_propval value; + + value.intval = SEC_BATTEY_CURRENT_UA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_AVG, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + + case BATT_TEMP: + switch (battery->pdata->thermal_source) { + case SEC_BATTERY_THERMAL_SOURCE_FG: + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_TEMP, value); + break; + case SEC_BATTERY_THERMAL_SOURCE_CALLBACK: + if (battery->pdata->get_temperature_callback) { + battery->pdata->get_temperature_callback( + POWER_SUPPLY_PROP_TEMP, &value); + } + break; + case SEC_BATTERY_THERMAL_SOURCE_ADC: + sec_bat_get_temperature_by_adc(battery, + POWER_SUPPLY_PROP_TEMP, &value); + break; + default: + break; + } + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + 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_SLATE_MODE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->slate_mode); + break; + + case BATT_LP_CHARGING: + if (battery->pdata->is_lpm) { + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->pdata->is_lpm() ? 1 : 0); + } + break; + case SIOP_ACTIVATED: + break; + case SIOP_LEVEL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->siop_level); + 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 FG_CAPACITY: + { + union power_supply_propval value; + + value.intval = + SEC_BATTEY_CAPACITY_DESIGNED; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%04x ", + value.intval); + + value.intval = + SEC_BATTEY_CAPACITY_ABSOLUTE; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%04x ", + value.intval); + + value.intval = + SEC_BATTEY_CAPACITY_TEMPERARY; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%04x ", + value.intval); + + value.intval = + SEC_BATTEY_CAPACITY_CURRENT; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%04x\n", + value.intval); + } + 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: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->cable_type == POWER_SUPPLY_TYPE_WIRELESS)); + break; + case WC_ENABLE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->wc_enable); + break; + case FACTORY_MODE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->factory_mode); + break; + case UPDATE: + break; + case TEST_MODE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->test_mode); + break; + + case BATT_EVENT_CALL: + case BATT_EVENT_2G_CALL: + case BATT_EVENT_TALK_GSM: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_2G_CALL) ? 1 : 0); + break; + case BATT_EVENT_3G_CALL: + case BATT_EVENT_TALK_WCDMA: + 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_LCD: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_LCD) ? 1 : 0); + break; + case BATT_EVENT_GPS: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->event & EVENT_GPS) ? 1 : 0); + break; + case BATT_EVENT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->event); + break; +#if defined(CONFIG_SAMSUNG_BATTERY_ENG_TEST) + case BATT_TEST_CHARGE_CURRENT: + { + union power_supply_propval value; + + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_STABILITY_TEST: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->stability_test); + break; +#endif + 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 = -EINVAL; + int x = 0; + + switch (offset) { + case BATT_RESET_SOC: + /* Do NOT reset fuel gauge in charging mode */ + if (battery->pdata->check_cable_callback && + battery->pdata->check_jig_status) { + if ((battery->pdata->check_cable_callback(battery) == + POWER_SUPPLY_TYPE_BATTERY) || + battery->pdata->check_jig_status()) { + union power_supply_propval value; + battery->voltage_now = 1234; + battery->voltage_avg = 1234; + power_supply_changed(&battery->psy_bat); + + value.intval = + SEC_FUELGAUGE_CAPACITY_TYPE_RESET; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_CAPACITY, value); + dev_info(battery->dev,"do reset SOC\n"); + /* 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_CURRENT_UA_NOW: + break; + case BATT_CURRENT_UA_AVG: + break; + case BATT_TEMP: + break; + case BATT_TEMP_ADC: + break; + case BATT_TEMP_AVER: + break; + case BATT_TEMP_ADC_AVER: + break; + case BATT_VF_ADC: + break; + case BATT_SLATE_MODE: + if (sscanf(buf, "%d\n", &x) == 1) { + union power_supply_propval value; + if (x == 1) { + value.intval = POWER_SUPPLY_TYPE_BATTERY; + battery->slate_mode = true; + } else if (x == 0) { + value.intval = POWER_SUPPLY_TYPE_USB; + battery->slate_mode = false; + } else { + dev_info(battery->dev, + "%s: SLATE MODE unknown command\n", + __func__); + return -EINVAL; + } + psy_do_property("battery", set, + POWER_SUPPLY_PROP_ONLINE, value); + if (battery->slate_mode) { + value.intval = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CURRENT_NOW, + value); + } + ret = count; + } + break; + + case BATT_LP_CHARGING: + break; + case SIOP_ACTIVATED: + break; + case SIOP_LEVEL: + if (sscanf(buf, "%d\n", &x) == 1) { + union power_supply_propval value; + dev_info(battery->dev, + "%s: siop level: %d\n", __func__, x); + if (x >= 0 && x <= 100) + battery->siop_level = x; + else + battery->siop_level = 100; + value.intval = battery->siop_level; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, value); + ret = count; + } + break; + case BATT_CHARGING_SOURCE: + break; + case FG_REG_DUMP: + break; + case FG_RESET_CAP: + break; + case FG_CAPACITY: + break; + case AUTH: + break; + case CHG_CURRENT_ADC: + break; + case WC_ADC: + break; + case WC_STATUS: + break; + case WC_ENABLE: + if (sscanf(buf, "%d\n", &x) == 1) { + if (x == 0) { + battery->wc_enable = false; + } else if (x == 1) { + battery->wc_enable = true; + } else { + dev_info(battery->dev, + "%s: WPC ENABLE unknown command\n", + __func__); + return -EINVAL; + } + wake_lock(&battery->cable_wake_lock); + queue_work(battery->monitor_wqueue, + &battery->cable_work); + ret = count; + } + break; + case FACTORY_MODE: + if (sscanf(buf, "%d\n", &x) == 1) { + battery->factory_mode = x ? true : false; + ret = count; + } + break; + case UPDATE: + if (sscanf(buf, "%d\n", &x) == 1) { + /* update battery info */ + sec_bat_get_battery_info(battery); + ret = count; + } + break; + case TEST_MODE: + if (sscanf(buf, "%d\n", &x) == 1) { + battery->test_mode = x; + wake_lock(&battery->monitor_wake_lock); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, 0); + ret = count; + } + break; + + case BATT_EVENT_CALL: + case BATT_EVENT_2G_CALL: + case BATT_EVENT_TALK_GSM: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_2G_CALL, x); + ret = count; + } + break; + case BATT_EVENT_3G_CALL: + case BATT_EVENT_TALK_WCDMA: + 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; + case BATT_EVENT_LCD: + if (sscanf(buf, "%d\n", &x) == 1) { + /* we need to test + sec_bat_event_set(battery, EVENT_LCD, x); + */ + ret = count; + } + break; + case BATT_EVENT_GPS: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_bat_event_set(battery, EVENT_GPS, x); + ret = count; + } + break; +#if defined(CONFIG_SAMSUNG_BATTERY_ENG_TEST) + case BATT_TEST_CHARGE_CURRENT: + if (sscanf(buf, "%d\n", &x) == 1) { + if (x >= 0 && x <= 2000) { + union power_supply_propval value; + dev_err(battery->dev, + "%s: BATT_TEST_CHARGE_CURRENT(%d)\n", __func__, x); + battery->pdata->charging_current[ + POWER_SUPPLY_TYPE_USB].input_current_limit = x; + battery->pdata->charging_current[ + POWER_SUPPLY_TYPE_USB].fast_charging_current = x; + if (x > 500) { + battery->eng_not_full_status = true; + battery->pdata->temp_check_type = + SEC_BATTERY_TEMP_CHECK_NONE; + battery->pdata->charging_total_time = + 10000 * 60 * 60; + } + if (battery->cable_type == POWER_SUPPLY_TYPE_USB) { + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CURRENT_NOW, + value); + } + } + ret = count; + } + break; + case BATT_STABILITY_TEST: + if (sscanf(buf, "%d\n", &x) == 1) { + union power_supply_propval value; + dev_err(battery->dev, + "%s: BATT_STABILITY_TEST(%d)\n", __func__, x); + if (x) { + battery->stability_test = true; + battery->eng_not_full_status = true; + value.intval = POWER_SUPPLY_TYPE_WIRELESS; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); + psy_do_property("battery", set, + POWER_SUPPLY_PROP_ONLINE, value); + } + else { + battery->stability_test = false; + value.intval = POWER_SUPPLY_TYPE_MAINS; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); + } + } + break; +#endif + 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); + int current_cable_type; + int full_check_type; + union power_supply_propval value; + + dev_dbg(battery->dev, + "%s: (%d,%d)\n", __func__, psp, val->intval); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (battery->charging_mode == SEC_BATTERY_CHARGING_1ST) + full_check_type = battery->pdata->full_check_type; + else + full_check_type = battery->pdata->full_check_type_2nd; + if ((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: + sec_bat_ovp_uvlo_result(battery, val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + current_cable_type = val->intval; + + if (current_cable_type < 0) { + dev_info(battery->dev, + "%s: ignore event(%d)\n", + __func__, current_cable_type); + } else if (current_cable_type == POWER_SUPPLY_TYPE_OTG) { + value.intval = current_cable_type; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_ONLINE, value); + wake_lock(&battery->monitor_wake_lock); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, 0); + + break; +#if 0 + } else if (current_cable_type == POWER_SUPPLY_TYPE_WIRELESS) { + battery->wc_status = true; + } else if (current_cable_type == POWER_SUPPLY_TYPE_WIRELESS_REMOVE) { + battery->wc_status = false; + if (battery->wire_status != POWER_SUPPLY_TYPE_BATTERY) + current_cable_type = battery->wire_status; +#endif + } else { + battery->wire_status = current_cable_type; + if ((battery->wire_status == POWER_SUPPLY_TYPE_BATTERY) + && battery->wc_status) + current_cable_type = POWER_SUPPLY_TYPE_WIRELESS; + } + dev_info(battery->dev, + "%s: current_cable(%d), wc_status(%d), wire_status(%d)\n", + __func__, current_cable_type, battery->wc_status, + battery->wire_status); + + /* cable is attached or detached + * if current_cable_type is minus value, + * check cable by sec_bat_get_cable_type() + * although SEC_BATTERY_CABLE_SOURCE_EXTERNAL is set + * (0 is POWER_SUPPLY_TYPE_UNKNOWN) + */ + if ((current_cable_type >= 0) && + (current_cable_type <= SEC_SIZEOF_POWER_SUPPLY_TYPE) && + (battery->pdata->cable_source_type & + SEC_BATTERY_CABLE_SOURCE_EXTERNAL || + battery->pdata->cable_source_type & + SEC_BATTERY_CABLE_SOURCE_EXTENDED)) { + + wake_lock(&battery->cable_wake_lock); + queue_work(battery->monitor_wqueue, + &battery->cable_work); + } else { + if (sec_bat_get_cable_type(battery, + battery->pdata->cable_source_type)) { + 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; + case POWER_SUPPLY_PROP_PRESENT: + cancel_delayed_work(&battery->monitor_work); + wake_lock(&battery->monitor_wake_lock); + queue_delayed_work_on(0, battery->monitor_wqueue, + &battery->monitor_work, 0); + break; + default: + return -EINVAL; + } + + 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); + union power_supply_propval value; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if ((battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) || + (battery->health == POWER_SUPPLY_HEALTH_UNDERVOLTAGE)) { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + if ((battery->pdata->cable_check_type & + SEC_BATTERY_CABLE_CHECK_NOUSBCHARGE) && + (battery->pdata->is_lpm && + !battery->pdata->is_lpm())) { + 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; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); + if (value.intval == POWER_SUPPLY_CHARGE_TYPE_UNKNOWN) + /* if error in CHARGE_TYPE of charger + * set CHARGE_TYPE as NONE + */ + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + else + val->intval = value.intval; + 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: + if (battery->pdata->check_jig_status) { + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + battery->voltage_now = value.intval; + dev_err(battery->dev, + "%s: voltage now(%d)\n", __func__, battery->voltage_now); + } + /* voltage value should be in uV */ + val->intval = battery->voltage_now * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: +#ifdef CONFIG_SEC_FACTORY + value.intval = SEC_BATTEY_VOLTAGE_AVERAGE; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + battery->voltage_avg = value.intval; + dev_err(battery->dev, + "%s: voltage avg(%d)\n", __func__, battery->voltage_avg); +#endif + /* 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; + /* charging mode (differ from power supply) */ + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = battery->charging_mode; + break; + case POWER_SUPPLY_PROP_CAPACITY: +#if defined(CONFIG_SAMSUNG_BATTERY_ENG_TEST) + if (battery->status == POWER_SUPPLY_STATUS_FULL) { + if(battery->eng_not_full_status == true) + val->intval = battery->capacity; + else + val->intval = 100; + } else { + val->intval = battery->capacity; + } +#else + /* In full-charged status, SOC is always 100% */ + if (battery->status == POWER_SUPPLY_STATUS_FULL) + val->intval = 100; + else + val->intval = battery->capacity; +#endif + 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; + + if ((battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) || + (battery->health == POWER_SUPPLY_HEALTH_UNDERVOLTAGE)) { + val->intval = 0; + return 0; + } + /* Set enable=1 only if the USB charger is connected */ + switch (battery->wire_status) { + case POWER_SUPPLY_TYPE_USB: + case POWER_SUPPLY_TYPE_USB_DCP: + case POWER_SUPPLY_TYPE_USB_CDP: + case POWER_SUPPLY_TYPE_USB_ACA: + case POWER_SUPPLY_TYPE_MHL_USB: + val->intval = 1; + break; + default: + val->intval = 0; + break; + } + + 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; + + if ((battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) || + (battery->health == POWER_SUPPLY_HEALTH_UNDERVOLTAGE)) { + val->intval = 0; + return 0; + } + + /* Set enable=1 only if the AC charger is connected */ + switch (battery->cable_type) { + case POWER_SUPPLY_TYPE_MAINS: + case POWER_SUPPLY_TYPE_HV_MAINS: + case POWER_SUPPLY_TYPE_MISC: + case POWER_SUPPLY_TYPE_CARDOCK: + case POWER_SUPPLY_TYPE_UARTOFF: + case POWER_SUPPLY_TYPE_LAN_HUB: + case POWER_SUPPLY_TYPE_UNKNOWN: + case POWER_SUPPLY_TYPE_MHL_500: + case POWER_SUPPLY_TYPE_MHL_900: + case POWER_SUPPLY_TYPE_MHL_1500: + case POWER_SUPPLY_TYPE_SMART_OTG: + case POWER_SUPPLY_TYPE_SMART_NOTG: + val->intval = 1; + break; + default: + val->intval = 0; + break; + } + + return 0; +} + +static int sec_wireless_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_wireless); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + if (battery->wc_status) + val->intval = 1; + else + val->intval = 0; + + return 0; +} + +static int sec_wireless_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_wireless); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + battery->wc_status = val->intval; + + wake_lock(&battery->cable_wake_lock); + queue_work(battery->monitor_wqueue, &battery->cable_work); + + return 0; +} + +static int sec_ps_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_ps); + union power_supply_propval value; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if ((val->intval == 0) || (val->intval ==1)) { + battery->ps_enable = val->intval; + dev_info(battery->dev, + "%s: power sharing cable set (%d)\n", __func__, battery->ps_enable); + value.intval = POWER_SUPPLY_TYPE_POWER_SHARING; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_ONLINE, value); + } else { + dev_err(battery->dev, + "%s: invalid setting (%d)\n", __func__, val->intval); + } + break; + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval == POWER_SUPPLY_TYPE_POWER_SHARING) { + battery->ps_status = true; + dev_info(battery->dev, + "%s: power sharing cable plugin (%d)\n", __func__, battery->ps_status); + wake_lock(&battery->monitor_wake_lock); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } else { + battery->ps_status = false; + dev_info(battery->dev, + "%s: power sharing cable plugout (%d)\n", __func__, battery->ps_status); + wake_lock(&battery->monitor_wake_lock); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int sec_ps_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_ps); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (battery->ps_enable) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_ONLINE: + if (battery->ps_status) + val->intval = 1; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* TODO unused */ +#if 0 +static irqreturn_t sec_bat_irq_thread(int irq, void *irq_data) +{ + struct sec_battery_info *battery = irq_data; + + dev_info(battery->dev, "%s:(bat_irq occured_start\n", __func__); + + if (battery->pdata->cable_check_type & + SEC_BATTERY_CABLE_CHECK_INT) { + if (battery->pdata->is_interrupt_cable_check_possible && + !battery->pdata->is_interrupt_cable_check_possible( + battery->extended_cable_type)) + goto no_cable_check; + else { + if (sec_bat_get_cable_type(battery, + battery->pdata->cable_source_type)) { + wake_lock(&battery->cable_wake_lock); + queue_work(battery->monitor_wqueue, + &battery->cable_work); + } + } + } + +no_cable_check: + if (battery->pdata->battery_check_type == + SEC_BATTERY_CHECK_INT) { + if (battery_pdata->check_battery_callback) + battery->present = battery->pdata->check_battery_callback(); + + wake_lock(&battery->monitor_wake_lock); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } + + return IRQ_HANDLED; +} +#endif + +#if defined(CONFIG_MUIC_NOTIFIER) +static int sec_bat_cable_check(struct sec_battery_info *battery, + muic_attached_dev_t attached_dev) +{ + int current_cable_type = POWER_SUPPLY_TYPE_UNKNOWN; + + pr_info("[%s]ATTACHED(%d)\n", __func__, attached_dev); + + switch (attached_dev) + { + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_SMARTDOCK_MUIC: + current_cable_type = POWER_SUPPLY_TYPE_BATTERY; + break; + case ATTACHED_DEV_OTG_MUIC: + current_cable_type = POWER_SUPPLY_TYPE_OTG; + break; + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + case ATTACHED_DEV_JIG_USB_ON_MUIC: + case ATTACHED_DEV_SMARTDOCK_USB_MUIC: + current_cable_type = POWER_SUPPLY_TYPE_USB; + break; + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + current_cable_type = POWER_SUPPLY_TYPE_UARTOFF; + break; + case ATTACHED_DEV_TA_MUIC: + case ATTACHED_DEV_CARDOCK_MUIC: + case ATTACHED_DEV_DESKDOCK_MUIC: + case ATTACHED_DEV_SMARTDOCK_TA_MUIC: + current_cable_type = POWER_SUPPLY_TYPE_MAINS; + break; + case ATTACHED_DEV_CDP_MUIC: + current_cable_type = POWER_SUPPLY_TYPE_USB_CDP; + break; + case ATTACHED_DEV_HV_MUIC: + current_cable_type = POWER_SUPPLY_TYPE_HV_MAINS; + break; + default: + pr_err("%s: invalid type for charger:%d\n", + __func__, attached_dev); + } + + return current_cable_type; + +} + +static int batt_handle_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + muic_attached_dev_t attached_dev = *(muic_attached_dev_t *)data; + const char *cmd; + int cable_type; + struct sec_battery_info *battery = + container_of(nb, struct sec_battery_info, + batt_nb); + + switch (action) { + case MUIC_NOTIFY_CMD_DETACH: + case MUIC_NOTIFY_CMD_LOGICALLY_DETACH: + cmd = "DETACH"; + cable_type = POWER_SUPPLY_TYPE_BATTERY; + break; + case MUIC_NOTIFY_CMD_ATTACH: + case MUIC_NOTIFY_CMD_LOGICALLY_ATTACH: + cmd = "ATTACH"; + cable_type = sec_bat_cable_check(battery, attached_dev); + break; + default: + cmd = "ERROR"; + cable_type = POWER_SUPPLY_TYPE_UNKNOWN; + break; + } + + if (cable_type < 0) { + dev_info(battery->dev, "%s: ignore event(%d)\n", + __func__, cable_type); + } else if (cable_type == POWER_SUPPLY_TYPE_POWER_SHARING) { + battery->ps_status = true; + dev_info(battery->dev, + "%s: power sharing cable plugin (%d)\n", __func__, battery->ps_status); + } else if (cable_type == POWER_SUPPLY_TYPE_WIRELESS) { + battery->wc_status = true; +#if 0 + } else if (cable_type == POWER_SUPPLY_TYPE_WIRELESS_REMOVE) { + battery->wc_status = false; + if (battery->wire_status != POWER_SUPPLY_TYPE_BATTERY) + cable_type = battery->wire_status; +#endif + } else { + battery->wire_status = cable_type; + if ((battery->wire_status == POWER_SUPPLY_TYPE_BATTERY) + && battery->wc_status && !battery->ps_status) + cable_type = POWER_SUPPLY_TYPE_WIRELESS; + } + dev_info(battery->dev, + "%s: current_cable(%d), wc_status(%d), wire_status(%d)\n", + __func__, cable_type, battery->wc_status, + battery->wire_status); + + if ((cable_type >= 0) && + cable_type <= SEC_SIZEOF_POWER_SUPPLY_TYPE) { + if (cable_type == POWER_SUPPLY_TYPE_POWER_SHARING) { + wake_lock(&battery->monitor_wake_lock); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } else if((cable_type == POWER_SUPPLY_TYPE_BATTERY) + && battery->ps_status) { + battery->ps_status = false; + dev_info(battery->dev, + "%s: power sharing cable plugout (%d)\n", __func__, battery->ps_status); + wake_lock(&battery->monitor_wake_lock); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } else if(cable_type != battery->cable_type) { + wake_lock(&battery->cable_wake_lock); + queue_work(battery->monitor_wqueue, &battery->cable_work); + } else { + dev_info(battery->dev, + "%s: Cable is Not Changed(%d)\n", + __func__, battery->cable_type); + } + } + + pr_info("%s: CMD=%s, attached_dev=%d\n", __func__, cmd, attached_dev); + + return 0; +} +#endif /* CONFIG_MUIC_NOTIFIER */ + +#ifdef CONFIG_OF +static int sec_bat_parse_dt(struct device *dev, + struct sec_battery_info *battery) +{ + struct device_node *np = dev->of_node; + sec_battery_platform_data_t *pdata = battery->pdata; + int ret, len; + unsigned int i; + const u32 *p; + + if (!np) { + pr_info("%s: np NULL\n", __func__); + return 1; + } + + ret = of_property_read_string(np, + "battery,vendor", (char const **)&pdata->vendor); + if (ret) + pr_info("%s: Vendor is Empty\n", __func__); + + ret = of_property_read_string(np, + "battery,charger_name", (char const **)&pdata->charger_name); + if (ret) + pr_info("%s: Vendor is Empty\n", __func__); + + ret = of_property_read_string(np, + "battery,fuelgauge_name", (char const **)&pdata->fuelgauge_name); + if (ret) + pr_info("%s: Vendor is Empty\n", __func__); + + ret = of_property_read_string(np, + "battery,chip_vendor", (char const **)&pdata->chip_vendor); + if (ret) + pr_info("%s: Vendor is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,technology", + &pdata->technology); + + ret = of_property_read_u32(np, "battery,battery_check_type", + &pdata->battery_check_type); + + p = of_get_property(np, "battery,polling_time", &len); + + len = len / sizeof(u32); + + pdata->polling_time = kzalloc(sizeof(*pdata->polling_time) * len, GFP_KERNEL); + + ret = of_property_read_u32_array(np, "battery,polling_time", + pdata->polling_time, len); + + p = of_get_property(np, "battery,temp_table_adc", &len); + + len = len / sizeof(u32); + + pdata->temp_adc_table_size = len; + pdata->temp_amb_adc_table_size = len; + + pdata->temp_adc_table = kzalloc(sizeof(sec_bat_adc_table_data_t) * + pdata->temp_adc_table_size, GFP_KERNEL); + pdata->temp_amb_adc_table = kzalloc(sizeof(sec_bat_adc_table_data_t) * + pdata->temp_adc_table_size, GFP_KERNEL); + + for(i = 0; i < pdata->temp_adc_table_size; i++) { + ret = of_property_read_u32_index(np, "battery,temp_table_adc", i, + &pdata->temp_adc_table[i].adc); + ret = of_property_read_u32_index(np, "battery,temp_table_data", i, + &pdata->temp_adc_table[i].data); + ret = of_property_read_u32_index(np, "battery,temp_table_adc", i, + &pdata->temp_amb_adc_table[i].adc); + ret = of_property_read_u32_index(np, "battery,temp_table_data", i, + &pdata->temp_amb_adc_table[i].data); + } + + p = of_get_property(np, "battery,input_current_limit", &len); + + len = len / sizeof(u32); + + pdata->charging_current = kzalloc(sizeof(sec_charging_current_t) * len, + GFP_KERNEL); + + for(i = 0; i < len; i++) { + ret = of_property_read_u32_index(np, + "battery,input_current_limit", i, + &pdata->charging_current[i].input_current_limit); + ret = of_property_read_u32_index(np, + "battery,fast_charging_current", i, + &pdata->charging_current[i].fast_charging_current); + ret = of_property_read_u32_index(np, + "battery,full_check_current_1st", i, + &pdata->charging_current[i].full_check_current_1st); + ret = of_property_read_u32_index(np, + "battery,full_check_current_2nd", i, + &pdata->charging_current[i].full_check_current_2nd); + } + + ret = of_property_read_u32(np, "battery,adc_check_count", + &pdata->adc_check_count); + + ret = of_property_read_u32(np, "battery,temp_adc_type", + &pdata->temp_adc_type); + + ret = of_property_read_u32(np, "battery,cable_check_type", + &pdata->cable_check_type); + + ret = of_property_read_u32(np, "battery,cable_source_type", + &pdata->cable_source_type); + + ret = of_property_read_u32(np, "battery,event_waiting_time", + &pdata->event_waiting_time); + + ret = of_property_read_u32(np, "battery,polling_type", + &pdata->polling_type); + + ret = of_property_read_u32(np, "battery,monitor_initial_count", + &pdata->monitor_initial_count); + + ret = of_property_read_u32(np, "battery,check_count", + &pdata->check_count); + + ret = of_property_read_u32(np, "battery,check_adc_max", + &pdata->check_adc_max); + + ret = of_property_read_u32(np, "battery,check_adc_min", + &pdata->check_adc_min); + + ret = of_property_read_u32(np, "battery,ovp_uvlo_check_type", + &pdata->ovp_uvlo_check_type); + + ret = of_property_read_u32(np, "battery,thermal_source", + &pdata->thermal_source); + + ret = of_property_read_u32(np, "battery,temp_check_type", + &pdata->temp_check_type); + + ret = of_property_read_u32(np, "battery,temp_check_count", + &pdata->temp_check_count); + + ret = of_property_read_u32(np, "battery,temp_high_threshold_event", + &pdata->temp_high_threshold_event); + + ret = of_property_read_u32(np, "battery,temp_high_recovery_event", + &pdata->temp_high_recovery_event); + + ret = of_property_read_u32(np, "battery,temp_low_threshold_event", + &pdata->temp_low_threshold_event); + + ret = of_property_read_u32(np, "battery,temp_low_recovery_event", + &pdata->temp_low_recovery_event); + + ret = of_property_read_u32(np, "battery,temp_high_threshold_event", + &pdata->temp_high_threshold_normal); + + ret = of_property_read_u32(np, "battery,temp_high_recovery_event", + &pdata->temp_high_recovery_normal); + + ret = of_property_read_u32(np, "battery,temp_low_threshold_event", + &pdata->temp_low_threshold_normal); + + ret = of_property_read_u32(np, "battery,temp_low_recovery_event", + &pdata->temp_low_recovery_normal); + + ret = of_property_read_u32(np, "battery,temp_high_threshold_event", + &pdata->temp_high_threshold_lpm); + + ret = of_property_read_u32(np, "battery,temp_high_recovery_event", + &pdata->temp_high_recovery_lpm); + + ret = of_property_read_u32(np, "battery,temp_low_threshold_event", + &pdata->temp_low_threshold_lpm); + + ret = of_property_read_u32(np, "battery,temp_low_recovery_event", + &pdata->temp_low_recovery_lpm); + + ret = of_property_read_u32(np, "battery,full_check_type", + &pdata->full_check_type); + + ret = of_property_read_u32(np, "battery,full_check_type_2nd", + &pdata->full_check_type_2nd); + + ret = of_property_read_u32(np, "battery,full_check_count", + &pdata->full_check_count); + + ret = of_property_read_u32(np, "battery,chg_gpio_full_check", + &pdata->chg_gpio_full_check); + + ret = of_property_read_u32(np, "battery,chg_polarity_full_check", + &pdata->chg_polarity_full_check); + + ret = of_property_read_u32(np, "battery,full_condition_type", + &pdata->full_condition_type); + + ret = of_property_read_u32(np, "battery,full_condition_soc", + &pdata->full_condition_soc); + + ret = of_property_read_u32(np, "battery,full_condition_vcell", + &pdata->full_condition_vcell); + + ret = of_property_read_u32(np, "battery,recharge_check_count", + &pdata->recharge_check_count); + + ret = of_property_read_u32(np, "battery,recharge_condition_type", + &pdata->recharge_condition_type); + + ret = of_property_read_u32(np, "battery,recharge_condition_soc", + &pdata->recharge_condition_soc); + + ret = of_property_read_u32(np, "battery,recharge_condition_vcell", + &pdata->recharge_condition_vcell); + + ret = of_property_read_u32(np, "battery,charging_total_time", + (unsigned int *)&pdata->charging_total_time); + + ret = of_property_read_u32(np, "battery,recharging_total_time", + (unsigned int *)&pdata->recharging_total_time); + + ret = of_property_read_u32(np, "battery,charging_reset_time", + (unsigned int *)&pdata->charging_reset_time); + + ret = of_property_read_u32(np, "battery,charging_reset_time", + (unsigned int *)&pdata->charging_reset_time); + + pr_info("%s: vendor : %s, technology : %d, cable_check_type : %d\n" + "cable_source_type : %d, event_waiting_time : %d\n" + "polling_type : %d, initial_count : %d, check_count : %d\n" + "check_adc_max : %d, check_adc_min : %d\n" + "ovp_uvlo_check_type : %d, thermal_source : %d\n" + "temp_check_type : %d, temp_check_count : %d\n" + "temp_high_threshold_even : %d, temp_high_recovery_event : %d\n", + __func__, + pdata->vendor, pdata->technology,pdata->cable_check_type, + pdata->cable_source_type, pdata->event_waiting_time, + pdata->polling_type, pdata->monitor_initial_count, + pdata->check_count, pdata->check_adc_max, pdata->check_adc_min, + pdata->ovp_uvlo_check_type, pdata->thermal_source, + pdata->temp_check_type, pdata->temp_check_count, + pdata->temp_high_threshold_event, pdata->temp_high_recovery_event); + + return ret; +} +#endif + +#ifdef CONFIG_OF +extern sec_battery_platform_data_t sec_battery_pdata; +#endif + +static int sec_battery_probe(struct platform_device *pdev) +{ + sec_battery_platform_data_t *pdata = NULL; + struct sec_battery_info *battery; + int ret = 0; +#ifndef CONFIG_OF + int i; +#endif + + union power_supply_propval value; + + dev_dbg(&pdev->dev, + "%s: SEC Battery Driver Loading\n", __func__); + + battery = kzalloc(sizeof(*battery), GFP_KERNEL); + if (!battery) + return -ENOMEM; + + if (pdev->dev.of_node) { + pdata = devm_kzalloc(&pdev->dev, + sizeof(sec_battery_platform_data_t), + GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, "Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_bat_free; + } + + battery->pdata = pdata; +#ifdef CONFIG_OF + if (sec_bat_parse_dt(&pdev->dev, battery)) + dev_err(&pdev->dev, + "%s: Failed to get battery init\n", __func__); +#endif + } else { + pdata = dev_get_platdata(&pdev->dev); + battery->pdata = pdata; + } + + platform_set_drvdata(pdev, battery); + + battery->dev = &pdev->dev; + + mutex_init(&battery->adclock); + + dev_dbg(battery->dev, "%s: ADC init\n", __func__); + +#ifdef CONFIG_OF + // adc_init(pdev, battery); +#else + for (i = 0; i < SEC_BAT_ADC_CHANNEL_FULL_CHECK; i++) + adc_init(pdev, pdata, i); +#endif + + 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; + battery->charging_fullcharged_time = 0; + battery->siop_level = 100; + battery->wc_enable = 1; +#if defined(CONFIG_SAMSUNG_BATTERY_ENG_TEST) + battery->stability_test = 0; + battery->eng_not_full_status = 0; +#endif + battery->wc_status = 0; + battery->ps_status= 0; + battery->wire_status = POWER_SUPPLY_TYPE_BATTERY; + +#if defined(ANDROID_ALARM_ACTIVATED) + alarm_init(&battery->event_termination_alarm, + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + sec_bat_event_expired_timer_func); +#else + alarm_init(&battery->event_termination_alarm, + ALARM_BOOTTIME, + sec_bat_event_expired_timer_func); +#endif + + 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->is_recharging = false; + battery->cable_type = POWER_SUPPLY_TYPE_BATTERY; + battery->test_mode = 0; + battery->factory_mode = false; + battery->slate_mode = 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; + battery->psy_wireless.name = "wireless", + battery->psy_wireless.type = POWER_SUPPLY_TYPE_WIRELESS, + battery->psy_wireless.supplied_to = supply_list, + battery->psy_wireless.num_supplicants = ARRAY_SIZE(supply_list), + battery->psy_wireless.properties = sec_power_props, + battery->psy_wireless.num_properties = ARRAY_SIZE(sec_power_props), + battery->psy_wireless.get_property = sec_wireless_get_property; + battery->psy_wireless.set_property = sec_wireless_set_property; + battery->psy_ps.name = "ps", + battery->psy_ps.type = POWER_SUPPLY_TYPE_POWER_SHARING, + battery->psy_ps.supplied_to = supply_list, + battery->psy_ps.num_supplicants = ARRAY_SIZE(supply_list), + battery->psy_ps.properties = sec_ps_props, + battery->psy_ps.num_properties = ARRAY_SIZE(sec_ps_props), + battery->psy_ps.get_property = sec_ps_get_property; + battery->psy_ps.set_property = sec_ps_set_property; + + /* 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_wake_lock; + } + + INIT_DELAYED_WORK(&battery->monitor_work, sec_bat_monitor_work); + INIT_WORK(&battery->cable_work, sec_bat_cable_work); + + switch (pdata->polling_type) { + case SEC_BATTERY_MONITOR_WORKQUEUE: + INIT_DELAYED_WORK(&battery->polling_work, + sec_bat_polling_work); + break; + case SEC_BATTERY_MONITOR_ALARM: +#if defined(ANDROID_ALARM_ACTIVATED) + battery->last_poll_time = alarm_get_elapsed_realtime(); + alarm_init(&battery->polling_alarm, + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + sec_bat_alarm); +#else + battery->last_poll_time = ktime_get_boottime(); + alarm_init(&battery->polling_alarm, ALARM_BOOTTIME, + sec_bat_alarm); +#endif + break; + + default: + break; + } + + /* init power supplier framework */ + ret = power_supply_register(&pdev->dev, &battery->psy_ps); + if (ret) { + dev_err(battery->dev, + "%s: Failed to Register psy_ps\n", __func__); + goto err_workqueue; + } + + ret = power_supply_register(&pdev->dev, &battery->psy_wireless); + if (ret) { + dev_err(battery->dev, + "%s: Failed to Register psy_wireless\n", __func__); + goto err_supply_unreg_ps; + } + + 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_wireless; + } + + 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; + } + + 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_supply_unreg_ac; + } + + if (battery->pdata->bat_gpio_init && !battery->pdata->bat_gpio_init()) { + dev_err(battery->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_supply_unreg_bat; + } +#if 0 + 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 Request IRQ, %d, %d\n", __func__, battery->pdata->bat_irq, ret); + goto err_supply_unreg_ac; + } + + 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); + } +#endif + + ret = sec_bat_create_attrs(battery->psy_bat.dev); + if (ret) { + dev_err(battery->dev, + "%s : Failed to create_attrs\n", __func__); + goto err_req_irq; + } + //sprdbat_creat_caliberate_attr(battery->psy_bat.dev); + +#if defined(CONFIG_MUIC_NOTIFIER) + muic_notifier_register(&battery->batt_nb, + batt_handle_notification, + MUIC_NOTIFY_DEV_CHARGER); +#endif + + if (pdata->initial_check) + pdata->initial_check(battery); + else + sec_bat_initial_check(); + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_ONLINE, value); + + if (value.intval == POWER_SUPPLY_TYPE_BATTERY) { + dev_info(&pdev->dev, + "%s: SEC Battery Driver Monitorwork\n", __func__); + wake_lock(&battery->monitor_wake_lock); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } + + if (battery->pdata->check_battery_callback) + battery->present = battery->pdata->check_battery_callback(battery); + + dev_info(battery->dev, + "%s: SEC Battery Driver Loaded\n", __func__); + //sec_bat_set_charge(battery, true); + return 0; + +err_req_irq: + if (battery->pdata->bat_irq) + free_irq(battery->pdata->bat_irq, battery); +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_supply_unreg_wireless: + power_supply_unregister(&battery->psy_wireless); +err_supply_unreg_ps: + power_supply_unregister(&battery->psy_ps); +err_workqueue: + destroy_workqueue(battery->monitor_wqueue); +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(pdata); +err_bat_free: + kfree(battery); + + return ret; +} + +static int sec_battery_remove(struct platform_device *pdev) +{ + struct sec_battery_info *battery = platform_get_drvdata(pdev); +#ifndef CONFIG_OF + int i; +#endif + + dev_dbg(battery->dev, "%s: Start\n", __func__); + + 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; + } + + alarm_cancel(&battery->event_termination_alarm); + 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); +#ifdef CONFIG_OF + // adc_exit(battery); +#else + for (i = 0; i < SEC_BAT_ADC_CHANNEL_FULL_CHECK; i++) + adc_exit(battery->pdata, i); +#endif + + power_supply_unregister(&battery->psy_ps); + power_supply_unregister(&battery->psy_wireless); + power_supply_unregister(&battery->psy_ac); + power_supply_unregister(&battery->psy_usb); + power_supply_unregister(&battery->psy_bat); + + dev_dbg(battery->dev, "%s: End\n", __func__); + kfree(battery); + + return 0; +} + +static int sec_battery_prepare(struct device *dev) +{ + struct sec_battery_info *battery + = dev_get_drvdata(dev); + + dev_dbg(battery->dev, "%s: Start\n", __func__); + + 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; + } + cancel_delayed_work_sync(&battery->monitor_work); + + battery->polling_in_sleep = true; + + sec_bat_set_polling(battery); + + /* cancel work for polling + * that is set in sec_bat_set_polling() + * no need for polling in sleep + */ + if (battery->pdata->polling_type == + SEC_BATTERY_MONITOR_WORKQUEUE) + cancel_delayed_work(&battery->polling_work); + + dev_dbg(battery->dev, "%s: End\n", __func__); + + 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); + + dev_dbg(battery->dev, "%s: Start\n", __func__); + + /* cancel current alarm and reset after monitor work */ + if (battery->pdata->polling_type == SEC_BATTERY_MONITOR_ALARM) + alarm_cancel(&battery->polling_alarm); + + wake_lock(&battery->monitor_wake_lock); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, msecs_to_jiffies(500)); + + dev_dbg(battery->dev, "%s: End\n", __func__); + + return; +} + +static void sec_battery_shutdown(struct device *dev) +{ +} + +#ifdef CONFIG_OF +static struct of_device_id sec_battery_dt_ids[] = { + { .compatible = "samsung,sec-battery" }, + { } +}; +MODULE_DEVICE_TABLE(of, sec_battery_dt_ids); +#endif /* CONFIG_OF */ + +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, +#ifdef CONFIG_OF + .of_match_table = sec_battery_dt_ids, +#endif + }, + .probe = sec_battery_probe, + .remove = 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"); diff --git a/drivers/battery/sec_charger.c b/drivers/battery/sec_charger.c new file mode 100755 index 00000000..1709dfa5 --- /dev/null +++ b/drivers/battery/sec_charger.c @@ -0,0 +1,745 @@ +/* + * sec_charger.c + * Samsung Mobile Charger 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/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_irq.h> +#include <linux/interrupt.h> +#include <linux/battery/sec_charger.h> + +extern int sec_chg_dt_init(struct device_node *np, + struct device *dev, + sec_battery_platform_data_t *pdata); + +static struct device_attribute sec_charger_attrs[] = { + SEC_CHARGER_ATTR(reg), + SEC_CHARGER_ATTR(data), + SEC_CHARGER_ATTR(regs), +}; + +static enum power_supply_property sec_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, +#if defined(CONFIG_FUELGAUGE_88PM822) || \ + defined(CONFIG_FUELGAUGE_88PM800) || \ + defined(CONFIG_FUELGAUGE_SPRD4SAMSUNG27X3) + POWER_SUPPLY_PROP_POWER_STATUS, + POWER_SUPPLY_PROP_CHARGE_NOW, +#endif +}; + +static int input_current[] = { + 800, + 800, + 800, +}; + +static int sec_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_charger_info *charger = + container_of(psy, struct sec_charger_info, psy_chg); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: /* input current limit set */ + val->intval = charger->charging_current_max; + break; + + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_HEALTH: + case POWER_SUPPLY_PROP_CURRENT_AVG: /* charging current */ + /* calculated input current limit value */ + case POWER_SUPPLY_PROP_CURRENT_NOW: +#if defined(CONFIG_FUELGAUGE_88PM822) || \ + defined(CONFIG_FUELGAUGE_88PM800) || \ + defined(CONFIG_FUELGAUGE_SPRD4SAMSUNG27X3) + case POWER_SUPPLY_PROP_POWER_STATUS: + case POWER_SUPPLY_PROP_CHARGE_NOW: +#endif + if (!sec_hal_chg_get_property(charger_variable, psp, val)) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = charger->charging_current * val->intval / 100; + break; + case POWER_SUPPLY_PROP_PRESENT: + if (!sec_hal_chg_get_property(charger_variable, psp, val)) + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_chg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_charger_info *charger = + container_of(psy, struct sec_charger_info, psy_chg); + union power_supply_propval input_value; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + charger->status = val->intval; + break; + + /* val->intval : type */ + case POWER_SUPPLY_PROP_ONLINE: + charger->cable_type = val->intval; + if (val->intval == POWER_SUPPLY_TYPE_BATTERY) + charger->is_charging = false; + else + charger->is_charging = true; + + /* current setting */ + if ((charger->pdata->siop_activated) && + ((charger->cable_type == POWER_SUPPLY_TYPE_MAINS) || + (charger->cable_type == POWER_SUPPLY_TYPE_MISC))) { + charger->charging_current_max = input_current[ + charger->pdata->siop_level]; + } else { + charger->charging_current_max = + charger->pdata->charging_current[ + val->intval].input_current_limit; + } + + charger->charging_current = + charger->pdata->charging_current[ + val->intval].fast_charging_current; + + if (!sec_hal_chg_set_property(charger_variable, psp, val)) + return -EINVAL; + break; + + /* val->intval : input current limit set */ + case POWER_SUPPLY_PROP_CURRENT_MAX: + charger->charging_current_max = val->intval; + /* to control charging current, + * use input current limit and set charging current as much as possible + * so we only control input current limit to control charge current + */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (!sec_hal_chg_set_property(charger_variable, psp, val)) + return -EINVAL; + break; + + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + charger->charging_current = val->intval; + + if (!sec_hal_chg_set_property(charger_variable, psp, val)) + return -EINVAL; + break; + + /* val->intval : SIOP level (%) + * SIOP charging current setting + */ + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + /* change val as charging current by SIOP level + * do NOT change initial charging current setting + */ +#ifdef CONFIG_CHARGER_SM5414 + if (!sec_hal_chg_set_property(charger_variable, psp, val)) + return -EINVAL; +#else + input_value.intval = + charger->charging_current * val->intval / 100; + + /* charging current should be over than USB charging current */ + if (charger->pdata->chg_functions_setting & + SEC_CHARGER_MINIMUM_SIOP_CHARGING_CURRENT) { + if (input_value.intval > 0 && + input_value.intval < + charger->pdata->charging_current[ + POWER_SUPPLY_TYPE_USB].fast_charging_current) + input_value.intval = + charger->pdata->charging_current[ + POWER_SUPPLY_TYPE_USB].fast_charging_current; + } + + /* set charging current as new value */ + if (!sec_hal_chg_set_property(charger_variable, + POWER_SUPPLY_PROP_CURRENT_AVG, &input_value)) + return -EINVAL; +#endif + break; +#ifdef CONFIG_CHARGER_SM5414 + case POWER_SUPPLY_PROP_HEALTH: + if (!sec_hal_chg_set_property(charger_variable, psp, val)) + return -EINVAL; + break; +#endif + + default: + return -EINVAL; + } + return 0; +} + +static void sec_chg_isr_work(struct work_struct *work) +{ + struct sec_charger_info *charger = + container_of(work, struct sec_charger_info, isr_work.work); + union power_supply_propval val; + int full_check_type; + + dev_info(&charger->client->dev, + "%s: Charger Interrupt\n", __func__); + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_CHARGE_NOW, val); + if (val.intval == SEC_BATTERY_CHARGING_1ST) + full_check_type = charger->pdata->full_check_type; + else + full_check_type = charger->pdata->full_check_type_2nd; + + if (full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) { + if (!sec_hal_chg_get_property(charger_variable, + POWER_SUPPLY_PROP_STATUS, &val)) + return; + + switch (val.intval) { + case POWER_SUPPLY_STATUS_DISCHARGING: + dev_err(&charger->client->dev, + "%s: Interrupted but Discharging\n", __func__); + break; + + case POWER_SUPPLY_STATUS_NOT_CHARGING: + dev_err(&charger->client->dev, + "%s: Interrupted but NOT Charging\n", __func__); + break; + + case POWER_SUPPLY_STATUS_FULL: + dev_info(&charger->client->dev, + "%s: Interrupted by Full\n", __func__); + +#if defined(CONFIG_CHARGER_BQ24157) + charger->cable_type = POWER_SUPPLY_TYPE_BATTERY; + charger->is_charging = false; + if (!sec_hal_chg_set_property(charger_variable, POWER_SUPPLY_PROP_ONLINE, &val)) + dev_err(&charger->client->dev, + "%s: Charging disable error\n", __func__); +#endif + + psy_do_property("battery", set, + POWER_SUPPLY_PROP_STATUS, val); + break; + + case POWER_SUPPLY_STATUS_CHARGING: + dev_err(&charger->client->dev, + "%s: Interrupted but Charging\n", __func__); + break; + + case POWER_SUPPLY_STATUS_UNKNOWN: + default: + dev_err(&charger->client->dev, + "%s: Invalid Charger Status\n", __func__); + break; + } + } + + if (charger->pdata->ovp_uvlo_check_type == + SEC_BATTERY_OVP_UVLO_CHGINT) { +#if !defined(CONFIG_CHARGER_SM5414) + if (!sec_hal_chg_get_property(charger_variable, + POWER_SUPPLY_PROP_HEALTH, &val)) + return; +#endif + +#if defined(CONFIG_CHARGER_SM5414) + msleep(200); + val.intval = sec_get_charging_health(charger->client); +#endif + + switch (val.intval) { + case POWER_SUPPLY_HEALTH_OVERHEAT: + case POWER_SUPPLY_HEALTH_COLD: + dev_err(&charger->client->dev, + "%s: Interrupted but Hot/Cold\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_DEAD: + dev_err(&charger->client->dev, + "%s: Interrupted but Dead\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + case POWER_SUPPLY_HEALTH_UNDERVOLTAGE: + dev_info(&charger->client->dev, + "%s: Interrupted by OVP/UVLO\n", __func__); + psy_do_property("battery", set, + POWER_SUPPLY_PROP_HEALTH, val); + break; + + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + dev_err(&charger->client->dev, + "%s: Interrupted but Unspec\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_GOOD: + dev_err(&charger->client->dev, + "%s: Interrupted but Good\n", __func__); + psy_do_property("battery", set, + POWER_SUPPLY_PROP_HEALTH, val); + break; + + case POWER_SUPPLY_HEALTH_UNKNOWN: + default: + dev_err(&charger->client->dev, + "%s: Invalid Charger Health\n", __func__); + break; + } + } + + if (charger->pdata->cable_check_type & SEC_BATTERY_CABLE_CHECK_CHGINT) { + if (!sec_hal_chg_get_property(charger_variable, + POWER_SUPPLY_PROP_ONLINE, &val)) + return; + + /* use SEC_BATTERY_CABLE_SOURCE_EXTERNAL for cable_source_type + * charger would call battery driver to set ONLINE property + * check battery driver loaded or not + */ + if (get_power_supply_by_name("battery")) { + psy_do_property("battery", set, + POWER_SUPPLY_PROP_ONLINE, val); + } else + charger->pdata->check_cable_result_callback(val.intval); + } +} + +static irqreturn_t sec_chg_irq_thread(int irq, void *irq_data) +{ + struct sec_charger_info *charger = irq_data; +#if defined(CONFIG_CHARGER_BQ24157) + schedule_delayed_work(&charger->isr_work, HZ * 0.5); +#else + schedule_delayed_work(&charger->isr_work, 0); +#endif + + return IRQ_HANDLED; +} + +static int sec_chg_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(sec_charger_attrs); i++) { + rc = device_create_file(dev, &sec_charger_attrs[i]); + if (rc) + goto create_attrs_failed; + } + goto create_attrs_succeed; + +create_attrs_failed: + dev_err(dev, "%s: failed (%d)\n", __func__, rc); + while (i--) + device_remove_file(dev, &sec_charger_attrs[i]); +create_attrs_succeed: + return rc; +} + +ssize_t sec_chg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const ptrdiff_t offset = attr - sec_charger_attrs; + int i = 0; + + switch (offset) { + case CHG_REG: + case CHG_DATA: + case CHG_REGS: + i = sec_hal_chg_show_attrs(dev, offset, buf); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t sec_chg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + const ptrdiff_t offset = attr - sec_charger_attrs; + int ret = 0; + + switch (offset) { + case CHG_REG: + case CHG_DATA: + ret = sec_hal_chg_store_attrs(dev, offset, buf, count); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +#if defined(CONFIG_CHARGER_MFD) +static int sec_charger_probe(struct platform_device *pdev) +{ + struct sec_charger_info *charger; + sec_charger_dev_t *mfd_dev = dev_get_drvdata(pdev->dev.parent); + sec_charger_pdata_t *pdata = dev_get_platdata(mfd_dev->dev); + int ret = 0; + + dev_info(&pdev->dev, + "%s: SEC Charger Driver Loading\n", __func__); + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + platform_set_drvdata(pdev, charger); + + charger->client = mfd_dev->i2c; + charger->pdata = pdata->charger_data; + + charger->psy_chg.name = "sec-charger"; + charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN; + charger->psy_chg.get_property = sec_chg_get_property; + charger->psy_chg.set_property = sec_chg_set_property; + charger->psy_chg.properties = sec_charger_props; + charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props); + + if (charger->pdata->chg_gpio_init) { + if (!charger->pdata->chg_gpio_init()) { + dev_err(&pdev->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_free; + } + } + + if (!sec_hal_chg_init(charger)) { + dev_err(&pdev->dev, + "%s: Failed to Initialize Charger\n", __func__); + goto err_free; + } + + ret = power_supply_register(&pdev->dev, &charger->psy_chg); + if (ret) { + dev_err(&pdev->dev, + "%s: Failed to Register psy_chg\n", __func__); + goto err_free; + } + + if (charger->pdata->chg_irq) { + INIT_DELAYED_WORK_DEFERRABLE( + &charger->isr_work, sec_chg_isr_work); + + ret = request_threaded_irq(charger->pdata->chg_irq, + NULL, sec_chg_irq_thread, + charger->pdata->chg_irq_attr, + "charger-irq", charger); + if (ret) { + dev_err(&pdev->dev, + "%s: Failed to Reqeust IRQ\n", __func__); + goto err_supply_unreg; + } + + ret = enable_irq_wake(charger->pdata->chg_irq); + if (ret < 0) + dev_err(&pdev->dev, + "%s: Failed to Enable Wakeup Source(%d)\n", + __func__, ret); + } + + ret = sec_chg_create_attrs(charger->psy_chg.dev); + if (ret) { + dev_err(&pdev->dev, + "%s : Failed to create_attrs\n", __func__); + goto err_req_irq; + } + + dev_info(&pdev->dev, + "%s: SEC Charger Driver Loaded\n", __func__); + return 0; + +err_req_irq: + if (charger->pdata->chg_irq) + free_irq(charger->pdata->chg_irq, charger); +err_supply_unreg: + power_supply_unregister(&charger->psy_chg); +err_free: + kfree(charger); + + return ret; +} + +static int sec_charger_remove(struct platform_device *pdev) +{ + return 0; +} + +static int sec_charger_suspend(struct device *dev) +{ + struct sec_charger_info *charger = dev_get_drvdata(dev); + + if (!sec_hal_chg_suspend(charger)) + dev_err(dev, "%s: Failed to Suspend Charger\n", __func__); + + return 0; +} + +static int sec_charger_resume(struct device *dev) +{ + struct sec_charger_info *charger = dev_get_drvdata(dev); + + if (!sec_hal_chg_resume(charger)) + dev_err(dev, "%s: Failed to Resume Charger\n", __func__); + + return 0; +} + +static void sec_charger_shutdown(struct device *dev) +{ + if (!sec_hal_chg_shutdown(client)) + dev_err(&client->dev, + "%s: Failed to Shutdown Charger\n", __func__); +} + +static const struct dev_pm_ops sec_charger_pm_ops = { + .suspend = sec_charger_suspend, + .resume = sec_charger_resume, +}; + +static struct platform_driver sec_charger_driver = { + .driver = { + .name = "sec-charger", + .owner = THIS_MODULE, + .pm = &sec_charger_pm_ops, + .shutdown = sec_charger_shutdown, + }, + .probe = sec_charger_probe, + .remove = sec_charger_remove, +}; + +static int __init sec_charger_init(void) +{ + pr_info("[%s]!!!!!!!! \n", __func__); + return platform_driver_register(&sec_charger_driver); +} + +static void __exit sec_charger_exit(void) +{ + platform_driver_unregister(&sec_charger_driver); +} + +#else + +static struct of_device_id sec_charger_dt_ids[] = { + { .compatible = "samsung,sec-charger", }, + {} +}; +MODULE_DEVICE_TABLE(of, sec_charger_dt_ids); + +static int sec_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + sec_battery_platform_data_t *pdata = client->dev.platform_data; + struct sec_charger_info *charger; + struct i2c_adapter *adapter = + to_i2c_adapter(client->dev.parent); + int ret = 0; + struct device_node *np = client->dev.of_node; + + dev_info(&client->dev, + "%s: SEC Charger Driver Loading\n", __func__); + + + if (IS_ENABLED(CONFIG_OF)) { + if (!pdata) + pdata = kzalloc(sizeof(sec_battery_platform_data_t), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + ret = sec_chg_dt_init(np, &client->dev, pdata); + if (ret) + return ret; + } else if (!pdata) { + dev_err(&client->dev, "%s: no platform data defined\n", + __func__); + return -EINVAL; + } + + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger->client = client; + charger->pdata = pdata; + + i2c_set_clientdata(client, charger); + + charger->psy_chg.name = "sec-charger"; + charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN; + charger->psy_chg.get_property = sec_chg_get_property; + charger->psy_chg.set_property = sec_chg_set_property; + charger->psy_chg.properties = sec_charger_props; + charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props); + + if (charger->pdata->chg_gpio_init) { + if (!charger->pdata->chg_gpio_init()) { + dev_err(&client->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_free; + } + } + + if (!sec_hal_chg_init(client)) { + dev_err(&client->dev, + "%s: Failed to Initialize Charger\n", __func__); + goto err_free; + } + + ret = power_supply_register(&client->dev, &charger->psy_chg); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Register psy_chg\n", __func__); + goto err_free; + } + + if (charger->pdata->chg_irq) { + INIT_DEFERRABLE_WORK( + &charger->isr_work, sec_chg_isr_work); + + ret = request_threaded_irq(charger->pdata->chg_irq, + NULL, sec_chg_irq_thread, + charger->pdata->chg_irq_attr | IRQF_ONESHOT, + "charger-irq", charger); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Reqeust IRQ\n", __func__); + goto err_supply_unreg; + } + + ret = enable_irq_wake(charger->pdata->chg_irq); + if (ret < 0) + dev_err(&client->dev, + "%s: Failed to Enable Wakeup Source(%d)\n", + __func__, ret); + } + + ret = sec_chg_create_attrs(charger->psy_chg.dev); + if (ret) { + dev_err(&client->dev, + "%s : Failed to create_attrs\n", __func__); + goto err_req_irq; + } + + dev_dbg(&client->dev, + "%s: SEC Charger Driver Loaded\n", __func__); + return 0; + +err_req_irq: + if (charger->pdata->chg_irq) + free_irq(charger->pdata->chg_irq, charger); +err_supply_unreg: + power_supply_unregister(&charger->psy_chg); +err_free: + kfree(charger); + kfree(pdata); + + return ret; +} + +static int sec_charger_remove( + struct i2c_client *client) +{ + return 0; +} + +static int sec_charger_suspend(struct i2c_client *client, + pm_message_t state) +{ + if (!sec_hal_chg_suspend(client)) + dev_err(&client->dev, + "%s: Failed to Suspend Charger\n", __func__); + + return 0; +} + +static int sec_charger_resume(struct i2c_client *client) +{ + if (!sec_hal_chg_resume(client)) + dev_err(&client->dev, + "%s: Failed to Resume Charger\n", __func__); + + return 0; +} + +static void sec_charger_shutdown(struct i2c_client *client) +{ + if (!sec_hal_chg_shutdown(client)) + dev_err(&client->dev, + "%s: Failed to Shutdown Charger\n", __func__); +} + +static const struct i2c_device_id sec_charger_id[] = { + {"sec-charger", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, sec_charger_id); + +static struct i2c_driver sec_charger_driver = { + .driver = { + .name = "sec-charger", + .of_match_table = sec_charger_dt_ids, + }, + .probe = sec_charger_probe, + .remove = sec_charger_remove, + .suspend = sec_charger_suspend, + .resume = sec_charger_resume, + .shutdown = sec_charger_shutdown, + .id_table = sec_charger_id, +}; + +static int __init sec_charger_init(void) +{ + return i2c_add_driver(&sec_charger_driver); +} + +static void __exit sec_charger_exit(void) +{ + i2c_del_driver(&sec_charger_driver); +} +#endif + +module_init(sec_charger_init); +module_exit(sec_charger_exit); + +MODULE_DESCRIPTION("Samsung Charger Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/sec_dt_init.c b/drivers/battery/sec_dt_init.c new file mode 100644 index 00000000..70559605 --- /dev/null +++ b/drivers/battery/sec_dt_init.c @@ -0,0 +1,1154 @@ +/* + * sec_dt_init.c + * Samsung battery data parsing from device tree + * + * Copyright (C) 2014 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 <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_irq.h> +#include <soc/sprd/adc.h> +#include <linux/battery/sec_battery.h> + +#include <soc/sprd/sci_glb_regs.h> +#include <soc/sprd/sci.h> +#include <soc/sprd/adi.h> +#include <soc/sprd/arch_misc.h> +#include <soc/sprd/hardware.h> +#include <linux/delay.h> + +#ifdef CONFIG_MFD_RT8973 +#include <linux/mfd/rt8973.h> +#endif +#ifdef CONFIG_SM5502_MUIC +#include <linux/mfd/sm5502-muic.h> +#endif +#ifdef CONFIG_MFD_SM5504 +#include <linux/mfd/sm5504.h> +#endif +#ifdef CONFIG_FUELGAUGE_SPRD4SAMSUNG27X3 +#include <linux/battery/fuelgauge/sprd27x3_fuelgauge4samsung.h> +#endif + +#define BATT_DET_CURRENT 313 + +static int current_cable_type = POWER_SUPPLY_TYPE_BATTERY; +static u8 attached_cable; +static bool is_jig_on; +/* static unsigned int lpcharge; */ +/* EXPORT_SYMBOL(lpcharge); */ +unsigned int lpcharge; +EXPORT_SYMBOL(lpcharge); +static unsigned chg_irq; +static int gpio_vbat_detect; + +#if defined(CONFIG_INPUT_TOUCHSCREEN) +void (*tsp_charger_status_cb)(int); +EXPORT_SYMBOL(tsp_charger_status_cb); +#endif + +static bool sec_fg_gpio_init(void) +{ + return true; +} + +static bool sec_fg_fuelalert_process(bool is_fuel_alerted) { return true; } + +static bool sec_bat_adc_none_init(struct platform_device *pdev) { return true; } +static bool sec_bat_adc_none_exit(void) { return true; } +static int sec_bat_adc_none_read(unsigned int channel) { return 0; } + +static bool sec_bat_adc_ap_init(struct platform_device *pdev) { return true; } +static bool sec_bat_adc_ap_exit(void) { return true; } + +static bool sec_bat_adc_ic_init(struct platform_device *pdev) { return true; } +static bool sec_bat_adc_ic_exit(void) { return true; } +static int sec_bat_adc_ic_read(unsigned int channel) { return 0; } + +static sec_bat_adc_api_t adc_api[SEC_BATTERY_ADC_TYPE_NUM] = { + { + .init = sec_bat_adc_none_init, + .exit = sec_bat_adc_none_exit, + .read = sec_bat_adc_none_read + }, + { + .init = sec_bat_adc_ap_init, + .exit = sec_bat_adc_ap_exit, + }, + { + .init = sec_bat_adc_ic_init, + .exit = sec_bat_adc_ic_exit, + .read = sec_bat_adc_ic_read + } +}; + +static bool sec_chg_gpio_init(void) +{ + return true; +} + +bool sec_bat_check_cable_result_callback(int cable_type) +{ + bool ret = true; + current_cable_type = cable_type; + + switch (cable_type) { + case POWER_SUPPLY_TYPE_USB: + pr_info("%s set vbus applied\n", + __func__); + break; + case POWER_SUPPLY_TYPE_BATTERY: + pr_info("%s set vbus cut\n", + __func__); + break; + case POWER_SUPPLY_TYPE_MAINS: + break; + default: + pr_err("%s cable type (%d)\n", + __func__, cable_type); + ret = false; + break; + } + + return ret; +} + +static bool sec_bat_ovp_uvlo_result_callback(int health) +{ + return true; +} + +/* Get LP charging mode state */ +static int sec_bat_is_lpm_check(char *str) +{ + if (strncmp(str, "charger", 7) == 0) + lpcharge = 1; + + pr_info("%s: Low power charging mode: %d\n", __func__, lpcharge); + + return lpcharge; +} +__setup("androidboot.mode=", sec_bat_is_lpm_check); + +bool sec_bat_is_lpm(void) +{ + return lpcharge == 1 ? true : false; +} +/* + * val.intval : temperature + */ +static bool sec_bat_get_temperature_callback( + enum power_supply_property psp, + union power_supply_propval *val) +{ + return true; +} + +void check_jig_status(int status) +{ + if (status) { + pr_info("%s: JIG On so reset fuel gauge capacity\n", __func__); + is_jig_on = true; + } + +} + +bool sec_bat_check_jig_status(void) +{ + return is_jig_on; +} + +//int sec_bat_check_cable_callback(void) +int sec_bat_check_cable_callback(struct sec_battery_info *battery) +{ + int ta_nconnected; + union power_supply_propval value; + struct power_supply *psy = power_supply_get_by_name("battery"); + + ta_nconnected = gpio_get_value(chg_irq); + + pr_info("%s : ta_nconnected : %d\n", __func__, ta_nconnected); + + if (ta_nconnected) + current_cable_type = POWER_SUPPLY_TYPE_BATTERY; + else + current_cable_type = POWER_SUPPLY_TYPE_MAINS; + +#ifdef CONFIG_MFD_RT8973 + if (attached_cable == MUIC_RT8973_CABLE_TYPE_0x1A) { + if (ta_nconnected) + current_cable_type = POWER_SUPPLY_TYPE_BATTERY; + else + current_cable_type = POWER_SUPPLY_TYPE_MISC; + } else + return current_cable_type; +#endif + +#ifdef CONFIG_MFD_SM5504 + if (attached_cable == MUIC_SM5504_CABLE_TYPE_0x1A) { + if (ta_nconnected) + current_cable_type = POWER_SUPPLY_TYPE_BATTERY; + else + current_cable_type = POWER_SUPPLY_TYPE_MISC; + } else + return current_cable_type; +#endif + if (!psy || !psy->set_property) + pr_err("%s: fail to get battery psy\n", __func__); + else { + value.intval = current_cable_type; + psy->set_property(psy, POWER_SUPPLY_PROP_ONLINE, &value); + } + + return current_cable_type; +} + +void sec_bat_initial_check(void) +{ + union power_supply_propval value; + + if (POWER_SUPPLY_TYPE_BATTERY < current_cable_type) { + value.intval = current_cable_type; + psy_do_property("battery", set, + POWER_SUPPLY_PROP_ONLINE, value); + } else { + psy_do_property("sec-charger", get, + POWER_SUPPLY_PROP_ONLINE, value); + if (value.intval == POWER_SUPPLY_TYPE_WPC) { + value.intval = POWER_SUPPLY_TYPE_WPC; + psy_do_property("battery", set, + POWER_SUPPLY_PROP_ONLINE, value); + } +#if 0 + else { + value.intval = POWER_SUPPLY_TYPE_BATTERY; + psy_do_property("sec-charger", set, + POWER_SUPPLY_PROP_ONLINE, value); + } +#endif + } +} + +static bool sec_bat_gpio_init(void) +{ + return true; +} + +//static bool sec_bat_check_callback(void) { return true; } +extern bool sec_bat_check_callback(struct sec_battery_info *battery){return true; } +static bool sec_bat_check_result_callback(void) { return true; } + +/* callback for OVP/UVLO check + * return : int + * battery health + */ +static int sec_bat_ovp_uvlo_callback(void) +{ + int health; + health = POWER_SUPPLY_HEALTH_GOOD; + + return health; +} + +#if defined(CONFIG_SENSORS_GRIP_SX9500) && defined(CONFIG_MACH_DEGAS) +extern void charger_status_cb(int status); +#endif + +#ifdef CONFIG_MFD_RT8973 +void sec_charger_cb(u8 cable_type) +{ + union power_supply_propval value; + struct power_supply *psy = power_supply_get_by_name("battery"); + + pr_info("%s: cable type (0x%02x)\n", __func__, cable_type); + + attached_cable = cable_type; + + switch (cable_type) { + case MUIC_RT8973_CABLE_TYPE_NONE: + case MUIC_RT8973_CABLE_TYPE_JIG_UART_OFF: + current_cable_type = POWER_SUPPLY_TYPE_BATTERY; + break; + case MUIC_RT8973_CABLE_TYPE_USB: + case MUIC_RT8973_CABLE_TYPE_L_USB: + case MUIC_RT8973_CABLE_TYPE_CDP: + case MUIC_RT8973_CABLE_TYPE_JIG_USB_ON: + case MUIC_RT8973_CABLE_TYPE_JIG_USB_OFF: + current_cable_type = POWER_SUPPLY_TYPE_USB; + break; + case MUIC_RT8973_CABLE_TYPE_REGULAR_TA: + case MUIC_RT8973_CABLE_TYPE_ATT_TA: + case MUIC_RT8973_CABLE_TYPE_JIG_UART_OFF_WITH_VBUS: + case MUIC_RT8973_CABLE_TYPE_JIG_UART_ON_WITH_VBUS: + case MUIC_RT8973_CABLE_TYPE_TYPE1_CHARGER: + current_cable_type = POWER_SUPPLY_TYPE_MAINS; + break; + case MUIC_RT8973_CABLE_TYPE_OTG: + goto skip; + case MUIC_RT8973_CABLE_TYPE_0x15: + case MUIC_RT8973_CABLE_TYPE_0x1A: + //case MUIC_RT8973_CABLE_TYPE_0x1A_VBUS: + current_cable_type = POWER_SUPPLY_TYPE_MISC; + break; + default: + pr_err("%s: invalid type for charger:%d\n", + __func__, cable_type); + current_cable_type = POWER_SUPPLY_TYPE_UNKNOWN; + goto skip; + } + + if (!psy || !psy->set_property) + pr_err("%s: fail to get battery psy\n", __func__); + else { + value.intval = current_cable_type; + psy->set_property(psy, POWER_SUPPLY_PROP_ONLINE, &value); + +#if defined(CONFIG_INPUT_TOUCHSCREEN) + /* for changing of tsp operation mode */ + if (tsp_charger_status_cb) + tsp_charger_status_cb(current_cable_type); +#endif + } + +skip: + return 0; +} +#endif + +#ifdef CONFIG_SM5502_MUIC +void sec_charger_cb(u8 cable_type) +{ + + union power_supply_propval value; + struct power_supply *psy = power_supply_get_by_name("battery"); + + pr_info("%s: cable type (0x%02x)\n", __func__, cable_type); + + attached_cable = cable_type; + is_jig_on = false; + + switch (cable_type) { + case CABLE_TYPE_NONE_MUIC: + case CABLE_TYPE2_DESKDOCK_MUIC: + current_cable_type = POWER_SUPPLY_TYPE_BATTERY; + break; + case CABLE_TYPE1_USB_MUIC: + current_cable_type = POWER_SUPPLY_TYPE_USB; + break; + case CABLE_TYPE1_TA_MUIC: + case CABLE_TYPE3_U200CHG_MUIC: + case CABLE_TYPE3_NONSTD_SDP_MUIC: + pr_info("%s: VBUS Online\n", __func__); + current_cable_type = POWER_SUPPLY_TYPE_MAINS; + break; + case CABLE_TYPE2_JIG_UART_OFF_VB_MUIC: + case CABLE_TYPE2_JIG_UART_ON_VB_MUIC: + is_jig_on = true; + current_cable_type = POWER_SUPPLY_TYPE_MAINS; + break; + case CABLE_TYPE1_OTG_MUIC: + goto skip; + case CABLE_TYPE2_JIG_UART_OFF_MUIC: + is_jig_on = true; + current_cable_type = POWER_SUPPLY_TYPE_BATTERY; + break; + case CABLE_TYPE2_JIG_USB_ON_MUIC: + case CABLE_TYPE2_JIG_USB_OFF_MUIC: + is_jig_on = true; + current_cable_type = POWER_SUPPLY_TYPE_USB; + break; + case CABLE_TYPE1_CARKIT_T1OR2_MUIC: + current_cable_type = POWER_SUPPLY_TYPE_USB; + break; + case CABLE_TYPE3_DESKDOCK_VB_MUIC: + current_cable_type = POWER_SUPPLY_TYPE_MISC; + break; + default: + pr_err("%s: invalid type for charger:%d\n", + __func__, cable_type); + current_cable_type = POWER_SUPPLY_TYPE_UNKNOWN; + goto skip; + } + + if (!psy || !psy->set_property) + pr_err("%s: fail to get battery psy\n", __func__); + else { + value.intval = current_cable_type; + psy->set_property(psy, POWER_SUPPLY_PROP_ONLINE, &value); + +#if defined(CONFIG_INPUT_TOUCHSCREEN) + /* for changing of tsp operation mode */ + if (tsp_charger_status_cb) + tsp_charger_status_cb(current_cable_type); +#endif + } +#if defined(CONFIG_SENSORS_GRIP_SX9500) && defined(CONFIG_MACH_DEGAS) + /* for grip sensor threshold change at TA/USB status - sx9500.c */ + charger_status_cb(current_cable_type); +#endif + +skip: + return; +} +#endif /* CONFIG_SM5502_MUIC */ + + +#ifdef CONFIG_MFD_SM5504 +void sec_charger_cb(u8 cable_type) +{ + union power_supply_propval value; + struct power_supply *psy = power_supply_get_by_name("battery"); + + pr_info("%s: cable type (0x%02x)\n", __func__, cable_type); + + attached_cable = cable_type; + + switch (cable_type) { + case MUIC_SM5504_CABLE_TYPE_NONE: + case MUIC_SM5504_CABLE_TYPE_JIG_UART_ON: + case MUIC_SM5504_CABLE_TYPE_JIG_UART_OFF: + case MUIC_SM5504_CABLE_TYPE_JIG_UART_ON_WITH_VBUS: + current_cable_type = POWER_SUPPLY_TYPE_BATTERY; + break; + case MUIC_SM5504_CABLE_TYPE_USB: + case MUIC_SM5504_CABLE_TYPE_CDP: + case MUIC_SM5504_CABLE_TYPE_L_USB: + case MUIC_SM5504_CABLE_TYPE_JIG_USB_ON: + case MUIC_SM5504_CABLE_TYPE_JIG_USB_OFF: + current_cable_type = POWER_SUPPLY_TYPE_USB; + break; + case MUIC_SM5504_CABLE_TYPE_REGULAR_TA: + case MUIC_SM5504_CABLE_TYPE_ATT_TA: + case MUIC_SM5504_CABLE_TYPE_JIG_UART_OFF_WITH_VBUS: + case MUIC_SM5504_CABLE_TYPE_TYPE1_CHARGER: + current_cable_type = POWER_SUPPLY_TYPE_MAINS; + break; + case MUIC_SM5504_CABLE_TYPE_UART: + current_cable_type = POWER_SUPPLY_TYPE_UNKNOWN; + break; + case MUIC_SM5504_CABLE_TYPE_OTG: + case MUIC_SM5504_CABLE_TYPE_OTG_WITH_VBUS: + current_cable_type = POWER_SUPPLY_TYPE_OTG; + break; + case MUIC_SM5504_CABLE_TYPE_0x15: + case MUIC_SM5504_CABLE_TYPE_0x1A: +// case MUIC_SM5504_CABLE_TYPE_0x1A_VBUS: + current_cable_type = POWER_SUPPLY_TYPE_MISC; + break; + default: + pr_err("%s: invalid type for charger:%d\n", + __func__, cable_type); + current_cable_type = POWER_SUPPLY_TYPE_UNKNOWN; + goto skip; + } + + if (!psy || !psy->set_property) + pr_err("%s: fail to get battery psy\n", __func__); + else { + value.intval = current_cable_type; + psy->set_property(psy, POWER_SUPPLY_PROP_ONLINE, &value); + +#if defined(CONFIG_INPUT_TOUCHSCREEN) + /* for changing of tsp operation mode */ + if (tsp_charger_status_cb) + tsp_charger_status_cb(current_cable_type); +#endif + } + +skip: + return 0; + +} +#endif /* CONFIG_MFD_SM5504 */ + +int sec_vf_adc_current_check(void) +{ + int battery_on = 0; + + sci_adi_set(ANA_REG_GLB_AUXAD_CTL, + (BIT_AUXAD_CURRENTSEN_EN | BITS_AUXAD_CURRENT_IBS(0x6))); + sci_adi_set(ANA_REG_GLB_BATDET_CUR_CTRL, + (BIT_BATDET_CUR_EN | BITS_BATDET_CUR_I(0x4))); + mdelay(5); + + if (gpio_get_value(gpio_vbat_detect)) + { + printk("%s : vbat good!\n", __func__); + battery_on = 1; + } + else + { + printk("%s : vbat is not connected!\n", __func__); + battery_on = 0; + } + + sci_adi_clr(ANA_REG_GLB_AUXAD_CTL, BIT_AUXAD_CURRENTSEN_EN); + sci_adi_clr(ANA_REG_GLB_BATDET_CUR_CTRL, BIT_BATDET_CUR_EN); + + return battery_on; +} + +/* vf adc check, only for MUIC & young2dtv */ +int sec_vf_adc_check(void) +{ + int adc = 0; + + adc = sci_adc_get_value(0, false); + pr_info("%s, vf adc : %d", __func__, adc); + + if (adc > 500) + return 0; + else + return 1; +} + +int sec_bat_dt_init(struct device_node *np, + struct device *dev, + sec_battery_platform_data_t *pdata) +{ + int ret, i, len; + unsigned int bat_irq_attr; + const u32 *p; + + ret = of_property_read_string(np, + "battery,vendor", (char const **)&pdata->vendor); + if (ret) + pr_info("%s: Vendor is Empty\n", __func__); + + ret = of_property_read_string(np, + "battery,charger_name", (char const **)&pdata->charger_name); + if (ret) + pr_info("%s: charger_name is Empty\n", __func__); + + ret = of_property_read_string(np, + "battery,fuelgauge_name", (char const **)&pdata->fuelgauge_name); + if (ret) + pr_info("%s: fuelguage_name is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,technology", + &pdata->technology); + if (ret) + pr_info("%s : technology is Empty\n", __func__); + + p = of_get_property(np, "battery,polling_time", &len); + if (!p) + return 1; + + len = len / sizeof(u32); + pdata->polling_time = kzalloc(sizeof(*pdata->polling_time) * len, GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,polling_time", + pdata->polling_time, len); + + p = of_get_property(np, "battery,temp_table_adc", &len); + if (!p) + return 1; + + len = len / sizeof(u32); + + pdata->temp_adc_table_size = len; + pdata->temp_amb_adc_table_size = len; + + pdata->temp_adc_table = + kzalloc(sizeof(sec_bat_adc_table_data_t) * + pdata->temp_adc_table_size, GFP_KERNEL); + pdata->temp_amb_adc_table = + kzalloc(sizeof(sec_bat_adc_table_data_t) * + pdata->temp_adc_table_size, GFP_KERNEL); + + for(i = 0; i < pdata->temp_adc_table_size; i++) { + ret = of_property_read_u32_index(np, + "battery,temp_table_adc", i, + &pdata->temp_adc_table[i].adc); + if (ret) + pr_info("%s : Temp_adc_table(adc) is Empty\n", + __func__); + + ret = of_property_read_u32_index(np, + "battery,temp_table_data", i, + &pdata->temp_adc_table[i].temperature); + if (ret) + pr_info("%s : Temp_adc_table(data) is Empty\n", + __func__); + + ret = of_property_read_u32_index(np, + "battery,temp_table_adc", i, + &pdata->temp_amb_adc_table[i].adc); + if (ret) + pr_info("%s : Temp_amb_adc_table(adc) is Empty\n", + __func__); + + ret = of_property_read_u32_index(np, + "battery,temp_table_data", i, + &pdata->temp_amb_adc_table[i].temperature); + if (ret) + pr_info("%s : Temp_amb_adc_table(data) is Empty\n", + __func__); + } + + p = of_get_property(np, "battery,input_current_limit", &len); + if (!p) + return 1; + + len = len / sizeof(u32); + + pdata->charging_current = + kzalloc(sizeof(sec_charging_current_t) * len, + GFP_KERNEL); + + for(i = 0; i < len; i++) { + ret = of_property_read_u32_index(np, + "battery,input_current_limit", i, + &pdata->charging_current[i].input_current_limit); + if (ret) + pr_info("%s : Input_current_limit is Empty\n", + __func__); + + ret = of_property_read_u32_index(np, + "battery,fast_charging_current", i, + &pdata->charging_current[i].fast_charging_current); + if (ret) + pr_info("%s : Fast charging current is Empty\n", + __func__); + + ret = of_property_read_u32_index(np, + "battery,full_check_current_1st", i, + &pdata->charging_current[i].full_check_current_1st); + if (ret) + pr_info("%s : Full check current 1st is Empty\n", + __func__); + + ret = of_property_read_u32_index(np, + "battery,full_check_current_2nd", i, + &pdata->charging_current[i].full_check_current_2nd); + if (ret) + pr_info("%s : Full check current 2nd is Empty\n", + __func__); + } + + ret = of_property_read_u32(np, "battery,adc_check_count", + &pdata->adc_check_count); + if (ret) + pr_info("%s : Adc check count is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,cable_check_type", + &pdata->cable_check_type); + if (ret) + pr_info("%s : Cable check type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,cable_source_type", + &pdata->cable_source_type); + if (ret) + pr_info("%s : Cable source type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,event_waiting_time", + &pdata->event_waiting_time); + if (ret) + pr_info("%s : Event waiting time is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,polling_type", + &pdata->polling_type); + if (ret) + pr_info("%s : Polling type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,monitor_initial_count", + &pdata->monitor_initial_count); + if (ret) + pr_info("%s : Monitor initial count is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,battery_check_type", + &pdata->battery_check_type); + if (ret) + pr_info("%s : Battery check type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,check_count", + &pdata->check_count); + + if (ret) + pr_info("%s : Check count is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,check_adc_max", + &pdata->check_adc_max); + if (ret) + pr_info("%s : Check adc max is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,check_adc_min", + &pdata->check_adc_min); + if (ret) + pr_info("%s : Check adc min is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,ovp_uvlo_check_type", + &pdata->ovp_uvlo_check_type); + if (ret) + pr_info("%s : Ovp Uvlo check type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,thermal_source", + &pdata->thermal_source); + if (ret) + pr_info("%s : Thermal source is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_check_type", + &pdata->temp_check_type); + if (ret) + pr_info("%s : Temp check type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_check_count", + &pdata->temp_check_count); + if (ret) + pr_info("%s : Temp check count is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_high_recovery_event", + &pdata->temp_high_recovery_event); + if (ret) + pr_info("%s : Temp high recovery event is Empty\n", __func__); + +#if defined(CONFIG_MACH_DEGAS_USA) + pdata->temp_high_threshold_event = 550; + pdata->temp_high_recovery_event = 480; + pdata->temp_low_threshold_event = 20; + pdata->temp_low_recovery_event = 50; + pdata->temp_high_threshold_normal = 550; + pdata->temp_high_recovery_normal = 480; + pdata->temp_low_threshold_normal = 20; + pdata->temp_low_recovery_normal = 50; + pdata->temp_high_threshold_lpm = 550; + pdata->temp_high_recovery_lpm = 480; + pdata->temp_low_threshold_lpm = 20; + pdata->temp_low_recovery_lpm = 50; +#elif defined(CONFIG_MACH_DEGAS_BMW) + pdata->temp_high_threshold_event = 450; + pdata->temp_high_recovery_event = 400; + pdata->temp_low_threshold_event = 0; + pdata->temp_low_recovery_event = 50; + pdata->temp_high_threshold_normal = 450; + pdata->temp_high_recovery_normal = 400; + pdata->temp_low_threshold_normal = 0; + pdata->temp_low_recovery_normal = 50; + pdata->temp_high_threshold_lpm = 450; + pdata->temp_high_recovery_lpm = 400; + pdata->temp_low_threshold_lpm = 0; + pdata->temp_low_recovery_lpm = 50; +#else + ret = of_property_read_u32(np, "battery,temp_highlimit_threshold_event", + &pdata->temp_highlimit_threshold_event); + if (ret) + pr_info("%s : Temp highlimit threshold event is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,battery,temp_highlimit_recovery_event", + &pdata->temp_highlimit_recovery_event); + if (ret) + pr_info("%s : Temp highlimit threshold event recovery is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_highlimit_threshold_normal", + &pdata->temp_highlimit_threshold_normal); + if (ret) + pr_info("%s : Temp highlimit threshold normal is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_highlimit_recovery_normal", + &pdata->temp_highlimit_recovery_normal); + if (ret) + pr_info("%s : Temp highlimit threshold normal recovery is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_highlimit_threshold_lpm", + &pdata->temp_highlimit_threshold_lpm); + if (ret) + pr_info("%s : Temp highlimit threshold lpm is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_highlimit_recovery_lpm", + &pdata->temp_highlimit_recovery_lpm); + if (ret) + pr_info("%s : Temp highlimit threshold lpm recovery is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_high_threshold_event", + &pdata->temp_high_threshold_event); + if (ret) + pr_info("%s : Temp high threshold event is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_low_threshold_event", + &pdata->temp_low_threshold_event); + if (ret) + pr_info("%s : Temp low threshold event is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_low_recovery_event", + &pdata->temp_low_recovery_event); + if (ret) + pr_info("%s : Temp low recovery event is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_high_threshold_normal", + &pdata->temp_high_threshold_normal); + if (ret) + pr_info("%s : Temp high threshold normal is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_high_recovery_normal", + &pdata->temp_high_recovery_normal); + if (ret) + pr_info("%s : Temp high recovery normal is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_low_threshold_normal", + &pdata->temp_low_threshold_normal); + if (ret) + pr_info("%s : Temp low threshold normal is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_low_recovery_normal", + &pdata->temp_low_recovery_normal); + + if (ret) + pr_info("%s : Temp low recovery normal is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_high_threshold_lpm", + &pdata->temp_high_threshold_lpm); + if (ret) + pr_info("%s : Temp high threshold lpm is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_high_recovery_lpm", + &pdata->temp_high_recovery_lpm); + if (ret) + pr_info("%s : Temp high recovery lpm is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_low_threshold_lpm", + &pdata->temp_low_threshold_lpm); + if (ret) + pr_info("%s : Temp low threshold lpm is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_low_recovery_lpm", + &pdata->temp_low_recovery_lpm); + if (ret) + pr_info("%s : Temp low recovery lpm is Empty\n", __func__); +#endif + + ret = of_property_read_u32(np, "battery,full_check_type", + &pdata->full_check_type); + if (ret) + pr_info("%s : Full check type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_check_type_2nd", + &pdata->full_check_type_2nd); + if (ret) + pr_info("%s : Full check type 2nd is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_check_count", + &pdata->full_check_count); + if (ret) + pr_info("%s : Full check count is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,chg_gpio_full_check", + &pdata->chg_gpio_full_check); + if (ret) + pr_info("%s : Chg gpio full check is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,chg_polarity_full_check", + &pdata->chg_polarity_full_check); + if (ret) + pr_info("%s : Chg polarity full check is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_condition_type", + &pdata->full_condition_type); + if (ret) + pr_info("%s : Full condition type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_condition_soc", + &pdata->full_condition_soc); + if (ret) + pr_info("%s : Full condition soc is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_condition_vcell", + &pdata->full_condition_vcell); + if (ret) + pr_info("%s : Full condition vcell is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,recharge_check_count", + &pdata->recharge_check_count); + if (ret) + pr_info("%s : Recharge check count is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,recharge_condition_type", + &pdata->recharge_condition_type); + if (ret) + pr_info("%s : Recharge condition type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,recharge_condition_soc", + &pdata->recharge_condition_soc); + if (ret) + pr_info("%s : Recharge condition soc is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,recharge_condition_vcell", + &pdata->recharge_condition_vcell); + if (ret) + pr_info("%s : Recharge condition vcell is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,recharge_condition_avgvcell", + &pdata->recharge_condition_avgvcell); + if (ret) + pr_info("%s : Recharge condition avgvcell is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,charging_total_time", + (unsigned int *)&pdata->charging_total_time); + if (ret) + pr_info("%s : Charging total time is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,recharging_total_time", + (unsigned int *)&pdata->recharging_total_time); + if (ret) + pr_info("%s : Recharging total time is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,charging_reset_time", + (unsigned int *)&pdata->charging_reset_time); + if (ret) + pr_info("%s : Charging reset time is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,charging_reset_time", + (unsigned int *)&pdata->charging_reset_time); + if (ret) + pr_info("%s : Charging reset time is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,bat-irq-attr", &bat_irq_attr); + if (ret) + pr_info("%s : Battery irq is Empty\n", __func__); + + pdata->bat_gpio_init = sec_bat_gpio_init; + pdata->check_battery_callback = sec_bat_check_callback; + pdata->check_battery_result_callback = sec_bat_check_result_callback; + pdata->check_cable_callback = sec_bat_check_cable_callback; + pdata->check_cable_result_callback = + sec_bat_check_cable_result_callback; + pdata->get_temperature_callback = sec_bat_get_temperature_callback; + pdata->initial_check = sec_bat_initial_check; + pdata->is_lpm = sec_bat_is_lpm; + pdata->ovp_uvlo_callback = sec_bat_ovp_uvlo_callback; + pdata->ovp_uvlo_result_callback = sec_bat_ovp_uvlo_result_callback; + pdata->check_jig_status = sec_bat_check_jig_status; + + for (i = 0; i < SEC_BATTERY_ADC_TYPE_NUM; i++) + pdata->adc_api[i] = adc_api[i]; + + return 0; +} + +int sec_chg_dt_init(struct device_node *np, + struct device *dev, + sec_battery_platform_data_t *pdata) +{ + int ret = 0, len = 0; + unsigned int chg_irq_attr = 0; + int chg_gpio_en = 0; + int chg_irq_gpio = 0; + + if (!np) + return -EINVAL; + + chg_gpio_en = of_get_named_gpio(np, "chgen-gpio", 0); + if (chg_gpio_en < 0) { + pr_err("%s: of_get_named_gpio failed: %d\n", __func__, + chg_gpio_en); + return chg_gpio_en; + } + + ret = of_property_read_u32(np, "chg-irq-attr", + &chg_irq_attr); + if (ret) { + pr_info("%s chg_irq_attr request failed: %d\n", __func__, chg_irq_attr); + } + + ret = gpio_request(chg_gpio_en, "chgen-gpio"); + if (ret) { + pr_err("%s gpio_request failed: %d\n", __func__, chg_gpio_en); + return ret; + } + + chg_irq_gpio = of_get_named_gpio(np, "chgirq-gpio", 0); + if (chg_irq_gpio < 0) + pr_err("%s: chgirq gpio get failed: %d\n", __func__, chg_irq_gpio); + + ret = gpio_request(chg_irq_gpio, "chgirq-gpio"); + if (ret) + pr_err("%s gpio_request failed: %d\n", __func__, chg_irq_gpio); + + ret = of_property_read_u32(np, "chg-float-voltage", + &pdata->chg_float_voltage); + if (ret) + return ret; + + np = of_find_node_by_name(NULL, "sec-battery"); + if (!np) { + pr_err("%s np NULL\n", __func__); + } + else { + int i = 0; + const u32 *p; + p = of_get_property(np, "battery,input_current_limit", &len); + if (!p){ + + pr_err("%s charger,input_current_limit is Empty\n", __func__); + // return 1; + } + else{ + + len = len / sizeof(u32); + + pdata->charging_current = kzalloc(sizeof(sec_charging_current_t) * len, + GFP_KERNEL); + + for(i = 0; i < len; i++) { + ret = of_property_read_u32_index(np, + "battery,input_current_limit", i, + &pdata->charging_current[i].input_current_limit); + if (ret) + pr_info("%s : Input_current_limit is Empty\n", + __func__); + + ret = of_property_read_u32_index(np, + "battery,fast_charging_current", i, + &pdata->charging_current[i].fast_charging_current); + if (ret) + pr_info("%s : Fast charging current is Empty\n", + __func__); + + ret = of_property_read_u32_index(np, + "battery,full_check_current_1st", i, + &pdata->charging_current[i].full_check_current_1st); + if (ret) + pr_info("%s : Full check current 1st is Empty\n", + __func__); + + ret = of_property_read_u32_index(np, + "battery,full_check_current_2nd", i, + &pdata->charging_current[i].full_check_current_2nd); + if (ret) + pr_info("%s : Full check current 2nd is Empty\n", + __func__); + } + } + } + + ret = of_property_read_u32(np, "battery,ovp_uvlo_check_type", + &pdata->ovp_uvlo_check_type); + if (ret) + pr_info("%s : Ovp Uvlo check type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_check_type", + &pdata->full_check_type); + if (ret) + pr_info("%s : Full check type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_check_type_2nd", + &pdata->full_check_type_2nd); + if (ret) + pr_info("%s : Full check type 2nd is Empty\n", __func__); + +#if defined(CONFIG_MACH_PIKEAYOUNG2DTV) + gpio_vbat_detect = BATT_DET_CURRENT; + + if (!gpio_is_valid(gpio_vbat_detect)) { + printk("%s: battery detect gpio %d failed\n", __func__, gpio_vbat_detect); + return 1; + } + gpio_request(gpio_vbat_detect, "battery-detect"); + gpio_direction_input(gpio_vbat_detect); + printk("%s: battery detect gpio %d\n", __func__, gpio_vbat_detect); +#endif + + if (chg_irq_gpio > 0) { + pdata->chg_irq_attr = chg_irq_attr; + pdata->chg_irq = gpio_to_irq(chg_irq_gpio); + } + pdata->chg_gpio_en = chg_gpio_en; + pdata->chg_gpio_init = sec_chg_gpio_init; + pdata->check_cable_result_callback = + sec_bat_check_cable_result_callback; + + return 0; +} + +#ifdef CONFIG_FUELGAUGE_SPRD4SAMSUNG27X3 +struct battery_data_t bat_data; +#endif + +int sec_fg_dt_init(struct device_node *np, + struct device *dev, + sec_battery_platform_data_t *pdata) +{ + int ret; + unsigned int fg_irq_attr; + + ret = of_property_read_u32(np, "capacity-max", &pdata->capacity_max); + if (ret) + return ret; + ret = of_property_read_u32(np, "capacity-max-margin", + &pdata->capacity_max_margin); + if (ret) + return ret; + ret = of_property_read_u32(np, "capacity-min", &pdata->capacity_min); + if (ret) + return ret; + ret = of_property_read_u32(np, "fg-irq-attr", &fg_irq_attr); + if (ret) + return ret; + ret = of_property_read_u32(np, "fuel-alert-soc", + &pdata->fuel_alert_soc); + if (ret) + return ret; + if (of_find_property(np, "repeated-fuelalert", NULL)) + pdata->repeated_fuelalert = true; + + ret = of_property_read_u32(np, "temp_adc_channel", + &pdata->temp_adc_channel); + if (ret) + return ret; + +#ifdef CONFIG_FUELGAUGE_SPRD4SAMSUNG27X3 + { + struct sprd_battery_platform_data *battery_data; + int len, i; + uint32_t cell_value; + struct device_node *temp_np = NULL; + + battery_data = devm_kzalloc(dev, sizeof(*battery_data), GFP_KERNEL); + temp_np = of_get_child_by_name(np, "sprd_fgu"); + if (!temp_np) { + return ERR_PTR(-EINVAL); + } + battery_data->irq_fgu = irq_of_parse_and_map(temp_np, 0); + of_property_read_u32(np, "chg_bat_safety_vol", &battery_data->chg_bat_safety_vol); + of_property_read_u32(np, "soft_vbat_uvlo", &battery_data->soft_vbat_uvlo); + of_property_read_u32(np, "vmod", &battery_data->fgu_mode); + of_property_read_u32(np, "alm_soc", &battery_data->alm_soc); + of_property_read_u32(np, "alm_vbat", &battery_data->alm_vol); + of_property_read_u32(np, "rint", &battery_data->rint); + of_property_read_u32(np, "cnom", &battery_data->cnom); + of_property_read_u32(np, "rsense_real", &battery_data->rsense_real); + of_property_read_u32(np, "rsense_spec", &battery_data->rsense_spec); + of_property_read_u32(np, "relax_current", &battery_data->relax_current); + of_property_read_u32(np, "cal_ajust", &battery_data->fgu_cal_ajust); + of_get_property(np, "ocv_table", &len); + len /= sizeof(u32); + battery_data->ocv_tab_size = len >> 1; + battery_data->ocv_tab = kzalloc(sizeof(struct sprdbat_table_data) * + battery_data->ocv_tab_size, GFP_KERNEL); + for (i = 0; i < len; i++) { + of_property_read_u32_index(np, "ocv_table", i, &cell_value); + if(i&0x1) { + battery_data->ocv_tab[i >> 1].y = cell_value; + } else { + battery_data->ocv_tab[i >> 1].x = cell_value; + } + } + bat_data.pdata = battery_data; + pdata->battery_data = &bat_data; + } +#endif + + pdata->fg_irq_attr = fg_irq_attr; + pdata->fuelalert_process = sec_fg_fuelalert_process; + pdata->fg_gpio_init = sec_fg_gpio_init; + pdata->capacity_calculation_type = + SEC_FUELGAUGE_CAPACITY_TYPE_RAW; + pdata->check_jig_status = sec_bat_check_jig_status; + + return 0; +} diff --git a/drivers/battery/sec_fuelgauge.c b/drivers/battery/sec_fuelgauge.c new file mode 100755 index 00000000..df999fa5 --- /dev/null +++ b/drivers/battery/sec_fuelgauge.c @@ -0,0 +1,781 @@ +/* + * sec_fuelgauge.c + * Samsung Mobile Fuel Gauge 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_fuelgauge.h> +#include <linux/of_gpio.h> +int sec_fg_dt_init(struct device_node *np, + struct device *dev, + sec_battery_platform_data_t *pdata); +static struct device_attribute sec_fg_attrs[] = { + SEC_FG_ATTR(reg), + SEC_FG_ATTR(data), + SEC_FG_ATTR(regs), +}; + +static enum power_supply_property sec_fuelgauge_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_AMBIENT, +}; +#ifdef CONFIG_FUELGAUGE_SPRD4SAMSUNG27X3 +#undef dev_dbg +#undef dev_err +#undef dev_info + +#define dev_dbg(dev, format, arg...) pr_info(format, ##arg) +#define dev_err(dev, format, arg...) pr_info(format, ##arg) +#define dev_info(dev, format, arg...) pr_info(format, ##arg) +#endif + +/* capacity is 0.1% unit */ +static void sec_fg_get_scaled_capacity( + struct sec_fuelgauge_info *fuelgauge, + union power_supply_propval *val) +{ + val->intval = (val->intval < fuelgauge->pdata->capacity_min) ? + 0 : ((val->intval - fuelgauge->pdata->capacity_min) * 1000 / + (fuelgauge->capacity_max - fuelgauge->pdata->capacity_min)); + + dev_dbg(&fuelgauge->client->dev, + "%s: scaled capacity (%d.%d)\n", + __func__, val->intval/10, val->intval%10); +} + +/* capacity is integer */ +static void sec_fg_get_atomic_capacity( + struct sec_fuelgauge_info *fuelgauge, + union power_supply_propval *val) +{ + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC) { + if (fuelgauge->capacity_old < val->intval) + val->intval = fuelgauge->capacity_old + 1; + else if (fuelgauge->capacity_old > val->intval) + val->intval = fuelgauge->capacity_old - 1; + } + + /* keep SOC stable in abnormal status */ + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL) { + if ((fuelgauge->is_charging && + fuelgauge->capacity_old > val->intval) || + (!fuelgauge->is_charging && + fuelgauge->capacity_old < val->intval)) { + dev_err(&fuelgauge->client->dev, + "%s: abnormal capacity (old %d : new %d)\n", + __func__, fuelgauge->capacity_old, val->intval); + val->intval = fuelgauge->capacity_old; + } + } + + /* updated old capacity */ + fuelgauge->capacity_old = val->intval; +} + +static int sec_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_fuelgauge_info *fuelgauge = + container_of(psy, struct sec_fuelgauge_info, psy_fg); + int soc_type = val->intval; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CURRENT_AVG: + case POWER_SUPPLY_PROP_ENERGY_NOW: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + case POWER_SUPPLY_PROP_PRESENT: + if (!sec_hal_fg_get_property(fuelgauge_variable, psp, val)) + return -EINVAL; + if (psp == POWER_SUPPLY_PROP_CAPACITY) { + if (soc_type == SEC_FUELGAUGE_CAPACITY_TYPE_RAW) + break; + + if (fuelgauge->pdata->capacity_calculation_type & + (SEC_FUELGAUGE_CAPACITY_TYPE_SCALE | + SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE)) + sec_fg_get_scaled_capacity(fuelgauge, val); + + /* capacity should be between 0% and 100% + * (0.1% degree) + */ + if (val->intval > 1000) + val->intval = 1000; + if (val->intval < 0) + val->intval = 0; + + /* get only integer part */ + val->intval /= 10; + + /* check whether doing the wake_unlock */ + if ((val->intval > fuelgauge->pdata->fuel_alert_soc) && + fuelgauge->is_fuel_alerted) { + wake_unlock(&fuelgauge->fuel_alert_wake_lock); + sec_hal_fg_fuelalert_init(fuelgauge_variable, + fuelgauge->pdata->fuel_alert_soc); + } + + /* (Only for atomic capacity) + * In initial time, capacity_old is 0. + * and in resume from sleep, + * capacity_old is too different from actual soc. + * should update capacity_old + * by val->intval in booting or resume. + */ + if (fuelgauge->initial_update_of_soc) { + /* updated old capacity */ + fuelgauge->capacity_old = val->intval; + fuelgauge->initial_update_of_soc = false; + break; + } + + if (fuelgauge->pdata->capacity_calculation_type & + (SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC | + SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL)) + sec_fg_get_atomic_capacity(fuelgauge, val); + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_fg_calculate_dynamic_scale( + struct sec_fuelgauge_info *fuelgauge) +{ + union power_supply_propval raw_soc_val; + + raw_soc_val.intval = SEC_FUELGAUGE_CAPACITY_TYPE_RAW; + if (!sec_hal_fg_get_property(fuelgauge_variable, + POWER_SUPPLY_PROP_CAPACITY, + &raw_soc_val)) + return -EINVAL; + raw_soc_val.intval /= 10; + + if (raw_soc_val.intval < + fuelgauge->pdata->capacity_max - + fuelgauge->pdata->capacity_max_margin) { + fuelgauge->capacity_max = + fuelgauge->pdata->capacity_max - + fuelgauge->pdata->capacity_max_margin; + dev_dbg(&fuelgauge->client->dev, "%s: capacity_max (%d)", + __func__, fuelgauge->capacity_max); + } else { + fuelgauge->capacity_max = + (raw_soc_val.intval > + fuelgauge->pdata->capacity_max + + fuelgauge->pdata->capacity_max_margin) ? + (fuelgauge->pdata->capacity_max + + fuelgauge->pdata->capacity_max_margin) : + raw_soc_val.intval; + dev_dbg(&fuelgauge->client->dev, "%s: raw soc (%d)", + __func__, fuelgauge->capacity_max); + } + + fuelgauge->capacity_max = + (fuelgauge->capacity_max * 99 / 100); + + dev_info(&fuelgauge->client->dev, "%s: %d is used for capacity_max\n", + __func__, fuelgauge->capacity_max); + + return fuelgauge->capacity_max; +} + +static int sec_fg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_fuelgauge_info *fuelgauge = + container_of(psy, struct sec_fuelgauge_info, psy_fg); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (val->intval == POWER_SUPPLY_STATUS_FULL) + sec_hal_fg_full_charged(fuelgauge_variable); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (val->intval == POWER_SUPPLY_TYPE_BATTERY) { + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE) + sec_fg_calculate_dynamic_scale(fuelgauge); + } + break; + case POWER_SUPPLY_PROP_ONLINE: + fuelgauge->cable_type = val->intval; + if (val->intval == POWER_SUPPLY_TYPE_BATTERY) + fuelgauge->is_charging = false; + else + fuelgauge->is_charging = true; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_RESET) { + if (!sec_hal_fg_reset(fuelgauge_variable)) + return -EINVAL; + else + break; + } + case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + if (!sec_hal_fg_set_property(fuelgauge_variable, psp, val)) + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +static void sec_fg_isr_work(struct work_struct *work) +{ + struct sec_fuelgauge_info *fuelgauge = + container_of(work, struct sec_fuelgauge_info, isr_work.work); + + /* process for fuel gauge chip */ + sec_hal_fg_fuelalert_process(fuelgauge, fuelgauge->is_fuel_alerted); + + /* process for others */ + if (fuelgauge->pdata->fuelalert_process != NULL) + fuelgauge->pdata->fuelalert_process(fuelgauge->is_fuel_alerted); +} + +static irqreturn_t sec_fg_irq_thread(int irq, void *irq_data) +{ + struct sec_fuelgauge_info *fuelgauge = irq_data; + bool fuel_alerted; + + if (fuelgauge->pdata->fuel_alert_soc >= 0) { + fuel_alerted = + sec_hal_fg_is_fuelalerted(fuelgauge_variable); + + dev_info(&fuelgauge->client->dev, + "%s: Fuel-alert %salerted!\n", + __func__, fuel_alerted ? "" : "NOT "); + + if (fuel_alerted == fuelgauge->is_fuel_alerted) { + if (!fuelgauge->pdata->repeated_fuelalert) { + dev_dbg(&fuelgauge->client->dev, + "%s: Fuel-alert Repeated (%d)\n", + __func__, fuelgauge->is_fuel_alerted); + return IRQ_HANDLED; + } + } + + if (fuel_alerted) + wake_lock(&fuelgauge->fuel_alert_wake_lock); + else + wake_unlock(&fuelgauge->fuel_alert_wake_lock); + + schedule_delayed_work(&fuelgauge->isr_work, 0); + + fuelgauge->is_fuel_alerted = fuel_alerted; + } + + return IRQ_HANDLED; +} + +static int sec_fg_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(sec_fg_attrs); i++) { + rc = device_create_file(dev, &sec_fg_attrs[i]); + if (rc) + goto create_attrs_failed; + } + goto create_attrs_succeed; + +create_attrs_failed: + dev_err(dev, "%s: failed (%d)\n", __func__, rc); + while (i--) + device_remove_file(dev, &sec_fg_attrs[i]); +create_attrs_succeed: + return rc; +} + +ssize_t sec_fg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const ptrdiff_t offset = attr - sec_fg_attrs; + int i = 0; + + switch (offset) { + case FG_REG: + case FG_DATA: + case FG_REGS: +// i = sec_hal_fg_show_attrs(dev, offset, buf); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t sec_fg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + const ptrdiff_t offset = attr - sec_fg_attrs; + int ret = 0; + + switch (offset) { + case FG_REG: + case FG_DATA: +// ret = sec_hal_fg_store_attrs(dev, offset, buf, count); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +#if defined(CONFIG_FUELGAUGE_MFD) || defined(CONFIG_FUELGAUGE_SPRD4SAMSUNG27X3) +static int sec_fuelgauge_probe(struct platform_device *pdev) +{ + struct sec_fuelgauge_info *fuelgauge; +#ifndef CONFIG_FUELGAUGE_SPRD4SAMSUNG27X3 + sec_fuelgauge_dev_t *mfd_dev = dev_get_drvdata(pdev->dev.parent); + sec_fuelgauge_pdata_t *pdata = dev_get_platdata(mfd_dev->dev); +#else + sec_battery_platform_data_t *pdata = dev_get_platdata(&pdev->dev); +#endif + int ret = 0; + bool fuelalert_init_ret = false; + union power_supply_propval raw_soc_val; + + dev_info(&pdev->dev, + "%s: SEC Fuelgauge Driver Loading\n", __func__); + + fuelgauge = kzalloc(sizeof(*fuelgauge), GFP_KERNEL); + if (!fuelgauge) + return -ENOMEM; + + platform_set_drvdata(pdev, fuelgauge); +#ifndef CONFIG_FUELGAUGE_SPRD4SAMSUNG27X3 + fuelgauge->client = mfd_dev->i2c; + fuelgauge->pdata = pdata->fuelgauge_data; +#else + fuelgauge->pdata = pdata; +#endif + + mutex_init(&fuelgauge->fg_lock); + + if (pdev->dev.of_node) { + pdata = devm_kzalloc(&pdev->dev, + sizeof(sec_battery_platform_data_t), + GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, "Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_free; + } + + fuelgauge->pdata = pdata; +#ifdef CONFIG_OF + if (sec_fg_dt_init(pdev->dev.of_node, &pdev->dev, fuelgauge->pdata)) + dev_err(&pdev->dev, + "%s: Failed to get fuel_int\n", __func__); +#endif + } + + fuelgauge->psy_fg.name = "sec-fuelgauge"; + fuelgauge->psy_fg.type = POWER_SUPPLY_TYPE_UNKNOWN; + fuelgauge->psy_fg.get_property = sec_fg_get_property; + fuelgauge->psy_fg.set_property = sec_fg_set_property; + fuelgauge->psy_fg.properties = sec_fuelgauge_props; + fuelgauge->psy_fg.num_properties = + ARRAY_SIZE(sec_fuelgauge_props); + fuelgauge->capacity_max = fuelgauge->pdata->capacity_max; + raw_soc_val.intval = SEC_FUELGAUGE_CAPACITY_TYPE_RAW; + sec_hal_fg_get_property(fuelgauge, + POWER_SUPPLY_PROP_CAPACITY, &raw_soc_val); + raw_soc_val.intval /= 10; + if(raw_soc_val.intval > fuelgauge->pdata->capacity_max) + sec_fg_calculate_dynamic_scale(fuelgauge); + + if (!fuelgauge->pdata->fg_gpio_init()) { + dev_err(&pdev->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_free; + } + + if (!sec_hal_fg_init(fuelgauge)) { + dev_err(&pdev->dev, + "%s: Failed to Initialize Fuelgauge\n", __func__); + goto err_free; + } + + ret = power_supply_register(&pdev->dev, &fuelgauge->psy_fg); + if (ret) { + dev_err(&pdev->dev, + "%s: Failed to Register psy_fg\n", __func__); + goto err_free; + } + + fuelgauge->is_fuel_alerted = false; + if (fuelgauge->pdata->fuel_alert_soc >= 0) { + fuelalert_init_ret = + sec_hal_fg_fuelalert_init(fuelgauge, + fuelgauge->pdata->fuel_alert_soc); + if (fuelalert_init_ret) + wake_lock_init(&fuelgauge->fuel_alert_wake_lock, + WAKE_LOCK_SUSPEND, "fuel_alerted"); + else { + dev_err(&pdev->dev, + "%s: Failed to Initialize Fuel-alert\n", + __func__); + goto err_irq; + } + } +#ifndef CONFIG_FUELGAUGE_SPRD4SAMSUNG27X3 + if (fuelgauge->pdata->fg_irq) { + INIT_DELAYED_WORK_DEFERRABLE( + &fuelgauge->isr_work, sec_fg_isr_work); + + fuelgauge->fg_irq = gpio_to_irq(fuelgauge->pdata->fg_irq); + + ret = request_threaded_irq(fuelgauge->fg_irq, + NULL, sec_fg_irq_thread, + fuelgauge->pdata->fg_irq_attr, + "fuelgauge-irq", fuelgauge); + if (ret) { + dev_err(&pdev->dev, + "%s: Failed to Reqeust IRQ\n", __func__); + goto err_supply_unreg; + } + + ret = enable_irq_wake(fuelgauge->pdata->fg_irq); + if (ret < 0) + dev_err(&pdev->dev, + "%s: Failed to Enable Wakeup Source(%d)\n", + __func__, ret); + } +#endif + fuelgauge->initial_update_of_soc = true; + + ret = sec_fg_create_attrs(fuelgauge->psy_fg.dev); + if (ret) { + dev_err(&pdev->dev, + "%s : Failed to create_attrs\n", __func__); + goto err_irq; + } + + dev_info(&pdev->dev, + "%s: SEC Fuelgauge Driver Loaded\n", __func__); + return 0; + +err_irq: + if (fuelgauge->pdata->fg_irq) + free_irq(fuelgauge->pdata->fg_irq, fuelgauge); + if (fuelalert_init_ret) + wake_lock_destroy(&fuelgauge->fuel_alert_wake_lock); +err_supply_unreg: + power_supply_unregister(&fuelgauge->psy_fg); +err_free: + mutex_destroy(&fuelgauge->fg_lock); + kfree(fuelgauge); + + return ret; +} + +static int sec_fuelgauge_remove(struct platform_device *pdev) +{ + return 0; +} + +static int sec_fuelgauge_suspend(struct device *dev) +{ + struct sec_fuelgauge_info *fuelgauge = dev_get_drvdata(dev); + + if (!sec_hal_fg_suspend(fuelgauge)) + dev_err(dev, + "%s: Failed to Suspend Fuelgauge\n", __func__); + + return 0; +} + +static int sec_fuelgauge_resume(struct device *dev) +{ + struct sec_fuelgauge_info *fuelgauge = dev_get_drvdata(dev); + + if (!sec_hal_fg_resume(fuelgauge)) + dev_err(dev, + "%s: Failed to Resume Fuelgauge\n", __func__); + + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC) + fuelgauge->initial_update_of_soc = true; + + return 0; +} + +static void sec_fuelgauge_shutdown(struct device *dev) +{ +} + +static const struct dev_pm_ops sec_fuelgauge_pm_ops = { + .suspend = sec_fuelgauge_suspend, + .resume = sec_fuelgauge_resume, +}; + +#ifdef CONFIG_OF +static struct of_device_id sec_fuelgauge_dt_ids[] = { + { .compatible = "samsung,sec-fuelgauge" }, + { } +}; +MODULE_DEVICE_TABLE(of, sec_fuelgauge_dt_ids); +#endif /* CONFIG_OF */ + +static struct platform_driver sec_fuelgauge_driver = { + .driver = { + .name = "sec-fuelgauge", + .pm = &sec_fuelgauge_pm_ops, + .shutdown = sec_fuelgauge_shutdown, +#ifdef CONFIG_OF + .of_match_table = sec_fuelgauge_dt_ids, +#endif + }, + .probe = sec_fuelgauge_probe, + .remove = sec_fuelgauge_remove, +}; + +static int __init sec_fuelgauge_init(void) +{ + pr_info("[%s] start !!!!! \n", __func__); + return platform_driver_register(&sec_fuelgauge_driver); +} + +static void __exit sec_fuelgauge_exit(void) +{ + platform_driver_unregister(&sec_fuelgauge_driver); +} + +#else +static int sec_fuelgauge_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sec_fuelgauge_info *fuelgauge; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int ret = 0; + bool fuelalert_init_ret = false; + union power_supply_propval raw_soc_val; + + dev_dbg(&client->dev, + "%s: SEC Fuelgauge Driver Loading\n", __func__); + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + fuelgauge = kzalloc(sizeof(*fuelgauge), GFP_KERNEL); + if (!fuelgauge) + return -ENOMEM; + + mutex_init(&fuelgauge->fg_lock); + + fuelgauge->client = client; + fuelgauge->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, fuelgauge); + + fuelgauge->psy_fg.name = "sec-fuelgauge"; + fuelgauge->psy_fg.type = POWER_SUPPLY_TYPE_UNKNOWN; + fuelgauge->psy_fg.get_property = sec_fg_get_property; + fuelgauge->psy_fg.set_property = sec_fg_set_property; + fuelgauge->psy_fg.properties = sec_fuelgauge_props; + fuelgauge->psy_fg.num_properties = + ARRAY_SIZE(sec_fuelgauge_props); + fuelgauge->capacity_max = fuelgauge->pdata->capacity_max; + raw_soc_val.intval = SEC_FUELGAUGE_CAPACITY_TYPE_RAW; + sec_hal_fg_get_property(fuelgauge->client, + POWER_SUPPLY_PROP_CAPACITY, &raw_soc_val); + raw_soc_val.intval /= 10; + if(raw_soc_val.intval > fuelgauge->pdata->capacity_max) + sec_fg_calculate_dynamic_scale(fuelgauge); + + if (!fuelgauge->pdata->fg_gpio_init()) { + dev_err(&client->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_free; + } + + if (!sec_hal_fg_init(fuelgauge->client)) { + dev_err(&client->dev, + "%s: Failed to Initialize Fuelgauge\n", __func__); + goto err_free; + } + + ret = power_supply_register(&client->dev, &fuelgauge->psy_fg); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Register psy_fg\n", __func__); + goto err_free; + } +#if 0 + fuelgauge->is_fuel_alerted = false; + if (fuelgauge->pdata->fuel_alert_soc >= 0) { + fuelalert_init_ret = + sec_hal_fg_fuelalert_init(fuelgauge->client, + fuelgauge->pdata->fuel_alert_soc); + if (fuelalert_init_ret) + wake_lock_init(&fuelgauge->fuel_alert_wake_lock, + WAKE_LOCK_SUSPEND, "fuel_alerted"); + else { + dev_err(&client->dev, + "%s: Failed to Initialize Fuel-alert\n", + __func__); + goto err_irq; + } + } + + if (fuelgauge->pdata->fg_irq) { + INIT_DELAYED_WORK(&fuelgauge->isr_work, sec_fg_isr_work); + + ret = request_threaded_irq(fuelgauge->pdata->fg_irq, + NULL, sec_fg_irq_thread, + fuelgauge->pdata->fg_irq_attr, + "fuelgauge-irq", fuelgauge); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Reqeust IRQ\n", __func__); + goto err_supply_unreg; + } + + ret = enable_irq_wake(fuelgauge->pdata->fg_irq); + if (ret < 0) + dev_err(&client->dev, + "%s: Failed to Enable Wakeup Source(%d)\n", + __func__, ret); + } +#endif + fuelgauge->initial_update_of_soc = true; + + ret = sec_fg_create_attrs(fuelgauge->psy_fg.dev); + if (ret) { + dev_err(&client->dev, + "%s : Failed to create_attrs\n", __func__); + goto err_irq; + } + + dev_dbg(&client->dev, + "%s: SEC Fuelgauge Driver Loaded\n", __func__); + return 0; + +err_irq: + if (fuelgauge->pdata->fg_irq) + free_irq(fuelgauge->pdata->fg_irq, fuelgauge); +err_supply_unreg: + power_supply_unregister(&fuelgauge->psy_fg); +err_free: + mutex_destroy(&fuelgauge->fg_lock); + kfree(fuelgauge); + + return ret; +} + +static int sec_fuelgauge_remove(struct i2c_client *client) +{ + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + + if (fuelgauge->pdata->fuel_alert_soc >= 0) + wake_lock_destroy(&fuelgauge->fuel_alert_wake_lock); + + return 0; +} + +static int sec_fuelgauge_suspend(struct device *dev) +{ + struct i2c_client *client = + container_of(dev, struct i2c_client, dev); + + if (!sec_hal_fg_suspend(client)) + dev_err(dev, "%s: Failed to Suspend Fuelgauge\n", __func__); + + return 0; +} + +static int sec_fuelgauge_resume(struct device *dev) +{ + struct i2c_client *client = + container_of(dev, struct i2c_client, dev); + struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); + + if (!sec_hal_fg_resume(client)) + dev_err(dev, "%s: Failed to Resume Fuelgauge\n", __func__); + + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC) + fuelgauge->initial_update_of_soc = true; + + return 0; +} + +static void sec_fuelgauge_shutdown(struct device *dev) +{ +} + +static const struct i2c_device_id sec_fuelgauge_id[] = { + {"sec-fuelgauge", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, sec_fuelgauge_id); + +static const struct dev_pm_ops sec_fuelgauge_pm_ops = { + .suspend = sec_fuelgauge_suspend, + .resume = sec_fuelgauge_resume, +}; + +static struct i2c_driver sec_fuelgauge_driver = { + .driver = { + .name = "sec-fuelgauge", + .owner = THIS_MODULE, + .pm = &sec_fuelgauge_pm_ops, + .shutdown = sec_fuelgauge_shutdown, + }, + .probe = sec_fuelgauge_probe, + .remove = sec_fuelgauge_remove, + .id_table = sec_fuelgauge_id, +}; + +static int __init sec_fuelgauge_init(void) +{ + return i2c_add_driver(&sec_fuelgauge_driver); +} + +static void __exit sec_fuelgauge_exit(void) +{ + i2c_del_driver(&sec_fuelgauge_driver); +} +#endif + +module_init(sec_fuelgauge_init); +module_exit(sec_fuelgauge_exit); + +MODULE_DESCRIPTION("Samsung Fuel Gauge Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/sm5414_charger.c b/drivers/battery/sm5414_charger.c new file mode 100644 index 00000000..cb544896 --- /dev/null +++ b/drivers/battery/sm5414_charger.c @@ -0,0 +1,666 @@ +/* + * SM5414_charger.c + * SiliconMitus SM5414 Charger Driver + * + * Copyright (C) 2013 SiliconMitus + * + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#define DEBUG + +#include <linux/battery/sec_charger.h> +#include <linux/seq_file.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/gpio.h> +#include <linux/gpio-pxa.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_irq.h> + +static int charger_health = POWER_SUPPLY_HEALTH_GOOD; + +static int SM5414_i2c_write(struct i2c_client *client, + int reg, u8 *buf) +{ + int ret; + ret = i2c_smbus_write_i2c_block_data(client, reg, 1, buf); + if (ret < 0) + dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret); + + return ret; +} + +static int SM5414_i2c_read(struct i2c_client *client, + int reg, u8 *buf) +{ + int ret; + ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); + if (ret < 0) + dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret); + + return ret; +} + +#if 0 +static void SM5414_test_read(struct i2c_client *client) +{ + u8 data = 0; + u32 addr = 0; + + //0x00~02 are R/C + for (addr = SM5414_INTMASK1; addr <= SM5414_CHGCTRL5; addr++) { + SM5414_i2c_read(client, addr, &data); + dev_info(&client->dev, + "SM5414 addr : 0x%02x data : 0x%02x\n", addr, data); + } +} +#endif + +static void SM5414_read_regs(struct i2c_client *client, char *str) +{ + u8 data = 0; + u32 addr = 0; + + //0x00~02 are R/C (read and clear) + for (addr = SM5414_INTMASK1; addr <= SM5414_CHGCTRL5; addr++) { + SM5414_i2c_read(client, addr, &data); + sprintf(str+strlen(str), "0x%x, ", data); + } +} + +static int SM5414_get_battery_present(struct i2c_client *client) +{ + u8 data; + + SM5414_i2c_read(client, SM5414_INT2, &data); + + pr_info("%s: SM5414_INT2 (0x%02x)\n", __func__, data); + + data = ((data & SM5414_INT2_NOBAT) >> 6); + + return data; +} + +static int SM5414_get_charging_status(struct i2c_client *client) +{ + int status = POWER_SUPPLY_STATUS_UNKNOWN; + struct sec_charger_info *charger = i2c_get_clientdata(client); + int nCHG; + u8 int2, chg_en; + union power_supply_propval value; + + SM5414_i2c_read(client, SM5414_INT2, &int2); + SM5414_i2c_read(client, SM5414_CTRL, &chg_en); + + if((int2 & SM5414_INT2_DONE) || (int2 & SM5414_INT2_TOPOFF)) { + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_CAPACITY, value); + if ((value.intval > 94) && + (charger->cable_type != POWER_SUPPLY_TYPE_BATTERY)) { + status = POWER_SUPPLY_STATUS_FULL; + charger->is_fullcharged = true; + dev_info(&client->dev, + "%s : Power Supply Full\n", __func__); + } + } else if (chg_en & CHARGE_EN) { + nCHG = gpio_get_value(charger->pdata->chg_gpio_en); + if ((nCHG) || (charger_health != POWER_SUPPLY_HEALTH_GOOD)) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; + } else { + status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + return (int)status; +} + +int sec_get_charging_health(struct i2c_client *client) +{ + static int health = POWER_SUPPLY_HEALTH_GOOD; + struct sec_charger_info *charger = i2c_get_clientdata(client); + u8 int1; + int vf_status; + + SM5414_i2c_read(client, SM5414_INT1, &int1); + + dev_info(&client->dev, + "%s : SM5414_INT1 : 0x%02x\n", __func__, int1); + +#if defined(CONFIG_MACH_PIKEAYOUNG2DTV) || defined(CONFIG_MACH_PIKEAJ1) + /* VF check by adc detection */ + if (!sec_vf_adc_check()) + return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; +#endif + + if (int1 & SM5414_INT1_VBUSOVP) { + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + charger_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (int1 & SM5414_INT1_VBUSUVLO) { + msleep(1000); + if (charger->cable_type != POWER_SUPPLY_TYPE_BATTERY) { + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + charger_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } + } else if (int1 & SM5414_INT1_VBUSINOK) { + health = POWER_SUPPLY_HEALTH_GOOD; + charger_health = POWER_SUPPLY_HEALTH_GOOD; + } + + return (int)health; +} + +static u8 SM5414_set_float_voltage_data( + struct i2c_client *client, int float_voltage) +{ + u8 data, float_reg; + + SM5414_i2c_read(client, SM5414_CHGCTRL3, &data); + + data &= BATREG_MASK; + + if (float_voltage < 4100) + float_voltage = 4100; + else if (float_voltage > 4475) + float_voltage = 4475; + + float_reg = (float_voltage - 4100) / 25; + data |= (float_reg << 4); + + SM5414_i2c_write(client, SM5414_CHGCTRL3, &data); + + SM5414_i2c_read(client, SM5414_CHGCTRL3, &data); + dev_dbg(&client->dev, + "%s : SM5414_CHGCTRL3 (float) : 0x%02x\n", __func__, data); + + return data; +} + +static u8 SM5414_set_input_current_limit_data( + struct i2c_client *client, int input_current) +{ + u8 set_reg, curr_reg = 0; + u8 chg_en; + struct sec_charger_info *charger = i2c_get_clientdata(client); + + if(input_current < 100) + input_current = 100; + else if (input_current >= 2050) + input_current = 2050; + + set_reg = (input_current - 100) / 50; + + curr_reg = ((input_current % 100) >= 50) ? 1 : 0; + + chg_en = gpio_get_value(charger->pdata->chg_gpio_en); + + if (!chg_en) { + SM5414_i2c_write(client, SM5414_VBUSCTRL, &set_reg); + } else { + while (set_reg >= curr_reg) { + SM5414_i2c_write(client, SM5414_VBUSCTRL, &curr_reg); + curr_reg += 2; + msleep(50); + }; + } + + SM5414_i2c_read(client, SM5414_VBUSCTRL, &curr_reg); + dev_dbg(&client->dev, + "%s : SM5414_VBUSCTRL (Input limit) : 0x%02x\n", + __func__, curr_reg); + + return curr_reg; +} + +static u8 SM5414_set_topoff_current_limit_data( + struct i2c_client *client, int topoff_current) +{ + u8 data; + u8 topoff_reg; + + SM5414_i2c_read(client, SM5414_CHGCTRL4, &data); + + data &= TOPOFF_MASK; + + if(topoff_current < 100) + topoff_current = 100; + else if (topoff_current > 650) + topoff_current = 650; + + topoff_reg = (topoff_current - 100) / 50; + data = data | (topoff_reg<<3); + + SM5414_i2c_write(client, SM5414_CHGCTRL4, &data); + + SM5414_i2c_read(client, SM5414_CHGCTRL4, &data); + dev_dbg(&client->dev, + "%s : SM5414_CHGCTRL4 (Top-off limit) : 0x%02x\n", + __func__, data); + + return data; +} + +static u8 SM5414_set_fast_charging_current_data( + struct i2c_client *client, int fast_charging_current) +{ + u8 data = 0; + + if(fast_charging_current < 100) + fast_charging_current = 100; + else if (fast_charging_current > 2500) + fast_charging_current = 2500; + + data = (fast_charging_current - 100) / 50; + + SM5414_i2c_write(client, SM5414_CHGCTRL2, &data); + SM5414_i2c_read(client, SM5414_CHGCTRL2, &data); + + dev_dbg(&client->dev, + "%s : SM5414_CHGCTRL2 (fast) : 0x%02x\n", __func__, data); + + return data; +} + +static u8 SM5414_set_toggle_charger(struct i2c_client *client, int enable) +{ + u8 chg_en=0; + u8 data=0; + + SM5414_i2c_read(client, SM5414_CTRL, &chg_en); + if (enable) + chg_en |= CHARGE_EN; + else + chg_en &= ~CHARGE_EN; + + SM5414_i2c_write(client, SM5414_CTRL, &chg_en); + + dev_info(&client->dev, "%s: SM5414 Charger toggled!! \n", __func__); + + SM5414_i2c_read(client, SM5414_CTRL, &chg_en); + dev_info(&client->dev, + "%s : chg_en value(07h register): 0x%02x\n", __func__, chg_en); + + SM5414_i2c_read(client, SM5414_CHGCTRL2, &data); + dev_info(&client->dev, + "%s : SM5414_CHGCTRL2 value: 0x%02x", __func__, data); + + return chg_en; +} + +static void SM5414_charger_function_control( + struct i2c_client *client) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + union power_supply_propval value; + u8 suspend_en; + + if (charger->charging_current < 0) { + dev_info(&client->dev, + "%s : OTG is activated. Ignore command!\n", __func__); + return; + } + + if (charger->cable_type == POWER_SUPPLY_TYPE_BATTERY) { + /* Disable Charger */ + dev_info(&client->dev, + "%s : Disable Charger, Battery Supply!\n", __func__); + // nCHG_EN is logic low so set 1 to disable charger + charger->is_fullcharged = false; + gpio_direction_output((charger->pdata->chg_gpio_en), 1); + } else { + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_CAPACITY, value); + if (value.intval > 0) { + SM5414_i2c_read(client, SM5414_CTRL, &suspend_en); + + /* Suspend enable for register reset */ + suspend_en |= 0x04; + SM5414_i2c_write(client, SM5414_CTRL, &suspend_en); + msleep(20); + suspend_en &= ~0x04; + SM5414_i2c_write(client, SM5414_CTRL, &suspend_en); + } + + dev_info(&client->dev, "%s : float voltage (%dmV)\n", + __func__, charger->pdata->chg_float_voltage); + + /* Set float voltage */ + SM5414_set_float_voltage_data( + client, charger->pdata->chg_float_voltage); + + dev_info(&client->dev, "%s : topoff current (%dmA)\n", + __func__, charger->pdata->charging_current[ + charger->cable_type].full_check_current_1st); + SM5414_set_topoff_current_limit_data( + client, charger->pdata->charging_current[ + charger->cable_type].full_check_current_1st); + + SM5414_set_input_current_limit_data(client, 100); + // nCHG_EN is logic low so set 0 to enable charger + gpio_direction_output((charger->pdata->chg_gpio_en), 0); + msleep(100); + + /* Young2 DTV event charging set */ + if (charger->pdata->siop_level == CALL_EVENT_SIOP) { + charger->charging_current = CALL_EVENT_CURRENT; + charger->input_current_limit = CALL_EVENT_CURRENT; + } else if (charger->pdata->siop_level == HIGH_TEMP_SIOP) { + charger->charging_current = HIGH_TEMP_CURRENT; + charger->input_current_limit = HIGH_TEMP_CURRENT; + } else { + charger->charging_current = + charger->pdata->charging_current + [charger->cable_type].fast_charging_current; + charger->input_current_limit = + charger->pdata->charging_current + [charger->cable_type].input_current_limit; + } + + /* Input current limit */ + dev_info(&client->dev, "%s : input current (%dmA)\n", + __func__, charger->input_current_limit); + + SM5414_set_input_current_limit_data( + client, charger->input_current_limit); + + /* Set fast charge current */ + dev_info(&client->dev, "%s : fast charging current (%dmA), siop_level=%d\n", + __func__, charger->charging_current, charger->pdata->siop_level); + SM5414_set_fast_charging_current_data( + client, charger->charging_current); + + dev_info(&client->dev, + "%s : Enable Charger!\n", __func__); + } +} + +static void SM5414_charger_otg_control( + struct i2c_client *client) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + u8 data; +//turn on/off ENBOOST + if (charger->cable_type == + POWER_SUPPLY_TYPE_BATTERY) { + dev_info(&client->dev, "%s : turn off OTG\n", __func__); + /* turn off OTG */ + SM5414_i2c_read(client, SM5414_CTRL, &data); + data &= 0xfe; + SM5414_i2c_write(client, SM5414_CTRL, &data); + } else { + dev_info(&client->dev, "%s : turn on OTG\n", __func__); + /* turn on OTG */ + SM5414_i2c_read(client, SM5414_CTRL, &data); + data |= 0x01; + SM5414_i2c_write(client, SM5414_CTRL, &data); + } +} + +static int SM5414_debugfs_show(struct seq_file *s, void *data) +{ + struct sec_charger_info *charger = s->private; + u8 reg; + u8 reg_data; + + seq_printf(s, "SM CHARGER IC :\n"); + seq_printf(s, "==================\n"); + for (reg = SM5414_INTMASK1; reg <= SM5414_CHGCTRL5; reg++) { + SM5414_i2c_read(charger->client, reg, ®_data); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + + seq_printf(s, "\n"); + return 0; +} + +static int SM5414_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, SM5414_debugfs_show, inode->i_private); +} + +static const struct file_operations SM5414_debugfs_fops = { + .open = SM5414_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +bool sec_hal_chg_init(struct i2c_client *client) +{ + u8 reg_data; + u8 int1 = 0; + u8 chg_en = 0; + struct sec_charger_info *charger = i2c_get_clientdata(client); + + dev_info(&client->dev, "%s: SM5414 Charger init (Starting)!! \n", __func__); + charger->is_fullcharged = false; + + SM5414_i2c_read(client, SM5414_CTRL, &chg_en); + chg_en |= CHARGE_EN; + SM5414_i2c_write(client, SM5414_CTRL, &chg_en); + msleep(50); + SM5414_i2c_read(client, SM5414_CTRL, &chg_en); + dev_info(&client->dev, + "%s : chg_en value(07h register): 0x%02x\n", __func__, chg_en); + + SM5414_i2c_read(client, SM5414_INT1, &int1); + dev_info(&client->dev, + "%s : SM5414_INT1 : 0x%02x\n", __func__, int1); + + reg_data = 0x1F; + SM5414_i2c_write(client, SM5414_INTMASK1, ®_data); + + reg_data = 0xFC; + SM5414_i2c_write(client, SM5414_INTMASK2, ®_data); + + SM5414_i2c_read(client, SM5414_CHGCTRL1, ®_data); + reg_data &= ~SM5414_CHGCTRL1_AUTOSTOP; + SM5414_i2c_write(client, SM5414_CHGCTRL1, ®_data); + + (void) debugfs_create_file("SM5414_regs", + S_IRUGO, NULL, (void *)charger, &SM5414_debugfs_fops); + + return true; +} + +bool sec_hal_chg_suspend(struct i2c_client *client) +{ + dev_info(&client->dev, + "%s: CHARGER - SM5414(suspend mode)!!\n", __func__); + + return true; +} + +bool sec_hal_chg_resume(struct i2c_client *client) +{ + dev_info(&client->dev, + "%s: CHARGER - SM5414(resume mode)!!\n", __func__); + + return true; +} + +bool sec_hal_chg_shutdown(struct i2c_client *client) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + dev_info(&client->dev, + "%s: CHARGER - SM5414(suspend)!!\n", __func__); + SM5414_set_toggle_charger(client, 1); + gpio_direction_output((charger->pdata->chg_gpio_en), 0); +} + +bool sec_hal_chg_get_property(struct i2c_client *client, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + u8 data; + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + break; + case POWER_SUPPLY_PROP_STATUS: + if (charger->is_fullcharged) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = SM5414_get_charging_status(client); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = charger_health; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + break; + // Have to mention the issue about POWER_SUPPLY_PROP_CHARGE_NOW to Marvel + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_AVG: + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (charger->charging_current) { + SM5414_i2c_read(client, SM5414_VBUSCTRL, &data); + data &= 0x3f; + val->intval = (100 + (data * 50)); + if (val->intval < 2050) + val->intval = (100 + (data * 50)); + /*dev_dbg(&client->dev, + "%s 1: set-current(%dmA), current now(%dmA)\n", + __func__, charger->charging_current, val->intval);*/ + } else { + val->intval = 100; + /*dev_dbg(&client->dev, + "%s 2: set-current(%dmA), current now(%dmA)\n", + __func__, charger->charging_current, val->intval);*/ + } + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = SM5414_get_battery_present(client); + break; + case POWER_SUPPLY_PROP_POWER_STATUS: + val->intval = POWER_SUPPLY_PWR_RDY_UNKNOWN; + break; + default: + return false; + } + return true; +} + +bool sec_hal_chg_set_property(struct i2c_client *client, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + charger->pdata->siop_level = val->intval; + /* val->intval : type */ + case POWER_SUPPLY_PROP_ONLINE: + if (charger->charging_current < 0) + SM5414_charger_otg_control(client); + else if (charger->charging_current > 0) + SM5414_charger_function_control(client); + else { + SM5414_charger_function_control(client); + SM5414_charger_otg_control(client); + } + dev_dbg(&client->dev, "%s: PROP_ONLINE \n", __func__); + break; + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + dev_dbg(&client->dev, "%s: PROP_CURRENT_NOW \n", __func__); + break; +#ifdef CONFIG_CHARGER_SM5414 + case POWER_SUPPLY_PROP_HEALTH: + charger_health = val->intval; + break; +#endif + default: + return false; + } + + return true; +} + +ssize_t sec_hal_chg_show_attrs(struct device *dev, + const ptrdiff_t offset, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_charger_info *chg = + container_of(psy, struct sec_charger_info, psy_chg); + int i = 0; + char *str = NULL; + + switch (offset) { +/* case CHG_REG: */ +/* break; */ + case CHG_DATA: + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", + chg->reg_data); + break; + case CHG_REGS: + str = kzalloc(sizeof(char)*1024, GFP_KERNEL); + if (!str) + return -ENOMEM; + + SM5414_read_regs(chg->client, str); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + str); + + kfree(str); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t sec_hal_chg_store_attrs(struct device *dev, + const ptrdiff_t offset, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_charger_info *chg = + container_of(psy, struct sec_charger_info, psy_chg); + int ret = 0; + int x = 0; + u8 data = 0; + + switch (offset) { + case CHG_REG: + if (sscanf(buf, "%x\n", &x) == 1) { + chg->reg_addr = x; + SM5414_i2c_read(chg->client, + chg->reg_addr, &data); + chg->reg_data = data; + dev_dbg(dev, "%s: (read) addr = 0x%x, data = 0x%x\n", + __func__, chg->reg_addr, chg->reg_data); + ret = count; + } + break; + case CHG_DATA: + if (sscanf(buf, "%x\n", &x) == 1) { + data = (u8)x; + dev_dbg(dev, "%s: (write) addr = 0x%x, data = 0x%x\n", + __func__, chg->reg_addr, data); + SM5414_i2c_write(chg->client, + chg->reg_addr, &data); + ret = count; + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} diff --git a/drivers/battery/sm5701_charger.c b/drivers/battery/sm5701_charger.c new file mode 100644 index 00000000..04045a32 --- /dev/null +++ b/drivers/battery/sm5701_charger.c @@ -0,0 +1,805 @@ +/*
+ * SM5701_charger.c
+ * SiliconMitus SM5701 Charger Driver
+ *
+ * Copyright (C) 2014 SiliconMitus
+ *
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/mfd/sm5701_core.h>
+#include <linux/battery/sec_charger.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#include <linux/gpio-pxa.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+
+extern int sec_chg_dt_init(struct device_node *np,
+ struct device *dev,
+ sec_battery_platform_data_t *pdata);
+extern led_state_charger;
+
+static enum power_supply_property sec_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+static void SM5701_charger_initialize(struct SM5701_charger_data *charger);
+
+static int SM5701_get_battery_present(struct SM5701_charger_data *charger)
+{
+ u8 data;
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_STATUS1, &data);
+
+ pr_info("%s: SM5701_STATUS1 (0x%02x)\n", __func__, data);
+
+ data = ((data & SM5701_STATUS1_NOBAT) >> SM5701_STATUS1_NOBAT_SHIFT);
+
+ return !data;
+}
+
+static int SM5701_get_charging_status(struct SM5701_charger_data *charger)
+{
+ int status = POWER_SUPPLY_STATUS_UNKNOWN;
+ int nCHG;
+ u8 stat2, chg_en, cln;
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_STATUS2, &stat2);
+ pr_info("%s : SM5701_STATUS2 : 0x%02x\n", __func__, stat2);
+
+// Clear interrupt register 2
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_INT2, &cln);
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_CNTL, &chg_en);
+ chg_en &= SM5701_CNTL_OPERATIONMODE;
+
+ nCHG = gpio_get_value(charger->pdata->chg_gpio_en);
+
+ if((stat2 & SM5701_STATUS2_DONE) || (stat2 & SM5701_STATUS2_TOPOFF)) {
+ status = POWER_SUPPLY_STATUS_FULL;
+ charger->is_fullcharged = true;
+ pr_info("%s : Status, Power Supply Full \n", __func__);
+ } else {
+ if (nCHG)
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+
+ return (int)status;
+}
+
+static int SM5701_get_charging_health(struct SM5701_charger_data *charger)
+{
+ static int health = POWER_SUPPLY_HEALTH_GOOD;
+ u8 stat1, cln;
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_STATUS1, &stat1);
+
+ // Clear interrupt register 1
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_INT1, &cln);
+
+ pr_info("%s : Health, SM5701_STATUS1 : 0x%02x\n", __func__, stat1);
+
+ if (stat1 & SM5701_STATUS1_VBUSOVP) {
+ health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ } else if (stat1 & SM5701_STATUS1_VBUSUVLO) {
+ if (charger->cable_type != POWER_SUPPLY_TYPE_BATTERY) {
+ health = POWER_SUPPLY_HEALTH_UNDERVOLTAGE;
+ }
+ } else if (stat1 & SM5701_INT1_VBUSOK) {
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ }
+
+ return (int)health;
+}
+
+static u8 SM5701_set_batreg_voltage(
+ struct SM5701_charger_data *charger, int batreg_voltage)
+{
+ u8 data = 0;
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_CHGCNTL3, &data);
+
+ data &= ~BATREG_MASK;
+
+ if (charger->dev_id < 3) {
+ if ((batreg_voltage*10) < 40125)
+ data = 0x00;
+ else if ((batreg_voltage*10) > 44000)
+ batreg_voltage = 4400;
+ else
+ data = ((batreg_voltage*10 - 40125) / 125);
+ } else {
+ if (batreg_voltage <= 3725)
+ data = 0x0;
+ else if (batreg_voltage >= 4400)
+ data = 0x1F;
+ else if (batreg_voltage <= 4300)
+ data = (batreg_voltage - 3725) / 25;
+ else
+ data = (batreg_voltage - 4300) * 10 / 125 + 23;
+ }
+
+ SM5701_reg_write(charger->SM5701->i2c, SM5701_CHGCNTL3, data);
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_CHGCNTL3, &data);
+ pr_info("%s : SM5701_CHGCNTL3 (Battery regulation voltage) : 0x%02x\n",
+ __func__, data);
+
+ return data;
+}
+
+static u8 SM5701_set_vbuslimit_current(
+ struct SM5701_charger_data *charger, int input_current)
+{
+ u8 data = 0, temp = 0;
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_VBUSCNTL, &data);
+ data &= ~SM5701_VBUSCNTL_VBUSLIMIT;
+
+ if (input_current >= 1200)
+ input_current = 1200;
+
+ if(input_current <= 100)
+ data &= ~SM5701_VBUSCNTL_VBUSLIMIT;
+ else if(input_current <= 500)
+ data |= 0x01;
+ else {
+ temp = (input_current/100)-5;
+ data |= temp;
+ }
+
+ SM5701_reg_write(charger->SM5701->i2c, SM5701_VBUSCNTL, data);
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_VBUSCNTL, &data);
+ pr_info("%s : SM5701_VBUSCNTL (Input current limit) : 0x%02x\n",
+ __func__, data);
+
+ return data;
+}
+
+static u8 SM5701_set_topoff(
+ struct SM5701_charger_data *charger, int topoff_current)
+{
+ u8 data = 0;
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_CHGCNTL1, &data);
+
+ data &= ~SM5701_CHGCNTL1_TOPOFF;
+
+ if(topoff_current < 100)
+ topoff_current = 100;
+ else if (topoff_current > 475)
+ topoff_current = 475;
+
+ data |= (topoff_current - 100) / 25;
+
+ SM5701_reg_write(charger->SM5701->i2c, SM5701_CHGCNTL1, data);
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_CHGCNTL1, &data);
+ pr_info("%s : SM5701_CHGCNTL1 (Top-off current threshold) : 0x%02x\n",
+ __func__, data);
+
+ return data;
+}
+
+static u8 SM5701_set_fastchg_current(
+ struct SM5701_charger_data *charger, int fast_charging_current)
+{
+ u8 data = 0;
+
+ if(fast_charging_current < 100)
+ fast_charging_current = 100;
+ else if (fast_charging_current > 1600)
+ fast_charging_current = 1600;
+
+ data = (fast_charging_current - 100) / 25;
+
+ SM5701_reg_write(charger->SM5701->i2c, SM5701_CHGCNTL2, data);
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_CHGCNTL2, &data);
+ pr_info("%s : SM5701_CHGCNTL2 (fastchg current) : 0x%02x\n",
+ __func__, data);
+
+ return data;
+}
+
+static int SM5701_get_charging_current(
+ struct SM5701_charger_data *charger)
+{
+ u8 data = 0;
+ int get_current = 0;
+
+ if (charger->charging_current) {
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_CHGCNTL2, &data);
+ data &= SM5701_CHGCNTL2_FASTCHG;
+ charger->charging_curr_step = 25;
+ get_current = (100 + (data * charger->charging_curr_step));
+ if (get_current <= 1600)
+ get_current = (100 + (data * charger->charging_curr_step));
+ } else {
+ get_current = 100;
+ }
+
+ get_current = (100 + (data * charger->charging_curr_step));
+
+ pr_debug("%s : Charging current : %dmA\n", __func__, get_current);
+ return get_current;
+}
+
+static u8 SM5701_toggle_charger(struct SM5701_charger_data *charger, int enable)
+{
+ u8 chg_en = 0, mask = 0;
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_CNTL, &chg_en);
+
+ mask = charger->dev_id < 3 ? OP_MODE_CHG_ON : OP_MODE_CHG_ON_REV3;
+ chg_en &= ~mask;
+
+ if ((led_state_charger == LED_DISABLE) || (charger->dev_id != 4)) {
+ if (enable)
+ chg_en |= mask;
+
+ SM5701_reg_write(charger->SM5701->i2c, SM5701_CNTL, chg_en);
+ gpio_direction_output((charger->pdata->chg_gpio_en), !enable);
+
+ pr_info("%s: SM5701 Charger toggled!! \n", __func__);
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_CNTL, &chg_en);
+ pr_info("%s : CNTL register (0x09) : 0x%02x\n", __func__, chg_en);
+
+ } else {
+ SM5701_set_operationmode(SM5701_OPERATIONMODE_FLASH_ON);
+ pr_info("%s: SM5701 Charger toggled!! - flash on!! \n", __func__);
+ gpio_direction_output((charger->pdata->chg_gpio_en), !enable);
+ }
+
+ return chg_en;
+}
+
+static void SM5701_isr_work(struct work_struct *work)
+{
+ union power_supply_propval val, value;
+ struct SM5701_charger_data *charger =
+ container_of(work, struct SM5701_charger_data, isr_work.work);;
+ int full_check_type;
+
+ psy_do_property("battery", get,
+ POWER_SUPPLY_PROP_CHARGE_NOW, val);
+ if (val.intval == SEC_BATTERY_CHARGING_1ST)
+ full_check_type = charger->pdata->full_check_type;
+ else
+ full_check_type = charger->pdata->full_check_type_2nd;
+
+ if (full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) {
+ val.intval = SM5701_get_charging_status(charger);
+
+ switch (val.intval) {
+ case POWER_SUPPLY_STATUS_FULL:
+ pr_err("%s: Interrupted by Full\n", __func__);
+ psy_do_property("battery", set,
+ POWER_SUPPLY_PROP_STATUS, val);
+ break;
+ }
+ }
+
+ if (charger->pdata->ovp_uvlo_check_type ==
+ SEC_BATTERY_OVP_UVLO_CHGINT) {
+ val.intval = SM5701_get_charging_health(charger);
+
+ switch (val.intval) {
+ case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
+ /* case POWER_SUPPLY_HEALTH_UNDERVOLTAGE: */
+ pr_info("%s: Interrupted by OVP/UVLO\n", __func__);
+ psy_do_property("battery", set,
+ POWER_SUPPLY_PROP_HEALTH, val);
+ break;
+ case POWER_SUPPLY_HEALTH_UNDERVOLTAGE:
+ /* case POWER_SUPPLY_HEALTH_UNDERVOLTAGE: */
+ psy_do_property("battery", get,
+ POWER_SUPPLY_PROP_ONLINE, value);
+
+ if (value.intval != POWER_SUPPLY_TYPE_BATTERY) {
+ pr_info("%s: Interrupted by OVP/UVLO\n", __func__);
+ psy_do_property("battery", set,
+ POWER_SUPPLY_PROP_HEALTH, val);
+ }
+ break;
+ case POWER_SUPPLY_HEALTH_GOOD:
+ pr_err("%s: Interrupted but Good\n", __func__);
+ psy_do_property("battery", set,
+ POWER_SUPPLY_PROP_HEALTH, val);
+ break;
+ default:
+ pr_err("%s: Invalid Charger Health\n", __func__);
+ break;
+ }
+ }
+
+ val.intval = SM5701_get_battery_present(charger);
+ pr_info("%s: battery status : %d\n", __func__, val.intval);
+ if (!val.intval)
+ psy_do_property("battery", set,
+ POWER_SUPPLY_PROP_PRESENT, val);
+}
+
+static irqreturn_t SM5701_irq_thread(int irq, void *irq_data)
+{
+ struct SM5701_charger_data *charger = irq_data;
+ pr_info("*** %s ***\n", __func__);
+ // Temporary delay of the interrupt to prevent MUIC interrupt running after it
+ schedule_delayed_work(&charger->isr_work, HZ * 0.25);
+ return IRQ_HANDLED;
+}
+
+static int SM5701_debugfs_show(struct seq_file *s, void *data)
+{
+ struct SM5701_charger_data *charger = s->private;
+ u8 reg;
+ u8 reg_data;
+
+ seq_printf(s, "SM5701 CHARGER IC :\n");
+ seq_printf(s, "===================\n");
+ for (reg = SM5701_INTMASK1; reg <= SM5701_FLEDCNTL6; reg++) {
+ SM5701_reg_read(charger->SM5701->i2c, reg, ®_data);
+ seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data);
+ }
+
+ seq_printf(s, "\n");
+ return 0;
+}
+
+static int SM5701_debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, SM5701_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations SM5701_debugfs_fops = {
+ .open = SM5701_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void SM5701_charger_initialize(struct SM5701_charger_data *charger)
+{
+ u8 reg_data = 0, status1 = 0;
+
+ SM5701_reg_read(charger->SM5701->i2c,
+ SM5701_DEVICE_ID, &charger->dev_id);
+ pr_info("%s: SM5701 Charger init, CHIP REV : %2d !! \n",
+ __func__, charger->dev_id);
+ charger->is_fullcharged = false;
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_VBUSCNTL, ®_data);
+ reg_data &= ~SM5701_VBUSCNTL_AICLEN;
+ SM5701_reg_write(charger->SM5701->i2c, SM5701_VBUSCNTL, reg_data);
+
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_STATUS1, &status1);
+ pr_info("%s : SM5701_STATUS1 : 0x%02x\n", __func__, status1);
+
+/* NOBAT, Enable OVP, UVLO, VBUSOK interrupts */
+ reg_data = 0x63;
+ SM5701_reg_write(charger->SM5701->i2c, SM5701_INTMASK1, reg_data);
+
+/* Mask CHGON, FASTTMROFFM */
+ reg_data = 0xFC;
+ SM5701_reg_write(charger->SM5701->i2c, SM5701_INTMASK2, reg_data);
+
+/* Set OVPSEL to 6.35V
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_CNTL, ®_data);
+ reg_data |= 0x1A;
+ SM5701_reg_write(charger->SM5701->i2c, SM5701_CNTL, reg_data);
+*/
+
+ /* Operating Frequency in PWM BUCK mode : 2.4KHz */
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_CNTL, ®_data);
+ reg_data &= ~0xC0;
+ reg_data |= (FREQ_24 | 0x4);
+ SM5701_reg_write(charger->SM5701->i2c, SM5701_CNTL, reg_data);
+
+ /* Disable AUTOSTOP */
+ SM5701_reg_read(charger->SM5701->i2c, SM5701_CHGCNTL1, ®_data);
+ reg_data &= ~SM5701_CHGCNTL1_AUTOSTOP;
+ SM5701_reg_write(charger->SM5701->i2c, SM5701_CHGCNTL1, reg_data);
+
+ (void) debugfs_create_file("SM5701_regs",
+ S_IRUGO, NULL, (void *)charger, &SM5701_debugfs_fops);
+
+ SM5701_test_read(charger->SM5701->i2c);
+}
+
+static int sec_chg_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct SM5701_charger_data *charger =
+ container_of(psy, struct SM5701_charger_data, psy_chg);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ printk("[%s] got val->intval:%d\n",__func__, val->intval);
+ if(val->intval != POWER_SUPPLY_TYPE_OTG) WARN_ON(1);
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (charger->is_fullcharged)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = SM5701_get_charging_status(charger);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = SM5701_get_charging_health(charger);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = SM5701_get_battery_present(charger);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = charger->charging_current_max;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval = charger->charging_current;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = SM5701_get_charging_current(charger);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ if (!charger->is_charging)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ else if (charger->aicl_on)
+ {
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_SLOW;
+ pr_info("%s: slow-charging mode\n", __func__);
+ }
+ else
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sec_chg_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct SM5701_charger_data *charger =
+ container_of(psy, struct SM5701_charger_data, psy_chg);
+ union power_supply_propval value;
+// int set_charging_current, set_charging_current_max;
+// const int usb_charging_current = charger->pdata->charging_current[
+// POWER_SUPPLY_TYPE_USB].fast_charging_current;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ charger->status = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ charger->cable_type = val->intval;
+ psy_do_property("battery", get,
+ POWER_SUPPLY_PROP_HEALTH, value);
+ if (val->intval == POWER_SUPPLY_TYPE_BATTERY) {
+ /* Disable Charger */
+ charger->is_charging = false;
+ charger->nchgen = true;
+ charger->aicl_on = false;
+#if 0
+ set_charging_current = 0;
+ set_charging_current_max =
+ charger->pdata->charging_current[
+ POWER_SUPPLY_TYPE_USB].input_current_limit;
+#endif
+ pr_info("%s : Disable Charger, Battery Supply!\n", __func__);
+ // nCHG_EN is logic low so set 1 to disable charger
+ charger->is_fullcharged = false;
+ } else if(val->intval == POWER_SUPPLY_TYPE_OTG){
+ charger->is_charging = false;
+ charger->nchgen = true;
+ charger->aicl_on = false;
+ charger->is_fullcharged = false;
+ charger->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ SM5701_set_bstout(SM5701_BSTOUT_5P0);
+ SM5701_set_operationmode(SM5701_OPERATIONMODE_OTG_ON_FLASH_ON);
+ SM5701_test_read(charger->SM5701->i2c);
+
+ } else {
+ charger->is_charging = true;
+ charger->nchgen = false;
+ charger->charging_current_max =
+ charger->pdata->charging_current
+ [charger->cable_type].input_current_limit;
+ charger->charging_current =
+ charger->pdata->charging_current
+ [charger->cable_type].fast_charging_current;
+ /* decrease the charging current according to siop level */
+
+ // If siop is used set charging current according to the following
+#if 0
+ set_charging_current =
+ charger->charging_current * charger->siop_level / 100;
+ if (set_charging_current > 0 &&
+ set_charging_current < usb_charging_current)
+ set_charging_current = usb_charging_current;
+
+ set_charging_current_max =
+ charger->charging_current_max;
+#endif
+ }
+ SM5701_operation_mode_function_control();
+ pr_info("%s : STATUS OF CHARGER ON(0)/OFF(1): %d\n", __func__, charger->nchgen);
+ SM5701_toggle_charger(charger, charger->is_charging);
+
+ /* if battery full, only disable charging */
+ if ((charger->status == POWER_SUPPLY_STATUS_CHARGING) ||
+ (charger->status == POWER_SUPPLY_STATUS_DISCHARGING) ||
+ (charger->status == POWER_SUPPLY_STATUS_FULL) ||
+ (value.intval == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) ||
+ (value.intval == POWER_SUPPLY_HEALTH_OVERHEATLIMIT)) {
+
+ /* Set float voltage */
+ pr_info("%s : float voltage (%dmV)\n",
+ __func__, charger->pdata->chg_float_voltage);
+ SM5701_set_batreg_voltage(
+ charger, charger->pdata->chg_float_voltage);
+
+ /* if battery is removed, put vbus current limit to minimum */
+ if ((value.intval == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) ||
+ (value.intval == POWER_SUPPLY_HEALTH_OVERHEATLIMIT))
+ SM5701_set_vbuslimit_current(charger, 100);
+ else {
+ /* Set input current limit */
+ pr_info("%s : vbus current limit (%dmA)\n",
+ __func__, charger->pdata->charging_current
+ [charger->cable_type].input_current_limit);
+
+ SM5701_set_vbuslimit_current(
+ charger, charger->pdata->charging_current
+ [charger->cable_type].input_current_limit);
+ }
+
+ /* Set topoff current */
+ pr_info("%s : topoff current (%dmA)\n",
+ __func__, charger->pdata->charging_current[
+ charger->cable_type].full_check_current_1st);
+ SM5701_set_topoff(
+ charger, charger->pdata->charging_current[
+ charger->cable_type].full_check_current_1st);
+
+ /* Set fast charge current */
+ pr_info("%s : fast charging current (%dmA), siop_level=%d\n",
+ __func__, charger->charging_current, charger->siop_level);
+ SM5701_set_fastchg_current(
+ charger, charger->charging_current);
+ }
+ break;
+ /* val->intval : input charging current */
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ charger->charging_current_max = val->intval;
+ break;
+ /* val->intval : charging current */
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ charger->charging_current = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ SM5701_set_fastchg_current(charger,
+ val->intval);
+ SM5701_set_vbuslimit_current(charger,
+ val->intval);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static struct of_device_id SM5701_charger_match_table[] = {
+ { .compatible = "sm,sm5701-charger",},
+ {},
+};
+#else
+#define SM5701_charger_match_table NULL
+#endif /* CONFIG_OF */
+
+static int SM5701_charger_probe(struct platform_device *pdev)
+{
+ struct SM5701_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+ struct SM5701_platform_data *pdata = dev_get_platdata(iodev->dev);
+ struct SM5701_charger_data *charger;
+ int ret = 0;
+
+ pr_info("%s: SM5701 Charger Probe Start\n", __func__);
+
+ charger = kzalloc(sizeof(*charger), GFP_KERNEL);
+ if (!charger)
+ return -ENOMEM;
+
+ if (pdev->dev.parent->of_node) {
+ pdev->dev.of_node = of_find_compatible_node(
+ of_node_get(pdev->dev.parent->of_node), NULL,
+ SM5701_charger_match_table[0].compatible);
+ }
+
+ charger->SM5701 = iodev;
+ charger->SM5701->dev = &pdev->dev;
+
+ if (pdev->dev.of_node) {
+ charger->pdata = devm_kzalloc(&pdev->dev, sizeof(*(charger->pdata)),
+ GFP_KERNEL);
+ if (!charger->pdata) {
+ dev_err(&pdev->dev, "Failed to allocate memory\n");
+ ret = -ENOMEM;
+ goto err_parse_dt_nomem;
+ }
+ ret = sec_chg_dt_init(pdev->dev.of_node, &pdev->dev, charger->pdata);
+ if (ret < 0)
+ goto err_parse_dt;
+ } else
+ charger->pdata = pdata->charger_data;
+
+ platform_set_drvdata(pdev, charger);
+
+ if (charger->pdata->charger_name == NULL)
+ charger->pdata->charger_name = "sec-charger";
+
+ charger->psy_chg.name = charger->pdata->charger_name;
+ charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN;
+ charger->psy_chg.get_property = sec_chg_get_property;
+ charger->psy_chg.set_property = sec_chg_set_property;
+ charger->psy_chg.properties = sec_charger_props;
+ charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props);
+
+ charger->siop_level = 100;
+ SM5701_charger_initialize(charger);
+
+ charger->input_curr_limit_step = 500;
+ charger->charging_curr_step= 25;
+ ret = power_supply_register(&pdev->dev, &charger->psy_chg);
+ if (ret) {
+ pr_err("%s: Failed to Register psy_chg\n", __func__);
+ goto err_power_supply_register;
+ }
+
+ SM5701_set_charger_data(charger);
+ if (charger->pdata->chg_irq) {
+ INIT_DELAYED_WORK(&charger->isr_work, SM5701_isr_work);
+
+ ret = request_threaded_irq(charger->pdata->chg_irq,
+ NULL, SM5701_irq_thread,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ /* charger->pdata->chg_irq_attr, */
+ "charger-irq", charger);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "%s: Failed to Reqeust IRQ\n", __func__);
+ goto err_request_irq;
+ }
+
+ ret = enable_irq_wake(charger->pdata->chg_irq);
+ if (ret < 0)
+ dev_err(&pdev->dev,
+ "%s: Failed to Enable Wakeup Source(%d)\n",
+ __func__, ret);
+ }
+ pr_info("%s: SM5701 Charger Probe Loaded\n", __func__);
+ return 0;
+
+err_request_irq:
+ power_supply_unregister(&charger->psy_chg);
+err_power_supply_register:
+ pr_info("%s: err_power_supply_register error \n", __func__);
+err_parse_dt:
+ pr_info("%s: err_parse_dt error \n", __func__);
+err_parse_dt_nomem:
+ pr_info("%s: err_parse_dt_nomem error \n", __func__);
+ kfree(charger);
+ return ret;
+}
+
+static int SM5701_charger_remove(struct platform_device *pdev)
+{
+ struct SM5701_charger_data *charger =
+ platform_get_drvdata(pdev);
+ power_supply_unregister(&charger->psy_chg);
+ kfree(charger);
+
+ return 0;
+}
+
+bool SM5701_charger_suspend(struct SM5701_charger_data *charger)
+{
+ pr_info("%s: CHARGER - SM5701(suspend mode)!!\n", __func__);
+ return true;
+}
+
+bool SM5701_charger_resume(struct SM5701_charger_data *charger)
+{
+ pr_info("%s: CHARGER - SM5701(resume mode)!!\n", __func__);
+ return true;
+}
+
+static void SM5701_charger_shutdown(struct device *dev)
+{
+ struct SM5701_charger_data *charger =
+ dev_get_drvdata(dev);
+
+ pr_info("%s: SM5701 Charger driver shutdown\n", __func__);
+ if (!charger->SM5701->i2c) {
+ pr_err("%s: no SM5701 i2c client\n", __func__);
+ return;
+ }
+}
+
+static const struct platform_device_id SM5701_charger_id[] = {
+ { "sm5701-charger", 0},
+ { },
+};
+MODULE_DEVICE_TABLE(platform, SM5701_charger_id);
+
+static struct platform_driver SM5701_charger_driver = {
+ .driver = {
+ .name = "sm5701-charger",
+ .owner = THIS_MODULE,
+ .of_match_table = SM5701_charger_match_table,
+ .shutdown = SM5701_charger_shutdown,
+ },
+ .probe = SM5701_charger_probe,
+ .remove = SM5701_charger_remove,
+ .id_table = SM5701_charger_id,
+};
+
+static int __init SM5701_charger_init(void)
+{
+ pr_info("%s\n", __func__);
+ return platform_driver_register(&SM5701_charger_driver);
+}
+
+subsys_initcall(SM5701_charger_init);
+
+static void __exit SM5701_charger_exit(void)
+{
+ platform_driver_unregister(&SM5701_charger_driver);
+}
+
+module_exit(SM5701_charger_exit);
+
+MODULE_DESCRIPTION("SILICONMITUS SM5701 Charger Driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:SM5701_charger");
+
diff --git a/drivers/battery/smb328_charger.c b/drivers/battery/smb328_charger.c new file mode 100755 index 00000000..23cf5e08 --- /dev/null +++ b/drivers/battery/smb328_charger.c @@ -0,0 +1,808 @@ +/* + * smb328_charger.c + * Samsung SMB328 Charger Driver + * + * Copyright (C) 2013 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 <linux/battery/sec_charger.h> +#include <linux/battery/charger/smb328_charger.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/interrupt.h> + +#include <linux/gpio-pxa.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_irq.h> + +#if defined(SIOP_CHARGING_CURRENT_LIMIT_FEATURE) +#define SIOP_CHARGING_LIMIT_CURRENT 800 +static bool is_siop_limited; +#endif + +static enum power_supply_property sec_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, +}; + +static int smb328_i2c_write(struct i2c_client *client, + int reg, u8 *buf) +{ + int ret; + ret = i2c_smbus_write_i2c_block_data(client, reg, 1, buf); + if (ret < 0) + pr_err("%s: Error(%d) : 0x%x\n", __func__, ret, reg); + + return ret; +} + +static int smb328_i2c_read(struct i2c_client *client, + int reg, u8 *buf) +{ + int ret; + ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); + if (ret < 0) + pr_err("%s: Error(%d) : 0x%x\n", __func__, ret, reg); + + return ret; +} + +static void smb328_i2c_write_array(struct i2c_client *client, + u8 *buf, int size) +{ + int i; + for (i = 0; i < size; i += 3) + smb328_i2c_write(client, (u8) (*(buf + i)), (buf + i) + 1); +} + +static void smb328_set_command(struct i2c_client *client, + int reg, int datum) +{ + int val; + u8 data = 0; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + pr_debug("%s : reg(0x%02x): 0x%02x", __func__, reg, data); + if (data != datum) { + data = datum; + if (smb328_i2c_write(client, reg, &data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) + pr_debug(" => 0x%02x\n", data); + } + } +} + +static void smb328_test_read(struct i2c_client *client) +{ + u8 data = 0; + u32 addr = 0; + for (addr = 0; addr < 0x0c; addr++) { + smb328_i2c_read(client, addr, &data); + pr_info("smb328 addr : 0x%02x data : 0x%02x\n", addr, data); + } + for (addr = 0x31; addr < 0x3D; addr++) { + smb328_i2c_read(client, addr, &data); + pr_info("smb328 addr : 0x%02x data : 0x%02x\n", addr, data); + } +} + +static void smb328_read_regs(struct i2c_client *client, char *str) +{ + u8 data = 0; + u32 addr = 0; + + for (addr = 0x0; addr < 0x0A; addr++) { + smb328_i2c_read(client, addr, &data); + sprintf(str+strlen(str), "0x%x, ", data); + } + + /* "#" considered as new line in application */ + sprintf(str+strlen(str), "#"); + + for (addr = 0x31; addr < 0x39; addr++) { + smb328_i2c_read(client, addr, &data); + sprintf(str+strlen(str), "0x%x, ", data); + } +} + +static int smb328_get_charging_status(struct i2c_client *client) +{ + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 stat_c = 0; + + smb328_i2c_read(client, SMB328_BATTERY_CHARGING_STATUS_C, &stat_c); + pr_debug("%s : Charging status C(0x%02x)\n", __func__, stat_c); + + /* At least one charge cycle terminated, + * Charge current < Termination Current + */ + if (stat_c & 0xc0) { + /* top-off by full charging */ + status = POWER_SUPPLY_STATUS_FULL; + return status; + } + + /* Is enabled ? */ + if (stat_c & 0x01) { + /* check for 0x30 : 'safety timer' (0b01 or 0b10) or + * 'waiting to begin charging' (0b11) + * check for 0x06 : no charging (0b00) + */ + /* not charging */ + if ((stat_c & 0x30) || !(stat_c & 0x06)) + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; + } else { + status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + return (int)status; +} + +static int smb328_get_battery_present(struct i2c_client *client) +{ + u8 reg_data, irq_data; + + smb328_i2c_read(client, SMB328_BATTERY_CHARGING_STATUS_B, ®_data); + smb328_i2c_read(client, SMB328_INTERRUPT_STATUS_C, &irq_data); + + reg_data = ((reg_data & 0x01) && (irq_data & 0x10)); + + return !reg_data; +} + +static void smb328_set_writable(struct i2c_client *client, int writable) +{ + u8 reg_data; + + smb328_i2c_read(client, SMB328_COMMAND, ®_data); + + if (writable) + reg_data |= CMD_A_ALLOW_WRITE; + else + reg_data &= ~CMD_A_ALLOW_WRITE; + + smb328_i2c_write(client, SMB328_COMMAND, ®_data); +} + +static u8 smb328_set_charge_enable(struct i2c_client *client, int enable) +{ + u8 chg_en; + u8 reg_data; + + reg_data = 0x00; + + smb328_i2c_write(client, SMB328_FUNCTION_CONTROL_C, ®_data); + + smb328_i2c_read(client, SMB328_COMMAND, &chg_en); + if (!enable) + chg_en |= CMD_CHARGE_EN; + else + chg_en &= ~CMD_CHARGE_EN; + + smb328_i2c_write(client, SMB328_COMMAND, &chg_en); + + return chg_en; +} + +static u8 smb328_set_float_voltage(struct i2c_client *client, int float_voltage) +{ + u8 reg_data, float_data; + + float_voltage = 4350; + + if (float_voltage < 3460) + float_data = 0; + else if (float_voltage <= 4340) + float_data = (float_voltage - 3500) / 20 + 2; + else if ((float_voltage == 4350) || (float_voltage == 4360)) + float_data = 0x2D; /* (4340 -3500)/20 + 1 */ + else + float_data = 0x3F; + + smb328_i2c_read(client, SMB328_FLOAT_VOLTAGE, ®_data); + reg_data &= ~CFG_FLOAT_VOLTAGE_MASK; + reg_data |= float_data << CFG_FLOAT_VOLTAGE_SHIFT; + + smb328_i2c_write(client, SMB328_FLOAT_VOLTAGE, ®_data); + + return reg_data; +} + +static u8 smb328_set_input_current_limit(struct i2c_client *client, + int input_current) +{ + u8 curr_data, reg_data; + + curr_data = input_current < 500 ? 0x0 : + input_current > 1200 ? 0x7 : + (input_current - 500) / 100; + + smb328_i2c_read(client, SMB328_INPUT_CURRENTLIMIT, ®_data); + reg_data &= ~CFG_INPUT_CURRENT_MASK; + reg_data |= curr_data << CFG_INPUT_CURRENT_SHIFT; + + smb328_i2c_write(client, SMB328_INPUT_CURRENTLIMIT, ®_data); + + pr_info("%s: set current limit : 0x%x\n", __func__, reg_data); + + return reg_data; +} + +static u8 smb328_set_termination_current_limit(struct i2c_client *client, + int termination_current) +{ + u8 reg_data, term_data; + + term_data = termination_current < 25 ? 0x0 : + termination_current > 200 ? 0x7 : + (termination_current - 25) / 25; + + /* Charge completion termination current */ + smb328_i2c_read(client, SMB328_CHARGE_CURRENT, ®_data); + reg_data &= ~CFG_TERMINATION_CURRENT_MASK; + reg_data |= term_data << CFG_TERMINATION_CURRENT_SHIFT; + + smb328_i2c_write(client, SMB328_CHARGE_CURRENT, ®_data); + + /* set STAT assertion termination current */ + smb328_i2c_read(client, SMB328_VARIOUS_FUNCTIONS, ®_data); + reg_data &= ~CFG_STAT_ASSETION_TERM_MASK; + reg_data |= term_data << CFG_STAT_ASSETION_TERM_SHIFT; + + smb328_i2c_write(client, SMB328_VARIOUS_FUNCTIONS, ®_data); + + return reg_data; +} + +static u8 smb328_set_fast_charging_current(struct i2c_client *client, + int fast_charging_current) +{ + u8 reg_data, chg_data; + + chg_data = fast_charging_current < 500 ? 0x0 : + fast_charging_current > 1200 ? 0x7 : + (fast_charging_current - 500) / 100; + + smb328_i2c_read(client, SMB328_CHARGE_CURRENT, ®_data); + reg_data &= ~CFG_CHARGE_CURRENT_MASK; + reg_data |= chg_data << CFG_CHARGE_CURRENT_SHIFT; + + smb328_i2c_write(client, SMB328_CHARGE_CURRENT, ®_data); + + pr_info("%s: Charge Current : 0x%x\n", __func__, reg_data); + + return reg_data; +} + +static void smb328_charger_function_control(struct sec_charger_info *charger) +{ + u8 reg_data, charge_mode; + + smb328_set_writable(charger->client, 1); + reg_data = 0x6E; + smb328_i2c_write(charger->client, SMB328_FUNCTION_CONTROL_B, ®_data); + + smb328_i2c_read(charger->client, + SMB328_INTERRUPT_SIGNAL_SELECTION, ®_data); + reg_data |= 0x1; + smb328_i2c_write(charger->client, + SMB328_INTERRUPT_SIGNAL_SELECTION, ®_data); + + if (charger->cable_type == POWER_SUPPLY_TYPE_BATTERY) { + /* turn off charger */ + smb328_set_charge_enable(charger->client, 0); + } else { + pr_info("%s: Input : %d, Charge : %d\n", __func__, + charger->pdata->charging_current[charger->cable_type].input_current_limit, + charger->pdata->charging_current[charger->cable_type].fast_charging_current); + /* AICL enable */ + smb328_i2c_read(charger->client, + SMB328_INPUT_CURRENTLIMIT, ®_data); + reg_data &= ~CFG_AICL_ENABLE; + reg_data &= ~CFG_AICL_VOLTAGE; /* AICL enable voltage 4.25V */ + smb328_i2c_write(charger->client, + SMB328_INPUT_CURRENTLIMIT, ®_data); + + /* Function control A */ + reg_data = 0xDA; + smb328_i2c_write(charger->client, + SMB328_FUNCTION_CONTROL_A, ®_data); + + /* 4.2V float voltage */ + smb328_set_float_voltage(charger->client, + charger->pdata->chg_float_voltage); + + /* Set termination current */ + smb328_set_termination_current_limit(charger->client, + charger->pdata->charging_current[charger-> + cable_type].full_check_current_1st); + + smb328_set_input_current_limit(charger->client, + charger->pdata->charging_current + [charger->cable_type].input_current_limit); + + smb328_set_fast_charging_current(charger->client, + charger->pdata->charging_current + [charger->cable_type].fast_charging_current); + + /* SET USB5/1, AC/USB Mode */ + charge_mode = (charger->cable_type == POWER_SUPPLY_TYPE_MAINS) || + (charger->cable_type == POWER_SUPPLY_TYPE_UARTOFF) ? + 0x3 : 0x2; + smb328_i2c_read(charger->client, SMB328_COMMAND, ®_data); + reg_data &= ~0x0C; + reg_data |= charge_mode << 2; + smb328_i2c_write(charger->client, SMB328_COMMAND, ®_data); + + smb328_set_charge_enable(charger->client, 1); + } + smb328_test_read(charger->client); + smb328_set_writable(charger->client, 0); +} + +static int smb328_get_charging_health(struct i2c_client *client) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + int health = POWER_SUPPLY_HEALTH_GOOD; + u8 status_reg, data; + + smb328_i2c_read(client, SMB328_INTERRUPT_STATUS_C, &status_reg); + pr_info("%s : Interrupt status C(0x%02x)\n", __func__, status_reg); + + /* Is enabled ? */ + if (status_reg & 0x2) { + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (status_reg & 0x4) { + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (status_reg & 0x80) { /* watchdog enable workaround */ + /* clear IRQ */ + data = 0xFF; + smb328_i2c_write(client, 0x30, &data); + /* reset function control */ + smb328_charger_function_control(charger); + } + + return (int)health; +} + +static int smb328_get_charge_now(struct i2c_client *client) +{ + u8 chg_now, data, addr; + + smb328_i2c_read(client, SMB328_BATTERY_CHARGING_STATUS_A, &chg_now); + + for (addr = 0; addr < 0x0c; addr++) { + smb328_i2c_read(client, addr, &data); + pr_debug("smb328 addr : 0x%02x data : 0x%02x\n", addr, data); + } + for (addr = 0x31; addr <= 0x3D; addr++) { + smb328_i2c_read(client, addr, &data); + pr_debug("smb328 addr : 0x%02x data : 0x%02x\n", addr, data); + } + + /* Clear IRQ */ + data = 0xFF; + smb328_i2c_write(client, 0x30, &data); + + return (chg_now & 0x2); +} + +static void smb328_charger_otg_control(struct sec_charger_info *charger, + int enable) +{ + u8 reg_data; + + smb328_i2c_read(charger->client, SMB328_OTG_POWER_AND_LDO, ®_data); + + if (!enable) + reg_data |= CFG_OTG_ENABLE; + else + reg_data &= ~CFG_OTG_ENABLE; + + smb328_set_writable(charger->client, 1); + smb328_i2c_write(charger->client, SMB328_OTG_POWER_AND_LDO, ®_data); + smb328_set_writable(charger->client, 0); +} + +static void smb328_irq_enable(struct i2c_client *client) +{ + u8 data; + + smb328_set_writable(client, 1); + data = 0x6D; + smb328_i2c_write(client, 0x04, &data); + + smb328_i2c_read(client, + SMB328_INTERRUPT_SIGNAL_SELECTION, &data); + data |= 0x1; + smb328_i2c_write(client, + SMB328_INTERRUPT_SIGNAL_SELECTION, &data); + + smb328_set_writable(client, 0); +} + +static void smb328_irq_disable(struct i2c_client *client) +{ + u8 data; + + smb328_set_writable(client, 1); + data = 0x6E; + smb328_i2c_write(client, 0x04, &data); + + smb328_i2c_read(client, + SMB328_INTERRUPT_SIGNAL_SELECTION, &data); + data &= 0xfe; + smb328_i2c_write(client, + SMB328_INTERRUPT_SIGNAL_SELECTION, &data); + + smb328_set_writable(client, 0); +} + +static int smb328_debugfs_show(struct seq_file *s, void *data) +{ + struct sec_charger_info *charger = s->private; + u8 reg; + u8 reg_data; + + seq_printf(s, "SMB CHARGER IC :\n"); + seq_printf(s, "==================\n"); + for (reg = 0x00; reg <= 0x0A; reg++) { + smb328_i2c_read(charger->client, reg, ®_data); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + + for (reg = 0x30; reg <= 0x39; reg++) { + smb328_i2c_read(charger->client, reg, ®_data); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + + seq_printf(s, "\n"); + return 0; +} + +static int smb328_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, smb328_debugfs_show, inode->i_private); +} + +static const struct file_operations smb328_debugfs_fops = { + .open = smb328_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static bool smb328_chg_init(struct sec_charger_info *charger) +{ + smb328_irq_disable(charger->client); + + (void) debugfs_create_file("smb328-regs", + S_IRUGO, NULL, (void *)charger, &smb328_debugfs_fops); + + return true; +} + +static int sec_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_charger_info *charger = + container_of(psy, struct sec_charger_info, psy_chg); + u8 data; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = smb328_get_charging_status(charger->client); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (!charger->is_charging) + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + else + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = smb328_get_charging_health(charger->client); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = smb328_get_battery_present(charger->client); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + smb328_irq_disable(charger->client); + if (charger->charging_current) { + smb328_i2c_read(charger->client, SMB328_CHARGE_CURRENT, &data); + switch (data >> 5) { + case 0: + val->intval = 450; + break; + case 1: + val->intval = 600; + break; + case 2: + val->intval = 700; + break; + case 3: + val->intval = 800; + break; + case 4: + val->intval = 900; + break; + case 5: + val->intval = 1000; + break; + case 6: + val->intval = 1100; + break; + case 7: + val->intval = 1200; + break; + } + } else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = smb328_get_charge_now(charger->client); + break; +#if defined(CONFIG_FUELGAUGE_88PM822) + case POWER_SUPPLY_PROP_POWER_STATUS: + val->intval = smb328_get_power_status(charger); + break; +#endif + default: + return false; + } + return true; +} + +static int sec_chg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_charger_info *charger = + container_of(psy, struct sec_charger_info, psy_chg); + int cable_type, current_now; + + switch (psp) { + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + smb328_set_writable(charger->client, 1); + current_now = + charger->charging_current * val->intval / 100; + smb328_set_fast_charging_current(charger->client, current_now); + smb328_set_writable(charger->client, 0); + break; + /* val->intval : type */ + case POWER_SUPPLY_PROP_ONLINE: + if (charger->charging_current < 0) + smb328_charger_otg_control(charger, 1); + else if (charger->charging_current > 0) { + smb328_charger_function_control(charger); + smb328_charger_otg_control(charger, 1); + } else { + smb328_charger_function_control(charger); + smb328_charger_otg_control(charger, 0); + } + break; + default: + return false; + } + return true; +} + +#ifdef CONFIG_OF +static int smb328_charger_parse_dt(struct device *dev, + sec_battery_platform_data_t *pdata) +{ + struct device_node *np = dev->of_node; + int ret = 0; + int i, len; + const u32 *p; + + if (np == NULL) { + pr_err("%s np NULL\n", __func__); + } else { + ret = of_property_read_u32(np, "chg_float_voltage", + &pdata->chg_float_voltage); + } + + np = of_find_node_by_name(NULL, "sec-battery"); + if (!np) { + pr_err("%s np NULL\n", __func__); + } else { + p = of_get_property(np, "battery,input_current_limit", &len); + if (!p) + return 1; + + len = len / sizeof(u32); + + pdata->charging_current = kzalloc(sizeof(sec_charging_current_t) * len, + GFP_KERNEL); + + for(i = 0; i < len; i++) { + ret = of_property_read_u32_index(np, + "battery,input_current_limit", i, + &pdata->charging_current[i].input_current_limit); + ret = of_property_read_u32_index(np, + "battery,fast_charging_current", i, + &pdata->charging_current[i].fast_charging_current); + ret = of_property_read_u32_index(np, + "battery,full_check_current_1st", i, + &pdata->charging_current[i].full_check_current_1st); + ret = of_property_read_u32_index(np, + "battery,full_check_current_2nd", i, + &pdata->charging_current[i].full_check_current_2nd); + } + } + + return ret; +} + +static struct of_device_id sec_charger_dt_ids[] = { + { .compatible = "QC,smb328", }, + {} +}; +MODULE_DEVICE_TABLE(of, sec_charger_dt_ids); + +#else +static int max77843_charger_parse_dt(struct max77843_charger_data *charger) +{ + return -ENOSYS; +} + +#define rt5033_charger_match_table NULL +#endif + + +static int smb328_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + sec_battery_platform_data_t *pdata; + struct sec_charger_info *charger; + int ret = 0; + + dev_info(&client->dev, + "%s: SEC Charger Driver Loading\n", __func__); + + if (IS_ENABLED(CONFIG_OF)) { + if (!pdata) + pdata = kzalloc(sizeof(sec_battery_platform_data_t), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + ret = smb328_charger_parse_dt(&client->dev, pdata); + if (ret) + return ret; + } else { + pdata = client->dev.platform_data; + } + + ret = i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK); + if (ret < 0) + return -EIO; + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger->client = client; + charger->pdata = pdata; + + i2c_set_clientdata(client, charger); + + charger->psy_chg.name = "sec-charger"; + charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN; + charger->psy_chg.get_property = sec_chg_get_property; + charger->psy_chg.set_property = sec_chg_set_property; + charger->psy_chg.properties = sec_charger_props; + charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props); + + if (charger->pdata->chg_gpio_init) { + if (!charger->pdata->chg_gpio_init()) { + dev_err(&client->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_free; + } + } + + pr_info("%s: Clinet Address : 0x%x, Add : 0x%x\n", __func__, charger->client->addr, &charger->client->addr); + if (!smb328_chg_init(charger)) { + dev_err(&client->dev, + "%s: Failed to Initialize Charger\n", __func__); + goto err_free; + } + + ret = power_supply_register(&client->dev, &charger->psy_chg); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Register psy_chg\n", __func__); + goto err_free; + } + + dev_dbg(&client->dev, + "%s: SEC Charger Driver Loaded\n", __func__); + return 0; + +err_supply_unreg: + power_supply_unregister(&charger->psy_chg); +err_free: + kfree(charger); + kfree(pdata); + + return ret; +} + +static int smb328_charger_remove( + struct i2c_client *client) +{ + return 0; +} + +static int smb328_charger_suspend(struct i2c_client *client, + pm_message_t state) +{ + return 0; +} + +static int smb328_charger_resume(struct i2c_client *client) +{ + return 0; +} + +static void smb328_charger_shutdown(struct i2c_client *client) +{ +} + +static const struct i2c_device_id sec_charger_id[] = { + {"smb328-charger", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, sec_charger_id); + +static struct i2c_driver sec_charger_driver = { + .driver = { + .name = "smb328-charger", + .of_match_table = sec_charger_dt_ids, + }, + .probe = smb328_charger_probe, + .remove = smb328_charger_remove, + .suspend = smb328_charger_suspend, + .resume = smb328_charger_resume, + .shutdown = smb328_charger_shutdown, + .id_table = sec_charger_id, +}; + +static int __init smb328_charger_init(void) +{ + return i2c_add_driver(&sec_charger_driver); +} + +static void __exit smb328_charger_exit(void) +{ + i2c_del_driver(&sec_charger_driver); +} + +module_init(smb328_charger_init); +module_exit(smb328_charger_exit); + +MODULE_DESCRIPTION("SMB328 Charger Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/smb358_charger.c b/drivers/battery/smb358_charger.c new file mode 100755 index 00000000..a6b0117b --- /dev/null +++ b/drivers/battery/smb358_charger.c @@ -0,0 +1,2022 @@ +/* + * smb358_charger.c + * Samsung SMB358 Charger 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_charger.h> + +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/of_gpio.h> + +#ifndef SLOW_CHARGING_CURRENT_STANDARD + +#if defined(CONFIG_MACH_HEAT_AIO) +#define SLOW_CHARGING_CURRENT_STANDARD 400 +#else +#define SLOW_CHARGING_CURRENT_STANDARD 1000 +#endif + +#endif + +static int smb358_i2c_write(struct i2c_client *client, + int reg, u8 *buf) +{ + int ret; + ret = i2c_smbus_write_i2c_block_data(client, reg, 1, buf); + if (ret < 0) + dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret); + return ret; +} + +static int smb358_i2c_read(struct i2c_client *client, + int reg, u8 *buf) +{ + int ret; + ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); + if (ret < 0) + dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret); + return ret; +} + +/*static void smb358_i2c_write_array(struct i2c_client *client, + u8 *buf, int size) +{ + int i; + for (i = 0; i < size; i += 3) + smb358_i2c_write(client, (u8) (*(buf + i)), (buf + i) + 1); +}*/ + +static int smb358_update_reg(struct i2c_client *client, int reg, u8 data) +{ + int ret; + u8 r_data = 0; + u8 w_data = 0; + u8 o_data = data; + + ret = smb358_i2c_read(client, reg, &r_data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - read(%d)\n", __func__, ret); + goto error; + } + + w_data = r_data | data; + ret = smb358_i2c_write(client, reg, &w_data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - write(%d)\n", __func__, ret); + goto error; + } + + ret = smb358_i2c_read(client, reg, &data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - read(%d)\n", __func__, ret); + goto error; + } + + dev_dbg(&client->dev, + "%s: reg(0x%02x) 0x%02x : 0x%02x -> 0x%02x -> 0x%02x\n", + __func__, reg, o_data, r_data, w_data, data); + +error: + return ret; +} + +static int smb358_clear_reg(struct i2c_client *client, int reg, u8 data) +{ + int ret; + u8 r_data = 0; + u8 w_data = 0; + u8 o_data = data; + + ret = smb358_i2c_read(client, reg, &r_data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - read(%d)\n", __func__, ret); + goto error; + } + + w_data = r_data & (~data); + ret = smb358_i2c_write(client, reg, &w_data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - write(%d)\n", __func__, ret); + goto error; + } + + ret = smb358_i2c_read(client, reg, &data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - read(%d)\n", __func__, ret); + goto error; + } + + dev_dbg(&client->dev, + "%s: reg(0x%02x)- 0x%02x : 0x%02x -> 0x%02x -> 0x%02x\n", + __func__, reg, o_data, r_data, w_data, data); + +error: + return ret; +} + +static int smb358_volatile_writes(struct i2c_client *client, u8 value) +{ + int ret = 0; + + if (value == SMB358_ENABLE_WRITE) { + ret = smb358_update_reg(client, SMB358_COMMAND_A, 0x80); + if (ret < 0) { + dev_err(&client->dev, "%s: error(%d)\n", __func__, ret); + goto error; + } + dev_dbg(&client->dev, "%s: ENABLED\n", __func__); + } else { + ret = smb358_clear_reg(client, SMB358_COMMAND_A, 0x80); + if (ret < 0) { + dev_err(&client->dev, "%s: error(%d)\n", __func__, ret); + goto error; + } + dev_dbg(&client->dev, "%s: DISABLED\n", __func__); + } + +error: + return ret; +} + +static void smb358_set_command(struct i2c_client *client, + int reg, u8 datum) +{ + int val; + u8 after_data; + + if (smb358_i2c_write(client, reg, &datum) < 0) + dev_err(&client->dev, + "%s : error!\n", __func__); + + val = smb358_i2c_read(client, reg, &after_data); + if (val >= 0) + dev_info(&client->dev, + "%s : reg(0x%02x) 0x%02x => 0x%02x\n", + __func__, reg, datum, after_data); + else + dev_err(&client->dev, "%s : error!\n", __func__); +} + +#if 0 +static void smb358_test_read(struct i2c_client *client) +{ + u8 data = 0; + u32 addr = 0; + for (addr = 0; addr <= 0x0f; addr++) { + smb358_i2c_read(client, addr, &data); + dev_dbg(&client->dev, + "%s : smb358 addr : 0x%02x data : 0x%02x\n", + __func__, addr, data); + } + for (addr = 0x30; addr <= 0x3f; addr++) { + smb358_i2c_read(client, addr, &data); + dev_dbg(&client->dev, + "%s : smb358 addr : 0x%02x data : 0x%02x\n", + __func__, addr, data); + } +} +#endif + +static void smb358_read_regs(struct i2c_client *client, char *str) +{ + u8 data = 0; + u32 addr = 0; + + for (addr = 0; addr <= 0x0f; addr++) { + smb358_i2c_read(client, addr, &data); + sprintf(str+strlen(str), "0x%x, ", data); + } + + /* "#" considered as new line in application */ + sprintf(str+strlen(str), "#"); + + for (addr = 0x30; addr <= 0x3f; addr++) { + smb358_i2c_read(client, addr, &data); + sprintf(str+strlen(str), "0x%x, ", data); + } +} + +static int smb358_get_aicl_current(u8 aicl_current) +{ + int data; + + if (aicl_current <= 0x10) + data = 300; + else if (aicl_current <= 0x11) + data = 500; + else if (aicl_current <= 0x12) + data = 700; + else if (aicl_current <= 0x13) + data = 1000; + else if (aicl_current <= 0x14) + data = 1200; + else if (aicl_current <= 0x15) + data = 1300; + else if (aicl_current <= 0x16) + data = 1800; + else + data = 2000; + + return data; +} + +static int smb358_get_charging_status(struct i2c_client *client) +{ + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 data_a = 0; + u8 data_b = 0; + u8 data_c = 0; + u8 data_d = 0; + u8 data_e = 0; + u8 therm_control_a = 0; + u8 other_control_a = 0; + + + /*smb358_test_read(client);*/ + + smb358_i2c_read(client, SMB358_STATUS_A, &data_a); + dev_dbg(&client->dev, + "%s : charger status A(0x%02x)\n", __func__, data_a); + smb358_i2c_read(client, SMB358_STATUS_B, &data_b); + dev_dbg(&client->dev, + "%s : charger status B(0x%02x)\n", __func__, data_b); + smb358_i2c_read(client, SMB358_STATUS_C, &data_c); + dev_dbg(&client->dev, + "%s : charger status C(0x%02x)\n", __func__, data_c); + smb358_i2c_read(client, SMB358_STATUS_D, &data_d); + dev_dbg(&client->dev, + "%s : charger status D(0x%02x)\n", __func__, data_d); + smb358_i2c_read(client, SMB358_STATUS_E, &data_e); + dev_dbg(&client->dev, + "%s : charger status E(0x%02x)\n", __func__, data_e); + smb358_i2c_read(client, SMB358_THERM_CONTROL_A, &therm_control_a); + dev_dbg(&client->dev, + "%s : THERM_CONTROL_A(0x%02x)\n", __func__, therm_control_a); + smb358_i2c_read(client, SMB358_OTHER_CONTROL_A, &other_control_a); + dev_dbg(&client->dev, + "%s : OTHER_CONTROL_A(0x%02x)\n", __func__, other_control_a); + /* At least one charge cycle terminated, + * Charge current < Termination Current + */ + if (data_c & 0x20) { + /* top-off by full charging */ + status = POWER_SUPPLY_STATUS_FULL; + goto charging_status_end; + } + + /* Is enabled ? */ + if (data_c & 0x01) { + /* check for 0x06 : no charging (0b00) */ + /* not charging */ + if (!(data_c & 0x06)) { + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + goto charging_status_end; + } else { + status = POWER_SUPPLY_STATUS_CHARGING; + goto charging_status_end; + } + } else + status = POWER_SUPPLY_STATUS_DISCHARGING; + +charging_status_end: + return (int)status; +} + +static int smb358_get_charging_health(struct i2c_client *client) +{ + int health = POWER_SUPPLY_HEALTH_GOOD; + u8 data_a = 0; + u8 data_b = 0; + u8 data_c = 0; + u8 data_d = 0; + u8 data_e = 0; + + smb358_i2c_read(client, SMB358_STATUS_A, &data_a); + dev_dbg(&client->dev, + "%s : charger status A(0x%02x)\n", __func__, data_a); + smb358_i2c_read(client, SMB358_STATUS_B, &data_b); + dev_dbg(&client->dev, + "%s : charger status B(0x%02x)\n", __func__, data_b); + smb358_i2c_read(client, SMB358_STATUS_C, &data_c); + dev_dbg(&client->dev, + "%s : charger status C(0x%02x)\n", __func__, data_c); + smb358_i2c_read(client, SMB358_STATUS_D, &data_d); + dev_dbg(&client->dev, + "%s : charger status D(0x%02x)\n", __func__, data_d); + smb358_i2c_read(client, SMB358_STATUS_E, &data_e); + dev_dbg(&client->dev, + "%s : charger status E(0x%02x)\n", __func__, data_e); + smb358_i2c_read(client, SMB358_INTERRUPT_STATUS_E, &data_e); + dev_dbg(&client->dev, + "%s : charger interrupt status E(0x%02x)\n", __func__, data_e); + + if (data_e & 0x01) + health = POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + else if (data_e & 0x04) + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + + return (int)health; +} + +/*static void smb358_allow_volatile_writes(struct i2c_client *client) +{ + int val, reg; + u8 data; + reg = SMB358_COMMAND_A; + val = smb358_i2c_read(client, reg, &data); + if ((val >= 0) && !(data & 0x80)) { + dev_dbg(&client->dev, + "%s : reg(0x%02x): 0x%02x", __func__, reg, data); + data |= (0x1 << 7); + if (smb358_i2c_write(client, reg, &data) < 0) + dev_err(&client->dev, "%s : error!\n", __func__); + val = smb358_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8) data; + dev_dbg(&client->dev, " => 0x%02x\n", data); + } + } +}*/ + +static u8 smb358_get_float_voltage_data(int float_voltage) +{ + u8 data; + + if (float_voltage < 3500) + data = 0; + else if(float_voltage <= 4340) + data = (float_voltage - 3500) / 20; + else if(float_voltage == 4350) + data = 0x2B; /* (4340 -3500)/20 + 1 */ + else if(float_voltage <= 4500) + data = (float_voltage - 3500) / 20 + 1; + else + data = 51; + + return data; +} + +static u8 smb358_get_input_current_limit_data( + struct sec_charger_info *charger, int input_current) +{ + u8 data; + + if (input_current <= 300) + data = 0x00; + else if (input_current <= 500) + data = 0x01; + else if (input_current <= 700) + data = 0x02; + else if (input_current <= 1000) + data = 0x03; + else if (input_current <= 1200) + data = 0x04; + else if (input_current <= 1500) + data = 0x05; + else if (input_current <= 1800) + data = 0x06; + else + data = 0x07; /* set input current to 2000mA */ + + return (data << 4); +} + +static u8 smb358_get_term_current_limit_data( + int termination_current) +{ + u8 data; + + if (termination_current <= 30) + data = 0x00; + else if (termination_current <= 40) + data = 0x01; + else if (termination_current <= 60) + data = 0x02; + else if (termination_current <= 80) + data = 0x03; + else if (termination_current <= 100) + data = 0x04; + else if (termination_current <= 125) + data = 0x05; + else if (termination_current <= 150) + data = 0x06; + else + data = 0x07; /* set termination current limit to 200mA */ + + return data; +} + +static u8 smb358_get_fast_charging_current_data( + int fast_charging_current) +{ + u8 data; + + if (fast_charging_current <= 200) + data = 0x00; + else if (fast_charging_current <= 450) + data = 0x01; + else if (fast_charging_current <= 600) + data = 0x02; + else if (fast_charging_current <= 900) + data = 0x03; + else if (fast_charging_current <= 1300) + data = 0x04; + else if (fast_charging_current <= 1500) + data = 0x05; + else if (fast_charging_current <= 1800) + data = 0x06; + else + data = 0x07; /* set fast charging current to 2000mA */ + + return data << 5; +} + +static void smb358_enter_suspend(struct i2c_client *client) +{ + u8 data = 0; + + pr_info("%s: ENTER SUSPEND\n", __func__); + smb358_update_reg(client, SMB358_COMMAND_A, 0x80); + smb358_set_command(client, SMB358_PIN_ENABLE_CONTROL, 0x18); + data = (data | 0x4); + smb358_set_command(client, SMB358_COMMAND_A, data); +} + +#if 0 +#if (defined(CONFIG_MACH_MILLET3G_EUR) || defined(CONFIG_MACH_MATISSE3G_OPEN) || defined(CONFIG_MACH_BERLUTI3G_EUR)) +static void smb358_aicl_calibrate(struct i2c_client *client) +{ + + struct sec_charger_info *charger = i2c_get_clientdata(client); + int ret = 0; + u8 data = 0; + u8 count = 0; + u8 current_initial = 0; + u8 current_final = 0; + + ret = smb358_i2c_read(client, SMB358_STATUS_E, &data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - read(%d)\n", __func__, ret); + } + + /* check If AICL complete */ + if(data & 0x10){ + current_initial = (data & 0x0F); + + if((current_initial >= 0x04) && (current_initial <= 0x05)){ + /* set 1000mA */ + ret = smb358_i2c_read(client, SMB358_INPUT_CURRENTLIMIT, &data); + data = data & 0x0F; + data = data | 0x30; + smb358_set_command(client,SMB358_INPUT_CURRENTLIMIT, data); + }else{ + /* set 1800mA */ + ret = smb358_i2c_read(client, SMB358_INPUT_CURRENTLIMIT, &data); + data = data & 0x0F; + data = data | 0x60; + smb358_set_command(client,SMB358_INPUT_CURRENTLIMIT, data); + } + + smb358_i2c_read(client, SMB358_INPUT_CURRENTLIMIT, &data); + current_final = (data & 0x0F); + + dev_err(&charger->client->dev, + "%s: AICL calibration success! input current (%dmA) -> (%dmA) ! \n", + __func__,current_initial,current_final); + return; + + }else{ + /* Incase of AICL not complete check three times */ + for(count=0 ;count < 3;count++) + { + msleep(300); + ret = smb358_i2c_read(client, SMB358_STATUS_E, &data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - read(%d)\n", __func__, ret); + } + + if(data & 0x10){ + current_initial = (data & 0x0F); + + if((current_initial >= 0x04) && (current_initial <= 0x05)){ + /* set 1000mA */ + ret = smb358_i2c_read(client, SMB358_INPUT_CURRENTLIMIT, &data); + data = data & 0x0F; + data = data | 0x30; + smb358_set_command(client,SMB358_INPUT_CURRENTLIMIT, data); + }else{ + /* set 1800mA */ + ret = smb358_i2c_read(client, SMB358_INPUT_CURRENTLIMIT, &data); + data = data & 0x0F; + data = data | 0x60; + smb358_set_command(client,SMB358_INPUT_CURRENTLIMIT, data); + } + + smb358_i2c_read(client, SMB358_INPUT_CURRENTLIMIT, &data); + current_final = (data & 0x0F); + + dev_err(&charger->client->dev, + "%s: AICL calibration success! input current (%dmA) -> (%dmA) ! \n", + __func__,current_initial,current_final); + return; + }else{ + dev_err(&charger->client->dev, + "%s: AICL not complete \n",__func__); + } + } + } + + dev_err(&charger->client->dev, + "%s: AICL calibration Failed! current (%dmA) ! \n",__func__,current_initial); + return; + +} +#endif +#endif + +static void smb358_check_slow_charging(struct work_struct *work) +{ + struct sec_charger_info *charger = + container_of(work, struct sec_charger_info, slow_work.work); + + u8 i, aicl_data; + int aicl_current = 0; + union power_supply_propval val; + + if (charger->pdata->chg_functions_setting & + SEC_CHARGER_NO_GRADUAL_CHARGING_CURRENT) { + pr_err("%s: aicl is disabled\n", __func__); + return; + } + if (charger->pdata->charging_current + [charger->cable_type].input_current_limit <= SLOW_CHARGING_CURRENT_STANDARD) { + charger->is_slow_charging = true; + } else { + for(i = 0; i < 20; i++) { + if (charger->cable_type == + POWER_SUPPLY_TYPE_BATTERY) { + pr_info("%s: cable is removed\n", __func__); + return; + } + msleep(200); + smb358_i2c_read(charger->client, SMB358_STATUS_E, &aicl_data); + if (aicl_data & 0x10) { /* check AICL complete */ + break; + } + if (i == 10) { + pr_info("%s: aicl not complete, retry\n", __func__); + /* disable AICL */ + smb358_set_command(charger->client, + SMB358_VARIOUS_FUNCTIONS, 0x81); + /* enable AICL */ + smb358_set_command(charger->client, + SMB358_VARIOUS_FUNCTIONS, 0x95); + } + } + + aicl_data &= 0xF; /* get only AICL result field */ + switch (aicl_data) { + case 0: /* AICL result 300mA */ + aicl_current = 300; + break; + case 1: /* AICL result 500mA */ + aicl_current = 500; + break; + case 2: /* AICL result 700mA */ + aicl_current = 700; + break; + case 3: /* AICL result 1000mA */ + aicl_current = 1000; + break; + case 4: /* AICL result 1200mA */ + aicl_current = 1200; + break; + case 5: /* AICL result 1300mA */ + aicl_current = 1300; + break; + case 6: /* AICL result 1800mA */ + aicl_current = 1800; + break; + case 7: /* AICL result 2000mA */ + aicl_current = 2000; + break; + default: /* etc */ + aicl_current = 2000; + break; + } + if (aicl_current <= SLOW_CHARGING_CURRENT_STANDARD) + charger->is_slow_charging = true; + else + charger->is_slow_charging = false; + } + + pr_info("%s: Slow(%d), aicl_current(%d), input_current(%d)\n", + __func__, charger->is_slow_charging, aicl_current, charger->pdata->charging_current + [charger->cable_type].input_current_limit); + + psy_do_property("battery", set, + POWER_SUPPLY_PROP_CHARGE_TYPE, val); + +} +static void smb358_charger_function_control( + struct i2c_client *client) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + union power_supply_propval val, input_value; + int status; + u8 data; + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_STATUS, input_value); + status = input_value.intval; + + charger->charging_current_max = + charger->pdata->charging_current[ + charger->cable_type].input_current_limit; + + charger->charging_current = + charger->pdata->charging_current[ + charger->cable_type].fast_charging_current; + + if (charger->charging_current < 0) { + dev_dbg(&client->dev, + "%s : OTG is activated. Ignore command!\n", __func__); + return; + } + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_HEALTH, input_value); + if (input_value.intval == + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + pr_info("[SMB358] Unspec_failure, charger suspend\n"); + smb358_enter_suspend(client); + } + else if (charger->cable_type == + POWER_SUPPLY_TYPE_BATTERY) { + /* Charger Disabled */ + smb358_set_command(client, SMB358_COMMAND_A, 0xc0); + if ((status == POWER_SUPPLY_STATUS_FULL) ||\ + (input_value.intval == POWER_SUPPLY_HEALTH_OVERHEAT) ||\ + (input_value.intval == POWER_SUPPLY_HEALTH_COLD)) + return; + pr_info("[SMB358] Set the registers to the default configuration\n"); + /* Set the registers to the default configuration */ + smb358_set_command(client, SMB358_CHARGE_CURRENT, 0xFE); + smb358_set_command(client, SMB358_INPUT_CURRENTLIMIT, 0x74); + smb358_set_command(client, SMB358_VARIOUS_FUNCTIONS, 0xD7); + data = 0x00; + data |= smb358_get_float_voltage_data(charger->pdata->chg_float_voltage); + smb358_set_command(client, SMB358_FLOAT_VOLTAGE, data); + /* Disable Automatic Recharge */ + smb358_set_command(client, SMB358_CHARGE_CONTROL, 0x84); + smb358_set_command(client, SMB358_PIN_ENABLE_CONTROL, 0x09); + smb358_set_command(client, SMB358_THERM_CONTROL_A, 0xF0); + smb358_set_command(client, SMB358_SYSOK_USB30_SELECTION, 0x08); + smb358_set_command(client, SMB358_OTHER_CONTROL_A, 0x01); + smb358_set_command(client, SMB358_OTG_TLIM_THERM_CONTROL, 0xF6); + smb358_set_command(client, SMB358_LIMIT_CELL_TEMPERATURE_MONITOR, 0xA5); + smb358_set_command(client, SMB358_STATUS_INTERRUPT, 0x00); + smb358_set_command(client, SMB358_COMMAND_B, 0x00); + if (charger->pdata->chg_irq) { + smb358_set_command(client, SMB358_STAT_TIMERS_CONTROL, 0x1F); + smb358_set_command(client, SMB358_FAULT_INTERRUPT, 0x0C); + } else { + smb358_set_command(client, SMB358_STAT_TIMERS_CONTROL, 0x0F); + smb358_set_command(client, SMB358_FAULT_INTERRUPT, 0x00); + } + + } else { + int full_check_type; + psy_do_property("battery", get, + POWER_SUPPLY_PROP_CHARGE_NOW, val); + if (val.intval == SEC_BATTERY_CHARGING_1ST) + full_check_type = charger->pdata->full_check_type; + else + full_check_type = charger->pdata->full_check_type_2nd; + + smb358_i2c_read(client, SMB358_COMMAND_A, &data); + + if ((data & 0x10) && charger->pdata->vbus_ctrl_gpio) { + int level; + /* disable otg & charging */ + smb358_clear_reg(client, SMB358_COMMAND_A, 0x12); + + /* turn off vbus */ + gpio_set_value(charger->pdata->vbus_ctrl_gpio, 1); + msleep(30); + + level = gpio_get_value_cansleep(charger->pdata->vbus_ctrl_gpio); + pr_info("[SMB358] vbus ctrl gpio %d level %d\n", charger->pdata->vbus_ctrl_gpio, level); + + /* turn on vbus */ + gpio_set_value(charger->pdata->vbus_ctrl_gpio, 0); + } + /* [STEP - 1] ================================================ + * Volatile write permission(bit 7) - allow(1) + * Charging Enable(bit 1) - Disabled(0, default) + * STAT Output(bit 0) - Enabled(0) + */ + if (data & 0x02) { + u8 status_c; + u8 status_e; + smb358_i2c_read(client, SMB358_STATUS_C, &status_c); + smb358_i2c_read(client, SMB358_STATUS_E, &status_e); + pr_info("[SMB358] status_c: 0x%x, status_e: 0x%x\n", status_c, status_e); + + /* no charge or aicl not complete*/ + if (((status_c & 0x06) == 0) || (status_e & 0x10) == 0) + smb358_set_command(client, + SMB358_COMMAND_A, 0xC0); + } + + /* [STEP - 2] ================================================ + * USB 5/1(9/1.5) Mode(bit 1) - USB1/USB1.5(0), USB5/USB9(1) + * USB/HC Mode(bit 0) - USB5/1 or USB9/1.5 Mode(0) + * High-Current Mode(1) + */ + switch (charger->cable_type) { + case POWER_SUPPLY_TYPE_UNKNOWN: + case POWER_SUPPLY_TYPE_MAINS: + case POWER_SUPPLY_TYPE_USB_CDP: + case POWER_SUPPLY_TYPE_MISC: + case POWER_SUPPLY_TYPE_WIRELESS: + case POWER_SUPPLY_TYPE_CARDOCK: + case POWER_SUPPLY_TYPE_UARTOFF: + case POWER_SUPPLY_TYPE_LAN_HUB: + case POWER_SUPPLY_TYPE_MHL_900: + case POWER_SUPPLY_TYPE_MHL_1500: + case POWER_SUPPLY_TYPE_SMART_NOTG: + /* High-current mode */ + data = 0x03; + break; + case POWER_SUPPLY_TYPE_UPS: + case POWER_SUPPLY_TYPE_USB: + case POWER_SUPPLY_TYPE_USB_DCP: + case POWER_SUPPLY_TYPE_USB_ACA: + case POWER_SUPPLY_TYPE_MHL_500: + case POWER_SUPPLY_TYPE_MHL_USB: + case POWER_SUPPLY_TYPE_SMART_OTG: + case POWER_SUPPLY_TYPE_POWER_SHARING: + /* USB5 */ + data = 0x02; + break; + default: + /* USB1 */ + data = 0x00; + break; + } + smb358_set_command(client, + SMB358_COMMAND_B, data); + + + /* [STEP 3] Charge Current(0x00) =============================== + * Set pre-charge current(bit 4:3) - 450mA(11) + * Set fast charge current(bit 7:5) + * Set termination current(bit 2:0) + */ + if (charger->siop_level < 100) { + charger->charging_current = + charger->charging_current * charger->siop_level / 100; + } + dev_info(&client->dev, + "%s : fast charging current (%dmA)\n", + __func__, charger->charging_current); + + data = 0x18; + data |= smb358_get_fast_charging_current_data( + charger->charging_current); + switch (full_check_type) { + case SEC_BATTERY_FULLCHARGED_CHGGPIO: + case SEC_BATTERY_FULLCHARGED_CHGINT: + case SEC_BATTERY_FULLCHARGED_CHGPSY: + if (val.intval == SEC_BATTERY_CHARGING_1ST) { + dev_info(&client->dev, + "%s : termination current (%dmA)\n", + __func__, charger->pdata->charging_current[ + charger->cable_type].full_check_current_1st); + data |= smb358_get_term_current_limit_data( + charger->pdata->charging_current[ + charger->cable_type].full_check_current_1st); + } else { + dev_info(&client->dev, + "%s : termination current (%dmA)\n", + __func__, charger->pdata->charging_current[ + charger->cable_type].full_check_current_2nd); + data |= smb358_get_term_current_limit_data( + charger->pdata->charging_current[ + charger->cable_type].full_check_current_2nd); + } + break; + } + smb358_set_command(client, + SMB358_CHARGE_CURRENT, data); + + /* [STEP - 4] ================================================= + * Enable(EN) Pin Control(bit 6) - i2c(0), Pin(1) + * Pin control(bit 5) - active high(0), active low(1) + * USB5/1/HC input State(bit3) - Dual-state input(1) + * USB Input Pre-bias(bit 0) - Enable(1) + */ + data = 0x09; + if (charger->pdata->chg_gpio_en) + data |= 0x40; + if (charger->pdata->chg_polarity_en) + data |= 0x20; + smb358_set_command(client, + SMB358_PIN_ENABLE_CONTROL, data); + + /* [STEP - 5] =============================================== */ + dev_info(&client->dev, "%s : input current (%dmA)\n", + __func__, charger->pdata->charging_current + [charger->cable_type].input_current_limit); + /* Input current limit */ + data = 0x00; + data |= smb358_get_input_current_limit_data( + charger, + charger->pdata->charging_current + [charger->cable_type].input_current_limit); + smb358_set_command(client, + SMB358_INPUT_CURRENTLIMIT, data); + + /* [STEP - 6] ================================================= + * Input to System FET(bit 7) - Controlled by Register(1) + * Max System voltage(bit 5) - Vflt + 0.1v(0) + * AICL(bit 4) - Enabled(1) + * VCHG Function(bit 0) - Enabled(1) + */ + if (charger->pdata->chg_functions_setting & + SEC_CHARGER_NO_GRADUAL_CHARGING_CURRENT) + /* disable AICL */ + smb358_set_command(client, + SMB358_VARIOUS_FUNCTIONS, 0x81); + else { + /* disable AICL */ + smb358_set_command(client, + SMB358_VARIOUS_FUNCTIONS, 0x81); + /* enable AICL */ + smb358_set_command(client, + SMB358_VARIOUS_FUNCTIONS, 0x95); + } + + /* [STEP - 7] ================================================= + * Pre-charged to Fast-charge Voltage Threshold(Bit 7:6) - 2.3V + * Float Voltage(bit 5:0) + */ + dev_dbg(&client->dev, "%s : float voltage (%dmV)\n", + __func__, charger->pdata->chg_float_voltage); + data = 0x00; + data |= smb358_get_float_voltage_data( + charger->pdata->chg_float_voltage); + smb358_set_command(client, + SMB358_FLOAT_VOLTAGE, data); + + /* [STEP - 8] ================================================= + * Charge control + * Automatic Recharge disable(bit 7), + * Current Termination disable(bit 6), + * BMD disable(bit 5:4), + * INOK Output Configuration : Push-pull(bit 3) + * AICL glitch filter duration : 20msec(bit 0) + * APSD disable + */ + data = 0xC0; + + switch (full_check_type) { + case SEC_BATTERY_FULLCHARGED_CHGGPIO: + case SEC_BATTERY_FULLCHARGED_CHGINT: + case SEC_BATTERY_FULLCHARGED_CHGPSY: + /* Enable Current Termination */ + data &= 0xBF; + break; + } + smb358_set_command(client, + SMB358_CHARGE_CONTROL, data); + + /* [STEP - 9] ================================================= + * STAT active low(bit 7), + * Complete charge Timeout(bit 3:2) - Disabled(11) + * Pre-charge Timeout(bit 1:0) - Disable(11) + */ + smb358_set_command(client, + SMB358_STAT_TIMERS_CONTROL, 0x1F); + +#if defined(CONFIG_MACH_MATISSELTE_VZW) + /* [STEP - 10] ================================================= + * Mininum System Voltage(bit 6) - 3.15v(0) + * Therm monitor(bit 4) - Disabled(1) + * Soft Cold/Hot Temp Limit Behavior(bit 3:2, bit 1:0) - + * Charger Current + Float voltage Compensation(11) + */ + smb358_set_command(client, + SMB358_THERM_CONTROL_A, 0xB0); + + /* [STEP - 11] ================================================ + * OTG/ID Pin Control(bit 7:6) - RID Disabled, OTG I2c(00) + * Minimum System Voltage(bit 4) - 3.15V(0) + * Low-Battery/SYSOK Voltage threshold(bit 3:0) - 2.5V(0001) + * if this bit is disabled, + * input current for system will be disabled + */ + smb358_set_command(client, + SMB358_OTHER_CONTROL_A, 0x11); +#elif defined(CONFIG_MACH_CHAGALL_USC) + /* [STEP - 10] ================================================= + * Mininum System Voltage(bit 6) - 3.45v(0) + * Therm monitor(bit 4) - Disabled(1) + * Soft Cold/Hot Temp Limit Behavior(bit 3:2, bit 1:0) - + * Charger Current + Float voltage Compensation(11) + */ + smb358_set_command(client, + SMB358_THERM_CONTROL_A, 0xB0); + + /* [STEP - 11] ================================================ + * OTG/ID Pin Control(bit 7:6) - RID Disabled, OTG I2c(00) + * Minimum System Voltage(bit 4) - 3.45V(0) + * Low-Battery/SYSOK Voltage threshold(bit 3:0) - 2.5V(0001) + * if this bit is disabled, + * input current for system will be disabled + */ + smb358_set_command(client, + SMB358_OTHER_CONTROL_A, 0x01); +#else +{ + u8 therm_control_a, other_control_a; + /* [STEP - 10] ================================================= + * Mininum System Voltage(bit 6) + * 3.15V(0), 3.45V(0), 3.60V(1), 3.75V(1) + * Therm monitor(bit 4) - Disabled(1) + * Soft Cold/Hot Temp Limit Behavior(bit 3:2, bit 1:0) - + * Charger Current + Float voltage Compensation(11) + */ + smb358_i2c_read(client, SMB358_THERM_CONTROL_A, &therm_control_a); + pr_info("%s : THERM_CONTROL_A(0x%02x)\n", __func__, therm_control_a); + smb358_i2c_read(client, SMB358_OTHER_CONTROL_A, &other_control_a); + pr_info("%s : OTHER_CONTROL_A(0x%02x)\n", __func__, other_control_a); + + pr_info("%s: chg_min_system_voltage(%d)\n", __func__, charger->pdata->chg_min_system_voltage); + + if (charger->pdata->chg_min_system_voltage <= 3150) { + therm_control_a &= ~(0x1 << 6); + other_control_a |= 0x1 << 4; + } else if (charger->pdata->chg_min_system_voltage <= 3450) { + therm_control_a &= ~(0x1 << 6); + other_control_a &= ~(0x1 << 4); + } else if (charger->pdata->chg_min_system_voltage <= 3600) { + therm_control_a |= 0x1 << 6; + other_control_a &= ~(0x1 << 4); + } else { + therm_control_a |= 0x1 << 6; + other_control_a |= 0x1 << 4; + } + + pr_info("%s: therm_control_a(0x%02x)\n", __func__, therm_control_a); + pr_info("%s: other_control_a(0x%02x)\n", __func__, other_control_a); + + smb358_set_command(client, + SMB358_THERM_CONTROL_A, therm_control_a); + + /* [STEP - 11] ================================================ + * OTG/ID Pin Control(bit 7:6) - RID Disabled, OTG I2c(00) + * Minimum System Voltage(bit 4) + * 3.15V(1), 3.45V(0), 3.60V(0), 3.75(1) + * Low-Battery/SYSOK Voltage threshold(bit 3:0) - 2.5V(0001) + * if this bit is disabled, + * input current for system will be disabled + */ + smb358_set_command(client, + SMB358_OTHER_CONTROL_A, other_control_a); +} +#endif + + /* [STEP - 12] ================================================ + * Charge Current Compensation(bit 7:6) - 200mA(00) + * Digital Thermal Regulation Threshold(bit 5:4) - 130c + * OTG current Limit at USBIN(Bit 3:2) - 900mA(11) + * OTG Battery UVLO Threshold(Bit 1:0) - 3.3V(11) + */ + smb358_set_command(client, + SMB358_OTG_TLIM_THERM_CONTROL, 0x3F); + + /* [STEP - 13] ================================================ + * Hard/Soft Limit Cell temp monitor + */ + smb358_set_command(client, + SMB358_LIMIT_CELL_TEMPERATURE_MONITOR, 0x01); + + /* [STEP - 14] ================================================ + * FAULT interrupt - Disabled for non chg_irq, OVP enabled for chg_irq + */ + if (charger->pdata->chg_irq) { + smb358_set_command(client, + SMB358_FAULT_INTERRUPT, 0x0C); + } else { + smb358_set_command(client, + SMB358_FAULT_INTERRUPT, 0x00); + } + + /* [STEP - 15] ================================================ + * STATUS interrupt - Clear + */ + smb358_set_command(client, + SMB358_STATUS_INTERRUPT, 0x00); + + /* [STEP - 16] ================================================ + * Volatile write permission(bit 7) - allowed(1) + * Charging Enable(bit 1) - Enabled(1) + * STAT Output(bit 0) - Enabled(0) + */ + smb358_set_command(client, + SMB358_COMMAND_A, 0xC2); + + schedule_delayed_work(&charger->slow_work, 0); + } +#if 0 +#if (defined(CONFIG_MACH_MILLET3G_EUR) || defined(CONFIG_MACH_MATISSE3G_OPEN) || defined(CONFIG_MACH_BERLUTI3G_EUR)) + /* Allow time for AICL to complete */ + msleep(1000); + smb358_aicl_calibrate(client); +#endif +#endif +} + +static void smb358_charger_otg_control( + struct i2c_client *client) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + + if (charger->cable_type == + POWER_SUPPLY_TYPE_BATTERY) { + dev_info(&client->dev, "%s : turn off OTG\n", __func__); + + /* disable otg */ + smb358_clear_reg(client, SMB358_COMMAND_A, 0x10); + } else { + /* Change "OTG output current limit" to 250mA */ + smb358_clear_reg(client, SMB358_OTG_TLIM_THERM_CONTROL, 0x0C); + + /* OTG Enabled*/ + smb358_update_reg(client, SMB358_COMMAND_A, 0x10); + + dev_info(&client->dev, "%s : turn on OTG\n", __func__); + smb358_set_command(client, SMB358_COMMAND_B, 0x00); + + /* Change "OTG output current limit" to 500mA */ + smb358_update_reg(client, SMB358_OTG_TLIM_THERM_CONTROL, 0x84); + } + +} + +static void smb358_set_charging_current( + struct i2c_client *client, int charging_current) +{ + u8 data; + + smb358_clear_reg(client, SMB358_COMMAND_A, 0x02); + + if (!charging_current) + return; + + smb358_i2c_read(client, SMB358_CHARGE_CURRENT, &data); + data &= 0x1f; + data |= smb358_get_fast_charging_current_data(charging_current); + smb358_set_command(client, SMB358_CHARGE_CURRENT, data); + + smb358_update_reg(client, SMB358_COMMAND_A, 0x02); +} + +static void smb358_set_charging_input_current_limit( + struct i2c_client *client, int input_current_limit) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + u8 data; + + /* Input current limit */ + data = 0; + data = smb358_get_input_current_limit_data( + charger, input_current_limit); + smb358_set_command(client, SMB358_INPUT_CURRENTLIMIT, data); +} + +void smb358_charger_shutdown(struct i2c_client *client) +{ + pr_info("%s: smb358 Charging Disabled\n", __func__); + + smb358_set_command(client, SMB358_THERM_CONTROL_A, 0xF0); + smb358_set_command(client, SMB358_COMMAND_A, 0x80); + smb358_volatile_writes(client, SMB358_DISABLE_WRITE); +} + +static int smb358_debugfs_show(struct seq_file *s, void *data) +{ + struct sec_charger_info *charger = s->private; + u8 reg; + u8 reg_data; + + seq_printf(s, "SMB CHARGER IC :\n"); + seq_printf(s, "==================\n"); + for (reg = 0x00; reg <= 0x0E; reg++) { + smb358_i2c_read(charger->client, reg, ®_data); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + + for (reg = 0x30; reg <= 0x3F; reg++) { + smb358_i2c_read(charger->client, reg, ®_data); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + + seq_printf(s, "\n"); + return 0; +} + +static int smb358_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, smb358_debugfs_show, inode->i_private); +} + +static const struct file_operations smb358_debugfs_fops = { + .open = smb358_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +bool smb358_hal_chg_init(struct i2c_client *client) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + + dev_info(&client->dev, + "%s: SMB358 Charger init(Start)!!\n", __func__); + + smb358_volatile_writes(client, SMB358_ENABLE_WRITE); + + /*smb358_test_read(client);*/ + (void) debugfs_create_file("smb358_regs", + S_IRUGO, NULL, (void *)charger, &smb358_debugfs_fops); + + return true; +} + +bool smb358_hal_chg_suspend(struct i2c_client *client) +{ + return true; +} + +bool smb358_hal_chg_resume(struct i2c_client *client) +{ + return true; +} + +bool smb358_hal_chg_get_property(struct i2c_client *client, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + u8 data; + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = smb358_get_charging_status(client); + break; + + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (charger->is_charging) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + if (charger->is_slow_charging) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_SLOW; + pr_info("%s: slow-charging mode\n", __func__); + } + } + else + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + + case POWER_SUPPLY_PROP_HEALTH: + val->intval = smb358_get_charging_health(client); + break; + /* calculated input current limit value */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CURRENT_AVG: /* charging current */ + if (charger->charging_current) { + smb358_i2c_read(client, SMB358_STATUS_B, &data); + if (data & 0x20) + switch (data & 0x07) { + case 0: + val->intval = 100; + break; + case 1: + val->intval = 200; + break; + case 2: + val->intval = 450; + break; + case 3: + val->intval = 600; + break; + case 4: + val->intval = 900; + break; + case 5: + val->intval = 1300; + break; + case 6: + val->intval = 1500; + break; + case 7: + val->intval = 1800; + break; + } + else + switch ((data & 0x18) >> 3) { + case 0: + val->intval = 100; + break; + case 1: + val->intval = 150; + break; + case 2: + val->intval = 200; + break; + case 3: + val->intval = 250; + break; + } + } else + val->intval = 0; + dev_dbg(&client->dev, + "%s : set-current(%dmA), current now(%dmA)\n", + __func__, charger->charging_current, val->intval); + break; + default: + return false; + } + return true; +} + +bool smb358_hal_chg_set_property(struct i2c_client *client, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + + switch (psp) { + /* val->intval : type */ + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval == POWER_SUPPLY_TYPE_POWER_SHARING) { + union power_supply_propval ps_status; + psy_do_property("ps", get, + POWER_SUPPLY_PROP_STATUS, ps_status); + if (ps_status.intval) { + charger->cable_type = POWER_SUPPLY_TYPE_OTG; + pr_info("%s: ps enable\n", __func__); + } else { + charger->cable_type = POWER_SUPPLY_TYPE_BATTERY; + pr_info("%s: ps disable\n", __func__); + } + } + if (charger->cable_type == POWER_SUPPLY_TYPE_OTG) { + smb358_charger_otg_control(client); + } else if (charger->cable_type == POWER_SUPPLY_TYPE_BATTERY) { + smb358_charger_function_control(client); + smb358_charger_otg_control(client); + } else { + smb358_charger_function_control(client); + } + /* smb358_test_read(client); */ + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: /* input current limit set */ + /* calculated input current limit value */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + smb358_set_charging_input_current_limit(client, val->intval); + break; + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + smb358_set_charging_current(client, val->intval); + break; + default: + return false; + } + return true; +} + +ssize_t smb358_hal_chg_show_attrs(struct device *dev, + const ptrdiff_t offset, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_charger_info *chg = + container_of(psy, struct sec_charger_info, psy_chg); + int i = 0; + char *str = NULL; + + switch (offset) { + case CHG_DATA: + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", + chg->reg_data); + break; + case CHG_REGS: + str = kzalloc(sizeof(char)*1024, GFP_KERNEL); + if (!str) + return -ENOMEM; + + smb358_read_regs(chg->client, str); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + str); + + kfree(str); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t smb358_hal_chg_store_attrs(struct device *dev, + const ptrdiff_t offset, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_charger_info *chg = + container_of(psy, struct sec_charger_info, psy_chg); + int ret = 0; + int x = 0; + u8 data = 0; + + switch (offset) { + case CHG_REG: + if (sscanf(buf, "%x\n", &x) == 1) { + chg->reg_addr = x; + smb358_i2c_read(chg->client, + chg->reg_addr, &data); + chg->reg_data = data; + dev_dbg(dev, "%s: (read) addr = 0x%x, data = 0x%x\n", + __func__, chg->reg_addr, chg->reg_data); + ret = count; + } + break; + case CHG_DATA: + if (sscanf(buf, "%x\n", &x) == 1) { + data = (u8)x; + dev_dbg(dev, "%s: (write) addr = 0x%x, data = 0x%x\n", + __func__, chg->reg_addr, data); + smb358_i2c_write(chg->client, + chg->reg_addr, &data); + ret = count; + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static struct device_attribute smb358_charger_attrs[] = { + SMB358_CHARGER_ATTR(reg), + SMB358_CHARGER_ATTR(data), + SMB358_CHARGER_ATTR(regs), +}; + +static enum power_supply_property smb358_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, +}; + +static int smb358_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_charger_info *charger = + container_of(psy, struct sec_charger_info, psy_chg); + u8 data = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: /* input current limit set */ + smb358_i2c_read(charger->client, SMB358_STATUS_E, &data); + if (data & 0x10) { + int aicl_result = smb358_get_aicl_current(data); + dev_info(&charger->client->dev, + "%s : AICL completed (%dmA)\n", __func__, aicl_result); + charger->charging_current_max = aicl_result; + } else { + dev_info(&charger->client->dev, + "%s : AICL is not completed \n", __func__); + charger->charging_current_max = 300; + } + val->intval = charger->charging_current_max; + break; + + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_HEALTH: + case POWER_SUPPLY_PROP_CURRENT_AVG: /* charging current */ + /* calculated input current limit value */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (!smb358_hal_chg_get_property(charger->client, psp, val)) + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +static int smb358_chg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_charger_info *charger = + container_of(psy, struct sec_charger_info, psy_chg); + union power_supply_propval input_value; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + charger->status = val->intval; + break; + + /* val->intval : type */ + case POWER_SUPPLY_PROP_ONLINE: + charger->cable_type = val->intval; + if (val->intval == POWER_SUPPLY_TYPE_BATTERY || \ + val->intval == POWER_SUPPLY_TYPE_OTG || \ + val->intval == POWER_SUPPLY_TYPE_POWER_SHARING) { + charger->is_charging = false; + charger->is_slow_charging = false; + } + else + charger->is_charging = true; + + if (!smb358_hal_chg_set_property(charger->client, psp, val)) + return -EINVAL; + break; + + /* val->intval : input current limit set */ + case POWER_SUPPLY_PROP_CURRENT_MAX: + charger->charging_current_max = val->intval; + /* to control charging current, + * use input current limit and set charging current as much as possible + * so we only control input current limit to control charge current + */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (!smb358_hal_chg_set_property(charger->client, psp, val)) + return -EINVAL; + break; + + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + charger->charging_current = val->intval; + + if (!smb358_hal_chg_set_property(charger->client, psp, val)) + return -EINVAL; + break; + + /* val->intval : SIOP level (%) + * SIOP charging current setting + */ + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + charger->siop_level = val->intval; + if (charger->is_charging) { + /* change val as charging current by SIOP level + * do NOT change initial charging current setting + */ + input_value.intval = + charger->pdata->charging_current[ + charger->cable_type].fast_charging_current * val->intval / 100; + + /* charging current should be over than USB charging current */ + if (charger->pdata->chg_functions_setting & + SEC_CHARGER_MINIMUM_SIOP_CHARGING_CURRENT) { + if (input_value.intval > 0 && + input_value.intval < + charger->pdata->charging_current[ + POWER_SUPPLY_TYPE_USB].fast_charging_current) + input_value.intval = + charger->pdata->charging_current[ + POWER_SUPPLY_TYPE_USB].fast_charging_current; + } + + /* set charging current as new value */ + if (!smb358_hal_chg_set_property(charger->client, + POWER_SUPPLY_PROP_CURRENT_AVG, &input_value)) + return -EINVAL; + } + break; + + default: + return -EINVAL; + } + return 0; +} + +static void smb358_chg_isr_work(struct work_struct *work) +{ + struct sec_charger_info *charger = + container_of(work, struct sec_charger_info, isr_work.work); + union power_supply_propval val; + int full_check_type; + + dev_info(&charger->client->dev, + "%s: Charger Interrupt\n", __func__); + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_CHARGE_NOW, val); + if (val.intval == SEC_BATTERY_CHARGING_1ST) + full_check_type = charger->pdata->full_check_type; + else + full_check_type = charger->pdata->full_check_type_2nd; + + if (full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) { + if (!smb358_hal_chg_get_property(charger->client, + POWER_SUPPLY_PROP_STATUS, &val)) + return; + + switch (val.intval) { + case POWER_SUPPLY_STATUS_DISCHARGING: + dev_err(&charger->client->dev, + "%s: Interrupted but Discharging\n", __func__); + break; + + case POWER_SUPPLY_STATUS_NOT_CHARGING: + dev_err(&charger->client->dev, + "%s: Interrupted but NOT Charging\n", __func__); + break; + + case POWER_SUPPLY_STATUS_FULL: + dev_info(&charger->client->dev, + "%s: Interrupted by Full\n", __func__); + psy_do_property("battery", set, + POWER_SUPPLY_PROP_STATUS, val); + break; + + case POWER_SUPPLY_STATUS_CHARGING: + dev_err(&charger->client->dev, + "%s: Interrupted but Charging\n", __func__); + break; + + case POWER_SUPPLY_STATUS_UNKNOWN: + default: + dev_err(&charger->client->dev, + "%s: Invalid Charger Status\n", __func__); + break; + } + } + + if (charger->pdata->ovp_uvlo_check_type == + SEC_BATTERY_OVP_UVLO_CHGINT) { + if (!smb358_hal_chg_get_property(charger->client, + POWER_SUPPLY_PROP_HEALTH, &val)) + return; + + switch (val.intval) { + case POWER_SUPPLY_HEALTH_OVERHEAT: + case POWER_SUPPLY_HEALTH_COLD: + dev_err(&charger->client->dev, + "%s: Interrupted but Hot/Cold\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_DEAD: + dev_err(&charger->client->dev, + "%s: Interrupted but Dead\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + case POWER_SUPPLY_HEALTH_UNDERVOLTAGE: + dev_info(&charger->client->dev, + "%s: Interrupted by OVP/UVLO\n", __func__); + /* Do not set POWER_SUPPLY_PROP_HEALTH + * excute monitor work again. + * ovp/uvlo is checked by polling + */ + psy_do_property("battery", set, + POWER_SUPPLY_PROP_CHARGE_TYPE, val); + break; + + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + dev_err(&charger->client->dev, + "%s: Interrupted but Unspec\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_GOOD: + dev_err(&charger->client->dev, + "%s: Interrupted but Good\n", __func__); + /* Do not set POWER_SUPPLY_PROP_HEALTH + * excute monitor work again. + * ovp/uvlo is checked by polling + */ + psy_do_property("battery", set, + POWER_SUPPLY_PROP_CHARGE_TYPE, val); + break; + + case POWER_SUPPLY_HEALTH_UNKNOWN: + default: + dev_err(&charger->client->dev, + "%s: Invalid Charger Health\n", __func__); + break; + } + } + + if (charger->pdata->cable_check_type & SEC_BATTERY_CABLE_CHECK_CHGINT) { + if (!smb358_hal_chg_get_property(charger->client, + POWER_SUPPLY_PROP_ONLINE, &val)) + return; + + /* use SEC_BATTERY_CABLE_SOURCE_EXTERNAL for cable_source_type + * charger would call battery driver to set ONLINE property + * check battery driver loaded or not + */ + if (get_power_supply_by_name("battery")) { + psy_do_property("battery", set, + POWER_SUPPLY_PROP_ONLINE, val); + } else { + if (charger->pdata->check_cable_result_callback) + charger->pdata->check_cable_result_callback(val.intval); + } + } +} + +static irqreturn_t smb358_chg_irq_thread(int irq, void *irq_data) +{ + struct sec_charger_info *charger = irq_data; + + schedule_delayed_work(&charger->isr_work, 0); + + return IRQ_HANDLED; +} + +static int smb358_chg_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(smb358_charger_attrs); i++) { + rc = device_create_file(dev, &smb358_charger_attrs[i]); + if (rc) + goto create_attrs_failed; + } + goto create_attrs_succeed; + +create_attrs_failed: + dev_err(dev, "%s: failed (%d)\n", __func__, rc); + while (i--) + device_remove_file(dev, &smb358_charger_attrs[i]); +create_attrs_succeed: + return rc; +} + +ssize_t smb358_chg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const ptrdiff_t offset = attr - smb358_charger_attrs; + int i = 0; + + switch (offset) { + case CHG_REG: + case CHG_DATA: + case CHG_REGS: + i = smb358_hal_chg_show_attrs(dev, offset, buf); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t smb358_chg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + const ptrdiff_t offset = attr - smb358_charger_attrs; + int ret = 0; + + switch (offset) { + case CHG_REG: + case CHG_DATA: + ret = smb358_hal_chg_store_attrs(dev, offset, buf, count); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +#ifdef CONFIG_OF +static int smb358_charger_read_u32_index_dt(const struct device_node *np, + const char *propname, + u32 index, u32 *out_value) +{ + struct property *prop = of_find_property(np, propname, NULL); + u32 len = (index + 1) * sizeof(*out_value); + + if (!prop) + return (-EINVAL); + if (!prop->value) + return (-ENODATA); + if (len > prop->length) + return (-EOVERFLOW); + + *out_value = be32_to_cpup(((__be32 *)prop->value) + index); + + return 0; +} +extern unsigned int system_rev; +static int smb358_charger_parse_dt(struct device *dev, + struct sec_charger_info *charger) +{ + struct device_node *np = dev->of_node; + sec_battery_platform_data_t *pdata = charger->pdata; + int ret = 0; + int i, len; + const u32 *p; + + if (np == NULL) { + pr_err("%s np NULL\n", __func__); + return -1; + } else { +#if defined(CONFIG_MACH_VIENNAVZW) || defined(CONFIG_MACH_VIENNAATT) + if (system_rev >= 0xC) + pdata->vbus_ctrl_gpio = 28; + else + pdata->vbus_ctrl_gpio = 0; + pr_info("%s reading vbus_ctrl_gpio = %d\n", + __func__, pdata->vbus_ctrl_gpio); +#else + ret = of_get_named_gpio(np, "charger,vbus_ctrl_gpio", 0); + if (ret > 0) { + pdata->vbus_ctrl_gpio = ret; + pr_info("%s reading vbus_ctrl_gpio = %d\n", __func__, ret); + } else { + pdata->vbus_ctrl_gpio = 0; + pr_info("%s vbus_ctrl_gpio read fail\n", __func__); + } +#endif + + np = of_find_node_by_name(NULL, "sec-battery"); + if (!np) { + pr_err("%s np NULL\n", __func__); + } + ret = of_property_read_u32(np, "battery,chg_float_voltage", + &pdata->chg_float_voltage); + if (ret < 0) + pr_err("%s: chg_float_voltage read failed (%d)\n", __func__, ret); + + ret = of_property_read_u32(np, "battery,chg_min_system_voltage", + &pdata->chg_min_system_voltage); + if (ret < 0) { + pr_err("%s: chg_min_system_voltage read failed (%d)\n", __func__, ret); + pdata->chg_min_system_voltage = 3750; + pr_err("%s: chg_min_system_voltage is used as default setting (%d)\n", + __func__, pdata->chg_min_system_voltage); + } + + ret = of_property_read_u32(np, "battery,ovp_uvlo_check_type", + &pdata->ovp_uvlo_check_type); + if (ret < 0) + pr_err("%s: ovp_uvlo_check_type read failed (%d)\n", __func__, ret); + + ret = of_get_named_gpio(np, "battery,chg_int", 0); + if (ret > 0) { + pdata->chg_irq = gpio_to_irq(ret); + pr_info("%s reading chg_int_gpio = %d\n", __func__, ret); + } else { + pr_info("%s reading chg_int_gpio is empty\n", __func__); + } + + ret = of_property_read_u32(np, "battery,chg_irq_attr", + (unsigned int *)&pdata->chg_irq_attr); + if (ret) + pr_info("%s: chg_irq_attr is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_check_type", + &pdata->full_check_type); + if (ret < 0) + pr_err("%s: full_check_type read failed (%d)\n", __func__, ret); + + p = of_get_property(np, "battery,input_current_limit", &len); + len = len / sizeof(u32); + pdata->charging_current = kzalloc(sizeof(sec_charging_current_t) * len, + GFP_KERNEL); + + for(i = 0; i < len; i++) { + ret = smb358_charger_read_u32_index_dt(np, + "battery,input_current_limit", i, + &pdata->charging_current[i].input_current_limit); + ret = smb358_charger_read_u32_index_dt(np, + "battery,fast_charging_current", i, + &pdata->charging_current[i].fast_charging_current); + ret = smb358_charger_read_u32_index_dt(np, + "battery,full_check_current_1st", i, + &pdata->charging_current[i].full_check_current_1st); + ret = smb358_charger_read_u32_index_dt(np, + "battery,full_check_current_2nd", i, + &pdata->charging_current[i].full_check_current_2nd); + } + } + return ret; +} +#else +static int smb358_charger_parse_dt(struct max77803_charger_data *charger) +{ + return 0; +} +#endif + +static int smb358_charger_probe( + struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = + to_i2c_adapter(client->dev.parent); + struct sec_charger_info *charger; + int ret = 0; + + dev_info(&client->dev, + "%s: SMB358 Charger Driver Loading\n", __func__); + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger->client = client; + if (client->dev.of_node) { + void * pdata = kzalloc(sizeof(sec_battery_platform_data_t), GFP_KERNEL); + if (!pdata) + goto err_free1; + charger->pdata = pdata; + if (smb358_charger_parse_dt(&client->dev, charger)) + dev_err(&client->dev, + "%s : Failed to get charger dt\n", __func__); + } else + charger->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, charger); + + charger->siop_level = 100; + charger->psy_chg.name = "sec-charger"; + charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN; + charger->psy_chg.get_property = smb358_chg_get_property; + charger->psy_chg.set_property = smb358_chg_set_property; + charger->psy_chg.properties = smb358_charger_props; + charger->psy_chg.num_properties = ARRAY_SIZE(smb358_charger_props); + charger->is_slow_charging = false; + + if (charger->pdata->chg_gpio_init) { + if (!charger->pdata->chg_gpio_init()) { + dev_err(&client->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_free; + } + } + + if (!smb358_hal_chg_init(charger->client)) { + dev_err(&client->dev, + "%s: Failed to Initialize Charger\n", __func__); + goto err_free; + } + + ret = power_supply_register(&client->dev, &charger->psy_chg); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Register psy_chg\n", __func__); + goto err_free; + } + + INIT_DELAYED_WORK(&charger->slow_work, + smb358_check_slow_charging); + + if (charger->pdata->chg_irq) { + INIT_DELAYED_WORK( + &charger->isr_work, smb358_chg_isr_work); + + ret = request_threaded_irq(charger->pdata->chg_irq, + NULL, smb358_chg_irq_thread, + charger->pdata->chg_irq_attr, + "charger-irq", charger); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Reqeust IRQ\n", __func__); + goto err_supply_unreg; + } + + ret = enable_irq_wake(charger->pdata->chg_irq); + if (ret < 0) + dev_err(&client->dev, + "%s: Failed to Enable Wakeup Source(%d)\n", + __func__, ret); + } + + ret = smb358_chg_create_attrs(charger->psy_chg.dev); + if (ret) { + dev_err(&client->dev, + "%s : Failed to create_attrs\n", __func__); + goto err_req_irq; + } + + dev_info(&client->dev, + "%s: SMB358 Charger Driver Loaded\n", __func__); + return 0; + +err_req_irq: + if (charger->pdata->chg_irq) + free_irq(charger->pdata->chg_irq, charger); +err_supply_unreg: + power_supply_unregister(&charger->psy_chg); +err_free: + kfree(charger->pdata); +err_free1: + kfree(charger); + + return ret; +} + +static int smb358_charger_remove( + struct i2c_client *client) +{ + return 0; +} + +static int smb358_charger_suspend(struct i2c_client *client, + pm_message_t state) +{ + if (!smb358_hal_chg_suspend(client)) + dev_err(&client->dev, + "%s: Failed to Suspend Charger\n", __func__); + + return 0; +} + +static int smb358_charger_resume(struct i2c_client *client) +{ + dev_info(&client->dev,"%s: start\n", __func__); + + if (!smb358_hal_chg_resume(client)) + dev_err(&client->dev, + "%s: Failed to Resume Charger\n", __func__); + + return 0; +} + +static const struct i2c_device_id smb358_charger_id[] = { + {"smb358", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, smb358_charger_id); +static struct of_device_id smb358_i2c_match_table[] = { + { .compatible = "smb358,i2c", }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, smb358_i2c_match_table); + +static struct i2c_driver smb358_charger_driver = { + .driver = { + .name = "smb358", + .owner = THIS_MODULE, + .of_match_table = smb358_i2c_match_table, + }, + .probe = smb358_charger_probe, + .remove = smb358_charger_remove, + .suspend = smb358_charger_suspend, + .resume = smb358_charger_resume, + .shutdown = smb358_charger_shutdown, + .id_table = smb358_charger_id, +}; + +static int __init smb358_charger_init(void) +{ + return i2c_add_driver(&smb358_charger_driver); +} + +static void __exit smb358_charger_exit(void) +{ + i2c_del_driver(&smb358_charger_driver); +} + +module_init(smb358_charger_init); +module_exit(smb358_charger_exit); + +MODULE_DESCRIPTION("Samsung SMB358 Charger Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/battery/sprd27x3_charger4samsung.c b/drivers/battery/sprd27x3_charger4samsung.c new file mode 100755 index 00000000..bc69c8ab --- /dev/null +++ b/drivers/battery/sprd27x3_charger4samsung.c @@ -0,0 +1,960 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/reboot.h> +#include <linux/string.h> +#include <linux/gpio.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/power_supply.h> + +#include <mach/hardware.h> +#include <mach/sci.h> +#include <mach/sci_glb_regs.h> +#include <mach/gpio.h> +#include <mach/adi.h> +#include <mach/adc.h> +#include <mach/usb.h> +#include <mach/irqs.h> +#include <linux/irq.h> +#include <asm/io.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <mach/pinmap.h> + +#include <linux/battery/sec_charger.h> +#define BATT_DETECT 43 +#define SIOP_CHARGING_LIMIT_CURRENT1 300 +#define SIOP_CHARGING_LIMIT_CURRENT2 350 +#define SIOP_CHARGING_LIMIT_CURRENT3 400 +#define SIOP_CHARGING_LIMIT_CURRENT4 450 + +extern void sprdfgu_adp_status_set(int plugin); +extern int sci_adc_get_value(unsigned chan, int scale); +void sprdchg_set_chg_ovp(uint32_t ovp_vol); +void sprdchg_set_cccvpoint(unsigned int cvpoint); +// static int sprdbat_adjust_cccvpoint(void); +static uint32_t sprdchg_tune_endvol_cccv(uint32_t chg_end_vol, uint32_t cal_cccv); +void sprdchg_stop_charge(void); +void sprdchg_start_charge(void); + +#define EIC_VCHG_OVI (A_EIC_START + 6) +static uint32_t irq_vchg_ovi; +static uint32_t irq_vf_det; +static int vchg_ovp_state = 0; +static int is_fully_charged = 0; + +struct sprdbat_auxadc_cal adc_cal = { + 4200, 3310, + 3600, 2832, + SPRDBAT_AUXADC_CAL_NO, +}; + +static ssize_t sprdbat_store_caliberate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); +static ssize_t sprdbat_show_caliberate(struct device *dev, + struct device_attribute *attr, + char *buf); +uint32_t sprdchg_read_vbat_vol(void); +uint32_t sprdchg_get_cccvpoint(void); +uint32_t sprdchg_read_vchg_vol(void); +void sprdchg_stop_charge(void); +static uint32_t sprdchg_read_chg_current(void); + +#define SPRDBAT_CALIBERATE_ATTR(_name) \ +{ \ + .attr = { .name = #_name, .mode = S_IRUGO | S_IWUSR | S_IWGRP, }, \ + .show = sprdbat_show_caliberate, \ + .store = sprdbat_store_caliberate, \ +} +#define SPRDBAT_CALIBERATE_ATTR_RO(_name) \ +{ \ + .attr = { .name = #_name, .mode = S_IRUGO, }, \ + .show = sprdbat_show_caliberate, \ +} +#define SPRDBAT_CALIBERATE_ATTR_WO(_name) \ +{ \ + .attr = { .name = #_name, .mode = S_IWUSR | S_IWGRP, }, \ + .store = sprdbat_store_caliberate, \ +} + +static struct device_attribute sprd_caliberate[] = { + SPRDBAT_CALIBERATE_ATTR_RO(real_time_voltage), + SPRDBAT_CALIBERATE_ATTR_WO(stop_charge), + SPRDBAT_CALIBERATE_ATTR_RO(real_time_current), + SPRDBAT_CALIBERATE_ATTR_WO(battery_0), + SPRDBAT_CALIBERATE_ATTR_WO(battery_1), + SPRDBAT_CALIBERATE_ATTR(hw_switch_point), + SPRDBAT_CALIBERATE_ATTR_RO(charger_voltage), + SPRDBAT_CALIBERATE_ATTR_RO(real_time_vbat_adc), + SPRDBAT_CALIBERATE_ATTR_WO(save_capacity), +}; + +enum sprdbat_attribute { + BATTERY_VOLTAGE = 0, + STOP_CHARGE, + BATTERY_NOW_CURRENT, + BATTERY_0, + BATTERY_1, + HW_SWITCH_POINT, + CHARGER_VOLTAGE, + BATTERY_ADC, + SAVE_CAPACITY, +}; + +extern struct device_attribute sprd_caliberate[]; +static ssize_t sprdbat_store_caliberate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long set_value; + const ptrdiff_t off = attr - sprd_caliberate; + + set_value = simple_strtoul(buf, NULL, 10); + pr_info("battery calibrate value %d %lu\n", off, set_value); + + //mutex_lock(&sprdbat_data->lock); + switch (off) { + case STOP_CHARGE: + sprdchg_stop_charge(); + break; + case BATTERY_0: + adc_cal.p0_vol = set_value & 0xffff; //only for debug + adc_cal.p0_adc = (set_value >> 16) & 0xffff; + break; + case BATTERY_1: + adc_cal.p1_vol = set_value & 0xffff; + adc_cal.p1_adc = (set_value >> 16) & 0xffff; + adc_cal.cal_type = SPRDBAT_AUXADC_CAL_NV; + break; + case HW_SWITCH_POINT: + sprdchg_set_cccvpoint(set_value); + + //if (sprdbat_cv_irq_dis && sprdfgu_is_new_chip()) { + break; + case SAVE_CAPACITY: + { +#if 0 + int temp = set_value - poweron_capacity; + + pr_info("battery temp:%d\n", temp); + if (abs(temp) > SPRDBAT_VALID_CAP || 0 == set_value) { + pr_info("battery poweron capacity:%lu,%d\n", + set_value, poweron_capacity); + sprdbat_data->bat_info.capacity = + poweron_capacity; + } else { + pr_info("battery old capacity:%lu,%d\n", + set_value, poweron_capacity); + sprdbat_data->bat_info.capacity = set_value; + } + power_supply_changed(&sprdbat_data->battery); +#endif + } + break; + default: + count = -EINVAL; + break; + } + //mutex_unlock(&sprdbat_data->lock); + return count; +} + +static ssize_t sprdbat_show_caliberate(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i = 0; + int status = POWER_SUPPLY_STATUS_UNKNOWN; + int ret; + const ptrdiff_t off = attr - sprd_caliberate; + int adc_value; + int voltage; + uint32_t now_current; + + ret = sci_adi_read(ANA_REG_GLB_CHGR_STATUS); + + if (ret & BIT_CHGR_ON) + status = POWER_SUPPLY_STATUS_CHARGING; + else + status = POWER_SUPPLY_STATUS_DISCHARGING; + + switch (off) { + case BATTERY_VOLTAGE: + voltage = sprdchg_read_vbat_vol(); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", voltage); + break; + case BATTERY_NOW_CURRENT: + if (status == POWER_SUPPLY_STATUS_CHARGING) { + now_current = sprdchg_read_chg_current(); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + now_current); + } else { + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + "discharging"); + } + break; + case HW_SWITCH_POINT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + sprdchg_get_cccvpoint()); + break; + case CHARGER_VOLTAGE: + if (status == POWER_SUPPLY_STATUS_CHARGING) { + voltage = sprdchg_read_vchg_vol(); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", voltage); + } else { + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + "discharging"); + } + + break; + case BATTERY_ADC: + adc_value = sci_adc_get_value(ADC_CHANNEL_VBAT, false); + if (adc_value < 0) + adc_value = 0; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", adc_value); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +int sprdbat_creat_caliberate_attr(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(sprd_caliberate); i++) { + rc = device_create_file(dev, &sprd_caliberate[i]); + if (rc) + goto sprd_attrs_failed; + } + goto sprd_attrs_succeed; + +sprd_attrs_failed: + while (i--) + device_remove_file(dev, &sprd_caliberate[i]); + +sprd_attrs_succeed: + return rc; +} + +static int __init adc_cal_start(char *str) +{ + unsigned int adc_data[2] = { 0 }; + char *cali_data = &str[1]; + if (str) { + pr_info("adc_cal%s!\n", str); + sscanf(cali_data, "%d,%d", &adc_data[0], &adc_data[1]); + pr_info("adc_data: 0x%x 0x%x!\n", adc_data[0], adc_data[1]); + adc_cal.p0_vol = adc_data[0] & 0xffff; + adc_cal.p0_adc = (adc_data[0] >> 16) & 0xffff; + adc_cal.p1_vol = adc_data[1] & 0xffff; + adc_cal.p1_adc = (adc_data[1] >> 16) & 0xffff; + adc_cal.cal_type = SPRDBAT_AUXADC_CAL_NV; + printk + ("auxadc cal from cmdline ok!!! adc_data[0]: 0x%x, adc_data[1]:0x%x\n", + adc_data[0], adc_data[1]); + } + return 1; +} +__setup("adc_cal", adc_cal_start); + +static __used irqreturn_t sprdchg_vchg_ovi_irq(int irq, void *irq_data) +{ + int value; + struct sec_charger_info *charger = irq_data; + + value = gpio_get_value(EIC_VCHG_OVI); + if (value) { + printk("charger ovi high\n"); + vchg_ovp_state = 1; + sprdchg_stop_charge(); + irq_set_irq_type(irq_vchg_ovi, + IRQ_TYPE_LEVEL_LOW); + } else { + vchg_ovp_state = 0; + if (charger->cable_type != POWER_SUPPLY_TYPE_BATTERY) { + sprdchg_start_charge(); + } + printk("charger ovi low\n"); + irq_set_irq_type(irq_vchg_ovi, + IRQ_TYPE_LEVEL_HIGH); + } + + return IRQ_HANDLED; +} + +static __used irqreturn_t sprdchg_vf_det_irq(int irq, void *irq_data) +{ + int adc; + struct sec_charger_info *charger = irq_data; + + adc = gpio_get_value(BATT_DETECT); + if (adc != 0) { + sprdchg_stop_charge(); + irq_set_irq_type(irq_vf_det, + IRQ_TYPE_LEVEL_LOW); + pr_info("sprdchg_get_batt_presence irq : %X\n", adc); + } else { + if (charger->cable_type != POWER_SUPPLY_TYPE_BATTERY) { + sprdchg_start_charge(); + } + irq_set_irq_type(irq_vf_det, + IRQ_TYPE_LEVEL_HIGH); + } + return IRQ_HANDLED; +} + +void sprdchg_init(struct sec_charger_info *charger) +{ + int ret = -ENODEV; + + sci_adi_set(ANA_REG_GLB_CHGR_CTRL2, BIT_CHGR_CC_EN); + + pr_info("########### CHARGer init ############\n"); + + sprdchg_set_chg_ovp(SPRDBAT_OVP_STOP_VOL); + +/*OVP interrupt to sprd27x3_charger4samsung.c,and OVP state is polled by sec_battery.c */ +/*Had better use interrupt for OVP,if OVI occur,should stop charging immediately*/ + ret = gpio_request(EIC_VCHG_OVI, "vchg_ovi"); + if (ret) { + printk("failed to request gpio: %d\n", ret); + } + gpio_direction_input(EIC_VCHG_OVI); + + irq_vchg_ovi = gpio_to_irq(EIC_VCHG_OVI); + + set_irq_flags(irq_vchg_ovi, IRQF_VALID | IRQF_NOAUTOEN); + ret = request_irq(irq_vchg_ovi, sprdchg_vchg_ovi_irq, + IRQF_NO_SUSPEND, "sprdbat_vchg_ovi", charger); + + ret = gpio_request(BATT_DETECT, "battery_Detect"); + if (ret) { + printk("failed to request gpio: %d\n", ret); + } + gpio_direction_input(BATT_DETECT); + gpio_export(BATT_DETECT,1); + + irq_vf_det = gpio_to_irq(BATT_DETECT); + + set_irq_flags(irq_vf_det, IRQF_VALID | IRQF_NOAUTOEN); + ret = request_irq(irq_vf_det, sprdchg_vf_det_irq, + IRQF_NO_SUSPEND, "irq_vf_det", charger); + + irq_set_irq_type(irq_vf_det, IRQ_TYPE_LEVEL_HIGH); + enable_irq(irq_vf_det); + +#if defined(CONFIG_MACH_YOUNG2) + __raw_writel((BITS_PIN_DS(1)|BITS_PIN_AF(3)|BIT_PIN_WPU|BIT_PIN_SLP_WPU|BIT_PIN_SLP_IE), SCI_ADDR(SPRD_PIN_BASE, 0x00D0)); +#endif +// + /*sprdbat_adjust_cccvpoint();*/ + + /*if want to support 4.35V battery,please change SPRDBAT_CHG_END_VOL_PURE to 4350*/ + { + uint32_t cv_point; + uint32_t target_cv; + extern int sci_efuse_cccv_cal_get(unsigned int *p_cal_data); + if (sci_efuse_cccv_cal_get(&cv_point)) { + printk("cccv_point efuse:%d\n", cv_point); + target_cv = sprdchg_tune_endvol_cccv(SPRDBAT_CHG_END_VOL_PURE, cv_point); + sprdchg_set_cccvpoint(target_cv); + printk("cccv_point sprdchg_tune_endvol_cccv:%d\n", target_cv); + } else { + sprdchg_set_cccvpoint(SPRDBAT_CCCV_DEFAULT); + printk("cccv_point default\n"); + } + } + + if (adc_cal.cal_type == SPRDBAT_AUXADC_CAL_NO) { + extern int sci_efuse_calibration_get(unsigned int *p_cal_data); + unsigned int efuse_cal_data[2] = { 0 }; + if (sci_efuse_calibration_get(efuse_cal_data)) { + adc_cal.p0_vol = efuse_cal_data[0] & 0xffff; + adc_cal.p0_adc = (efuse_cal_data[0] >> 16) & 0xffff; + adc_cal.p1_vol = efuse_cal_data[1] & 0xffff; + adc_cal.p1_adc = (efuse_cal_data[1] >> 16) & 0xffff; + adc_cal.cal_type = SPRDBAT_AUXADC_CAL_CHIP; + printk + ("auxadc cal from efuse ok!!! efuse_cal_data[0]: 0x%x, efuse_cal_data[1]:0x%x\n", + efuse_cal_data[0], efuse_cal_data[1]); + } + } + sci_adi_write((ANA_CTL_EIC_BASE + 0x50), 100, (0xFFF)); //eic debunce + printk("ANA_CTL_EIC_BASE0x%x\n", sci_adi_read(ANA_CTL_EIC_BASE + 0x50)); +} + +static int sprdchg_get_batt_presence(void) +{ + int adc; + + adc = gpio_get_value(BATT_DETECT); + pr_info("sprdchg_get_batt_presence : %X\n", adc); + if (adc==0) + return 1; + else + return 0; +} + +uint16_t sprdchg_bat_adc_to_vol(uint16_t adcvalue) +{ + int32_t temp; + + temp = adc_cal.p0_vol - adc_cal.p1_vol; + temp = temp * (adcvalue - adc_cal.p0_adc); + temp = temp / (adc_cal.p0_adc - adc_cal.p1_adc); + temp = temp + adc_cal.p0_vol; + + return temp; +} + +static uint16_t sprdbat_charger_adc_to_vol(uint16_t adcvalue) +{ + uint32_t result; + uint32_t vbat_vol = sprdchg_bat_adc_to_vol(adcvalue); + uint32_t m, n; + uint32_t bat_numerators, bat_denominators; + uint32_t vchg_numerators, vchg_denominators; + + sci_adc_get_vol_ratio(ADC_CHANNEL_VBAT, 0, &bat_numerators, + &bat_denominators); + sci_adc_get_vol_ratio(SPRDBAT_ADC_CHANNEL_VCHG, 0, &vchg_numerators, + &vchg_denominators); + + ///v1 = vbat_vol*0.268 = vol_bat_m * r2 /(r1+r2) + n = bat_denominators * vchg_numerators; + m = vbat_vol * bat_numerators * (vchg_denominators); + result = (m + n / 2) / n; + return result; + +} + +uint32_t sprdchg_read_vchg_vol(void) +{ + int vchg_value; + vchg_value = sci_adc_get_value(SPRDBAT_ADC_CHANNEL_VCHG, false); + return sprdbat_charger_adc_to_vol(vchg_value); +} + +void sprdchg_set_chg_ovp(uint32_t ovp_vol) +{ + uint32_t temp; + + if (ovp_vol > SPRDBAT_CHG_OVP_LEVEL_MAX) { + ovp_vol = SPRDBAT_CHG_OVP_LEVEL_MAX; + } + + if (ovp_vol < SPRDBAT_CHG_OVP_LEVEL_MIN) { + ovp_vol = SPRDBAT_CHG_OVP_LEVEL_MIN; + } + + temp = ((ovp_vol - SPRDBAT_CHG_OVP_LEVEL_MIN) / 100); + + sci_adi_clr(ANA_REG_GLB_CHGR_CTRL2, BIT_CHGR_CC_EN); + + sci_adi_write(ANA_REG_GLB_CHGR_CTRL1, + BITS_VCHG_OVP_V(temp), BITS_VCHG_OVP_V(~0)); + + sci_adi_set(ANA_REG_GLB_CHGR_CTRL2, BIT_CHGR_CC_EN); +} + +void sprdchg_set_chg_cur(uint32_t chg_current) +{ + uint32_t temp; + + if (chg_current > SPRDBAT_CHG_CUR_LEVEL_MAX) { + chg_current = SPRDBAT_CHG_CUR_LEVEL_MAX; + } + + if (chg_current < SPRDBAT_CHG_CUR_LEVEL_MIN) { + chg_current = SPRDBAT_CHG_CUR_LEVEL_MIN; + } + + /*if chg_current >= 1400,one step is 100mA*/ + if (chg_current < 1400) { + temp = ((chg_current - 300) / 50); + } else { + temp = ((chg_current - 1400) / 100); + temp += 0x16; + } + printk("sprdchg_set_chg_cur : %d\n", + chg_current); + + sci_adi_clr(ANA_REG_GLB_CHGR_CTRL2, BIT_CHGR_CC_EN); + + sci_adi_write(ANA_REG_GLB_CHGR_CTRL1, + BITS_CHGR_CC_I(temp), BITS_CHGR_CC_I(~0)); + + sci_adi_set(ANA_REG_GLB_CHGR_CTRL2, BIT_CHGR_CC_EN); +} + +void sprdchg_set_cccvpoint(unsigned int cvpoint) +{ + sci_adi_write(ANA_REG_GLB_CHGR_CTRL0, + BITS_CHGR_CV_V(cvpoint), BITS_CHGR_CV_V(~0)); + +} + +uint32_t sprdchg_get_cccvpoint(void) +{ + int shft = __ffs(BITS_CHGR_CV_V(~0)); + return (sci_adi_read(ANA_REG_GLB_CHGR_CTRL0) & BITS_CHGR_CV_V(~0)) >> + shft; +} +static uint32_t sprdchg_tune_endvol_cccv(uint32_t chg_end_vol, uint32_t cal_cccv) +{ + uint32_t cv; + + BUG_ON(chg_end_vol > 4400); + sci_adi_write(ANA_REG_GLB_CHGR_CTRL0, + BITS_CHGR_END_V(0), BITS_CHGR_END_V(~0)); + if (chg_end_vol >= 4200) { + if (chg_end_vol < 4300) { + cv = (((chg_end_vol - 4200) * 10) + + (ONE_CCCV_STEP_VOL >> 1)) / ONE_CCCV_STEP_VOL + + cal_cccv; + if (cv > SPRDBAT_CCCV_MAX) { + printk("sprdchg: cv > SPRDBAT_CCCV_MAX!\n"); + sci_adi_write(ANA_REG_GLB_CHGR_CTRL0, + BITS_CHGR_END_V(1), + BITS_CHGR_END_V(~0)); + return (cal_cccv - (((4300 - chg_end_vol) * 10) + + (ONE_CCCV_STEP_VOL >> 1)) / + ONE_CCCV_STEP_VOL); + } else { + return cv; + } + } else { + cv = (((chg_end_vol - 4300) * 10) + + (ONE_CCCV_STEP_VOL >> 1)) / ONE_CCCV_STEP_VOL + + cal_cccv; + if (cv > SPRDBAT_CCCV_MAX) { + printk("sprdchg: cv > SPRDBAT_CCCV_MAX!\n"); + sci_adi_write(ANA_REG_GLB_CHGR_CTRL0, + BITS_CHGR_END_V(2), + BITS_CHGR_END_V(~0)); + return (cal_cccv - (((4400 - chg_end_vol) * 10) + + (ONE_CCCV_STEP_VOL >> 1)) / + ONE_CCCV_STEP_VOL); + } else { + sci_adi_write(ANA_REG_GLB_CHGR_CTRL0, + BITS_CHGR_END_V(1), + BITS_CHGR_END_V(~0)); + return cv; + } + } + } else { + cv = (((4200 - chg_end_vol) * 10) + + (ONE_CCCV_STEP_VOL >> 1)) / ONE_CCCV_STEP_VOL; + if (cv > cal_cccv) { + return 0; + } else { + return (cal_cccv - cv); + } + } +} +/*please do NOT call it on charge init phase, +it only be used on SPRD code when cccv point be NOT calibrated by ATE */ +#if 0 +static int sprdbat_adjust_cccvpoint(void) +{ + uint32_t cv; + uint32_t vbat_now; + + union power_supply_propval value; + + psy_do_property("sec-fuelgauge", get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + + vbat_now = value.intval; + + if (vbat_now <= SPRDBAT_CHG_END_VOL_PURE) { + cv = ((SPRDBAT_CHG_END_VOL_PURE - + vbat_now) * 10) / ONE_CCCV_STEP_VOL + 1; + cv += sprdchg_get_cccvpoint(); + + printk("sprdbat_adjust_cccvpoint turn high cv:0x%x\n", + cv); + //BUG_ON(cv > SPRDBAT_CCCV_MAX); + if (cv > SPRDBAT_CCCV_MAX) { + cv = SPRDBAT_CCCV_MAX; + } + + sprdchg_set_cccvpoint(cv); + } else { + cv = sprdchg_get_cccvpoint(); + //BUG_ON(cv < (SPRDBAT_CCCV_MIN + 2)); + if (cv < (SPRDBAT_CCCV_MIN + 1)) { + cv = SPRDBAT_CCCV_MIN + 1; + } + cv -= 1; + + printk("sprdbat_adjust_cccvpoint turn low cv:0x%x\n", + cv); + sprdchg_set_cccvpoint(cv); + } + return 0; +} +#endif +static void _sprdchg_set_recharge(void) +{ + sci_adi_set(ANA_REG_GLB_CHGR_CTRL2, BIT_RECHG); +} + +static void _sprdchg_stop_recharge(void) +{ + sci_adi_clr(ANA_REG_GLB_CHGR_CTRL2, BIT_RECHG); +} + +void sprdchg_stop_charge(void) +{ +#if defined(CONFIG_ARCH_SCX15) ||defined(CONFIG_ADIE_SC2723S) ||defined(CONFIG_ADIE_SC2723) + sci_adi_write(ANA_REG_GLB_CHGR_CTRL0, BIT_CHGR_PD, BIT_CHGR_PD); +#else + sci_adi_write(ANA_REG_GLB_CHGR_CTRL0, + BIT_CHGR_PD_RTCSET, + BIT_CHGR_PD_RTCCLR | BIT_CHGR_PD_RTCSET); +#endif + _sprdchg_stop_recharge(); +} + +void sprdchg_start_charge(void) +{ +#if defined(CONFIG_ARCH_SCX15) ||defined(CONFIG_ADIE_SC2723S) ||defined(CONFIG_ADIE_SC2723) + sci_adi_write(ANA_REG_GLB_CHGR_CTRL0, 0, BIT_CHGR_PD); +#else + sci_adi_write(ANA_REG_GLB_CHGR_CTRL0, + BIT_CHGR_PD_RTCCLR, + BIT_CHGR_PD_RTCCLR | BIT_CHGR_PD_RTCSET); +#endif + _sprdchg_set_recharge(); +} + +// static int sprd_get_charging_health(struct sec_charger_info *charger); +void sprdchg_put_chgcur(uint32_t chging_current); +uint32_t sprdchg_get_chgcur_ave(void); + + +void sprdchg_set_charge(struct sec_charger_info *charger) +{ + union power_supply_propval value; + pr_info("########### CHARGING ############\n"); + //charger->cable_type = POWER_SUPPLY_TYPE_MAINS; + + vchg_ovp_state = 0; + if (charger->cable_type == POWER_SUPPLY_TYPE_BATTERY) { + if (is_fully_charged) { + psy_do_property("battery", get, + POWER_SUPPLY_PROP_CHARGE_NOW, value); + if (value.intval != SEC_BATTERY_CHARGING_2ND) + is_fully_charged = 0; + } + disable_irq_nosync(irq_vchg_ovi); + + sprdchg_stop_charge(); + } else { + if (((charger->siop_level < 100) && (charger->siop_level > 0)) && + charger->cable_type == POWER_SUPPLY_TYPE_MAINS) { + if (charger->siop_level >= 80) { + sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT4); + } else if (charger->siop_level >= 70) { + sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT3); + } else if (charger->siop_level >= 60) { + sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT2); + } else { + sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT1); + } + } else { + sprdchg_set_chg_cur(charger->pdata->charging_current[ + charger->cable_type].fast_charging_current); + } + sprdchg_start_charge(); + irq_set_irq_type(irq_vchg_ovi, IRQ_TYPE_LEVEL_HIGH); + enable_irq(irq_vchg_ovi); + } +} + +static uint32_t _sprdchg_read_chg_current(void) +{ + uint32_t vbat, isense; + uint32_t cnt = 0; + + for (cnt = 0; cnt < 3; cnt++) { + isense = + sprdchg_bat_adc_to_vol(sci_adc_get_value(ADC_CHANNEL_ISENSE, + false)); + vbat = + sprdchg_bat_adc_to_vol(sci_adc_get_value(ADC_CHANNEL_VBAT, + false)); + if (isense >= vbat) { + break; + } + } + if (isense > vbat) { + uint32_t temp = ((isense - vbat) * 1000) / 68; //(vol/68mohm) + //printk(KERN_ERR "sprdchg: sprdchg_read_chg_current:%d\n", temp); + return temp; + } else { + printk(KERN_ERR + "chg_current warning....isense:%d....vbat:%d\n", + isense, vbat); + return 0; + } +} + +uint32_t sprdchg_read_chg_current(void) +{ +#define CUR_RESULT_NUM 9 + + int i, temp; + volatile int j; + uint32_t cur_result[CUR_RESULT_NUM]; + + for (i = 0; i < CUR_RESULT_NUM; i++) { + cur_result[i] = _sprdchg_read_chg_current(); + } + + for (j = 1; j <= CUR_RESULT_NUM - 1; j++) { + for (i = 0; i < CUR_RESULT_NUM - j; i++) { + if (cur_result[i] > cur_result[i + 1]) { + temp = cur_result[i]; + cur_result[i] = cur_result[i + 1]; + cur_result[i + 1] = temp; + } + } + } + + return cur_result[1]; +} + +static int sprd_get_charging_status(struct sec_charger_info *charger) +{ + int status = POWER_SUPPLY_STATUS_UNKNOWN; + int ret; + int cur; + union power_supply_propval value; + + ret = sci_adi_read(ANA_REG_GLB_CHGR_STATUS); + printk("ANA_REG_GLB_CHGR_STATUS : 0x%x\n", ret);//for debuf + + if (ret & BIT_CHGR_ON) + status = POWER_SUPPLY_STATUS_CHARGING; + else + status = POWER_SUPPLY_STATUS_DISCHARGING; + + if (status == POWER_SUPPLY_STATUS_CHARGING) { +#if 0 //deleted by mingwei + for (i = 0; i < SPRDBAT_AVERAGE_COUNT; i++) { + cur = sprdchg_read_chg_current(); + sprdchg_put_chgcur(cur); + } + cur = sprdchg_get_chgcur_ave(); + pr_info("charging current : %d\n", cur); + if ((cur <= charger->pdata->charging_current[ + charger->cable_type].full_check_current_1st) \ + && (cur > 0)) { + status = POWER_SUPPLY_STATUS_FULL; + is_fully_charged = 1; + } +#endif + /*please use battery current of fuelgauge and cv status to check charging full condition, + because charging current of SPRD is unstable*/ +#if 1 + psy_do_property("battery", get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + cur = value.intval; + pr_info("sprd_get_charging_status current : %d\n", cur); + if ((ret & BIT_CHGR_CV_STATUS)\ + &&(cur <= charger->pdata->charging_current[ + charger->cable_type].full_check_current_1st) ) + status = POWER_SUPPLY_STATUS_FULL; +#endif + } + + if (is_fully_charged) { + status = POWER_SUPPLY_STATUS_FULL; + } + + if (vchg_ovp_state) { + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + pr_info("charging status : %d\n", status); + + return status; +} +/*Had better use interrupt for OVP,if OVI occur,should stop charging immediately*/ +#if 0 +static int sprd_get_charging_health(struct sec_charger_info *charger) +{ + int health = POWER_SUPPLY_HEALTH_GOOD; + int ret; + static int before_flag = 0; + + ret = sci_adi_read(ANA_REG_GLB_CHGR_STATUS); + + if (ret & 0x01) { + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + sprdchg_stop_charge(); + } + + if ((before_flag == POWER_SUPPLY_HEALTH_OVERVOLTAGE) && + (health == POWER_SUPPLY_HEALTH_GOOD)) { + sprdchg_set_chg_cur(charger->pdata->charging_current[ + charger->cable_type].fast_charging_current); + sprdchg_start_charge(); + pr_info("%s: not-charging -> charging\n", __func__); + } + before_flag = health; + pr_info("charging health : %d\n", health); + + return health; +} +#endif + +uint32_t chg_cur_buf[SPRDBAT_AVERAGE_COUNT]; +void sprdchg_put_chgcur(uint32_t chging_current) +{ + static uint32_t cnt = 0; + + if (cnt == SPRDBAT_AVERAGE_COUNT) { + cnt = 0; + } + chg_cur_buf[cnt++] = chging_current; +} + +uint32_t sprdchg_get_chgcur_ave(void) +{ + uint32_t i, sum = 0; + int count = 0; + + for (i = 0; i < SPRDBAT_AVERAGE_COUNT; i++) { + sum = sum + chg_cur_buf[i]; + if (!chg_cur_buf[i]) + count++; + } + + if (count == SPRDBAT_AVERAGE_COUNT) + return 0; + else + return sum / (SPRDBAT_AVERAGE_COUNT - count); +} + +uint32_t sprdchg_read_vbat_vol(void) +{ + uint32_t voltage; + voltage = + sprdchg_bat_adc_to_vol(sci_adc_get_value(ADC_CHANNEL_VBAT, false)); + + return voltage; +} + +bool sec_hal_chg_init(struct sec_charger_info *charger) +{ + sprdchg_init(charger); + + return true; +} + +bool sec_hal_chg_suspend(struct sec_charger_info *charger) +{ + return true; +} + +bool sec_hal_chg_resume(struct sec_charger_info *charger) +{ + return true; +} + +bool sec_hal_chg_get_property(struct sec_charger_info *charger, + enum power_supply_property psp, + union power_supply_propval *val) +{ + val->intval = 1; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = sprd_get_charging_status(charger); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case POWER_SUPPLY_PROP_HEALTH: + //val->intval = sprd_get_charging_health(charger); + if (vchg_ovp_state) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } + break; + case POWER_SUPPLY_PROP_ONLINE: + //val->intval = charger->cable_type; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = sprdchg_get_batt_presence(); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_AVG: + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = 1; + break; + default: + return false; + } + return true; +} + +bool sec_hal_chg_set_property(struct sec_charger_info *charger, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + charger->status = val->intval; + break; + case POWER_SUPPLY_PROP_ONLINE: + charger->cable_type = val->intval; + sprdchg_set_charge(charger); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + charger->siop_level = val->intval; + if (((charger->siop_level < 100) && (charger->siop_level > 0)) && + charger->cable_type == POWER_SUPPLY_TYPE_MAINS) { + if (charger->siop_level >= 80) { + sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT4); + } else if (charger->siop_level >= 70) { + sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT3); + } else if (charger->siop_level >= 60) { + sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT2); + } else { + sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT1); + } + } else { + sprdchg_set_chg_cur(charger->pdata->charging_current[ + charger->cable_type].fast_charging_current); + } + if (charger->cable_type != POWER_SUPPLY_TYPE_BATTERY) + sprdchg_start_charge(); + break; + default: + return false; + } + return true; +} diff --git a/drivers/battery/sprd27x3_fuelgauge4samsung.c b/drivers/battery/sprd27x3_fuelgauge4samsung.c new file mode 100755 index 00000000..ced830ae --- /dev/null +++ b/drivers/battery/sprd27x3_fuelgauge4samsung.c @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2013 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/err.h> +#include <soc/sprd/hardware.h> +#include <soc/sprd/sci.h> +#include <soc/sprd/sci_glb_regs.h> +#include <linux/io.h> +#include <soc/sprd/adi.h> +#include <linux/wakelock.h> +#include <linux/hrtimer.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/battery/sec_fuelgauge.h> +#include <linux/sprd_battery_common.h> + +int sprdbat_interpolate(int x, int n, struct sprdbat_table_data *tab) +{ + int index; + int y; + + if (x >= tab[0].x) + y = tab[0].y; + else if (x <= tab[n - 1].x) + y = tab[n - 1].y; + else { + /* find interval */ + for (index = 1; index < n; index++) + if (x > tab[index].x) + break; + /* interpolate */ + y = (tab[index - 1].y - tab[index].y) * (x - tab[index].x) + * 2 / (tab[index - 1].x - tab[index].x); + y = (y + 1) / 2; + y += tab[index].y; + } + return y; +} +static void print_pdata(struct sprd_battery_platform_data *pdata) +{ +#define PDATA_LOG(format, arg...) printk("sprdbat pdata: " format, ## arg) + int i; + + + PDATA_LOG("chg_bat_safety_vol:%d\n", pdata->chg_bat_safety_vol); + + PDATA_LOG("irq_fgu:%d\n", pdata->irq_fgu); + PDATA_LOG("fgu_mode:%d\n", pdata->fgu_mode); + PDATA_LOG("alm_soc:%d\n", pdata->alm_soc); + PDATA_LOG("alm_vol:%d\n", pdata->alm_vol); + PDATA_LOG("soft_vbat_uvlo:%d\n", pdata->soft_vbat_uvlo); + PDATA_LOG("soft_vbat_ovp:%d\n", pdata->soft_vbat_ovp); + PDATA_LOG("rint:%d\n", pdata->rint); + PDATA_LOG("cnom:%d\n", pdata->cnom); + PDATA_LOG("rsense_real:%d\n", pdata->rsense_real); + PDATA_LOG("rsense_spec:%d\n", pdata->rsense_spec); + PDATA_LOG("relax_current:%d\n", pdata->relax_current); + PDATA_LOG("fgu_cal_ajust:%d\n", pdata->fgu_cal_ajust); + PDATA_LOG("qmax_update_period:%d\n", pdata->qmax_update_period); + PDATA_LOG("ocv_tab_size:%d\n", pdata->ocv_tab_size); + for (i = 0; i < pdata->ocv_tab_size; i++) { + PDATA_LOG("ocv_tab i=%d x:%d,y:%d\n", i, pdata->ocv_tab[i].x, + pdata->ocv_tab[i].y); + } + for (i = 0; i < pdata->temp_tab_size; i++) { + PDATA_LOG("temp_tab_size i=%d x:%d,y:%d\n", i, + pdata->temp_tab[i].x, pdata->temp_tab[i].y); + } + +} + +static uint32_t init_flag = 0; +#define SPRDBAT_ONE_PERCENT_TIME (40) +#define SPRDBAT_AVOID_JUMPING_TEMI (SPRDBAT_ONE_PERCENT_TIME) + +static unsigned long sprdbat_update_capacity_time; +static uint32_t update_capacity; + +static unsigned long sprdbat_last_query_time; +struct sprd_battery_platform_data *bat_pdata; + +bool sec_hal_fg_init(fuelgauge_variable_t * fg_var) +{ + struct sprd_battery_platform_data *pdata = (&get_battery_data(fg_var))->pdata ; + bat_pdata = pdata; + pr_info("register sec_hal_fg_init\n"); + print_pdata(pdata); //debug + sprdfgu_init(pdata); + { + struct timespec cur_time; + get_monotonic_boottime(&cur_time); + sprdbat_update_capacity_time = cur_time.tv_sec; + update_capacity = sprdfgu_poweron_capacity(); + } + init_flag = 1; + return true; +} + +static uint32_t sprdbat_update_capacty(void) +{ + uint32_t fgu_capacity = sprdfgu_read_capacity(); + uint32_t flush_time = 0; + uint32_t period_time = 0; + struct timespec cur_time; + union power_supply_propval value; + + psy_do_property("battery", get, POWER_SUPPLY_PROP_STATUS, value); + + get_monotonic_boottime(&cur_time); + flush_time = cur_time.tv_sec - sprdbat_update_capacity_time; + period_time = cur_time.tv_sec - sprdbat_last_query_time; + sprdbat_last_query_time = cur_time.tv_sec; + + pr_info("fgu_capacity: = %d,flush_time: = %d,period_time:=%d\n", + fgu_capacity, flush_time, period_time); + + switch (value.intval) { + case POWER_SUPPLY_STATUS_CHARGING: + if (fgu_capacity < update_capacity) { + if (sprdfgu_read_batcurrent() > 0) { + pr_info("avoid vol jumping\n"); + fgu_capacity = update_capacity; + } else { + if (period_time <= SPRDBAT_AVOID_JUMPING_TEMI) { + fgu_capacity =update_capacity - 1; + pr_info("avoid jumping! fgu_capacity: = %d\n", fgu_capacity); + } + if ((update_capacity - fgu_capacity) >= + (flush_time / SPRDBAT_ONE_PERCENT_TIME)) { + fgu_capacity = + update_capacity - + flush_time / SPRDBAT_ONE_PERCENT_TIME; + } + } + } else if (fgu_capacity > update_capacity) { + if (period_time <= SPRDBAT_AVOID_JUMPING_TEMI) { + fgu_capacity = + update_capacity + 1; + pr_info("avoid jumping! fgu_capacity: = %d\n",fgu_capacity); + } + if ((fgu_capacity - update_capacity) >= + (flush_time / SPRDBAT_ONE_PERCENT_TIME)) { + fgu_capacity = + update_capacity + + flush_time / SPRDBAT_ONE_PERCENT_TIME; + } + } + /*when soc=100 and adp plugin occur, keep on 100, */ + if (100 == update_capacity) { + sprdbat_update_capacity_time = cur_time.tv_sec; + fgu_capacity = 100; + } else { + if (fgu_capacity >= 100) { + fgu_capacity = 99; + } + } + + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: + case POWER_SUPPLY_STATUS_DISCHARGING: + if (fgu_capacity >= update_capacity) { + fgu_capacity = update_capacity; + } else { + if (period_time <= SPRDBAT_AVOID_JUMPING_TEMI) { + fgu_capacity = + update_capacity - 1; + pr_info + ("avoid jumping! fgu_capacity: = %d\n", + fgu_capacity); + } + if ((update_capacity - fgu_capacity) >= + (flush_time / SPRDBAT_ONE_PERCENT_TIME)) { + fgu_capacity = + update_capacity - + flush_time / SPRDBAT_ONE_PERCENT_TIME; + } + } + + if (sprdfgu_read_vbat_vol() < bat_pdata->soft_vbat_uvlo) { + pr_info("soft uvlo vol 0...\n"); + fgu_capacity = 0; + } + + break; + case POWER_SUPPLY_STATUS_FULL: + sprdbat_update_capacity_time = cur_time.tv_sec; + if (fgu_capacity != 100) { + fgu_capacity = 100; + } + break; + default: + break; + } + + if (fgu_capacity != update_capacity) { + update_capacity = fgu_capacity; + sprdbat_update_capacity_time = cur_time.tv_sec; + } + sprdfgu_record_cap(update_capacity); + return update_capacity; + +} + +bool sec_hal_fg_reset(fuelgauge_variable_t * fg_var) +{ + msleep(1000); //wait a moment,insure voltage REG of FG is updated after power supply voltage be changed + sprdfgu_reset(); + { + struct timespec cur_time; + get_monotonic_boottime(&cur_time); + sprdbat_update_capacity_time = cur_time.tv_sec; //time need to be updated,too. + update_capacity = sprdfgu_poweron_capacity(); //same as your change + } + return true; +} +bool sec_hal_fg_suspend(fuelgauge_variable_t * fg_var) +{ + sprdfgu_pm_op(1); + return true; +} + +bool sec_hal_fg_resume(fuelgauge_variable_t * fg_var) +{ + sprdfgu_pm_op(0); + return true; +} +bool sec_hal_fg_fuelalert_init(fuelgauge_variable_t * fg_var , int val) +{ + return true; +} +bool sec_hal_fg_is_fuelalerted(fuelgauge_variable_t * fg_var) +{ + return false; +} +bool sec_hal_fg_full_charged(fuelgauge_variable_t * fg_var) +{ + return true; +} + +bool sec_hal_fg_fuelalert_process(void *irq_data, bool is_fuel_alerted) +{ + return true; +} + +bool sec_hal_fg_get_property(fuelgauge_variable_t * fg_var, + enum power_supply_property psp, + union power_supply_propval *val) +{ + + switch (psp) { + + /* Cell voltage (VCELL, mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = sprdfgu_read_vbat_vol(); + break; + + /* Additional Voltage Information (mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + switch (val->intval) { + case SEC_BATTEY_VOLTAGE_AVERAGE: + val->intval = sprdfgu_read_vbat_vol(); + break; + case SEC_BATTEY_VOLTAGE_OCV: + val->intval = sprdfgu_read_vbat_ocv(); + break; + } + break; + + /* Current (mA) */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = sprdfgu_read_batcurrent(); + break; + + /* Average Current (mA) */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = sprdfgu_read_batcurrent(); + break; + + /* SOC (%) */ + case POWER_SUPPLY_PROP_CAPACITY: + if (init_flag) { +#if 0 + int cap = sprdfgu_read_capacity(); + if (100 == cap) { + union power_supply_propval value; + psy_do_property("battery", get, + POWER_SUPPLY_PROP_STATUS, + value); + if (POWER_SUPPLY_STATUS_CHARGING == + value.intval) { + pr_info + ("sec_hal_fg_get_property soc == 100, but is charging\n", + psp); + cap = 99; + } + } +#else + int cap = sprdbat_update_capacty(); +#endif + val->intval = cap * 10; + } else { + val->intval = 500; + } + break; + + /* Battery Temperature */ + case POWER_SUPPLY_PROP_TEMP: + + /* Target Temperature */ + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + default: + return false; + } + pr_info("sec_hal_fg_get_property-%d,val->intval = %d\n", psp, + val->intval); + return true; +} + +bool sec_hal_fg_set_property(fuelgauge_variable_t * fg_var, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + switch (psp) { + + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval == POWER_SUPPLY_TYPE_BATTERY) { + sprdfgu_adp_status_set(0); + } else { + sprdfgu_adp_status_set(1); + } + break; + /* Battery Temperature */ + case POWER_SUPPLY_PROP_TEMP: + + /* Target Temperature */ + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + break; + default: + return false; + } + return true; +} + diff --git a/drivers/battery/sprd_battery.h b/drivers/battery/sprd_battery.h new file mode 100644 index 00000000..9012f94d --- /dev/null +++ b/drivers/battery/sprd_battery.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _SPRD_BATTERY_H_ +#define _SPRD_BATTERY_H_ + +#include <linux/types.h> +#include <linux/hrtimer.h> +#include <linux/wakelock.h> +#include <linux/power_supply.h> +#include <linux/sprd_battery_common.h> + +#include <mach/adc.h> + +#if defined(CONFIG_SPRD_2713_POWER) || defined(CONFIG_SPRD_EXT_IC_POWER) +#include "../power/sprd_2713_charge.h" +#include "../power/sprd_2713_fgu.h" +#endif + +#define SPRDBAT_AUXADC_CAL_NO 0 +#define SPRDBAT_AUXADC_CAL_NV 1 +#define SPRDBAT_AUXADC_CAL_CHIP 2 + +#define SPRDBAT_CHG_END_NONE_BIT 0 +#define SPRDBAT_CHG_END_FULL_BIT (1 << 0) +#define SPRDBAT_CHG_END_OTP_OVERHEAT_BIT (1 << 1) +#define SPRDBAT_CHG_END_OTP_COLD_BIT (1 << 2) +#define SPRDBAT_CHG_END_TIMEOUT_BIT (1 << 3) +#define SPRDBAT_CHG_END_OVP_BIT (1 << 4) + +#define SPRDBAT_AVERAGE_COUNT 8 +#define SPRDBAT_PLUG_WAKELOCK_TIME_SEC 3 + +#define SPRDBAT_AUXADC_CAL_TYPE_NO 0 +#define SPRDBAT_AUXADC_CAL_TYPE_NV 1 +#define SPRDBAT_AUXADC_CAL_TYPE_EFUSE 2 + +enum sprd_adapter_type { + ADP_TYPE_UNKNOW = 0, //unknow adapter type + ADP_TYPE_CDP = 1, //Charging Downstream Port,USB&standard charger + ADP_TYPE_DCP = 2, //Dedicated Charging Port, standard charger + ADP_TYPE_SDP = 4, //Standard Downstream Port,USB and nonstandard charge +}; + +struct sprdbat_info { + uint32_t module_state; + uint32_t bat_health; + uint32_t chging_current; + int bat_current; + uint32_t chg_current_type; + uint32_t adp_type; + uint32_t usb_online; + uint32_t ac_online; + uint32_t vchg_vol; + uint32_t cccv_point; + unsigned long chg_start_time; + uint32_t chg_stop_flags; + uint32_t vbat_vol; + uint32_t vbat_ocv; + int cur_temp; + uint32_t capacity; + uint32_t soc; + uint32_t chg_this_timeout; +}; + +struct sprdbat_drivier_data { + struct sprd_battery_platform_data *pdata; + struct sprdbat_info bat_info; + struct mutex lock; + struct device *dev; + struct power_supply battery; + struct power_supply ac; + struct power_supply usb; + uint32_t gpio_charger_detect; + uint32_t gpio_chg_cv_state; + uint32_t gpio_vchg_ovi; + uint32_t irq_charger_detect; + uint32_t irq_chg_cv_state; + uint32_t irq_vchg_ovi; + struct wake_lock charger_plug_out_lock; + struct workqueue_struct *monitor_wqueue; + struct delayed_work cv_irq_work; + struct delayed_work battery_work; + struct delayed_work battery_sleep_work; + struct work_struct ovi_irq_work; + struct delayed_work *charge_work; + int (*start_charge) (void); + int (*stop_charge) (void); +#ifdef CONFIG_LEDS_TRIGGERS + struct led_classdev charging_led; +#endif + +}; + +struct sprdbat_auxadc_cal { + uint16_t p0_vol; //4.2V + uint16_t p0_adc; + uint16_t p1_vol; //3.6V + uint16_t p1_adc; + uint16_t cal_type; +}; + +#define sprdbat_read_vbat_vol sprdfgu_read_vbat_vol +#define sprdbat_read_temp sprdchg_read_temp +#define sprdbat_adp_plug_nodify sprdfgu_adp_status_set +#define sprdbat_read_temp_adc sprdchg_read_temp_adc + +int sprdbat_interpolate(int x, int n, struct sprdbat_table_data *tab); + +#ifdef SPRDBAT_TWO_CHARGE_CHANNEL +#define SPRDBAT_CHG_EVENT_EXT_OVI 1 +#define SPRDBAT_CHG_EVENT_EXT_OVI_RESTART 2 +void sprdchg_start_charge_ext(void); +void sprdchg_stop_charge_ext(void); +int sprdchg_is_chg_done_ext(void); +int sprdchg_charge_init_ext(struct platform_device *pdev); +void sprdchg_chg_monitor_cb_ext(void *data); +void sprdchg_open_ovi_fun_ext(void); +void sprdchg_close_ovi_fun_ext(void); +void sprdbat_charge_event_ext(uint32_t event); +#endif + +#endif /* _CHG_DRVAPI_H_ */ |
