summaryrefslogtreecommitdiff
path: root/mali_kbase/platform/pixel/pixel_gpu_sysfs.c
diff options
context:
space:
mode:
authorSidath Senanayake <sidaths@google.com>2020-08-28 00:13:49 +0100
committerSidath Senanayake <sidaths@google.com>2020-09-02 21:01:36 +0100
commit6512329057af12dd9d81e23f62d890bfca7b1849 (patch)
treeb0f77bc9fa7f5ba3a510d0327ca46802f15280ec /mali_kbase/platform/pixel/pixel_gpu_sysfs.c
parent626f28c4da2128d5dcaed68a22e3f81f69439119 (diff)
downloadgpu-6512329057af12dd9d81e23f62d890bfca7b1849.tar.gz
mali_kbase: platform: Add GPU DVFS support and metrics
Adds support for clocking the GPU clocks according to a DVFS table specified in the GPU's device tree entry using a simple governor. Support also added for tracking GPU power status along with time in state for each DVFS OPP. Bug: 156057140 Bug: 158091247 Signed-off-by: Sidath Senanayake <sidaths@google.com> Change-Id: I0b83336c24f0b724a26f4de6b97cf15d9687377b
Diffstat (limited to 'mali_kbase/platform/pixel/pixel_gpu_sysfs.c')
-rw-r--r--mali_kbase/platform/pixel/pixel_gpu_sysfs.c422
1 files changed, 422 insertions, 0 deletions
diff --git a/mali_kbase/platform/pixel/pixel_gpu_sysfs.c b/mali_kbase/platform/pixel/pixel_gpu_sysfs.c
new file mode 100644
index 0000000..2beb632
--- /dev/null
+++ b/mali_kbase/platform/pixel/pixel_gpu_sysfs.c
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2020 Google LLC.
+ *
+ * Author: Sidath Senanayake <sidaths@google.com>
+ */
+
+/* Mali core includes */
+#include <mali_kbase.h>
+
+/* Pixel integration includes */
+#include "mali_kbase_config_platform.h"
+#include "pixel_gpu_control.h"
+#include "pixel_gpu_debug.h"
+#include "pixel_gpu_dvfs.h"
+
+/* Helper functions */
+
+/**
+ * get_level_from_clock() - Helper function to get the level index corresponding to a clock.
+ *
+ * @kbdev: The &struct kbase_device for the GPU.
+ * @clock: The frequency (in kHz) of the GPU Top Level clock to get the level from.
+ *
+ * Return: The level corresponding to @clock, -1 on failure.
+ */
+static int get_level_from_clock(struct kbase_device *kbdev, int clock)
+{
+ struct pixel_context *pc = kbdev->platform_context;
+ int i;
+
+ for (i = 0; i < pc->dvfs.table_size; i++) {
+ if (pc->dvfs.table[i].clk0 == clock)
+ return i;
+ }
+
+ return -1;
+}
+
+/* Custom attributes */
+
+static ssize_t clock_info_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ ssize_t ret = 0;
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ /* We use level_target in case the clock has been set while the GPU was powered down */
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ "Power status : %s\n"
+ "gpu0 clock (top level) : %d kHz\n"
+ "gpu1 clock (shaders) : %d kHz\n",
+
+ (gpu_power_status(kbdev) ? "on" : "off"),
+ pc->dvfs.table[pc->dvfs.level_target].clk0,
+ pc->dvfs.table[pc->dvfs.level_target].clk1);
+
+ return ret;
+}
+
+static ssize_t dvfs_table_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int i;
+ ssize_t ret = 0;
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ " gpu_0 gpu_0 gpu_1 gpu_1 util util hyste- int_clk mif_clk cpu0_clk cpu1_clk cpu2_clk\n"
+ " clk vol clk vol min max resis min min min min limit\n"
+ "------- ------- ------- ------- ---- ---- ------ ------- -------- -------- -------- --------\n");
+
+ for (i = pc->dvfs.level_max; i <= pc->dvfs.level_min; i++) {
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ "%7d %7d %7d %7d %4d %4d %6d %7d %8d %8d %8d ",
+ pc->dvfs.table[i].clk0,
+ pc->dvfs.table[i].vol0,
+ pc->dvfs.table[i].clk1,
+ pc->dvfs.table[i].vol1,
+ pc->dvfs.table[i].util_min,
+ pc->dvfs.table[i].util_max,
+ pc->dvfs.table[i].hysteresis,
+ pc->dvfs.table[i].qos.int_min,
+ pc->dvfs.table[i].qos.mif_min,
+ pc->dvfs.table[i].qos.cpu0_min,
+ pc->dvfs.table[i].qos.cpu1_min);
+
+ if (pc->dvfs.table[i].qos.cpu2_max == CPU_FREQ_MAX)
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%8s\n", "none");
+ else
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%8d\n",
+ pc->dvfs.table[i].qos.cpu2_max);
+ }
+
+ return ret;
+}
+
+static ssize_t power_stats_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int i;
+ ssize_t ret = 0;
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ /* First trigger an update */
+ mutex_lock(&pc->dvfs.lock);
+ gpu_dvfs_metrics_update(kbdev, pc->dvfs.level, gpu_power_status(kbdev));
+ mutex_unlock(&pc->dvfs.lock);
+
+ ret = scnprintf(buf + ret, PAGE_SIZE - ret, "DVFS stats: (times in ms)\n");
+
+ for (i = 0; i < pc->dvfs.table_size; i++) {
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ "%d:\n\ttotal_time = %llu\n\tcount = %d\n\tlast_entry_time = %llu\n",
+ pc->dvfs.table[i].clk0,
+ pc->dvfs.table[i].metrics.time_total / NSEC_PER_MSEC,
+ pc->dvfs.table[i].metrics.entry_count,
+ pc->dvfs.table[i].metrics.time_last_entry / NSEC_PER_MSEC);
+ }
+
+
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret, "Summary stats: (times in ms)\n");
+
+ ret += scnprintf(
+ buf + ret, PAGE_SIZE - ret,
+ "ON:\n\ttotal_time = %llu\n\tcount = %d\n\tlast_entry_time = %llu\n",
+ pc->pm.power_on_metrics.time_total / NSEC_PER_MSEC,
+ pc->pm.power_on_metrics.entry_count,
+ pc->pm.power_on_metrics.time_last_entry / NSEC_PER_MSEC);
+
+ ret += scnprintf(
+ buf + ret, PAGE_SIZE - ret,
+ "OFF:\n\ttotal_time = %llu\n\tcount = %d\n\tlast_entry_time = %llu\n",
+ pc->pm.power_off_metrics.time_total / NSEC_PER_MSEC,
+ pc->pm.power_off_metrics.entry_count,
+ pc->pm.power_off_metrics.time_last_entry / NSEC_PER_MSEC);
+
+ return ret;
+}
+
+DEVICE_ATTR_RO(clock_info);
+DEVICE_ATTR_RO(dvfs_table);
+DEVICE_ATTR_RO(power_stats);
+
+
+/* devfreq-like attributes */
+
+static ssize_t cur_freq_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ /* We use level_target in case the clock has been set while the GPU was powered down */
+ return scnprintf(buf, PAGE_SIZE, "%d\n", pc->dvfs.table[pc->dvfs.level_target].clk0);
+}
+
+static ssize_t available_frequencies_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int i;
+ ssize_t ret = 0;
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ for (i = 0; i < pc->dvfs.table_size; i++)
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%d ", pc->dvfs.table[i].clk0);
+
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n");
+
+ return ret;
+}
+
+static ssize_t max_freq_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", pc->dvfs.table[pc->dvfs.level_max].clk0);
+}
+
+static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", pc->dvfs.table[pc->dvfs.level_min].clk0);
+}
+
+static ssize_t scaling_max_freq_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", pc->dvfs.table[pc->dvfs.level_scaling_max].clk0);
+}
+
+static ssize_t scaling_max_freq_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int level, ret;
+ unsigned int clock;
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ ret = kstrtoint(buf, 0, &clock);
+ if (ret)
+ return -EINVAL;
+
+ level = get_level_from_clock(kbdev, clock);
+ if (level < 0)
+ return -EINVAL;
+
+ mutex_lock(&pc->dvfs.lock);
+ pc->dvfs.level_scaling_max = level;
+ pc->dvfs.level_scaling_min = max(level, pc->dvfs.level_scaling_min);
+ gpu_dvfs_update_level_locks(kbdev);
+ mutex_unlock(&pc->dvfs.lock);
+
+ return count;
+}
+
+static ssize_t scaling_min_freq_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", pc->dvfs.table[pc->dvfs.level_scaling_min].clk0);
+}
+
+static ssize_t scaling_min_freq_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret, level;
+ unsigned int clock;
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ ret = kstrtoint(buf, 0, &clock);
+ if (ret)
+ return -EINVAL;
+
+ level = get_level_from_clock(kbdev, clock);
+ if (level < 0)
+ return -EINVAL;
+
+ mutex_lock(&pc->dvfs.lock);
+ pc->dvfs.level_scaling_min = level;
+ pc->dvfs.level_scaling_max = min(level, pc->dvfs.level_scaling_max);
+ gpu_dvfs_update_level_locks(kbdev);
+ mutex_unlock(&pc->dvfs.lock);
+
+ return count;
+}
+
+static ssize_t time_in_state_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int i;
+ ssize_t ret = 0;
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ /* First trigger an update */
+ mutex_lock(&pc->dvfs.lock);
+ gpu_dvfs_metrics_update(kbdev, pc->dvfs.level, gpu_power_status(kbdev));
+ mutex_unlock(&pc->dvfs.lock);
+
+ for (i = pc->dvfs.level_max; i <= pc->dvfs.level_min; i++)
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%8d %9d\n", pc->dvfs.table[i].clk0,
+ (u32)(pc->dvfs.table[i].metrics.time_total / NSEC_PER_MSEC));
+
+ return ret;
+}
+
+static ssize_t available_governors_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return gpu_dvfs_governor_print_available(buf, PAGE_SIZE);
+}
+
+static ssize_t governor_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct kbase_device *kbdev = dev->driver_data;
+
+ return gpu_dvfs_governor_print_curr(kbdev, buf, PAGE_SIZE);
+}
+
+static ssize_t governor_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ enum gpu_dvfs_governor_type gov;
+ ssize_t ret = count;
+ struct kbase_device *kbdev = dev->driver_data;
+ struct pixel_context *pc = kbdev->platform_context;
+
+ if (!pc)
+ return -ENODEV;
+
+ gov = gpu_dvfs_governor_get_id(buf);
+
+ if (gov == GPU_DVFS_GOVERNOR_INVALID)
+ ret = -EINVAL;
+ else if (gov != pc->dvfs.governor.curr) {
+ mutex_lock(&pc->dvfs.lock);
+ if (gpu_dvfs_governor_set_governor(kbdev, gov))
+ ret = -EINVAL;
+ mutex_unlock(&pc->dvfs.lock);
+ }
+
+ return ret;
+}
+
+/* Define devfreq-like attributes */
+DEVICE_ATTR_RO(available_frequencies);
+DEVICE_ATTR_RO(cur_freq);
+DEVICE_ATTR_RO(max_freq);
+DEVICE_ATTR_RO(min_freq);
+DEVICE_ATTR_RW(scaling_max_freq);
+DEVICE_ATTR_RW(scaling_min_freq);
+DEVICE_ATTR_RO(time_in_state);
+DEVICE_ATTR_RO(available_governors);
+DEVICE_ATTR_RW(governor);
+
+/* Initialization code */
+
+/**
+ * attribs - An array containing all sysfs files for the Pixel GPU sysfs system.
+ *
+ * This array contains the list of all files that will be set up and removed by the Pixel GPU sysfs
+ * system. It allows for more compact initialization and termination code below.
+ */
+static struct {
+ const char *name;
+ const struct device_attribute *attr;
+} attribs[] = {
+ {"clock_info", &dev_attr_clock_info},
+ {"dvfs_table", &dev_attr_dvfs_table},
+ {"power_stats", &dev_attr_power_stats},
+ {"available_frequencies", &dev_attr_available_frequencies},
+ {"cur_freq", &dev_attr_cur_freq},
+ {"max_freq", &dev_attr_max_freq},
+ {"min_freq", &dev_attr_min_freq},
+ {"scaling_max_freq", &dev_attr_scaling_max_freq},
+ {"scaling_min_freq", &dev_attr_scaling_min_freq},
+ {"time_in_state", &dev_attr_time_in_state},
+ {"available_governors", &dev_attr_available_governors},
+ {"governor", &dev_attr_governor}
+};
+
+
+/**
+ * gpu_sysfs_init() - Initializes the Pixel GPU sysfs system.
+ *
+ * @kbdev: The &struct kbase_device for the GPU.
+ *
+ * Return: On success, returns 0. -ENOENT if creating a sysfs file results in an error.
+ */
+int gpu_sysfs_init(struct kbase_device *kbdev)
+{
+ int i;
+ struct device *dev = kbdev->dev;
+
+ for (i = 0; i < ARRAY_SIZE(attribs); i++) {
+ if (device_create_file(dev, attribs[i].attr)) {
+ GPU_LOG(LOG_ERROR, kbdev, "failed to create sysfs file %s\n",
+ attribs[i].name);
+ return -ENOENT;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * gpu_sysfs_term() - Terminates the Pixel GPU sysfs system.
+ *
+ * @kbdev: The &struct kbase_device for the GPU.
+ */
+void gpu_sysfs_term(struct kbase_device *kbdev)
+{
+ int i;
+ struct device *dev = kbdev->dev;
+
+ for (i = 0; i < ARRAY_SIZE(attribs); i++)
+ device_remove_file(dev, attribs[i].attr);
+}