arm: msm: add vreg reference count

Support independent enable and disable by clients for common
vreg. First enable switches on and last disable switches off.

This change has no check for voltage level so clients
must agree on level for common vreg.

Signed-off-by: Matthew Wilson <mtwilson@quicinc.com>
diff --git a/arch/arm/mach-msm/vreg.c b/arch/arm/mach-msm/vreg.c
index 8b0f7b2..63db0b3 100644
--- a/arch/arm/mach-msm/vreg.c
+++ b/arch/arm/mach-msm/vreg.c
@@ -19,6 +19,7 @@
 #include <linux/device.h>
 #include <linux/init.h>
 #include <linux/debugfs.h>
+#include <linux/string.h>
 #include <mach/vreg.h>
 
 #include "proc_comm.h"
@@ -27,43 +28,44 @@
 	const char *name;
 	unsigned id;
 	int status;
+	unsigned refcnt;
 };
 
-#define VREG(_name, _id, _status) \
-	{ .name = _name, .id = _id, .status = _status }
+#define VREG(_name, _id, _status, _refcnt) \
+	{ .name = _name, .id = _id, .status = _status, .refcnt = _refcnt }
 
 static struct vreg vregs[] = {
-	VREG("msma",	0, 0),
-	VREG("msmp",	1, 0),
-	VREG("msme1",	2, 0),
-	VREG("msmc1",	3, 0),
-	VREG("msmc2",	4, 0),
-	VREG("gp3",	5, 0),
-	VREG("msme2",	6, 0),
-	VREG("gp4",	7, 0),
-	VREG("gp1",	8, 0),
-	VREG("tcxo",	9, 0),
-	VREG("pa",	10, 0),
-	VREG("rftx",	11, 0),
-	VREG("rfrx1",	12, 0),
-	VREG("rfrx2",	13, 0),
-	VREG("synt",	14, 0),
-	VREG("wlan",	15, 0),
-	VREG("usb",	16, 0),
-	VREG("boost",	17, 0),
-	VREG("mmc",	18, 0),
-	VREG("ruim",	19, 0),
-	VREG("msmc0",	20, 0),
-	VREG("gp2",	21, 0),
-	VREG("gp5",	22, 0),
-	VREG("gp6",	23, 0),
-	VREG("rf",	24, 0),
-	VREG("rf_vco",	26, 0),
-	VREG("mpll",	27, 0),
-	VREG("s2",	28, 0),
-	VREG("s3",	29, 0),
-	VREG("rfubm",	30, 0),
-	VREG("ncp",	31, 0),
+	VREG("msma",	0, 0, 0),
+	VREG("msmp",	1, 0, 0),
+	VREG("msme1",	2, 0, 0),
+	VREG("msmc1",	3, 0, 0),
+	VREG("msmc2",	4, 0, 0),
+	VREG("gp3",	5, 0, 0),
+	VREG("msme2",	6, 0, 0),
+	VREG("gp4",	7, 0, 0),
+	VREG("gp1",	8, 0, 0),
+	VREG("tcxo",	9, 0, 0),
+	VREG("pa",	10, 0, 0),
+	VREG("rftx",	11, 0, 0),
+	VREG("rfrx1",	12, 0, 0),
+	VREG("rfrx2",	13, 0, 0),
+	VREG("synt",	14, 0, 0),
+	VREG("wlan",	15, 0, 0),
+	VREG("usb",	16, 0, 0),
+	VREG("boost",	17, 0, 0),
+	VREG("mmc",	18, 0, 0),
+	VREG("ruim",	19, 0, 0),
+	VREG("msmc0",	20, 0, 0),
+	VREG("gp2",	21, 0, 0),
+	VREG("gp5",	22, 0, 0),
+	VREG("gp6",	23, 0, 0),
+	VREG("rf",	24, 0, 0),
+	VREG("rf_vco",	26, 0, 0),
+	VREG("mpll",	27, 0, 0),
+	VREG("s2",	28, 0, 0),
+	VREG("s3",	29, 0, 0),
+	VREG("rfubm",	30, 0, 0),
+	VREG("ncp",	31, 0, 0),
 };
 
 struct vreg *vreg_get(struct device *dev, const char *id)
@@ -85,7 +87,12 @@
 	unsigned id = vreg->id;
 	unsigned enable = 1;
 
-	vreg->status = msm_proc_comm(PCOM_VREG_SWITCH, &id, &enable);
+	if (vreg->refcnt == 0)
+		vreg->status = msm_proc_comm(PCOM_VREG_SWITCH, &id, &enable);
+
+	if ((vreg->refcnt < UINT_MAX) && (!vreg->status))
+		vreg->refcnt++;
+
 	return vreg->status;
 }
 
@@ -94,7 +101,15 @@
 	unsigned id = vreg->id;
 	unsigned enable = 0;
 
-	vreg->status = msm_proc_comm(PCOM_VREG_SWITCH, &id, &enable);
+	if (!vreg->refcnt)
+		return 0;
+
+	if (vreg->refcnt == 1)
+		vreg->status = msm_proc_comm(PCOM_VREG_SWITCH, &id, &enable);
+
+	if (!vreg->status)
+		vreg->refcnt--;
+
 	return vreg->status;
 }
 
@@ -137,21 +152,49 @@
 	return 0;
 }
 
+static int vreg_debug_count_set(void *data, u64 val)
+{
+	struct vreg *vreg = data;
+	if (val > UINT_MAX)
+		val = UINT_MAX;
+	vreg->refcnt = val;
+	return 0;
+}
+
+static int vreg_debug_count_get(void *data, u64 *val)
+{
+	struct vreg *vreg = data;
+
+	*val = vreg->refcnt;
+
+	return 0;
+}
+
 DEFINE_SIMPLE_ATTRIBUTE(vreg_fops, vreg_debug_get, vreg_debug_set, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(vreg_count_fops, vreg_debug_count_get,
+			vreg_debug_count_set, "%llu\n");
 
 static int __init vreg_debug_init(void)
 {
 	struct dentry *dent;
 	int n;
+	char name[32];
+	const char *refcnt_name = "_refcnt";
 
 	dent = debugfs_create_dir("vreg", 0);
 	if (IS_ERR(dent))
 		return 0;
 
-	for (n = 0; n < ARRAY_SIZE(vregs); n++)
+	for (n = 0; n < ARRAY_SIZE(vregs); n++) {
 		(void) debugfs_create_file(vregs[n].name, 0644,
 					   dent, vregs + n, &vreg_fops);
 
+		strlcpy(name, vregs[n].name, sizeof(name));
+		strlcat(name, refcnt_name, sizeof(name));
+		(void) debugfs_create_file(name, 0644,
+					   dent, vregs + n, &vreg_count_fops);
+	}
+
 	return 0;
 }