aboutsummaryrefslogtreecommitdiff
path: root/drivers/gpu/arm/midgard/mali_kbase_ipa.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/arm/midgard/mali_kbase_ipa.c')
-rwxr-xr-xdrivers/gpu/arm/midgard/mali_kbase_ipa.c431
1 files changed, 431 insertions, 0 deletions
diff --git a/drivers/gpu/arm/midgard/mali_kbase_ipa.c b/drivers/gpu/arm/midgard/mali_kbase_ipa.c
new file mode 100755
index 000000000000..c579d0a589f7
--- /dev/null
+++ b/drivers/gpu/arm/midgard/mali_kbase_ipa.c
@@ -0,0 +1,431 @@
+/*
+ *
+ * (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);