15bc9900aSSibi Sankar // SPDX-License-Identifier: GPL-2.0
25bc9900aSSibi Sankar /*
36a61d1d1SOdelu Kukatla * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
45bc9900aSSibi Sankar */
55bc9900aSSibi Sankar
6*c39ab570SAndy Shevchenko #include <linux/args.h>
75bc9900aSSibi Sankar #include <linux/bitfield.h>
85bc9900aSSibi Sankar #include <linux/clk.h>
95bc9900aSSibi Sankar #include <linux/interconnect-provider.h>
105bc9900aSSibi Sankar #include <linux/io.h>
115bc9900aSSibi Sankar #include <linux/kernel.h>
125bc9900aSSibi Sankar #include <linux/module.h>
13cff66aceSRob Herring #include <linux/of.h>
145bc9900aSSibi Sankar #include <linux/platform_device.h>
155bc9900aSSibi Sankar
165bc9900aSSibi Sankar #include <dt-bindings/interconnect/qcom,osm-l3.h>
175bc9900aSSibi Sankar
185bc9900aSSibi Sankar #define LUT_MAX_ENTRIES 40U
195bc9900aSSibi Sankar #define LUT_SRC GENMASK(31, 30)
205bc9900aSSibi Sankar #define LUT_L_VAL GENMASK(7, 0)
215bc9900aSSibi Sankar #define CLK_HW_DIV 2
225bc9900aSSibi Sankar
232bf706eaSSibi Sankar /* OSM Register offsets */
245bc9900aSSibi Sankar #define REG_ENABLE 0x0
252bf706eaSSibi Sankar #define OSM_LUT_ROW_SIZE 32
262bf706eaSSibi Sankar #define OSM_REG_FREQ_LUT 0x110
272bf706eaSSibi Sankar #define OSM_REG_PERF_STATE 0x920
285bc9900aSSibi Sankar
29d7e19be6SSibi Sankar /* EPSS Register offsets */
30d7e19be6SSibi Sankar #define EPSS_LUT_ROW_SIZE 4
319235253eSBjorn Andersson #define EPSS_REG_L3_VOTE 0x90
32d7e19be6SSibi Sankar #define EPSS_REG_FREQ_LUT 0x100
33d7e19be6SSibi Sankar #define EPSS_REG_PERF_STATE 0x320
34d7e19be6SSibi Sankar
355bc9900aSSibi Sankar #define OSM_L3_MAX_LINKS 1
365bc9900aSSibi Sankar
378bf5d31cSBjorn Andersson #define to_osm_l3_provider(_provider) \
385bc9900aSSibi Sankar container_of(_provider, struct qcom_osm_l3_icc_provider, provider)
395bc9900aSSibi Sankar
405bc9900aSSibi Sankar struct qcom_osm_l3_icc_provider {
415bc9900aSSibi Sankar void __iomem *base;
425bc9900aSSibi Sankar unsigned int max_state;
432bf706eaSSibi Sankar unsigned int reg_perf_state;
445bc9900aSSibi Sankar unsigned long lut_tables[LUT_MAX_ENTRIES];
455bc9900aSSibi Sankar struct icc_provider provider;
465bc9900aSSibi Sankar };
475bc9900aSSibi Sankar
485bc9900aSSibi Sankar /**
498bf5d31cSBjorn Andersson * struct qcom_osm_l3_node - Qualcomm specific interconnect nodes
505bc9900aSSibi Sankar * @name: the node name used in debugfs
515bc9900aSSibi Sankar * @links: an array of nodes where we can go next while traversing
525bc9900aSSibi Sankar * @id: a unique node identifier
535bc9900aSSibi Sankar * @num_links: the total number of @links
545bc9900aSSibi Sankar * @buswidth: width of the interconnect between a node and the bus
555bc9900aSSibi Sankar */
568bf5d31cSBjorn Andersson struct qcom_osm_l3_node {
575bc9900aSSibi Sankar const char *name;
585bc9900aSSibi Sankar u16 links[OSM_L3_MAX_LINKS];
595bc9900aSSibi Sankar u16 id;
605bc9900aSSibi Sankar u16 num_links;
615bc9900aSSibi Sankar u16 buswidth;
625bc9900aSSibi Sankar };
635bc9900aSSibi Sankar
648bf5d31cSBjorn Andersson struct qcom_osm_l3_desc {
652ccf33c0SKrzysztof Kozlowski const struct qcom_osm_l3_node * const *nodes;
665bc9900aSSibi Sankar size_t num_nodes;
672bf706eaSSibi Sankar unsigned int lut_row_size;
682bf706eaSSibi Sankar unsigned int reg_freq_lut;
692bf706eaSSibi Sankar unsigned int reg_perf_state;
705bc9900aSSibi Sankar };
715bc9900aSSibi Sankar
724529992cSBjorn Andersson enum {
734529992cSBjorn Andersson OSM_L3_MASTER_NODE = 10000,
744529992cSBjorn Andersson OSM_L3_SLAVE_NODE,
754529992cSBjorn Andersson };
764529992cSBjorn Andersson
775bc9900aSSibi Sankar #define DEFINE_QNODE(_name, _id, _buswidth, ...) \
788bf5d31cSBjorn Andersson static const struct qcom_osm_l3_node _name = { \
795bc9900aSSibi Sankar .name = #_name, \
805bc9900aSSibi Sankar .id = _id, \
815bc9900aSSibi Sankar .buswidth = _buswidth, \
82*c39ab570SAndy Shevchenko .num_links = COUNT_ARGS(__VA_ARGS__), \
835bc9900aSSibi Sankar .links = { __VA_ARGS__ }, \
845bc9900aSSibi Sankar }
855bc9900aSSibi Sankar
864529992cSBjorn Andersson DEFINE_QNODE(osm_l3_master, OSM_L3_MASTER_NODE, 16, OSM_L3_SLAVE_NODE);
874529992cSBjorn Andersson DEFINE_QNODE(osm_l3_slave, OSM_L3_SLAVE_NODE, 16);
885bc9900aSSibi Sankar
894529992cSBjorn Andersson static const struct qcom_osm_l3_node * const osm_l3_nodes[] = {
904529992cSBjorn Andersson [MASTER_OSM_L3_APPS] = &osm_l3_master,
914529992cSBjorn Andersson [SLAVE_OSM_L3] = &osm_l3_slave,
924529992cSBjorn Andersson };
934529992cSBjorn Andersson
944529992cSBjorn Andersson DEFINE_QNODE(epss_l3_master, OSM_L3_MASTER_NODE, 32, OSM_L3_SLAVE_NODE);
954529992cSBjorn Andersson DEFINE_QNODE(epss_l3_slave, OSM_L3_SLAVE_NODE, 32);
964529992cSBjorn Andersson
974529992cSBjorn Andersson static const struct qcom_osm_l3_node * const epss_l3_nodes[] = {
984529992cSBjorn Andersson [MASTER_EPSS_L3_APPS] = &epss_l3_master,
994529992cSBjorn Andersson [SLAVE_EPSS_L3_SHARED] = &epss_l3_slave,
1005bc9900aSSibi Sankar };
1015bc9900aSSibi Sankar
102d623264fSBjorn Andersson static const struct qcom_osm_l3_desc osm_l3 = {
1034529992cSBjorn Andersson .nodes = osm_l3_nodes,
1044529992cSBjorn Andersson .num_nodes = ARRAY_SIZE(osm_l3_nodes),
1052bf706eaSSibi Sankar .lut_row_size = OSM_LUT_ROW_SIZE,
1062bf706eaSSibi Sankar .reg_freq_lut = OSM_REG_FREQ_LUT,
1072bf706eaSSibi Sankar .reg_perf_state = OSM_REG_PERF_STATE,
1085bc9900aSSibi Sankar };
1095bc9900aSSibi Sankar
1109235253eSBjorn Andersson static const struct qcom_osm_l3_desc epss_l3_perf_state = {
1114529992cSBjorn Andersson .nodes = epss_l3_nodes,
1124529992cSBjorn Andersson .num_nodes = ARRAY_SIZE(epss_l3_nodes),
113d7e19be6SSibi Sankar .lut_row_size = EPSS_LUT_ROW_SIZE,
114d7e19be6SSibi Sankar .reg_freq_lut = EPSS_REG_FREQ_LUT,
115d7e19be6SSibi Sankar .reg_perf_state = EPSS_REG_PERF_STATE,
116d7e19be6SSibi Sankar };
117d7e19be6SSibi Sankar
1189235253eSBjorn Andersson static const struct qcom_osm_l3_desc epss_l3_l3_vote = {
1199235253eSBjorn Andersson .nodes = epss_l3_nodes,
1209235253eSBjorn Andersson .num_nodes = ARRAY_SIZE(epss_l3_nodes),
1219235253eSBjorn Andersson .lut_row_size = EPSS_LUT_ROW_SIZE,
1229235253eSBjorn Andersson .reg_freq_lut = EPSS_REG_FREQ_LUT,
1239235253eSBjorn Andersson .reg_perf_state = EPSS_REG_L3_VOTE,
1249235253eSBjorn Andersson };
1259235253eSBjorn Andersson
qcom_osm_l3_set(struct icc_node * src,struct icc_node * dst)1268bf5d31cSBjorn Andersson static int qcom_osm_l3_set(struct icc_node *src, struct icc_node *dst)
1275bc9900aSSibi Sankar {
1285bc9900aSSibi Sankar struct qcom_osm_l3_icc_provider *qp;
1295bc9900aSSibi Sankar struct icc_provider *provider;
1308bf5d31cSBjorn Andersson const struct qcom_osm_l3_node *qn;
1315bc9900aSSibi Sankar unsigned int index;
1325bc9900aSSibi Sankar u64 rate;
1335bc9900aSSibi Sankar
1345bc9900aSSibi Sankar qn = src->data;
1355bc9900aSSibi Sankar provider = src->provider;
1368bf5d31cSBjorn Andersson qp = to_osm_l3_provider(provider);
1375bc9900aSSibi Sankar
138b6bcef16SBjorn Andersson rate = icc_units_to_bps(dst->peak_bw);
1395bc9900aSSibi Sankar do_div(rate, qn->buswidth);
1405bc9900aSSibi Sankar
1415bc9900aSSibi Sankar for (index = 0; index < qp->max_state - 1; index++) {
1425bc9900aSSibi Sankar if (qp->lut_tables[index] >= rate)
1435bc9900aSSibi Sankar break;
1445bc9900aSSibi Sankar }
1455bc9900aSSibi Sankar
1462bf706eaSSibi Sankar writel_relaxed(index, qp->base + qp->reg_perf_state);
1475bc9900aSSibi Sankar
1485bc9900aSSibi Sankar return 0;
1495bc9900aSSibi Sankar }
1505bc9900aSSibi Sankar
qcom_osm_l3_remove(struct platform_device * pdev)1515bc9900aSSibi Sankar static int qcom_osm_l3_remove(struct platform_device *pdev)
1525bc9900aSSibi Sankar {
1535bc9900aSSibi Sankar struct qcom_osm_l3_icc_provider *qp = platform_get_drvdata(pdev);
1545bc9900aSSibi Sankar
155174941edSJohan Hovold icc_provider_deregister(&qp->provider);
1565bc9900aSSibi Sankar icc_nodes_remove(&qp->provider);
157f221bd78SUwe Kleine-König
158f221bd78SUwe Kleine-König return 0;
1595bc9900aSSibi Sankar }
1605bc9900aSSibi Sankar
qcom_osm_l3_probe(struct platform_device * pdev)1615bc9900aSSibi Sankar static int qcom_osm_l3_probe(struct platform_device *pdev)
1625bc9900aSSibi Sankar {
1635bc9900aSSibi Sankar u32 info, src, lval, i, prev_freq = 0, freq;
1645bc9900aSSibi Sankar static unsigned long hw_rate, xo_rate;
1655bc9900aSSibi Sankar struct qcom_osm_l3_icc_provider *qp;
1668bf5d31cSBjorn Andersson const struct qcom_osm_l3_desc *desc;
1675bc9900aSSibi Sankar struct icc_onecell_data *data;
1685bc9900aSSibi Sankar struct icc_provider *provider;
1692ccf33c0SKrzysztof Kozlowski const struct qcom_osm_l3_node * const *qnodes;
1705bc9900aSSibi Sankar struct icc_node *node;
1715bc9900aSSibi Sankar size_t num_nodes;
1725bc9900aSSibi Sankar struct clk *clk;
1735bc9900aSSibi Sankar int ret;
1745bc9900aSSibi Sankar
1755bc9900aSSibi Sankar clk = clk_get(&pdev->dev, "xo");
1765bc9900aSSibi Sankar if (IS_ERR(clk))
1775bc9900aSSibi Sankar return PTR_ERR(clk);
1785bc9900aSSibi Sankar
1795bc9900aSSibi Sankar xo_rate = clk_get_rate(clk);
1805bc9900aSSibi Sankar clk_put(clk);
1815bc9900aSSibi Sankar
1825bc9900aSSibi Sankar clk = clk_get(&pdev->dev, "alternate");
1835bc9900aSSibi Sankar if (IS_ERR(clk))
1845bc9900aSSibi Sankar return PTR_ERR(clk);
1855bc9900aSSibi Sankar
1865bc9900aSSibi Sankar hw_rate = clk_get_rate(clk) / CLK_HW_DIV;
1875bc9900aSSibi Sankar clk_put(clk);
1885bc9900aSSibi Sankar
1895bc9900aSSibi Sankar qp = devm_kzalloc(&pdev->dev, sizeof(*qp), GFP_KERNEL);
1905bc9900aSSibi Sankar if (!qp)
1915bc9900aSSibi Sankar return -ENOMEM;
1925bc9900aSSibi Sankar
1935bc9900aSSibi Sankar qp->base = devm_platform_ioremap_resource(pdev, 0);
1945bc9900aSSibi Sankar if (IS_ERR(qp->base))
1955bc9900aSSibi Sankar return PTR_ERR(qp->base);
1965bc9900aSSibi Sankar
1975bc9900aSSibi Sankar /* HW should be in enabled state to proceed */
1985bc9900aSSibi Sankar if (!(readl_relaxed(qp->base + REG_ENABLE) & 0x1)) {
1995bc9900aSSibi Sankar dev_err(&pdev->dev, "error hardware not enabled\n");
2005bc9900aSSibi Sankar return -ENODEV;
2015bc9900aSSibi Sankar }
2025bc9900aSSibi Sankar
2032bf706eaSSibi Sankar desc = device_get_match_data(&pdev->dev);
2042bf706eaSSibi Sankar if (!desc)
2052bf706eaSSibi Sankar return -EINVAL;
2062bf706eaSSibi Sankar
2072bf706eaSSibi Sankar qp->reg_perf_state = desc->reg_perf_state;
2082bf706eaSSibi Sankar
2095bc9900aSSibi Sankar for (i = 0; i < LUT_MAX_ENTRIES; i++) {
2102bf706eaSSibi Sankar info = readl_relaxed(qp->base + desc->reg_freq_lut +
2112bf706eaSSibi Sankar i * desc->lut_row_size);
2125bc9900aSSibi Sankar src = FIELD_GET(LUT_SRC, info);
2135bc9900aSSibi Sankar lval = FIELD_GET(LUT_L_VAL, info);
2145bc9900aSSibi Sankar if (src)
2155bc9900aSSibi Sankar freq = xo_rate * lval;
2165bc9900aSSibi Sankar else
2175bc9900aSSibi Sankar freq = hw_rate;
2185bc9900aSSibi Sankar
2195bc9900aSSibi Sankar /* Two of the same frequencies signify end of table */
2205bc9900aSSibi Sankar if (i > 0 && prev_freq == freq)
2215bc9900aSSibi Sankar break;
2225bc9900aSSibi Sankar
2235bc9900aSSibi Sankar dev_dbg(&pdev->dev, "index=%d freq=%d\n", i, freq);
2245bc9900aSSibi Sankar
2255bc9900aSSibi Sankar qp->lut_tables[i] = freq;
2265bc9900aSSibi Sankar prev_freq = freq;
2275bc9900aSSibi Sankar }
2285bc9900aSSibi Sankar qp->max_state = i;
2295bc9900aSSibi Sankar
2305bc9900aSSibi Sankar qnodes = desc->nodes;
2315bc9900aSSibi Sankar num_nodes = desc->num_nodes;
2325bc9900aSSibi Sankar
233f77ebddaSDmitry Baryshkov data = devm_kzalloc(&pdev->dev, struct_size(data, nodes, num_nodes), GFP_KERNEL);
2345bc9900aSSibi Sankar if (!data)
2355bc9900aSSibi Sankar return -ENOMEM;
236dd4904f3SKees Cook data->num_nodes = num_nodes;
2375bc9900aSSibi Sankar
2385bc9900aSSibi Sankar provider = &qp->provider;
2395bc9900aSSibi Sankar provider->dev = &pdev->dev;
2408bf5d31cSBjorn Andersson provider->set = qcom_osm_l3_set;
2415bc9900aSSibi Sankar provider->aggregate = icc_std_aggregate;
2425bc9900aSSibi Sankar provider->xlate = of_icc_xlate_onecell;
2435bc9900aSSibi Sankar provider->data = data;
2445bc9900aSSibi Sankar
245174941edSJohan Hovold icc_provider_init(provider);
2465bc9900aSSibi Sankar
2475bc9900aSSibi Sankar for (i = 0; i < num_nodes; i++) {
2485bc9900aSSibi Sankar size_t j;
2495bc9900aSSibi Sankar
2505bc9900aSSibi Sankar node = icc_node_create(qnodes[i]->id);
2515bc9900aSSibi Sankar if (IS_ERR(node)) {
2525bc9900aSSibi Sankar ret = PTR_ERR(node);
2535bc9900aSSibi Sankar goto err;
2545bc9900aSSibi Sankar }
2555bc9900aSSibi Sankar
2565bc9900aSSibi Sankar node->name = qnodes[i]->name;
2578bf5d31cSBjorn Andersson /* Cast away const and add it back in qcom_osm_l3_set() */
258b1a367bbSStephen Boyd node->data = (void *)qnodes[i];
2595bc9900aSSibi Sankar icc_node_add(node, provider);
2605bc9900aSSibi Sankar
2615bc9900aSSibi Sankar for (j = 0; j < qnodes[i]->num_links; j++)
2625bc9900aSSibi Sankar icc_link_create(node, qnodes[i]->links[j]);
2635bc9900aSSibi Sankar
2645bc9900aSSibi Sankar data->nodes[i] = node;
2655bc9900aSSibi Sankar }
2665bc9900aSSibi Sankar
267174941edSJohan Hovold ret = icc_provider_register(provider);
268174941edSJohan Hovold if (ret)
269174941edSJohan Hovold goto err;
270174941edSJohan Hovold
2715bc9900aSSibi Sankar platform_set_drvdata(pdev, qp);
2725bc9900aSSibi Sankar
2735bc9900aSSibi Sankar return 0;
2745bc9900aSSibi Sankar err:
2755bc9900aSSibi Sankar icc_nodes_remove(provider);
2765bc9900aSSibi Sankar
2775bc9900aSSibi Sankar return ret;
2785bc9900aSSibi Sankar }
2795bc9900aSSibi Sankar
2805bc9900aSSibi Sankar static const struct of_device_id osm_l3_of_match[] = {
2819235253eSBjorn Andersson { .compatible = "qcom,epss-l3", .data = &epss_l3_l3_vote },
2829235253eSBjorn Andersson { .compatible = "qcom,osm-l3", .data = &osm_l3 },
283d623264fSBjorn Andersson { .compatible = "qcom,sc7180-osm-l3", .data = &osm_l3 },
2849235253eSBjorn Andersson { .compatible = "qcom,sc7280-epss-l3", .data = &epss_l3_perf_state },
285d623264fSBjorn Andersson { .compatible = "qcom,sdm845-osm-l3", .data = &osm_l3 },
286d623264fSBjorn Andersson { .compatible = "qcom,sm8150-osm-l3", .data = &osm_l3 },
287d623264fSBjorn Andersson { .compatible = "qcom,sc8180x-osm-l3", .data = &osm_l3 },
2889235253eSBjorn Andersson { .compatible = "qcom,sm8250-epss-l3", .data = &epss_l3_perf_state },
2895bc9900aSSibi Sankar { }
2905bc9900aSSibi Sankar };
2915bc9900aSSibi Sankar MODULE_DEVICE_TABLE(of, osm_l3_of_match);
2925bc9900aSSibi Sankar
2935bc9900aSSibi Sankar static struct platform_driver osm_l3_driver = {
2945bc9900aSSibi Sankar .probe = qcom_osm_l3_probe,
2955bc9900aSSibi Sankar .remove = qcom_osm_l3_remove,
2965bc9900aSSibi Sankar .driver = {
2975bc9900aSSibi Sankar .name = "osm-l3",
2985bc9900aSSibi Sankar .of_match_table = osm_l3_of_match,
2997d3b0b0dSGeorgi Djakov .sync_state = icc_sync_state,
3005bc9900aSSibi Sankar },
3015bc9900aSSibi Sankar };
3025bc9900aSSibi Sankar module_platform_driver(osm_l3_driver);
3035bc9900aSSibi Sankar
3045bc9900aSSibi Sankar MODULE_DESCRIPTION("Qualcomm OSM L3 interconnect driver");
3055bc9900aSSibi Sankar MODULE_LICENSE("GPL v2");
306