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 | |
9 | #include <linux/clk.h> |
10 | #include <linux/clk-provider.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/pm_opp.h> |
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 | |
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, id: "xo" ); |
50 | if (IS_ERR(ptr: xo_clk)) |
51 | return NULL; |
52 | |
53 | xo_freq = clk_get_rate(clk: 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, n: count + 1, size: 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: &freq); |
71 | if (IS_ERR(ptr: 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 | |
88 | static int qcom_a53pll_probe(struct platform_device *pdev) |
89 | { |
90 | struct device *dev = &pdev->dev; |
91 | struct device_node *np = dev->of_node; |
92 | struct regmap *regmap; |
93 | struct clk_pll *pll; |
94 | void __iomem *base; |
95 | struct clk_init_data init = { }; |
96 | int ret; |
97 | |
98 | pll = devm_kzalloc(dev, size: sizeof(*pll), GFP_KERNEL); |
99 | if (!pll) |
100 | return -ENOMEM; |
101 | |
102 | base = devm_platform_ioremap_resource(pdev, index: 0); |
103 | if (IS_ERR(ptr: base)) |
104 | return PTR_ERR(ptr: base); |
105 | |
106 | regmap = devm_regmap_init_mmio(dev, base, &a53pll_regmap_config); |
107 | if (IS_ERR(ptr: regmap)) |
108 | return PTR_ERR(ptr: 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; |
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 | } |
123 | |
124 | /* Use an unique name by appending @unit-address */ |
125 | init.name = devm_kasprintf(dev, GFP_KERNEL, fmt: "a53pll%s" , |
126 | strchrnul(np->full_name, '@')); |
127 | if (!init.name) |
128 | return -ENOMEM; |
129 | |
130 | init.parent_data = &(const struct clk_parent_data){ |
131 | .fw_name = "xo" , .name = "xo_board" , |
132 | }; |
133 | init.num_parents = 1; |
134 | init.ops = &clk_pll_sr2_ops; |
135 | pll->clkr.hw.init = &init; |
136 | |
137 | ret = devm_clk_register_regmap(dev, rclk: &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, get: of_clk_hw_simple_get, |
144 | data: &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" }, |
155 | { .compatible = "qcom,msm8939-a53pll" }, |
156 | { } |
157 | }; |
158 | MODULE_DEVICE_TABLE(of, qcom_a53pll_match_table); |
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" ); |
171 | |