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