aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-s3c2440/mach-gta02.c8
-rw-r--r--drivers/i2c/chips/pcf50633.c212
-rw-r--r--include/linux/pcf506xx.h2
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
};