| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * NVIDIA Tegra20 devfreq driver |
| * |
| * Copyright (C) 2019 GRATE-DRIVER project |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/devfreq.h> |
| #include <linux/io.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/of_device.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_opp.h> |
| #include <linux/slab.h> |
| |
| #include <soc/tegra/mc.h> |
| |
| #include "governor.h" |
| |
| #define MC_STAT_CONTROL 0x90 |
| #define MC_STAT_EMC_CLOCK_LIMIT 0xa0 |
| #define MC_STAT_EMC_CLOCKS 0xa4 |
| #define MC_STAT_EMC_CONTROL 0xa8 |
| #define MC_STAT_EMC_COUNT 0xb8 |
| |
| #define EMC_GATHER_CLEAR (1 << 8) |
| #define EMC_GATHER_ENABLE (3 << 8) |
| |
| struct tegra_devfreq { |
| struct devfreq *devfreq; |
| struct clk *emc_clock; |
| void __iomem *regs; |
| }; |
| |
| static int tegra_devfreq_target(struct device *dev, unsigned long *freq, |
| u32 flags) |
| { |
| struct tegra_devfreq *tegra = dev_get_drvdata(dev); |
| struct devfreq *devfreq = tegra->devfreq; |
| struct dev_pm_opp *opp; |
| unsigned long rate; |
| int err; |
| |
| opp = devfreq_recommended_opp(dev, freq, flags); |
| if (IS_ERR(opp)) |
| return PTR_ERR(opp); |
| |
| rate = dev_pm_opp_get_freq(opp); |
| dev_pm_opp_put(opp); |
| |
| err = clk_set_min_rate(tegra->emc_clock, rate); |
| if (err) |
| return err; |
| |
| err = clk_set_rate(tegra->emc_clock, 0); |
| if (err) |
| goto restore_min_rate; |
| |
| return 0; |
| |
| restore_min_rate: |
| clk_set_min_rate(tegra->emc_clock, devfreq->previous_freq); |
| |
| return err; |
| } |
| |
| static int tegra_devfreq_get_dev_status(struct device *dev, |
| struct devfreq_dev_status *stat) |
| { |
| struct tegra_devfreq *tegra = dev_get_drvdata(dev); |
| |
| /* |
| * EMC_COUNT returns number of memory events, that number is lower |
| * than the number of clocks. Conversion ratio of 1/8 results in a |
| * bit higher bandwidth than actually needed, it is good enough for |
| * the time being because drivers don't support requesting minimum |
| * needed memory bandwidth yet. |
| * |
| * TODO: adjust the ratio value once relevant drivers will support |
| * memory bandwidth management. |
| */ |
| stat->busy_time = readl_relaxed(tegra->regs + MC_STAT_EMC_COUNT); |
| stat->total_time = readl_relaxed(tegra->regs + MC_STAT_EMC_CLOCKS) / 8; |
| stat->current_frequency = clk_get_rate(tegra->emc_clock); |
| |
| writel_relaxed(EMC_GATHER_CLEAR, tegra->regs + MC_STAT_CONTROL); |
| writel_relaxed(EMC_GATHER_ENABLE, tegra->regs + MC_STAT_CONTROL); |
| |
| return 0; |
| } |
| |
| static struct devfreq_dev_profile tegra_devfreq_profile = { |
| .polling_ms = 500, |
| .target = tegra_devfreq_target, |
| .get_dev_status = tegra_devfreq_get_dev_status, |
| }; |
| |
| static struct tegra_mc *tegra_get_memory_controller(void) |
| { |
| struct platform_device *pdev; |
| struct device_node *np; |
| struct tegra_mc *mc; |
| |
| np = of_find_compatible_node(NULL, NULL, "nvidia,tegra20-mc-gart"); |
| if (!np) |
| return ERR_PTR(-ENOENT); |
| |
| pdev = of_find_device_by_node(np); |
| of_node_put(np); |
| if (!pdev) |
| return ERR_PTR(-ENODEV); |
| |
| mc = platform_get_drvdata(pdev); |
| if (!mc) |
| return ERR_PTR(-EPROBE_DEFER); |
| |
| return mc; |
| } |
| |
| static int tegra_devfreq_probe(struct platform_device *pdev) |
| { |
| struct tegra_devfreq *tegra; |
| struct tegra_mc *mc; |
| unsigned long max_rate; |
| unsigned long rate; |
| int err; |
| |
| mc = tegra_get_memory_controller(); |
| if (IS_ERR(mc)) { |
| err = PTR_ERR(mc); |
| dev_err(&pdev->dev, "failed to get memory controller: %d\n", |
| err); |
| return err; |
| } |
| |
| tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); |
| if (!tegra) |
| return -ENOMEM; |
| |
| /* EMC is a system-critical clock that is always enabled */ |
| tegra->emc_clock = devm_clk_get(&pdev->dev, "emc"); |
| if (IS_ERR(tegra->emc_clock)) { |
| err = PTR_ERR(tegra->emc_clock); |
| dev_err(&pdev->dev, "failed to get emc clock: %d\n", err); |
| return err; |
| } |
| |
| tegra->regs = mc->regs; |
| |
| max_rate = clk_round_rate(tegra->emc_clock, ULONG_MAX); |
| |
| for (rate = 0; rate <= max_rate; rate++) { |
| rate = clk_round_rate(tegra->emc_clock, rate); |
| |
| err = dev_pm_opp_add(&pdev->dev, rate, 0); |
| if (err) { |
| dev_err(&pdev->dev, "failed to add opp: %d\n", err); |
| goto remove_opps; |
| } |
| } |
| |
| /* |
| * Reset statistic gathers state, select global bandwidth for the |
| * statistics collection mode and set clocks counter saturation |
| * limit to maximum. |
| */ |
| writel_relaxed(0x00000000, tegra->regs + MC_STAT_CONTROL); |
| writel_relaxed(0x00000000, tegra->regs + MC_STAT_EMC_CONTROL); |
| writel_relaxed(0xffffffff, tegra->regs + MC_STAT_EMC_CLOCK_LIMIT); |
| |
| platform_set_drvdata(pdev, tegra); |
| |
| tegra->devfreq = devfreq_add_device(&pdev->dev, &tegra_devfreq_profile, |
| DEVFREQ_GOV_SIMPLE_ONDEMAND, NULL); |
| if (IS_ERR(tegra->devfreq)) { |
| err = PTR_ERR(tegra->devfreq); |
| goto remove_opps; |
| } |
| |
| return 0; |
| |
| remove_opps: |
| dev_pm_opp_remove_all_dynamic(&pdev->dev); |
| |
| return err; |
| } |
| |
| static int tegra_devfreq_remove(struct platform_device *pdev) |
| { |
| struct tegra_devfreq *tegra = platform_get_drvdata(pdev); |
| |
| devfreq_remove_device(tegra->devfreq); |
| dev_pm_opp_remove_all_dynamic(&pdev->dev); |
| |
| return 0; |
| } |
| |
| static struct platform_driver tegra_devfreq_driver = { |
| .probe = tegra_devfreq_probe, |
| .remove = tegra_devfreq_remove, |
| .driver = { |
| .name = "tegra20-devfreq", |
| }, |
| }; |
| module_platform_driver(tegra_devfreq_driver); |
| |
| MODULE_ALIAS("platform:tegra20-devfreq"); |
| MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>"); |
| MODULE_DESCRIPTION("NVIDIA Tegra20 devfreq driver"); |
| MODULE_LICENSE("GPL v2"); |