PM / devfreq: Introduce M4M cache hwmon device
Introduce M4M cache hwmon device to scale M4M based on hardware counter values. Change-Id: I6a1582e1e66ff3051fcf7f917efb959fe7af96ae Signed-off-by: Junjie Wu <junjiew@codeaurora.org> [junjiew@codeaurora.org: Dropped changes in arch/arm64/Kconfig] Signed-off-by: Junjie Wu <junjiew@codeaurora.org>
This commit is contained in:
parent
1352d7b50d
commit
09229aeed6
4 changed files with 452 additions and 0 deletions
22
Documentation/devicetree/bindings/devfreq/m4m-hwmon.txt
Normal file
22
Documentation/devicetree/bindings/devfreq/m4m-hwmon.txt
Normal file
|
@ -0,0 +1,22 @@
|
|||
MSM M4M hardware monitor device
|
||||
|
||||
m4m-hwmon is a device that represents the MSM M4M hardware monitors
|
||||
that can be used to measure the various types of requests in the MSM M4M.
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be "qcom,m4m-hwmon"
|
||||
- reg: Pairs of physical base addresses and region sizes of
|
||||
memory mapped registers.
|
||||
- interrupts: Lists the threshold IRQ.
|
||||
- qcom,counter-event-sel: Array of counter and event selection values.
|
||||
- qcom,target-dev: The DT device that is monitored by this MSM M4M
|
||||
counter configuration.
|
||||
|
||||
Example:
|
||||
qcom,m4m-hwmon {
|
||||
compatible = "qcom,m4m-hwmon";
|
||||
reg = <0x6530000 0x160>;
|
||||
interrupts = <0 19 4>;
|
||||
qcom,counter-event-sel = <4 0x100>;
|
||||
qcom,target-dev = <&m4m_cache>;
|
||||
};
|
|
@ -107,6 +107,15 @@ config MSMCCI_HWMON
|
|||
registers to monitor cache and inform governor. It can also set an
|
||||
IRQ when count exceeds a programmable limit.
|
||||
|
||||
config MSM_M4M_HWMON
|
||||
tristate "MSM M4M cache monitor hardware"
|
||||
depends on ARCH_MSM
|
||||
help
|
||||
MSM M4M has counters that can be used to monitor requests coming to
|
||||
M4M. MSM M4M hardware monitor device programs corresponding registers
|
||||
to monitor cache and inform governor. It can also set an IRQ when
|
||||
count exceeds a programmable limit.
|
||||
|
||||
config DEVFREQ_GOV_MSM_BW_HWMON
|
||||
tristate "HW monitor based governor for device BW"
|
||||
depends on MSM_BIMC_BWMON
|
||||
|
|
|
@ -9,6 +9,7 @@ obj-$(CONFIG_DEVFREQ_GOV_CPUFREQ) += governor_cpufreq.o
|
|||
obj-$(CONFIG_MSM_BIMC_BWMON) += bimc-bwmon.o
|
||||
obj-$(CONFIG_ARMBW_HWMON) += armbw-pm.o
|
||||
obj-$(CONFIG_MSMCCI_HWMON) += msmcci-hwmon.o
|
||||
obj-$(CONFIG_MSM_M4M_HWMON) += m4m-hwmon.o
|
||||
obj-$(CONFIG_DEVFREQ_GOV_MSM_BW_HWMON) += governor_bw_hwmon.o
|
||||
obj-$(CONFIG_DEVFREQ_GOV_MSM_CACHE_HWMON) += governor_cache_hwmon.o
|
||||
obj-$(CONFIG_DEVFREQ_GOV_SPDM_HYP) += governor_spdm_bw_hyp.o
|
||||
|
|
420
drivers/devfreq/m4m-hwmon.c
Normal file
420
drivers/devfreq/m4m-hwmon.c
Normal file
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
* Copyright (c) 2014-2015, 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) "m4m-hwmon: " 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/of_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include "governor_cache_hwmon.h"
|
||||
|
||||
#define cntr_offset(idx) (sizeof(u32) * idx)
|
||||
|
||||
/* register offsets from base address */
|
||||
#define DCVS_VERSION(m) ((m)->base + 0x0)
|
||||
#define GLOBAL_CR_CTL(m) ((m)->base + 0x8)
|
||||
#define GLOBAL_CR_RESET(m) ((m)->base + 0xC)
|
||||
#define OVSTAT(m) ((m)->base + 0x30)
|
||||
#define OVCLR(m) ((m)->base + 0x34)
|
||||
#define OVSET(m) ((m)->base + 0x3C) /* unused */
|
||||
#define EVCNTR(m, x) ((m)->base + 0x40 + cntr_offset(x))
|
||||
#define CNTCTL(m, x) ((m)->base + 0x100 + cntr_offset(x))
|
||||
/* counter 0/1 does not have type control */
|
||||
#define EVTYPER_START 2
|
||||
#define EVTYPER(x) ((m)->base + 0x140 + cntr_offset(x))
|
||||
|
||||
/* bitmasks for GLOBAL_CR_CTL and CNTCTLx */
|
||||
#define CNT_EN BIT(0)
|
||||
#define IRQ_EN BIT(1)
|
||||
|
||||
/* non-configurable counters */
|
||||
#define CYC_CNTR_IDX 0
|
||||
#define WASTED_CYC_CNTR_IDX 1
|
||||
|
||||
struct m4m_counter {
|
||||
int idx;
|
||||
u32 event_mask;
|
||||
unsigned int last_start;
|
||||
};
|
||||
|
||||
struct m4m_hwmon {
|
||||
void __iomem *base;
|
||||
struct m4m_counter cntr[MAX_NUM_GROUPS];
|
||||
int num_cntr;
|
||||
int irq;
|
||||
struct cache_hwmon hw;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
#define to_mon(ptr) container_of(ptr, struct m4m_hwmon, hw)
|
||||
|
||||
static DEFINE_SPINLOCK(init_lock);
|
||||
|
||||
/* Should only be called once while HW is in POR state */
|
||||
static inline void mon_global_init(struct m4m_hwmon *m)
|
||||
{
|
||||
writel_relaxed(CNT_EN | IRQ_EN, GLOBAL_CR_CTL(m));
|
||||
}
|
||||
|
||||
static inline void _mon_disable_cntr_and_irq(struct m4m_hwmon *m, int cntr_idx)
|
||||
{
|
||||
writel_relaxed(0, CNTCTL(m, cntr_idx));
|
||||
}
|
||||
|
||||
static inline void _mon_enable_cntr_and_irq(struct m4m_hwmon *m, int cntr_idx)
|
||||
{
|
||||
writel_relaxed(CNT_EN | IRQ_EN, CNTCTL(m, cntr_idx));
|
||||
}
|
||||
|
||||
static void mon_disable(struct m4m_hwmon *m)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < m->num_cntr; i++)
|
||||
_mon_disable_cntr_and_irq(m, m->cntr[i].idx);
|
||||
/* make sure all counter/irq are indeed disabled */
|
||||
mb();
|
||||
}
|
||||
|
||||
static void mon_enable(struct m4m_hwmon *m)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < m->num_cntr; i++)
|
||||
_mon_enable_cntr_and_irq(m, m->cntr[i].idx);
|
||||
}
|
||||
|
||||
static inline void _mon_ov_clear(struct m4m_hwmon *m, int cntr_idx)
|
||||
{
|
||||
writel_relaxed(BIT(cntr_idx), OVCLR(m));
|
||||
}
|
||||
|
||||
static void mon_ov_clear(struct m4m_hwmon *m, enum request_group grp)
|
||||
{
|
||||
_mon_ov_clear(m, m->cntr[grp].idx);
|
||||
}
|
||||
|
||||
static inline u32 mon_irq_status(struct m4m_hwmon *m)
|
||||
{
|
||||
return readl_relaxed(OVSTAT(m));
|
||||
}
|
||||
|
||||
static bool mon_is_ovstat_set(struct m4m_hwmon *m)
|
||||
{
|
||||
int i;
|
||||
u32 status = mon_irq_status(m);
|
||||
|
||||
for (i = 0; i < m->num_cntr; i++)
|
||||
if (status & BIT(m->cntr[i].idx))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* counter must be stopped first */
|
||||
static unsigned long _mon_get_count(struct m4m_hwmon *m,
|
||||
int cntr_idx, unsigned int start)
|
||||
{
|
||||
unsigned long cnt;
|
||||
u32 cur_cnt = readl_relaxed(EVCNTR(m, cntr_idx));
|
||||
u32 ov = readl_relaxed(OVSTAT(m)) & BIT(cntr_idx);
|
||||
|
||||
if (!ov && cur_cnt < start) {
|
||||
dev_warn(m->dev, "Counter%d overflowed but not detected\n",
|
||||
cntr_idx);
|
||||
ov = 1;
|
||||
}
|
||||
|
||||
if (ov)
|
||||
cnt = U32_MAX - start + cur_cnt;
|
||||
else
|
||||
cnt = cur_cnt - start;
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static unsigned long mon_get_count(struct m4m_hwmon *m,
|
||||
enum request_group grp)
|
||||
{
|
||||
return _mon_get_count(m, m->cntr[grp].idx, m->cntr[grp].last_start);
|
||||
}
|
||||
|
||||
static inline void mon_set_limit(struct m4m_hwmon *m, enum request_group grp,
|
||||
unsigned int limit)
|
||||
{
|
||||
u32 start = U32_MAX - limit;
|
||||
|
||||
writel_relaxed(start, EVCNTR(m, m->cntr[grp].idx));
|
||||
m->cntr[grp].last_start = start;
|
||||
}
|
||||
|
||||
static inline void mon_enable_cycle_cntr(struct m4m_hwmon *m)
|
||||
{
|
||||
writel_relaxed(CNT_EN, CNTCTL(m, CYC_CNTR_IDX));
|
||||
}
|
||||
|
||||
static inline void mon_disable_cycle_cntr(struct m4m_hwmon *m)
|
||||
{
|
||||
_mon_disable_cntr_and_irq(m, CYC_CNTR_IDX);
|
||||
}
|
||||
|
||||
static inline unsigned long mon_get_cycle_count(struct m4m_hwmon *m)
|
||||
{
|
||||
return _mon_get_count(m, CYC_CNTR_IDX, 0);
|
||||
}
|
||||
|
||||
static inline void mon_clear_cycle_cntr(struct m4m_hwmon *m)
|
||||
{
|
||||
writel_relaxed(0, EVCNTR(m, CYC_CNTR_IDX));
|
||||
_mon_ov_clear(m, CYC_CNTR_IDX);
|
||||
}
|
||||
|
||||
static void mon_init(struct m4m_hwmon *m)
|
||||
{
|
||||
static bool mon_inited;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
spin_lock_irqsave(&init_lock, flags);
|
||||
if (!mon_inited)
|
||||
mon_global_init(m);
|
||||
spin_unlock_irqrestore(&init_lock, flags);
|
||||
|
||||
/* configure counter events */
|
||||
for (i = 0; i < m->num_cntr; i++)
|
||||
writel_relaxed(m->cntr[i].event_mask, EVTYPER(m->cntr[i].idx));
|
||||
}
|
||||
|
||||
static irqreturn_t m4m_hwmon_intr_handler(int irq, void *dev)
|
||||
{
|
||||
struct m4m_hwmon *m = dev;
|
||||
|
||||
if (mon_is_ovstat_set(m)) {
|
||||
update_cache_hwmon(&m->hw);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
static int count_to_mrps(unsigned 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 m4m_meas_mrps_and_set_irq(struct cache_hwmon *hw,
|
||||
unsigned int tol, unsigned int us, struct mrps_stats *mrps)
|
||||
{
|
||||
struct m4m_hwmon *m = to_mon(hw);
|
||||
unsigned long count, cyc_count;
|
||||
unsigned long f = hw->df->previous_freq;
|
||||
unsigned int sample_ms = hw->df->profile->polling_ms;
|
||||
int i;
|
||||
u32 limit;
|
||||
|
||||
mon_disable(m);
|
||||
mon_disable_cycle_cntr(m);
|
||||
|
||||
/* calculate mrps and set limit */
|
||||
for (i = 0; i < m->num_cntr; i++) {
|
||||
count = mon_get_count(m, i);
|
||||
mrps->mrps[i] = count_to_mrps(count, us);
|
||||
limit = mrps_to_count(mrps->mrps[i], sample_ms, tol);
|
||||
mon_ov_clear(m, i);
|
||||
mon_set_limit(m, i, limit);
|
||||
dev_dbg(m->dev, "Counter[%d] count 0x%lx, limit 0x%x\n",
|
||||
m->cntr[i].idx, count, limit);
|
||||
}
|
||||
|
||||
/* get cycle count and calculate busy percent */
|
||||
cyc_count = mon_get_cycle_count(m);
|
||||
mrps->busy_percent = mult_frac(cyc_count, 1000, us) * 100 / f;
|
||||
mon_clear_cycle_cntr(m);
|
||||
dev_dbg(m->dev, "Cycle count 0x%lx\n", cyc_count);
|
||||
|
||||
/* re-enable monitor */
|
||||
mon_enable(m);
|
||||
mon_enable_cycle_cntr(m);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int m4m_start_hwmon(struct cache_hwmon *hw, struct mrps_stats *mrps)
|
||||
{
|
||||
struct m4m_hwmon *m = to_mon(hw);
|
||||
unsigned int sample_ms = hw->df->profile->polling_ms;
|
||||
int ret, i;
|
||||
u32 limit;
|
||||
|
||||
ret = request_threaded_irq(m->irq, NULL, m4m_hwmon_intr_handler,
|
||||
IRQF_ONESHOT | IRQF_SHARED,
|
||||
dev_name(m->dev), m);
|
||||
if (ret) {
|
||||
dev_err(m->dev, "Unable to register for irq\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
mon_init(m);
|
||||
mon_disable(m);
|
||||
for (i = 0; i < m->num_cntr; i++) {
|
||||
mon_ov_clear(m, i);
|
||||
limit = mrps_to_count(mrps->mrps[i], sample_ms, 0);
|
||||
mon_set_limit(m, i, limit);
|
||||
}
|
||||
mon_clear_cycle_cntr(m);
|
||||
mon_enable(m);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void m4m_stop_hwmon(struct cache_hwmon *hw)
|
||||
{
|
||||
struct m4m_hwmon *m = to_mon(hw);
|
||||
int i;
|
||||
|
||||
mon_disable(m);
|
||||
free_irq(m->irq, m);
|
||||
for (i = 0; i < m->num_cntr; i++)
|
||||
mon_ov_clear(m, i);
|
||||
}
|
||||
|
||||
/* device probe functions */
|
||||
static struct of_device_id match_table[] = {
|
||||
{ .compatible = "qcom,m4m-hwmon" },
|
||||
{}
|
||||
};
|
||||
|
||||
static int m4m_hwmon_parse_cntr(struct device *dev,
|
||||
struct m4m_hwmon *m)
|
||||
{
|
||||
u32 *data;
|
||||
const char *prop_name = "qcom,counter-event-sel";
|
||||
int ret, len, i;
|
||||
|
||||
if (!of_find_property(dev->of_node, prop_name, &len))
|
||||
return -EINVAL;
|
||||
len /= sizeof(*data);
|
||||
|
||||
if (len % 2 || len > MAX_NUM_GROUPS * 2)
|
||||
return -EINVAL;
|
||||
|
||||
data = devm_kcalloc(dev, len, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
ret = of_property_read_u32_array(dev->of_node, prop_name, data, len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
len /= 2;
|
||||
m->num_cntr = len;
|
||||
for (i = 0; i < len; i++) {
|
||||
/* disallow non-configurable counters */
|
||||
if (data[i * 2] < EVTYPER_START)
|
||||
return -EINVAL;
|
||||
m->cntr[i].idx = data[i * 2];
|
||||
m->cntr[i].event_mask = data[i * 2 + 1];
|
||||
}
|
||||
|
||||
devm_kfree(dev, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int m4m_hwmon_driver_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res;
|
||||
struct m4m_hwmon *m;
|
||||
int ret;
|
||||
|
||||
m = devm_kzalloc(dev, sizeof(*m), GFP_KERNEL);
|
||||
if (!m)
|
||||
return -ENOMEM;
|
||||
m->dev = dev;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(dev, "base not found!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
m->base = devm_ioremap(dev, res->start, resource_size(res));
|
||||
if (!m->base)
|
||||
return -ENOMEM;
|
||||
|
||||
m->irq = platform_get_irq(pdev, 0);
|
||||
if (m->irq < 0) {
|
||||
dev_err(dev, "Unable to get IRQ number\n");
|
||||
return m->irq;
|
||||
}
|
||||
|
||||
ret = m4m_hwmon_parse_cntr(dev, m);
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to parse counter events\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
m->hw.of_node = of_parse_phandle(dev->of_node, "qcom,target-dev", 0);
|
||||
if (!m->hw.of_node)
|
||||
return -EINVAL;
|
||||
m->hw.start_hwmon = &m4m_start_hwmon;
|
||||
m->hw.stop_hwmon = &m4m_stop_hwmon;
|
||||
m->hw.meas_mrps_and_set_irq = &m4m_meas_mrps_and_set_irq;
|
||||
|
||||
ret = register_cache_hwmon(dev, &m->hw);
|
||||
if (ret) {
|
||||
dev_err(dev, "Dev BW hwmon registration failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver m4m_hwmon_driver = {
|
||||
.probe = m4m_hwmon_driver_probe,
|
||||
.driver = {
|
||||
.name = "m4m-hwmon",
|
||||
.of_match_table = match_table,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init m4m_hwmon_init(void)
|
||||
{
|
||||
return platform_driver_register(&m4m_hwmon_driver);
|
||||
}
|
||||
module_init(m4m_hwmon_init);
|
||||
|
||||
static void __exit m4m_hwmon_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&m4m_hwmon_driver);
|
||||
}
|
||||
module_exit(m4m_hwmon_exit);
|
||||
|
||||
MODULE_DESCRIPTION("M4M hardware monitor driver");
|
||||
MODULE_LICENSE("GPL v2");
|
Loading…
Add table
Reference in a new issue