Index: 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 #include #include +#include +#include #include #include @@ -23,15 +25,54 @@ #include +#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");