[v12 9/9] Loongson: YeeLoong: add power_supply based battery driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Wu Zhangjin <wuzhangjin@xxxxxxxxx>

(This patch is one of the "yeeloong platform driver" patchset.)

Changes from old revision:

  o Fixes the bugs
  In the old driver, it didn't show the current charging status and reported
  the wrong full charge and voltage value.

    Capacity = Charge_full_last (Dynamic) / Charge_full_design (Fixed)
    Percentage Charge = Charge_now (Dyn-Dynamic) / Charge_full_last (Dynamic)

Based on the old emulated APM battery driver and the power_supply class,
this patch adds a new battery driver.

References:
1. Documentation/power/power_supply_class.txt
2. drivers/power/

Signed-off-by: Liu Shiwei <liushiwei@xxxxxxxxx>
Signed-off-by: Wu Zhangjin <wuzhangjin@xxxxxxxxx>
---
 drivers/platform/mips/Kconfig           |    1 +
 drivers/platform/mips/yeeloong_laptop.c |  228 +++++++++++++++++++++++++++++++
 2 files changed, 229 insertions(+), 0 deletions(-)

diff --git a/drivers/platform/mips/Kconfig b/drivers/platform/mips/Kconfig
index 01560b0..cdfccea 100644
--- a/drivers/platform/mips/Kconfig
+++ b/drivers/platform/mips/Kconfig
@@ -21,6 +21,7 @@ config LEMOTE_YEELOONG2F
 	select HWMON
 	select VIDEO_OUTPUT_CONTROL
 	select INPUT_SPARSEKMAP
+	select POWER_SUPPLY
 	depends on INPUT
 	help
 	  YeeLoong netbook is a mini laptop made by Lemote, which is basically
diff --git a/drivers/platform/mips/yeeloong_laptop.c b/drivers/platform/mips/yeeloong_laptop.c
index 877257a..ead26e0 100644
--- a/drivers/platform/mips/yeeloong_laptop.c
+++ b/drivers/platform/mips/yeeloong_laptop.c
@@ -19,6 +19,7 @@
 #include <linux/input/sparse-keymap.h>
 #include <linux/interrupt.h>
 #include <linux/delay.h>
+#include <linux/power_supply.h>	/* for AC & Battery subdriver */
 
 #include <cs5536/cs5536.h>
 
@@ -349,6 +350,213 @@ static void yeeloong_hwmon_exit(void)
 	}
 }
 
+/* AC & Battery subdriver */
+
+static struct power_supply yeeloong_ac, yeeloong_bat;
+
+#define AC_OFFLINE          0
+#define AC_ONLINE           1
+
+static int yeeloong_get_ac_props(struct power_supply *psy,
+				enum power_supply_property psp,
+				union power_supply_propval *val)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		val->intval = ((ec_read(REG_BAT_POWER)) & BIT_BAT_POWER_ACIN) ?
+			AC_ONLINE : AC_OFFLINE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static enum power_supply_property yeeloong_ac_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+};
+
+static struct power_supply yeeloong_ac = {
+	.name = "yeeloong-ac",
+	.type = POWER_SUPPLY_TYPE_MAINS,
+	.properties = yeeloong_ac_props,
+	.num_properties = ARRAY_SIZE(yeeloong_ac_props),
+	.get_property = yeeloong_get_ac_props,
+};
+
+#define BAT_CAP_CRITICAL 5
+#define BAT_CAP_HIGH     99
+
+static int yeeloong_bat_get_ex_property(enum power_supply_property psp,
+				     union power_supply_propval *val)
+{
+	int bat_in, curr_cap, cap_level, status, charge, health;
+
+	status = ec_read(REG_BAT_STATUS);
+	bat_in = status & BIT_BAT_STATUS_IN;
+	curr_cap = get_bat_info(RELATIVE_CAP);
+	if (status & BIT_BAT_STATUS_FULL)
+		curr_cap = 100;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = bat_in;
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = curr_cap;
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+		cap_level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+		if (status & BIT_BAT_STATUS_LOW) {
+			cap_level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+			if (curr_cap <= BAT_CAP_CRITICAL)
+				cap_level =
+					POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+		} else if (status & BIT_BAT_STATUS_FULL) {
+			cap_level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+			if (curr_cap >= BAT_CAP_HIGH)
+				cap_level = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
+		} else if (status & BIT_BAT_STATUS_DESTROY)
+			cap_level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+		val->intval = cap_level;
+		break;
+	case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+		/* seconds */
+		val->intval = bat_in ? (curr_cap - 3) * 54 + 142 : 0;
+		break;
+	case POWER_SUPPLY_PROP_STATUS:
+		if (!bat_in)
+			charge = POWER_SUPPLY_STATUS_UNKNOWN;
+		else {
+			if (status & BIT_BAT_STATUS_FULL) {
+				val->intval = POWER_SUPPLY_STATUS_FULL;
+				break;
+			}
+
+			charge = ec_read(REG_BAT_CHARGE);
+			if (charge & FLAG_BAT_CHARGE_DISCHARGE)
+				charge = POWER_SUPPLY_STATUS_DISCHARGING;
+			else if (charge & FLAG_BAT_CHARGE_CHARGE)
+				charge = POWER_SUPPLY_STATUS_CHARGING;
+			else
+				charge = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		}
+		val->intval = charge;
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		if (!bat_in) /* no battery present */
+			health = POWER_SUPPLY_HEALTH_UNKNOWN;
+		else { /* Assume it is good */
+			health = POWER_SUPPLY_HEALTH_GOOD;
+			if (status &
+				(BIT_BAT_STATUS_DESTROY | BIT_BAT_STATUS_LOW))
+				health = POWER_SUPPLY_HEALTH_DEAD;
+			if (ec_read(REG_BAT_CHARGE_STATUS) &
+				BIT_BAT_CHARGE_STATUS_OVERTEMP)
+				health = POWER_SUPPLY_HEALTH_OVERHEAT;
+		}
+		val->intval = health;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_NOW:	/* 1/100(%)*1000 µAh */
+		val->intval = curr_cap * get_bat_info(FULLCHG_CAP) * 10;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int yeeloong_get_bat_props(struct power_supply *psy,
+				     enum power_supply_property psp,
+				     union power_supply_propval *val)
+{
+	switch (psp) {
+	/* Fixed information */
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+		val->intval = get_bat_info(DESIGN_VOL) * 1000;	/* mV -> µV */
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+		val->intval = get_bat_info(DESIGN_CAP) * 1000;	/*mAh -> µAh*/
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_FULL:
+		val->intval = get_bat_info(FULLCHG_CAP) * 1000;	/* µAh */
+		break;
+	case POWER_SUPPLY_PROP_MANUFACTURER:
+		val->strval =
+			(ec_read(REG_BAT_VENDOR) == FLAG_BAT_VENDOR_SANYO) ?
+			"SANYO" : "SIMPLO";
+		break;
+	/* Dynamic information */
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		val->intval = get_battery_current() * 1000;	/* mA -> µA */
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		val->intval = get_battery_voltage() * 1000;	/* mV -> µV */
+		break;
+	case POWER_SUPPLY_PROP_TEMP:
+		val->intval = get_battery_temp();	/* Celcius */
+		break;
+	/* Dynamic but relative information */
+	default:
+		return yeeloong_bat_get_ex_property(psp, val);
+	}
+
+	return 0;
+}
+
+static enum power_supply_property yeeloong_bat_props[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+	POWER_SUPPLY_PROP_CHARGE_FULL,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+	POWER_SUPPLY_PROP_TEMP,
+	POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static struct power_supply yeeloong_bat = {
+	.name = "yeeloong-bat",
+	.type = POWER_SUPPLY_TYPE_BATTERY,
+	.properties = yeeloong_bat_props,
+	.num_properties = ARRAY_SIZE(yeeloong_bat_props),
+	.get_property = yeeloong_get_bat_props,
+};
+
+static int ac_bat_initialized;
+
+static int yeeloong_bat_init(void)
+{
+	int ret;
+
+	ret = power_supply_register(NULL, &yeeloong_ac);
+	if (ret)
+		return ret;
+	ret = power_supply_register(NULL, &yeeloong_bat);
+	if (ret) {
+		power_supply_unregister(&yeeloong_ac);
+		return ret;
+	}
+	ac_bat_initialized = 1;
+
+	return 0;
+}
+
+static void yeeloong_bat_exit(void)
+{
+	ac_bat_initialized = 0;
+
+	power_supply_unregister(&yeeloong_ac);
+	power_supply_unregister(&yeeloong_bat);
+}
+
 /* video output subdriver */
 
 static int lcd_video_output_get(struct output_device *od)
@@ -623,6 +831,15 @@ static int usb0_handler(int status)
 	return status;
 }
 
+static int ac_bat_handler(int status)
+{
+	if (ac_bat_initialized) {
+		power_supply_changed(&yeeloong_ac);
+		power_supply_changed(&yeeloong_bat);
+	}
+	return status;
+}
+
 static void do_event_action(int event)
 {
 	sci_handler handler;
@@ -668,6 +885,9 @@ static void do_event_action(int event)
 	case EVENT_AUDIO_VOLUME:
 		reg = REG_AUDIO_VOLUME;
 		break;
+	case EVENT_AC_BAT:
+		handler = ac_bat_handler;
+		break;
 	default:
 		break;
 	}
@@ -926,6 +1146,13 @@ static int __init yeeloong_init(void)
 		return ret;
 	}
 
+	ret = yeeloong_bat_init();
+	if (ret) {
+		pr_err("Fail to register yeeloong battery driver.\n");
+		yeeloong_bat_exit();
+		return ret;
+	}
+
 	ret = yeeloong_hwmon_init();
 	if (ret) {
 		pr_err("Fail to register yeeloong hwmon driver.\n");
@@ -955,6 +1182,7 @@ static void __exit yeeloong_exit(void)
 	yeeloong_hotkey_exit();
 	yeeloong_vo_exit();
 	yeeloong_hwmon_exit();
+	yeeloong_bat_exit();
 	yeeloong_backlight_exit();
 	platform_driver_unregister(&platform_driver);
 
-- 
1.7.0.1



[Index of Archives]     [Linux MIPS Home]     [LKML Archive]     [Linux ARM Kernel]     [Linux ARM]     [Linux]     [Git]     [Yosemite News]     [Linux SCSI]     [Linux Hams]

  Powered by Linux