dev: qpnp_wled: Add LCDB support for PMI632

Add LCDB support to enable LCD biasing control for PMI632.

Change-Id: I820ac91bb74c1806a48b772a8622c1aec428cdff
Signed-off-by: Umang Agrawal <uagrawal@codeaurora.org>
diff --git a/dev/qpnp_wled/include/qpnp_lcdb.h b/dev/qpnp_wled/include/qpnp_lcdb.h
new file mode 100644
index 0000000..0eef610
--- /dev/null
+++ b/dev/qpnp_wled/include/qpnp_lcdb.h
@@ -0,0 +1,80 @@
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials provided
+ *     with the distribution.
+ *   * Neither the name of The Linux Foundation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <bits.h>
+#include <debug.h>
+#include <reg.h>
+
+#define QPNP_LCDB_BASE				0x3ec00
+#define QPNP_LCDB_BST_OUTPUT_VOLTAGE_REG	0x41
+#define QPNP_LCDB_LDO_OUTPUT_VOLTAGE_REG	0x71
+#define QPNP_LCDB_NCP_OUTPUT_VOLTAGE_REG	0x81
+#define QPNP_LCDB_ENABLE_CTL_REG		0x46
+#define QPNP_LCDB_PWRUP_PWRDN_CTL_REG		0x66
+#define QPNP_LCDB_MODULE_RDY_REG		0x45
+
+#define QPNP_LCDB_BST_OUTPUT_VOLTAGE_MASK	0x3F
+#define QPNP_LCDB_LDO_OUTPUT_VOLTAGE_MASK	0x1F
+#define QPNP_LCDB_NCP_OUTPUT_VOLTAGE_MASK	0x1F
+#define QPNP_LCDB_ENABLE_CTL_MASK		0x80
+#define QPNP_LCDB_PWRUP_DLY_MASK		0x0C
+#define QPNP_LCDB_PWRDN_DLY_MASK		0x03
+#define QPNP_LCDB_MODULE_RDY_MASK		0x80
+
+#define LCDB_LDO_INIT_VOLTAGE_UV		4000000
+#define LCDB_NCP_INIT_VOLTAGE_UV		4000000
+#define LCDB_BOOST_INIT_VOLTAGE_UV		4700000
+#define LDO_VREG_STEP_SIZE_UV			50000
+#define NCP_VREG_STEP_SIZE_UV			50000
+#define BST_VREG_STEP_SIZE_UV			25000
+#define LCDB_BOOST_HEADROOM_VOLT_UV		150000
+#define QPNP_LCDB_PWRUP_DLY_MAX_MS		3
+#define QPNP_LCDB_PWRDN_DLY_MAX_MS		3
+#define LCDB_MODULE_RDY				BIT(7)
+
+#define LCDB_ENABLE_SHIFT			7
+#define LCDB_PWRUP_DLY_SHIFT			2
+
+struct qpnp_lcdb {
+	uint32_t lcdb_base;
+	uint16_t lcdb_pwrup_dly_ms;
+	uint16_t lcdb_pwrdn_dly_ms;
+	uint32_t ldo_min_volt;
+	uint32_t ldo_max_volt;
+	uint32_t ldo_init_volt;
+	uint32_t ncp_min_volt;
+	uint32_t ncp_max_volt;
+	uint32_t ncp_init_volt;
+	uint32_t bst_init_volt;
+};
+
+/* LCDB Init */
+int qpnp_lcdb_init(struct qpnp_wled_config_data *config);
+
+/* LCDB Enable */
+int qpnp_lcdb_enable(bool enable);
diff --git a/dev/qpnp_wled/qpnp_lcdb.c b/dev/qpnp_wled/qpnp_lcdb.c
new file mode 100644
index 0000000..f2eba5b
--- /dev/null
+++ b/dev/qpnp_wled/qpnp_lcdb.c
@@ -0,0 +1,216 @@
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials provided
+ *     with the distribution.
+ *   * Neither the name of The Linux Foundation, Inc. nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <err.h>
+#include <debug.h>
+#include <spmi.h>
+#include <qpnp_wled.h>
+#include <qpnp_lcdb.h>
+#include <qtimer.h>
+#include <target.h>
+
+struct qpnp_lcdb *lcdb;
+
+int qpnp_lcdb_enable(bool state)
+{
+	if (!lcdb) {
+		dprintf(CRITICAL, "%s: lcdb is not initialized yet\n", __func__);
+		return ERROR;
+	}
+
+	pmic_spmi_reg_mask_write(lcdb->lcdb_base + QPNP_LCDB_ENABLE_CTL_REG,
+		QPNP_LCDB_ENABLE_CTL_MASK, (state << LCDB_ENABLE_SHIFT));
+
+	return 0;
+}
+
+static int qpnp_lcdb_set_voltage(struct qpnp_lcdb *lcdb)
+{
+	int rc = -1;
+	uint32_t new_uV, boost_ref_volt;
+	uint8_t val;
+
+	/* Set LDO Voltage */
+	if (lcdb->ldo_min_volt < lcdb->ldo_init_volt) {
+		dprintf(CRITICAL, "qpnp_ldo_set_voltage failed, min_uV %d is "
+			"less than the minimum supported voltage %d\n",
+			lcdb->ldo_min_volt, lcdb->ldo_init_volt);
+		return rc;
+	}
+
+	val = ((lcdb->ldo_min_volt - lcdb->ldo_init_volt) +
+		LDO_VREG_STEP_SIZE_UV - 1) / LDO_VREG_STEP_SIZE_UV;
+	new_uV = val * LDO_VREG_STEP_SIZE_UV + lcdb->ldo_init_volt;
+	if (new_uV > lcdb->ldo_max_volt) {
+		dprintf(CRITICAL, "qpnp_ldo_set_voltage unable to set voltage "
+			"(%d %d)\n", lcdb->ldo_min_volt, lcdb->ldo_max_volt);
+		return rc;
+	}
+
+	/* Value adjustment as initial 1V has 100mV step and rest 50mV step */
+	if ( val > 18 )
+		val = val - 9;
+	else
+		val = val / 2;
+
+	pmic_spmi_reg_mask_write(lcdb->lcdb_base +
+				QPNP_LCDB_LDO_OUTPUT_VOLTAGE_REG,
+				QPNP_LCDB_LDO_OUTPUT_VOLTAGE_MASK, val);
+
+	udelay(2);
+
+	/* Set NCP voltage */
+	if (lcdb->ncp_min_volt < lcdb->ncp_init_volt) {
+		dprintf(CRITICAL, "qpnp_ncp_set_voltage failed, min_uV %d is "
+			"less than the minimum supported voltage %d\n",
+			lcdb->ncp_min_volt, lcdb->ncp_init_volt);
+		return rc;
+	}
+
+	val = ((lcdb->ncp_min_volt - lcdb->ncp_init_volt) +
+			NCP_VREG_STEP_SIZE_UV - 1) / NCP_VREG_STEP_SIZE_UV;
+	new_uV = val * NCP_VREG_STEP_SIZE_UV + lcdb->ncp_init_volt;
+	if (new_uV > lcdb->ncp_max_volt) {
+		dprintf(CRITICAL, "qpnp_ncp_set_voltage unable to set voltage "
+			"(%d %d)\n", lcdb->ncp_min_volt, lcdb->ncp_max_volt);
+		return rc;
+	}
+
+	/* Value adjustment as initial 1V has 100mV step and rest 50mV step */
+	if ( val > 18 )
+		val = val - 9;
+	else
+		val = val / 2;
+
+	pmic_spmi_reg_mask_write(lcdb->lcdb_base +
+				QPNP_LCDB_NCP_OUTPUT_VOLTAGE_REG,
+				QPNP_LCDB_NCP_OUTPUT_VOLTAGE_MASK, val);
+
+	udelay(2);
+
+	/* Set Boost voltage */
+	boost_ref_volt = ((lcdb->ldo_max_volt > lcdb->ncp_max_volt) ?
+			lcdb->ldo_max_volt : lcdb->ncp_max_volt) +
+			LCDB_BOOST_HEADROOM_VOLT_UV;
+	if (boost_ref_volt < lcdb->bst_init_volt)
+		boost_ref_volt = lcdb->bst_init_volt;
+
+	val = ((boost_ref_volt - lcdb->bst_init_volt) +
+		BST_VREG_STEP_SIZE_UV - 1) / BST_VREG_STEP_SIZE_UV;
+	new_uV = val * BST_VREG_STEP_SIZE_UV + lcdb->bst_init_volt;
+
+	pmic_spmi_reg_mask_write(lcdb->lcdb_base +
+				QPNP_LCDB_BST_OUTPUT_VOLTAGE_REG,
+				QPNP_LCDB_BST_OUTPUT_VOLTAGE_MASK, val);
+
+	return 0;
+}
+
+static int qpnp_lcdb_config(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0;
+	uint8_t reg = 0;
+
+	/* TODO: Set power up & down delay register */
+	if (lcdb->lcdb_pwrup_dly_ms > QPNP_LCDB_PWRUP_DLY_MAX_MS)
+		lcdb->lcdb_pwrup_dly_ms = QPNP_LCDB_PWRUP_DLY_MAX_MS;
+
+	if (lcdb->lcdb_pwrdn_dly_ms > QPNP_LCDB_PWRDN_DLY_MAX_MS)
+		lcdb->lcdb_pwrdn_dly_ms = QPNP_LCDB_PWRDN_DLY_MAX_MS;
+
+	reg = pmic_spmi_reg_read(lcdb->lcdb_base +
+			QPNP_LCDB_PWRUP_PWRDN_CTL_REG);
+
+	/* Set power up delay */
+	reg &= QPNP_LCDB_PWRUP_DLY_MASK;
+	reg |= (lcdb->lcdb_pwrup_dly_ms << LCDB_PWRUP_DLY_SHIFT);
+
+	/* Set power down delay */
+	reg &= QPNP_LCDB_PWRDN_DLY_MASK;
+	reg |= (lcdb->lcdb_pwrdn_dly_ms);
+
+	pmic_spmi_reg_write(lcdb->lcdb_base + QPNP_LCDB_PWRUP_PWRDN_CTL_REG,
+			reg);
+
+	/* Make LCDB module ready */
+	pmic_spmi_reg_mask_write(lcdb->lcdb_base + QPNP_LCDB_MODULE_RDY_REG,
+		QPNP_LCDB_MODULE_RDY_MASK, LCDB_MODULE_RDY);
+
+	/* Set regulator voltage */
+	rc = qpnp_lcdb_set_voltage(lcdb);
+
+	return rc;
+}
+
+static int qpnp_lcdb_setup(struct qpnp_lcdb *lcdb,
+			struct qpnp_wled_config_data *config)
+{
+	lcdb->lcdb_base = QPNP_LCDB_BASE;
+	lcdb->lcdb_pwrup_dly_ms = config->pwr_up_delay;
+	lcdb->lcdb_pwrdn_dly_ms = config->pwr_down_delay;
+	lcdb->ldo_min_volt = config->lab_min_volt;
+	lcdb->ldo_max_volt = config->lab_max_volt;
+	lcdb->ldo_init_volt = LCDB_LDO_INIT_VOLTAGE_UV;
+	lcdb->ncp_min_volt = config->ibb_min_volt;
+	lcdb->ncp_max_volt = config->ibb_max_volt;
+	lcdb->ncp_init_volt = LCDB_NCP_INIT_VOLTAGE_UV;
+	lcdb->bst_init_volt = LCDB_BOOST_INIT_VOLTAGE_UV;
+
+	return 0;
+}
+
+int qpnp_lcdb_init(struct qpnp_wled_config_data *config)
+{
+	int rc;
+
+	if (!target_is_pmi_enabled())
+		return ERR_NOT_FOUND;
+
+	lcdb = malloc(sizeof(struct qpnp_lcdb));
+	if (!lcdb)
+		return ERR_NO_MEMORY;
+
+	memset(lcdb, 0, sizeof(struct qpnp_lcdb));
+
+	rc = qpnp_lcdb_setup(lcdb, config);
+	if(rc) {
+		dprintf(CRITICAL, "Setting LCDB parameters failed\n");
+		return rc;
+	}
+
+	rc = qpnp_lcdb_config(lcdb);
+	if (rc) {
+		dprintf(CRITICAL, "lcdb config failed\n");
+		return rc;
+	}
+
+	return rc;
+}
diff --git a/dev/qpnp_wled/rules.mk b/dev/qpnp_wled/rules.mk
index d60c07f..464d497 100644
--- a/dev/qpnp_wled/rules.mk
+++ b/dev/qpnp_wled/rules.mk
@@ -3,4 +3,5 @@
 INCLUDES += -I$(LOCAL_DIR)/include
 
 OBJS += \
-	$(LOCAL_DIR)/qpnp_wled.o
+	$(LOCAL_DIR)/qpnp_wled.o \
+	$(LOCAL_DIR)/qpnp_lcdb.o