summaryrefslogtreecommitdiff
path: root/drivers/devfreq
diff options
context:
space:
mode:
authorKumar Gala <galak@codeaurora.org>2014-08-04 13:41:06 -0500
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-23 20:03:44 -0700
commita770f34aad82c5c2e01fcc0a57e5a1556107b3a5 (patch)
treecc05814f7c0bb8ac34cf7a398ea40dfaca984b48 /drivers/devfreq
parent413a95377802dcea598bbcdb22f06ed4f5c06996 (diff)
PM / devfreq: Add Krait L2 cache HW monitor
This is a snapshot of the Krait L2 cache HW monitor driver as of msm-3.10 commit: acdce027751d5a7488b283f0ce3111f873a5816d (Merge "defconfig: arm64: Enable ONESHOT_SYNC for msm8994") Signed-off-by: Kumar Gala <galak@codeaurora.org>
Diffstat (limited to 'drivers/devfreq')
-rw-r--r--drivers/devfreq/Makefile1
-rw-r--r--drivers/devfreq/krait-l2pm.c446
2 files changed, 447 insertions, 0 deletions
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
index 862083911ef2..00ea6770b693 100644
--- a/drivers/devfreq/Makefile
+++ b/drivers/devfreq/Makefile
@@ -5,6 +5,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o
obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o
obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o
obj-$(CONFIG_DEVFREQ_GOV_QCOM_ADRENO_TZ) += governor_msm_adreno_tz.o
+obj-$(CONFIG_ARCH_MSM_KRAIT) += krait-l2pm.o
obj-$(CONFIG_DEVFREQ_GOV_MSM_CACHE_HWMON) += governor_cache_hwmon.o
# DEVFREQ Drivers
diff --git a/drivers/devfreq/krait-l2pm.c b/drivers/devfreq/krait-l2pm.c
new file mode 100644
index 000000000000..3772786950b7
--- /dev/null
+++ b/drivers/devfreq/krait-l2pm.c
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2013-2014, The Linux Foundation. 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.
+ */
+
+#define pr_fmt(fmt) "krait-l2pm: " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/spinlock.h>
+#include "governor_bw_hwmon.h"
+#include "governor_cache_hwmon.h"
+
+#include <mach/msm-krait-l2-accessors.h>
+
+#define L2PMRESR(n) (0x410 + n)
+#define L2PMCR 0x400
+#define L2PMCNTENCLR 0x402
+#define L2PMCNTENSET 0x403
+#define L2PMINTENCLR 0x404
+#define L2PMINTENSET 0x405
+#define L2PMOVSR 0x406
+#define L2PMOVSSET 0x407
+#define L2PMCCNTR 0x409
+#define L2PMnEVCNTCR(n) (0x420 + n * 0x10)
+#define L2PMnEVCNTR(n) (0x421 + n * 0x10)
+#define L2PMnEVCNTSR(n) (0x422 + n * 0x10)
+#define L2PMnEVFILTER(n) (0x423 + n * 0x10)
+#define L2PMnEVTYPER(n) (0x424 + n * 0x10)
+
+static DEFINE_SPINLOCK(mon_lock);
+
+static void global_mon_enable(bool en)
+{
+ static unsigned int cnt;
+ u32 regval;
+
+ spin_lock(&mon_lock);
+ if (en) {
+ cnt++;
+ } else {
+ if (cnt)
+ cnt--;
+ }
+
+ /* Global counter enable */
+ regval = get_l2_indirect_reg(L2PMCR);
+ if (cnt)
+ regval |= BIT(0);
+ else
+ regval &= ~BIT(0);
+ set_l2_indirect_reg(L2PMCR, regval);
+ spin_unlock(&mon_lock);
+}
+
+static void mon_enable(int n)
+{
+ /* Clear previous overflow state for event counter n */
+ set_l2_indirect_reg(L2PMOVSR, BIT(n));
+
+ /* Enable event counter n */
+ set_l2_indirect_reg(L2PMCNTENSET, BIT(n));
+}
+
+static void mon_disable(int n)
+{
+ /* Disable event counter n */
+ set_l2_indirect_reg(L2PMCNTENCLR, BIT(n));
+}
+
+static void mon_irq_enable(int n, bool en)
+{
+ if (en)
+ set_l2_indirect_reg(L2PMINTENSET, BIT(n));
+ else
+ set_l2_indirect_reg(L2PMINTENCLR, BIT(n));
+}
+
+static int mon_overflow(int n)
+{
+ return get_l2_indirect_reg(L2PMOVSR) & BIT(n);
+}
+
+/* Returns start counter value to be used with mon_get_count() */
+static u32 mon_set_limit(int n, u32 count)
+{
+ u32 regval;
+
+ regval = 0xFFFFFFFF - count;
+ set_l2_indirect_reg(n == 31 ? L2PMCCNTR : L2PMnEVCNTR(n), regval);
+ pr_debug("EV%d start val: %x\n", n, regval);
+
+ return regval;
+}
+
+static long mon_get_count(int n, u32 start_val)
+{
+ u32 overflow, count;
+
+ count = get_l2_indirect_reg(n == 31 ? L2PMCCNTR : L2PMnEVCNTR(n));
+ overflow = get_l2_indirect_reg(L2PMOVSR);
+
+ pr_debug("EV%d ov: %x, cnt: %x\n", n, overflow, count);
+
+ if (overflow & BIT(n))
+ return 0xFFFFFFFF - start_val + count;
+ else
+ return count - start_val;
+}
+
+#define RD_MON 0
+#define WR_MON 1
+#define L2_H_REQ_MON 2
+#define L2_M_REQ_MON 3
+#define L2_CYC_MON 31
+
+/* ********** CPUBW specific code ********** */
+
+static u32 bytes_per_beat;
+static u32 prev_r_start_val;
+static u32 prev_w_start_val;
+static int bw_irq;
+
+static void mon_bw_init(void)
+{
+ /* Set up counters 0/1 to count write/read beats */
+ set_l2_indirect_reg(L2PMRESR(2), 0x8B0B0000);
+ set_l2_indirect_reg(L2PMnEVCNTCR(RD_MON), 0x0);
+ set_l2_indirect_reg(L2PMnEVCNTCR(WR_MON), 0x0);
+ set_l2_indirect_reg(L2PMnEVCNTR(RD_MON), 0xFFFFFFFF);
+ set_l2_indirect_reg(L2PMnEVCNTR(WR_MON), 0xFFFFFFFF);
+ set_l2_indirect_reg(L2PMnEVFILTER(RD_MON), 0xF003F);
+ set_l2_indirect_reg(L2PMnEVFILTER(WR_MON), 0xF003F);
+ set_l2_indirect_reg(L2PMnEVTYPER(RD_MON), 0xA);
+ set_l2_indirect_reg(L2PMnEVTYPER(WR_MON), 0xB);
+}
+
+/* Returns MBps of read/writes for the sampling window. */
+static unsigned int beats_to_mbps(long long beats, unsigned int us)
+{
+ beats *= USEC_PER_SEC;
+ beats *= bytes_per_beat;
+ do_div(beats, us);
+ beats = DIV_ROUND_UP_ULL(beats, SZ_1M);
+
+ return beats;
+}
+
+static unsigned int mbps_to_beats(unsigned long mbps, unsigned int ms,
+ unsigned int tolerance_percent)
+{
+ mbps *= (100 + tolerance_percent) * ms;
+ mbps /= 100;
+ mbps = DIV_ROUND_UP(mbps, MSEC_PER_SEC);
+ mbps = mult_frac(mbps, SZ_1M, bytes_per_beat);
+ return mbps;
+}
+
+static unsigned long meas_bw_and_set_irq(struct bw_hwmon *hw,
+ unsigned int tol, unsigned int us)
+{
+ unsigned long r_mbps, w_mbps;
+ u32 r_limit, w_limit;
+ unsigned int sample_ms = hw->df->profile->polling_ms;
+
+ mon_disable(RD_MON);
+ mon_disable(WR_MON);
+
+ r_mbps = mon_get_count(RD_MON, prev_r_start_val);
+ r_mbps = beats_to_mbps(r_mbps, us);
+ w_mbps = mon_get_count(WR_MON, prev_w_start_val);
+ w_mbps = beats_to_mbps(w_mbps, us);
+
+ r_limit = mbps_to_beats(r_mbps, sample_ms, tol);
+ w_limit = mbps_to_beats(w_mbps, sample_ms, tol);
+
+ prev_r_start_val = mon_set_limit(RD_MON, r_limit);
+ prev_w_start_val = mon_set_limit(WR_MON, w_limit);
+
+ mon_enable(RD_MON);
+ mon_enable(WR_MON);
+
+ pr_debug("R/W = %ld/%ld\n", r_mbps, w_mbps);
+
+ return r_mbps + w_mbps;
+}
+
+static irqreturn_t bwmon_intr_handler(int irq, void *dev)
+{
+ if (mon_overflow(RD_MON) || mon_overflow(WR_MON)) {
+ update_bw_hwmon(dev);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static int start_bw_hwmon(struct bw_hwmon *hw, unsigned long mbps)
+{
+ u32 limit;
+ int ret;
+
+ ret = request_threaded_irq(bw_irq, NULL, bwmon_intr_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ "bw_hwmon", hw);
+ if (ret) {
+ pr_err("Unable to register interrupt handler!\n");
+ return ret;
+ }
+
+ mon_bw_init();
+ mon_disable(RD_MON);
+ mon_disable(WR_MON);
+
+ limit = mbps_to_beats(mbps, hw->df->profile->polling_ms, 0);
+ limit /= 2;
+ prev_r_start_val = mon_set_limit(RD_MON, limit);
+ prev_w_start_val = mon_set_limit(WR_MON, limit);
+
+ mon_irq_enable(RD_MON, true);
+ mon_irq_enable(WR_MON, true);
+ mon_enable(RD_MON);
+ mon_enable(WR_MON);
+ global_mon_enable(true);
+
+ return 0;
+}
+
+static void stop_bw_hwmon(struct bw_hwmon *hw)
+{
+ disable_irq(bw_irq);
+ free_irq(bw_irq, hw);
+ global_mon_enable(false);
+ mon_disable(RD_MON);
+ mon_disable(WR_MON);
+ mon_irq_enable(RD_MON, false);
+ mon_irq_enable(WR_MON, false);
+}
+
+static struct devfreq_governor devfreq_gov_cpubw_hwmon = {
+ .name = "cpubw_hwmon",
+};
+
+static struct bw_hwmon cpubw_hwmon = {
+ .start_hwmon = &start_bw_hwmon,
+ .stop_hwmon = &stop_bw_hwmon,
+ .meas_bw_and_set_irq = &meas_bw_and_set_irq,
+ .gov = &devfreq_gov_cpubw_hwmon,
+};
+
+/* ********** Cache reqs specific code ********** */
+
+static u32 prev_req_start_val;
+
+static void mon_mrps_init(void)
+{
+ /* Cache bank requests */
+ set_l2_indirect_reg(L2PMRESR(0), 0x86000001);
+ set_l2_indirect_reg(L2PMnEVCNTCR(L2_H_REQ_MON), 0x0);
+ set_l2_indirect_reg(L2PMnEVCNTR(L2_H_REQ_MON), 0x0);
+ set_l2_indirect_reg(L2PMnEVFILTER(L2_H_REQ_MON), 0xF003F);
+ set_l2_indirect_reg(L2PMnEVTYPER(L2_H_REQ_MON), 0x0);
+ set_l2_indirect_reg(L2PMnEVCNTCR(L2_M_REQ_MON), 0x0);
+ set_l2_indirect_reg(L2PMnEVCNTR(L2_M_REQ_MON), 0x0);
+ set_l2_indirect_reg(L2PMnEVFILTER(L2_M_REQ_MON), 0xF003F);
+ set_l2_indirect_reg(L2PMnEVTYPER(L2_M_REQ_MON), 0x3);
+}
+
+/* Returns million requests/sec for the sampling window. */
+static int count_to_mrps(long long count, unsigned int us)
+{
+ do_div(count, us);
+ count++;
+ return count;
+}
+
+static unsigned int mrps_to_count(unsigned int mrps, unsigned int ms,
+ unsigned int tolerance)
+{
+ mrps += tolerance;
+ mrps *= ms * USEC_PER_MSEC;
+ return mrps;
+}
+
+static unsigned long meas_mrps_and_set_irq(struct devfreq *df,
+ unsigned int tol, unsigned int us,
+ struct mrps_stats *mrps)
+{
+ u32 limit;
+ unsigned int sample_ms = df->profile->polling_ms;
+ unsigned long f = df->previous_freq;
+ unsigned long t_mrps, m_mrps, l2_cyc;
+
+ mon_disable(L2_H_REQ_MON);
+ mon_disable(L2_M_REQ_MON);
+ mon_disable(L2_CYC_MON);
+
+ t_mrps = mon_get_count(L2_H_REQ_MON, prev_req_start_val);
+ t_mrps = count_to_mrps(t_mrps, us);
+ m_mrps = mon_get_count(L2_M_REQ_MON, 0);
+ m_mrps = count_to_mrps(m_mrps, us);
+
+ l2_cyc = mon_get_count(L2_CYC_MON, 0);
+
+ limit = mrps_to_count(t_mrps, sample_ms, tol);
+ prev_req_start_val = mon_set_limit(L2_H_REQ_MON, limit);
+ mon_set_limit(L2_M_REQ_MON, 0xFFFFFFFF);
+ mon_set_limit(L2_CYC_MON, 0xFFFFFFFF);
+
+ mon_enable(L2_H_REQ_MON);
+ mon_enable(L2_M_REQ_MON);
+ mon_enable(L2_CYC_MON);
+
+ mrps->high = t_mrps - m_mrps;
+ mrps->med = m_mrps;
+ mrps->low = 0;
+ mrps->busy_percent = mult_frac(l2_cyc, 1000, us) * 100 / f;
+
+ return 0;
+}
+
+static bool is_valid_mrps_irq(struct devfreq *df)
+{
+ return mon_overflow(L2_H_REQ_MON) || mon_overflow(L2_M_REQ_MON);
+}
+
+static int start_mrps_hwmon(struct devfreq *df, struct mrps_stats *mrps)
+{
+ u32 limit;
+
+ mon_mrps_init();
+ mon_disable(L2_H_REQ_MON);
+ mon_disable(L2_M_REQ_MON);
+ mon_disable(L2_CYC_MON);
+
+ limit = mrps_to_count(mrps->high, df->profile->polling_ms, 0);
+ prev_req_start_val = mon_set_limit(L2_H_REQ_MON, limit);
+ mon_set_limit(L2_M_REQ_MON, 0xFFFFFFFF);
+ mon_set_limit(L2_CYC_MON, 0xFFFFFFFF);
+
+ mon_irq_enable(L2_H_REQ_MON, true);
+ mon_irq_enable(L2_M_REQ_MON, true);
+ mon_enable(L2_H_REQ_MON);
+ mon_enable(L2_M_REQ_MON);
+ mon_enable(L2_CYC_MON);
+ global_mon_enable(true);
+
+ return 0;
+}
+
+static void stop_mrps_hwmon(struct devfreq *df)
+{
+ global_mon_enable(false);
+ mon_disable(L2_H_REQ_MON);
+ mon_disable(L2_M_REQ_MON);
+ mon_disable(L2_CYC_MON);
+ mon_irq_enable(L2_H_REQ_MON, false);
+ mon_irq_enable(L2_M_REQ_MON, false);
+}
+
+static struct cache_hwmon mrps_hwmon = {
+ .start_hwmon = &start_mrps_hwmon,
+ .stop_hwmon = &stop_mrps_hwmon,
+ .is_valid_irq = &is_valid_mrps_irq,
+ .meas_mrps_and_set_irq = &meas_mrps_and_set_irq,
+};
+
+/*************************************************************************/
+
+static int krait_l2pm_driver_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret, ret2;
+
+ bw_irq = platform_get_irq(pdev, 0);
+ if (bw_irq < 0) {
+ pr_err("Unable to get IRQ number\n");
+ return bw_irq;
+ }
+ mrps_hwmon.irq = bw_irq;
+
+ ret = of_property_read_u32(dev->of_node, "qcom,bytes-per-beat",
+ &bytes_per_beat);
+ if (ret) {
+ pr_err("Unable to read bytes per beat\n");
+ return ret;
+ }
+
+ ret = register_bw_hwmon(dev, &cpubw_hwmon);
+ if (ret)
+ pr_err("CPUBW hwmon registration failed\n");
+
+ ret2 = register_cache_hwmon(&mrps_hwmon);
+ if (ret2)
+ pr_err("Cache hwmon registration failed\n");
+
+ if (ret && ret2)
+ return ret2;
+
+ return 0;
+}
+
+static struct of_device_id match_table[] = {
+ { .compatible = "qcom,kraitbw-l2pm" },
+ {}
+};
+
+static struct platform_driver krait_l2pm_driver = {
+ .probe = krait_l2pm_driver_probe,
+ .driver = {
+ .name = "kraitbw-l2pm",
+ .of_match_table = match_table,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init krait_l2pm_init(void)
+{
+ return platform_driver_register(&krait_l2pm_driver);
+}
+module_init(krait_l2pm_init);
+
+static void __exit krait_l2pm_exit(void)
+{
+ platform_driver_unregister(&krait_l2pm_driver);
+}
+module_exit(krait_l2pm_exit);
+
+MODULE_DESCRIPTION("Krait L2 performance monitor driver");
+MODULE_LICENSE("GPL v2");