diff options
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/Kconfig | 12 | ||||
-rw-r--r-- | drivers/power/Makefile | 2 | ||||
-rw-r--r-- | drivers/power/fuel_gauge.c | 204 | ||||
-rwxr-xr-x[-rw-r--r--] | drivers/power/max17040_battery.c | 112 | ||||
-rw-r--r-- | drivers/power/pda_power.c | 71 | ||||
-rw-r--r-- | drivers/power/power_supply_core.c | 30 | ||||
-rw-r--r-- | drivers/power/s3c_fake_battery.c | 576 | ||||
-rwxr-xr-x | drivers/power/s5pc110_battery.c | 1041 | ||||
-rw-r--r-- | drivers/power/s5pc110_battery.h | 91 |
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 */ |