linux/drivers/gpu/drm/lima/lima_devfreq.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
   4 *
   5 * Based on panfrost_devfreq.c:
   6 *   Copyright 2019 Collabora ltd.
   7 */
   8#include <linux/clk.h>
   9#include <linux/devfreq.h>
  10#include <linux/devfreq_cooling.h>
  11#include <linux/device.h>
  12#include <linux/platform_device.h>
  13#include <linux/pm_opp.h>
  14#include <linux/property.h>
  15
  16#include "lima_device.h"
  17#include "lima_devfreq.h"
  18
  19static void lima_devfreq_update_utilization(struct lima_devfreq *devfreq)
  20{
  21        ktime_t now, last;
  22
  23        now = ktime_get();
  24        last = devfreq->time_last_update;
  25
  26        if (devfreq->busy_count > 0)
  27                devfreq->busy_time += ktime_sub(now, last);
  28        else
  29                devfreq->idle_time += ktime_sub(now, last);
  30
  31        devfreq->time_last_update = now;
  32}
  33
  34static int lima_devfreq_target(struct device *dev, unsigned long *freq,
  35                               u32 flags)
  36{
  37        struct dev_pm_opp *opp;
  38        int err;
  39
  40        opp = devfreq_recommended_opp(dev, freq, flags);
  41        if (IS_ERR(opp))
  42                return PTR_ERR(opp);
  43        dev_pm_opp_put(opp);
  44
  45        err = dev_pm_opp_set_rate(dev, *freq);
  46        if (err)
  47                return err;
  48
  49        return 0;
  50}
  51
  52static void lima_devfreq_reset(struct lima_devfreq *devfreq)
  53{
  54        devfreq->busy_time = 0;
  55        devfreq->idle_time = 0;
  56        devfreq->time_last_update = ktime_get();
  57}
  58
  59static int lima_devfreq_get_dev_status(struct device *dev,
  60                                       struct devfreq_dev_status *status)
  61{
  62        struct lima_device *ldev = dev_get_drvdata(dev);
  63        struct lima_devfreq *devfreq = &ldev->devfreq;
  64        unsigned long irqflags;
  65
  66        status->current_frequency = clk_get_rate(ldev->clk_gpu);
  67
  68        spin_lock_irqsave(&devfreq->lock, irqflags);
  69
  70        lima_devfreq_update_utilization(devfreq);
  71
  72        status->total_time = ktime_to_ns(ktime_add(devfreq->busy_time,
  73                                                   devfreq->idle_time));
  74        status->busy_time = ktime_to_ns(devfreq->busy_time);
  75
  76        lima_devfreq_reset(devfreq);
  77
  78        spin_unlock_irqrestore(&devfreq->lock, irqflags);
  79
  80        dev_dbg(ldev->dev, "busy %lu total %lu %lu %% freq %lu MHz\n",
  81                status->busy_time, status->total_time,
  82                status->busy_time / (status->total_time / 100),
  83                status->current_frequency / 1000 / 1000);
  84
  85        return 0;
  86}
  87
  88static struct devfreq_dev_profile lima_devfreq_profile = {
  89        .polling_ms = 50, /* ~3 frames */
  90        .target = lima_devfreq_target,
  91        .get_dev_status = lima_devfreq_get_dev_status,
  92};
  93
  94void lima_devfreq_fini(struct lima_device *ldev)
  95{
  96        struct lima_devfreq *devfreq = &ldev->devfreq;
  97
  98        if (devfreq->cooling) {
  99                devfreq_cooling_unregister(devfreq->cooling);
 100                devfreq->cooling = NULL;
 101        }
 102
 103        if (devfreq->devfreq) {
 104                devm_devfreq_remove_device(ldev->dev, devfreq->devfreq);
 105                devfreq->devfreq = NULL;
 106        }
 107
 108        if (devfreq->opp_of_table_added) {
 109                dev_pm_opp_of_remove_table(ldev->dev);
 110                devfreq->opp_of_table_added = false;
 111        }
 112
 113        if (devfreq->regulators_opp_table) {
 114                dev_pm_opp_put_regulators(devfreq->regulators_opp_table);
 115                devfreq->regulators_opp_table = NULL;
 116        }
 117
 118        if (devfreq->clkname_opp_table) {
 119                dev_pm_opp_put_clkname(devfreq->clkname_opp_table);
 120                devfreq->clkname_opp_table = NULL;
 121        }
 122}
 123
 124int lima_devfreq_init(struct lima_device *ldev)
 125{
 126        struct thermal_cooling_device *cooling;
 127        struct device *dev = ldev->dev;
 128        struct opp_table *opp_table;
 129        struct devfreq *devfreq;
 130        struct lima_devfreq *ldevfreq = &ldev->devfreq;
 131        struct dev_pm_opp *opp;
 132        unsigned long cur_freq;
 133        int ret;
 134
 135        if (!device_property_present(dev, "operating-points-v2"))
 136                /* Optional, continue without devfreq */
 137                return 0;
 138
 139        spin_lock_init(&ldevfreq->lock);
 140
 141        opp_table = dev_pm_opp_set_clkname(dev, "core");
 142        if (IS_ERR(opp_table)) {
 143                ret = PTR_ERR(opp_table);
 144                goto err_fini;
 145        }
 146
 147        ldevfreq->clkname_opp_table = opp_table;
 148
 149        opp_table = dev_pm_opp_set_regulators(dev,
 150                                              (const char *[]){ "mali" },
 151                                              1);
 152        if (IS_ERR(opp_table)) {
 153                ret = PTR_ERR(opp_table);
 154
 155                /* Continue if the optional regulator is missing */
 156                if (ret != -ENODEV)
 157                        goto err_fini;
 158        } else {
 159                ldevfreq->regulators_opp_table = opp_table;
 160        }
 161
 162        ret = dev_pm_opp_of_add_table(dev);
 163        if (ret)
 164                goto err_fini;
 165        ldevfreq->opp_of_table_added = true;
 166
 167        lima_devfreq_reset(ldevfreq);
 168
 169        cur_freq = clk_get_rate(ldev->clk_gpu);
 170
 171        opp = devfreq_recommended_opp(dev, &cur_freq, 0);
 172        if (IS_ERR(opp)) {
 173                ret = PTR_ERR(opp);
 174                goto err_fini;
 175        }
 176
 177        lima_devfreq_profile.initial_freq = cur_freq;
 178        dev_pm_opp_put(opp);
 179
 180        devfreq = devm_devfreq_add_device(dev, &lima_devfreq_profile,
 181                                          DEVFREQ_GOV_SIMPLE_ONDEMAND, NULL);
 182        if (IS_ERR(devfreq)) {
 183                dev_err(dev, "Couldn't initialize GPU devfreq\n");
 184                ret = PTR_ERR(devfreq);
 185                goto err_fini;
 186        }
 187
 188        ldevfreq->devfreq = devfreq;
 189
 190        cooling = of_devfreq_cooling_register(dev->of_node, devfreq);
 191        if (IS_ERR(cooling))
 192                dev_info(dev, "Failed to register cooling device\n");
 193        else
 194                ldevfreq->cooling = cooling;
 195
 196        return 0;
 197
 198err_fini:
 199        lima_devfreq_fini(ldev);
 200        return ret;
 201}
 202
 203void lima_devfreq_record_busy(struct lima_devfreq *devfreq)
 204{
 205        unsigned long irqflags;
 206
 207        if (!devfreq->devfreq)
 208                return;
 209
 210        spin_lock_irqsave(&devfreq->lock, irqflags);
 211
 212        lima_devfreq_update_utilization(devfreq);
 213
 214        devfreq->busy_count++;
 215
 216        spin_unlock_irqrestore(&devfreq->lock, irqflags);
 217}
 218
 219void lima_devfreq_record_idle(struct lima_devfreq *devfreq)
 220{
 221        unsigned long irqflags;
 222
 223        if (!devfreq->devfreq)
 224                return;
 225
 226        spin_lock_irqsave(&devfreq->lock, irqflags);
 227
 228        lima_devfreq_update_utilization(devfreq);
 229
 230        WARN_ON(--devfreq->busy_count < 0);
 231
 232        spin_unlock_irqrestore(&devfreq->lock, irqflags);
 233}
 234
 235int lima_devfreq_resume(struct lima_devfreq *devfreq)
 236{
 237        unsigned long irqflags;
 238
 239        if (!devfreq->devfreq)
 240                return 0;
 241
 242        spin_lock_irqsave(&devfreq->lock, irqflags);
 243
 244        lima_devfreq_reset(devfreq);
 245
 246        spin_unlock_irqrestore(&devfreq->lock, irqflags);
 247
 248        return devfreq_resume_device(devfreq->devfreq);
 249}
 250
 251int lima_devfreq_suspend(struct lima_devfreq *devfreq)
 252{
 253        if (!devfreq->devfreq)
 254                return 0;
 255
 256        return devfreq_suspend_device(devfreq->devfreq);
 257}
 258