| 1 | |
| 2 | |
| 3 | * Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved.
|
| 4 |
|
| 5 |
|
| 6 | #define dev_fmt(fmt) "tegra-soc: " fmt
|
| 7 |
|
| 8 | #include <linux/clk.h>
|
| 9 | #include <linux/device.h>
|
| 10 | #include <linux/export.h>
|
| 11 | #include <linux/of.h>
|
| 12 | #include <linux/pm_opp.h>
|
| 13 | #include <linux/pm_runtime.h>
|
| 14 |
|
| 15 | #include <soc/tegra/common.h>
|
| 16 | #include <soc/tegra/fuse.h>
|
| 17 |
|
| 18 | static const struct of_device_id tegra_machine_match[] = {
|
| 19 | { .compatible = "nvidia,tegra20", },
|
| 20 | { .compatible = "nvidia,tegra30", },
|
| 21 | { .compatible = "nvidia,tegra114", },
|
| 22 | { .compatible = "nvidia,tegra124", },
|
| 23 | { .compatible = "nvidia,tegra132", },
|
| 24 | { .compatible = "nvidia,tegra210", },
|
| 25 | { }
|
| 26 | };
|
| 27 |
|
| 28 | bool soc_is_tegra(void)
|
| 29 | {
|
| 30 | return of_machine_device_match(tegra_machine_match);
|
| 31 | }
|
| 32 |
|
| 33 | static int tegra_core_dev_init_opp_state(struct device *dev)
|
| 34 | {
|
| 35 | unsigned long rate;
|
| 36 | struct clk *clk;
|
| 37 | bool rpm_enabled;
|
| 38 | int err;
|
| 39 |
|
| 40 | clk = devm_clk_get(dev, NULL);
|
| 41 | if (IS_ERR(clk)) {
|
| 42 | dev_err(dev, "failed to get clk: %pe\n", clk);
|
| 43 | return PTR_ERR(clk);
|
| 44 | }
|
| 45 |
|
| 46 | rate = clk_get_rate(clk);
|
| 47 | if (!rate) {
|
| 48 | dev_err(dev, "failed to get clk rate\n");
|
| 49 | return -EINVAL;
|
| 50 | }
|
| 51 |
|
| 52 | |
| 53 | * Runtime PM of the device must be enabled in order to set up
|
| 54 | * GENPD's performance properly because GENPD core checks whether
|
| 55 | * device is suspended and this check doesn't work while RPM is
|
| 56 | * disabled. This makes sure the OPP vote below gets cached in
|
| 57 | * GENPD for the device. Instead, the vote is done the next time
|
| 58 | * the device gets runtime resumed.
|
| 59 |
|
| 60 | rpm_enabled = pm_runtime_enabled(dev);
|
| 61 | if (!rpm_enabled)
|
| 62 | pm_runtime_enable(dev);
|
| 63 |
|
| 64 |
|
| 65 | if (!pm_runtime_enabled(dev)) {
|
| 66 | dev_WARN(dev, "failed to enable runtime PM\n");
|
| 67 | pm_runtime_disable(dev);
|
| 68 | return -EINVAL;
|
| 69 | }
|
| 70 |
|
| 71 |
|
| 72 | err = dev_pm_opp_set_rate(dev, rate);
|
| 73 |
|
| 74 | if (!rpm_enabled)
|
| 75 | pm_runtime_disable(dev);
|
| 76 |
|
| 77 | if (err) {
|
| 78 | dev_err(dev, "failed to initialize OPP clock: %d\n", err);
|
| 79 | return err;
|
| 80 | }
|
| 81 |
|
| 82 | return 0;
|
| 83 | }
|
| 84 |
|
| 85 | |
| 86 | * devm_tegra_core_dev_init_opp_table() - initialize OPP table
|
| 87 | * @dev: device for which OPP table is initialized
|
| 88 | * @params: pointer to the OPP table configuration
|
| 89 | *
|
| 90 | * This function will initialize OPP table and sync OPP state of a Tegra SoC
|
| 91 | * core device.
|
| 92 | *
|
| 93 | * Return: 0 on success or errorno.
|
| 94 |
|
| 95 | int devm_tegra_core_dev_init_opp_table(struct device *dev,
|
| 96 | struct tegra_core_opp_params *params)
|
| 97 | {
|
| 98 | u32 hw_version;
|
| 99 | int err;
|
| 100 | |
| 101 | * The clk's connection id to set is NULL and this is a NULL terminated
|
| 102 | * array, hence two NULL entries.
|
| 103 |
|
| 104 | const char *clk_names[] = { NULL, NULL };
|
| 105 | struct dev_pm_opp_config config = {
|
| 106 | |
| 107 | * For some devices we don't have any OPP table in the DT, and
|
| 108 | * in order to use the same code path for all the devices, we
|
| 109 | * create a dummy OPP table for them via this. The dummy OPP
|
| 110 | * table is only capable of doing clk_set_rate() on invocation
|
| 111 | * of dev_pm_opp_set_rate() and doesn't provide any other
|
| 112 | * functionality.
|
| 113 |
|
| 114 | .clk_names = clk_names,
|
| 115 | };
|
| 116 |
|
| 117 | if (of_machine_is_compatible("nvidia,tegra20")) {
|
| 118 | hw_version = BIT(tegra_sku_info.soc_process_id);
|
| 119 | config.supported_hw = &hw_version;
|
| 120 | config.supported_hw_count = 1;
|
| 121 | } else if (of_machine_is_compatible("nvidia,tegra30") ||
|
| 122 | of_machine_is_compatible("nvidia,tegra114")) {
|
| 123 | hw_version = BIT(tegra_sku_info.soc_speedo_id);
|
| 124 | config.supported_hw = &hw_version;
|
| 125 | config.supported_hw_count = 1;
|
| 126 | }
|
| 127 |
|
| 128 | err = devm_pm_opp_set_config(dev, &config);
|
| 129 | if (err) {
|
| 130 | dev_err(dev, "failed to set OPP config: %d\n", err);
|
| 131 | return err;
|
| 132 | }
|
| 133 |
|
| 134 | |
| 135 | * Tegra124+ doesn't support OPP yet, return early for pre-Tegra124
|
| 136 | * case.
|
| 137 |
|
| 138 | if (!config.supported_hw)
|
| 139 | return -ENODEV;
|
| 140 |
|
| 141 | |
| 142 | * Older device-trees have an empty OPP table, we will get
|
| 143 | * -ENODEV from devm_pm_opp_of_add_table() in this case.
|
| 144 |
|
| 145 | err = devm_pm_opp_of_add_table(dev);
|
| 146 | if (err) {
|
| 147 | if (err != -ENODEV)
|
| 148 | dev_err(dev, "failed to add OPP table: %d\n", err);
|
| 149 |
|
| 150 | return err;
|
| 151 | }
|
| 152 |
|
| 153 | if (params->init_state) {
|
| 154 | err = tegra_core_dev_init_opp_state(dev);
|
| 155 | if (err)
|
| 156 | return err;
|
| 157 | }
|
| 158 |
|
| 159 | return 0;
|
| 160 | }
|
| 161 | EXPORT_SYMBOL_GPL(devm_tegra_core_dev_init_opp_table);
|