cs: move qdss code under drivers
In preparation to create generic coresight drivers code move
existing qdss code under drivers/cs and include/linux
Change-Id: If7697a9961006fe94818e32d19aea75c56ce225d
Signed-off-by: Pratik Patel <pratikp@codeaurora.org>
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 286a4d4..7225bdf 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -148,4 +148,6 @@
source "drivers/gud/Kconfig"
+source "drivers/cs/Kconfig"
+
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index bea505c..3272e2c 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -140,3 +140,5 @@
#MobiCore
obj-$(CONFIG_MOBICORE_SUPPORT) += gud/
+
+obj-$(CONFIG_MSM_QDSS) += cs/
diff --git a/drivers/cs/Kconfig b/drivers/cs/Kconfig
new file mode 100644
index 0000000..1219af1
--- /dev/null
+++ b/drivers/cs/Kconfig
@@ -0,0 +1,32 @@
+config MSM_QDSS
+ bool "CoreSight tracing"
+ help
+ Enables support for CoreSight tracing. This uses CoreSight trace
+ components and buses to support both hardware (eg. processor ETM)
+ and hardware assisted software instrumentation based (eg. STM)
+ tracing.
+
+ For production builds, you should probably say 'N' here to avoid
+ potential power, performance and memory penalty.
+
+config MSM_QDSS_STM_DEFAULT_ENABLE
+ bool "Turn on CoreSight STM tracing by default"
+ depends on MSM_QDSS
+ help
+ Turns on CoreSight STM tracing (hardware assisted software
+ instrumentation based tracing) by default. Otherwise, tracing is
+ disabled by default but can be enabled via sysfs.
+
+ For production builds, you should probably say 'N' here to avoid
+ potential power, performance and memory penalty.
+
+config MSM_QDSS_ETM_DEFAULT_ENABLE
+ bool "Turn on CoreSight ETM tracing by default"
+ depends on MSM_QDSS
+ help
+ Turns on CoreSight ETM tracing (processor tracing) by default.
+ Otherwise, tracing is disabled by default but can be enabled via
+ sysfs.
+
+ For production builds, you should probably say 'N' here to avoid
+ potential power, performance and memory penalty.
diff --git a/drivers/cs/Makefile b/drivers/cs/Makefile
new file mode 100644
index 0000000..9a634a1
--- /dev/null
+++ b/drivers/cs/Makefile
@@ -0,0 +1,2 @@
+
+obj-$(CONFIG_MSM_QDSS) += qdss.o qdss-etb.o qdss-tpiu.o qdss-funnel.o qdss-stm.o qdss-etm.o
diff --git a/drivers/cs/qdss-etb.c b/drivers/cs/qdss-etb.c
new file mode 100644
index 0000000..ccea2fa
--- /dev/null
+++ b/drivers/cs/qdss-etb.c
@@ -0,0 +1,419 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+
+#include "qdss-priv.h"
+
+#define etb_writel(etb, val, off) __raw_writel((val), etb.base + off)
+#define etb_readl(etb, off) __raw_readl(etb.base + off)
+
+#define ETB_RAM_DEPTH_REG (0x004)
+#define ETB_STATUS_REG (0x00C)
+#define ETB_RAM_READ_DATA_REG (0x010)
+#define ETB_RAM_READ_POINTER (0x014)
+#define ETB_RAM_WRITE_POINTER (0x018)
+#define ETB_TRG (0x01C)
+#define ETB_CTL_REG (0x020)
+#define ETB_RWD_REG (0x024)
+#define ETB_FFSR (0x300)
+#define ETB_FFCR (0x304)
+#define ETB_ITMISCOP0 (0xEE0)
+#define ETB_ITTRFLINACK (0xEE4)
+#define ETB_ITTRFLIN (0xEE8)
+#define ETB_ITATBDATA0 (0xEEC)
+#define ETB_ITATBCTR2 (0xEF0)
+#define ETB_ITATBCTR1 (0xEF4)
+#define ETB_ITATBCTR0 (0xEF8)
+
+
+#define BYTES_PER_WORD 4
+#define ETB_SIZE_WORDS 4096
+#define FRAME_SIZE_WORDS 4
+
+#define ETB_LOCK() \
+do { \
+ mb(); \
+ etb_writel(etb, 0x0, CS_LAR); \
+} while (0)
+#define ETB_UNLOCK() \
+do { \
+ etb_writel(etb, CS_UNLOCK_MAGIC, CS_LAR); \
+ mb(); \
+} while (0)
+
+struct etb_ctx {
+ uint8_t *buf;
+ void __iomem *base;
+ bool enabled;
+ bool reading;
+ spinlock_t spinlock;
+ atomic_t in_use;
+ struct device *dev;
+ struct kobject *kobj;
+ uint32_t trigger_cntr;
+};
+
+static struct etb_ctx etb;
+
+static void __etb_enable(void)
+{
+ int i;
+
+ ETB_UNLOCK();
+
+ etb_writel(etb, 0x0, ETB_RAM_WRITE_POINTER);
+ for (i = 0; i < ETB_SIZE_WORDS; i++)
+ etb_writel(etb, 0x0, ETB_RWD_REG);
+
+ etb_writel(etb, 0x0, ETB_RAM_WRITE_POINTER);
+ etb_writel(etb, 0x0, ETB_RAM_READ_POINTER);
+
+ etb_writel(etb, etb.trigger_cntr, ETB_TRG);
+ etb_writel(etb, BIT(13) | BIT(0), ETB_FFCR);
+ etb_writel(etb, BIT(0), ETB_CTL_REG);
+
+ ETB_LOCK();
+}
+
+void etb_enable(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&etb.spinlock, flags);
+ __etb_enable();
+ etb.enabled = true;
+ dev_info(etb.dev, "ETB enabled\n");
+ spin_unlock_irqrestore(&etb.spinlock, flags);
+}
+
+static void __etb_disable(void)
+{
+ int count;
+ uint32_t ffcr;
+
+ ETB_UNLOCK();
+
+ ffcr = etb_readl(etb, ETB_FFCR);
+ ffcr |= (BIT(12) | BIT(6));
+ etb_writel(etb, ffcr, ETB_FFCR);
+
+ for (count = TIMEOUT_US; BVAL(etb_readl(etb, ETB_FFCR), 6) != 0
+ && count > 0; count--)
+ udelay(1);
+ WARN(count == 0, "timeout while flushing ETB, ETB_FFCR: %#x\n",
+ etb_readl(etb, ETB_FFCR));
+
+ etb_writel(etb, 0x0, ETB_CTL_REG);
+
+ for (count = TIMEOUT_US; BVAL(etb_readl(etb, ETB_FFSR), 1) != 1
+ && count > 0; count--)
+ udelay(1);
+ WARN(count == 0, "timeout while disabling ETB, ETB_FFSR: %#x\n",
+ etb_readl(etb, ETB_FFSR));
+
+ ETB_LOCK();
+}
+
+void etb_disable(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&etb.spinlock, flags);
+ __etb_disable();
+ etb.enabled = false;
+ dev_info(etb.dev, "ETB disabled\n");
+ spin_unlock_irqrestore(&etb.spinlock, flags);
+}
+
+static void __etb_dump(void)
+{
+ int i;
+ uint8_t *buf_ptr;
+ uint32_t read_data;
+ uint32_t read_ptr;
+ uint32_t write_ptr;
+ uint32_t frame_off;
+ uint32_t frame_endoff;
+
+ ETB_UNLOCK();
+
+ read_ptr = etb_readl(etb, ETB_RAM_READ_POINTER);
+ write_ptr = etb_readl(etb, ETB_RAM_WRITE_POINTER);
+
+ frame_off = write_ptr % FRAME_SIZE_WORDS;
+ frame_endoff = FRAME_SIZE_WORDS - frame_off;
+ if (frame_off) {
+ dev_err(etb.dev, "write_ptr: %lu not aligned to formatter "
+ "frame size\n", (unsigned long)write_ptr);
+ dev_err(etb.dev, "frameoff: %lu, frame_endoff: %lu\n",
+ (unsigned long)frame_off, (unsigned long)frame_endoff);
+ write_ptr += frame_endoff;
+ }
+
+ if ((etb_readl(etb, ETB_STATUS_REG) & BIT(0)) == 0)
+ etb_writel(etb, 0x0, ETB_RAM_READ_POINTER);
+ else
+ etb_writel(etb, write_ptr, ETB_RAM_READ_POINTER);
+
+ buf_ptr = etb.buf;
+ for (i = 0; i < ETB_SIZE_WORDS; i++) {
+ read_data = etb_readl(etb, ETB_RAM_READ_DATA_REG);
+ *buf_ptr++ = read_data >> 0;
+ *buf_ptr++ = read_data >> 8;
+ *buf_ptr++ = read_data >> 16;
+ *buf_ptr++ = read_data >> 24;
+ }
+
+ if (frame_off) {
+ buf_ptr -= (frame_endoff * BYTES_PER_WORD);
+ for (i = 0; i < frame_endoff; i++) {
+ *buf_ptr++ = 0x0;
+ *buf_ptr++ = 0x0;
+ *buf_ptr++ = 0x0;
+ *buf_ptr++ = 0x0;
+ }
+ }
+
+ etb_writel(etb, read_ptr, ETB_RAM_READ_POINTER);
+
+ ETB_LOCK();
+}
+
+void etb_dump(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&etb.spinlock, flags);
+ if (etb.enabled) {
+ __etb_disable();
+ __etb_dump();
+ __etb_enable();
+
+ dev_info(etb.dev, "ETB dumped\n");
+ }
+ spin_unlock_irqrestore(&etb.spinlock, flags);
+}
+
+static int etb_open(struct inode *inode, struct file *file)
+{
+ if (atomic_cmpxchg(&etb.in_use, 0, 1))
+ return -EBUSY;
+
+ dev_dbg(etb.dev, "%s: successfully opened\n", __func__);
+ return 0;
+}
+
+static ssize_t etb_read(struct file *file, char __user *data,
+ size_t len, loff_t *ppos)
+{
+ if (etb.reading == false) {
+ etb_dump();
+ etb.reading = true;
+ }
+
+ if (*ppos + len > ETB_SIZE_WORDS * BYTES_PER_WORD)
+ len = ETB_SIZE_WORDS * BYTES_PER_WORD - *ppos;
+
+ if (copy_to_user(data, etb.buf + *ppos, len)) {
+ dev_dbg(etb.dev, "%s: copy_to_user failed\n", __func__);
+ return -EFAULT;
+ }
+
+ *ppos += len;
+
+ dev_dbg(etb.dev, "%s: %d bytes copied, %d bytes left\n",
+ __func__, len, (int) (ETB_SIZE_WORDS * BYTES_PER_WORD - *ppos));
+
+ return len;
+}
+
+static int etb_release(struct inode *inode, struct file *file)
+{
+ etb.reading = false;
+
+ atomic_set(&etb.in_use, 0);
+
+ dev_dbg(etb.dev, "%s: released\n", __func__);
+
+ return 0;
+}
+
+static const struct file_operations etb_fops = {
+ .owner = THIS_MODULE,
+ .open = etb_open,
+ .read = etb_read,
+ .release = etb_release,
+};
+
+static struct miscdevice etb_misc = {
+ .name = "msm_etb",
+ .minor = MISC_DYNAMIC_MINOR,
+ .fops = &etb_fops,
+};
+
+#define ETB_ATTR(__name) \
+static struct kobj_attribute __name##_attr = \
+ __ATTR(__name, S_IRUGO | S_IWUSR, __name##_show, __name##_store)
+
+static ssize_t trigger_cntr_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ etb.trigger_cntr = val;
+ return n;
+}
+static ssize_t trigger_cntr_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val = etb.trigger_cntr;
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+ETB_ATTR(trigger_cntr);
+
+static int __devinit etb_sysfs_init(void)
+{
+ int ret;
+
+ etb.kobj = kobject_create_and_add("etb", qdss_get_modulekobj());
+ if (!etb.kobj) {
+ dev_err(etb.dev, "failed to create ETB sysfs kobject\n");
+ ret = -ENOMEM;
+ goto err_create;
+ }
+
+ ret = sysfs_create_file(etb.kobj, &trigger_cntr_attr.attr);
+ if (ret) {
+ dev_err(etb.dev, "failed to create ETB sysfs trigger_cntr"
+ " attribute\n");
+ goto err_file;
+ }
+
+ return 0;
+err_file:
+ kobject_put(etb.kobj);
+err_create:
+ return ret;
+}
+
+static void __devexit etb_sysfs_exit(void)
+{
+ sysfs_remove_file(etb.kobj, &trigger_cntr_attr.attr);
+ kobject_put(etb.kobj);
+}
+
+static int __devinit etb_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -EINVAL;
+ goto err_res;
+ }
+
+ etb.base = ioremap_nocache(res->start, resource_size(res));
+ if (!etb.base) {
+ ret = -EINVAL;
+ goto err_ioremap;
+ }
+
+ etb.dev = &pdev->dev;
+
+ spin_lock_init(&etb.spinlock);
+
+ ret = misc_register(&etb_misc);
+ if (ret)
+ goto err_misc;
+
+ etb.buf = kzalloc(ETB_SIZE_WORDS * BYTES_PER_WORD, GFP_KERNEL);
+ if (!etb.buf) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ etb_sysfs_init();
+
+ dev_info(etb.dev, "ETB initialized\n");
+ return 0;
+
+err_alloc:
+ misc_deregister(&etb_misc);
+err_misc:
+ iounmap(etb.base);
+err_ioremap:
+err_res:
+ dev_err(etb.dev, "ETB init failed\n");
+ return ret;
+}
+
+static int __devexit etb_remove(struct platform_device *pdev)
+{
+ if (etb.enabled)
+ etb_disable();
+ etb_sysfs_exit();
+ kfree(etb.buf);
+ misc_deregister(&etb_misc);
+ iounmap(etb.base);
+
+ return 0;
+}
+
+static struct of_device_id etb_match[] = {
+ {.compatible = "qcom,msm-etb"},
+ {}
+};
+
+static struct platform_driver etb_driver = {
+ .probe = etb_probe,
+ .remove = __devexit_p(etb_remove),
+ .driver = {
+ .name = "msm_etb",
+ .owner = THIS_MODULE,
+ .of_match_table = etb_match,
+ },
+};
+
+static int __init etb_init(void)
+{
+ return platform_driver_register(&etb_driver);
+}
+module_init(etb_init);
+
+static void __exit etb_exit(void)
+{
+ platform_driver_unregister(&etb_driver);
+}
+module_exit(etb_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("CoreSight Embedded Trace Buffer driver");
diff --git a/drivers/cs/qdss-etm.c b/drivers/cs/qdss-etm.c
new file mode 100644
index 0000000..e2a38de
--- /dev/null
+++ b/drivers/cs/qdss-etm.c
@@ -0,0 +1,1336 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/smp.h>
+#include <linux/wakelock.h>
+#include <linux/pm_qos.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <asm/sections.h>
+#include <mach/socinfo.h>
+
+#include "qdss-priv.h"
+
+#define etm_writel(etm, cpu, val, off) \
+ __raw_writel((val), etm.base + (SZ_4K * cpu) + off)
+#define etm_readl(etm, cpu, off) \
+ __raw_readl(etm.base + (SZ_4K * cpu) + off)
+
+/*
+ * Device registers:
+ * 0x000 - 0x2FC: Trace registers
+ * 0x300 - 0x314: Management registers
+ * 0x318 - 0xEFC: Trace registers
+ *
+ * Coresight registers
+ * 0xF00 - 0xF9C: Management registers
+ * 0xFA0 - 0xFA4: Management registers in PFTv1.0
+ * Trace registers in PFTv1.1
+ * 0xFA8 - 0xFFC: Management registers
+ */
+
+/* Trace registers (0x000-0x2FC) */
+#define ETMCR (0x000)
+#define ETMCCR (0x004)
+#define ETMTRIGGER (0x008)
+#define ETMSR (0x010)
+#define ETMSCR (0x014)
+#define ETMTSSCR (0x018)
+#define ETMTEEVR (0x020)
+#define ETMTECR1 (0x024)
+#define ETMFFLR (0x02C)
+#define ETMACVRn(n) (0x040 + (n * 4))
+#define ETMACTRn(n) (0x080 + (n * 4))
+#define ETMCNTRLDVRn(n) (0x140 + (n * 4))
+#define ETMCNTENRn(n) (0x150 + (n * 4))
+#define ETMCNTRLDEVRn(n) (0x160 + (n * 4))
+#define ETMCNTVRn(n) (0x170 + (n * 4))
+#define ETMSQ12EVR (0x180)
+#define ETMSQ21EVR (0x184)
+#define ETMSQ23EVR (0x188)
+#define ETMSQ31EVR (0x18C)
+#define ETMSQ32EVR (0x190)
+#define ETMSQ13EVR (0x194)
+#define ETMSQR (0x19C)
+#define ETMEXTOUTEVRn(n) (0x1A0 + (n * 4))
+#define ETMCIDCVRn(n) (0x1B0 + (n * 4))
+#define ETMCIDCMR (0x1BC)
+#define ETMIMPSPEC0 (0x1C0)
+#define ETMIMPSPEC1 (0x1C4)
+#define ETMIMPSPEC2 (0x1C8)
+#define ETMIMPSPEC3 (0x1CC)
+#define ETMIMPSPEC4 (0x1D0)
+#define ETMIMPSPEC5 (0x1D4)
+#define ETMIMPSPEC6 (0x1D8)
+#define ETMIMPSPEC7 (0x1DC)
+#define ETMSYNCFR (0x1E0)
+#define ETMIDR (0x1E4)
+#define ETMCCER (0x1E8)
+#define ETMEXTINSELR (0x1EC)
+#define ETMTESSEICR (0x1F0)
+#define ETMEIBCR (0x1F4)
+#define ETMTSEVR (0x1F8)
+#define ETMAUXCR (0x1FC)
+#define ETMTRACEIDR (0x200)
+#define ETMVMIDCVR (0x240)
+/* Management registers (0x300-0x314) */
+#define ETMOSLAR (0x300)
+#define ETMOSLSR (0x304)
+#define ETMOSSRR (0x308)
+#define ETMPDCR (0x310)
+#define ETMPDSR (0x314)
+
+#define ETM_MAX_ADDR_CMP (16)
+#define ETM_MAX_CNTR (4)
+#define ETM_MAX_CTXID_CMP (3)
+
+#define ETM_MODE_EXCLUDE BIT(0)
+#define ETM_MODE_CYCACC BIT(1)
+#define ETM_MODE_STALL BIT(2)
+#define ETM_MODE_TIMESTAMP BIT(3)
+#define ETM_MODE_CTXID BIT(4)
+#define ETM_MODE_ALL (0x1F)
+
+#define ETM_EVENT_MASK (0x1FFFF)
+#define ETM_SYNC_MASK (0xFFF)
+#define ETM_ALL_MASK (0xFFFFFFFF)
+
+#define ETM_SEQ_STATE_MAX_VAL (0x2)
+
+enum {
+ ETM_ADDR_TYPE_NONE,
+ ETM_ADDR_TYPE_SINGLE,
+ ETM_ADDR_TYPE_RANGE,
+ ETM_ADDR_TYPE_START,
+ ETM_ADDR_TYPE_STOP,
+};
+
+#define ETM_LOCK(cpu) \
+do { \
+ mb(); \
+ etm_writel(etm, cpu, 0x0, CS_LAR); \
+} while (0)
+#define ETM_UNLOCK(cpu) \
+do { \
+ etm_writel(etm, cpu, CS_UNLOCK_MAGIC, CS_LAR); \
+ mb(); \
+} while (0)
+
+
+#ifdef MODULE_PARAM_PREFIX
+#undef MODULE_PARAM_PREFIX
+#endif
+#define MODULE_PARAM_PREFIX "qdss."
+
+#ifdef CONFIG_MSM_QDSS_ETM_DEFAULT_ENABLE
+static int etm_boot_enable = 1;
+#else
+static int etm_boot_enable;
+#endif
+module_param_named(
+ etm_boot_enable, etm_boot_enable, int, S_IRUGO
+);
+
+struct etm_ctx {
+ void __iomem *base;
+ bool enabled;
+ struct wake_lock wake_lock;
+ struct pm_qos_request qos_req;
+ struct qdss_source *src;
+ struct mutex mutex;
+ struct device *dev;
+ struct kobject *kobj;
+ uint8_t arch;
+ uint8_t nr_addr_cmp;
+ uint8_t nr_cntr;
+ uint8_t nr_ext_inp;
+ uint8_t nr_ext_out;
+ uint8_t nr_ctxid_cmp;
+ uint8_t reset;
+ uint32_t mode;
+ uint32_t ctrl;
+ uint32_t trigger_event;
+ uint32_t startstop_ctrl;
+ uint32_t enable_event;
+ uint32_t enable_ctrl1;
+ uint32_t fifofull_level;
+ uint8_t addr_idx;
+ uint32_t addr_val[ETM_MAX_ADDR_CMP];
+ uint32_t addr_acctype[ETM_MAX_ADDR_CMP];
+ uint32_t addr_type[ETM_MAX_ADDR_CMP];
+ uint8_t cntr_idx;
+ uint32_t cntr_rld_val[ETM_MAX_CNTR];
+ uint32_t cntr_event[ETM_MAX_CNTR];
+ uint32_t cntr_rld_event[ETM_MAX_CNTR];
+ uint32_t cntr_val[ETM_MAX_CNTR];
+ uint32_t seq_12_event;
+ uint32_t seq_21_event;
+ uint32_t seq_23_event;
+ uint32_t seq_31_event;
+ uint32_t seq_32_event;
+ uint32_t seq_13_event;
+ uint32_t seq_curr_state;
+ uint8_t ctxid_idx;
+ uint32_t ctxid_val[ETM_MAX_CTXID_CMP];
+ uint32_t ctxid_mask;
+ uint32_t sync_freq;
+ uint32_t timestamp_event;
+};
+
+static struct etm_ctx etm = {
+ .trigger_event = 0x406F,
+ .enable_event = 0x6F,
+ .enable_ctrl1 = 0x1,
+ .fifofull_level = 0x28,
+ .addr_val = {(uint32_t) _stext, (uint32_t) _etext},
+ .addr_type = {ETM_ADDR_TYPE_RANGE, ETM_ADDR_TYPE_RANGE},
+ .cntr_event = {[0 ... (ETM_MAX_CNTR - 1)] = 0x406F},
+ .cntr_rld_event = {[0 ... (ETM_MAX_CNTR - 1)] = 0x406F},
+ .seq_12_event = 0x406F,
+ .seq_21_event = 0x406F,
+ .seq_23_event = 0x406F,
+ .seq_31_event = 0x406F,
+ .seq_32_event = 0x406F,
+ .seq_13_event = 0x406F,
+ .sync_freq = 0x80,
+ .timestamp_event = 0x406F,
+};
+
+
+/* ETM clock is derived from the processor clock and gets enabled on a
+ * logical OR of below items on Krait (pass2 onwards):
+ * 1.CPMR[ETMCLKEN] is 1
+ * 2.ETMCR[PD] is 0
+ * 3.ETMPDCR[PU] is 1
+ * 4.Reset is asserted (core or debug)
+ * 5.APB memory mapped requests (eg. EDAP access)
+ *
+ * 1., 2. and 3. above are permanent enables whereas 4. and 5. are temporary
+ * enables
+ *
+ * We rely on 5. to be able to access ETMCR and then use 2. above for ETM
+ * clock vote in the driver and the save-restore code uses 1. above
+ * for its vote
+ */
+static void etm_set_pwrdwn(int cpu)
+{
+ uint32_t etmcr;
+
+ etmcr = etm_readl(etm, cpu, ETMCR);
+ etmcr |= BIT(0);
+ etm_writel(etm, cpu, etmcr, ETMCR);
+}
+
+static void etm_clr_pwrdwn(int cpu)
+{
+ uint32_t etmcr;
+
+ etmcr = etm_readl(etm, cpu, ETMCR);
+ etmcr &= ~BIT(0);
+ etm_writel(etm, cpu, etmcr, ETMCR);
+}
+
+static void etm_set_prog(int cpu)
+{
+ uint32_t etmcr;
+ int count;
+
+ etmcr = etm_readl(etm, cpu, ETMCR);
+ etmcr |= BIT(10);
+ etm_writel(etm, cpu, etmcr, ETMCR);
+
+ for (count = TIMEOUT_US; BVAL(etm_readl(etm, cpu, ETMSR), 1) != 1
+ && count > 0; count--)
+ udelay(1);
+ WARN(count == 0, "timeout while setting prog bit, ETMSR: %#x\n",
+ etm_readl(etm, cpu, ETMSR));
+}
+
+static void etm_clr_prog(int cpu)
+{
+ uint32_t etmcr;
+ int count;
+
+ etmcr = etm_readl(etm, cpu, ETMCR);
+ etmcr &= ~BIT(10);
+ etm_writel(etm, cpu, etmcr, ETMCR);
+
+ for (count = TIMEOUT_US; BVAL(etm_readl(etm, cpu, ETMSR), 1) != 0
+ && count > 0; count--)
+ udelay(1);
+ WARN(count == 0, "timeout while clearing prog bit, ETMSR: %#x\n",
+ etm_readl(etm, cpu, ETMSR));
+}
+
+static void __etm_enable(int cpu)
+{
+ int i;
+
+ ETM_UNLOCK(cpu);
+ /* Vote for ETM power/clock enable */
+ etm_clr_pwrdwn(cpu);
+ etm_set_prog(cpu);
+
+ etm_writel(etm, cpu, etm.ctrl | BIT(10), ETMCR);
+ etm_writel(etm, cpu, etm.trigger_event, ETMTRIGGER);
+ etm_writel(etm, cpu, etm.startstop_ctrl, ETMTSSCR);
+ etm_writel(etm, cpu, etm.enable_event, ETMTEEVR);
+ etm_writel(etm, cpu, etm.enable_ctrl1, ETMTECR1);
+ etm_writel(etm, cpu, etm.fifofull_level, ETMFFLR);
+ for (i = 0; i < etm.nr_addr_cmp; i++) {
+ etm_writel(etm, cpu, etm.addr_val[i], ETMACVRn(i));
+ etm_writel(etm, cpu, etm.addr_acctype[i], ETMACTRn(i));
+ }
+ for (i = 0; i < etm.nr_cntr; i++) {
+ etm_writel(etm, cpu, etm.cntr_rld_val[i], ETMCNTRLDVRn(i));
+ etm_writel(etm, cpu, etm.cntr_event[i], ETMCNTENRn(i));
+ etm_writel(etm, cpu, etm.cntr_rld_event[i], ETMCNTRLDEVRn(i));
+ etm_writel(etm, cpu, etm.cntr_val[i], ETMCNTVRn(i));
+ }
+ etm_writel(etm, cpu, etm.seq_12_event, ETMSQ12EVR);
+ etm_writel(etm, cpu, etm.seq_21_event, ETMSQ21EVR);
+ etm_writel(etm, cpu, etm.seq_23_event, ETMSQ23EVR);
+ etm_writel(etm, cpu, etm.seq_31_event, ETMSQ31EVR);
+ etm_writel(etm, cpu, etm.seq_32_event, ETMSQ32EVR);
+ etm_writel(etm, cpu, etm.seq_13_event, ETMSQ13EVR);
+ etm_writel(etm, cpu, etm.seq_curr_state, ETMSQR);
+ for (i = 0; i < etm.nr_ext_out; i++)
+ etm_writel(etm, cpu, 0x0000406F, ETMEXTOUTEVRn(i));
+ for (i = 0; i < etm.nr_ctxid_cmp; i++)
+ etm_writel(etm, cpu, etm.ctxid_val[i], ETMCIDCVRn(i));
+ etm_writel(etm, cpu, etm.ctxid_mask, ETMCIDCMR);
+ etm_writel(etm, cpu, etm.sync_freq, ETMSYNCFR);
+ etm_writel(etm, cpu, 0x00000000, ETMEXTINSELR);
+ etm_writel(etm, cpu, etm.timestamp_event, ETMTSEVR);
+ etm_writel(etm, cpu, 0x00000000, ETMAUXCR);
+ etm_writel(etm, cpu, cpu+1, ETMTRACEIDR);
+ etm_writel(etm, cpu, 0x00000000, ETMVMIDCVR);
+
+ etm_clr_prog(cpu);
+ ETM_LOCK(cpu);
+}
+
+static int etm_enable(void)
+{
+ int ret, cpu;
+
+ if (etm.enabled) {
+ dev_err(etm.dev, "ETM tracing already enabled\n");
+ ret = -EPERM;
+ goto err;
+ }
+
+ wake_lock(&etm.wake_lock);
+ /* 1. causes all online cpus to come out of idle PC
+ * 2. prevents idle PC until save restore flag is enabled atomically
+ *
+ * we rely on the user to prevent hotplug on/off racing with this
+ * operation and to ensure cores where trace is expected to be turned
+ * on are already hotplugged on
+ */
+ pm_qos_update_request(&etm.qos_req, 0);
+
+ ret = qdss_enable(etm.src);
+ if (ret)
+ goto err_qdss;
+
+ for_each_online_cpu(cpu)
+ __etm_enable(cpu);
+
+ etm.enabled = true;
+
+ pm_qos_update_request(&etm.qos_req, PM_QOS_DEFAULT_VALUE);
+ wake_unlock(&etm.wake_lock);
+
+ dev_info(etm.dev, "ETM tracing enabled\n");
+ return 0;
+
+err_qdss:
+ pm_qos_update_request(&etm.qos_req, PM_QOS_DEFAULT_VALUE);
+ wake_unlock(&etm.wake_lock);
+err:
+ return ret;
+}
+
+static void __etm_disable(int cpu)
+{
+ ETM_UNLOCK(cpu);
+ etm_set_prog(cpu);
+
+ /* program trace enable to low by using always false event */
+ etm_writel(etm, cpu, 0x6F | BIT(14), ETMTEEVR);
+
+ /* Vote for ETM power/clock disable */
+ etm_set_pwrdwn(cpu);
+ ETM_LOCK(cpu);
+}
+
+static int etm_disable(void)
+{
+ int ret, cpu;
+
+ if (!etm.enabled) {
+ dev_err(etm.dev, "ETM tracing already disabled\n");
+ ret = -EPERM;
+ goto err;
+ }
+
+ wake_lock(&etm.wake_lock);
+ /* 1. causes all online cpus to come out of idle PC
+ * 2. prevents idle PC until save restore flag is disabled atomically
+ *
+ * we rely on the user to prevent hotplug on/off racing with this
+ * operation and to ensure cores where trace is expected to be turned
+ * off are already hotplugged on
+ */
+ pm_qos_update_request(&etm.qos_req, 0);
+
+ for_each_online_cpu(cpu)
+ __etm_disable(cpu);
+
+ qdss_disable(etm.src);
+
+ etm.enabled = false;
+
+ pm_qos_update_request(&etm.qos_req, PM_QOS_DEFAULT_VALUE);
+ wake_unlock(&etm.wake_lock);
+
+ dev_info(etm.dev, "ETM tracing disabled\n");
+ return 0;
+err:
+ return ret;
+}
+
+/* Memory mapped writes to clear os lock not supported */
+static void etm_os_unlock(void *unused)
+{
+ unsigned long value = 0x0;
+
+ asm("mcr p14, 1, %0, c1, c0, 4\n\t" : : "r" (value));
+ asm("isb\n\t");
+}
+
+#define ETM_STORE(__name, mask) \
+static ssize_t __name##_store(struct kobject *kobj, \
+ struct kobj_attribute *attr, \
+ const char *buf, size_t n) \
+{ \
+ unsigned long val; \
+ \
+ if (sscanf(buf, "%lx", &val) != 1) \
+ return -EINVAL; \
+ \
+ etm.__name = val & mask; \
+ return n; \
+}
+
+#define ETM_SHOW(__name) \
+static ssize_t __name##_show(struct kobject *kobj, \
+ struct kobj_attribute *attr, \
+ char *buf) \
+{ \
+ unsigned long val = etm.__name; \
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); \
+}
+
+#define ETM_ATTR(__name) \
+static struct kobj_attribute __name##_attr = \
+ __ATTR(__name, S_IRUGO | S_IWUSR, __name##_show, __name##_store)
+#define ETM_ATTR_RO(__name) \
+static struct kobj_attribute __name##_attr = \
+ __ATTR(__name, S_IRUGO, __name##_show, NULL)
+
+static ssize_t enabled_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ int ret = 0;
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ if (val)
+ ret = etm_enable();
+ else
+ ret = etm_disable();
+ mutex_unlock(&etm.mutex);
+
+ if (ret)
+ return ret;
+ return n;
+}
+ETM_SHOW(enabled);
+ETM_ATTR(enabled);
+
+ETM_SHOW(nr_addr_cmp);
+ETM_ATTR_RO(nr_addr_cmp);
+ETM_SHOW(nr_cntr);
+ETM_ATTR_RO(nr_cntr);
+ETM_SHOW(nr_ctxid_cmp);
+ETM_ATTR_RO(nr_ctxid_cmp);
+
+/* Reset to trace everything i.e. exclude nothing. */
+static ssize_t reset_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ int i;
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ if (val) {
+ etm.mode = ETM_MODE_EXCLUDE;
+ etm.ctrl = 0x0;
+ if (cpu_is_krait_v1()) {
+ etm.mode |= ETM_MODE_CYCACC;
+ etm.ctrl |= BIT(12);
+ }
+ etm.trigger_event = 0x406F;
+ etm.startstop_ctrl = 0x0;
+ etm.enable_event = 0x6F;
+ etm.enable_ctrl1 = 0x1000000;
+ etm.fifofull_level = 0x28;
+ etm.addr_idx = 0x0;
+ for (i = 0; i < etm.nr_addr_cmp; i++) {
+ etm.addr_val[i] = 0x0;
+ etm.addr_acctype[i] = 0x0;
+ etm.addr_type[i] = ETM_ADDR_TYPE_NONE;
+ }
+ etm.cntr_idx = 0x0;
+ for (i = 0; i < etm.nr_cntr; i++) {
+ etm.cntr_rld_val[i] = 0x0;
+ etm.cntr_event[i] = 0x406F;
+ etm.cntr_rld_event[i] = 0x406F;
+ etm.cntr_val[i] = 0x0;
+ }
+ etm.seq_12_event = 0x406F;
+ etm.seq_21_event = 0x406F;
+ etm.seq_23_event = 0x406F;
+ etm.seq_31_event = 0x406F;
+ etm.seq_32_event = 0x406F;
+ etm.seq_13_event = 0x406F;
+ etm.seq_curr_state = 0x0;
+ etm.ctxid_idx = 0x0;
+ for (i = 0; i < etm.nr_ctxid_cmp; i++)
+ etm.ctxid_val[i] = 0x0;
+ etm.ctxid_mask = 0x0;
+ etm.sync_freq = 0x80;
+ etm.timestamp_event = 0x406F;
+ }
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+ETM_SHOW(reset);
+ETM_ATTR(reset);
+
+static ssize_t mode_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ etm.mode = val & ETM_MODE_ALL;
+
+ if (etm.mode & ETM_MODE_EXCLUDE)
+ etm.enable_ctrl1 |= BIT(24);
+ else
+ etm.enable_ctrl1 &= ~BIT(24);
+
+ if (etm.mode & ETM_MODE_CYCACC)
+ etm.ctrl |= BIT(12);
+ else
+ etm.ctrl &= ~BIT(12);
+
+ if (etm.mode & ETM_MODE_STALL)
+ etm.ctrl |= BIT(7);
+ else
+ etm.ctrl &= ~BIT(7);
+
+ if (etm.mode & ETM_MODE_TIMESTAMP)
+ etm.ctrl |= BIT(28);
+ else
+ etm.ctrl &= ~BIT(28);
+ if (etm.mode & ETM_MODE_CTXID)
+ etm.ctrl |= (BIT(14) | BIT(15));
+ else
+ etm.ctrl &= ~(BIT(14) | BIT(15));
+ mutex_unlock(&etm.mutex);
+
+ return n;
+}
+ETM_SHOW(mode);
+ETM_ATTR(mode);
+
+ETM_STORE(trigger_event, ETM_EVENT_MASK);
+ETM_SHOW(trigger_event);
+ETM_ATTR(trigger_event);
+
+ETM_STORE(enable_event, ETM_EVENT_MASK);
+ETM_SHOW(enable_event);
+ETM_ATTR(enable_event);
+
+ETM_STORE(fifofull_level, ETM_ALL_MASK);
+ETM_SHOW(fifofull_level);
+ETM_ATTR(fifofull_level);
+
+static ssize_t addr_idx_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+ if (val >= etm.nr_addr_cmp)
+ return -EINVAL;
+
+ /* Use mutex to ensure index doesn't change while it gets dereferenced
+ * multiple times within a mutex block elsewhere.
+ */
+ mutex_lock(&etm.mutex);
+ etm.addr_idx = val;
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+ETM_SHOW(addr_idx);
+ETM_ATTR(addr_idx);
+
+static ssize_t addr_single_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+ uint8_t idx;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ idx = etm.addr_idx;
+ if (!(etm.addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ etm.addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) {
+ mutex_unlock(&etm.mutex);
+ return -EPERM;
+ }
+
+ etm.addr_val[idx] = val;
+ etm.addr_type[idx] = ETM_ADDR_TYPE_SINGLE;
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+static ssize_t addr_single_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val;
+ uint8_t idx;
+
+ mutex_lock(&etm.mutex);
+ idx = etm.addr_idx;
+ if (!(etm.addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ etm.addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) {
+ mutex_unlock(&etm.mutex);
+ return -EPERM;
+ }
+
+ val = etm.addr_val[idx];
+ mutex_unlock(&etm.mutex);
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+ETM_ATTR(addr_single);
+
+static ssize_t addr_range_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val1, val2;
+ uint8_t idx;
+
+ if (sscanf(buf, "%lx %lx", &val1, &val2) != 2)
+ return -EINVAL;
+ /* lower address comparator cannot have a higher address value */
+ if (val1 > val2)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ idx = etm.addr_idx;
+ if (idx % 2 != 0) {
+ mutex_unlock(&etm.mutex);
+ return -EPERM;
+ }
+ if (!((etm.addr_type[idx] == ETM_ADDR_TYPE_NONE &&
+ etm.addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) ||
+ (etm.addr_type[idx] == ETM_ADDR_TYPE_RANGE &&
+ etm.addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) {
+ mutex_unlock(&etm.mutex);
+ return -EPERM;
+ }
+
+ etm.addr_val[idx] = val1;
+ etm.addr_type[idx] = ETM_ADDR_TYPE_RANGE;
+ etm.addr_val[idx + 1] = val2;
+ etm.addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE;
+ etm.enable_ctrl1 |= (1 << (idx/2));
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+static ssize_t addr_range_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val1, val2;
+ uint8_t idx;
+
+ mutex_lock(&etm.mutex);
+ idx = etm.addr_idx;
+ if (idx % 2 != 0) {
+ mutex_unlock(&etm.mutex);
+ return -EPERM;
+ }
+ if (!((etm.addr_type[idx] == ETM_ADDR_TYPE_NONE &&
+ etm.addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) ||
+ (etm.addr_type[idx] == ETM_ADDR_TYPE_RANGE &&
+ etm.addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) {
+ mutex_unlock(&etm.mutex);
+ return -EPERM;
+ }
+
+ val1 = etm.addr_val[idx];
+ val2 = etm.addr_val[idx + 1];
+ mutex_unlock(&etm.mutex);
+ return scnprintf(buf, PAGE_SIZE, "%#lx %#lx\n", val1, val2);
+}
+ETM_ATTR(addr_range);
+
+static ssize_t addr_start_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+ uint8_t idx;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ idx = etm.addr_idx;
+ if (!(etm.addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ etm.addr_type[idx] == ETM_ADDR_TYPE_START)) {
+ mutex_unlock(&etm.mutex);
+ return -EPERM;
+ }
+
+ etm.addr_val[idx] = val;
+ etm.addr_type[idx] = ETM_ADDR_TYPE_START;
+ etm.startstop_ctrl |= (1 << idx);
+ etm.enable_ctrl1 |= BIT(25);
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+static ssize_t addr_start_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val;
+ uint8_t idx;
+
+ mutex_lock(&etm.mutex);
+ idx = etm.addr_idx;
+ if (!(etm.addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ etm.addr_type[idx] == ETM_ADDR_TYPE_START)) {
+ mutex_unlock(&etm.mutex);
+ return -EPERM;
+ }
+
+ val = etm.addr_val[idx];
+ mutex_unlock(&etm.mutex);
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+ETM_ATTR(addr_start);
+
+static ssize_t addr_stop_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+ uint8_t idx;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ idx = etm.addr_idx;
+ if (!(etm.addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ etm.addr_type[idx] == ETM_ADDR_TYPE_STOP)) {
+ mutex_unlock(&etm.mutex);
+ return -EPERM;
+ }
+
+ etm.addr_val[idx] = val;
+ etm.addr_type[idx] = ETM_ADDR_TYPE_STOP;
+ etm.startstop_ctrl |= (1 << (idx + 16));
+ etm.enable_ctrl1 |= BIT(25);
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+static ssize_t addr_stop_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val;
+ uint8_t idx;
+
+ mutex_lock(&etm.mutex);
+ idx = etm.addr_idx;
+ if (!(etm.addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ etm.addr_type[idx] == ETM_ADDR_TYPE_STOP)) {
+ mutex_unlock(&etm.mutex);
+ return -EPERM;
+ }
+
+ val = etm.addr_val[idx];
+ mutex_unlock(&etm.mutex);
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+ETM_ATTR(addr_stop);
+
+static ssize_t addr_acctype_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ etm.addr_acctype[etm.addr_idx] = val;
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+static ssize_t addr_acctype_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val;
+
+ mutex_lock(&etm.mutex);
+ val = etm.addr_acctype[etm.addr_idx];
+ mutex_unlock(&etm.mutex);
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+ETM_ATTR(addr_acctype);
+
+static ssize_t cntr_idx_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+ if (val >= etm.nr_cntr)
+ return -EINVAL;
+
+ /* Use mutex to ensure index doesn't change while it gets dereferenced
+ * multiple times within a mutex block elsewhere.
+ */
+ mutex_lock(&etm.mutex);
+ etm.cntr_idx = val;
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+ETM_SHOW(cntr_idx);
+ETM_ATTR(cntr_idx);
+
+static ssize_t cntr_rld_val_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ etm.cntr_rld_val[etm.cntr_idx] = val;
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+static ssize_t cntr_rld_val_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val;
+ mutex_lock(&etm.mutex);
+ val = etm.cntr_rld_val[etm.cntr_idx];
+ mutex_unlock(&etm.mutex);
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+ETM_ATTR(cntr_rld_val);
+
+static ssize_t cntr_event_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ etm.cntr_event[etm.cntr_idx] = val & ETM_EVENT_MASK;
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+static ssize_t cntr_event_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val;
+
+ mutex_lock(&etm.mutex);
+ val = etm.cntr_event[etm.cntr_idx];
+ mutex_unlock(&etm.mutex);
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+ETM_ATTR(cntr_event);
+
+static ssize_t cntr_rld_event_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ etm.cntr_rld_event[etm.cntr_idx] = val & ETM_EVENT_MASK;
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+static ssize_t cntr_rld_event_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val;
+
+ mutex_lock(&etm.mutex);
+ val = etm.cntr_rld_event[etm.cntr_idx];
+ mutex_unlock(&etm.mutex);
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+ETM_ATTR(cntr_rld_event);
+
+static ssize_t cntr_val_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ etm.cntr_val[etm.cntr_idx] = val;
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+static ssize_t cntr_val_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val;
+
+ mutex_lock(&etm.mutex);
+ val = etm.cntr_val[etm.cntr_idx];
+ mutex_unlock(&etm.mutex);
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+ETM_ATTR(cntr_val);
+
+ETM_STORE(seq_12_event, ETM_EVENT_MASK);
+ETM_SHOW(seq_12_event);
+ETM_ATTR(seq_12_event);
+
+ETM_STORE(seq_21_event, ETM_EVENT_MASK);
+ETM_SHOW(seq_21_event);
+ETM_ATTR(seq_21_event);
+
+ETM_STORE(seq_23_event, ETM_EVENT_MASK);
+ETM_SHOW(seq_23_event);
+ETM_ATTR(seq_23_event);
+
+ETM_STORE(seq_31_event, ETM_EVENT_MASK);
+ETM_SHOW(seq_31_event);
+ETM_ATTR(seq_31_event);
+
+ETM_STORE(seq_32_event, ETM_EVENT_MASK);
+ETM_SHOW(seq_32_event);
+ETM_ATTR(seq_32_event);
+
+ETM_STORE(seq_13_event, ETM_EVENT_MASK);
+ETM_SHOW(seq_13_event);
+ETM_ATTR(seq_13_event);
+
+static ssize_t seq_curr_state_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+ if (val > ETM_SEQ_STATE_MAX_VAL)
+ return -EINVAL;
+
+ etm.seq_curr_state = val;
+ return n;
+}
+ETM_SHOW(seq_curr_state);
+ETM_ATTR(seq_curr_state);
+
+static ssize_t ctxid_idx_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+ if (val >= etm.nr_ctxid_cmp)
+ return -EINVAL;
+
+ /* Use mutex to ensure index doesn't change while it gets dereferenced
+ * multiple times within a mutex block elsewhere.
+ */
+ mutex_lock(&etm.mutex);
+ etm.ctxid_idx = val;
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+ETM_SHOW(ctxid_idx);
+ETM_ATTR(ctxid_idx);
+
+static ssize_t ctxid_val_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ mutex_lock(&etm.mutex);
+ etm.ctxid_val[etm.ctxid_idx] = val;
+ mutex_unlock(&etm.mutex);
+ return n;
+}
+static ssize_t ctxid_val_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val;
+
+ mutex_lock(&etm.mutex);
+ val = etm.ctxid_val[etm.ctxid_idx];
+ mutex_unlock(&etm.mutex);
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+ETM_ATTR(ctxid_val);
+
+ETM_STORE(ctxid_mask, ETM_ALL_MASK);
+ETM_SHOW(ctxid_mask);
+ETM_ATTR(ctxid_mask);
+
+ETM_STORE(sync_freq, ETM_SYNC_MASK);
+ETM_SHOW(sync_freq);
+ETM_ATTR(sync_freq);
+
+ETM_STORE(timestamp_event, ETM_EVENT_MASK);
+ETM_SHOW(timestamp_event);
+ETM_ATTR(timestamp_event);
+
+static struct attribute *etm_attrs[] = {
+ &nr_addr_cmp_attr.attr,
+ &nr_cntr_attr.attr,
+ &nr_ctxid_cmp_attr.attr,
+ &reset_attr.attr,
+ &mode_attr.attr,
+ &trigger_event_attr.attr,
+ &enable_event_attr.attr,
+ &fifofull_level_attr.attr,
+ &addr_idx_attr.attr,
+ &addr_single_attr.attr,
+ &addr_range_attr.attr,
+ &addr_start_attr.attr,
+ &addr_stop_attr.attr,
+ &addr_acctype_attr.attr,
+ &cntr_idx_attr.attr,
+ &cntr_rld_val_attr.attr,
+ &cntr_event_attr.attr,
+ &cntr_rld_event_attr.attr,
+ &cntr_val_attr.attr,
+ &seq_12_event_attr.attr,
+ &seq_21_event_attr.attr,
+ &seq_23_event_attr.attr,
+ &seq_31_event_attr.attr,
+ &seq_32_event_attr.attr,
+ &seq_13_event_attr.attr,
+ &seq_curr_state_attr.attr,
+ &ctxid_idx_attr.attr,
+ &ctxid_val_attr.attr,
+ &ctxid_mask_attr.attr,
+ &sync_freq_attr.attr,
+ ×tamp_event_attr.attr,
+ NULL,
+};
+
+static struct attribute_group etm_attr_grp = {
+ .attrs = etm_attrs,
+};
+
+static int __devinit etm_sysfs_init(void)
+{
+ int ret;
+
+ etm.kobj = kobject_create_and_add("etm", qdss_get_modulekobj());
+ if (!etm.kobj) {
+ dev_err(etm.dev, "failed to create ETM sysfs kobject\n");
+ ret = -ENOMEM;
+ goto err_create;
+ }
+
+ ret = sysfs_create_file(etm.kobj, &enabled_attr.attr);
+ if (ret) {
+ dev_err(etm.dev, "failed to create ETM sysfs enabled"
+ " attribute\n");
+ goto err_file;
+ }
+
+ if (sysfs_create_group(etm.kobj, &etm_attr_grp))
+ dev_err(etm.dev, "failed to create ETM sysfs group\n");
+
+ return 0;
+err_file:
+ kobject_put(etm.kobj);
+err_create:
+ return ret;
+}
+
+static void __devexit etm_sysfs_exit(void)
+{
+ sysfs_remove_group(etm.kobj, &etm_attr_grp);
+ sysfs_remove_file(etm.kobj, &enabled_attr.attr);
+ kobject_put(etm.kobj);
+}
+
+static bool __devinit etm_arch_supported(uint8_t arch)
+{
+ switch (arch) {
+ case PFT_ARCH_V1_1:
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+static int __devinit etm_arch_init(void)
+{
+ int ret, i;
+ /* use cpu 0 for setup */
+ int cpu = 0;
+ uint32_t etmidr;
+ uint32_t etmccr;
+
+ /* Unlock OS lock first to allow memory mapped reads and writes */
+ etm_os_unlock(NULL);
+ smp_call_function(etm_os_unlock, NULL, 1);
+ ETM_UNLOCK(cpu);
+ /* Vote for ETM power/clock enable */
+ etm_clr_pwrdwn(cpu);
+ /* Set prog bit. It will be set from reset but this is included to
+ * ensure it is set
+ */
+ etm_set_prog(cpu);
+
+ /* find all capabilities */
+ etmidr = etm_readl(etm, cpu, ETMIDR);
+ etm.arch = BMVAL(etmidr, 4, 11);
+ if (etm_arch_supported(etm.arch) == false) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ etmccr = etm_readl(etm, cpu, ETMCCR);
+ etm.nr_addr_cmp = BMVAL(etmccr, 0, 3) * 2;
+ etm.nr_cntr = BMVAL(etmccr, 13, 15);
+ etm.nr_ext_inp = BMVAL(etmccr, 17, 19);
+ etm.nr_ext_out = BMVAL(etmccr, 20, 22);
+ etm.nr_ctxid_cmp = BMVAL(etmccr, 24, 25);
+
+ if (cpu_is_krait_v1()) {
+ /* Krait pass1 doesn't support include filtering and non-cycle
+ * accurate tracing
+ */
+ etm.mode = (ETM_MODE_EXCLUDE | ETM_MODE_CYCACC);
+ etm.ctrl = 0x1000;
+ etm.enable_ctrl1 = 0x1000000;
+ for (i = 0; i < etm.nr_addr_cmp; i++) {
+ etm.addr_val[i] = 0x0;
+ etm.addr_acctype[i] = 0x0;
+ etm.addr_type[i] = ETM_ADDR_TYPE_NONE;
+ }
+ }
+
+ /* Vote for ETM power/clock disable */
+ etm_set_pwrdwn(cpu);
+ ETM_LOCK(cpu);
+
+ return 0;
+err:
+ return ret;
+}
+
+static int __devinit etm_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -EINVAL;
+ goto err_res;
+ }
+
+ etm.base = ioremap_nocache(res->start, resource_size(res));
+ if (!etm.base) {
+ ret = -EINVAL;
+ goto err_ioremap;
+ }
+
+ etm.dev = &pdev->dev;
+
+ mutex_init(&etm.mutex);
+ wake_lock_init(&etm.wake_lock, WAKE_LOCK_SUSPEND, "msm_etm");
+ pm_qos_add_request(&etm.qos_req, PM_QOS_CPU_DMA_LATENCY,
+ PM_QOS_DEFAULT_VALUE);
+ etm.src = qdss_get("msm_etm");
+ if (IS_ERR(etm.src)) {
+ ret = PTR_ERR(etm.src);
+ goto err_qdssget;
+ }
+
+ ret = qdss_clk_enable();
+ if (ret)
+ goto err_clk;
+
+ ret = etm_arch_init();
+ if (ret)
+ goto err_arch;
+
+ ret = etm_sysfs_init();
+ if (ret)
+ goto err_sysfs;
+
+ etm.enabled = false;
+
+ qdss_clk_disable();
+
+ dev_info(etm.dev, "ETM initialized\n");
+
+ if (etm_boot_enable)
+ etm_enable();
+
+ return 0;
+
+err_sysfs:
+err_arch:
+ qdss_clk_disable();
+err_clk:
+ qdss_put(etm.src);
+err_qdssget:
+ pm_qos_remove_request(&etm.qos_req);
+ wake_lock_destroy(&etm.wake_lock);
+ mutex_destroy(&etm.mutex);
+ iounmap(etm.base);
+err_ioremap:
+err_res:
+ dev_err(etm.dev, "ETM init failed\n");
+ return ret;
+}
+
+static int __devexit etm_remove(struct platform_device *pdev)
+{
+ if (etm.enabled)
+ etm_disable();
+ etm_sysfs_exit();
+ qdss_put(etm.src);
+ pm_qos_remove_request(&etm.qos_req);
+ wake_lock_destroy(&etm.wake_lock);
+ mutex_destroy(&etm.mutex);
+ iounmap(etm.base);
+
+ return 0;
+}
+
+static struct of_device_id etm_match[] = {
+ {.compatible = "qcom,msm-etm"},
+ {}
+};
+
+static struct platform_driver etm_driver = {
+ .probe = etm_probe,
+ .remove = __devexit_p(etm_remove),
+ .driver = {
+ .name = "msm_etm",
+ .owner = THIS_MODULE,
+ .of_match_table = etm_match,
+ },
+};
+
+int __init etm_init(void)
+{
+ return platform_driver_register(&etm_driver);
+}
+module_init(etm_init);
+
+void __exit etm_exit(void)
+{
+ platform_driver_unregister(&etm_driver);
+}
+module_exit(etm_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("CoreSight Program Flow Trace driver");
diff --git a/drivers/cs/qdss-funnel.c b/drivers/cs/qdss-funnel.c
new file mode 100644
index 0000000..1c19ebd
--- /dev/null
+++ b/drivers/cs/qdss-funnel.c
@@ -0,0 +1,239 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/err.h>
+
+#include "qdss-priv.h"
+
+#define funnel_writel(funnel, id, val, off) \
+ __raw_writel((val), funnel.base + (SZ_4K * id) + off)
+#define funnel_readl(funnel, id, off) \
+ __raw_readl(funnel.base + (SZ_4K * id) + off)
+
+#define FUNNEL_FUNCTL (0x000)
+#define FUNNEL_PRICTL (0x004)
+#define FUNNEL_ITATBDATA0 (0xEEC)
+#define FUNNEL_ITATBCTR2 (0xEF0)
+#define FUNNEL_ITATBCTR1 (0xEF4)
+#define FUNNEL_ITATBCTR0 (0xEF8)
+
+
+#define FUNNEL_LOCK(id) \
+do { \
+ mb(); \
+ funnel_writel(funnel, id, 0x0, CS_LAR); \
+} while (0)
+#define FUNNEL_UNLOCK(id) \
+do { \
+ funnel_writel(funnel, id, CS_UNLOCK_MAGIC, CS_LAR); \
+ mb(); \
+} while (0)
+
+#define FUNNEL_HOLDTIME_MASK (0xF00)
+#define FUNNEL_HOLDTIME_SHFT (0x8)
+#define FUNNEL_HOLDTIME (0x7 << FUNNEL_HOLDTIME_SHFT)
+
+struct funnel_ctx {
+ void __iomem *base;
+ bool enabled;
+ struct mutex mutex;
+ struct device *dev;
+ struct kobject *kobj;
+ uint32_t priority;
+};
+
+static struct funnel_ctx funnel;
+
+static void __funnel_enable(uint8_t id, uint32_t port_mask)
+{
+ uint32_t functl;
+
+ FUNNEL_UNLOCK(id);
+
+ functl = funnel_readl(funnel, id, FUNNEL_FUNCTL);
+ functl &= ~FUNNEL_HOLDTIME_MASK;
+ functl |= FUNNEL_HOLDTIME;
+ functl |= port_mask;
+ funnel_writel(funnel, id, functl, FUNNEL_FUNCTL);
+ funnel_writel(funnel, id, funnel.priority, FUNNEL_PRICTL);
+
+ FUNNEL_LOCK(id);
+}
+
+void funnel_enable(uint8_t id, uint32_t port_mask)
+{
+ mutex_lock(&funnel.mutex);
+ __funnel_enable(id, port_mask);
+ funnel.enabled = true;
+ dev_info(funnel.dev, "FUNNEL port mask 0x%lx enabled\n",
+ (unsigned long) port_mask);
+ mutex_unlock(&funnel.mutex);
+}
+
+static void __funnel_disable(uint8_t id, uint32_t port_mask)
+{
+ uint32_t functl;
+
+ FUNNEL_UNLOCK(id);
+
+ functl = funnel_readl(funnel, id, FUNNEL_FUNCTL);
+ functl &= ~port_mask;
+ funnel_writel(funnel, id, functl, FUNNEL_FUNCTL);
+
+ FUNNEL_LOCK(id);
+}
+
+void funnel_disable(uint8_t id, uint32_t port_mask)
+{
+ mutex_lock(&funnel.mutex);
+ __funnel_disable(id, port_mask);
+ funnel.enabled = false;
+ dev_info(funnel.dev, "FUNNEL port mask 0x%lx disabled\n",
+ (unsigned long) port_mask);
+ mutex_unlock(&funnel.mutex);
+}
+
+#define FUNNEL_ATTR(__name) \
+static struct kobj_attribute __name##_attr = \
+ __ATTR(__name, S_IRUGO | S_IWUSR, __name##_show, __name##_store)
+
+static ssize_t priority_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ funnel.priority = val;
+ return n;
+}
+static ssize_t priority_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val = funnel.priority;
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+FUNNEL_ATTR(priority);
+
+static int __devinit funnel_sysfs_init(void)
+{
+ int ret;
+
+ funnel.kobj = kobject_create_and_add("funnel", qdss_get_modulekobj());
+ if (!funnel.kobj) {
+ dev_err(funnel.dev, "failed to create FUNNEL sysfs kobject\n");
+ ret = -ENOMEM;
+ goto err_create;
+ }
+
+ ret = sysfs_create_file(funnel.kobj, &priority_attr.attr);
+ if (ret) {
+ dev_err(funnel.dev, "failed to create FUNNEL sysfs priority"
+ " attribute\n");
+ goto err_file;
+ }
+
+ return 0;
+err_file:
+ kobject_put(funnel.kobj);
+err_create:
+ return ret;
+}
+
+static void __devexit funnel_sysfs_exit(void)
+{
+ sysfs_remove_file(funnel.kobj, &priority_attr.attr);
+ kobject_put(funnel.kobj);
+}
+
+static int __devinit funnel_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -EINVAL;
+ goto err_res;
+ }
+
+ funnel.base = ioremap_nocache(res->start, resource_size(res));
+ if (!funnel.base) {
+ ret = -EINVAL;
+ goto err_ioremap;
+ }
+
+ funnel.dev = &pdev->dev;
+
+ mutex_init(&funnel.mutex);
+
+ funnel_sysfs_init();
+
+ dev_info(funnel.dev, "FUNNEL initialized\n");
+ return 0;
+
+err_ioremap:
+err_res:
+ dev_err(funnel.dev, "FUNNEL init failed\n");
+ return ret;
+}
+
+static int __devexit funnel_remove(struct platform_device *pdev)
+{
+ if (funnel.enabled)
+ funnel_disable(0x0, 0xFF);
+ funnel_sysfs_exit();
+ mutex_destroy(&funnel.mutex);
+ iounmap(funnel.base);
+
+ return 0;
+}
+
+static struct of_device_id funnel_match[] = {
+ {.compatible = "qcom,msm-funnel"},
+ {}
+};
+
+static struct platform_driver funnel_driver = {
+ .probe = funnel_probe,
+ .remove = __devexit_p(funnel_remove),
+ .driver = {
+ .name = "msm_funnel",
+ .owner = THIS_MODULE,
+ .of_match_table = funnel_match,
+ },
+};
+
+static int __init funnel_init(void)
+{
+ return platform_driver_register(&funnel_driver);
+}
+module_init(funnel_init);
+
+static void __exit funnel_exit(void)
+{
+ platform_driver_unregister(&funnel_driver);
+}
+module_exit(funnel_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("CoreSight Funnel driver");
diff --git a/drivers/cs/qdss-priv.h b/drivers/cs/qdss-priv.h
new file mode 100644
index 0000000..868514c
--- /dev/null
+++ b/drivers/cs/qdss-priv.h
@@ -0,0 +1,70 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. 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 _ARCH_ARM_MACH_MSM_QDSS_H_
+#define _ARCH_ARM_MACH_MSM_QDSS_H_
+
+#include <linux/bitops.h>
+#include <linux/qdss.h>
+
+/* Coresight management registers (0xF00-0xFCC)
+ * 0xFA0 - 0xFA4: Management registers in PFTv1.0
+ * Trace registers in PFTv1.1
+ */
+#define CS_ITCTRL (0xF00)
+#define CS_CLAIMSET (0xFA0)
+#define CS_CLAIMCLR (0xFA4)
+#define CS_LAR (0xFB0)
+#define CS_LSR (0xFB4)
+#define CS_AUTHSTATUS (0xFB8)
+#define CS_DEVID (0xFC8)
+#define CS_DEVTYPE (0xFCC)
+/* Peripheral id registers (0xFD0-0xFEC) */
+#define CS_PIDR4 (0xFD0)
+#define CS_PIDR5 (0xFD4)
+#define CS_PIDR6 (0xFD8)
+#define CS_PIDR7 (0xFDC)
+#define CS_PIDR0 (0xFE0)
+#define CS_PIDR1 (0xFE4)
+#define CS_PIDR2 (0xFE8)
+#define CS_PIDR3 (0xFEC)
+/* Component id registers (0xFF0-0xFFC) */
+#define CS_CIDR0 (0xFF0)
+#define CS_CIDR1 (0xFF4)
+#define CS_CIDR2 (0xFF8)
+#define CS_CIDR3 (0xFFC)
+
+/* DBGv7 with baseline CP14 registers implemented */
+#define ARM_DEBUG_ARCH_V7B (0x3)
+/* DBGv7 with all CP14 registers implemented */
+#define ARM_DEBUG_ARCH_V7 (0x4)
+#define ARM_DEBUG_ARCH_V7_1 (0x5)
+#define ETM_ARCH_V3_3 (0x23)
+#define PFT_ARCH_V1_1 (0x31)
+
+#define TIMEOUT_US (100)
+#define CS_UNLOCK_MAGIC (0xC5ACCE55)
+
+#define BM(lsb, msb) ((BIT(msb) - BIT(lsb)) + BIT(msb))
+#define BMVAL(val, lsb, msb) ((val & BM(lsb, msb)) >> lsb)
+#define BVAL(val, n) ((val & BIT(n)) >> n)
+
+void etb_enable(void);
+void etb_disable(void);
+void etb_dump(void);
+void tpiu_disable(void);
+void funnel_enable(uint8_t id, uint32_t port_mask);
+void funnel_disable(uint8_t id, uint32_t port_mask);
+
+struct kobject *qdss_get_modulekobj(void);
+
+#endif
diff --git a/drivers/cs/qdss-stm.c b/drivers/cs/qdss-stm.c
new file mode 100644
index 0000000..0d44c1a
--- /dev/null
+++ b/drivers/cs/qdss-stm.c
@@ -0,0 +1,602 @@
+/* Copyright (c) 2012, Code Aurora Forum. 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+#include <mach/stm.h>
+
+#include "qdss-priv.h"
+
+#define stm_writel(stm, val, off) \
+ __raw_writel((val), stm.base + off)
+#define stm_readl(stm, val, off) \
+ __raw_readl(stm.base + off)
+
+#define NR_STM_CHANNEL (32)
+#define BYTES_PER_CHANNEL (256)
+
+enum {
+ STM_PKT_TYPE_DATA = 0x98,
+ STM_PKT_TYPE_FLAG = 0xE8,
+ STM_PKT_TYPE_TRIG = 0xF8,
+};
+
+enum {
+ STM_OPTION_MARKED = 0x10,
+};
+
+#define STM_TRACE_BUF_SIZE (1024)
+
+#define OST_START_TOKEN (0x30)
+#define OST_VERSION (0x1)
+
+#define stm_channel_addr(ch) \
+ (stm.chs.base + (ch * BYTES_PER_CHANNEL))
+#define stm_channel_off(type, opts) (type & ~opts)
+
+#define STM_LOCK() \
+do { \
+ mb(); \
+ stm_writel(stm, 0x0, CS_LAR); \
+} while (0)
+#define STM_UNLOCK() \
+do { \
+ stm_writel(stm, CS_UNLOCK_MAGIC, CS_LAR); \
+ mb(); \
+} while (0)
+
+#define STMSPER (0xE00)
+#define STMSPTER (0xE20)
+#define STMTCSR (0xE80)
+#define STMSYNCR (0xE90)
+
+#ifdef CONFIG_MSM_QDSS_STM_DEFAULT_ENABLE
+static int stm_boot_enable = 1;
+#else
+static int stm_boot_enable;
+#endif
+
+module_param_named(
+ stm_boot_enable, stm_boot_enable, int, S_IRUGO
+);
+
+static int stm_boot_nr_channel;
+
+module_param_named(
+ stm_boot_nr_channel, stm_boot_nr_channel, int, S_IRUGO
+);
+
+struct channel_space {
+ void __iomem *base;
+ unsigned long *bitmap;
+};
+
+struct stm_ctx {
+ void __iomem *base;
+ bool enabled;
+ struct qdss_source *src;
+ struct device *dev;
+ struct kobject *kobj;
+ uint32_t entity;
+ struct channel_space chs;
+};
+
+static struct stm_ctx stm = {
+ .entity = OST_ENTITY_ALL,
+};
+
+
+static void __stm_enable(void)
+{
+ STM_UNLOCK();
+
+ stm_writel(stm, 0x80, STMSYNCR);
+ stm_writel(stm, 0xFFFFFFFF, STMSPTER);
+ stm_writel(stm, 0xFFFFFFFF, STMSPER);
+ stm_writel(stm, 0x30003, STMTCSR);
+
+ STM_LOCK();
+}
+
+static int stm_enable(void)
+{
+ int ret;
+
+ if (stm.enabled) {
+ dev_err(stm.dev, "STM tracing already enabled\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = qdss_enable(stm.src);
+ if (ret)
+ goto err;
+
+ __stm_enable();
+
+ stm.enabled = true;
+
+ dev_info(stm.dev, "STM tracing enabled\n");
+ return 0;
+
+err:
+ return ret;
+}
+
+static void __stm_disable(void)
+{
+ STM_UNLOCK();
+
+ stm_writel(stm, 0x30000, STMTCSR);
+ stm_writel(stm, 0x0, STMSPER);
+ stm_writel(stm, 0x0, STMSPTER);
+
+ STM_LOCK();
+}
+
+static int stm_disable(void)
+{
+ int ret;
+
+ if (!stm.enabled) {
+ dev_err(stm.dev, "STM tracing already disabled\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ __stm_disable();
+
+ qdss_disable(stm.src);
+
+ stm.enabled = false;
+
+ dev_info(stm.dev, "STM tracing disabled\n");
+ return 0;
+
+err:
+ return ret;
+}
+
+static uint32_t stm_channel_alloc(uint32_t off)
+{
+ uint32_t ch;
+
+ do {
+ ch = find_next_zero_bit(stm.chs.bitmap, NR_STM_CHANNEL, off);
+ } while ((ch < NR_STM_CHANNEL) && test_and_set_bit(ch, stm.chs.bitmap));
+
+ return ch;
+}
+
+static void stm_channel_free(uint32_t ch)
+{
+ clear_bit(ch, stm.chs.bitmap);
+}
+
+static int stm_send(void *addr, const void *data, uint32_t size)
+{
+ uint64_t prepad = 0;
+ uint64_t postpad = 0;
+ char *pad;
+ uint8_t off, endoff;
+ uint32_t len = size;
+
+ /* only 64bit writes are supported, we rely on the compiler to
+ * generate STRD instruction for the casted 64bit assignments
+ */
+
+ off = (unsigned long)data & 0x7;
+
+ if (off) {
+ endoff = 8 - off;
+ pad = (char *)&prepad;
+ pad += off;
+
+ while (endoff && size) {
+ *pad++ = *(char *)data++;
+ endoff--;
+ size--;
+ }
+ *(volatile uint64_t __force *)addr = prepad;
+ }
+
+ /* now we are 64bit aligned */
+ while (size >= 8) {
+ *(volatile uint64_t __force *)addr = *(uint64_t *)data;
+ data += 8;
+ size -= 8;
+ }
+
+ if (size) {
+ pad = (char *)&postpad;
+
+ while (size) {
+ *pad++ = *(char *)data++;
+ size--;
+ }
+ *(volatile uint64_t __force *)addr = postpad;
+ }
+
+ return roundup(len + off, 8);
+}
+
+static int stm_trace_ost_header(unsigned long ch_addr, uint32_t options,
+ uint8_t entity_id, uint8_t proto_id,
+ const void *payload_data, uint32_t payload_size)
+{
+ void *addr;
+ uint8_t prepad_size;
+ uint64_t header;
+ char *hdr;
+
+ hdr = (char *)&header;
+
+ hdr[0] = OST_START_TOKEN;
+ hdr[1] = OST_VERSION;
+ hdr[2] = entity_id;
+ hdr[3] = proto_id;
+ prepad_size = (unsigned long)payload_data & 0x7;
+ *(uint32_t *)(hdr + 4) = (prepad_size << 24) | payload_size;
+
+ /* for 64bit writes, header is expected to be of the D32M, D32M */
+ options |= STM_OPTION_MARKED;
+ options &= ~STM_OPTION_TIMESTAMPED;
+ addr = (void *)(ch_addr | stm_channel_off(STM_PKT_TYPE_DATA, options));
+
+ return stm_send(addr, &header, sizeof(header));
+}
+
+static int stm_trace_data(unsigned long ch_addr, uint32_t options,
+ const void *data, uint32_t size)
+{
+ void *addr;
+
+ options &= ~STM_OPTION_TIMESTAMPED;
+ addr = (void *)(ch_addr | stm_channel_off(STM_PKT_TYPE_DATA, options));
+
+ return stm_send(addr, data, size);
+}
+
+static int stm_trace_ost_tail(unsigned long ch_addr, uint32_t options)
+{
+ void *addr;
+ uint64_t tail = 0x0;
+
+ addr = (void *)(ch_addr | stm_channel_off(STM_PKT_TYPE_FLAG, options));
+
+ return stm_send(addr, &tail, sizeof(tail));
+}
+
+static inline int __stm_trace(uint32_t options, uint8_t entity_id,
+ uint8_t proto_id, const void *data, uint32_t size)
+{
+ int len = 0;
+ uint32_t ch;
+ unsigned long ch_addr;
+
+ /* allocate channel and get the channel address */
+ ch = stm_channel_alloc(0);
+ ch_addr = (unsigned long)stm_channel_addr(ch);
+
+ /* send the ost header */
+ len += stm_trace_ost_header(ch_addr, options, entity_id, proto_id, data,
+ size);
+
+ /* send the payload data */
+ len += stm_trace_data(ch_addr, options, data, size);
+
+ /* send the ost tail */
+ len += stm_trace_ost_tail(ch_addr, options);
+
+ /* we are done, free the channel */
+ stm_channel_free(ch);
+
+ return len;
+}
+
+/**
+ * stm_trace - trace the binary or string data through STM
+ * @options: tracing options - guaranteed, timestamped, etc
+ * @entity_id: entity representing the trace data
+ * @proto_id: protocol id to distinguish between different binary formats
+ * @data: pointer to binary or string data buffer
+ * @size: size of data to send
+ *
+ * Packetizes the data as the payload to an OST packet and sends it over STM
+ *
+ * CONTEXT:
+ * Can be called from any context.
+ *
+ * RETURNS:
+ * number of bytes transfered over STM
+ */
+int stm_trace(uint32_t options, uint8_t entity_id, uint8_t proto_id,
+ const void *data, uint32_t size)
+{
+ /* we don't support sizes more than 24bits (0 to 23) */
+ if (!(stm.enabled && (stm.entity & entity_id) &&
+ (size < 0x1000000)))
+ return 0;
+
+ return __stm_trace(options, entity_id, proto_id, data, size);
+}
+EXPORT_SYMBOL(stm_trace);
+
+static ssize_t stm_write(struct file *file, const char __user *data,
+ size_t size, loff_t *ppos)
+{
+ char *buf;
+
+ if (!stm.enabled)
+ return -EINVAL;
+
+ if (!(stm.entity & OST_ENTITY_DEV_NODE))
+ return size;
+
+ if (size > STM_TRACE_BUF_SIZE)
+ size = STM_TRACE_BUF_SIZE;
+
+ buf = kmalloc(size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ if (copy_from_user(buf, data, size)) {
+ kfree(buf);
+ dev_dbg(stm.dev, "%s: copy_from_user failed\n", __func__);
+ return -EFAULT;
+ }
+
+ __stm_trace(STM_OPTION_TIMESTAMPED, OST_ENTITY_DEV_NODE, 0, buf, size);
+
+ kfree(buf);
+
+ return size;
+}
+
+static const struct file_operations stm_fops = {
+ .owner = THIS_MODULE,
+ .write = stm_write,
+ .llseek = no_llseek,
+};
+
+static struct miscdevice stm_misc = {
+ .name = "msm_stm",
+ .minor = MISC_DYNAMIC_MINOR,
+ .fops = &stm_fops,
+};
+
+#define STM_ATTR(__name) \
+static struct kobj_attribute __name##_attr = \
+ __ATTR(__name, S_IRUGO | S_IWUSR, __name##_show, __name##_store)
+
+static ssize_t enabled_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ int ret = 0;
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ if (val)
+ ret = stm_enable();
+ else
+ ret = stm_disable();
+
+ if (ret)
+ return ret;
+ return n;
+}
+static ssize_t enabled_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val = stm.enabled;
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+STM_ATTR(enabled);
+
+static ssize_t entity_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ stm.entity = val;
+ return n;
+}
+static ssize_t entity_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val = stm.entity;
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+STM_ATTR(entity);
+
+static int __devinit stm_sysfs_init(void)
+{
+ int ret;
+
+ stm.kobj = kobject_create_and_add("stm", qdss_get_modulekobj());
+ if (!stm.kobj) {
+ dev_err(stm.dev, "failed to create STM sysfs kobject\n");
+ ret = -ENOMEM;
+ goto err_create;
+ }
+
+ ret = sysfs_create_file(stm.kobj, &enabled_attr.attr);
+ if (ret) {
+ dev_err(stm.dev, "failed to create STM sysfs enabled attr\n");
+ goto err_file;
+ }
+
+ if (sysfs_create_file(stm.kobj, &entity_attr.attr))
+ dev_err(stm.dev, "failed to create STM sysfs entity attr\n");
+
+ return 0;
+err_file:
+ kobject_put(stm.kobj);
+err_create:
+ return ret;
+}
+
+static void __devexit stm_sysfs_exit(void)
+{
+ sysfs_remove_file(stm.kobj, &entity_attr.attr);
+ sysfs_remove_file(stm.kobj, &enabled_attr.attr);
+ kobject_put(stm.kobj);
+}
+
+static int __devinit stm_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *res;
+ size_t res_size, bitmap_size;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -EINVAL;
+ goto err_res0;
+ }
+
+ stm.base = ioremap_nocache(res->start, resource_size(res));
+ if (!stm.base) {
+ ret = -EINVAL;
+ goto err_ioremap0;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res) {
+ ret = -EINVAL;
+ goto err_res1;
+ }
+
+ if (stm_boot_nr_channel) {
+ res_size = min((resource_size_t)(stm_boot_nr_channel *
+ BYTES_PER_CHANNEL), resource_size(res));
+ bitmap_size = stm_boot_nr_channel * sizeof(long);
+ } else {
+ res_size = min((resource_size_t)(NR_STM_CHANNEL *
+ BYTES_PER_CHANNEL), resource_size(res));
+ bitmap_size = NR_STM_CHANNEL * sizeof(long);
+ }
+
+ stm.chs.bitmap = kzalloc(bitmap_size, GFP_KERNEL);
+ if (!stm.chs.bitmap) {
+ ret = -ENOMEM;
+ goto err_bitmap;
+ }
+
+ stm.chs.base = ioremap_nocache(res->start, res_size);
+ if (!stm.chs.base) {
+ ret = -EINVAL;
+ goto err_ioremap1;
+ }
+
+ stm.dev = &pdev->dev;
+
+ ret = misc_register(&stm_misc);
+ if (ret)
+ goto err_misc;
+
+ stm.src = qdss_get("msm_stm");
+ if (IS_ERR(stm.src)) {
+ ret = PTR_ERR(stm.src);
+ goto err_qdssget;
+ }
+
+ ret = stm_sysfs_init();
+ if (ret)
+ goto err_sysfs;
+
+ if (stm_boot_enable)
+ stm_enable();
+
+ dev_info(stm.dev, "STM initialized\n");
+ return 0;
+
+err_sysfs:
+ qdss_put(stm.src);
+err_qdssget:
+ misc_deregister(&stm_misc);
+err_misc:
+ iounmap(stm.chs.base);
+err_ioremap1:
+ kfree(stm.chs.bitmap);
+err_bitmap:
+err_res1:
+ iounmap(stm.base);
+err_ioremap0:
+err_res0:
+ dev_err(stm.dev, "STM init failed\n");
+ return ret;
+}
+
+static int __devexit stm_remove(struct platform_device *pdev)
+{
+ if (stm.enabled)
+ stm_disable();
+ stm_sysfs_exit();
+ qdss_put(stm.src);
+ misc_deregister(&stm_misc);
+ iounmap(stm.chs.base);
+ kfree(stm.chs.bitmap);
+ iounmap(stm.base);
+
+ return 0;
+}
+
+static struct of_device_id stm_match[] = {
+ {.compatible = "qcom,msm-stm"},
+ {}
+};
+
+static struct platform_driver stm_driver = {
+ .probe = stm_probe,
+ .remove = __devexit_p(stm_remove),
+ .driver = {
+ .name = "msm_stm",
+ .owner = THIS_MODULE,
+ .of_match_table = stm_match,
+ },
+};
+
+static int __init stm_init(void)
+{
+ return platform_driver_register(&stm_driver);
+}
+module_init(stm_init);
+
+static void __exit stm_exit(void)
+{
+ platform_driver_unregister(&stm_driver);
+}
+module_exit(stm_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("CoreSight System Trace Macrocell driver");
diff --git a/drivers/cs/qdss-tpiu.c b/drivers/cs/qdss-tpiu.c
new file mode 100644
index 0000000..23905f0
--- /dev/null
+++ b/drivers/cs/qdss-tpiu.c
@@ -0,0 +1,148 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/err.h>
+
+#include "qdss-priv.h"
+
+#define tpiu_writel(tpiu, val, off) __raw_writel((val), tpiu.base + off)
+#define tpiu_readl(tpiu, off) __raw_readl(tpiu.base + off)
+
+#define TPIU_SUPP_PORTSZ (0x000)
+#define TPIU_CURR_PORTSZ (0x004)
+#define TPIU_SUPP_TRIGMODES (0x100)
+#define TPIU_TRIG_CNTRVAL (0x104)
+#define TPIU_TRIG_MULT (0x108)
+#define TPIU_SUPP_TESTPATM (0x200)
+#define TPIU_CURR_TESTPATM (0x204)
+#define TPIU_TEST_PATREPCNTR (0x208)
+#define TPIU_FFSR (0x300)
+#define TPIU_FFCR (0x304)
+#define TPIU_FSYNC_CNTR (0x308)
+#define TPIU_EXTCTL_INPORT (0x400)
+#define TPIU_EXTCTL_OUTPORT (0x404)
+#define TPIU_ITTRFLINACK (0xEE4)
+#define TPIU_ITTRFLIN (0xEE8)
+#define TPIU_ITATBDATA0 (0xEEC)
+#define TPIU_ITATBCTR2 (0xEF0)
+#define TPIU_ITATBCTR1 (0xEF4)
+#define TPIU_ITATBCTR0 (0xEF8)
+
+
+#define TPIU_LOCK() \
+do { \
+ mb(); \
+ tpiu_writel(tpiu, 0x0, CS_LAR); \
+} while (0)
+#define TPIU_UNLOCK() \
+do { \
+ tpiu_writel(tpiu, CS_UNLOCK_MAGIC, CS_LAR); \
+ mb(); \
+} while (0)
+
+struct tpiu_ctx {
+ void __iomem *base;
+ bool enabled;
+ struct device *dev;
+};
+
+static struct tpiu_ctx tpiu;
+
+static void __tpiu_disable(void)
+{
+ TPIU_UNLOCK();
+
+ tpiu_writel(tpiu, 0x3000, TPIU_FFCR);
+ tpiu_writel(tpiu, 0x3040, TPIU_FFCR);
+
+ TPIU_LOCK();
+}
+
+void tpiu_disable(void)
+{
+ __tpiu_disable();
+ tpiu.enabled = false;
+ dev_info(tpiu.dev, "TPIU disabled\n");
+}
+
+static int __devinit tpiu_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -EINVAL;
+ goto err_res;
+ }
+
+ tpiu.base = ioremap_nocache(res->start, resource_size(res));
+ if (!tpiu.base) {
+ ret = -EINVAL;
+ goto err_ioremap;
+ }
+
+ tpiu.dev = &pdev->dev;
+
+ dev_info(tpiu.dev, "TPIU initialized\n");
+ return 0;
+
+err_ioremap:
+err_res:
+ dev_err(tpiu.dev, "TPIU init failed\n");
+ return ret;
+}
+
+static int __devexit tpiu_remove(struct platform_device *pdev)
+{
+ if (tpiu.enabled)
+ tpiu_disable();
+ iounmap(tpiu.base);
+
+ return 0;
+}
+
+static struct of_device_id tpiu_match[] = {
+ {.compatible = "qcom,msm-tpiu"},
+ {}
+};
+
+static struct platform_driver tpiu_driver = {
+ .probe = tpiu_probe,
+ .remove = __devexit_p(tpiu_remove),
+ .driver = {
+ .name = "msm_tpiu",
+ .owner = THIS_MODULE,
+ .of_match_table = tpiu_match,
+ },
+};
+
+static int __init tpiu_init(void)
+{
+ return platform_driver_register(&tpiu_driver);
+}
+module_init(tpiu_init);
+
+static void __exit tpiu_exit(void)
+{
+ platform_driver_unregister(&tpiu_driver);
+}
+module_exit(tpiu_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("CoreSight Trace Port Interface Unit driver");
diff --git a/drivers/cs/qdss.c b/drivers/cs/qdss.c
new file mode 100644
index 0000000..a7e8b85
--- /dev/null
+++ b/drivers/cs/qdss.c
@@ -0,0 +1,388 @@
+/* Copyright (c) 2012, Code Aurora Forum. 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/clk.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 afamily;
+ 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;
+ struct clk *clk;
+};
+
+static struct qdss_ctx qdss;
+
+/**
+ * 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)
+{
+ 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.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.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_disable_sink - force disable the current qdss sink(s)
+ *
+ * Force disable the current qdss sink(s) to stop the sink from accepting any
+ * trace generated subsequent to this call. This function should only be used
+ * as a way to stop the sink from getting polluted with trace data that is
+ * uninteresting after an event of interest has occured.
+ *
+ * CONTEXT:
+ * Can be called from atomic or non-atomic context.
+ */
+void qdss_disable_sink(void)
+{
+ if (qdss.afamily) {
+ etb_dump();
+ etb_disable();
+ }
+}
+EXPORT_SYMBOL(qdss_disable_sink);
+
+/**
+ * qdss_clk_enable - enable qdss clocks
+ *
+ * Enables qdss clocks
+ *
+ * CONTEXT:
+ * Might sleep. Should be called from a non-atomic context.
+ *
+ * RETURNS:
+ * 0 on success, non-zero on failure
+ */
+int qdss_clk_enable(void)
+{
+ return clk_prepare_enable(qdss.clk);
+}
+EXPORT_SYMBOL(qdss_clk_enable);
+
+/**
+ * qdss_clk_disable - disable qdss clocks
+ *
+ * Disables qdss clocks
+ *
+ * CONTEXT:
+ * Might sleep. Should be called from a non-atomic context.
+ */
+void qdss_clk_disable(void)
+{
+ clk_disable_unprepare(qdss.clk);
+}
+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)
+
+static ssize_t max_clk_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned long val;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ qdss.max_clk = val;
+ return n;
+}
+static ssize_t max_clk_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ unsigned long val = qdss.max_clk;
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+QDSS_ATTR(max_clk);
+
+static void __devinit 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;
+
+ qdss.modulekobj = kset_find_obj(module_kset, KBUILD_MODNAME);
+ if (!qdss.modulekobj) {
+ pr_err("failed to find QDSS sysfs module kobject\n");
+ ret = -ENOENT;
+ goto err;
+ }
+
+ ret = sysfs_create_file(qdss.modulekobj, &max_clk_attr.attr);
+ if (ret) {
+ pr_err("failed to create QDSS sysfs max_clk attribute\n");
+ goto err;
+ }
+
+ return 0;
+err:
+ return ret;
+}
+
+static void __devexit qdss_sysfs_exit(void)
+{
+ sysfs_remove_file(qdss.modulekobj, &max_clk_attr.attr);
+}
+
+static int __devinit qdss_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct msm_qdss_platform_data *pdata;
+
+ mutex_init(&qdss.sources_mutex);
+ mutex_init(&qdss.sink_mutex);
+
+ INIT_LIST_HEAD(&qdss.sources);
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata)
+ goto err_pdata;
+
+ qdss.clk = clk_get(&pdev->dev, "core_clk");
+ if (IS_ERR(qdss.clk)) {
+ ret = PTR_ERR(qdss.clk);
+ pr_info("clk get failed\n");
+ goto err_clk_get;
+ }
+
+ ret = clk_set_rate(qdss.clk, QDSS_CLK_ON_DBG);
+ if (ret) {
+ pr_info("clk rate failed\n");
+ goto err_clk_rate;
+ }
+
+ qdss.afamily = pdata->afamily;
+ qdss_add_sources(pdata->src_table, pdata->size);
+
+ pr_info("QDSS arch initialized\n");
+ return 0;
+err_clk_rate:
+ clk_put(qdss.clk);
+err_clk_get:
+err_pdata:
+ mutex_destroy(&qdss.sink_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();
+ clk_put(qdss.clk);
+ mutex_destroy(&qdss.sink_mutex);
+ mutex_destroy(&qdss.sources_mutex);
+
+ return 0;
+}
+
+static struct of_device_id qdss_match[] = {
+ {.compatible = "qcom,msm-qdss"},
+ {}
+};
+
+static struct platform_driver qdss_driver = {
+ .probe = qdss_probe,
+ .remove = __devexit_p(qdss_remove),
+ .driver = {
+ .name = "msm_qdss",
+ .owner = THIS_MODULE,
+ .of_match_table = qdss_match,
+ },
+};
+
+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 module initialized\n");
+ return 0;
+err_sysfs:
+ return ret;
+}
+module_init(qdss_module_init);
+
+static void __exit qdss_exit(void)
+{
+ platform_driver_unregister(&qdss_driver);
+}
+module_exit(qdss_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Qualcomm Debug SubSystem Driver");