diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/power | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2 kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip |
samsung update 1
Diffstat (limited to 'drivers/power')
23 files changed, 18188 insertions, 25 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index e57b50b3856..9474894e36b 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -177,7 +177,7 @@ config BATTERY_S3C_ADC tristate "Battery driver for Samsung ADC based monitoring" depends on S3C_ADC help - Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery + Say Y to enable support for batteries with samsung chip. config CHARGER_PCF50633 tristate "NXP PCF50633 MBC" @@ -235,4 +235,123 @@ config CHARGER_GPIO This driver can be build as a module. If so, the module will be called gpio-charger. +config BATTERY_SAMSUNG + tristate "Fake battery driver for samsung smdk boards" + depends on PLAT_SAMSUNG + help + Say Y to enable support for batteries with samsung chip. + +config BATTERY_SAMSUNG_S2PLUS + tristate "Fake battery driver for samsung s2plus boards" + depends on PLAT_SAMSUNG + help + Say Y to enable support for batteries with s2plus board. + +config CHARGER_MAX8997 + tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" + depends on MFD_MAX8997 && REGULATOR_MAX8997 + help + Say Y to enable support for the battery charger control sysfs and + platform data of MAX8997/LP3974 PMICs. + +config CHARGER_MAX8997_U1 + tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver for Samsung Galaxy S2 ICS upgrade" + depends on MFD_MAX8997 && REGULATOR_MAX8997 + help + Say Y to enable support for the battery charger control sysfs and + platform data of MAX8997/LP3974 PMICs. + +config CHARGER_MAX8997_PX + tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver for Samsung Galaxy Tab ICS upgrade" + depends on MFD_MAX8997 && REGULATOR_MAX8997 + +config BATTERY_SEC_U1 + tristate "Battery driver for Samsung Galaxy S2" + depends on PLAT_SAMSUNG + help + Say Y to enable support for batteries with samsung chip. + +config BATTERY_SEC_PX + tristate "Battery driver for Samsung Galaxy Tab" + depends on PLAT_SAMSUNG + +config CHARGER_MAX8922_U1 + tristate "MAX8922 battery charger support for Samsung Galaxy S2 ICS upgrade" + depends on PLAT_SAMSUNG + help + Say Y here to enable support for the battery charger in the Maxim + MAX8922 Charger IC. + +config CHARGER_MAX8922_S2PLUS + tristate "MAX8922 battery charger support for Samsung Galaxy S2 ICS upgrade" + depends on PLAT_SAMSUNG + default n + help + Say Y here to enable support for the battery charger in the Maxim + MAX8922 Charger IC. + +config BATTERY_MAX17042_FUELGAUGE_U1 + tristate "Maxim MAX17042/8997/8966 Fuel Gauge for Samsung Galaxy S2 ICS upgrade" + depends on I2C + help + MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries + in handheld and portable equipment. The MAX17042 is configured + to operate with a single lithium cell. MAX8997 and MAX8966 are + multi-function devices that include fuel gauages that are compatible + with MAX17042. + +config BATTERY_MAX17042_FUELGAUGE_PX + tristate "Maxim MAX17042/8997/8966 Fuel Gauge for Samsung Galaxy Tab ICS upgrade" + depends on I2C + help + MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries + in handheld and portable equipment. The MAX17042 is configured + to operate with a single lithium cell. MAX8997 and MAX8966 are + multi-function devices that include fuel gauages that are compatible + with MAX17042. + +config SMB136_CHARGER + tristate "SMB136 battery charger support" + depends on PLAT_SAMSUNG + help + Say Y here to enable support for the battery charger in the SUMMIT + SMB136 Charger IC. + +config SMB136_CHARGER_Q1 + tristate "SMB136 battery charger support Q1 HWREV01" + depends on PLAT_SAMSUNG + help + Say Y here to enable support for the battery charger in the SUMMIT + SMB136 Charger IC. + Support Q1 HWREV01. + +config SMB328_CHARGER + tristate "SMB328 battery charger support" + depends on PLAT_SAMSUNG + help + Say Y here to enable support for the battery charger in the SUMMIT + SMB328 Charger IC. + +config SMB347_CHARGER + tristate "SMB347 battery charger support" + depends on PLAT_SAMSUNG + help + Say Y here to enable support for the battery charger in the SUMMIT + SMB328 Charger IC. + +config CHARGER_MANAGER + tristate "Battery charger manager for multiple chargers" + help + Say Y to enable charger-manager support, which allows multiple + chargers attached to a battery and multiple batteries attached to a + system. The charger-manager also can monitor charging status in + runtime and in suspend-to-RAM by waking up the system periodically + with help of pm-loop support. + +config SAMSUNG_LPM_MODE + bool "Off charging mode support in sec battery driver" + depends on BATTERY_SEC_PX + help + Say Y to include support for samsungs off charging support + endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 009a90fa8ac..9be94fd25c9 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -36,3 +36,25 @@ obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o +obj-$(CONFIG_BATTERY_SAMSUNG) += samsung_fake_battery.o +obj-$(CONFIG_BATTERY_SAMSUNG_S2PLUS) += samsung_fake_battery.o +obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o +obj-$(CONFIG_CHARGER_MAX8997_U1) += max8997_charger_u1.o +obj-$(CONFIG_CHARGER_MAX8922_U1) += max8922_charger_u1.o +obj-$(CONFIG_CHARGER_MAX8922_S2PLUS) += max8922_charger_s2plus.o +obj-$(CONFIG_CHARGER_MAX8997_PX) += max8997_charger_px.o +ifeq ($(CONFIG_TARGET_LOCALE_KOR),y) +obj-$(CONFIG_BATTERY_SEC_U1) += sec_battery_u1_kor.o +obj-$(CONFIG_BATTERY_MAX17042_FUELGAUGE_U1) += max17042_fuelgauge_u1_kor.o +else +obj-$(CONFIG_BATTERY_SEC_U1) += sec_battery_u1.o +obj-$(CONFIG_BATTERY_MAX17042_FUELGAUGE_U1) += max17042_fuelgauge_u1.o +endif +obj-$(CONFIG_BATTERY_SEC_PX) += sec_battery_px.o +obj-$(CONFIG_BATTERY_MAX17042_FUELGAUGE_PX) += max17042_fuelgauge_px.o +obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o +obj-$(CONFIG_SMB136_CHARGER) += smb136_charger.o +obj-$(CONFIG_SMB136_CHARGER_Q1) += smb136_charger_q1.o +obj-$(CONFIG_SMB328_CHARGER) += smb328_charger.o +obj-$(CONFIG_SMB347_CHARGER) += smb347_charger.o + diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c new file mode 100644 index 00000000000..5dfbf603a8d --- /dev/null +++ b/drivers/power/charger-manager.c @@ -0,0 +1,1662 @@ +/* linux/drivers/power/charger-manager.c + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * Samsung SoC based charger control. This driver enables to control + * charger during suspend-to-mem. + * + * 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/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/rtc.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/power/charger-manager.h> +#include <linux/regulator/consumer.h> +#include <linux/jack.h> + +static const char * const default_event_names[] = { + [CM_EVENT_UNDESCRIBED] = "Undescribed", + [CM_EVENT_BATT_FULL] = "Battery Full", + [CM_EVENT_BATT_IN] = "Battery Inserted", + [CM_EVENT_BATT_OUT] = "Battery Pulled Out", + [CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach", + [CM_EVENT_CHG_START_STOP] = "Charging Start/Stop", + [CM_EVENT_OTHERS] = "Other battery events" +}; + +/* + * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for + * delayed works so that we can run delayed works with CM_JIFFIES_SMALL + * without any delays. + */ +#define CM_JIFFIES_SMALL (2) + +/* If y is valid (> 0) and smaller than x, do x = y */ +#define CM_MIN_VALID(x, y) x = (((y > 0) && ((x) > (y))) ? (y) : (x)) + +/* + * Regard CM_RTC_SMALL (sec) is small enough to ignore error in invoking + * rtc alarm. It should be 2 or larger + */ +#define CM_RTC_SMALL (2) + +#define UEVENT_BUF_SIZE 32 + +LIST_HEAD(cm_list); +static DEFINE_MUTEX(cm_list_mtx); + +static int charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); +static void fullbatt_handler(struct charger_manager *cm); + +/* About in-suspend (suspend-again) monitoring */ +static struct rtc_device *rtc_dev; +static struct rtc_wkalrm rtc_wkalarm_save; /* Backup RTC alarm */ +static unsigned long rtc_wkalarm_save_; /* 0 if not available */ +static bool cm_suspended; +static bool cm_rtc_set; +static unsigned long cm_suspend_duration_ms; + +/* About normal (not suspended) monitoring */ +static unsigned long polling_jiffy = ULONG_MAX; /* ULONG_MAX: no polling */ +static unsigned long next_polling; /* Next appointed polling time */ +static struct workqueue_struct *cm_wq; /* init at driver add */ +static struct delayed_work cm_monitor_work; /* init at driver add */ + +/* Global charger-manager description */ +static struct charger_global_desc *g_desc; /* init with setup_charger_manager */ + +static int is_full(struct charger_manager *cm) +{ + union power_supply_propval val; + int ret = 0; + + ret = charger_get_property(&cm->charger_psy, + POWER_SUPPLY_PROP_STATUS, &val); + + if (!ret && val.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) { + ret = charger_get_property(&cm->charger_psy, + POWER_SUPPLY_PROP_CHARGE_FULL, &val); + if (!ret && val.intval) { + printk(KERN_DEBUG"[CM %s:%d fully Charged.\n", + __func__, __LINE__); + fullbatt_handler(cm); + } + } + + return 0; +} + +/** + * is_batt_present - See if the battery presents in place. + * @cm: the Charger Manager representing the battery. + */ +static bool is_batt_present(struct charger_manager *cm) +{ + union power_supply_propval val; + bool present = false; + int i, ret; + + switch (cm->desc->battery_present) { + case CM_ASSUME_ALWAYS_TRUE: + present = true; + break; + case CM_ASSUME_ALWAYS_FALSE: + present = false; + break; + case CM_FUEL_GAUGE: + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_PRESENT, &val); + if (ret == 0 && val.intval) + present = true; + break; + case CM_CHARGER_STAT: + val.intval = 0; + /* If one of them thinks it exists, it exists. */ + for (i = 0; cm->charger_stat[i]; i++) { + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_PRESENT, &val); + if (ret == 0 && val.intval) { + present = true; + break; + } + } + break; + } + + return present; +} + +/** + * is_ext_pwr_online - See if an external power source is attached to charge + * @cm: the Charger Manager representing the battery. + * + * Returns true if at least one of the chargers of the battery has an external + * power source attached to charge the battery regardless of whether it is + * actually charging or not. + */ +static bool is_ext_pwr_online(struct charger_manager *cm) +{ + union power_supply_propval val; + bool online = false; + int i, ret; + + /* If at least one of them has one, it's yes. */ + for (i = 0; cm->charger_stat[i]; i++) { + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_ONLINE, &val); + if (ret == 0 && val.intval) { + online = true; + break; + } + } + + return online; +} + +/** + * get_batt_uV - Get the voltage level of the battery + * @cm: the Charger Manager representing the battery. + * @uV: the voltage level returned. + * + * Returns 0 if there is no error. + * Returns a negative value on error. + */ +static int get_batt_uV(struct charger_manager *cm, int *uV) +{ + union power_supply_propval val; + int ret; + + if (cm->fuel_gauge) + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &val); + else + return -ENODEV; + + if (ret) + return ret; + + *uV = (val.intval > cm->desc->fullbatt_uV) ? + cm->desc->fullbatt_uV : val.intval; + return 0; +} + +/** + * is_charging - Returns true if the battery is being charged. + * @cm: the Charger Manager representing the battery. + */ +static bool is_charging(struct charger_manager *cm) +{ + int i, ret; + bool charging = false; + union power_supply_propval val; + static bool present_warned; + bool warned = false; + + /* If there is no battery, it cannot be charged */ + if (!is_batt_present(cm)) + return false; + + /* If at least one of the charger is charging, return yes */ + for (i = 0; cm->charger_stat[i]; i++) { + /* 1. The charger sholuld not be DISABLED */ + if (cm->emergency_stop) + continue; + if (cm->user_prohibit) + continue; + if (!cm->charger_enabled) + continue; + + /* 2. The charger should be online (ext-power) */ + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_ONLINE, &val); + if (ret) { + dev_warn(cm->dev, "Cannot read ONLINE value from %s.\n", + cm->desc->psy_charger_stat[i]); + continue; + } + if (val.intval == 0) + continue; + + /* 3. The charger should have the battery connected */ + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_PRESENT, &val); + /* PRESENT is optional. assume it's "yes" */ + if (ret && !present_warned) { + dev_warn(cm->dev, "Cannot read PRESENT value from %s" + ".\n", cm->desc->psy_charger_stat[i]); + warned = true; + } else if (!ret && val.intval == 0) + continue; + + /* + * 4. The charger should not be FULL, DISCHARGING, + * or NOT_CHARGING. + */ + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_STATUS, &val); + if (ret) { + dev_warn(cm->dev, "Cannot read STATUS value from %s.\n", + cm->desc->psy_charger_stat[i]); + continue; + } + if (val.intval == POWER_SUPPLY_STATUS_FULL || + val.intval == POWER_SUPPLY_STATUS_DISCHARGING || + val.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) + continue; + + /* Then, this is charging. */ + charging = true; + break; + } + + if (warned) + present_warned = true; + + return charging; +} + +/** + * try_charger_enable - Enable/Disable chargers altogether + * @cm: the Charger Manager representing the battery. + * @enable: true: enable / false: force_disable + * + * Note that Charger Manager keeps the charger enabled regardless whether + * the charger is charging or not (because battery is full or no external + * power source exists) except when CM needs to disable chargers forcibly + * bacause of emergency causes; when the battery is overheated of too cold. + */ +static int try_charger_enable(struct charger_manager *cm, bool enable) +{ + int i; + int err = 0; + struct charger_desc *desc = cm->desc; + + printk(KERN_INFO"[CM] %s:%d status:%d", __func__, __LINE__, enable); + + /* Ignore if it's redundent command */ + if (enable && cm->charger_enabled) + return 0; + if (!enable && !cm->charger_enabled) + return 0; + + if (enable) { + if (cm->emergency_stop || cm->user_prohibit) + return -EAGAIN; /* Do it again later */ + for (i = 0 ; i < desc->num_charger_regulators ; i++) + regulator_enable(desc->charger_regulators[i].consumer); + } else { + for (i = 0 ; i < desc->num_charger_regulators ; i++) { + regulator_force_disable( + desc->charger_regulators[i].consumer); + } + } + + if (!err) + cm->charger_enabled = enable; + + return err; +} + +/** + * try_charger_restart - Restart charging. + * @cm: the Charger Manager representing the battery. + * + * Restart charging by turning off and on the charger. + */ +static int try_charger_restart(struct charger_manager *cm) +{ + int err; + + if (cm->emergency_stop) + return -EAGAIN; + if (cm->user_prohibit) + return -EAGAIN; + + err = try_charger_enable(cm, false); + if (err) + return err; + + return try_charger_enable(cm, true); +} + +/** + * uevent_notify - Let users know something has changed. + * @cm: the Charger Manager representing the battery. + * @event: the event string. + * + * If @event is null, it implies that uevent_notify is called + * by resume function. When called in the resume function, cm_suspended + * should be already reset to false in order to let uevent_notify + * notify the recent event during the suspend to users. While + * suspended, uevent_notify does not notify users, but tracks + * events so that uevent_notify can notify users later after resumed. + */ +static void uevent_notify(struct charger_manager *cm, const char *event) +{ + static char env_str[UEVENT_BUF_SIZE + 1] = ""; + static char env_str_save[UEVENT_BUF_SIZE + 1] = ""; + + if (cm_suspended) { + /* Nothing in suspended-event buffer */ + if (env_str_save[0] == 0) { + if (!strncmp(env_str, event, UEVENT_BUF_SIZE)) + return; /* status not changed */ + strncpy(env_str_save, event, UEVENT_BUF_SIZE); + return; + } + + if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE)) + return; /* Duplicated. */ + else + strncpy(env_str_save, event, UEVENT_BUF_SIZE); + + return; + } + + /* It's called at RESUME */ + if (event == NULL) { + /* No messages pending */ + if (!env_str_save[0]) + return; + + strncpy(env_str, env_str_save, UEVENT_BUF_SIZE); + kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); + env_str_save[0] = 0; + + return; + } + + /* The system is running and it's not in resume */ + + /* status not changed */ + if (!strncmp(env_str, event, UEVENT_BUF_SIZE)) + return; + + /* save the status and notify the update */ + strncpy(env_str, event, UEVENT_BUF_SIZE); + kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); + + dev_info(cm->dev, event); +} + +/** + * fullbatt_vchk - Check voltage drop some times after "FULL" event. + * @work: the work_struct appointing the function + * + * If a user has designated "fullbatt_vchkdrop_ms/uV" values with + * charger_desc, Charger Manager checks voltage drop after the battery + * "FULL" event. It checks whether the voltage has dropped more than + * fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms. + */ +static void fullbatt_vchk(struct work_struct *work) +{ + struct delayed_work *dwork = + container_of(work, struct delayed_work, work); + struct charger_manager *cm = container_of(dwork, + struct charger_manager, fullbatt_vchk_work); + struct charger_desc *desc = cm->desc; + int batt_uV, err; + + /* remove the appointment for fullbatt_vchk */ + cm->fullbatt_vchk_jiffies_at = 0; + + if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) + return; + + err = get_batt_uV(cm, &batt_uV); + if (err) { + dev_err(cm->dev, "%s: get_batt_uV error(%d).\n", __func__, err); + return; + } + + dev_dbg(cm->dev, "VBATT dropped %uuV after full-batt.\n", + cm->fullbatt_vchk_uV - batt_uV); + + if ((cm->fullbatt_vchk_uV - batt_uV) > desc->fullbatt_vchkdrop_uV) { + try_charger_restart(cm); + uevent_notify(cm, "Recharge"); + } +} + +/** + * _cm_monitor - Monitor the temperature and return true for exceptions. + * @cm: the Charger Manager representing the battery. + * + * Returns true if there is an event to notify for the battery. + * (True if the status of "emergency_stop" changes) + */ +static bool _cm_monitor(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + int temp = desc->is_temperature_error(&cm->last_temp_mC); + + printk(KERN_DEBUG"[CM] %s:%d temp:%d\n", __func__, __LINE__, temp); + + if (!is_batt_present(cm)) { + dev_dbg(cm->dev, "Battery is not present.\nSystem shutdown.\n"); + if (pm_power_off) + pm_power_off(); + } + + is_full(cm); + + dev_dbg(cm->dev, "monitoring (%2.2d.%3.3dC)\n", + cm->last_temp_mC / 1000, cm->last_temp_mC % 1000); + + + /* It has been stopped already */ + if (temp && cm->emergency_stop) + return false; + + /* It has been charging already */ + if (!temp && !cm->emergency_stop) + return false; + + if (temp) { + cm->emergency_stop = temp; + try_charger_enable(cm, false); + if (temp > 0) + uevent_notify(cm, "OVERHEAD"); + else + uevent_notify(cm, "COLD"); + } else { + cm->emergency_stop = 0; + if (!try_charger_enable(cm, true)) + uevent_notify(cm, "CHARGING"); + } + + return true; +} + +/** + * cm_monitor - Monitor every battery. + * + * Returns true if there is an event to notify from any of the batteries. + * (True if the status of "emergency_stop" changes) + */ +static bool cm_monitor(void) +{ + bool stop = false; + struct charger_manager *cm; + + mutex_lock(&cm_list_mtx); + + list_for_each_entry(cm, &cm_list, entry) + stop |= _cm_monitor(cm); + + mutex_unlock(&cm_list_mtx); + + return stop; +} + +/** + * is_polling_required - Return true if need to continue polling for this CM. + * @cm: the Charger Manager representing the battery. + */ +static bool is_polling_required(struct charger_manager *cm) +{ + switch (cm->desc->polling_mode) { + case CM_POLL_DISABLE: + return false; + case CM_POLL_ALWAYS: + return true; + case CM_POLL_EXTERNAL_POWER_ONLY: + return is_ext_pwr_online(cm); + case CM_POLL_CHARGING_ONLY: + return is_charging(cm); + default: + dev_warn(cm->dev, "Incorrect polling_mode (%d)\n", + cm->desc->polling_mode); + } + + return false; +} + +/** + * _setup_polling - Setup the next instance of polling. + * @work: work_struct of the function _setup_polling. + */ +static void _setup_polling(struct work_struct *work) +{ + unsigned long min = ULONG_MAX; + struct charger_manager *cm; + bool keep_polling = false; + unsigned long _next_polling; + + mutex_lock(&cm_list_mtx); + + list_for_each_entry(cm, &cm_list, entry) { + if (is_polling_required(cm) && cm->desc->polling_interval_ms) { + keep_polling = true; + + if (min > cm->desc->polling_interval_ms) + min = cm->desc->polling_interval_ms; + } + } + + polling_jiffy = msecs_to_jiffies(min); + if (polling_jiffy <= CM_JIFFIES_SMALL) + polling_jiffy = CM_JIFFIES_SMALL + 1; + + if (!keep_polling) + polling_jiffy = ULONG_MAX; + if (polling_jiffy == ULONG_MAX) + goto out; + + WARN(cm_wq == NULL, "charger-manager: workqueue not initialized" + ". try it later. %s\n", __func__); + + _next_polling = jiffies + polling_jiffy; + + if (!delayed_work_pending(&cm_monitor_work) || + (delayed_work_pending(&cm_monitor_work) && + time_after(next_polling, _next_polling))) { + cancel_delayed_work(&cm_monitor_work); + next_polling = jiffies + polling_jiffy; + queue_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy); + } + +out: + mutex_unlock(&cm_list_mtx); +} +static DECLARE_WORK(setup_polling, _setup_polling); + +/** + * cm_monitor_poller - The Monitor / Poller. + * @work: work_struct of the function cm_monitor_poller + * + * During non-suspended state, cm_monitor_poller is used to poll and monitor + * the batteries. + */ +static void cm_monitor_poller(struct work_struct *work) +{ + cm_monitor(); + schedule_work(&setup_polling); +} + +/** + * fullbatt_handler - Event handler for CM_EVENT_BATT_FULL + * @cm: the Charger Manager representing the battery. + */ +static void fullbatt_handler(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + + if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) + goto out; + + if (cm_suspended) + cm->cancel_suspend = true; + + cancel_delayed_work(&cm->fullbatt_vchk_work); + queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work, + msecs_to_jiffies(desc->fullbatt_vchkdrop_ms)); + cm->fullbatt_vchk_jiffies_at = jiffies + msecs_to_jiffies( + desc->fullbatt_vchkdrop_ms); + + if (cm->fullbatt_vchk_jiffies_at == 0) + cm->fullbatt_vchk_jiffies_at = 1; + +out: + dev_info(cm->dev, "IRQHANDLE: Battery Fully Charged.\n"); + uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]); +} + +/** + * battout_handler - Event handler for CM_EVENT_BATT_OUT + * @cm: the Charger Manager representing the battery. + */ +static void battout_handler(struct charger_manager *cm) +{ + if (cm_suspended) + cm->cancel_suspend = true; + + if (!is_batt_present(cm)) { + dev_emerg(cm->dev, "Battery Pulled Out!\n"); + uevent_notify(cm, default_event_names[CM_EVENT_BATT_OUT]); + } else { + uevent_notify(cm, "Battery Reinserted?"); + } +} + +/** + * misc_event_handler - Handler for other evnets + * @cm: the Charger Manager representing the battery. + * @type: the Charger Manager representing the battery. + */ +static void misc_event_handler(struct charger_manager *cm, + enum cm_event_types type) +{ + if (cm_suspended) + cm->cancel_suspend = true; + + if (!delayed_work_pending(&cm_monitor_work) && + is_polling_required(cm) && cm->desc->polling_interval_ms) + schedule_work(&setup_polling); + uevent_notify(cm, default_event_names[type]); +} + +/** + * cm_setup_timer - For in-suspend monitoring setup wakeup alarm for suspend_again. + * + * Returns true if the alarm is set for Charger Manager to use. + * Returns false if + * cm_setup_timer fails to set an alarm, + * cm_setup_timer does not need to set an alarm for Charger Manager, + * or an alarm previously configured is to be used. + */ +static bool cm_setup_timer(void) +{ + struct charger_manager *cm; + unsigned int wakeup_ms = UINT_MAX; + bool ret = false; + + mutex_lock(&cm_list_mtx); + + list_for_each_entry(cm, &cm_list, entry) { + unsigned int fbchk_ms = 0; + + /* fullbatt_vchk is required. setup timer for that */ + if (cm->fullbatt_vchk_jiffies_at) { + fbchk_ms = jiffies_to_msecs(cm->fullbatt_vchk_jiffies_at + - jiffies); + if (cm->fullbatt_vchk_jiffies_at <= jiffies || + msecs_to_jiffies(fbchk_ms) < CM_JIFFIES_SMALL) { + fullbatt_vchk(&cm->fullbatt_vchk_work.work); + fbchk_ms = 0; + } + } + CM_MIN_VALID(wakeup_ms, fbchk_ms); + + /* Skip if polling is not required for this CM */ + switch (cm->desc->polling_mode) { + case CM_POLL_DISABLE: + continue; + case CM_POLL_ALWAYS: + break; + case CM_POLL_EXTERNAL_POWER_ONLY: + if (!is_ext_pwr_online(cm)) + continue; + break; + case CM_POLL_CHARGING_ONLY: + if (!is_charging(cm) && !cm->emergency_stop) + continue; + break; + default: + dev_warn(cm->dev, "Incorrect polling_mode (%d)\n", + cm->desc->polling_mode); + break; + } + + if (cm->desc->polling_interval_ms == 0) + continue; + + CM_MIN_VALID(wakeup_ms, cm->desc->polling_interval_ms); + } + + /* TODO: Reviewing Here */ + + mutex_unlock(&cm_list_mtx); + + if (wakeup_ms < UINT_MAX && wakeup_ms > 0) { + pr_info("Charger Manager wakeup timer: %u ms.\n", wakeup_ms); + if (rtc_dev) { + struct rtc_wkalrm tmp; + unsigned long time, now; + unsigned long add = DIV_ROUND_UP(wakeup_ms, 1000); + + /* + * Set alarm with the polling interval (wakeup_ms) + * except when rtc_wkalarm_save comes first. + * However, the alarm time should be NOW + + * CM_RTC_SMALL or later. + */ + tmp.enabled = 1; + rtc_read_time(rtc_dev, &tmp.time); + rtc_tm_to_time(&tmp.time, &now); + if (add < CM_RTC_SMALL) + add = CM_RTC_SMALL; + time = now + add; + + ret = true; + + if (rtc_wkalarm_save.enabled && rtc_wkalarm_save_ && + rtc_wkalarm_save_ < time) { + if (rtc_wkalarm_save_ < now + CM_RTC_SMALL) + time = now + CM_RTC_SMALL; + else + time = rtc_wkalarm_save_; + + /* The timer is not appointed by CM */ + ret = false; + } + + pr_info("Waking up after %lu secs.\n", + time - now); + + rtc_time_to_tm(time, &tmp.time); + rtc_set_alarm(rtc_dev, &tmp); + cm_suspend_duration_ms += wakeup_ms; + return ret; + } + } + + if (rtc_dev) + rtc_set_alarm(rtc_dev, &rtc_wkalarm_save); + return false; +} + +static int charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct charger_manager *cm = container_of(psy, + struct charger_manager, charger_psy); + struct charger_desc *desc = cm->desc; + int i, ret = 0, uV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (is_charging(cm)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (is_ext_pwr_online(cm)) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (cm->emergency_stop > 0) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (cm->emergency_stop < 0) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + if (is_batt_present(cm)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = get_batt_uV(cm, &i); + val->intval = i; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CURRENT_NOW, val); + break; + case POWER_SUPPLY_PROP_TEMP: + /* in thenth of centigrade */ + if (cm->last_temp_mC == INT_MIN) + desc->is_temperature_error(&cm->last_temp_mC); + val->intval = cm->last_temp_mC / 100; + if (!desc->measure_battery_temp) + ret = -ENODEV; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + /* in thenth of centigrade */ + if (cm->last_temp_mC == INT_MIN) + desc->is_temperature_error(&cm->last_temp_mC); + val->intval = cm->last_temp_mC / 100; + if (!desc->measure_ambient_temp) + ret = -ENODEV; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (!cm->fuel_gauge) { + ret = -ENODEV; + break; + } + + if (!is_batt_present(cm)) { + /* There is no battery. Assume 100% */ + val->intval = 100; + break; + } + + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CAPACITY, val); + if (ret) + break; + + if (val->intval > 100) { + /* More than 100%? */ + val->intval = 100; + break; + } + if (val->intval < 0) { + /* Lower than 0%? */ + val->intval = 0; + } + + /* Do not adjust SOC when charging: voltage is overrated */ + if (is_charging(cm)) + break; + + /* + * If the capacity value is inconsistent, calibrate it base on + * the battery voltage values and the thresholds given as desc + */ + ret = get_batt_uV(cm, &uV); + if (ret) { + /* Voltage information not available. No calibration */ + ret = 0; + break; + } + + if (desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV && + !is_charging(cm)) { + val->intval = 100; + break; + } + + printk(KERN_DEBUG"[CM] %s:%d capacity:%d\n", + __func__, __LINE__, val->intval); + + break; + case POWER_SUPPLY_PROP_ONLINE: + if (is_ext_pwr_online(cm)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (is_ext_pwr_online(cm)) { + /* Not full if it's charging. */ + if (is_charging(cm)) { + val->intval = 0; + break; + } + + } else { + val->intval = 0; + break; + } + + /* FIXME: use STATUS information */ + + /* Full if it's over the fullbatt voltage */ + ret = get_batt_uV(cm, &uV); + if (!ret && desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV && + !is_charging(cm)) { + val->intval = 1; + break; + } + + /* Full if the cap is 100 */ + if (cm->fuel_gauge) { + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CAPACITY, val); + if (!ret && + (val->intval >= (100 - desc->soc_margin)) && + !is_charging(cm)) { + val->intval = 1; + break; + } + } + + val->intval = 0; + ret = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (is_charging(cm)) { + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_NOW, + val); + if (ret) { + val->intval = 1; + ret = 0; + } else { + /* If CHARGE_NOW is supplied, use it */ + val->intval = (val->intval > 0) ? + val->intval : 1; + } + } else { + val->intval = 0; + } + break; + default: + return -EINVAL; + } + return ret; +} + +#define NUM_CHARGER_PSY_OPTIONAL (4) +static enum power_supply_property default_charger_props[] = { + /* Guaranteed to provide */ + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + /* + * Optional properties are: + * POWER_SUPPLY_PROP_CHARGE_NOW, + * POWER_SUPPLY_PROP_CURRENT_NOW, + * POWER_SUPPLY_PROP_TEMP, and + * POWER_SUPPLY_PROP_TEMP_AMBIENT, + */ +}; + +static struct power_supply psy_default = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, /* Need to reconsider */ + /* .supplied_to = {"battery"}, */ + /* .num_supplicants = 1, */ + .properties = default_charger_props, + .num_properties = ARRAY_SIZE(default_charger_props), + .get_property = charger_get_property, +}; + +static bool _cm_fbchk_in_suspend(struct charger_manager *cm) +{ + unsigned long jiffy_now = jiffies; + + if (!cm->fullbatt_vchk_jiffies_at) + return false; + + if (g_desc && g_desc->assume_timer_stops_in_suspend) + jiffy_now += msecs_to_jiffies(cm_suspend_duration_ms); + + /* Execute now if it's going to be executed not too long after */ + jiffy_now += CM_JIFFIES_SMALL; + + if (time_after_eq(jiffy_now, cm->fullbatt_vchk_jiffies_at)) { + fullbatt_vchk(&cm->fullbatt_vchk_work.work); + return true; + } + + return false; +} + +bool cm_suspend_again(void) +{ + struct charger_manager *cm; + bool ret = false; + + if (!g_desc) + return false; + if (!g_desc->is_rtc_only_wakeup_reason) + return false; + if (!g_desc->is_rtc_only_wakeup_reason()) + return false; + if (!cm_rtc_set) + return false; + if (cm_wq == NULL) /* CM not initialized */ + return false; + + if (cm_monitor()) + goto out; + + ret = true; + mutex_lock(&cm_list_mtx); + list_for_each_entry(cm, &cm_list, entry) { + _cm_fbchk_in_suspend(cm); + + if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) || + cm->status_save_batt != is_batt_present(cm)) + ret = false; + } + mutex_unlock(&cm_list_mtx); + + cm_rtc_set = cm_setup_timer(); +out: + /* It's about the time when the non-CM appointed timer goes off */ + if (rtc_wkalarm_save.enabled) { + unsigned long now; + struct rtc_time tmp; + + rtc_read_time(rtc_dev, &tmp); + rtc_tm_to_time(&tmp, &now); + + if (rtc_wkalarm_save_ && + now + CM_RTC_SMALL >= rtc_wkalarm_save_) + return false; + /* TODO: Test rtc aie handlers' reaction to this */ + } + pr_emerg("%s:%d\n", __func__, __LINE__); + return ret; +} +EXPORT_SYMBOL_GPL(cm_suspend_again); + +int setup_charger_manager(struct charger_global_desc *gd) +{ + if (!gd) + return -EINVAL; + + if (rtc_dev) + rtc_class_close(rtc_dev); + rtc_dev = NULL; + g_desc = NULL; + + if (!gd->is_rtc_only_wakeup_reason) { + pr_err("The callback is_wktimer_only_wkreason is not given.\n"); + return -EINVAL; + } + + if (gd->rtc) { + rtc_dev = rtc_class_open(gd->rtc); + if (IS_ERR_OR_NULL(rtc_dev)) { + rtc_dev = NULL; + /* Retry at probe. RTC may be not registered yet */ + } + } else { + pr_warn("No wktimer is given for charger manager." + "In-suspend monitoring won't work.\n"); + } + + g_desc = gd; + return 0; +} +EXPORT_SYMBOL_GPL(setup_charger_manager); + +bool is_charger_manager_active(void) +{ + /* Should have called setup_charger_manager */ + if (!g_desc) + return false; + + /* Should have at least one instance of charger_manager */ + if (list_empty(&cm_list)) + return false; + + return true; +} +EXPORT_SYMBOL_GPL(is_charger_manager_active); + +#ifdef CONFIG_EXTCON +/* + * This function enable or disable charger for charging according to + * cable state when charger cable is attached or detached. So, each + * cable has requested different current limit to protect over-current + * issue and then it set current limit of charger(regulator) according to + * a kind of charger cable before enabling charger. + */ +static void charger_extcon_work(struct work_struct *work) +{ + struct charger_cable *cable = + container_of(work, struct charger_cable, wq); + int ret; + + if (cable->attached && cable->min_uA != 0 && cable->max_uA != 0) { + ret = regulator_set_current_limit(cable->charger->consumer, + cable->min_uA, cable->max_uA); + if (ret < 0) { + pr_err("Cannot set current limit of %s (%s)\n", + cable->charger->regulator_name, cable->name); + return; + } + + pr_info("Set current limit of %s : %duA ~ %duA\n", + cable->charger->regulator_name, + cable->min_uA, cable->max_uA); + } + + try_charger_enable(cable->cm, cable->attached); + +#ifdef CONFIG_JACK_MON + /* + * FIXME: Extcon framework will be used instead of jack handler( + * CONFIG_JACK_MON). But, SLP Platform have still used jack handler + * for external connector (e.g., TA, USB and so on). + * The jack handler will be removed after replacing from jack handler + * to Extcon framework. + */ + jack_event_handler("charger", cable->attached); +#endif +} + +/* This function executes workqueue when charger cable is attached/detached. */ +static int charger_extcon_notifier(struct notifier_block *self, + unsigned long event, void *ptr) +{ + struct charger_cable *cable = + container_of(self, struct charger_cable, nb); + + cable->attached = event; + schedule_work(&cable->wq); + + return NOTIFY_DONE; +} + +/* This function initialize charger cable with Extcon framework */ +static int charger_extcon_init(struct charger_manager *cm, + struct charger_cable *cable) +{ + int ret = 0; + + /* + * Charger manager use Extcon framework to identify + * the charger cable among various external connector + * cable (e.g., TA, USB, Dock and so on). + */ + INIT_WORK(&cable->wq, charger_extcon_work); + cable->nb.notifier_call = charger_extcon_notifier; + ret = extcon_register_interest(&cable->extcon_dev, + cable->extcon_name, cable->name, &cable->nb); + if (ret < 0) { + pr_info("Cannot register extcon_dev for %s(cable: %s).\n", + cable->extcon_name, + cable->name); + ret = -EINVAL; + } + + return ret; +} + +#endif /* CONFIG_EXTCON */ + +static int charger_manager_probe(struct platform_device *pdev) +{ + struct charger_desc *desc = dev_get_platdata(&pdev->dev); + struct charger_manager *cm; + int ret = 0, i, j; + union power_supply_propval val; + + if (g_desc && !rtc_dev && g_desc->rtc) { + rtc_dev = rtc_class_open(g_desc->rtc); + if (IS_ERR_OR_NULL(rtc_dev)) { + rtc_dev = NULL; + dev_err(&pdev->dev, "Cannot get RTC %s.\n", + g_desc->rtc); + ret = -ENODEV; + goto err_alloc; + } + } + + if (!desc) { + dev_err(&pdev->dev, "No platform data (desc) found.\n"); + ret = -ENODEV; + goto err_alloc; + } + + cm = kzalloc(sizeof(struct charger_manager), GFP_KERNEL); + if (!cm) { + dev_err(&pdev->dev, "Cannot allocate memory.\n"); + ret = -ENOMEM; + goto err_alloc; + } + + /* Basic Values. Unspecified are Null or 0 */ + cm->dev = &pdev->dev; + cm->desc = desc; + cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */ + + /* + * The following two do not need to be errors. + * Users may intentionally ignore those two features. + */ + if (desc->fullbatt_uV == 0) { + dev_info(&pdev->dev, "Ignoring full-battery voltage threshold" + " as it is not supplied."); + } + + cm->fullbatt_vchk_uV = desc->fullbatt_uV; + + if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) { + dev_info(&pdev->dev, "Disabling full-battery voltage drop " + "checking mechanism as it is not supplied."); + desc->fullbatt_vchkdrop_ms = 0; + desc->fullbatt_vchkdrop_uV = 0; + } + + if (!desc->charger_regulators || desc->num_charger_regulators < 1) { + ret = -EINVAL; + dev_err(&pdev->dev, "charger_regulators undefined.\n"); + goto err_no_charger; + } + + for (i = 0 ; i < desc->num_charger_regulators ; i++) { + struct charger_regulator *charger + = &desc->charger_regulators[i]; + + charger->consumer = regulator_get(&pdev->dev, + charger->regulator_name); + if (charger->consumer == NULL) { + dev_err(&pdev->dev, "Cannot find charger(%s)n", + charger->regulator_name); + ret = -EINVAL; + goto err_no_charger_stat; + } +#ifdef CONFIG_EXTCON + for (j = 0 ; j < charger->num_cables ; j++) { + struct charger_cable *cable = &charger->cables[j]; + + ret = charger_extcon_init(cm, cable); + if (ret < 0) { + dev_err(&pdev->dev, "Cannot find charger(%s)n", + charger->regulator_name); + goto err_extcon; + } + cable->charger = charger; + cable->cm = cm; + } +#endif + } + + if (!desc->psy_charger_stat || !desc->psy_charger_stat[0]) { + dev_err(&pdev->dev, "No power supply defined.\n"); + ret = -EINVAL; + goto err_extcon; + } + + for (i = 0; desc->psy_charger_stat[i]; i++) + /* Counting index only */ ; + + cm->charger_stat = kzalloc(sizeof(struct power_supply *) * (i + 1), + GFP_KERNEL); + if (!cm->charger_stat) { + ret = -ENOMEM; + goto err_extcon; + } + + for (i = 0; desc->psy_charger_stat[i]; i++) { + cm->charger_stat[i] = power_supply_get_by_name( + desc->psy_charger_stat[i]); + if (!cm->charger_stat[i]) { + dev_err(&pdev->dev, "Cannot find power supply " + "\"%s\"\n", + desc->psy_charger_stat[i]); + ret = -ENODEV; + goto err_chg_stat; + } + } + + cm->fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge); + if (!cm->fuel_gauge) { + dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n", + desc->psy_fuel_gauge); + ret = -ENODEV; + goto err_chg_stat; + } + + if (desc->polling_interval_ms == 0 || + msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL) { + dev_err(&pdev->dev, "polling_interval_ms is too small\n"); + ret = -EINVAL; + goto err_chg_stat; + } + + if (!desc->is_temperature_error) { + dev_err(&pdev->dev, "there is no is_temperature_error\n"); + ret = -EINVAL; + goto err_chg_stat; + } + + platform_set_drvdata(pdev, cm); + + memcpy(&cm->charger_psy, &psy_default, + sizeof(psy_default)); + if (!desc->psy_name) { + strncpy(cm->psy_name_buf, psy_default.name, + PSY_NAME_MAX); + } else { + strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX); + } + cm->charger_psy.name = cm->psy_name_buf; + + /* Allocate for psy properties because they may vary */ + cm->charger_psy.properties = kzalloc(sizeof(enum power_supply_property) + * (ARRAY_SIZE(default_charger_props) + + NUM_CHARGER_PSY_OPTIONAL), + GFP_KERNEL); + if (!cm->charger_psy.properties) { + dev_err(&pdev->dev, "Cannot allocate for psy properties.\n"); + ret = -ENOMEM; + goto err_chg_stat; + } + memcpy(cm->charger_psy.properties, default_charger_props, + sizeof(enum power_supply_property) * + ARRAY_SIZE(default_charger_props)); + cm->charger_psy.num_properties = psy_default.num_properties; + + /* Find which optional psy-properties are available */ + if (!cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_NOW, &val)) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_CHARGE_NOW; + cm->charger_psy.num_properties++; + } + if (!cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CURRENT_NOW, + &val)) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_CURRENT_NOW; + cm->charger_psy.num_properties++; + } + if (desc->measure_ambient_temp) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP_AMBIENT; + cm->charger_psy.num_properties++; + } + if (desc->measure_battery_temp) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP; + cm->charger_psy.num_properties++; + } + + if (power_supply_register(NULL, &cm->charger_psy)) { + dev_err(&pdev->dev, "Cannot register charger-manager with" + " name \"%s\".\n", cm->charger_psy.name); + ret = -EINVAL; + goto err_psy; + } + + /* Fullbat vchk should be ready before registering irq handlers */ + INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk); + + /* Add to the list */ + mutex_lock(&cm_list_mtx); + list_add(&cm->entry, &cm_list); + mutex_unlock(&cm_list_mtx); + + schedule_work(&setup_polling); + + return 0; + +err_psy: + kfree(cm->charger_psy.properties); +err_chg_stat: + kfree(cm->charger_stat); +#ifdef CONFIG_EXTCON +err_extcon: + for (i = 0 ; i < desc->num_charger_regulators ; i++) { + struct charger_regulator *charger + = &desc->charger_regulators[i]; + for (j = 0 ; j < charger->num_cables ; j++) { + struct charger_cable *cable = &charger->cables[j]; + extcon_unregister_interest(&cable->extcon_dev); + } + } +#endif +err_no_charger_stat: + for (i = 0 ; i < desc->num_charger_regulators ; i++) + regulator_put(desc->charger_regulators[i].consumer); +err_no_charger: + kfree(cm); +err_alloc: + return ret; +} + +static int __devexit charger_manager_remove(struct platform_device *pdev) +{ + struct charger_manager *cm = platform_get_drvdata(pdev); + struct charger_desc *desc = cm->desc; + int i, j; + + /* Remove from the list */ + mutex_lock(&cm_list_mtx); + list_del(&cm->entry); + mutex_unlock(&cm_list_mtx); + + schedule_work(&setup_polling); + + power_supply_unregister(&cm->charger_psy); + +#ifdef CONFIG_EXTCON + for (i = 0 ; i < desc->num_charger_regulators ; i++) { + struct charger_regulator *charger + = &desc->charger_regulators[i]; + for (j = 0 ; j < charger->num_cables ; j++) { + struct charger_cable *cable = &charger->cables[j]; + extcon_unregister_interest(&cable->extcon_dev); + } + } +#endif + + for (i = 0 ; i < desc->num_charger_regulators ; i++) + regulator_put(desc->charger_regulators[i].consumer); + + kfree(cm->charger_psy.properties); + kfree(cm->charger_stat); + + kfree(cm); + return 0; +} + +const struct platform_device_id charger_manager_id[] = { + { "charger-manager", 0 }, + { }, +}; + +static int cm_suspend_noirq(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, + dev); + struct charger_manager *cm = platform_get_drvdata(pdev); + + if (cm->cancel_suspend) { + cm->cancel_suspend = false; + return -EAGAIN; + } + + return 0; +} + +static int cm_suspend_prepare(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, + dev); + struct charger_manager *cm = platform_get_drvdata(pdev); + + if (!cm_suspended) { + if (rtc_dev) { + struct rtc_time tmp; + unsigned long now; + + rtc_read_alarm(rtc_dev, &rtc_wkalarm_save); + rtc_read_time(rtc_dev, &tmp); + + if (rtc_wkalarm_save.enabled) { + rtc_tm_to_time(&rtc_wkalarm_save.time, + &rtc_wkalarm_save_); + rtc_tm_to_time(&tmp, &now); + if (now > rtc_wkalarm_save_) + rtc_wkalarm_save_ = 0; + } else { + rtc_wkalarm_save_ = 0; + } + } + cm_suspended = true; + } + + cancel_delayed_work(&cm->fullbatt_vchk_work); + cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm); + cm->status_save_batt = is_batt_present(cm); + + if (!cm_rtc_set) { + cm_suspend_duration_ms = 0; + cm_rtc_set = cm_setup_timer(); + } + + return 0; +} + +static void cm_suspend_complete(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, + dev); + struct charger_manager *cm = platform_get_drvdata(pdev); + + if (cm_suspended) { + if (rtc_dev) { + struct rtc_wkalrm tmp; + + rtc_read_alarm(rtc_dev, &tmp); + rtc_wkalarm_save.pending = tmp.pending; + rtc_set_alarm(rtc_dev, &rtc_wkalarm_save); + } + cm_suspended = false; + cm_rtc_set = false; + } + + /* Re-enqueue delayed work (fullbatt_vchk_work) */ + if (cm->fullbatt_vchk_jiffies_at) { + unsigned long delay = 0; + unsigned long now = jiffies; + + if (time_after_eq(now + CM_JIFFIES_SMALL, + cm->fullbatt_vchk_jiffies_at)) { + delay = (unsigned long)((long)(now + CM_JIFFIES_SMALL) + - (long)(cm->fullbatt_vchk_jiffies_at)); + delay = jiffies_to_msecs(delay); + } else { + delay = 0; + } + + /* + * Account for cm_suspend_duration_ms if + * assume_timer_stops_in_suspend is active + */ + if (g_desc && g_desc->assume_timer_stops_in_suspend) { + if (delay > cm_suspend_duration_ms) + delay -= cm_suspend_duration_ms; + else + delay = 0; + } + + queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work, + msecs_to_jiffies(delay)); + } + cm->cancel_suspend = false; + uevent_notify(cm, NULL); +} + +static const struct dev_pm_ops charger_manager_pm = { + .prepare = cm_suspend_prepare, + .suspend_noirq = cm_suspend_noirq, + .complete = cm_suspend_complete, +}; + +static struct platform_driver charger_manager_driver = { + .driver = { + .name = "charger-manager", + .owner = THIS_MODULE, + .pm = &charger_manager_pm, + }, + .probe = charger_manager_probe, + .remove = __devexit_p(charger_manager_remove), + .id_table = charger_manager_id, +}; + +static int __init charger_manager_init(void) +{ + cm_wq = create_freezable_workqueue("charger_manager"); + INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller); + + return platform_driver_register(&charger_manager_driver); +} +/* Charger manager depends on other devices. Init this later than others */ +late_initcall(charger_manager_init); + +static void __exit charger_manager_cleanup(void) +{ + destroy_workqueue(cm_wq); + cm_wq = NULL; + + platform_driver_unregister(&charger_manager_driver); +} +module_exit(charger_manager_cleanup); + +static bool find_power_supply(struct charger_manager *cm, + struct power_supply *psy) +{ + int i; + bool found = false; + + for (i = 0; cm->charger_stat[i]; i++) { + if (psy == cm->charger_stat[i]) { + found = true; + break; + } + } + + return found; +} + +void cm_notify_event(struct power_supply *psy, enum cm_event_types type, + char *msg) +{ + struct charger_manager *cm; + bool found_power_supply = false; + + if (psy == NULL) + return; + + mutex_lock(&cm_list_mtx); + list_for_each_entry(cm, &cm_list, entry) { + found_power_supply = find_power_supply(cm, psy); + if (found_power_supply) + break; + } + mutex_unlock(&cm_list_mtx); + + if (!found_power_supply) + return; + + switch (type) { + case CM_EVENT_BATT_FULL: + fullbatt_handler(cm); + break; + case CM_EVENT_BATT_OUT: + battout_handler(cm); + break; + case CM_EVENT_BATT_IN: + case CM_EVENT_EXT_PWR_IN_OUT ... CM_EVENT_CHG_START_STOP: + misc_event_handler(cm, type); + break; + case CM_EVENT_UNDESCRIBED: + case CM_EVENT_OTHERS: + uevent_notify(cm, msg ? msg : default_event_names[type]); + break; + default: + dev_err(cm->dev, "%s type not specified.\n", __func__); + break; + } +} +EXPORT_SYMBOL_GPL(cm_notify_event); + +struct charger_manager *get_charger_manager(char *psy_name) +{ + struct power_supply *psy = power_supply_get_by_name(psy_name); + + return container_of(psy, struct charger_manager, charger_psy); +} +EXPORT_SYMBOL_GPL(get_charger_manager); + +void cm_prohibit_charging(struct charger_manager *cm) +{ + cm->user_prohibit = true; + try_charger_enable(cm, false); +} +EXPORT_SYMBOL_GPL(cm_prohibit_charging); + +void cm_allow_charging(struct charger_manager *cm) +{ + cm->user_prohibit = false; + try_charger_enable(cm, true); +} +EXPORT_SYMBOL_GPL(cm_allow_charging); + +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_DESCRIPTION("Charger Manager"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("charger-manager"); diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index c5c8805156c..4842eaa3297 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -174,7 +174,10 @@ static int __devinit max17042_probe(struct i2c_client *client, i2c_set_clientdata(client, chip); - chip->battery.name = "max17042_battery"; + if (chip->pdata->psy_name) + chip->battery.name = chip->pdata->psy_name; + else + chip->battery.name = "max17042_battery"; chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; chip->battery.get_property = max17042_get_property; chip->battery.properties = max17042_battery_props; diff --git a/drivers/power/max17042_fuelgauge_px.c b/drivers/power/max17042_fuelgauge_px.c new file mode 100644 index 00000000000..6244f2be6b8 --- /dev/null +++ b/drivers/power/max17042_fuelgauge_px.c @@ -0,0 +1,1622 @@ +/* + * max17042_battery.c + * fuel-gauge systems for lithium-ion (Li+) batteries + * + * Copyright (C) 2010 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/types.h> +#include <linux/irq.h> +#include <linux/pm.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/firmware.h> +#include <linux/wakelock.h> +#include <linux/blkdev.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/rtc.h> +#include <mach/gpio.h> +#include <linux/power/sec_battery_px.h> +#include <linux/power/max17042_fuelgauge_px.h> + +#define PRINT_COUNT 1 + +static struct i2c_client *fg_i2c_client; +struct max17042_chip *max17042_chip_data; + +static int fg_get_battery_type(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + + return chip->info.battery_type; +} + +static int fg_i2c_read(struct i2c_client *client, u8 reg, u8 *data, u8 length) +{ + s32 value; + + value = i2c_smbus_read_i2c_block_data(client, reg, length, data); + if (value < 0 || value != length) { + pr_err("%s: Failed to fg_i2c_read. status = %d\n", + __func__, value); + return -1; + } + + return 0; +} + +static int fg_i2c_write(struct i2c_client *client, u8 reg, u8 *data, u8 length) +{ + s32 value; + + value = i2c_smbus_write_i2c_block_data(client, reg, length, data); + if (value < 0) { + pr_err("%s: Failed to fg_i2c_write, error code=%d\n", + __func__, value); + return -1; + } + + return 0; +} + +static int fg_read_register(u8 addr) +{ + struct i2c_client *client = fg_i2c_client; + u8 data[2]; + + if (fg_i2c_read(client, addr, data, 2) < 0) { + pr_err("%s: Failed to read addr(0x%x)\n", __func__, addr); + return -1; + } + + return (data[1] << 8) | data[0]; +} + +static int fg_write_register(u8 addr, u16 w_data) +{ + struct i2c_client *client = fg_i2c_client; + u8 data[2]; + + data[0] = w_data & 0xFF; + data[1] = w_data >> 8; + + if (fg_i2c_write(client, addr, data, 2) < 0) { + pr_err("%s: Failed to write addr(0x%x)\n", __func__, addr); + return -1; + } + + return 0; +} + +static int fg_read_16register(u8 addr, u16 *r_data) +{ + struct i2c_client *client = fg_i2c_client; + u8 data[32]; + int i = 0; + + if (fg_i2c_read(client, addr, data, 32) < 0) { + pr_err("%s: Failed to read addr(0x%x)\n", __func__, addr); + return -1; + } + + for (i = 0; i < 16; i++) + r_data[i] = (data[2 * i + 1] << 8) | data[2 * i]; + + return 0; +} + +void fg_write_and_verify_register(u8 addr, u16 w_data) +{ + u16 r_data; + u8 retry_cnt = 2; + + while (retry_cnt) { + fg_write_register(addr, w_data); + r_data = fg_read_register(addr); + + if (r_data != w_data) { + pr_err("%s: verification failed (addr : 0x%x, w_data : 0x%x, r_data : 0x%x)\n", + __func__, addr, w_data, r_data); + retry_cnt--; + } else + break; + } +} + +static void fg_test_print(void) +{ + struct i2c_client *client = fg_i2c_client; + u8 data[2]; + u32 average_vcell; + u16 w_data; + u32 temp; + u32 temp2; + u16 fullcap, remcap, mixcap, avcap; + + if (fg_i2c_read(client, AVR_VCELL_REG, data, 2) < 0) { + pr_err("%s: Failed to read VCELL\n", __func__); + return; + } + + w_data = (data[1]<<8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + average_vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + average_vcell += (temp2 << 4); + + fullcap = fg_read_register(FULLCAP_REG); + remcap = fg_read_register(REMCAP_REP_REG); + mixcap = fg_read_register(REMCAP_MIX_REG); + avcap = fg_read_register(REMCAP_AV_REG); + + pr_info("avg_vcell(%d), fullcap(%d), remcap(%d), mixcap(%d), avcap(%d)\n", + average_vcell, fullcap/2, remcap/2, mixcap/2, avcap/2); + msleep(20); +} + +static int fg_read_voltage_now(void) +{ + struct i2c_client *client = fg_i2c_client; + u8 data[2]; + u32 voltage_now; + u16 w_data; + u32 temp; + u32 temp2; + + if (fg_i2c_read(client, VCELL_REG, data, 2) < 0) { + pr_err("%s: Failed to read VCELL\n", __func__); + return -1; + } + + w_data = (data[1]<<8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + voltage_now = temp / 1000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000; + voltage_now += (temp2 << 4); + + return voltage_now; +} + +static int fg_read_vcell(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + u32 vcell; + u16 w_data; + u32 temp; + u32 temp2; + + if (fg_i2c_read(client, VCELL_REG, data, 2) < 0) { + pr_err("%s: Failed to read VCELL\n", __func__); + return -1; + } + + w_data = (data[1]<<8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + vcell += (temp2 << 4); + + if (!(chip->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: VCELL(%d), data(0x%04x)\n", + __func__, vcell, (data[1]<<8) | data[0]); + + return vcell; +} + +static int fg_read_avg_vcell(void) +{ + struct i2c_client *client = fg_i2c_client; + u8 data[2]; + u32 avg_vcell; + u16 w_data; + u32 temp; + u32 temp2; + + if (fg_i2c_read(client, AVR_VCELL_REG, data, 2) < 0) { + pr_err("%s: Failed to read VCELL\n", __func__); + return -1; + } + + w_data = (data[1]<<8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + avg_vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + avg_vcell += (temp2 << 4); + + return avg_vcell; +} + +static int fg_read_vfocv(void) +{ + struct i2c_client *client = fg_i2c_client; + u8 data[2]; + u32 vfocv = 0; + u16 w_data; + u32 temp; + u32 temp2; + + if (fg_i2c_read(client, VFOCV_REG, data, 2) < 0) { + pr_err("%s: Failed to read VFOCV\n", __func__); + return -1; + } + + w_data = (data[1]<<8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vfocv = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + vfocv += (temp2 << 4); + + return vfocv; +} + +static int fg_check_battery_present(void) +{ + struct i2c_client *client = fg_i2c_client; + u8 status_data[2]; + int ret = 1; + + /* 1. Check Bst bit */ + if (fg_i2c_read(client, STATUS_REG, status_data, 2) < 0) { + pr_err("%s: Failed to read STATUS_REG\n", __func__); + return 0; + } + + if (status_data[0] & (0x1 << 3)) { + pr_info("%s - addr(0x01), data(0x%04x)\n", __func__, + (status_data[1]<<8) | status_data[0]); + pr_info("%s: battery is absent!!\n", __func__); + ret = 0; + } + + return ret; +} + + +static int fg_read_temp(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2] = { 0, }; + int temper = 0; + + if (fg_check_battery_present()) { + if (fg_i2c_read(client, TEMPERATURE_REG, data, 2) < 0) { + pr_err("%s: Failed to read TEMPERATURE_REG\n", + __func__); + return -1; + } + + if (data[1]&(0x1 << 7)) { + temper = ((~(data[1]))&0xFF)+1; + temper *= (-1000); + } else { + temper = data[1] & 0x7f; + temper *= 1000; + temper += data[0] * 39 / 10; + + /* Adjust temperature over 47, only for SDI battery */ + if (fg_get_battery_type() == SDI_BATTERY_TYPE) { + if (temper >= 47000 && temper < 60000) { + temper = temper * SDI_TRIM1_1 / 100; + temper -= SDI_TRIM1_2; + } else if (temper >= 60000) { + temper = temper * SDI_TRIM2_1 / 100; + temper -= 51000; + } + } + } + } else + temper = 20000; + + if (!(chip->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: TEMPERATURE(%d), data(0x%04x)\n", __func__, + temper, (data[1]<<8) | data[0]); + + return temper; +} + +static int fg_read_soc(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + u32 soc = 0; + + if (fg_i2c_read(client, SOCREP_REG, data, 2) < 0) { + pr_err("%s: Failed to read SOCREP\n", __func__); + return -1; + } + + soc = data[1]; + + if (!(chip->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: SOC(%d), data(0x%04x)\n", __func__, + soc, (data[1]<<8) | data[0]); + + return soc; +} + +static int fg_read_vfsoc(void) +{ + struct i2c_client *client = fg_i2c_client; + u8 data[2]; + + if (fg_i2c_read(client, VFSOC_REG, data, 2) < 0) { + pr_err("%s: Failed to read VFSOC\n", __func__); + return -1; + } + + return (int)data[1]; +} + +static int fg_read_current(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data1[2], data2[2]; + u32 temp, sign; + s32 i_current; + s32 avg_current; + + if (fg_i2c_read(client, CURRENT_REG, data1, 2) < 0) { + pr_err("%s: Failed to read CURRENT\n", __func__); + return -1; + } + + if (fg_i2c_read(client, AVG_CURRENT_REG, data2, 2) < 0) { + pr_err("%s: Failed to read AVERAGE CURRENT\n", __func__); + return -1; + } + + temp = ((data1[1]<<8) | data1[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else + sign = POSITIVE; + + i_current = temp * MAX17042_CURRENT_UNIT; + if (sign) + i_current *= -1; + + temp = ((data2[1]<<8) | data2[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else + sign = POSITIVE; + + avg_current = temp * MAX17042_CURRENT_UNIT; + if (sign) + avg_current *= -1; + + if (!(chip->info.pr_cnt++ % PRINT_COUNT)) { + fg_test_print(); + pr_info("%s: CURRENT(%dmA), AVG_CURRENT(%dmA)\n", __func__, + i_current, avg_current); + chip->info.pr_cnt = 1; + /* Read max17042's all registers every 5 minute. */ + fg_periodic_read(); + } + + return i_current; +} + +static int fg_read_avg_current(void) +{ + struct i2c_client *client = fg_i2c_client; + u8 data2[2]; + u32 temp, sign; + s32 avg_current; + + if (fg_i2c_read(client, AVG_CURRENT_REG, data2, 2) < 0) { + pr_err("%s: Failed to read AVERAGE CURRENT\n", __func__); + return -1; + } + + temp = ((data2[1]<<8) | data2[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else + sign = POSITIVE; + + avg_current = temp * MAX17042_CURRENT_UNIT; + + if (sign) + avg_current *= -1; + + return avg_current; +} + +int fg_reset_soc(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; +#if defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE) + u32 fullcap; + int vfocv = 0; +#endif + + /* delay for current stablization */ + msleep(500); + + pr_info("%s: %s - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, "Before quick-start", fg_read_vcell(), + fg_read_vfocv(), fg_read_vfsoc(), fg_read_soc()); + pr_info("%s: Before quick-start - current(%d), avg current(%d)\n", + __func__, fg_read_current(), + fg_read_avg_current()); + + if (!chip->pdata->check_jig_status()) { + pr_info("%s: Return by No JIG_ON signal\n", __func__); + return 0; + } + + fg_write_register(CYCLES_REG, 0); + + if (fg_i2c_read(client, MISCCFG_REG, data, 2) < 0) { + pr_err("%s: Failed to read MiscCFG\n", __func__); + return -1; + } + + data[1] |= (0x1 << 2); + if (fg_i2c_write(client, MISCCFG_REG, data, 2) < 0) { + pr_err("%s: Failed to write MiscCFG\n", __func__); + return -1; + } + + msleep(250); + fg_write_register(FULLCAP_REG, chip->info.capacity); + msleep(500); + + pr_info("%s: %s - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, "After quick-start", fg_read_vcell(), + fg_read_vfocv(), fg_read_vfsoc(), fg_read_soc()); + pr_info("%s: After quick-start - current(%d), avg current(%d)\n", + __func__, fg_read_current(), fg_read_avg_current()); + fg_write_register(CYCLES_REG, 0x00a0); + +/* P8 is not turned off by Quickstart @3.4V(It's not a problem, depend on + * mode data. Power off for factory test(File system, etc..) */ +#if defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE) +#define QUICKSTART_POWER_OFF_VOLTAGE 3400 + vfocv = fg_read_vfocv(); + if (vfocv < QUICKSTART_POWER_OFF_VOLTAGE) { + pr_info("%s: Power off condition(%d)\n", __func__, vfocv); + + fullcap = fg_read_register(FULLCAP_REG); + /* FullCAP * 0.009 */ + fg_write_register(REMCAP_REP_REG, (u16)(fullcap * 9 / 1000)); + msleep(200); + pr_info("%s: new soc=%d, vfocv=%d\n", __func__, + fg_read_soc(), vfocv); + } + + pr_info("%s: Additional step - VfOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, fg_read_vfocv(), fg_read_vfsoc(), fg_read_soc()); +#endif + + return 0; +} + + +int fg_reset_capacity(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + + return fg_write_register(DESIGNCAP_REG, chip->info.vfcapacity-1); +} + +int fg_adjust_capacity(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + + data[0] = 0; + data[1] = 0; + + /* 1. Write RemCapREP(05h)=0; */ + if (fg_i2c_write(client, REMCAP_REP_REG, data, 2) < 0) { + pr_err("%s: Failed to write RemCap_REP\n", __func__); + return -1; + } + msleep(200); + + pr_info("%s: After adjust - RepSOC(%d)\n", __func__, fg_read_soc()); + + chip->info.soc_restart_flag = 1; + + return 0; +} + +void fg_low_batt_compensation(u32 level) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + int read_val; + u32 temp; + + pr_info("%s: Adjust SOCrep to %d!!\n", __func__, level); + + read_val = fg_read_register(FULLCAP_REG); + if (read_val < 0) + return; + + /* RemCapREP (05h) = FullCap(10h) x 0.0304 or 0.0104 */ + temp = read_val * (level*100 + 4) / 10000; + fg_write_register(REMCAP_REP_REG, (u16)temp); + + /* (0x67) * 0.0039 = 0.4%*/ + temp = 0; + temp = (u16)((level << 8) | 0x67); + fg_write_register(SOCREP_REG, temp); + + chip->info.low_batt_comp_flag = 1; +} + +void fg_periodic_read(void) +{ + int i; + u16 data[0x10]; + struct timespec ts; + struct rtc_time tm; + + getnstimeofday(&ts); + rtc_time_to_tm(ts.tv_sec, &tm); + + pr_info("[MAX17042] %d/%d/%d %02d:%02d,", + tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900, tm.tm_hour, + tm.tm_min); + + for (i = 0; i < 16; i++) { + fg_read_16register(i, data); + + pr_info("%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x00], data[0x01], data[0x02], data[0x03], + data[0x04], data[0x05], data[0x06], data[0x07]); + pr_info("%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x08], data[0x09], data[0x0a], data[0x0b], + data[0x0c], data[0x0d], data[0x0e], data[0x0f]); + if (i == 4) + i = 13; + + msleep(20); + } + pr_info("\n"); +} + +static void fg_read_model_data(void) +{ + u16 data0[16], data1[16], data2[16]; + int i; + int relock_check; + + pr_info("[FG_Model] "); + + /* Unlock model access */ + fg_write_register(0x62, 0x0059); + fg_write_register(0x63, 0x00C4); + + /* Read model data */ + fg_read_16register(0x80, data0); + fg_read_16register(0x90, data1); + fg_read_16register(0xa0, data2); + + /* Print model data */ + for (i = 0; i < 16; i++) + pr_info("0x%04x, ", data0[i]); + + for (i = 0; i < 16; i++) + pr_info("0x%04x, ", data1[i]); + + for (i = 0; i < 16; i++) { + if (i == 15) + pr_info("0x%04x", data2[i]); + else + pr_info("0x%04x, ", data2[i]); + } + + do { + relock_check = 0; + /* Lock model access */ + fg_write_register(0x62, 0x0000); + fg_write_register(0x63, 0x0000); + + /* Read model data again */ + fg_read_16register(0x80, data0); + fg_read_16register(0x90, data1); + fg_read_16register(0xa0, data2); + + for (i = 0; i < 16; i++) { + if (data0[i] || data1[i] || data2[i]) { + pr_info("%s: data is non-zero, lock again!\n", + __func__); + relock_check = 1; + } + } + } while (relock_check); + +} + +int fg_alert_init(void) +{ + struct i2c_client *client = fg_i2c_client; + u8 misccgf_data[2]; + u8 salrt_data[2]; + u8 config_data[2]; + u8 valrt_data[2]; + u8 talrt_data[2]; + u16 read_data = 0; + + /* Using RepSOC */ + if (fg_i2c_read(client, MISCCFG_REG, misccgf_data, 2) < 0) { + pr_err("%s: Failed to read MISCCFG_REG\n", __func__); + return -1; + } + misccgf_data[0] = misccgf_data[0] & ~(0x03); + + if (fg_i2c_write(client, MISCCFG_REG, misccgf_data, 2) < 0) { + pr_info("%s: Failed to write MISCCFG_REG\n", __func__); + return -1; + } + + /* SALRT Threshold setting */ + salrt_data[1] = 0xff; + salrt_data[0] = 0x01; + if (fg_i2c_write(client, SALRT_THRESHOLD_REG, salrt_data, 2) < 0) { + pr_info("%s: Failed to write SALRT_THRESHOLD_REG\n", __func__); + return -1; + } + + /* Reset VALRT Threshold setting (disable) */ + valrt_data[1] = 0xFF; + valrt_data[0] = 0x00; + if (fg_i2c_write(client, VALRT_THRESHOLD_REG, valrt_data, 2) < 0) { + pr_info("%s: Failed to write VALRT_THRESHOLD_REG\n", __func__); + return -1; + } + + read_data = fg_read_register((u8)VALRT_THRESHOLD_REG); + if (read_data != 0xff00) + pr_err("%s: VALRT_THRESHOLD_REG is not valid (0x%x)\n", + __func__, read_data); + + /* Reset TALRT Threshold setting (disable) */ + talrt_data[1] = 0x7F; + talrt_data[0] = 0x80; + if (fg_i2c_write(client, TALRT_THRESHOLD_REG, talrt_data, 2) < 0) { + pr_info("%s: Failed to write TALRT_THRESHOLD_REG\n", __func__); + return -1; + } + + read_data = fg_read_register((u8)TALRT_THRESHOLD_REG); + if (read_data != 0x7f80) + pr_err("%s: TALRT_THRESHOLD_REG is not valid (0x%x)\n", + __func__, read_data); + + mdelay(100); + + /* Enable SOC alerts */ + if (fg_i2c_read(client, CONFIG_REG, config_data, 2) < 0) { + pr_err("%s: Failed to read CONFIG_REG\n", __func__); + return -1; + } + config_data[0] = config_data[0] | (0x1 << 2); + + if (fg_i2c_write(client, CONFIG_REG, config_data, 2) < 0) { + pr_info("%s: Failed to write CONFIG_REG\n", __func__); + return -1; + } + + return 1; +} + +static int fg_check_status_reg(void) +{ + struct i2c_client *client = fg_i2c_client; + u8 status_data[2]; + int ret = 0; + + /* 1. Check Smn was generatedread */ + if (fg_i2c_read(client, STATUS_REG, status_data, 2) < 0) { + pr_err("%s: Failed to read STATUS_REG\n", __func__); + return -1; + } + pr_info("%s - addr(0x00), data(0x%04x)\n", __func__, + (status_data[1]<<8) | status_data[0]); + + if (status_data[1] & (0x1 << 2)) + ret = 1; + + /* 2. clear Status reg */ + status_data[1] = 0; + if (fg_i2c_write(client, STATUS_REG, status_data, 2) < 0) { + pr_info("%s: Failed to write STATUS_REG\n", __func__); + return -1; + } + + return ret; +} + +void fg_fullcharged_compensation(u32 is_recharging, u32 pre_update) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + static int new_fcap; + pr_info("%s: is_recharging(%d), pre_update(%d)\n", __func__, + is_recharging, pre_update); + + new_fcap = fg_read_register(FULLCAP_REG); + if (new_fcap < 0) + new_fcap = chip->info.capacity; + + pr_info("%s: prevFCap(0x%04x), newFcap(0x%04x)\n", __func__, + chip->info.prev_fullcap, new_fcap); + if (new_fcap > (chip->info.capacity * 110 / 100)) { + new_fcap = (chip->info.capacity * 110) / 100; + pr_info("%s: init FCap +10%%: newFcap(0x%04x)\n", + __func__, new_fcap); + fg_write_register(REMCAP_REP_REG, (u16)(new_fcap)); + fg_write_register(FULLCAP_REG, (u16)(new_fcap)); + } else if (new_fcap < (chip->info.capacity * 50 / 100)) { + new_fcap = (chip->info.capacity * 50) / 100; + pr_info("%s: init FCap -50%%: newFcap(0x%04x)\n", + __func__, new_fcap); + fg_write_register(REMCAP_REP_REG, (u16)(new_fcap)); + fg_write_register(FULLCAP_REG, (u16)(new_fcap)); + } else { + if (new_fcap > (chip->info.prev_fullcap * 110 / 100)) { + new_fcap = (chip->info.prev_fullcap * 110) / 100; + pr_info("%s: prev FCap +10%%: newFcap(0x%04x)\n", + __func__, new_fcap); + fg_write_register(REMCAP_REP_REG, (u16)(new_fcap)); + fg_write_register(FULLCAP_REG, (u16)(new_fcap)); + } else if (new_fcap < (chip->info.prev_fullcap * 90 / 100)) { + new_fcap = (chip->info.prev_fullcap * 90) / 100; + pr_info("%s: prev FCap -10%%: newFcap(0x%04x)\n", + __func__, new_fcap); + fg_write_register(REMCAP_REP_REG, (u16)(new_fcap)); + fg_write_register(FULLCAP_REG, (u16)(new_fcap)); + } else { + pr_info("%s: keep FCap: newFcap(0x%04x)\n", + __func__, new_fcap); + } + } + + /* 4. Write RepSOC(06h)=100%; */ + fg_write_register(SOCREP_REG, (u16)(0x64 << 8)); + + /* 5. Write MixSOC(0Dh)=100%; */ + fg_write_register(SOCMIX_REG, (u16)(0x64 << 8)); + + /* 6. Write AVSOC(0Eh)=100%; */ + fg_write_register(SOCAV_REG, (u16)(0x64 << 8)); + + /* if pre_update case, skip updating PrevFullCAP value. */ + if (!pre_update) + chip->info.prev_fullcap = fg_read_register(FULLCAP_REG); + + pr_info("%s: FullCap(0x%04x), RemCap(0x%04x)\n", __func__, + fg_read_register(FULLCAP_REG), + fg_read_register(REMCAP_REP_REG)); + + fg_periodic_read(); +} + +void fg_check_vf_fullcap_range(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + static int new_vffcap; + u16 print_flag = 1; + pr_debug("%s\n", __func__); + + new_vffcap = fg_read_register(FULLCAP_NOM_REG); + if (new_vffcap < 0) + new_vffcap = chip->info.vfcapacity; + + pr_info("%s: prevVFFCap(0x%04x), newVFFcap(0x%04x)\n", __func__, + chip->info.prev_vffcap, new_vffcap); + if (new_vffcap > (chip->info.vfcapacity * 110 / 100)) { + new_vffcap = (chip->info.vfcapacity * 110) / 100; + pr_info("%s: init VFFCap +10%%: newVFFcap(0x%04x)\n", + __func__, new_vffcap); + fg_write_register(DQACC_REG, (u16)(new_vffcap / 4)); + fg_write_register(DPACC_REG, (u16)0x3200); + } else if (new_vffcap < (chip->info.vfcapacity * 50 / 100)) { + new_vffcap = (chip->info.vfcapacity * 50) / 100; + pr_info("%s: init VFFCap -50%%: newVFFcap(0x%04x)\n", + __func__, new_vffcap); + fg_write_register(DQACC_REG, (u16)(new_vffcap / 4)); + fg_write_register(DPACC_REG, (u16)0x3200); + } else { + if (new_vffcap > (chip->info.prev_vffcap * 105 / 100)) { + new_vffcap = (chip->info.prev_vffcap * 105) / 100; + pr_info("%s: prev VFFCap +10%%: newVFFcap(0x%04x)\n", + __func__, new_vffcap); + fg_write_register(DQACC_REG, (u16)(new_vffcap / 4)); + fg_write_register(DPACC_REG, (u16)0x3200); + } else if (new_vffcap < (chip->info.prev_vffcap * 90 / 100)) { + new_vffcap = (chip->info.prev_vffcap * 90) / 100; + pr_info("%s: prev VFFCap -10%%: newVFFcap(0x%04x)\n", + __func__, new_vffcap); + fg_write_register(DQACC_REG, (u16)(new_vffcap / 4)); + fg_write_register(DPACC_REG, (u16)0x3200); + } else { + pr_info("%s: keep VFFCap: newVFFcap(0x%04x)\n", + __func__, new_vffcap); + print_flag = 0; + } + } + + /* delay for register setting (dQacc, dPacc) */ + if (print_flag) + msleep(300); + + chip->info.prev_vffcap = fg_read_register(FULLCAP_NOM_REG); + + if (print_flag) + pr_info("%s: VfFullCap(0x%04x), dQacc(0x%04x), dPacc(0x%04x)\n", + __func__, + fg_read_register(FULLCAP_NOM_REG), + fg_read_register(DQACC_REG), + fg_read_register(DPACC_REG)); +} + +int fg_check_cap_corruption(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + int vfsoc = fg_read_vfsoc(); + int repsoc = fg_read_soc(); + int mixcap = fg_read_register(REMCAP_MIX_REG); + int vfocv = fg_read_register(VFOCV_REG); + int remcap = fg_read_register(REMCAP_REP_REG); + int fullcapacity = fg_read_register(FULLCAP_REG); + int vffullcapacity = fg_read_register(FULLCAP_NOM_REG); + u32 temp, temp2, new_vfocv, pr_vfocv; + int designcap; + int ret = 0; + + /* If usgin Jig or low batt compensation flag is set, + then skip checking. */ + if (chip->pdata->check_jig_status()) { + fg_write_register(DESIGNCAP_REG, chip->info.vfcapacity - 1); + designcap = fg_read_register(DESIGNCAP_REG); + pr_info("%s: return by jig, vfcap(0x%04x), designcap(0x%04x)\n", + __func__, chip->info.vfcapacity, designcap); + return 0; + } + + if (vfsoc < 0 || repsoc < 0 || mixcap < 0 || vfocv < 0 || + remcap < 0 || fullcapacity < 0 || vffullcapacity < 0) + return 0; + + /* Check full charge learning case. */ + if (((vfsoc >= 70) + && ((remcap >= (fullcapacity * 995 / 1000)) + && (remcap <= (fullcapacity * 1005 / 1000)))) + || chip->info.low_batt_comp_flag + || chip->info.soc_restart_flag) { + + chip->info.prev_repsoc = repsoc; + chip->info.prev_remcap = remcap; + chip->info.prev_fullcapacity = fullcapacity; + if (chip->info.soc_restart_flag) + chip->info.soc_restart_flag = 0; + + ret = 1; + } + + /* ocv calculation for print */ + temp = (vfocv & 0xFFF) * 78125; + pr_vfocv = temp / 1000000; + + temp = ((vfocv & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + pr_vfocv += (temp2 << 4); + + /* MixCap differ is greater than 265mAh */ + if ((((vfsoc+5) < chip->info.prev_vfsoc) + || (vfsoc > (chip->info.prev_vfsoc+5))) + || (((repsoc+5) < chip->info.prev_repsoc) + || (repsoc > (chip->info.prev_repsoc+5))) + || (((mixcap+530) < chip->info.prev_mixcap) + || (mixcap > (chip->info.prev_mixcap+530)))) { + fg_periodic_read(); + + pr_info("[FG_Recovery] (B) VfSOC(%d), prevVfSOC(%d),", + vfsoc, chip->info.prev_vfsoc); + pr_info(" RepSOC(%d), prevRepSOC(%d), MixCap(%d),", + repsoc, chip->info.prev_repsoc, (mixcap/2)); + pr_info(" prevMixCap(%d),VfOCV(0x%04x, %d)\n", + (chip->info.prev_mixcap/2), vfocv, pr_vfocv); + + mutex_lock(&chip->fg_lock); + + fg_write_and_verify_register(REMCAP_MIX_REG, + chip->info.prev_mixcap); + fg_write_register(VFOCV_REG, chip->info.prev_vfocv); + mdelay(200); + + fg_write_and_verify_register(REMCAP_REP_REG, + chip->info.prev_remcap); + vfsoc = fg_read_register(VFSOC_REG); + fg_write_register(0x60, 0x0080); + fg_write_and_verify_register(0x48, vfsoc); + fg_write_register(0x60, 0x0000); + + fg_write_and_verify_register(0x45, + (chip->info.prev_vfcapacity / 4)); + fg_write_and_verify_register(0x46, 0x3200); + fg_write_and_verify_register(FULLCAP_REG, + chip->info.prev_fullcapacity); + fg_write_and_verify_register(FULLCAP_NOM_REG, + chip->info.prev_vfcapacity); + + mutex_unlock(&chip->fg_lock); + + msleep(200); + + /* ocv calculation for print */ + new_vfocv = fg_read_register(VFOCV_REG); + temp = (new_vfocv & 0xFFF) * 78125; + pr_vfocv = temp / 1000000; + + temp = ((new_vfocv & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + pr_vfocv += (temp2 << 4); + + pr_info("[FG_Recovery] (A) newVfSOC(%d), newRepSOC(%d),", + fg_read_vfsoc(), + fg_read_soc()); + pr_info(" newMixCap(%d), newVfOCV(0x%04x, %d)\n", + (fg_read_register(REMCAP_MIX_REG)/2), + new_vfocv, + pr_vfocv); + + fg_periodic_read(); + + ret = 1; + } else { + chip->info.prev_vfsoc = vfsoc; + chip->info.prev_repsoc = repsoc; + chip->info.prev_remcap = remcap; + chip->info.prev_mixcap = mixcap; + chip->info.prev_fullcapacity = fullcapacity; + chip->info.prev_vfcapacity = vffullcapacity; + chip->info.prev_vfocv = vfocv; + } + + return ret; +} + +void fg_set_full_charged(void) +{ + pr_info("[FG_Set_Full] (B) FullCAP(%d), RemCAP(%d)\n", + (fg_read_register(FULLCAP_REG)/2), + (fg_read_register(REMCAP_REP_REG)/2)); + + fg_write_register(FULLCAP_REG, (u16)fg_read_register(REMCAP_REP_REG)); + + pr_info("[FG_Set_Full] (A) FullCAP(%d), RemCAP(%d)\n", + (fg_read_register(FULLCAP_REG)/2), + (fg_read_register(REMCAP_REP_REG)/2)); +} + +static void display_low_batt_comp_cnt(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 type_str[10]; + + if (fg_get_battery_type() == SDI_BATTERY_TYPE) + sprintf(type_str, "SDI"); + else if (fg_get_battery_type() == ATL_BATTERY_TYPE) + sprintf(type_str, "ATL"); + else + sprintf(type_str, "Unknown"); + + pr_info("Check Array(%s):[%d,%d], [%d,%d], [%d,%d], [%d,%d], [%d,%d]\n", + type_str, + chip->info.low_batt_comp_cnt[0][0], + chip->info.low_batt_comp_cnt[0][1], + chip->info.low_batt_comp_cnt[1][0], + chip->info.low_batt_comp_cnt[1][1], + chip->info.low_batt_comp_cnt[2][0], + chip->info.low_batt_comp_cnt[2][1], + chip->info.low_batt_comp_cnt[3][0], + chip->info.low_batt_comp_cnt[3][1], + chip->info.low_batt_comp_cnt[4][0], + chip->info.low_batt_comp_cnt[4][1]); +} + +static void add_low_batt_comp_cnt(int range, int level) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + int i; + int j; + + /* Increase the requested count value, and reset others. */ + chip->info.low_batt_comp_cnt[range-1][level/2]++; + + for (i = 0; i < LOW_BATT_COMP_RANGE_NUM; i++) { + for (j = 0; j < LOW_BATT_COMP_LEVEL_NUM; j++) { + if (i == range-1 && j == level/2) + continue; + else + chip->info.low_batt_comp_cnt[i][j] = 0; + } + } +} + +#define POWER_OFF_SOC_HIGH_MARGIN 0x200 +#define POWER_OFF_VOLTAGE_HIGH_MARGIN 3500 +#define POWER_OFF_VOLTAGE_NOW_LOW_MARGIN 3350 +#define POWER_OFF_VOLTAGE_AVG_LOW_MARGIN 3400 + +void prevent_early_late_poweroff(int vcell, int *fg_soc) +{ + struct i2c_client *client = fg_i2c_client; + int repsoc, repsoc_data = 0; + int read_val; + int avg_vcell; + u8 data[2]; + + if (fg_i2c_read(client, SOCREP_REG, data, 2) < 0) { + pr_err("%s: Failed to read SOCREP\n", __func__); + return; + } + repsoc = data[1]; + repsoc_data = ((data[1] << 8) | data[0]); + + if (repsoc_data > POWER_OFF_SOC_HIGH_MARGIN) + return; + + avg_vcell = fg_read_avg_vcell(); + pr_info("%s: soc=%d%%(0x%04x), vcell=%d avg_vcell=%d\n", + __func__, repsoc, repsoc_data, vcell, avg_vcell); + + if (vcell > POWER_OFF_VOLTAGE_HIGH_MARGIN) { + read_val = fg_read_register(FULLCAP_REG); + /* FullCAP * 0.013 */ + fg_write_register(REMCAP_REP_REG, (u16)(read_val * 13 / 1000)); + msleep(200); + *fg_soc = fg_read_soc(); + pr_info("%s: new soc=%d, vcell=%d, avg_vcell=%d\n", + __func__, *fg_soc, vcell, avg_vcell); + } else if ((vcell < POWER_OFF_VOLTAGE_NOW_LOW_MARGIN) && + (avg_vcell < POWER_OFF_VOLTAGE_AVG_LOW_MARGIN)) { + read_val = fg_read_register(FULLCAP_REG); + /* FullCAP * 0.009 */ + fg_write_register(REMCAP_REP_REG, (u16)(read_val * 9 / 1000)); + msleep(200); + *fg_soc = fg_read_soc(); + pr_info("%s: new soc=%d, vcell=%d, avg_vcell=%d\n", + __func__, *fg_soc, vcell, avg_vcell); + } +} + +void reset_low_batt_comp_cnt(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + + memset(chip->info.low_batt_comp_cnt, 0, + sizeof(chip->info.low_batt_comp_cnt)); +} + +static int check_low_batt_comp_condtion(int *nLevel) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + int i; + int j; + int ret = 0; + + for (i = 0; i < LOW_BATT_COMP_RANGE_NUM; i++) { + for (j = 0; j < LOW_BATT_COMP_LEVEL_NUM; j++) { + if (chip->info.low_batt_comp_cnt[i][j] \ + >= MAX_LOW_BATT_CHECK_CNT) { + display_low_batt_comp_cnt(); + ret = 1; + *nLevel = j*2 + 1; + break; + } + } + } + + return ret; +} + +static int get_low_batt_threshold(int range, int level, int nCurrent) +{ + int ret = 0; + + if (fg_get_battery_type() == SDI_BATTERY_TYPE) { + switch (range) { +/* P4 & P8 needs one more level */ +#if defined(CONFIG_MACH_P4NOTE) \ + || defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE) + case 5: + if (level == 1) + ret = SDI_Range5_1_Offset + \ + ((nCurrent * SDI_Range5_1_Slope) / 1000); + else if (level == 3) + ret = SDI_Range5_3_Offset + \ + ((nCurrent * SDI_Range5_3_Slope) / 1000); + break; +#endif + case 4: + if (level == 1) + ret = SDI_Range4_1_Offset + \ + ((nCurrent * SDI_Range4_1_Slope) / 1000); + else if (level == 3) + ret = SDI_Range4_3_Offset + \ + ((nCurrent * SDI_Range4_3_Slope) / 1000); + break; + + case 3: + if (level == 1) + ret = SDI_Range3_1_Offset + \ + ((nCurrent * SDI_Range3_1_Slope) / 1000); + else if (level == 3) + ret = SDI_Range3_3_Offset + \ + ((nCurrent * SDI_Range3_3_Slope) / 1000); + break; + + case 2: + if (level == 1) + ret = SDI_Range2_1_Offset + \ + ((nCurrent * SDI_Range2_1_Slope) / 1000); + else if (level == 3) + ret = SDI_Range2_3_Offset + \ + ((nCurrent * SDI_Range2_3_Slope) / 1000); + break; + + case 1: + if (level == 1) + ret = SDI_Range1_1_Offset + \ + ((nCurrent * SDI_Range1_1_Slope) / 1000); + else if (level == 3) + ret = SDI_Range1_3_Offset + \ + ((nCurrent * SDI_Range1_3_Slope) / 1000); + break; + + default: + break; + } + } else if (fg_get_battery_type() == ATL_BATTERY_TYPE) { + switch (range) { + case 4: + if (level == 1) + ret = ATL_Range4_1_Offset + \ + ((nCurrent * ATL_Range4_1_Slope) / 1000); + else if (level == 3) + ret = ATL_Range4_3_Offset + \ + ((nCurrent * ATL_Range4_3_Slope) / 1000); + break; + + case 3: + if (level == 1) + ret = ATL_Range3_1_Offset + \ + ((nCurrent * ATL_Range3_1_Slope) / 1000); + else if (level == 3) + ret = ATL_Range3_3_Offset + \ + ((nCurrent * ATL_Range3_3_Slope) / 1000); + break; + + case 2: + if (level == 1) + ret = ATL_Range2_1_Offset + \ + ((nCurrent * ATL_Range2_1_Slope) / 1000); + else if (level == 3) + ret = ATL_Range2_3_Offset + \ + ((nCurrent * ATL_Range2_3_Slope) / 1000); + break; + + case 1: + if (level == 1) + ret = ATL_Range1_1_Offset + \ + ((nCurrent * ATL_Range1_1_Slope) / 1000); + else if (level == 3) + ret = ATL_Range1_3_Offset + \ + ((nCurrent * ATL_Range1_3_Slope) / 1000); + break; + + default: + break; + } + } + + return ret; +} + +int low_batt_compensation(int fg_soc, int fg_vcell, int fg_current) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + int fg_avg_current = 0; + int fg_min_current = 0; + int new_level = 0; + int bCntReset = 0; + + /* Not charging, flag is none, Under 3.60V or 3.45V */ + if (!chip->info.low_batt_comp_flag && \ + (fg_vcell <= chip->info.check_start_vol)) { + + fg_avg_current = fg_read_avg_current(); + fg_min_current = min(fg_avg_current, fg_current); + + if (fg_min_current < CURRENT_RANGE_MAX) { + if (fg_soc >= 2 && \ + fg_vcell < get_low_batt_threshold( \ + CURRENT_RANGE_MAX_NUM, + 1, fg_min_current)) + add_low_batt_comp_cnt(CURRENT_RANGE_MAX_NUM, 1); + else if (fg_soc >= 4 && \ + fg_vcell < get_low_batt_threshold( \ + CURRENT_RANGE_MAX_NUM, + 3, fg_min_current)) + add_low_batt_comp_cnt(CURRENT_RANGE_MAX_NUM, 3); + else + bCntReset = 1; + } +/* P4 & P8 needs more level */ +#if defined(CONFIG_MACH_P4NOTE) \ + || defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE) + else if (fg_min_current >= CURRENT_RANGE5 && \ + fg_min_current < CURRENT_RANGE4) { + if (fg_soc >= 2 && fg_vcell < \ + get_low_batt_threshold(4, 1, fg_min_current)) + + add_low_batt_comp_cnt(4, 1); + else if (fg_soc >= 4 && fg_vcell < \ + get_low_batt_threshold(4, 3, fg_min_current)) + + add_low_batt_comp_cnt(4, 3); + else + bCntReset = 1; + } +#endif + else if (fg_min_current >= CURRENT_RANGE4 && \ + fg_min_current < CURRENT_RANGE3) { + if (fg_soc >= 2 && fg_vcell < \ + get_low_batt_threshold(3, 1, fg_min_current)) + + add_low_batt_comp_cnt(3, 1); + else if (fg_soc >= 4 && fg_vcell < \ + get_low_batt_threshold(3, 3, fg_min_current)) + add_low_batt_comp_cnt(3, 3); + else + bCntReset = 1; + } else if (fg_min_current >= CURRENT_RANGE3 && \ + fg_min_current < CURRENT_RANGE2) { + if (fg_soc >= 2 && fg_vcell < \ + get_low_batt_threshold(2, 1, fg_min_current)) + + add_low_batt_comp_cnt(2, 1); + else if (fg_soc >= 4 && fg_vcell < \ + get_low_batt_threshold(2, 3, fg_min_current)) + + add_low_batt_comp_cnt(2, 3); + else + bCntReset = 1; + } else if (fg_min_current >= CURRENT_RANGE2 && \ + fg_min_current < CURRENT_RANGE1) { + if (fg_soc >= 2 && fg_vcell < \ + get_low_batt_threshold(1, 1, fg_min_current)) + + add_low_batt_comp_cnt(1, 1); + else if (fg_soc >= 4 && fg_vcell < \ + get_low_batt_threshold(1, 3, fg_min_current)) + + add_low_batt_comp_cnt(1, 3); + else + bCntReset = 1; + } + + + if (check_low_batt_comp_condtion(&new_level)) { +#if defined(CONFIG_MACH_P4NOTE) || \ + defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE) + /* + * Disable 3% low battery compensation + * duplicated action with 1% low battery compensation + */ + if (new_level < 2) +#endif + fg_low_batt_compensation(new_level); + reset_low_batt_comp_cnt(); + } + + if (bCntReset) + reset_low_batt_comp_cnt(); + + /* if compensation finished, then read SOC again!!*/ + if (chip->info.low_batt_comp_flag) { + pr_info("%s: MIN_CURRENT(%d), AVG_CURRENT(%d),", + __func__, fg_min_current, fg_avg_current); + pr_info(" CURRENT(%d), SOC(%d), VCELL(%d)\n", + fg_current, fg_soc, fg_vcell); +#if defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE) + /* Do not update soc right after low battery compensation */ + /* to prevent from powering-off suddenly (only for P8s) */ + pr_info("%s: SOC is set to %d\n", + __func__, fg_read_soc()); +#else + fg_soc = fg_read_soc(); + pr_info("%s: SOC is set to %d\n", __func__, fg_soc); +#endif + } + } + +#if defined(CONFIG_MACH_P4NOTE) || defined(CONFIG_MACH_P2) + /* Prevent power off over 3500mV */ + /* Force power off under 3400mV */ + prevent_early_late_poweroff(fg_vcell, &fg_soc); +#endif + + return fg_soc; +} + +static void fg_set_battery_type(void) +{ + struct i2c_client *client = fg_i2c_client; + struct max17042_chip *chip = i2c_get_clientdata(client); + + u16 data; + u8 type_str[10]; + + data = fg_read_register(DESIGNCAP_REG); + + if ((data == chip->pdata->sdi_vfcapacity) || \ + (data == chip->pdata->sdi_vfcapacity-1)) + chip->info.battery_type = SDI_BATTERY_TYPE; + else if ((data == chip->pdata->atl_vfcapacity) || \ + (data == chip->pdata->atl_vfcapacity-1)) + chip->info.battery_type = ATL_BATTERY_TYPE; + else { + pr_info("%s: Unknown battery is set to SDI type.\n", __func__); + chip->info.battery_type = SDI_BATTERY_TYPE; + } + + if (chip->info.battery_type == SDI_BATTERY_TYPE) + sprintf(type_str, "SDI"); + else if (chip->info.battery_type == ATL_BATTERY_TYPE) + sprintf(type_str, "ATL"); + else + sprintf(type_str, "Unknown"); + + pr_info("%s: DesignCAP(0x%04x), Battery type(%s)\n", + __func__, data, type_str); + + switch (chip->info.battery_type) { + case ATL_BATTERY_TYPE: + chip->info.capacity = chip->pdata->atl_capacity; + chip->info.vfcapacity = chip->pdata->atl_vfcapacity; + break; + + case SDI_BATTERY_TYPE: + default: + chip->info.capacity = chip->pdata->sdi_capacity; + chip->info.vfcapacity = chip->pdata->sdi_vfcapacity; + break; + } + + /* If not initialized yet, then init threshold values. */ + if (!chip->info.check_start_vol) { + if (chip->info.battery_type == SDI_BATTERY_TYPE) + chip->info.check_start_vol = \ + chip->pdata->sdi_low_bat_comp_start_vol; + else if (chip->info.battery_type == ATL_BATTERY_TYPE) + chip->info.check_start_vol = \ + chip->pdata->atl_low_bat_comp_start_vol; + } + +} + +int get_fuelgauge_capacity(enum capacity_type type) +{ + int cap = -1; + pr_debug("%s\n", __func__); + + switch (type) { + case CAPACITY_TYPE_FULL: + cap = fg_read_register(FULLCAP_REG); + break; + case CAPACITY_TYPE_MIX: + cap = fg_read_register(REMCAP_MIX_REG); + break; + case CAPACITY_TYPE_AV: + cap = fg_read_register(REMCAP_AV_REG); + break; + case CAPACITY_TYPE_REP: + cap = fg_read_register(REMCAP_REP_REG); + break; + default: + pr_info("%s: invalid type(%d)\n", __func__, type); + cap = -EINVAL; + break; + } + + pr_debug("%s: type(%d), cap(0x%x, %d)\n", __func__, + type, cap, (cap / 2)); + return cap; +} + +int get_fuelgauge_value(int data) +{ + int ret; + + switch (data) { + case FG_LEVEL: + ret = fg_read_soc(); + break; + + case FG_TEMPERATURE: + ret = fg_read_temp(); + break; + + case FG_VOLTAGE: + ret = fg_read_vcell(); + break; + + case FG_CURRENT: + ret = fg_read_current(); + break; + + case FG_CURRENT_AVG: + ret = fg_read_avg_current(); + break; + + case FG_BATTERY_TYPE: + ret = fg_get_battery_type(); + break; + + case FG_CHECK_STATUS: + ret = fg_check_status_reg(); + break; + + case FG_VF_SOC: + ret = fg_read_vfsoc(); + break; + + case FG_VOLTAGE_NOW: + ret = fg_read_voltage_now(); + break; + + default: + ret = -1; + break; + } + + return ret; +} + +static int fg_i2c_remove(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + kfree(chip); + return 0; +} + +static int +fg_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct max17042_chip *chip; + + if (!client->dev.platform_data) { + pr_err("%s: No platform data\n", __func__); + return -EINVAL; + } + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) { + pr_info("failed to allocate memory.\n"); + return -ENOMEM; + } + + chip->client = client; + chip->pdata = client->dev.platform_data; + i2c_set_clientdata(client, chip); + + pr_info("%s: fuelgauge driver loading\n", __func__); + + max17042_chip_data = chip; + /* rest of the initialisation goes here. */ + pr_info("Fuel gauge attach success!!!\n"); + + fg_i2c_client = client; + fg_set_battery_type(); + + /* Init parameters to prevent wrong compensation. */ + chip->info.prev_fullcap = fg_read_register(FULLCAP_REG); + chip->info.prev_vffcap = fg_read_register(FULLCAP_NOM_REG); + /* Init FullCAP of first full charging. */ + chip->info.full_charged_cap = chip->info.prev_fullcap; + + chip->info.prev_vfsoc = fg_read_vfsoc(); + chip->info.prev_repsoc = fg_read_soc(); + chip->info.prev_remcap = fg_read_register(REMCAP_REP_REG); + chip->info.prev_mixcap = fg_read_register(REMCAP_MIX_REG); + chip->info.prev_vfocv = fg_read_register(VFOCV_REG); + chip->info.prev_fullcapacity = chip->info.prev_fullcap; + chip->info.prev_vfcapacity = chip->info.prev_vffcap; + + mutex_init(&chip->fg_lock); + + fg_alert_init(); + fg_read_model_data(); + fg_periodic_read(); + return 0; +} + +static const struct i2c_device_id fg_device_id[] = { + {"fuelgauge", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, fg_device_id); + +static struct i2c_driver fg_i2c_driver = { + .driver = { + .name = "fuelgauge", + .owner = THIS_MODULE, + }, + .probe = fg_i2c_probe, + .remove = fg_i2c_remove, + .id_table = fg_device_id, +}; + +static int __init max17042_init(void) +{ + return i2c_add_driver(&fg_i2c_driver); +} +subsys_initcall(max17042_init); + +static void __exit max17042_exit(void) +{ + i2c_del_driver(&fg_i2c_driver); +} +module_exit(max17042_exit); + +MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>"); +MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/power/max17042_fuelgauge_u1.c b/drivers/power/max17042_fuelgauge_u1.c new file mode 100644 index 00000000000..1434c72063a --- /dev/null +++ b/drivers/power/max17042_fuelgauge_u1.c @@ -0,0 +1,1022 @@ +/* + * max17042-fuelgauge.c + * fuel-gauge systems for lithium-ion (Li+) batteries + * + * Copyright (C) 2010 Samsung Electronics + * + * based on max17040_battery.c + * + * <ms925.kim@samsung.com> + * + * 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/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/power_supply.h> +#include <linux/power/max17042_fuelgauge_u1.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> + +static ssize_t sec_fg_show_property(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t sec_fg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +struct max17042_chip { + struct i2c_client *client; + struct delayed_work work; + struct power_supply battery; + struct max17042_platform_data *pdata; + + int vcell; /* battery voltage */ + int avgvcell; /* average battery voltage */ + int vfocv; /* calculated battery voltage */ + int soc; /* battery capacity */ + int raw_soc; /* fuel gauge raw data */ + int temperature; + int fuel_alert_soc; /* fuel alert threshold */ + bool is_fuel_alerted; /* fuel alerted */ + struct wake_lock fuel_alert_wake_lock; + bool is_enable; /* can be fuel guage enable */ + +#ifdef RECAL_SOC_FOR_MAXIM + int cnt; + int recalc_180s; + int boot_cnt; +#endif +}; + +static int max17042_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, + battery); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (val->intval) { + case 0: /*vcell */ + val->intval = chip->vcell; + break; + case 1: /*vfocv */ + val->intval = chip->vfocv; + break; + } + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = chip->avgvcell; + break; + case POWER_SUPPLY_PROP_CAPACITY: + switch (val->intval) { + case 0: /*normal soc */ + val->intval = chip->soc; + break; + case 1: /*raw soc */ + val->intval = chip->raw_soc; + break; + } + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = chip->temperature / 100; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max17042_write_reg(struct i2c_client *client, int reg, u8 * buf) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, reg, 2, buf); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d (reg = 0x%x)\n", + __func__, ret, reg); + + return ret; +} + +static int max17042_read_reg(struct i2c_client *client, int reg, u8 * buf) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, reg, 2, buf); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d (reg = 0x%x)\n", + __func__, ret, reg); + + return ret; +} + +static void max17042_write_reg_array(struct i2c_client *client, + struct max17042_reg_data *data, int size) +{ + int i; + + for (i = 0; i < size; i += 3) + max17042_write_reg(client, (data + i)->reg_addr, + ((u8 *) (data + i)) + 1); +} + +static void max17042_init_regs(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + /* struct max17042_platform_data *pdata = client->dev.platform_data; */ + + dev_info(&client->dev, "%s\n", __func__); + + if (chip->is_enable) { + /* max17042_write_reg_array(client, pdata->init, + pdata->init_size); */ + + if (max17042_read_reg(client, MAX17042_REG_FILTERCFG, data) < 0) + return; + + /* Clear average vcell (12 sec) */ + data[0] &= 0x8f; + + max17042_write_reg(client, MAX17042_REG_FILTERCFG, data); + } +} + +static void max17042_alert_init(struct i2c_client *client) +{ + struct max17042_platform_data *pdata = client->dev.platform_data; + + dev_info(&client->dev, "%s\n", __func__); + + max17042_write_reg_array(client, pdata->alert_init, + pdata->alert_init_size); +} + +static int max17042_read_vfocv(struct i2c_client *client) +{ + u8 data[2]; + u32 vfocv = 0; + struct max17042_chip *chip = i2c_get_clientdata(client); + + if (chip->is_enable) { + if (max17042_read_reg(client, MAX17042_REG_VFOCV, data) < 0) + return -1; + + vfocv = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000; + + chip->vfocv = vfocv; + + return vfocv; + } else + return 4000; + +} + +#if 0 +static int max17042_read_vfsoc(struct i2c_client *client) +{ + u8 data[2]; + u32 vfsoc = 0; + +#ifndef NO_READ_I2C_FOR_MAXIM + if (max17042_read_reg(client, MAX17042_REG_SOC_VF, data) < 0) + return -1; + + vfsoc = data[1]; + + return vfsoc; +#else + return 100; +#endif +} +#else +static void max17042_get_soc(struct i2c_client *client); +static int max17042_read_vfsoc(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + max17042_get_soc(client); + + return chip->soc; +} +#endif + +static void max17042_reset_soc(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + + if (chip->is_enable) { + dev_info(&client->dev, "%s : Before quick-start - " + "VfOCV(%d), VfSOC(%d)\n", + __func__, max17042_read_vfocv(client), + max17042_read_vfsoc(client)); + + if (max17042_read_reg(client, MAX17042_REG_MISCCFG, data) < 0) + return; + + /* Set bit10 makes quick start */ + data[1] |= (0x1 << 2); + max17042_write_reg(client, MAX17042_REG_MISCCFG, data); + + msleep(500); + + dev_info(&client->dev, "%s : After quick-start - " + "VfOCV(%d), VfSOC(%d)\n", + __func__, max17042_read_vfocv(client), + max17042_read_vfsoc(client)); + } + + return; +} + +static void max17042_get_vcell(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + + if (chip->is_enable) { + if (max17042_read_reg(client, MAX17042_REG_VCELL, data) < 0) + return; + + chip->vcell = ((data[0] >> 3) + (data[1] << 5)) * 625; + + if (max17042_read_reg(client, MAX17042_REG_AVGVCELL, data) < 0) + return; + + chip->avgvcell = ((data[0] >> 3) + (data[1] << 5)) * 625; + } else { + chip->vcell = 4000000; + chip->avgvcell = 4000000; + } +} + +static int max17042_recalc_soc(struct i2c_client *client, int boot_cnt) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + struct power_supply *psy = power_supply_get_by_name("battery"); + union power_supply_propval value; + u8 data[2]; + u16 temp_vfocv = 0; + int soc; + + if (chip->is_enable) { + /* AverageVcell : 175.8ms * 256 = 45s sampling */ + if (boot_cnt < 4) /* 30s using VCELL */ + max17042_read_reg(client, MAX17042_REG_VCELL, data); + else + max17042_read_reg(client, MAX17042_REG_AVGVCELL, data); + temp_vfocv = (data[1] << 8); + temp_vfocv |= data[0]; + + if (psy != NULL) { + psy->get_property(psy, + POWER_SUPPLY_PROP_ONLINE, &value); + + if (value.intval == 0) + temp_vfocv = temp_vfocv + 0x0380; /* +70mV */ + else + temp_vfocv = temp_vfocv - 0x0380; /* -70mV */ + + dev_info(&client->dev, "cable = %d, ", value.intval); + } else + temp_vfocv = temp_vfocv + 0x0380; /* +70mV */ + + data[1] = temp_vfocv >> 8; + data[0] = 0x00FF & temp_vfocv; + dev_info(&client->dev, "forced write to vfocv %d mV\n", + (temp_vfocv >> 4) * 125 / 100); + max17042_write_reg(client, MAX17042_REG_VFOCV, data); + + msleep(200); + + max17042_read_reg(client, MAX17042_REG_SOC_VF, data); + soc = min((int)data[1], 100); + + max17042_read_reg(client, MAX17042_REG_VCELL, data); + dev_info(&client->dev, "new vcell = %d, vfocv = %d, soc = %d\n", + ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000, + max17042_read_vfocv(client), soc); + } else + soc = 70; + + return soc; +} + +static void max17042_get_soc(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + int soc; + int diff = 0; + + if (chip->is_enable) { + if (max17042_read_reg(client, MAX17042_REG_SOC_VF, data) < 0) + return; + dev_info(&chip->client->dev, "%s : soc(%02x%02x)\n", + __func__, data[1], data[0]); + + soc = (data[1] * 100) + (data[0] * 100 / 256); + + chip->raw_soc = min(soc / 100, 100); + +#ifdef RECAL_SOC_FOR_MAXIM + if (chip->pdata->need_soc_recal()) { + dev_info(&client->dev, + "%s : recalculate soc\n", __func__); + + /*modified 3.6V cut-off */ + /*raw 3% ~ 95% */ + soc = (soc < 300) ? 0 : ((soc - 300) * 100 / 9200) + 1; + + if (chip->boot_cnt < 4) + chip->boot_cnt++; + + dev_info(&client->dev, + "vcell = %d, vfocv = %d, soc = %d\n", + chip->vcell, max17042_read_vfocv(client), soc); + + if (soc < 5) { + dev_info(&client->dev, + "recalc force soc = %d, diff = %d!\n", + soc, diff); + chip->recalc_180s = 1; + } + + /*when using fuelgauge level, diff is calculated */ + if (chip->recalc_180s == 0 && chip->boot_cnt != 1) { + if (chip->soc > soc) + diff = chip->soc - soc; + else + diff = soc - chip->soc; + } else /*during recalc, diff is not valid */ + diff = 0; + + if (diff > 10) { + dev_info(&client->dev, + "recalc 180s soc = %d, diff = %d!\n", + soc, diff); + chip->recalc_180s = 1; + } + + if (chip->recalc_180s == 1) { + if (chip->cnt < 18) { + chip->cnt++; + soc = max17042_recalc_soc(client, + chip->boot_cnt); + } else { + chip->recalc_180s = 0; + chip->cnt = 0; + } + } + } else { + /*modified 3.4V cut-off */ + /*raw 1.6% ~ 97.6% */ + soc = (soc > 100) ? ((soc - 60) * 100 / 9700) : 0; + /*raw 1.5% ~ 95% */ + /*soc = (soc < 150) ? 0 : ((soc - 150) * 100 / 9350) + 1; */ + } +#else + /* adjusted soc by adding 0.45 */ + soc += 45; + soc /= 100; +#endif + } else + soc = 70; + + soc = min(soc, 100); + + chip->soc = soc; + + dev_info(&client->dev, "%s : use raw (%d), soc (%d)\n", + __func__, chip->raw_soc, soc); +} + +static void max17042_get_temperature(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + s32 temper = 0; + + if (chip->is_enable) { + if (max17042_read_reg(client, MAX17042_REG_TEMPERATURE, data) < 0) + return; + + /* data[] store 2's compliment format number */ + if (data[1] & (0x1 << 7)) { + /* Negative */ + temper = ((~(data[1])) & 0xFF) + 1; + temper *= (-1000); + } else { + temper = data[1] & 0x7F; + temper *= 1000; + temper += data[0] * 39 / 10; + } + + dev_dbg(&client->dev, "%s: MAX17042 Temperature = %d\n", + __func__, chip->temperature); + } else + temper = 40; + + chip->temperature = temper; +} + +static void max17042_get_version(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + + if (chip->is_enable) { + if (max17042_read_reg(client, MAX17042_REG_VERSION, data) < 0) + return; + + dev_info(&client->dev, "MAX17042 Fuel-Gauge Ver %d%d\n", + data[0], data[1]); + } +} + +static int max17042_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, + battery); + u8 data[2]; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP: + if (chip->pdata->enable_gauging_temperature && + chip->is_enable) { + data[0] = 0; + data[1] = val->intval; + max17042_write_reg(chip->client, + MAX17042_REG_TEMPERATURE, data); + } else + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +static void max17042_work(struct work_struct *work) +{ + struct max17042_chip *chip; + + chip = container_of(work, struct max17042_chip, work.work); + + max17042_get_vcell(chip->client); + max17042_read_vfocv(chip->client); + max17042_get_soc(chip->client); + + if (chip->pdata->enable_gauging_temperature) + max17042_get_temperature(chip->client); + + /* polling check for fuel alert for booting in low battery*/ + if (chip->raw_soc < chip->fuel_alert_soc) { + if (!(chip->is_fuel_alerted) && + chip->pdata->low_batt_cb) { + wake_lock(&chip->fuel_alert_wake_lock); + chip->pdata->low_batt_cb(); + chip->is_fuel_alerted = true; + + dev_info(&chip->client->dev, + "fuel alert activated by polling check (raw:%d)\n", + chip->raw_soc); + } else + dev_info(&chip->client->dev, + "fuel alert already activated (raw:%d)\n", + chip->raw_soc); + } else if (chip->raw_soc == chip->fuel_alert_soc) { + if (chip->is_fuel_alerted) { + wake_unlock(&chip->fuel_alert_wake_lock); + chip->is_fuel_alerted = false; + + dev_info(&chip->client->dev, + "fuel alert deactivated by polling check (raw:%d)\n", + chip->raw_soc); + } + } + +#ifdef LOG_REG_FOR_MAXIM + { + int reg; + int i; + u8 data[2]; + u8 buf[1024]; + + i = 0; + for (reg = 0; reg < 0x50; reg++) { + max17042_read_reg(chip->client, reg, data); + i += sprintf(buf + i, "0x%02x%02xh,", data[1], data[0]); + } + printk(KERN_INFO "%s", buf); + + i = 0; + for (reg = 0xe0; reg < 0x100; reg++) { + max17042_read_reg(chip->client, reg, data); + i += sprintf(buf + i, "0x%02x%02xh,", data[1], data[0]); + } + printk(KERN_INFO "%s", buf); + } +#endif + + schedule_delayed_work(&chip->work, MAX17042_LONG_DELAY); +} + +static enum power_supply_property max17042_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, +}; + +#define SEC_FG_ATTR(_name) \ +{ \ + .attr = { .name = #_name, \ + .mode = 0664 }, \ + .show = sec_fg_show_property, \ + .store = sec_fg_store, \ +} + +static struct device_attribute sec_fg_attrs[] = { + SEC_FG_ATTR(fg_reset_soc), + SEC_FG_ATTR(fg_read_soc), +}; + +enum { + FG_RESET_SOC = 0, + FG_READ_SOC, +}; + +static ssize_t sec_fg_show_property(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, + battery); + + int i = 0; + const ptrdiff_t off = attr - sec_fg_attrs; + + switch (off) { + case FG_READ_SOC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + max17042_read_vfsoc(chip->client)); + break; + default: + i = -EINVAL; + } + + return i; +} + +static ssize_t sec_fg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, + battery); + + int x = 0; + int ret = 0; + const ptrdiff_t off = attr - sec_fg_attrs; + + switch (off) { + case FG_RESET_SOC: + if (sscanf(buf, "%d\n", &x) == 1) { + if (x == 1) + max17042_reset_soc(chip->client); + ret = count; + } +#if 1 +/* tester requested to sustain unity with parental models but + keep source code for later */ + { + struct power_supply *psy = + power_supply_get_by_name("battery"); + union power_supply_propval value; + + if (!psy) { + pr_err("%s: fail to get battery ps\n", __func__); + return -ENODEV; + } + + value.intval = 0; /* dummy value */ + psy->set_property(psy, + POWER_SUPPLY_PROP_CAPACITY, &value); + } +#endif + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int fuelgauge_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 fg_attrs_failed; + } + goto succeed; + +fg_attrs_failed: + while (i--) + device_remove_file(dev, &sec_fg_attrs[i]); +succeed: + return rc; +} + +static bool max17042_check_status(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + bool ret = false; + + if (chip->is_enable) { + /* check if Smn was generated */ + if (max17042_read_reg(client, MAX17042_REG_STATUS, data) < 0) + return ret; + + dev_info(&client->dev, "%s : status_reg(%02x%02x)\n", + __func__, data[1], data[0]); + + /* minimum SOC threshold exceeded. */ + if (data[1] & (0x1 << 2)) + ret = true; + + /* clear status reg */ + if (!ret) { + data[1] = 0; + max17042_write_reg(client, MAX17042_REG_STATUS, data); + msleep(200); + } + } else + ret = true; + + return ret; +} + +static irqreturn_t max17042_irq_thread(int irq, void *irq_data) +{ + u8 data[2]; + bool max17042_alert_status = false; + struct max17042_chip *chip = irq_data; + + /* update SOC */ + max17042_get_soc(chip->client); + + max17042_alert_status = max17042_check_status(chip->client); + + if (max17042_alert_status /* && !(chip->is_fuel_alerted)*/) { + if (max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data) + < 0) + return IRQ_HANDLED; + + data[1] |= (0x1 << 3); + + if (chip->pdata->low_batt_cb && !(chip->is_fuel_alerted)) { + wake_lock(&chip->fuel_alert_wake_lock); + chip->pdata->low_batt_cb(); + chip->is_fuel_alerted = true; + } else + dev_err(&chip->client->dev, + "failed to call low_batt_cb()\n"); + + max17042_write_reg(chip->client, MAX17042_REG_CONFIG, data); + + dev_info(&chip->client->dev, + "%s : low batt alerted!! config_reg(%02x%02x)\n", + __func__, data[1], data[0]); + } else if (!max17042_alert_status /* && (chip->is_fuel_alerted)*/) { + if (chip->is_fuel_alerted) + wake_unlock(&chip->fuel_alert_wake_lock); + chip->is_fuel_alerted = false; + + if (max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data) + < 0) + return IRQ_HANDLED; + + data[1] &= (~(0x1 << 3)); + + max17042_write_reg(chip->client, MAX17042_REG_CONFIG, data); + + dev_info(&chip->client->dev, + "%s : low batt released!! config_reg(%02x%02x)\n", + __func__, data[1], data[0]); + } + + max17042_read_reg(chip->client, MAX17042_REG_VCELL, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_VCELL(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(chip->client, MAX17042_REG_TEMPERATURE, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_TEMPERATURE(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_CONFIG(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(chip->client, MAX17042_REG_VFOCV, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_VFOCV(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(chip->client, MAX17042_REG_SOC_VF, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_SOC_VF(%02x%02x)\n", + __func__, data[1], data[0]); + + dev_err(&chip->client->dev, + "%s : PMIC IRQ (%d), FUEL GAUGE IRQ (%d)\n", + __func__, gpio_get_value(GPIO_PMIC_IRQ), + gpio_get_value(chip->pdata->alert_gpio)); + +#if 0 + **max17042_read_reg(chip->client, MAX17042_REG_STATUS, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_STATUS(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_VALRT_TH, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_VALRT_TH(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_TALRT_TH, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_TALRT_TH(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_SALRT_TH, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_SALRT_TH(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_AVGVCELL, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_AVGVCELL(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_VERSION, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_VERSION(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_LEARNCFG, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_LEARNCFG(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_MISCCFG, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_MISCCFG(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_CGAIN, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_CGAIN(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_RCOMP, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_RCOMP(%02x%02x)\n", + __func__, data[1], data[0]); +#endif + + return IRQ_HANDLED; +} + +static int max17042_irq_init(struct max17042_chip *chip) +{ + int ret; + u8 data[2]; + + /* 1. Set max17042 alert configuration. */ + max17042_alert_init(chip->client); + + chip->is_fuel_alerted = false; + + /* 2. Request irq */ + if (chip->pdata->alert_irq) { + ret = request_threaded_irq(chip->pdata->alert_irq, NULL, + max17042_irq_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "max17042 fuel alert", chip); + if (ret) { + dev_err(&chip->client->dev, "failed to reqeust IRQ\n"); + return ret; + } + + ret = enable_irq_wake(chip->pdata->alert_irq); + if (ret < 0) + dev_err(&chip->client->dev, + "failed to enable wakeup src %d\n", ret); + } + + if (max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data) + < 0) + return -1; + + /*Enable Alert (Aen = 1) */ + data[0] |= (0x1 << 2); + + max17042_write_reg(chip->client, MAX17042_REG_CONFIG, data); + + dev_info(&chip->client->dev, "%s : config_reg(%02x%02x) irq(%d)\n", + __func__, data[1], data[0], chip->pdata->alert_irq); + + return 0; +} + +static int __devinit max17042_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct max17042_chip *chip; + int i; + struct max17042_reg_data *data; + int ret; + u8 i2c_data[2]; + + dev_info(&client->dev, "%s: MAX17042 Driver Loading\n", __func__); + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, chip); + + chip->battery.name = "fuelgauge"; + chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; + chip->battery.get_property = max17042_get_property; + chip->battery.set_property = max17042_set_property; + chip->battery.properties = max17042_battery_props; + chip->battery.num_properties = ARRAY_SIZE(max17042_battery_props); + +#ifdef RECAL_SOC_FOR_MAXIM + chip->cnt = 0; + chip->recalc_180s = 0; + chip->boot_cnt = 0; +#endif + + ret = power_supply_register(&client->dev, &chip->battery); + if (ret) { + dev_err(&client->dev, "failed: power supply register\n"); + kfree(chip); + return ret; + } + + if (max17042_read_reg(client, MAX17042_REG_VERSION, i2c_data) < 0) + chip->is_enable = false; + else + chip->is_enable = true; + + dev_info(&client->dev, "%s : is enable (%d)\n", + __func__, chip->is_enable); + + /* initialize fuel gauge registers */ + max17042_init_regs(client); + + if (chip->is_enable) { + /* register low batt intr */ + chip->pdata->alert_irq = gpio_to_irq(chip->pdata->alert_gpio); + + wake_lock_init(&chip->fuel_alert_wake_lock, WAKE_LOCK_SUSPEND, + "fuel_alerted"); + + data = chip->pdata->alert_init; + for (i = 0; i < chip->pdata->alert_init_size; i += 3) + if ((data + i)->reg_addr == + MAX17042_REG_SALRT_TH) + chip->fuel_alert_soc = + (data + i)->reg_data1; + + dev_info(&client->dev, "fuel alert soc (%d)\n", + chip->fuel_alert_soc); + + ret = max17042_irq_init(chip); + if (ret) + goto err_kfree; + } + + max17042_get_version(client); + + /* create fuelgauge attributes */ + fuelgauge_create_attrs(chip->battery.dev); + + INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17042_work); + schedule_delayed_work(&chip->work, MAX17042_SHORT_DELAY); + + dev_info(&client->dev, "%s: MAX17042 Driver Loaded\n", __func__); + + return 0; + +err_kfree: + wake_lock_destroy(&chip->fuel_alert_wake_lock); + kfree(chip); + return ret; +} + +static int __devexit max17042_remove(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + power_supply_unregister(&chip->battery); + cancel_delayed_work(&chip->work); + wake_lock_destroy(&chip->fuel_alert_wake_lock); + kfree(chip); + return 0; +} + +#ifdef CONFIG_PM + +static int max17042_suspend(struct i2c_client *client, pm_message_t state) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + cancel_delayed_work(&chip->work); + return 0; +} + +static int max17042_resume(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + schedule_delayed_work(&chip->work, MAX17042_SHORT_DELAY); + return 0; +} + +#else + +#define max17042_suspend NULL +#define max17042_resume NULL + +#endif /* CONFIG_PM */ + +static const struct i2c_device_id max17042_id[] = { + {"max17042", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max17042_id); + +static struct i2c_driver max17042_i2c_driver = { + .driver = { + .name = "max17042", + }, + .probe = max17042_probe, + .remove = __devexit_p(max17042_remove), + .suspend = max17042_suspend, + .resume = max17042_resume, + .id_table = max17042_id, +}; + +static int __init max17042_init(void) +{ + return i2c_add_driver(&max17042_i2c_driver); +} + +module_init(max17042_init); + +static void __exit max17042_exit(void) +{ + i2c_del_driver(&max17042_i2c_driver); +} + +module_exit(max17042_exit); + +MODULE_AUTHOR("<ms925.kim@samsung.com>"); +MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max17042_fuelgauge_u1_kor.c b/drivers/power/max17042_fuelgauge_u1_kor.c new file mode 100644 index 00000000000..a7590055680 --- /dev/null +++ b/drivers/power/max17042_fuelgauge_u1_kor.c @@ -0,0 +1,855 @@ +/* + * max17042-fuelgauge.c + * fuel-gauge systems for lithium-ion (Li+) batteries + * + * Copyright (C) 2010 Samsung Electronics + * + * based on max17040_battery.c + * + * <ms925.kim@samsung.com> + * + * 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/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/power_supply.h> +#include <linux/power/max17042_fuelgauge_u1.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> + +static ssize_t sec_fg_show_property(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t sec_fg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +struct max17042_chip { + struct i2c_client *client; + struct delayed_work work; + struct power_supply battery; + struct max17042_platform_data *pdata; + + int vcell; /* battery voltage */ + int avgvcell; /* average battery voltage */ + int vfocv; /* calculated battery voltage */ + int soc; /* battery capacity */ + int raw_soc; /* fuel gauge raw data */ + int config; + int rcomp; + int status; + int temperature; + bool is_fuel_alerted; /* fuel alerted */ +}; + +static int max17042_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, + battery); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (val->intval) { + case 0: /*vcell */ + val->intval = chip->vcell; + break; + case 1: /*vfocv */ + val->intval = chip->vfocv; + break; + } + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = chip->avgvcell; + break; + case POWER_SUPPLY_PROP_CAPACITY: + switch (val->intval) { + case 0: /*normal soc */ + val->intval = chip->soc; + break; + case 1: /*raw soc */ + val->intval = chip->raw_soc; + break; + } + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = chip->temperature / 100; /* result unit 0.1'C */ + break; + default: + return -EINVAL; + } + return 0; +} + +static int max17042_write_reg(struct i2c_client *client, int reg, u8 * buf) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, reg, 2, buf); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d (reg = 0x%x)\n", + __func__, ret, reg); + + return ret; +} + +static int max17042_read_reg(struct i2c_client *client, int reg, u8 * buf) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, reg, 2, buf); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d (reg = 0x%x)\n", + __func__, ret, reg); + + return ret; +} + +static void max17042_write_reg_array(struct i2c_client *client, + struct max17042_reg_data *data, int size) +{ + int i; + + for (i = 0; i < size; i += 3) + max17042_write_reg(client, (data + i)->reg_addr, + ((u8 *) (data + i)) + 1); +} + +static void max17042_init_regs(struct i2c_client *client) +{ + u8 data[2]; + /*struct max17042_platform_data *pdata = client->dev.platform_data; */ + + dev_info(&client->dev, "%s\n", __func__); + +/* max17042_write_reg_array(client, pdata->init, + pdata->init_size);*/ + + if (max17042_read_reg(client, MAX17042_REG_FILTERCFG, data) < 0) + return; + + /* Clear average vcell (12 sec) */ + data[0] &= 0x8f; + + max17042_write_reg(client, MAX17042_REG_FILTERCFG, data); +} + +static void max17042_alert_init(struct i2c_client *client) +{ + struct max17042_platform_data *pdata = client->dev.platform_data; + + dev_info(&client->dev, "%s\n", __func__); + + max17042_write_reg_array(client, pdata->alert_init, + pdata->alert_init_size); +} + +static int max17042_read_vfocv(struct i2c_client *client) +{ + u8 data[2]; + u32 vfocv = 0; + struct max17042_chip *chip = i2c_get_clientdata(client); + + if (max17042_read_reg(client, MAX17042_REG_VFOCV, data) < 0) + return -1; + + vfocv = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000; + + chip->vfocv = vfocv; + + return vfocv; +} + +static int max17042_read_vfsoc(struct i2c_client *client) +{ + u8 data[2]; + u32 vfsoc = 0; + + if (max17042_read_reg(client, MAX17042_REG_SOC_VF, data) < 0) + return -1; + + vfsoc = data[1]; + + return vfsoc; +} + +static void max17042_reset_soc(struct i2c_client *client) +{ + u8 data[2]; + + dev_info(&client->dev, "%s : Before quick-start - " + "VfOCV(%d), VfSOC(%d)\n", + __func__, max17042_read_vfocv(client), + max17042_read_vfsoc(client)); + + if (max17042_read_reg(client, MAX17042_REG_MISCCFG, data) < 0) + return; + + /* Set bit10 makes quick start */ + data[1] |= (0x1 << 2); + max17042_write_reg(client, MAX17042_REG_MISCCFG, data); + + msleep(500); + + dev_info(&client->dev, "%s : After quick-start - " + "VfOCV(%d), VfSOC(%d)\n", + __func__, max17042_read_vfocv(client), + max17042_read_vfsoc(client)); + +#if 0 + data[0] = 0x0F; + data[1] = 0x00; + max17042_write_reg(client, 0x60, data); +#endif + + return; +} + +static void max17042_get_vcell(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + + if (max17042_read_reg(client, MAX17042_REG_VCELL, data) < 0) + return; + + chip->vcell = ((data[0] >> 3) + (data[1] << 5)) * 625; + + if (max17042_read_reg(client, MAX17042_REG_AVGVCELL, data) < 0) + return; + + chip->avgvcell = ((data[0] >> 3) + (data[1] << 5)) * 625; + + /* printk(KERN_ERR "%s : vcell = %dmV\n", __func__, chip->vcell); */ +} + +static void max17042_get_config(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + + if (max17042_read_reg(client, MAX17042_REG_CONFIG, data) < 0) + return; + + chip->config = (data[1] << 8) | data[0]; + + /* printk(KERN_ERR "%s : %x, %x, config = 0x%x\n", __func__, + data[1], data[0], chip->config); */ +} + +static void max17042_get_rcomp(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + + if (max17042_read_reg(client, MAX17042_REG_RCOMP, data) < 0) + return; + + chip->rcomp = (data[1] << 8) | data[0]; + + /* printk(KERN_ERR "%s : %x, %x, rcomp = 0x%x\n", __func__, + data[1], data[0], chip->rcomp); */ +} + +static void max17042_set_rcomp(struct i2c_client *client, u16 rcomp) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + + data[0] = rcomp & 0xff; + data[1] = rcomp >> 8; + if (max17042_write_reg(client, MAX17042_REG_RCOMP, data) < 0) + return; +} + +static void max17042_get_status(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + + if (max17042_read_reg(client, MAX17042_REG_STATUS, data) < 0) + return; + + chip->status = (data[1] << 8) | data[0]; + + /* printk(KERN_ERR "%s : %x, %x = status = 0x%x\n", __func__, + data[1], data[0], chip->status); */ +} + +static void max17042_get_soc(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + unsigned int soc, psoc; + int temp_soc; + static int fg_zcount; + + if (max17042_read_reg(client, MAX17042_REG_SOC_VF, data) < 0) + return; + + psoc = (data[1]*100)+(data[0]*100/256); + chip->raw_soc = psoc; + + if (psoc > 100) { + temp_soc = ((psoc-60)*10000)/9700; + /* under 160(psoc), 0% */ + } else + temp_soc = 0; + + soc = temp_soc/100; + soc = min(soc, (uint)100); + + if (soc == 0) { + fg_zcount++; + if (fg_zcount >= 3) { + /* pr_info("[fg] real 0%\n"); */ + soc = 0; + fg_zcount = 0; + } else + soc = chip->soc; + } else + fg_zcount = 0; + + chip->soc = soc; + + /* pr_info("soc = %d%%, raw_soc = %d\n", + chip->soc, chip->raw_soc); */ +} + +static void max17042_get_temperature(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + s32 temper = 0; + + if (max17042_read_reg(client, MAX17042_REG_TEMPERATURE, data) < 0) + return; + + /* data[] store 2's compliment format number */ + if (data[1] & (0x1 << 7)) { + /* Negative */ + temper = ((~(data[1])) & 0xFF) + 1; + temper *= (-1000); + } else { + temper = data[1] & 0x7F; + temper *= 1000; + temper += data[0] * 39 / 10; + } + + dev_dbg(&client->dev, "%s: MAX17042 Temperature = %d\n", + __func__, chip->temperature); + + chip->temperature = temper; +} + +static void max17042_get_version(struct i2c_client *client) +{ + u8 data[2]; + + if (max17042_read_reg(client, MAX17042_REG_VERSION, data) < 0) + return; + + dev_info(&client->dev, "MAX17042 Fuel-Gauge Ver %d%d\n", + data[0], data[1]); +} + +static void max17042_disable_intr(struct i2c_client *client) +{ + u8 data[2]; + + pr_info("%s :\n", __func__); + + if (max17042_read_reg(client, MAX17042_REG_CONFIG, data) < 0) { + dev_err(&client->dev, "%s : fail to read reg\n", __func__); + return; + } + data[0] &= (~(0x1 << 2)); + + max17042_write_reg(client, MAX17042_REG_CONFIG, data); +} + +static void max17042_enable_intr(struct i2c_client *client) +{ + u8 data[2]; + + pr_info("%s :\n", __func__); + + if (max17042_read_reg(client, MAX17042_REG_CONFIG, data) < 0) { + dev_err(&client->dev, "%s : fail to read reg\n", __func__); + return; + } + data[0] |= (0x1 << 2); + + max17042_write_reg(client, MAX17042_REG_CONFIG, data); +} + +static void max17042_clear_intr(struct i2c_client *client) +{ + u8 data[2]; + + pr_info("%s :\n", __func__); + + if (max17042_read_reg(client, MAX17042_REG_STATUS, data) < 0) { + dev_err(&client->dev, "%s : fail to read reg\n", __func__); + return; + } + + data[1] &= 0x88; + max17042_write_reg(client, MAX17042_REG_STATUS, data); + msleep(200); +} + +static int max17042_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, + battery); + u8 data[2]; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP: + if (chip->pdata->enable_gauging_temperature) { + data[0] = 0; + data[1] = val->intval; + /* + pr_info("MAX17042" + "Temperature(write) = %d\n", + data[1]); + */ + max17042_write_reg(chip->client, + MAX17042_REG_TEMPERATURE, data); + } else + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +static void max17042_work(struct work_struct *work) +{ + struct max17042_chip *chip; + + chip = container_of(work, struct max17042_chip, work.work); + + max17042_get_vcell(chip->client); + max17042_read_vfocv(chip->client); + max17042_get_soc(chip->client); + max17042_get_status(chip->client); + + if (chip->pdata->enable_gauging_temperature) + max17042_get_temperature(chip->client); + + if ((chip->status&0x02) == 0x02) { + max17042_get_config(chip->client); + pr_info("config 0x%x, status 0x%x, vcell 0x%x, row_soc 0x%x\n", + chip->config, chip->status, chip->vcell, chip->raw_soc); + /* + panic("[1]fuel gauge reset occurred!"); + */ + } + + if (chip->is_fuel_alerted) { + if (!(chip->status&0x0400)) { /* Smn clear check */ + chip->is_fuel_alerted = false; + max17042_enable_intr(chip->client); + max17042_get_config(chip->client); + pr_info("config 0x%x, status 0x%x\n", + chip->config, chip->status); + } else { + max17042_get_config(chip->client); + if (chip->config&0x4) /* intr disable check */ + max17042_disable_intr(chip->client); + } + } + + schedule_delayed_work(&chip->work, MAX17042_LONG_DELAY); +} + +static enum power_supply_property max17042_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, +}; + +#define SEC_FG_ATTR(_name) \ +{ \ + .attr = { .name = #_name, \ + .mode = 0664 }, \ + .show = sec_fg_show_property, \ + .store = sec_fg_store, \ +} + +static struct device_attribute sec_fg_attrs[] = { + SEC_FG_ATTR(fg_reset_soc), + SEC_FG_ATTR(fg_read_soc), +}; + +enum { + FG_RESET_SOC = 0, + FG_READ_SOC, +}; + +static ssize_t sec_fg_show_property(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, + battery); + + int i = 0; + const ptrdiff_t off = attr - sec_fg_attrs; + + switch (off) { + case FG_READ_SOC: + max17042_get_soc(chip->client); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + chip->soc); + break; + default: + i = -EINVAL; + } + + return i; +} + +static ssize_t sec_fg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, + battery); + + int x = 0; + int ret = 0; + const ptrdiff_t off = attr - sec_fg_attrs; + + switch (off) { + case FG_RESET_SOC: + if (sscanf(buf, "%d\n", &x) == 1) { + if (x == 1) + max17042_reset_soc(chip->client); + ret = count; + } +#if 1 +/* tester requested to sustain unity with parental models but + keep source code for later */ + { + struct power_supply *psy = + power_supply_get_by_name("battery"); + union power_supply_propval value; + + if (!psy) { + pr_err("%s: fail to get battery ps\n", + __func__); + return -ENODEV; + } + + value.intval = 0; /* dummy value */ + psy->set_property(psy, + POWER_SUPPLY_PROP_CAPACITY, &value); + } +#endif + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int fuelgauge_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 fg_attrs_failed; + } + goto succeed; + +fg_attrs_failed: + while (i--) + device_remove_file(dev, &sec_fg_attrs[i]); +succeed: + return rc; +} + +static bool max17042_check_status(struct i2c_client *client) +{ + u8 data[2]; + bool ret = false; + + /* check if Smn was generated */ + if (max17042_read_reg(client, MAX17042_REG_STATUS, data) < 0) + return -1; + + dev_info(&client->dev, "%s : status_reg(%02x%02x)\n", + __func__, data[1], data[0]); + + /* minimum SOC threshold exceeded. */ + if (data[1] & (0x1 << 2)) + ret = true; + + /* clear status reg */ + if (!ret) { + data[1] = 0; + max17042_write_reg(client, MAX17042_REG_STATUS, data); + msleep(200); + } + + return ret; +} + +static irqreturn_t max17042_irq_thread(int irq, void *irq_data) +{ + bool max17042_alert_status = false; + struct max17042_chip *chip = irq_data; + + max17042_get_soc(chip->client); + max17042_alert_status = max17042_check_status(chip->client); + + if (max17042_alert_status) { + chip->is_fuel_alerted = true; + if (chip->pdata->low_batt_cb) + chip->pdata->low_batt_cb(); + max17042_disable_intr(chip->client); + max17042_get_config(chip->client); + max17042_get_status(chip->client); + pr_info("status = 0x%x\n", chip->status); + } else { + printk(KERN_ERR "fuelguage invalid alert!\n"); + } + + dev_info(&chip->client->dev, + "%s : PMIC IRQ (%d), FUEL GAUGE IRQ (%d)," + "STATUS (0x%x), CONFIG (0x%x)\n", + __func__, gpio_get_value(GPIO_PMIC_IRQ), + gpio_get_value(chip->pdata->alert_gpio), + chip->status, chip->config); + + msleep(200); + + return IRQ_HANDLED; +} + +static int max17042_irq_init(struct max17042_chip *chip) +{ + int ret; + u8 data[2]; + + /* 1. Set max17042 alert configuration. */ + max17042_alert_init(chip->client); + + chip->is_fuel_alerted = false; + + /* 2. Request irq */ + if (chip->client->irq) { + ret = request_threaded_irq(chip->client->irq, NULL, + max17042_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "max17042 fuel alert", chip); + if (ret) { + dev_err(&chip->client->dev, "failed to reqeust IRQ\n"); + return ret; + } + + ret = enable_irq_wake(chip->client->irq); + if (ret < 0) + dev_err(&chip->client->dev, + "failed to enable wakeup src %d\n", ret); + } + + if (max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data) + < 0) + return -1; + + /*Enable Alert (Aen = 1) */ + data[0] |= (0x1 << 2); + + max17042_write_reg(chip->client, MAX17042_REG_CONFIG, data); + + dev_info(&chip->client->dev, "%s : config_reg(%02x%02x)\n", + __func__, data[1], data[0]); + + return 0; +} + +static int __devinit max17042_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct max17042_chip *chip; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, chip); + + chip->battery.name = "fuelgauge", + chip->battery.type = POWER_SUPPLY_TYPE_BATTERY, + chip->battery.get_property = max17042_get_property, + chip->battery.set_property = max17042_set_property, + chip->battery.properties = max17042_battery_props, + chip->battery.num_properties = ARRAY_SIZE(max17042_battery_props), + ret = power_supply_register(&client->dev, &chip->battery); + if (ret) { + dev_err(&client->dev, "failed: power supply register\n"); + kfree(chip); + return ret; + } + + /* initialize fuel gauge registers */ + max17042_init_regs(client); + + /* check rcomp & config */ + max17042_get_rcomp(client); + max17042_get_config(client); + pr_info("rcomp = 0x%04x, config = 0x%04x\n", + chip->rcomp, chip->config); + + if (chip->rcomp != MAX17042_NEW_RCOMP) { + max17042_set_rcomp(client, MAX17042_NEW_RCOMP); + pr_info("set new rcomp = 0x%04x\n", MAX17042_NEW_RCOMP); + max17042_get_rcomp(client); + pr_info("new rcomp = 0x%04x\n", chip->rcomp); + } + + /* register low batt intr */ + ret = max17042_irq_init(chip); + if (ret) + goto err_kfree; + + max17042_get_version(client); + + /* create fuelgauge attributes */ + fuelgauge_create_attrs(chip->battery.dev); + + max17042_get_status(client); + if (chip->status & 0x7700) { + max17042_clear_intr(client); + max17042_get_status(client); + pr_info("reset status = 0x%x\n", chip->status); + } + + INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17042_work); + schedule_delayed_work(&chip->work, MAX17042_SHORT_DELAY); + + return 0; + +err_kfree: + kfree(chip); + return ret; +} + +static int __devexit max17042_remove(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + power_supply_unregister(&chip->battery); + cancel_delayed_work(&chip->work); + kfree(chip); + return 0; +} + +#ifdef CONFIG_PM + +static int max17042_suspend(struct i2c_client *client, pm_message_t state) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + cancel_delayed_work(&chip->work); + return 0; +} + +static int max17042_resume(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + schedule_delayed_work(&chip->work, MAX17042_SHORT_DELAY); + return 0; +} + +#else + +#define max17042_suspend NULL +#define max17042_resume NULL + +#endif /* CONFIG_PM */ + +static const struct i2c_device_id max17042_id[] = { + {"max17042", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max17042_id); + +static struct i2c_driver max17042_i2c_driver = { + .driver = { + .name = "max17042", + }, + .probe = max17042_probe, + .remove = __devexit_p(max17042_remove), + .suspend = max17042_suspend, + .resume = max17042_resume, + .id_table = max17042_id, +}; + +static int __init max17042_init(void) +{ + return i2c_add_driver(&max17042_i2c_driver); +} + +module_init(max17042_init); + +static void __exit max17042_exit(void) +{ + i2c_del_driver(&max17042_i2c_driver); +} + +module_exit(max17042_exit); + +MODULE_AUTHOR("<ms925.kim@samsung.com>"); +MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max8922_charger_s2plus.c b/drivers/power/max8922_charger_s2plus.c new file mode 100644 index 00000000000..86b8af71113 --- /dev/null +++ b/drivers/power/max8922_charger_s2plus.c @@ -0,0 +1,320 @@ +/* + * max8922-charger.c + * MAXIM 8922 charger interface driver + * + * Copyright (C) 2011 Samsung Electronics + * + * <ms925.kim@samsung.com> + * + * Copyright (C) 2012 Samsung Electronics + * Jaecheol kim <jc22.kim@samsung.com> + * + * modify S2PLUS usb charger based on max8922_charger_u1.c + * + * 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/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/power_supply.h> +#include <linux/power/max8922_charger_u1.h> +#include <linux/battery/samsung_battery.h> + +struct max8922_info { + struct device *dev; + struct power_supply psy_bat; + struct max8922_platform_data *pdata; + bool is_usb_cable; + int irq_chg_ing; +}; + +static enum power_supply_property max8922_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, +}; + +static inline int max8922_is_charging(struct max8922_info *info) +{ + int ta_nconnected = gpio_get_value(info->pdata->gpio_ta_nconnected); + int chg_ing = gpio_get_value(info->pdata->gpio_chg_ing); + + dev_info(info->dev, "%s: charging state = 0x%x\n", __func__, + (ta_nconnected << 1) | chg_ing); + + /*return (ta_nconnected == 0 && chg_ing == 0); */ + return (ta_nconnected << 1) | chg_ing; +} + +static int max8922_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8922_info *info = + container_of(psy, struct max8922_info, psy_bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + switch (max8922_is_charging(info)) { + case 0: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case 1: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + } + break; + case POWER_SUPPLY_PROP_ONLINE: + /* battery is always online */ + val->intval = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max8922_enable_charging(struct max8922_info *info, bool enable) +{ + int gpio_chg_en = info->pdata->gpio_chg_en; + unsigned long flags; + + dev_info(info->dev, "%s: %s charging,%s\n", __func__, + enable ? "enable" : "disable", + info->is_usb_cable ? "USB" : "TA"); + + if (enable) { + if (info->is_usb_cable) { + /* Charging by USB cable */ + gpio_direction_output(gpio_chg_en, GPIO_LEVEL_HIGH); + } else { + /* Charging by TA cable */ + gpio_direction_output(gpio_chg_en, GPIO_LEVEL_HIGH); + mdelay(5); + + local_irq_save(flags); + gpio_direction_output(gpio_chg_en, GPIO_LEVEL_LOW); + udelay(300); + gpio_direction_output(gpio_chg_en, GPIO_LEVEL_HIGH); + local_irq_restore(flags); + } + } else + gpio_direction_output(gpio_chg_en, GPIO_LEVEL_LOW); + + msleep(300); + return max8922_is_charging(info); +} + +static int max8922_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max8922_info *info = + container_of(psy, struct max8922_info, psy_bat); + bool enable; + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_NOW: /* Set charging current */ + info->is_usb_cable = (val->intval <= CHARGER_USB_CURRENT); + break; + case POWER_SUPPLY_PROP_STATUS: /* Enable/Disable charging */ + enable = (val->intval == POWER_SUPPLY_STATUS_CHARGING); + max8922_enable_charging(info, enable); + break; + default: + return -EINVAL; + } + return 0; +} + +static irqreturn_t max8922_chg_ing_irq(int irq, void *data) +{ + struct max8922_info *info = data; + int ret = 0; + + dev_info(info->dev, "chg_ing IRQ occurred!\n"); + + if (gpio_get_value(info->pdata->gpio_ta_nconnected)) + return IRQ_HANDLED; + + if (info->pdata->topoff_cb) + ret = info->pdata->topoff_cb(); + + if (ret) { + dev_err(info->dev, "%s: error from topoff_cb(%d)\n", __func__, + ret); + return IRQ_HANDLED; + } + + return IRQ_HANDLED; +} + +static __devinit int max8922_probe(struct platform_device *pdev) +{ + struct max8922_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct max8922_info *info; + int ret; + + dev_info(&pdev->dev, "%s : MAX8922 Charger Driver Loading\n", __func__); + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(pdev, info); + + info->dev = &pdev->dev; + info->pdata = pdata; + + info->psy_bat.name = "max8922-charger", + info->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY, + info->psy_bat.properties = max8922_battery_props, + info->psy_bat.num_properties = ARRAY_SIZE(max8922_battery_props), + info->psy_bat.get_property = max8922_get_property, + info->psy_bat.set_property = max8922_set_property, + ret = power_supply_register(&pdev->dev, &info->psy_bat); + if (ret) { + dev_err(info->dev, "Failed to register psy_bat\n"); + goto err_kfree; + } + + if (pdata->cfg_gpio) { + ret = pdata->cfg_gpio(); + if (ret) { + dev_err(info->dev, "failed to configure GPIO\n"); + goto err_kfree; + } + } + + if (gpio_is_valid(pdata->gpio_chg_en)) { + if (!pdata->gpio_chg_en) { + dev_err(info->dev, "gpio_chg_en defined as 0\n"); + WARN_ON(!pdata->gpio_chg_en); + ret = -EIO; + goto err_kfree; + } + gpio_request(pdata->gpio_chg_en, "MAX8922 CHG_EN"); + } + + if (gpio_is_valid(pdata->gpio_chg_ing)) { + if (!pdata->gpio_chg_ing) { + dev_err(info->dev, "gpio_chg_ing defined as 0\n"); + WARN_ON(!pdata->gpio_chg_ing); + ret = -EIO; + goto err_kfree; + } + gpio_request(pdata->gpio_chg_ing, "MAX8922 CHG_ING"); + } + + if (gpio_is_valid(pdata->gpio_ta_nconnected)) { + if (!pdata->gpio_ta_nconnected) { + dev_err(info->dev, "gpio_ta_nconnected defined as 0\n"); + WARN_ON(!pdata->gpio_ta_nconnected); + ret = -EIO; + goto err_kfree; + } + gpio_request(pdata->gpio_ta_nconnected, + "MAX8922 TA_nCONNECTED"); + } +#if 0 + info->irq_chg_ing = gpio_to_irq(pdata->gpio_chg_ing); + + ret = request_threaded_irq(info->irq_chg_ing, NULL, + max8922_chg_ing_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "chg_ing", info); + if (ret) + dev_err(&pdev->dev, "%s: fail to request chg_ing IRQ:" + " %d: %d\n", __func__, info->irq_chg_ing, ret); +#endif + + return 0; +err_kfree: + power_supply_unregister(&info->psy_bat); + platform_set_drvdata(pdev, NULL); + kfree(info); + return ret; +} + +static int __devexit max8922_remove(struct platform_device *pdev) +{ + struct max8922_info *info = platform_get_drvdata(pdev); + + power_supply_unregister(&info->psy_bat); + + gpio_free(info->pdata->gpio_chg_en); + gpio_free(info->pdata->gpio_chg_ing); + gpio_free(info->pdata->gpio_ta_nconnected); + + kfree(info); + + return 0; +} + +#ifdef CONFIG_PM +static int max8922_suspend(struct device *dev) +{ + struct max8922_info *info = dev_get_drvdata(dev); + + if (info && info->irq_chg_ing) + disable_irq(info->irq_chg_ing); + + return 0; +} + +static int max8922_resume(struct device *dev) +{ + struct max8922_info *info = dev_get_drvdata(dev); + + if (info && info->irq_chg_ing) + enable_irq(info->irq_chg_ing); + + return 0; +} +#else +#define max8922_charger_suspend NULL +#define max8922_charger_resume NULL +#endif + +static const struct dev_pm_ops max8922_pm_ops = { + .suspend = max8922_suspend, + .resume = max8922_resume, +}; + +static struct platform_driver max8922_driver = { + .driver = { + .name = "max8922-charger", + .owner = THIS_MODULE, + .pm = &max8922_pm_ops, + }, + .probe = max8922_probe, + .remove = __devexit_p(max8922_remove), +}; + +static int __init max8922_init(void) +{ + return platform_driver_register(&max8922_driver); +} + +static void __exit max8922_exit(void) +{ + platform_driver_register(&max8922_driver); +} + +module_init(max8922_init); +module_exit(max8922_exit); + +MODULE_DESCRIPTION("MAXIM 8922 charger control driver"); +MODULE_AUTHOR("<ms925.kim@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max8922_charger_u1.c b/drivers/power/max8922_charger_u1.c new file mode 100644 index 00000000000..5cbbb08e500 --- /dev/null +++ b/drivers/power/max8922_charger_u1.c @@ -0,0 +1,314 @@ +/* + * max8922-charger.c + * MAXIM 8922 charger interface driver + * + * Copyright (C) 2011 Samsung Electronics + * + * <ms925.kim@samsung.com> + * + * 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/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/power_supply.h> +#include <linux/power/max8922_charger_u1.h> + +struct max8922_info { + struct device *dev; + struct power_supply psy_bat; + struct max8922_platform_data *pdata; + bool is_usb_cable; + int irq_chg_ing; +}; + +static enum power_supply_property max8922_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, +}; + +static inline int max8922_is_charging(struct max8922_info *info) +{ + int ta_nconnected = gpio_get_value(info->pdata->gpio_ta_nconnected); + int chg_ing = gpio_get_value(info->pdata->gpio_chg_ing); + + dev_info(info->dev, "%s: charging state = 0x%x\n", __func__, + (ta_nconnected << 1) | chg_ing); + + /*return (ta_nconnected == 0 && chg_ing == 0); */ + return (ta_nconnected << 1) | chg_ing; +} + +static int max8922_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8922_info *info = + container_of(psy, struct max8922_info, psy_bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + switch (max8922_is_charging(info)) { + case 0: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case 1: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + } + break; + case POWER_SUPPLY_PROP_ONLINE: + /* battery is always online */ + val->intval = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max8922_enable_charging(struct max8922_info *info, bool enable) +{ + int gpio_chg_en = info->pdata->gpio_chg_en; + unsigned long flags; + + dev_info(info->dev, "%s: %s charging,%s\n", __func__, + enable ? "enable" : "disable", + info->is_usb_cable ? "USB" : "TA"); + + if (enable) { + if (info->is_usb_cable) { + /* Charging by USB cable */ + gpio_direction_output(gpio_chg_en, GPIO_LEVEL_HIGH); + } else { + /* Charging by TA cable */ + gpio_direction_output(gpio_chg_en, GPIO_LEVEL_HIGH); + mdelay(5); + + local_irq_save(flags); + gpio_direction_output(gpio_chg_en, GPIO_LEVEL_LOW); + udelay(300); + gpio_direction_output(gpio_chg_en, GPIO_LEVEL_HIGH); + local_irq_restore(flags); + } + } else + gpio_direction_output(gpio_chg_en, GPIO_LEVEL_LOW); + + msleep(300); + return max8922_is_charging(info); +} + +static int max8922_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max8922_info *info = + container_of(psy, struct max8922_info, psy_bat); + bool enable; + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_NOW: /* Set charging current */ + info->is_usb_cable = (val->intval <= 450); + break; + case POWER_SUPPLY_PROP_STATUS: /* Enable/Disable charging */ + enable = (val->intval == POWER_SUPPLY_STATUS_CHARGING); + max8922_enable_charging(info, enable); + break; + default: + return -EINVAL; + } + return 0; +} + +static irqreturn_t max8922_chg_ing_irq(int irq, void *data) +{ + struct max8922_info *info = data; + int ret = 0; + + dev_info(info->dev, "chg_ing IRQ occurred!\n"); + + if (gpio_get_value(info->pdata->gpio_ta_nconnected)) + return IRQ_HANDLED; + + if (info->pdata->topoff_cb) + ret = info->pdata->topoff_cb(); + + if (ret) { + dev_err(info->dev, "%s: error from topoff_cb(%d)\n", __func__, + ret); + return IRQ_HANDLED; + } + + return IRQ_HANDLED; +} + +static __devinit int max8922_probe(struct platform_device *pdev) +{ + struct max8922_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct max8922_info *info; + int ret; + + dev_info(&pdev->dev, "%s : MAX8922 Charger Driver Loading\n", __func__); + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(pdev, info); + + info->dev = &pdev->dev; + info->pdata = pdata; + + info->psy_bat.name = "max8922-charger", + info->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY, + info->psy_bat.properties = max8922_battery_props, + info->psy_bat.num_properties = ARRAY_SIZE(max8922_battery_props), + info->psy_bat.get_property = max8922_get_property, + info->psy_bat.set_property = max8922_set_property, + ret = power_supply_register(&pdev->dev, &info->psy_bat); + if (ret) { + dev_err(info->dev, "Failed to register psy_bat\n"); + goto err_kfree; + } + + if (pdata->cfg_gpio) { + ret = pdata->cfg_gpio(); + if (ret) { + dev_err(info->dev, "failed to configure GPIO\n"); + goto err_kfree; + } + } + + if (gpio_is_valid(pdata->gpio_chg_en)) { + if (!pdata->gpio_chg_en) { + dev_err(info->dev, "gpio_chg_en defined as 0\n"); + WARN_ON(!pdata->gpio_chg_en); + ret = -EIO; + goto err_kfree; + } + gpio_request(pdata->gpio_chg_en, "MAX8922 CHG_EN"); + } + + if (gpio_is_valid(pdata->gpio_chg_ing)) { + if (!pdata->gpio_chg_ing) { + dev_err(info->dev, "gpio_chg_ing defined as 0\n"); + WARN_ON(!pdata->gpio_chg_ing); + ret = -EIO; + goto err_kfree; + } + gpio_request(pdata->gpio_chg_ing, "MAX8922 CHG_ING"); + } + + if (gpio_is_valid(pdata->gpio_ta_nconnected)) { + if (!pdata->gpio_ta_nconnected) { + dev_err(info->dev, "gpio_ta_nconnected defined as 0\n"); + WARN_ON(!pdata->gpio_ta_nconnected); + ret = -EIO; + goto err_kfree; + } + gpio_request(pdata->gpio_ta_nconnected, + "MAX8922 TA_nCONNECTED"); + } +#if 0 + info->irq_chg_ing = gpio_to_irq(pdata->gpio_chg_ing); + + ret = request_threaded_irq(info->irq_chg_ing, NULL, + max8922_chg_ing_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "chg_ing", info); + if (ret) + dev_err(&pdev->dev, "%s: fail to request chg_ing IRQ:" + " %d: %d\n", __func__, info->irq_chg_ing, ret); +#endif + + return 0; +err_kfree: + power_supply_unregister(&info->psy_bat); + platform_set_drvdata(pdev, NULL); + kfree(info); + return ret; +} + +static int __devexit max8922_remove(struct platform_device *pdev) +{ + struct max8922_info *info = platform_get_drvdata(pdev); + + power_supply_unregister(&info->psy_bat); + + gpio_free(info->pdata->gpio_chg_en); + gpio_free(info->pdata->gpio_chg_ing); + gpio_free(info->pdata->gpio_ta_nconnected); + + kfree(info); + + return 0; +} + +#ifdef CONFIG_PM +static int max8922_suspend(struct device *dev) +{ + struct max8922_info *info = dev_get_drvdata(dev); + + if (info && info->irq_chg_ing) + disable_irq(info->irq_chg_ing); + + return 0; +} + +static int max8922_resume(struct device *dev) +{ + struct max8922_info *info = dev_get_drvdata(dev); + + if (info && info->irq_chg_ing) + enable_irq(info->irq_chg_ing); + + return 0; +} +#else +#define max8922_charger_suspend NULL +#define max8922_charger_resume NULL +#endif + +static const struct dev_pm_ops max8922_pm_ops = { + .suspend = max8922_suspend, + .resume = max8922_resume, +}; + +static struct platform_driver max8922_driver = { + .driver = { + .name = "max8922-charger", + .owner = THIS_MODULE, + .pm = &max8922_pm_ops, + }, + .probe = max8922_probe, + .remove = __devexit_p(max8922_remove), +}; + +static int __init max8922_init(void) +{ + return platform_driver_register(&max8922_driver); +} + +static void __exit max8922_exit(void) +{ + platform_driver_register(&max8922_driver); +} + +module_init(max8922_init); +module_exit(max8922_exit); + +MODULE_DESCRIPTION("MAXIM 8922 charger control driver"); +MODULE_AUTHOR("<ms925.kim@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max8997_charger.c b/drivers/power/max8997_charger.c new file mode 100644 index 00000000000..9514d743f75 --- /dev/null +++ b/drivers/power/max8997_charger.c @@ -0,0 +1,216 @@ +/* + * max8997_charger.c - Power supply consumer driver for the Maxim 8997/8966 + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/mfd/max8997.h> +#include <linux/mfd/max8997-private.h> + +struct charger_data { + struct device *dev; + struct max8997_dev *iodev; + struct power_supply battery; +}; + +static enum power_supply_property max8997_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, /* "FULL" or "NOT FULL" only. */ + POWER_SUPPLY_PROP_PRESENT, /* the presence of battery */ + POWER_SUPPLY_PROP_ONLINE, /* charger is active or not */ +}; + +/* Note that the charger control is done by a current regulator "CHARGER" */ +static int max8997_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct charger_data *charger = container_of(psy, + struct charger_data, battery); + struct i2c_client *i2c = charger->iodev->i2c; + int ret; + u8 reg; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = 0; + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, ®); + if (ret) + return ret; + + if ((reg & (1 << 0)) == 0x1) + val->intval = POWER_SUPPLY_STATUS_FULL; + else if ((reg & (1 << 1)) && !(reg & (1 << 2)) && + (reg & (1 << 3)) && (reg & (3 << 4)) && + !(reg & (1 << 6))) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if ((reg & (1 << 1)) && !(reg & (1 << 6))) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 0; + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, ®); + if (ret) + return ret; + if ((reg & (1 << 2)) == 0x0) + val->intval = 1; + + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, ®); + if (ret) + return ret; + /* DCINOK */ + if (reg & (1 << 1)) + val->intval = 1; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static __devinit int max8997_battery_probe(struct platform_device *pdev) +{ + int ret = 0; + struct charger_data *charger; + struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev); + + if (!pdata) + return -EINVAL; + + if (pdata->eoc_mA) { + u8 val = (pdata->eoc_mA - 50) / 10; + if (val < 0) + val = 0; + if (val > 0xf) + val = 0xf; + + ret = max8997_update_reg(iodev->i2c, + MAX8997_REG_MBCCTRL5, val, 0xf); + if (ret < 0) { + dev_err(&pdev->dev, "Cannot use i2c bus.\n"); + return ret; + } + } + + switch (pdata->timeout) { + case 5: + ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, + 0x2 << 4, 0x7 << 4); + break; + case 6: + ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, + 0x3 << 4, 0x7 << 4); + break; + case 7: + ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, + 0x4 << 4, 0x7 << 4); + break; + case 0: + ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1, + 0x7 << 4, 0x7 << 4); + break; + default: + dev_err(&pdev->dev, "incorrect timeout value (%d)\n", + pdata->timeout); + return -EINVAL; + } + if (ret < 0) { + dev_err(&pdev->dev, "Cannot use i2c bus.\n"); + return ret; + } + + charger = kzalloc(sizeof(struct charger_data), GFP_KERNEL); + if (charger == NULL) { + dev_err(&pdev->dev, "Cannot allocate memory.\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, charger); + + charger->battery.name = "max8997_pmic"; + charger->battery.type = POWER_SUPPLY_TYPE_BATTERY; + charger->battery.get_property = max8997_battery_get_property; + charger->battery.properties = max8997_battery_props; + charger->battery.num_properties = ARRAY_SIZE(max8997_battery_props); + + charger->dev = &pdev->dev; + charger->iodev = iodev; + + ret = power_supply_register(&pdev->dev, &charger->battery); + if (ret) { + dev_err(&pdev->dev, "failed: power supply register\n"); + goto err; + } + + return 0; +err: + kfree(charger); + return ret; +} + +static int __devexit max8997_battery_remove(struct platform_device *pdev) +{ + struct charger_data *charger = platform_get_drvdata(pdev); + + power_supply_unregister(&charger->battery); + kfree(charger); + return 0; +} + +static const struct platform_device_id max8997_battery_id[] = { + { "max8997-battery", 0 }, +}; + +static struct platform_driver max8997_battery_driver = { + .driver = { + .name = "max8997-battery", + .owner = THIS_MODULE, + }, + .probe = max8997_battery_probe, + .remove = __devexit_p(max8997_battery_remove), + .id_table = max8997_battery_id, +}; + +static int __init max8997_battery_init(void) +{ + return platform_driver_register(&max8997_battery_driver); +} +subsys_initcall(max8997_battery_init); + +static void __exit max8997_battery_cleanup(void) +{ + platform_driver_unregister(&max8997_battery_driver); +} +module_exit(max8997_battery_cleanup); + +MODULE_DESCRIPTION("MAXIM 8997/8966 battery control driver"); +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max8997_charger_px.c b/drivers/power/max8997_charger_px.c new file mode 100644 index 00000000000..9582d6670eb --- /dev/null +++ b/drivers/power/max8997_charger_px.c @@ -0,0 +1,485 @@ +/* + * max8997-charger.c + * MAXIM 8997 charger interface driver + * + * Copyright (C) 2010 Samsung Electronics + * + * <ms925.kim@samsung.com> + * + * 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/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/power_supply.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/max8997.h> +#include <linux/mfd/max8997-private.h> + +/* MAX8997_REG_STATUS4 */ +#define DCINOK_SHIFT 1 +#define DCINOK_MASK (1 << DCINOK_SHIFT) +#define DETBAT_SHIFT 2 +#define DETBAT_MASK (1 << DETBAT_SHIFT) + +/* MAX8997_REG_MBCCTRL1 */ +#define TFCH_SHIFT 4 +#define TFCH_MASK (7 << TFCH_SHIFT) + +/* MAX8997_REG_MBCCTRL2 */ +#define MBCHOSTEN_SHIFT 6 +#define VCHGR_FC_SHIFT 7 +#define MBCHOSTEN_MASK (1 << MBCHOSTEN_SHIFT) +#define VCHGR_FC_MASK (1 << VCHGR_FC_SHIFT) + +/* MAX8997_REG_MBCCTRL3 */ +#define MBCCV_SHIFT 0 +#define MBCCV_MASK (0xF << MBCCV_SHIFT) + +/* MAX8997_REG_MBCCTRL4 */ +#define MBCICHFC_SHIFT 0 +#define MBCICHFC_MASK (0xF << MBCICHFC_SHIFT) + +/* MAX8997_REG_MBCCTRL5 */ +#define ITOPOFF_SHIFT 0 +#define ITOPOFF_MASK (0xF << ITOPOFF_SHIFT) + +/* MAX8997_REG_MBCCTRL6 */ +#define AUTOSTOP_SHIFT 5 +#define AUTOSTOP_MASK (1 << AUTOSTOP_SHIFT) + +/* MAX8997_REG_OTPCGHCVS */ +#define OTPCGHCVS_SHIFT 0 +#define OTPCGHCVS_MASK (3 << OTPCGHCVS_SHIFT) + +enum { + BAT_NOT_DETECTED, + BAT_DETECTED +}; + +struct chg_data { + struct device *dev; + struct max8997_dev *max8997; + struct power_supply psy_bat; + struct max8997_power_data *power; + int irq_topoff; + int irq_chgins; + int irq_chgrm; +}; + +static enum power_supply_property max8997_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, +}; + +/* vf check */ +static bool max8997_check_detbat(struct chg_data *chg) +{ + struct i2c_client *i2c = chg->max8997->i2c; + u8 data = 0; + int ret; + + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &data); + + if (ret < 0) { + dev_err(chg->dev, "%s: max8997_read_reg error(%d)\n", __func__, + ret); + return ret; + } + + if (data & DETBAT_MASK) + printk(KERN_WARNING "%s: batt not detected(0x%x)\n", __func__, + data); + + return data & DETBAT_MASK; +} + +/* whether charging enabled or not */ +static bool max8997_check_vdcin(struct chg_data *chg) +{ + struct i2c_client *i2c = chg->max8997->i2c; + u8 data = 0; + int ret; + + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &data); + + if (ret < 0) { + dev_err(chg->dev, "%s: max8997_read_reg error(%d)\n", __func__, + ret); + return ret; + } + + return data & DCINOK_MASK; +} + +static int max8997_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct chg_data *chg = container_of(psy, struct chg_data, psy_bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (max8997_check_vdcin(chg)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_PRESENT: + if (max8997_check_detbat(chg)) + val->intval = BAT_NOT_DETECTED; + else + val->intval = BAT_DETECTED; + break; + case POWER_SUPPLY_PROP_ONLINE: + /* battery is always online */ + val->intval = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max8997_disable_charging(struct chg_data *chg) +{ + struct i2c_client *i2c = chg->max8997->i2c; + int ret; + u8 mask; + + dev_info(chg->dev, "%s: disable charging\n", __func__); + mask = MBCHOSTEN_MASK | VCHGR_FC_MASK; + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL2, 0, mask); + if (ret < 0) + dev_err(chg->dev, "%s: fail update reg!!!\n", __func__); + + return ret; +} + +static int max8997_set_charging_current(struct chg_data *chg, int chg_current) +{ + struct i2c_client *i2c = chg->max8997->i2c; + int ret; + u8 val; + + if (chg_current < 200 || chg_current > 950) + return -EINVAL; + + val = ((chg_current - 200) / 50) & 0xf; + + dev_info(chg->dev, "%s: charging current=%d", __func__, chg_current); + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL4, + (val << MBCICHFC_SHIFT), MBCICHFC_MASK); + if (ret) + dev_err(chg->dev, "%s: fail to write chg current(%d)\n", + __func__, ret); + + return ret; +} + +static int max8997_enable_charging(struct chg_data *chg) +{ + struct i2c_client *i2c = chg->max8997->i2c; + int ret; + u8 val, mask; + + /* set auto stop disable */ + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL6, + (0 << AUTOSTOP_SHIFT), AUTOSTOP_MASK); + if (ret) + dev_err(chg->dev, "%s: failt to disable autostop(%d)\n", + __func__, ret); + + /* set fast charging enable and main battery charging enable */ + val = (1 << MBCHOSTEN_SHIFT) | (1 << VCHGR_FC_SHIFT); + mask = MBCHOSTEN_MASK | VCHGR_FC_MASK; + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL2, val, mask); + if (ret) + dev_err(chg->dev, "%s: failt to enable charging(%d)\n", + __func__, ret); + + return ret; +} + +/* TODO: remove this function later */ +static int max8997_enable_charging_x(struct chg_data *chg, int charge_type) +{ + struct i2c_client *i2c = chg->max8997->i2c; + int ret; + u8 val, mask; + + /* enable charging */ + if (charge_type == POWER_SUPPLY_CHARGE_TYPE_FAST) { + /* ac */ + dev_info(chg->dev, "%s: TA charging", __func__); + /* set fast charging current : 650mA */ + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL4, + (9 << MBCICHFC_SHIFT), MBCICHFC_MASK); + if (ret) + goto err; + + } else if (charge_type == POWER_SUPPLY_CHARGE_TYPE_TRICKLE) { + /* usb */ + dev_info(chg->dev, "%s: USB charging", __func__); + /* set fast charging current : 450mA */ + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL4, + (5 << MBCICHFC_SHIFT), MBCICHFC_MASK); + if (ret) + goto err; + } else { + dev_err(chg->dev, "%s: invalid arg\n", __func__); + ret = -EINVAL; + goto err; + } + + /* set auto stop disable */ + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL6, + (0 << AUTOSTOP_SHIFT), AUTOSTOP_MASK); + if (ret) + goto err; + + /* set fast charging enable and main battery charging enable */ + val = (1 << MBCHOSTEN_SHIFT) | (1 << VCHGR_FC_SHIFT); + mask = MBCHOSTEN_MASK | VCHGR_FC_MASK; + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL2, val, mask); + if (ret) + goto err; + + return 0; +err: + dev_err(chg->dev, "%s: max8997 update reg error!(%d)\n", __func__, ret); + return ret; +} + +static int max8997_chg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + int ret, reg_val; + struct chg_data *chg = container_of(psy, struct chg_data, psy_bat); + struct i2c_client *i2c = chg->max8997->i2c; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: /* TODO: remove this */ + if (val->intval == POWER_SUPPLY_CHARGE_TYPE_NONE) + ret = max8997_disable_charging(chg); + else + ret = max8997_enable_charging_x(chg, val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: /* Set charging current */ + ret = max8997_set_charging_current(chg, val->intval); + break; + case POWER_SUPPLY_PROP_STATUS: /* Enable/Disable charging */ + if (val->intval == POWER_SUPPLY_STATUS_CHARGING) + ret = max8997_enable_charging(chg); + else + ret = max8997_disable_charging(chg); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: /* Set recharging current */ + if (val->intval < 50 || val->intval > 200) { + dev_err(chg->dev, "%s: invalid topoff current(%d)\n", + __func__, val->intval); + return -EINVAL; + } + reg_val = (val->intval - 50) / 10; + + dev_info(chg->dev, "%s: Set toppoff current to 0x%x\n", + __func__, reg_val); + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL5, + (reg_val << ITOPOFF_SHIFT), ITOPOFF_MASK); + if (ret) { + dev_err(chg->dev, "%s: max8997 update reg error(%d)\n", + __func__, ret); + return ret; + } + break; + default: + return -EINVAL; + } + return ret; +} + +static irqreturn_t max8997_chg_topoff_irq(int irq, void *data) +{ + struct chg_data *chg = data; + int ret = 0; + + if (chg->power->topoff_cb) + ret = chg->power->topoff_cb(); + + if (ret) { + dev_err(chg->dev, "%s: error from topoff_cb(%d)\n", __func__, + ret); + return IRQ_HANDLED; + } + + dev_info(chg->dev, " Topoff IRQ occurred!\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t max8997_chg_charger_irq(int irq, void *data) +{ + struct chg_data *chg = data; + int ret = 0; + bool insert = false; + + insert = (irq == chg->irq_chgins); + + dev_info(chg->dev, "%s: charger IRQ(%d) occurred!\n", __func__, insert); + + if (chg->power->set_charger) + ret = chg->power->set_charger(insert); + + if (ret) { + dev_err(chg->dev, "%s: error from set_charger(%d)\n", __func__, + ret); + return IRQ_HANDLED; + } + + dev_info(chg->dev, "%s: charger IRQ(%d) occurred!\n", __func__, insert); + + return IRQ_HANDLED; +} + +static __devinit int max8997_charger_probe(struct platform_device *pdev) +{ + struct max8997_dev *max8997 = dev_get_drvdata(pdev->dev.parent); + struct max8997_platform_data *pdata = dev_get_platdata(max8997->dev); + struct i2c_client *i2c = max8997->i2c; + struct chg_data *chg; + int ret = 0; + + dev_info(&pdev->dev, "%s : MAX8997 Charger Driver Loading\n", __func__); + + chg = kzalloc(sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + chg->dev = &pdev->dev; + chg->max8997 = max8997; + chg->power = pdata->power; + chg->irq_topoff = max8997->irq_base + MAX8997_IRQ_TOPOFF; + + chg->irq_chgins = max8997->irq_base + MAX8997_IRQ_CHGINS; + chg->irq_chgrm = max8997->irq_base + MAX8997_IRQ_CHGRM; + + chg->psy_bat.name = "max8997-charger", + chg->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY, + chg->psy_bat.properties = max8997_battery_props, + chg->psy_bat.num_properties = ARRAY_SIZE(max8997_battery_props), + chg->psy_bat.get_property = max8997_chg_get_property, + chg->psy_bat.set_property = max8997_chg_set_property, + + platform_set_drvdata(pdev, chg); + + /* TODO: configure by platform data*/ + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL1, /* Disable */ + (0x7 << TFCH_SHIFT), TFCH_MASK); + if (ret < 0) + goto err_kfree; + + /* TODO: configure by platform data*/ + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL3, /* 4.2V */ + (0x0 << MBCCV_SHIFT), MBCCV_MASK); + if (ret < 0) + goto err_kfree; + + /* Set OVP voltage threshold */ + ret = max8997_update_reg(i2c, MAX8997_REG_OTPCGHCVS, + (0x01 << OTPCGHCVS_SHIFT), OTPCGHCVS_MASK); + if (ret < 0) + goto err_kfree; + + /* init power supplier framework */ + ret = power_supply_register(&pdev->dev, &chg->psy_bat); + if (ret) { + pr_err("Failed to register power supply psy_bat\n"); + goto err_kfree; + } + + ret = request_threaded_irq(chg->irq_topoff, NULL, + max8997_chg_topoff_irq, 0, "chg-topoff", chg); + if (ret < 0) + dev_err(&pdev->dev, "%s: fail to request topoff IRQ: %d: %d\n", + __func__, chg->irq_topoff, ret); + + ret = request_threaded_irq(chg->irq_chgins, NULL, + max8997_chg_charger_irq, 0, "chg-insert", chg); + if (ret < 0) + dev_err(&pdev->dev, "%s: fail to request chgins IRQ: %d: %d\n", + __func__, chg->irq_chgins, ret); + + ret = request_threaded_irq(chg->irq_chgrm, NULL, + max8997_chg_charger_irq, 0, "chg-remove", chg); + if (ret < 0) + dev_err(&pdev->dev, "%s: fail to request chgrm IRQ: %d: %d\n", + __func__, chg->irq_chgrm, ret); + + return 0; + +err_kfree: + kfree(chg); + return ret; +} + +static int __devexit max8997_charger_remove(struct platform_device *pdev) +{ + struct chg_data *chg = platform_get_drvdata(pdev); + + power_supply_unregister(&chg->psy_bat); + + kfree(chg); + + return 0; +} + +static int max8997_charger_suspend(struct device *dev) +{ + return 0; +} + +static int max8997_charger_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops max8997_charger_pm_ops = { + .suspend = max8997_charger_suspend, + .resume = max8997_charger_resume, +}; + +static struct platform_driver max8997_charger_driver = { + .driver = { + .name = "max8997-charger", + .owner = THIS_MODULE, + .pm = &max8997_charger_pm_ops, + }, + .probe = max8997_charger_probe, + .remove = __devexit_p(max8997_charger_remove), +}; + +static int __init max8997_charger_init(void) +{ + return platform_driver_register(&max8997_charger_driver); +} + +static void __exit max8997_charger_exit(void) +{ + platform_driver_register(&max8997_charger_driver); +} + +module_init(max8997_charger_init); +module_exit(max8997_charger_exit); + +MODULE_DESCRIPTION("MAXIM 8997 charger control driver"); +MODULE_AUTHOR("<ms925.kim@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max8997_charger_u1.c b/drivers/power/max8997_charger_u1.c new file mode 100644 index 00000000000..af4ef44ef51 --- /dev/null +++ b/drivers/power/max8997_charger_u1.c @@ -0,0 +1,478 @@ +/* + * max8997-charger.c + * MAXIM 8997 charger interface driver + * + * Copyright (C) 2010 Samsung Electronics + * + * <ms925.kim@samsung.com> + * + * 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/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/power_supply.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/max8997.h> +#include <linux/mfd/max8997-private.h> + +/* MAX8997_REG_STATUS4 */ +#define DCINOK_SHIFT 1 +#define DCINOK_MASK (1 << DCINOK_SHIFT) +#define DETBAT_SHIFT 2 +#define DETBAT_MASK (1 << DETBAT_SHIFT) + +/* MAX8997_REG_MBCCTRL1 */ +#define TFCH_SHIFT 4 +#define TFCH_MASK (7 << TFCH_SHIFT) + +/* MAX8997_REG_MBCCTRL2 */ +#define MBCHOSTEN_SHIFT 6 +#define VCHGR_FC_SHIFT 7 +#define MBCHOSTEN_MASK (1 << MBCHOSTEN_SHIFT) +#define VCHGR_FC_MASK (1 << VCHGR_FC_SHIFT) + +/* MAX8997_REG_MBCCTRL3 */ +#define MBCCV_SHIFT 0 +#define MBCCV_MASK (0xF << MBCCV_SHIFT) + +/* MAX8997_REG_MBCCTRL4 */ +#define MBCICHFC_SHIFT 0 +#define MBCICHFC_MASK (0xF << MBCICHFC_SHIFT) + +/* MAX8997_REG_MBCCTRL5 */ +#define ITOPOFF_SHIFT 0 +#define ITOPOFF_MASK (0xF << ITOPOFF_SHIFT) + +/* MAX8997_REG_MBCCTRL6 */ +#define AUTOSTOP_SHIFT 5 +#define AUTOSTOP_MASK (1 << AUTOSTOP_SHIFT) + +enum { + BAT_NOT_DETECTED, + BAT_DETECTED +}; + +struct chg_data { + struct device *dev; + struct max8997_dev *max8997; + struct power_supply psy_bat; + struct max8997_power_data *power; + int irq_topoff; + int irq_chgins; + int irq_chgrm; +}; + +static enum power_supply_property max8997_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, +}; + +/* vf check */ +static bool max8997_check_detbat(struct chg_data *chg) +{ + struct i2c_client *i2c = chg->max8997->i2c; + u8 data = 0; + int ret; + + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &data); + + if (ret < 0) { + dev_err(chg->dev, "%s: max8997_read_reg error(%d)\n", + __func__, ret); + return ret; + } + + if (data & DETBAT_MASK) + dev_info(chg->dev, "%s: batt not detected(0x%x)\n", + __func__, data); + + return data & DETBAT_MASK; +} + +/* whether charging enabled or not */ +static bool max8997_check_vdcin(struct chg_data *chg) +{ + struct i2c_client *i2c = chg->max8997->i2c; + u8 data = 0; + int ret; + + ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &data); + + if (ret < 0) { + dev_err(chg->dev, "%s: max8997_read_reg error(%d)\n", __func__, + ret); + return ret; + } + + return data & DCINOK_MASK; +} + +static int max8997_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct chg_data *chg = container_of(psy, struct chg_data, psy_bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (max8997_check_vdcin(chg)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_PRESENT: + if (max8997_check_detbat(chg)) + val->intval = BAT_NOT_DETECTED; + else + val->intval = BAT_DETECTED; + break; + case POWER_SUPPLY_PROP_ONLINE: + /* battery is always online */ + val->intval = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max8997_disable_charging(struct chg_data *chg) +{ + struct i2c_client *i2c = chg->max8997->i2c; + int ret; + u8 mask; + + dev_info(chg->dev, "%s: disable charging\n", __func__); + mask = MBCHOSTEN_MASK | VCHGR_FC_MASK; + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL2, 0, mask); + if (ret < 0) + dev_err(chg->dev, "%s: fail update reg!!!\n", __func__); + + return ret; +} + +static int max8997_set_charging_current(struct chg_data *chg, int chg_current) +{ + struct i2c_client *i2c = chg->max8997->i2c; + int ret; + u8 val; + + if (chg_current < 200 || chg_current > 950) + return -EINVAL; + + val = ((chg_current - 200) / 50) & 0xf; + + dev_info(chg->dev, "%s: charging current=%d", __func__, chg_current); + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL4, + (val << MBCICHFC_SHIFT), MBCICHFC_MASK); + if (ret) + dev_err(chg->dev, "%s: fail to write chg current(%d)\n", + __func__, ret); + + return ret; +} + +static int max8997_enable_charging(struct chg_data *chg) +{ + struct i2c_client *i2c = chg->max8997->i2c; + int ret; + u8 val, mask; + + /* set auto stop disable */ + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL6, + (0 << AUTOSTOP_SHIFT), AUTOSTOP_MASK); + if (ret) + dev_err(chg->dev, "%s: failt to disable autostop(%d)\n", + __func__, ret); + + /* set fast charging enable and main battery charging enable */ + val = (1 << MBCHOSTEN_SHIFT) | (1 << VCHGR_FC_SHIFT); + mask = MBCHOSTEN_MASK | VCHGR_FC_MASK; + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL2, val, mask); + if (ret) + dev_err(chg->dev, "%s: failt to enable charging(%d)\n", + __func__, ret); + + return ret; +} + +/* TODO: remove this function later */ +static int max8997_enable_charging_x(struct chg_data *chg, int charge_type) +{ + struct i2c_client *i2c = chg->max8997->i2c; + int ret; + u8 val, mask; + + /* enable charging */ + if (charge_type == POWER_SUPPLY_CHARGE_TYPE_FAST) { + /* ac */ + dev_info(chg->dev, "%s: TA charging", __func__); + /* set fast charging current : 650mA */ + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL4, + (9 << MBCICHFC_SHIFT), MBCICHFC_MASK); + if (ret) + goto err; + + } else if (charge_type == POWER_SUPPLY_CHARGE_TYPE_TRICKLE) { + /* usb */ + dev_info(chg->dev, "%s: USB charging", __func__); + /* set fast charging current : 450mA */ + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL4, + (5 << MBCICHFC_SHIFT), MBCICHFC_MASK); + if (ret) + goto err; + } else { + dev_err(chg->dev, "%s: invalid arg\n", __func__); + ret = -EINVAL; + goto err; + } + + /* set auto stop disable */ + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL6, + (0 << AUTOSTOP_SHIFT), AUTOSTOP_MASK); + if (ret) + goto err; + + /* set fast charging enable and main battery charging enable */ + val = (1 << MBCHOSTEN_SHIFT) | (1 << VCHGR_FC_SHIFT); + mask = MBCHOSTEN_MASK | VCHGR_FC_MASK; + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL2, val, mask); + if (ret) + goto err; + + return 0; +err: + dev_err(chg->dev, "%s: max8997 update reg error!(%d)\n", __func__, ret); + return ret; +} + +static int max8997_chg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + int ret, reg_val; + struct chg_data *chg = container_of(psy, struct chg_data, psy_bat); + struct i2c_client *i2c = chg->max8997->i2c; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: /* TODO: remove this */ + if (val->intval == POWER_SUPPLY_CHARGE_TYPE_NONE) + ret = max8997_disable_charging(chg); + else + ret = max8997_enable_charging_x(chg, val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: /* Set charging current */ + ret = max8997_set_charging_current(chg, val->intval); + break; + case POWER_SUPPLY_PROP_STATUS: /* Enable/Disable charging */ + if (val->intval == POWER_SUPPLY_STATUS_CHARGING) + ret = max8997_enable_charging(chg); + else + ret = max8997_disable_charging(chg); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: /* Set recharging current */ + if (val->intval < 50 || val->intval > 200) { + dev_err(chg->dev, "%s: invalid topoff current(%d)\n", + __func__, val->intval); + return -EINVAL; + } + reg_val = (val->intval - 50) / 10; + + dev_info(chg->dev, "%s: Set toppoff current to 0x%x\n", + __func__, reg_val); + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL5, + (reg_val << ITOPOFF_SHIFT), ITOPOFF_MASK); + if (ret) { + dev_err(chg->dev, "%s: max8997 update reg error(%d)\n", + __func__, ret); + return ret; + } + break; + default: + return -EINVAL; + } + return ret; +} + +static irqreturn_t max8997_chg_topoff_irq(int irq, void *data) +{ + struct chg_data *chg = data; + int ret = 0; + + if (chg->power->topoff_cb) + ret = chg->power->topoff_cb(); + + if (ret) { + dev_err(chg->dev, "%s: error from topoff_cb(%d)\n", __func__, + ret); + return IRQ_HANDLED; + } + + dev_info(chg->dev, " Topoff IRQ occurred!\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t max8997_chg_charger_irq(int irq, void *data) +{ + struct chg_data *chg = data; + int ret = 0; + bool insert = false; + + insert = (irq == chg->irq_chgins); + + dev_info(chg->dev, "%s: charger IRQ(%d) occurred!\n", __func__, insert); + + if (system_rev >= 0x3) + return IRQ_HANDLED; + + if (chg->power->set_charger) + ret = chg->power->set_charger(insert); + + if (ret) { + dev_err(chg->dev, "%s: error from set_charger(%d)\n", __func__, + ret); + return IRQ_HANDLED; + } + + dev_info(chg->dev, "%s: charger IRQ(%d) occurred!\n", __func__, insert); + + return IRQ_HANDLED; +} + +static __devinit int max8997_charger_probe(struct platform_device *pdev) +{ + struct max8997_dev *max8997 = dev_get_drvdata(pdev->dev.parent); + struct max8997_platform_data *pdata = dev_get_platdata(max8997->dev); + struct i2c_client *i2c = max8997->i2c; + struct chg_data *chg; + int ret = 0; + + dev_info(&pdev->dev, "%s : MAX8997 Charger Driver Loading\n", __func__); + + chg = kzalloc(sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + chg->dev = &pdev->dev; + chg->max8997 = max8997; + chg->power = pdata->power; + chg->irq_topoff = max8997->irq_base + MAX8997_IRQ_TOPOFF; + + chg->irq_chgins = max8997->irq_base + MAX8997_IRQ_CHGINS; + chg->irq_chgrm = max8997->irq_base + MAX8997_IRQ_CHGRM; + + chg->psy_bat.name = "max8997-charger", + chg->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY, + chg->psy_bat.properties = max8997_battery_props, + chg->psy_bat.num_properties = ARRAY_SIZE(max8997_battery_props), + chg->psy_bat.get_property = max8997_chg_get_property, + chg->psy_bat.set_property = max8997_chg_set_property, + + platform_set_drvdata(pdev, chg); + + /* TODO: configure by platform data*/ + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL1, /* Disable */ + (0x7 << TFCH_SHIFT), TFCH_MASK); + if (ret < 0) + goto err_kfree; + + /* TODO: configure by platform data*/ + ret = max8997_update_reg(i2c, MAX8997_REG_MBCCTRL3, /* 4.2V */ + (0x0 << MBCCV_SHIFT), MBCCV_MASK); + if (ret < 0) + goto err_kfree; + + /* init power supplier framework */ + ret = power_supply_register(&pdev->dev, &chg->psy_bat); + if (ret) { + pr_err("Failed to register power supply psy_bat\n"); + goto err_kfree; + } + + ret = request_threaded_irq(chg->irq_topoff, NULL, + max8997_chg_topoff_irq, 0, "chg-topoff", chg); + if (ret < 0) + dev_err(&pdev->dev, "%s: fail to request topoff IRQ: %d: %d\n", + __func__, chg->irq_topoff, ret); + + ret = request_threaded_irq(chg->irq_chgins, NULL, + max8997_chg_charger_irq, 0, "chg-insert", chg); + if (ret < 0) + dev_err(&pdev->dev, "%s: fail to request chgins IRQ: %d: %d\n", + __func__, chg->irq_chgins, ret); + + ret = request_threaded_irq(chg->irq_chgrm, NULL, + max8997_chg_charger_irq, 0, "chg-remove", chg); + if (ret < 0) + dev_err(&pdev->dev, "%s: fail to request chgrm IRQ: %d: %d\n", + __func__, chg->irq_chgrm, ret); + + return 0; + +err_kfree: + kfree(chg); + return ret; +} + +static int __devexit max8997_charger_remove(struct platform_device *pdev) +{ + struct chg_data *chg = platform_get_drvdata(pdev); + + power_supply_unregister(&chg->psy_bat); + + kfree(chg); + + return 0; +} + +static int max8997_charger_suspend(struct device *dev) +{ + return 0; +} + +static int max8997_charger_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops max8997_charger_pm_ops = { + .suspend = max8997_charger_suspend, + .resume = max8997_charger_resume, +}; + +static struct platform_driver max8997_charger_driver = { + .driver = { + .name = "max8997-charger", + .owner = THIS_MODULE, + .pm = &max8997_charger_pm_ops, + }, + .probe = max8997_charger_probe, + .remove = __devexit_p(max8997_charger_remove), +}; + +static int __init max8997_charger_init(void) +{ + return platform_driver_register(&max8997_charger_driver); +} + +static void __exit max8997_charger_exit(void) +{ + platform_driver_register(&max8997_charger_driver); +} + +module_init(max8997_charger_init); +module_exit(max8997_charger_exit); + +MODULE_DESCRIPTION("MAXIM 8997 charger control driver"); +MODULE_AUTHOR("<ms925.kim@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c index 69f8aa3a6a4..81b720107c3 100644 --- a/drivers/power/pda_power.c +++ b/drivers/power/pda_power.c @@ -14,6 +14,7 @@ #include <linux/platform_device.h> #include <linux/err.h> #include <linux/interrupt.h> +#include <linux/notifier.h> #include <linux/power_supply.h> #include <linux/pda_power.h> #include <linux/regulator/consumer.h> @@ -38,9 +39,8 @@ static struct timer_list supply_timer; static struct timer_list polling_timer; static int polling; -#ifdef CONFIG_USB_OTG_UTILS static struct otg_transceiver *transceiver; -#endif +static struct notifier_block otg_nb; static struct regulator *ac_draw; enum { @@ -222,7 +222,42 @@ static void polling_timer_func(unsigned long unused) #ifdef CONFIG_USB_OTG_UTILS static int otg_is_usb_online(void) { - return (transceiver->state == OTG_STATE_B_PERIPHERAL); + return (transceiver->last_event == USB_EVENT_VBUS || + transceiver->last_event == USB_EVENT_ENUMERATED); +} + +static int otg_is_ac_online(void) +{ + return (transceiver->last_event == USB_EVENT_CHARGER); +} + +static int otg_handle_notification(struct notifier_block *nb, + unsigned long event, void *unused) +{ + switch (event) { + case USB_EVENT_CHARGER: + ac_status = PDA_PSY_TO_CHANGE; + break; + case USB_EVENT_VBUS: + case USB_EVENT_ENUMERATED: + usb_status = PDA_PSY_TO_CHANGE; + break; + case USB_EVENT_NONE: + ac_status = PDA_PSY_TO_CHANGE; + usb_status = PDA_PSY_TO_CHANGE; + break; + default: + return NOTIFY_OK; + } + + /* + * Wait a bit before reading ac/usb line status and setting charger, + * because ac/usb status readings may lag from irq. + */ + mod_timer(&charger_timer, + jiffies + msecs_to_jiffies(pdata->wait_for_status)); + + return NOTIFY_OK; } #endif @@ -282,6 +317,14 @@ static int pda_power_probe(struct platform_device *pdev) ret = PTR_ERR(ac_draw); } + transceiver = otg_get_transceiver(); + if (transceiver && !pdata->is_usb_online) { + pdata->is_usb_online = otg_is_usb_online; + } + if (transceiver && !pdata->is_ac_online) { + pdata->is_ac_online = otg_is_ac_online; + } + if (pdata->is_ac_online) { ret = power_supply_register(&pdev->dev, &pda_psy_ac); if (ret) { @@ -303,13 +346,6 @@ static int pda_power_probe(struct platform_device *pdev) } } -#ifdef CONFIG_USB_OTG_UTILS - transceiver = otg_get_transceiver(); - if (transceiver && !pdata->is_usb_online) { - pdata->is_usb_online = otg_is_usb_online; - } -#endif - if (pdata->is_usb_online) { ret = power_supply_register(&pdev->dev, &pda_psy_usb); if (ret) { @@ -331,6 +367,16 @@ static int pda_power_probe(struct platform_device *pdev) } } + if (transceiver && pdata->use_otg_notifier) { + otg_nb.notifier_call = otg_handle_notification; + ret = otg_register_notifier(transceiver, &otg_nb); + if (ret) { + dev_err(dev, "failure to register otg notifier\n"); + goto otg_reg_notifier_failed; + } + polling = 0; + } + if (polling) { dev_dbg(dev, "will poll for status\n"); setup_timer(&polling_timer, polling_timer_func, 0); @@ -343,16 +389,17 @@ static int pda_power_probe(struct platform_device *pdev) return 0; +otg_reg_notifier_failed: + if (pdata->is_usb_online && usb_irq) + free_irq(usb_irq->start, &pda_psy_usb); usb_irq_failed: if (pdata->is_usb_online) power_supply_unregister(&pda_psy_usb); usb_supply_failed: if (pdata->is_ac_online && ac_irq) free_irq(ac_irq->start, &pda_psy_ac); -#ifdef CONFIG_USB_OTG_UTILS if (transceiver) otg_put_transceiver(transceiver); -#endif ac_irq_failed: if (pdata->is_ac_online) power_supply_unregister(&pda_psy_ac); diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 329b46b2327..03810ce5633 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -41,23 +41,40 @@ static int __power_supply_changed_work(struct device *dev, void *data) static void power_supply_changed_work(struct work_struct *work) { + unsigned long flags; struct power_supply *psy = container_of(work, struct power_supply, changed_work); dev_dbg(psy->dev, "%s\n", __func__); - class_for_each_device(power_supply_class, NULL, psy, - __power_supply_changed_work); + spin_lock_irqsave(&psy->changed_lock, flags); + if (psy->changed) { + psy->changed = false; + spin_unlock_irqrestore(&psy->changed_lock, flags); - power_supply_update_leds(psy); + class_for_each_device(power_supply_class, NULL, psy, + __power_supply_changed_work); - kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE); + power_supply_update_leds(psy); + + kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE); + spin_lock_irqsave(&psy->changed_lock, flags); + } + if (!psy->changed) + wake_unlock(&psy->work_wake_lock); + spin_unlock_irqrestore(&psy->changed_lock, flags); } void power_supply_changed(struct power_supply *psy) { + unsigned long flags; + dev_dbg(psy->dev, "%s\n", __func__); + spin_lock_irqsave(&psy->changed_lock, flags); + psy->changed = true; + wake_lock(&psy->work_wake_lock); + spin_unlock_irqrestore(&psy->changed_lock, flags); schedule_work(&psy->changed_work); } EXPORT_SYMBOL_GPL(power_supply_changed); @@ -181,6 +198,9 @@ int power_supply_register(struct device *parent, struct power_supply *psy) if (rc) goto device_add_failed; + spin_lock_init(&psy->changed_lock); + wake_lock_init(&psy->work_wake_lock, WAKE_LOCK_SUSPEND, "power-supply"); + rc = power_supply_create_triggers(psy); if (rc) goto create_triggers_failed; @@ -190,6 +210,7 @@ int power_supply_register(struct device *parent, struct power_supply *psy) goto success; create_triggers_failed: + wake_lock_destroy(&psy->work_wake_lock); device_del(dev); kobject_set_name_failed: device_add_failed: @@ -203,6 +224,7 @@ void power_supply_unregister(struct power_supply *psy) { cancel_work_sync(&psy->changed_work); power_supply_remove_triggers(psy); + wake_lock_destroy(&psy->work_wake_lock); device_unregister(psy->dev); } EXPORT_SYMBOL_GPL(power_supply_unregister); diff --git a/drivers/power/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c index d36c289aaef..9f869c1ad28 100644 --- a/drivers/power/s3c_adc_battery.c +++ b/drivers/power/s3c_adc_battery.c @@ -1,11 +1,13 @@ /* - * iPAQ h1930/h1940/rx1950 battery controller driver - * Copyright (c) Vasily Khoruzhick - * Based on h1940_battery.c by Arnaud Patard + * linux/drivers/power/s3c_adc_battery.c * - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file COPYING in the main directory of this archive for - * more details. + * Battery measurement code for samsung smdk platform. + * + * Copyright (C) 2011 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. * */ @@ -266,7 +268,7 @@ static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id) return IRQ_HANDLED; } -static int __init s3c_adc_bat_probe(struct platform_device *pdev) +static int __devinit s3c_adc_bat_probe(struct platform_device *pdev) { struct s3c_adc_client *client; struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; diff --git a/drivers/power/samsung_fake_battery.c b/drivers/power/samsung_fake_battery.c new file mode 100644 index 00000000000..d64ee99f921 --- /dev/null +++ b/drivers/power/samsung_fake_battery.c @@ -0,0 +1,546 @@ +/* + * linux/drivers/power/samsung_fake_battery.c + * + * Battery measurement code for samsung smdk platform. + * + * Copyright (C) 2011 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/kernel.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/irq.h> +#include <linux/wakelock.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <plat/gpio-cfg.h> + +#define FAKE_BAT_LEVEL 80 + +static struct wake_lock vbus_wake_lock; + +/* Prototypes */ +static ssize_t samsung_fake_bat_show_property(struct device *dev, + struct device_attribute *attr, + char *buf); +static ssize_t samsung_fake_bat_store_property(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +static struct device *dev; +static int samsung_fake_battery_initial; + +static char *status_text[] = { + [POWER_SUPPLY_STATUS_UNKNOWN] = "Unknown", + [POWER_SUPPLY_STATUS_CHARGING] = "Charging", + [POWER_SUPPLY_STATUS_DISCHARGING] = "Discharging", + [POWER_SUPPLY_STATUS_NOT_CHARGING] = "Not Charging", + [POWER_SUPPLY_STATUS_FULL] = "Full", +}; + +typedef enum { + CHARGER_BATTERY = 0, + CHARGER_USB, + CHARGER_AC, + CHARGER_DISCHARGE +} charger_type_t; + +struct battery_info { + u32 batt_id; /* Battery ID from ADC */ + u32 batt_vol; /* Battery voltage from ADC */ + u32 batt_vol_adc; /* Battery ADC value */ + u32 batt_vol_adc_cal; /* Battery ADC value (calibrated)*/ + u32 batt_temp; /* Battery Temperature (C) from ADC */ + u32 batt_temp_adc; /* Battery Temperature ADC value */ + u32 batt_temp_adc_cal; /* Battery Temperature ADC value (calibrated) */ + u32 batt_current; /* Battery current from ADC */ + u32 level; /* formula */ + u32 charging_source; /* 0: no cable, 1:usb, 2:AC */ + u32 charging_enabled; /* 0: Disable, 1: Enable */ + u32 batt_health; /* Battery Health (Authority) */ + u32 batt_is_full; /* 0 : Not full 1: Full */ +}; + +/* lock to protect the battery info */ +static DEFINE_MUTEX(work_lock); + +struct samsung_fake_battery_info { + int present; + int polling; + unsigned long polling_interval; + + struct battery_info bat_info; +}; +static struct samsung_fake_battery_info samsung_fake_bat_info; + +static int samsung_get_bat_level(struct power_supply *bat_ps) +{ + return FAKE_BAT_LEVEL; +} + +static int samsung_get_bat_vol(struct power_supply *bat_ps) +{ + int bat_vol = 0; + + return bat_vol; +} + +static u32 samsung_get_bat_health(void) +{ + return samsung_fake_bat_info.bat_info.batt_health; +} + +static int samsung_get_bat_temp(struct power_supply *bat_ps) +{ + int temp = 0; + + return temp; +} + +static int samsung_fake_bat_get_charging_status(void) +{ + charger_type_t charger = CHARGER_BATTERY; + int ret = 0; + + charger = samsung_fake_bat_info.bat_info.charging_source; + + switch (charger) { + case CHARGER_BATTERY: + ret = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case CHARGER_USB: + case CHARGER_AC: + if (samsung_fake_bat_info.bat_info.level == 100 && + samsung_fake_bat_info.bat_info.batt_is_full) + ret = POWER_SUPPLY_STATUS_FULL; + else + ret = POWER_SUPPLY_STATUS_CHARGING; + break; + case CHARGER_DISCHARGE: + ret = POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + ret = POWER_SUPPLY_STATUS_UNKNOWN; + } + dev_dbg(dev, "%s : %s\n", __func__, status_text[ret]); + + return ret; +} + +static int samsung_fake_bat_get_property(struct power_supply *bat_ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + dev_dbg(bat_ps->dev, "%s : psp = %d\n", __func__, psp); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = samsung_fake_bat_get_charging_status(); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = samsung_get_bat_health(); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = samsung_fake_bat_info.present; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = samsung_fake_bat_info.bat_info.level; + dev_dbg(dev, "%s : level = %d\n", __func__, + val->intval); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = samsung_fake_bat_info.bat_info.batt_temp; + dev_dbg(bat_ps->dev, "%s : temp = %d\n", __func__, + val->intval); + break; + default: + return -EINVAL; + } + return 0; +} + +static int samsung_power_get_property(struct power_supply *bat_ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + charger_type_t charger; + + dev_dbg(bat_ps->dev, "%s : psp = %d\n", __func__, psp); + + charger = samsung_fake_bat_info.bat_info.charging_source; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (bat_ps->type == POWER_SUPPLY_TYPE_MAINS) + val->intval = (charger == CHARGER_AC ? 1 : 0); + else if (bat_ps->type == POWER_SUPPLY_TYPE_USB) + val->intval = (charger == CHARGER_USB ? 1 : 0); + else + val->intval = 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +#define SAMSUNG_FAKE_BAT_ATTR(_name) \ +{ \ + .attr = { .name = #_name,}, \ + .show = samsung_fake_bat_show_property, \ + .store = samsung_fake_bat_store_property, \ +} + +static struct device_attribute samsung_fake_battery_attrs[] = { + SAMSUNG_FAKE_BAT_ATTR(batt_vol), + SAMSUNG_FAKE_BAT_ATTR(batt_vol_adc), + SAMSUNG_FAKE_BAT_ATTR(batt_vol_adc_cal), + SAMSUNG_FAKE_BAT_ATTR(batt_temp), + SAMSUNG_FAKE_BAT_ATTR(batt_temp_adc), + SAMSUNG_FAKE_BAT_ATTR(batt_temp_adc_cal), +}; + +enum { + BATT_VOL = 0, + BATT_VOL_ADC, + BATT_VOL_ADC_CAL, + BATT_TEMP, + BATT_TEMP_ADC, + BATT_TEMP_ADC_CAL, +}; + +static int samsung_fake_bat_create_attrs(struct device * dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(samsung_fake_battery_attrs); i++) { + rc = device_create_file(dev, &samsung_fake_battery_attrs[i]); + if (rc) + goto attrs_failed; + } + goto succeed; + +attrs_failed: + while (i--) + device_remove_file(dev, &samsung_fake_battery_attrs[i]); +succeed: + return rc; +} + +static ssize_t samsung_fake_bat_show_property(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int i = 0; + const ptrdiff_t off = attr - samsung_fake_battery_attrs; + + switch (off) { + case BATT_VOL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + samsung_fake_bat_info.bat_info.batt_vol); + break; + case BATT_VOL_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + samsung_fake_bat_info.bat_info.batt_vol_adc); + break; + case BATT_VOL_ADC_CAL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + samsung_fake_bat_info.bat_info.batt_vol_adc_cal); + break; + case BATT_TEMP: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + samsung_fake_bat_info.bat_info.batt_temp); + break; + case BATT_TEMP_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + samsung_fake_bat_info.bat_info.batt_temp_adc); + break; + case BATT_TEMP_ADC_CAL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + samsung_fake_bat_info.bat_info.batt_temp_adc_cal); + break; + default: + i = -EINVAL; + } + + return i; +} + +static ssize_t samsung_fake_bat_store_property(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int x = 0; + int ret = 0; + const ptrdiff_t off = attr - samsung_fake_battery_attrs; + + switch (off) { + case BATT_VOL_ADC_CAL: + if (sscanf(buf, "%d\n", &x) == 1) { + samsung_fake_bat_info.bat_info.batt_vol_adc_cal = x; + ret = count; + } + dev_info(dev, "%s : batt_vol_adc_cal = %d\n", __func__, x); + break; + case BATT_TEMP_ADC_CAL: + if (sscanf(buf, "%d\n", &x) == 1) { + samsung_fake_bat_info.bat_info.batt_temp_adc_cal = x; + ret = count; + } + dev_info(dev, "%s : batt_temp_adc_cal = %d\n", __func__, x); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static enum power_supply_property samsung_fake_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static enum power_supply_property samsung_power_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *supply_list[] = { + "battery", +}; + +static struct power_supply samsung_power_supplies[] = { + { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = samsung_fake_battery_properties, + .num_properties = ARRAY_SIZE(samsung_fake_battery_properties), + .get_property = samsung_fake_bat_get_property, + }, { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .supplied_to = supply_list, + .num_supplicants = ARRAY_SIZE(supply_list), + .properties = samsung_power_properties, + .num_properties = ARRAY_SIZE(samsung_power_properties), + .get_property = samsung_power_get_property, + }, { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .supplied_to = supply_list, + .num_supplicants = ARRAY_SIZE(supply_list), + .properties = samsung_power_properties, + .num_properties = ARRAY_SIZE(samsung_power_properties), + .get_property = samsung_power_get_property, + }, +}; + +static int samsung_cable_status_update(int status) +{ + int ret = 0; + charger_type_t source = CHARGER_BATTERY; + + dev_dbg(dev, "%s\n", __func__); + + if (!samsung_fake_battery_initial) + return -EPERM; + + switch (status) { + case CHARGER_BATTERY: + dev_dbg(dev, "%s : cable NOT PRESENT\n", __func__); + samsung_fake_bat_info.bat_info.charging_source = CHARGER_BATTERY; + break; + case CHARGER_USB: + dev_dbg(dev, "%s : cable USB\n", __func__); + samsung_fake_bat_info.bat_info.charging_source = CHARGER_USB; + break; + case CHARGER_AC: + dev_dbg(dev, "%s : cable AC\n", __func__); + samsung_fake_bat_info.bat_info.charging_source = CHARGER_AC; + break; + case CHARGER_DISCHARGE: + dev_dbg(dev, "%s : Discharge\n", __func__); + samsung_fake_bat_info.bat_info.charging_source = CHARGER_DISCHARGE; + break; + default: + dev_err(dev, "%s : Nat supported status\n", __func__); + ret = -EINVAL; + } + source = samsung_fake_bat_info.bat_info.charging_source; + + if (source == CHARGER_USB || source == CHARGER_AC) + wake_lock(&vbus_wake_lock); + else + wake_lock_timeout(&vbus_wake_lock, HZ / 2); + + /* if the power source changes, all power supplies may change state */ + power_supply_changed(&samsung_power_supplies[CHARGER_BATTERY]); + + dev_dbg(dev, "%s : call power_supply_changed\n", __func__); + return ret; +} + +static void samsung_fake_bat_status_update(struct power_supply *bat_ps) +{ + int old_level, old_temp, old_is_full; + dev_dbg(dev, "%s ++\n", __func__); + + if (!samsung_fake_battery_initial) + return; + + mutex_lock(&work_lock); + + old_temp = samsung_fake_bat_info.bat_info.batt_temp; + old_level = samsung_fake_bat_info.bat_info.level; + old_is_full = samsung_fake_bat_info.bat_info.batt_is_full; + samsung_fake_bat_info.bat_info.batt_temp = samsung_get_bat_temp(bat_ps); + samsung_fake_bat_info.bat_info.level = samsung_get_bat_level(bat_ps); + samsung_fake_bat_info.bat_info.batt_vol = samsung_get_bat_vol(bat_ps); + + if (old_level != samsung_fake_bat_info.bat_info.level || + old_temp != samsung_fake_bat_info.bat_info.batt_temp || + old_is_full != samsung_fake_bat_info.bat_info.batt_is_full) { + power_supply_changed(bat_ps); + dev_dbg(dev, "%s : call power_supply_changed\n", __func__); + } + + mutex_unlock(&work_lock); + dev_dbg(dev, "%s --\n", __func__); +} + +void samsung_cable_check_status(int flag) +{ + charger_type_t status = 0; + + if (flag == 0) + status = CHARGER_BATTERY; + else + status = CHARGER_USB; + + samsung_cable_status_update(status); +} +EXPORT_SYMBOL(samsung_cable_check_status); + +#ifdef CONFIG_PM +static int samsung_fake_bat_suspend(struct device *dev) +{ + return 0; +} + +static void samsung_fake_bat_resume(struct device *dev) +{ +} +#else +#define samsung_fake_bat_suspend NULL +#define samsung_fake_bat_resume NULL +#endif /* CONFIG_PM */ + +static int __devinit samsung_fake_bat_probe(struct platform_device *pdev) +{ + int i; + int ret = 0; + + dev = &pdev->dev; + dev_info(dev, "%s\n", __func__); + + samsung_fake_bat_info.present = 1; + + samsung_fake_bat_info.bat_info.batt_id = 0; + samsung_fake_bat_info.bat_info.batt_vol = 0; + samsung_fake_bat_info.bat_info.batt_vol_adc = 0; + samsung_fake_bat_info.bat_info.batt_vol_adc_cal = 0; + samsung_fake_bat_info.bat_info.batt_temp = 0; + samsung_fake_bat_info.bat_info.batt_temp_adc = 0; + samsung_fake_bat_info.bat_info.batt_temp_adc_cal = 0; + samsung_fake_bat_info.bat_info.batt_current = 0; + samsung_fake_bat_info.bat_info.level = 0; + samsung_fake_bat_info.bat_info.charging_source = CHARGER_BATTERY; + samsung_fake_bat_info.bat_info.charging_enabled = 0; + samsung_fake_bat_info.bat_info.batt_health = POWER_SUPPLY_HEALTH_GOOD; + + /* init power supplier framework */ + for (i = 0; i < ARRAY_SIZE(samsung_power_supplies); i++) { + ret = power_supply_register(&pdev->dev, + &samsung_power_supplies[i]); + if (ret) { + dev_err(dev, "Failed to register" + "power supply %d,%d\n", i, ret); + goto __end__; + } + } + + /* create sec detail attributes */ + samsung_fake_bat_create_attrs(samsung_power_supplies[CHARGER_BATTERY].dev); + + samsung_fake_battery_initial = 1; + + samsung_fake_bat_status_update( + &samsung_power_supplies[CHARGER_BATTERY]); + +__end__: + return ret; +} + +static int __devexit samsung_fake_bat_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(samsung_power_supplies); i++) + power_supply_unregister(&samsung_power_supplies[i]); + + return 0; +} + +static const struct dev_pm_ops samsung_fake_bat_pm_ops = { + .prepare = samsung_fake_bat_suspend, + .complete = samsung_fake_bat_resume, +}; + +static struct platform_driver samsung_fake_bat_driver = { + .driver = { + .name = "samsung-fake-battery", + .owner = THIS_MODULE, + .pm = &samsung_fake_bat_pm_ops, + }, + .probe = samsung_fake_bat_probe, + .remove = __devexit_p(samsung_fake_bat_remove), +}; + +static int __init samsung_fake_bat_init(void) +{ + wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present"); + + return platform_driver_register(&samsung_fake_bat_driver); +} + +static void __exit samsung_fake_bat_exit(void) +{ + platform_driver_unregister(&samsung_fake_bat_driver); +} + +module_init(samsung_fake_bat_init); +module_exit(samsung_fake_bat_exit); + +MODULE_AUTHOR("HuiSung Kang <hs1218.kang@samsung.com>"); +MODULE_DESCRIPTION("SAMSUNG Fake battery driver for SMDK Board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/sec_battery_px.c b/drivers/power/sec_battery_px.c new file mode 100644 index 00000000000..0b5d8d4056f --- /dev/null +++ b/drivers/power/sec_battery_px.c @@ -0,0 +1,2148 @@ +/* + * sec_battery.c + * charger systems for lithium-ion (Li+) batteries + * + * Copyright (C) 2010 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/kernel.h> +#include <linux/module.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/power_supply.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/irq.h> +#include <linux/wakelock.h> +#include <linux/android_alarm.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <linux/earlysuspend.h> +#include <linux/io.h> +#include <mach/gpio.h> +#include <linux/power/sec_battery_px.h> +#include <linux/power/max17042_fuelgauge_px.h> +#include <linux/mfd/max8997.h> + +#include <plat/adc.h> +#include <mach/usb_switch.h> + +#define FAST_POLL 40 /* 40 sec */ +#define SLOW_POLL (30*60) /* 30 min */ + +/* SIOP */ +#define CHARGING_CURRENT_HIGH_LOW_STANDARD 450 +#define CHARGING_CURRENT_USB 500 +#define SIOP_ACTIVE_CHARGE_CURRENT 450 +#define SIOP_DEACTIVE_CHARGE_CURRENT 1500 + +enum { + SIOP_DEACTIVE = 0, + SIOP_ACTIVE, +}; + +#if defined(CONFIG_MACH_P2) +#define P2_CHARGING_FEATURE_02 /* SMB136 + MAX17042, Cable detect by TA_nCon */ +#endif + +#if defined(CONFIG_MACH_P4NOTE) +#define P4_CHARGING_FEATURE_01 /* SMB347 + MAX17042, use TA_nCON */ +#endif + +#if defined(CONFIG_MACH_P8) || defined(CONFIG_MACH_P8LTE) +#define P8_CHARGING_FEATURE_01 /* MAX8903 + MAX17042, use TA_nCON */ +#endif + +static char *supply_list[] = { + "battery", +}; + +static enum power_supply_property sec_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static enum power_supply_property sec_power_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +struct battery_info { + u32 batt_id; /* Battery ID from ADC */ + s32 batt_vol; /* Battery voltage from ADC */ + s32 batt_temp; /* Battery Temperature (C) from ADC */ + s32 batt_current; /* Battery current from ADC */ + u32 level; /* formula */ + u32 charging_source; /* 0: no cable, 1:usb, 2:AC */ + u32 charging_enabled; /* 0: Disable, 1: Enable */ + u32 charging_current; /* Charging current */ + u32 batt_full_count; /* full checked count */ + u32 batt_health; /* Battery Health (Authority) */ + u32 batt_is_full; /* 0 : Not full 1: Full */ + u32 batt_is_recharging; /* 0 : Not recharging 1: Recharging */ + u32 batt_improper_ta; /* 1: improper ta */ + u32 abstimer_is_active; /* 0 : Not active 1: Active */ + u32 siop_activated; /* 0 : Not active 1: Active */ +}; + +struct battery_data { + struct device *dev; + struct sec_battery_platform_data *pdata; + struct battery_info info; + struct power_supply psy_battery; + struct power_supply psy_usb; + struct power_supply psy_ac; + struct workqueue_struct *sec_TA_workqueue; + struct work_struct battery_work; + struct work_struct cable_work; + struct delayed_work TA_work; + struct delayed_work fuelgauge_work; + struct delayed_work fuelgauge_recovery_work; + struct delayed_work fullcharging_work; + struct delayed_work full_comp_work; + struct alarm alarm; + struct mutex work_lock; + struct wake_lock vbus_wake_lock; + struct wake_lock cable_wake_lock; + struct wake_lock work_wake_lock; + struct wake_lock fullcharge_wake_lock; + struct s3c_adc_client *padc; + +#ifdef __TEST_DEVICE_DRIVER__ + struct wake_lock wake_lock_for_dev; +#endif /* __TEST_DEVICE_DRIVER__ */ + enum charger_type current_cable_status; + enum charger_type previous_cable_status; + int present; + unsigned int device_state; + unsigned int charging_mode_booting; + int sec_battery_initial; + int low_batt_boot_flag; + u32 full_charge_comp_recharge_info; + unsigned long charging_start_time; + int connect_irq; + int charging_irq; + bool slow_poll; + ktime_t last_poll; + int fg_skip; + int fg_skip_cnt; + int fg_chk_cnt; + int recharging_cnt; + int previous_charging_status; + int full_check_flag; + bool is_first_check; + /* 0:Default, 1:Only charger, 2:Only PMIC */ + int cable_detect_source; + int pmic_cable_state; + bool is_low_batt_alarm; +}; + +struct battery_data *debug_batterydata; + +static void sec_cable_charging(struct battery_data *battery); +static void sec_set_chg_en(struct battery_data *battery, int enable); +static void sec_cable_changed(struct battery_data *battery); +static ssize_t sec_bat_show_property(struct device *dev, + struct device_attribute *attr, + char *buf); +static ssize_t sec_bat_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +#ifdef __TEST_DEVICE_DRIVER__ +static ssize_t sec_batt_test_show_property(struct device *dev, + struct device_attribute *attr, + char *buf); +static ssize_t sec_batt_test_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +static int bat_temp_force_state; +#endif /* __TEST_DEVICE_DRIVER__ */ + +static bool check_UV_charging_case(void); +static void sec_bat_status_update(struct power_supply *bat_ps); + +static int get_cached_charging_status(struct battery_data *battery) +{ + struct power_supply *psy = power_supply_get_by_name("max8997-charger"); + union power_supply_propval value; + u32 ta_con = 0; + + if (!psy) { + pr_err("%s: fail to get max8997-charger ps\n", __func__); + return 0; + } + + if (psy->get_property) { + psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value); + if (value.intval == POWER_SUPPLY_STATUS_CHARGING) + ta_con = 1; + } + + pr_info("PMIC detect : %s\n", ta_con ? "TA connected" : + "TA NOT connected"); + return ta_con; +} + +static int check_ta_conn(struct battery_data *battery) +{ + u32 value; + + if (battery->cable_detect_source == 2) { + value = get_cached_charging_status(battery); + pr_info("Cable detect from PMIC instead of TA_nConnected(%d)\n", + value); + return value; + } + value = gpio_get_value(battery->pdata->charger.connect_line); + +#if defined(P4_CHARGING_FEATURE_01) +#if defined(CONFIG_MACH_P4NOTE) + value = !value; +#else + /* P4C H/W rev0.2, 0.3, 0.4 : High active */ + if ((system_rev >= 2) && (system_rev <= 5)) + value = !value; +#endif +#endif + pr_debug("Charger detect : %s\n", value ? "TA NOT connected" : + "TA connected"); + return !value; +} + +#ifdef CONFIG_SAMSUNG_LPM_MODE +static void lpm_mode_check(struct battery_data *battery) +{ + battery->charging_mode_booting = \ + battery->pdata->check_lp_charging_boot(); + pr_info("%s : charging_mode_booting(%d)\n", __func__, + battery->charging_mode_booting); +} +#endif + +#if defined(P4_CHARGING_FEATURE_01) || defined(P8_CHARGING_FEATURE_01) +#define CHARGING_CURRENT_HIGH 1500 +#define CHARGING_CURRENT_LOW CHARGING_CURRENT_USB +static void sec_set_charging(struct battery_data *battery, int charger_type) +{ + switch (charger_type) { + case CHARGER_AC: + gpio_set_value(battery->pdata->charger.currentset_line, 1); + battery->info.charging_current = CHARGING_CURRENT_HIGH; + break; + default: + /* For P8 Audio station, the charging current driven by TA + path is not as per the required spec hence we drive the + input charging current by USB path for Sound Station.*/ + gpio_set_value(battery->pdata->charger.currentset_line, 0); + battery->info.charging_current = CHARGING_CURRENT_LOW; + break; + } + pr_info("%s: Set charging current as %dmA.\n", __func__, + battery->info.charging_current); +} +#endif + +static void sec_battery_alarm(struct alarm *alarm) +{ + struct battery_data *battery = + container_of(alarm, struct battery_data, alarm); + + pr_debug("%s : sec_battery_alarm.....\n", __func__); + wake_lock(&battery->work_wake_lock); + schedule_work(&battery->battery_work); +} + +static void sec_program_alarm(struct battery_data *battery, int seconds) +{ + ktime_t low_interval = ktime_set(seconds - 10, 0); + ktime_t slack = ktime_set(20, 0); + ktime_t next; + + pr_debug("%s : sec_program_alarm.....\n", __func__); + next = ktime_add(battery->last_poll, low_interval); + alarm_start_range(&battery->alarm, next, ktime_add(next, slack)); +} + +static +enum charger_type sec_get_dedicted_charger_type(struct battery_data *battery) +{ + /* N.B. If check_ta_conn() is true something valid is + connceted to the device for charging. + By default this something is considered to be USB.*/ + enum charger_type result = CHARGER_USB; + int avg_vol = 0; + int adc_1, adc_2; + int vol_1, vol_2; + int accessory_line; + + mutex_lock(&battery->work_lock); + + /* ADC check margin (300~500ms) */ + msleep(150); + + usb_switch_lock(); + usb_switch_set_path(USB_PATH_ADCCHECK); + +#if defined(CONFIG_STMPE811_ADC) + vol_1 = stmpe811_get_adc_data(6); + vol_2 = stmpe811_get_adc_data(6); +#else + /* ADC values Update Margin */ + msleep(30); + + adc_1 = s3c_adc_read(battery->padc, 5); + adc_2 = s3c_adc_read(battery->padc, 5); + + vol_1 = (adc_1 * 3300) / 4095; + vol_2 = (adc_2 * 3300) / 4095; +#endif + avg_vol = (vol_1 + vol_2)/2; + + if ((avg_vol > 800) && (avg_vol < 1800)) { +#if defined(P4_CHARGING_FEATURE_01) + accessory_line = gpio_get_value( + battery->pdata->charger.accessory_line); + pr_info("%s: accessory line(%d)\n", __func__, accessory_line); + + if (accessory_line == 0) /* HDMI dock cable connected*/ + result = CHARGER_DOCK; + else + result = CHARGER_AC; +#else + result = CHARGER_AC; /* TA connected. */ +#endif + } else if ((avg_vol > 550) && (avg_vol < 700)) + result = CHARGER_MISC; /* Samsung Audio Station connected.*/ + else + result = CHARGER_USB; + + usb_switch_clr_path(USB_PATH_ADCCHECK); + usb_switch_unlock(); + + mutex_unlock(&battery->work_lock); + + pr_info("%s : result(%d), avg_vol(%d)\n", __func__, result, avg_vol); + return result; +} + +static void sec_get_cable_status(struct battery_data *battery) +{ + if (check_ta_conn(battery)) { + battery->current_cable_status = + sec_get_dedicted_charger_type(battery); + battery->info.batt_full_count = 0; + } else { + battery->current_cable_status = CHARGER_BATTERY; + battery->info.batt_improper_ta = 0; + } + + if (battery->pdata->inform_charger_connection) + battery->pdata->inform_charger_connection( + battery->current_cable_status); + + pr_info("%s: current_status : %d\n", + __func__, battery->current_cable_status); +} + +/* Processes when the interrupt line is asserted. */ +#define TA_DISCONNECT_RECHECK_TIME 300 +static void sec_TA_work_handler(struct work_struct *work) +{ + struct battery_data *battery = + container_of(work, struct battery_data, TA_work.work); + int ta_state = 0; + + /* Prevent unstable VBUS signal from PC */ + ta_state = check_ta_conn(battery); + if (ta_state == 0) { + msleep(TA_DISCONNECT_RECHECK_TIME); + if (ta_state != check_ta_conn(battery)) { + pr_info("%s: unstable ta_state(%d), ignore it.\n", + __func__, ta_state); + enable_irq(battery->connect_irq); + return; + } + } + pr_info("%s: ta_state(%d)\n", __func__, ta_state); + + sec_get_cable_status(battery); + sec_cable_changed(battery); + + enable_irq(battery->connect_irq); +} + +static irqreturn_t sec_TA_nCHG_interrupt_handler(int irq, void *arg) +{ + struct battery_data *battery = (struct battery_data *)arg; + pr_info("%s(%d)\n", __func__, + gpio_get_value(battery->pdata->charger.fullcharge_line)); + + disable_irq_nosync(irq); + cancel_delayed_work(&battery->fullcharging_work); + schedule_delayed_work(&battery->fullcharging_work, + msecs_to_jiffies(300)); + wake_lock_timeout(&battery->fullcharge_wake_lock, HZ * 30); + + return IRQ_HANDLED; +} + +static irqreturn_t sec_TA_nCon_interrupt_handler(int irq, void *arg) +{ + struct battery_data *battery = (struct battery_data *)arg; + pr_info("%s(%d)\n", __func__, + gpio_get_value(battery->pdata->charger.connect_line)); + + disable_irq_nosync(irq); + cancel_delayed_work(&battery->TA_work); + queue_delayed_work(battery->sec_TA_workqueue, &battery->TA_work, 10); + wake_lock_timeout(&battery->cable_wake_lock, HZ * 2); + + return IRQ_HANDLED; +} + +static u32 get_charger_status(struct battery_data *battery) +{ + if (check_ta_conn(battery)) { + if (battery->current_cable_status == CHARGER_AC) + pr_info("Charger Status : AC\n"); + else if (battery->current_cable_status == CHARGER_DOCK) + pr_info("Charger Status : HDMI_DOCK\n"); + else if (battery->current_cable_status == CHARGER_MISC) + pr_info("Charger Status : MISC\n"); + else + pr_info("Charger Status : USB\n"); + return 1; + } else { + pr_info("Charger Status : Battery\n"); + return 0; + } +} + +static int is_over_abs_time(struct battery_data *battery) +{ + unsigned int total_time; + ktime_t ktime; + struct timespec cur_time; + + if (!battery->charging_start_time) + return 0; + + ktime = alarm_get_elapsed_realtime(); + cur_time = ktime_to_timespec(ktime); + + if (battery->info.batt_is_recharging) + total_time = battery->pdata->recharge_duration; + else + total_time = battery->pdata->charge_duration; + + if (battery->charging_start_time + total_time < cur_time.tv_sec) { + pr_info("Charging time out"); + return 1; + } else + return 0; +} + +static int sec_get_bat_level(struct power_supply *bat_ps) +{ + struct battery_data *battery = container_of(bat_ps, + struct battery_data, psy_battery); + int fg_soc; + int fg_vfsoc; + int fg_vcell; + int fg_current; + int avg_current; + int recover_flag = 0; + + recover_flag = fg_check_cap_corruption(); + + /* check VFcapacity every five minutes */ + if (!(battery->fg_chk_cnt++ % 10)) { + fg_check_vf_fullcap_range(); + battery->fg_chk_cnt = 1; + } + + fg_soc = get_fuelgauge_value(FG_LEVEL); + if (fg_soc < 0) { + pr_info("Can't read soc!!!"); + fg_soc = battery->info.level; + } + + if (!battery->pdata->check_jig_status() && \ + !max17042_chip_data->info.low_batt_comp_flag) { + if (((fg_soc+5) < max17042_chip_data->info.prev_repsoc) || + (fg_soc > (max17042_chip_data->info.prev_repsoc+5))) + battery->fg_skip = 1; + } + + /* skip one time (maximum 30 seconds) because of corruption. */ + if (battery->fg_skip) { + pr_info("%s: skip update until corruption check " + "is done (fg_skip_cnt:%d)\n", + __func__, ++battery->fg_skip_cnt); + fg_soc = battery->info.level; + if (recover_flag || battery->fg_skip_cnt > 10) { + battery->fg_skip = 0; + battery->fg_skip_cnt = 0; + } + } + + if (battery->low_batt_boot_flag) { + fg_soc = 0; + + if (check_ta_conn(battery) && !check_UV_charging_case()) { + fg_adjust_capacity(); + battery->low_batt_boot_flag = 0; + } + + if (!check_ta_conn(battery)) + battery->low_batt_boot_flag = 0; + } + + fg_vcell = get_fuelgauge_value(FG_VOLTAGE); + if (fg_vcell < 0) { + pr_info("Can't read vcell!!!"); + fg_vcell = battery->info.batt_vol; + } else + battery->info.batt_vol = fg_vcell; + + fg_current = get_fuelgauge_value(FG_CURRENT); + battery->info.batt_current = fg_current; + avg_current = get_fuelgauge_value(FG_CURRENT_AVG); + fg_vfsoc = get_fuelgauge_value(FG_VF_SOC); + +/* P4-Creative does not set full flag by force */ +#if !defined(CONFIG_MACH_P4NOTE) + /* Algorithm for reducing time to fully charged (from MAXIM) */ + if (battery->info.charging_enabled && /* Charging is enabled */ + !battery->info.batt_is_recharging && /* Not Recharging */ + ((battery->info.charging_source == CHARGER_AC) || + (battery->info.charging_source == CHARGER_MISC)) && + !battery->is_first_check && /* Skip the first check */ + (fg_vfsoc > 70 && (fg_current > 20 && fg_current < 250) && + (avg_current > 20 && avg_current < 260))) { + + if (battery->full_check_flag == 2) { + pr_info("%s: force fully charged SOC !! (%d)", __func__, + battery->full_check_flag); + fg_set_full_charged(); + fg_soc = get_fuelgauge_value(FG_LEVEL); + } else if (battery->full_check_flag < 2) + pr_info("%s: full_check_flag (%d)", __func__, + battery->full_check_flag); + + /* prevent overflow */ + if (battery->full_check_flag++ > 10000) + battery->full_check_flag = 3; + } else + battery->full_check_flag = 0; +#endif + + if (battery->info.charging_source == CHARGER_AC && + battery->info.batt_improper_ta == 0) { + if (is_over_abs_time(battery)) { + battery->info.abstimer_is_active = 1; + pr_info("%s: Charging time is over. Active abs timer.", + __func__); + pr_info(" VCELL(%d), SOC(%d)\n", fg_vcell, fg_soc); + sec_set_chg_en(battery, 0); + goto __end__; + } + } else { + if (battery->info.abstimer_is_active) { + battery->info.abstimer_is_active = 0; + pr_info("%s: Inactive abs timer.\n", __func__); + } + } + + if ((fg_vcell <= battery->pdata->recharge_voltage) || + (fg_vcell <= 4000)) { + if (battery->info.batt_is_full + || (battery->info.abstimer_is_active + && !battery->info.charging_enabled)) { + if (++battery->recharging_cnt > 1) { + pr_info("%s: Recharging start(%d ? %d).\n", + __func__, + fg_vcell, + battery->pdata->recharge_voltage); + battery->info.batt_is_recharging = 1; + sec_set_chg_en(battery, 1); + battery->recharging_cnt = 0; + } + } else + battery->recharging_cnt = 0; + } else + battery->recharging_cnt = 0; + + if (fg_soc > 100) + fg_soc = 100; + + /* Checks vcell level and tries to compensate SOC if needed.*/ + /* If jig cable is connected, then skip low batt compensation check. */ + if (!battery->pdata->check_jig_status() && \ + !battery->info.charging_enabled) + fg_soc = low_batt_compensation(fg_soc, fg_vcell, fg_current); + + /* Compensation for Charging cut off current in P8. + * On P8 we increase the charging current to 1.8A + * to complete charging in spec time. + * This causes the cut-off/ termination current to be high and + * hence to set the charging cut -off current to expected value + * we control it through ADC channel 6 of AP. + * Due to some prob in HW sometimes the CURRENT_MEA goes + * below V_TOP_OFF even for very low SOCs hence + * check for CURRENT_MEA signal only at + * a very later stage of charging process. + * For now this code is enabled only for P8-LTE. + * Enable it for other P8 Models only if HW is updated. + */ +#ifdef CONFIG_MACH_P8LTE + /* Charging is done using TA only. */ + /* TA should be a proper connection. */ + /* Charging should be Enabled. */ + /* Should not be in Recharging Path. */ + /* VCELL > 4.19V. */ + /* VFSOC > 72. 72 * 1.333 = 95.976 */ + if ((battery->info.charging_source == CHARGER_AC) && + (battery->info.batt_improper_ta == 0) && + (battery->info.charging_enabled) && + (battery->info.batt_is_recharging == 0) && + (fg_vcell > 4190) && + (fg_vfsoc > 72)) { + /* Read ADC Voltage. */ + if (battery->padc) { + int Viset = s3c_adc_read(battery->padc, + SEC_CURR_MEA_ADC_CH); + + if (!Viset) { + /* Connecting TA after boot shows latency + * in ADC update for the first time. + * Wait for ~700ms the ADC value to be updated. + * This is just a conservative number. + */ + pr_info( + "%s: Waiting ADC on Ch6 to be updated\n", + __func__); + mdelay(700); + Viset = s3c_adc_read(battery->padc, + SEC_CURR_MEA_ADC_CH); + } + + if (Viset < V_TOP_OFF) { + if (battery->info.batt_full_count++ < + COUNT_TOP_OFF) { + pr_info( + "%s: Charging current < top-off\n", + __func__); + } else { + /* Disable the charging process. */ + pr_err( + "%s: Charging Disabled: V-topoff(%d)", + __func__, V_TOP_OFF); + pr_err(" is more than V-Iset(%d)\n", + Viset); + + battery->info.batt_full_count = 0; + sec_cable_charging(battery); + } + } else + battery->info.batt_full_count = 0; + } else { + pr_err( + "%s: Can't read the ADC regsiter\n", + __func__); + } + } +#endif + +__end__: + pr_debug("fg_vcell = %d, fg_soc = %d, is_full = %d, full_count = %d", + fg_vcell, fg_soc, + battery->info.batt_is_full, + battery->info.batt_full_count); + + if (battery->is_first_check) + battery->is_first_check = false; + + if (battery->info.batt_is_full && + (battery->info.charging_source != CHARGER_USB)) + fg_soc = 100; + + return fg_soc; +} + +static int sec_get_bat_vol(struct power_supply *bat_ps) +{ + struct battery_data *battery = container_of(bat_ps, + struct battery_data, psy_battery); + return battery->info.batt_vol; +} + +static bool check_UV_charging_case(void) +{ + int fg_vcell = get_fuelgauge_value(FG_VOLTAGE); + int fg_current = get_fuelgauge_value(FG_CURRENT); + int threshold = 0; + + if (get_fuelgauge_value(FG_BATTERY_TYPE) == SDI_BATTERY_TYPE) + threshold = 3300 + ((fg_current * 17) / 100); + else if (get_fuelgauge_value(FG_BATTERY_TYPE) == ATL_BATTERY_TYPE) + threshold = 3300 + ((fg_current * 13) / 100); + + if (fg_vcell <= threshold) + return 1; + else + return 0; +} + +static void sec_set_time_for_charging(struct battery_data *battery, int mode) +{ + if (mode) { + ktime_t ktime; + struct timespec cur_time; + + ktime = alarm_get_elapsed_realtime(); + cur_time = ktime_to_timespec(ktime); + + /* record start time for abs timer */ + battery->charging_start_time = cur_time.tv_sec; + } else { + /* initialize start time for abs timer */ + battery->charging_start_time = 0; + } +} + +static void disable_internal_charger(void) +{ + struct power_supply *internal_psy = \ + power_supply_get_by_name("max8997-charger"); + union power_supply_propval value; + int ret; + + /* Disable inner charger(MAX8997) */ + if (internal_psy) { + value.intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = internal_psy->set_property(internal_psy, \ + POWER_SUPPLY_PROP_STATUS, &value); + if (ret) + pr_err("%s: fail to set %s discharging status(%d)\n", + __func__, internal_psy->name, ret); + } +} +static void sec_set_chg_current(struct battery_data *battery, int set_current) +{ +#if defined(P8_CHARGING_FEATURE_01) + if ((set_current > CHARGING_CURRENT_HIGH_LOW_STANDARD) && \ + (battery->current_cable_status == CHARGER_AC)) + sec_set_charging(battery, CHARGER_AC); + else if (battery->current_cable_status == CHARGER_MISC) + sec_set_charging(battery, CHARGER_MISC); + else + sec_set_charging(battery, CHARGER_USB); +#else /* P4C H/W rev0.0 does not support yet */ + if (battery->current_cable_status == CHARGER_AC) { + if (battery->pdata->set_charging_current) + battery->pdata->set_charging_current((int)set_current); + + if (battery->pdata->get_charging_current) + battery->info.charging_current = \ + battery->pdata->get_charging_current(); + } else { + battery->info.charging_current = CHARGING_CURRENT_USB; + } +#endif +} + +static void sec_set_chg_en(struct battery_data *battery, int enable) +{ +#if defined(P2_CHARGING_FEATURE_02) || defined(P4_CHARGING_FEATURE_02) + /* Disable internal charger(MAX8997) */ + /* because of using external charger */ + disable_internal_charger(); + + if (battery->pdata->set_charging_state) { + if (battery->current_cable_status == CHARGER_AC) + battery->pdata->set_charging_state(enable, + CABLE_TYPE_TA); + else if (battery->current_cable_status == CHARGER_MISC) + battery->pdata->set_charging_state(enable, + CABLE_TYPE_STATION); + else + battery->pdata->set_charging_state(enable, + CABLE_TYPE_USB); + } + + if ((enable) || (battery->pdata->get_charging_current)) + battery->info.charging_current = \ + battery->pdata->get_charging_current(); + + sec_set_time_for_charging(battery, enable); + + if (!enable) + battery->info.batt_is_recharging = 0; + + pr_info("%s: External charger is %s", __func__, (enable ? "enabled." : \ + "disabled.")); +#elif defined(P4_CHARGING_FEATURE_01) + int charger_enable_line = battery->pdata->charger.enable_line; + + /* Disable internal charger(MAX8997) */ + /* because of using external charger */ + disable_internal_charger(); + + /* In case of HDMI connecting, set charging current 1.5A */ +#if defined(CONFIG_MACH_P4NOTE) + if (battery->pdata->set_charging_state) { + if (battery->current_cable_status == CHARGER_AC) + battery->pdata->set_charging_state(enable, + CABLE_TYPE_TA); + else if (battery->current_cable_status == CHARGER_MISC) + battery->pdata->set_charging_state(enable, + CABLE_TYPE_STATION); + else if (battery->current_cable_status == CHARGER_DOCK) + battery->pdata->set_charging_state(enable, + CABLE_TYPE_DESKDOCK); + else + battery->pdata->set_charging_state(enable, + CABLE_TYPE_USB); + } + + if (battery->pdata->get_charging_current) + battery->info.charging_current = \ + battery->pdata->get_charging_current(); + + sec_set_time_for_charging(battery, enable); + + if (!enable) + battery->info.batt_is_recharging = 0; +#else + if (system_rev >= 2) { + if (battery->pdata->set_charging_state) { + if (battery->current_cable_status == CHARGER_AC) + battery->pdata->set_charging_state(enable, + CABLE_TYPE_TA); + else if (battery->current_cable_status == CHARGER_MISC) + battery->pdata->set_charging_state(enable, + CABLE_TYPE_STATION); + else if (battery->current_cable_status == CHARGER_DOCK) + battery->pdata->set_charging_state(enable, + CABLE_TYPE_DESKDOCK); + else + battery->pdata->set_charging_state(enable, + CABLE_TYPE_USB); + } + + if (battery->pdata->get_charging_current) + battery->info.charging_current = \ + battery->pdata->get_charging_current(); + + sec_set_time_for_charging(battery, enable); + + if (!enable) + battery->info.batt_is_recharging = 0; + } else { + if (enable) { + sec_set_charging(battery, + battery->current_cable_status); + gpio_set_value(charger_enable_line, 0); + + pr_info("%s: Enabling external charger ", __func__); + sec_set_time_for_charging(battery, 1); + } else { + gpio_set_value(charger_enable_line, 1); + pr_info("%s: Disabling external charger ", __func__); + + sec_set_time_for_charging(battery, 0); + battery->info.batt_is_recharging = 0; + } + } +#endif +#else + int charger_enable_line = battery->pdata->charger.enable_line; + + /* Disable internal charger(MAX8997) */ + /* because of using external charger */ + disable_internal_charger(); + + if (enable) { + sec_set_charging(battery, battery->current_cable_status); + gpio_set_value(charger_enable_line, 0); + + pr_info("%s: Enabling the external charger ", __func__); + sec_set_time_for_charging(battery, 1); + } else { + gpio_set_value(charger_enable_line, 1); + pr_info("%s: Disabling the external charger ", __func__); + + sec_set_time_for_charging(battery, 0); + battery->info.batt_is_recharging = 0; + } +#endif + battery->info.charging_enabled = enable; +} + +static void sec_temp_control(struct battery_data *battery, int mode) +{ + switch (mode) { + case POWER_SUPPLY_HEALTH_GOOD: + pr_debug("GOOD"); + battery->info.batt_health = mode; + break; + case POWER_SUPPLY_HEALTH_OVERHEAT: + pr_debug("OVERHEAT"); + battery->info.batt_health = mode; + break; + case POWER_SUPPLY_HEALTH_COLD: + pr_debug("FREEZE"); + battery->info.batt_health = mode; + break; + default: + break; + } + schedule_work(&battery->cable_work); +} + +static int sec_get_bat_temp(struct power_supply *bat_ps) +{ + struct battery_data *battery = container_of(bat_ps, + struct battery_data, psy_battery); + int health = battery->info.batt_health; + int update = 0; + int battery_temp; + int temp_high_threshold; + int temp_high_recovery; + int temp_low_threshold; + int temp_low_recovery; + + battery_temp = get_fuelgauge_value(FG_TEMPERATURE); + + temp_high_threshold = battery->pdata->temp_high_threshold; + temp_high_recovery = battery->pdata->temp_high_recovery; + temp_low_threshold = battery->pdata->temp_low_threshold; + temp_low_recovery = battery->pdata->temp_low_recovery; + +#ifdef __TEST_DEVICE_DRIVER__ + switch (bat_temp_force_state) { + case 0: + break; + case 1: + battery_temp = temp_high_threshold + 1; + break; + case 2: + battery_temp = temp_low_threshold - 1; + break; + default: + break; + } +#endif /* __TEST_DEVICE_DRIVER__ */ + + if (battery_temp >= temp_high_threshold) { + if (health != POWER_SUPPLY_HEALTH_OVERHEAT && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + pr_info("battery overheat(%d>=%d),charging unavailable", + battery_temp, temp_high_threshold); + sec_temp_control(battery, POWER_SUPPLY_HEALTH_OVERHEAT); + update = 1; + } + } else if (battery_temp <= temp_high_recovery && + battery_temp >= temp_low_recovery) { + if (health == POWER_SUPPLY_HEALTH_OVERHEAT || + health == POWER_SUPPLY_HEALTH_COLD) { + pr_info("battery recovery(%d,%d~%d),charging available", + battery_temp, temp_low_recovery, + temp_high_recovery); + sec_temp_control(battery, POWER_SUPPLY_HEALTH_GOOD); + update = 1; + } + } else if (battery_temp <= temp_low_threshold) { + if (health != POWER_SUPPLY_HEALTH_COLD && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + pr_info("battery cold(%d <= %d), charging unavailable.", + battery_temp, temp_low_threshold); + sec_temp_control(battery, POWER_SUPPLY_HEALTH_COLD); + update = 1; + } + } + + if (update) + pr_info("sec_get_bat_temp = %d.", battery_temp); + + return battery_temp/100; +} + +static int sec_bat_get_charging_status(struct battery_data *battery) +{ + switch (battery->info.charging_source) { + case CHARGER_BATTERY: + case CHARGER_USB: + return POWER_SUPPLY_STATUS_DISCHARGING; + case CHARGER_AC: + case CHARGER_MISC: + case CHARGER_DOCK: + if (battery->info.batt_is_full || + battery->info.level == 100) + return POWER_SUPPLY_STATUS_FULL; + else if (battery->info.batt_improper_ta) + return POWER_SUPPLY_STATUS_DISCHARGING; + else + return POWER_SUPPLY_STATUS_CHARGING; + case CHARGER_DISCHARGE: + return POWER_SUPPLY_STATUS_NOT_CHARGING; + default: + return POWER_SUPPLY_STATUS_UNKNOWN; + } +} + +static int sec_bat_set_property(struct power_supply *ps, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct battery_data *battery = container_of(ps, + struct battery_data, psy_battery); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + battery->pmic_cable_state = val->intval; + pr_info("PMIC cable state: %d\n", battery->pmic_cable_state); + if (battery->cable_detect_source == 2) { + /* cable is attached or detached. + called by USB switch(MUIC) */ + sec_get_cable_status(battery); + sec_cable_changed(battery); + } + battery->cable_detect_source = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_bat_get_property(struct power_supply *bat_ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct battery_data *battery = container_of(bat_ps, + struct battery_data, psy_battery); + + pr_debug("psp = %d", psp); + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = sec_bat_get_charging_status(battery); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = battery->info.batt_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = battery->present; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = battery->info.level; + pr_info("level = %d\n", val->intval); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = battery->info.batt_temp; + pr_debug("temp = %d\n", val->intval); + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_usb_get_property(struct power_supply *usb_ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct battery_data *battery = container_of(usb_ps, + struct battery_data, psy_usb); + + enum charger_type charger = battery->info.charging_source; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = (charger == CHARGER_USB ? 1 : 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int sec_ac_get_property(struct power_supply *ac_ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct battery_data *battery = container_of(ac_ps, + struct battery_data, psy_ac); + + enum charger_type charger = battery->info.charging_source; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if ((charger == CHARGER_AC) || (charger == CHARGER_MISC) + || (charger == CHARGER_DOCK)) + val->intval = 1; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +#define SEC_BATTERY_ATTR(_name)\ +{\ + .attr = { .name = #_name, .mode = S_IRUGO | (S_IWUSR | S_IWGRP) },\ + .show = sec_bat_show_property,\ + .store = sec_bat_store,\ +} + +static struct device_attribute sec_battery_attrs[] = { + SEC_BATTERY_ATTR(batt_vol), + SEC_BATTERY_ATTR(temp), +#ifdef CONFIG_MACH_SAMSUNG_P5 + SEC_BATTERY_ATTR(batt_temp_cels), +#endif + SEC_BATTERY_ATTR(batt_charging_source), + SEC_BATTERY_ATTR(fg_soc), +#if defined(CONFIG_MACH_P4NOTE) + SEC_BATTERY_ATTR(batt_reset_soc), +#else + SEC_BATTERY_ATTR(reset_soc), +#endif + SEC_BATTERY_ATTR(fg_reset_cap), + SEC_BATTERY_ATTR(fg_reg), + SEC_BATTERY_ATTR(batt_type), + SEC_BATTERY_ATTR(batt_temp_check), + SEC_BATTERY_ATTR(batt_full_check), + SEC_BATTERY_ATTR(batt_current_now), + SEC_BATTERY_ATTR(siop_activated), +#ifdef CONFIG_SAMSUNG_LPM_MODE + SEC_BATTERY_ATTR(batt_lp_charging), + SEC_BATTERY_ATTR(voltage_now), +#endif + SEC_BATTERY_ATTR(jig_on), + SEC_BATTERY_ATTR(fg_capacity), +}; + +enum { + BATT_VOL = 0, + BATT_TEMP, +#ifdef CONFIG_MACH_SAMSUNG_P5 + BATT_TEMP_CELS, +#endif + BATT_CHARGING_SOURCE, + BATT_FG_SOC, + BATT_RESET_SOC, + BATT_RESET_CAP, + BATT_FG_REG, + BATT_BATT_TYPE, + BATT_TEMP_CHECK, + BATT_FULL_CHECK, + BATT_CURRENT_NOW, + SIOP_ACTIVATED, +#ifdef CONFIG_SAMSUNG_LPM_MODE + BATT_LP_CHARGING, + VOLTAGE_NOW, +#endif + JIG_ON, + FG_CAPACITY, +}; + +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 sec_attrs_failed; + } + return 0; + +sec_attrs_failed: + while (i--) + device_remove_file(dev, &sec_battery_attrs[i]); + return rc; +} + +static ssize_t sec_bat_show_property(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int i = 0; +#ifdef CONFIG_MACH_SAMSUNG_P5 + s32 temp = 0; +#endif + u8 batt_str[5]; + const ptrdiff_t off = attr - sec_battery_attrs; + + switch (off) { + case BATT_VOL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + get_fuelgauge_value(FG_VOLTAGE)); + break; + case BATT_TEMP: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + debug_batterydata->info.batt_temp); + break; +#ifdef CONFIG_MACH_SAMSUNG_P5 + case BATT_TEMP_CELS: + temp = ((debug_batterydata->info.batt_temp) / 10); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + temp); + break; +#endif + case BATT_CHARGING_SOURCE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + debug_batterydata->info.charging_source); + break; + case BATT_FG_SOC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + get_fuelgauge_value(FG_LEVEL)); + break; + case BATT_BATT_TYPE: + if (get_fuelgauge_value(FG_BATTERY_TYPE) == SDI_BATTERY_TYPE) + sprintf(batt_str, "SDI"); + else if (get_fuelgauge_value(FG_BATTERY_TYPE) == + ATL_BATTERY_TYPE) + sprintf(batt_str, "ATL"); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s_%s\n", + batt_str, batt_str); + break; + case BATT_TEMP_CHECK: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + debug_batterydata->info.batt_health); + break; + case BATT_FULL_CHECK: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + debug_batterydata->info.batt_is_full); + break; + case BATT_CURRENT_NOW: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + debug_batterydata->info.charging_current); + break; + case SIOP_ACTIVATED: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + debug_batterydata->info.siop_activated); + break; +#ifdef CONFIG_SAMSUNG_LPM_MODE + case BATT_LP_CHARGING: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + debug_batterydata->charging_mode_booting); + break; + case VOLTAGE_NOW: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + get_fuelgauge_value(FG_VOLTAGE_NOW)); + break; +#endif + case JIG_ON: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + debug_batterydata->pdata->check_jig_status()); + break; + case FG_CAPACITY: + i += scnprintf(buf + i, PAGE_SIZE - i, + "0x%04x 0x%04x 0x%04x 0x%04x\n", + get_fuelgauge_capacity(CAPACITY_TYPE_FULL), + get_fuelgauge_capacity(CAPACITY_TYPE_MIX), + get_fuelgauge_capacity(CAPACITY_TYPE_AV), + get_fuelgauge_capacity(CAPACITY_TYPE_REP)); + break; + default: + i = -EINVAL; + } + + return i; +} + +static ssize_t sec_bat_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int x = 0; + int ret = 0; + const ptrdiff_t off = attr - sec_battery_attrs; + + switch (off) { + case BATT_RESET_SOC: + if (sscanf(buf, "%d\n", &x) == 1) { + if (x == 1) { + if (!check_ta_conn(debug_batterydata)) + fg_reset_soc(); + } + ret = count; + } + sec_bat_status_update(&debug_batterydata->psy_battery); + pr_debug("Reset SOC:%d ", x); + break; + case BATT_RESET_CAP: + if (sscanf(buf, "%d\n", &x) == 1 || + x == 2 || x == 3 || x == 4) { + if (x == 1 || x == 2 || + x == 3 || x == 4) + fg_reset_capacity(); + ret = count; + } + pr_debug("%s: Reset CAP:%d\n", __func__, x); + break; + case BATT_FG_REG: + if (sscanf(buf, "%d\n", &x) == 1) { + if (x == 1) + fg_periodic_read(); + ret = count; + } + pr_debug("%s: FG Register:%d\n", __func__, x); + break; + case BATT_CURRENT_NOW: + if (sscanf(buf, "%d\n", &x) == 1) { + sec_set_chg_current(debug_batterydata, (int)x); + ret = count; + } + pr_debug("%s: BATT_CURRENT_NOW :%d\n", __func__, x); + break; + case SIOP_ACTIVATED: + if (sscanf(buf, "%d\n", &x) == 1) { + debug_batterydata->info.siop_activated = (int)x; + if (debug_batterydata->info.siop_activated == + SIOP_ACTIVE) + sec_set_chg_current(debug_batterydata, + SIOP_ACTIVE_CHARGE_CURRENT); + else + sec_set_chg_current(debug_batterydata, + SIOP_DEACTIVE_CHARGE_CURRENT); + ret = count; + } + pr_info("%s: SIOP_ACTIVATED :%d\n", __func__, x); + break; +#ifdef CONFIG_SAMSUNG_LPM_MODE + case BATT_LP_CHARGING: + if (sscanf(buf, "%d\n", &x) == 1) { + debug_batterydata->charging_mode_booting = x; + ret = count; + } + break; +#endif + default: + ret = -EINVAL; + } + return ret; +} + +#ifdef __TEST_DEVICE_DRIVER__ +#define SEC_TEST_ATTR(_name)\ +{\ + .attr = { .name = #_name, .mode = S_IRUGO | (S_IWUSR | S_IWGRP) },\ + .show = sec_batt_test_show_property,\ + .store = sec_batt_test_store,\ +} + +static struct device_attribute sec_batt_test_attrs[] = { + SEC_TEST_ATTR(suspend_lock), + SEC_TEST_ATTR(control_temp), +}; + +enum { + SUSPEND_LOCK = 0, + CTRL_TEMP, +}; + +static int sec_batt_test_create_attrs(struct device *dev) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(sec_batt_test_attrs); i++) { + ret = device_create_file(dev, &sec_batt_test_attrs[i]); + if (ret) + goto sec_attrs_failed; + } + goto succeed; + +sec_attrs_failed: + while (i--) + device_remove_file(dev, &sec_batt_test_attrs[i]); +succeed: + return ret; +} + +static ssize_t sec_batt_test_show_property(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int i = 0; + const ptrdiff_t off = attr - sec_batt_test_attrs; + + switch (off) { + default: + i = -EINVAL; + } + + return i; +} + +static ssize_t sec_batt_test_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int mode = 0; + int ret = 0; + const ptrdiff_t off = attr - sec_batt_test_attrs; + + switch (off) { + case SUSPEND_LOCK: + if (sscanf(buf, "%d\n", &mode) != 1) + break; + + dev_dbg(dev, "%s: suspend lock (%d)\n", __func__, mode); + if (mode) + wake_lock(&debug_batterydata->wake_lock_for_dev); + else + wake_lock_timeout(&debug_batterydata->wake_lock_for_dev, + HZ / 2); + ret = count; + break; + + case CTRL_TEMP: + if (sscanf(buf, "%d\n", &mode) == 1) { + dev_info(dev, "%s: control temp (%d)\n", __func__, + mode); + bat_temp_force_state = mode; + ret = count; + } + break; + + default: + ret = -EINVAL; + } + + return ret; +} +#endif /* __TEST_DEVICE_DRIVER__ */ + +int check_usb_status; +static int sec_cable_status_update(struct battery_data *battery, int status) +{ + int ret = 0; + enum charger_type source = CHARGER_BATTERY; + + pr_debug("Update cable status "); + + if (!battery->sec_battery_initial) + return -EPERM; + + switch (status) { + case CHARGER_BATTERY: + pr_info("cable NOT PRESENT "); + battery->info.charging_source = CHARGER_BATTERY; + break; + case CHARGER_USB: + pr_info("cable USB"); + battery->info.charging_source = CHARGER_USB; + break; + case CHARGER_AC: + pr_info("cable AC"); + battery->info.charging_source = CHARGER_AC; + break; + case CHARGER_DOCK: + pr_info("cable DOCK"); + battery->info.charging_source = CHARGER_DOCK; + break; + case CHARGER_MISC: + pr_info("cable MISC"); + battery->info.charging_source = CHARGER_AC; +#if defined(CONFIG_MACH_P8LTE) || defined(CONFIG_MACH_P8) + battery->info.charging_source = CHARGER_MISC; +#endif + break; + case CHARGER_DISCHARGE: + pr_info("Discharge"); + battery->info.charging_source = CHARGER_DISCHARGE; + break; + default: + pr_info("Not supported status"); + ret = -EINVAL; + } + check_usb_status = source = battery->info.charging_source; + + if (source == CHARGER_USB || source == CHARGER_AC || \ + source == CHARGER_MISC || source == CHARGER_DOCK) { + wake_lock(&battery->vbus_wake_lock); + } else { + /* give userspace some time to see the uevent and update + * LED state or whatnot... + */ + if (!get_charger_status(battery)) { + if (battery->charging_mode_booting) + wake_lock_timeout(&battery->vbus_wake_lock, + 5 * HZ); + else + wake_lock_timeout(&battery->vbus_wake_lock, + HZ / 2); + } + } + + wake_lock(&battery->work_wake_lock); + schedule_work(&battery->battery_work); + + pr_debug("call power_supply_changed "); + + return ret; +} + +static void sec_bat_status_update(struct power_supply *bat_ps) +{ + struct battery_data *battery = container_of(bat_ps, + struct battery_data, psy_battery); + + int old_level, old_temp, old_health, old_is_full; + + if (!battery->sec_battery_initial) + return; + + mutex_lock(&battery->work_lock); + old_temp = battery->info.batt_temp; + old_health = battery->info.batt_health; + old_level = battery->info.level; + old_is_full = battery->info.batt_is_full; + + battery->info.batt_temp = sec_get_bat_temp(bat_ps); + if (!battery->is_low_batt_alarm) + battery->info.level = sec_get_bat_level(bat_ps); + + if (!battery->info.charging_enabled && + !battery->info.batt_is_full && + !battery->pdata->check_jig_status()) { + if (battery->info.level > old_level) + battery->info.level = old_level; + } + + battery->info.batt_vol = sec_get_bat_vol(bat_ps); + + if (battery->pdata->get_charging_state) + battery->pdata->get_charging_state(); + + power_supply_changed(bat_ps); + pr_debug("call power_supply_changed"); + + pr_info("BAT : soc(%d), vcell(%dmV), curr(%dmA), temp(%d.%d), chg(%d)", + battery->info.level, + battery->info.batt_vol, + battery->info.batt_current, + battery->info.batt_temp/10, + battery->info.batt_temp%10, + battery->info.charging_enabled); + pr_info(", full(%d), rechg(%d), lowbat(%d), cable(%d)\n", + battery->info.batt_is_full, + battery->info.batt_is_recharging, + battery->is_low_batt_alarm, + battery->current_cable_status); + + mutex_unlock(&battery->work_lock); +} + +static void sec_cable_check_status(struct battery_data *battery) +{ + enum charger_type status = 0; + + mutex_lock(&battery->work_lock); + + if (get_charger_status(battery)) { + pr_info("%s: Returning to Normal discharge path.\n", + __func__); + cancel_delayed_work(&battery->fuelgauge_recovery_work); + battery->is_low_batt_alarm = false; + + if (battery->info.batt_health != POWER_SUPPLY_HEALTH_GOOD) { + pr_info("Unhealth battery state! "); + status = CHARGER_DISCHARGE; + sec_set_chg_en(battery, 0); + goto __end__; + } + + status = battery->current_cable_status; + + sec_set_chg_en(battery, 1); + + max17042_chip_data->info.low_batt_comp_flag = 0; + reset_low_batt_comp_cnt(); + + } else { + status = CHARGER_BATTERY; + sec_set_chg_en(battery, 0); + } +__end__: + sec_cable_status_update(battery, status); + mutex_unlock(&battery->work_lock); +} + +static void sec_bat_work(struct work_struct *work) +{ + struct battery_data *battery = + container_of(work, struct battery_data, battery_work); + unsigned long flags; + pr_debug("%s\n", __func__); + + sec_bat_status_update(&battery->psy_battery); + battery->last_poll = alarm_get_elapsed_realtime(); + + /* prevent suspend before starting the alarm */ + local_irq_save(flags); + wake_unlock(&battery->work_wake_lock); + sec_program_alarm(battery, FAST_POLL); + local_irq_restore(flags); +} + +static void sec_cable_work(struct work_struct *work) +{ + struct battery_data *battery = + container_of(work, struct battery_data, cable_work); + pr_debug("%s\n", __func__); + + sec_cable_check_status(battery); +} + +static int sec_bat_suspend(struct device *dev) +{ + struct battery_data *battery = dev_get_drvdata(dev); + pr_info("%s start\n", __func__); + + if (!get_charger_status(battery)) { + sec_program_alarm(battery, SLOW_POLL); + battery->slow_poll = 1; + } + + pr_info("%s end\n", __func__); + return 0; +} + +static void sec_bat_resume(struct device *dev) +{ + struct battery_data *battery = dev_get_drvdata(dev); + pr_info("%s start\n", __func__); + + irq_set_irq_type(battery->charging_irq + , IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING); + + if (battery->slow_poll) { + sec_program_alarm(battery, FAST_POLL); + battery->slow_poll = 0; + } + + wake_lock(&battery->work_wake_lock); + schedule_work(&battery->battery_work); + + pr_info("%s end\n", __func__); +} + +static void sec_cable_changed(struct battery_data *battery) +{ + pr_debug("charger changed "); + + if (!battery->sec_battery_initial) + return; + + if (!battery->charging_mode_booting) + battery->info.batt_is_full = 0; + + battery->info.batt_health = POWER_SUPPLY_HEALTH_GOOD; + + schedule_work(&battery->cable_work); + + /* + * Wait a bit before reading ac/usb line status and setting charger, + * because ac/usb status readings may lag from irq. + */ + + battery->last_poll = alarm_get_elapsed_realtime(); + sec_program_alarm(battery, FAST_POLL); +} + +void sec_cable_charging(struct battery_data *battery) +{ + battery->full_charge_comp_recharge_info = + battery->info.batt_is_recharging; + + if (!battery->sec_battery_initial) + return; + + if (battery->info.charging_enabled && + battery->info.batt_health == POWER_SUPPLY_HEALTH_GOOD) { + sec_set_chg_en(battery, 0); + battery->info.batt_is_full = 1; + /* full charge compensation algorithm by MAXIM */ + fg_fullcharged_compensation( + battery->full_charge_comp_recharge_info, 1); + + cancel_delayed_work(&battery->full_comp_work); + schedule_delayed_work(&battery->full_comp_work, 100); + + pr_info("battery is full charged "); + } + + wake_lock(&battery->work_wake_lock); + schedule_work(&battery->battery_work); +} + +static irqreturn_t low_battery_isr(int irq, void *arg) +{ + struct battery_data *battery = (struct battery_data *)arg; + pr_debug("%s(%d)", __func__, + gpio_get_value(max17042_chip_data->pdata->fuel_alert_line)); + + cancel_delayed_work(&battery->fuelgauge_work); + schedule_delayed_work(&battery->fuelgauge_work, 0); + + return IRQ_HANDLED; +} + +void fuelgauge_recovery_handler(struct work_struct *work) +{ + struct battery_data *battery = + container_of(work, struct battery_data, + fuelgauge_recovery_work.work); + int current_soc; + + if (battery->info.level > 0) { + pr_err("%s: Reduce the Reported SOC by 1 unit, wait for 30s\n", + __func__); + if (!battery->info.charging_enabled) + wake_lock_timeout(&battery->vbus_wake_lock, HZ); + current_soc = get_fuelgauge_value(FG_LEVEL); + if (current_soc) { + pr_info("%s: Returning to Normal discharge path.\n", + __func__); + pr_info(" Actual SOC(%d) non-zero.\n", + current_soc); + battery->is_low_batt_alarm = false; + return; + } else { + battery->info.level--; + pr_err("%s: New Reduced Reported SOC (%d).\n", + __func__, battery->info.level); + power_supply_changed(&battery->psy_battery); + queue_delayed_work(battery->sec_TA_workqueue, + &battery->fuelgauge_recovery_work, + msecs_to_jiffies(30000)); + } + } else { + if (!get_charger_status(battery)) { + pr_err("Set battery level as 0, power off.\n"); + battery->info.level = 0; + wake_lock_timeout(&battery->vbus_wake_lock, HZ); + power_supply_changed(&battery->psy_battery); + } + } +} + +#define STABLE_LOW_BATTERY_DIFF 3 +#define STABLE_LOW_BATTERY_DIFF_LOWBATT 1 +int _low_battery_alarm_(struct battery_data *battery) +{ + int overcurrent_limit_in_soc; + int current_soc = get_fuelgauge_value(FG_LEVEL); + + if (battery->info.level <= STABLE_LOW_BATTERY_DIFF) + overcurrent_limit_in_soc = STABLE_LOW_BATTERY_DIFF_LOWBATT; + else + overcurrent_limit_in_soc = STABLE_LOW_BATTERY_DIFF; + + if ((battery->info.level - current_soc) > overcurrent_limit_in_soc) { + pr_info("%s: Abnormal Current Consumption jump by %d units.\n", + __func__, ((battery->info.level - current_soc))); + pr_info("Last Reported SOC (%d).\n", + battery->info.level); + + battery->is_low_batt_alarm = true; + + if (battery->info.level > 0) { + queue_delayed_work(battery->sec_TA_workqueue, + &battery->fuelgauge_recovery_work, 0); + return 0; + } + } + + if (!get_charger_status(battery)) { + pr_err("Set battery level as 0, power off.\n"); + battery->info.level = 0; + wake_lock_timeout(&battery->vbus_wake_lock, HZ); + power_supply_changed(&battery->psy_battery); + } + + return 0; +} + +static void fuelgauge_work_handler(struct work_struct *work) +{ + struct battery_data *battery = + container_of(work, struct battery_data, fuelgauge_work.work); + pr_info("low battery alert!"); + if (get_fuelgauge_value(FG_CHECK_STATUS)) + _low_battery_alarm_(battery); +} + +static void full_comp_work_handler(struct work_struct *work) +{ + struct battery_data *battery = + container_of(work, struct battery_data, full_comp_work.work); + int avg_current = get_fuelgauge_value(FG_CURRENT_AVG); + + if (avg_current >= 25) { + cancel_delayed_work(&battery->full_comp_work); + schedule_delayed_work(&battery->full_comp_work, 100); + } else { + pr_info("%s: full charge compensation start (avg_current %d)\n", + __func__, avg_current); + fg_fullcharged_compensation( + battery->full_charge_comp_recharge_info, 0); + } +} + +#define BATTERY_FULL_THRESHOLD_SOC 95 /* 95 / 1.333 = 71.27 */ +#if defined(P4_CHARGING_FEATURE_01) +#define BATTERY_FULL_THRESHOLD_VFSOC 60 /* 60 * 1.333 = 79.98 */ +#else +#define BATTERY_FULL_THRESHOLD_VFSOC 71 /* 71 * 1.333 = 94.64 */ +#endif + +static void fullcharging_work_handler(struct work_struct *work) +{ + struct battery_data *battery = + container_of(work, struct battery_data, fullcharging_work.work); + int check_charger_state = 1; + int fg_soc, fg_vfsoc; + pr_info("%s : nCHG intr!!, fullcharge_line=%d", + __func__, + gpio_get_value(battery->pdata->charger.fullcharge_line)); + + if (gpio_get_value(battery->pdata->charger.fullcharge_line) == 1) { +#if defined(P2_CHARGING_FEATURE_02) + /* Check charger state */ + if (battery->pdata->get_charging_state) { + if (battery->pdata->get_charging_state() == \ + POWER_SUPPLY_STATUS_UNKNOWN) { + pr_info("Disconnect cable and check charger\n"); + if (battery->pmic_cable_state == 0) { + battery->cable_detect_source = 2; + sec_get_cable_status(battery); + sec_cable_changed(battery); + check_charger_state = 0; + } + } + } +#endif + +#if defined(P4_CHARGING_FEATURE_01) + fg_vfsoc = get_fuelgauge_value(FG_VF_SOC); + check_charger_state = battery->pdata->get_charger_is_full(); + /* Check full charged state */ + if (get_charger_status(battery) != 0 + && fg_vfsoc > BATTERY_FULL_THRESHOLD_VFSOC + && check_charger_state == POWER_SUPPLY_STATUS_FULL) { + pr_info("Battery is full\n"); + sec_cable_charging(battery); + } +#else + fg_soc = get_fuelgauge_value(FG_LEVEL); + fg_vfsoc = get_fuelgauge_value(FG_VF_SOC); + /* Check full charged state */ + if (get_charger_status(battery) != 0 + && fg_soc > BATTERY_FULL_THRESHOLD_SOC + && fg_vfsoc > BATTERY_FULL_THRESHOLD_VFSOC + && check_charger_state == 1) { + pr_info("Battery is full\n"); + sec_cable_charging(battery); + } +#endif + } else { + pr_info("Charger is working. Cable state will be updated.\n"); + /* Cable detect from default */ + battery->cable_detect_source = 0; + } + + enable_irq(battery->charging_irq); +} + +static int __devinit sec_bat_probe(struct platform_device *pdev) +{ + struct sec_battery_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct battery_data *battery; + int ret; + unsigned long trigger; + int irq_num; + + pr_info("%s : SEC Battery Driver Loading\n", __func__); + + battery = kzalloc(sizeof(*battery), GFP_KERNEL); + if (!battery) + return -ENOMEM; + + battery->pdata = pdata; + if (!battery->pdata) { + pr_err("%s : No platform data\n", __func__); + ret = -EINVAL; + goto err_pdata; + } + + battery->pdata->init_charger_gpio(); + + platform_set_drvdata(pdev, battery); + debug_batterydata = battery; + + battery->present = 1; + battery->info.level = 100; + battery->info.charging_source = CHARGER_BATTERY; + battery->info.batt_health = POWER_SUPPLY_HEALTH_GOOD; + battery->info.abstimer_is_active = 0; + battery->is_first_check = true; + battery->is_low_batt_alarm = false; + + battery->psy_battery.name = "battery"; + battery->psy_battery.type = POWER_SUPPLY_TYPE_BATTERY; + battery->psy_battery.properties = sec_battery_properties; + battery->psy_battery.num_properties = \ + ARRAY_SIZE(sec_battery_properties); + battery->psy_battery.get_property = sec_bat_get_property; + battery->psy_battery.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_properties; + battery->psy_usb.num_properties = ARRAY_SIZE(sec_power_properties); + 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_properties; + battery->psy_ac.num_properties = ARRAY_SIZE(sec_power_properties); + battery->psy_ac.get_property = sec_ac_get_property; + + mutex_init(&battery->work_lock); + + wake_lock_init(&battery->vbus_wake_lock, WAKE_LOCK_SUSPEND, + "vbus wake lock"); + wake_lock_init(&battery->work_wake_lock, WAKE_LOCK_SUSPEND, + "batt_work wake lock"); + wake_lock_init(&battery->cable_wake_lock, WAKE_LOCK_SUSPEND, + "temp wake lock"); + wake_lock_init(&battery->fullcharge_wake_lock, WAKE_LOCK_SUSPEND, + "fullcharge wake lock"); +#ifdef __TEST_DEVICE_DRIVER__ + wake_lock_init(&battery->wake_lock_for_dev, WAKE_LOCK_SUSPEND, + "test mode wake lock"); +#endif /* __TEST_DEVICE_DRIVER__ */ + + INIT_WORK(&battery->battery_work, sec_bat_work); + INIT_WORK(&battery->cable_work, sec_cable_work); + INIT_DELAYED_WORK(&battery->fuelgauge_work, fuelgauge_work_handler); + INIT_DELAYED_WORK(&battery->fuelgauge_recovery_work, + fuelgauge_recovery_handler); + INIT_DELAYED_WORK(&battery->fullcharging_work, + fullcharging_work_handler); + INIT_DELAYED_WORK(&battery->full_comp_work, full_comp_work_handler); + INIT_DELAYED_WORK(&battery->TA_work, sec_TA_work_handler); + + battery->sec_TA_workqueue = create_singlethread_workqueue( + "sec_TA_workqueue"); + if (!battery->sec_TA_workqueue) { + pr_err("Failed to create single workqueue\n"); + ret = -ENOMEM; + goto err_workqueue_init; + } + + battery->padc = s3c_adc_register(pdev, NULL, NULL, 0); + + battery->last_poll = alarm_get_elapsed_realtime(); + alarm_init(&battery->alarm, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + sec_battery_alarm); + + ret = power_supply_register(&pdev->dev, &battery->psy_battery); + if (ret) { + pr_err("Failed to register battery power supply.\n"); + goto err_battery_psy_register; + } + + ret = power_supply_register(&pdev->dev, &battery->psy_usb); + if (ret) { + pr_err("Failed to register USB power supply.\n"); + goto err_usb_psy_register; + } + + ret = power_supply_register(&pdev->dev, &battery->psy_ac); + if (ret) { + pr_err("Failed to register AC power supply.\n"); + goto err_ac_psy_register; + } + + /* create sec detail attributes */ + sec_bat_create_attrs(battery->psy_battery.dev); + +#ifdef __TEST_DEVICE_DRIVER__ + sec_batt_test_create_attrs(battery->psy_ac.dev); +#endif /* __TEST_DEVICE_DRIVER__ */ + + battery->sec_battery_initial = 1; + battery->low_batt_boot_flag = 0; + + /* Get initial cable status */ + sec_get_cable_status(battery); + battery->previous_cable_status = battery->current_cable_status; + sec_cable_check_status(battery); + + /* TA_nCHG irq */ + battery->charging_irq = gpio_to_irq(pdata->charger.fullcharge_line); + trigger = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; + if (request_threaded_irq + (battery->charging_irq, NULL, sec_TA_nCHG_interrupt_handler, + trigger, "TA_nCHG intr", battery)) { + pr_err("sec_TA_nCHG_interrupt_handler register failed!\n"); + goto err_charger_irq; + } + + /* TA_nConnected irq */ + battery->connect_irq = gpio_to_irq(pdata->charger.connect_line); + trigger = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; + if (request_threaded_irq + (battery->connect_irq, NULL, sec_TA_nCon_interrupt_handler, trigger, + "TA_nConnected intr", battery)) { + pr_err("sec_TA_nCon_interrupt_handler register failed!\n"); + goto err_charger_irq; + } + + if (enable_irq_wake(battery->connect_irq)) + pr_err("TA_nConnected enable_irq_wake fail!\n"); + + if (check_ta_conn(battery) && check_UV_charging_case()) + battery->low_batt_boot_flag = 1; + + mutex_lock(&battery->work_lock); + fg_alert_init(); + mutex_unlock(&battery->work_lock); + + /* before enable fullcharge interrupt, check fullcharge */ + if (((battery->info.charging_source == CHARGER_AC) || + (battery->info.charging_source == CHARGER_MISC) || + (battery->info.charging_source == CHARGER_DOCK)) + && battery->info.charging_enabled + && gpio_get_value(pdata->charger.fullcharge_line) == 1) + sec_cable_charging(battery); + + /* Request IRQ */ + irq_num = gpio_to_irq(max17042_chip_data->pdata->fuel_alert_line); + if (request_threaded_irq(irq_num, NULL, low_battery_isr, + IRQF_TRIGGER_FALLING, "FUEL_ALRT irq", + battery)) + pr_err("Can NOT request irq 'IRQ_FUEL_ALERT' %d ", irq_num); + + if (enable_irq_wake(irq_num)) + pr_err("FUEL_ALERT enable_irq_wake fail!\n"); + +#ifdef CONFIG_SAMSUNG_LPM_MODE + lpm_mode_check(battery); +#endif + + return 0; + +err_charger_irq: + alarm_cancel(&battery->alarm); + power_supply_unregister(&battery->psy_ac); +err_ac_psy_register: + power_supply_unregister(&battery->psy_usb); +err_usb_psy_register: + power_supply_unregister(&battery->psy_battery); +err_battery_psy_register: + destroy_workqueue(battery->sec_TA_workqueue); +err_workqueue_init: + wake_lock_destroy(&battery->vbus_wake_lock); + wake_lock_destroy(&battery->work_wake_lock); + wake_lock_destroy(&battery->cable_wake_lock); + wake_lock_destroy(&battery->fullcharge_wake_lock); + mutex_destroy(&battery->work_lock); +err_pdata: + kfree(battery); + + return ret; +} + +static int __devexit sec_bat_remove(struct platform_device *pdev) +{ + struct battery_data *battery = platform_get_drvdata(pdev); + + free_irq(gpio_to_irq(max17042_chip_data->pdata->fuel_alert_line), NULL); + free_irq(gpio_to_irq(battery->pdata->charger.connect_line), NULL); + + alarm_cancel(&battery->alarm); + power_supply_unregister(&battery->psy_ac); + power_supply_unregister(&battery->psy_usb); + power_supply_unregister(&battery->psy_battery); + + destroy_workqueue(battery->sec_TA_workqueue); + cancel_delayed_work(&battery->fuelgauge_work); + cancel_delayed_work(&battery->fuelgauge_recovery_work); + cancel_delayed_work(&battery->fullcharging_work); + cancel_delayed_work(&battery->full_comp_work); + + wake_lock_destroy(&battery->vbus_wake_lock); + wake_lock_destroy(&battery->work_wake_lock); + wake_lock_destroy(&battery->cable_wake_lock); + wake_lock_destroy(&battery->fullcharge_wake_lock); + mutex_destroy(&battery->work_lock); + + kfree(battery); + + return 0; +} + +static const struct dev_pm_ops sec_battery_pm_ops = { + .prepare = sec_bat_suspend, + .complete = sec_bat_resume, +}; + +static struct platform_driver sec_bat_driver = { + .driver = { + .name = "sec-battery", + .owner = THIS_MODULE, + .pm = &sec_battery_pm_ops, + }, + .probe = sec_bat_probe, + .remove = __devexit_p(sec_bat_remove), +}; + +static int __init sec_bat_init(void) +{ + return platform_driver_register(&sec_bat_driver); +} + +static void __exit sec_bat_exit(void) +{ + platform_driver_unregister(&sec_bat_driver); +} + +late_initcall(sec_bat_init); +module_exit(sec_bat_exit); + +MODULE_DESCRIPTION("battery driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/power/sec_battery_u1.c b/drivers/power/sec_battery_u1.c new file mode 100644 index 00000000000..a4422518b22 --- /dev/null +++ b/drivers/power/sec_battery_u1.c @@ -0,0 +1,3442 @@ +/* + * sec_battery.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2010 Samsung Electronics + * + * <ms925.kim@samsung.com> + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/wakelock.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <linux/android_alarm.h> +#include <plat/adc.h> +#include <linux/power/sec_battery_u1.h> + +#if defined(CONFIG_TARGET_LOCALE_NA) || defined(CONFIG_TARGET_LOCALE_NAATT) +#define POLLING_INTERVAL (10 * 1000) +#else +#define POLLING_INTERVAL (40 * 1000) +#endif /* CONFIG_TARGET_LOCALE_NA */ + +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK +#if defined(CONFIG_TARGET_LOCALE_NAATT) +#define VF_CHECK_INTERVAL (5 * 1000) + +#define MAX_VF 1800 +#define MIN_VF 1100 +#define VF_COUNT 1 +#elif defined(CONFIG_MACH_Q1_BD) +#define VF_CHECK_INTERVAL (5 * 1000) +#define MAX_VF_ADC 2500 +#define MIN_VF_ADC 1800 +#define ADC_CH_VF 1 +#else +#define VF_CHECK_INTERVAL (10 * 1000) +#endif +#endif +#define FULL_CHARGING_TIME (6 * 60 * 60 * HZ) /* 6hr */ +#ifdef CONFIG_TARGET_LOCALE_NA +#define RECHARGING_TIME (2 * 60 * 60 * HZ) /* 2hr */ +#else +#define RECHARGING_TIME (90 * 60 * HZ) /* 1.5hr */ +#endif +#define RESETTING_CHG_TIME (10 * 60 * HZ) /* 10Min */ +#if defined(CONFIG_TARGET_LOCALE_NAATT) +#define EVENT_OVER_TIME (10 * 60 * HZ) /* 10Min */ +#endif +#ifdef CONFIG_TARGET_LOCALE_NA +#define BAT_USE_TIMER_EXPIRE (10 * 60 * HZ) /* 10 min */ +#endif + +#if defined(CONFIG_TARGET_LOCALE_NA) || defined(CONFIG_MACH_Q1_BD) +#define RECHARGING_VOLTAGE (4130 * 1000) /* 4.13 V */ +#else +#define RECHARGING_VOLTAGE (4150 * 1000) /* 4.15 V */ +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +#define RECHARGING_CND_COUNT 2 +#endif + +#define FG_T_SOC 0 +#define FG_T_VCELL 1 +#define FG_T_TEMPER 2 +#define FG_T_PSOC 3 +#define FG_T_VFOCV 4 +#define FG_T_AVGVCELL 5 + +#define ADC_SAMPLING_CNT 7 +#if defined(CONFIG_MACH_Q1_BD) +#define ADC_CH_CHGCURRENT 0 +#else +#define ADC_CH_CHGCURRENT 1 +#endif +#define ADC_TOTAL_COUNT 5 + +#if defined(CONFIG_TARGET_LOCALE_NAATT) +#define OFFSET_VIDEO_PLAY (0x1 << 0) +#define OFFSET_MP3_PLAY (0x1 << 1) +#define OFFSET_VOICE_CALL_2G (0x1 << 2) +#define OFFSET_VOICE_CALL_3G (0x1 << 3) +#define OFFSET_DATA_CALL (0x1 << 4) +#define OFFSET_WIFI (0x1 << 5) +#define OFFSET_GPS (0x1 << 6) +#define OFFSET_CAMERA_ON (0x1 << 7) +#define OFFSET_RECORDING_ON (0x1 << 8) +#endif + +/* put off current during charging*/ +#ifdef CONFIG_TARGET_LOCALE_NA +#define USE_CALL (0x1 << 0) +#define USE_VIDEO (0x1 << 1) +#define USE_MUSIC (0x1 << 2) +#define USE_BROWSER (0x1 << 3) +#define USE_HOTSPOT (0x1 << 4) +#define USE_CAMERA (0x1 << 5) +#define USE_DATA_CALL (0x1 << 6) +#define USE_WIMAX (0x1 << 7) +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +#ifdef CONFIG_MACH_U1_NA_SPR_EPIC2_REV00 +#define EVENT_BLOCK_TEMP_ADC 755 +#define HIGH_BLOCK_TEMP_ADC 715 +#define LOW_BLOCK_TEMP_ADC 521 +#define HIGH_RECOVER_TEMP_ADC 714 +#define LOW_RECOVER_TEMP_ADC 522 + +#define HIGH_BLOCK_TEMP_ADC_LPM 715 +#define LOW_BLOCK_TEMP_ADC_LPM 521 +#define HIGH_RECOVER_TEMP_ADC_LPM 714 +#define LOW_RECOVER_TEMP_ADC_LPM 522 +#else +#define EVENT_BLOCK_TEMP_ADC 777 +#define HIGH_BLOCK_TEMP_ADC 729 +#define LOW_BLOCK_TEMP_ADC 502 +#define HIGH_RECOVER_TEMP_ADC 699 +#define LOW_RECOVER_TEMP_ADC 516 + +#define HIGH_BLOCK_TEMP_ADC_LPM 710 +#define LOW_BLOCK_TEMP_ADC_LPM 506 +#define HIGH_RECOVER_TEMP_ADC_LPM 702 +#define LOW_RECOVER_TEMP_ADC_LPM 512 +#endif +#elif defined(CONFIG_TARGET_LOCALE_NAATT) +#define EVENT_BLOCK_TEMP 620 +#define HIGH_BLOCK_TEMP 490 +#define LOW_BLOCK_TEMP (-30) + +#define EVENT_RECOVER_TEMP 430 +#define HIGH_RECOVER_TEMP 430 +#define LOW_RECOVER_TEMP 0 +#elif defined(CONFIG_MACH_Q1_BD) +#define HIGH_BLOCK_TEMP 650 +#define LOW_BLOCK_TEMP (-50) +#define HIGH_RECOVER_TEMP 430 +#define LOW_RECOVER_TEMP 0 +#else +#define HIGH_BLOCK_TEMP 650 +#define LOW_BLOCK_TEMP (-30) +#define HIGH_RECOVER_TEMP 430 +#define LOW_RECOVER_TEMP 0 +#endif + +#if defined(CONFIG_TARGET_LOCALE_NAATT) +#define HIGH_DEC_CURR_TEMP 410 +#define HIGH_INC_CURR_TEMP 380 +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +#if defined(CONFIG_MACH_U1_NA_SPR_EPIC2_REV00) +#define CURRENT_OF_FULL_CHG 1 +#else +#define CURRENT_OF_FULL_CHG 350 +#endif /* CONFIG_MACH_U1_NA_SPR_EPIC2_REV00 */ +#elif defined(CONFIG_MACH_Q1_BD) +#ifdef SEC_BATTERY_1ST_2ND_TOPOFF +#if 0 +#define CURRENT_1ST_FULL_CHG 1000 /* 360mA */ +#define CURRENT_2ND_FULL_CHG 650 /* 220mA */ +#endif +#define CURRENT_1ST_FULL_CHG 580 /* 190mA */ +#define CURRENT_2ND_FULL_CHG 540 /* 170mA */ +#else +#define CURRENT_OF_FULL_CHG 850 +#endif +#else +#define CURRENT_OF_FULL_CHG 520 +#endif + +#define TEMP_BLOCK_COUNT 3 +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK +#define BAT_DET_COUNT 0 +#else +#define BAT_DET_COUNT 1 +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +#define FULL_CHG_COND_COUNT 2 +#else +#define FULL_CHG_COND_COUNT 3 +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +#define FULL_CHARGE_COND_VOLTAGE (4000 * 1000) /* 4.00 V */ +#else +#define FULL_CHARGE_COND_VOLTAGE (4150 * 1000) /* 4.15 V */ +#endif +#define INIT_CHECK_COUNT 4 + +#ifdef CONFIG_TARGET_LOCALE_NA +#define SPRINT_SLATE_TEST +#endif + +enum tmu_status_t { + TMU_STATUS_NORMAL = 0, + TMU_STATUS_TRIPPED, + TMU_STATUS_THROTTLED, + TMU_STATUS_WARNING, +}; + +enum cable_type_t { + CABLE_TYPE_NONE = 0, + CABLE_TYPE_USB, + CABLE_TYPE_AC, + CABLE_TYPE_MISC, +}; + +enum batt_full_t { + BATT_NOT_FULL = 0, + BATT_FULL, +}; + +enum { + BAT_NOT_DETECTED, + BAT_DETECTED +}; + +#if defined(CONFIG_TARGET_LOCALE_NAATT) +enum batt_temp_extra { + BATT_TEMP_EXT_NONE = 0, + BATT_TEMP_EXT_CAMCORDING_NORMAL, + BATT_TEMP_EXT_CAMCORDING_HIGH, +}; +#endif + +static ssize_t sec_bat_show_property(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t sec_bat_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +struct adc_sample { + int average_adc; + int adc_arr[ADC_TOTAL_COUNT]; + int index; +}; + +struct sec_bat_info { + struct device *dev; + + char *fuel_gauge_name; + char *charger_name; + char *sub_charger_name; + + unsigned int adc_arr_size; + struct sec_bat_adc_table_data *adc_table; + unsigned int adc_channel; + struct adc_sample temper_adc_sample; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + int vf_adc_channel; +#endif + + struct power_supply psy_bat; + struct power_supply psy_usb; + struct power_supply psy_ac; + + struct wake_lock vbus_wake_lock; + struct wake_lock monitor_wake_lock; + struct wake_lock cable_wake_lock; + + enum cable_type_t cable_type; + enum batt_full_t batt_full_status; + + unsigned int batt_temp; /* Battery Temperature (C) */ + int batt_temp_high_cnt; + int batt_temp_low_cnt; + int batt_temp_recover_cnt; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + enum batt_temp_extra batt_temp_ext; + enum batt_temp_extra batt_temp_ext_pre; +#endif + unsigned int batt_health; + unsigned int batt_vcell; + unsigned int batt_vfocv; + unsigned int batt_soc; + unsigned int batt_raw_soc; + unsigned int polling_interval; + int charging_status; + int charging_int_full_count; + int charging_adc_full_count; +#ifdef CONFIG_TARGET_LOCALE_NA + int recharging_int_threshold_count; +#endif + + unsigned int batt_temp_adc; +#ifdef CONFIG_TARGET_LOCALE_NA + unsigned int batt_temp_radc; +#endif + unsigned int batt_current_adc; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + int batt_vf_adc; + int batt_event_status; + unsigned long event_end_time; +#elif defined(CONFIG_TARGET_LOCALE_NA) + int use_call; + int use_video; + int use_music; + int use_browser; + int use_hotspot; + int use_camera; + int use_data_call; + int use_wimax; + int batt_event_status; + unsigned long event_expired_time; +#endif + struct s3c_adc_client *padc; + + struct workqueue_struct *monitor_wqueue; + struct work_struct monitor_work; + struct work_struct cable_work; + struct delayed_work polling_work; + + unsigned long charging_start_time; + unsigned long charging_passed_time; + unsigned long charging_next_time; + unsigned int recharging_status; + unsigned int batt_lpm_state; +#ifdef SPRINT_SLATE_TEST + bool slate_test_mode; +#endif + + struct mutex adclock; + + unsigned int (*get_lpcharging_state) (void); + int present; + int present_count; + bool use_sub_charger; + + int test_value; + int initial_check_count; + struct proc_dir_entry *entry; + +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK + unsigned int vf_check_interval; + struct delayed_work vf_check_work; +#endif + int batt_tmu_status; +}; + +static char *supply_list[] = { + "battery", +}; + +static enum power_supply_property sec_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CURRENT_AVG, +}; + +static enum power_supply_property sec_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +#ifdef CONFIG_TARGET_LOCALE_NA +static struct sec_bat_info *pchg; +#endif + +struct power_supply *get_power_supply_by_name(char *name) +{ + if (!name) + return (struct power_supply *)NULL; + else + return power_supply_get_by_name(name); +} + +static int sec_bat_enable_charging(struct sec_bat_info *info, bool enable); + +static int calculate_average_adc(struct sec_bat_info *info, + struct adc_sample *sample, int adc) +{ + int i, total_adc = 0; + int average_adc = sample->average_adc; + int index = sample->index; + + if (adc < 0 || adc == 0) { + dev_err(info->dev, "%s: invalid adc : %d\n", __func__, adc); + return 0; + } + + if (!average_adc) { + average_adc = adc; + for (i = 0; i < ADC_TOTAL_COUNT; i++) + sample->adc_arr[i] = adc; + } else { + sample->index = ++index >= ADC_TOTAL_COUNT ? 0 : index; + sample->adc_arr[sample->index] = adc; + for (i = 0; i < ADC_TOTAL_COUNT; i++) + total_adc += sample->adc_arr[i]; + + average_adc = total_adc / ADC_TOTAL_COUNT; + } + + sample->average_adc = average_adc; + dev_dbg(info->dev, "%s: i(%d) adc=%d, avg_adc=%d\n", __func__, + sample->index, adc, average_adc); + + return average_adc; +} + +static int sec_bat_get_fuelgauge_data(struct sec_bat_info *info, int type) +{ + struct power_supply *psy + = get_power_supply_by_name(info->fuel_gauge_name); + union power_supply_propval value; + + if (!psy) { + dev_err(info->dev, "%s: fail to get fuel gauge ps\n", __func__); + return -ENODEV; + } + + switch (type) { + case FG_T_VCELL: + value.intval = 0; /*vcell */ + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &value); + break; + case FG_T_VFOCV: + value.intval = 1; /*vfocv */ + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &value); + break; + case FG_T_AVGVCELL: + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_AVG, &value); + break; + case FG_T_SOC: + value.intval = 0; /*normal soc */ + psy->get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &value); + break; + case FG_T_PSOC: + value.intval = 1; /*raw soc */ + psy->get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &value); + break; + case FG_T_TEMPER: + psy->get_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + break; + default: + return -ENODEV; + } + + return value.intval; +} + +static int sec_bat_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_bat_info *info = container_of(ps, struct sec_bat_info, + psy_bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = info->charging_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = info->batt_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = info->batt_temp; + break; + case POWER_SUPPLY_PROP_ONLINE: + /* battery is always online */ + /* val->intval = 1; */ + val->intval = info->cable_type; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = info->batt_vcell; + if (val->intval == -1) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_CAPACITY: +#ifdef CONFIG_TARGET_LOCALE_NA + if (info->charging_status != POWER_SUPPLY_STATUS_FULL + && info->batt_soc == 100) { + val->intval = 99; + break; + } +#endif /*CONFIG_TARGET_LOCALE_NA */ + + if (info->charging_status == POWER_SUPPLY_STATUS_FULL) { + val->intval = 100; + break; + } + val->intval = info->batt_soc; + if (val->intval == -1) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = -1; + break; + default: + return -EINVAL; + } + return 0; +} + +static void sec_bat_handle_charger_topoff(struct sec_bat_info *info) +{ +#ifdef SEC_BATTERY_1ST_2ND_TOPOFF + if (info->charging_status != POWER_SUPPLY_STATUS_FULL) { + /* 1st top-off */ + info->charging_status = POWER_SUPPLY_STATUS_FULL; + + dev_info(info->dev, "%s: Charging 1st Top-off\n", __func__); + } else { + /* 2nd top-off, recharging top-off */ + if (!sec_bat_enable_charging(info, false)) { + info->batt_full_status = BATT_FULL; + info->recharging_status = false; + } + + dev_info(info->dev, "%s: Charging 2nd Top-off\n", __func__); + } +#else + if (!sec_bat_enable_charging(info, false)) { + info->charging_status = POWER_SUPPLY_STATUS_FULL; + info->batt_full_status = BATT_FULL; + info->recharging_status = false; + + dev_info(info->dev, "%s: Charging Top-off\n", __func__); + } +#endif +} + +static int sec_bat_set_property(struct power_supply *ps, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_bat_info *info = container_of(ps, struct sec_bat_info, + psy_bat); + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval value; + + if (!psy) { + dev_err(info->dev, "%s: fail to get %s ps\n", + __func__, info->charger_name); + return -EINVAL; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + dev_info(info->dev, "%s: topoff intr\n", __func__); + if (val->intval != POWER_SUPPLY_STATUS_FULL) + return -EINVAL; + + if (info->use_sub_charger) { +#if !defined(CONFIG_MACH_Q1_BD) + if (info->cable_type == CABLE_TYPE_USB || + info->cable_type == CABLE_TYPE_MISC) +#endif + sec_bat_handle_charger_topoff(info); + break; + } + + if (info->batt_full_status == BATT_NOT_FULL) { + info->recharging_status = false; + info->batt_full_status = BATT_FULL; + info->charging_status = POWER_SUPPLY_STATUS_FULL; + + info->charging_start_time = 0; + info->charging_passed_time = 0; + info->charging_next_time = 0; + + /* disable charging */ + value.intval = POWER_SUPPLY_STATUS_DISCHARGING; + psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, + &value); + } +#if 0 /* for reference */ + if (info->batt_full_status == BATT_NOT_FULL) { + info->batt_full_status = BATT_1ST_FULL; + info->charging_status = POWER_SUPPLY_STATUS_FULL; + /* TODO: set topoff current 60mA */ + value.intval = 120; + psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL, + &value); + } else { + info->batt_full_status = BATT_2ND_FULL; + info->recharging_status = false; + /* disable charging */ + value.intval = POWER_SUPPLY_STATUS_DISCHARGING; + psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, + &value); + } +#endif + break; + case POWER_SUPPLY_PROP_CAPACITY: + dev_info(info->dev, "%s: refresh battery data\n", __func__); + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + /* TODO: lowbatt interrupt: called by fuel gauge */ + dev_info(info->dev, "%s: lowbatt intr\n", __func__); + if (val->intval != POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL) + return -EINVAL; + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + break; + case POWER_SUPPLY_PROP_ONLINE: + /* cable is attached or detached. called by USB switch(MUIC) */ + dev_info(info->dev, "%s: cable was changed(%d)\n", __func__, + val->intval); + switch (val->intval) { + case POWER_SUPPLY_TYPE_BATTERY: + info->cable_type = CABLE_TYPE_NONE; + break; + case POWER_SUPPLY_TYPE_MAINS: + info->cable_type = CABLE_TYPE_AC; + break; + case POWER_SUPPLY_TYPE_USB: + info->cable_type = CABLE_TYPE_USB; + break; + case POWER_SUPPLY_TYPE_MISC: + info->cable_type = CABLE_TYPE_MISC; + break; + default: + return -EINVAL; + } + wake_lock(&info->cable_wake_lock); + queue_work(info->monitor_wqueue, &info->cable_work); + break; + case POWER_SUPPLY_PROP_HEALTH: + /* call by TMU driver + * alarm for abnormal temperature increasement + */ + info->batt_tmu_status = val->intval; + + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + dev_info(info->dev, "%s: TMU status has been changed(%d)\n", + __func__, info->batt_tmu_status); + + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_usb_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_bat_info *info = container_of(ps, struct sec_bat_info, + psy_usb); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + /* Set enable=1 only if the USB charger is connected */ + val->intval = (info->cable_type == CABLE_TYPE_USB); + + return 0; +} + +static int sec_ac_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_bat_info *info = container_of(ps, struct sec_bat_info, + psy_ac); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + /* Set enable=1 only if the AC charger is connected */ + val->intval = (info->cable_type == CABLE_TYPE_AC) || + (info->cable_type == CABLE_TYPE_MISC); + + return 0; +} + +static int sec_bat_get_adc_data(struct sec_bat_info *info, int adc_ch) +{ + int adc_data; + int adc_max = 0; + int adc_min = 0; + int adc_total = 0; + int i; + int err_value; + + for (i = 0; i < ADC_SAMPLING_CNT; i++) { + adc_data = s3c_adc_read(info->padc, adc_ch); + if (adc_data < 0) { + dev_err(info->dev, "%s : err(%d) returned, skip read\n", + __func__, adc_data); + err_value = adc_data; + 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) / (ADC_SAMPLING_CNT - 2); + err: + return err_value; +} + +#ifdef CONFIG_TARGET_LOCALE_NA +static inline int s3c_read_temper_adc(struct sec_bat_info *info) +{ + int adc; + + mutex_lock(&info->adclock); + adc = sec_bat_get_adc_data(info, info->adc_channel); + mutex_unlock(&info->adclock); + if (adc <= 0) + adc = info->batt_temp_adc; + + info->batt_temp_adc = + calculate_average_adc(info, &info->temper_adc_sample, adc); + + return info->batt_temp_adc; +} +#else +static inline int s3c_read_temper_adc(struct sec_bat_info *info) +{ + int adc; + + mutex_lock(&info->adclock); + adc = sec_bat_get_adc_data(info, info->adc_channel); + mutex_unlock(&info->adclock); + if (adc <= 0) + adc = info->batt_temp_adc; + info->batt_temp_adc = adc; + + return adc; +} +#endif + +#if defined(CONFIG_TARGET_LOCALE_NAATT) +static int is_event_end_timer_running(struct sec_bat_info *info) +{ + unsigned long passed_time = 0; + + if (info->event_end_time == 0xFFFFFFFF) /*initial value */ + return false; + + if (jiffies >= info->event_end_time) + passed_time = jiffies - info->event_end_time; + else + passed_time = 0xFFFFFFFF - info->event_end_time + jiffies; + + if (time_after(passed_time, (unsigned long)EVENT_OVER_TIME)) { + info->event_end_time = 0xFFFFFFFF; + dev_info(info->dev, "%s: Event timer is over 10 min\n", + __func__); + return false; + } else { + dev_info(info->dev, "%s: Event timer is running(%u s)\n", + __func__, jiffies_to_msecs(passed_time) / 1000); + return true; + } + + return false; +} + +static int get_recording_state(struct sec_bat_info *info) +{ + return info->batt_event_status & OFFSET_RECORDING_ON; +} +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +static int is_event_end_timer_running(struct sec_bat_info *info) +{ + unsigned long passed_time = 0; + + if (info->event_expired_time == 0xFFFFFFFF) /*initial value */ + return false; + + if (jiffies >= info->event_expired_time) + passed_time = jiffies - info->event_expired_time; + else + passed_time = 0xFFFFFFFF - info->event_expired_time + jiffies; + + if (time_after(passed_time, (unsigned long)BAT_USE_TIMER_EXPIRE)) { + info->event_expired_time = 0xFFFFFFFF; + dev_info(info->dev, "[SPR_NA] %s: Event timer is over 10 min\n", + __func__); + return false; + } else { + dev_info(info->dev, + "[SPR_NA] %s: Event timer is running(%u s)\n", + __func__, jiffies_to_msecs(passed_time) / 1000); + return true; + } + + return false; +} + +static unsigned long sec_rescale_temp_adc(struct sec_bat_info *info) +{ + int adc_tmp = info->batt_temp_adc; + int adc_tmp1 = 0; + int adc_tmp2 = 0; + + adc_tmp1 = adc_tmp * 10; + adc_tmp2 = (40950 - adc_tmp1); + adc_tmp = adc_tmp2 / 50; + if ((adc_tmp2 % 10) >= 5) + adc_tmp += 1; + + info->batt_temp_radc = adc_tmp; + + dev_dbg(info->dev, "deb***: [battery] bat temper : %d, rescale : %d\n", + info->batt_temp_adc, adc_tmp); + + return adc_tmp; +} +#endif + +#ifdef CONFIG_TARGET_LOCALE_NA +static int sec_bat_check_temper(struct sec_bat_info *info) +{ + struct power_supply *psy + = get_power_supply_by_name(info->fuel_gauge_name); + union power_supply_propval value; + int ret; + + int temp; + int temp_adc = s3c_read_temper_adc(info); + int temp_radc = sec_rescale_temp_adc(info); + int health = info->batt_health; + int low = 0; + int high = 0; + int mid = 0; + + /*calculate_average_adc(info, &info->temper_adc_sample, temp_adc);*/ + + if (!info->adc_table || !info->adc_arr_size) { + /* using fake temp */ + temp = 300; + info->batt_temp = temp; + return temp; + } + high = info->adc_arr_size - 1; + + while (low <= high) { + mid = (low + high) / 2; + if (info->adc_table[mid].adc > temp_adc) + high = mid - 1; + else if (info->adc_table[mid].adc < temp_adc) + low = mid + 1; + else + break; + } + temp = info->adc_table[mid].temperature; + + info->batt_temp = temp; + + if (info->get_lpcharging_state) { + if (info->get_lpcharging_state()) { + if (temp_radc >= HIGH_BLOCK_TEMP_ADC_LPM) { + if (health != POWER_SUPPLY_HEALTH_OVERHEAT && + health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_high_cnt < + TEMP_BLOCK_COUNT) + info->batt_temp_high_cnt++; + dev_info(info->dev, "%s: high count = %d\n", + __func__, info->batt_temp_high_cnt); + } else if (temp_radc <= HIGH_RECOVER_TEMP_ADC_LPM && + temp_radc >= LOW_RECOVER_TEMP_ADC_LPM) { + if (health == POWER_SUPPLY_HEALTH_OVERHEAT || + health == POWER_SUPPLY_HEALTH_COLD) { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + + if (info->batt_temp_recover_cnt < + TEMP_BLOCK_COUNT) + info->batt_temp_recover_cnt++; + dev_info(info->dev, + "%s: recovery count = %d\n", + __func__, + info->batt_temp_recover_cnt); + } + } else if (temp_radc <= LOW_BLOCK_TEMP_ADC_LPM) { + if (health != POWER_SUPPLY_HEALTH_COLD && + health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_low_cnt < + TEMP_BLOCK_COUNT) + info->batt_temp_low_cnt++; + dev_info(info->dev, "%s: low count = %d\n", + __func__, info->batt_temp_low_cnt); + } else { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + } + } else { + if ((info->batt_event_status) + || (is_event_end_timer_running(info))) { + dev_info(info->dev, + "[NA_SPR] Changed Put off Current", + __func__); + if (temp_radc >= EVENT_BLOCK_TEMP_ADC) { + if (health != + POWER_SUPPLY_HEALTH_OVERHEAT + && health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_high_cnt < + TEMP_BLOCK_COUNT) + info-> + batt_temp_high_cnt++; + dev_info(info->dev, + "%s: high count = %d\n", + __func__, + info->batt_temp_high_cnt); + } else if (temp_radc <= HIGH_RECOVER_TEMP_ADC + && temp_radc >= + LOW_RECOVER_TEMP_ADC) { + if (health == + POWER_SUPPLY_HEALTH_OVERHEAT + || health == + POWER_SUPPLY_HEALTH_COLD) { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + + if (info-> + batt_temp_recover_cnt < + TEMP_BLOCK_COUNT) + info-> + batt_temp_recover_cnt++; + dev_info(info->dev, + "%s: recovery count = %d\n", + __func__, + info-> + batt_temp_recover_cnt); + } + } else if (temp_radc <= LOW_BLOCK_TEMP_ADC) { + if (health != POWER_SUPPLY_HEALTH_COLD + && health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_low_cnt < + TEMP_BLOCK_COUNT) + info-> + batt_temp_low_cnt++; + dev_info(info->dev, + "%s: low count = %d\n", + __func__, + info->batt_temp_low_cnt); + } else { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + } + } else { + if (temp_radc >= HIGH_BLOCK_TEMP_ADC) { + if (health != + POWER_SUPPLY_HEALTH_OVERHEAT + && health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_high_cnt < + TEMP_BLOCK_COUNT) + info-> + batt_temp_high_cnt++; + dev_info(info->dev, + "%s: high count = %d\n", + __func__, + info->batt_temp_high_cnt); + } else if (temp_radc <= HIGH_RECOVER_TEMP_ADC + && temp_radc >= + LOW_RECOVER_TEMP_ADC) { + if (health == + POWER_SUPPLY_HEALTH_OVERHEAT + || health == + POWER_SUPPLY_HEALTH_COLD) { + + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + + if (info-> + batt_temp_recover_cnt < + TEMP_BLOCK_COUNT) + info-> + batt_temp_recover_cnt++; + dev_info(info->dev, + "%s: recovery count = %d\n", + __func__, + info-> + batt_temp_recover_cnt); + } + } else if (temp_radc <= LOW_BLOCK_TEMP_ADC) { + if (health != POWER_SUPPLY_HEALTH_COLD + && health != + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_low_cnt < + TEMP_BLOCK_COUNT) + info-> + batt_temp_low_cnt++; + dev_info(info->dev, + "%s: low count = %d\n", + __func__, + info->batt_temp_low_cnt); + } else { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + } + } + } + } + + if (info->cable_type == CABLE_TYPE_NONE) { + if (info->batt_health == POWER_SUPPLY_HEALTH_OVERHEAT || + info->batt_health == POWER_SUPPLY_HEALTH_COLD) { + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + goto skip; + } + } + + if (info->batt_temp_high_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (info->batt_temp_low_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_COLD; + else if (info->batt_temp_recover_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + + /*else + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; */ + + /*else if (info->batt_temp_recover_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; */ + skip: + /* Set temperature to fuel gauge */ + if (info->fuel_gauge_name) { + value.intval = info->batt_temp / 10; + ret = psy->set_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + if (ret) { + dev_err(info->dev, "%s: fail to set temperature(%d)\n", + __func__, ret); + } + } + + dev_info(info->dev, "%s: temp=%d, adc=%d\n", __func__, temp, temp_adc); + + return temp; +} + +static void sec_bat_get_dcinovp(struct sec_bat_info *info) +{ + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval value; + + if (!psy) { + dev_err(info->dev, "%s: fail to get %s ps\n", + __func__, info->charger_name); + return; + } + + if (info->slate_test_mode) { + info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; + info->cable_type = CABLE_TYPE_NONE; + } else { + psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value); + info->charging_status = value.intval; + } +} + +#elif defined(CONFIG_TARGET_LOCALE_NAATT) +static int sec_bat_check_temper(struct sec_bat_info *info) +{ + struct power_supply *psy + = get_power_supply_by_name(info->fuel_gauge_name); + union power_supply_propval value; + int ret; + + int temp; + int temp_adc = s3c_read_temper_adc(info); + int health = info->batt_health; + int low = 0; + int high = 0; + int mid = 0; + + calculate_average_adc(info, &info->temper_adc_sample, temp_adc); + + if (!info->adc_table || !info->adc_arr_size) { + /* using fake temp */ + temp = 300; + info->batt_temp = temp; + return temp; + } + high = info->adc_arr_size - 1; + + while (low <= high) { + mid = (low + high) / 2; + if (info->adc_table[mid].adc > temp_adc) + high = mid - 1; + else if (info->adc_table[mid].adc < temp_adc) + low = mid + 1; + else + break; + } + temp = info->adc_table[mid].temperature; + + info->batt_temp = temp; + + if ((info->batt_event_status) || (is_event_end_timer_running(info))) { + /*printk("[%s] event temp is applied\n", __func__); */ + if (temp >= EVENT_BLOCK_TEMP) { + if (health != POWER_SUPPLY_HEALTH_OVERHEAT && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_high_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_high_cnt++; + dev_info(info->dev, + "%s: event high count = %d(in event)\n", + __func__, info->batt_temp_high_cnt); + } else if (temp <= EVENT_RECOVER_TEMP + && temp >= LOW_RECOVER_TEMP) { + if (health == POWER_SUPPLY_HEALTH_OVERHEAT + || health == POWER_SUPPLY_HEALTH_COLD) { + + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + + if (info->batt_temp_recover_cnt < + TEMP_BLOCK_COUNT) + info->batt_temp_recover_cnt++; + dev_info(info->dev, + "%s: event recovery count = %d\n", + __func__, info->batt_temp_recover_cnt); + } + } else if (temp <= LOW_BLOCK_TEMP) { + if (health != POWER_SUPPLY_HEALTH_COLD && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_low_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_low_cnt++; + dev_info(info->dev, + "%s: event low count = %d(in event)\n", + __func__, info->batt_temp_low_cnt); + } else { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + } + } else { +#if defined(CONFIG_TARGET_LOCALE_NAATT) + info->batt_temp_ext_pre = info->batt_temp_ext; + switch (info->batt_temp_ext) { + case BATT_TEMP_EXT_NONE: + if (get_recording_state(info)) + info->batt_temp_ext = + BATT_TEMP_EXT_CAMCORDING_NORMAL; + break; + case BATT_TEMP_EXT_CAMCORDING_NORMAL: + if (!get_recording_state(info)) + info->batt_temp_ext = BATT_TEMP_EXT_NONE; + else if (temp >= HIGH_DEC_CURR_TEMP) + info->batt_temp_ext = + BATT_TEMP_EXT_CAMCORDING_HIGH; + break; + case BATT_TEMP_EXT_CAMCORDING_HIGH: + if (!get_recording_state(info)) + info->batt_temp_ext = BATT_TEMP_EXT_NONE; + else if (temp <= HIGH_INC_CURR_TEMP) + info->batt_temp_ext = + BATT_TEMP_EXT_CAMCORDING_NORMAL; + break; + default: + break; + } + dev_info(info->dev, + "%s: batt_temp_ext_pre=%d, batt_temp_ext=%d\n", + __func__, info->batt_temp_ext_pre, + info->batt_temp_ext); +#endif + /*printk("[%s] nomal temp is applied\n", __func__); */ + if (temp >= HIGH_BLOCK_TEMP) { + if (health != POWER_SUPPLY_HEALTH_OVERHEAT && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_high_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_high_cnt++; + dev_info(info->dev, "%s: high count = %d\n", + __func__, info->batt_temp_high_cnt); + } else if (temp <= HIGH_RECOVER_TEMP + && temp >= LOW_RECOVER_TEMP) { + if (health == POWER_SUPPLY_HEALTH_OVERHEAT + || health == POWER_SUPPLY_HEALTH_COLD) { + + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + + if (info->batt_temp_recover_cnt < + TEMP_BLOCK_COUNT) + info->batt_temp_recover_cnt++; + dev_info(info->dev, "%s: recovery count = %d\n", + __func__, info->batt_temp_recover_cnt); + } + } else if (temp <= LOW_BLOCK_TEMP) { + if (health != POWER_SUPPLY_HEALTH_COLD && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_low_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_low_cnt++; + dev_info(info->dev, "%s: low count = %d\n", + __func__, info->batt_temp_low_cnt); + } else { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + } + } + + if (info->cable_type == CABLE_TYPE_NONE) { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + } + + if (info->batt_temp_high_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (info->batt_temp_low_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_COLD; + else if (info->batt_temp_recover_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + + /* Set temperature to fuel gauge */ + if (info->fuel_gauge_name) { + value.intval = info->batt_temp / 10; + ret = psy->set_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + if (ret) { + dev_err(info->dev, "%s: fail to set temperature(%d)\n", + __func__, ret); + } + } + + dev_info(info->dev, "%s: temp=%d, adc=%d\n", __func__, temp, temp_adc); + + return temp; +} + +#else +static int sec_bat_check_temper(struct sec_bat_info *info) +{ + struct power_supply *psy + = get_power_supply_by_name(info->fuel_gauge_name); + union power_supply_propval value; + int ret; + + int temp; + int temp_adc = s3c_read_temper_adc(info); + int health = info->batt_health; + int low = 0; + int high = 0; + int mid = 0; + + calculate_average_adc(info, &info->temper_adc_sample, temp_adc); + + if (!info->adc_table || !info->adc_arr_size) { + /* using fake temp */ + temp = 300; + info->batt_temp = temp; + return temp; + } + high = info->adc_arr_size - 1; + + while (low <= high) { + mid = (low + high) / 2; + if (info->adc_table[mid].adc > temp_adc) + high = mid - 1; + else if (info->adc_table[mid].adc < temp_adc) + low = mid + 1; + else + break; + } + temp = info->adc_table[mid].temperature; + + info->batt_temp = temp; + + if (temp >= HIGH_BLOCK_TEMP) { + if (health != POWER_SUPPLY_HEALTH_OVERHEAT && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_high_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_high_cnt++; + dev_info(info->dev, "%s: high count = %d\n", + __func__, info->batt_temp_high_cnt); + } else if (temp <= HIGH_RECOVER_TEMP && temp >= LOW_RECOVER_TEMP) { + if (health == POWER_SUPPLY_HEALTH_OVERHEAT || + health == POWER_SUPPLY_HEALTH_COLD) + if (info->batt_temp_recover_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_recover_cnt++; + dev_info(info->dev, "%s: recovery count = %d\n", + __func__, info->batt_temp_recover_cnt); + } else if (temp <= LOW_BLOCK_TEMP) { + if (health != POWER_SUPPLY_HEALTH_COLD && + health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) + if (info->batt_temp_low_cnt < TEMP_BLOCK_COUNT) + info->batt_temp_low_cnt++; + dev_info(info->dev, "%s: low count = %d\n", + __func__, info->batt_temp_low_cnt); + } else { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + } + + if (info->batt_temp_high_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (info->batt_temp_low_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_COLD; + else if (info->batt_temp_recover_cnt >= TEMP_BLOCK_COUNT) + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + + /* Set temperature to fuel gauge */ + if (info->fuel_gauge_name) { + value.intval = info->batt_temp / 10; + ret = psy->set_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + if (ret) { + dev_err(info->dev, "%s: fail to set temperature(%d)\n", + __func__, ret); + } + } + + dev_info(info->dev, "%s: temp=%d, adc=%d\n", __func__, temp, temp_adc); + + return temp; +} +#endif +static void check_chgcurrent(struct sec_bat_info *info) +{ + unsigned long chg_current_adc = 0; + + mutex_lock(&info->adclock); + chg_current_adc = sec_bat_get_adc_data(info, ADC_CH_CHGCURRENT); + mutex_unlock(&info->adclock); + if (chg_current_adc < 0) + chg_current_adc = info->batt_current_adc; + info->batt_current_adc = chg_current_adc; + + dev_dbg(info->dev, + "[battery] charging current = %d\n", info->batt_current_adc); +} + +static bool sec_check_chgcurrent(struct sec_bat_info *info) +{ + switch (info->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + info->batt_current_adc = 0; + if ((info->batt_full_status == BATT_FULL) && + (info->recharging_status == false)) + break; + case POWER_SUPPLY_STATUS_CHARGING: + check_chgcurrent(info); + + if (info->batt_vfocv >= FULL_CHARGE_COND_VOLTAGE / 1000) { + +#ifdef SEC_BATTERY_1ST_2ND_TOPOFF + if (((info->charging_status == + POWER_SUPPLY_STATUS_CHARGING) + && (info->batt_current_adc <= + CURRENT_1ST_FULL_CHG)) + || + ((info->charging_status == POWER_SUPPLY_STATUS_FULL) + && (info->batt_current_adc <= + CURRENT_2ND_FULL_CHG))) { +#else + if (info->batt_current_adc <= CURRENT_OF_FULL_CHG) { +#endif + /*TA full charged */ + info->charging_adc_full_count++; + if (info->charging_adc_full_count >= + FULL_CHG_COND_COUNT) { + info->charging_adc_full_count = 0; + sec_bat_handle_charger_topoff(info); + + return true; + } + dev_info(info->dev, "%s : ADC full cnt = %d\n", + __func__, + info->charging_adc_full_count); + } else + info->charging_adc_full_count = 0; + } + break; + default: + info->charging_adc_full_count = 0; + info->batt_current_adc = 0; + break; + } + + return false; +} + +static void sec_bat_update_info(struct sec_bat_info *info) +{ + info->batt_raw_soc = sec_bat_get_fuelgauge_data(info, FG_T_PSOC); + info->batt_soc = sec_bat_get_fuelgauge_data(info, FG_T_SOC); + info->batt_vcell = sec_bat_get_fuelgauge_data(info, FG_T_VCELL); + info->batt_vfocv = sec_bat_get_fuelgauge_data(info, FG_T_VFOCV); +} + +static int sec_bat_enable_charging_main(struct sec_bat_info *info, bool enable) +{ + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval val_type, val_chg_current, val_topoff; + int ret; + + if (!psy) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return -ENODEV; + } + + info->batt_full_status = BATT_NOT_FULL; + + if (enable) { /* Enable charging */ + switch (info->cable_type) { + case CABLE_TYPE_USB: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + break; + case CABLE_TYPE_AC: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 650; /* mA */ + break; + case CABLE_TYPE_MISC: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + break; + default: + dev_err(info->dev, "%s: Invalid func use\n", __func__); + return -EINVAL; + } + + /* Set charging current */ + ret = psy->set_property(psy, POWER_SUPPLY_PROP_CURRENT_NOW, + &val_chg_current); + if (ret) { + dev_err(info->dev, "%s: fail to set charging cur(%d)\n", + __func__, ret); + return ret; + } + + /* Set topoff current */ + /* mA: TODO: should change 200->160 */ + val_topoff.intval = 200; + ret = psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL, + &val_topoff); + if (ret) { + dev_err(info->dev, "%s: fail to set topoff cur(%d)\n", + __func__, ret); + return ret; + } + + /*Reset charging start time only in initial charging start */ + if (info->charging_start_time == 0) { + info->charging_start_time = jiffies; + info->charging_next_time = RESETTING_CHG_TIME; + } + } else { /* Disable charging */ + val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING; + + info->charging_start_time = 0; + info->charging_passed_time = 0; + info->charging_next_time = 0; + } + + ret = psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + return ret; + } + return 0; +} + +static int sec_bat_enable_charging_sub(struct sec_bat_info *info, bool enable) +{ + struct power_supply *psy_main = + get_power_supply_by_name(info->charger_name); + struct power_supply *psy_sub = + get_power_supply_by_name(info->sub_charger_name); + union power_supply_propval val_type, val_chg_current; + int ret; + + if (!psy_main || !psy_sub) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return -ENODEV; + } + + info->batt_full_status = BATT_NOT_FULL; + + if (enable) { /* Enable charging */ + val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging" + " status-main(%d)\n", __func__, ret); + return ret; + } +#if defined(CONFIG_MACH_Q1_BD) + /* Set charging current only in charging start time */ + if (info->charging_start_time == 0) { +#endif + switch (info->cable_type) { + case CABLE_TYPE_USB: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + break; + case CABLE_TYPE_AC: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + if (info->batt_temp_ext == + BATT_TEMP_EXT_CAMCORDING_HIGH) + val_chg_current.intval = 450; /* mA */ + else +#endif + val_chg_current.intval = 650; /* mA */ + break; + case CABLE_TYPE_MISC: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + break; + default: + dev_err(info->dev, "%s: Invalid func use\n", + __func__); + return -EINVAL; + } + + /* Set charging current */ + ret = psy_sub->set_property(psy_sub, + POWER_SUPPLY_PROP_CURRENT_NOW, + &val_chg_current); + if (ret) { + dev_err(info->dev, + "%s: fail to set charging cur(%d)\n", + __func__, ret); + return ret; + } +#if defined(CONFIG_MACH_Q1_BD) + /* Reset charging */ + } else + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; +#endif + + /*Reset charging start time only in initial charging start */ + if (info->charging_start_time == 0) { + info->charging_start_time = jiffies; + info->charging_next_time = RESETTING_CHG_TIME; + } + } else { /* Disable charging */ + val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging" + " status-main(%d)\n", __func__, ret); + return ret; + } + info->charging_start_time = 0; + info->charging_passed_time = 0; + info->charging_next_time = 0; + } + + ret = psy_sub->set_property(psy_sub, POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int sec_bat_enable_charging(struct sec_bat_info *info, bool enable) +{ + if (enable && (info->batt_health != POWER_SUPPLY_HEALTH_GOOD)) { + info->charging_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + dev_info(info->dev, "%s: Battery is NOT good!!!\n", __func__); + return -EPERM; + } + + if (info->use_sub_charger) + return sec_bat_enable_charging_sub(info, enable); + else + return sec_bat_enable_charging_main(info, enable); +} + +static void sec_bat_cable_work(struct work_struct *work) +{ + struct sec_bat_info *info = container_of(work, struct sec_bat_info, + cable_work); + + switch (info->cable_type) { + case CABLE_TYPE_NONE: + if (!sec_bat_enable_charging(info, false)) { + info->batt_full_status = BATT_NOT_FULL; + info->recharging_status = false; + info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + info->batt_temp_ext = BATT_TEMP_EXT_NONE; + info->batt_temp_ext_pre = BATT_TEMP_EXT_NONE; +#endif + wake_lock_timeout(&info->vbus_wake_lock, HZ * 5); + } + break; + case CABLE_TYPE_USB: + case CABLE_TYPE_AC: + case CABLE_TYPE_MISC: + if (!sec_bat_enable_charging(info, true)) { + info->charging_status = POWER_SUPPLY_STATUS_CHARGING; + wake_lock(&info->vbus_wake_lock); + } + break; + default: + dev_err(info->dev, "%s: Invalid cable type\n", __func__); + break; + } + + power_supply_changed(&info->psy_ac); + power_supply_changed(&info->psy_usb); + + wake_unlock(&info->cable_wake_lock); +} + +static bool sec_bat_charging_time_management(struct sec_bat_info *info) +{ + if (info->charging_start_time == 0) { + dev_info(info->dev, + "%s: charging_start_time has never been used since initializing\n", + __func__); + return false; + } + + if (jiffies >= info->charging_start_time) + info->charging_passed_time = + jiffies - info->charging_start_time; + else + info->charging_passed_time = + 0xFFFFFFFF - info->charging_start_time + jiffies; + + switch (info->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + if (info->recharging_status == true && + time_after(info->charging_passed_time, + (unsigned long)RECHARGING_TIME)) { + if (!sec_bat_enable_charging(info, false)) { + info->recharging_status = false; + dev_info(info->dev, + "%s: Recharging timer expired\n", + __func__); + return true; + } + } +#ifndef SEC_BATTERY_1ST_2ND_TOPOFF + break; +#endif + case POWER_SUPPLY_STATUS_CHARGING: +#if defined(CONFIG_MACH_Q1_BD) + if (info->cable_type != CABLE_TYPE_USB) { +#endif + if (time_after(info->charging_passed_time, + (unsigned long)FULL_CHARGING_TIME)) { + if (!sec_bat_enable_charging(info, false)) { + info->charging_status = + POWER_SUPPLY_STATUS_FULL; + info->batt_full_status = BATT_FULL; + info->recharging_status = false; + + dev_info(info->dev, + "%s: Charging timer expired\n", + __func__); + return true; + } + } +#if defined(CONFIG_MACH_Q1_BD) + } +#endif + if (time_after(info->charging_passed_time, + info->charging_next_time)) { + /*reset current in charging status */ + if (!sec_bat_enable_charging(info, true)) { + info->charging_next_time = + info->charging_passed_time + + RESETTING_CHG_TIME; + + dev_info(info->dev, + "%s: Reset charging current\n", + __func__); + } + } + break; + default: + dev_info(info->dev, "%s: Undefine Battery Status\n", __func__); + return false; + } + + dev_info(info->dev, "Time past : %u secs\n", + jiffies_to_msecs(info->charging_passed_time) / 1000); + + return false; +} + +#if defined(CONFIG_TARGET_LOCALE_NAATT) +static void sec_bat_check_vf_adc(struct sec_bat_info *info) +{ + int adc; + static int invalid_vf_count; + + if (system_rev == 11) + return; + + mutex_lock(&info->adclock); + adc = sec_bat_get_adc_data(info, info->vf_adc_channel); + mutex_unlock(&info->adclock); + + if (adc <= 0) + adc = info->batt_vf_adc; + + info->batt_vf_adc = adc; + + if ((info->batt_vf_adc > MAX_VF || info->batt_vf_adc < MIN_VF) + && ((info->cable_type == CABLE_TYPE_AC) + || info->cable_type == CABLE_TYPE_USB)) { + invalid_vf_count++; + + printk(KERN_DEBUG + "[%s] invalid VF is detected... (vf = %d, count = %d)\n", + __func__, info->batt_vf_adc, invalid_vf_count); + + if (invalid_vf_count >= VF_COUNT) { + printk(KERN_DEBUG + "[%s] invalid VF is detected over %d times and now power off.\n", + __func__, invalid_vf_count); + if (pm_power_off) + pm_power_off(); + } + } else { + invalid_vf_count = 0; + } + + return; +} +#endif + +static void sec_bat_check_vf(struct sec_bat_info *info) +{ + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval value; + int ret; + + if (!psy) { + dev_err(info->dev, "%s: fail to get %s ps\n", + __func__, info->charger_name); + return; + } + +#if defined(CONFIG_MACH_Q1_BD) + if (system_rev <= HWREV_FOR_BATTERY) { + int adc; + + mutex_lock(&info->adclock); + adc = sec_bat_get_adc_data(info, ADC_CH_VF); + mutex_unlock(&info->adclock); + + dev_info(info->dev, "%s: adc (%d/%d)\n", __func__, adc, + HWREV_FOR_BATTERY); + + if (adc > MAX_VF_ADC || adc < MIN_VF_ADC) + value.intval = BAT_NOT_DETECTED; + else + value.intval = BAT_DETECTED; + } else { +#endif + ret = psy->get_property(psy, POWER_SUPPLY_PROP_PRESENT, &value); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", + __func__, ret); + return; + } +#if defined(CONFIG_MACH_Q1_BD) + } +#endif + + if (value.intval == BAT_NOT_DETECTED) { + if (info->present_count < BAT_DET_COUNT) + info->present_count++; + else { + info->present = BAT_NOT_DETECTED; + if (info->batt_health == POWER_SUPPLY_HEALTH_GOOD) + info->batt_health = + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + } + } else { + info->present = BAT_DETECTED; + if ((info->batt_health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) || + (info->batt_health == POWER_SUPPLY_HEALTH_UNKNOWN)) + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + info->present_count = 0; + } + + dev_info(info->dev, "%s: Battery Health (%d)\n", + __func__, info->batt_health); + return; +} + +#if (defined(CONFIG_MACH_Q1_BD) && defined(CONFIG_SMB328_CHARGER)) +static void sec_bat_check_ovp(struct sec_bat_info *info) +{ + struct power_supply *psy = + get_power_supply_by_name(info->sub_charger_name); + union power_supply_propval value; + int ret; + + if (!psy) { + dev_err(info->dev, "%s: fail to get %s ps\n", + __func__, info->sub_charger_name); + return; + } + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_HEALTH, &value); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to get health(%d)\n", + __func__, ret); + return; + } + + if (value.intval == POWER_SUPPLY_HEALTH_OVERVOLTAGE) + info->batt_health = value.intval; +} +#endif + +static bool sec_bat_check_ing_level_trigger(struct sec_bat_info *info) +{ + struct power_supply *psy; + union power_supply_propval value; + int ret; + + if (info->use_sub_charger && info->sub_charger_name) { + psy = get_power_supply_by_name(info->sub_charger_name); + + if (!psy) { + dev_err(info->dev, "%s: fail to get %s ps\n", + __func__, info->sub_charger_name); + return false; + } + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", + __func__, ret); + return false; + } + + switch (info->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + if (info->recharging_status == false) + break; + case POWER_SUPPLY_STATUS_CHARGING: + switch (value.intval) { +#ifdef SEC_BATTERY_TOPOFF_BY_CHARGER + case POWER_SUPPLY_STATUS_FULL: + /* top-off by full charging */ + if (info->batt_vcell >= + FULL_CHARGE_COND_VOLTAGE) { + sec_bat_handle_charger_topoff(info); + dev_info(info->dev, + "%s : top-off by full charging\n", + __func__); + return true; + } else { + /*reactivate charging in */ + /*next monitor work */ + /*for abnormal */ + /*full-charged status */ + info->charging_next_time = + info->charging_passed_time + HZ; + } + break; +#endif +#if defined(CONFIG_MACH_Q1_BD) + case POWER_SUPPLY_STATUS_NOT_CHARGING: + ret = + psy->get_property(psy, + POWER_SUPPLY_PROP_HEALTH, + &value); + + if (ret < 0) { + dev_err(info->dev, + "%s: fail to get health(%d)\n", + __func__, ret); + return false; + } + info->batt_health = value.intval; +#else + case POWER_SUPPLY_STATUS_DISCHARGING: + if (info->cable_type != CABLE_TYPE_NONE) + info->batt_health = + POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: +#if defined(CONFIG_MACH_U1_NA_SPR_EPIC2_REV00) + if (info->cable_type == CABLE_TYPE_USB || + info->cable_type == CABLE_TYPE_MISC || + info->cable_type == CABLE_TYPE_AC) { +#else + if (info->cable_type == CABLE_TYPE_USB || + info->cable_type == CABLE_TYPE_MISC) { +#endif + if (info->batt_vcell >= + FULL_CHARGE_COND_VOLTAGE) { + /* USB full charged */ + info->charging_int_full_count++; + if (info-> + charging_int_full_count >= + FULL_CHG_COND_COUNT) { + info-> + charging_int_full_count + = 0; + sec_bat_handle_charger_topoff + (info); + return true; + } + dev_info(info->dev, + "%s : full interrupt cnt = %d\n", + __func__, + info-> + charging_int_full_count); + } else { + info->charging_int_full_count = + 0; + /*reactivate charging in */ + /*next monitor work */ + /*for abnormal */ + /*full-charged status */ + info->charging_next_time = + info->charging_passed_time + + HZ; + } + } + break; +#endif + } + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: +#if defined(CONFIG_MACH_Q1_BD) + if (info->batt_health == + POWER_SUPPLY_HEALTH_OVERVOLTAGE) { + ret = + psy->get_property(psy, + POWER_SUPPLY_PROP_HEALTH, + &value); + + if (ret < 0) { + dev_err(info->dev, + "%s: fail to get health(%d)\n", + __func__, ret); + return false; + } + info->batt_health = value.intval; + } +#else + if (info->batt_health == POWER_SUPPLY_HEALTH_OVERVOLTAGE + && value.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; +#endif + break; + default: + info->charging_int_full_count = 0; + break; + } + return false; + } else { + psy = get_power_supply_by_name(info->charger_name); + + if (!psy) { + dev_err(info->dev, "%s: fail to get %s ps\n", + __func__, info->charger_name); + return false; + } + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", + __func__, ret); + return false; + } + + switch (info->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + if (info->recharging_status == false) + break; + case POWER_SUPPLY_STATUS_CHARGING: + switch (value.intval) { + case POWER_SUPPLY_STATUS_DISCHARGING: + if (info->cable_type == CABLE_TYPE_USB || + info->cable_type == CABLE_TYPE_MISC) { + if (info->batt_vcell >= + FULL_CHARGE_COND_VOLTAGE) { + /*USB full charged */ + info->charging_int_full_count++; + if (info-> + charging_int_full_count >= + FULL_CHG_COND_COUNT) { + info-> + charging_int_full_count + = 0; + sec_bat_handle_charger_topoff + (info); + return true; + } + dev_info(info->dev, + "%s : full interrupt cnt = %d\n", + __func__, + info-> + charging_int_full_count); + } else { + info->charging_int_full_count = + 0; + /*reactivate charging in */ + /*next monitor work */ + /*for abnormal full-charged status */ + info->charging_next_time = + info->charging_passed_time + + HZ; + } + } + break; + } + break; + default: + info->charging_int_full_count = 0; + break; + } + return false; + } +} + +static void sec_bat_monitor_work(struct work_struct *work) +{ + struct sec_bat_info *info = container_of(work, struct sec_bat_info, + monitor_work); + + sec_bat_check_temper(info); +#ifndef SEC_BATTERY_INDEPEDENT_VF_CHECK + sec_bat_check_vf(info); +#endif + sec_bat_update_info(info); +#ifdef SPRINT_SLATE_TEST + if (info->charging_status != POWER_SUPPLY_STATUS_FULL && + info->batt_health == POWER_SUPPLY_HEALTH_GOOD) + if (info->batt_temp_recover_cnt == 0 + || info->batt_temp_recover_cnt > 3) + sec_bat_get_dcinovp(info); +#endif + if (sec_bat_charging_time_management(info)) + goto full_charged; + + if (sec_bat_check_ing_level_trigger(info)) + goto full_charged; + +#if !defined(CONFIG_MACH_Q1_BD) + if (info->cable_type == CABLE_TYPE_AC) +#endif + if (sec_check_chgcurrent(info)) + goto full_charged; + + if (info->cable_type == CABLE_TYPE_NONE && + info->charging_status == POWER_SUPPLY_STATUS_FULL) { + dev_err(info->dev, "invalid full state\n"); + info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + if (info->test_value) { + switch (info->test_value) { + case 1: /*full status */ + info->charging_status = POWER_SUPPLY_STATUS_FULL; + break; + case 2: /*low temperature */ + info->batt_health = POWER_SUPPLY_HEALTH_COLD; + break; + case 3: /*high temperature */ + info->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + case 4: /*over voltage */ + info->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + case 5: /*abnormal battery */ + info->batt_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + case 6: /*tmu tripped */ + info->batt_tmu_status = TMU_STATUS_TRIPPED; + break; + } + } + + switch (info->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + +#ifdef CONFIG_TARGET_LOCALE_NA + if (info->batt_vcell < RECHARGING_VOLTAGE && + info->recharging_status == false) { + info->recharging_int_threshold_count++; + if (info->recharging_int_threshold_count + >= RECHARGING_CND_COUNT) { + if (!sec_bat_enable_charging(info, true)) { + info->recharging_int_threshold_count = + 0; + info->recharging_status = true; + + dev_info(info->dev, + "%s: Start Recharging, Vcell = %d\n", + __func__, info->batt_vcell); + } + } + } else { + info->recharging_int_threshold_count = 0; + } +#else + if (info->batt_vcell < RECHARGING_VOLTAGE && + info->recharging_status == false) { + if (!sec_bat_enable_charging(info, true)) { + info->recharging_status = true; + + dev_info(info->dev, + "%s: Start Recharging, Vcell = %d\n", + __func__, info->batt_vcell); + } + } +#endif + break; + case POWER_SUPPLY_STATUS_CHARGING: + switch (info->batt_health) { +#if defined(CONFIG_TARGET_LOCALE_NAATT) + case POWER_SUPPLY_HEALTH_GOOD: + if (info->batt_temp_ext_pre != info->batt_temp_ext) { + sec_bat_enable_charging(info, false); + + if (!sec_bat_enable_charging(info, true)) { + dev_info(info->dev, + "%s: Changed charging current\n", + __func__); + } + } + break; +#endif + case POWER_SUPPLY_HEALTH_OVERHEAT: + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + case POWER_SUPPLY_HEALTH_DEAD: + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + if (!sec_bat_enable_charging(info, false)) { + info->charging_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + info->recharging_status = false; + + dev_info(info->dev, "%s: Not charging\n", + __func__); + } + break; + default: + break; + } + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + dev_dbg(info->dev, "%s: Discharging\n", __func__); + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (info->batt_health == POWER_SUPPLY_HEALTH_GOOD) { + dev_info(info->dev, "%s: recover health state\n", + __func__); + if (info->cable_type != CABLE_TYPE_NONE) { + if (!sec_bat_enable_charging(info, true)) + info->charging_status = + POWER_SUPPLY_STATUS_CHARGING; + } else + info->charging_status + = POWER_SUPPLY_STATUS_DISCHARGING; + } + break; + default: + dev_info(info->dev, "%s: Undefined Battery Status\n", __func__); + return; + } + + full_charged: +#if defined(CONFIG_TARGET_LOCALE_NAATT) + dev_info(info->dev, + "soc(%d), vfocv(%d), vcell(%d), temp(%d), charging(%d), health(%d), vf(%d)\n", + info->batt_soc, info->batt_vfocv, info->batt_vcell / 1000, + info->batt_temp / 10, info->charging_status, info->batt_health, + info->batt_vf_adc); +#else + dev_info(info->dev, + "soc(%d), vfocv(%d), vcell(%d), temp(%d), charging(%d), health(%d), chg_adc(%d)\n", + info->batt_soc, info->batt_vfocv, info->batt_vcell / 1000, + info->batt_temp / 10, info->charging_status, + info->batt_health, info->batt_current_adc); +#endif + + power_supply_changed(&info->psy_bat); + + wake_unlock(&info->monitor_wake_lock); + + return; +} + +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK +static void sec_bat_vf_check_work(struct work_struct *work) +{ + struct sec_bat_info *info; + info = container_of(work, struct sec_bat_info, vf_check_work.work); + + sec_bat_check_vf(info); + +#if defined(CONFIG_TARGET_LOCALE_NAATT) + sec_bat_check_vf_adc(info); +#endif + +#if (defined(CONFIG_MACH_Q1_BD) && defined(CONFIG_SMB328_CHARGER)) + sec_bat_check_ovp(info); + + if ((info->batt_health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) || + (info->batt_health == POWER_SUPPLY_HEALTH_OVERVOLTAGE)) { + dev_info(info->dev, + "%s: Battery Disconnected or OVP activated\n", + __func__); +#else + if (info->batt_health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + dev_info(info->dev, "%s: Battery Disconnected\n", __func__); +#endif + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + } + + schedule_delayed_work(&info->vf_check_work, + msecs_to_jiffies(VF_CHECK_INTERVAL)); +} +#endif + +static void sec_bat_polling_work(struct work_struct *work) +{ + struct sec_bat_info *info; + info = container_of(work, struct sec_bat_info, polling_work.work); + + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + if (info->initial_check_count) { + schedule_delayed_work(&info->polling_work, HZ); + info->initial_check_count--; + } else + schedule_delayed_work(&info->polling_work, + msecs_to_jiffies(info->polling_interval)); +} + +#define SEC_BATTERY_ATTR(_name) \ +{ \ + .attr = { .name = #_name, \ + .mode = 0664 }, \ + .show = sec_bat_show_property, \ + .store = sec_bat_store, \ +} + +static struct device_attribute sec_battery_attrs[] = { + SEC_BATTERY_ATTR(batt_vol), + SEC_BATTERY_ATTR(batt_soc), + SEC_BATTERY_ATTR(batt_vfocv), + SEC_BATTERY_ATTR(batt_temp), + SEC_BATTERY_ATTR(batt_temp_adc), +#ifdef CONFIG_TARGET_LOCALE_NA + SEC_BATTERY_ATTR(batt_temp_radc), +#endif + SEC_BATTERY_ATTR(batt_temp_adc_avg), + SEC_BATTERY_ATTR(charging_source), + SEC_BATTERY_ATTR(batt_lp_charging), + SEC_BATTERY_ATTR(video), + SEC_BATTERY_ATTR(mp3), + SEC_BATTERY_ATTR(batt_type), + SEC_BATTERY_ATTR(batt_full_check), + SEC_BATTERY_ATTR(batt_temp_check), + SEC_BATTERY_ATTR(batt_temp_adc_spec), + SEC_BATTERY_ATTR(batt_test_value), + SEC_BATTERY_ATTR(batt_current_now), + SEC_BATTERY_ATTR(batt_current_adc), + SEC_BATTERY_ATTR(siop_activated), + SEC_BATTERY_ATTR(system_rev), +#ifdef CONFIG_TARGET_LOCALE_NA + SEC_BATTERY_ATTR(fg_soc), +#else + SEC_BATTERY_ATTR(fg_psoc), +#endif /* CONFIG_TARGET_LOCALE_NA */ + SEC_BATTERY_ATTR(batt_lpm_state), + SEC_BATTERY_ATTR(batt_tmu_status), +#ifdef SPRINT_SLATE_TEST + SEC_BATTERY_ATTR(slate_test_mode), +#endif +#if defined(CONFIG_TARGET_LOCALE_NAATT) + SEC_BATTERY_ATTR(batt_v_f_adc), + SEC_BATTERY_ATTR(batt_voice_call_2g), + SEC_BATTERY_ATTR(batt_voice_call_3g), + SEC_BATTERY_ATTR(batt_data_call), + SEC_BATTERY_ATTR(wifi), + SEC_BATTERY_ATTR(batt_gps), + SEC_BATTERY_ATTR(camera), + SEC_BATTERY_ATTR(recording), +#elif defined(CONFIG_TARGET_LOCALE_NA) + SEC_BATTERY_ATTR(call), + /*SEC_BATTERY_ATTR(video),*/ + /*SEC_BATTERY_ATTR(music),*/ + SEC_BATTERY_ATTR(browser), + SEC_BATTERY_ATTR(hotspot), + SEC_BATTERY_ATTR(camera), + SEC_BATTERY_ATTR(data_call), + SEC_BATTERY_ATTR(wimax), +#endif +}; + +enum { + BATT_VOL = 0, + BATT_SOC, + BATT_VFOCV, + BATT_TEMP, + BATT_TEMP_ADC, +#ifdef CONFIG_TARGET_LOCALE_NA + BATT_TEMP_RADC, +#endif + BATT_TEMP_ADC_AVG, + CHARGING_SOURCE, + BATT_LP_CHARGING, + BATT_VIDEO, + BATT_MP3, + BATT_TYPE, + BATT_FULL_CHECK, + BATT_TEMP_CHECK, + BATT_TEMP_ADC_SPEC, + BATT_TEST_VALUE, + BATT_CURRENT_NOW, + BATT_CURRENT_ADC, + BATT_SIOP_ACTIVATED, + BATT_SYSTEM_REV, + BATT_FG_PSOC, + BATT_LPM_STATE, + BATT_TMU_STATUS, +#ifdef SPRINT_SLATE_TEST + SLATE_TEST_MODE, +#endif +#if defined(CONFIG_TARGET_LOCALE_NAATT) + BATT_V_F_ADC, + BATT_VOICE_CALL_2G, + BATT_VOICE_CALL_3G, + BATT_DATA_CALL, + BATT_WIFI, + BATT_GPS, + BATT_CAMERA, + BATT_RECORDING, +#endif +#if defined(CONFIG_TARGET_LOCALE_NA) + BATT_CALL, + BATT_BROWSER, + BATT_HOTSOPT, + BATT_CAMERA, + BATT_DATA_CALL, + BATT_WIMAX, +#endif +}; + +#if defined(CONFIG_TARGET_LOCALE_NAATT) +static void sec_bat_check_event_status(struct sec_bat_info *info, int mode, + int offset) +{ + int is_event_running = 0; + + if ((info->batt_event_status != 0) + /*&& (info->cable_type != CABLE_TYPE_NONE) */ + ) + is_event_running = 1; + + if (mode) { + if (!(info->batt_event_status & offset)) + info->batt_event_status |= offset; + } else { + if (info->batt_event_status & offset) + info->batt_event_status &= ~offset; + } + + printk(KERN_DEBUG "[%s] current batt_event_status = 0x%x\n", __func__, + info->batt_event_status); + + if ((info->batt_event_status == 0) && (is_event_running == 1)) + info->event_end_time = jiffies; + + return; +} +#elif defined(CONFIG_TARGET_LOCALE_NA) +static void sec_bat_check_event_status(struct sec_bat_info *info, int mode, + int offset) +{ + int is_event_running = 0; + + if ((info->batt_event_status != 0) + /*&& (info->cable_type != CABLE_TYPE_NONE) */ + ) + is_event_running = 1; + + if (mode) { + if (!(info->batt_event_status & offset)) + info->batt_event_status |= offset; + } else { + if (info->batt_event_status & offset) + info->batt_event_status &= ~offset; + } + + printk(KERN_DEBUG "[%s] current batt_event_status = 0x%x\n", __func__, + info->batt_event_status); + + if ((info->batt_event_status == 0) && (is_event_running == 1)) + info->event_expired_time = jiffies; + + return; +} + +int sec_bat_use_wimax(int onoff) +{ + struct sec_bat_info *info = pchg; + sec_bat_check_event_status(info, onoff, USE_WIMAX); +} +EXPORT_SYMBOL(sec_bat_use_wimax); +#endif + +static ssize_t sec_bat_show_property(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_bat_info *info = dev_get_drvdata(dev->parent); + int i = 0, val; + const ptrdiff_t off = attr - sec_battery_attrs; + + switch (off) { + case BATT_VOL: + val = sec_bat_get_fuelgauge_data(info, FG_T_AVGVCELL); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_SOC: + val = sec_bat_get_fuelgauge_data(info, FG_T_SOC); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_VFOCV: + val = sec_bat_get_fuelgauge_data(info, FG_T_VFOCV); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_TEMP: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->batt_temp); + break; + case BATT_TEMP_ADC: + val = s3c_read_temper_adc(info); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; +#ifdef CONFIG_TARGET_LOCALE_NA + case BATT_TEMP_RADC: + val = s3c_read_temper_adc(info); + val = sec_rescale_temp_adc(info); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_temp_radc); + break; +#endif + case BATT_TEMP_ADC_AVG: + val = info->temper_adc_sample.average_adc; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case CHARGING_SOURCE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->cable_type); + break; + case BATT_LP_CHARGING: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->get_lpcharging_state()); + break; + case BATT_VIDEO: + /* TODO */ +#ifdef CONFIG_TARGET_LOCALE_NA + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->use_video); +#else + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); +#endif + break; + case BATT_MP3: + /* TODO */ +#ifdef CONFIG_TARGET_LOCALE_NA + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->use_music); +#else + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); +#endif + break; + case BATT_TYPE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", "SDI_SDI"); + break; + case BATT_FULL_CHECK: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (info->charging_status == + POWER_SUPPLY_STATUS_FULL) + ? 1 : 0); + break; + case BATT_TEMP_CHECK: + i += scnprintf(buf + i, PAGE_SIZE - i, + "%d\n", info->batt_health); + break; + case BATT_TEMP_ADC_SPEC: +#ifdef CONFIG_TARGET_LOCALE_NA + i += scnprintf(buf + i, PAGE_SIZE - i, + "(HIGH: %d - %d, LOW: %d - %d)\n", + HIGH_BLOCK_TEMP_ADC, HIGH_RECOVER_TEMP_ADC, + LOW_BLOCK_TEMP_ADC, LOW_RECOVER_TEMP_ADC); +#else + i += scnprintf(buf + i, PAGE_SIZE - i, + "(HIGH: %d - %d, LOW: %d - %d)\n", + HIGH_BLOCK_TEMP / 10, HIGH_RECOVER_TEMP / 10, + LOW_BLOCK_TEMP / 10, LOW_RECOVER_TEMP / 10); +#endif + break; + case BATT_TEST_VALUE: + i += scnprintf(buf + i, PAGE_SIZE - i, + "0-normal, 1-full, 2-low, 3-high, 4-over, 5-cf, 6-trip (%d)\n", + info->test_value); + break; + case BATT_CURRENT_NOW: + case BATT_SIOP_ACTIVATED: + { + struct power_supply *psy = + get_power_supply_by_name(info->sub_charger_name); + union power_supply_propval value; + + if (!psy) { + pr_err("%s: fail to get battery ps\n", + __func__); + return -ENODEV; + } + + /* initialized as NOT charging */ + value.intval = 0; + + if ((info->charging_status == + POWER_SUPPLY_STATUS_CHARGING) + || (info->charging_status == + POWER_SUPPLY_STATUS_FULL)) + psy->get_property(psy, + POWER_SUPPLY_PROP_CURRENT_NOW, + &value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_CURRENT_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_current_adc); + break; + case BATT_SYSTEM_REV: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", system_rev); + break; + case BATT_FG_PSOC: + val = sec_bat_get_fuelgauge_data(info, FG_T_PSOC); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_LPM_STATE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_lpm_state); + break; + case BATT_TMU_STATUS: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_tmu_status); + break; +#ifdef SPRINT_SLATE_TEST + case SLATE_TEST_MODE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->slate_test_mode); + break; +#endif +#if defined(CONFIG_TARGET_LOCALE_NAATT) + case BATT_V_F_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_vf_adc); + break; + case BATT_VOICE_CALL_2G: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_VOICE_CALL_3G: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_DATA_CALL: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_WIFI: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_GPS: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_CAMERA: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_RECORDING: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; +#endif +#ifdef CONFIG_TARGET_LOCALE_NA + case BATT_CALL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->use_call); + break; + case BATT_BROWSER: + i += scnprintf(buf + i, PAGE_SIZE - i, + "%d\n", info->use_browser); + break; + case BATT_HOTSOPT: + i += scnprintf(buf + i, PAGE_SIZE - i, + "%d\n", info->use_hotspot); + break; + case BATT_CAMERA: + i += scnprintf(buf + i, PAGE_SIZE - i, + "%d\n", info->use_camera); + break; + case BATT_DATA_CALL: + i += scnprintf(buf + i, PAGE_SIZE - i, + "%d\n", info->use_data_call); + break; + case BATT_WIMAX: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", info->use_wimax); + break; +#endif + default: + i = -EINVAL; + break; + } + + return i; +} + +static ssize_t sec_bat_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = 0, x = 0; + const ptrdiff_t off = attr - sec_battery_attrs; + struct sec_bat_info *info = dev_get_drvdata(dev->parent); + + switch (off) { + case BATT_CURRENT_NOW: + if (sscanf(buf, "%d\n", &x) == 1) { + ret = count; + if ((info->charging_status == + POWER_SUPPLY_STATUS_CHARGING) + || (info->charging_status == + POWER_SUPPLY_STATUS_FULL)) { + struct power_supply *psy = + get_power_supply_by_name(info-> + sub_charger_name); + union power_supply_propval value; + + if (!psy) { + pr_err("%s: fail to get battery ps\n", + __func__); + return -ENODEV; + } + + value.intval = x; /* charging current */ + psy->set_property(psy, + POWER_SUPPLY_PROP_CURRENT_NOW, + &value); + + /* reflect changing current */ + value.intval = true; + psy->set_property(psy, POWER_SUPPLY_PROP_ONLINE, + &value); + } else + pr_err("%s: NOT charging status\n", __func__); + } + break; + case BATT_SIOP_ACTIVATED: + if (sscanf(buf, "%d\n", &x) == 1) { + ret = count; + if ((info->charging_status == + POWER_SUPPLY_STATUS_CHARGING) || + (info->charging_status == + POWER_SUPPLY_STATUS_FULL)) { + struct power_supply *psy = + get_power_supply_by_name(info-> + sub_charger_name); + union power_supply_propval value; + + if (!psy) { + pr_err("%s: fail to get battery ps\n", + __func__); + return -ENODEV; + } + + if (x) { + /* charging current for SIOP */ + value.intval = 450; + } else { + switch (info->cable_type) { + case CABLE_TYPE_USB: + case CABLE_TYPE_MISC: + value.intval = 450; /* mA */ + break; + case CABLE_TYPE_AC: + value.intval = 650; /* mA */ + break; + default: + dev_err(info->dev, + "%s: Invalid func use\n", + __func__); + return -EINVAL; + } + } + psy->set_property(psy, + POWER_SUPPLY_PROP_CURRENT_NOW, + &value); + + /* reflect changing current */ + value.intval = true; + psy->set_property(psy, + POWER_SUPPLY_PROP_ONLINE, + &value); + } else + pr_err("%s: NOT charging status\n", __func__); + } + break; + case BATT_VIDEO: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: video(%d)\n", __func__, x); + ret = count; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + sec_bat_check_event_status(info, x, OFFSET_VIDEO_PLAY); +#elif defined(CONFIG_TARGET_LOCALE_NA) + info->use_video = x; + sec_bat_check_event_status(info, x, USE_VIDEO); +#endif + } + break; + case BATT_MP3: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: mp3(%d)\n", __func__, x); + ret = count; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + sec_bat_check_event_status(info, x, OFFSET_MP3_PLAY); +#elif defined(CONFIG_TARGET_LOCALE_NA) + info->use_music = x; + sec_bat_check_event_status(info, x, USE_MUSIC); +#endif + } + break; + case BATT_TEST_VALUE: + if (sscanf(buf, "%d\n", &x) == 1) { + info->test_value = x; + printk(KERN_DEBUG "%s : test case : %d\n", __func__, + info->test_value); + ret = count; + } + break; + case BATT_LPM_STATE: + if (sscanf(buf, "%d\n", &x) == 1) { + info->batt_lpm_state = x; + ret = count; + } + break; +#ifdef SPRINT_SLATE_TEST + case SLATE_TEST_MODE: + if (strncmp(buf, "1", 1) == 0) { + info->slate_test_mode = true; + ret = count; + } else { + info->slate_test_mode = false; + ret = count; + } + break; +#endif +#if defined(CONFIG_TARGET_LOCALE_NAATT) + case BATT_VOICE_CALL_2G: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: call 2G(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, + OFFSET_VOICE_CALL_2G); + } + break; + case BATT_VOICE_CALL_3G: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: call 3G(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, + OFFSET_VOICE_CALL_3G); + } + break; + case BATT_DATA_CALL: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: data call(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, OFFSET_DATA_CALL); + } + break; + case BATT_WIFI: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: wifi(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, OFFSET_WIFI); + } + break; + case BATT_GPS: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: gps(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, OFFSET_GPS); + } + break; + case BATT_CAMERA: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: camera(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, OFFSET_CAMERA_ON); + } + break; + case BATT_RECORDING: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: recording(%d)\n", __func__, x); + ret = count; + sec_bat_check_event_status(info, x, + OFFSET_RECORDING_ON); + } + break; +#endif +#ifdef CONFIG_TARGET_LOCALE_NA + case BATT_CALL: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + info->use_call = x; + dev_info(info->dev, "[NA_SPR]%s: call (%d)\n", __func__, + x); + ret = count; + sec_bat_check_event_status(info, x, USE_CALL); + } + break; + case BATT_BROWSER: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + info->use_browser = x; + dev_info(info->dev, "[NA_SPR]%s: BROWSER(%d)\n", + __func__, x); + ret = count; + sec_bat_check_event_status(info, x, USE_BROWSER); + } + break; + case BATT_HOTSOPT: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + info->use_hotspot = x; + dev_info(info->dev, "[NA_SPR]%s: HOTSPOT(%d)\n", + __func__, x); + ret = count; + sec_bat_check_event_status(info, x, USE_HOTSPOT); + } + break; + case BATT_CAMERA: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "[NA_SPR]%s: CAMERA(%d)\n", + __func__, x); + ret = count; + sec_bat_check_event_status(info, x, USE_CAMERA); + } + break; + case BATT_DATA_CALL: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + info->use_data_call = x; + dev_info(info->dev, "[NA_SPR]%s: DATA CALL(%d)\n", + __func__, x); + ret = count; + sec_bat_check_event_status(info, x, USE_DATA_CALL); + } + break; + case BATT_WIMAX: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + info->use_wimax = x; + dev_info(info->dev, "[NA_SPR]%s: WIMAX(%d)\n", __func__, + x); + ret = count; + sec_bat_check_event_status(info, x, USE_WIMAX); + } + 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 sec_attrs_failed; + } + goto succeed; + + sec_attrs_failed: + while (i--) + device_remove_file(dev, &sec_battery_attrs[i]); + succeed: + return rc; +} + +static int sec_bat_is_charging(struct sec_bat_info *info) +{ + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval value; + int ret; + + if (!psy) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return -ENODEV; + } + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value); + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", __func__, + ret); + return ret; + } + + return value.intval; +} + +static int sec_bat_read_proc(char *buf, char **start, + off_t offset, int count, int *eof, void *data) +{ + struct sec_bat_info *info = data; + struct timespec cur_time; + ktime_t ktime; + int len = 0; + + ktime = alarm_get_elapsed_realtime(); + cur_time = ktime_to_timespec(ktime); + +#ifdef CONFIG_TARGET_LOCALE_NA + len = sprintf(buf, + "%lu, %u, %u, %u, %u, %u, %d, %d, %d, %u, %u, %u, %u, %u, %u, %u, %d, %lu\n", + cur_time.tv_sec, + info->batt_raw_soc, + info->batt_soc, + info->batt_vfocv, + info->batt_vcell, + info->batt_current_adc, + info->batt_full_status, + info->charging_int_full_count, + info->charging_adc_full_count, + info->recharging_status, + info->batt_temp_adc, + info->batt_temp_radc, + info->batt_health, + info->charging_status, + info->batt_tmu_status, + info->present, + info->cable_type, info->charging_passed_time); + + return len; +} +#else + len = sprintf(buf, + "%lu, %u, %u, %u, %u, %u, %d, %d, %d, %u, %u, %u, %u, %u, %u, %d, %lu\n", + cur_time.tv_sec, + info->batt_raw_soc, + info->batt_soc, + info->batt_vfocv, + info->batt_vcell, + info->batt_current_adc, + info->batt_full_status, + info->charging_int_full_count, + info->charging_adc_full_count, + info->recharging_status, + info->batt_temp_adc, + info->batt_health, + info->charging_status, + info->batt_tmu_status, + info->present, + info->cable_type, info->charging_passed_time); + + return len; +} +#endif + +static __devinit int sec_bat_probe(struct platform_device *pdev) +{ + struct sec_bat_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct sec_bat_info *info; + struct power_supply *psy; + union power_supply_propval value; + int ret = 0; + + dev_info(&pdev->dev, "%s: SEC Battery Driver Loading\n", __func__); + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(pdev, info); + + info->dev = &pdev->dev; + if (!pdata->fuel_gauge_name || !pdata->charger_name) { + dev_err(info->dev, "%s: no fuel gauge or charger name\n", + __func__); + goto err_kfree; + } + info->fuel_gauge_name = pdata->fuel_gauge_name; + info->charger_name = pdata->charger_name; + info->sub_charger_name = pdata->sub_charger_name; +#if defined(CONFIG_MACH_U1_NA_SPR_EPIC2_REV00) + if (system_rev <= HWREV_FOR_BATTERY) { +#else + if (system_rev >= HWREV_FOR_BATTERY) { +#endif + dev_info(&pdev->dev, "%s: use sub-charger (%s)\n", + __func__, info->sub_charger_name); + info->adc_arr_size = pdata->adc_sub_arr_size; + info->adc_table = pdata->adc_sub_table; + info->adc_channel = pdata->adc_sub_channel; + info->use_sub_charger = true; + } else { + dev_info(&pdev->dev, "%s: use main charger (%s)\n", + __func__, info->charger_name); + info->adc_arr_size = pdata->adc_arr_size; + info->adc_table = pdata->adc_table; + info->adc_channel = pdata->adc_channel; + info->use_sub_charger = false; + } + info->get_lpcharging_state = pdata->get_lpcharging_state; +#if defined(CONFIG_TARGET_LOCALE_NAATT) + info->vf_adc_channel = pdata->adc_vf_channel; +#endif + + info->psy_bat.name = "battery", + info->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY, + info->psy_bat.properties = sec_battery_props, + info->psy_bat.num_properties = ARRAY_SIZE(sec_battery_props), + info->psy_bat.get_property = sec_bat_get_property, + info->psy_bat.set_property = sec_bat_set_property, + info->psy_usb.name = "usb", + info->psy_usb.type = POWER_SUPPLY_TYPE_USB, + info->psy_usb.supplied_to = supply_list, + info->psy_usb.num_supplicants = ARRAY_SIZE(supply_list), + info->psy_usb.properties = sec_power_props, + info->psy_usb.num_properties = ARRAY_SIZE(sec_power_props), + info->psy_usb.get_property = sec_usb_get_property, + info->psy_ac.name = "ac", + info->psy_ac.type = POWER_SUPPLY_TYPE_MAINS, + info->psy_ac.supplied_to = supply_list, + info->psy_ac.num_supplicants = ARRAY_SIZE(supply_list), + info->psy_ac.properties = sec_power_props, + info->psy_ac.num_properties = ARRAY_SIZE(sec_power_props), + info->psy_ac.get_property = sec_ac_get_property; + + wake_lock_init(&info->vbus_wake_lock, WAKE_LOCK_SUSPEND, + "vbus_present"); + wake_lock_init(&info->monitor_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-monitor"); + wake_lock_init(&info->cable_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-cable"); + + psy = get_power_supply_by_name(info->charger_name); + + if (!psy) { + dev_err(info->dev, "%s: fail to get charger\n", __func__); + return -ENODEV; + } + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_PRESENT, &value); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", + __func__, ret); + return -ENODEV; + } + info->present = value.intval; + + if (info->present == BAT_NOT_DETECTED) + info->batt_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + +#if defined(CONFIG_MACH_Q1_BD) + info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; +#else + info->charging_status = sec_bat_is_charging(info); + if (info->charging_status < 0) + info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; +#endif + + info->present_count = 0; + info->charging_int_full_count = 0; + info->charging_adc_full_count = 0; +#ifdef CONFIG_TARGET_LOCALE_NA + info->recharging_int_threshold_count = 0; +#endif + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + info->batt_temp_recover_cnt = 0; + + info->initial_check_count = INIT_CHECK_COUNT; + + mutex_init(&info->adclock); + + info->padc = s3c_adc_register(pdev, NULL, NULL, 0); + info->charging_start_time = 0; + +#if defined(CONFIG_TARGET_LOCALE_NAATT) + info->batt_vf_adc = 0; + info->batt_event_status = 0; + info->event_end_time = 0xFFFFFFFF; +#elif defined(CONFIG_TARGET_LOCALE_NA) + info->use_call = 0; + info->use_video = 0; + info->use_music = 0; + info->use_browser = 0; + info->use_hotspot = 0; + info->use_camera = 0; + info->use_data_call = 0; + info->use_wimax = 0; + info->batt_event_status = 0; + info->event_expired_time = 0xFFFFFFFF; +#endif + if (info->get_lpcharging_state) { + if (info->get_lpcharging_state()) + info->polling_interval = POLLING_INTERVAL / 4; + else + info->polling_interval = POLLING_INTERVAL; + } + + info->test_value = 0; + + info->batt_tmu_status = TMU_STATUS_NORMAL; + + /* init power supplier framework */ + ret = power_supply_register(&pdev->dev, &info->psy_bat); + if (ret) { + dev_err(info->dev, "%s: failed to register psy_bat\n", + __func__); + goto err_wake_lock; + } + + ret = power_supply_register(&pdev->dev, &info->psy_usb); + if (ret) { + dev_err(info->dev, "%s: failed to register psy_usb\n", + __func__); + goto err_supply_unreg_bat; + } + + ret = power_supply_register(&pdev->dev, &info->psy_ac); + if (ret) { + dev_err(info->dev, "%s: failed to register psy_ac\n", __func__); + goto err_supply_unreg_usb; + } + + /* create sec detail attributes */ + sec_bat_create_attrs(info->psy_bat.dev); + + info->entry = create_proc_entry("batt_info_proc", S_IRUGO, NULL); + if (!info->entry) + dev_err(info->dev, "%s: failed to create proc_entry\n", + __func__); + else { + info->entry->read_proc = sec_bat_read_proc; + info->entry->data = (struct sec_bat_info *)info; + } + + info->monitor_wqueue = create_freezable_workqueue(dev_name(&pdev->dev)); + if (!info->monitor_wqueue) { + dev_err(info->dev, "%s: fail to create workqueue\n", __func__); + goto err_supply_unreg_ac; + } + + INIT_WORK(&info->monitor_work, sec_bat_monitor_work); + INIT_WORK(&info->cable_work, sec_bat_cable_work); + + INIT_DELAYED_WORK_DEFERRABLE(&info->polling_work, sec_bat_polling_work); + schedule_delayed_work(&info->polling_work, 0); + +#ifdef CONFIG_TARGET_LOCALE_NA + pchg = info; /* using pointer - for wimax */ +#endif + +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK + INIT_DELAYED_WORK_DEFERRABLE(&info->vf_check_work, + sec_bat_vf_check_work); + schedule_delayed_work(&info->vf_check_work, 0); +#endif + +#if defined(CONFIG_MACH_Q1_BD) + if (pdata->initial_check) + pdata->initial_check(); +#endif + + dev_info(info->dev, "%s: SEC Battery Driver Loaded\n", __func__); + return 0; + + err_supply_unreg_ac: + power_supply_unregister(&info->psy_ac); + err_supply_unreg_usb: + power_supply_unregister(&info->psy_usb); + err_supply_unreg_bat: + power_supply_unregister(&info->psy_bat); + err_wake_lock: + wake_lock_destroy(&info->vbus_wake_lock); + wake_lock_destroy(&info->monitor_wake_lock); + wake_lock_destroy(&info->cable_wake_lock); + s3c_adc_release(info->padc); + mutex_destroy(&info->adclock); + err_kfree: + kfree(info); + + return ret; +} + +static int __devexit sec_bat_remove(struct platform_device *pdev) +{ + struct sec_bat_info *info = platform_get_drvdata(pdev); + + remove_proc_entry("batt_info_proc", NULL); + + flush_workqueue(info->monitor_wqueue); + destroy_workqueue(info->monitor_wqueue); + + cancel_delayed_work(&info->polling_work); +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK + cancel_delayed_work(&info->vf_check_work); +#endif + + power_supply_unregister(&info->psy_bat); + power_supply_unregister(&info->psy_usb); + power_supply_unregister(&info->psy_ac); + + wake_lock_destroy(&info->vbus_wake_lock); + wake_lock_destroy(&info->monitor_wake_lock); + wake_lock_destroy(&info->cable_wake_lock); + mutex_destroy(&info->adclock); + + s3c_adc_release(info->padc); + + kfree(info); + + return 0; +} + +static int sec_bat_suspend(struct device *dev) +{ + struct sec_bat_info *info = dev_get_drvdata(dev); + + cancel_work_sync(&info->monitor_work); + cancel_delayed_work(&info->polling_work); +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK + cancel_delayed_work(&info->vf_check_work); +#endif + + return 0; +} + +static int sec_bat_resume(struct device *dev) +{ + struct sec_bat_info *info = dev_get_drvdata(dev); + + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + schedule_delayed_work(&info->polling_work, + msecs_to_jiffies(info->polling_interval)); + +#ifdef SEC_BATTERY_INDEPEDENT_VF_CHECK + schedule_delayed_work(&info->vf_check_work, + msecs_to_jiffies(VF_CHECK_INTERVAL)); +#endif + + return 0; +} + +#if defined(CONFIG_TARGET_LOCALE_NA) +static void sec_bat_shutdown(struct device *dev) +{ + struct sec_bat_info *info = dev_get_drvdata(dev); + struct power_supply *psy_main, *psy_sub; + union power_supply_propval val_type; + int ret; + + if (!info->use_sub_charger) + return; + + psy_main = get_power_supply_by_name(info->charger_name); + psy_sub = get_power_supply_by_name(info->sub_charger_name); + + if (!psy_main || !psy_sub) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return; + } + + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + ret = + psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + return; + } + + val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = + psy_sub->set_property(psy_sub, POWER_SUPPLY_PROP_STATUS, &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + return; + } +} +#elif defined(CONFIG_MACH_Q1_BD) +static void sec_bat_shutdown(struct device *dev) +{ + struct sec_bat_info *info = dev_get_drvdata(dev); +#if defined(CONFIG_SMB328_CHARGER) + struct power_supply *psy_sub = + get_power_supply_by_name("smb328-charger"); + union power_supply_propval value; + int ret; + + if (!psy_sub) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return; + } + + /* only for OTG */ + value.intval = false; + ret = psy_sub->set_property(psy_sub, + POWER_SUPPLY_PROP_CHARGE_TYPE, &value); + if (ret) { + dev_info(info->dev, "%s: fail to set OTG (%d)\n", + __func__, ret); + return; + } +#endif +} +#else +#define sec_bat_shutdown NULL +#endif + +static const struct dev_pm_ops sec_bat_pm_ops = { + .suspend = sec_bat_suspend, + .resume = sec_bat_resume, +}; + +static struct platform_driver sec_bat_driver = { + .driver = { + .name = "sec-battery", + .owner = THIS_MODULE, + .pm = &sec_bat_pm_ops, + .shutdown = sec_bat_shutdown, + }, + .probe = sec_bat_probe, + .remove = __devexit_p(sec_bat_remove), +}; + +static int __init sec_bat_init(void) +{ + return platform_driver_register(&sec_bat_driver); +} + +static void __exit sec_bat_exit(void) +{ + platform_driver_unregister(&sec_bat_driver); +} + +late_initcall(sec_bat_init); +module_exit(sec_bat_exit); + +MODULE_DESCRIPTION("SEC battery driver"); +MODULE_AUTHOR("<ms925.kim@samsung.com>"); +MODULE_AUTHOR("<joshua.chang@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/sec_battery_u1_kor.c b/drivers/power/sec_battery_u1_kor.c new file mode 100644 index 00000000000..147072e7c1c --- /dev/null +++ b/drivers/power/sec_battery_u1_kor.c @@ -0,0 +1,2299 @@ +/* + * sec_battery.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2010 Samsung Electronics + * + * <ms925.kim@samsung.com> + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/wakelock.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <linux/android_alarm.h> +#include <plat/adc.h> +#include <linux/power/sec_battery_u1.h> + +#define POLLING_INTERVAL (40 * 1000) +#define MEASURE_DSG_INTERVAL (20 * 1000) +#define MEASURE_CHG_INTERVAL (3 * 1000) +#define FULL_CHARGING_TIME (6 * 60 * 60 * HZ) /* 6hr */ +#define RECHARGING_TIME (2 * 60 * 60 * HZ) /* 2hr */ +#define RESETTING_CHG_TIME (10 * 60 * HZ) /* 10Min */ +#define RECHARGING_VOLTAGE (4130 * 1000) /* 4.13 V */ +#if defined(CONFIG_MACH_U1_KOR_LGT) +#define HIGH_BLOCK_TEMP_ADC 395 +#define HIGH_RECOVER_TEMP_ADC 345 +#define LOW_BLOCK_TEMP_ADC 253 +#define LOW_RECOVER_TEMP_ADC 258 +#else +#define HIGH_BLOCK_TEMP_ADC 390 +#define HIGH_RECOVER_TEMP_ADC 348 +#define LOW_BLOCK_TEMP_ADC 247 +#define LOW_RECOVER_TEMP_ADC 256 +#endif + +#define FG_T_SOC 0 +#define FG_T_VCELL 1 +#define FG_T_TEMPER 2 +#define FG_T_PSOC 3 +#define FG_T_VFOCV 4 +#define FG_T_AVGVCELL 5 + +#define ADC_SAMPLING_CNT 6 +#define ADC_CH_CHGCURRENT_MAIN 0 +#define ADC_CH_CHGCURRENT_SUB 1 +#define ADC_CH_TEMPERATURE_MAIN 7 +#define ADC_CH_TEMPERATURE_SUB 6 /* 6 is near AP side */ +/* #define ADC_TOTAL_COUNT 5 */ + +#define CURRENT_OF_FULL_CHG_MAIN 300 +#define CURRENT_OF_FULL_CHG_SUB 500 +/* all count duration = (count - 1) * poll interval */ +#define RE_CHG_COND_COUNT 4 +#define RE_CHG_MIN_COUNT 2 +#define TEMP_BLOCK_COUNT 3 +#define BAT_DET_COUNT 2 +#define FULL_CHG_COND_COUNT 2 +#define OVP_COND_COUNT 2 +#define USB_FULL_COND_COUNT 3 +#define USB_FULL_COND_VOLTAGE 4180000 +#define FULL_CHARGE_COND_VOLTAGE 4100000 +#define INIT_CHECK_COUNT 4 +#define CALL_EXCEPTION_VOLTAGE_UP 3400000 +#define CALL_EXCEPTION_VOLTAGE_DN 3200000 + +/* for backup +extern int get_proximity_activation_state(void); +extern int get_proximity_approach_state(void); +*/ + +enum tmu_status_t { + TMU_STATUS_NORMAL = 0, + TMU_STATUS_TRIPPED, + TMU_STATUS_THROTTLED, + TMU_STATUS_WARNING, +}; + +enum cable_type_t { + CABLE_TYPE_NONE = 0, + CABLE_TYPE_USB, + CABLE_TYPE_AC, + CABLE_TYPE_MISC, +}; + +enum batt_full_t { + BATT_NOT_FULL = 0, + BATT_FULL, +}; + +enum { + BAT_NOT_DETECTED, + BAT_DETECTED +}; + +static ssize_t sec_bat_show_property(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t sec_bat_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +struct battest_info { + int rechg_count; + int full_count; + int full_count_sub; + int test_value; + int test_esuspend; + bool is_rechg_state; +}; + +/* +struct adc_sample { + int average_adc; + int adc_arr[ADC_TOTAL_COUNT]; + int index; +}; +*/ + +struct sec_bat_info { + struct device *dev; + + char *fuel_gauge_name; + char *charger_name; + char *sub_charger_name; + + unsigned int adc_arr_size; + struct sec_bat_adc_table_data *adc_table; + /* + unsigned int adc_channel; + struct adc_sample temper_adc_sample; + */ + + struct power_supply psy_bat; + struct power_supply psy_usb; + struct power_supply psy_ac; + + struct wake_lock vbus_wake_lock; + struct wake_lock monitor_wake_lock; + struct wake_lock cable_wake_lock; + struct wake_lock test_wake_lock; + struct wake_lock measure_wake_lock; + + enum cable_type_t cable_type; + enum batt_full_t batt_full_status; + + unsigned int batt_temp; /* Battery Temperature (C) */ + int batt_temp_high_cnt; + int batt_temp_low_cnt; + unsigned int batt_health; + unsigned int batt_vcell; + unsigned int batt_vfocv; + unsigned int batt_soc; + unsigned int batt_raw_soc; + unsigned int batt_presoc; + unsigned int polling_interval; + unsigned int measure_interval; + int charging_status; + /* int charging_full_count; */ + + unsigned int batt_temp_adc; + unsigned int batt_temp_adc_sub; + unsigned int batt_temp_radc; + unsigned int batt_temp_radc_sub; + unsigned int batt_current_adc; + unsigned int present; + struct battest_info test_info; + + struct s3c_adc_client *padc; + + struct workqueue_struct *monitor_wqueue; + struct work_struct monitor_work; + struct delayed_work cable_work; + struct delayed_work polling_work; + struct delayed_work measure_work; + + unsigned long charging_start_time; + unsigned long charging_passed_time; + unsigned long next_check_time; + unsigned int recharging_status; + unsigned int batt_lpm_state; + unsigned int sub_chg_status; + unsigned int voice_call_state; + unsigned int is_call_except; + unsigned int charging_set_current; + + struct mutex adclock; + + unsigned int (*get_lpcharging_state) (void); + bool charging_enabled; + bool use_sub_charger; + bool sub_chg_ovp; + int initial_check_count; + struct proc_dir_entry *entry; + + int batt_tmu_status; +}; + +static char *supply_list[] = { + "battery", +}; + +static enum power_supply_property sec_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TECHNOLOGY, +}; + +static enum power_supply_property sec_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +struct power_supply *get_power_supply_by_name(char *name) +{ + if (!name) + return (struct power_supply *)NULL; + else + return power_supply_get_by_name(name); +} + +#if 0 +static int calculate_average_adc(struct sec_bat_info *info, + struct adc_sample *sample, int adc) +{ + int i, total_adc = 0; + int average_adc = sample->average_adc; + int index = sample->index; + + if (adc < 0 || adc == 0) { + dev_err(info->dev, "%s: invalid adc : %d\n", __func__, adc); + return 0; + } + + if (!average_adc) { + average_adc = adc; + for (i = 0; i < ADC_TOTAL_COUNT; i++) + sample->adc_arr[i] = adc; + } else { + sample->index = ++index >= ADC_TOTAL_COUNT ? 0 : index; + sample->adc_arr[sample->index] = adc; + for (i = 0; i < ADC_TOTAL_COUNT; i++) + total_adc += sample->adc_arr[i]; + + average_adc = total_adc / ADC_TOTAL_COUNT; + } + + sample->average_adc = average_adc; + dev_dbg(info->dev, "%s: i(%d) adc=%d, avg_adc=%d\n", __func__, + sample->index, adc, average_adc); + + return average_adc; +} +#endif + +static int sec_bat_check_vf(struct sec_bat_info *info) +{ + int health = info->batt_health; + + if (info->present == 0) { + if (info->test_info.test_value == 999) { + pr_info("test case : %d\n", info->test_info.test_value); + health = POWER_SUPPLY_HEALTH_UNKNOWN; + } else + health = POWER_SUPPLY_HEALTH_DEAD; + } else { + health = POWER_SUPPLY_HEALTH_GOOD; + } + + /* update health */ + if (health != info->batt_health) { + if (health == POWER_SUPPLY_HEALTH_UNKNOWN || + health == POWER_SUPPLY_HEALTH_DEAD){ + info->batt_health = health; + pr_info("vf error update\n"); + } else if (info->batt_health != POWER_SUPPLY_HEALTH_OVERHEAT && + info->batt_health != POWER_SUPPLY_HEALTH_COLD && + health == POWER_SUPPLY_HEALTH_GOOD) { + info->batt_health = health; + pr_info("recovery form vf error\n"); + } + } + + return 0; +} + +static int sec_bat_check_detbat(struct sec_bat_info *info) +{ + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval value; + static int cnt; + int vf_state = BAT_DETECTED; + int ret; + + if (!psy) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return -ENODEV; + } + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_PRESENT, &value); + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", + __func__, ret); + return -ENODEV; + } + + if ((info->cable_type != CABLE_TYPE_NONE) && + (value.intval == BAT_NOT_DETECTED)) { + if (cnt <= BAT_DET_COUNT) + cnt++; + if (cnt >= BAT_DET_COUNT) + vf_state = BAT_NOT_DETECTED; + else + vf_state = BAT_DETECTED; + } else { + vf_state = BAT_DETECTED; + cnt = 0; + } + + if (info->present == 1 && + vf_state == BAT_NOT_DETECTED) { + pr_info("detbat state(->%d) changed\n", vf_state); + info->present = 0; + cancel_work_sync(&info->monitor_work); + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + } else if (info->present == 0 && + vf_state == BAT_DETECTED) { + pr_info("detbat state(->%d) changed\n", vf_state); + info->present = 1; + cancel_work_sync(&info->monitor_work); + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + } + + return value.intval; +} + +static int sec_bat_get_fuelgauge_data(struct sec_bat_info *info, int type) +{ + struct power_supply *psy + = get_power_supply_by_name(info->fuel_gauge_name); + union power_supply_propval value; + + if (!psy) { + dev_err(info->dev, "%s: fail to get fuel gauge ps\n", __func__); + return -ENODEV; + } + + switch (type) { + case FG_T_VCELL: + value.intval = 0; /*vcell */ + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &value); + break; + case FG_T_VFOCV: + value.intval = 1; /*vfocv */ + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &value); + break; + case FG_T_AVGVCELL: + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_AVG, &value); + break; + case FG_T_SOC: + value.intval = 0; /*normal soc */ + psy->get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &value); + break; + case FG_T_PSOC: + value.intval = 1; /*raw soc */ + psy->get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &value); + break; + case FG_T_TEMPER: + psy->get_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + break; + default: + return -ENODEV; + } + + return value.intval; +} + +static int sec_bat_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_bat_info *info = container_of(ps, struct sec_bat_info, + psy_bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (info->test_info.test_value == 999) { + pr_info("batt test case : %d\n", + info->test_info.test_value); + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + } else + val->intval = info->charging_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = info->batt_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = info->present; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = info->batt_temp; + break; + case POWER_SUPPLY_PROP_ONLINE: + /* battery is always online */ + /* val->intval = 1; */ + val->intval = info->cable_type; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = info->batt_vcell; + if (val->intval == -1) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (info->charging_status == POWER_SUPPLY_STATUS_FULL) { + val->intval = 100; + break; + } + val->intval = info->batt_soc; + if (val->intval == -1) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_bat_handle_sub_charger_topoff(struct sec_bat_info *info) +{ + struct power_supply *psy_sub = + get_power_supply_by_name(info->sub_charger_name); + union power_supply_propval value; + int ret = 0; + + if (!psy_sub) { + dev_err(info->dev, "%s: fail to get sub charger ps\n", + __func__); + return -ENODEV; + } + + if (info->batt_full_status == BATT_NOT_FULL) { + info->charging_status = POWER_SUPPLY_STATUS_FULL; + info->batt_full_status = BATT_FULL; + info->recharging_status = false; + info->charging_passed_time = 0; + info->charging_start_time = 0; + /* disable charging */ + value.intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = psy_sub->set_property(psy_sub, POWER_SUPPLY_PROP_STATUS, + &value); + info->charging_enabled = false; + } + return ret; +} + +static int sec_bat_set_property(struct power_supply *ps, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_bat_info *info = container_of(ps, struct sec_bat_info, + psy_bat); + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval value; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + dev_info(info->dev, "%s: topoff intr\n", __func__); + if (val->intval != POWER_SUPPLY_STATUS_FULL) + return -EINVAL; + + if (info->use_sub_charger) { + if (info->cable_type == CABLE_TYPE_USB || + info->cable_type == CABLE_TYPE_MISC) + sec_bat_handle_sub_charger_topoff(info); + break; + } + + if (info->batt_full_status == BATT_NOT_FULL) { + info->recharging_status = false; + info->batt_full_status = BATT_FULL; + info->charging_status = POWER_SUPPLY_STATUS_FULL; + /* disable charging */ + value.intval = POWER_SUPPLY_STATUS_DISCHARGING; + psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, + &value); + info->charging_enabled = false; + } +#if 0 /* for reference */ + if (info->batt_full_status == BATT_NOT_FULL) { + info->batt_full_status = BATT_1ST_FULL; + info->charging_status = POWER_SUPPLY_STATUS_FULL; + /* TODO: set topoff current 60mA */ + value.intval = 120; + psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL, + &value); + } else { + info->batt_full_status = BATT_2ND_FULL; + info->recharging_status = false; + /* disable charging */ + value.intval = POWER_SUPPLY_STATUS_DISCHARGING; + psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, + &value); + } +#endif + break; + case POWER_SUPPLY_PROP_CAPACITY: + dev_info(info->dev, "%s: refresh battery data\n", __func__); + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + /* TODO: lowbatt interrupt: called by fuel gauge */ + dev_info(info->dev, "%s: lowbatt intr\n", __func__); + if (val->intval != POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL) + return -EINVAL; + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + break; + case POWER_SUPPLY_PROP_ONLINE: + /* cable is attached or detached. called by USB switch(MUIC) */ + dev_info(info->dev, "%s: cable was changed(%d)\n", __func__, + val->intval); + switch (val->intval) { + case POWER_SUPPLY_TYPE_BATTERY: + info->cable_type = CABLE_TYPE_NONE; + break; + case POWER_SUPPLY_TYPE_MAINS: + info->cable_type = CABLE_TYPE_AC; + break; + case POWER_SUPPLY_TYPE_USB: + info->cable_type = CABLE_TYPE_USB; + break; + case POWER_SUPPLY_TYPE_MISC: + info->cable_type = CABLE_TYPE_MISC; + break; + default: + return -EINVAL; + } + wake_lock(&info->cable_wake_lock); + /* TODO : fix delay time */ + queue_delayed_work(info->monitor_wqueue, &info->cable_work, 0); + break; + case POWER_SUPPLY_PROP_HEALTH: + /* call by TMU driver + * alarm for abnormal temperature increasement + */ + info->batt_tmu_status = val->intval; + + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + dev_info(info->dev, "%s: TMU status has been changed(%d)\n", + __func__, info->batt_tmu_status); + + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_usb_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_bat_info *info = container_of(ps, struct sec_bat_info, + psy_usb); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + /* Set enable=1 only if the USB charger is connected */ + val->intval = (info->cable_type == CABLE_TYPE_USB); + + return 0; +} + +static int sec_ac_get_property(struct power_supply *ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_bat_info *info = container_of(ps, struct sec_bat_info, + psy_ac); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + /* Set enable=1 only if the AC charger is connected */ + val->intval = (info->cable_type == CABLE_TYPE_AC) || + (info->cable_type == CABLE_TYPE_MISC); + + return 0; +} + +static int sec_bat_get_adc_data(struct sec_bat_info *info, int adc_ch) +{ + int adc_data; + int adc_max = 0; + int adc_min = 0; + int adc_total = 0; + int i; + int err_value; + + for (i = 0; i < ADC_SAMPLING_CNT; i++) { + adc_data = s3c_adc_read(info->padc, adc_ch); + + if (adc_data < 0) { + pr_err("err(%d) returned, skip adc read\n", adc_data); + err_value = adc_data; + 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) / (ADC_SAMPLING_CNT - 2); +err: + return err_value; +} + +static inline int s3c_read_temper_adc(struct sec_bat_info *info) +{ + int adc; + + mutex_lock(&info->adclock); + adc = sec_bat_get_adc_data(info, ADC_CH_TEMPERATURE_MAIN); + mutex_unlock(&info->adclock); + if (adc <= 0) + adc = info->batt_temp_adc; + info->batt_temp_adc = adc; + + return adc; +} + +static inline int s3c_read_temper_adc_sub(struct sec_bat_info *info) +{ + int adc; + int adc_tmp1 = 0; + int adc_tmp2 = 0; + unsigned int temp_radc_sub = 0; + + mutex_lock(&info->adclock); + adc = sec_bat_get_adc_data(info, ADC_CH_TEMPERATURE_SUB); + mutex_unlock(&info->adclock); + if (adc <= 0) + adc = info->batt_temp_adc_sub; + info->batt_temp_adc_sub = adc; + + temp_radc_sub = info->batt_temp_adc_sub; + adc_tmp1 = temp_radc_sub * 10; + adc_tmp2 = (40950 - adc_tmp1); + temp_radc_sub = adc_tmp2 / 100; + if ((adc_tmp2 % 10) >= 5) + temp_radc_sub += 1; + + info->batt_temp_radc_sub = temp_radc_sub; + /* pr_info("[battery] lcd temper : %d, rescale : %d\n", + info->batt_temp_adc_sub, temp_radc_sub); */ + + return adc; +} + +static unsigned long sec_rescale_temp_adc(struct sec_bat_info *info) +{ + int adc_tmp = info->batt_temp_adc; + int adc_tmp1 = 0; + int adc_tmp2 = 0; + + adc_tmp1 = adc_tmp * 10; + adc_tmp2 = (40950 - adc_tmp1); + adc_tmp = adc_tmp2 / 100; + if ((adc_tmp2 % 10) >= 5) + adc_tmp += 1; + + info->batt_temp_radc = adc_tmp; + + /* pr_info("[battery] bat temper : %d, rescale : %d\n", + info->batt_temp_adc, adc_tmp); */ + + return adc_tmp; +} + +static int sec_bat_check_temper(struct sec_bat_info *info) +{ + struct power_supply *psy + = get_power_supply_by_name(info->fuel_gauge_name); + union power_supply_propval value; + int ret; + int temp; + int low = 0; + int high = 0; + int mid = 0; + + if (!info->adc_table || !info->adc_arr_size) { + /* using fake temp */ + temp = 300; + info->batt_temp = temp; + goto skip_tupdate; + } + + high = info->adc_arr_size - 1; + + while (low <= high) { + mid = (low + high) / 2; + if (info->adc_table[mid].adc > info->batt_temp_adc) + high = mid - 1; + else if (info->adc_table[mid].adc < info->batt_temp_adc) + low = mid + 1; + else + break; + } + temp = info->adc_table[mid].temperature; + /* pr_info("%s : temperature form adc table : %d\n", __func__, temp); */ + info->batt_temp = temp; + +skip_tupdate: + /* Set temperature to fuel gauge */ + if (info->fuel_gauge_name) { + value.intval = info->batt_temp / 10; + /* value.intval = temp / 10; */ + ret = psy->set_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + if (ret) { + dev_err(info->dev, "%s: fail to set temperature(%d)\n", + __func__, ret); + return ret; + } + } + + return 0; +} + +static int sec_bat_check_temper_adc(struct sec_bat_info *info) +{ + int temp_adc = s3c_read_temper_adc(info); + int rescale_adc = 0; + int health = info->batt_health; + + rescale_adc = sec_rescale_temp_adc(info); + + if (info->test_info.test_value == 1) { + pr_info("test case : %d\n", info->test_info.test_value); + rescale_adc = HIGH_BLOCK_TEMP_ADC + 1; + if (info->cable_type == CABLE_TYPE_NONE) + rescale_adc = HIGH_RECOVER_TEMP_ADC - 1; + info->batt_temp_radc = rescale_adc; + } + + if (info->cable_type == CABLE_TYPE_NONE || + info->test_info.test_value == 999) { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + health = POWER_SUPPLY_HEALTH_GOOD; + goto skip_hupdate; + } + + if (rescale_adc >= HIGH_BLOCK_TEMP_ADC) { + if (health != POWER_SUPPLY_HEALTH_OVERHEAT) + if (info->batt_temp_high_cnt <= TEMP_BLOCK_COUNT) + info->batt_temp_high_cnt++; + } else if (rescale_adc <= HIGH_RECOVER_TEMP_ADC && + rescale_adc >= LOW_RECOVER_TEMP_ADC) { + if (health == POWER_SUPPLY_HEALTH_OVERHEAT || + health == POWER_SUPPLY_HEALTH_COLD) { + info->batt_temp_high_cnt = 0; + info->batt_temp_low_cnt = 0; + } + } else if (rescale_adc <= LOW_BLOCK_TEMP_ADC) { + if (health != POWER_SUPPLY_HEALTH_COLD) + if (info->batt_temp_low_cnt <= TEMP_BLOCK_COUNT) + info->batt_temp_low_cnt++; + } + + if (info->batt_temp_high_cnt >= TEMP_BLOCK_COUNT) + health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (info->batt_temp_low_cnt >= TEMP_BLOCK_COUNT) + health = POWER_SUPPLY_HEALTH_COLD; + else + health = POWER_SUPPLY_HEALTH_GOOD; + +skip_hupdate: + if (info->batt_health != POWER_SUPPLY_HEALTH_UNKNOWN && + info->batt_health != POWER_SUPPLY_HEALTH_DEAD && + health != info->batt_health) { + info->batt_health = health; + cancel_work_sync(&info->monitor_work); + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + } + + return 0; +} + + + +static void check_chgcurrent_sub(struct sec_bat_info *info) +{ + int chg_current_adc = 0; + + mutex_lock(&info->adclock); + chg_current_adc = sec_bat_get_adc_data(info, ADC_CH_CHGCURRENT_SUB); + mutex_unlock(&info->adclock); + if (chg_current_adc < 0) + chg_current_adc = info->batt_current_adc; + info->batt_current_adc = chg_current_adc; + + dev_dbg(info->dev, + "[battery] charging current = %d\n", info->batt_current_adc); +} + +/* only for sub-charger */ +static void sec_check_chgcurrent(struct sec_bat_info *info) +{ + static int cnt; + + if (info->charging_enabled && info->cable_type == CABLE_TYPE_AC) { + check_chgcurrent_sub(info); + /* AGAIN_FEATURE */ + if (info->batt_current_adc <= CURRENT_OF_FULL_CHG_SUB) + check_chgcurrent_sub(info); + + /* if (info->test_info.test_value == 2) + { + pr_info("batt test case : %d\n", + info->test_info.test_value); + info->batt_current_adc = CURRENT_OF_FULL_CHG_SUB - 1; + cnt = FULL_CHG_COND_COUNT; + } else */ + if (info->test_info.test_value == 3) { + pr_info("batt test case : %d\n", + info->test_info.test_value); + info->batt_current_adc = CURRENT_OF_FULL_CHG_SUB + 1; + cnt = 0; + } + + if (info->batt_vcell >= FULL_CHARGE_COND_VOLTAGE) { + if (info->batt_current_adc <= CURRENT_OF_FULL_CHG_SUB) { + cnt++; + pr_info("full state? %d, %d\n", + info->batt_current_adc, cnt); + if (cnt >= FULL_CHG_COND_COUNT) { + pr_info("full state!! %d/%d\n", + cnt, FULL_CHG_COND_COUNT); + sec_bat_handle_sub_charger_topoff(info); + cnt = 0; + } + } + } else + cnt = 0; + } else { + cnt = 0; + info->batt_current_adc = 0; + } + info->test_info.full_count = cnt; +} + + +static int sec_check_recharging(struct sec_bat_info *info) +{ + static int cnt; + + if (info->batt_vcell > RECHARGING_VOLTAGE) { + cnt = 0; + return 0; + } else { + /* AGAIN_FEATURE */ + info->batt_vcell = sec_bat_get_fuelgauge_data(info, FG_T_VCELL); + if (info->batt_vcell <= RECHARGING_VOLTAGE) { + cnt++; + if (cnt >= RE_CHG_COND_COUNT) { + cnt = 0; + info->test_info.is_rechg_state = true; + return 1; + } else if (cnt >= RE_CHG_MIN_COUNT && + info->batt_vcell <= FULL_CHARGE_COND_VOLTAGE) { + cnt = 0; + info->test_info.is_rechg_state = true; + return 1; + } else + return 0; + } else { + cnt = 0; + return 0; + } + } + info->test_info.rechg_count = cnt; +} + +static void sec_bat_update_info(struct sec_bat_info *info) +{ + info->batt_presoc = info->batt_soc; + info->batt_raw_soc = sec_bat_get_fuelgauge_data(info, FG_T_PSOC); + info->batt_soc = sec_bat_get_fuelgauge_data(info, FG_T_SOC); + info->batt_vcell = sec_bat_get_fuelgauge_data(info, FG_T_VCELL); + info->batt_vfocv = sec_bat_get_fuelgauge_data(info, FG_T_VFOCV); + /* info->batt_temp = sec_bat_get_fuelgauge_data(info, FG_T_TEMPER); */ + /* pr_info("temperature form FG : %d\n", info->batt_temp); */ +} + +static int sec_bat_enable_charging_main(struct sec_bat_info *info, bool enable) +{ + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval val_type, val_chg_current, val_topoff; + int ret; + + if (!psy) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return -ENODEV; + } + + info->batt_full_status = BATT_NOT_FULL; + + if (enable) { /* Enable charging */ + switch (info->cable_type) { + case CABLE_TYPE_USB: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + break; + case CABLE_TYPE_AC: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 650; /* mA */ + break; + case CABLE_TYPE_MISC: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + break; + default: + dev_err(info->dev, "%s: Invalid func use\n", __func__); + return -EINVAL; + } + + /* Set charging current */ + ret = psy->set_property(psy, POWER_SUPPLY_PROP_CURRENT_NOW, + &val_chg_current); + if (ret) { + dev_err(info->dev, "%s: fail to set charging cur(%d)\n", + __func__, ret); + return ret; + } + + /* Set topoff current */ + /* mA */ + val_topoff.intval = 200; + ret = psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL, + &val_topoff); + if (ret) { + dev_err(info->dev, "%s: fail to set topoff cur(%d)\n", + __func__, ret); + return ret; + } + + info->charging_start_time = jiffies; + } else { /* Disable charging */ + val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING; + info->charging_passed_time = 0; + info->charging_start_time = 0; + } + + ret = psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + return ret; + } + + info->charging_enabled = enable; + + return 0; +} + +static int sec_bat_enable_charging_sub(struct sec_bat_info *info, bool enable) +{ + struct power_supply *psy_main = + get_power_supply_by_name(info->charger_name); + struct power_supply *psy_sub = + get_power_supply_by_name(info->sub_charger_name); + union power_supply_propval val_type, val_chg_current; + int ret; + + if (!psy_main || !psy_sub) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return -ENODEV; + } + + info->batt_full_status = BATT_NOT_FULL; + + if (enable) { /* Enable charging */ + val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging" + " status-main(%d)\n", __func__, ret); + return ret; + } + + switch (info->cable_type) { + case CABLE_TYPE_USB: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + break; + case CABLE_TYPE_AC: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 650; /* mA */ + break; + case CABLE_TYPE_MISC: + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + break; + default: + dev_err(info->dev, "%s: Invalid func use\n", __func__); + return -EINVAL; + } + + info->charging_set_current = val_chg_current.intval; + + /* Set charging current */ + ret = psy_sub->set_property(psy_sub, + POWER_SUPPLY_PROP_CURRENT_NOW, + &val_chg_current); + if (ret) { + dev_err(info->dev, "%s: fail to set charging cur(%d)\n", + __func__, ret); + return ret; + } + + info->charging_start_time = jiffies; + info->next_check_time = + info->charging_start_time + RESETTING_CHG_TIME; + } else { /* Disable charging */ + val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging" + " status-main(%d)\n", __func__, ret); + return ret; + } + info->next_check_time = 0; + info->charging_passed_time = 0; + info->charging_set_current = 0; + info->charging_start_time = 0; + } + + ret = psy_sub->set_property(psy_sub, POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + return ret; + } + + info->charging_enabled = enable; + + return 0; +} + +static int sec_bat_enable_charging(struct sec_bat_info *info, bool enable) +{ + if (info->use_sub_charger) + return sec_bat_enable_charging_sub(info, enable); + else + return sec_bat_enable_charging_main(info, enable); +} + +static void sec_bat_cable_work(struct work_struct *work) +{ + struct sec_bat_info *info = container_of(work, struct sec_bat_info, + cable_work.work); + + switch (info->cable_type) { + case CABLE_TYPE_NONE: + /* TODO : check DCIN state again*/ + info->sub_chg_ovp = false; + info->batt_full_status = BATT_NOT_FULL; + info->recharging_status = false; + info->test_info.is_rechg_state = false; + info->charging_start_time = 0; + info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; + sec_bat_enable_charging(info, false); + wake_lock_timeout(&info->vbus_wake_lock, HZ * 5); + cancel_delayed_work(&info->measure_work); + info->measure_interval = MEASURE_DSG_INTERVAL; + wake_lock(&info->measure_wake_lock); + queue_delayed_work(info->monitor_wqueue, + &info->measure_work, 0); + /* schedule_delayed_work(&info->measure_work, 0); */ + break; + case CABLE_TYPE_USB: + case CABLE_TYPE_AC: + case CABLE_TYPE_MISC: + /* TODO : check DCIN state again*/ + info->charging_status = POWER_SUPPLY_STATUS_CHARGING; + sec_bat_enable_charging(info, true); + wake_lock(&info->vbus_wake_lock); + cancel_delayed_work(&info->measure_work); + info->measure_interval = MEASURE_CHG_INTERVAL; + wake_lock(&info->measure_wake_lock); + queue_delayed_work(info->monitor_wqueue, + &info->measure_work, 0); + /* schedule_delayed_work(&info->measure_work, 0); */ + break; + default: + dev_err(info->dev, "%s: Invalid cable type\n", __func__); + break; + } + + power_supply_changed(&info->psy_ac); + power_supply_changed(&info->psy_usb); + /* TBD */ + /* + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + */ + + wake_unlock(&info->cable_wake_lock); +} + +static void sec_bat_charging_time_management(struct sec_bat_info *info) +{ + unsigned long charging_time; + + if (info->charging_start_time == 0) { + dev_dbg(info->dev, "%s: charging_start_time has never" + " been used since initializing\n", __func__); + return; + } + + if (jiffies >= info->charging_start_time) + charging_time = jiffies - info->charging_start_time; + else + charging_time = 0xFFFFFFFF - info->charging_start_time + + jiffies; + + info->charging_passed_time = charging_time; + + switch (info->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + if (time_after(charging_time, (unsigned long)RECHARGING_TIME) && + info->recharging_status == true) { + sec_bat_enable_charging(info, false); + info->recharging_status = false; + dev_info(info->dev, "%s: Recharging timer expired\n", + __func__); + } + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (time_after(charging_time, + (unsigned long)FULL_CHARGING_TIME)) { + sec_bat_enable_charging(info, false); + info->charging_status = POWER_SUPPLY_STATUS_FULL; + + dev_info(info->dev, "%s: Charging timer expired\n", + __func__); + } + break; + default: + dev_info(info->dev, "%s: Undefine Battery Status\n", __func__); + return; + } + + dev_dbg(info->dev, "Time past : %u secs\n", + jiffies_to_msecs(charging_time) / 1000); + + return; +} + + +static void sec_bat_check_ing_level_trigger(struct sec_bat_info *info) +{ + struct power_supply *psy_sub = + get_power_supply_by_name(info->sub_charger_name); + union power_supply_propval value; + union power_supply_propval val_type, val_chg_current; + static int full_cnt; + static int ovp_cnt; + int ret; + + ret = psy_sub->get_property(psy_sub, POWER_SUPPLY_PROP_STATUS, &value); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", + __func__, ret); + return; + } + + info->sub_chg_status = value.intval; + + if (info->charging_enabled) { + switch (value.intval) { + case POWER_SUPPLY_STATUS_DISCHARGING: /* overvoltage */ + ovp_cnt++; + if (ovp_cnt >= OVP_COND_COUNT) { + pr_info("over/under voltage detected!! (%d)\n", + ovp_cnt); + info->sub_chg_ovp = true; + info->batt_full_status = BATT_NOT_FULL; + info->recharging_status = false; + info->test_info.is_rechg_state = false; + info->charging_start_time = 0; + info->charging_status = + POWER_SUPPLY_STATUS_DISCHARGING; + sec_bat_enable_charging(info, false); + } + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: /* full */ + if (info->cable_type == CABLE_TYPE_USB || + info->cable_type == CABLE_TYPE_MISC) { + pr_info("usb full cnt = %d\n", full_cnt); + full_cnt++; + if (full_cnt >= USB_FULL_COND_COUNT && + info->batt_vcell >= + USB_FULL_COND_VOLTAGE) { + full_cnt = 0; + sec_bat_handle_sub_charger_topoff(info); + } + } else { + full_cnt = 0; + } + break; + case POWER_SUPPLY_STATUS_CHARGING: /* charging */ + dev_dbg(info->dev, "%s : passed_time(%lu) > next_time(%lu)\n", + __func__, info->charging_passed_time, + info->next_check_time); + if (time_after(info->charging_passed_time, + info->next_check_time)) { + info->next_check_time = + info->charging_passed_time + + RESETTING_CHG_TIME; + if (info->is_call_except) { + pr_info("call exception case, skip resetting\n"); + full_cnt = 0; + ovp_cnt = 0; + break; + } + + if (info->cable_type == CABLE_TYPE_AC) { + val_type.intval = + POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 650; /* mA */ + + ret = psy_sub->set_property(psy_sub, + POWER_SUPPLY_PROP_CURRENT_NOW, + &val_chg_current); + if (ret) { + dev_err(info->dev, "%s: fail to set charging cur(%d)\n", + __func__, ret); + } + + ret = psy_sub->set_property(psy_sub, + POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + } + + dev_info(info->dev, "%s: reset charging current\n", + __func__); + } + } + full_cnt = 0; + ovp_cnt = 0; + break; + default: + full_cnt = 0; + ovp_cnt = 0; + } + } else { + if (info->charging_status == POWER_SUPPLY_STATUS_DISCHARGING && + info->sub_chg_status == + POWER_SUPPLY_STATUS_NOT_CHARGING && + info->sub_chg_ovp == true) { + pr_info("recover from ovp, charge again!!\n"); + info->sub_chg_ovp = false; + info->charging_status = POWER_SUPPLY_STATUS_CHARGING; + sec_bat_enable_charging(info, true); + } + full_cnt = 0; + ovp_cnt = 0; + } +} + +static int sec_main_charger_disable(struct sec_bat_info *info) +{ + struct power_supply *psy_main = + get_power_supply_by_name(info->charger_name); + union power_supply_propval val_type; + int ret; + + val_type.intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = psy_main->set_property(psy_main, POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging" + " status-main(%d)\n", __func__, ret); + } + + return ret; +} + +static int sec_main_charger_default(struct sec_bat_info *info) +{ + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval val_type, val_chg_current; + int ret; + + if (!psy) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return -ENODEV; + } + + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; /* mA */ + + /* Set charging current */ + ret = psy->set_property(psy, POWER_SUPPLY_PROP_CURRENT_NOW, + &val_chg_current); + if (ret) { + dev_err(info->dev, "%s: fail to set charging cur(%d)\n", + __func__, ret); + return ret; + } + + ret = psy->set_property(psy, POWER_SUPPLY_PROP_STATUS, &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + return ret; + } + + return 0; +} + +#if 0 /* for backup */ +static void sec_batt_check_call_exception_test(struct sec_bat_info *info) +{ + struct power_supply *psy_sub = + get_power_supply_by_name(info->sub_charger_name); + union power_supply_propval val_type, val_chg_current; + int proximity_activation = 0; + int proximity_approach = 0; + int ret; + + if (info->cable_type != CABLE_TYPE_AC) { + info->is_call_except = 0; + return; + } + + if (!info->charging_enabled) { + if (info->is_call_except) + info->is_call_except = 0; + return; + } + + if (!info->is_call_except) + info->is_call_except = 1; + else + info->is_call_except = 0; + + val_type.intval = POWER_SUPPLY_STATUS_UNKNOWN; + + if (info->charging_set_current == 650 && + info->is_call_except == 1) { + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; + info->charging_set_current = val_chg_current.intval; + pr_info("call exception enable!\n"); + } else if (info->charging_set_current == 450 && + info->is_call_except == 0) { + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 650; + info->charging_set_current = val_chg_current.intval; + pr_info("call exception disable!\n"); + } + + if (val_type.intval == POWER_SUPPLY_STATUS_CHARGING) { + ret = psy_sub->set_property(psy_sub, + POWER_SUPPLY_PROP_CURRENT_NOW, + &val_chg_current); + if (ret) { + dev_err(info->dev, "%s: fail to set charging cur(%d)\n", + __func__, ret); + } + + ret = psy_sub->set_property(psy_sub, + POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + } + + dev_info(info->dev, "%s: reset charging current\n", + __func__); + } +} +#endif + +#if 0 /* for backup */ +static void sec_batt_check_call_exception(struct sec_bat_info *info) +{ + struct power_supply *psy_sub = + get_power_supply_by_name(info->sub_charger_name); + union power_supply_propval val_type, val_chg_current; + int proximity_activation = 0; + int proximity_approach = 0; + int ret; + + if (info->cable_type != CABLE_TYPE_AC) { + info->is_call_except = 0; + return; + } + + if (!info->charging_enabled) { + if (info->is_call_except) + info->is_call_except = 0; + return; + } + + proximity_activation = get_proximity_activation_state(); + proximity_approach = get_proximity_approach_state(); + + if (info->voice_call_state && + proximity_activation && + proximity_approach) { + /* info->is_call_except = 1; */ + info->batt_vcell = sec_bat_get_fuelgauge_data(info, FG_T_VCELL); + if (info->batt_vcell >= CALL_EXCEPTION_VOLTAGE_DN && + info->batt_vcell < CALL_EXCEPTION_VOLTAGE_UP && + info->is_call_except == 0) { + info->is_call_except = 0; + } else if (info->batt_vcell < CALL_EXCEPTION_VOLTAGE_DN) { + info->is_call_except = 0; + } else { + info->is_call_except = 1; + } + } else { + info->is_call_except = 0; + } + + val_type.intval = POWER_SUPPLY_STATUS_UNKNOWN; + + if (info->charging_set_current == 650 && + info->is_call_except == 1) { + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 450; + info->charging_set_current = val_chg_current.intval; + pr_info("call exception enable! (%d, %d, %d)\n", + info->voice_call_state, proximity_activation, + proximity_approach); + } else if (info->charging_set_current == 450 && + info->is_call_except == 0) { + val_type.intval = POWER_SUPPLY_STATUS_CHARGING; + val_chg_current.intval = 650; + info->charging_set_current = val_chg_current.intval; + pr_info("call exception disable! (%d, %d, %d)\n", + info->voice_call_state, proximity_activation, + proximity_approach); + } + + if (val_type.intval == POWER_SUPPLY_STATUS_CHARGING) { + ret = psy_sub->set_property(psy_sub, + POWER_SUPPLY_PROP_CURRENT_NOW, + &val_chg_current); + if (ret) { + dev_err(info->dev, "%s: fail to set charging cur(%d)\n", + __func__, ret); + } + + ret = psy_sub->set_property(psy_sub, + POWER_SUPPLY_PROP_STATUS, + &val_type); + if (ret) { + dev_err(info->dev, "%s: fail to set charging status(%d)\n", + __func__, ret); + } + + dev_info(info->dev, "%s: reset charging current\n", + __func__); + } +} +#endif + +static void sec_bat_monitor_work(struct work_struct *work) +{ + struct sec_bat_info *info = container_of(work, struct sec_bat_info, + monitor_work); + + wake_lock(&info->monitor_wake_lock); + + sec_bat_charging_time_management(info); + + sec_bat_update_info(info); + sec_bat_check_temper(info); + sec_bat_check_vf(info); + + if (info->use_sub_charger) { + sec_bat_check_ing_level_trigger(info); + sec_check_chgcurrent(info); + + /*if (info->sub_chg_status == POWER_SUPPLY_STATUS_CHARGING && + info->test_info.test_value == 2) { + pr_info("batt test case : %d\n", + info->test_info.test_value); + sec_bat_handle_sub_charger_topoff(info); + }*/ + } + + if (info->test_info.test_value == 6) + info->batt_tmu_status = TMU_STATUS_WARNING; + + switch (info->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + if (sec_check_recharging(info) && + info->recharging_status == false) { + info->recharging_status = true; + sec_bat_enable_charging(info, true); + + dev_info(info->dev, + "%s: Start Recharging, Vcell = %d\n", __func__, + info->batt_vcell); + } + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (info->batt_health == POWER_SUPPLY_HEALTH_OVERHEAT + || info->batt_health == POWER_SUPPLY_HEALTH_COLD) { + sec_bat_enable_charging(info, false); + info->charging_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + info->test_info.is_rechg_state = false; + + dev_info(info->dev, "%s: Not charging\n", __func__); + } else if (info->batt_health == POWER_SUPPLY_HEALTH_DEAD) { + sec_bat_enable_charging(info, false); + info->charging_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + info->test_info.is_rechg_state = false; + dev_info(info->dev, "%s: Not charging (VF err!)\n", + __func__); + } + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + dev_dbg(info->dev, "%s: Discharging\n", __func__); + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (info->batt_health == POWER_SUPPLY_HEALTH_GOOD) { + dev_info(info->dev, "%s: recover health state\n", + __func__); + if (info->cable_type != CABLE_TYPE_NONE) { + sec_bat_enable_charging(info, true); + info->charging_status + = POWER_SUPPLY_STATUS_CHARGING; + } else + info->charging_status + = POWER_SUPPLY_STATUS_DISCHARGING; + } + break; + default: + dev_info(info->dev, "%s: Undefined Battery Status\n", __func__); + return; + } + + if (info->batt_soc != info->batt_presoc) + pr_info("[fg] p:%d, s1:%d, s2:%d, v:%d, t:%d\n", + info->batt_raw_soc, + info->batt_soc, info->batt_presoc, + info->batt_vcell, info->batt_temp_radc); + + power_supply_changed(&info->psy_bat); + + wake_unlock(&info->monitor_wake_lock); + + return; +} + +static void sec_bat_polling_work(struct work_struct *work) +{ + struct sec_bat_info *info; + info = container_of(work, struct sec_bat_info, polling_work.work); + + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + if (info->initial_check_count) { + schedule_delayed_work(&info->polling_work, HZ); + info->initial_check_count--; + } else + schedule_delayed_work(&info->polling_work, + msecs_to_jiffies(info->polling_interval)); +} + +static void sec_bat_measure_work(struct work_struct *work) +{ + struct sec_bat_info *info; + info = container_of(work, struct sec_bat_info, measure_work.work); + + wake_lock(&info->measure_wake_lock); + sec_bat_check_temper_adc(info); + if (sec_bat_check_detbat(info) == BAT_NOT_DETECTED + && info->present == 1) { + msleep(100); + sec_bat_check_detbat(info); /* AGAIN_FEATURE */ + } + /* TBD */ + /* + sec_batt_check_call_exception(info); + sec_batt_check_call_exception_test(info); */ /* for test */ + + if (info->initial_check_count) { + queue_delayed_work(info->monitor_wqueue, &info->measure_work, + HZ); + } else { + queue_delayed_work(info->monitor_wqueue, &info->measure_work, + msecs_to_jiffies(info->measure_interval)); + } + /* + schedule_delayed_work(&info->measure_work, + msecs_to_jiffies(info->measure_interval)); + */ + wake_unlock(&info->measure_wake_lock); +} + +#define SEC_BATTERY_ATTR(_name) \ +{ \ + .attr = { .name = #_name, \ + .mode = 0664 }, \ + .show = sec_bat_show_property, \ + .store = sec_bat_store, \ +} + +static struct device_attribute sec_battery_attrs[] = { + SEC_BATTERY_ATTR(batt_vol), + SEC_BATTERY_ATTR(batt_soc), + SEC_BATTERY_ATTR(batt_vfocv), + SEC_BATTERY_ATTR(batt_temp), + SEC_BATTERY_ATTR(batt_temp_adc), + SEC_BATTERY_ATTR(batt_temp_adc_avg), + SEC_BATTERY_ATTR(batt_temp_adc_sub), + SEC_BATTERY_ATTR(charging_source), + SEC_BATTERY_ATTR(batt_lp_charging), + SEC_BATTERY_ATTR(video), + SEC_BATTERY_ATTR(mp3), + SEC_BATTERY_ATTR(batt_type), + SEC_BATTERY_ATTR(batt_full_check), + SEC_BATTERY_ATTR(batt_temp_check), + SEC_BATTERY_ATTR(batt_temp_adc_spec), + SEC_BATTERY_ATTR(batt_test_value), + SEC_BATTERY_ATTR(batt_temp_radc), + SEC_BATTERY_ATTR(batt_current_adc), + SEC_BATTERY_ATTR(batt_esus_test), + SEC_BATTERY_ATTR(system_rev), + SEC_BATTERY_ATTR(fg_psoc), + SEC_BATTERY_ATTR(batt_lpm_state), + SEC_BATTERY_ATTR(sub_chg_state), + SEC_BATTERY_ATTR(lpm_reboot_event), + SEC_BATTERY_ATTR(talk_wcdma), + SEC_BATTERY_ATTR(talk_gsm), + SEC_BATTERY_ATTR(batt_tmu_status), +}; + +enum { + BATT_VOL = 0, + BATT_SOC, + BATT_VFOCV, + BATT_TEMP, + BATT_TEMP_ADC, + BATT_TEMP_ADC_AVG, + BATT_TEMP_ADC_SUB, + CHARGING_SOURCE, + BATT_LP_CHARGING, + BATT_VIDEO, + BATT_MP3, + BATT_TYPE, + BATT_FULL_CHECK, + BATT_TEMP_CHECK, + BATT_TEMP_ADC_SPEC, + BATT_TEST_VALUE, + BATT_TEMP_RADC, + BATT_CURRENT_ADC, + BATT_ESUS_TEST, + BATT_SYSTEM_REV, + BATT_FG_PSOC, + BATT_LPM_STATE, + BATT_SUB_CHG_STATE, + LPM_REBOOT_EVENT, + BATT_WCDMA_CALL, + BATT_GSM_CALL, + BATT_TMU_STATUS, +}; + +static ssize_t sec_bat_show_property(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_bat_info *info = dev_get_drvdata(dev->parent); + int i = 0, val; + const ptrdiff_t off = attr - sec_battery_attrs; + + switch (off) { + case BATT_VOL: + val = sec_bat_get_fuelgauge_data(info, FG_T_AVGVCELL); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_SOC: + val = sec_bat_get_fuelgauge_data(info, FG_T_SOC); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_VFOCV: + val = sec_bat_get_fuelgauge_data(info, FG_T_VFOCV); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_TEMP: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_temp); + break; + case BATT_TEMP_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_temp_adc); + break; + case BATT_TEMP_ADC_AVG: + /* + val = info->temper_adc_sample.average_adc; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + */ + break; + case BATT_TEMP_ADC_SUB: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_temp_radc_sub); + break; + case CHARGING_SOURCE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->cable_type); + break; + case BATT_LP_CHARGING: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->get_lpcharging_state()); + break; + case BATT_VIDEO: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_MP3: + /* TODO */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", 0); + break; + case BATT_TYPE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", "SDI_SDI"); + break; + case BATT_FULL_CHECK: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (info->charging_status == + POWER_SUPPLY_STATUS_FULL) ? 1 : 0); + break; + case BATT_TEMP_CHECK: + i += scnprintf(buf + i, PAGE_SIZE - i, + "%d\n", info->batt_health); + break; + case BATT_TEMP_ADC_SPEC: + i += scnprintf(buf + i, PAGE_SIZE - i, + "(HIGH: %d - %d, LOW: %d - %d)\n", + HIGH_BLOCK_TEMP_ADC, HIGH_RECOVER_TEMP_ADC, + LOW_BLOCK_TEMP_ADC, LOW_RECOVER_TEMP_ADC); + break; + case BATT_TEST_VALUE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->test_info.test_value); + break; + case BATT_TEMP_RADC: + val = s3c_read_temper_adc(info); + val = sec_rescale_temp_adc(info); + i += scnprintf(buf + i, PAGE_SIZE - i, + "%d\n", info->batt_temp_radc); + break; + case BATT_CURRENT_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_current_adc); + break; + case BATT_ESUS_TEST: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->test_info.test_esuspend); + break; + case BATT_SYSTEM_REV: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", system_rev); + break; + case BATT_FG_PSOC: + val = sec_bat_get_fuelgauge_data(info, FG_T_PSOC); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", val); + break; + case BATT_LPM_STATE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_lpm_state); + break; + case BATT_SUB_CHG_STATE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->sub_chg_status); + break; + case BATT_TMU_STATUS: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + info->batt_tmu_status); + break; + default: + i = -EINVAL; + } + + return i; +} + +static ssize_t sec_bat_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = 0, x = 0; + const ptrdiff_t off = attr - sec_battery_attrs; + struct sec_bat_info *info = dev_get_drvdata(dev->parent); + + switch (off) { + case BATT_VIDEO: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: video(%d)\n", __func__, x); + ret = count; + } + break; + case BATT_MP3: + /* TODO */ + if (sscanf(buf, "%d\n", &x) == 1) { + dev_info(info->dev, "%s: mp3(%d)\n", __func__, x); + ret = count; + } + break; + case BATT_ESUS_TEST: /* early_suspend test */ + if (sscanf(buf, "%d\n", &x) == 1) { + if (x == 0) { + info->test_info.test_esuspend = 0; + wake_lock_timeout(&info->test_wake_lock, + 5 * HZ); + cancel_delayed_work(&info->measure_work); + info->measure_interval = MEASURE_DSG_INTERVAL; + wake_lock(&info->measure_wake_lock); + queue_delayed_work(info->monitor_wqueue, + &info->measure_work, 0); + /* schedule_delayed_work(&info->measure_work, + 0); */ + } else { + info->test_info.test_esuspend = 1; + wake_lock(&info->test_wake_lock); + cancel_delayed_work(&info->measure_work); + info->measure_interval = MEASURE_CHG_INTERVAL; + wake_lock(&info->measure_wake_lock); + queue_delayed_work(info->monitor_wqueue, + &info->measure_work, 0); + /* schedule_delayed_work(&info->measure_work, + 0); */ + } + ret = count; + } + break; + case BATT_TEST_VALUE: + if (sscanf(buf, "%d\n", &x) == 1) { + if (x == 0) + info->test_info.test_value = 0; + else if (x == 1) + /* for temp warning event */ + info->test_info.test_value = 1; + else if (x == 2) + /* for full event */ + /* info->test_info.test_value = 2; */ + /* disable full test interface. */ + info->test_info.test_value = 0; + else if (x == 3) + /* for abs time event */ + info->test_info.test_value = 3; + else if (x == 999) { + /* for pop-up disable */ + info->test_info.test_value = 999; + if ((info->batt_health == + POWER_SUPPLY_HEALTH_OVERHEAT) || + (info->batt_health == + POWER_SUPPLY_HEALTH_COLD)) { + info->batt_health = + POWER_SUPPLY_HEALTH_GOOD; + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, + &info->monitor_work); + } + } else if (x == 6) + /* for tmu test */ + info->test_info.test_value = 6; + else + info->test_info.test_value = 0; + pr_info("batt test case : %d\n", + info->test_info.test_value); + ret = count; + } + break; + case BATT_LPM_STATE: + if (sscanf(buf, "%d\n", &x) == 1) { + info->batt_lpm_state = x; + ret = count; + } + break; + case LPM_REBOOT_EVENT: + if (sscanf(buf, "%d\n", &x) == 1) { + /* disable sub-charger and enable main-charger */ + pr_info("lpm reboot event is trigered\n"); + if (info->use_sub_charger) { + sec_bat_enable_charging(info, false); + sec_main_charger_default(info); + } + ret = count; + } + break; + case BATT_WCDMA_CALL: + case BATT_GSM_CALL: + if (sscanf(buf, "%d\n", &x) == 1) { + info->voice_call_state = x; + pr_info("voice call = %d, %d\n", + x, info->voice_call_state); + } + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int sec_bat_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(sec_battery_attrs); i++) { + rc = device_create_file(dev, &sec_battery_attrs[i]); + if (rc) + goto sec_attrs_failed; + } + goto succeed; + +sec_attrs_failed: + while (i--) + device_remove_file(dev, &sec_battery_attrs[i]); +succeed: + return rc; +} + +static int sec_bat_is_charging(struct sec_bat_info *info) +{ + struct power_supply *psy = get_power_supply_by_name(info->charger_name); + union power_supply_propval value; + int ret; + + if (!psy) { + dev_err(info->dev, "%s: fail to get charger ps\n", __func__); + return -ENODEV; + } + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value); + if (ret < 0) { + dev_err(info->dev, "%s: fail to get status(%d)\n", __func__, + ret); + return ret; + } + + return value.intval; +} + +static int sec_bat_read_proc(char *buf, char **start, + off_t offset, int count, int *eof, void *data) +{ + struct sec_bat_info *info = data; + struct timespec cur_time; + ktime_t ktime; + int len = 0; + + ktime = alarm_get_elapsed_realtime(); + cur_time = ktime_to_timespec(ktime); + + len = sprintf(buf, "%lu %u %u %u %u " + "%u %d %d %d %d " + "%d %u %u %u %u " + "%u %u %d %d %lu\n", + cur_time.tv_sec, + info->batt_raw_soc, + info->batt_soc, + info->batt_vcell, + info->batt_current_adc, + info->charging_enabled, + info->batt_full_status, + info->test_info.full_count, + info->test_info.full_count_sub, + info->test_info.rechg_count, + info->test_info.is_rechg_state, + info->recharging_status, + info->batt_temp_radc, + info->batt_temp_radc_sub, + info->batt_health, + info->charging_status, + info->present, + info->cable_type, + info->batt_tmu_status, + info->charging_passed_time); + return len; +} + +static __devinit int sec_bat_probe(struct platform_device *pdev) +{ + struct sec_bat_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct sec_bat_info *info; + int ret = 0; + + dev_info(&pdev->dev, "%s: SEC Battery Driver Loading\n", __func__); + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(pdev, info); + + info->dev = &pdev->dev; + if (!pdata->fuel_gauge_name || !pdata->charger_name) { + dev_err(info->dev, "%s: no fuel gauge or charger name\n", + __func__); + goto err_kfree; + } + info->fuel_gauge_name = pdata->fuel_gauge_name; + info->charger_name = pdata->charger_name; + info->adc_arr_size = pdata->adc_arr_size; + info->adc_table = pdata->adc_table; + + info->get_lpcharging_state = pdata->get_lpcharging_state; + + if (pdata->sub_charger_name) { + info->sub_charger_name = pdata->sub_charger_name; + if (system_rev >= HWREV_FOR_BATTERY) + info->use_sub_charger = true; + } + + info->psy_bat.name = "battery", + info->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY, + info->psy_bat.properties = sec_battery_props, + info->psy_bat.num_properties = ARRAY_SIZE(sec_battery_props), + info->psy_bat.get_property = sec_bat_get_property, + info->psy_bat.set_property = sec_bat_set_property, + info->psy_usb.name = "usb", + info->psy_usb.type = POWER_SUPPLY_TYPE_USB, + info->psy_usb.supplied_to = supply_list, + info->psy_usb.num_supplicants = ARRAY_SIZE(supply_list), + info->psy_usb.properties = sec_power_props, + info->psy_usb.num_properties = ARRAY_SIZE(sec_power_props), + info->psy_usb.get_property = sec_usb_get_property, + info->psy_ac.name = "ac", + info->psy_ac.type = POWER_SUPPLY_TYPE_MAINS, + info->psy_ac.supplied_to = supply_list, + info->psy_ac.num_supplicants = ARRAY_SIZE(supply_list), + info->psy_ac.properties = sec_power_props, + info->psy_ac.num_properties = ARRAY_SIZE(sec_power_props), + info->psy_ac.get_property = sec_ac_get_property; + + wake_lock_init(&info->vbus_wake_lock, WAKE_LOCK_SUSPEND, + "vbus_present"); + wake_lock_init(&info->monitor_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-monitor"); + wake_lock_init(&info->cable_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-cable"); + wake_lock_init(&info->test_wake_lock, WAKE_LOCK_SUSPEND, + "bat_esus_test"); + wake_lock_init(&info->measure_wake_lock, WAKE_LOCK_SUSPEND, + "sec-battery-measure"); + + info->batt_health = POWER_SUPPLY_HEALTH_GOOD; + info->charging_status = sec_bat_is_charging(info); + if (info->charging_status < 0) + info->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; + else if (info->charging_status == POWER_SUPPLY_STATUS_CHARGING) + info->cable_type = CABLE_TYPE_USB; /* default */ + info->batt_soc = 100; + info->recharging_status = false; + info->present = 1; + info->initial_check_count = INIT_CHECK_COUNT; + + mutex_init(&info->adclock); + + info->padc = s3c_adc_register(pdev, NULL, NULL, 0); + info->charging_start_time = 0; + info->is_call_except = 0; + + if (info->get_lpcharging_state) { + if (info->get_lpcharging_state()) + info->polling_interval = POLLING_INTERVAL / 4; + else + info->polling_interval = POLLING_INTERVAL; + } + + info->batt_tmu_status = TMU_STATUS_NORMAL; + + if (info->charging_status == POWER_SUPPLY_STATUS_CHARGING) + info->measure_interval = MEASURE_CHG_INTERVAL; + else + info->measure_interval = MEASURE_DSG_INTERVAL; + + /* init power supplier framework */ + ret = power_supply_register(&pdev->dev, &info->psy_bat); + if (ret) { + dev_err(info->dev, "%s: failed to register psy_bat\n", + __func__); + goto err_wake_lock; + } + + ret = power_supply_register(&pdev->dev, &info->psy_usb); + if (ret) { + dev_err(info->dev, "%s: failed to register psy_usb\n", + __func__); + goto err_supply_unreg_bat; + } + + ret = power_supply_register(&pdev->dev, &info->psy_ac); + if (ret) { + dev_err(info->dev, "%s: failed to register psy_ac\n", __func__); + goto err_supply_unreg_usb; + } + + /* create sec detail attributes */ + sec_bat_create_attrs(info->psy_bat.dev); + + info->entry = create_proc_entry("batt_info_proc", S_IRUGO, NULL); + if (!info->entry) + dev_err(info->dev, "%s: failed to create proc_entry\n", + __func__); + else { + info->entry->read_proc = sec_bat_read_proc; + info->entry->data = (struct sec_bat_info *)info; + } + + info->monitor_wqueue = + create_freezable_workqueue(dev_name(&pdev->dev)); + if (!info->monitor_wqueue) { + dev_err(info->dev, "%s: fail to create workqueue\n", __func__); + goto err_supply_unreg_ac; + } + + if (info->charging_status == POWER_SUPPLY_STATUS_DISCHARGING) + sec_main_charger_disable(info); + + INIT_WORK(&info->monitor_work, sec_bat_monitor_work); + INIT_DELAYED_WORK_DEFERRABLE(&info->cable_work, sec_bat_cable_work); + + INIT_DELAYED_WORK_DEFERRABLE(&info->polling_work, sec_bat_polling_work); + schedule_delayed_work(&info->polling_work, 0); + + INIT_DELAYED_WORK_DEFERRABLE(&info->measure_work, sec_bat_measure_work); + schedule_delayed_work(&info->measure_work, 0); + + return 0; + +err_supply_unreg_ac: + power_supply_unregister(&info->psy_ac); +err_supply_unreg_usb: + power_supply_unregister(&info->psy_usb); +err_supply_unreg_bat: + power_supply_unregister(&info->psy_bat); +err_wake_lock: + wake_lock_destroy(&info->vbus_wake_lock); + wake_lock_destroy(&info->monitor_wake_lock); + wake_lock_destroy(&info->cable_wake_lock); + wake_lock_destroy(&info->test_wake_lock); + wake_lock_destroy(&info->measure_wake_lock); + mutex_destroy(&info->adclock); +err_kfree: + kfree(info); + + return ret; +} + +static int __devexit sec_bat_remove(struct platform_device *pdev) +{ + struct sec_bat_info *info = platform_get_drvdata(pdev); + + remove_proc_entry("batt_info_proc", NULL); + + flush_workqueue(info->monitor_wqueue); + destroy_workqueue(info->monitor_wqueue); + + cancel_delayed_work(&info->cable_work); + cancel_delayed_work(&info->polling_work); + cancel_delayed_work(&info->measure_work); + + power_supply_unregister(&info->psy_bat); + power_supply_unregister(&info->psy_usb); + power_supply_unregister(&info->psy_ac); + + wake_lock_destroy(&info->vbus_wake_lock); + wake_lock_destroy(&info->monitor_wake_lock); + wake_lock_destroy(&info->cable_wake_lock); + wake_lock_destroy(&info->test_wake_lock); + wake_lock_destroy(&info->measure_wake_lock); + mutex_destroy(&info->adclock); + + s3c_adc_release(info->padc); + + kfree(info); + + return 0; +} + +static int sec_bat_suspend(struct device *dev) +{ + struct sec_bat_info *info = dev_get_drvdata(dev); + + cancel_work_sync(&info->monitor_work); + cancel_delayed_work(&info->cable_work); + cancel_delayed_work(&info->polling_work); + cancel_delayed_work(&info->measure_work); + + return 0; +} + +static int sec_bat_resume(struct device *dev) +{ + struct sec_bat_info *info = dev_get_drvdata(dev); + + wake_lock(&info->monitor_wake_lock); + queue_work(info->monitor_wqueue, &info->monitor_work); + + schedule_delayed_work(&info->polling_work, + msecs_to_jiffies(info->polling_interval)); + schedule_delayed_work(&info->measure_work, + msecs_to_jiffies(info->measure_interval)); + + return 0; +} + +#define sec_bat_shutdown NULL + +static const struct dev_pm_ops sec_bat_pm_ops = { + .suspend = sec_bat_suspend, + .resume = sec_bat_resume, +}; + +static struct platform_driver sec_bat_driver = { + .driver = { + .name = "sec-battery", + .owner = THIS_MODULE, + .pm = &sec_bat_pm_ops, + .shutdown = sec_bat_shutdown, + }, + .probe = sec_bat_probe, + .remove = __devexit_p(sec_bat_remove), +}; + +static int __init sec_bat_init(void) +{ + return platform_driver_register(&sec_bat_driver); +} + +static void __exit sec_bat_exit(void) +{ + platform_driver_unregister(&sec_bat_driver); +} + +late_initcall(sec_bat_init); +module_exit(sec_bat_exit); + +MODULE_DESCRIPTION("SEC battery driver"); +MODULE_AUTHOR("<ms925.kim@samsung.com>"); +MODULE_AUTHOR("<joshua.chang@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/smb136_charger.c b/drivers/power/smb136_charger.c new file mode 100644 index 00000000000..10afcc7600c --- /dev/null +++ b/drivers/power/smb136_charger.c @@ -0,0 +1,465 @@ +/* + * smb136_charger.c + * + * Copyright (C) 2011 Samsung Electronics + * Ikkeun Kim <iks.kim@samsung.com> + * + * 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/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/power_supply.h> +#include <linux/power/sec_battery_px.h> +#include <linux/power/smb136_charger.h> +#include <linux/mfd/max8997.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <plat/gpio-cfg.h> +#include <mach/gpio-p2.h> + + +/* Slave address */ +#define SMB136_SLAVE_ADDR 0x9A + +/* SMB136 Registers. */ +#define SMB_ChargeCurrent 0x00 +#define SMB_InputCurrentLimit 0x01 +#define SMB_FloatVoltage 0x02 +#define SMB_ControlA 0x03 +#define SMB_ControlB 0x04 +#define SMB_PinControl 0x05 +#define SMB_OTGControl 0x06 +#define SMB_Fault 0x07 +#define SMB_Temperature 0x08 +#define SMB_SafetyTimer 0x09 +#define SMB_VSYS 0x0A +#define SMB_I2CAddr 0x0B + +#define SMB_IRQreset 0x30 +#define SMB_CommandA 0x31 +#define SMB_StatusA 0x32 +#define SMB_StatusB 0x33 +#define SMB_StatusC 0x34 +#define SMB_StatusD 0x35 +#define SMB_StatusE 0x36 +#define SMB_StatusF 0x37 +#define SMB_StatusG 0x38 +#define SMB_StatusH 0x39 +#define SMB_DeviceID 0x3B +#define SMB_CommandB 0x3C + +/* SMB_StatusC register bit. */ +#define SMB_USB 1 +#define SMB_CHARGER 0 +#define Compelete 1 +#define Busy 0 +#define InputCurrent275 0xE +#define InputCurrent500 0xF +#define InputCurrent700 0x0 +#define InputCurrent800 0x1 +#define InputCurrent900 0x2 +#define InputCurrent1000 0x3 +#define InputCurrent1100 0x4 +#define InputCurrent1200 0x5 +#define InputCurrent1300 0x6 +#define InputCurrent1400 0x7 + +/* SIOP */ +#define CHARGING_CURRENT_HIGH_LOW_STANDARD 450 + +struct smb136_chg_data { + struct i2c_client *client; + struct smb_charger_data *pdata; + struct smb_charger_callbacks *callbacks; +}; + +static struct smb136_chg_data *smb136_chg; +static int charger_i2c_init; + + +static int smb136_i2c_read(struct i2c_client *client, u8 reg, u8 *data) +{ + int ret = 0; + + if (!client) + return -ENODEV; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + return -EIO; + + *data = ret & 0xff; + return 0; +} + +static int smb136_i2c_write(struct i2c_client *client, u8 reg, u8 data) +{ + if (!client) + return -ENODEV; + + return i2c_smbus_write_byte_data(client, reg, data); +} + +static void smb136_test_read(void) +{ + struct smb136_chg_data *chg = smb136_chg; + u8 data = 0; + u32 addr = 0; + + if (!charger_i2c_init) { + pr_info("%s : smb136 charger IC i2c is not initialized!!\n" + , __func__); + return ; + } + + for (addr = 0; addr < 0x0c; addr++) { + smb136_i2c_read(chg->client, addr, &data); + pr_info("SMB136 addr : 0x%02x data : 0x%02x\n", addr, data); + } + + for (addr = 0x31; addr < 0x3D; addr++) { + smb136_i2c_read(chg->client, addr, &data); + pr_info("SMB136 addr : 0x%02x data : 0x%02x\n", addr, data); + } +} + +static int smb136_get_charging_state(void) +{ + struct smb136_chg_data *chg = smb136_chg; + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 command_a_data, statue_e_data, statue_h_data; + + smb136_i2c_read(chg->client, SMB_CommandA, &command_a_data); + smb136_i2c_read(chg->client, SMB_StatusE, &statue_e_data); + smb136_i2c_read(chg->client, SMB_StatusH, &statue_h_data); + + if (statue_h_data & 0x01) { + pr_debug("%s: POWER_SUPPLY_STATUS_CHARGING\n", __func__); + return POWER_SUPPLY_STATUS_CHARGING; + } else { + if (!(statue_e_data & 0x06)) { + pr_debug("%s: POWER_SUPPLY_STATUS_DISCHARGING\n" + , __func__); + return POWER_SUPPLY_STATUS_DISCHARGING; + } else if ((statue_e_data & 0xc0)) { + pr_debug("%s: POWER_SUPPLY_STATUS_FULL\n", __func__); + return POWER_SUPPLY_STATUS_FULL; + } + } + + pr_info("%s: POWER_SUPPLY_STATUS_UNKNOWN\n", __func__); + pr_info("%s: 0x31h(0x%02x), 0x36h(0x%02x), 0x39h(0x%02x)\n", + __func__, command_a_data, statue_e_data, statue_h_data); + return (int)status; +} + +static void smb136_set_charging_state(int en, int cable_status) +{ + struct smb136_chg_data *chg = smb136_chg; + u8 data = 0; + + if (!charger_i2c_init) { + pr_info("%s : smb136 charger IC i2c is not initialized!!\n" + , __func__); + return; + } + + pr_info("%s : enable(%d), cable_status(%d)\n" + , __func__, en, cable_status); + + if (en) { /* enable */ + /* 2. Change USB5/1/HC Control from Pin to I2C */ + smb136_i2c_write(chg->client, SMB_PinControl, 0x8); + udelay(10); + + /* 1. USB 100mA Mode, USB5/1 Current Levels */ + /* Prevent in-rush current */ + data = 0x80; + smb136_i2c_write(chg->client, SMB_CommandA, data); + udelay(10); + + /* 3. Set charge current to 100mA */ + /* Prevent in-rush current */ + data = 0x14; + smb136_i2c_write(chg->client, SMB_ChargeCurrent, data); + udelay(10); + + /* 4. Disable Automatic Input Current Limit */ + data = 0xe6; + smb136_i2c_write(chg->client, SMB_InputCurrentLimit, data); + udelay(10); + + /* 4. Automatic Recharge Disabed */ + data = 0x8c; + smb136_i2c_write(chg->client, SMB_ControlA, data); + udelay(10); + + /* 5. Safty timer Disabled */ + data = 0x28; + smb136_i2c_write(chg->client, SMB_ControlB, data); + udelay(10); + + /* 6. Disable USB D+/D- Detection */ + data = 0x28; + smb136_i2c_write(chg->client, SMB_OTGControl, data); + udelay(10); + + /* 7. Set Output Polarity for STAT */ + data = 0xCA; + smb136_i2c_write(chg->client, SMB_FloatVoltage, data); + udelay(10); + + /* 9. Re-load Enable */ + data = 0x4b; + smb136_i2c_write(chg->client, SMB_SafetyTimer, data); + udelay(10); + + /* Enable charge */ + gpio_set_value(chg->pdata->enable, 0); + + switch (cable_status) { + case CABLE_TYPE_TA: + /* HC mode */ + data = 0x8c; + smb136_i2c_write(chg->client, SMB_CommandA, data); + udelay(10); + + /* Set charge current to 1500mA */ + data = 0xf4; + smb136_i2c_write(chg->client, SMB_ChargeCurrent, data); + udelay(10); + break; + case CABLE_TYPE_STATION: + /* HC mode */ + data = 0x8c; + smb136_i2c_write(chg->client, SMB_CommandA, data); + udelay(10); + + /* Set charge current to 750mA */ + data = 0x54; + smb136_i2c_write(chg->client, SMB_ChargeCurrent, data); + udelay(10); + + /* Disable Automatic Input Current Limit, + * USBIN Current Limit to 700mA */ + data = 0x06; + smb136_i2c_write(chg->client, + SMB_InputCurrentLimit, data); + udelay(10); + break; + case CABLE_TYPE_USB: + default: + /* Prevent in-rush current */ + msleep(100); + + /* USBIN 500mA mode */ + data = 0x88; + smb136_i2c_write(chg->client, SMB_CommandA, data); + udelay(10); + + /* Set charge current to 500mA */ + data = 0x14; + smb136_i2c_write(chg->client, SMB_ChargeCurrent, data); + udelay(10); + break; + } + } else { + gpio_set_value(chg->pdata->enable, 1); + + /* USB 100mA Mode, USB5/1 Current Levels */ + /* Prevent in-rush current */ + data = 0x80; + smb136_i2c_write(chg->client, SMB_CommandA, data); + udelay(10); + + /* Set charge current to 100mA */ + /* Prevent in-rush current */ + data = 0x14; + smb136_i2c_write(chg->client, SMB_ChargeCurrent, data); + udelay(10); + } + + return; +} + +static int smb136_get_charging_current(void) +{ + struct smb136_chg_data *chg = smb136_chg; + u8 data = 0; + int get_current = 0; + + smb136_i2c_read(chg->client, SMB_ChargeCurrent, &data); + switch (data >> 5) { + case 0: + get_current = 500; break; + case 1: + get_current = 650; break; + case 2: + get_current = 750; break; + case 3: + get_current = 850; break; + case 4: + get_current = 950; break; + case 5: + get_current = 1100; break; + case 6: + get_current = 1300; break; + case 7: + get_current = 1500; break; + default: + get_current = 500; + break; + } + pr_debug("%s: Get charging current as %dmA.\n", __func__, get_current); + return get_current; +} + +static void smb136_set_charging_current(int set_current) +{ + struct smb136_chg_data *chg = smb136_chg; + u8 data = 0; + + if (set_current > 450) { + /* HC mode */ + data = 0x8c; + smb136_i2c_write(chg->client, SMB_CommandA, data); + udelay(10); + + /* Set charge current to 1500mA */ + data = 0xf4; + smb136_i2c_write(chg->client, SMB_ChargeCurrent, data); + udelay(10); + } else { + /* USBIN 500mA mode */ + data = 0x88; + smb136_i2c_write(chg->client, SMB_CommandA, data); + udelay(10); + + /* Set charge current to 500mA */ + data = 0x14; + smb136_i2c_write(chg->client, SMB_ChargeCurrent, data); + udelay(10); + } + pr_debug("%s: Set charging current as %dmA.\n", __func__, set_current); +} + +static int smb136_i2c_probe +(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct smb136_chg_data *chg; + int ret = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + pr_info("%s : SMB136 Charger Driver Loading\n", __func__); + + chg = kzalloc(sizeof(struct smb136_chg_data), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + chg->callbacks = kzalloc(sizeof(struct smb_charger_callbacks) + , GFP_KERNEL); + if (!chg->callbacks) { + pr_err("%s : No callbacks\n", __func__); + ret = -ENOMEM; + goto err_callbacks; + } + + chg->client = client; + if (!chg->client) { + pr_err("%s : No client\n", __func__); + ret = -EINVAL; + goto err_client; + } + + chg->pdata = client->dev.platform_data; + if (!chg->pdata) { + pr_err("%s : No platform data supplied\n", __func__); + ret = -EINVAL; + goto err_pdata; + } + + i2c_set_clientdata(client, chg); + smb136_chg = chg; + pr_info("register callback functions!\n"); + + chg->callbacks->set_charging_state = smb136_set_charging_state; + chg->callbacks->get_charging_state = smb136_get_charging_state; + chg->callbacks->set_charging_current = smb136_set_charging_current; + chg->callbacks->get_charging_current = smb136_get_charging_current; + if (chg->pdata && chg->pdata->register_callbacks) + chg->pdata->register_callbacks(chg->callbacks); + + charger_i2c_init = 1; + pr_info("Smb136 charger attach success!!!\n"); + + smb136_test_read(); + + return 0; + +err_pdata: +err_client: +err_callbacks: + kfree(chg); + return ret; +} + +static int __devexit smb136_remove(struct i2c_client *client) +{ + struct smb136_chg_data *chg = i2c_get_clientdata(client); + + if (chg->pdata && chg->pdata->unregister_callbacks) + chg->pdata->unregister_callbacks(); + + kfree(chg); + return 0; +} + +static const struct i2c_device_id smb136_id[] = { + { "smb136-charger", 0 }, + { } +}; + + +static struct i2c_driver smb136_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "smb136-charger", + }, + .id_table = smb136_id, + .probe = smb136_i2c_probe, + .remove = __devexit_p(smb136_remove), + .command = NULL, +}; + + +MODULE_DEVICE_TABLE(i2c, smb136_id); + +static int __init smb136_init(void) +{ + return i2c_add_driver(&smb136_i2c_driver); +} + +static void __exit smb136_exit(void) +{ + i2c_del_driver(&smb136_i2c_driver); +} + +module_init(smb136_init); +module_exit(smb136_exit); + +MODULE_AUTHOR("Ikkeun Kim <iks.kim@samsung.com>"); +MODULE_DESCRIPTION("smb136 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/smb136_charger_q1.c b/drivers/power/smb136_charger_q1.c new file mode 100644 index 00000000000..da545e0a179 --- /dev/null +++ b/drivers/power/smb136_charger_q1.c @@ -0,0 +1,531 @@ +/* + * smb136_charger.c + * + * Copyright (C) 2011 Samsung Electronics + * Ikkeun Kim <iks.kim@samsung.com> + * + * 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/power/smb136_charger_q1.h> +#define DEBUG + +enum cable_type_t { + CABLE_TYPE_NONE = 0, + CABLE_TYPE_USB, + CABLE_TYPE_AC, + CABLE_TYPE_MISC, +}; + +static int smb136_i2c_read(struct i2c_client *client, u8 reg, u8 *data) +{ + int ret = 0; + + if (!client) + return -ENODEV; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + return -EIO; + + *data = ret & 0xff; + return 0; +} + +static int smb136_i2c_write(struct i2c_client *client, u8 reg, u8 data) +{ + if (!client) + return -ENODEV; + + return i2c_smbus_write_byte_data(client, reg, data); +} + +static void smb136_test_read(struct i2c_client *client) +{ + struct smb136_chip *chg = i2c_get_clientdata(client); + u8 data = 0; + u32 addr = 0; + + for (addr = 0; addr < 0x0c; addr++) { + smb136_i2c_read(chg->client, addr, &data); + dev_info(&client->dev, + "SMB136 addr : 0x%02x data : 0x%02x\n", addr, data); + } + + for (addr = 0x31; addr < 0x3D; addr++) { + smb136_i2c_read(chg->client, addr, &data); + dev_info(&client->dev, + "SMB136 addr : 0x%02x data : 0x%02x\n", addr, data); + } +} + +static int smb136_get_charging_status(struct i2c_client *client) +{ + struct smb136_chip *chg = i2c_get_clientdata(client); + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 data1 = 0; + u8 data2 = 0; + + smb136_i2c_read(chg->client, 0x36, &data1); + smb136_i2c_read(chg->client, 0x39, &data2); + dev_info(&client->dev, "%s : 0x36h(0x%02x), 0x39h(0x%02x)\n", + __func__, data1, data2); + + if (data2 & 0x01) + status = POWER_SUPPLY_STATUS_CHARGING; + else { + if ((data1 & 0x08) == 0x08) { + /* if error bit check, + ignore the status of charger-ic */ + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else if ((data1 & 0xc0) == 0xc0) { + /* At least one charge cycle terminated, + Charge current < Termination Current */ + status = POWER_SUPPLY_STATUS_FULL; + } + } + + return (int)status; +} + +static int smb136_charging(struct i2c_client *client) +{ + struct smb136_chip *chg = i2c_get_clientdata(client); + u8 data = 0; + int gpio = 0; + + dev_info(&client->dev, "%s : enable(%d), cable(%d)\n", + __func__, chg->is_enable, chg->cable_type); + + if (chg->is_enable) { + switch (chg->cable_type) { + case CABLE_TYPE_AC: + /* 1. HC mode */ + data = 0x8c; + + smb136_i2c_write(chg->client, SMB_CommandA, data); + udelay(10); + + /* 2. Change USB5/1/HC Control from Pin to I2C */ + /* 2. EN pin control - active low */ + smb136_i2c_write(chg->client, SMB_PinControl, 0x8); + udelay(10); + + smb136_i2c_write(chg->client, SMB_CommandA, 0x8c); + udelay(10); + + /* 3. Set charge current to 950mA, + termination current to 150mA */ + data = 0x94; + + smb136_i2c_write(chg->client, SMB_ChargeCurrent, data); + udelay(10); + break; + case CABLE_TYPE_USB: + default: + /* 1. USBIN 500mA mode */ + data = 0x88; + + smb136_i2c_write(chg->client, SMB_CommandA, data); + udelay(10); + + /* 2. Change USB5/1/HC Control from Pin to I2C */ + /* 2. EN pin control - active low */ + smb136_i2c_write(chg->client, SMB_PinControl, 0x8); + udelay(10); + + smb136_i2c_write(chg->client, SMB_CommandA, 0x88); + udelay(10); + + /* 3. Set charge current to 500mA, + termination current to 150mA */ + data = 0x14; + + smb136_i2c_write(chg->client, SMB_ChargeCurrent, data); + udelay(10); + break; + } + + /* 3. Enable Automatic Input Current Limit to 1000mA + (threshold 4.25V) */ + data = 0x60; + smb136_i2c_write(chg->client, SMB_InputCurrentLimit, data); + udelay(10); + + /* 4. Automatic Recharge Disabed */ + data = 0x8c; + smb136_i2c_write(chg->client, SMB_ControlA, data); + udelay(10); + + /* 5. Safety timer Disabled */ + data = 0x28; + smb136_i2c_write(chg->client, SMB_ControlB, data); + udelay(10); + + /* 6. Disable USB D+/D- Detection */ + data = 0x28; + smb136_i2c_write(chg->client, SMB_OTGControl, data); + udelay(10); + + /* 7. Set Output Polarity for STAT */ + /* 7. Set float voltage to 4.2V */ + data = 0x4a; + smb136_i2c_write(chg->client, SMB_FloatVoltage, data); + udelay(10); + + /* 8. Re-load Enable */ + data = 0x4b; + smb136_i2c_write(chg->client, SMB_SafetyTimer, data); + udelay(10); + } else { + /* do nothing... */ + } + + /* CHG_EN pin control - active low */ + gpio = gpio_request(chg->pdata->gpio_chg_en, "CHG_EN"); + if (!gpio) { + gpio_direction_output(chg->pdata->gpio_chg_en, + !(chg->is_enable)); + dev_info(&client->dev, "gpio(CHG_EN)is %d\n", + gpio_get_value(chg->pdata->gpio_chg_en)); + gpio_free(chg->pdata->gpio_chg_en); + } else + dev_err(&client->dev, + "faile to request gpio(CHG_EN)\n"); + + /* smb136_test_read(client); */ + + return 0; +} + +static int smb136_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb136_chip *chip = container_of(psy, + struct smb136_chip, + charger); + u8 data; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = smb136_get_charging_status(chip->client); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = chip->cable_type; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chip->is_enable; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + smb136_i2c_read(chip->client, SMB_ChargeCurrent, &data); + switch (data >> 5) { + case 0: + val->intval = 500; + break; + case 1: + val->intval = 650; + break; + case 2: + val->intval = 750; + break; + case 3: + val->intval = 850; + break; + case 4: + val->intval = 950; + break; + case 5: + val->intval = 1100; + break; + case 6: + val->intval = 1300; + break; + case 7: + val->intval = 1500; + break; + } + break; + default: + return -EINVAL; + } + + dev_info(&chip->client->dev, "%s: smb136_get_property (%d,%d)\n", + __func__, psp, val->intval); + + return 0; +} + +static int smb136_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb136_chip *chip = container_of(psy, + struct smb136_chip, + charger); + + dev_info(&chip->client->dev, "%s: smb136_set_property (%d,%d)\n", + __func__, psp, val->intval); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + chip->is_enable = (val->intval == POWER_SUPPLY_STATUS_CHARGING); + smb136_charging(chip->client); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + switch (val->intval) { + case CABLE_TYPE_USB: + case CABLE_TYPE_AC: + chip->cable_type = val->intval; + break; + default: + dev_err(&chip->client->dev, "cable type NOT supported!\n"); + chip->cable_type = CABLE_TYPE_NONE; + break; + } + break; + case POWER_SUPPLY_PROP_ONLINE: + chip->is_enable = (bool)val->intval; + smb136_charging(chip->client); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (val->intval <= 450) + chip->cable_type = CABLE_TYPE_USB; /* USB */ + else + chip->cable_type = CABLE_TYPE_AC; /* TA */ + break; + default: + return -EINVAL; + } + return 0; +} + +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1) +static bool is_ovp_status; +#endif + +static irqreturn_t smb136_irq_thread(int irq, void *data) +{ + struct smb136_chip *chip = data; +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1) + int ret = 0; + u8 data1 = 0; +#endif + + dev_info(&chip->client->dev, "%s\n", __func__); + + smb136_test_read(chip->client); +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1) + smb136_i2c_read(chip->client, 0x33, &data1); + + if (data1 & 0x02) { + if (is_ovp_status == false) { + is_ovp_status = true; + if (chip->pdata->ovp_cb) + ret = chip->pdata->ovp_cb(true); + dev_info(&chip->client->dev, "$s OVP!!\n"); + } + } else { + if (is_ovp_status == true) { + is_ovp_status = false; + if (chip->pdata->ovp_cb) + ret = chip->pdata->ovp_cb(false); + dev_info(&chip->client->dev, + "$s ovp status released!!\n"); + } + } +#endif + + return IRQ_HANDLED; +} + +static int smb136_irq_init(struct smb136_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + + if (client->irq) { + ret = request_threaded_irq(client->irq, NULL, +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1) + smb136_irq_thread, IRQ_TYPE_EDGE_BOTH, +#else + smb136_irq_thread, IRQ_TYPE_EDGE_FALLING, +#endif + "SMB136 charger", chip); + if (ret) { + dev_err(&client->dev, "failed to reqeust IRQ\n"); + return ret; + } + + ret = enable_irq_wake(client->irq); + if (ret < 0) + dev_err(&client->dev, + "failed to enable wakeup src %d\n", ret); + } + + return 0; +} + +static int smb136_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct smb136_chip *chip; + int ret = 0; + int gpio = 0; + u8 data; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + if (smb136_i2c_read(client, 0x36, &data) < 0) /* check HW */ + return -EIO; + + dev_info(&client->dev, + "%s : SMB136 Charger Driver Loading\n", __func__); + + chip = kzalloc(sizeof(struct smb136_chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, chip); + + if (!chip->pdata) { + dev_err(&client->dev, + "%s : No platform data supplied\n", __func__); + ret = -EINVAL; + goto err_pdata; + } + + if (chip->pdata->set_charger_name) + chip->pdata->set_charger_name(); + + chip->is_enable = false; + chip->cable_type = CABLE_TYPE_NONE; + + chip->charger.name = "smb136-charger"; + chip->charger.type = POWER_SUPPLY_TYPE_BATTERY; + chip->charger.get_property = smb136_get_property; + chip->charger.set_property = smb136_set_property; + chip->charger.properties = smb136_charger_props; + chip->charger.num_properties = ARRAY_SIZE(smb136_charger_props); + + ret = power_supply_register(&client->dev, &chip->charger); + if (ret) { + dev_err(&client->dev, + "failed: power supply register\n"); + kfree(chip); + return ret; + } + + /* CHG_EN pin control - active low */ + if (chip->pdata->gpio_chg_en) { + s3c_gpio_cfgpin(chip->pdata->gpio_chg_en, S3C_GPIO_OUTPUT); + s3c_gpio_setpull(chip->pdata->gpio_chg_en, S3C_GPIO_PULL_NONE); + + gpio = gpio_request(chip->pdata->gpio_chg_en, "CHG_EN"); + if (!gpio) { + gpio_direction_output(chip->pdata->gpio_chg_en, + GPIO_LEVEL_HIGH); + gpio_free(chip->pdata->gpio_chg_en); + } else + dev_err(&client->dev, + "faile to request gpio(CHG_EN)\n"); + } + + if (chip->pdata->gpio_otg_en) { + s3c_gpio_cfgpin(chip->pdata->gpio_otg_en, S3C_GPIO_OUTPUT); + s3c_gpio_setpull(chip->pdata->gpio_otg_en, S3C_GPIO_PULL_NONE); + + gpio = gpio_request(chip->pdata->gpio_otg_en, "OTG_EN"); + if (!gpio) { + gpio_direction_output(chip->pdata->gpio_otg_en, + GPIO_LEVEL_LOW); + gpio_free(chip->pdata->gpio_otg_en); + } else + dev_err(&client->dev, + "faile to request gpio(OTG_EN)\n"); + } + + if (chip->pdata->gpio_ta_nconnected) { + s3c_gpio_cfgpin(chip->pdata->gpio_ta_nconnected, + S3C_GPIO_INPUT); + s3c_gpio_setpull(chip->pdata->gpio_ta_nconnected, + S3C_GPIO_PULL_NONE); + } + + if (chip->pdata->gpio_chg_ing) { +#if 1 +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1) + s3c_gpio_cfgpin(chip->pdata->gpio_chg_ing, S3C_GPIO_SFN(0xf)); +#endif + client->irq = gpio_to_irq(chip->pdata->gpio_chg_ing); + ret = smb136_irq_init(chip); + if (ret) + goto err_pdata; +#else + s3c_gpio_cfgpin(chip->pdata->gpio_chg_ing, S3C_GPIO_INPUT); + s3c_gpio_setpull(chip->pdata->gpio_chg_ing, S3C_GPIO_PULL_NONE); +#endif + } + +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1) + is_ovp_status = false; +#endif + + smb136_test_read(client); + + return 0; + +err_pdata: + kfree(chip); + return ret; +} + +static int __devexit smb136_remove(struct i2c_client *client) +{ + struct smb136_chip *chip = i2c_get_clientdata(client); + + kfree(chip); + return 0; +} + +static const struct i2c_device_id smb136_id[] = { + {"smb136-charger", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, smb136_id); + +static struct i2c_driver smb136_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "smb136-charger", + }, + .probe = smb136_probe, + .remove = __devexit_p(smb136_remove), + .command = NULL, + .id_table = smb136_id, +}; + +static int __init smb136_init(void) +{ + return i2c_add_driver(&smb136_i2c_driver); +} + +static void __exit smb136_exit(void) +{ + i2c_del_driver(&smb136_i2c_driver); +} + +module_init(smb136_init); +module_exit(smb136_exit); + +MODULE_AUTHOR("Ikkeun Kim <iks.kim@samsung.com>"); +MODULE_DESCRIPTION("smb136 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/smb328_charger.c b/drivers/power/smb328_charger.c new file mode 100644 index 00000000000..93fd8cbbfa2 --- /dev/null +++ b/drivers/power/smb328_charger.c @@ -0,0 +1,1046 @@ +/* + * smb328_charger.c + * + * Copyright (C) 2011 Samsung Electronics + * Ikkeun Kim <iks.kim@samsung.com> + * + * 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/power/smb328_charger.h> +#define DEBUG + +enum cable_type_t { + CABLE_TYPE_NONE = 0, + CABLE_TYPE_USB, + CABLE_TYPE_AC, + CABLE_TYPE_MISC, + CABLE_TYPE_OTG, +}; + +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB328_CHARGER) +static bool is_ovp_status; +#endif + +static int smb328_i2c_read(struct i2c_client *client, u8 reg, u8 *data) +{ + int ret = 0; + + if (!client) + return -ENODEV; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + return -EIO; + + *data = ret & 0xff; + return *data; +} + +static int smb328_i2c_write(struct i2c_client *client, u8 reg, u8 data) +{ + if (!client) + return -ENODEV; + + return i2c_smbus_write_byte_data(client, reg, data); +} + +static void smb328_test_read(struct i2c_client *client) +{ + struct smb328_chip *chg = i2c_get_clientdata(client); + u8 data = 0; + u32 addr = 0; + + for (addr = 0; addr < 0x0c; addr++) { + smb328_i2c_read(chg->client, addr, &data); + dev_info(&client->dev, + "smb328 addr : 0x%02x data : 0x%02x\n", addr, data); + } + + for (addr = 0x30; addr < 0x3D; addr++) { + smb328_i2c_read(chg->client, addr, &data); + dev_info(&client->dev, + "smb328 addr : 0x%02x data : 0x%02x\n", addr, data); + } +} + +static void smb328a_charger_function_conrol(struct i2c_client *client); + +static int smb328_get_charging_status(struct i2c_client *client) +{ + struct smb328_chip *chg = i2c_get_clientdata(client); + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 data_a = 0; + u8 data_b = 0; + u8 data_c = 0; + + smb328_i2c_read(chg->client, + SMB328A_BATTERY_CHARGING_STATUS_A, &data_a); + dev_info(&client->dev, "%s : charging status A(0x%02x)\n", + __func__, data_a); + smb328_i2c_read(chg->client, + SMB328A_BATTERY_CHARGING_STATUS_B, &data_b); + dev_info(&client->dev, "%s : charging status B(0x%02x)\n", + __func__, data_b); + smb328_i2c_read(chg->client, + SMB328A_BATTERY_CHARGING_STATUS_C, &data_c); + dev_info(&client->dev, "%s : charging status C(0x%02x)\n", + __func__, data_c); + + /* check for safety timer in USB charging */ + /* If safety timer is activated in USB charging, reset charger */ +#if 1 + /* write 0xAA in register 0x30 to reset watchdog timer, */ + /* it can replace this work-around */ + if (chg->is_enable && chg->cable_type == CABLE_TYPE_USB) { + if ((data_c & 0x30) == 0x20) { /* safety timer activated */ + /* reset charger */ + dev_info(&client->dev, + "%s : Reset charger, safety timer is activated!\n", + __func__); + + chg->is_enable = false; + smb328a_charger_function_conrol(chg->client); + + chg->is_enable = true; + smb328a_charger_function_conrol(chg->client); + } + } +#endif + + /* At least one charge cycle terminated, */ + /* Charge current < Termination Current */ + if ((data_c & 0xc0) == 0xc0) { + /* top-off by full charging */ + status = POWER_SUPPLY_STATUS_FULL; + goto charging_status_end; + } + + /* Is enabled ? */ + if (data_c & 0x01) { + /* check for 0x30 : 'safety timer' (0b01 or 0b10) or */ + /* 'waiting to begin charging' (0b11) */ + /* check for 0x06 : no charging (0b00) */ + if ((data_c & 0x30) || !(data_c & 0x06)) { + /* not charging */ + 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 smb328_get_charging_health(struct i2c_client *client) +{ + struct smb328_chip *chg = i2c_get_clientdata(client); + int health = POWER_SUPPLY_HEALTH_GOOD; + u8 data_a = 0; + u8 data_b = 0; + u8 data_c = 0; + + smb328_i2c_read(chg->client, + SMB328A_BATTERY_CHARGING_STATUS_A, &data_a); + dev_info(&client->dev, "%s : charging status A(0x%02x)\n", + __func__, data_a); + smb328_i2c_read(chg->client, + SMB328A_BATTERY_CHARGING_STATUS_B, &data_b); + dev_info(&client->dev, "%s : charging status B(0x%02x)\n", + __func__, data_b); + smb328_i2c_read(chg->client, + SMB328A_BATTERY_CHARGING_STATUS_C, &data_c); + dev_info(&client->dev, "%s : charging status C(0x%02x)\n", + __func__, data_c); + + /* Is enabled ? */ + if (data_c & 0x01) { + if (!(data_a & 0x02)) /* Input current is NOT OK */ + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } + +#if defined(CONFIG_MACH_Q1_CHN) + { + u8 data_chn; + smb328_i2c_read(chg->client, 0x37, &data_chn); + dev_info(&client->dev, + "%s : charging interrupt status C(0x%02x)\n", + __func__, data_c); + + if (data_chn & 0x04) + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + + if (health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) + is_ovp_status = true; + else + is_ovp_status = false; + } +#endif + return (int)health; +} + +#if defined(CONFIG_MACH_Q1_CHN) +static int smb328_is_ovp_status(struct i2c_client *client) +{ + struct smb328_chip *chg = i2c_get_clientdata(client); + int status = POWER_SUPPLY_HEALTH_UNKNOWN; + u8 data = 0; + + smb328_i2c_read(chg->client, 0x37, &data); + dev_info(&client->dev, "%s : 0x37h(0x%02x)\n", + __func__, data); + + if (data & 0x04) { + is_ovp_status = true; + status = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else { + is_ovp_status = false; + status = POWER_SUPPLY_HEALTH_GOOD; + } + + return (int)status; +} +#endif + +static void smb328a_allow_volatile_writes(struct i2c_client *client) +{ + int val, reg; + u8 data; + + reg = SMB328A_COMMAND; + val = smb328_i2c_read(client, reg, &data); + if ((val >= 0) && !(val & 0x80)) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + data |= (0x1 << 7); + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)data; + pr_info("%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } +} + +static void smb328a_charger_function_conrol(struct i2c_client *client) +{ + struct smb328_chip *chip = i2c_get_clientdata(client); + int val, reg; + u8 data, set_data; + + if (chip->is_otg) { + dev_info(&client->dev, + "%s : OTG is activated. Ignore command (type:%d, enable:%s)\n", + __func__, chip->cable_type, + chip->is_enable ? "true" : "false"); + return; + } + + smb328a_allow_volatile_writes(client); + + if (!chip->is_enable) { + reg = SMB328A_FUNCTION_CONTROL_B; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + if (data != 0x0) { + data = 0x0; + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + reg = SMB328A_COMMAND; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + data = 0x98; /* turn off charger */ + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } else { + /* reset watchdog timer if it occured */ + reg = SMB328A_CLEAR_IRQ; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + data = 0xaa; + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + + reg = SMB328A_INPUT_AND_CHARGE_CURRENTS; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + if (chip->cable_type == CABLE_TYPE_AC) { + /* fast 1000mA, termination 200mA */ + set_data = 0xb7; + } else if (chip->cable_type == CABLE_TYPE_MISC) { + /* fast 700mA, termination 200mA */ + set_data = 0x57; + } else { + /* fast 500mA, termination 200mA */ + set_data = 0x17; + } + if (data != set_data) { + /* this can be changed with top-off setting */ + data = set_data; + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + reg = SMB328A_CURRENT_TERMINATION; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + if (chip->cable_type == CABLE_TYPE_AC) { + /* input 1A, threshold 4.25V, AICL enable */ + set_data = 0xb0; + } else if (chip->cable_type == CABLE_TYPE_MISC) { + /* input 700mA, threshold 4.25V, AICL enable */ + set_data = 0x50; + } else { + /* input 450mA, threshold 4.25V, AICL disable */ + set_data = 0x14; +#if defined(CONFIG_MACH_Q1_CHN) + /* turn off pre-bias for ovp */ + set_data &= ~(0x10); +#endif + } + if (data != set_data) { + data = set_data; + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + reg = SMB328A_FLOAT_VOLTAGE; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + if (data != 0xca) { + data = 0xca; /* 4.2V float voltage */ + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + reg = SMB328A_FUNCTION_CONTROL_A1; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); +#if 1 + if (data != 0xda) { + data = 0xda; /* top-off by ADC */ +#else + if (data != 0x9a) { + data = 0x9a; /* top-off by charger */ +#endif + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + reg = SMB328A_FUNCTION_CONTROL_A2; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + /* 0x4c -> 0x4e (watchdog timer enabled - SUMMIT) */ + if (data != 0x4e) { + data = 0x4e; + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + reg = SMB328A_FUNCTION_CONTROL_B; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + if (data != 0x0) { +#if defined(CONFIG_MACH_Q1_CHN) + data = 0x80; +#else + data = 0x0; +#endif + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + reg = SMB328A_OTG_PWR_AND_LDO_CONTROL; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + set_data = 0xf5; + if (chip->cable_type == CABLE_TYPE_AC) + set_data = 0xf5; + else if (chip->cable_type == CABLE_TYPE_MISC) + set_data = 0xf5; + else + set_data = 0xcd; + if (data != set_data) { + data = set_data; + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + reg = SMB328A_VARIOUS_CONTROL_FUNCTION_A; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + if (data != 0xf6) { + /* this can be changed with top-off setting */ + data = 0xf6; + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + reg = SMB328A_CELL_TEMPERATURE_MONITOR; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + if (data != 0x0) { + data = 0x0; + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + reg = SMB328A_INTERRUPT_SIGNAL_SELECTION; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + if (data != 0x0) { + data = 0x0; + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + reg = SMB328A_COMMAND; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + /* turn on charger */ + if (chip->cable_type == CABLE_TYPE_AC) + data = 0x8c; + else if (chip->cable_type == CABLE_TYPE_MISC) + data = 0x88; + else + data = 0x88; /* USB */ + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } +} + +static void smb328a_charger_otg_conrol(struct i2c_client *client) +{ + struct smb328_chip *chip = i2c_get_clientdata(client); + int val, reg; + u8 data; + + smb328a_allow_volatile_writes(client); + + if (chip->is_otg) { + reg = SMB328A_FUNCTION_CONTROL_B; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + if (data != 0x0) { + data = 0x0; + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + /* delay for reset of charger */ + mdelay(150); + + reg = SMB328A_OTG_PWR_AND_LDO_CONTROL; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + data = 0xcd; /* OTG 350mA */ + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + + reg = SMB328A_COMMAND; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + data = 0x9a; /* turn on OTG */ + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } else { + reg = SMB328A_FUNCTION_CONTROL_B; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + if (data != 0x0) { + data = 0x0c; /* turn off charger */ + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } + + /* delay for reset of charger */ + mdelay(150); + + reg = SMB328A_COMMAND; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", + __func__, reg, data); + data = 0x98; /* turn off OTG */ + if (smb328_i2c_write(client, reg, data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8)val; + dev_info(&client->dev, + "%s : => reg (0x%x) = 0x%x\n", + __func__, reg, data); + } + } + } +} + +static int smb328_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb328_chip *chip = container_of(psy, + struct smb328_chip, + charger); + u8 data; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = smb328_get_charging_status(chip->client); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = chip->cable_type; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = smb328_get_charging_health(chip->client); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chip->is_enable; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (chip->is_enable) { + smb328_i2c_read(chip->client, + SMB328A_INPUT_AND_CHARGE_CURRENTS, &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; +#if defined(CONFIG_MACH_Q1_CHN) + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = smb328_is_ovp_status(chip->client); + break; +#endif + default: + return -EINVAL; + } + + dev_info(&chip->client->dev, "%s: smb328_get_property (%d,%d)\n", + __func__, psp, val->intval); + + return 0; +} + +static int smb328_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb328_chip *chip = container_of(psy, + struct smb328_chip, + charger); + + dev_info(&chip->client->dev, "%s: smb328_set_property (%d,%d)\n", + __func__, psp, val->intval); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: +#if defined(CONFIG_MACH_Q1_CHN) + is_ovp_status = false; +#endif + chip->is_enable = (val->intval == POWER_SUPPLY_STATUS_CHARGING); + smb328a_charger_function_conrol(chip->client); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + /* only for OTG support */ + chip->is_otg = val->intval; + smb328a_charger_otg_conrol(chip->client); + smb328_test_read(chip->client); + break; + case POWER_SUPPLY_PROP_HEALTH: + break; + case POWER_SUPPLY_PROP_ONLINE: + chip->is_enable = (bool)val->intval; + smb328a_charger_function_conrol(chip->client); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (val->intval <= 450) + chip->cable_type = CABLE_TYPE_USB; + else + chip->cable_type = CABLE_TYPE_AC; + break; + default: + return -EINVAL; + } + return 0; +} + +static irqreturn_t smb328_irq_thread(int irq, void *data) +{ + struct smb328_chip *chip = data; + int ret = 0; +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB328_CHARGER) + u8 data1 = 0; +#endif + + dev_info(&chip->client->dev, "%s: chg_ing IRQ occurred!\n", __func__); + +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB328_CHARGER) + smb328_i2c_read(chip->client, 0x37, &data1); + + if (data1 & 0x04) { + /* if usbin(usb vbus) is in over-voltage status. */ + if (is_ovp_status == false) { + is_ovp_status = true; + if (chip->pdata->ovp_cb) + ret = chip->pdata->ovp_cb(true); + dev_info(&chip->client->dev, "$s OVP!!\n"); + } + } else { + if (is_ovp_status == true) { + is_ovp_status = false; + if (chip->pdata->ovp_cb) + ret = chip->pdata->ovp_cb(false); + dev_info(&chip->client->dev, + "$s ovp status released!!\n"); + } + } +#else + if (chip->pdata->topoff_cb) + ret = chip->pdata->topoff_cb(); + + if (ret) { + dev_err(&chip->client->dev, + "%s: error from topoff_cb(%d)\n", + __func__, ret); + return IRQ_HANDLED; + } +#endif + return IRQ_HANDLED; +} + +static int smb328_irq_init(struct smb328_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + + if (client->irq) { + ret = request_threaded_irq(client->irq, NULL, + smb328_irq_thread, +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB328_CHARGER) + IRQ_TYPE_EDGE_BOTH, +#else + IRQF_TRIGGER_RISING | IRQF_ONESHOT, +#endif + "SMB328 charger", chip); + if (ret) { + dev_err(&client->dev, "failed to reqeust IRQ\n"); + return ret; + } + + ret = enable_irq_wake(client->irq); + if (ret < 0) + dev_err(&client->dev, + "failed to enable wakeup src %d\n", ret); + } + + return 0; +} + +static int smb328_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct smb328_chip *chip; + int ret = 0; + int gpio = 0; + u8 data; + int i; + + i = 10; + while (1) { + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + goto I2CERROR; + + if (smb328_i2c_read(client, 0x36, &data) >= 0) /* check HW */ + break; + +I2CERROR: + if (!i--) + return -EIO; + msleep(300); + } + + dev_info(&client->dev, + "%s : SMB328 Charger Driver Loading\n", __func__); + + chip = kzalloc(sizeof(struct smb328_chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, chip); + + if (!chip->pdata) { + dev_err(&client->dev, + "%s : No platform data supplied\n", __func__); + ret = -EINVAL; + goto err_pdata; + } + + if (chip->pdata->set_charger_name) + chip->pdata->set_charger_name(); + + chip->is_otg = false; + chip->is_enable = false; + chip->cable_type = CABLE_TYPE_NONE; + + chip->charger.name = "smb328-charger"; + chip->charger.type = POWER_SUPPLY_TYPE_BATTERY; + chip->charger.get_property = smb328_get_property; + chip->charger.set_property = smb328_set_property; + chip->charger.properties = smb328_charger_props; + chip->charger.num_properties = ARRAY_SIZE(smb328_charger_props); + + ret = power_supply_register(&client->dev, &chip->charger); + if (ret) { + dev_err(&client->dev, "failed: power supply register\n"); + kfree(chip); + return ret; + } + + /* CHG_EN pin control - active low */ + if (chip->pdata->gpio_chg_en) { + s3c_gpio_cfgpin(chip->pdata->gpio_chg_en, S3C_GPIO_OUTPUT); + s3c_gpio_setpull(chip->pdata->gpio_chg_en, S3C_GPIO_PULL_NONE); + + gpio = gpio_request(chip->pdata->gpio_chg_en, "CHG_EN"); + if (!gpio) { + gpio_direction_output(chip->pdata->gpio_chg_en, + GPIO_LEVEL_HIGH); + gpio_free(chip->pdata->gpio_chg_en); + } else + dev_err(&client->dev, + "faile to request gpio(CHG_EN)\n"); + } + + if (chip->pdata->gpio_otg_en) { + s3c_gpio_cfgpin(chip->pdata->gpio_otg_en, S3C_GPIO_OUTPUT); + s3c_gpio_setpull(chip->pdata->gpio_otg_en, S3C_GPIO_PULL_NONE); + + gpio = gpio_request(chip->pdata->gpio_otg_en, "OTG_EN"); + if (!gpio) { + gpio_direction_output(chip->pdata->gpio_otg_en, + GPIO_LEVEL_LOW); + gpio_free(chip->pdata->gpio_otg_en); + } else + dev_err(&client->dev, + "faile to request gpio(OTG_EN)\n"); + } + + if (chip->pdata->gpio_ta_nconnected) { + s3c_gpio_cfgpin(chip->pdata->gpio_ta_nconnected, + S3C_GPIO_INPUT); + s3c_gpio_setpull(chip->pdata->gpio_ta_nconnected, + S3C_GPIO_PULL_NONE); + } + + if (chip->pdata->gpio_chg_ing) { +#if defined(CONFIG_MACH_Q1_CHN) +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB328_CHARGER) + /* set external interrupt */ + s3c_gpio_cfgpin(chip->pdata->gpio_chg_ing, + S3C_GPIO_SFN(0xf)); +#endif + client->irq = gpio_to_irq(chip->pdata->gpio_chg_ing); + ret = smb328_irq_init(chip); + if (ret) + goto err_pdata; +#else + s3c_gpio_cfgpin(chip->pdata->gpio_chg_ing, + S3C_GPIO_INPUT); + s3c_gpio_setpull(chip->pdata->gpio_chg_ing, + S3C_GPIO_PULL_NONE); +#endif + } + +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB328_CHARGER) + is_ovp_status = false; +#endif + + smb328_test_read(client); + + return 0; + +err_pdata: + kfree(chip); + return ret; +} + +static int __devexit smb328_remove(struct i2c_client *client) +{ + struct smb328_chip *chip = i2c_get_clientdata(client); + + kfree(chip); + return 0; +} + +static const struct i2c_device_id smb328_id[] = { + {"smb328-charger", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, smb328_id); + +static struct i2c_driver smb328_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "smb328-charger", + }, + .probe = smb328_probe, + .remove = __devexit_p(smb328_remove), + .command = NULL, + .id_table = smb328_id, +}; + +static int __init smb328_init(void) +{ + return i2c_add_driver(&smb328_i2c_driver); +} + +static void __exit smb328_exit(void) +{ + i2c_del_driver(&smb328_i2c_driver); +} + +module_init(smb328_init); +module_exit(smb328_exit); + +MODULE_AUTHOR("Ikkeun Kim <iks.kim@samsung.com>"); +MODULE_DESCRIPTION("smb328 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/smb347_charger.c b/drivers/power/smb347_charger.c new file mode 100755 index 00000000000..a33977665e4 --- /dev/null +++ b/drivers/power/smb347_charger.c @@ -0,0 +1,497 @@ +/* + * smb347_charger.c + * + * Copyright (C) 2011 Samsung Electronics + * SangYoung Son <hello.son@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/power_supply.h> +#include <linux/power/sec_battery_px.h> +#include <linux/power/smb347_charger.h> +#include <linux/mfd/max8997.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <plat/gpio-cfg.h> + +/* Slave address */ +#define SMB347_SLAVE_ADDR 0x0C + +/* SMB347 Registers. */ +#define SMB347_CHARGE_CURRENT 0X00 +#define SMB347_INPUT_CURRENTLIMIT 0X01 +#define SMB347_VARIOUS_FUNCTIONS 0X02 +#define SMB347_FLOAT_VOLTAGE 0X03 +#define SMB347_CHARGE_CONTROL 0X04 +#define SMB347_STAT_TIMERS_CONTROL 0x05 +#define SMB347_PIN_ENABLE_CONTROL 0x06 +#define SMB347_THERM_CONTROL_A 0x07 +#define SMB347_SYSOK_USB30_SELECTION 0x08 +#define SMB347_OTHER_CONTROL_A 0x09 +#define SMB347_OTG_TLIM_THERM_CONTROL 0x0A +#define SMB347_LIMIT_CELL_TEMPERATURE_MONITOR 0x0B +#define SMB347_FAULT_INTERRUPT 0x0C +#define SMB347_STATUS_INTERRUPT 0x0D +#define SMB347_I2C_BUS_SLAVE_ADDR 0x0E + +#define SMB347_COMMAND_A 0x30 +#define SMB347_COMMAND_B 0x31 +#define SMB347_COMMAND_C 0x33 +#define SMB347_INTERRUPT_STATUS_A 0x35 +#define SMB347_INTERRUPT_STATUS_B 0x36 +#define SMB347_INTERRUPT_STATUS_C 0x37 +#define SMB347_INTERRUPT_STATUS_D 0x38 +#define SMB347_INTERRUPT_STATUS_E 0x39 +#define SMB347_INTERRUPT_STATUS_F 0x3A +#define SMB347_STATUS_A 0x3B +#define SMB347_STATUS_B 0x3C +#define SMB347_STATUS_C 0x3D +#define SMB347_STATUS_D 0x3E +#define SMB347_STATUS_E 0x3F + +/* Status register C */ +#define SMB347_CHARGING_ENABLE (1 << 0) +#define SMB347_CHARGING_STATUS (1 << 5) +#define SMB347_CHARGER_ERROR (1 << 6) + +struct smb347_chg_data { + struct i2c_client *client; + struct smb_charger_data *pdata; + struct smb_charger_callbacks *callbacks; +}; + +static struct smb347_chg_data *smb347_chg; + +static bool smb347_check_powersource(struct smb347_chg_data *chg) +{ +#if defined(CONFIG_MACH_P4NOTE) + /* p4 note pq has no problem for charger power */ + return true; +#endif + + /* Power source needs for only P4C H/W rev0.2 */ + if (system_rev != 2) + return true; + + /* V_BUS detect by TA_nConnected */ + if (!gpio_get_value(chg->pdata->ta_nconnected)) { + pr_err("smb347 power source is not detected\n"); + return false; + } + + return true; +} + +static int smb347_i2c_read(struct i2c_client *client, u8 reg, u8 *data) +{ + struct smb347_chg_data *chg = smb347_chg; + int ret = 0; + + /* Only for P4C rev0.2, Check vbus for opeartion charger */ + if (!smb347_check_powersource(chg)) + return -EINVAL; + + if (!client) { + pr_err("smb347 i2c client error(addr : 0x%02x)\n", reg); + return -ENODEV; + } + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) { + pr_err("smb347 i2c read error(addr : 0x%02x)\n", reg); + return -EIO; + } + + *data = ret & 0xff; + return 0; +} + +static int smb347_i2c_write(struct i2c_client *client, u8 reg, u8 data) +{ + struct smb347_chg_data *chg = smb347_chg; + int ret = 0; + + /* Only for P4C rev0.2, Check vbus for opeartion charger */ + if (!smb347_check_powersource(chg)) + return -EINVAL; + + if (!client) { + pr_err("smb347 i2c client error(addr:0x%02x data:0x%02x)\n", + reg, data); + return -ENODEV; + } + + ret = i2c_smbus_write_byte_data(client, reg, data); + if (ret < 0) { + pr_err("smb347 i2c write error(addr:0x%02x data:0x%02x)\n", + reg, data); + return -EIO; + } + + udelay(10); + return ret; +} + +static void smb347_test_read(void) +{ + struct smb347_chg_data *chg = smb347_chg; + u8 data = 0; + u32 addr = 0; + pr_info("%s\n", __func__); + + /* Only for P4C rev0.2, Check vbus for opeartion charger */ + if (!smb347_check_powersource(chg)) + return; + + for (addr = 0; addr <= 0x0E; addr++) { + smb347_i2c_read(chg->client, addr, &data); + pr_info("smb347 addr : 0x%02x data : 0x%02x\n", addr, data); + } + + for (addr = 0x30; addr <= 0x3F; addr++) { + smb347_i2c_read(chg->client, addr, &data); + pr_info("smb347 addr : 0x%02x data : 0x%02x\n", addr, data); + } +} + +static void smb347_enable_charging(struct smb347_chg_data *chg) +{ + pr_info("%s\n", __func__); + smb347_i2c_write(chg->client, SMB347_COMMAND_A, 0x82); +} + +static void smb347_disable_charging(struct smb347_chg_data *chg) +{ + pr_info("%s\n", __func__); + smb347_i2c_write(chg->client, SMB347_COMMAND_A, 0x80); +} + +static void smb347_charger_init(struct smb347_chg_data *chg) +{ + pr_info("%s\n", __func__); + + /* Only for P4C rev0.2, Check vbus for opeartion charger */ + if (!smb347_check_powersource(chg)) + return; + + /* Set GPIO_TA_EN as HIGH, charging disable */ + smb347_disable_charging(chg); + mdelay(100); + + /* Allow volatile writes to CONFIG registers */ + smb347_i2c_write(chg->client, SMB347_COMMAND_A, 0x80); + + /* Command B : USB1 mode, USB mode */ + smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x00); + + /* Charge curr : Fast-chg 2200mA */ + /* Pre-charge curr 250mA, Term curr 250mA */ + smb347_i2c_write(chg->client, SMB347_CHARGE_CURRENT, 0xDD); + + /* Pin enable control : Charger enable control EN Pin - I2C */ + /* : USB5/1/HC or USB9/1.5/HC Control - Register Control */ + /* : USB5/1/HC Input state - Tri-state Input */ + smb347_i2c_write(chg->client, SMB347_PIN_ENABLE_CONTROL, 0x00); + + /* Input current limit : DCIN 1800mA, USBIN HC 1800mA */ + smb347_i2c_write(chg->client, SMB347_INPUT_CURRENTLIMIT, 0x66); + + /* Various func. : USBIN primary input, VCHG func. enable */ + smb347_i2c_write(chg->client, SMB347_VARIOUS_FUNCTIONS, 0xA7); + + /* Float voltage : 4.2V */ + smb347_i2c_write(chg->client, SMB347_FLOAT_VOLTAGE, 0x63); + + /* Charge control : Auto recharge disable, APSD disable */ + smb347_i2c_write(chg->client, SMB347_CHARGE_CONTROL, 0x80); + + /* STAT, Timer control : STAT active low, Complete time out 1527min. */ + smb347_i2c_write(chg->client, SMB347_STAT_TIMERS_CONTROL, 0x1A); + + /* Therm control : Therm monitor disable */ + smb347_i2c_write(chg->client, SMB347_THERM_CONTROL_A, 0xBF); + + /* Other control */ + smb347_i2c_write(chg->client, SMB347_OTHER_CONTROL_A, 0x0D); + + /* OTG tlim therm control */ + smb347_i2c_write(chg->client, SMB347_OTG_TLIM_THERM_CONTROL, 0x3F); + + /* Limit cell temperature */ + smb347_i2c_write(chg->client, SMB347_LIMIT_CELL_TEMPERATURE_MONITOR, + 0x01); + + /* Fault interrupt : Clear */ + smb347_i2c_write(chg->client, SMB347_FAULT_INTERRUPT, 0x00); + + /* STATUS ingerrupt : Clear */ + smb347_i2c_write(chg->client, SMB347_STATUS_INTERRUPT, 0x00); +} + +static int smb347_get_charging_state(void) +{ + struct smb347_chg_data *chg = smb347_chg; + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 data = 0; + + smb347_i2c_read(chg->client, SMB347_STATUS_C, &data); + pr_info("%s : 0x%xh(0x%02x)\n", __func__, SMB347_STATUS_C, data); + + if (data & SMB347_CHARGING_ENABLE) + status = POWER_SUPPLY_STATUS_CHARGING; + else { + /* if error bit check, ignore the status of charger-ic */ + if (data & SMB347_CHARGER_ERROR) + status = POWER_SUPPLY_STATUS_DISCHARGING; + /* At least one charge cycle terminated */ + /*Charge current < Termination Current */ + else if (data & SMB347_CHARGING_STATUS) + status = POWER_SUPPLY_STATUS_FULL; + } + + return status; +} + +static int smb347_get_charger_is_full(void) +{ + struct smb347_chg_data *chg = smb347_chg; + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 data = 0; + + smb347_i2c_read(chg->client, SMB347_STATUS_C, &data); + pr_info("%s : 0x%xh(0x%02x)\n", __func__, SMB347_STATUS_C, data); + + if (data & SMB347_CHARGER_ERROR) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else if (data & SMB347_CHARGING_STATUS) + status = POWER_SUPPLY_STATUS_FULL; + + return status; +} + +static void smb347_set_charging_state(int enable, int charging_mode) +{ + struct smb347_chg_data *chg = smb347_chg; + pr_info("%s : enable(%d), charging_mode(%d)\n", + __func__, enable, charging_mode); + + if (enable) { + /* Only for P4C rev0.2, Check vbus for opeartion charger */ + if (!smb347_check_powersource(chg)) + return; + + /* Init smb347 charger */ + smb347_charger_init(chg); + + switch (charging_mode) { + case CABLE_TYPE_TA: + /* Input current limit : DCIN 1800mA, USBIN HC 1800mA */ + smb347_i2c_write(chg->client, + SMB347_INPUT_CURRENTLIMIT, 0x66); + + /* CommandB : High-current mode */ + smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x03); + + pr_info("%s : 1.8A charging enable\n", __func__); + break; + case CABLE_TYPE_DESKDOCK: + /* Input current limit : DCIN 1500mA, USBIN HC 1500mA */ + smb347_i2c_write(chg->client, + SMB347_INPUT_CURRENTLIMIT, 0x55); + + /* CommandB : High-current mode */ + smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x03); + pr_info("%s : 1.5A charging enable\n", __func__); + break; + case CABLE_TYPE_USB: + /* CommandB : USB5 */ + smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x02); + pr_info("%s : LOW(USB5) charging enable\n", __func__); + break; + default: + /* CommandB : USB1 */ + smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x00); + pr_info("%s : LOW(USB1) charging enable\n", __func__); + break; + } + + smb347_enable_charging(chg); + } else { + smb347_disable_charging(chg); + } + + smb347_test_read(); +} + +int smb347_get_charging_current(void) +{ + struct smb347_chg_data *chg = smb347_chg; + u8 data = 0; + int get_current = 0; + + smb347_i2c_read(chg->client, SMB347_CHARGE_CURRENT, &data); + switch (data >> 5) { + case 0: + get_current = 700; + break; + case 1: + get_current = 900; + break; + case 2: + get_current = 1200; + break; + case 3: + get_current = 1500; + break; + case 4: + get_current = 1800; + break; + case 5: + get_current = 2000; + break; + case 6: + get_current = 2200; + break; + case 7: + get_current = 2500; + break; + default: + get_current = 700; + break; + } + pr_debug("%s: Get charging current as %dmA.\n", __func__, get_current); + return get_current; +} + +void smb347_set_charging_current(int set_current) +{ + struct smb347_chg_data *chg = smb347_chg; + + if (set_current > 450) { + /* CommandB : High-current mode */ + smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x03); + udelay(10); + } else { + /* CommandB : USB5 */ + smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x02); + udelay(10); + } + pr_debug("%s: Set charging current as %dmA.\n", __func__, set_current); +} + +static int smb347_i2c_probe +(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct smb347_chg_data *chg; + int ret = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + pr_info("%s : smb347 Charger Driver Loading\n", __func__); + + chg = kzalloc(sizeof(struct smb347_chg_data), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + chg->callbacks = kzalloc(sizeof(struct smb_charger_callbacks), + GFP_KERNEL); + if (!chg->callbacks) { + kfree(chg); + return -ENOMEM; + } + + chg->client = client; + chg->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, chg); + smb347_chg = chg; + + if (!chg->pdata) { + pr_err("%s : No platform data supplied\n", __func__); + ret = -EINVAL; + goto err_pdata; + } + + pr_info("register callback functions!\n"); + chg->callbacks->set_charging_state = smb347_set_charging_state; + chg->callbacks->get_charging_state = smb347_get_charging_state; + chg->callbacks->set_charging_current = smb347_set_charging_current; + chg->callbacks->get_charging_current = smb347_get_charging_current; + chg->callbacks->get_charger_is_full = smb347_get_charger_is_full; + if (chg->pdata && chg->pdata->register_callbacks) + chg->pdata->register_callbacks(chg->callbacks); + + pr_info("smb347 charger initialized.\n"); + + return 0; + +err_pdata: + kfree(chg->callbacks); + kfree(chg); + return ret; +} + +static int __devexit smb347_remove(struct i2c_client *client) +{ + struct smb347_chg_data *chg = i2c_get_clientdata(client); + + if (chg->pdata && chg->pdata->unregister_callbacks) + chg->pdata->unregister_callbacks(); + + kfree(chg); + return 0; +} + +static const struct i2c_device_id smb347_id[] = { + { "smb347-charger", 0 }, +}; + + +static struct i2c_driver smb347_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "smb347-charger", + }, + .probe = smb347_i2c_probe, + .remove = smb347_remove, + .id_table = smb347_id, +}; + + +MODULE_DEVICE_TABLE(i2c, smb347_id); + +static int __init smb347_init(void) +{ + return i2c_add_driver(&smb347_i2c_driver); +} + +static void __exit smb347_exit(void) +{ + i2c_del_driver(&smb347_i2c_driver); +} + +module_init(smb347_init); +module_exit(smb347_exit); + +MODULE_AUTHOR("SangYoung Son <hello.son@samsung.com>"); +MODULE_DESCRIPTION("smb347 charger driver"); +MODULE_LICENSE("GPL"); |