linux/drivers/gpu/drm/panfrost/panfrost_devfreq.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/* Copyright 2019 Collabora ltd. */
   3#include <linux/devfreq.h>
   4#include <linux/devfreq_cooling.h>
   5#include <linux/platform_device.h>
   6#include <linux/pm_opp.h>
   7#include <linux/clk.h>
   8#include <linux/regulator/consumer.h>
   9
  10#include "panfrost_device.h"
  11#include "panfrost_devfreq.h"
  12#include "panfrost_features.h"
  13#include "panfrost_issues.h"
  14#include "panfrost_gpu.h"
  15#include "panfrost_regs.h"
  16
  17static void panfrost_devfreq_update_utilization(struct panfrost_device *pfdev);
  18
  19static int panfrost_devfreq_target(struct device *dev, unsigned long *freq,
  20                                   u32 flags)
  21{
  22        struct dev_pm_opp *opp;
  23        int err;
  24
  25        opp = devfreq_recommended_opp(dev, freq, flags);
  26        if (IS_ERR(opp))
  27                return PTR_ERR(opp);
  28        dev_pm_opp_put(opp);
  29
  30        err = dev_pm_opp_set_rate(dev, *freq);
  31        if (err)
  32                return err;
  33
  34        return 0;
  35}
  36
  37static void panfrost_devfreq_reset(struct panfrost_device *pfdev)
  38{
  39        pfdev->devfreq.busy_time = 0;
  40        pfdev->devfreq.idle_time = 0;
  41        pfdev->devfreq.time_last_update = ktime_get();
  42}
  43
  44static int panfrost_devfreq_get_dev_status(struct device *dev,
  45                                           struct devfreq_dev_status *status)
  46{
  47        struct panfrost_device *pfdev = dev_get_drvdata(dev);
  48
  49        panfrost_devfreq_update_utilization(pfdev);
  50
  51        status->current_frequency = clk_get_rate(pfdev->clock);
  52        status->total_time = ktime_to_ns(ktime_add(pfdev->devfreq.busy_time,
  53                                                   pfdev->devfreq.idle_time));
  54
  55        status->busy_time = ktime_to_ns(pfdev->devfreq.busy_time);
  56
  57        panfrost_devfreq_reset(pfdev);
  58
  59        dev_dbg(pfdev->dev, "busy %lu total %lu %lu %% freq %lu MHz\n", status->busy_time,
  60                status->total_time,
  61                status->busy_time / (status->total_time / 100),
  62                status->current_frequency / 1000 / 1000);
  63
  64        return 0;
  65}
  66
  67static struct devfreq_dev_profile panfrost_devfreq_profile = {
  68        .polling_ms = 50, /* ~3 frames */
  69        .target = panfrost_devfreq_target,
  70        .get_dev_status = panfrost_devfreq_get_dev_status,
  71};
  72
  73int panfrost_devfreq_init(struct panfrost_device *pfdev)
  74{
  75        int ret;
  76        struct dev_pm_opp *opp;
  77        unsigned long cur_freq;
  78        struct device *dev = &pfdev->pdev->dev;
  79        struct devfreq *devfreq;
  80        struct thermal_cooling_device *cooling;
  81
  82        ret = dev_pm_opp_of_add_table(dev);
  83        if (ret == -ENODEV) /* Optional, continue without devfreq */
  84                return 0;
  85        else if (ret)
  86                return ret;
  87
  88        panfrost_devfreq_reset(pfdev);
  89
  90        cur_freq = clk_get_rate(pfdev->clock);
  91
  92        opp = devfreq_recommended_opp(dev, &cur_freq, 0);
  93        if (IS_ERR(opp))
  94                return PTR_ERR(opp);
  95
  96        panfrost_devfreq_profile.initial_freq = cur_freq;
  97        dev_pm_opp_put(opp);
  98
  99        devfreq = devm_devfreq_add_device(dev, &panfrost_devfreq_profile,
 100                                          DEVFREQ_GOV_SIMPLE_ONDEMAND, NULL);
 101        if (IS_ERR(devfreq)) {
 102                DRM_DEV_ERROR(dev, "Couldn't initialize GPU devfreq\n");
 103                dev_pm_opp_of_remove_table(dev);
 104                return PTR_ERR(devfreq);
 105        }
 106        pfdev->devfreq.devfreq = devfreq;
 107
 108        cooling = of_devfreq_cooling_register(dev->of_node, devfreq);
 109        if (IS_ERR(cooling))
 110                DRM_DEV_INFO(dev, "Failed to register cooling device\n");
 111        else
 112                pfdev->devfreq.cooling = cooling;
 113
 114        return 0;
 115}
 116
 117void panfrost_devfreq_fini(struct panfrost_device *pfdev)
 118{
 119        if (pfdev->devfreq.cooling)
 120                devfreq_cooling_unregister(pfdev->devfreq.cooling);
 121        dev_pm_opp_of_remove_table(&pfdev->pdev->dev);
 122}
 123
 124void panfrost_devfreq_resume(struct panfrost_device *pfdev)
 125{
 126        if (!pfdev->devfreq.devfreq)
 127                return;
 128
 129        panfrost_devfreq_reset(pfdev);
 130
 131        devfreq_resume_device(pfdev->devfreq.devfreq);
 132}
 133
 134void panfrost_devfreq_suspend(struct panfrost_device *pfdev)
 135{
 136        if (!pfdev->devfreq.devfreq)
 137                return;
 138
 139        devfreq_suspend_device(pfdev->devfreq.devfreq);
 140}
 141
 142static void panfrost_devfreq_update_utilization(struct panfrost_device *pfdev)
 143{
 144        ktime_t now;
 145        ktime_t last;
 146
 147        if (!pfdev->devfreq.devfreq)
 148                return;
 149
 150        now = ktime_get();
 151        last = pfdev->devfreq.time_last_update;
 152
 153        if (atomic_read(&pfdev->devfreq.busy_count) > 0)
 154                pfdev->devfreq.busy_time += ktime_sub(now, last);
 155        else
 156                pfdev->devfreq.idle_time += ktime_sub(now, last);
 157
 158        pfdev->devfreq.time_last_update = now;
 159}
 160
 161void panfrost_devfreq_record_busy(struct panfrost_device *pfdev)
 162{
 163        panfrost_devfreq_update_utilization(pfdev);
 164        atomic_inc(&pfdev->devfreq.busy_count);
 165}
 166
 167void panfrost_devfreq_record_idle(struct panfrost_device *pfdev)
 168{
 169        int count;
 170
 171        panfrost_devfreq_update_utilization(pfdev);
 172        count = atomic_dec_if_positive(&pfdev->devfreq.busy_count);
 173        WARN_ON(count < 0);
 174}
 175