aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/power/samsung_fake_battery.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/samsung_fake_battery.c')
-rw-r--r--drivers/power/samsung_fake_battery.c546
1 files changed, 546 insertions, 0 deletions
diff --git a/drivers/power/samsung_fake_battery.c b/drivers/power/samsung_fake_battery.c
new file mode 100644
index 0000000..d64ee99
--- /dev/null
+++ b/drivers/power/samsung_fake_battery.c
@@ -0,0 +1,546 @@
+/*
+ * linux/drivers/power/samsung_fake_battery.c
+ *
+ * Battery measurement code for samsung smdk platform.
+ *
+ * Copyright (C) 2011 Samsung Electronics.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/wakelock.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <plat/gpio-cfg.h>
+
+#define FAKE_BAT_LEVEL 80
+
+static struct wake_lock vbus_wake_lock;
+
+/* Prototypes */
+static ssize_t samsung_fake_bat_show_property(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+static ssize_t samsung_fake_bat_store_property(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+
+static struct device *dev;
+static int samsung_fake_battery_initial;
+
+static char *status_text[] = {
+ [POWER_SUPPLY_STATUS_UNKNOWN] = "Unknown",
+ [POWER_SUPPLY_STATUS_CHARGING] = "Charging",
+ [POWER_SUPPLY_STATUS_DISCHARGING] = "Discharging",
+ [POWER_SUPPLY_STATUS_NOT_CHARGING] = "Not Charging",
+ [POWER_SUPPLY_STATUS_FULL] = "Full",
+};
+
+typedef enum {
+ CHARGER_BATTERY = 0,
+ CHARGER_USB,
+ CHARGER_AC,
+ CHARGER_DISCHARGE
+} charger_type_t;
+
+struct battery_info {
+ u32 batt_id; /* Battery ID from ADC */
+ u32 batt_vol; /* Battery voltage from ADC */
+ u32 batt_vol_adc; /* Battery ADC value */
+ u32 batt_vol_adc_cal; /* Battery ADC value (calibrated)*/
+ u32 batt_temp; /* Battery Temperature (C) from ADC */
+ u32 batt_temp_adc; /* Battery Temperature ADC value */
+ u32 batt_temp_adc_cal; /* Battery Temperature ADC value (calibrated) */
+ u32 batt_current; /* Battery current from ADC */
+ u32 level; /* formula */
+ u32 charging_source; /* 0: no cable, 1:usb, 2:AC */
+ u32 charging_enabled; /* 0: Disable, 1: Enable */
+ u32 batt_health; /* Battery Health (Authority) */
+ u32 batt_is_full; /* 0 : Not full 1: Full */
+};
+
+/* lock to protect the battery info */
+static DEFINE_MUTEX(work_lock);
+
+struct samsung_fake_battery_info {
+ int present;
+ int polling;
+ unsigned long polling_interval;
+
+ struct battery_info bat_info;
+};
+static struct samsung_fake_battery_info samsung_fake_bat_info;
+
+static int samsung_get_bat_level(struct power_supply *bat_ps)
+{
+ return FAKE_BAT_LEVEL;
+}
+
+static int samsung_get_bat_vol(struct power_supply *bat_ps)
+{
+ int bat_vol = 0;
+
+ return bat_vol;
+}
+
+static u32 samsung_get_bat_health(void)
+{
+ return samsung_fake_bat_info.bat_info.batt_health;
+}
+
+static int samsung_get_bat_temp(struct power_supply *bat_ps)
+{
+ int temp = 0;
+
+ return temp;
+}
+
+static int samsung_fake_bat_get_charging_status(void)
+{
+ charger_type_t charger = CHARGER_BATTERY;
+ int ret = 0;
+
+ charger = samsung_fake_bat_info.bat_info.charging_source;
+
+ switch (charger) {
+ case CHARGER_BATTERY:
+ ret = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case CHARGER_USB:
+ case CHARGER_AC:
+ if (samsung_fake_bat_info.bat_info.level == 100 &&
+ samsung_fake_bat_info.bat_info.batt_is_full)
+ ret = POWER_SUPPLY_STATUS_FULL;
+ else
+ ret = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case CHARGER_DISCHARGE:
+ ret = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ default:
+ ret = POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+ dev_dbg(dev, "%s : %s\n", __func__, status_text[ret]);
+
+ return ret;
+}
+
+static int samsung_fake_bat_get_property(struct power_supply *bat_ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ dev_dbg(bat_ps->dev, "%s : psp = %d\n", __func__, psp);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = samsung_fake_bat_get_charging_status();
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = samsung_get_bat_health();
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = samsung_fake_bat_info.present;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = samsung_fake_bat_info.bat_info.level;
+ dev_dbg(dev, "%s : level = %d\n", __func__,
+ val->intval);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = samsung_fake_bat_info.bat_info.batt_temp;
+ dev_dbg(bat_ps->dev, "%s : temp = %d\n", __func__,
+ val->intval);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int samsung_power_get_property(struct power_supply *bat_ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ charger_type_t charger;
+
+ dev_dbg(bat_ps->dev, "%s : psp = %d\n", __func__, psp);
+
+ charger = samsung_fake_bat_info.bat_info.charging_source;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (bat_ps->type == POWER_SUPPLY_TYPE_MAINS)
+ val->intval = (charger == CHARGER_AC ? 1 : 0);
+ else if (bat_ps->type == POWER_SUPPLY_TYPE_USB)
+ val->intval = (charger == CHARGER_USB ? 1 : 0);
+ else
+ val->intval = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#define SAMSUNG_FAKE_BAT_ATTR(_name) \
+{ \
+ .attr = { .name = #_name,}, \
+ .show = samsung_fake_bat_show_property, \
+ .store = samsung_fake_bat_store_property, \
+}
+
+static struct device_attribute samsung_fake_battery_attrs[] = {
+ SAMSUNG_FAKE_BAT_ATTR(batt_vol),
+ SAMSUNG_FAKE_BAT_ATTR(batt_vol_adc),
+ SAMSUNG_FAKE_BAT_ATTR(batt_vol_adc_cal),
+ SAMSUNG_FAKE_BAT_ATTR(batt_temp),
+ SAMSUNG_FAKE_BAT_ATTR(batt_temp_adc),
+ SAMSUNG_FAKE_BAT_ATTR(batt_temp_adc_cal),
+};
+
+enum {
+ BATT_VOL = 0,
+ BATT_VOL_ADC,
+ BATT_VOL_ADC_CAL,
+ BATT_TEMP,
+ BATT_TEMP_ADC,
+ BATT_TEMP_ADC_CAL,
+};
+
+static int samsung_fake_bat_create_attrs(struct device * dev)
+{
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(samsung_fake_battery_attrs); i++) {
+ rc = device_create_file(dev, &samsung_fake_battery_attrs[i]);
+ if (rc)
+ goto attrs_failed;
+ }
+ goto succeed;
+
+attrs_failed:
+ while (i--)
+ device_remove_file(dev, &samsung_fake_battery_attrs[i]);
+succeed:
+ return rc;
+}
+
+static ssize_t samsung_fake_bat_show_property(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i = 0;
+ const ptrdiff_t off = attr - samsung_fake_battery_attrs;
+
+ switch (off) {
+ case BATT_VOL:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ samsung_fake_bat_info.bat_info.batt_vol);
+ break;
+ case BATT_VOL_ADC:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ samsung_fake_bat_info.bat_info.batt_vol_adc);
+ break;
+ case BATT_VOL_ADC_CAL:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ samsung_fake_bat_info.bat_info.batt_vol_adc_cal);
+ break;
+ case BATT_TEMP:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ samsung_fake_bat_info.bat_info.batt_temp);
+ break;
+ case BATT_TEMP_ADC:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ samsung_fake_bat_info.bat_info.batt_temp_adc);
+ break;
+ case BATT_TEMP_ADC_CAL:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ samsung_fake_bat_info.bat_info.batt_temp_adc_cal);
+ break;
+ default:
+ i = -EINVAL;
+ }
+
+ return i;
+}
+
+static ssize_t samsung_fake_bat_store_property(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int x = 0;
+ int ret = 0;
+ const ptrdiff_t off = attr - samsung_fake_battery_attrs;
+
+ switch (off) {
+ case BATT_VOL_ADC_CAL:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ samsung_fake_bat_info.bat_info.batt_vol_adc_cal = x;
+ ret = count;
+ }
+ dev_info(dev, "%s : batt_vol_adc_cal = %d\n", __func__, x);
+ break;
+ case BATT_TEMP_ADC_CAL:
+ if (sscanf(buf, "%d\n", &x) == 1) {
+ samsung_fake_bat_info.bat_info.batt_temp_adc_cal = x;
+ ret = count;
+ }
+ dev_info(dev, "%s : batt_temp_adc_cal = %d\n", __func__, x);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property samsung_fake_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static enum power_supply_property samsung_power_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *supply_list[] = {
+ "battery",
+};
+
+static struct power_supply samsung_power_supplies[] = {
+ {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = samsung_fake_battery_properties,
+ .num_properties = ARRAY_SIZE(samsung_fake_battery_properties),
+ .get_property = samsung_fake_bat_get_property,
+ }, {
+ .name = "usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .supplied_to = supply_list,
+ .num_supplicants = ARRAY_SIZE(supply_list),
+ .properties = samsung_power_properties,
+ .num_properties = ARRAY_SIZE(samsung_power_properties),
+ .get_property = samsung_power_get_property,
+ }, {
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .supplied_to = supply_list,
+ .num_supplicants = ARRAY_SIZE(supply_list),
+ .properties = samsung_power_properties,
+ .num_properties = ARRAY_SIZE(samsung_power_properties),
+ .get_property = samsung_power_get_property,
+ },
+};
+
+static int samsung_cable_status_update(int status)
+{
+ int ret = 0;
+ charger_type_t source = CHARGER_BATTERY;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (!samsung_fake_battery_initial)
+ return -EPERM;
+
+ switch (status) {
+ case CHARGER_BATTERY:
+ dev_dbg(dev, "%s : cable NOT PRESENT\n", __func__);
+ samsung_fake_bat_info.bat_info.charging_source = CHARGER_BATTERY;
+ break;
+ case CHARGER_USB:
+ dev_dbg(dev, "%s : cable USB\n", __func__);
+ samsung_fake_bat_info.bat_info.charging_source = CHARGER_USB;
+ break;
+ case CHARGER_AC:
+ dev_dbg(dev, "%s : cable AC\n", __func__);
+ samsung_fake_bat_info.bat_info.charging_source = CHARGER_AC;
+ break;
+ case CHARGER_DISCHARGE:
+ dev_dbg(dev, "%s : Discharge\n", __func__);
+ samsung_fake_bat_info.bat_info.charging_source = CHARGER_DISCHARGE;
+ break;
+ default:
+ dev_err(dev, "%s : Nat supported status\n", __func__);
+ ret = -EINVAL;
+ }
+ source = samsung_fake_bat_info.bat_info.charging_source;
+
+ if (source == CHARGER_USB || source == CHARGER_AC)
+ wake_lock(&vbus_wake_lock);
+ else
+ wake_lock_timeout(&vbus_wake_lock, HZ / 2);
+
+ /* if the power source changes, all power supplies may change state */
+ power_supply_changed(&samsung_power_supplies[CHARGER_BATTERY]);
+
+ dev_dbg(dev, "%s : call power_supply_changed\n", __func__);
+ return ret;
+}
+
+static void samsung_fake_bat_status_update(struct power_supply *bat_ps)
+{
+ int old_level, old_temp, old_is_full;
+ dev_dbg(dev, "%s ++\n", __func__);
+
+ if (!samsung_fake_battery_initial)
+ return;
+
+ mutex_lock(&work_lock);
+
+ old_temp = samsung_fake_bat_info.bat_info.batt_temp;
+ old_level = samsung_fake_bat_info.bat_info.level;
+ old_is_full = samsung_fake_bat_info.bat_info.batt_is_full;
+ samsung_fake_bat_info.bat_info.batt_temp = samsung_get_bat_temp(bat_ps);
+ samsung_fake_bat_info.bat_info.level = samsung_get_bat_level(bat_ps);
+ samsung_fake_bat_info.bat_info.batt_vol = samsung_get_bat_vol(bat_ps);
+
+ if (old_level != samsung_fake_bat_info.bat_info.level ||
+ old_temp != samsung_fake_bat_info.bat_info.batt_temp ||
+ old_is_full != samsung_fake_bat_info.bat_info.batt_is_full) {
+ power_supply_changed(bat_ps);
+ dev_dbg(dev, "%s : call power_supply_changed\n", __func__);
+ }
+
+ mutex_unlock(&work_lock);
+ dev_dbg(dev, "%s --\n", __func__);
+}
+
+void samsung_cable_check_status(int flag)
+{
+ charger_type_t status = 0;
+
+ if (flag == 0)
+ status = CHARGER_BATTERY;
+ else
+ status = CHARGER_USB;
+
+ samsung_cable_status_update(status);
+}
+EXPORT_SYMBOL(samsung_cable_check_status);
+
+#ifdef CONFIG_PM
+static int samsung_fake_bat_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static void samsung_fake_bat_resume(struct device *dev)
+{
+}
+#else
+#define samsung_fake_bat_suspend NULL
+#define samsung_fake_bat_resume NULL
+#endif /* CONFIG_PM */
+
+static int __devinit samsung_fake_bat_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret = 0;
+
+ dev = &pdev->dev;
+ dev_info(dev, "%s\n", __func__);
+
+ samsung_fake_bat_info.present = 1;
+
+ samsung_fake_bat_info.bat_info.batt_id = 0;
+ samsung_fake_bat_info.bat_info.batt_vol = 0;
+ samsung_fake_bat_info.bat_info.batt_vol_adc = 0;
+ samsung_fake_bat_info.bat_info.batt_vol_adc_cal = 0;
+ samsung_fake_bat_info.bat_info.batt_temp = 0;
+ samsung_fake_bat_info.bat_info.batt_temp_adc = 0;
+ samsung_fake_bat_info.bat_info.batt_temp_adc_cal = 0;
+ samsung_fake_bat_info.bat_info.batt_current = 0;
+ samsung_fake_bat_info.bat_info.level = 0;
+ samsung_fake_bat_info.bat_info.charging_source = CHARGER_BATTERY;
+ samsung_fake_bat_info.bat_info.charging_enabled = 0;
+ samsung_fake_bat_info.bat_info.batt_health = POWER_SUPPLY_HEALTH_GOOD;
+
+ /* init power supplier framework */
+ for (i = 0; i < ARRAY_SIZE(samsung_power_supplies); i++) {
+ ret = power_supply_register(&pdev->dev,
+ &samsung_power_supplies[i]);
+ if (ret) {
+ dev_err(dev, "Failed to register"
+ "power supply %d,%d\n", i, ret);
+ goto __end__;
+ }
+ }
+
+ /* create sec detail attributes */
+ samsung_fake_bat_create_attrs(samsung_power_supplies[CHARGER_BATTERY].dev);
+
+ samsung_fake_battery_initial = 1;
+
+ samsung_fake_bat_status_update(
+ &samsung_power_supplies[CHARGER_BATTERY]);
+
+__end__:
+ return ret;
+}
+
+static int __devexit samsung_fake_bat_remove(struct platform_device *pdev)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(samsung_power_supplies); i++)
+ power_supply_unregister(&samsung_power_supplies[i]);
+
+ return 0;
+}
+
+static const struct dev_pm_ops samsung_fake_bat_pm_ops = {
+ .prepare = samsung_fake_bat_suspend,
+ .complete = samsung_fake_bat_resume,
+};
+
+static struct platform_driver samsung_fake_bat_driver = {
+ .driver = {
+ .name = "samsung-fake-battery",
+ .owner = THIS_MODULE,
+ .pm = &samsung_fake_bat_pm_ops,
+ },
+ .probe = samsung_fake_bat_probe,
+ .remove = __devexit_p(samsung_fake_bat_remove),
+};
+
+static int __init samsung_fake_bat_init(void)
+{
+ wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present");
+
+ return platform_driver_register(&samsung_fake_bat_driver);
+}
+
+static void __exit samsung_fake_bat_exit(void)
+{
+ platform_driver_unregister(&samsung_fake_bat_driver);
+}
+
+module_init(samsung_fake_bat_init);
+module_exit(samsung_fake_bat_exit);
+
+MODULE_AUTHOR("HuiSung Kang <hs1218.kang@samsung.com>");
+MODULE_DESCRIPTION("SAMSUNG Fake battery driver for SMDK Board");
+MODULE_LICENSE("GPL");