[PATCH 6/9] fix-pcf50633-usb-curlim-workqueue-migration.patch

Andy Green andy at openmoko.com
Fri Jun 13 23:16:37 CEST 2008


pcf50633 needs to take responsibility for managing current limit
changes asycnhrnously, ie, from USB stack enumeration.  It's a feature of
pcf50633 not mach-gta02.c, and we can do better with taking care about
keeping it from firing at a bad time in there too.

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

 arch/arm/mach-s3c2440/mach-gta02.c |   23 +-------
 drivers/i2c/chips/pcf50633.c       |   99 +++++++++++++++++++++++++++++++++++-
 include/linux/pcf50633.h           |    5 ++
 3 files changed, 104 insertions(+), 23 deletions(-)

diff --git a/arch/arm/mach-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c
index ac4539d..9c4e122 100644
--- a/arch/arm/mach-s3c2440/mach-gta02.c
+++ b/arch/arm/mach-s3c2440/mach-gta02.c
@@ -816,29 +816,11 @@ static void gta02_udc_command(enum s3c2410_udc_cmd_e cmd)
 	}
 }
 
-/* use a work queue, since I2C API inherently schedules
- * and we get called in hardirq context from UDC driver */
-
-struct vbus_draw {
-	struct work_struct work;
-	int ma;
-};
-static struct vbus_draw gta02_udc_vbus_drawer;
-
-static void __gta02_udc_vbus_draw(struct work_struct *work)
-{
-	if (!pcf50633_global) {
-		printk(KERN_ERR  "pcf50633 not initialized yet, can't change "
-		       "vbus_draw\n");
-		return;
-	}
-	pcf50633_usb_curlim_set(pcf50633_global, gta02_udc_vbus_drawer.ma);
-}
+/* get PMU to set USB current limit accordingly */
 
 static void gta02_udc_vbus_draw(unsigned int ma)
 {
-	gta02_udc_vbus_drawer.ma = ma;
-	schedule_work(&gta02_udc_vbus_drawer.work);
+	pcf50633_notify_usb_current_limit_change(pcf50633_global, ma);
 }
 
 static struct s3c2410_udc_mach_info gta02_udc_cfg = {
@@ -1485,7 +1467,6 @@ static void __init gta02_machine_init(void)
 	s3c2410_gpio_setpin(S3C2410_GPD13, 1);
 	s3c2410_gpio_cfgpin(S3C2410_GPD13, S3C2410_GPIO_OUTPUT);
 
-	INIT_WORK(&gta02_udc_vbus_drawer.work, __gta02_udc_vbus_draw);
 	s3c24xx_udc_set_platdata(&gta02_udc_cfg);
 	set_s3c2410ts_info(&gta02_ts_cfg);
 
diff --git a/drivers/i2c/chips/pcf50633.c b/drivers/i2c/chips/pcf50633.c
index 96857a3..e53bec0 100644
--- a/drivers/i2c/chips/pcf50633.c
+++ b/drivers/i2c/chips/pcf50633.c
@@ -125,6 +125,7 @@ struct pcf50633_data {
 	int have_been_suspended;
 	int usb_removal_count;
 	unsigned char pcfirq_resume[5];
+	int probe_completed;
 
 	/* if he pulls battery while charging, we notice that and correctly
 	 * report that the charger is idle.  But there is no interrupt that
@@ -138,6 +139,12 @@ struct pcf50633_data {
 	int usb_removal_count_nobat;
 	int jiffies_last_bat_ins;
 
+	/* current limit notification handler stuff */
+	struct mutex working_lock_usb_curlimit;
+	struct work_struct work_usb_curlimit;
+	int pending_curlimit;
+	int usb_removal_count_usb_curlimit;
+
 	int last_curlim_set;
 
 	int coldplug_done; /* cleared by probe, set by first work service */
@@ -603,6 +610,92 @@ static void add_request_to_adc_queue(struct pcf50633_data *pcf,
 		trigger_next_adc_job_if_any(pcf);
 }
 
+/*
+ * we get run to handle servicing the async notification from USB stack that
+ * we got enumerated and allowed to draw a particular amount of current
+ */
+
+static void pcf50633_work_usbcurlim(struct work_struct *work)
+{
+	struct pcf50633_data *pcf =
+		    container_of(work, struct pcf50633_data, work_usb_curlimit);
+
+	mutex_lock(&pcf->working_lock_usb_curlimit);
+
+	dev_info(&pcf->client.dev, "pcf50633_work_usbcurlim\n");
+
+	if (!pcf->probe_completed)
+		goto reschedule;
+
+	/* we got a notification from USB stack before we completed resume...
+	 * that can only make trouble, reschedule for a retry
+	 */
+	if (pcf->have_been_suspended && (pcf->have_been_suspended < 3))
+		goto reschedule;
+
+	/*
+	 * did he pull USB before we managed to set the limit?
+	 */
+	if (pcf->usb_removal_count_usb_curlimit != pcf->usb_removal_count)
+		goto bail;
+
+	/* OK let's set the requested limit and finish */
+
+	dev_info(&pcf->client.dev, "pcf50633_work_usbcurlim setting %dmA\n",
+							 pcf->pending_curlimit);
+	pcf50633_usb_curlim_set(pcf, pcf->pending_curlimit);
+
+bail:
+	mutex_unlock(&pcf->working_lock_usb_curlimit);
+	return;
+
+reschedule:
+	dev_info(&pcf->client.dev, "pcf50633_work_usbcurlim rescheduling\n");
+	if (!schedule_work(&pcf->work_usb_curlimit))
+		dev_err(&pcf->client.dev, "work item may be lost\n");
+
+	mutex_unlock(&pcf->working_lock_usb_curlimit);
+	/* don't spew, delaying whatever else is happening */
+	msleep(1);
+}
+
+
+/* this is an export to allow machine to set USB current limit according to
+ * notifications of USB stack about enumeration state.  We spawn a work
+ * function to handle the actual setting, because suspend / resume and such
+ * can be in a bad state since this gets called externally asychronous to
+ * anything else going on in pcf50633.
+ */
+
+int pcf50633_notify_usb_current_limit_change(struct pcf50633_data *pcf,
+								unsigned int ma)
+{
+	/* can happen if he calls with pcf50633_global before probe
+	 * have to bail with error since we can't even schedule the work
+	 */
+	if (!pcf) {
+		dev_err(&pcf->client.dev,
+			"pcf50633_notify_usb_current_limit_change "
+			"called with NULL pcf\n");
+		return -EBUSY;
+	}
+
+	dev_info(&pcf->client.dev,
+		 "pcf50633_notify_usb_current_limit_change %dmA\n", ma);
+
+	/* prepare to detect USB power removal before we complete */
+	pcf->usb_removal_count_usb_curlimit = pcf->usb_removal_count;
+
+	pcf->pending_curlimit = ma;
+
+	if (!schedule_work(&pcf->work_usb_curlimit))
+		dev_err(&pcf->client.dev, "work item may be lost\n");
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50633_notify_usb_current_limit_change);
+
+
 /* 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
@@ -1967,8 +2060,10 @@ 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);
+	mutex_init(&data->working_lock_usb_curlimit);
 	INIT_WORK(&data->work, pcf50633_work);
 	INIT_WORK(&data->work_nobat, pcf50633_work_nobat);
+	INIT_WORK(&data->work_usb_curlimit, pcf50633_work_usbcurlim);
 	data->irq = irq;
 	data->working = 0;
 	data->onkey_seconds = -1;
@@ -2068,15 +2163,15 @@ static int pcf50633_detect(struct i2c_adapter *adapter, int address, int kind)
 		backlight_update_status(data->backlight);
 	}
 
+	data->probe_completed = 1;
 
 	if (machine_is_neo1973_gta02()) {
 		gta01_pm_gps_dev.dev.parent = &new_client->dev;
 		gta01_pm_bt_dev.dev.parent = &new_client->dev;
 		platform_device_register(&gta01_pm_bt_dev);
 		platform_device_register(&gta01_pm_gps_dev);
-	} else {
+	} else
 		apm_get_power_status = pcf50633_get_power_status;
-	}
 
 	return 0;
 exit_rtc:
diff --git a/include/linux/pcf50633.h b/include/linux/pcf50633.h
index 0522d92..fa1c7e8 100644
--- a/include/linux/pcf50633.h
+++ b/include/linux/pcf50633.h
@@ -129,6 +129,11 @@ extern void
 pcf50633_register_resume_dependency(struct pcf50633_data *pcf,
 					struct resume_dependency *dep);
 
+extern int
+pcf50633_notify_usb_current_limit_change(struct pcf50633_data *pcf,
+							       unsigned int ma);
+
+
 /* 0 = initialized and resumed and ready to roll, !=0 = either not
  * initialized or not resumed yet
  */





More information about the openmoko-kernel mailing list