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