diff options
-rw-r--r-- | Documentation/devicetree/bindings/clock/axs10x-i2s-pll-clock.txt | 25 | ||||
-rw-r--r-- | drivers/clk/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/axs10x/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/axs10x/i2s_pll_clock.c | 228 |
4 files changed, 255 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/clock/axs10x-i2s-pll-clock.txt b/Documentation/devicetree/bindings/clock/axs10x-i2s-pll-clock.txt new file mode 100644 index 000000000000..5ffc8df7e6da --- /dev/null +++ b/Documentation/devicetree/bindings/clock/axs10x-i2s-pll-clock.txt | |||
@@ -0,0 +1,25 @@ | |||
1 | Binding for the AXS10X I2S PLL clock | ||
2 | |||
3 | This binding uses the common clock binding[1]. | ||
4 | |||
5 | [1] Documentation/devicetree/bindings/clock/clock-bindings.txt | ||
6 | |||
7 | Required properties: | ||
8 | - compatible: shall be "snps,axs10x-i2s-pll-clock" | ||
9 | - reg : address and length of the I2S PLL register set. | ||
10 | - clocks: shall be the input parent clock phandle for the PLL. | ||
11 | - #clock-cells: from common clock binding; Should always be set to 0. | ||
12 | |||
13 | Example: | ||
14 | pll_clock: pll_clock { | ||
15 | compatible = "fixed-clock"; | ||
16 | clock-frequency = <27000000>; | ||
17 | #clock-cells = <0>; | ||
18 | }; | ||
19 | |||
20 | i2s_clock@100a0 { | ||
21 | compatible = "snps,axs10x-i2s-pll-clock"; | ||
22 | reg = <0x100a0 0x10>; | ||
23 | clocks = <&pll_clock>; | ||
24 | #clock-cells = <0>; | ||
25 | }; | ||
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 4ef71a13ab37..6d1e3bef4e30 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile | |||
@@ -86,3 +86,4 @@ obj-$(CONFIG_X86) += x86/ | |||
86 | obj-$(CONFIG_ARCH_ZX) += zte/ | 86 | obj-$(CONFIG_ARCH_ZX) += zte/ |
87 | obj-$(CONFIG_ARCH_ZYNQ) += zynq/ | 87 | obj-$(CONFIG_ARCH_ZYNQ) += zynq/ |
88 | obj-$(CONFIG_H8300) += h8300/ | 88 | obj-$(CONFIG_H8300) += h8300/ |
89 | obj-$(CONFIG_ARC_PLAT_AXS10X) += axs10x/ | ||
diff --git a/drivers/clk/axs10x/Makefile b/drivers/clk/axs10x/Makefile new file mode 100644 index 000000000000..01996b871b06 --- /dev/null +++ b/drivers/clk/axs10x/Makefile | |||
@@ -0,0 +1 @@ | |||
obj-y += i2s_pll_clock.o | |||
diff --git a/drivers/clk/axs10x/i2s_pll_clock.c b/drivers/clk/axs10x/i2s_pll_clock.c new file mode 100644 index 000000000000..411310d29581 --- /dev/null +++ b/drivers/clk/axs10x/i2s_pll_clock.c | |||
@@ -0,0 +1,228 @@ | |||
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 | |||
185 | clk_name = node->name; | ||
186 | init.name = clk_name; | ||
187 | init.ops = &i2s_pll_ops; | ||
188 | parent_name = of_clk_get_parent_name(node, 0); | ||
189 | init.parent_names = &parent_name; | ||
190 | init.num_parents = 1; | ||
191 | pll_clk->hw.init = &init; | ||
192 | pll_clk->dev = dev; | ||
193 | |||
194 | clk = devm_clk_register(dev, &pll_clk->hw); | ||
195 | if (IS_ERR(clk)) { | ||
196 | dev_err(dev, "failed to register %s clock (%ld)\n", | ||
197 | clk_name, PTR_ERR(clk)); | ||
198 | return PTR_ERR(clk); | ||
199 | } | ||
200 | |||
201 | return of_clk_add_provider(node, of_clk_src_simple_get, clk); | ||
202 | } | ||
203 | |||
204 | static int i2s_pll_clk_remove(struct platform_device *pdev) | ||
205 | { | ||
206 | of_clk_del_provider(pdev->dev.of_node); | ||
207 | return 0; | ||
208 | } | ||
209 | |||
210 | static const struct of_device_id i2s_pll_clk_id[] = { | ||
211 | { .compatible = "snps,axs10x-i2s-pll-clock", }, | ||
212 | { }, | ||
213 | }; | ||
214 | MODULE_DEVICE_TABLE(of, i2s_pll_clk_id); | ||
215 | |||
216 | static struct platform_driver i2s_pll_clk_driver = { | ||
217 | .driver = { | ||
218 | .name = "axs10x-i2s-pll-clock", | ||
219 | .of_match_table = i2s_pll_clk_id, | ||
220 | }, | ||
221 | .probe = i2s_pll_clk_probe, | ||
222 | .remove = i2s_pll_clk_remove, | ||
223 | }; | ||
224 | module_platform_driver(i2s_pll_clk_driver); | ||
225 | |||
226 | MODULE_AUTHOR("Jose Abreu <joabreu@synopsys.com>"); | ||
227 | MODULE_DESCRIPTION("Synopsys AXS10X SDP I2S PLL Clock Driver"); | ||
228 | MODULE_LICENSE("GPL v2"); | ||