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 } 10419969707SMartin Blumenstingl 10519969707SMartin Blumenstingl int lima_devfreq_init(struct lima_device *ldev) 10619969707SMartin Blumenstingl { 10719969707SMartin Blumenstingl struct thermal_cooling_device *cooling; 1082ce216edSRobin Murphy struct device *dev = ldev->dev; 10919969707SMartin Blumenstingl struct devfreq *devfreq; 11019969707SMartin Blumenstingl struct lima_devfreq *ldevfreq = &ldev->devfreq; 11119969707SMartin Blumenstingl struct dev_pm_opp *opp; 11219969707SMartin Blumenstingl unsigned long cur_freq; 11319969707SMartin Blumenstingl int ret; 11487686cc8SViresh Kumar const char *regulator_names[] = { "mali", NULL }; 11519969707SMartin Blumenstingl 11619969707SMartin Blumenstingl if (!device_property_present(dev, "operating-points-v2")) 11719969707SMartin Blumenstingl /* Optional, continue without devfreq */ 11819969707SMartin Blumenstingl return 0; 11919969707SMartin Blumenstingl 12019969707SMartin Blumenstingl spin_lock_init(&ldevfreq->lock); 12119969707SMartin Blumenstingl 122*e17a025aSErico Nunes /* 123*e17a025aSErico Nunes * clkname is set separately so it is not affected by the optional 124*e17a025aSErico Nunes * regulator setting which may return error. 125*e17a025aSErico Nunes */ 126*e17a025aSErico Nunes ret = devm_pm_opp_set_clkname(dev, "core"); 127*e17a025aSErico Nunes if (ret) 128*e17a025aSErico Nunes return ret; 129*e17a025aSErico Nunes 130*e17a025aSErico Nunes ret = devm_pm_opp_set_regulators(dev, regulator_names); 131864a2701SYangtao Li if (ret) { 13219969707SMartin Blumenstingl /* Continue if the optional regulator is missing */ 13319969707SMartin Blumenstingl if (ret != -ENODEV) 134864a2701SYangtao Li return ret; 13519969707SMartin Blumenstingl } 13619969707SMartin Blumenstingl 137864a2701SYangtao Li ret = devm_pm_opp_of_add_table(dev); 13819969707SMartin Blumenstingl if (ret) 139864a2701SYangtao Li return ret; 14019969707SMartin Blumenstingl 14119969707SMartin Blumenstingl lima_devfreq_reset(ldevfreq); 14219969707SMartin Blumenstingl 14319969707SMartin Blumenstingl cur_freq = clk_get_rate(ldev->clk_gpu); 14419969707SMartin Blumenstingl 14519969707SMartin Blumenstingl opp = devfreq_recommended_opp(dev, &cur_freq, 0); 146864a2701SYangtao Li if (IS_ERR(opp)) 147864a2701SYangtao Li return PTR_ERR(opp); 14819969707SMartin Blumenstingl 14919969707SMartin Blumenstingl lima_devfreq_profile.initial_freq = cur_freq; 15019969707SMartin Blumenstingl dev_pm_opp_put(opp); 15119969707SMartin Blumenstingl 1521d048afeSChristian Hewitt /* 1531d048afeSChristian Hewitt * Setup default thresholds for the simple_ondemand governor. 1541d048afeSChristian Hewitt * The values are chosen based on experiments. 1551d048afeSChristian Hewitt */ 1561d048afeSChristian Hewitt ldevfreq->gov_data.upthreshold = 30; 1571d048afeSChristian Hewitt ldevfreq->gov_data.downdifferential = 5; 1581d048afeSChristian Hewitt 15919969707SMartin Blumenstingl devfreq = devm_devfreq_add_device(dev, &lima_devfreq_profile, 1601d048afeSChristian Hewitt DEVFREQ_GOV_SIMPLE_ONDEMAND, 1611d048afeSChristian Hewitt &ldevfreq->gov_data); 16219969707SMartin Blumenstingl if (IS_ERR(devfreq)) { 16319969707SMartin Blumenstingl dev_err(dev, "Couldn't initialize GPU devfreq\n"); 164864a2701SYangtao Li return PTR_ERR(devfreq); 16519969707SMartin Blumenstingl } 16619969707SMartin Blumenstingl 16719969707SMartin Blumenstingl ldevfreq->devfreq = devfreq; 16819969707SMartin Blumenstingl 16919969707SMartin Blumenstingl cooling = of_devfreq_cooling_register(dev->of_node, devfreq); 17019969707SMartin Blumenstingl if (IS_ERR(cooling)) 17119969707SMartin Blumenstingl dev_info(dev, "Failed to register cooling device\n"); 17219969707SMartin Blumenstingl else 17319969707SMartin Blumenstingl ldevfreq->cooling = cooling; 17419969707SMartin Blumenstingl 17519969707SMartin Blumenstingl return 0; 17619969707SMartin Blumenstingl } 17719969707SMartin Blumenstingl 17819969707SMartin Blumenstingl void lima_devfreq_record_busy(struct lima_devfreq *devfreq) 17919969707SMartin Blumenstingl { 18019969707SMartin Blumenstingl unsigned long irqflags; 18119969707SMartin Blumenstingl 18219969707SMartin Blumenstingl if (!devfreq->devfreq) 18319969707SMartin Blumenstingl return; 18419969707SMartin Blumenstingl 18519969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags); 18619969707SMartin Blumenstingl 18719969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq); 18819969707SMartin Blumenstingl 18919969707SMartin Blumenstingl devfreq->busy_count++; 19019969707SMartin Blumenstingl 19119969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags); 19219969707SMartin Blumenstingl } 19319969707SMartin Blumenstingl 19419969707SMartin Blumenstingl void lima_devfreq_record_idle(struct lima_devfreq *devfreq) 19519969707SMartin Blumenstingl { 19619969707SMartin Blumenstingl unsigned long irqflags; 19719969707SMartin Blumenstingl 19819969707SMartin Blumenstingl if (!devfreq->devfreq) 19919969707SMartin Blumenstingl return; 20019969707SMartin Blumenstingl 20119969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags); 20219969707SMartin Blumenstingl 20319969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq); 20419969707SMartin Blumenstingl 20519969707SMartin Blumenstingl WARN_ON(--devfreq->busy_count < 0); 20619969707SMartin Blumenstingl 20719969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags); 20819969707SMartin Blumenstingl } 2094836cf04SQiang Yu 2104836cf04SQiang Yu int lima_devfreq_resume(struct lima_devfreq *devfreq) 2114836cf04SQiang Yu { 2124836cf04SQiang Yu unsigned long irqflags; 2134836cf04SQiang Yu 2144836cf04SQiang Yu if (!devfreq->devfreq) 2154836cf04SQiang Yu return 0; 2164836cf04SQiang Yu 2174836cf04SQiang Yu spin_lock_irqsave(&devfreq->lock, irqflags); 2184836cf04SQiang Yu 2194836cf04SQiang Yu lima_devfreq_reset(devfreq); 2204836cf04SQiang Yu 2214836cf04SQiang Yu spin_unlock_irqrestore(&devfreq->lock, irqflags); 2224836cf04SQiang Yu 2234836cf04SQiang Yu return devfreq_resume_device(devfreq->devfreq); 2244836cf04SQiang Yu } 2254836cf04SQiang Yu 2264836cf04SQiang Yu int lima_devfreq_suspend(struct lima_devfreq *devfreq) 2274836cf04SQiang Yu { 2284836cf04SQiang Yu if (!devfreq->devfreq) 2294836cf04SQiang Yu return 0; 2304836cf04SQiang Yu 2314836cf04SQiang Yu return devfreq_suspend_device(devfreq->devfreq); 2324836cf04SQiang Yu } 233