pm8xxx-led: Add API to configure pm8xxx LEDs

Add pm8xxx_led_configure API. It takes LED Id, mode,
and maximum current to be set.
Moreover,
1) Convert current values to actual current level using
   appropriate LED current factor.
2) Check brightness values against acceptable
   limits.

Signed-off-by: Jay Chokshi <jchokshi@codeaurora.org>
diff --git a/drivers/leds/leds-pm8xxx.c b/drivers/leds/leds-pm8xxx.c
index c34bf30..7497d04 100644
--- a/drivers/leds/leds-pm8xxx.c
+++ b/drivers/leds/leds-pm8xxx.c
@@ -10,6 +10,8 @@
  * GNU General Public License for more details.
  */
 
+#define pr_fmt(fmt)	"%s: " fmt, __func__
+
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/slab.h>
@@ -35,24 +37,27 @@
 #define PM8XXX_DRV_LED_CTRL_MASK	0xf8
 #define PM8XXX_DRV_LED_CTRL_SHIFT	0x03
 
-#define MAX_FLASH_LED_CURRENT	300
-#define MAX_LC_LED_CURRENT	40
-#define MAX_KP_BL_LED_CURRENT	300
+#define MAX_FLASH_LED_CURRENT		300
+#define MAX_LC_LED_CURRENT		40
+#define MAX_KP_BL_LED_CURRENT		300
 
-#define MAX_KEYPAD_BL_LEVEL	(1 << 4)
-#define MAX_LED_DRV_LEVEL	20 /* 2 * 20 mA */
+#define PM8XXX_ID_LED_CURRENT_FACTOR	2  /* Iout = x * 2mA */
+#define PM8XXX_ID_FLASH_CURRENT_FACTOR	20 /* Iout = x * 20mA */
+
+#define PM8XXX_FLASH_MODE_DBUS1		1
+#define PM8XXX_FLASH_MODE_DBUS2		2
+#define PM8XXX_FLASH_MODE_PWM		3
 
 #define PM8XXX_LED_OFFSET(id) ((id) - PM8XXX_ID_LED_0)
 
-#define MAX_KB_LED_BRIGHTNESS		15
 #define MAX_LC_LED_BRIGHTNESS		20
-#define MAX_FLASH_LED_BRIGHTNESS	15
+#define MAX_FLASH_BRIGHTNESS		15
+#define MAX_KB_LED_BRIGHTNESS		15
 
 /**
  * struct pm8xxx_led_data - internal led data structure
  * @led_classdev - led class device
  * @id - led index
- * @led_brightness - led brightness levels
  * @work - workqueue for led
  * @lock - to protect the transactions
  * @reg - cached value of led register
@@ -61,12 +66,13 @@
 	struct led_classdev	cdev;
 	int			id;
 	u8			reg;
-	enum led_brightness	brightness;
 	struct device		*dev;
 	struct work_struct	work;
 	struct mutex		lock;
 };
 
+static struct pm8xxx_led_data *pm8xxx_leds;
+
 static void led_kp_set(struct pm8xxx_led_data *led, enum led_brightness value)
 {
 	int rc;
@@ -133,21 +139,22 @@
 {
 	struct pm8xxx_led_data *led = container_of(work,
 					 struct pm8xxx_led_data, work);
+	int level = led->cdev.brightness;
 
 	mutex_lock(&led->lock);
 
 	switch (led->id) {
 	case PM8XXX_ID_LED_KB_LIGHT:
-		led_kp_set(led, led->brightness);
+		led_kp_set(led, level);
 	break;
 	case PM8XXX_ID_LED_0:
 	case PM8XXX_ID_LED_1:
 	case PM8XXX_ID_LED_2:
-		led_lc_set(led, led->brightness);
+		led_lc_set(led, level);
 	break;
 	case PM8XXX_ID_FLASH_LED_0:
 	case PM8XXX_ID_FLASH_LED_1:
-		led_flash_set(led, led->brightness);
+		led_flash_set(led, level);
 	break;
 	}
 
@@ -157,21 +164,106 @@
 static void pm8xxx_led_set(struct led_classdev *led_cdev,
 	enum led_brightness value)
 {
-	struct pm8xxx_led_data *led;
+	struct	pm8xxx_led_data *led;
 
 	led = container_of(led_cdev, struct pm8xxx_led_data, cdev);
 
-	led->brightness = value;
+	if (value < LED_OFF || value > led->cdev.max_brightness) {
+		dev_err(led->cdev.dev, "Invalid brightness value exceeds");
+		return;
+	}
+
+	led->cdev.brightness = value;
 	schedule_work(&led->work);
 }
 
+int pm8xxx_led_config(enum pm8xxx_leds led_id,
+		enum pm8xxx_led_modes led_mode, int max_current)
+{
+	int rc = 0;
+	struct pm8xxx_led_data *led = pm8xxx_leds;
+
+	if (led_id < PM8XXX_ID_LED_KB_LIGHT ||
+			led_id > PM8XXX_ID_FLASH_LED_1) {
+		pr_err("Invalid led no. provided\n");
+		return -EINVAL;
+	}
+
+	if (led_mode < PM8XXX_LED_MODE_MANUAL ||
+			led_mode > PM8XXX_LED_MODE_DTEST4) {
+		pr_err("Invalid led mode provided\n");
+		return -EINVAL;
+	}
+
+	while (led->id != 0) {
+		if (led->id == led_id)
+			break;
+		led++;
+	}
+
+	if (led->id == 0) {
+		pr_err("Could not find LED with the given id");
+		return -EINVAL;
+	}
+
+	mutex_lock(&led->lock);
+
+	switch (led_id) {
+	case PM8XXX_ID_LED_0:
+	case PM8XXX_ID_LED_1:
+	case PM8XXX_ID_LED_2:
+		led->cdev.max_brightness = max_current /
+						PM8XXX_ID_LED_CURRENT_FACTOR;
+		led->reg |= led_mode;
+		break;
+	case PM8XXX_ID_LED_KB_LIGHT:
+	case PM8XXX_ID_FLASH_LED_0:
+	case PM8XXX_ID_FLASH_LED_1:
+		led->cdev.max_brightness = max_current /
+						PM8XXX_ID_FLASH_CURRENT_FACTOR;
+		switch (led_mode) {
+		case PM8XXX_LED_MODE_PWM1:
+		case PM8XXX_LED_MODE_PWM2:
+		case PM8XXX_LED_MODE_PWM3:
+			led->reg |=
+				PM8XXX_FLASH_MODE_PWM;
+			break;
+		case PM8XXX_LED_MODE_DTEST1:
+			led->reg |=
+				PM8XXX_FLASH_MODE_DBUS1;
+			break;
+		case PM8XXX_LED_MODE_DTEST2:
+			led->reg |=
+				PM8XXX_FLASH_MODE_DBUS2;
+			break;
+		default:
+			led->reg |=
+				PM8XXX_LED_MODE_MANUAL;
+			break;
+		}
+		break;
+	default:
+		rc = -EINVAL;
+		pr_err("LED Id is invalid");
+		break;
+	}
+
+	mutex_unlock(&led->lock);
+
+	if (!rc)
+		pm8xxx_led_set(&led->cdev, led->cdev.max_brightness);
+
+	return rc;
+}
+EXPORT_SYMBOL(pm8xxx_led_config);
+
 static enum led_brightness pm8xxx_led_get(struct led_classdev *led_cdev)
 {
 	struct pm8xxx_led_data *led;
 
 	led = container_of(led_cdev, struct pm8xxx_led_data, cdev);
 
-	return led->brightness;
+	return led->cdev.brightness;
 }
 
 static int __devinit get_max_brightness(enum pm8xxx_leds id)
@@ -185,7 +277,7 @@
 		return MAX_LC_LED_BRIGHTNESS;
 	case PM8XXX_ID_FLASH_LED_0:
 	case PM8XXX_ID_FLASH_LED_1:
-		return MAX_FLASH_LED_CURRENT;
+		return MAX_FLASH_BRIGHTNESS;
 	default:
 		return 0;
 	}
@@ -234,7 +326,10 @@
 		return -EINVAL;
 	}
 
-	led = kcalloc(pdata->num_leds, sizeof(*led), GFP_KERNEL);
+	/* Let the last member of the list be zero to
+	 * mark the end of the list.
+	 */
+	led = kcalloc(pdata->num_leds + 1, sizeof(*led), GFP_KERNEL);
 	if (led == NULL) {
 		dev_err(&pdev->dev, "failed to alloc memory\n");
 		return -ENOMEM;
@@ -260,9 +355,9 @@
 		led_dat->cdev.brightness_get    = pm8xxx_led_get;
 		led_dat->cdev.brightness	= LED_OFF;
 		led_dat->cdev.flags		= LED_CORE_SUSPENDRESUME;
-
-		led_dat->cdev.max_brightness = get_max_brightness(led_dat->id);
-		led_dat->dev = &pdev->dev;
+		led_dat->cdev.max_brightness	=
+						get_max_brightness(led_dat->id);
+		led_dat->dev			= &pdev->dev;
 
 		rc =  get_init_value(led_dat, &led_dat->reg);
 		if (rc < 0)
@@ -279,6 +374,8 @@
 		}
 	}
 
+	pm8xxx_leds = led;
+
 	platform_set_drvdata(pdev, led);
 
 	return 0;
@@ -325,7 +422,7 @@
 {
 	return platform_driver_register(&pm8xxx_led_driver);
 }
-module_init(pm8xxx_led_init);
+subsys_initcall(pm8xxx_led_init);
 
 static void __exit pm8xxx_led_exit(void)
 {