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/board-8930.c b/arch/arm/mach-msm/board-8930.c
index c713567..864d7b6 100644
--- a/arch/arm/mach-msm/board-8930.c
+++ b/arch/arm/mach-msm/board-8930.c
@@ -1693,6 +1693,7 @@
 	&msm_device_tz_log,
 
 #ifdef CONFIG_MSM_QDSS
+	&msm_qdss_device,
 	&msm_etb_device,
 	&msm_tpiu_device,
 	&msm_funnel_device,
diff --git a/arch/arm/mach-msm/board-8960.c b/arch/arm/mach-msm/board-8960.c
index 70a65d0..724dd80 100644
--- a/arch/arm/mach-msm/board-8960.c
+++ b/arch/arm/mach-msm/board-8960.c
@@ -2241,6 +2241,7 @@
 	&msm8960_rpm_stat_device,
 	&msm_device_tz_log,
 #ifdef CONFIG_MSM_QDSS
+	&msm_qdss_device,
 	&msm_etb_device,
 	&msm_tpiu_device,
 	&msm_funnel_device,
diff --git a/arch/arm/mach-msm/devices-8960.c b/arch/arm/mach-msm/devices-8960.c
index b03f137..0ab81a4 100644
--- a/arch/arm/mach-msm/devices-8960.c
+++ b/arch/arm/mach-msm/devices-8960.c
@@ -34,6 +34,7 @@
 #include <sound/msm-dai-q6.h>
 #include <sound/apr_audio.h>
 #include <mach/msm_tsif.h>
+#include <mach/qdss.h>
 #include "clock.h"
 #include "devices.h"
 #include "devices-msm8x60.h"
@@ -3167,6 +3168,26 @@
 #define MSM_FUNNEL_PHYS_BASE		(MSM_QDSS_PHYS_BASE + 0x4000)
 #define MSM_ETM_PHYS_BASE		(MSM_QDSS_PHYS_BASE + 0x1C000)
 
+#define QDSS_SOURCE(src_name, fpm) { .name = src_name, .fport_mask = fpm, }
+
+static struct qdss_source msm_qdss_sources[] = {
+	QDSS_SOURCE("msm_etm", 0x3),
+};
+
+static struct msm_qdss_platform_data qdss_pdata = {
+	.src_table = msm_qdss_sources,
+	.size = ARRAY_SIZE(msm_qdss_sources),
+	.afamily = 1,
+};
+
+struct platform_device msm_qdss_device = {
+	.name          = "msm_qdss",
+	.id            = -1,
+	.dev           = {
+		.platform_data = &qdss_pdata,
+	},
+};
+
 static struct resource msm_etb_resources[] = {
 	{
 		.start = MSM_ETB_PHYS_BASE,
diff --git a/arch/arm/mach-msm/devices.h b/arch/arm/mach-msm/devices.h
index ac9feee..d041761 100644
--- a/arch/arm/mach-msm/devices.h
+++ b/arch/arm/mach-msm/devices.h
@@ -318,6 +318,7 @@
 extern struct platform_device msm9615_device_watchdog;
 extern struct platform_device fsm9xxx_device_watchdog;
 
+extern struct platform_device msm_qdss_device;
 extern struct platform_device msm_etb_device;
 extern struct platform_device msm_tpiu_device;
 extern struct platform_device msm_funnel_device;
diff --git a/arch/arm/mach-msm/include/mach/qdss.h b/arch/arm/mach-msm/include/mach/qdss.h
index 530bcb2..3b236b8 100644
--- a/arch/arm/mach-msm/include/mach/qdss.h
+++ b/arch/arm/mach-msm/include/mach/qdss.h
@@ -13,10 +13,30 @@
 #ifndef __MACH_QDSS_H
 #define __MACH_QDSS_H
 
+struct qdss_source {
+	struct list_head link;
+	const char *name;
+	uint32_t fport_mask;
+};
+
+struct msm_qdss_platform_data {
+	struct qdss_source *src_table;
+	size_t size;
+	uint8_t afamily;
+};
+
 #ifdef CONFIG_MSM_QDSS
+extern struct qdss_source *qdss_get(const char *name);
+extern void qdss_put(struct qdss_source *src);
+extern int qdss_enable(struct qdss_source *src);
+extern void qdss_disable(struct qdss_source *src);
 extern int qdss_clk_enable(void);
 extern void qdss_clk_disable(void);
 #else
+static inline struct qdss_source *qdss_get(const char *name) { return NULL; }
+static inline void qdss_put(struct qdss_source *src) {}
+static inline int qdss_enable(struct qdss_source *src) { return -ENOSYS; }
+static inline void qdss_disable(struct qdss_source *src) {}
 static inline int qdss_clk_enable(void) { return -ENOSYS; }
 static inline void qdss_clk_disable(void) {}
 #endif
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);