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 = { 84904beebbSLukasz Luba .timer = DEVFREQ_TIMER_DELAYED, 8519969707SMartin Blumenstingl .polling_ms = 50, /* ~3 frames */ 8619969707SMartin Blumenstingl .target = lima_devfreq_target, 8719969707SMartin Blumenstingl .get_dev_status = lima_devfreq_get_dev_status, 8819969707SMartin Blumenstingl }; 8919969707SMartin Blumenstingl 9019969707SMartin Blumenstingl void lima_devfreq_fini(struct lima_device *ldev) 9119969707SMartin Blumenstingl { 9219969707SMartin Blumenstingl struct lima_devfreq *devfreq = &ldev->devfreq; 9319969707SMartin Blumenstingl 9419969707SMartin Blumenstingl if (devfreq->cooling) { 9519969707SMartin Blumenstingl devfreq_cooling_unregister(devfreq->cooling); 9619969707SMartin Blumenstingl devfreq->cooling = NULL; 9719969707SMartin Blumenstingl } 9819969707SMartin Blumenstingl 9919969707SMartin Blumenstingl if (devfreq->devfreq) { 1002ce216edSRobin Murphy devm_devfreq_remove_device(ldev->dev, devfreq->devfreq); 10119969707SMartin Blumenstingl devfreq->devfreq = NULL; 10219969707SMartin Blumenstingl } 10319969707SMartin Blumenstingl 1042ce216edSRobin Murphy dev_pm_opp_of_remove_table(ldev->dev); 10519969707SMartin Blumenstingl 10619969707SMartin Blumenstingl dev_pm_opp_put_regulators(devfreq->regulators_opp_table); 10719969707SMartin Blumenstingl dev_pm_opp_put_clkname(devfreq->clkname_opp_table); 10872ba9e22SViresh Kumar devfreq->regulators_opp_table = NULL; 10919969707SMartin Blumenstingl devfreq->clkname_opp_table = NULL; 11019969707SMartin Blumenstingl } 11119969707SMartin Blumenstingl 11219969707SMartin Blumenstingl int lima_devfreq_init(struct lima_device *ldev) 11319969707SMartin Blumenstingl { 11419969707SMartin Blumenstingl struct thermal_cooling_device *cooling; 1152ce216edSRobin Murphy struct device *dev = ldev->dev; 11619969707SMartin Blumenstingl struct opp_table *opp_table; 11719969707SMartin Blumenstingl struct devfreq *devfreq; 11819969707SMartin Blumenstingl struct lima_devfreq *ldevfreq = &ldev->devfreq; 11919969707SMartin Blumenstingl struct dev_pm_opp *opp; 12019969707SMartin Blumenstingl unsigned long cur_freq; 12119969707SMartin Blumenstingl int ret; 12219969707SMartin Blumenstingl 12319969707SMartin Blumenstingl if (!device_property_present(dev, "operating-points-v2")) 12419969707SMartin Blumenstingl /* Optional, continue without devfreq */ 12519969707SMartin Blumenstingl return 0; 12619969707SMartin Blumenstingl 12719969707SMartin Blumenstingl spin_lock_init(&ldevfreq->lock); 12819969707SMartin Blumenstingl 12919969707SMartin Blumenstingl opp_table = dev_pm_opp_set_clkname(dev, "core"); 13019969707SMartin Blumenstingl if (IS_ERR(opp_table)) { 13119969707SMartin Blumenstingl ret = PTR_ERR(opp_table); 13219969707SMartin Blumenstingl goto err_fini; 13319969707SMartin Blumenstingl } 13419969707SMartin Blumenstingl 13519969707SMartin Blumenstingl ldevfreq->clkname_opp_table = opp_table; 13619969707SMartin Blumenstingl 13719969707SMartin Blumenstingl opp_table = dev_pm_opp_set_regulators(dev, 13819969707SMartin Blumenstingl (const char *[]){ "mali" }, 13919969707SMartin Blumenstingl 1); 14019969707SMartin Blumenstingl if (IS_ERR(opp_table)) { 14119969707SMartin Blumenstingl ret = PTR_ERR(opp_table); 14219969707SMartin Blumenstingl 14319969707SMartin Blumenstingl /* Continue if the optional regulator is missing */ 14419969707SMartin Blumenstingl if (ret != -ENODEV) 14519969707SMartin Blumenstingl goto err_fini; 14619969707SMartin Blumenstingl } else { 14719969707SMartin Blumenstingl ldevfreq->regulators_opp_table = opp_table; 14819969707SMartin Blumenstingl } 14919969707SMartin Blumenstingl 15019969707SMartin Blumenstingl ret = dev_pm_opp_of_add_table(dev); 15119969707SMartin Blumenstingl if (ret) 15219969707SMartin Blumenstingl goto err_fini; 15319969707SMartin Blumenstingl 15419969707SMartin Blumenstingl lima_devfreq_reset(ldevfreq); 15519969707SMartin Blumenstingl 15619969707SMartin Blumenstingl cur_freq = clk_get_rate(ldev->clk_gpu); 15719969707SMartin Blumenstingl 15819969707SMartin Blumenstingl opp = devfreq_recommended_opp(dev, &cur_freq, 0); 15919969707SMartin Blumenstingl if (IS_ERR(opp)) { 16019969707SMartin Blumenstingl ret = PTR_ERR(opp); 16119969707SMartin Blumenstingl goto err_fini; 16219969707SMartin Blumenstingl } 16319969707SMartin Blumenstingl 16419969707SMartin Blumenstingl lima_devfreq_profile.initial_freq = cur_freq; 16519969707SMartin Blumenstingl dev_pm_opp_put(opp); 16619969707SMartin Blumenstingl 167*1d048afeSChristian Hewitt /* 168*1d048afeSChristian Hewitt * Setup default thresholds for the simple_ondemand governor. 169*1d048afeSChristian Hewitt * The values are chosen based on experiments. 170*1d048afeSChristian Hewitt */ 171*1d048afeSChristian Hewitt ldevfreq->gov_data.upthreshold = 30; 172*1d048afeSChristian Hewitt ldevfreq->gov_data.downdifferential = 5; 173*1d048afeSChristian Hewitt 17419969707SMartin Blumenstingl devfreq = devm_devfreq_add_device(dev, &lima_devfreq_profile, 175*1d048afeSChristian Hewitt DEVFREQ_GOV_SIMPLE_ONDEMAND, 176*1d048afeSChristian Hewitt &ldevfreq->gov_data); 17719969707SMartin Blumenstingl if (IS_ERR(devfreq)) { 17819969707SMartin Blumenstingl dev_err(dev, "Couldn't initialize GPU devfreq\n"); 17919969707SMartin Blumenstingl ret = PTR_ERR(devfreq); 18019969707SMartin Blumenstingl goto err_fini; 18119969707SMartin Blumenstingl } 18219969707SMartin Blumenstingl 18319969707SMartin Blumenstingl ldevfreq->devfreq = devfreq; 18419969707SMartin Blumenstingl 18519969707SMartin Blumenstingl cooling = of_devfreq_cooling_register(dev->of_node, devfreq); 18619969707SMartin Blumenstingl if (IS_ERR(cooling)) 18719969707SMartin Blumenstingl dev_info(dev, "Failed to register cooling device\n"); 18819969707SMartin Blumenstingl else 18919969707SMartin Blumenstingl ldevfreq->cooling = cooling; 19019969707SMartin Blumenstingl 19119969707SMartin Blumenstingl return 0; 19219969707SMartin Blumenstingl 19319969707SMartin Blumenstingl err_fini: 19419969707SMartin Blumenstingl lima_devfreq_fini(ldev); 19519969707SMartin Blumenstingl return ret; 19619969707SMartin Blumenstingl } 19719969707SMartin Blumenstingl 19819969707SMartin Blumenstingl void lima_devfreq_record_busy(struct lima_devfreq *devfreq) 19919969707SMartin Blumenstingl { 20019969707SMartin Blumenstingl unsigned long irqflags; 20119969707SMartin Blumenstingl 20219969707SMartin Blumenstingl if (!devfreq->devfreq) 20319969707SMartin Blumenstingl return; 20419969707SMartin Blumenstingl 20519969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags); 20619969707SMartin Blumenstingl 20719969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq); 20819969707SMartin Blumenstingl 20919969707SMartin Blumenstingl devfreq->busy_count++; 21019969707SMartin Blumenstingl 21119969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags); 21219969707SMartin Blumenstingl } 21319969707SMartin Blumenstingl 21419969707SMartin Blumenstingl void lima_devfreq_record_idle(struct lima_devfreq *devfreq) 21519969707SMartin Blumenstingl { 21619969707SMartin Blumenstingl unsigned long irqflags; 21719969707SMartin Blumenstingl 21819969707SMartin Blumenstingl if (!devfreq->devfreq) 21919969707SMartin Blumenstingl return; 22019969707SMartin Blumenstingl 22119969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags); 22219969707SMartin Blumenstingl 22319969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq); 22419969707SMartin Blumenstingl 22519969707SMartin Blumenstingl WARN_ON(--devfreq->busy_count < 0); 22619969707SMartin Blumenstingl 22719969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags); 22819969707SMartin Blumenstingl } 2294836cf04SQiang Yu 2304836cf04SQiang Yu int lima_devfreq_resume(struct lima_devfreq *devfreq) 2314836cf04SQiang Yu { 2324836cf04SQiang Yu unsigned long irqflags; 2334836cf04SQiang Yu 2344836cf04SQiang Yu if (!devfreq->devfreq) 2354836cf04SQiang Yu return 0; 2364836cf04SQiang Yu 2374836cf04SQiang Yu spin_lock_irqsave(&devfreq->lock, irqflags); 2384836cf04SQiang Yu 2394836cf04SQiang Yu lima_devfreq_reset(devfreq); 2404836cf04SQiang Yu 2414836cf04SQiang Yu spin_unlock_irqrestore(&devfreq->lock, irqflags); 2424836cf04SQiang Yu 2434836cf04SQiang Yu return devfreq_resume_device(devfreq->devfreq); 2444836cf04SQiang Yu } 2454836cf04SQiang Yu 2464836cf04SQiang Yu int lima_devfreq_suspend(struct lima_devfreq *devfreq) 2474836cf04SQiang Yu { 2484836cf04SQiang Yu if (!devfreq->devfreq) 2494836cf04SQiang Yu return 0; 2504836cf04SQiang Yu 2514836cf04SQiang Yu return devfreq_suspend_device(devfreq->devfreq); 2524836cf04SQiang Yu } 253