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 int err; 3919969707SMartin Blumenstingl 4019969707SMartin Blumenstingl opp = devfreq_recommended_opp(dev, freq, flags); 4119969707SMartin Blumenstingl if (IS_ERR(opp)) 4219969707SMartin Blumenstingl return PTR_ERR(opp); 4319969707SMartin Blumenstingl dev_pm_opp_put(opp); 4419969707SMartin Blumenstingl 4519969707SMartin Blumenstingl err = dev_pm_opp_set_rate(dev, *freq); 4619969707SMartin Blumenstingl if (err) 4719969707SMartin Blumenstingl return err; 4819969707SMartin Blumenstingl 4919969707SMartin Blumenstingl return 0; 5019969707SMartin Blumenstingl } 5119969707SMartin Blumenstingl 5219969707SMartin Blumenstingl static void lima_devfreq_reset(struct lima_devfreq *devfreq) 5319969707SMartin Blumenstingl { 5419969707SMartin Blumenstingl devfreq->busy_time = 0; 5519969707SMartin Blumenstingl devfreq->idle_time = 0; 5619969707SMartin Blumenstingl devfreq->time_last_update = ktime_get(); 5719969707SMartin Blumenstingl } 5819969707SMartin Blumenstingl 5919969707SMartin Blumenstingl static int lima_devfreq_get_dev_status(struct device *dev, 6019969707SMartin Blumenstingl struct devfreq_dev_status *status) 6119969707SMartin Blumenstingl { 6219969707SMartin Blumenstingl struct lima_device *ldev = dev_get_drvdata(dev); 6319969707SMartin Blumenstingl struct lima_devfreq *devfreq = &ldev->devfreq; 6419969707SMartin Blumenstingl unsigned long irqflags; 6519969707SMartin Blumenstingl 6619969707SMartin Blumenstingl status->current_frequency = clk_get_rate(ldev->clk_gpu); 6719969707SMartin Blumenstingl 6819969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags); 6919969707SMartin Blumenstingl 7019969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq); 7119969707SMartin Blumenstingl 7219969707SMartin Blumenstingl status->total_time = ktime_to_ns(ktime_add(devfreq->busy_time, 7319969707SMartin Blumenstingl devfreq->idle_time)); 7419969707SMartin Blumenstingl status->busy_time = ktime_to_ns(devfreq->busy_time); 7519969707SMartin Blumenstingl 7619969707SMartin Blumenstingl lima_devfreq_reset(devfreq); 7719969707SMartin Blumenstingl 7819969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags); 7919969707SMartin Blumenstingl 8019969707SMartin Blumenstingl dev_dbg(ldev->dev, "busy %lu total %lu %lu %% freq %lu MHz\n", 8119969707SMartin Blumenstingl status->busy_time, status->total_time, 8219969707SMartin Blumenstingl status->busy_time / (status->total_time / 100), 8319969707SMartin Blumenstingl status->current_frequency / 1000 / 1000); 8419969707SMartin Blumenstingl 8519969707SMartin Blumenstingl return 0; 8619969707SMartin Blumenstingl } 8719969707SMartin Blumenstingl 8819969707SMartin Blumenstingl static struct devfreq_dev_profile lima_devfreq_profile = { 8919969707SMartin Blumenstingl .polling_ms = 50, /* ~3 frames */ 9019969707SMartin Blumenstingl .target = lima_devfreq_target, 9119969707SMartin Blumenstingl .get_dev_status = lima_devfreq_get_dev_status, 9219969707SMartin Blumenstingl }; 9319969707SMartin Blumenstingl 9419969707SMartin Blumenstingl void lima_devfreq_fini(struct lima_device *ldev) 9519969707SMartin Blumenstingl { 9619969707SMartin Blumenstingl struct lima_devfreq *devfreq = &ldev->devfreq; 9719969707SMartin Blumenstingl 9819969707SMartin Blumenstingl if (devfreq->cooling) { 9919969707SMartin Blumenstingl devfreq_cooling_unregister(devfreq->cooling); 10019969707SMartin Blumenstingl devfreq->cooling = NULL; 10119969707SMartin Blumenstingl } 10219969707SMartin Blumenstingl 10319969707SMartin Blumenstingl if (devfreq->devfreq) { 104*2ce216edSRobin Murphy devm_devfreq_remove_device(ldev->dev, devfreq->devfreq); 10519969707SMartin Blumenstingl devfreq->devfreq = NULL; 10619969707SMartin Blumenstingl } 10719969707SMartin Blumenstingl 10819969707SMartin Blumenstingl if (devfreq->opp_of_table_added) { 109*2ce216edSRobin Murphy dev_pm_opp_of_remove_table(ldev->dev); 11019969707SMartin Blumenstingl devfreq->opp_of_table_added = false; 11119969707SMartin Blumenstingl } 11219969707SMartin Blumenstingl 11319969707SMartin Blumenstingl if (devfreq->regulators_opp_table) { 11419969707SMartin Blumenstingl dev_pm_opp_put_regulators(devfreq->regulators_opp_table); 11519969707SMartin Blumenstingl devfreq->regulators_opp_table = NULL; 11619969707SMartin Blumenstingl } 11719969707SMartin Blumenstingl 11819969707SMartin Blumenstingl if (devfreq->clkname_opp_table) { 11919969707SMartin Blumenstingl dev_pm_opp_put_clkname(devfreq->clkname_opp_table); 12019969707SMartin Blumenstingl devfreq->clkname_opp_table = NULL; 12119969707SMartin Blumenstingl } 12219969707SMartin Blumenstingl } 12319969707SMartin Blumenstingl 12419969707SMartin Blumenstingl int lima_devfreq_init(struct lima_device *ldev) 12519969707SMartin Blumenstingl { 12619969707SMartin Blumenstingl struct thermal_cooling_device *cooling; 127*2ce216edSRobin Murphy struct device *dev = ldev->dev; 12819969707SMartin Blumenstingl struct opp_table *opp_table; 12919969707SMartin Blumenstingl struct devfreq *devfreq; 13019969707SMartin Blumenstingl struct lima_devfreq *ldevfreq = &ldev->devfreq; 13119969707SMartin Blumenstingl struct dev_pm_opp *opp; 13219969707SMartin Blumenstingl unsigned long cur_freq; 13319969707SMartin Blumenstingl int ret; 13419969707SMartin Blumenstingl 13519969707SMartin Blumenstingl if (!device_property_present(dev, "operating-points-v2")) 13619969707SMartin Blumenstingl /* Optional, continue without devfreq */ 13719969707SMartin Blumenstingl return 0; 13819969707SMartin Blumenstingl 13919969707SMartin Blumenstingl spin_lock_init(&ldevfreq->lock); 14019969707SMartin Blumenstingl 14119969707SMartin Blumenstingl opp_table = dev_pm_opp_set_clkname(dev, "core"); 14219969707SMartin Blumenstingl if (IS_ERR(opp_table)) { 14319969707SMartin Blumenstingl ret = PTR_ERR(opp_table); 14419969707SMartin Blumenstingl goto err_fini; 14519969707SMartin Blumenstingl } 14619969707SMartin Blumenstingl 14719969707SMartin Blumenstingl ldevfreq->clkname_opp_table = opp_table; 14819969707SMartin Blumenstingl 14919969707SMartin Blumenstingl opp_table = dev_pm_opp_set_regulators(dev, 15019969707SMartin Blumenstingl (const char *[]){ "mali" }, 15119969707SMartin Blumenstingl 1); 15219969707SMartin Blumenstingl if (IS_ERR(opp_table)) { 15319969707SMartin Blumenstingl ret = PTR_ERR(opp_table); 15419969707SMartin Blumenstingl 15519969707SMartin Blumenstingl /* Continue if the optional regulator is missing */ 15619969707SMartin Blumenstingl if (ret != -ENODEV) 15719969707SMartin Blumenstingl goto err_fini; 15819969707SMartin Blumenstingl } else { 15919969707SMartin Blumenstingl ldevfreq->regulators_opp_table = opp_table; 16019969707SMartin Blumenstingl } 16119969707SMartin Blumenstingl 16219969707SMartin Blumenstingl ret = dev_pm_opp_of_add_table(dev); 16319969707SMartin Blumenstingl if (ret) 16419969707SMartin Blumenstingl goto err_fini; 16519969707SMartin Blumenstingl ldevfreq->opp_of_table_added = true; 16619969707SMartin Blumenstingl 16719969707SMartin Blumenstingl lima_devfreq_reset(ldevfreq); 16819969707SMartin Blumenstingl 16919969707SMartin Blumenstingl cur_freq = clk_get_rate(ldev->clk_gpu); 17019969707SMartin Blumenstingl 17119969707SMartin Blumenstingl opp = devfreq_recommended_opp(dev, &cur_freq, 0); 17219969707SMartin Blumenstingl if (IS_ERR(opp)) { 17319969707SMartin Blumenstingl ret = PTR_ERR(opp); 17419969707SMartin Blumenstingl goto err_fini; 17519969707SMartin Blumenstingl } 17619969707SMartin Blumenstingl 17719969707SMartin Blumenstingl lima_devfreq_profile.initial_freq = cur_freq; 17819969707SMartin Blumenstingl dev_pm_opp_put(opp); 17919969707SMartin Blumenstingl 18019969707SMartin Blumenstingl devfreq = devm_devfreq_add_device(dev, &lima_devfreq_profile, 18119969707SMartin Blumenstingl DEVFREQ_GOV_SIMPLE_ONDEMAND, NULL); 18219969707SMartin Blumenstingl if (IS_ERR(devfreq)) { 18319969707SMartin Blumenstingl dev_err(dev, "Couldn't initialize GPU devfreq\n"); 18419969707SMartin Blumenstingl ret = PTR_ERR(devfreq); 18519969707SMartin Blumenstingl goto err_fini; 18619969707SMartin Blumenstingl } 18719969707SMartin Blumenstingl 18819969707SMartin Blumenstingl ldevfreq->devfreq = devfreq; 18919969707SMartin Blumenstingl 19019969707SMartin Blumenstingl cooling = of_devfreq_cooling_register(dev->of_node, devfreq); 19119969707SMartin Blumenstingl if (IS_ERR(cooling)) 19219969707SMartin Blumenstingl dev_info(dev, "Failed to register cooling device\n"); 19319969707SMartin Blumenstingl else 19419969707SMartin Blumenstingl ldevfreq->cooling = cooling; 19519969707SMartin Blumenstingl 19619969707SMartin Blumenstingl return 0; 19719969707SMartin Blumenstingl 19819969707SMartin Blumenstingl err_fini: 19919969707SMartin Blumenstingl lima_devfreq_fini(ldev); 20019969707SMartin Blumenstingl return ret; 20119969707SMartin Blumenstingl } 20219969707SMartin Blumenstingl 20319969707SMartin Blumenstingl void lima_devfreq_record_busy(struct lima_devfreq *devfreq) 20419969707SMartin Blumenstingl { 20519969707SMartin Blumenstingl unsigned long irqflags; 20619969707SMartin Blumenstingl 20719969707SMartin Blumenstingl if (!devfreq->devfreq) 20819969707SMartin Blumenstingl return; 20919969707SMartin Blumenstingl 21019969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags); 21119969707SMartin Blumenstingl 21219969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq); 21319969707SMartin Blumenstingl 21419969707SMartin Blumenstingl devfreq->busy_count++; 21519969707SMartin Blumenstingl 21619969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags); 21719969707SMartin Blumenstingl } 21819969707SMartin Blumenstingl 21919969707SMartin Blumenstingl void lima_devfreq_record_idle(struct lima_devfreq *devfreq) 22019969707SMartin Blumenstingl { 22119969707SMartin Blumenstingl unsigned long irqflags; 22219969707SMartin Blumenstingl 22319969707SMartin Blumenstingl if (!devfreq->devfreq) 22419969707SMartin Blumenstingl return; 22519969707SMartin Blumenstingl 22619969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags); 22719969707SMartin Blumenstingl 22819969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq); 22919969707SMartin Blumenstingl 23019969707SMartin Blumenstingl WARN_ON(--devfreq->busy_count < 0); 23119969707SMartin Blumenstingl 23219969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags); 23319969707SMartin Blumenstingl } 234