aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/power
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/Kconfig12
-rw-r--r--drivers/power/Makefile2
-rw-r--r--drivers/power/fuel_gauge.c204
-rwxr-xr-x[-rw-r--r--]drivers/power/max17040_battery.c112
-rw-r--r--drivers/power/pda_power.c71
-rw-r--r--drivers/power/power_supply_core.c30
-rw-r--r--drivers/power/s3c_fake_battery.c576
-rwxr-xr-xdrivers/power/s5pc110_battery.c1041
-rw-r--r--drivers/power/s5pc110_battery.h91
9 files changed, 2072 insertions, 67 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index e57b50b..e00bf5a 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -173,12 +173,24 @@ config BATTERY_Z2
help
Say Y to include support for the battery on the Zipit Z2.
+config BATTERY_S3C
+ tristate "S3C fake battery driver"
+ depends on ARCH_S5PV210
+ help
+ Say Y to enable support for batteries with s5pc110 chip.
+
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
+config BATTERY_S5PC110
+ tristate "Battery driver for CRESPO(S5PC110)"
+ depends on ARCH_S5PV210
+ help
+ Say Y to enable support for batteries with s5pc110 chip.
+
config CHARGER_PCF50633
tristate "NXP PCF50633 MBC"
depends on MFD_PCF50633
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 009a90f..327d490 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -28,7 +28,9 @@ obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o
obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o
obj-$(CONFIG_BATTERY_Z2) += z2_battery.o
+obj-$(CONFIG_BATTERY_S3C) += s3c_fake_battery.o
obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o
+obj-$(CONFIG_BATTERY_S5PC110) += s5pc110_battery.o
obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o
obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o
diff --git a/drivers/power/fuel_gauge.c b/drivers/power/fuel_gauge.c
new file mode 100644
index 0000000..1ca2531
--- /dev/null
+++ b/drivers/power/fuel_gauge.c
@@ -0,0 +1,204 @@
+/* Register address */
+#define VCELL_REG 0x02
+#define SOCREP_REG 0x04
+#define MISCCFG_REG 0x06
+#define RCOMP_REG 0x0C
+#define CMD_REG 0xFE
+
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+
+int fuel_guage_init;
+EXPORT_SYMBOL(fuel_guage_init);
+
+static struct i2c_driver fg_i2c_driver;
+static struct i2c_client *fg_i2c_client;
+
+struct fg_state{
+ struct i2c_client *client;
+};
+
+static int fg_i2c_read(struct i2c_client *client, u8 reg, u8 *data, u8 length)
+{
+ int value = i2c_smbus_read_word_data(client, reg);
+
+ if (value < 0) {
+ pr_err("%s: Failed to fg_i2c_read\n", __func__);
+ return -1;
+ }
+
+ *data = value & 0x00ff;
+ *(data+1) = (value & 0xff00) >> 8;
+
+ return 0;
+}
+
+static int fg_i2c_write(struct i2c_client *client, u8 reg, u8 *data, u8 length)
+{
+ u16 value = (*(data+1) << 8) | (*(data)) ;
+
+ return i2c_smbus_write_word_data(client, reg, value);
+}
+
+int fg_read_vcell(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ u8 data[2];
+ u32 vcell = 0;
+
+ if (!fuel_guage_init) {
+ pr_err("%s : fuel guage IC is not initialized!!\n", __func__);
+ return -1;
+ }
+
+ if (fg_i2c_read(client, VCELL_REG, data, 2) < 0) {
+ pr_err("%s: Failed to read VCELL\n", __func__);
+ return -1;
+ }
+
+ vcell = ((((data[0] << 4) & 0xFF0) | ((data[1] >> 4) & 0xF)) * 125)/100;
+
+ return vcell;
+}
+
+int fg_read_soc(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ u8 data[2];
+ u32 soc = 0;
+ u32 temp = 0;
+ u32 temp_soc = 0;
+
+ if (!fuel_guage_init) {
+ pr_err("%s : fuel guage IC is not initialized!!\n", __func__);
+ return -1;
+ }
+
+ if (fg_i2c_read(client, SOCREP_REG, data, 2) < 0) {
+ pr_err("%s: Failed to read SOCREP\n", __func__);
+ return -1;
+ }
+
+ temp = data[0] * 100 + ((data[1] * 100) / 256);
+
+ if (temp >= 100)
+ temp_soc = temp;
+ else {
+ if (temp >= 70)
+ temp_soc = 100;
+ else
+ temp_soc = 0;
+ }
+
+ /* rounding off and Changing to percentage */
+ soc = temp_soc / 100;
+
+ if (temp_soc % 100 >= 50)
+ soc += 1;
+
+ if (soc >= 26)
+ soc += 4;
+ else
+ soc = (30 * temp_soc) / 26 / 100;
+
+ if (soc >= 100)
+ soc = 100;
+
+ return soc;
+}
+
+int fg_reset_soc(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ u8 data[2];
+ s32 ret = 0;
+
+ if (!fuel_guage_init) {
+ pr_err("%s : fuel guage IC is not initialized!!\n", __func__);
+ return -1;
+ }
+
+ /* Quick-start */
+ data[0] = 0x40;
+ data[1] = 0x00;
+
+ if (fg_i2c_write(client, MISCCFG_REG, data, 2) < 0) {
+ pr_err("%s: Failed to write MiscCFG\n", __func__);
+ return -1;
+ }
+
+ msleep(500);
+
+ return ret;
+}
+
+void fuel_gauge_rcomp(void)
+{
+ struct i2c_client *client = fg_i2c_client;
+ u8 rst_cmd[2];
+
+ if (!fuel_guage_init) {
+ pr_err("%s : fuel guage IC is not initialized!!\n", __func__);
+ return ;
+ }
+
+ rst_cmd[0] = 0xB0;
+ rst_cmd[1] = 0x00;
+
+ if (fg_i2c_write(client, RCOMP_REG, rst_cmd, 2) < 0)
+ pr_err("%s: failed fuel_gauge_rcomp\n", __func__);
+}
+
+static int fg_i2c_remove(struct i2c_client *client)
+{
+ struct fg_state *fg = i2c_get_clientdata(client);
+
+ kfree(fg);
+ return 0;
+}
+
+static int fg_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct fg_state *fg;
+
+ fuel_guage_init = 0;
+ fg_i2c_client = NULL;
+
+ fg = kzalloc(sizeof(struct fg_state), GFP_KERNEL);
+ if (fg == NULL) {
+ pr_err("failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ fg->client = client;
+ i2c_set_clientdata(client, fg);
+
+ /* rest of the initialisation goes here. */
+
+ pr_info("Fuel guage attach success!!!\n");
+
+ fg_i2c_client = client;
+
+ fuel_guage_init = 1;
+
+ return 0;
+}
+
+
+static const struct i2c_device_id fg_device_id[] = {
+ {"max1704x", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, fg_device_id);
+
+
+static struct i2c_driver fg_i2c_driver = {
+ .driver = {
+ .name = "max1704x",
+ .owner = THIS_MODULE,
+ },
+ .probe = fg_i2c_probe,
+ .remove = fg_i2c_remove,
+ .id_table = fg_device_id,
+};
diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c
index 2f2f9a6..aedb539 100644..100755
--- a/drivers/power/max17040_battery.c
+++ b/drivers/power/max17040_battery.c
@@ -20,6 +20,7 @@
#include <linux/power_supply.h>
#include <linux/max17040_battery.h>
#include <linux/slab.h>
+#include <linux/time.h>
#define MAX17040_VCELL_MSB 0x02
#define MAX17040_VCELL_LSB 0x03
@@ -39,9 +40,9 @@
struct max17040_chip {
struct i2c_client *client;
- struct delayed_work work;
struct power_supply battery;
struct max17040_platform_data *pdata;
+ struct timespec next_update_time;
/* State Of Connect */
int online;
@@ -53,12 +54,20 @@ struct max17040_chip {
int status;
};
+static void max17040_update_values(struct max17040_chip *chip);
+
static int max17040_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct max17040_chip *chip = container_of(psy,
struct max17040_chip, battery);
+ struct timespec now;
+
+ ktime_get_ts(&now);
+ monotonic_to_bootbased(&now);
+ if (timespec_compare(&now, &chip->next_update_time) >= 0)
+ max17040_update_values(chip);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
@@ -68,7 +77,7 @@ static int max17040_get_property(struct power_supply *psy,
val->intval = chip->online;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- val->intval = chip->vcell;
+ val->intval = chip->vcell * 1250;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = chip->soc;
@@ -103,12 +112,6 @@ static int max17040_read_reg(struct i2c_client *client, int reg)
return ret;
}
-static void max17040_reset(struct i2c_client *client)
-{
- max17040_write_reg(client, MAX17040_CMD_MSB, 0x54);
- max17040_write_reg(client, MAX17040_CMD_LSB, 0x00);
-}
-
static void max17040_get_vcell(struct i2c_client *client)
{
struct max17040_chip *chip = i2c_get_clientdata(client);
@@ -126,11 +129,39 @@ static void max17040_get_soc(struct i2c_client *client)
struct max17040_chip *chip = i2c_get_clientdata(client);
u8 msb;
u8 lsb;
+ u32 soc = 0;
+ u32 temp = 0;
+ u32 temp_soc = 0;
msb = max17040_read_reg(client, MAX17040_SOC_MSB);
lsb = max17040_read_reg(client, MAX17040_SOC_LSB);
- chip->soc = msb;
+ temp = msb * 100 + ((lsb * 100) / 256);
+
+ if (temp >= 100)
+ temp_soc = temp;
+ else {
+ if (temp >= 70)
+ temp_soc = 100;
+ else
+ temp_soc = 0;
+ }
+
+ /* rounding off and Changing to percentage */
+ soc = temp_soc / 100;
+
+ if (temp_soc % 100 >= 50)
+ soc += 1;
+
+ if (soc >= 26)
+ soc += 4;
+ else
+ soc = (30 * temp_soc) / 26 / 100;
+
+ if (soc >= 100)
+ soc = 100;
+
+ chip->soc = soc;
}
static void max17040_get_version(struct i2c_client *client)
@@ -148,7 +179,7 @@ static void max17040_get_online(struct i2c_client *client)
{
struct max17040_chip *chip = i2c_get_clientdata(client);
- if (chip->pdata->battery_online)
+ if (chip->pdata && chip->pdata->battery_online)
chip->online = chip->pdata->battery_online();
else
chip->online = 1;
@@ -158,7 +189,8 @@ static void max17040_get_status(struct i2c_client *client)
{
struct max17040_chip *chip = i2c_get_clientdata(client);
- if (!chip->pdata->charger_online || !chip->pdata->charger_enable) {
+ if (!chip->pdata || !chip->pdata->charger_online ||
+ !chip->pdata->charger_enable) {
chip->status = POWER_SUPPLY_STATUS_UNKNOWN;
return;
}
@@ -176,18 +208,17 @@ static void max17040_get_status(struct i2c_client *client)
chip->status = POWER_SUPPLY_STATUS_FULL;
}
-static void max17040_work(struct work_struct *work)
+static void max17040_update_values(struct max17040_chip *chip)
{
- struct max17040_chip *chip;
-
- chip = container_of(work, struct max17040_chip, work.work);
-
max17040_get_vcell(chip->client);
max17040_get_soc(chip->client);
max17040_get_online(chip->client);
max17040_get_status(chip->client);
- schedule_delayed_work(&chip->work, MAX17040_DELAY);
+ /* next update must be at least 1 second later */
+ ktime_get_ts(&chip->next_update_time);
+ monotonic_to_bootbased(&chip->next_update_time);
+ chip->next_update_time.tv_sec++;
}
static enum power_supply_property max17040_battery_props[] = {
@@ -222,18 +253,23 @@ static int __devinit max17040_probe(struct i2c_client *client,
chip->battery.properties = max17040_battery_props;
chip->battery.num_properties = ARRAY_SIZE(max17040_battery_props);
- ret = power_supply_register(&client->dev, &chip->battery);
+ max17040_update_values(chip);
+
+ if (chip->pdata && chip->pdata->power_supply_register)
+ ret = chip->pdata->power_supply_register(&client->dev, &chip->battery);
+ else
+ ret = power_supply_register(&client->dev, &chip->battery);
if (ret) {
dev_err(&client->dev, "failed: power supply register\n");
kfree(chip);
return ret;
}
- max17040_reset(client);
max17040_get_version(client);
- INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);
- schedule_delayed_work(&chip->work, MAX17040_DELAY);
+ if (chip->pdata)
+ i2c_smbus_write_word_data(client, MAX17040_RCOMP_MSB,
+ swab16(chip->pdata->rcomp_value));
return 0;
}
@@ -242,38 +278,14 @@ static int __devexit max17040_remove(struct i2c_client *client)
{
struct max17040_chip *chip = i2c_get_clientdata(client);
- power_supply_unregister(&chip->battery);
- cancel_delayed_work(&chip->work);
+ if (chip->pdata && chip->pdata->power_supply_unregister)
+ chip->pdata->power_supply_unregister(&chip->battery);
+ else
+ power_supply_unregister(&chip->battery);
kfree(chip);
return 0;
}
-#ifdef CONFIG_PM
-
-static int max17040_suspend(struct i2c_client *client,
- pm_message_t state)
-{
- struct max17040_chip *chip = i2c_get_clientdata(client);
-
- cancel_delayed_work(&chip->work);
- return 0;
-}
-
-static int max17040_resume(struct i2c_client *client)
-{
- struct max17040_chip *chip = i2c_get_clientdata(client);
-
- schedule_delayed_work(&chip->work, MAX17040_DELAY);
- return 0;
-}
-
-#else
-
-#define max17040_suspend NULL
-#define max17040_resume NULL
-
-#endif /* CONFIG_PM */
-
static const struct i2c_device_id max17040_id[] = {
{ "max17040", 0 },
{ }
@@ -286,8 +298,6 @@ static struct i2c_driver max17040_i2c_driver = {
},
.probe = max17040_probe,
.remove = __devexit_p(max17040_remove),
- .suspend = max17040_suspend,
- .resume = max17040_resume,
.id_table = max17040_id,
};
diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c
index 69f8aa3..81b7201 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 329b46b..03810ce 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_fake_battery.c b/drivers/power/s3c_fake_battery.c
new file mode 100644
index 0000000..437702c
--- /dev/null
+++ b/drivers/power/s3c_fake_battery.c
@@ -0,0 +1,576 @@
+/*
+ * linux/drivers/power/s3c_fake_battery.c
+ *
+ * Battery measurement code for S3C platform.
+ *
+ * based on palmtx_battery.c
+ *
+ * Copyright (C) 2009 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 DRIVER_NAME "sec-fake-battery"
+
+static struct wake_lock vbus_wake_lock;
+
+/* Prototypes */
+extern int s3c_adc_get_adc_data(int channel);
+
+static ssize_t s3c_bat_show_property(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+static ssize_t s3c_bat_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+
+#define FAKE_BAT_LEVEL 80
+
+static struct device *dev;
+static int s3c_battery_initial;
+static int force_update;
+
+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 s3c_battery_info {
+ int present;
+ int polling;
+ unsigned long polling_interval;
+
+ struct battery_info bat_info;
+};
+static struct s3c_battery_info s3c_bat_info;
+
+static int s3c_get_bat_level(struct power_supply *bat_ps)
+{
+ return FAKE_BAT_LEVEL;
+}
+
+static int s3c_get_bat_vol(struct power_supply *bat_ps)
+{
+ int bat_vol = 0;
+
+ return bat_vol;
+}
+
+static u32 s3c_get_bat_health(void)
+{
+ return s3c_bat_info.bat_info.batt_health;
+}
+
+static int s3c_get_bat_temp(struct power_supply *bat_ps)
+{
+ int temp = 0;
+
+ return temp;
+}
+
+static int s3c_bat_get_charging_status(void)
+{
+ charger_type_t charger = CHARGER_BATTERY;
+ int ret = 0;
+
+ charger = s3c_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 (s3c_bat_info.bat_info.level == 100
+ && s3c_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 s3c_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 = s3c_bat_get_charging_status();
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = s3c_get_bat_health();
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = s3c_bat_info.present;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = s3c_bat_info.bat_info.level;
+ dev_dbg(dev, "%s : level = %d\n", __func__,
+ val->intval);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = s3c_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 s3c_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 = s3c_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 SEC_BATTERY_ATTR(_name) \
+{ \
+ .attr = { .name = #_name, .mode = S_IRUGO | S_IWUGO, .owner = THIS_MODULE }, \
+ .show = s3c_bat_show_property, \
+ .store = s3c_bat_store, \
+}
+
+static struct device_attribute s3c_battery_attrs[] = {
+ SEC_BATTERY_ATTR(batt_vol),
+ SEC_BATTERY_ATTR(batt_vol_adc),
+ SEC_BATTERY_ATTR(batt_vol_adc_cal),
+ SEC_BATTERY_ATTR(batt_temp),
+ SEC_BATTERY_ATTR(batt_temp_adc),
+ SEC_BATTERY_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 s3c_bat_create_attrs(struct device * dev)
+{
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(s3c_battery_attrs); i++) {
+ rc = device_create_file(dev, &s3c_battery_attrs[i]);
+ if (rc)
+ goto s3c_attrs_failed;
+ }
+ goto succeed;
+
+s3c_attrs_failed:
+ while (i--)
+ device_remove_file(dev, &s3c_battery_attrs[i]);
+succeed:
+ return rc;
+}
+
+static ssize_t s3c_bat_show_property(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i = 0;
+ const ptrdiff_t off = attr - s3c_battery_attrs;
+
+ switch (off) {
+ case BATT_VOL:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ s3c_bat_info.bat_info.batt_vol);
+ break;
+ case BATT_VOL_ADC:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ s3c_bat_info.bat_info.batt_vol_adc);
+ break;
+ case BATT_VOL_ADC_CAL:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ s3c_bat_info.bat_info.batt_vol_adc_cal);
+ break;
+ case BATT_TEMP:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ s3c_bat_info.bat_info.batt_temp);
+ break;
+ case BATT_TEMP_ADC:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ s3c_bat_info.bat_info.batt_temp_adc);
+ break;
+ case BATT_TEMP_ADC_CAL:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ s3c_bat_info.bat_info.batt_temp_adc_cal);
+ break;
+ default:
+ i = -EINVAL;
+ }
+
+ return i;
+}
+
+static ssize_t s3c_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 - s3c_battery_attrs;
+
+ switch (off) {
+ case BATT_VOL_ADC_CAL:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ s3c_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) {
+ s3c_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 s3c_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 s3c_power_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *supply_list[] = {
+ "battery",
+};
+
+static struct power_supply s3c_power_supplies[] = {
+ {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = s3c_battery_properties,
+ .num_properties = ARRAY_SIZE(s3c_battery_properties),
+ .get_property = s3c_bat_get_property,
+ },
+ {
+ .name = "usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .supplied_to = supply_list,
+ .num_supplicants = ARRAY_SIZE(supply_list),
+ .properties = s3c_power_properties,
+ .num_properties = ARRAY_SIZE(s3c_power_properties),
+ .get_property = s3c_power_get_property,
+ },
+ {
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .supplied_to = supply_list,
+ .num_supplicants = ARRAY_SIZE(supply_list),
+ .properties = s3c_power_properties,
+ .num_properties = ARRAY_SIZE(s3c_power_properties),
+ .get_property = s3c_power_get_property,
+ },
+};
+
+static int s3c_cable_status_update(int status)
+{
+ int ret = 0;
+ charger_type_t source = CHARGER_BATTERY;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if(!s3c_battery_initial)
+ return -EPERM;
+
+ switch(status) {
+ case CHARGER_BATTERY:
+ dev_dbg(dev, "%s : cable NOT PRESENT\n", __func__);
+ s3c_bat_info.bat_info.charging_source = CHARGER_BATTERY;
+ break;
+ case CHARGER_USB:
+ dev_dbg(dev, "%s : cable USB\n", __func__);
+ s3c_bat_info.bat_info.charging_source = CHARGER_USB;
+ break;
+ case CHARGER_AC:
+ dev_dbg(dev, "%s : cable AC\n", __func__);
+ s3c_bat_info.bat_info.charging_source = CHARGER_AC;
+ break;
+ case CHARGER_DISCHARGE:
+ dev_dbg(dev, "%s : Discharge\n", __func__);
+ s3c_bat_info.bat_info.charging_source = CHARGER_DISCHARGE;
+ break;
+ default:
+ dev_err(dev, "%s : Nat supported status\n", __func__);
+ ret = -EINVAL;
+ }
+ source = s3c_bat_info.bat_info.charging_source;
+
+ if (source == CHARGER_USB || source == CHARGER_AC) {
+ wake_lock(&vbus_wake_lock);
+ } else {
+ /* give userspace some time to see the uevent and update
+ * LED state or whatnot...
+ */
+ wake_lock_timeout(&vbus_wake_lock, HZ / 2);
+ }
+
+ /* if the power source changes, all power supplies may change state */
+ power_supply_changed(&s3c_power_supplies[CHARGER_BATTERY]);
+ /*
+ power_supply_changed(&s3c_power_supplies[CHARGER_USB]);
+ power_supply_changed(&s3c_power_supplies[CHARGER_AC]);
+ */
+ dev_dbg(dev, "%s : call power_supply_changed\n", __func__);
+ return ret;
+}
+
+static void s3c_bat_status_update(struct power_supply *bat_ps)
+{
+ int old_level, old_temp, old_is_full;
+ dev_dbg(dev, "%s ++\n", __func__);
+
+ if(!s3c_battery_initial)
+ return;
+
+ mutex_lock(&work_lock);
+ old_temp = s3c_bat_info.bat_info.batt_temp;
+ old_level = s3c_bat_info.bat_info.level;
+ old_is_full = s3c_bat_info.bat_info.batt_is_full;
+ s3c_bat_info.bat_info.batt_temp = s3c_get_bat_temp(bat_ps);
+ s3c_bat_info.bat_info.level = s3c_get_bat_level(bat_ps);
+ s3c_bat_info.bat_info.batt_vol = s3c_get_bat_vol(bat_ps);
+
+ if (old_level != s3c_bat_info.bat_info.level
+ || old_temp != s3c_bat_info.bat_info.batt_temp
+ || old_is_full != s3c_bat_info.bat_info.batt_is_full
+ || force_update) {
+ force_update = 0;
+ 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 s3c_cable_check_status(int flag)
+{
+ charger_type_t status = 0;
+
+ if (flag == 0) // Battery
+ status = CHARGER_BATTERY;
+ else // USB
+ status = CHARGER_USB;
+ s3c_cable_status_update(status);
+}
+EXPORT_SYMBOL(s3c_cable_check_status);
+
+#ifdef CONFIG_PM
+static int s3c_bat_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ dev_info(dev, "%s\n", __func__);
+
+ return 0;
+}
+
+static int s3c_bat_resume(struct platform_device *pdev)
+{
+ dev_info(dev, "%s\n", __func__);
+
+ return 0;
+}
+#else
+#define s3c_bat_suspend NULL
+#define s3c_bat_resume NULL
+#endif /* CONFIG_PM */
+
+static int __devinit s3c_bat_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret = 0;
+
+ dev = &pdev->dev;
+ dev_info(dev, "%s\n", __func__);
+
+ s3c_bat_info.present = 1;
+
+ s3c_bat_info.bat_info.batt_id = 0;
+ s3c_bat_info.bat_info.batt_vol = 0;
+ s3c_bat_info.bat_info.batt_vol_adc = 0;
+ s3c_bat_info.bat_info.batt_vol_adc_cal = 0;
+ s3c_bat_info.bat_info.batt_temp = 0;
+ s3c_bat_info.bat_info.batt_temp_adc = 0;
+ s3c_bat_info.bat_info.batt_temp_adc_cal = 0;
+ s3c_bat_info.bat_info.batt_current = 0;
+ s3c_bat_info.bat_info.level = 0;
+ s3c_bat_info.bat_info.charging_source = CHARGER_BATTERY;
+ s3c_bat_info.bat_info.charging_enabled = 0;
+ s3c_bat_info.bat_info.batt_health = POWER_SUPPLY_HEALTH_GOOD;
+
+ /* init power supplier framework */
+ for (i = 0; i < ARRAY_SIZE(s3c_power_supplies); i++) {
+ ret = power_supply_register(&pdev->dev,
+ &s3c_power_supplies[i]);
+ if (ret) {
+ dev_err(dev, "Failed to register"
+ "power supply %d,%d\n", i, ret);
+ goto __end__;
+ }
+ }
+
+ /* create sec detail attributes */
+ s3c_bat_create_attrs(s3c_power_supplies[CHARGER_BATTERY].dev);
+
+ s3c_battery_initial = 1;
+ force_update = 0;
+
+ s3c_bat_status_update(
+ &s3c_power_supplies[CHARGER_BATTERY]);
+
+__end__:
+ return ret;
+}
+
+static int __devexit s3c_bat_remove(struct platform_device *pdev)
+{
+ int i;
+ dev_info(dev, "%s\n", __func__);
+
+ for (i = 0; i < ARRAY_SIZE(s3c_power_supplies); i++) {
+ power_supply_unregister(&s3c_power_supplies[i]);
+ }
+
+ return 0;
+}
+
+static struct platform_driver s3c_bat_driver = {
+ .driver.name = DRIVER_NAME,
+ .driver.owner = THIS_MODULE,
+ .probe = s3c_bat_probe,
+ .remove = __devexit_p(s3c_bat_remove),
+ .suspend = s3c_bat_suspend,
+ .resume = s3c_bat_resume,
+};
+
+/* Initailize GPIO */
+static void s3c_bat_init_hw(void)
+{
+}
+
+static int __init s3c_bat_init(void)
+{
+ pr_info("%s\n", __func__);
+
+ s3c_bat_init_hw();
+
+ wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present");
+
+ return platform_driver_register(&s3c_bat_driver);
+}
+
+static void __exit s3c_bat_exit(void)
+{
+ pr_info("%s\n", __func__);
+ platform_driver_unregister(&s3c_bat_driver);
+}
+
+module_init(s3c_bat_init);
+module_exit(s3c_bat_exit);
+
+MODULE_AUTHOR("HuiSung Kang <hs1218.kang@samsung.com>");
+MODULE_DESCRIPTION("S3C battery driver for SMDK Board");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/s5pc110_battery.c b/drivers/power/s5pc110_battery.c
new file mode 100755
index 0000000..3567e1b
--- /dev/null
+++ b/drivers/power/s5pc110_battery.c
@@ -0,0 +1,1041 @@
+/*
+ * linux/drivers/power/s3c6410_battery.c
+ *
+ * Battery measurement code for S3C6410 platform.
+ *
+ * based on palmtx_battery.c
+ *
+ * Copyright (C) 2009 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 <asm/mach-types.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mfd/max8998.h>
+#include <linux/mfd/max8998-private.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/driver.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/wakelock.h>
+#include <linux/workqueue.h>
+#include <mach/battery.h>
+
+#ifdef CONFIG_MACH_HERRING
+#include <mach/gpio-herring.h>
+#endif
+
+#ifdef CONFIG_MACH_ARIES
+#include <mach/gpio-aries.h>
+#endif
+
+#include <mach/hardware.h>
+#include <mach/map.h>
+#include <mach/regs-clock.h>
+#include <mach/regs-gpio.h>
+#include <mach/adc.h>
+#include <plat/gpio-cfg.h>
+#include <linux/android_alarm.h>
+#include "s5pc110_battery.h"
+#include <linux/mfd/max8998.h>
+
+#define POLLING_INTERVAL 1000
+#define ADC_TOTAL_COUNT 10
+#define ADC_DATA_ARR_SIZE 6
+
+#define OFFSET_VIBRATOR_ON (0x1 << 0)
+#define OFFSET_CAMERA_ON (0x1 << 1)
+#define OFFSET_MP3_PLAY (0x1 << 2)
+#define OFFSET_VIDEO_PLAY (0x1 << 3)
+#define OFFSET_VOICE_CALL_2G (0x1 << 4)
+#define OFFSET_VOICE_CALL_3G (0x1 << 5)
+#define OFFSET_DATA_CALL (0x1 << 6)
+#define OFFSET_LCD_ON (0x1 << 7)
+#define OFFSET_TA_ATTACHED (0x1 << 8)
+#define OFFSET_CAM_FLASH (0x1 << 9)
+#define OFFSET_BOOTING (0x1 << 10)
+
+#define FAST_POLL (1 * 60)
+#define SLOW_POLL (10 * 60)
+
+#define DISCONNECT_BAT_FULL 0x1
+#define DISCONNECT_TEMP_OVERHEAT 0x2
+#define DISCONNECT_TEMP_FREEZE 0x4
+#define DISCONNECT_OVER_TIME 0x8
+
+#define ATTACH_USB 1
+#define ATTACH_TA 2
+
+#if defined (CONFIG_SAMSUNG_GALAXYS) || defined (CONFIG_SAMSUNG_GALAXYSB) || defined(CONFIG_SAMSUNG_CAPTIVATE)
+ #define HIGH_BLOCK_TEMP 630
+ #define HIGH_RECOVER_TEMP 580
+ #define LOW_BLOCK_TEMP (-40)
+ #define LOW_RECOVER_TEMP 10
+#else
+ #define HIGH_BLOCK_TEMP 500
+ #define HIGH_RECOVER_TEMP 420
+ #define LOW_BLOCK_TEMP 0
+ #define LOW_RECOVER_TEMP 20
+#endif
+
+struct battery_info {
+ u32 batt_temp; /* Battery Temperature (C) from ADC */
+ u32 batt_temp_adc; /* Battery Temperature ADC value */
+ u32 batt_health; /* Battery Health (Authority) */
+ u32 dis_reason;
+ u32 batt_vcell;
+ u32 batt_soc;
+ u32 charging_status;
+ bool batt_is_full; /* 0 : Not full 1: Full */
+};
+
+struct adc_sample_info {
+ unsigned int cnt;
+ int total_adc;
+ int average_adc;
+ int adc_arr[ADC_TOTAL_COUNT];
+ int index;
+};
+
+struct chg_data {
+ struct device *dev;
+ struct max8998_dev *iodev;
+ struct work_struct bat_work;
+ struct max8998_charger_data *pdata;
+
+ struct power_supply psy_bat;
+ struct power_supply psy_usb;
+ struct power_supply psy_ac;
+ struct alarm alarm;
+ struct workqueue_struct *monitor_wqueue;
+ struct wake_lock vbus_wake_lock;
+ struct wake_lock work_wake_lock;
+ struct adc_sample_info adc_sample[ENDOFADC];
+ struct battery_info bat_info;
+ struct mutex mutex;
+
+ enum cable_type_t cable_status;
+ bool charging;
+ bool set_charge_timeout;
+ int present;
+ int timestamp;
+ int set_batt_full;
+ unsigned long discharging_time;
+ unsigned int polling_interval;
+ int slow_poll;
+ ktime_t last_poll;
+ struct max8998_charger_callbacks callbacks;
+};
+
+static bool lpm_charging_mode;
+
+static char *supply_list[] = {
+ "battery",
+};
+
+static enum power_supply_property max8998_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 s3c_power_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static ssize_t s3c_bat_show_attrs(struct device *dev,
+ struct device_attribute *attr, char *buf);
+
+static ssize_t s3c_bat_store_attrs(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count);
+
+#define SEC_BATTERY_ATTR(_name) \
+{ \
+ .attr = {.name = #_name, .mode = 0664 }, \
+ .show = s3c_bat_show_attrs, \
+ .store = s3c_bat_store_attrs, \
+}
+
+static struct device_attribute s3c_battery_attrs[] = {
+ SEC_BATTERY_ATTR(charging_mode_booting),
+ SEC_BATTERY_ATTR(batt_temp_check),
+ SEC_BATTERY_ATTR(batt_full_check),
+};
+
+static bool max8998_check_vdcin(struct chg_data *chg)
+{
+ u8 data = 0;
+ int ret;
+
+ ret = max8998_read_reg(chg->iodev->i2c, MAX8998_REG_STATUS2, &data);
+
+ if (ret < 0) {
+ pr_err("max8998_read_reg error\n");
+ return ret;
+ }
+
+ return data & MAX8998_MASK_VDCIN;
+}
+
+static void max8998_set_cable(struct max8998_charger_callbacks *ptr,
+ enum cable_type_t status)
+{
+ struct chg_data *chg = container_of(ptr, struct chg_data, callbacks);
+ chg->cable_status = status;
+
+ if (lpm_charging_mode &&
+ (max8998_check_vdcin(chg) != 1) &&
+ pm_power_off)
+ pm_power_off();
+
+ power_supply_changed(&chg->psy_ac);
+ power_supply_changed(&chg->psy_usb);
+ wake_lock(&chg->work_wake_lock);
+ queue_work(chg->monitor_wqueue, &chg->bat_work);
+}
+
+static void check_lpm_charging_mode(struct chg_data *chg)
+{
+ if (readl(S5P_INFORM5)) {
+ lpm_charging_mode = 1;
+ if (max8998_check_vdcin(chg) != 1)
+ if (pm_power_off)
+ pm_power_off();
+ } else
+ lpm_charging_mode = 0;
+
+ pr_info("%s : lpm_charging_mode(%d)\n", __func__, lpm_charging_mode);
+}
+
+bool charging_mode_get(void)
+{
+ return lpm_charging_mode;
+}
+EXPORT_SYMBOL(charging_mode_get);
+
+static int s3c_bat_get_property(struct power_supply *bat_ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct chg_data *chg = container_of(bat_ps,
+ struct chg_data, psy_bat);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = chg->bat_info.charging_status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = chg->bat_info.batt_health;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = chg->present;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = chg->bat_info.batt_temp;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ /* battery is always online */
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (chg->pdata && chg->pdata->psy_fuelgauge &&
+ chg->pdata->psy_fuelgauge->get_property &&
+ chg->pdata->psy_fuelgauge->get_property(
+ chg->pdata->psy_fuelgauge, psp, val) < 0)
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int s3c_usb_get_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct chg_data *chg = container_of(ps, struct chg_data, psy_usb);
+
+ if (psp != POWER_SUPPLY_PROP_ONLINE)
+ return -EINVAL;
+
+ /* Set enable=1 only if the USB charger is connected */
+ val->intval = ((chg->cable_status == CABLE_TYPE_USB) &&
+ max8998_check_vdcin(chg));
+
+ return 0;
+}
+
+static int s3c_ac_get_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct chg_data *chg = container_of(ps, struct chg_data, psy_ac);
+
+ if (psp != POWER_SUPPLY_PROP_ONLINE)
+ return -EINVAL;
+
+ /* Set enable=1 only if the AC charger is connected */
+ val->intval = ((chg->cable_status == CABLE_TYPE_AC) &&
+ max8998_check_vdcin(chg));
+
+ return 0;
+}
+
+static int s3c_bat_get_adc_data(enum adc_channel_type adc_ch)
+{
+ int adc_data;
+ int adc_max = 0;
+ int adc_min = 0;
+ int adc_total = 0;
+ int i;
+
+ for (i = 0; i < ADC_DATA_ARR_SIZE; i++) {
+ adc_data = s3c_adc_get_adc_data(adc_ch);
+
+ 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_DATA_ARR_SIZE - 2);
+}
+
+static unsigned long calculate_average_adc(enum adc_channel_type channel,
+ int adc, struct chg_data *chg)
+{
+ unsigned int cnt = 0;
+ int total_adc = 0;
+ int average_adc = 0;
+ int index = 0;
+
+ cnt = chg->adc_sample[channel].cnt;
+ total_adc = chg->adc_sample[channel].total_adc;
+
+ if (adc <= 0) {
+ pr_err("%s : invalid adc : %d\n", __func__, adc);
+ adc = chg->adc_sample[channel].average_adc;
+ }
+
+ if (cnt < ADC_TOTAL_COUNT) {
+ chg->adc_sample[channel].adc_arr[cnt] = adc;
+ chg->adc_sample[channel].index = cnt;
+ chg->adc_sample[channel].cnt = ++cnt;
+
+ total_adc += adc;
+ average_adc = total_adc / cnt;
+ } else {
+ index = chg->adc_sample[channel].index;
+ if (++index >= ADC_TOTAL_COUNT)
+ index = 0;
+
+ total_adc = total_adc - chg->adc_sample[channel].adc_arr[index]
+ + adc;
+ average_adc = total_adc / ADC_TOTAL_COUNT;
+
+ chg->adc_sample[channel].adc_arr[index] = adc;
+ chg->adc_sample[channel].index = index;
+ }
+
+ chg->adc_sample[channel].total_adc = total_adc;
+ chg->adc_sample[channel].average_adc = average_adc;
+
+ chg->bat_info.batt_temp_adc = average_adc;
+
+ return average_adc;
+}
+
+static unsigned long s3c_read_temp(struct chg_data *chg)
+{
+ int adc = 0;
+
+ adc = s3c_bat_get_adc_data(S3C_ADC_TEMPERATURE);
+
+ return calculate_average_adc(S3C_ADC_TEMPERATURE, adc, chg);
+}
+
+static int s3c_get_bat_temp(struct chg_data *chg)
+{
+ int temp = 0;
+ int temp_adc = s3c_read_temp(chg);
+ int health = chg->bat_info.batt_health;
+ int left_side = 0;
+ int right_side = chg->pdata->adc_array_size - 1;
+ int mid;
+
+ while (left_side <= right_side) {
+ mid = (left_side + right_side) / 2 ;
+ if (mid == 0 || mid == chg->pdata->adc_array_size - 1 ||
+ (chg->pdata->adc_table[mid].adc_value <= temp_adc &&
+ chg->pdata->adc_table[mid+1].adc_value > temp_adc)) {
+ temp = chg->pdata->adc_table[mid].temperature;
+ break;
+ } else if (temp_adc - chg->pdata->adc_table[mid].adc_value > 0)
+ left_side = mid + 1;
+ else
+ right_side = mid - 1;
+ }
+
+ chg->bat_info.batt_temp = temp;
+ if (temp >= HIGH_BLOCK_TEMP) {
+ if (health != POWER_SUPPLY_HEALTH_OVERHEAT &&
+ health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ chg->bat_info.batt_health =
+ POWER_SUPPLY_HEALTH_OVERHEAT;
+ } else if (temp <= HIGH_RECOVER_TEMP && temp >= LOW_RECOVER_TEMP) {
+ if (health == POWER_SUPPLY_HEALTH_OVERHEAT ||
+ health == POWER_SUPPLY_HEALTH_COLD)
+ chg->bat_info.batt_health =
+ POWER_SUPPLY_HEALTH_GOOD;
+ } else if (temp <= LOW_BLOCK_TEMP) {
+ if (health != POWER_SUPPLY_HEALTH_COLD &&
+ health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)
+ chg->bat_info.batt_health =
+ POWER_SUPPLY_HEALTH_COLD;
+ }
+
+ pr_debug("%s : temp = %d, adc = %d\n", __func__, temp, temp_adc);
+
+ return temp;
+}
+
+static void s3c_bat_discharge_reason(struct chg_data *chg)
+{
+ int discharge_reason;
+ ktime_t ktime;
+ struct timespec cur_time;
+ union power_supply_propval value;
+
+ if (chg->pdata && chg->pdata->psy_fuelgauge &&
+ chg->pdata->psy_fuelgauge->get_property) {
+ chg->pdata->psy_fuelgauge->get_property(chg->pdata->psy_fuelgauge,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW, &value);
+ chg->bat_info.batt_vcell = value.intval;
+
+ chg->pdata->psy_fuelgauge->get_property(chg->pdata->psy_fuelgauge,
+ POWER_SUPPLY_PROP_CAPACITY, &value);
+ chg->bat_info.batt_soc = value.intval;
+ }
+
+ discharge_reason = chg->bat_info.dis_reason & 0xf;
+
+ if (discharge_reason & DISCONNECT_BAT_FULL &&
+ chg->bat_info.batt_vcell < RECHARGE_COND_VOLTAGE)
+ chg->bat_info.dis_reason &= ~DISCONNECT_BAT_FULL;
+
+ if (discharge_reason & DISCONNECT_TEMP_OVERHEAT &&
+ chg->bat_info.batt_temp <=
+ HIGH_RECOVER_TEMP)
+ chg->bat_info.dis_reason &= ~DISCONNECT_TEMP_OVERHEAT;
+
+ if (discharge_reason & DISCONNECT_TEMP_FREEZE &&
+ chg->bat_info.batt_temp >=
+ LOW_RECOVER_TEMP)
+ chg->bat_info.dis_reason &= ~DISCONNECT_TEMP_FREEZE;
+
+ if (discharge_reason & DISCONNECT_OVER_TIME &&
+ chg->bat_info.batt_vcell < RECHARGE_COND_VOLTAGE)
+ chg->bat_info.dis_reason &= ~DISCONNECT_OVER_TIME;
+
+ if (chg->set_batt_full)
+ chg->bat_info.dis_reason |= DISCONNECT_BAT_FULL;
+
+ if (chg->bat_info.batt_health != POWER_SUPPLY_HEALTH_GOOD)
+ chg->bat_info.dis_reason |= chg->bat_info.batt_health ==
+ POWER_SUPPLY_HEALTH_OVERHEAT ?
+ DISCONNECT_TEMP_OVERHEAT : DISCONNECT_TEMP_FREEZE;
+
+ ktime = alarm_get_elapsed_realtime();
+ cur_time = ktime_to_timespec(ktime);
+
+ if (chg->discharging_time &&
+ cur_time.tv_sec > chg->discharging_time) {
+ chg->set_charge_timeout = true;
+ chg->bat_info.dis_reason |= DISCONNECT_OVER_TIME;
+ }
+
+ pr_debug("%s : Current Voltage : %d\n \
+ Current time : %ld discharging_time : %ld\n \
+ discharging reason : %d\n", \
+ __func__, chg->bat_info.batt_vcell, cur_time.tv_sec,
+ chg->discharging_time, chg->bat_info.dis_reason);
+}
+
+static int max8998_charging_control(struct chg_data *chg)
+{
+ int ret;
+ struct i2c_client *i2c = chg->iodev->i2c;
+
+ if (!chg->charging) {
+ /* disable charging */
+ ret = max8998_update_reg(i2c, MAX8998_REG_CHGR2,
+ (1 << MAX8998_SHIFT_CHGEN), MAX8998_MASK_CHGEN);
+ if (ret < 0)
+ goto err;
+
+ pr_debug("%s : charging disabled", __func__);
+ } else {
+ /* enable charging */
+ if (chg->cable_status == CABLE_TYPE_AC) {
+ /* ac */
+ ret = max8998_write_reg(i2c, MAX8998_REG_CHGR1,
+ (2 << MAX8998_SHIFT_TOPOFF) |
+ (3 << MAX8998_SHIFT_RSTR) |
+ (5 << MAX8998_SHIFT_ICHG));
+ if (ret < 0)
+ goto err;
+
+ pr_debug("%s : TA charging enabled", __func__);
+ } else {
+ /* usb */
+ ret = max8998_write_reg(i2c, MAX8998_REG_CHGR1,
+ (6 << MAX8998_SHIFT_TOPOFF) |
+ (3 << MAX8998_SHIFT_RSTR) |
+ (2 << MAX8998_SHIFT_ICHG));
+ if (ret < 0)
+ goto err;
+
+ pr_debug("%s : USB charging enabled", __func__);
+ }
+
+ ret = max8998_write_reg(i2c, MAX8998_REG_CHGR2,
+ (2 << MAX8998_SHIFT_ESAFEOUT) |
+ (2 << MAX8998_SHIFT_FT) |
+ (0 << MAX8998_SHIFT_CHGEN));
+ if (ret < 0)
+ goto err;
+ }
+
+ return 0;
+err:
+ pr_err("max8998_read_reg error\n");
+ return ret;
+}
+
+static int s3c_cable_status_update(struct chg_data *chg)
+{
+ int ret;
+ bool vdc_status;
+ ktime_t ktime;
+ struct timespec cur_time;
+
+ /* if max8998 has detected vdcin */
+ if (max8998_check_vdcin(chg)) {
+ vdc_status = 1;
+ if (chg->bat_info.dis_reason) {
+ pr_info("%s : battery status discharging : %d\n",
+ __func__, chg->bat_info.dis_reason);
+ /* have vdcin, but cannot charge */
+ chg->charging = false;
+ ret = max8998_charging_control(chg);
+ if (ret < 0)
+ goto err;
+ chg->bat_info.charging_status =
+ chg->bat_info.batt_is_full ?
+ POWER_SUPPLY_STATUS_FULL :
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ chg->discharging_time = 0;
+ chg->set_batt_full = 0;
+ goto update;
+ } else if (chg->discharging_time == 0) {
+ ktime = alarm_get_elapsed_realtime();
+ cur_time = ktime_to_timespec(ktime);
+ chg->discharging_time =
+ chg->bat_info.batt_is_full ||
+ chg->set_charge_timeout ?
+ cur_time.tv_sec + TOTAL_RECHARGING_TIME :
+ cur_time.tv_sec + TOTAL_CHARGING_TIME;
+ }
+
+ /* able to charge */
+ chg->charging = true;
+ ret = max8998_charging_control(chg);
+ if (ret < 0)
+ goto err;
+
+ chg->bat_info.charging_status = chg->bat_info.batt_is_full ?
+ POWER_SUPPLY_STATUS_FULL : POWER_SUPPLY_STATUS_CHARGING;
+
+ } else {
+ /* no vdc in, not able to charge */
+ vdc_status = 0;
+ chg->charging = false;
+ ret = max8998_charging_control(chg);
+ if (ret < 0)
+ goto err;
+
+ chg->bat_info.charging_status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ chg->bat_info.batt_is_full = false;
+ chg->set_charge_timeout = false;
+ chg->set_batt_full = 0;
+ chg->bat_info.dis_reason = 0;
+ chg->discharging_time = 0;
+
+ if (lpm_charging_mode && pm_power_off)
+ pm_power_off();
+ }
+
+update:
+ if ((chg->cable_status == CABLE_TYPE_USB) && vdc_status)
+ wake_lock(&chg->vbus_wake_lock);
+ else
+ wake_lock_timeout(&chg->vbus_wake_lock, HZ / 2);
+
+ return 0;
+err:
+ return ret;
+}
+
+static void s3c_program_alarm(struct chg_data *chg, int seconds)
+{
+ ktime_t low_interval = ktime_set(seconds - 10, 0);
+ ktime_t slack = ktime_set(20, 0);
+ ktime_t next;
+
+ next = ktime_add(chg->last_poll, low_interval);
+ alarm_start_range(&chg->alarm, next, ktime_add(next, slack));
+}
+
+static void s3c_bat_work(struct work_struct *work)
+{
+ struct chg_data *chg =
+ container_of(work, struct chg_data, bat_work);
+ int ret;
+ struct timespec ts;
+ unsigned long flags;
+ mutex_lock(&chg->mutex);
+
+ s3c_get_bat_temp(chg);
+ s3c_bat_discharge_reason(chg);
+
+ ret = s3c_cable_status_update(chg);
+ if (ret < 0)
+ goto err;
+
+ mutex_unlock(&chg->mutex);
+
+ power_supply_changed(&chg->psy_bat);
+
+ chg->last_poll = alarm_get_elapsed_realtime();
+ ts = ktime_to_timespec(chg->last_poll);
+ chg->timestamp = ts.tv_sec;
+
+ /* prevent suspend before starting the alarm */
+ local_irq_save(flags);
+ wake_unlock(&chg->work_wake_lock);
+ s3c_program_alarm(chg, FAST_POLL);
+ local_irq_restore(flags);
+ return;
+err:
+ mutex_unlock(&chg->mutex);
+ wake_unlock(&chg->work_wake_lock);
+ pr_err("battery workqueue fail\n");
+}
+
+static void s3c_battery_alarm(struct alarm *alarm)
+{
+ struct chg_data *chg =
+ container_of(alarm, struct chg_data, alarm);
+
+ wake_lock(&chg->work_wake_lock);
+ queue_work(chg->monitor_wqueue, &chg->bat_work);
+}
+
+
+static ssize_t s3c_bat_show_attrs(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct chg_data *chg = container_of(psy, struct chg_data, psy_bat);
+ int i = 0;
+ const ptrdiff_t off = attr - s3c_battery_attrs;
+ union power_supply_propval value;
+
+ switch (off) {
+ case CHARGING_MODE_BOOTING:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", lpm_charging_mode);
+ break;
+ case BATT_TEMP_CHECK:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", chg->bat_info.batt_health);
+ break;
+ case BATT_FULL_CHECK:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", chg->bat_info.batt_is_full);
+ break;
+ default:
+ i = -EINVAL;
+ }
+
+ return i;
+}
+
+static ssize_t s3c_bat_store_attrs(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+// struct power_supply *psy = dev_get_drvdata(dev);
+// struct chg_data *chg = container_of(psy, struct chg_data, psy_bat);
+ int x = 0;
+ int ret = 0;
+ const ptrdiff_t off = attr - s3c_battery_attrs;
+
+ switch (off) {
+ case CHARGING_MODE_BOOTING:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ lpm_charging_mode = x;
+ ret = count;
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int s3c_bat_create_attrs(struct device *dev)
+{
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(s3c_battery_attrs); i++) {
+ rc = device_create_file(dev, &s3c_battery_attrs[i]);
+ if (rc)
+ goto s3c_attrs_failed;
+ }
+ goto succeed;
+
+s3c_attrs_failed:
+ while (i--)
+ device_remove_file(dev, &s3c_battery_attrs[i]);
+succeed:
+ return rc;
+}
+
+static irqreturn_t max8998_int_work_func(int irq, void *max8998_chg)
+{
+ int ret;
+ u8 data = 0;
+ struct chg_data *chg;
+ struct i2c_client *i2c;
+
+ chg = max8998_chg;
+ i2c = chg->iodev->i2c;
+
+ ret = max8998_read_reg(i2c, MAX8998_REG_IRQ1, &data);
+ if (ret < 0)
+ goto err;
+
+ ret = max8998_read_reg(i2c, MAX8998_REG_IRQ3, &data);
+ if (ret < 0)
+ goto err;
+
+ if ((data & 0x4) || (ret != 0)) {
+ pr_info("%s : pmic interrupt\n", __func__);
+ chg->set_batt_full = 1;
+ chg->bat_info.batt_is_full = true;
+ }
+
+ wake_lock(&chg->work_wake_lock);
+ queue_work(chg->monitor_wqueue, &chg->bat_work);
+
+ return IRQ_HANDLED;
+err:
+ pr_err("%s : pmic read error\n", __func__);
+ return IRQ_HANDLED;
+}
+
+static __devinit int max8998_charger_probe(struct platform_device *pdev)
+{
+ struct max8998_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+ struct max8998_platform_data *pdata = dev_get_platdata(iodev->dev);
+ struct chg_data *chg;
+ struct i2c_client *i2c = iodev->i2c;
+ int ret = 0;
+
+ pr_info("%s : MAX8998 Charger Driver Loading\n", __func__);
+
+ chg = kzalloc(sizeof(*chg), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ chg->iodev = iodev;
+ chg->pdata = pdata->charger;
+
+ if (!chg->pdata || !chg->pdata->adc_table) {
+ pr_err("%s : No platform data & adc_table supplied\n", __func__);
+ ret = -EINVAL;
+ goto err_bat_table;
+ }
+
+ chg->psy_bat.name = "battery",
+ chg->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY,
+ chg->psy_bat.properties = max8998_battery_props,
+ chg->psy_bat.num_properties = ARRAY_SIZE(max8998_battery_props),
+ chg->psy_bat.get_property = s3c_bat_get_property,
+
+ chg->psy_usb.name = "usb",
+ chg->psy_usb.type = POWER_SUPPLY_TYPE_USB,
+ chg->psy_usb.supplied_to = supply_list,
+ chg->psy_usb.num_supplicants = ARRAY_SIZE(supply_list),
+ chg->psy_usb.properties = s3c_power_properties,
+ chg->psy_usb.num_properties = ARRAY_SIZE(s3c_power_properties),
+ chg->psy_usb.get_property = s3c_usb_get_property,
+
+ chg->psy_ac.name = "ac",
+ chg->psy_ac.type = POWER_SUPPLY_TYPE_MAINS,
+ chg->psy_ac.supplied_to = supply_list,
+ chg->psy_ac.num_supplicants = ARRAY_SIZE(supply_list),
+ chg->psy_ac.properties = s3c_power_properties,
+ chg->psy_ac.num_properties = ARRAY_SIZE(s3c_power_properties),
+ chg->psy_ac.get_property = s3c_ac_get_property,
+
+ chg->present = 1;
+ chg->polling_interval = POLLING_INTERVAL;
+ chg->bat_info.batt_health = POWER_SUPPLY_HEALTH_GOOD;
+ chg->bat_info.batt_is_full = false;
+ chg->set_charge_timeout = false;
+
+ chg->cable_status = CABLE_TYPE_NONE;
+
+ mutex_init(&chg->mutex);
+
+ platform_set_drvdata(pdev, chg);
+
+ ret = max8998_update_reg(i2c, MAX8998_REG_CHGR1, /* disable */
+ (0x3 << MAX8998_SHIFT_RSTR), MAX8998_MASK_RSTR);
+ if (ret < 0)
+ goto err_kfree;
+
+ ret = max8998_update_reg(i2c, MAX8998_REG_CHGR2, /* 6 Hr */
+ (0x2 << MAX8998_SHIFT_FT), MAX8998_MASK_FT);
+ if (ret < 0)
+ goto err_kfree;
+
+ ret = max8998_update_reg(i2c, MAX8998_REG_CHGR2, /* 4.2V */
+ (0x0 << MAX8998_SHIFT_BATTSL), MAX8998_MASK_BATTSL);
+ if (ret < 0)
+ goto err_kfree;
+
+ ret = max8998_update_reg(i2c, MAX8998_REG_CHGR2, /* 105c */
+ (0x0 << MAX8998_SHIFT_TMP), MAX8998_MASK_TMP);
+ if (ret < 0)
+ goto err_kfree;
+
+ pr_info("%s : pmic interrupt registered\n", __func__);
+ ret = max8998_write_reg(i2c, MAX8998_REG_IRQM1,
+ ~(MAX8998_MASK_DCINR | MAX8998_MASK_DCINF));
+ if (ret < 0)
+ goto err_kfree;
+
+ ret = max8998_write_reg(i2c, MAX8998_REG_IRQM2, 0xFF);
+ if (ret < 0)
+ goto err_kfree;
+
+ ret = max8998_write_reg(i2c, MAX8998_REG_IRQM3, ~0x4);
+ if (ret < 0)
+ goto err_kfree;
+
+ ret = max8998_write_reg(i2c, MAX8998_REG_IRQM4, 0xFF);
+ if (ret < 0)
+ goto err_kfree;
+
+ wake_lock_init(&chg->vbus_wake_lock, WAKE_LOCK_SUSPEND,
+ "vbus_present");
+ wake_lock_init(&chg->work_wake_lock, WAKE_LOCK_SUSPEND,
+ "max8998-charger");
+
+ INIT_WORK(&chg->bat_work, s3c_bat_work);
+
+ chg->monitor_wqueue =
+ create_freezable_workqueue(dev_name(&pdev->dev));
+ if (!chg->monitor_wqueue) {
+ pr_err("Failed to create freezeable workqueue\n");
+ ret = -ENOMEM;
+ goto err_wake_lock;
+ }
+
+ chg->last_poll = alarm_get_elapsed_realtime();
+ alarm_init(&chg->alarm, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP,
+ s3c_battery_alarm);
+
+ check_lpm_charging_mode(chg);
+
+ /* 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_wqueue;
+ }
+
+ ret = power_supply_register(&pdev->dev, &chg->psy_usb);
+ if (ret) {
+ pr_err("Failed to register power supply psy_usb\n");
+ goto err_supply_unreg_bat;
+ }
+
+ ret = power_supply_register(&pdev->dev, &chg->psy_ac);
+ if (ret) {
+ pr_err("Failed to register power supply psy_ac\n");
+ goto err_supply_unreg_usb;
+ }
+
+ ret = request_threaded_irq(iodev->i2c->irq, NULL,
+ max8998_int_work_func,
+ IRQF_TRIGGER_FALLING, "max8998-charger", chg);
+ if (ret) {
+ pr_err("%s : Failed to request pmic irq\n", __func__);
+ goto err_supply_unreg_ac;
+ }
+
+ ret = enable_irq_wake(iodev->i2c->irq);
+ if (ret) {
+ pr_err("Failed to enable pmic irq wake\n");
+ goto err_irq;
+ }
+
+ ret = s3c_bat_create_attrs(chg->psy_bat.dev);
+ if (ret) {
+ pr_err("%s : Failed to create_attrs\n", __func__);
+ goto err_irq;
+ }
+
+ chg->callbacks.set_cable = max8998_set_cable;
+ if (chg->pdata->register_callbacks)
+ chg->pdata->register_callbacks(&chg->callbacks);
+
+ wake_lock(&chg->work_wake_lock);
+ queue_work(chg->monitor_wqueue, &chg->bat_work);
+
+ return 0;
+
+err_irq:
+ free_irq(iodev->i2c->irq, NULL);
+err_supply_unreg_ac:
+ power_supply_unregister(&chg->psy_ac);
+err_supply_unreg_usb:
+ power_supply_unregister(&chg->psy_usb);
+err_supply_unreg_bat:
+ power_supply_unregister(&chg->psy_bat);
+err_wqueue:
+ destroy_workqueue(chg->monitor_wqueue);
+ cancel_work_sync(&chg->bat_work);
+ alarm_cancel(&chg->alarm);
+err_wake_lock:
+ wake_lock_destroy(&chg->work_wake_lock);
+ wake_lock_destroy(&chg->vbus_wake_lock);
+err_kfree:
+ mutex_destroy(&chg->mutex);
+err_bat_table:
+ kfree(chg);
+ return ret;
+}
+
+static int __devexit max8998_charger_remove(struct platform_device *pdev)
+{
+ struct chg_data *chg = platform_get_drvdata(pdev);
+
+ alarm_cancel(&chg->alarm);
+ free_irq(chg->iodev->i2c->irq, NULL);
+ flush_workqueue(chg->monitor_wqueue);
+ destroy_workqueue(chg->monitor_wqueue);
+ power_supply_unregister(&chg->psy_bat);
+ power_supply_unregister(&chg->psy_usb);
+ power_supply_unregister(&chg->psy_ac);
+
+ wake_lock_destroy(&chg->vbus_wake_lock);
+ mutex_destroy(&chg->mutex);
+ kfree(chg);
+
+ return 0;
+}
+
+static int max8998_charger_suspend(struct device *dev)
+{
+
+ struct chg_data *chg = dev_get_drvdata(dev);
+ if (!chg->charging) {
+ s3c_program_alarm(chg, SLOW_POLL);
+ chg->slow_poll = 1;
+ }
+
+ return 0;
+}
+
+static void max8998_charger_resume(struct device *dev)
+{
+
+ struct chg_data *chg = dev_get_drvdata(dev);
+ /* We might be on a slow sample cycle. If we're
+ * resuming we should resample the battery state
+ * if it's been over a minute since we last did
+ * so, and move back to sampling every minute until
+ * we suspend again.
+ */
+ if (chg->slow_poll) {
+ s3c_program_alarm(chg, FAST_POLL);
+ chg->slow_poll = 0;
+ }
+}
+
+static const struct dev_pm_ops max8998_charger_pm_ops = {
+ .prepare = max8998_charger_suspend,
+ .complete = max8998_charger_resume,
+};
+
+static struct platform_driver max8998_charger_driver = {
+ .driver = {
+ .name = "max8998-charger",
+ .owner = THIS_MODULE,
+ .pm = &max8998_charger_pm_ops,
+ },
+ .probe = max8998_charger_probe,
+ .remove = __devexit_p(max8998_charger_remove),
+};
+
+static int __init max8998_charger_init(void)
+{
+ return platform_driver_register(&max8998_charger_driver);
+}
+
+static void __exit max8998_charger_exit(void)
+{
+ platform_driver_register(&max8998_charger_driver);
+}
+
+late_initcall(max8998_charger_init);
+module_exit(max8998_charger_exit);
+
+MODULE_AUTHOR("Minsung Kim <ms925.kim@samsung.com>");
+MODULE_DESCRIPTION("S3C6410 battery driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/s5pc110_battery.h b/drivers/power/s5pc110_battery.h
new file mode 100644
index 0000000..b3e002f
--- /dev/null
+++ b/drivers/power/s5pc110_battery.h
@@ -0,0 +1,91 @@
+/*
+ * linux/drivers/power/s3c6410_battery.h
+ *
+ * Battery measurement code for S3C6410 platform.
+ *
+ * Copyright (C) 2009 Samsung Electronics.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#define DRIVER_NAME "sec-battery"
+
+/*
+ * Battery Table
+ */
+#define BATT_CAL 2447 /* 3.60V */
+
+#define BATT_MAXIMUM 406 /* 4.176V */
+#define BATT_FULL 353 /* 4.10V */
+#define BATT_SAFE_RECHARGE 353 /* 4.10V */
+#define BATT_ALMOST_FULL 188 /* 3.8641V */
+#define BATT_HIGH 112 /* 3.7554V */
+#define BATT_MED 66 /* 3.6907V */
+#define BATT_LOW 43 /* 3.6566V */
+#define BATT_CRITICAL 8 /* 3.6037V */
+#define BATT_MINIMUM (-28) /* 3.554V */
+#define BATT_OFF (-128) /* 3.4029V */
+
+/*
+ * ADC channel
+ */
+enum adc_channel_type{
+ S3C_ADC_VOLTAGE = 0,
+ S3C_ADC_CHG_CURRENT = 2,
+ S3C_ADC_EAR = 3,
+ S3C_ADC_TEMPERATURE = 6,
+ S3C_ADC_V_F,
+ ENDOFADC
+};
+
+enum {
+ BATT_VOL = 0,
+ BATT_VOL_ADC,
+ BATT_VOL_ADC_CAL,
+ BATT_TEMP,
+ BATT_TEMP_ADC,
+ BATT_TEMP_ADC_CAL,
+ BATT_VOL_ADC_AVER,
+ BATT_CHARGING_SOURCE,
+ BATT_VIBRATOR,
+ BATT_CAMERA,
+ BATT_MP3,
+ BATT_VIDEO,
+ BATT_VOICE_CALL_2G,
+ BATT_VOICE_CALL_3G,
+ BATT_DATA_CALL,
+ BATT_DEV_STATE,
+ BATT_COMPENSATION,
+ BATT_BOOTING,
+ BATT_FG_SOC,
+ BATT_RESET_SOC,
+};
+
+enum {
+ CHARGING_MODE_BOOTING,
+ BATT_TEMP_CHECK,
+ BATT_FULL_CHECK,
+};
+
+#define TOTAL_CHARGING_TIME (6*60*60) /* 6 hours */
+#define TOTAL_RECHARGING_TIME (90*60) /* 1.5 hours */
+
+#define COMPENSATE_VIBRATOR 19
+#define COMPENSATE_CAMERA 25
+#define COMPENSATE_MP3 17
+#define COMPENSATE_VIDEO 28
+#define COMPENSATE_VOICE_CALL_2G 13
+#define COMPENSATE_VOICE_CALL_3G 14
+#define COMPENSATE_DATA_CALL 25
+#define COMPENSATE_LCD 0
+#define COMPENSATE_TA 0
+#define COMPENSATE_CAM_FALSH 0
+#define COMPENSATE_BOOTING 52
+
+#define SOC_LB_FOR_POWER_OFF 27
+
+#define RECHARGE_COND_VOLTAGE 4130000
+#define RECHARGE_COND_TIME (30*1000) /* 30 seconds */