diff options
-rw-r--r-- | arch/arm/mach-s3c2440/mach-gta02.c | 8 | ||||
-rw-r--r-- | drivers/i2c/chips/pcf50633.c | 212 | ||||
-rw-r--r-- | 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 601f7bc9591..d7882ea5a14 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 8ba81d24254..38cabd23c51 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); @@ -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 33be73eeb2c..9069bd4e088 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 }; |