[PATCH 09/10] fix-pcf50633-charger-type-detect.patch

warmcat andy at openmoko.com
Mon Feb 11 17:35:09 CET 2008


From: Andy Green <andy at openmoko.com>

In this code there are three kinds of charger understood:

 - no charger -- nothing active is in the USB socket... no charging
 - Host / 500mA charger -- we are plugged into a host OR a dumb wall adapter, charge at <=500mA
   NOTE: the pcf50633 maintains total max draw at 500mA in this case, making sure that the
         device gets the current it needs to operate and any left over goes to the battery
 - 1A charger -- if it sees the magic ID resistor in the 1A charger, it allows to charge to 1A

Fix the ADC code so that ACCSW is enabled, otherwise we will just
see 0V everywhere.

Turn the ADC code around so it is serviced by the workqueue handler only.
Deal with different mux requests by creating a FIFO to queue them.
Change battvolt to use the FIFO method with sleeping spinning.

Add /sys nodes

cat /sys/devices/platform/s3c2440-i2c/i2c-adapter/i2c-0/0-0073/charger_adc
43

gets you the raw ADC reading of the charger type resistor

cat /sys/devices/platform/s3c2440-i2c/i2c-adapter/i2c-0/0-0073/charger_type
charger 1A mode 1A

get you the device's understanding of what is plugged into it.  The mode part
is the current limit setting for charging on the PMU itself.

Also remove some inline attributes.

Delete temp stuff that isn't used on GTA02 (we have temp from battery itself)

Adjust sysfs table so it doesn't have literal length and add warnings that it is
modified at runtime

Signed-off-by: Andy Green <andy at openmoko.com>
---

 drivers/i2c/chips/pcf50633.c |  341 ++++++++++++++++++++++++++++++------------
 1 files changed, 242 insertions(+), 99 deletions(-)


diff --git a/drivers/i2c/chips/pcf50633.c b/drivers/i2c/chips/pcf50633.c
index ef50321..802ac33 100644
--- a/drivers/i2c/chips/pcf50633.c
+++ b/drivers/i2c/chips/pcf50633.c
@@ -35,6 +35,7 @@
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/workqueue.h>
+#include <linux/delay.h>
 #include <linux/rtc.h>
 #include <linux/bcd.h>
 #include <linux/watchdog.h>
@@ -94,6 +95,17 @@ enum close_state {
 	CLOSE_STATE_ALLOW = 0x2342,
 };
 
+enum charger_type {
+	CHARGER_TYPE_NONE = 0,
+	CHARGER_TYPE_HOSTUSB,
+	CHARGER_TYPE_1A
+};
+
+#define ADC_NOM_CHG_DETECT_1A 6
+#define ADC_NOM_CHG_DETECT_NONE 43
+
+#define MAX_ADC_FIFO_DEPTH 8
+
 struct pcf50633_data {
 	struct i2c_client client;
 	struct pcf50633_platform_data *pdata;
@@ -109,7 +121,20 @@ struct pcf50633_data {
 	int onkey_seconds;
 	int irq;
 
-	int coldplug_done;
+	int coldplug_done; /* cleared by probe, set by first work service */
+	int flag_bat_voltage_read; /* ipc to /sys batt voltage read func */
+
+	int charger_adc_result_raw;
+	enum charger_type charger_type;
+
+	/* we have a FIFO of ADC measurement requests that are used only by
+	 * the workqueue service code after the ADC completion interrupt
+	 */
+	int adc_queue_mux[MAX_ADC_FIFO_DEPTH]; /* which ADC input to use */
+	int adc_queue_avg[MAX_ADC_FIFO_DEPTH]; /* amount of averaging */
+	int adc_queue_head; /* head owned by foreground code */
+	int adc_queue_tail; /* tail owned by service code */
+
 #ifdef CONFIG_PM
 	struct {
 		u_int8_t int1m, int2m, int3m, int4m, int5m;
@@ -134,26 +159,11 @@ EXPORT_SYMBOL_GPL(pcf50633_global);
 
 static struct platform_device *pcf50633_pdev;
 
-/* This is a mitsubishi TN11-3H103J T,B NTC Thermistor -10..79 centigrade */
-static const u_int16_t ntc_table_tn11_3h103j[] = {
-	/* -10 */
-	40260, 38560, 36950, 35410, 33950, 32550, 31220, 29960, 28750, 27590,
-	26490, 25440, 24440, 23480, 22560, 21680, 20830, 20020, 19240, 18500,
-	17780, 17710, 16440, 15810, 15210, 14630, 14070, 13540, 13030, 12540,
-	12070, 11620, 11190, 10780, 10380, 10000, 9635, 9286, 8950, 8629,
-	8320, 8024, 7740, 7467, 7205, 6954, 6713, 6481, 6258, 6044,
-	5839, 5641, 5451, 5269, 5093, 4924, 4762, 4605, 4455, 4310,
-	4171, 4037, 3908, 3784, 3664, 3549, 3438, 3313, 3227, 3128,
-	3032, 2939, 2850, 2763, 2680, 2600, 2522, 2448, 2375, 2306,
-	2239, 2174, 2111, 2050, 1922, 1935, 1881, 1828, 1776, 1727,
-};
-
-
 /***********************************************************************
  * Low-Level routines
  ***********************************************************************/
 
-static inline int __reg_write(struct pcf50633_data *pcf, u_int8_t reg, u_int8_t val)
+static int __reg_write(struct pcf50633_data *pcf, u_int8_t reg, u_int8_t val)
 {
 	return i2c_smbus_write_byte_data(&pcf->client, reg, val);
 }
@@ -169,7 +179,7 @@ static int reg_write(struct pcf50633_data *pcf, u_int8_t reg, u_int8_t val)
 	return ret;
 }
 
-static inline int32_t __reg_read(struct pcf50633_data *pcf, u_int8_t reg)
+static int32_t __reg_read(struct pcf50633_data *pcf, u_int8_t reg)
 {
 	int32_t ret;
 
@@ -225,43 +235,33 @@ static int reg_clear_bits(struct pcf50633_data *pcf, u_int8_t reg, u_int8_t val)
 	return ret;
 }
 
-/* synchronously read one ADC channel (busy-wait for result to be complete) */
-static u_int16_t adc_read(struct pcf50633_data *pcf,  int channel, int avg,
-			  u_int16_t *data2)
+/* asynchronously setup reading one ADC channel */
+static void async_adc_read_setup(struct pcf50633_data *pcf,
+				 int channel, int avg)
 {
-	u_int8_t adcs3, adcs2, adcs1;
-	u_int16_t ret;
-
-	DEBUGP("entering (pcf=%p, channel=%u, data2=%p)\n",
-		pcf, channel, data2);
-
 	channel &= PCF50633_ADCC1_ADCMUX_MASK;
 
-	mutex_lock(&pcf->lock);
+	/* kill ratiometric, but enable ACCSW biasing */
+	__reg_write(pcf, PCF50633_REG_ADCC2, 0x00);
+	__reg_write(pcf, PCF50633_REG_ADCC3, 0x01);
 
 	/* start ADC conversion of selected channel */
 	__reg_write(pcf, PCF50633_REG_ADCC1, channel | avg |
 		    PCF50633_ADCC1_ADCSTART | PCF50633_ADCC1_RES_10BIT);
 
-	do {
-		adcs3 = __reg_read(pcf, PCF50633_REG_ADCS3);
-	} while (!(adcs3 & PCF50633_ADCS3_ADCRDY));
+}
 
-	adcs1 = __reg_read(pcf, PCF50633_REG_ADCS1);
-	ret = (adcs1 << 2) | (adcs3 & PCF50633_ADCS3_ADCDAT1L_MASK);
+static u_int16_t async_adc_complete(struct pcf50633_data *pcf)
+{
+	u_int16_t ret = (__reg_read(pcf, PCF50633_REG_ADCS1) << 2) |
+			(__reg_read(pcf, PCF50633_REG_ADCS3) &
+						  PCF50633_ADCS3_ADCDAT1L_MASK);
 
-	if (data2) {
-		adcs2 = __reg_read(pcf, PCF50633_REG_ADCS2);
-		*data2 = (adcs2 << 2) | (adcs3 & PCF50633_ADCS3_ADCDAT2L_MASK)
-					>> PCF50633_ADCS3_ADCDAT2L_SHIFT;
-	}
+	return ret;
+}
 
-	mutex_unlock(&pcf->lock);
 
-	DEBUGP("returning %u %u\n", ret, data2 ? *data2 : 0);
 
-	return ret;
-}
 
 /***********************************************************************
  * Voltage / ADC
@@ -487,12 +487,77 @@ int pcf50633_gpio_get(struct pcf50633_data *pcf, enum pcf50633_gpio gpio)
 }
 EXPORT_SYMBOL_GPL(pcf50633_gpio_get);
 
+static int interpret_charger_type_from_adc(struct pcf50633_data *pcf,
+					   int sample)
+{
+	/* 1A capable charger? */
+
+	if (sample < ((ADC_NOM_CHG_DETECT_NONE + ADC_NOM_CHG_DETECT_1A) / 2))
+		return CHARGER_TYPE_1A;
+
+	/* well then, nothing in the USB hole, or USB host / unk adapter */
+
+	if (pcf->flags & PCF50633_F_USB_PRESENT) /* ooh power is in there */
+		return CHARGER_TYPE_HOSTUSB; /* HOSTUSB is the catchall */
+
+	return CHARGER_TYPE_NONE; /* no really -- nothing in there */
+}
+
+
+
+static void configure_pmu_for_charger(struct pcf50633_data *pcf,
+				      enum charger_type type)
+{
+	switch (type) {
+	case CHARGER_TYPE_NONE:
+		__reg_write(pcf, PCF50633_REG_MBCC7,
+						    PCF50633_MBCC7_USB_SUSPEND);
+		break;
+	/*
+	 * the PCF50633 has a feature that it will supply only excess current
+	 * from the charger that is not used to power the device.  So this
+	 * 500mA setting is "up to 500mA" according to that.
+	 */
+	case CHARGER_TYPE_HOSTUSB:
+		__reg_write(pcf, PCF50633_REG_MBCC7, PCF50633_MBCC7_USB_500mA);
+		break;
+	case CHARGER_TYPE_1A:
+		__reg_write(pcf, PCF50633_REG_MBCC7, PCF50633_MBCC7_USB_1000mA);
+		break;
+	}
+}
+
+static void trigger_next_adc_job_if_any(struct pcf50633_data *pcf)
+{
+	if (pcf->adc_queue_head == pcf->adc_queue_tail)
+		return;
+	async_adc_read_setup(pcf,
+			     pcf->adc_queue_mux[pcf->adc_queue_tail],
+			     pcf->adc_queue_avg[pcf->adc_queue_tail]);
+}
+
+static void add_request_to_adc_queue(struct pcf50633_data *pcf,
+				     int mux, int avg)
+{
+	int old_head = pcf->adc_queue_head;
+	pcf->adc_queue_mux[pcf->adc_queue_head] = mux;
+	pcf->adc_queue_avg[pcf->adc_queue_head] = avg;
+
+	pcf->adc_queue_head = (pcf->adc_queue_head + 1) &
+			      (MAX_ADC_FIFO_DEPTH - 1);
+
+	/* it was idle before we just added this?  we need to kick it then */
+	if (old_head == pcf->adc_queue_tail)
+		trigger_next_adc_job_if_any(pcf);
+}
+
 static void pcf50633_work(struct work_struct *work)
 {
 	struct pcf50633_data *pcf =
 			container_of(work, struct pcf50633_data, work);
 	u_int8_t pcfirq[5];
 	int ret;
+	int tail;
 
 	mutex_lock(&pcf->working_lock);
 	pcf->working = 1;
@@ -569,28 +634,34 @@ 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);
-
+		/* 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 ");
-		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);
+		/* 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);
+			/* 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_ALARM) {
 		DEBUGPC("ALARM ");
 		if (pcf->pdata->used_features & PCF50633_FEAT_RTC)
-			rtc_update_irq(pcf->rtc, 1,
-				       RTC_AF | RTC_IRQF);
+			rtc_update_irq(pcf->rtc, 1, RTC_AF | RTC_IRQF);
 	}
 	if (pcfirq[0] & PCF50633_INT1_SECOND) {
 		DEBUGPC("SECOND ");
 		if (pcf->flags & PCF50633_F_RTC_SECOND)
-			rtc_update_irq(pcf->rtc, 1,
-				       RTC_PF | RTC_IRQF);
+			rtc_update_irq(pcf->rtc, 1, RTC_PF | RTC_IRQF);
 
 		if (pcf->onkey_seconds >= 0 &&
 		    pcf->flags & PCF50633_F_PWR_PRESSED) {
@@ -647,6 +718,10 @@ static void pcf50633_work(struct work_struct *work)
 	}
 	if (pcfirq[2] & PCF50633_INT3_CHGHALT) {
 		DEBUGPC("CHGHALT ");
+		/*
+		 * this is really "battery not pulling current" -- it can
+		 * appear with no battery attached
+		 */
 		/* FIXME: signal this to userspace */
 	}
 	if (pcfirq[2] & PCF50633_INT3_THLIMON) {
@@ -670,6 +745,26 @@ static void pcf50633_work(struct work_struct *work)
 	if (pcfirq[2] & PCF50633_INT3_ADCRDY) {
 		/* ADC result ready */
 		DEBUGPC("ADCRDY ");
+		tail = pcf->adc_queue_tail;
+		pcf->adc_queue_tail = (pcf->adc_queue_tail + 1) &
+				      (MAX_ADC_FIFO_DEPTH - 1);
+
+		switch (pcf->adc_queue_mux[tail]) {
+		case PCF50633_ADCC1_MUX_BATSNS_RES: /* battery voltage */
+			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);
+			pcf->charger_type = interpret_charger_type_from_adc(
+					     pcf, pcf->charger_adc_result_raw);
+			configure_pmu_for_charger(pcf, pcf->charger_type);
+			break;
+		default:
+			async_adc_complete(pcf);
+			break;
+		}
+		trigger_next_adc_job_if_any(pcf);
 	}
 	if (pcfirq[2] & PCF50633_INT3_ONKEY1S) {
 		/* ONKEY pressed for more than 1 second */
@@ -729,11 +824,24 @@ static void pcf50633_work(struct work_struct *work)
 		DEBUGPC("HIGHTMP ");
 		apm_queue_event(APM_CRITICAL_SUSPEND);
 	}
-	if (pcfirq[3] & (PCF50633_INT4_AUTOPWRFAIL|PCF50633_INT4_DWN1PWRFAIL|
-		   PCF50633_INT4_DWN2PWRFAIL|PCF50633_INT4_LEDPWRFAIL|
-		   PCF50633_INT4_LEDOVP)) {
-		/* Some regulator failed */
-		DEBUGPC("REGULATOR_FAIL ");
+	if (pcfirq[3] & PCF50633_INT4_AUTOPWRFAIL) {
+		DEBUGPC("PCF50633_INT4_AUTOPWRFAIL ");
+		/* FIXME: deal with this */
+	}
+	if (pcfirq[3] & PCF50633_INT4_DWN1PWRFAIL) {
+		DEBUGPC("PCF50633_INT4_DWN1PWRFAIL ");
+		/* FIXME: deal with this */
+	}
+	if (pcfirq[3] & PCF50633_INT4_DWN2PWRFAIL) {
+		DEBUGPC("PCF50633_INT4_DWN2PWRFAIL ");
+		/* FIXME: deal with this */
+	}
+	if (pcfirq[3] & PCF50633_INT4_LEDPWRFAIL) {
+		DEBUGPC("PCF50633_INT4_LEDPWRFAIL ");
+		/* FIXME: deal with this */
+	}
+	if (pcfirq[3] & PCF50633_INT4_LEDOVP) {
+		DEBUGPC("PCF50633_INT4_LEDOVP ");
 		/* FIXME: deal with this */
 	}
 
@@ -782,10 +890,21 @@ static u_int8_t battvolt_scale(u_int16_t battvolt)
 
 u_int16_t pcf50633_battvolt(struct pcf50633_data *pcf)
 {
-	u_int16_t adc;
-	adc = adc_read(pcf, PCF50633_ADCC1_MUX_BATSNS_RES, 0, NULL);
+	int count = 10;
+
+	pcf->flag_bat_voltage_read = -1;
+	add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_BATSNS_RES,
+				      PCF50633_ADCC1_AVERAGE_16);
+
+	while ((count--) && (pcf->flag_bat_voltage_read < 0))
+		msleep(1);
 
-	return adc_to_batt_millivolts(adc);
+	if (count < 0) { /* timeout somehow */
+		DEBUGPC("pcf50633_battvolt timeout :-(\n");
+		return -1;
+	}
+
+	return adc_to_batt_millivolts(pcf->flag_bat_voltage_read);
 }
 EXPORT_SYMBOL_GPL(pcf50633_battvolt);
 
@@ -957,8 +1076,9 @@ void pcf50633_charge_enable(struct pcf50633_data *pcf, int on)
 }
 EXPORT_SYMBOL_GPL(pcf50633_charge_enable);
 
+#if 0
 #define ONE			1000000
-static inline u_int16_t adc_to_rntc(struct pcf50633_data *pcf, u_int16_t adc)
+static u_int16_t adc_to_rntc(struct pcf50633_data *pcf, u_int16_t adc)
 {
 	u_int32_t r_batt = (adc * pcf->pdata->r_fix_batt) / (1023 - adc);
 	u_int16_t r_ntc;
@@ -968,57 +1088,25 @@ static inline u_int16_t adc_to_rntc(struct pcf50633_data *pcf, u_int16_t adc)
 
 	return r_ntc;
 }
-
-static inline int16_t rntc_to_temp(u_int16_t rntc)
-{
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(ntc_table_tn11_3h103j); i++) {
-		if (rntc > ntc_table_tn11_3h103j[i])
-			return i - 10;
-	}
-	return 2342;
-}
-
+#endif
 static ssize_t show_battemp(struct device *dev, struct device_attribute *attr,
 			    char *buf)
 {
-#if 0
-	struct i2c_client *client = to_i2c_client(dev);
-	struct pcf50633_data *pcf = i2c_get_clientdata(client);
-	u_int16_t adc;
-
-	adc = adc_read(pcf, PCF50633_ADCC1_MUX_BATTEMP, 0, NULL);
-
-	return sprintf(buf, "%d\n", rntc_to_temp(adc_to_rntc(pcf, adc)));
-#endif
 	return sprintf(buf, "\n");
 }
 static DEVICE_ATTR(battemp, S_IRUGO | S_IWUSR, show_battemp, NULL);
-
-static inline u_int16_t adc_to_chg_milliamps(struct pcf50633_data *pcf,
+#if 0
+static u_int16_t adc_to_chg_milliamps(struct pcf50633_data *pcf,
 					     u_int16_t adc_adcin1,
 					     u_int16_t adc_batvolt)
 {
 	u_int32_t res = ((adc_adcin1 - adc_batvolt) * 6000);
 	return res / (pcf->pdata->r_sense_milli * 1024 / 1000);
 }
-
+#endif
 static ssize_t show_chgcur(struct device *dev, struct device_attribute *attr,
 			   char *buf)
 {
-#if 0
-	struct i2c_client *client = to_i2c_client(dev);
-	struct pcf50633_data *pcf = i2c_get_clientdata(client);
-	u_int16_t adc_batvolt, adc_adcin1;
-	u_int16_t ma;
-
-	adc_batvolt = adc_read(pcf, PCF50606_ADCMUX_BATVOLT_ADCIN1,
-			       &adc_adcin1);
-	ma = adc_to_chg_milliamps(pcf, adc_adcin1, adc_batvolt);
-
-	return sprintf(buf, "%u\n", ma);
-#endif
 	return sprintf(buf, "\n");
 }
 static DEVICE_ATTR(chgcur, S_IRUGO | S_IWUSR, show_chgcur, NULL);
@@ -1365,6 +1453,50 @@ static struct backlight_ops pcf50633bl_ops = {
 };
 
 /*
+ * Charger type
+ */
+
+static ssize_t show_charger_type(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+	static const char *names_charger_type[] = {
+		[CHARGER_TYPE_NONE] 	= "none",
+		[CHARGER_TYPE_HOSTUSB] 	= "host/500mA usb",
+		[CHARGER_TYPE_1A] 	= "charger 1A",
+	};
+	static const char *names_charger_modes[] = {
+		[PCF50633_MBCC7_USB_1000mA] 	= "1A",
+		[PCF50633_MBCC7_USB_500mA] 	= "500mA",
+		[PCF50633_MBCC7_USB_100mA] 	= "100mA",
+		[PCF50633_MBCC7_USB_SUSPEND] 	= "suspend",
+	};
+	int mode = reg_read(pcf, PCF50633_REG_MBCC7) & PCF56033_MBCC7_USB_MASK;
+
+	return sprintf(buf, "%s mode %s\n",
+			    names_charger_type[pcf->charger_type],
+			    names_charger_modes[mode]);
+}
+
+static DEVICE_ATTR(charger_type, 0444, show_charger_type, NULL);
+
+/*
+ * Charger adc
+ */
+
+static ssize_t show_charger_adc(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+
+	return sprintf(buf, "%d\n", pcf->charger_adc_result_raw);
+}
+
+static DEVICE_ATTR(charger_adc, 0444, show_charger_adc, NULL);
+
+/*
  * Dump regs
  */
 
@@ -1422,6 +1554,9 @@ static struct platform_device gta01_pm_bt_dev = {
 };
 #endif
 
+/*
+ * CARE!  This table is modified at runtime!
+ */
 static struct attribute *pcf_sysfs_entries[] = {
 	&dev_attr_voltage_auto.attr,
 	&dev_attr_voltage_down1.attr,
@@ -1434,8 +1569,16 @@ static struct attribute *pcf_sysfs_entries[] = {
 	&dev_attr_voltage_ldo5.attr,
 	&dev_attr_voltage_ldo6.attr,
 	&dev_attr_voltage_hcldo.attr,
+	&dev_attr_charger_type.attr,
+	&dev_attr_charger_adc.attr,
 	&dev_attr_dump_regs.attr,
-	NULL
+	NULL, /* going to add things at this point! */
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
 };
 
 static struct attribute_group pcf_attr_group = {





More information about the openmoko-kernel mailing list