summaryrefslogtreecommitdiff
path: root/drivers/battery
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/battery')
-rwxr-xr-xdrivers/battery/Kconfig412
-rw-r--r--drivers/battery/Makefile32
-rwxr-xr-xdrivers/battery/sec_adc.c180
-rwxr-xr-xdrivers/battery/sec_battery.c3955
-rwxr-xr-xdrivers/battery/sec_charger.c745
-rw-r--r--drivers/battery/sec_dt_init.c1154
-rwxr-xr-xdrivers/battery/sec_fuelgauge.c781
-rw-r--r--drivers/battery/sm5414_charger.c666
-rw-r--r--drivers/battery/sm5701_charger.c805
-rwxr-xr-xdrivers/battery/smb328_charger.c808
-rwxr-xr-xdrivers/battery/smb358_charger.c2022
-rwxr-xr-xdrivers/battery/sprd27x3_charger4samsung.c960
-rwxr-xr-xdrivers/battery/sprd27x3_fuelgauge4samsung.c357
-rw-r--r--drivers/battery/sprd_battery.h133
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, &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, &reg_data);
+
+ reg_data = 0xFC;
+ SM5414_i2c_write(client, SM5414_INTMASK2, &reg_data);
+
+ SM5414_i2c_read(client, SM5414_CHGCTRL1, &reg_data);
+ reg_data &= ~SM5414_CHGCTRL1_AUTOSTOP;
+ SM5414_i2c_write(client, SM5414_CHGCTRL1, &reg_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, &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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_data);
+
+ if (writable)
+ reg_data |= CMD_A_ALLOW_WRITE;
+ else
+ reg_data &= ~CMD_A_ALLOW_WRITE;
+
+ smb328_i2c_write(client, SMB328_COMMAND, &reg_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, &reg_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, &reg_data);
+ reg_data &= ~CFG_FLOAT_VOLTAGE_MASK;
+ reg_data |= float_data << CFG_FLOAT_VOLTAGE_SHIFT;
+
+ smb328_i2c_write(client, SMB328_FLOAT_VOLTAGE, &reg_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, &reg_data);
+ reg_data &= ~CFG_INPUT_CURRENT_MASK;
+ reg_data |= curr_data << CFG_INPUT_CURRENT_SHIFT;
+
+ smb328_i2c_write(client, SMB328_INPUT_CURRENTLIMIT, &reg_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, &reg_data);
+ reg_data &= ~CFG_TERMINATION_CURRENT_MASK;
+ reg_data |= term_data << CFG_TERMINATION_CURRENT_SHIFT;
+
+ smb328_i2c_write(client, SMB328_CHARGE_CURRENT, &reg_data);
+
+ /* set STAT assertion termination current */
+ smb328_i2c_read(client, SMB328_VARIOUS_FUNCTIONS, &reg_data);
+ reg_data &= ~CFG_STAT_ASSETION_TERM_MASK;
+ reg_data |= term_data << CFG_STAT_ASSETION_TERM_SHIFT;
+
+ smb328_i2c_write(client, SMB328_VARIOUS_FUNCTIONS, &reg_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, &reg_data);
+ reg_data &= ~CFG_CHARGE_CURRENT_MASK;
+ reg_data |= chg_data << CFG_CHARGE_CURRENT_SHIFT;
+
+ smb328_i2c_write(client, SMB328_CHARGE_CURRENT, &reg_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, &reg_data);
+
+ smb328_i2c_read(charger->client,
+ SMB328_INTERRUPT_SIGNAL_SELECTION, &reg_data);
+ reg_data |= 0x1;
+ smb328_i2c_write(charger->client,
+ SMB328_INTERRUPT_SIGNAL_SELECTION, &reg_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, &reg_data);
+ reg_data &= ~CFG_AICL_ENABLE;
+ reg_data &= ~CFG_AICL_VOLTAGE; /* AICL enable voltage 4.25V */
+ smb328_i2c_write(charger->client,
+ SMB328_INPUT_CURRENTLIMIT, &reg_data);
+
+ /* Function control A */
+ reg_data = 0xDA;
+ smb328_i2c_write(charger->client,
+ SMB328_FUNCTION_CONTROL_A, &reg_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, &reg_data);
+ reg_data &= ~0x0C;
+ reg_data |= charge_mode << 2;
+ smb328_i2c_write(charger->client, SMB328_COMMAND, &reg_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, &reg_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, &reg_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, &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, &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, &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, &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_ */