aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/clk/ti/apll.c
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 /drivers/clk/ti/apll.c
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>
Diffstat (limited to 'drivers/clk/ti/apll.c')
-rw-r--r--drivers/clk/ti/apll.c223
1 files changed, 223 insertions, 0 deletions
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);