From a770f34aad82c5c2e01fcc0a57e5a1556107b3a5 Mon Sep 17 00:00:00 2001 From: Kumar Gala Date: Mon, 4 Aug 2014 13:41:06 -0500 Subject: 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 --- drivers/devfreq/Makefile | 1 + drivers/devfreq/krait-l2pm.c | 446 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 447 insertions(+) create mode 100644 drivers/devfreq/krait-l2pm.c (limited to 'drivers/devfreq') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "governor_bw_hwmon.h" +#include "governor_cache_hwmon.h" + +#include + +#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"); -- cgit v1.2.3