regulator: qpnp-labibb: Add support for notifier callback

Some drivers (e.g. OLEDB) would need to know about LAB_VREG_OK
status from the LABIBB module. Hence, add support for the
notifier callback so that required drivers can be notified about
LAB_VREG_OK status.

Change-Id: Ib60c94c7557ee6ffb7595dee5bd268bb76faaf6e
Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/regulator/qpnp-labibb-regulator.txt b/Documentation/devicetree/bindings/regulator/qpnp-labibb-regulator.txt
index d08ca95..c9cfc88 100644
--- a/Documentation/devicetree/bindings/regulator/qpnp-labibb-regulator.txt
+++ b/Documentation/devicetree/bindings/regulator/qpnp-labibb-regulator.txt
@@ -149,6 +149,8 @@
 					already. If it it not specified, then
 					output voltage can be configured to
 					any value in the allowed limit.
+- qcom,notify-lab-vreg-ok-sts:		A boolean property which upon set will
+					poll and notify the lab_vreg_ok status.
 
 Following properties are available only for PM660A:
 
diff --git a/drivers/regulator/qpnp-labibb-regulator.c b/drivers/regulator/qpnp-labibb-regulator.c
index cf8f000..dbe2a08 100644
--- a/drivers/regulator/qpnp-labibb-regulator.c
+++ b/drivers/regulator/qpnp-labibb-regulator.c
@@ -19,16 +19,19 @@
 #include <linux/kernel.h>
 #include <linux/regmap.h>
 #include <linux/module.h>
+#include <linux/notifier.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
 #include <linux/of_irq.h>
 #include <linux/spmi.h>
 #include <linux/platform_device.h>
 #include <linux/string.h>
+#include <linux/workqueue.h>
 #include <linux/regulator/driver.h>
 #include <linux/regulator/machine.h>
 #include <linux/regulator/of_regulator.h>
 #include <linux/qpnp/qpnp-revid.h>
+#include <linux/regulator/qpnp-labibb-regulator.h>
 
 #define QPNP_LABIBB_REGULATOR_DRIVER_NAME	"qcom,qpnp-labibb-regulator"
 
@@ -594,6 +597,7 @@
 	const struct lab_ver_ops	*lab_ver_ops;
 	struct mutex			bus_mutex;
 	enum qpnp_labibb_mode		mode;
+	struct work_struct		lab_vreg_ok_work;
 	bool				standalone;
 	bool				ttw_en;
 	bool				in_ttw_mode;
@@ -603,10 +607,13 @@
 	bool				ttw_force_lab_on;
 	bool				skip_2nd_swire_cmd;
 	bool				pfm_enable;
+	bool				notify_lab_vreg_ok_sts;
 	u32				swire_2nd_cmd_delay;
 	u32				swire_ibb_ps_enable_delay;
 };
 
+static RAW_NOTIFIER_HEAD(labibb_notifier);
+
 struct ibb_ver_ops {
 	int (*set_default_voltage)(struct qpnp_labibb *labibb,
 			bool use_default);
@@ -2124,6 +2131,36 @@
 	return rc;
 }
 
+static void qpnp_lab_vreg_notifier_work(struct work_struct *work)
+{
+	int rc = 0;
+	u16 retries = 1000, dly = 5000;
+	u8 val;
+	struct qpnp_labibb *labibb  = container_of(work, struct qpnp_labibb,
+							lab_vreg_ok_work);
+
+	while (retries--) {
+		rc = qpnp_labibb_read(labibb, labibb->lab_base +
+					REG_LAB_STATUS1, &val, 1);
+		if (rc < 0) {
+			pr_err("read register %x failed rc = %d\n",
+				REG_LAB_STATUS1, rc);
+			return;
+		}
+
+		if (val & LAB_STATUS1_VREG_OK) {
+			raw_notifier_call_chain(&labibb_notifier,
+						LAB_VREG_OK, NULL);
+			break;
+		}
+
+		usleep_range(dly, dly + 100);
+	}
+
+	if (!retries)
+		pr_err("LAB_VREG_OK not set, failed to notify\n");
+}
+
 static int qpnp_labibb_regulator_enable(struct qpnp_labibb *labibb)
 {
 	int rc;
@@ -2326,6 +2363,9 @@
 		labibb->lab_vreg.vreg_enabled = 1;
 	}
 
+	if (labibb->notify_lab_vreg_ok_sts)
+		schedule_work(&labibb->lab_vreg_ok_work);
+
 	return 0;
 }
 
@@ -2578,6 +2618,9 @@
 		return rc;
 	}
 
+	labibb->notify_lab_vreg_ok_sts = of_property_read_bool(of_node,
+					"qcom,notify-lab-vreg-ok-sts");
+
 	rc = of_property_read_u32(of_node, "qcom,qpnp-lab-soft-start",
 					&(labibb->lab_vreg.soft_start));
 	if (!rc) {
@@ -3817,6 +3860,8 @@
 			goto fail_registration;
 		}
 	}
+
+	INIT_WORK(&labibb->lab_vreg_ok_work, qpnp_lab_vreg_notifier_work);
 	dev_set_drvdata(&pdev->dev, labibb);
 	pr_info("LAB/IBB registered successfully, lab_vreg enable=%d ibb_vreg enable=%d swire_control=%d\n",
 						labibb->lab_vreg.vreg_enabled,
@@ -3834,6 +3879,18 @@
 	return rc;
 }
 
+int qpnp_labibb_notifier_register(struct notifier_block *nb)
+{
+	return raw_notifier_chain_register(&labibb_notifier, nb);
+}
+EXPORT_SYMBOL(qpnp_labibb_notifier_register);
+
+int qpnp_labibb_notifier_unregister(struct notifier_block *nb)
+{
+	return raw_notifier_chain_unregister(&labibb_notifier, nb);
+}
+EXPORT_SYMBOL(qpnp_labibb_notifier_unregister);
+
 static int qpnp_labibb_regulator_remove(struct platform_device *pdev)
 {
 	struct qpnp_labibb *labibb = dev_get_drvdata(&pdev->dev);
@@ -3843,6 +3900,8 @@
 			regulator_unregister(labibb->lab_vreg.rdev);
 		if (labibb->ibb_vreg.rdev)
 			regulator_unregister(labibb->ibb_vreg.rdev);
+
+		cancel_work_sync(&labibb->lab_vreg_ok_work);
 	}
 	return 0;
 }
diff --git a/include/linux/regulator/qpnp-labibb-regulator.h b/include/linux/regulator/qpnp-labibb-regulator.h
new file mode 100644
index 0000000..2470695
--- /dev/null
+++ b/include/linux/regulator/qpnp-labibb-regulator.h
@@ -0,0 +1,23 @@
+/* Copyright (c) 2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _QPNP_LABIBB_REGULATOR_H
+#define _QPNP_LABIBB_REGULATOR_H
+
+enum labibb_notify_event {
+	LAB_VREG_OK = 1,
+};
+
+int qpnp_labibb_notifier_register(struct notifier_block *nb);
+int qpnp_labibb_notifier_unregister(struct notifier_block *nb);
+
+#endif