1 | // SPDX-License-Identifier: GPL-2.0-only |
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 | const struct of_device_id *match; |
31 | struct device_node *root; |
32 | |
33 | root = of_find_node_by_path(path: "/" ); |
34 | if (!root) |
35 | return false; |
36 | |
37 | match = of_match_node(matches: tegra_machine_match, node: root); |
38 | of_node_put(node: root); |
39 | |
40 | return match != NULL; |
41 | } |
42 | |
43 | static int tegra_core_dev_init_opp_state(struct device *dev) |
44 | { |
45 | unsigned long rate; |
46 | struct clk *clk; |
47 | bool rpm_enabled; |
48 | int err; |
49 | |
50 | clk = devm_clk_get(dev, NULL); |
51 | if (IS_ERR(ptr: clk)) { |
52 | dev_err(dev, "failed to get clk: %pe\n" , clk); |
53 | return PTR_ERR(ptr: clk); |
54 | } |
55 | |
56 | rate = clk_get_rate(clk); |
57 | if (!rate) { |
58 | dev_err(dev, "failed to get clk rate\n" ); |
59 | return -EINVAL; |
60 | } |
61 | |
62 | /* |
63 | * Runtime PM of the device must be enabled in order to set up |
64 | * GENPD's performance properly because GENPD core checks whether |
65 | * device is suspended and this check doesn't work while RPM is |
66 | * disabled. This makes sure the OPP vote below gets cached in |
67 | * GENPD for the device. Instead, the vote is done the next time |
68 | * the device gets runtime resumed. |
69 | */ |
70 | rpm_enabled = pm_runtime_enabled(dev); |
71 | if (!rpm_enabled) |
72 | pm_runtime_enable(dev); |
73 | |
74 | /* should never happen in practice */ |
75 | if (!pm_runtime_enabled(dev)) { |
76 | dev_WARN(dev, "failed to enable runtime PM\n" ); |
77 | pm_runtime_disable(dev); |
78 | return -EINVAL; |
79 | } |
80 | |
81 | /* first dummy rate-setting initializes voltage vote */ |
82 | err = dev_pm_opp_set_rate(dev, target_freq: rate); |
83 | |
84 | if (!rpm_enabled) |
85 | pm_runtime_disable(dev); |
86 | |
87 | if (err) { |
88 | dev_err(dev, "failed to initialize OPP clock: %d\n" , err); |
89 | return err; |
90 | } |
91 | |
92 | return 0; |
93 | } |
94 | |
95 | /** |
96 | * devm_tegra_core_dev_init_opp_table() - initialize OPP table |
97 | * @dev: device for which OPP table is initialized |
98 | * @params: pointer to the OPP table configuration |
99 | * |
100 | * This function will initialize OPP table and sync OPP state of a Tegra SoC |
101 | * core device. |
102 | * |
103 | * Return: 0 on success or errorno. |
104 | */ |
105 | int devm_tegra_core_dev_init_opp_table(struct device *dev, |
106 | struct tegra_core_opp_params *params) |
107 | { |
108 | u32 hw_version; |
109 | int err; |
110 | /* |
111 | * The clk's connection id to set is NULL and this is a NULL terminated |
112 | * array, hence two NULL entries. |
113 | */ |
114 | const char *clk_names[] = { NULL, NULL }; |
115 | struct dev_pm_opp_config config = { |
116 | /* |
117 | * For some devices we don't have any OPP table in the DT, and |
118 | * in order to use the same code path for all the devices, we |
119 | * create a dummy OPP table for them via this. The dummy OPP |
120 | * table is only capable of doing clk_set_rate() on invocation |
121 | * of dev_pm_opp_set_rate() and doesn't provide any other |
122 | * functionality. |
123 | */ |
124 | .clk_names = clk_names, |
125 | }; |
126 | |
127 | if (of_machine_is_compatible(compat: "nvidia,tegra20" )) { |
128 | hw_version = BIT(tegra_sku_info.soc_process_id); |
129 | config.supported_hw = &hw_version; |
130 | config.supported_hw_count = 1; |
131 | } else if (of_machine_is_compatible(compat: "nvidia,tegra30" )) { |
132 | hw_version = BIT(tegra_sku_info.soc_speedo_id); |
133 | config.supported_hw = &hw_version; |
134 | config.supported_hw_count = 1; |
135 | } |
136 | |
137 | err = devm_pm_opp_set_config(dev, config: &config); |
138 | if (err) { |
139 | dev_err(dev, "failed to set OPP config: %d\n" , err); |
140 | return err; |
141 | } |
142 | |
143 | /* |
144 | * Tegra114+ doesn't support OPP yet, return early for non tegra20/30 |
145 | * case. |
146 | */ |
147 | if (!config.supported_hw) |
148 | return -ENODEV; |
149 | |
150 | /* |
151 | * Older device-trees have an empty OPP table, we will get |
152 | * -ENODEV from devm_pm_opp_of_add_table() in this case. |
153 | */ |
154 | err = devm_pm_opp_of_add_table(dev); |
155 | if (err) { |
156 | if (err != -ENODEV) |
157 | dev_err(dev, "failed to add OPP table: %d\n" , err); |
158 | |
159 | return err; |
160 | } |
161 | |
162 | if (params->init_state) { |
163 | err = tegra_core_dev_init_opp_state(dev); |
164 | if (err) |
165 | return err; |
166 | } |
167 | |
168 | return 0; |
169 | } |
170 | EXPORT_SYMBOL_GPL(devm_tegra_core_dev_init_opp_table); |
171 | |