Commit | Line | Data |
---|---|---|
923587aa JA |
1 | /* |
2 | * Synopsys AXS10X SDP I2S PLL clock driver | |
3 | * | |
4 | * Copyright (C) 2016 Synopsys | |
5 | * | |
6 | * This file is licensed under the terms of the GNU General Public | |
7 | * License version 2. This program is licensed "as is" without any | |
8 | * warranty of any kind, whether express or implied. | |
9 | */ | |
10 | ||
11 | #include <linux/platform_device.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/clk-provider.h> | |
14 | #include <linux/err.h> | |
15 | #include <linux/device.h> | |
16 | #include <linux/of_address.h> | |
17 | #include <linux/slab.h> | |
18 | #include <linux/of.h> | |
19 | ||
20 | /* PLL registers addresses */ | |
21 | #define PLL_IDIV_REG 0x0 | |
22 | #define PLL_FBDIV_REG 0x4 | |
23 | #define PLL_ODIV0_REG 0x8 | |
24 | #define PLL_ODIV1_REG 0xC | |
25 | ||
26 | struct i2s_pll_cfg { | |
27 | unsigned int rate; | |
28 | unsigned int idiv; | |
29 | unsigned int fbdiv; | |
30 | unsigned int odiv0; | |
31 | unsigned int odiv1; | |
32 | }; | |
33 | ||
34 | static const struct i2s_pll_cfg i2s_pll_cfg_27m[] = { | |
35 | /* 27 Mhz */ | |
36 | { 1024000, 0x104, 0x451, 0x10E38, 0x2000 }, | |
37 | { 1411200, 0x104, 0x596, 0x10D35, 0x2000 }, | |
38 | { 1536000, 0x208, 0xA28, 0x10B2C, 0x2000 }, | |
39 | { 2048000, 0x82, 0x451, 0x10E38, 0x2000 }, | |
40 | { 2822400, 0x82, 0x596, 0x10D35, 0x2000 }, | |
41 | { 3072000, 0x104, 0xA28, 0x10B2C, 0x2000 }, | |
42 | { 2116800, 0x82, 0x3CF, 0x10C30, 0x2000 }, | |
43 | { 2304000, 0x104, 0x79E, 0x10B2C, 0x2000 }, | |
44 | { 0, 0, 0, 0, 0 }, | |
45 | }; | |
46 | ||
47 | static const struct i2s_pll_cfg i2s_pll_cfg_28m[] = { | |
48 | /* 28.224 Mhz */ | |
49 | { 1024000, 0x82, 0x105, 0x107DF, 0x2000 }, | |
50 | { 1411200, 0x28A, 0x1, 0x10001, 0x2000 }, | |
51 | { 1536000, 0xA28, 0x187, 0x10042, 0x2000 }, | |
52 | { 2048000, 0x41, 0x105, 0x107DF, 0x2000 }, | |
53 | { 2822400, 0x145, 0x1, 0x10001, 0x2000 }, | |
54 | { 3072000, 0x514, 0x187, 0x10042, 0x2000 }, | |
55 | { 2116800, 0x514, 0x42, 0x10001, 0x2000 }, | |
56 | { 2304000, 0x619, 0x82, 0x10001, 0x2000 }, | |
57 | { 0, 0, 0, 0, 0 }, | |
58 | }; | |
59 | ||
60 | struct i2s_pll_clk { | |
61 | void __iomem *base; | |
62 | struct clk_hw hw; | |
63 | struct device *dev; | |
64 | }; | |
65 | ||
66 | static inline void i2s_pll_write(struct i2s_pll_clk *clk, unsigned int reg, | |
67 | unsigned int val) | |
68 | { | |
69 | writel_relaxed(val, clk->base + reg); | |
70 | } | |
71 | ||
72 | static inline unsigned int i2s_pll_read(struct i2s_pll_clk *clk, | |
73 | unsigned int reg) | |
74 | { | |
75 | return readl_relaxed(clk->base + reg); | |
76 | } | |
77 | ||
78 | static inline struct i2s_pll_clk *to_i2s_pll_clk(struct clk_hw *hw) | |
79 | { | |
80 | return container_of(hw, struct i2s_pll_clk, hw); | |
81 | } | |
82 | ||
83 | static inline unsigned int i2s_pll_get_value(unsigned int val) | |
84 | { | |
85 | return (val & 0x3F) + ((val >> 6) & 0x3F); | |
86 | } | |
87 | ||
88 | static const struct i2s_pll_cfg *i2s_pll_get_cfg(unsigned long prate) | |
89 | { | |
90 | switch (prate) { | |
91 | case 27000000: | |
92 | return i2s_pll_cfg_27m; | |
93 | case 28224000: | |
94 | return i2s_pll_cfg_28m; | |
95 | default: | |
96 | return NULL; | |
97 | } | |
98 | } | |
99 | ||
100 | static unsigned long i2s_pll_recalc_rate(struct clk_hw *hw, | |
101 | unsigned long parent_rate) | |
102 | { | |
103 | struct i2s_pll_clk *clk = to_i2s_pll_clk(hw); | |
104 | unsigned int idiv, fbdiv, odiv; | |
105 | ||
106 | idiv = i2s_pll_get_value(i2s_pll_read(clk, PLL_IDIV_REG)); | |
107 | fbdiv = i2s_pll_get_value(i2s_pll_read(clk, PLL_FBDIV_REG)); | |
108 | odiv = i2s_pll_get_value(i2s_pll_read(clk, PLL_ODIV0_REG)); | |
109 | ||
110 | return ((parent_rate / idiv) * fbdiv) / odiv; | |
111 | } | |
112 | ||
113 | static long i2s_pll_round_rate(struct clk_hw *hw, unsigned long rate, | |
114 | unsigned long *prate) | |
115 | { | |
116 | struct i2s_pll_clk *clk = to_i2s_pll_clk(hw); | |
117 | const struct i2s_pll_cfg *pll_cfg = i2s_pll_get_cfg(*prate); | |
118 | int i; | |
119 | ||
120 | if (!pll_cfg) { | |
121 | dev_err(clk->dev, "invalid parent rate=%ld\n", *prate); | |
122 | return -EINVAL; | |
123 | } | |
124 | ||
125 | for (i = 0; pll_cfg[i].rate != 0; i++) | |
126 | if (pll_cfg[i].rate == rate) | |
127 | return rate; | |
128 | ||
129 | return -EINVAL; | |
130 | } | |
131 | ||
132 | static int i2s_pll_set_rate(struct clk_hw *hw, unsigned long rate, | |
133 | unsigned long parent_rate) | |
134 | { | |
135 | struct i2s_pll_clk *clk = to_i2s_pll_clk(hw); | |
136 | const struct i2s_pll_cfg *pll_cfg = i2s_pll_get_cfg(parent_rate); | |
137 | int i; | |
138 | ||
139 | if (!pll_cfg) { | |
140 | dev_err(clk->dev, "invalid parent rate=%ld\n", parent_rate); | |
141 | return -EINVAL; | |
142 | } | |
143 | ||
144 | for (i = 0; pll_cfg[i].rate != 0; i++) { | |
145 | if (pll_cfg[i].rate == rate) { | |
146 | i2s_pll_write(clk, PLL_IDIV_REG, pll_cfg[i].idiv); | |
147 | i2s_pll_write(clk, PLL_FBDIV_REG, pll_cfg[i].fbdiv); | |
148 | i2s_pll_write(clk, PLL_ODIV0_REG, pll_cfg[i].odiv0); | |
149 | i2s_pll_write(clk, PLL_ODIV1_REG, pll_cfg[i].odiv1); | |
150 | return 0; | |
151 | } | |
152 | } | |
153 | ||
154 | dev_err(clk->dev, "invalid rate=%ld, parent_rate=%ld\n", rate, | |
155 | parent_rate); | |
156 | return -EINVAL; | |
157 | } | |
158 | ||
159 | static const struct clk_ops i2s_pll_ops = { | |
160 | .recalc_rate = i2s_pll_recalc_rate, | |
161 | .round_rate = i2s_pll_round_rate, | |
162 | .set_rate = i2s_pll_set_rate, | |
163 | }; | |
164 | ||
165 | static int i2s_pll_clk_probe(struct platform_device *pdev) | |
166 | { | |
167 | struct device *dev = &pdev->dev; | |
168 | struct device_node *node = dev->of_node; | |
169 | const char *clk_name; | |
170 | const char *parent_name; | |
171 | struct clk *clk; | |
172 | struct i2s_pll_clk *pll_clk; | |
173 | struct clk_init_data init; | |
174 | struct resource *mem; | |
175 | ||
176 | pll_clk = devm_kzalloc(dev, sizeof(*pll_clk), GFP_KERNEL); | |
177 | if (!pll_clk) | |
178 | return -ENOMEM; | |
179 | ||
180 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
181 | pll_clk->base = devm_ioremap_resource(dev, mem); | |
182 | if (IS_ERR(pll_clk->base)) | |
183 | return PTR_ERR(pll_clk->base); | |
184 | ||
6205406c | 185 | memset(&init, 0, sizeof(init)); |
923587aa JA |
186 | clk_name = node->name; |
187 | init.name = clk_name; | |
188 | init.ops = &i2s_pll_ops; | |
189 | parent_name = of_clk_get_parent_name(node, 0); | |
190 | init.parent_names = &parent_name; | |
191 | init.num_parents = 1; | |
192 | pll_clk->hw.init = &init; | |
193 | pll_clk->dev = dev; | |
194 | ||
195 | clk = devm_clk_register(dev, &pll_clk->hw); | |
196 | if (IS_ERR(clk)) { | |
197 | dev_err(dev, "failed to register %s clock (%ld)\n", | |
198 | clk_name, PTR_ERR(clk)); | |
199 | return PTR_ERR(clk); | |
200 | } | |
201 | ||
202 | return of_clk_add_provider(node, of_clk_src_simple_get, clk); | |
203 | } | |
204 | ||
205 | static int i2s_pll_clk_remove(struct platform_device *pdev) | |
206 | { | |
207 | of_clk_del_provider(pdev->dev.of_node); | |
208 | return 0; | |
209 | } | |
210 | ||
211 | static const struct of_device_id i2s_pll_clk_id[] = { | |
212 | { .compatible = "snps,axs10x-i2s-pll-clock", }, | |
213 | { }, | |
214 | }; | |
215 | MODULE_DEVICE_TABLE(of, i2s_pll_clk_id); | |
216 | ||
217 | static struct platform_driver i2s_pll_clk_driver = { | |
218 | .driver = { | |
219 | .name = "axs10x-i2s-pll-clock", | |
220 | .of_match_table = i2s_pll_clk_id, | |
221 | }, | |
222 | .probe = i2s_pll_clk_probe, | |
223 | .remove = i2s_pll_clk_remove, | |
224 | }; | |
225 | module_platform_driver(i2s_pll_clk_driver); | |
226 | ||
227 | MODULE_AUTHOR("Jose Abreu <joabreu@synopsys.com>"); | |
228 | MODULE_DESCRIPTION("Synopsys AXS10X SDP I2S PLL Clock Driver"); | |
229 | MODULE_LICENSE("GPL v2"); |