From 86bec3382cd1694a3b5cd6d8796cfcbdbe5ca5ff Mon Sep 17 00:00:00 2001 From: Andy Green Date: Fri, 25 Jul 2008 23:06:11 +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 --- 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 8ba81d2..38cabd2 100644 --- a/drivers/i2c/chips/pcf50633.c +++ b/drivers/i2c/chips/pcf50633.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -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); @@ -759,6 +843,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) { @@ -796,8 +907,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); @@ -829,8 +939,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 */ @@ -1063,10 +1202,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) @@ -1074,8 +1216,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); @@ -1105,16 +1279,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); @@ -1703,7 +1897,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.3