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