Merge "drivers: soc: llcc_perfmon: Periodic counter dump support"
diff --git a/drivers/soc/qcom/llcc_perfmon.c b/drivers/soc/qcom/llcc_perfmon.c
index 567ee35..39276a9 100644
--- a/drivers/soc/qcom/llcc_perfmon.c
+++ b/drivers/soc/qcom/llcc_perfmon.c
@@ -21,6 +21,7 @@
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/err.h>
+#include <linux/hrtimer.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <linux/soc/qcom/llcc-qcom.h>
@@ -39,10 +40,12 @@
* struct llcc_perfmon_counter_map - llcc perfmon counter map info
* @port_sel: Port selected for configured counter
* @event_sel: Event selected for configured counter
+ * @counter_dump: Cumulative counter dump
*/
struct llcc_perfmon_counter_map {
unsigned int port_sel;
unsigned int event_sel;
+ unsigned long long counter_dump;
};
struct llcc_perfmon_private;
@@ -74,6 +77,8 @@
* @filtered_ports: Port filter enabled
* @port_configd: Number of perfmon port configuration supported
* @mutex: mutex to protect this structure
+ * @hrtimer: hrtimer instance for timer functionality
+ * @expires: timer expire time in nano seconds
*/
struct llcc_perfmon_private {
struct regmap *llcc_map;
@@ -87,6 +92,8 @@
unsigned int filtered_ports;
unsigned int port_configd;
struct mutex mutex;
+ struct hrtimer hrtimer;
+ ktime_t expires;
};
static inline void llcc_bcast_write(struct llcc_perfmon_private *llcc_priv,
@@ -114,6 +121,34 @@
llcc_bcast_write(llcc_priv, offset, readval);
}
+static void perfmon_counter_dump(struct llcc_perfmon_private *llcc_priv)
+{
+ uint32_t val;
+ unsigned int i, j;
+ unsigned long long total;
+
+ llcc_bcast_write(llcc_priv, PERFMON_DUMP, MONITOR_DUMP);
+ for (i = 0; i < llcc_priv->configured_counters - 1; i++) {
+ total = 0;
+ for (j = 0; j < llcc_priv->num_banks; j++) {
+ regmap_read(llcc_priv->llcc_map, llcc_priv->bank_off[j]
+ + LLCC_COUNTER_n_VALUE(i), &val);
+ total += val;
+ }
+
+ llcc_priv->configured[i].counter_dump += total;
+ }
+
+ total = 0;
+ for (j = 0; j < llcc_priv->num_banks; j++) {
+ regmap_read(llcc_priv->llcc_map, llcc_priv->bank_off[j] +
+ LLCC_COUNTER_n_VALUE(i), &val);
+ total += val;
+ }
+
+ llcc_priv->configured[i].counter_dump += total;
+}
+
static ssize_t perfmon_counter_dump_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -128,21 +163,73 @@
return cnt;
}
- llcc_bcast_write(llcc_priv, PERFMON_DUMP, MONITOR_DUMP);
+ if (llcc_priv->expires.tv64) {
+ perfmon_counter_dump(llcc_priv);
+ for (i = 0; i < llcc_priv->configured_counters - 1; i++) {
+ print = snprintf(buf, MAX_STRING_SIZE, "Port %02d,",
+ llcc_priv->configured[i].port_sel);
+ buf += print;
+ cnt += print;
+ print = snprintf(buf, MAX_STRING_SIZE, "Event %02d,",
+ llcc_priv->configured[i].event_sel);
+ buf += print;
+ cnt += print;
- for (i = 0; i < llcc_priv->configured_counters - 1; i++) {
- print = snprintf(buf, MAX_STRING_SIZE, "Port %02d,",
- llcc_priv->configured[i].port_sel);
+ print = snprintf(buf, MAX_STRING_SIZE, "0x%016llx\n",
+ llcc_priv->configured[i].counter_dump);
+ buf += print;
+ cnt += print;
+ llcc_priv->configured[i].counter_dump = 0;
+ }
+
+ print = snprintf(buf, MAX_STRING_SIZE, "CYCLE COUNT, ,");
buf += print;
cnt += print;
- print = snprintf(buf, MAX_STRING_SIZE, "Event %02d,",
- llcc_priv->configured[i].event_sel);
+ print = snprintf(buf, MAX_STRING_SIZE, "0x%016llx\n",
+ llcc_priv->configured[i].counter_dump);
+ buf += print;
+ cnt += print;
+ llcc_priv->configured[i].counter_dump = 0;
+ hrtimer_forward_now(&llcc_priv->hrtimer, llcc_priv->expires);
+ } else {
+ llcc_bcast_write(llcc_priv, PERFMON_DUMP, MONITOR_DUMP);
+
+ for (i = 0; i < llcc_priv->configured_counters - 1; i++) {
+ print = snprintf(buf, MAX_STRING_SIZE, "Port %02d,",
+ llcc_priv->configured[i].port_sel);
+ buf += print;
+ cnt += print;
+ print = snprintf(buf, MAX_STRING_SIZE, "Event %02d,",
+ llcc_priv->configured[i].event_sel);
+ buf += print;
+ cnt += print;
+ total = 0;
+ for (j = 0; j < llcc_priv->num_banks; j++) {
+ regmap_read(llcc_priv->llcc_map,
+ llcc_priv->bank_off[j]
+ + LLCC_COUNTER_n_VALUE(i),
+ &val);
+ print = snprintf(buf, MAX_STRING_SIZE,
+ "0x%08x,", val);
+ buf += print;
+ cnt += print;
+ total += val;
+ }
+
+ print = snprintf(buf, MAX_STRING_SIZE, "0x%09llx\n",
+ total);
+ buf += print;
+ cnt += print;
+ }
+
+ print = snprintf(buf, MAX_STRING_SIZE, "CYCLE COUNT, ,");
buf += print;
cnt += print;
total = 0;
for (j = 0; j < llcc_priv->num_banks; j++) {
- regmap_read(llcc_priv->llcc_map, llcc_priv->bank_off[j]
- + LLCC_COUNTER_n_VALUE(i), &val);
+ regmap_read(llcc_priv->llcc_map,
+ llcc_priv->bank_off[j] +
+ LLCC_COUNTER_n_VALUE(i), &val);
print = snprintf(buf, MAX_STRING_SIZE, "0x%08x,", val);
buf += print;
cnt += print;
@@ -154,22 +241,6 @@
cnt += print;
}
- print = snprintf(buf, MAX_STRING_SIZE, "CYCLE COUNT, ,");
- buf += print;
- cnt += print;
- total = 0;
- for (j = 0; j < llcc_priv->num_banks; j++) {
- regmap_read(llcc_priv->llcc_map, llcc_priv->bank_off[j] +
- LLCC_COUNTER_n_VALUE(i), &val);
- print = snprintf(buf, MAX_STRING_SIZE, "0x%08x,", val);
- buf += print;
- cnt += print;
- total += val;
- }
-
- print = snprintf(buf, MAX_STRING_SIZE, "0x%09llx\n", total);
- buf += print;
- cnt += print;
return cnt;
}
@@ -459,13 +530,54 @@
pr_err("start failed. perfmon not configured\n");
val = MANUAL_MODE | MONITOR_EN;
+ if (llcc_priv->expires.tv64) {
+ if (hrtimer_is_queued(&llcc_priv->hrtimer))
+ hrtimer_forward_now(&llcc_priv->hrtimer,
+ llcc_priv->expires);
+ else
+ hrtimer_start(&llcc_priv->hrtimer,
+ llcc_priv->expires,
+ HRTIMER_MODE_REL_PINNED);
+ }
+
} else {
+ if (llcc_priv->expires.tv64)
+ hrtimer_cancel(&llcc_priv->hrtimer);
+
if (!llcc_priv->configured_counters)
pr_err("stop failed. perfmon not configured\n");
}
mask = PERFMON_MODE_MONITOR_MODE_MASK | PERFMON_MODE_MONITOR_EN_MASK;
llcc_bcast_modify(llcc_priv, PERFMON_MODE, val, mask);
+
+
+ mutex_unlock(&llcc_priv->mutex);
+ return count;
+}
+
+static ssize_t perfmon_ns_periodic_dump_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct llcc_perfmon_private *llcc_priv = dev_get_drvdata(dev);
+
+ if (kstrtos64(buf, 10, &llcc_priv->expires.tv64))
+ return -EINVAL;
+
+ mutex_lock(&llcc_priv->mutex);
+ if (!llcc_priv->expires.tv64) {
+ hrtimer_cancel(&llcc_priv->hrtimer);
+ mutex_unlock(&llcc_priv->mutex);
+ return count;
+ }
+
+ if (hrtimer_is_queued(&llcc_priv->hrtimer))
+ hrtimer_forward_now(&llcc_priv->hrtimer, llcc_priv->expires);
+ else
+ hrtimer_start(&llcc_priv->hrtimer, llcc_priv->expires,
+ HRTIMER_MODE_REL_PINNED);
+
mutex_unlock(&llcc_priv->mutex);
return count;
}
@@ -508,6 +620,7 @@
static DEVICE_ATTR_WO(perfmon_filter_remove);
static DEVICE_ATTR_WO(perfmon_start);
static DEVICE_ATTR_RO(perfmon_scid_status);
+static DEVICE_ATTR_WO(perfmon_ns_periodic_dump);
static struct attribute *llcc_perfmon_attrs[] = {
&dev_attr_perfmon_counter_dump.attr,
@@ -517,6 +630,7 @@
&dev_attr_perfmon_filter_remove.attr,
&dev_attr_perfmon_start.attr,
&dev_attr_perfmon_scid_status.attr,
+ &dev_attr_perfmon_ns_periodic_dump.attr,
NULL,
};
@@ -989,6 +1103,16 @@
llcc_priv->port_ops[event_port_num] = ops;
}
+static enum hrtimer_restart llcc_perfmon_timer_handler(struct hrtimer *hrtimer)
+{
+ struct llcc_perfmon_private *llcc_priv = container_of(hrtimer,
+ struct llcc_perfmon_private, hrtimer);
+
+ perfmon_counter_dump(llcc_priv);
+ hrtimer_forward_now(&llcc_priv->hrtimer, llcc_priv->expires);
+ return HRTIMER_RESTART;
+}
+
static int llcc_perfmon_probe(struct platform_device *pdev)
{
int result = 0;
@@ -1038,6 +1162,9 @@
llcc_register_event_port(llcc_priv, &trp_port_ops, EVENT_PORT_TRP);
llcc_register_event_port(llcc_priv, &drp_port_ops, EVENT_PORT_DRP);
llcc_register_event_port(llcc_priv, &pmgr_port_ops, EVENT_PORT_PMGR);
+ hrtimer_init(&llcc_priv->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ llcc_priv->hrtimer.function = llcc_perfmon_timer_handler;
+ llcc_priv->expires.tv64 = 0;
return 0;
}
@@ -1045,6 +1172,9 @@
{
struct llcc_perfmon_private *llcc_priv = platform_get_drvdata(pdev);
+ while (hrtimer_active(&llcc_priv->hrtimer))
+ hrtimer_cancel(&llcc_priv->hrtimer);
+
mutex_destroy(&llcc_priv->mutex);
sysfs_remove_group(&pdev->dev.kobj, &llcc_perfmon_group);
platform_set_drvdata(pdev, NULL);