aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJ Keerthy <j-keerthy@ti.com>2013-07-23 02:35:37 -0400
committerMike Turquette <mturquette@linaro.org>2014-01-17 15:35:31 -0500
commit3cf467a9969db8297dcf783c5b09b0df8fda863b (patch)
treebc17da1cc3f192ade4e939e0ae1193a9a66a0bb0
parent62125a46cd717ad8fa6a64d260d46a0a108e6222 (diff)
CLK: TI: DRA7: Add APLL support
The patch adds support for DRA7 PCIe APLL. The APLL sources the optional functional clocks for PCIe module. APLL stands for Analog PLL. This is different when comapred with DPLL meaning Digital PLL, the phase detection is done using an analog circuit. Signed-off-by: J Keerthy <j-keerthy@ti.com> Signed-off-by: Tero Kristo <t-kristo@ti.com> Acked-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Mike Turquette <mturquette@linaro.org>
-rw-r--r--Documentation/devicetree/bindings/clock/ti/apll.txt31
-rw-r--r--drivers/clk/ti/Makefile2
-rw-r--r--drivers/clk/ti/apll.c223
3 files changed, 255 insertions, 1 deletions
diff --git a/Documentation/devicetree/bindings/clock/ti/apll.txt b/Documentation/devicetree/bindings/clock/ti/apll.txt
new file mode 100644
index 000000000000..7faf5a68b3be
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/ti/apll.txt
@@ -0,0 +1,31 @@
1Binding for Texas Instruments APLL clock.
2
3Binding status: Unstable - ABI compatibility may be broken in the future
4
5This binding uses the common clock binding[1]. It assumes a
6register-mapped APLL with usually two selectable input clocks
7(reference clock and bypass clock), with analog phase locked
8loop logic for multiplying the input clock to a desired output
9clock. This clock also typically supports different operation
10modes (locked, low power stop etc.) APLL mostly behaves like
11a subtype of a DPLL [2], although a simplified one at that.
12
13[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
14[2] Documentation/devicetree/bindings/clock/ti/dpll.txt
15
16Required properties:
17- compatible : shall be "ti,dra7-apll-clock"
18- #clock-cells : from common clock binding; shall be set to 0.
19- clocks : link phandles of parent clocks (clk-ref and clk-bypass)
20- reg : address and length of the register set for controlling the APLL.
21 It contains the information of registers in the following order:
22 "control" - contains the control register base address
23 "idlest" - contains the idlest register base address
24
25Examples:
26 apll_pcie_ck: apll_pcie_ck@4a008200 {
27 #clock-cells = <0>;
28 clocks = <&apll_pcie_in_clk_mux>, <&dpll_pcie_ref_ck>;
29 reg = <0x4a00821c 0x4>, <0x4a008220 0x4>;
30 compatible = "ti,dra7-apll-clock";
31 };
diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile
index 4a8f846740cb..26e994ac3557 100644
--- a/drivers/clk/ti/Makefile
+++ b/drivers/clk/ti/Makefile
@@ -1,7 +1,7 @@
1ifneq ($(CONFIG_OF),) 1ifneq ($(CONFIG_OF),)
2obj-y += clk.o autoidle.o clockdomain.o 2obj-y += clk.o autoidle.o clockdomain.o
3clk-common = dpll.o composite.o divider.o gate.o \ 3clk-common = dpll.o composite.o divider.o gate.o \
4 fixed-factor.o mux.o 4 fixed-factor.o mux.o apll.o
5obj-$(CONFIG_ARCH_OMAP4) += $(clk-common) clk-44xx.o 5obj-$(CONFIG_ARCH_OMAP4) += $(clk-common) clk-44xx.o
6obj-$(CONFIG_SOC_OMAP5) += $(clk-common) clk-54xx.o 6obj-$(CONFIG_SOC_OMAP5) += $(clk-common) clk-54xx.o
7endif 7endif
diff --git a/drivers/clk/ti/apll.c b/drivers/clk/ti/apll.c
new file mode 100644
index 000000000000..b986f61f5a77
--- /dev/null
+++ b/drivers/clk/ti/apll.c
@@ -0,0 +1,223 @@
1/*
2 * OMAP APLL clock support
3 *
4 * Copyright (C) 2013 Texas Instruments, Inc.
5 *
6 * J Keerthy <j-keerthy@ti.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 *
12 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
13 * kind, whether express or implied; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 */
17
18#include <linux/clk-provider.h>
19#include <linux/module.h>
20#include <linux/slab.h>
21#include <linux/io.h>
22#include <linux/err.h>
23#include <linux/string.h>
24#include <linux/log2.h>
25#include <linux/of.h>
26#include <linux/of_address.h>
27#include <linux/clk/ti.h>
28#include <linux/delay.h>
29
30#define APLL_FORCE_LOCK 0x1
31#define APLL_AUTO_IDLE 0x2
32#define MAX_APLL_WAIT_TRIES 1000000
33
34#undef pr_fmt
35#define pr_fmt(fmt) "%s: " fmt, __func__
36
37static int dra7_apll_enable(struct clk_hw *hw)
38{
39 struct clk_hw_omap *clk = to_clk_hw_omap(hw);
40 int r = 0, i = 0;
41 struct dpll_data *ad;
42 const char *clk_name;
43 u8 state = 1;
44 u32 v;
45
46 ad = clk->dpll_data;
47 if (!ad)
48 return -EINVAL;
49
50 clk_name = __clk_get_name(clk->hw.clk);
51
52 state <<= __ffs(ad->idlest_mask);
53
54 /* Check is already locked */
55 v = ti_clk_ll_ops->clk_readl(ad->idlest_reg);
56
57 if ((v & ad->idlest_mask) == state)
58 return r;
59
60 v = ti_clk_ll_ops->clk_readl(ad->control_reg);
61 v &= ~ad->enable_mask;
62 v |= APLL_FORCE_LOCK << __ffs(ad->enable_mask);
63 ti_clk_ll_ops->clk_writel(v, ad->control_reg);
64
65 state <<= __ffs(ad->idlest_mask);
66
67 while (1) {
68 v = ti_clk_ll_ops->clk_readl(ad->idlest_reg);
69 if ((v & ad->idlest_mask) == state)
70 break;
71 if (i > MAX_APLL_WAIT_TRIES)
72 break;
73 i++;
74 udelay(1);
75 }
76
77 if (i == MAX_APLL_WAIT_TRIES) {
78 pr_warn("clock: %s failed transition to '%s'\n",
79 clk_name, (state) ? "locked" : "bypassed");
80 } else {
81 pr_debug("clock: %s transition to '%s' in %d loops\n",
82 clk_name, (state) ? "locked" : "bypassed", i);
83
84 r = 0;
85 }
86
87 return r;
88}
89
90static void dra7_apll_disable(struct clk_hw *hw)
91{
92 struct clk_hw_omap *clk = to_clk_hw_omap(hw);
93 struct dpll_data *ad;
94 u8 state = 1;
95 u32 v;
96
97 ad = clk->dpll_data;
98
99 state <<= __ffs(ad->idlest_mask);
100
101 v = ti_clk_ll_ops->clk_readl(ad->control_reg);
102 v &= ~ad->enable_mask;
103 v |= APLL_AUTO_IDLE << __ffs(ad->enable_mask);
104 ti_clk_ll_ops->clk_writel(v, ad->control_reg);
105}
106
107static int dra7_apll_is_enabled(struct clk_hw *hw)
108{
109 struct clk_hw_omap *clk = to_clk_hw_omap(hw);
110 struct dpll_data *ad;
111 u32 v;
112
113 ad = clk->dpll_data;
114
115 v = ti_clk_ll_ops->clk_readl(ad->control_reg);
116 v &= ad->enable_mask;
117
118 v >>= __ffs(ad->enable_mask);
119
120 return v == APLL_AUTO_IDLE ? 0 : 1;
121}
122
123static u8 dra7_init_apll_parent(struct clk_hw *hw)
124{
125 return 0;
126}
127
128static const struct clk_ops apll_ck_ops = {
129 .enable = &dra7_apll_enable,
130 .disable = &dra7_apll_disable,
131 .is_enabled = &dra7_apll_is_enabled,
132 .get_parent = &dra7_init_apll_parent,
133};
134
135static void __init omap_clk_register_apll(struct clk_hw *hw,
136 struct device_node *node)
137{
138 struct clk_hw_omap *clk_hw = to_clk_hw_omap(hw);
139 struct dpll_data *ad = clk_hw->dpll_data;
140 struct clk *clk;
141
142 ad->clk_ref = of_clk_get(node, 0);
143 ad->clk_bypass = of_clk_get(node, 1);
144
145 if (IS_ERR(ad->clk_ref) || IS_ERR(ad->clk_bypass)) {
146 pr_debug("clk-ref or clk-bypass for %s not ready, retry\n",
147 node->name);
148 if (!ti_clk_retry_init(node, hw, omap_clk_register_apll))
149 return;
150
151 goto cleanup;
152 }
153
154 clk = clk_register(NULL, &clk_hw->hw);
155 if (!IS_ERR(clk)) {
156 of_clk_add_provider(node, of_clk_src_simple_get, clk);
157 kfree(clk_hw->hw.init->parent_names);
158 kfree(clk_hw->hw.init);
159 return;
160 }
161
162cleanup:
163 kfree(clk_hw->dpll_data);
164 kfree(clk_hw->hw.init->parent_names);
165 kfree(clk_hw->hw.init);
166 kfree(clk_hw);
167}
168
169static void __init of_dra7_apll_setup(struct device_node *node)
170{
171 struct dpll_data *ad = NULL;
172 struct clk_hw_omap *clk_hw = NULL;
173 struct clk_init_data *init = NULL;
174 const char **parent_names = NULL;
175 int i;
176
177 ad = kzalloc(sizeof(*ad), GFP_KERNEL);
178 clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL);
179 init = kzalloc(sizeof(*init), GFP_KERNEL);
180 if (!ad || !clk_hw || !init)
181 goto cleanup;
182
183 clk_hw->dpll_data = ad;
184 clk_hw->hw.init = init;
185 clk_hw->flags = MEMMAP_ADDRESSING;
186
187 init->name = node->name;
188 init->ops = &apll_ck_ops;
189
190 init->num_parents = of_clk_get_parent_count(node);
191 if (init->num_parents < 1) {
192 pr_err("dra7 apll %s must have parent(s)\n", node->name);
193 goto cleanup;
194 }
195
196 parent_names = kzalloc(sizeof(char *) * init->num_parents, GFP_KERNEL);
197 if (!parent_names)
198 goto cleanup;
199
200 for (i = 0; i < init->num_parents; i++)
201 parent_names[i] = of_clk_get_parent_name(node, i);
202
203 init->parent_names = parent_names;
204
205 ad->control_reg = ti_clk_get_reg_addr(node, 0);
206 ad->idlest_reg = ti_clk_get_reg_addr(node, 1);
207
208 if (!ad->control_reg || !ad->idlest_reg)
209 goto cleanup;
210
211 ad->idlest_mask = 0x1;
212 ad->enable_mask = 0x3;
213
214 omap_clk_register_apll(&clk_hw->hw, node);
215 return;
216
217cleanup:
218 kfree(parent_names);
219 kfree(ad);
220 kfree(clk_hw);
221 kfree(init);
222}
223CLK_OF_DECLARE(dra7_apll_clock, "ti,dra7-apll-clock", of_dra7_apll_setup);