diff options
Diffstat (limited to 'target/linux/s3c24xx/patches-2.6.24/1170-introduce-charging-led-behaviour.patch.patch')
-rw-r--r-- | target/linux/s3c24xx/patches-2.6.24/1170-introduce-charging-led-behaviour.patch.patch | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/target/linux/s3c24xx/patches-2.6.24/1170-introduce-charging-led-behaviour.patch.patch b/target/linux/s3c24xx/patches-2.6.24/1170-introduce-charging-led-behaviour.patch.patch new file mode 100644 index 0000000000..c2833bdb23 --- /dev/null +++ b/target/linux/s3c24xx/patches-2.6.24/1170-introduce-charging-led-behaviour.patch.patch @@ -0,0 +1,423 @@ +From bf74b8891e80dfceb72002f12c7d3c9978631dc0 Mon Sep 17 00:00:00 2001 +From: Andy Green <andy@openmoko.com> +Date: Wed, 2 Jul 2008 22:37:47 +0100 +Subject: [PATCH] introduce-charging-led-behaviour.patch + +Creates a new behaviour requested by Will that the red LED on GTA02 +is lit during battery charging.and goes out when the battery is full. + +This is done by leveraging the PMU interrupts, but in one scenario +there is no interrupt that occurs, when the battery is replaced after +being removed with the USB power in all the while. So a sleepy work +function is started under those circumstances to watch for battery +reinsertion or USB cable pull. + +100mA limit was not being observed under some conditions so this was +fixed and tested with a USB cable with D+/D- disconnected. 1A +charger behaviour was also tested. + +Showing the charging action exposes some inconsistency in pcf50633 +charging action. If your battery is nearly full, it will keep +charging it at decreasing current even after it thinks it is at +100% capacity for a long while. But if you pull that same battery +and re-insert it, the charger state machine in pcf50633 believe it is +full and won't charge it. + +Signed-off-by: Andy Green <andy@openmoko.com> +--- + arch/arm/mach-s3c2440/mach-gta02.c | 8 ++ + drivers/i2c/chips/pcf50633.c | 212 ++++++++++++++++++++++++++++++++++-- + include/linux/pcf506xx.h | 2 + + 3 files changed, 214 insertions(+), 8 deletions(-) + +diff --git a/arch/arm/mach-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c +index 601f7bc..d7882ea 100644 +--- a/arch/arm/mach-s3c2440/mach-gta02.c ++++ b/arch/arm/mach-s3c2440/mach-gta02.c +@@ -437,6 +437,14 @@ static int pmu_callback(struct device *dev, unsigned int feature, + case PMU_EVT_USB_REMOVE: + pcf50633_charge_enable(pcf50633_global, 0); + break; ++ case PMU_EVT_CHARGER_IDLE: ++ /* printk(KERN_ERR"PMU_EVT_CHARGER_IDLE\n"); */ ++ neo1973_gpb_setpin(GTA02_GPIO_AUX_LED, 0); ++ break; ++ case PMU_EVT_CHARGER_ACTIVE: ++ /* printk(KERN_ERR"PMU_EVT_CHARGER_ACTIVE\n"); */ ++ neo1973_gpb_setpin(GTA02_GPIO_AUX_LED, 1); ++ break; + default: + break; + } +diff --git a/drivers/i2c/chips/pcf50633.c b/drivers/i2c/chips/pcf50633.c +index 2878baa..2ed6dc0 100644 +--- a/drivers/i2c/chips/pcf50633.c ++++ b/drivers/i2c/chips/pcf50633.c +@@ -47,6 +47,7 @@ + #include <linux/platform_device.h> + #include <linux/pcf50633.h> + #include <linux/apm-emulation.h> ++#include <linux/jiffies.h> + + #include <asm/mach-types.h> + #include <asm/arch/gta02.h> +@@ -121,8 +122,23 @@ struct pcf50633_data { + int onkey_seconds; + int irq; + int have_been_suspended; ++ int usb_removal_count; + unsigned char pcfirq_resume[5]; + ++ /* if he pulls battery while charging, we notice that and correctly ++ * report that the charger is idle. But there is no interrupt that ++ * fires if he puts a battery back in and charging resumes. So when ++ * the battery is pulled, we run this work function looking for ++ * either charger resumption or USB cable pull ++ */ ++ struct mutex working_lock_nobat; ++ struct work_struct work_nobat; ++ int working_nobat; ++ int usb_removal_count_nobat; ++ int jiffies_last_bat_ins; ++ ++ int last_curlim_set; ++ + int coldplug_done; /* cleared by probe, set by first work service */ + int flag_bat_voltage_read; /* ipc to /sys batt voltage read func */ + +@@ -259,6 +275,8 @@ static u_int16_t async_adc_complete(struct pcf50633_data *pcf) + (__reg_read(pcf, PCF50633_REG_ADCS3) & + PCF50633_ADCS3_ADCDAT1L_MASK); + ++ DEBUGPC("adc result = %d\n", ret); ++ + return ret; + } + +@@ -512,8 +530,7 @@ static void configure_pmu_for_charger(struct pcf50633_data *pcf, + { + switch (type) { + case CHARGER_TYPE_NONE: +- __reg_write(pcf, PCF50633_REG_MBCC7, +- PCF50633_MBCC7_USB_SUSPEND); ++ pcf50633_usb_curlim_set(pcf, 0); + break; + /* + * the PCF50633 has a feature that it will supply only excess current +@@ -521,10 +538,19 @@ static void configure_pmu_for_charger(struct pcf50633_data *pcf, + * 500mA setting is "up to 500mA" according to that. + */ + case CHARGER_TYPE_HOSTUSB: +- __reg_write(pcf, PCF50633_REG_MBCC7, PCF50633_MBCC7_USB_500mA); ++ /* USB subsystem should call pcf50633_usb_curlim_set to set ++ * what was negotiated with the host when it is enumerated ++ * successfully. If we get called again after a good ++ * negotiation, we keep what was negotiated. (Removal of ++ * USB plug destroys pcf->last_curlim_set to 0) ++ */ ++ if (pcf->last_curlim_set > 100) ++ pcf50633_usb_curlim_set(pcf, pcf->last_curlim_set); ++ else ++ pcf50633_usb_curlim_set(pcf, 100); + break; + case CHARGER_TYPE_1A: +- __reg_write(pcf, PCF50633_REG_MBCC7, PCF50633_MBCC7_USB_1000mA); ++ pcf50633_usb_curlim_set(pcf, 1000); + /* + * stop GPO / EN_HOSTUSB power driving out on the same + * USB power pins we have a 1A charger on right now! +@@ -536,6 +562,12 @@ static void configure_pmu_for_charger(struct pcf50633_data *pcf, + PCF50633_REG_GPIO1CFG) & 0xf0); + break; + } ++ ++ /* max out USB fast charge current -- actual current drawn is ++ * additionally limited by USB limit so no worries ++ */ ++ __reg_write(pcf, PCF50633_REG_MBCC5, 0xff); ++ + } + + static void trigger_next_adc_job_if_any(struct pcf50633_data *pcf) +@@ -562,6 +594,49 @@ static void add_request_to_adc_queue(struct pcf50633_data *pcf, + trigger_next_adc_job_if_any(pcf); + } + ++/* we are run when we see a NOBAT situation, because there is no interrupt ++ * source in pcf50633 that triggers on resuming charging. It watches to see ++ * if charging resumes, it reassesses the charging source if it does. If the ++ * USB power disappears, it is also a sign there must be a battery and it is ++ * NOT being charged, so it exits since the next move must be USB insertion for ++ * change of charger state ++ */ ++ ++static void pcf50633_work_nobat(struct work_struct *work) ++{ ++ struct pcf50633_data *pcf = ++ container_of(work, struct pcf50633_data, work_nobat); ++ ++ mutex_lock(&pcf->working_lock_nobat); ++ pcf->working_nobat = 1; ++ mutex_unlock(&pcf->working_lock_nobat); ++ ++ while (1) { ++ msleep(1000); ++ ++ /* there's a battery in there now? */ ++ if (reg_read(pcf, PCF50633_REG_MBCS3) & 0x40) { ++ ++ pcf->jiffies_last_bat_ins = jiffies; ++ ++ /* figure out our charging stance */ ++ add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_ADCIN1, ++ PCF50633_ADCC1_AVERAGE_16); ++ goto bail; ++ } ++ ++ /* he pulled USB cable since we were started? exit then */ ++ if (pcf->usb_removal_count_nobat != pcf->usb_removal_count) ++ goto bail; ++ } ++ ++bail: ++ mutex_lock(&pcf->working_lock_nobat); ++ pcf->working_nobat = 0; ++ mutex_unlock(&pcf->working_lock_nobat); ++} ++ ++ + static void pcf50633_work(struct work_struct *work) + { + struct pcf50633_data *pcf = +@@ -674,20 +749,29 @@ static void pcf50633_work(struct work_struct *work) + if (pcf->pdata->cb) + pcf->pdata->cb(&pcf->client.dev, + PCF50633_FEAT_MBC, PMU_EVT_USB_INSERT); ++ msleep(500); /* debounce, allow to see any ID resistor */ + /* completion irq will figure out our charging stance */ + add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_ADCIN1, + PCF50633_ADCC1_AVERAGE_16); + } + if (pcfirq[0] & PCF50633_INT1_USBREM) { + DEBUGPC("USBREM "); ++ ++ pcf->usb_removal_count++; ++ + /* only deal if we had understood it was in */ + if (pcf->flags & PCF50633_F_USB_PRESENT) { + input_report_key(pcf->input_dev, KEY_POWER2, 0); + apm_queue_event(APM_POWER_STATUS_CHANGE); + pcf->flags &= ~PCF50633_F_USB_PRESENT; ++ + if (pcf->pdata->cb) + pcf->pdata->cb(&pcf->client.dev, + PCF50633_FEAT_MBC, PMU_EVT_USB_REMOVE); ++ ++ /* destroy any memory of grant of power from host */ ++ pcf->last_curlim_set = 0; ++ + /* completion irq will figure out our charging stance */ + add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_ADCIN1, + PCF50633_ADCC1_AVERAGE_16); +@@ -760,6 +844,33 @@ static void pcf50633_work(struct work_struct *work) + + if (pcfirq[2] & PCF50633_INT3_BATFULL) { + DEBUGPC("BATFULL "); ++ ++ /* the problem is, we get a false BATFULL if we inserted battery ++ * while USB powered. Defeat BATFULL if we recently inserted ++ * battery ++ */ ++ ++ if ((jiffies - pcf->jiffies_last_bat_ins) < (HZ * 2)) { ++ ++ DEBUGPC("*** Ignoring BATFULL ***\n"); ++ ++ ret = reg_read(pcf, PCF50633_REG_MBCC7) & ++ PCF56033_MBCC7_USB_MASK; ++ ++ ++ reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, ++ PCF56033_MBCC7_USB_MASK, ++ PCF50633_MBCC7_USB_SUSPEND); ++ ++ reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, ++ PCF56033_MBCC7_USB_MASK, ++ ret); ++ } else { ++ if (pcf->pdata->cb) ++ pcf->pdata->cb(&pcf->client.dev, ++ PCF50633_FEAT_MBC, PMU_EVT_CHARGER_IDLE); ++ } ++ + /* FIXME: signal this to userspace */ + } + if (pcfirq[2] & PCF50633_INT3_CHGHALT) { +@@ -797,8 +908,7 @@ static void pcf50633_work(struct work_struct *work) + + switch (pcf->adc_queue_mux[tail]) { + case PCF50633_ADCC1_MUX_BATSNS_RES: /* battery voltage */ +- pcf->flag_bat_voltage_read = +- async_adc_complete(pcf); ++ pcf->flag_bat_voltage_read = async_adc_complete(pcf); + break; + case PCF50633_ADCC1_MUX_ADCIN1: /* charger type */ + pcf->charger_adc_result_raw = async_adc_complete(pcf); +@@ -830,8 +940,37 @@ static void pcf50633_work(struct work_struct *work) + (PCF50633_MBCS1_USBPRES | PCF50633_MBCS1_USBOK)) { + /* + * hey no need to freak out, we have some kind of +- * valid charger power ++ * valid charger power to keep us going -- but note that ++ * we are not actually charging anything ++ */ ++ if (pcf->pdata->cb) ++ pcf->pdata->cb(&pcf->client.dev, ++ PCF50633_FEAT_MBC, PMU_EVT_CHARGER_IDLE); ++ ++ reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, ++ PCF50633_MBCC1_RESUME, ++ PCF50633_MBCC1_RESUME); ++ ++ /* ++ * Well, we are not charging anything right this second ++ * ... however in the next ~30s before we get the next ++ * NOBAT, he might insert a battery. So we schedule a ++ * work function checking to see if ++ * we started charging something during that time. ++ * USB removal as well as charging terminates the work ++ * function so we can't get terminally confused + */ ++ mutex_lock(&pcf->working_lock_nobat); ++ if (!pcf->working_nobat) { ++ pcf->usb_removal_count_nobat = ++ pcf->usb_removal_count; ++ ++ if (!schedule_work(&pcf->work_nobat)) ++ DEBUGPC("failed to schedule nobat\n"); ++ } ++ mutex_unlock(&pcf->working_lock_nobat); ++ ++ + DEBUGPC("(NO)BAT "); + } else { + /* Really low battery voltage, we have 8 seconds left */ +@@ -1064,10 +1203,13 @@ void pcf50633_usb_curlim_set(struct pcf50633_data *pcf, int ma) + { + u_int8_t bits; + ++ pcf->last_curlim_set = ma; ++ + dev_dbg(&pcf->client.dev, "setting usb current limit to %d ma", ma); + +- if (ma >= 1000) ++ if (ma >= 1000) { + bits = PCF50633_MBCC7_USB_1000mA; ++ } + else if (ma >= 500) + bits = PCF50633_MBCC7_USB_500mA; + else if (ma >= 100) +@@ -1075,8 +1217,40 @@ void pcf50633_usb_curlim_set(struct pcf50633_data *pcf, int ma) + else + bits = PCF50633_MBCC7_USB_SUSPEND; + ++ DEBUGPC("pcf50633_usb_curlim_set -> %dmA\n", ma); ++ ++ if (!pcf->pdata->cb) ++ goto set_it; ++ ++ switch (bits) { ++ case PCF50633_MBCC7_USB_100mA: ++ case PCF50633_MBCC7_USB_SUSPEND: ++ /* no charging is gonna be happening */ ++ pcf->pdata->cb(&pcf->client.dev, ++ PCF50633_FEAT_MBC, PMU_EVT_CHARGER_IDLE); ++ break; ++ default: /* right charging context that if there is power, we charge */ ++ if (pcf->flags & PCF50633_F_USB_PRESENT) ++ pcf->pdata->cb(&pcf->client.dev, ++ PCF50633_FEAT_MBC, PMU_EVT_CHARGER_ACTIVE); ++ break; ++ } ++ ++set_it: + reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, PCF56033_MBCC7_USB_MASK, + bits); ++ ++ /* clear batfull */ ++ reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, ++ PCF50633_MBCC1_AUTORES, ++ 0); ++ reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, ++ PCF50633_MBCC1_RESUME, ++ PCF50633_MBCC1_RESUME); ++ reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, ++ PCF50633_MBCC1_AUTORES, ++ PCF50633_MBCC1_AUTORES); ++ + } + EXPORT_SYMBOL_GPL(pcf50633_usb_curlim_set); + +@@ -1106,16 +1280,36 @@ static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, NULL); + void pcf50633_charge_enable(struct pcf50633_data *pcf, int on) + { + u_int8_t bits; ++ u_int8_t usblim; + + if (!(pcf->pdata->used_features & PCF50633_FEAT_MBC)) + return; + ++ DEBUGPC("pcf50633_charge_enable %d\n", on); ++ + if (on) { + pcf->flags |= PCF50633_F_CHG_ENABLED; + bits = PCF50633_MBCC1_CHGENA; ++ usblim = reg_read(pcf, PCF50633_REG_MBCC7) & ++ PCF56033_MBCC7_USB_MASK; ++ switch (usblim) { ++ case PCF50633_MBCC7_USB_1000mA: ++ case PCF50633_MBCC7_USB_500mA: ++ if (pcf->flags & PCF50633_F_USB_PRESENT) ++ if (pcf->pdata->cb) ++ pcf->pdata->cb(&pcf->client.dev, ++ PCF50633_FEAT_MBC, ++ PMU_EVT_CHARGER_ACTIVE); ++ break; ++ default: ++ break; ++ } + } else { + pcf->flags &= ~PCF50633_F_CHG_ENABLED; + bits = 0; ++ if (pcf->pdata->cb) ++ pcf->pdata->cb(&pcf->client.dev, ++ PCF50633_FEAT_MBC, PMU_EVT_CHARGER_IDLE); + } + reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, PCF50633_MBCC1_CHGENA, + bits); +@@ -1712,7 +1906,9 @@ static int pcf50633_detect(struct i2c_adapter *adapter, int address, int kind) + + mutex_init(&data->lock); + mutex_init(&data->working_lock); ++ mutex_init(&data->working_lock_nobat); + INIT_WORK(&data->work, pcf50633_work); ++ INIT_WORK(&data->work_nobat, pcf50633_work_nobat); + data->irq = irq; + data->working = 0; + data->onkey_seconds = -1; +diff --git a/include/linux/pcf506xx.h b/include/linux/pcf506xx.h +index 33be73e..9069bd4 100644 +--- a/include/linux/pcf506xx.h ++++ b/include/linux/pcf506xx.h +@@ -21,6 +21,8 @@ enum pmu_event { + PMU_EVT_USB_INSERT, + PMU_EVT_USB_REMOVE, + #endif ++ PMU_EVT_CHARGER_ACTIVE, ++ PMU_EVT_CHARGER_IDLE, + __NUM_PMU_EVTS + }; + +-- +1.5.6.5 + |