Commit | Line | Data |
---|---|---|
5bc9900a SS |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
6a61d1d1 | 3 | * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. |
5bc9900a SS |
4 | */ |
5 | ||
6 | #include <linux/bitfield.h> | |
7 | #include <linux/clk.h> | |
8 | #include <linux/interconnect-provider.h> | |
9 | #include <linux/io.h> | |
10 | #include <linux/kernel.h> | |
11 | #include <linux/module.h> | |
12 | #include <linux/of_device.h> | |
13 | #include <linux/platform_device.h> | |
14 | ||
15 | #include <dt-bindings/interconnect/qcom,osm-l3.h> | |
16 | ||
03c4e618 | 17 | #include "sc7180.h" |
6a61d1d1 | 18 | #include "sc7280.h" |
ffef0b13 | 19 | #include "sc8180x.h" |
5bc9900a | 20 | #include "sdm845.h" |
f9951945 | 21 | #include "sm8150.h" |
d7e19be6 | 22 | #include "sm8250.h" |
5bc9900a SS |
23 | |
24 | #define LUT_MAX_ENTRIES 40U | |
25 | #define LUT_SRC GENMASK(31, 30) | |
26 | #define LUT_L_VAL GENMASK(7, 0) | |
5bc9900a SS |
27 | #define CLK_HW_DIV 2 |
28 | ||
2bf706ea | 29 | /* OSM Register offsets */ |
5bc9900a | 30 | #define REG_ENABLE 0x0 |
2bf706ea SS |
31 | #define OSM_LUT_ROW_SIZE 32 |
32 | #define OSM_REG_FREQ_LUT 0x110 | |
33 | #define OSM_REG_PERF_STATE 0x920 | |
5bc9900a | 34 | |
d7e19be6 SS |
35 | /* EPSS Register offsets */ |
36 | #define EPSS_LUT_ROW_SIZE 4 | |
9235253e | 37 | #define EPSS_REG_L3_VOTE 0x90 |
d7e19be6 SS |
38 | #define EPSS_REG_FREQ_LUT 0x100 |
39 | #define EPSS_REG_PERF_STATE 0x320 | |
40 | ||
5bc9900a SS |
41 | #define OSM_L3_MAX_LINKS 1 |
42 | ||
8bf5d31c | 43 | #define to_osm_l3_provider(_provider) \ |
5bc9900a SS |
44 | container_of(_provider, struct qcom_osm_l3_icc_provider, provider) |
45 | ||
46 | struct qcom_osm_l3_icc_provider { | |
47 | void __iomem *base; | |
48 | unsigned int max_state; | |
2bf706ea | 49 | unsigned int reg_perf_state; |
5bc9900a SS |
50 | unsigned long lut_tables[LUT_MAX_ENTRIES]; |
51 | struct icc_provider provider; | |
52 | }; | |
53 | ||
54 | /** | |
8bf5d31c | 55 | * struct qcom_osm_l3_node - Qualcomm specific interconnect nodes |
5bc9900a SS |
56 | * @name: the node name used in debugfs |
57 | * @links: an array of nodes where we can go next while traversing | |
58 | * @id: a unique node identifier | |
59 | * @num_links: the total number of @links | |
60 | * @buswidth: width of the interconnect between a node and the bus | |
61 | */ | |
8bf5d31c | 62 | struct qcom_osm_l3_node { |
5bc9900a SS |
63 | const char *name; |
64 | u16 links[OSM_L3_MAX_LINKS]; | |
65 | u16 id; | |
66 | u16 num_links; | |
67 | u16 buswidth; | |
68 | }; | |
69 | ||
8bf5d31c | 70 | struct qcom_osm_l3_desc { |
2ccf33c0 | 71 | const struct qcom_osm_l3_node * const *nodes; |
5bc9900a | 72 | size_t num_nodes; |
2bf706ea SS |
73 | unsigned int lut_row_size; |
74 | unsigned int reg_freq_lut; | |
75 | unsigned int reg_perf_state; | |
5bc9900a SS |
76 | }; |
77 | ||
4529992c BA |
78 | enum { |
79 | OSM_L3_MASTER_NODE = 10000, | |
80 | OSM_L3_SLAVE_NODE, | |
81 | }; | |
82 | ||
5bc9900a | 83 | #define DEFINE_QNODE(_name, _id, _buswidth, ...) \ |
8bf5d31c | 84 | static const struct qcom_osm_l3_node _name = { \ |
5bc9900a SS |
85 | .name = #_name, \ |
86 | .id = _id, \ | |
87 | .buswidth = _buswidth, \ | |
88 | .num_links = ARRAY_SIZE(((int[]){ __VA_ARGS__ })), \ | |
89 | .links = { __VA_ARGS__ }, \ | |
90 | } | |
91 | ||
4529992c BA |
92 | DEFINE_QNODE(osm_l3_master, OSM_L3_MASTER_NODE, 16, OSM_L3_SLAVE_NODE); |
93 | DEFINE_QNODE(osm_l3_slave, OSM_L3_SLAVE_NODE, 16); | |
94 | ||
95 | static const struct qcom_osm_l3_node * const osm_l3_nodes[] = { | |
96 | [MASTER_OSM_L3_APPS] = &osm_l3_master, | |
97 | [SLAVE_OSM_L3] = &osm_l3_slave, | |
98 | }; | |
99 | ||
100 | DEFINE_QNODE(epss_l3_master, OSM_L3_MASTER_NODE, 32, OSM_L3_SLAVE_NODE); | |
101 | DEFINE_QNODE(epss_l3_slave, OSM_L3_SLAVE_NODE, 32); | |
5bc9900a | 102 | |
4529992c BA |
103 | static const struct qcom_osm_l3_node * const epss_l3_nodes[] = { |
104 | [MASTER_EPSS_L3_APPS] = &epss_l3_master, | |
105 | [SLAVE_EPSS_L3_SHARED] = &epss_l3_slave, | |
5bc9900a SS |
106 | }; |
107 | ||
d623264f | 108 | static const struct qcom_osm_l3_desc osm_l3 = { |
4529992c BA |
109 | .nodes = osm_l3_nodes, |
110 | .num_nodes = ARRAY_SIZE(osm_l3_nodes), | |
2bf706ea SS |
111 | .lut_row_size = OSM_LUT_ROW_SIZE, |
112 | .reg_freq_lut = OSM_REG_FREQ_LUT, | |
113 | .reg_perf_state = OSM_REG_PERF_STATE, | |
5bc9900a SS |
114 | }; |
115 | ||
9235253e | 116 | static const struct qcom_osm_l3_desc epss_l3_perf_state = { |
4529992c BA |
117 | .nodes = epss_l3_nodes, |
118 | .num_nodes = ARRAY_SIZE(epss_l3_nodes), | |
d7e19be6 SS |
119 | .lut_row_size = EPSS_LUT_ROW_SIZE, |
120 | .reg_freq_lut = EPSS_REG_FREQ_LUT, | |
121 | .reg_perf_state = EPSS_REG_PERF_STATE, | |
122 | }; | |
123 | ||
9235253e BA |
124 | static const struct qcom_osm_l3_desc epss_l3_l3_vote = { |
125 | .nodes = epss_l3_nodes, | |
126 | .num_nodes = ARRAY_SIZE(epss_l3_nodes), | |
127 | .lut_row_size = EPSS_LUT_ROW_SIZE, | |
128 | .reg_freq_lut = EPSS_REG_FREQ_LUT, | |
129 | .reg_perf_state = EPSS_REG_L3_VOTE, | |
130 | }; | |
131 | ||
8bf5d31c | 132 | static int qcom_osm_l3_set(struct icc_node *src, struct icc_node *dst) |
5bc9900a SS |
133 | { |
134 | struct qcom_osm_l3_icc_provider *qp; | |
135 | struct icc_provider *provider; | |
8bf5d31c | 136 | const struct qcom_osm_l3_node *qn; |
5bc9900a | 137 | unsigned int index; |
5bc9900a SS |
138 | u64 rate; |
139 | ||
140 | qn = src->data; | |
141 | provider = src->provider; | |
8bf5d31c | 142 | qp = to_osm_l3_provider(provider); |
5bc9900a | 143 | |
b6bcef16 | 144 | rate = icc_units_to_bps(dst->peak_bw); |
5bc9900a SS |
145 | do_div(rate, qn->buswidth); |
146 | ||
147 | for (index = 0; index < qp->max_state - 1; index++) { | |
148 | if (qp->lut_tables[index] >= rate) | |
149 | break; | |
150 | } | |
151 | ||
2bf706ea | 152 | writel_relaxed(index, qp->base + qp->reg_perf_state); |
5bc9900a SS |
153 | |
154 | return 0; | |
155 | } | |
156 | ||
157 | static int qcom_osm_l3_remove(struct platform_device *pdev) | |
158 | { | |
159 | struct qcom_osm_l3_icc_provider *qp = platform_get_drvdata(pdev); | |
160 | ||
174941ed | 161 | icc_provider_deregister(&qp->provider); |
5bc9900a | 162 | icc_nodes_remove(&qp->provider); |
f221bd78 UKK |
163 | |
164 | return 0; | |
5bc9900a SS |
165 | } |
166 | ||
167 | static int qcom_osm_l3_probe(struct platform_device *pdev) | |
168 | { | |
169 | u32 info, src, lval, i, prev_freq = 0, freq; | |
170 | static unsigned long hw_rate, xo_rate; | |
171 | struct qcom_osm_l3_icc_provider *qp; | |
8bf5d31c | 172 | const struct qcom_osm_l3_desc *desc; |
5bc9900a SS |
173 | struct icc_onecell_data *data; |
174 | struct icc_provider *provider; | |
2ccf33c0 | 175 | const struct qcom_osm_l3_node * const *qnodes; |
5bc9900a SS |
176 | struct icc_node *node; |
177 | size_t num_nodes; | |
178 | struct clk *clk; | |
179 | int ret; | |
180 | ||
181 | clk = clk_get(&pdev->dev, "xo"); | |
182 | if (IS_ERR(clk)) | |
183 | return PTR_ERR(clk); | |
184 | ||
185 | xo_rate = clk_get_rate(clk); | |
186 | clk_put(clk); | |
187 | ||
188 | clk = clk_get(&pdev->dev, "alternate"); | |
189 | if (IS_ERR(clk)) | |
190 | return PTR_ERR(clk); | |
191 | ||
192 | hw_rate = clk_get_rate(clk) / CLK_HW_DIV; | |
193 | clk_put(clk); | |
194 | ||
195 | qp = devm_kzalloc(&pdev->dev, sizeof(*qp), GFP_KERNEL); | |
196 | if (!qp) | |
197 | return -ENOMEM; | |
198 | ||
199 | qp->base = devm_platform_ioremap_resource(pdev, 0); | |
200 | if (IS_ERR(qp->base)) | |
201 | return PTR_ERR(qp->base); | |
202 | ||
203 | /* HW should be in enabled state to proceed */ | |
204 | if (!(readl_relaxed(qp->base + REG_ENABLE) & 0x1)) { | |
205 | dev_err(&pdev->dev, "error hardware not enabled\n"); | |
206 | return -ENODEV; | |
207 | } | |
208 | ||
2bf706ea SS |
209 | desc = device_get_match_data(&pdev->dev); |
210 | if (!desc) | |
211 | return -EINVAL; | |
212 | ||
213 | qp->reg_perf_state = desc->reg_perf_state; | |
214 | ||
5bc9900a | 215 | for (i = 0; i < LUT_MAX_ENTRIES; i++) { |
2bf706ea SS |
216 | info = readl_relaxed(qp->base + desc->reg_freq_lut + |
217 | i * desc->lut_row_size); | |
5bc9900a SS |
218 | src = FIELD_GET(LUT_SRC, info); |
219 | lval = FIELD_GET(LUT_L_VAL, info); | |
220 | if (src) | |
221 | freq = xo_rate * lval; | |
222 | else | |
223 | freq = hw_rate; | |
224 | ||
225 | /* Two of the same frequencies signify end of table */ | |
226 | if (i > 0 && prev_freq == freq) | |
227 | break; | |
228 | ||
229 | dev_dbg(&pdev->dev, "index=%d freq=%d\n", i, freq); | |
230 | ||
231 | qp->lut_tables[i] = freq; | |
232 | prev_freq = freq; | |
233 | } | |
234 | qp->max_state = i; | |
235 | ||
5bc9900a SS |
236 | qnodes = desc->nodes; |
237 | num_nodes = desc->num_nodes; | |
238 | ||
f77ebdda | 239 | data = devm_kzalloc(&pdev->dev, struct_size(data, nodes, num_nodes), GFP_KERNEL); |
5bc9900a SS |
240 | if (!data) |
241 | return -ENOMEM; | |
242 | ||
243 | provider = &qp->provider; | |
244 | provider->dev = &pdev->dev; | |
8bf5d31c | 245 | provider->set = qcom_osm_l3_set; |
5bc9900a SS |
246 | provider->aggregate = icc_std_aggregate; |
247 | provider->xlate = of_icc_xlate_onecell; | |
5bc9900a SS |
248 | provider->data = data; |
249 | ||
174941ed | 250 | icc_provider_init(provider); |
5bc9900a SS |
251 | |
252 | for (i = 0; i < num_nodes; i++) { | |
253 | size_t j; | |
254 | ||
255 | node = icc_node_create(qnodes[i]->id); | |
256 | if (IS_ERR(node)) { | |
257 | ret = PTR_ERR(node); | |
258 | goto err; | |
259 | } | |
260 | ||
261 | node->name = qnodes[i]->name; | |
8bf5d31c | 262 | /* Cast away const and add it back in qcom_osm_l3_set() */ |
b1a367bb | 263 | node->data = (void *)qnodes[i]; |
5bc9900a SS |
264 | icc_node_add(node, provider); |
265 | ||
266 | for (j = 0; j < qnodes[i]->num_links; j++) | |
267 | icc_link_create(node, qnodes[i]->links[j]); | |
268 | ||
269 | data->nodes[i] = node; | |
270 | } | |
271 | data->num_nodes = num_nodes; | |
272 | ||
174941ed JH |
273 | ret = icc_provider_register(provider); |
274 | if (ret) | |
275 | goto err; | |
276 | ||
5bc9900a SS |
277 | platform_set_drvdata(pdev, qp); |
278 | ||
279 | return 0; | |
280 | err: | |
281 | icc_nodes_remove(provider); | |
5bc9900a SS |
282 | |
283 | return ret; | |
284 | } | |
285 | ||
286 | static const struct of_device_id osm_l3_of_match[] = { | |
9235253e BA |
287 | { .compatible = "qcom,epss-l3", .data = &epss_l3_l3_vote }, |
288 | { .compatible = "qcom,osm-l3", .data = &osm_l3 }, | |
d623264f | 289 | { .compatible = "qcom,sc7180-osm-l3", .data = &osm_l3 }, |
9235253e | 290 | { .compatible = "qcom,sc7280-epss-l3", .data = &epss_l3_perf_state }, |
d623264f BA |
291 | { .compatible = "qcom,sdm845-osm-l3", .data = &osm_l3 }, |
292 | { .compatible = "qcom,sm8150-osm-l3", .data = &osm_l3 }, | |
293 | { .compatible = "qcom,sc8180x-osm-l3", .data = &osm_l3 }, | |
9235253e | 294 | { .compatible = "qcom,sm8250-epss-l3", .data = &epss_l3_perf_state }, |
5bc9900a SS |
295 | { } |
296 | }; | |
297 | MODULE_DEVICE_TABLE(of, osm_l3_of_match); | |
298 | ||
299 | static struct platform_driver osm_l3_driver = { | |
300 | .probe = qcom_osm_l3_probe, | |
301 | .remove = qcom_osm_l3_remove, | |
302 | .driver = { | |
303 | .name = "osm-l3", | |
304 | .of_match_table = osm_l3_of_match, | |
7d3b0b0d | 305 | .sync_state = icc_sync_state, |
5bc9900a SS |
306 | }, |
307 | }; | |
308 | module_platform_driver(osm_l3_driver); | |
309 | ||
310 | MODULE_DESCRIPTION("Qualcomm OSM L3 interconnect driver"); | |
311 | MODULE_LICENSE("GPL v2"); |