rendered paste bodyIndex: blueangel_battery.c
===================================================================
RCS file: /cvs/linux/kernel26/arch/arm/mach-pxa/htcblueangel/blueangel_battery.c,v
retrieving revision 1.5
diff -u -r1.5 blueangel_battery.c
--- blueangel_battery.c 22 Dec 2006 03:57:06 -0000 1.5
+++ blueangel_battery.c 30 Dec 2006 21:16:56 -0000
@@ -1,6 +1,6 @@
/*
* Driver interface to the battery on the HTC Blueangel
-*
+* from H2000
* Use consistent with the GNU GPL is permitted,
* provided that this copyright notice is
* preserved in its entirety in all copies and derived works.
@@ -12,6 +12,8 @@
#include <linux/version.h>
#include <linux/battery.h>
#include <linux/leds.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
#include <linux/soc/asic3_base.h>
#include <linux/platform_device.h>
@@ -23,15 +25,54 @@
#include <linux/soc/tsc2200.h>
+#include "../../../../drivers/w1/w1.h"
+#include "../../../../drivers/w1/slaves/w1_ds2760.h"
+
#ifdef DEBUG
#define dprintk(x...) printk(x)
#else
#define dprintk(x...)
#endif
+#define BATTERY_CHECK_INTERVAL (HZ * 60) /* every 60 seconds */
+
+#define BLUEANGEL_BATTERY_MAX_VOLTAGE 4400 /* My measurements */
+#define BLUEANGEL_BATTERY_MIN_VOLTAGE 0 /* Maybe incorrect */
+#define BLUEANGEL_BATTERY_MAX_CURRENT 950 /* MUST BE MEASURED */
+#define BLUEANGEL_BATTERY_MIN_CURRENT -950 /* Maybe incorrect */
+#define BLUEANGEL_BATTERY_MIN_CHARGE 0 /* Maybe incorrect */
+
+/* DS2760 device */
+struct device *ds2760_dev = NULL;
+
+/* Cache the battery status for this many seconds. */
+#define STATUS_CACHE_TIME (HZ * 1)
+static struct ds2760_status battery_status;
+
+
extern struct platform_device blueangel_tsc2200;
+extern struct platform_device blueangel_asic3;
+
DEFINE_LED_TRIGGER(ledtrig_cradle);
+DEFINE_LED_TRIGGER(ledtrig_phone);
+/* w1_samcop device */
+static struct workqueue_struct *probe_q;
+static struct work_struct probe_work;
+
+/* battery monitor */
+static struct timer_list monitor_timer;
+static struct workqueue_struct *monitor_q;
+static struct work_struct monitor_work;
+
+static int battery_charge_state;
+#define BATTERY_STATE_UNKNOWN 0
+#define BATTERY_DISCHARGING 1
+#define BATTERY_CHARGING 2
+#define BATTERY_FULL 3
+
+/* AC present? */
+static int power_status;
static void bat_tsc2200_start_conv_scan(void)
@@ -47,18 +88,6 @@
TSC2200_CTRLREG_ADC_PV (TSC2200_CTRLREG_ADC_PV_500uS) );
}
-static void bat_tsc2200_start_conv_temp(void)
-{
- tsc2200_write(&blueangel_tsc2200.dev, TSC2200_CTRLREG_REF, 31);
- tsc2200_write(&blueangel_tsc2200.dev, TSC2200_CTRLREG_ADC,
- TSC2200_CTRLREG_ADC_AD3 |
- TSC2200_CTRLREG_ADC_AD1 |
- TSC2200_CTRLREG_ADC_RES (TSC2200_CTRLREG_ADC_RES_12BITP) |
- TSC2200_CTRLREG_ADC_AVG (TSC2200_CTRLREG_ADC_8AVG) |
- TSC2200_CTRLREG_ADC_CL (TSC2200_CTRLREG_ADC_CL_2MHZ_12BIT) |
- TSC2200_CTRLREG_ADC_PV (TSC2200_CTRLREG_ADC_PV_500uS) );
-}
-
static void bat_tsc2200_stop_conv(void)
{
tsc2200_write(&blueangel_tsc2200.dev, TSC2200_CTRLREG_ADC,
@@ -70,6 +99,60 @@
TSC2200_CTRLREG_ADC_PV (TSC2200_CTRLREG_ADC_PV_500uS) );
}
+int blueangel_battery_read_status(void)
+{
+ if (jiffies < (battery_status.update_time + STATUS_CACHE_TIME))
+ return 0;
+
+ if (!ds2760_dev)
+ return 0;
+
+ if (!w1_ds2760_status(ds2760_dev, &battery_status)) {
+ printk("call to w1_ds2760_status failed (0x%08x)\n",
+ (unsigned int)ds2760_dev);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+blueangel_battery_match_callback(struct device *dev, void *data)
+{
+ struct w1_slave *sl;
+
+ if (!(dev->driver && dev->driver->name &&
+ (strcmp(dev->driver->name, "w1_slave_driver") == 0)))
+ return 0;
+
+ sl = container_of(dev, struct w1_slave, dev);
+
+ /* DS2760 w1 slave device names begin with the family number 0x30. */
+ if (strncmp(sl->name, "30-", 3) != 0)
+ return 0;
+
+ return 1;
+}
+
+void
+blueangel_battery_probe_work(void *data)
+{
+ struct bus_type *bus;
+
+ /* Get the battery w1 slave device. */
+ bus = find_bus("w1");
+ if (bus)
+ ds2760_dev = bus_find_device(bus, NULL, NULL,
+ blueangel_battery_match_callback);
+
+ if (!ds2760_dev) {
+ /* No DS2760 device found; try again later. */
+ //queue_delayed_work(probe_q, &probe_work, HZ * 5);
+ return;
+ }
+}
+
+
static int blueangel_battery_get_status(struct battery *bat)
{
dprintk("%s: in.\n", __FUNCTION__);
@@ -108,116 +191,176 @@
static int blueangel_battery_get_voltage(struct battery *bat)
{
int retval;
-
- dprintk("%s: in.\n", __FUNCTION__);
-
- //down_interruptible(&tsc2200_sem);
- //tsc2200_lock(&blueangel_tsc2200.dev);
- bat_tsc2200_start_conv_scan();
-
- while ( !(tsc2200_read(&blueangel_tsc2200.dev, TSC2200_CTRLREG_ADC) & 0x4000) &&
- !tsc2200_dav(&blueangel_tsc2200.dev )) {
- dprintk("B2: %X\n", tsc2200_read(&blueangel_tsc2200.dev, TSC2200_CTRLREG_ADC));
- mdelay(1);
- }
-
- retval = tsc2200_read(&blueangel_tsc2200.dev, TSC2200_DATAREG_AUX2);
-
- bat_tsc2200_stop_conv();
- //tsc2200_unlock(&blueangel_tsc2200.dev);
- //up(&tsc2200_sem);
+ blueangel_battery_read_status();
+ retval = battery_status.voltage_mV;
return retval;
}
static int blueangel_battery_get_temp(struct battery *bat)
{
int retval;
+ blueangel_battery_read_status();
+ retval = battery_status.temp_C;
- dprintk("%s: in.\n", __FUNCTION__);
+ return retval;
+}
+blueangel_battery_get_max_voltage(struct battery *bat)
+{
+ return BLUEANGEL_BATTERY_MAX_VOLTAGE;
+}
- //down_interruptible(&tsc2200_sem);
- //tsc2200_lock(&blueangel_tsc2200.dev);
- bat_tsc2200_start_conv_temp();
+int
+blueangel_battery_get_min_voltage(struct battery *bat)
+{
+ return BLUEANGEL_BATTERY_MIN_VOLTAGE;
+}
- while ( !(tsc2200_read(&blueangel_tsc2200.dev, TSC2200_CTRLREG_ADC) & 0x4000) &&
- !tsc2200_dav(&blueangel_tsc2200.dev)) {
- dprintk("1: %X\n", tsc2200_read(&blueangel_tsc2200.dev, TSC2200_CTRLREG_ADC));
- mdelay(1);
- }
-
- retval = tsc2200_read(&blueangel_tsc2200.dev, TSC2200_DATAREG_TEMP1);
- dprintk("retval=0x%x\n", retval);
-
- bat_tsc2200_stop_conv();
- //tsc2200_unlock(&blueangel_tsc2200.dev);
- //up(&tsc2200_sem);
- dprintk("%s: out\n", __FUNCTION__);
- return retval;
+int
+blueangel_battery_get_max_current(struct battery *bat)
+{
+ return BLUEANGEL_BATTERY_MAX_CURRENT;
+}
+
+int
+blueangel_battery_get_min_current(struct battery *bat)
+{
+ return BLUEANGEL_BATTERY_MIN_CURRENT;
+}
+
+int
+blueangel_battery_get_current(struct battery *bat)
+{
+ blueangel_battery_read_status();
+ return battery_status.current_mA;
+}
+int
+blueangel_battery_get_max_charge(struct battery *bat)
+{
+ blueangel_battery_read_status();
+ return battery_status.rated_capacity;
}
+int
+blueangel_battery_get_min_charge(struct battery *bat)
+{
+ return BLUEANGEL_BATTERY_MIN_CHARGE;
+}
+
+int
+blueangel_battery_get_charge(struct battery *bat)
+{
+ blueangel_battery_read_status();
+ return battery_status.accum_current_mAh;
+}
+
+
struct battery battery_dev = {
- .name = "Main battery",
+ .name = "main",
.id = "battery0",
.get_voltage = blueangel_battery_get_voltage,
- .get_status = blueangel_battery_get_status,
- //.get_temp = blueangel_battery_get_temp,
+ .get_min_voltage = blueangel_battery_get_min_voltage,
+ .get_max_voltage = blueangel_battery_get_max_voltage,
+ .get_current = blueangel_battery_get_current,
+ .get_min_current = blueangel_battery_get_min_current,
+ .get_max_current = blueangel_battery_get_max_current,
+ .get_charge = blueangel_battery_get_charge,
+ .get_min_charge = blueangel_battery_get_min_charge,
+ .get_max_charge = blueangel_battery_get_max_charge,
+ .get_temp = blueangel_battery_get_temp,
+ .get_status = blueangel_battery_get_status,
+
};
struct battery battery_dev1 = {
- .name = "Backup battery",
+ .name = "backup",
.id = "battery1",
.get_voltage = blueangel_battery_get_voltage1,
.get_status = blueangel_battery_get_status,
- //.get_temp = blueangel_battery_get_tempi1,
};
+static irqreturn_t
+blueangel_ac_plug_isr (int isr, void *data)
+{
+
+ /* Update the LED status right away, for timely feedback. */
+ /* Set the proper charging rate. */
+ if (!(asic3_get_gpio_status_d(&blueangel_asic3.dev) & GPIOD_AC_CHARGER_N)) {
+ power_status = APM_AC_ONLINE;
+ }
+ if (!(GPLR(GPIO_NR_BLUEANGEL_USB_DETECT_N) & GPIO_bit(GPIO_NR_BLUEANGEL_USB_DETECT_N))) {
+ power_status = APM_AC_ONLINE;
+ }
+ if (power_status) {
+ /* Start off with a solid LED; if we're charging,
+ the LED state will be updated when the queued work runs. */
+ #ifdef CONFIG_LEDS_TRIGGERS
+ led_trigger_event(ledtrig_cradle, LED_FULL);
+ #endif
+ } else {
+ #ifdef CONFIG_LEDS_TRIGGERS
+ led_trigger_event(ledtrig_cradle, LED_OFF);
+ #endif
+ }
+
+ /* Force the data to be re-read. */
+ battery_status.update_time = 0;
+
+ /* Wait a few seconds to let the DS2760 update its current reading. */
+ queue_delayed_work(monitor_q, &monitor_work, HZ * 7);
+
+ return IRQ_HANDLED;
+}
+
+
static void
blueangel_battery_get_power_status (struct apm_power_info *info)
{
- int val;
- int val1;
- int full=0x8ea;
- int empty=0x8a3;
-
- int full1=0xb0f;
- int empty1=0x8a3;
- val=blueangel_battery_get_voltage(NULL);
- val1=blueangel_battery_get_voltage1(NULL);
-
- info->battery_life = (val1-empty)*100/(full-empty);
- if (info->battery_life < 0 || info->battery_life > 100)
- printk("battery voltage (0x%x) not within [0x%x,0x%x]. Please report.\n", val, full, empty);
- if (info->battery_life < 0)
- info->battery_life = 0;
- if (info->battery_life > 100)
- info->battery_life = 100;
+ blueangel_battery_read_status();
+
info->ac_line_status = APM_AC_OFFLINE;
- if (!(asic3_get_gpio_status_d(&blueangel_tsc2200.dev) & GPIOD_AC_CHARGER_N)) {
+ if (!(asic3_get_gpio_status_d(&blueangel_asic3.dev) & GPIOD_AC_CHARGER_N)) {
info->ac_line_status = APM_AC_ONLINE;
}
if (!(GPLR(GPIO_NR_BLUEANGEL_USB_DETECT_N) & GPIO_bit(GPIO_NR_BLUEANGEL_USB_DETECT_N))) {
info->ac_line_status = APM_AC_ONLINE;
}
- info->units=APM_UNITS_MINS;
- info->time = 360 * info->battery_life/100; /* time remaining */
- info->battery_flag = 0;
- if (info->ac_line_status == APM_AC_OFFLINE) {
+ power_status = info->ac_line_status;
+
+ if (info->ac_line_status == APM_AC_ONLINE && current > 0) {
#ifdef CONFIG_LEDS_TRIGGERS
- led_trigger_event(ledtrig_cradle, LED_OFF);
+ led_trigger_event(ledtrig_cradle, LED_FULL);
#endif
- info->battery_status = APM_BATTERY_STATUS_CRITICAL;
- if(info->battery_life > 5) {
- info->battery_status = APM_BATTERY_STATUS_LOW;
- }
- if(info->battery_life > 20) {
- info->battery_status = APM_BATTERY_STATUS_HIGH;
- }
- } else {
- info->battery_status = APM_BATTERY_STATUS_CHARGING;
+ info->battery_status = APM_BATTERY_STATUS_CHARGING;
+ } else {
#ifdef CONFIG_LEDS_TRIGGERS
- led_trigger_event(ledtrig_cradle, LED_FULL);
+ led_trigger_event(ledtrig_cradle, LED_OFF);
#endif
- }
+
+ info->battery_status = battery_status.accum_current_mAh >
+ ((battery_status.rated_capacity * 75) / 100) ?
+ APM_BATTERY_STATUS_HIGH :
+ battery_status.accum_current_mAh >
+ ((battery_status.rated_capacity* 25) / 100) ?
+ APM_BATTERY_STATUS_LOW : APM_BATTERY_STATUS_CRITICAL;
+ }
+
+ info->battery_flag = info->battery_status;
+
+ if (battery_status.rated_capacity)
+ info->battery_life = (battery_status.accum_current_mAh * 100) /
+ battery_status.rated_capacity;
+
+ if (info->battery_life > 100)
+ info->battery_life = 100;
+ if (info->battery_life < 0)
+ info->battery_life = 0;
+
+ if (battery_status.current_mA) {
+ info->time = - ((battery_status.accum_current_mAh * 6000) /
+ battery_status.current_mA) / 100;
+ info->units = APM_UNITS_MINS;
+ } else
+ info->units = APM_UNITS_UNKNOWN;
}
static int bat_setup(void)
@@ -227,17 +370,131 @@
return 0;
}
-static int __init bat_init(void)
+static void
+blueangel_battery_timer(unsigned long data)
{
- bat_setup();
- blueangel_battery_get_voltage(NULL);
- blueangel_battery_get_voltage1(NULL);
- blueangel_battery_get_temp(NULL);
- apm_get_power_status = blueangel_battery_get_power_status;
+ queue_work(monitor_q, &monitor_work);
+ mod_timer(&monitor_timer, jiffies + BATTERY_CHECK_INTERVAL);
+}
+
+void
+blueangel_battery_update_status(void *data)
+{
+ static int full_counter;
+
+ blueangel_battery_read_status();
+
+ if (battery_charge_state == BATTERY_STATE_UNKNOWN)
+ full_counter = 0;
+
+ /* Set the proper charging rate. */
+ if (!(asic3_get_gpio_status_d(&blueangel_asic3.dev) & GPIOD_AC_CHARGER_N)) {
+ power_status = APM_AC_ONLINE;
+ }
+ if (!(GPLR(GPIO_NR_BLUEANGEL_USB_DETECT_N) & GPIO_bit(GPIO_NR_BLUEANGEL_USB_DETECT_N))) {
+ power_status = APM_AC_ONLINE;
+ }
+ if (power_status) {
+
+ asic3_set_gpio_out_c(&blueangel_asic3.dev,1 << GPIOC_CHARGER_MODE, 1 << GPIOC_CHARGER_MODE);
+
+ if (battery_status.current_mA > 10) {
+
+ battery_charge_state = BATTERY_CHARGING;
+ full_counter = 0;
+
+ } else if (battery_status.current_mA < 10 &&
+ battery_charge_state != BATTERY_FULL) {
+
+ /* Don't consider the battery to be full unless
+ * we've seen the current < 10 mA at least two
+ * consecutive times. */
+
+ full_counter++;
+
+ if (full_counter < 2)
+ battery_charge_state = BATTERY_CHARGING;
+ else {
+
+ unsigned char acr[2];
+
+ acr[0] = (battery_status.rated_capacity * 4) >> 8;
+ acr[1] = (battery_status.rated_capacity * 4) & 0xff;
+
+ if (w1_ds2760_write(ds2760_dev, acr,
+ DS2760_CURRENT_ACCUM_MSB, 2) < 2)
+ printk(KERN_ERR "ACR reset failed\n");
+ battery_charge_state = BATTERY_FULL;
+ #ifdef CONFIG_LEDS_TRIGGERS
+ led_trigger_event(ledtrig_phone, LED_FULL);
+ #endif
+ }
+ }
+
+ if (battery_charge_state == BATTERY_CHARGING) {
+ #ifdef CONFIG_LEDS_TRIGGERS
+ led_trigger_event(ledtrig_cradle, LED_FULL);
+ #endif
+ } else {
+
+ /* Set the LED solid while on AC and not charging. */
+ #ifdef CONFIG_LEDS_TRIGGERS
+ led_trigger_event(ledtrig_phone, LED_FULL);
+ led_trigger_event(ledtrig_cradle, LED_OFF);
+ #endif
+ }
+
+ } else {
+
+ battery_charge_state = BATTERY_DISCHARGING;
+ full_counter = 0;
+ asic3_set_gpio_out_c(&blueangel_asic3.dev,1 << GPIOC_CHARGER_MODE, 0);
+
+ /* Turn off the LED while unplugged from AC. */
#ifdef CONFIG_LEDS_TRIGGERS
- led_trigger_register_simple("charge", &ledtrig_cradle);
+ led_trigger_event(ledtrig_cradle, LED_OFF);
#endif
+ }
+
+}
+
+
+
+static int __init bat_init(void)
+{
+ bat_setup();
+
+ battery_charge_state = BATTERY_STATE_UNKNOWN;
+
+ /* Install an interrupt handler for AC plug/unplug. */
+ set_irq_type(IRQ_GPIO(GPIO_NR_BLUEANGEL_USB_DETECT_N), IRQT_BOTHEDGE);
+ request_irq(IRQ_GPIO(GPIO_NR_BLUEANGEL_USB_DETECT_N), blueangel_ac_plug_isr, SA_SAMPLE_RANDOM,
+ "AC plug", NULL);
+
+ /* Create a workqueue in which the battery monitor will run. */
+ monitor_q = create_singlethread_workqueue("battmon");
+ INIT_WORK(&monitor_work, blueangel_battery_update_status, NULL);
+
+ /* Create a timer to run the battery monitor every
+ BATTERY_CHECK_INTERVAL seconds. */
+ init_timer(&monitor_timer);
+ monitor_timer.function = blueangel_battery_timer;
+ monitor_timer.data = 0;
+ /* Start the first monitor task a few seconds from now. */
+ mod_timer(&monitor_timer, jiffies + HZ * 10);
+
+ apm_get_power_status = blueangel_battery_get_power_status;
+ #ifdef CONFIG_LEDS_TRIGGERS
+ led_trigger_register_simple("charge", &ledtrig_cradle);
+ led_trigger_register_simple("phone", &ledtrig_phone);
+ #endif
+
+ /* Get the DS2760 W1 device. */
+ probe_q = create_singlethread_workqueue("battmonprb");
+ INIT_WORK(&probe_work, blueangel_battery_probe_work, NULL);
+ blueangel_battery_probe_work(NULL);
+ //enable_irq(IRQ_GPIO(GPIO_NR_BLUEANGEL_USB_DETECT_N));
return 0;
}
@@ -246,14 +503,31 @@
apm_get_power_status = NULL;
battery_class_unregister(&battery_dev);
battery_class_unregister(&battery_dev1);
- #ifdef CONFIG_LEDS_TRIGGERS
- led_trigger_unregister_simple(ledtrig_cradle);
- #endif
+
+ disable_irq(IRQ_GPIO(GPIO_NR_BLUEANGEL_USB_DETECT_N));
+ free_irq(IRQ_GPIO(GPIO_NR_BLUEANGEL_USB_DETECT_N), NULL);
+
+
+ cancel_delayed_work(&monitor_work);
+ flush_workqueue(monitor_q);
+ destroy_workqueue(monitor_q);
+
+ cancel_delayed_work(&probe_work);
+ flush_workqueue(probe_q);
+ destroy_workqueue(probe_q);
+
+ if (ds2760_dev)
+ put_device(ds2760_dev);
+ #ifdef CONFIG_LEDS_TRIGGERS
+ led_trigger_event(ledtrig_phone, LED_OFF);
+ led_trigger_unregister_simple(ledtrig_phone);
+ led_trigger_event(ledtrig_cradle, LED_OFF);
+ led_trigger_unregister_simple(ledtrig_cradle);
+ #endif
}
module_init(bat_init)
module_exit(bat_exit)
-MODULE_AUTHOR("Matthias Burghardt");
-MODULE_DESCRIPTION("Battery (TI TSC2200) support for the HTC Blueangel");
+MODULE_DESCRIPTION("Battery (TI TSC2200) and DS2760 Support for the HTC Blueangel");
MODULE_LICENSE("GPL");