From 8aefbe43a7864e611dca9821daec3e10009e7171 Mon Sep 17 00:00:00 2001
From: Mike Westerhof <mwester@dls.net>
Date: Thu, 13 Nov 2008 20:50:55 +0000
Subject: [PATCH] gta01-battery-driver.patch

Adds a simple pass-through battery driver module for the GTA01.
This will simplify user-space by providing the same sysfs API
on both GTA01 and GTA02, and is a first step towards eliminating
the need for APM emulation.

Signed-off-by: Mike Westerhof <mwester@dls.net>
---
 arch/arm/configs/gta02-moredrivers-defconfig |    1 +
 defconfig-gta01                              |    1 +
 defconfig-gta02                              |    1 +
 drivers/i2c/chips/pcf50606.c                 |   96 +++++++++++++++++++++++++
 drivers/power/Kconfig                        |    6 ++
 drivers/power/Makefile                       |    1 +
 drivers/power/gta01_battery.c                |   97 ++++++++++++++++++++++++++
 7 files changed, 203 insertions(+), 0 deletions(-)
 create mode 100644 drivers/power/gta01_battery.c

diff --git a/arch/arm/configs/gta02-moredrivers-defconfig b/arch/arm/configs/gta02-moredrivers-defconfig
index 113eaec..5e1547e 100644
--- a/arch/arm/configs/gta02-moredrivers-defconfig
+++ b/arch/arm/configs/gta02-moredrivers-defconfig
@@ -1060,6 +1060,7 @@ CONFIG_POWER_SUPPLY_DEBUG=y
 CONFIG_PDA_POWER=y
 CONFIG_APM_POWER=y
 # CONFIG_BATTERY_DS2760 is not set
+# CONFIG_BATTERY_GTA01 is not set
 CONFIG_BATTERY_BQ27000_HDQ=y
 CONFIG_GTA02_HDQ=y
 CONFIG_HWMON=y
diff --git a/defconfig-gta01 b/defconfig-gta01
index cecb57f..e2e4330 100644
--- a/defconfig-gta01
+++ b/defconfig-gta01
@@ -1021,6 +1021,7 @@ CONFIG_POWER_SUPPLY=y
 # CONFIG_PDA_POWER is not set
 # CONFIG_APM_POWER is not set
 # CONFIG_BATTERY_DS2760 is not set
+CONFIG_BATTERY_GTA01=y
 CONFIG_BATTERY_BQ27000_HDQ=y
 CONFIG_GTA02_HDQ=y
 # CONFIG_HWMON is not set
diff --git a/defconfig-gta02 b/defconfig-gta02
index 619f7f2..2a6e398 100644
--- a/defconfig-gta02
+++ b/defconfig-gta02
@@ -1021,6 +1021,7 @@ CONFIG_POWER_SUPPLY=y
 # CONFIG_PDA_POWER is not set
 CONFIG_APM_POWER=y
 # CONFIG_BATTERY_DS2760 is not set
+# CONFIG_BATTERY_GTA01 is not set
 CONFIG_BATTERY_BQ27000_HDQ=y
 CONFIG_GTA02_HDQ=y
 # CONFIG_HWMON is not set
diff --git a/drivers/i2c/chips/pcf50606.c b/drivers/i2c/chips/pcf50606.c
index 706ce6d..f585013 100644
--- a/drivers/i2c/chips/pcf50606.c
+++ b/drivers/i2c/chips/pcf50606.c
@@ -50,6 +50,7 @@
 #include <linux/platform_device.h>
 #include <linux/pcf50606.h>
 #include <linux/apm-emulation.h>
+#include <linux/power_supply.h>
 
 #include <asm/mach-types.h>
 #include <asm/arch/gta01.h>
@@ -141,6 +142,12 @@ struct pcf50606_data {
 
 static struct i2c_driver pcf50606_driver;
 
+/* This global is set by the pcf50606 driver to the correct callback
+ * for the gta01 battery driver. */
+int (*pmu_bat_get_property)(struct power_supply *, enum power_supply_property,
+			    union power_supply_propval *);
+EXPORT_SYMBOL(pmu_bat_get_property);
+
 /* This is an ugly construct on how to access the (currently single/global)
  * pcf50606 handle from other code in the kernel.  I didn't really come up with
  * a more decent method of dynamically resolving this */
@@ -1270,6 +1277,92 @@ static void pcf50606_get_power_status(struct apm_power_info *info)
 }
 
 /***********************************************************************
+ * Battery driver interface
+ ***********************************************************************/
+static int pcf50606_bat_get_property(struct power_supply *psy,
+				     enum power_supply_property psp,
+				     union power_supply_propval *val)
+{
+	u_int16_t adc, adc_adcin1;
+	u_int8_t mbcc1, chgmod;
+	struct pcf50606_data *pcf = pcf50606_global;
+	int ret = 0;
+
+	switch (psp) {
+
+	case POWER_SUPPLY_PROP_STATUS:
+		if (!(reg_read(pcf, PCF50606_REG_OOCS) & PCF50606_OOCS_EXTON)) {
+			/* No charger, clearly we're discharging then */
+			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+		} else {
+
+			/* We have a charger present, get charge mode */
+			mbcc1 = reg_read(pcf, PCF50606_REG_MBCC1);
+			chgmod = (mbcc1 & PCF50606_MBCC1_CHGMOD_MASK);
+			switch (chgmod) {
+
+			/* TODO: How to determine POWER_SUPPLY_STATUS_FULL? */
+
+			case PCF50606_MBCC1_CHGMOD_QUAL:
+			case PCF50606_MBCC1_CHGMOD_PRE:
+			case PCF50606_MBCC1_CHGMOD_IDLE:
+				val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+				break;
+
+			case PCF50606_MBCC1_CHGMOD_TRICKLE:
+			case PCF50606_MBCC1_CHGMOD_FAST_CCCV:
+			case PCF50606_MBCC1_CHGMOD_FAST_NOCC:
+			case PCF50606_MBCC1_CHGMOD_FAST_NOCV:
+			case PCF50606_MBCC1_CHGMOD_FAST_SW:
+				val->intval = POWER_SUPPLY_STATUS_CHARGING;
+				break;
+
+			default:
+				val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+				break;
+
+			}
+		}
+
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = 1;   /* Must be, or the magic smoke comes out */
+		break;
+
+	case POWER_SUPPLY_PROP_ONLINE:
+		val->intval = !!(reg_read(pcf, PCF50606_REG_OOCS) &
+				 PCF50606_OOCS_EXTON);
+		break;
+
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		adc = adc_read(pcf, PCF50606_ADCMUX_BATVOLT_RES, NULL);
+		/* (adc * 6000000) / 1024 ==  (adc * 46875) / 8 */
+		val->intval = (adc * 46875) / 8;
+		break;
+
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		adc = adc_read(pcf, PCF50606_ADCMUX_BATVOLT_ADCIN1,
+			       &adc_adcin1);
+		val->intval = adc_to_chg_milliamps(pcf, adc_adcin1, adc) * 1000;
+		break;
+
+	case POWER_SUPPLY_PROP_TEMP:
+		adc = adc_read(pcf, PCF50606_ADCMUX_BATTEMP, NULL);
+		val->intval = rntc_to_temp(adc_to_rntc(pcf, adc)) * 10;
+		break;
+
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = battvolt_scale(pcf50606_battvolt(pcf));
+		break;
+
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+/***********************************************************************
  * RTC
  ***********************************************************************/
 
@@ -1900,6 +1993,7 @@ static int pcf50606_detect(struct i2c_adapter *adapter, int address, int kind)
 	}
 
 	apm_get_power_status = pcf50606_get_power_status;
+	pmu_bat_get_property = pcf50606_bat_get_property;
 
 #ifdef CONFIG_MACH_NEO1973_GTA01
 	if (machine_is_neo1973_gta01()) {
@@ -1962,6 +2056,8 @@ static int pcf50606_detach_client(struct i2c_client *client)
 	struct pcf50606_data *pcf = i2c_get_clientdata(client);
 
 	apm_get_power_status = NULL;
+	pmu_bat_get_property = NULL;
+
 	input_unregister_device(pcf->input_dev);
 
 	if (pcf->pdata->used_features & PCF50606_FEAT_PWM_BL)
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 8c50ecb..470e08c 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -62,5 +62,11 @@ config GTA02_HDQ
 	  on the Neo Freerunner.  You probably want to select
 	  at least BATTERY_BQ27000_HDQ as well
 
+config BATTERY_GTA01
+	tristate "Neo GTA01 battery"
+	depends on MACH_NEO1973_GTA01
+	help
+	  Say Y to enable support for the battery on the Neo GTA01
+
 endif # POWER_SUPPLY
 
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index d7e87ad..2013e89 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -21,5 +21,6 @@ obj-$(CONFIG_BATTERY_DS2760)	+= ds2760_battery.o
 obj-$(CONFIG_BATTERY_PMU)	+= pmu_battery.o
 obj-$(CONFIG_BATTERY_OLPC)	+= olpc_battery.o
 obj-$(CONFIG_BATTERY_BQ27000_HDQ)	+= bq27000_battery.o
+obj-$(CONFIG_BATTERY_GTA01)	+= gta01_battery.o
 
 obj-$(CONFIG_GTA02_HDQ)		+= gta02_hdq.o
diff --git a/drivers/power/gta01_battery.c b/drivers/power/gta01_battery.c
new file mode 100644
index 0000000..5acb45c
--- /dev/null
+++ b/drivers/power/gta01_battery.c
@@ -0,0 +1,97 @@
+/*
+ * Battery driver for the Openmoko GTA01 device, using the pcf50606 chip.
+ *
+ * This is nothing more than a write-thru interface to the real logic,
+ * which is part of the pcf50606.c multifunction chip driver.
+ *	Copyright © 2008  Mike Westerhof <mwester@dls.net>
+ *
+ *
+ * Portions liberally borrowed from olpc_battery.c, copyright below:
+ *	Copyright © 2006  David Woodhouse <dwmw2@infradead.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+
+/*********************************************************************
+ * This global is set by the pcf50606 driver to the correct callback
+ *********************************************************************/
+
+extern int (*pmu_bat_get_property)(struct power_supply *,
+				   enum power_supply_property,
+				   union power_supply_propval *);
+
+
+/*********************************************************************
+ *		Battery properties
+ *********************************************************************/
+static int gta01_bat_get_property(struct power_supply *psy,
+				  enum power_supply_property psp,
+				  union power_supply_propval *val)
+{
+	if (pmu_bat_get_property)
+		return (pmu_bat_get_property)(psy, psp, val);
+	else
+		return -ENODEV;
+}
+
+static enum power_supply_property gta01_bat_props[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_TEMP,
+	POWER_SUPPLY_PROP_CAPACITY,
+};
+
+/*********************************************************************
+ *		Initialisation
+ *********************************************************************/
+
+static struct platform_device *bat_pdev;
+
+static struct power_supply gta01_bat = {
+	.properties = gta01_bat_props,
+	.num_properties = ARRAY_SIZE(gta01_bat_props),
+	.get_property = gta01_bat_get_property,
+	.use_for_apm = 0,  /* pcf50606 driver has its own apm driver */
+};
+
+static int __init gta01_bat_init(void)
+{
+	int ret;
+
+	bat_pdev = platform_device_register_simple("gta01-battery", 0, NULL, 0);
+	if (IS_ERR(bat_pdev))
+		return PTR_ERR(bat_pdev);
+
+	gta01_bat.name = bat_pdev->name;
+
+	ret = power_supply_register(&bat_pdev->dev, &gta01_bat);
+	if (ret)
+		platform_device_unregister(bat_pdev);
+
+	return ret;
+}
+
+static void __exit gta01_bat_exit(void)
+{
+	power_supply_unregister(&gta01_bat);
+	platform_device_unregister(bat_pdev);
+}
+
+module_init(gta01_bat_init);
+module_exit(gta01_bat_exit);
+
+MODULE_AUTHOR("Mike Westerhof <mwester@dls.net>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Battery driver for GTA01");
-- 
1.5.6.5