1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (c) 2020, The Linux Foundation. All rights reserved.
6 #include <linux/bitfield.h>
8 #include <linux/interconnect-provider.h>
10 #include <linux/kernel.h>
11 #include <linux/module.h>
12 #include <linux/of_device.h>
13 #include <linux/platform_device.h>
15 #include <dt-bindings/interconnect/qcom,osm-l3.h>
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
26 /* Register offsets */
27 #define REG_ENABLE 0x0
28 #define REG_FREQ_LUT 0x110
29 #define REG_PERF_STATE 0x920
31 #define OSM_L3_MAX_LINKS 1
33 #define to_qcom_provider(_provider) \
34 container_of(_provider, struct qcom_osm_l3_icc_provider, provider)
36 struct qcom_osm_l3_icc_provider
{
38 unsigned int max_state
;
39 unsigned long lut_tables
[LUT_MAX_ENTRIES
];
40 struct icc_provider provider
;
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
51 struct qcom_icc_node
{
53 u16 links
[OSM_L3_MAX_LINKS
];
59 struct qcom_icc_desc
{
60 struct qcom_icc_node
**nodes
;
64 #define DEFINE_QNODE(_name, _id, _buswidth, ...) \
65 static struct qcom_icc_node _name = { \
68 .buswidth = _buswidth, \
69 .num_links = ARRAY_SIZE(((int[]){ __VA_ARGS__ })), \
70 .links = { __VA_ARGS__ }, \
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);
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
,
81 static const struct qcom_icc_desc sdm845_icc_osm_l3
= {
82 .nodes
= sdm845_osm_l3_nodes
,
83 .num_nodes
= ARRAY_SIZE(sdm845_osm_l3_nodes
),
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);
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
,
94 static const struct qcom_icc_desc sc7180_icc_osm_l3
= {
95 .nodes
= sc7180_osm_l3_nodes
,
96 .num_nodes
= ARRAY_SIZE(sc7180_osm_l3_nodes
),
99 static int qcom_icc_set(struct icc_node
*src
, struct icc_node
*dst
)
101 struct qcom_osm_l3_icc_provider
*qp
;
102 struct icc_provider
*provider
;
103 struct qcom_icc_node
*qn
;
111 provider
= src
->provider
;
112 qp
= to_qcom_provider(provider
);
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
);
118 rate
= max(agg_avg
, agg_peak
);
119 rate
= icc_units_to_bps(rate
);
120 do_div(rate
, qn
->buswidth
);
122 for (index
= 0; index
< qp
->max_state
- 1; index
++) {
123 if (qp
->lut_tables
[index
] >= rate
)
127 writel_relaxed(index
, qp
->base
+ REG_PERF_STATE
);
132 static int qcom_osm_l3_remove(struct platform_device
*pdev
)
134 struct qcom_osm_l3_icc_provider
*qp
= platform_get_drvdata(pdev
);
136 icc_nodes_remove(&qp
->provider
);
137 return icc_provider_del(&qp
->provider
);
140 static int qcom_osm_l3_probe(struct platform_device
*pdev
)
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
;
154 clk
= clk_get(&pdev
->dev
, "xo");
158 xo_rate
= clk_get_rate(clk
);
161 clk
= clk_get(&pdev
->dev
, "alternate");
165 hw_rate
= clk_get_rate(clk
) / CLK_HW_DIV
;
168 qp
= devm_kzalloc(&pdev
->dev
, sizeof(*qp
), GFP_KERNEL
);
172 qp
->base
= devm_platform_ioremap_resource(pdev
, 0);
173 if (IS_ERR(qp
->base
))
174 return PTR_ERR(qp
->base
);
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");
182 for (i
= 0; i
< LUT_MAX_ENTRIES
; i
++) {
183 info
= readl_relaxed(qp
->base
+ REG_FREQ_LUT
+
185 src
= FIELD_GET(LUT_SRC
, info
);
186 lval
= FIELD_GET(LUT_L_VAL
, info
);
188 freq
= xo_rate
* lval
;
192 /* Two of the same frequencies signify end of table */
193 if (i
> 0 && prev_freq
== freq
)
196 dev_dbg(&pdev
->dev
, "index=%d freq=%d\n", i
, freq
);
198 qp
->lut_tables
[i
] = freq
;
203 desc
= device_get_match_data(&pdev
->dev
);
207 qnodes
= desc
->nodes
;
208 num_nodes
= desc
->num_nodes
;
210 data
= devm_kcalloc(&pdev
->dev
, num_nodes
, sizeof(*node
), GFP_KERNEL
);
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
;
222 ret
= icc_provider_add(provider
);
224 dev_err(&pdev
->dev
, "error adding interconnect provider\n");
228 for (i
= 0; i
< num_nodes
; i
++) {
231 node
= icc_node_create(qnodes
[i
]->id
);
237 node
->name
= qnodes
[i
]->name
;
238 node
->data
= qnodes
[i
];
239 icc_node_add(node
, provider
);
241 for (j
= 0; j
< qnodes
[i
]->num_links
; j
++)
242 icc_link_create(node
, qnodes
[i
]->links
[j
]);
244 data
->nodes
[i
] = node
;
246 data
->num_nodes
= num_nodes
;
248 platform_set_drvdata(pdev
, qp
);
252 icc_nodes_remove(provider
);
253 icc_provider_del(provider
);
258 static const struct of_device_id osm_l3_of_match
[] = {
259 { .compatible
= "qcom,sc7180-osm-l3", .data
= &sc7180_icc_osm_l3
},
260 { .compatible
= "qcom,sdm845-osm-l3", .data
= &sdm845_icc_osm_l3
},
263 MODULE_DEVICE_TABLE(of
, osm_l3_of_match
);
265 static struct platform_driver osm_l3_driver
= {
266 .probe
= qcom_osm_l3_probe
,
267 .remove
= qcom_osm_l3_remove
,
270 .of_match_table
= osm_l3_of_match
,
273 module_platform_driver(osm_l3_driver
);
275 MODULE_DESCRIPTION("Qualcomm OSM L3 interconnect driver");
276 MODULE_LICENSE("GPL v2");