Commit | Line | Data |
---|---|---|
0c6ab1b8 GD |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Qualcomm A53 PLL driver | |
4 | * | |
5 | * Copyright (c) 2017, Linaro Limited | |
6 | * Author: Georgi Djakov <georgi.djakov@linaro.org> | |
7 | */ | |
8 | ||
5d9bc010 | 9 | #include <linux/clk.h> |
0c6ab1b8 GD |
10 | #include <linux/clk-provider.h> |
11 | #include <linux/kernel.h> | |
12 | #include <linux/platform_device.h> | |
5d9bc010 | 13 | #include <linux/pm_opp.h> |
0c6ab1b8 GD |
14 | #include <linux/regmap.h> |
15 | #include <linux/module.h> | |
16 | ||
17 | #include "clk-pll.h" | |
18 | #include "clk-regmap.h" | |
19 | ||
20 | static const struct pll_freq_tbl a53pll_freq[] = { | |
21 | { 998400000, 52, 0x0, 0x1, 0 }, | |
22 | { 1094400000, 57, 0x0, 0x1, 0 }, | |
23 | { 1152000000, 62, 0x0, 0x1, 0 }, | |
24 | { 1209600000, 63, 0x0, 0x1, 0 }, | |
25 | { 1248000000, 65, 0x0, 0x1, 0 }, | |
26 | { 1363200000, 71, 0x0, 0x1, 0 }, | |
27 | { 1401600000, 73, 0x0, 0x1, 0 }, | |
28 | { } | |
29 | }; | |
30 | ||
31 | static const struct regmap_config a53pll_regmap_config = { | |
32 | .reg_bits = 32, | |
33 | .reg_stride = 4, | |
34 | .val_bits = 32, | |
35 | .max_register = 0x40, | |
36 | .fast_io = true, | |
37 | }; | |
38 | ||
5d9bc010 SG |
39 | static struct pll_freq_tbl *qcom_a53pll_get_freq_tbl(struct device *dev) |
40 | { | |
41 | struct pll_freq_tbl *freq_tbl; | |
42 | unsigned long xo_freq; | |
43 | unsigned long freq; | |
44 | struct clk *xo_clk; | |
45 | int count; | |
46 | int ret; | |
47 | int i; | |
48 | ||
49 | xo_clk = devm_clk_get(dev, "xo"); | |
50 | if (IS_ERR(xo_clk)) | |
51 | return NULL; | |
52 | ||
53 | xo_freq = clk_get_rate(xo_clk); | |
54 | ||
55 | ret = devm_pm_opp_of_add_table(dev); | |
56 | if (ret) | |
57 | return NULL; | |
58 | ||
59 | count = dev_pm_opp_get_opp_count(dev); | |
60 | if (count <= 0) | |
61 | return NULL; | |
62 | ||
63 | freq_tbl = devm_kcalloc(dev, count + 1, sizeof(*freq_tbl), GFP_KERNEL); | |
64 | if (!freq_tbl) | |
65 | return NULL; | |
66 | ||
67 | for (i = 0, freq = 0; i < count; i++, freq++) { | |
68 | struct dev_pm_opp *opp; | |
69 | ||
70 | opp = dev_pm_opp_find_freq_ceil(dev, &freq); | |
71 | if (IS_ERR(opp)) | |
72 | return NULL; | |
73 | ||
74 | /* Skip the freq that is not divisible */ | |
75 | if (freq % xo_freq) | |
76 | continue; | |
77 | ||
78 | freq_tbl[i].freq = freq; | |
79 | freq_tbl[i].l = freq / xo_freq; | |
80 | freq_tbl[i].n = 1; | |
81 | ||
82 | dev_pm_opp_put(opp); | |
83 | } | |
84 | ||
85 | return freq_tbl; | |
86 | } | |
87 | ||
0c6ab1b8 GD |
88 | static int qcom_a53pll_probe(struct platform_device *pdev) |
89 | { | |
90 | struct device *dev = &pdev->dev; | |
05cc560c | 91 | struct device_node *np = dev->of_node; |
0c6ab1b8 | 92 | struct regmap *regmap; |
0c6ab1b8 GD |
93 | struct clk_pll *pll; |
94 | void __iomem *base; | |
95 | struct clk_init_data init = { }; | |
96 | int ret; | |
97 | ||
98 | pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL); | |
99 | if (!pll) | |
100 | return -ENOMEM; | |
101 | ||
aacbbe6b | 102 | base = devm_platform_ioremap_resource(pdev, 0); |
0c6ab1b8 GD |
103 | if (IS_ERR(base)) |
104 | return PTR_ERR(base); | |
105 | ||
106 | regmap = devm_regmap_init_mmio(dev, base, &a53pll_regmap_config); | |
107 | if (IS_ERR(regmap)) | |
108 | return PTR_ERR(regmap); | |
109 | ||
110 | pll->l_reg = 0x04; | |
111 | pll->m_reg = 0x08; | |
112 | pll->n_reg = 0x0c; | |
113 | pll->config_reg = 0x14; | |
114 | pll->mode_reg = 0x00; | |
115 | pll->status_reg = 0x1c; | |
116 | pll->status_bit = 16; | |
5d9bc010 SG |
117 | |
118 | pll->freq_tbl = qcom_a53pll_get_freq_tbl(dev); | |
119 | if (!pll->freq_tbl) { | |
120 | /* Fall on a53pll_freq if no freq_tbl is found from OPP */ | |
121 | pll->freq_tbl = a53pll_freq; | |
122 | } | |
0c6ab1b8 | 123 | |
05cc560c SG |
124 | /* Use an unique name by appending @unit-address */ |
125 | init.name = devm_kasprintf(dev, GFP_KERNEL, "a53pll%s", | |
126 | strchrnul(np->full_name, '@')); | |
127 | if (!init.name) | |
128 | return -ENOMEM; | |
129 | ||
867bc326 DB |
130 | init.parent_data = &(const struct clk_parent_data){ |
131 | .fw_name = "xo", .name = "xo_board", | |
132 | }; | |
0c6ab1b8 GD |
133 | init.num_parents = 1; |
134 | init.ops = &clk_pll_sr2_ops; | |
0c6ab1b8 GD |
135 | pll->clkr.hw.init = &init; |
136 | ||
137 | ret = devm_clk_register_regmap(dev, &pll->clkr); | |
138 | if (ret) { | |
139 | dev_err(dev, "failed to register regmap clock: %d\n", ret); | |
140 | return ret; | |
141 | } | |
142 | ||
143 | ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, | |
144 | &pll->clkr.hw); | |
145 | if (ret) { | |
146 | dev_err(dev, "failed to add clock provider: %d\n", ret); | |
147 | return ret; | |
148 | } | |
149 | ||
150 | return 0; | |
151 | } | |
152 | ||
153 | static const struct of_device_id qcom_a53pll_match_table[] = { | |
154 | { .compatible = "qcom,msm8916-a53pll" }, | |
5d9bc010 | 155 | { .compatible = "qcom,msm8939-a53pll" }, |
0c6ab1b8 GD |
156 | { } |
157 | }; | |
790b516a | 158 | MODULE_DEVICE_TABLE(of, qcom_a53pll_match_table); |
0c6ab1b8 GD |
159 | |
160 | static struct platform_driver qcom_a53pll_driver = { | |
161 | .probe = qcom_a53pll_probe, | |
162 | .driver = { | |
163 | .name = "qcom-a53pll", | |
164 | .of_match_table = qcom_a53pll_match_table, | |
165 | }, | |
166 | }; | |
167 | module_platform_driver(qcom_a53pll_driver); | |
168 | ||
169 | MODULE_DESCRIPTION("Qualcomm A53 PLL Driver"); | |
170 | MODULE_LICENSE("GPL v2"); |