leds: leds-qpnp: Add support for MPP controlled leds

Add appropriate settings to allow software to control
leds via MPP ports.

Change-Id: I923e31756526b6afa91ec41efc64414f63b8b888
Acked-by: Chun Zhang <chunz@qti.qualcomm.com>
Signed-off-by: Amy Maloche <amaloche@codeaurora.org>
diff --git a/drivers/leds/leds-qpnp.c b/drivers/leds/leds-qpnp.c
index 1d34b06..3667296 100644
--- a/drivers/leds/leds-qpnp.c
+++ b/drivers/leds/leds-qpnp.c
@@ -159,6 +159,27 @@
 #define	PWM_LUT_MAX_SIZE		63
 #define RGB_LED_DISABLE			0x00
 
+#define MPP_MAX_LEVEL			LED_FULL
+#define LED_MPP_MODE_CTRL(base)		(base + 0x40)
+#define LED_MPP_VIN_CTRL(base)		(base + 0x41)
+#define LED_MPP_EN_CTRL(base)		(base + 0x46)
+#define LED_MPP_SINK_CTRL(base)		(base + 0x4C)
+
+#define LED_MPP_CURRENT_DEFAULT		10
+#define LED_MPP_SOURCE_SEL_DEFAULT	LED_MPP_MODE_ENABLE
+
+#define LED_MPP_SINK_MASK		0x07
+#define LED_MPP_MODE_MASK		0x7F
+#define LED_MPP_EN_MASK			0x80
+
+#define LED_MPP_MODE_SINK		(0x06 << 4)
+#define LED_MPP_MODE_ENABLE		0x01
+#define LED_MPP_MODE_OUTPUT		0x10
+#define LED_MPP_MODE_DISABLE		0x00
+#define LED_MPP_EN_ENABLE		0x80
+#define LED_MPP_EN_DISABLE		0x00
+
+#define MPP_SOURCE_DTEST1		0x08
 /**
  * enum qpnp_leds - QPNP supported led ids
  * @QPNP_ID_WLED - White led backlight
@@ -170,6 +191,7 @@
 	QPNP_ID_RGB_RED,
 	QPNP_ID_RGB_GREEN,
 	QPNP_ID_RGB_BLUE,
+	QPNP_ID_LED_MPP,
 	QPNP_ID_MAX,
 };
 
@@ -240,6 +262,11 @@
 static u8 rgb_pwm_debug_regs[] = {
 	0x45, 0x46, 0x47,
 };
+
+static u8 mpp_debug_regs[] = {
+	0x40, 0x41, 0x42, 0x45, 0x46, 0x4c,
+};
+
 /**
  *  wled_config_data - wled configuration data
  *  @num_strings - number of wled strings supported
@@ -264,6 +291,16 @@
 };
 
 /**
+ *  mpp_config_data - mpp configuration data
+ *  @current_setting - current setting, 5ma-40ma in 5ma increments
+ */
+struct mpp_config_data {
+	u8	current_setting;
+	u8	source_sel;
+	u8	mode_ctrl;
+};
+
+/**
  *  flash_config_data - flash configuration data
  *  @current_prgm - current to be programmed, scaled by max level
  *  @clamp_curr - clamp current to use
@@ -336,6 +373,7 @@
 	struct wled_config_data *wled_cfg;
 	struct flash_config_data	*flash_cfg;
 	struct rgb_config_data	*rgb_cfg;
+	struct mpp_config_data	*mpp_cfg;
 	int			max_current;
 	bool			default_on;
 	int			turn_off_delay_ms;
@@ -458,6 +496,68 @@
 	return 0;
 }
 
+static int qpnp_mpp_set(struct qpnp_led_data *led)
+{
+	int rc, val;
+
+	if (led->cdev.brightness) {
+		val = (led->cdev.brightness * LED_MPP_SINK_MASK) / LED_FULL;
+		rc = qpnp_led_masked_write(led,
+				LED_MPP_SINK_CTRL(led->base),
+				LED_MPP_SINK_MASK, val);
+		if (rc) {
+			dev_err(&led->spmi_dev->dev,
+				"Failed to write led enable reg\n");
+			return rc;
+		}
+
+		val = led->mpp_cfg->source_sel | led->mpp_cfg->mode_ctrl;
+
+		rc = qpnp_led_masked_write(led,
+		LED_MPP_MODE_CTRL(led->base), LED_MPP_MODE_MASK,
+		val);
+		if (rc) {
+			dev_err(&led->spmi_dev->dev,
+					"Failed to write led mode reg\n");
+			return rc;
+		}
+
+		rc = qpnp_led_masked_write(led,
+				LED_MPP_EN_CTRL(led->base), LED_MPP_EN_MASK,
+				LED_MPP_EN_ENABLE);
+			if (rc) {
+				dev_err(&led->spmi_dev->dev,
+						"Failed to write led enable " \
+						"reg\n");
+				return rc;
+			}
+	} else {
+		rc = qpnp_led_masked_write(led,
+					LED_MPP_MODE_CTRL(led->base),
+					LED_MPP_MODE_MASK,
+					LED_MPP_MODE_DISABLE);
+		if (rc) {
+			dev_err(&led->spmi_dev->dev,
+					"Failed to write led mode reg\n");
+			return rc;
+		}
+
+		rc = qpnp_led_masked_write(led,
+					LED_MPP_EN_CTRL(led->base),
+					LED_MPP_EN_MASK,
+					LED_MPP_EN_DISABLE);
+		if (rc) {
+			dev_err(&led->spmi_dev->dev,
+					"Failed to write led enable reg\n");
+			return rc;
+		}
+	}
+
+	qpnp_dump_regs(led, mpp_debug_regs, ARRAY_SIZE(mpp_debug_regs));
+
+	return 0;
+}
+
 static int qpnp_flash_set(struct qpnp_led_data *led)
 {
 	int rc;
@@ -750,6 +850,12 @@
 			dev_err(&led->spmi_dev->dev,
 				"RGB set brightness failed (%d)\n", rc);
 		break;
+	case QPNP_ID_LED_MPP:
+		rc = qpnp_mpp_set(led);
+		if (rc < 0)
+			dev_err(&led->spmi_dev->dev,
+					"MPP set brightness failed (%d)\n", rc);
+		break;
 	default:
 		dev_err(&led->spmi_dev->dev, "Invalid LED(%d)\n", led->id);
 		break;
@@ -772,6 +878,9 @@
 	case QPNP_ID_RGB_BLUE:
 		led->cdev.max_brightness = RGB_MAX_LEVEL;
 		break;
+	case QPNP_ID_LED_MPP:
+		led->cdev.max_brightness = MPP_MAX_LEVEL;
+		break;
 	default:
 		dev_err(&led->spmi_dev->dev, "Invalid LED(%d)\n", led->id);
 		return -EINVAL;
@@ -1211,6 +1320,8 @@
 			dev_err(&led->spmi_dev->dev,
 				"RGB initialize failed(%d)\n", rc);
 		break;
+	case QPNP_ID_LED_MPP:
+		break;
 	default:
 		dev_err(&led->spmi_dev->dev, "Invalid LED(%d)\n", led->id);
 		return -EINVAL;
@@ -1536,6 +1647,43 @@
 	return 0;
 }
 
+static int __devinit qpnp_get_config_mpp(struct qpnp_led_data *led,
+		struct device_node *node)
+{
+	int rc;
+	u32 val;
+
+	led->mpp_cfg = devm_kzalloc(&led->spmi_dev->dev,
+			sizeof(struct mpp_config_data), GFP_KERNEL);
+	if (!led->mpp_cfg) {
+		dev_err(&led->spmi_dev->dev, "Unable to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	led->mpp_cfg->current_setting = LED_MPP_CURRENT_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,current-setting", &val);
+	if (!rc)
+		led->mpp_cfg->current_setting = (u8) val;
+	else if (rc != -EINVAL)
+		return rc;
+
+	led->mpp_cfg->source_sel = LED_MPP_SOURCE_SEL_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,source-sel", &val);
+	if (!rc)
+		led->mpp_cfg->source_sel = (u8) val;
+	else if (rc != -EINVAL)
+		return rc;
+
+	led->mpp_cfg->mode_ctrl = LED_MPP_MODE_SINK;
+	rc = of_property_read_u32(node, "qcom,mode-ctrl", &val);
+	if (!rc)
+		led->mpp_cfg->mode_ctrl = (u8) val;
+	else if (rc != -EINVAL)
+		return rc;
+
+	return 0;
+}
+
 static int __devinit qpnp_leds_probe(struct spmi_device *spmi)
 {
 	struct qpnp_led_data *led, *led_array;
@@ -1638,6 +1786,13 @@
 					"Unable to read rgb config data\n");
 				goto fail_id_check;
 			}
+		} else if (strncmp(led_label, "mpp", sizeof("mpp")) == 0) {
+			rc = qpnp_get_config_mpp(led, temp);
+			if (rc < 0) {
+				dev_err(&led->spmi_dev->dev,
+						"Unable to read mpp config data\n");
+				goto fail_id_check;
+			}
 		} else {
 			dev_err(&led->spmi_dev->dev, "No LED matching label\n");
 			rc = -EINVAL;