Dmitry Osipenko | d196175 | 2019-05-02 02:38:15 +0300 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * NVIDIA Tegra20 devfreq driver |
| 4 | * |
| 5 | * Copyright (C) 2019 GRATE-DRIVER project |
| 6 | */ |
| 7 | |
| 8 | #include <linux/clk.h> |
| 9 | #include <linux/devfreq.h> |
| 10 | #include <linux/io.h> |
| 11 | #include <linux/kernel.h> |
| 12 | #include <linux/module.h> |
| 13 | #include <linux/of_device.h> |
| 14 | #include <linux/platform_device.h> |
| 15 | #include <linux/pm_opp.h> |
| 16 | #include <linux/slab.h> |
| 17 | |
| 18 | #include <soc/tegra/mc.h> |
| 19 | |
| 20 | #include "governor.h" |
| 21 | |
| 22 | #define MC_STAT_CONTROL 0x90 |
| 23 | #define MC_STAT_EMC_CLOCK_LIMIT 0xa0 |
| 24 | #define MC_STAT_EMC_CLOCKS 0xa4 |
| 25 | #define MC_STAT_EMC_CONTROL 0xa8 |
| 26 | #define MC_STAT_EMC_COUNT 0xb8 |
| 27 | |
| 28 | #define EMC_GATHER_CLEAR (1 << 8) |
| 29 | #define EMC_GATHER_ENABLE (3 << 8) |
| 30 | |
| 31 | struct tegra_devfreq { |
| 32 | struct devfreq *devfreq; |
| 33 | struct clk *emc_clock; |
| 34 | void __iomem *regs; |
| 35 | }; |
| 36 | |
| 37 | static int tegra_devfreq_target(struct device *dev, unsigned long *freq, |
| 38 | u32 flags) |
| 39 | { |
| 40 | struct tegra_devfreq *tegra = dev_get_drvdata(dev); |
| 41 | struct devfreq *devfreq = tegra->devfreq; |
| 42 | struct dev_pm_opp *opp; |
| 43 | unsigned long rate; |
| 44 | int err; |
| 45 | |
| 46 | opp = devfreq_recommended_opp(dev, freq, flags); |
| 47 | if (IS_ERR(opp)) |
| 48 | return PTR_ERR(opp); |
| 49 | |
| 50 | rate = dev_pm_opp_get_freq(opp); |
| 51 | dev_pm_opp_put(opp); |
| 52 | |
| 53 | err = clk_set_min_rate(tegra->emc_clock, rate); |
| 54 | if (err) |
| 55 | return err; |
| 56 | |
| 57 | err = clk_set_rate(tegra->emc_clock, 0); |
| 58 | if (err) |
| 59 | goto restore_min_rate; |
| 60 | |
| 61 | return 0; |
| 62 | |
| 63 | restore_min_rate: |
| 64 | clk_set_min_rate(tegra->emc_clock, devfreq->previous_freq); |
| 65 | |
| 66 | return err; |
| 67 | } |
| 68 | |
| 69 | static int tegra_devfreq_get_dev_status(struct device *dev, |
| 70 | struct devfreq_dev_status *stat) |
| 71 | { |
| 72 | struct tegra_devfreq *tegra = dev_get_drvdata(dev); |
| 73 | |
| 74 | /* |
| 75 | * EMC_COUNT returns number of memory events, that number is lower |
| 76 | * than the number of clocks. Conversion ratio of 1/8 results in a |
| 77 | * bit higher bandwidth than actually needed, it is good enough for |
| 78 | * the time being because drivers don't support requesting minimum |
| 79 | * needed memory bandwidth yet. |
| 80 | * |
| 81 | * TODO: adjust the ratio value once relevant drivers will support |
| 82 | * memory bandwidth management. |
| 83 | */ |
| 84 | stat->busy_time = readl_relaxed(tegra->regs + MC_STAT_EMC_COUNT); |
| 85 | stat->total_time = readl_relaxed(tegra->regs + MC_STAT_EMC_CLOCKS) / 8; |
| 86 | stat->current_frequency = clk_get_rate(tegra->emc_clock); |
| 87 | |
| 88 | writel_relaxed(EMC_GATHER_CLEAR, tegra->regs + MC_STAT_CONTROL); |
| 89 | writel_relaxed(EMC_GATHER_ENABLE, tegra->regs + MC_STAT_CONTROL); |
| 90 | |
| 91 | return 0; |
| 92 | } |
| 93 | |
| 94 | static struct devfreq_dev_profile tegra_devfreq_profile = { |
| 95 | .polling_ms = 500, |
| 96 | .target = tegra_devfreq_target, |
| 97 | .get_dev_status = tegra_devfreq_get_dev_status, |
| 98 | }; |
| 99 | |
| 100 | static struct tegra_mc *tegra_get_memory_controller(void) |
| 101 | { |
| 102 | struct platform_device *pdev; |
| 103 | struct device_node *np; |
| 104 | struct tegra_mc *mc; |
| 105 | |
| 106 | np = of_find_compatible_node(NULL, NULL, "nvidia,tegra20-mc-gart"); |
| 107 | if (!np) |
| 108 | return ERR_PTR(-ENOENT); |
| 109 | |
| 110 | pdev = of_find_device_by_node(np); |
| 111 | of_node_put(np); |
| 112 | if (!pdev) |
| 113 | return ERR_PTR(-ENODEV); |
| 114 | |
| 115 | mc = platform_get_drvdata(pdev); |
| 116 | if (!mc) |
| 117 | return ERR_PTR(-EPROBE_DEFER); |
| 118 | |
| 119 | return mc; |
| 120 | } |
| 121 | |
| 122 | static int tegra_devfreq_probe(struct platform_device *pdev) |
| 123 | { |
| 124 | struct tegra_devfreq *tegra; |
| 125 | struct tegra_mc *mc; |
| 126 | unsigned long max_rate; |
| 127 | unsigned long rate; |
| 128 | int err; |
| 129 | |
| 130 | mc = tegra_get_memory_controller(); |
| 131 | if (IS_ERR(mc)) { |
| 132 | err = PTR_ERR(mc); |
| 133 | dev_err(&pdev->dev, "failed to get memory controller: %d\n", |
| 134 | err); |
| 135 | return err; |
| 136 | } |
| 137 | |
| 138 | tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); |
| 139 | if (!tegra) |
| 140 | return -ENOMEM; |
| 141 | |
| 142 | /* EMC is a system-critical clock that is always enabled */ |
| 143 | tegra->emc_clock = devm_clk_get(&pdev->dev, "emc"); |
| 144 | if (IS_ERR(tegra->emc_clock)) { |
| 145 | err = PTR_ERR(tegra->emc_clock); |
| 146 | dev_err(&pdev->dev, "failed to get emc clock: %d\n", err); |
| 147 | return err; |
| 148 | } |
| 149 | |
| 150 | tegra->regs = mc->regs; |
| 151 | |
| 152 | max_rate = clk_round_rate(tegra->emc_clock, ULONG_MAX); |
| 153 | |
| 154 | for (rate = 0; rate <= max_rate; rate++) { |
| 155 | rate = clk_round_rate(tegra->emc_clock, rate); |
| 156 | |
| 157 | err = dev_pm_opp_add(&pdev->dev, rate, 0); |
| 158 | if (err) { |
| 159 | dev_err(&pdev->dev, "failed to add opp: %d\n", err); |
| 160 | goto remove_opps; |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | /* |
| 165 | * Reset statistic gathers state, select global bandwidth for the |
| 166 | * statistics collection mode and set clocks counter saturation |
| 167 | * limit to maximum. |
| 168 | */ |
| 169 | writel_relaxed(0x00000000, tegra->regs + MC_STAT_CONTROL); |
| 170 | writel_relaxed(0x00000000, tegra->regs + MC_STAT_EMC_CONTROL); |
| 171 | writel_relaxed(0xffffffff, tegra->regs + MC_STAT_EMC_CLOCK_LIMIT); |
| 172 | |
| 173 | platform_set_drvdata(pdev, tegra); |
| 174 | |
| 175 | tegra->devfreq = devfreq_add_device(&pdev->dev, &tegra_devfreq_profile, |
| 176 | DEVFREQ_GOV_SIMPLE_ONDEMAND, NULL); |
| 177 | if (IS_ERR(tegra->devfreq)) { |
| 178 | err = PTR_ERR(tegra->devfreq); |
| 179 | goto remove_opps; |
| 180 | } |
| 181 | |
| 182 | return 0; |
| 183 | |
| 184 | remove_opps: |
| 185 | dev_pm_opp_remove_all_dynamic(&pdev->dev); |
| 186 | |
| 187 | return err; |
| 188 | } |
| 189 | |
| 190 | static int tegra_devfreq_remove(struct platform_device *pdev) |
| 191 | { |
| 192 | struct tegra_devfreq *tegra = platform_get_drvdata(pdev); |
| 193 | |
| 194 | devfreq_remove_device(tegra->devfreq); |
| 195 | dev_pm_opp_remove_all_dynamic(&pdev->dev); |
| 196 | |
| 197 | return 0; |
| 198 | } |
| 199 | |
| 200 | static struct platform_driver tegra_devfreq_driver = { |
| 201 | .probe = tegra_devfreq_probe, |
| 202 | .remove = tegra_devfreq_remove, |
| 203 | .driver = { |
| 204 | .name = "tegra20-devfreq", |
| 205 | }, |
| 206 | }; |
| 207 | module_platform_driver(tegra_devfreq_driver); |
| 208 | |
| 209 | MODULE_ALIAS("platform:tegra20-devfreq"); |
| 210 | MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>"); |
| 211 | MODULE_DESCRIPTION("NVIDIA Tegra20 devfreq driver"); |
| 212 | MODULE_LICENSE("GPL v2"); |