119969707SMartin Blumenstingl // SPDX-License-Identifier: GPL-2.0 219969707SMartin Blumenstingl /* 319969707SMartin Blumenstingl * Copyright 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com> 419969707SMartin Blumenstingl * 519969707SMartin Blumenstingl * Based on panfrost_devfreq.c: 619969707SMartin Blumenstingl * Copyright 2019 Collabora ltd. 719969707SMartin Blumenstingl */ 819969707SMartin Blumenstingl #include <linux/clk.h> 919969707SMartin Blumenstingl #include <linux/devfreq.h> 1019969707SMartin Blumenstingl #include <linux/devfreq_cooling.h> 1119969707SMartin Blumenstingl #include <linux/device.h> 1219969707SMartin Blumenstingl #include <linux/platform_device.h> 1319969707SMartin Blumenstingl #include <linux/pm_opp.h> 1419969707SMartin Blumenstingl #include <linux/property.h> 1519969707SMartin Blumenstingl 1619969707SMartin Blumenstingl #include "lima_device.h" 1719969707SMartin Blumenstingl #include "lima_devfreq.h" 1819969707SMartin Blumenstingl 1919969707SMartin Blumenstingl static void lima_devfreq_update_utilization(struct lima_devfreq *devfreq) 2019969707SMartin Blumenstingl { 2119969707SMartin Blumenstingl ktime_t now, last; 2219969707SMartin Blumenstingl 2319969707SMartin Blumenstingl now = ktime_get(); 2419969707SMartin Blumenstingl last = devfreq->time_last_update; 2519969707SMartin Blumenstingl 2619969707SMartin Blumenstingl if (devfreq->busy_count > 0) 2719969707SMartin Blumenstingl devfreq->busy_time += ktime_sub(now, last); 2819969707SMartin Blumenstingl else 2919969707SMartin Blumenstingl devfreq->idle_time += ktime_sub(now, last); 3019969707SMartin Blumenstingl 3119969707SMartin Blumenstingl devfreq->time_last_update = now; 3219969707SMartin Blumenstingl } 3319969707SMartin Blumenstingl 3419969707SMartin Blumenstingl static int lima_devfreq_target(struct device *dev, unsigned long *freq, 3519969707SMartin Blumenstingl u32 flags) 3619969707SMartin Blumenstingl { 3719969707SMartin Blumenstingl struct dev_pm_opp *opp; 3819969707SMartin Blumenstingl 3919969707SMartin Blumenstingl opp = devfreq_recommended_opp(dev, freq, flags); 4019969707SMartin Blumenstingl if (IS_ERR(opp)) 4119969707SMartin Blumenstingl return PTR_ERR(opp); 4219969707SMartin Blumenstingl dev_pm_opp_put(opp); 4319969707SMartin Blumenstingl 448f9d7ef3SLiu Shixin return dev_pm_opp_set_rate(dev, *freq); 4519969707SMartin Blumenstingl } 4619969707SMartin Blumenstingl 4719969707SMartin Blumenstingl static void lima_devfreq_reset(struct lima_devfreq *devfreq) 4819969707SMartin Blumenstingl { 4919969707SMartin Blumenstingl devfreq->busy_time = 0; 5019969707SMartin Blumenstingl devfreq->idle_time = 0; 5119969707SMartin Blumenstingl devfreq->time_last_update = ktime_get(); 5219969707SMartin Blumenstingl } 5319969707SMartin Blumenstingl 5419969707SMartin Blumenstingl static int lima_devfreq_get_dev_status(struct device *dev, 5519969707SMartin Blumenstingl struct devfreq_dev_status *status) 5619969707SMartin Blumenstingl { 5719969707SMartin Blumenstingl struct lima_device *ldev = dev_get_drvdata(dev); 5819969707SMartin Blumenstingl struct lima_devfreq *devfreq = &ldev->devfreq; 5919969707SMartin Blumenstingl unsigned long irqflags; 6019969707SMartin Blumenstingl 6119969707SMartin Blumenstingl status->current_frequency = clk_get_rate(ldev->clk_gpu); 6219969707SMartin Blumenstingl 6319969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags); 6419969707SMartin Blumenstingl 6519969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq); 6619969707SMartin Blumenstingl 6719969707SMartin Blumenstingl status->total_time = ktime_to_ns(ktime_add(devfreq->busy_time, 6819969707SMartin Blumenstingl devfreq->idle_time)); 6919969707SMartin Blumenstingl status->busy_time = ktime_to_ns(devfreq->busy_time); 7019969707SMartin Blumenstingl 7119969707SMartin Blumenstingl lima_devfreq_reset(devfreq); 7219969707SMartin Blumenstingl 7319969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags); 7419969707SMartin Blumenstingl 7519969707SMartin Blumenstingl dev_dbg(ldev->dev, "busy %lu total %lu %lu %% freq %lu MHz\n", 7619969707SMartin Blumenstingl status->busy_time, status->total_time, 7719969707SMartin Blumenstingl status->busy_time / (status->total_time / 100), 7819969707SMartin Blumenstingl status->current_frequency / 1000 / 1000); 7919969707SMartin Blumenstingl 8019969707SMartin Blumenstingl return 0; 8119969707SMartin Blumenstingl } 8219969707SMartin Blumenstingl 8319969707SMartin Blumenstingl static struct devfreq_dev_profile lima_devfreq_profile = { 8419969707SMartin Blumenstingl .polling_ms = 50, /* ~3 frames */ 8519969707SMartin Blumenstingl .target = lima_devfreq_target, 8619969707SMartin Blumenstingl .get_dev_status = lima_devfreq_get_dev_status, 8719969707SMartin Blumenstingl }; 8819969707SMartin Blumenstingl 8919969707SMartin Blumenstingl void lima_devfreq_fini(struct lima_device *ldev) 9019969707SMartin Blumenstingl { 9119969707SMartin Blumenstingl struct lima_devfreq *devfreq = &ldev->devfreq; 9219969707SMartin Blumenstingl 9319969707SMartin Blumenstingl if (devfreq->cooling) { 9419969707SMartin Blumenstingl devfreq_cooling_unregister(devfreq->cooling); 9519969707SMartin Blumenstingl devfreq->cooling = NULL; 9619969707SMartin Blumenstingl } 9719969707SMartin Blumenstingl 9819969707SMartin Blumenstingl if (devfreq->devfreq) { 992ce216edSRobin Murphy devm_devfreq_remove_device(ldev->dev, devfreq->devfreq); 10019969707SMartin Blumenstingl devfreq->devfreq = NULL; 10119969707SMartin Blumenstingl } 10219969707SMartin Blumenstingl } 10319969707SMartin Blumenstingl 10419969707SMartin Blumenstingl int lima_devfreq_init(struct lima_device *ldev) 10519969707SMartin Blumenstingl { 10619969707SMartin Blumenstingl struct thermal_cooling_device *cooling; 1072ce216edSRobin Murphy struct device *dev = ldev->dev; 10819969707SMartin Blumenstingl struct devfreq *devfreq; 10919969707SMartin Blumenstingl struct lima_devfreq *ldevfreq = &ldev->devfreq; 11019969707SMartin Blumenstingl struct dev_pm_opp *opp; 11119969707SMartin Blumenstingl unsigned long cur_freq; 11219969707SMartin Blumenstingl int ret; 11319969707SMartin Blumenstingl 11419969707SMartin Blumenstingl if (!device_property_present(dev, "operating-points-v2")) 11519969707SMartin Blumenstingl /* Optional, continue without devfreq */ 11619969707SMartin Blumenstingl return 0; 11719969707SMartin Blumenstingl 11819969707SMartin Blumenstingl spin_lock_init(&ldevfreq->lock); 11919969707SMartin Blumenstingl 120*864a2701SYangtao Li ret = devm_pm_opp_set_clkname(dev, "core"); 121*864a2701SYangtao Li if (ret) 122*864a2701SYangtao Li return ret; 12319969707SMartin Blumenstingl 124*864a2701SYangtao Li ret = devm_pm_opp_set_regulators(dev, (const char *[]){ "mali" }, 1); 125*864a2701SYangtao Li if (ret) { 12619969707SMartin Blumenstingl /* Continue if the optional regulator is missing */ 12719969707SMartin Blumenstingl if (ret != -ENODEV) 128*864a2701SYangtao Li return ret; 12919969707SMartin Blumenstingl } 13019969707SMartin Blumenstingl 131*864a2701SYangtao Li ret = devm_pm_opp_of_add_table(dev); 13219969707SMartin Blumenstingl if (ret) 133*864a2701SYangtao Li return ret; 13419969707SMartin Blumenstingl 13519969707SMartin Blumenstingl lima_devfreq_reset(ldevfreq); 13619969707SMartin Blumenstingl 13719969707SMartin Blumenstingl cur_freq = clk_get_rate(ldev->clk_gpu); 13819969707SMartin Blumenstingl 13919969707SMartin Blumenstingl opp = devfreq_recommended_opp(dev, &cur_freq, 0); 140*864a2701SYangtao Li if (IS_ERR(opp)) 141*864a2701SYangtao Li return PTR_ERR(opp); 14219969707SMartin Blumenstingl 14319969707SMartin Blumenstingl lima_devfreq_profile.initial_freq = cur_freq; 14419969707SMartin Blumenstingl dev_pm_opp_put(opp); 14519969707SMartin Blumenstingl 14619969707SMartin Blumenstingl devfreq = devm_devfreq_add_device(dev, &lima_devfreq_profile, 14719969707SMartin Blumenstingl DEVFREQ_GOV_SIMPLE_ONDEMAND, NULL); 14819969707SMartin Blumenstingl if (IS_ERR(devfreq)) { 14919969707SMartin Blumenstingl dev_err(dev, "Couldn't initialize GPU devfreq\n"); 150*864a2701SYangtao Li return PTR_ERR(devfreq); 15119969707SMartin Blumenstingl } 15219969707SMartin Blumenstingl 15319969707SMartin Blumenstingl ldevfreq->devfreq = devfreq; 15419969707SMartin Blumenstingl 15519969707SMartin Blumenstingl cooling = of_devfreq_cooling_register(dev->of_node, devfreq); 15619969707SMartin Blumenstingl if (IS_ERR(cooling)) 15719969707SMartin Blumenstingl dev_info(dev, "Failed to register cooling device\n"); 15819969707SMartin Blumenstingl else 15919969707SMartin Blumenstingl ldevfreq->cooling = cooling; 16019969707SMartin Blumenstingl 16119969707SMartin Blumenstingl return 0; 16219969707SMartin Blumenstingl } 16319969707SMartin Blumenstingl 16419969707SMartin Blumenstingl void lima_devfreq_record_busy(struct lima_devfreq *devfreq) 16519969707SMartin Blumenstingl { 16619969707SMartin Blumenstingl unsigned long irqflags; 16719969707SMartin Blumenstingl 16819969707SMartin Blumenstingl if (!devfreq->devfreq) 16919969707SMartin Blumenstingl return; 17019969707SMartin Blumenstingl 17119969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags); 17219969707SMartin Blumenstingl 17319969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq); 17419969707SMartin Blumenstingl 17519969707SMartin Blumenstingl devfreq->busy_count++; 17619969707SMartin Blumenstingl 17719969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags); 17819969707SMartin Blumenstingl } 17919969707SMartin Blumenstingl 18019969707SMartin Blumenstingl void lima_devfreq_record_idle(struct lima_devfreq *devfreq) 18119969707SMartin Blumenstingl { 18219969707SMartin Blumenstingl unsigned long irqflags; 18319969707SMartin Blumenstingl 18419969707SMartin Blumenstingl if (!devfreq->devfreq) 18519969707SMartin Blumenstingl return; 18619969707SMartin Blumenstingl 18719969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags); 18819969707SMartin Blumenstingl 18919969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq); 19019969707SMartin Blumenstingl 19119969707SMartin Blumenstingl WARN_ON(--devfreq->busy_count < 0); 19219969707SMartin Blumenstingl 19319969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags); 19419969707SMartin Blumenstingl } 1954836cf04SQiang Yu 1964836cf04SQiang Yu int lima_devfreq_resume(struct lima_devfreq *devfreq) 1974836cf04SQiang Yu { 1984836cf04SQiang Yu unsigned long irqflags; 1994836cf04SQiang Yu 2004836cf04SQiang Yu if (!devfreq->devfreq) 2014836cf04SQiang Yu return 0; 2024836cf04SQiang Yu 2034836cf04SQiang Yu spin_lock_irqsave(&devfreq->lock, irqflags); 2044836cf04SQiang Yu 2054836cf04SQiang Yu lima_devfreq_reset(devfreq); 2064836cf04SQiang Yu 2074836cf04SQiang Yu spin_unlock_irqrestore(&devfreq->lock, irqflags); 2084836cf04SQiang Yu 2094836cf04SQiang Yu return devfreq_resume_device(devfreq->devfreq); 2104836cf04SQiang Yu } 2114836cf04SQiang Yu 2124836cf04SQiang Yu int lima_devfreq_suspend(struct lima_devfreq *devfreq) 2134836cf04SQiang Yu { 2144836cf04SQiang Yu if (!devfreq->devfreq) 2154836cf04SQiang Yu return 0; 2164836cf04SQiang Yu 2174836cf04SQiang Yu return devfreq_suspend_device(devfreq->devfreq); 2184836cf04SQiang Yu } 219