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