]>
Commit | Line | Data |
---|---|---|
5bc9900a SS |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (c) 2020, The Linux Foundation. All rights reserved. | |
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" |
5bc9900a SS |
18 | #include "sdm845.h" |
19 | ||
20 | #define LUT_MAX_ENTRIES 40U | |
21 | #define LUT_SRC GENMASK(31, 30) | |
22 | #define LUT_L_VAL GENMASK(7, 0) | |
23 | #define LUT_ROW_SIZE 32 | |
24 | #define CLK_HW_DIV 2 | |
25 | ||
26 | /* Register offsets */ | |
27 | #define REG_ENABLE 0x0 | |
28 | #define REG_FREQ_LUT 0x110 | |
29 | #define REG_PERF_STATE 0x920 | |
30 | ||
31 | #define OSM_L3_MAX_LINKS 1 | |
32 | ||
33 | #define to_qcom_provider(_provider) \ | |
34 | container_of(_provider, struct qcom_osm_l3_icc_provider, provider) | |
35 | ||
36 | struct qcom_osm_l3_icc_provider { | |
37 | void __iomem *base; | |
38 | unsigned int max_state; | |
39 | unsigned long lut_tables[LUT_MAX_ENTRIES]; | |
40 | struct icc_provider provider; | |
41 | }; | |
42 | ||
43 | /** | |
44 | * struct qcom_icc_node - Qualcomm specific interconnect nodes | |
45 | * @name: the node name used in debugfs | |
46 | * @links: an array of nodes where we can go next while traversing | |
47 | * @id: a unique node identifier | |
48 | * @num_links: the total number of @links | |
49 | * @buswidth: width of the interconnect between a node and the bus | |
50 | */ | |
51 | struct qcom_icc_node { | |
52 | const char *name; | |
53 | u16 links[OSM_L3_MAX_LINKS]; | |
54 | u16 id; | |
55 | u16 num_links; | |
56 | u16 buswidth; | |
57 | }; | |
58 | ||
59 | struct qcom_icc_desc { | |
60 | struct qcom_icc_node **nodes; | |
61 | size_t num_nodes; | |
62 | }; | |
63 | ||
64 | #define DEFINE_QNODE(_name, _id, _buswidth, ...) \ | |
65 | static struct qcom_icc_node _name = { \ | |
66 | .name = #_name, \ | |
67 | .id = _id, \ | |
68 | .buswidth = _buswidth, \ | |
69 | .num_links = ARRAY_SIZE(((int[]){ __VA_ARGS__ })), \ | |
70 | .links = { __VA_ARGS__ }, \ | |
71 | } | |
72 | ||
73 | DEFINE_QNODE(sdm845_osm_apps_l3, SDM845_MASTER_OSM_L3_APPS, 16, SDM845_SLAVE_OSM_L3); | |
74 | DEFINE_QNODE(sdm845_osm_l3, SDM845_SLAVE_OSM_L3, 16); | |
75 | ||
76 | static struct qcom_icc_node *sdm845_osm_l3_nodes[] = { | |
77 | [MASTER_OSM_L3_APPS] = &sdm845_osm_apps_l3, | |
78 | [SLAVE_OSM_L3] = &sdm845_osm_l3, | |
79 | }; | |
80 | ||
5409e0cc | 81 | static const struct qcom_icc_desc sdm845_icc_osm_l3 = { |
5bc9900a SS |
82 | .nodes = sdm845_osm_l3_nodes, |
83 | .num_nodes = ARRAY_SIZE(sdm845_osm_l3_nodes), | |
84 | }; | |
85 | ||
03c4e618 SS |
86 | DEFINE_QNODE(sc7180_osm_apps_l3, SC7180_MASTER_OSM_L3_APPS, 16, SC7180_SLAVE_OSM_L3); |
87 | DEFINE_QNODE(sc7180_osm_l3, SC7180_SLAVE_OSM_L3, 16); | |
88 | ||
89 | static struct qcom_icc_node *sc7180_osm_l3_nodes[] = { | |
90 | [MASTER_OSM_L3_APPS] = &sc7180_osm_apps_l3, | |
91 | [SLAVE_OSM_L3] = &sc7180_osm_l3, | |
92 | }; | |
93 | ||
5409e0cc | 94 | static const struct qcom_icc_desc sc7180_icc_osm_l3 = { |
03c4e618 SS |
95 | .nodes = sc7180_osm_l3_nodes, |
96 | .num_nodes = ARRAY_SIZE(sc7180_osm_l3_nodes), | |
97 | }; | |
98 | ||
5bc9900a SS |
99 | static int qcom_icc_set(struct icc_node *src, struct icc_node *dst) |
100 | { | |
101 | struct qcom_osm_l3_icc_provider *qp; | |
102 | struct icc_provider *provider; | |
103 | struct qcom_icc_node *qn; | |
104 | struct icc_node *n; | |
105 | unsigned int index; | |
106 | u32 agg_peak = 0; | |
107 | u32 agg_avg = 0; | |
108 | u64 rate; | |
109 | ||
110 | qn = src->data; | |
111 | provider = src->provider; | |
112 | qp = to_qcom_provider(provider); | |
113 | ||
114 | list_for_each_entry(n, &provider->nodes, node_list) | |
115 | provider->aggregate(n, 0, n->avg_bw, n->peak_bw, | |
116 | &agg_avg, &agg_peak); | |
117 | ||
118 | rate = max(agg_avg, agg_peak); | |
119 | rate = icc_units_to_bps(rate); | |
120 | do_div(rate, qn->buswidth); | |
121 | ||
122 | for (index = 0; index < qp->max_state - 1; index++) { | |
123 | if (qp->lut_tables[index] >= rate) | |
124 | break; | |
125 | } | |
126 | ||
127 | writel_relaxed(index, qp->base + REG_PERF_STATE); | |
128 | ||
129 | return 0; | |
130 | } | |
131 | ||
132 | static int qcom_osm_l3_remove(struct platform_device *pdev) | |
133 | { | |
134 | struct qcom_osm_l3_icc_provider *qp = platform_get_drvdata(pdev); | |
135 | ||
136 | icc_nodes_remove(&qp->provider); | |
137 | return icc_provider_del(&qp->provider); | |
138 | } | |
139 | ||
140 | static int qcom_osm_l3_probe(struct platform_device *pdev) | |
141 | { | |
142 | u32 info, src, lval, i, prev_freq = 0, freq; | |
143 | static unsigned long hw_rate, xo_rate; | |
144 | struct qcom_osm_l3_icc_provider *qp; | |
145 | const struct qcom_icc_desc *desc; | |
146 | struct icc_onecell_data *data; | |
147 | struct icc_provider *provider; | |
148 | struct qcom_icc_node **qnodes; | |
149 | struct icc_node *node; | |
150 | size_t num_nodes; | |
151 | struct clk *clk; | |
152 | int ret; | |
153 | ||
154 | clk = clk_get(&pdev->dev, "xo"); | |
155 | if (IS_ERR(clk)) | |
156 | return PTR_ERR(clk); | |
157 | ||
158 | xo_rate = clk_get_rate(clk); | |
159 | clk_put(clk); | |
160 | ||
161 | clk = clk_get(&pdev->dev, "alternate"); | |
162 | if (IS_ERR(clk)) | |
163 | return PTR_ERR(clk); | |
164 | ||
165 | hw_rate = clk_get_rate(clk) / CLK_HW_DIV; | |
166 | clk_put(clk); | |
167 | ||
168 | qp = devm_kzalloc(&pdev->dev, sizeof(*qp), GFP_KERNEL); | |
169 | if (!qp) | |
170 | return -ENOMEM; | |
171 | ||
172 | qp->base = devm_platform_ioremap_resource(pdev, 0); | |
173 | if (IS_ERR(qp->base)) | |
174 | return PTR_ERR(qp->base); | |
175 | ||
176 | /* HW should be in enabled state to proceed */ | |
177 | if (!(readl_relaxed(qp->base + REG_ENABLE) & 0x1)) { | |
178 | dev_err(&pdev->dev, "error hardware not enabled\n"); | |
179 | return -ENODEV; | |
180 | } | |
181 | ||
182 | for (i = 0; i < LUT_MAX_ENTRIES; i++) { | |
183 | info = readl_relaxed(qp->base + REG_FREQ_LUT + | |
184 | i * LUT_ROW_SIZE); | |
185 | src = FIELD_GET(LUT_SRC, info); | |
186 | lval = FIELD_GET(LUT_L_VAL, info); | |
187 | if (src) | |
188 | freq = xo_rate * lval; | |
189 | else | |
190 | freq = hw_rate; | |
191 | ||
192 | /* Two of the same frequencies signify end of table */ | |
193 | if (i > 0 && prev_freq == freq) | |
194 | break; | |
195 | ||
196 | dev_dbg(&pdev->dev, "index=%d freq=%d\n", i, freq); | |
197 | ||
198 | qp->lut_tables[i] = freq; | |
199 | prev_freq = freq; | |
200 | } | |
201 | qp->max_state = i; | |
202 | ||
203 | desc = device_get_match_data(&pdev->dev); | |
204 | if (!desc) | |
205 | return -EINVAL; | |
206 | ||
207 | qnodes = desc->nodes; | |
208 | num_nodes = desc->num_nodes; | |
209 | ||
210 | data = devm_kcalloc(&pdev->dev, num_nodes, sizeof(*node), GFP_KERNEL); | |
211 | if (!data) | |
212 | return -ENOMEM; | |
213 | ||
214 | provider = &qp->provider; | |
215 | provider->dev = &pdev->dev; | |
216 | provider->set = qcom_icc_set; | |
217 | provider->aggregate = icc_std_aggregate; | |
218 | provider->xlate = of_icc_xlate_onecell; | |
219 | INIT_LIST_HEAD(&provider->nodes); | |
220 | provider->data = data; | |
221 | ||
222 | ret = icc_provider_add(provider); | |
223 | if (ret) { | |
224 | dev_err(&pdev->dev, "error adding interconnect provider\n"); | |
225 | return ret; | |
226 | } | |
227 | ||
228 | for (i = 0; i < num_nodes; i++) { | |
229 | size_t j; | |
230 | ||
231 | node = icc_node_create(qnodes[i]->id); | |
232 | if (IS_ERR(node)) { | |
233 | ret = PTR_ERR(node); | |
234 | goto err; | |
235 | } | |
236 | ||
237 | node->name = qnodes[i]->name; | |
238 | node->data = qnodes[i]; | |
239 | icc_node_add(node, provider); | |
240 | ||
241 | for (j = 0; j < qnodes[i]->num_links; j++) | |
242 | icc_link_create(node, qnodes[i]->links[j]); | |
243 | ||
244 | data->nodes[i] = node; | |
245 | } | |
246 | data->num_nodes = num_nodes; | |
247 | ||
248 | platform_set_drvdata(pdev, qp); | |
249 | ||
250 | return 0; | |
251 | err: | |
252 | icc_nodes_remove(provider); | |
253 | icc_provider_del(provider); | |
254 | ||
255 | return ret; | |
256 | } | |
257 | ||
258 | static const struct of_device_id osm_l3_of_match[] = { | |
03c4e618 | 259 | { .compatible = "qcom,sc7180-osm-l3", .data = &sc7180_icc_osm_l3 }, |
5bc9900a SS |
260 | { .compatible = "qcom,sdm845-osm-l3", .data = &sdm845_icc_osm_l3 }, |
261 | { } | |
262 | }; | |
263 | MODULE_DEVICE_TABLE(of, osm_l3_of_match); | |
264 | ||
265 | static struct platform_driver osm_l3_driver = { | |
266 | .probe = qcom_osm_l3_probe, | |
267 | .remove = qcom_osm_l3_remove, | |
268 | .driver = { | |
269 | .name = "osm-l3", | |
270 | .of_match_table = osm_l3_of_match, | |
271 | }, | |
272 | }; | |
273 | module_platform_driver(osm_l3_driver); | |
274 | ||
275 | MODULE_DESCRIPTION("Qualcomm OSM L3 interconnect driver"); | |
276 | MODULE_LICENSE("GPL v2"); |