PM / devfreq: Add MSM BIMC bwmon support for bw_hwmon governor

The BIMC bwmon device supports monitoring read/write traffic from each BIMC
master port. It also has the capability to raise an IRQ when the traffic
count exceeds a programmable threshold. This allows for it to be used with
the bw_hwmon governor to scale the BW requests from each BIMC master.

Change-Id: Ie8a1471226411e23954ed556292186a5a864ddc1
Signed-off-by: Saravana Kannan <skannan@codeaurora.org>
This commit is contained in:
Saravana Kannan 2014-05-28 18:59:54 -07:00 committed by Jonathan Avila
parent ccfe8ac53a
commit 805b890be1
4 changed files with 363 additions and 0 deletions

View File

@ -0,0 +1,25 @@
MSM BIMC bandwidth monitor device
bimc-bwmon is a device that represents the MSM BIMC bandwidth monitors that
can be used to measure the bandwidth of read/write traffic from the BIMC
master ports. For example, the CPU subsystem sits on one BIMC master port.
Required properties:
- compatible: Must be "qcom,bimc-bwmon"
- reg: Pairs of physical base addresses and region sizes of
memory mapped registers.
- reg-names: Names of the bases for the above registers. Expected
bases are: "base", "global_base"
- interrupts: Lists the threshold IRQ.
- qcom,mport: The hardware master port that this device can monitor
- qcom,target-dev: The DT device that corresponds to this master port
Example:
qcom,cpu-bwmon {
compatible = "qcom,bimc-bwmon";
reg = <0xfc388000 0x300>, <0xfc381000 0x200>;
reg-names = "base", "global_base";
interrupts = <0 183 1>;
qcom,mport = <0>;
qcom,target-dev = <&cpubw>;
};

View File

@ -73,6 +73,15 @@ config DEVFREQ_GOV_PASSIVE
through sysfs entries. The passive governor recommends that
devfreq device uses the OPP table to get the frequency/voltage.
config QCOM_BIMC_BWMON
tristate "QCOM BIMC Bandwidth monitor hardware"
depends on ARCH_QCOM
help
The BIMC Bandwidth monitor hardware allows for monitoring the
traffic coming from each master port connected to the BIMC. It also
has the capability to raise an IRQ when the count exceeds a
programmable limit.
config DEVFREQ_GOV_QCOM_BW_HWMON
tristate "HW monitor based governor for device BW"
depends on QCOM_BIMC_BWMON

View File

@ -6,6 +6,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_PASSIVE) += governor_passive.o
obj-$(CONFIG_QCOM_BIMC_BWMON) += bimc-bwmon.o
obj-$(CONFIG_DEVFREQ_GOV_QCOM_BW_HWMON) += governor_bw_hwmon.o
obj-$(CONFIG_DEVFREQ_GOV_QCOM_CACHE_HWMON) += governor_cache_hwmon.o

View File

@ -0,0 +1,328 @@
/*
* Copyright (c) 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) "bimc-bwmon: " 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"
#define GLB_INT_STATUS(m) ((m)->global_base + 0x100)
#define GLB_INT_CLR(m) ((m)->global_base + 0x108)
#define GLB_INT_EN(m) ((m)->global_base + 0x10C)
#define MON_INT_STATUS(m) ((m)->base + 0x100)
#define MON_INT_CLR(m) ((m)->base + 0x108)
#define MON_INT_EN(m) ((m)->base + 0x10C)
#define MON_EN(m) ((m)->base + 0x280)
#define MON_CLEAR(m) ((m)->base + 0x284)
#define MON_CNT(m) ((m)->base + 0x288)
#define MON_THRES(m) ((m)->base + 0x290)
#define MON_MASK(m) ((m)->base + 0x298)
#define MON_MATCH(m) ((m)->base + 0x29C)
struct bwmon {
void __iomem *base;
void __iomem *global_base;
unsigned int mport;
unsigned int irq;
struct device *dev;
struct bw_hwmon hw;
};
#define to_bwmon(ptr) container_of(ptr, struct bwmon, hw)
static DEFINE_SPINLOCK(glb_lock);
static void mon_enable(struct bwmon *m)
{
writel_relaxed(0x1, MON_EN(m));
}
static void mon_disable(struct bwmon *m)
{
writel_relaxed(0x0, MON_EN(m));
}
static void mon_clear(struct bwmon *m)
{
writel_relaxed(0x1, MON_CLEAR(m));
/*
* The counter clear and IRQ clear bits are not in the same 4KB
* region. So, we need to make sure the counter clear is completed
* before we try to clear the IRQ or do any other counter operations.
*/
mb();
}
static void mon_irq_enable(struct bwmon *m)
{
u32 val;
spin_lock(&glb_lock);
val = readl_relaxed(GLB_INT_EN(m));
val |= 1 << m->mport;
writel_relaxed(val, GLB_INT_EN(m));
spin_unlock(&glb_lock);
val = readl_relaxed(MON_INT_EN(m));
val |= 0x1;
writel_relaxed(val, MON_INT_EN(m));
}
static void mon_irq_disable(struct bwmon *m)
{
u32 val;
spin_lock(&glb_lock);
val = readl_relaxed(GLB_INT_EN(m));
val &= ~(1 << m->mport);
writel_relaxed(val, GLB_INT_EN(m));
spin_unlock(&glb_lock);
val = readl_relaxed(MON_INT_EN(m));
val &= ~0x1;
writel_relaxed(val, MON_INT_EN(m));
}
static int mon_irq_status(struct bwmon *m)
{
u32 mval;
mval = readl_relaxed(MON_INT_STATUS(m));
dev_dbg(m->dev, "IRQ status p:%x, g:%x\n", mval,
readl_relaxed(GLB_INT_STATUS(m)));
return mval & 0x1;
}
static void mon_irq_clear(struct bwmon *m)
{
writel_relaxed(0x1, MON_INT_CLR(m));
mb();
writel_relaxed(1 << m->mport, GLB_INT_CLR(m));
mb();
}
static void mon_set_limit(struct bwmon *m, u32 count)
{
writel_relaxed(count, MON_THRES(m));
dev_dbg(m->dev, "Thres: %08x\n", count);
}
static u32 mon_get_limit(struct bwmon *m)
{
return readl_relaxed(MON_THRES(m));
}
static unsigned long mon_get_count(struct bwmon *m)
{
unsigned long count;
count = readl_relaxed(MON_CNT(m));
dev_dbg(m->dev, "Counter: %08lx\n", count);
if (mon_irq_status(m))
count += mon_get_limit(m);
dev_dbg(m->dev, "Actual Count: %08lx\n", count);
return count;
}
/* ********** CPUBW specific code ********** */
/* Returns MBps of read/writes for the sampling window. */
static unsigned int bytes_to_mbps(long long bytes, unsigned int us)
{
bytes *= USEC_PER_SEC;
do_div(bytes, us);
bytes = DIV_ROUND_UP_ULL(bytes, SZ_1M);
return bytes;
}
static unsigned int mbps_to_bytes(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 *= SZ_1M;
return mbps;
}
static unsigned long meas_bw_and_set_irq(struct bw_hwmon *hw,
unsigned int tol, unsigned int us)
{
unsigned long mbps;
u32 limit;
unsigned int sample_ms = hw->df->profile->polling_ms;
struct bwmon *m = to_bwmon(hw);
mon_disable(m);
mbps = mon_get_count(m);
mbps = bytes_to_mbps(mbps, us);
/*
* The fudging of mbps when calculating limit is to workaround a HW
* design issue. Needs further tuning.
*/
limit = mbps_to_bytes(max(mbps, 400UL), sample_ms, tol);
mon_set_limit(m, limit);
mon_clear(m);
mon_irq_clear(m);
mon_enable(m);
dev_dbg(m->dev, "MBps = %lu\n", mbps);
return mbps;
}
static irqreturn_t bwmon_intr_handler(int irq, void *dev)
{
struct bwmon *m = dev;
if (mon_irq_status(m)) {
update_bw_hwmon(&m->hw);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int start_bw_hwmon(struct bw_hwmon *hw, unsigned long mbps)
{
struct bwmon *m = to_bwmon(hw);
u32 limit;
int ret;
ret = request_threaded_irq(m->irq, NULL, bwmon_intr_handler,
IRQF_ONESHOT | IRQF_SHARED,
dev_name(m->dev), m);
if (ret) {
dev_err(m->dev, "Unable to register interrupt handler!\n");
return ret;
}
mon_disable(m);
limit = mbps_to_bytes(mbps, hw->df->profile->polling_ms, 0);
mon_set_limit(m, limit);
mon_clear(m);
mon_irq_clear(m);
mon_irq_enable(m);
mon_enable(m);
return 0;
}
static void stop_bw_hwmon(struct bw_hwmon *hw)
{
struct bwmon *m = to_bwmon(hw);
disable_irq(m->irq);
free_irq(m->irq, m);
mon_disable(m);
mon_irq_disable(m);
mon_clear(m);
mon_irq_clear(m);
}
/*************************************************************************/
static int bimc_bwmon_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
struct bwmon *m;
int ret;
u32 data;
m = devm_kzalloc(dev, sizeof(*m), GFP_KERNEL);
if (!m)
return -ENOMEM;
m->dev = dev;
ret = of_property_read_u32(dev->of_node, "qcom,mport", &data);
if (ret) {
dev_err(dev, "mport not found!\n");
return ret;
}
m->mport = data;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "base");
if (!res) {
dev_err(dev, "base not found!\n");
return -EINVAL;
}
m->base = devm_ioremap(dev, res->start, resource_size(res));
if (!m->base) {
dev_err(dev, "Unable map base!\n");
return -ENOMEM;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "global_base");
if (!res) {
dev_err(dev, "global_base not found!\n");
return -EINVAL;
}
m->global_base = devm_ioremap(dev, res->start, resource_size(res));
if (!m->global_base) {
dev_err(dev, "Unable map global_base!\n");
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;
}
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 = &start_bw_hwmon;
m->hw.stop_hwmon = &stop_bw_hwmon;
m->hw.meas_bw_and_set_irq = &meas_bw_and_set_irq;
ret = register_bw_hwmon(dev, &m->hw);
if (ret) {
dev_err(dev, "Dev BW hwmon registration failed\n");
return ret;
}
return 0;
}
static const struct of_device_id bimc_bwmon_match_table[] = {
{ .compatible = "qcom,bimc-bwmon" },
{}
};
static struct platform_driver bimc_bwmon_driver = {
.probe = bimc_bwmon_driver_probe,
.driver = {
.name = "bimc-bwmon",
.of_match_table = bimc_bwmon_match_table,
},
};
module_platform_driver(bimc_bwmon_driver);
MODULE_DESCRIPTION("BIMC bandwidth monitor driver");
MODULE_LICENSE("GPL v2");