led: leds-qpnp: Add work queue to LED driver

Regulator operation requires non-atomic context and current driver
does not put LED to sleep. This causes APPs crash. By introducing
work queue, the problem is solved.

Change-Id: Ifbd9f37fee893cb48c06e41a82202b93f3313704
Signed-off-by: Chun Zhang <chunz@codeaurora.org>
diff --git a/drivers/leds/leds-qpnp.c b/drivers/leds/leds-qpnp.c
index bac8678..92ca5a0 100644
--- a/drivers/leds/leds-qpnp.c
+++ b/drivers/leds/leds-qpnp.c
@@ -414,6 +414,7 @@
  * struct qpnp_led_data - internal led data structure
  * @led_classdev - led class device
  * @delayed_work - delayed work for turning off the LED
+ * @work - workqueue for led
  * @id - led index
  * @base_reg - base register given in device tree
  * @lock - to protect the transactions
@@ -427,11 +428,12 @@
 	struct led_classdev	cdev;
 	struct spmi_device	*spmi_dev;
 	struct delayed_work	dwork;
+	struct work_struct	work;
 	int			id;
 	u16			base;
 	u8			reg;
 	u8			num_leds;
-	spinlock_t		lock;
+	struct mutex		lock;
 	struct wled_config_data *wled_cfg;
 	struct flash_config_data	*flash_cfg;
 	struct kpdbl_config_data	*kpdbl_cfg;
@@ -943,9 +945,7 @@
 static void qpnp_led_set(struct led_classdev *led_cdev,
 				enum led_brightness value)
 {
-	int rc, i;
 	struct qpnp_led_data *led;
-	struct qpnp_led_data *led_array;
 
 	led = container_of(led_cdev, struct qpnp_led_data, cdev);
 	if (value < LED_OFF || value > led->cdev.max_brightness) {
@@ -953,6 +953,16 @@
 		return;
 	}
 
+	led->cdev.brightness = value;
+	schedule_work(&led->work);
+}
+
+static void __qpnp_led_work(struct qpnp_led_data *led,
+				enum led_brightness value)
+{
+	int rc, i;
+	struct qpnp_led_data *led_array;
+
 	if (led->id == QPNP_ID_FLASH1_LED0 || led->id == QPNP_ID_FLASH1_LED1) {
 		if (!led->flash_cfg->flash_on && value > 0) {
 			led_array = dev_get_drvdata(&led->spmi_dev->dev);
@@ -980,8 +990,7 @@
 		}
 	}
 
-	spin_lock(&led->lock);
-	led->cdev.brightness = value;
+	mutex_lock(&led->lock);
 
 	switch (led->id) {
 	case QPNP_ID_WLED:
@@ -1021,7 +1030,7 @@
 		dev_err(&led->spmi_dev->dev, "Invalid LED(%d)\n", led->id);
 		break;
 	}
-	spin_unlock(&led->lock);
+	mutex_unlock(&led->lock);
 
 	if (led->id == QPNP_ID_FLASH1_LED0 || led->id == QPNP_ID_FLASH1_LED1) {
 		if (led->flash_cfg->flash_on && !value) {
@@ -1050,6 +1059,16 @@
 	}
 }
 
+static void qpnp_led_work(struct work_struct *work)
+{
+	struct qpnp_led_data *led = container_of(work,
+					struct qpnp_led_data, work);
+
+	__qpnp_led_work(led, led->cdev.brightness);
+
+	return;
+}
+
 static int __devinit qpnp_led_set_max_brightness(struct qpnp_led_data *led)
 {
 	switch (led->id) {
@@ -2331,7 +2350,8 @@
 			goto fail_id_check;
 		}
 
-		spin_lock_init(&led->lock);
+		mutex_init(&led->lock);
+		INIT_WORK(&led->work, qpnp_led_work);
 
 		rc =  qpnp_led_initialize(led);
 		if (rc < 0)
@@ -2380,7 +2400,8 @@
 		/* configure default state */
 		if (led->default_on) {
 			led->cdev.brightness = led->cdev.max_brightness;
-			qpnp_led_set(&led->cdev, led->cdev.brightness);
+			__qpnp_led_work(led, led->cdev.brightness);
+			schedule_work(&led->work);
 			if (led->turn_off_delay_ms > 0)
 				qpnp_led_turn_off(led);
 		} else
@@ -2392,8 +2413,11 @@
 	return 0;
 
 fail_id_check:
-	for (i = 0; i < parsed_leds; i++)
+	for (i = 0; i < parsed_leds; i++) {
+		mutex_destroy(&led_array[i].lock);
 		led_classdev_unregister(&led_array[i].cdev);
+	}
+
 	return rc;
 }
 
@@ -2403,6 +2427,8 @@
 	int i, parsed_leds = led_array->num_leds;
 
 	for (i = 0; i < parsed_leds; i++) {
+		cancel_work_sync(&led_array[i].work);
+		mutex_destroy(&led_array[i].lock);
 		led_classdev_unregister(&led_array[i].cdev);
 		switch (led_array[i].id) {
 		case QPNP_ID_WLED: