coresight: implement runtime pc save control
Add runtime control for enabling/disabling program counter save feature.
Enabling program counter save feature will enable pc to be saved on
reset but implies ETM being left powered on. So we provide user runtime
control to enable the feature when debugging or disable it while taking
power measurements.
Change-Id: Ib007da851ee7f3b0fac195da62aac7def68cc67a
Signed-off-by: Pratik Patel <pratikp@codeaurora.org>
diff --git a/drivers/coresight/Kconfig b/drivers/coresight/Kconfig
index a23fff0..ea4ad4f 100644
--- a/drivers/coresight/Kconfig
+++ b/drivers/coresight/Kconfig
@@ -31,6 +31,16 @@
For production builds, you should probably say 'N' here to avoid
potential power, performance and memory penalty.
+config MSM_QDSS_ETM_PCSAVE_DEFAULT_ENABLE
+ bool "Turn on PC saving by default"
+ depends on MSM_QDSS
+ help
+ Turns on program counter saving on reset by default. Otherwise,
+ PC saving is disabled by default but can be enabled via sysfs.
+
+ For production builds, you should probably say 'N' here to avoid
+ potential power penalty.
+
config CONTROL_TRACE
tristate "Turn on to control tracing"
help
diff --git a/drivers/coresight/coresight-etm.c b/drivers/coresight/coresight-etm.c
index bb24ff9..50bae55 100644
--- a/drivers/coresight/coresight-etm.c
+++ b/drivers/coresight/coresight-etm.c
@@ -177,6 +177,15 @@
boot_enable, boot_enable, int, S_IRUGO
);
+#ifdef CONFIG_MSM_QDSS_ETM_PCSAVE_DEFAULT_ENABLE
+static int boot_pcsave_enable = 1;
+#else
+static int boot_pcsave_enable;
+#endif
+module_param_named(
+ boot_pcsave_enable, boot_pcsave_enable, int, S_IRUGO
+);
+
struct etm_drvdata {
void __iomem *base;
struct device *dev;
@@ -194,6 +203,7 @@
uint8_t reset;
uint32_t mode;
uint32_t ctrl;
+ uint8_t ctrl_pwrdwn;
uint32_t trigger_event;
uint32_t startstop_ctrl;
uint32_t enable_event;
@@ -220,6 +230,9 @@
uint32_t ctxid_mask;
uint32_t sync_freq;
uint32_t timestamp_event;
+ uint8_t pdcr_pwrup;
+ bool pcsave_impl;
+ bool pcsave_enable;
};
static struct etm_drvdata *etm0drvdata;
@@ -326,6 +339,70 @@
etm_readl(drvdata, ETMSR));
}
+static void etm_save_pwrdwn(struct etm_drvdata *drvdata)
+{
+ drvdata->ctrl_pwrdwn = BVAL(etm_readl(drvdata, ETMCR), 0);
+}
+
+static void etm_restore_pwrdwn(struct etm_drvdata *drvdata)
+{
+ uint32_t etmcr;
+
+ etmcr = etm_readl(drvdata, ETMCR);
+ etmcr = (etmcr & ~BIT(0)) | drvdata->ctrl_pwrdwn;
+ etm_writel(drvdata, etmcr, ETMCR);
+}
+
+static void etm_save_pwrup(struct etm_drvdata *drvdata)
+{
+ drvdata->pdcr_pwrup = BVAL(etm_readl_mm(drvdata, ETMPDCR), 3);
+}
+
+static void etm_restore_pwrup(struct etm_drvdata *drvdata)
+{
+ uint32_t etmpdcr;
+
+ etmpdcr = etm_readl_mm(drvdata, ETMPDCR);
+ etmpdcr = (etmpdcr & ~BIT(3)) | (drvdata->pdcr_pwrup << 3);
+ etm_writel_mm(drvdata, etmpdcr, ETMPDCR);
+}
+
+static void etm_enable_pcsave(void *info)
+{
+ struct etm_drvdata *drvdata = info;
+
+ ETM_UNLOCK(drvdata);
+
+ etm_save_pwrup(drvdata);
+ /*
+ * ETMPDCR is only accessible via memory mapped interface and so use
+ * it first to enable power/clock to allow subsequent cp14 accesses.
+ */
+ etm_set_pwrup(drvdata);
+ etm_clr_pwrdwn(drvdata);
+ etm_restore_pwrup(drvdata);
+
+ ETM_LOCK(drvdata);
+}
+
+static void etm_disable_pcsave(void *info)
+{
+ struct etm_drvdata *drvdata = info;
+
+ ETM_UNLOCK(drvdata);
+
+ etm_save_pwrup(drvdata);
+ /*
+ * ETMPDCR is only accessible via memory mapped interface and so use
+ * it first to enable power/clock to allow subsequent cp14 accesses.
+ */
+ etm_set_pwrup(drvdata);
+ etm_set_pwrdwn(drvdata);
+ etm_restore_pwrup(drvdata);
+
+ ETM_LOCK(drvdata);
+}
+
static void __etm_enable(void *info)
{
int i;
@@ -333,8 +410,13 @@
struct etm_drvdata *drvdata = info;
ETM_UNLOCK(drvdata);
- /* Vote for ETM power/clock enable */
+ /*
+ * Vote for ETM power/clock enable. ETMPDCR is only accessible via
+ * memory mapped interface and so use it first to enable power/clock
+ * to allow subsequent cp14 accesses.
+ */
etm_set_pwrup(drvdata);
+ etm_save_pwrdwn(drvdata);
/*
* Clear power down bit since when this bit is set writes to
* certain registers might be ignored.
@@ -381,6 +463,7 @@
etm_writel(drvdata, 0x00000000, ETMVMIDCVR);
etm_clr_prog(drvdata);
+ etm_restore_pwrdwn(drvdata);
ETM_LOCK(drvdata);
dev_dbg(drvdata->dev, "cpu: %d enable smp call done\n", drvdata->cpu);
@@ -418,12 +501,18 @@
struct etm_drvdata *drvdata = info;
ETM_UNLOCK(drvdata);
+ etm_save_pwrdwn(drvdata);
+ /*
+ * Clear power down bit since when this bit is set writes to
+ * certain registers might be ignored.
+ */
+ etm_clr_pwrdwn(drvdata);
etm_set_prog(drvdata);
/* program trace enable to low by using always false event */
etm_writel(drvdata, 0x6F | BIT(14), ETMTEEVR);
- etm_set_pwrdwn(drvdata);
+ etm_restore_pwrdwn(drvdata);
/* Vote for ETM power/clock disable */
etm_clr_pwrup(drvdata);
ETM_LOCK(drvdata);
@@ -1380,6 +1469,7 @@
{
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
unsigned long val = drvdata->sync_freq;
+
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1425,6 +1515,60 @@
static DEVICE_ATTR(timestamp_event, S_IRUGO | S_IWUSR, etm_show_timestamp_event,
etm_store_timestamp_event);
+static ssize_t etm_show_pcsave(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ unsigned long val;
+
+ val = drvdata->pcsave_enable;
+ return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
+}
+
+static int __etm_store_pcsave(struct etm_drvdata *drvdata, unsigned long val)
+{
+ int ret;
+
+ ret = clk_prepare_enable(drvdata->clk);
+ if (ret)
+ return ret;
+
+ mutex_lock(&drvdata->mutex);
+ if (val) {
+ smp_call_function_single(drvdata->cpu, etm_enable_pcsave,
+ drvdata, 1);
+ drvdata->pcsave_enable = true;
+ } else {
+ smp_call_function_single(drvdata->cpu, etm_disable_pcsave,
+ drvdata, 1);
+ drvdata->pcsave_enable = false;
+ }
+ mutex_unlock(&drvdata->mutex);
+
+ clk_disable_unprepare(drvdata->clk);
+ return 0;
+}
+
+static ssize_t etm_store_pcsave(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ unsigned long val;
+ int ret;
+
+ if (sscanf(buf, "%lx", &val) != 1)
+ return -EINVAL;
+
+ ret = __etm_store_pcsave(drvdata, val);
+ if (ret)
+ return ret;
+
+ return size;
+}
+static DEVICE_ATTR(pcsave, S_IRUGO | S_IWUSR, etm_show_pcsave,
+ etm_store_pcsave);
+
static struct attribute *etm_attrs[] = {
&dev_attr_nr_addr_cmp.attr,
&dev_attr_nr_cntr.attr,
@@ -1505,7 +1649,11 @@
struct etm_drvdata *drvdata = info;
ETM_UNLOCK(drvdata);
- /* Vote for ETM power/clock enable */
+ /*
+ * Vote for ETM power/clock enable. ETMPDCR is only accessible via
+ * memory mapped interface and so use it first to enable power/clock
+ * to allow subsequent cp14 accesses.
+ */
etm_set_pwrup(drvdata);
/*
* Clear power down bit since when this bit is set writes to
@@ -1697,11 +1845,24 @@
goto err0;
}
+ if (pdev->dev.of_node)
+ drvdata->pcsave_impl = of_property_read_bool(pdev->dev.of_node,
+ "qcom,pc-save");
+ if (drvdata->pcsave_impl) {
+ ret = device_create_file(&drvdata->csdev->dev,
+ &dev_attr_pcsave);
+ if (ret)
+ dev_err(dev, "ETM pcsave dev node creation failed\n");
+ }
+
dev_info(dev, "ETM initialized\n");
if (boot_enable)
coresight_enable(drvdata->csdev);
+ if (drvdata->pcsave_impl && boot_pcsave_enable)
+ __etm_store_pcsave(drvdata, true);
+
return 0;
err1:
clk_disable_unprepare(drvdata->clk);
@@ -1715,6 +1876,7 @@
{
struct etm_drvdata *drvdata = platform_get_drvdata(pdev);
+ device_remove_file(&drvdata->csdev->dev, &dev_attr_pcsave);
coresight_unregister(drvdata->csdev);
wake_lock_destroy(&drvdata->wake_lock);
mutex_destroy(&drvdata->mutex);