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