blob: c579d0a589f708cb161ca6a98cca1d22e0bea55a [file] [log] [blame]
/*
*
* (C) COPYRIGHT 2015-2016 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU licence.
*
* A copy of the licence is included with the program, and can also be obtained
* from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include <linux/of.h>
#include <linux/sysfs.h>
#include <mali_kbase.h>
#define NR_IPA_GROUPS 8
struct kbase_ipa_context;
/**
* struct ipa_group - represents a single IPA group
* @name: name of the IPA group
* @capacitance: capacitance constant for IPA group
* @calc_power: function to calculate power for IPA group
*/
struct ipa_group {
const char *name;
u32 capacitance;
u32 (*calc_power)(struct kbase_ipa_context *,
struct ipa_group *);
};
#include <mali_kbase_ipa_tables.h>
/**
* struct kbase_ipa_context - IPA context per device
* @kbdev: pointer to kbase device
* @groups: array of IPA groups for this context
* @vinstr_cli: vinstr client handle
* @vinstr_buffer: buffer to dump hardware counters onto
* @ipa_lock: protects the entire IPA context
*/
struct kbase_ipa_context {
struct kbase_device *kbdev;
struct ipa_group groups[NR_IPA_GROUPS];
struct kbase_vinstr_client *vinstr_cli;
void *vinstr_buffer;
struct mutex ipa_lock;
};
static ssize_t show_ipa_group(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct kbase_device *kbdev = dev_get_drvdata(dev);
struct kbase_ipa_context *ctx = kbdev->ipa_ctx;
ssize_t count = -EINVAL;
size_t i;
mutex_lock(&ctx->ipa_lock);
for (i = 0; i < ARRAY_SIZE(ctx->groups); i++) {
if (!strcmp(ctx->groups[i].name, attr->attr.name)) {
count = snprintf(buf, PAGE_SIZE, "%lu\n",
(unsigned long)ctx->groups[i].capacitance);
break;
}
}
mutex_unlock(&ctx->ipa_lock);
return count;
}
static ssize_t set_ipa_group(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct kbase_device *kbdev = dev_get_drvdata(dev);
struct kbase_ipa_context *ctx = kbdev->ipa_ctx;
unsigned long capacitance;
size_t i;
int err;
err = kstrtoul(buf, 0, &capacitance);
if (err < 0)
return err;
if (capacitance > U32_MAX)
return -ERANGE;
mutex_lock(&ctx->ipa_lock);
for (i = 0; i < ARRAY_SIZE(ctx->groups); i++) {
if (!strcmp(ctx->groups[i].name, attr->attr.name)) {
ctx->groups[i].capacitance = capacitance;
mutex_unlock(&ctx->ipa_lock);
return count;
}
}
mutex_unlock(&ctx->ipa_lock);
return -EINVAL;
}
static DEVICE_ATTR(group0, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group);
static DEVICE_ATTR(group1, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group);
static DEVICE_ATTR(group2, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group);
static DEVICE_ATTR(group3, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group);
static DEVICE_ATTR(group4, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group);
static DEVICE_ATTR(group5, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group);
static DEVICE_ATTR(group6, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group);
static DEVICE_ATTR(group7, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group);
static struct attribute *kbase_ipa_attrs[] = {
&dev_attr_group0.attr,
&dev_attr_group1.attr,
&dev_attr_group2.attr,
&dev_attr_group3.attr,
&dev_attr_group4.attr,
&dev_attr_group5.attr,
&dev_attr_group6.attr,
&dev_attr_group7.attr,
NULL,
};
static struct attribute_group kbase_ipa_attr_group = {
.name = "ipa",
.attrs = kbase_ipa_attrs,
};
static void init_ipa_groups(struct kbase_ipa_context *ctx)
{
memcpy(ctx->groups, ipa_groups_def, sizeof(ctx->groups));
}
#if defined(CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0))
static int update_ipa_groups_from_dt(struct kbase_ipa_context *ctx)
{
struct kbase_device *kbdev = ctx->kbdev;
struct device_node *np, *child;
struct ipa_group *group;
size_t nr_groups;
size_t i;
int err;
np = of_get_child_by_name(kbdev->dev->of_node, "ipa-groups");
if (!np)
return 0;
nr_groups = 0;
for_each_available_child_of_node(np, child)
nr_groups++;
if (!nr_groups || nr_groups > ARRAY_SIZE(ctx->groups)) {
dev_err(kbdev->dev, "invalid number of IPA groups: %zu", nr_groups);
err = -EINVAL;
goto err0;
}
for_each_available_child_of_node(np, child) {
const char *name;
u32 capacitance;
name = of_get_property(child, "label", NULL);
if (!name) {
dev_err(kbdev->dev, "label missing for IPA group");
err = -EINVAL;
goto err0;
}
err = of_property_read_u32(child, "capacitance",
&capacitance);
if (err < 0) {
dev_err(kbdev->dev, "capacitance missing for IPA group");
goto err0;
}
for (i = 0; i < ARRAY_SIZE(ctx->groups); i++) {
group = &ctx->groups[i];
if (!strcmp(group->name, name)) {
group->capacitance = capacitance;
break;
}
}
}
of_node_put(np);
return 0;
err0:
of_node_put(np);
return err;
}
#else
static int update_ipa_groups_from_dt(struct kbase_ipa_context *ctx)
{
return 0;
}
#endif
static int reset_ipa_groups(struct kbase_ipa_context *ctx)
{
init_ipa_groups(ctx);
return update_ipa_groups_from_dt(ctx);
}
static inline u32 read_hwcnt(struct kbase_ipa_context *ctx,
u32 offset)
{
u8 *p = ctx->vinstr_buffer;
return *(u32 *)&p[offset];
}
static inline u32 add_saturate(u32 a, u32 b)
{
if (U32_MAX - a < b)
return U32_MAX;
return a + b;
}
/*
* Calculate power estimation based on hardware counter `c'
* across all shader cores.
*/
static u32 calc_power_sc_single(struct kbase_ipa_context *ctx,
struct ipa_group *group, u32 c)
{
struct kbase_device *kbdev = ctx->kbdev;
u64 core_mask;
u32 base = 0, r = 0;
core_mask = kbdev->gpu_props.props.coherency_info.group[0].core_mask;
while (core_mask != 0ull) {
if ((core_mask & 1ull) != 0ull) {
u64 n = read_hwcnt(ctx, base + c);
u32 d = read_hwcnt(ctx, GPU_ACTIVE);
u32 s = group->capacitance;
r = add_saturate(r, div_u64(n * s, d));
}
base += NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT;
core_mask >>= 1;
}
return r;
}
/*
* Calculate power estimation based on hardware counter `c1'
* and `c2' across all shader cores.
*/
static u32 calc_power_sc_double(struct kbase_ipa_context *ctx,
struct ipa_group *group, u32 c1, u32 c2)
{
struct kbase_device *kbdev = ctx->kbdev;
u64 core_mask;
u32 base = 0, r = 0;
core_mask = kbdev->gpu_props.props.coherency_info.group[0].core_mask;
while (core_mask != 0ull) {
if ((core_mask & 1ull) != 0ull) {
u64 n = read_hwcnt(ctx, base + c1);
u32 d = read_hwcnt(ctx, GPU_ACTIVE);
u32 s = group->capacitance;
r = add_saturate(r, div_u64(n * s, d));
n = read_hwcnt(ctx, base + c2);
r = add_saturate(r, div_u64(n * s, d));
}
base += NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT;
core_mask >>= 1;
}
return r;
}
static u32 calc_power_single(struct kbase_ipa_context *ctx,
struct ipa_group *group, u32 c)
{
u64 n = read_hwcnt(ctx, c);
u32 d = read_hwcnt(ctx, GPU_ACTIVE);
u32 s = group->capacitance;
return div_u64(n * s, d);
}
static u32 calc_power_group0(struct kbase_ipa_context *ctx,
struct ipa_group *group)
{
return calc_power_single(ctx, group, L2_ANY_LOOKUP);
}
static u32 calc_power_group1(struct kbase_ipa_context *ctx,
struct ipa_group *group)
{
return calc_power_single(ctx, group, TILER_ACTIVE);
}
static u32 calc_power_group2(struct kbase_ipa_context *ctx,
struct ipa_group *group)
{
return calc_power_sc_single(ctx, group, FRAG_ACTIVE);
}
static u32 calc_power_group3(struct kbase_ipa_context *ctx,
struct ipa_group *group)
{
return calc_power_sc_double(ctx, group, VARY_SLOT_32,
VARY_SLOT_16);
}
static u32 calc_power_group4(struct kbase_ipa_context *ctx,
struct ipa_group *group)
{
return calc_power_sc_single(ctx, group, TEX_COORD_ISSUE);
}
static u32 calc_power_group5(struct kbase_ipa_context *ctx,
struct ipa_group *group)
{
return calc_power_sc_single(ctx, group, EXEC_INSTR_COUNT);
}
static u32 calc_power_group6(struct kbase_ipa_context *ctx,
struct ipa_group *group)
{
return calc_power_sc_double(ctx, group, BEATS_RD_LSC,
BEATS_WR_LSC);
}
static u32 calc_power_group7(struct kbase_ipa_context *ctx,
struct ipa_group *group)
{
return calc_power_sc_single(ctx, group, EXEC_CORE_ACTIVE);
}
static int attach_vinstr(struct kbase_ipa_context *ctx)
{
struct kbase_device *kbdev = ctx->kbdev;
struct kbase_uk_hwcnt_reader_setup setup;
size_t dump_size;
dump_size = kbase_vinstr_dump_size(kbdev);
ctx->vinstr_buffer = kzalloc(dump_size, GFP_KERNEL);
if (!ctx->vinstr_buffer) {
dev_err(kbdev->dev, "Failed to allocate IPA dump buffer");
return -1;
}
setup.jm_bm = ~0u;
setup.shader_bm = ~0u;
setup.tiler_bm = ~0u;
setup.mmu_l2_bm = ~0u;
ctx->vinstr_cli = kbase_vinstr_hwcnt_kernel_setup(kbdev->vinstr_ctx,
&setup, ctx->vinstr_buffer);
if (!ctx->vinstr_cli) {
dev_err(kbdev->dev, "Failed to register IPA with vinstr core");
kfree(ctx->vinstr_buffer);
ctx->vinstr_buffer = NULL;
return -1;
}
return 0;
}
static void detach_vinstr(struct kbase_ipa_context *ctx)
{
if (ctx->vinstr_cli)
kbase_vinstr_detach_client(ctx->vinstr_cli);
ctx->vinstr_cli = NULL;
kfree(ctx->vinstr_buffer);
ctx->vinstr_buffer = NULL;
}
struct kbase_ipa_context *kbase_ipa_init(struct kbase_device *kbdev)
{
struct kbase_ipa_context *ctx;
int err;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return NULL;
mutex_init(&ctx->ipa_lock);
ctx->kbdev = kbdev;
err = reset_ipa_groups(ctx);
if (err < 0)
goto err0;
err = sysfs_create_group(&kbdev->dev->kobj, &kbase_ipa_attr_group);
if (err < 0)
goto err0;
return ctx;
err0:
kfree(ctx);
return NULL;
}
void kbase_ipa_term(struct kbase_ipa_context *ctx)
{
struct kbase_device *kbdev = ctx->kbdev;
detach_vinstr(ctx);
sysfs_remove_group(&kbdev->dev->kobj, &kbase_ipa_attr_group);
kfree(ctx);
}
u32 kbase_ipa_dynamic_power(struct kbase_ipa_context *ctx, int *err)
{
struct ipa_group *group;
u32 power = 0;
size_t i;
mutex_lock(&ctx->ipa_lock);
if (!ctx->vinstr_cli) {
*err = attach_vinstr(ctx);
if (*err < 0)
goto err0;
}
*err = kbase_vinstr_hwc_dump(ctx->vinstr_cli,
BASE_HWCNT_READER_EVENT_MANUAL);
if (*err)
goto err0;
for (i = 0; i < ARRAY_SIZE(ctx->groups); i++) {
group = &ctx->groups[i];
power = add_saturate(power, group->calc_power(ctx, group));
}
err0:
mutex_unlock(&ctx->ipa_lock);
return power;
}
KBASE_EXPORT_TEST_API(kbase_ipa_dynamic_power);