msm: qdss: add support for qdss_get/put and qdss_enable/disable

Implement qdss_get/put and qdss_enable/disable external apis. Add
qdss platform virtual device and platform data to support the
implementation. Also split the QDSS driver initcall to allow early
init of data structures required to support qdss_get calls made by
other trace source drivers that want to use the services of the
qdss driver.

QDSS source drivers will use these apis to setup QDSS to collect
the trace data that they will generate. QDSS logical driver will
internally call the apis for the etb, funnel and tpiu device
drivers.

Change-Id: I06ece3fba110398b346c86f8b479153c60777540
Signed-off-by: Pratik Patel <pratikp@codeaurora.org>
diff --git a/arch/arm/mach-msm/qdss.c b/arch/arm/mach-msm/qdss.c
index effb09c..cfe65ad 100644
--- a/arch/arm/mach-msm/qdss.c
+++ b/arch/arm/mach-msm/qdss.c
@@ -24,26 +24,155 @@
 #include "rpm_resources.h"
 #include "qdss-priv.h"
 
+#define MAX_STR_LEN	(65535)
+
 enum {
 	QDSS_CLK_OFF,
 	QDSS_CLK_ON_DBG,
 	QDSS_CLK_ON_HSDBG,
 };
 
+/*
+ * Exclusion rules for structure fields.
+ *
+ * S: qdss.sources_mutex protected.
+ * I: qdss.sink_mutex protected.
+ * C: qdss.clk_mutex protected.
+ */
 struct qdss_ctx {
-	struct kobject	*modulekobj;
-	uint8_t		max_clk;
-	uint8_t		clk_count;
-	struct mutex	clk_mutex;
+	struct kobject			*modulekobj;
+	struct msm_qdss_platform_data	*pdata;
+	struct list_head		sources;	/* S: sources list */
+	struct mutex			sources_mutex;
+	uint8_t				sink_count;	/* I: sink count */
+	struct mutex			sink_mutex;
+	uint8_t				max_clk;
+	uint8_t				clk_count;	/* C: clk count */
+	struct mutex			clk_mutex;
 };
 
 static struct qdss_ctx qdss;
 
-
-struct kobject *qdss_get_modulekobj(void)
+/**
+ * qdss_get - get the qdss source handle
+ * @name: name of the qdss source
+ *
+ * Searches the sources list to get the qdss source handle for this source.
+ *
+ * CONTEXT:
+ * Typically called from init or probe functions
+ *
+ * RETURNS:
+ * pointer to struct qdss_source on success, %NULL on failure
+ */
+struct qdss_source *qdss_get(const char *name)
 {
-	return qdss.modulekobj;
+	struct qdss_source *src, *source = NULL;
+
+	mutex_lock(&qdss.sources_mutex);
+	list_for_each_entry(src, &qdss.sources, link) {
+		if (src->name) {
+			if (strncmp(src->name, name, MAX_STR_LEN))
+				continue;
+			source = src;
+			break;
+		}
+	}
+	mutex_unlock(&qdss.sources_mutex);
+
+	return source ? source : ERR_PTR(-ENOENT);
 }
+EXPORT_SYMBOL(qdss_get);
+
+/**
+ * qdss_put - release the qdss source handle
+ * @name: name of the qdss source
+ *
+ * CONTEXT:
+ * Typically called from driver remove or exit functions
+ */
+void qdss_put(struct qdss_source *src)
+{
+}
+EXPORT_SYMBOL(qdss_put);
+
+/**
+ * qdss_enable - enable qdss for the source
+ * @src: handle for the source making the call
+ *
+ * Enables qdss block (relevant funnel ports and sink) if not already
+ * enabled, otherwise increments the reference count
+ *
+ * CONTEXT:
+ * Might sleep. Uses a mutex lock. Should be called from a non-atomic context.
+ *
+ * RETURNS:
+ * 0 on success, non-zero on failure
+ */
+int qdss_enable(struct qdss_source *src)
+{
+	int ret;
+
+	if (!src)
+		return -EINVAL;
+
+	ret = qdss_clk_enable();
+	if (ret)
+		goto err;
+
+	if ((qdss.pdata)->afamily) {
+		mutex_lock(&qdss.sink_mutex);
+		if (qdss.sink_count == 0) {
+			etb_disable();
+			tpiu_disable();
+			/* enable ETB first to avoid losing any trace data */
+			etb_enable();
+		}
+		qdss.sink_count++;
+		mutex_unlock(&qdss.sink_mutex);
+	}
+
+	funnel_enable(0x0, src->fport_mask);
+	return 0;
+err:
+	return ret;
+}
+EXPORT_SYMBOL(qdss_enable);
+
+/**
+ * qdss_disable - disable qdss for the source
+ * @src: handle for the source making the call
+ *
+ * Disables qdss block (relevant funnel ports and sink) if the reference count
+ * is one, otherwise decrements the reference count
+ *
+ * CONTEXT:
+ * Might sleep. Uses a mutex lock. Should be called from a non-atomic context.
+ */
+void qdss_disable(struct qdss_source *src)
+{
+	if (!src)
+		return;
+
+	if ((qdss.pdata)->afamily) {
+		mutex_lock(&qdss.sink_mutex);
+		if (WARN(qdss.sink_count == 0, "qdss is unbalanced\n"))
+			goto out;
+		if (qdss.sink_count == 1) {
+			etb_dump();
+			etb_disable();
+		}
+		qdss.sink_count--;
+		mutex_unlock(&qdss.sink_mutex);
+	}
+
+	funnel_disable(0x0, src->fport_mask);
+	qdss_clk_disable();
+	return;
+out:
+	mutex_unlock(&qdss.sink_mutex);
+}
+EXPORT_SYMBOL(qdss_disable);
 
 /**
  * qdss_clk_enable - enable qdss clocks
@@ -111,6 +240,11 @@
 }
 EXPORT_SYMBOL(qdss_clk_disable);
 
+struct kobject *qdss_get_modulekobj(void)
+{
+	return qdss.modulekobj;
+}
+
 #define QDSS_ATTR(name)						\
 static struct kobj_attribute name##_attr =				\
 		__ATTR(name, S_IRUGO | S_IWUSR, name##_show, name##_store)
@@ -136,6 +270,16 @@
 }
 QDSS_ATTR(max_clk);
 
+static void __init qdss_add_sources(struct qdss_source *srcs, size_t num)
+{
+	mutex_lock(&qdss.sources_mutex);
+	while (num--) {
+		list_add_tail(&srcs->link, &qdss.sources);
+		srcs++;
+	}
+	mutex_unlock(&qdss.sources_mutex);
+}
+
 static int __init qdss_sysfs_init(void)
 {
 	int ret;
@@ -163,29 +307,80 @@
 	sysfs_remove_file(qdss.modulekobj, &max_clk_attr.attr);
 }
 
-static int __init qdss_init(void)
+static int __devinit qdss_probe(struct platform_device *pdev)
 {
 	int ret;
+	struct qdss_source *src_table;
+	size_t num_srcs;
 
+	mutex_init(&qdss.sources_mutex);
 	mutex_init(&qdss.clk_mutex);
+	mutex_init(&qdss.sink_mutex);
+
+	if (pdev->dev.platform_data == NULL) {
+		pr_err("%s: platform data is NULL\n", __func__);
+		ret = -ENODEV;
+		goto err_pdata;
+	}
+	qdss.pdata = pdev->dev.platform_data;
+
+	INIT_LIST_HEAD(&qdss.sources);
+	src_table = (qdss.pdata)->src_table;
+	num_srcs = (qdss.pdata)->size;
+	qdss_add_sources(src_table, num_srcs);
+
+	pr_info("QDSS arch initialized\n");
+	return 0;
+err_pdata:
+	mutex_destroy(&qdss.sink_mutex);
+	mutex_destroy(&qdss.clk_mutex);
+	mutex_destroy(&qdss.sources_mutex);
+	pr_err("QDSS init failed\n");
+	return ret;
+}
+
+static int __devexit qdss_remove(struct platform_device *pdev)
+{
+	qdss_sysfs_exit();
+	mutex_destroy(&qdss.sink_mutex);
+	mutex_destroy(&qdss.clk_mutex);
+	mutex_destroy(&qdss.sources_mutex);
+
+	return 0;
+}
+
+static struct platform_driver qdss_driver = {
+	.probe          = qdss_probe,
+	.remove         = __devexit_p(qdss_remove),
+	.driver         = {
+		.name   = "msm_qdss",
+	},
+};
+
+static int __init qdss_init(void)
+{
+	return platform_driver_register(&qdss_driver);
+}
+arch_initcall(qdss_init);
+
+static int __init qdss_module_init(void)
+{
+	int ret;
 
 	ret = qdss_sysfs_init();
 	if (ret)
 		goto err_sysfs;
 
-	pr_info("QDSS initialized\n");
+	pr_info("QDSS module initialized\n");
 	return 0;
 err_sysfs:
-	mutex_destroy(&qdss.clk_mutex);
-	pr_err("QDSS init failed\n");
 	return ret;
 }
-module_init(qdss_init);
+module_init(qdss_module_init);
 
 static void __exit qdss_exit(void)
 {
-	qdss_sysfs_exit();
-	mutex_destroy(&qdss.clk_mutex);
+	platform_driver_unregister(&qdss_driver);
 }
 module_exit(qdss_exit);