aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Ujfalusi <peter.ujfalusi@ti.com>2014-05-07 06:20:47 -0400
committerTero Kristo <t-kristo@ti.com>2014-05-28 06:06:54 -0400
commit9ac33b0ce81fa48dd39e7ddfc1bf4519052181dd (patch)
treeadf61f1b79d26caba94187e200786f92c6290811
parent2d5e447914722f3c79103ad54baa1170661ac553 (diff)
CLK: TI: Driver for DRA7 ATL (Audio Tracking Logic)
Audio Tracking Logic is designed to be used by HD Radio applications to synchronize the audio output clocks to the baseband clock. ATL can be also used to track errors between two reference clocks (BWS, AWS) and generate a modulated clock output which averages to some desired frequency. In essence ATL is generating a clock to be used by an audio codec and also to be used by the SoC as MCLK. To be able to integrate the ATL provided clocks to the clock tree we need two types of DT binding: - DT clock nodes to represent the ATL clocks towards the CCF - binding for the ATL IP itself which is going to handle the hw configuration The reason for this type of setup is that ATL itself is a separate device in the SoC, it has it's own address space and clock domain. Other IPs can use the ATL generated clock as their functional clock (McASPs for example) and external components like audio codecs can also use the very same clock as their MCLK. The ATL IP in DRA7 contains 4 ATL instences. Signed-off-by: Peter Ujfalusi <peter.ujfalusi@ti.com> Signed-off-by: Tero Kristo <t-kristo@ti.com>
-rw-r--r--drivers/clk/ti/Makefile3
-rw-r--r--drivers/clk/ti/clk-dra7-atl.c312
2 files changed, 314 insertions, 1 deletions
diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile
index 4afeaed9e9ba..ed4d0aaf8916 100644
--- a/drivers/clk/ti/Makefile
+++ b/drivers/clk/ti/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_ARCH_OMAP2) += $(clk-common) interface.o clk-2xxx.o
7obj-$(CONFIG_ARCH_OMAP3) += $(clk-common) interface.o clk-3xxx.o 7obj-$(CONFIG_ARCH_OMAP3) += $(clk-common) interface.o clk-3xxx.o
8obj-$(CONFIG_ARCH_OMAP4) += $(clk-common) clk-44xx.o 8obj-$(CONFIG_ARCH_OMAP4) += $(clk-common) clk-44xx.o
9obj-$(CONFIG_SOC_OMAP5) += $(clk-common) clk-54xx.o 9obj-$(CONFIG_SOC_OMAP5) += $(clk-common) clk-54xx.o
10obj-$(CONFIG_SOC_DRA7XX) += $(clk-common) clk-7xx.o 10obj-$(CONFIG_SOC_DRA7XX) += $(clk-common) clk-7xx.o \
11 clk-dra7-atl.o
11obj-$(CONFIG_SOC_AM43XX) += $(clk-common) clk-43xx.o 12obj-$(CONFIG_SOC_AM43XX) += $(clk-common) clk-43xx.o
12endif 13endif
diff --git a/drivers/clk/ti/clk-dra7-atl.c b/drivers/clk/ti/clk-dra7-atl.c
new file mode 100644
index 000000000000..4a65b410e4d5
--- /dev/null
+++ b/drivers/clk/ti/clk-dra7-atl.c
@@ -0,0 +1,312 @@
1/*
2 * DRA7 ATL (Audio Tracking Logic) clock driver
3 *
4 * Copyright (C) 2013 Texas Instruments, Inc.
5 *
6 * Peter Ujfalusi <peter.ujfalusi@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/module.h>
19#include <linux/clk-provider.h>
20#include <linux/slab.h>
21#include <linux/io.h>
22#include <linux/of.h>
23#include <linux/of_address.h>
24#include <linux/platform_device.h>
25#include <linux/pm_runtime.h>
26
27#define DRA7_ATL_INSTANCES 4
28
29#define DRA7_ATL_PPMR_REG(id) (0x200 + (id * 0x80))
30#define DRA7_ATL_BBSR_REG(id) (0x204 + (id * 0x80))
31#define DRA7_ATL_ATLCR_REG(id) (0x208 + (id * 0x80))
32#define DRA7_ATL_SWEN_REG(id) (0x210 + (id * 0x80))
33#define DRA7_ATL_BWSMUX_REG(id) (0x214 + (id * 0x80))
34#define DRA7_ATL_AWSMUX_REG(id) (0x218 + (id * 0x80))
35#define DRA7_ATL_PCLKMUX_REG(id) (0x21c + (id * 0x80))
36
37#define DRA7_ATL_SWEN BIT(0)
38#define DRA7_ATL_DIVIDER_MASK (0x1f)
39#define DRA7_ATL_PCLKMUX BIT(0)
40struct dra7_atl_clock_info;
41
42struct dra7_atl_desc {
43 struct clk *clk;
44 struct clk_hw hw;
45 struct dra7_atl_clock_info *cinfo;
46 int id;
47
48 bool probed; /* the driver for the IP has been loaded */
49 bool valid; /* configured */
50 bool enabled;
51 u32 bws; /* Baseband Word Select Mux */
52 u32 aws; /* Audio Word Select Mux */
53 u32 divider; /* Cached divider value */
54};
55
56struct dra7_atl_clock_info {
57 struct device *dev;
58 void __iomem *iobase;
59
60 struct dra7_atl_desc *cdesc;
61};
62
63#define to_atl_desc(_hw) container_of(_hw, struct dra7_atl_desc, hw)
64
65static inline void atl_write(struct dra7_atl_clock_info *cinfo, u32 reg,
66 u32 val)
67{
68 __raw_writel(val, cinfo->iobase + reg);
69}
70
71static inline int atl_read(struct dra7_atl_clock_info *cinfo, u32 reg)
72{
73 return __raw_readl(cinfo->iobase + reg);
74}
75
76static int atl_clk_enable(struct clk_hw *hw)
77{
78 struct dra7_atl_desc *cdesc = to_atl_desc(hw);
79
80 if (!cdesc->probed)
81 goto out;
82
83 if (unlikely(!cdesc->valid))
84 dev_warn(cdesc->cinfo->dev, "atl%d has not been configured\n",
85 cdesc->id);
86 pm_runtime_get_sync(cdesc->cinfo->dev);
87
88 atl_write(cdesc->cinfo, DRA7_ATL_ATLCR_REG(cdesc->id),
89 cdesc->divider - 1);
90 atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), DRA7_ATL_SWEN);
91
92out:
93 cdesc->enabled = true;
94
95 return 0;
96}
97
98static void atl_clk_disable(struct clk_hw *hw)
99{
100 struct dra7_atl_desc *cdesc = to_atl_desc(hw);
101
102 if (!cdesc->probed)
103 goto out;
104
105 atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), 0);
106 pm_runtime_put_sync(cdesc->cinfo->dev);
107
108out:
109 cdesc->enabled = false;
110}
111
112static int atl_clk_is_enabled(struct clk_hw *hw)
113{
114 struct dra7_atl_desc *cdesc = to_atl_desc(hw);
115
116 return cdesc->enabled;
117}
118
119static unsigned long atl_clk_recalc_rate(struct clk_hw *hw,
120 unsigned long parent_rate)
121{
122 struct dra7_atl_desc *cdesc = to_atl_desc(hw);
123
124 return parent_rate / cdesc->divider;
125}
126
127static long atl_clk_round_rate(struct clk_hw *hw, unsigned long rate,
128 unsigned long *parent_rate)
129{
130 unsigned divider;
131
132 divider = (*parent_rate + rate / 2) / rate;
133 if (divider > DRA7_ATL_DIVIDER_MASK + 1)
134 divider = DRA7_ATL_DIVIDER_MASK + 1;
135
136 return *parent_rate / divider;
137}
138
139static int atl_clk_set_rate(struct clk_hw *hw, unsigned long rate,
140 unsigned long parent_rate)
141{
142 struct dra7_atl_desc *cdesc = to_atl_desc(hw);
143 u32 divider;
144
145 divider = ((parent_rate + rate / 2) / rate) - 1;
146 if (divider > DRA7_ATL_DIVIDER_MASK)
147 divider = DRA7_ATL_DIVIDER_MASK;
148
149 cdesc->divider = divider + 1;
150
151 return 0;
152}
153
154const struct clk_ops atl_clk_ops = {
155 .enable = atl_clk_enable,
156 .disable = atl_clk_disable,
157 .is_enabled = atl_clk_is_enabled,
158 .recalc_rate = atl_clk_recalc_rate,
159 .round_rate = atl_clk_round_rate,
160 .set_rate = atl_clk_set_rate,
161};
162
163static void __init of_dra7_atl_clock_setup(struct device_node *node)
164{
165 struct dra7_atl_desc *clk_hw = NULL;
166 struct clk_init_data init = { 0 };
167 const char **parent_names = NULL;
168 struct clk *clk;
169
170 clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL);
171 if (!clk_hw) {
172 pr_err("%s: could not allocate dra7_atl_desc\n", __func__);
173 return;
174 }
175
176 clk_hw->hw.init = &init;
177 clk_hw->divider = 1;
178 init.name = node->name;
179 init.ops = &atl_clk_ops;
180 init.flags = CLK_IGNORE_UNUSED;
181 init.num_parents = of_clk_get_parent_count(node);
182
183 if (init.num_parents != 1) {
184 pr_err("%s: atl clock %s must have 1 parent\n", __func__,
185 node->name);
186 goto cleanup;
187 }
188
189 parent_names = kzalloc(sizeof(char *), GFP_KERNEL);
190
191 if (!parent_names)
192 goto cleanup;
193
194 parent_names[0] = of_clk_get_parent_name(node, 0);
195
196 init.parent_names = parent_names;
197
198 clk = clk_register(NULL, &clk_hw->hw);
199
200 if (!IS_ERR(clk)) {
201 of_clk_add_provider(node, of_clk_src_simple_get, clk);
202 return;
203 }
204cleanup:
205 kfree(parent_names);
206 kfree(clk_hw);
207}
208CLK_OF_DECLARE(dra7_atl_clock, "ti,dra7-atl-clock", of_dra7_atl_clock_setup);
209
210static int of_dra7_atl_clk_probe(struct platform_device *pdev)
211{
212 struct device_node *node = pdev->dev.of_node;
213 struct dra7_atl_clock_info *cinfo;
214 int i;
215 int ret = 0;
216
217 if (!node)
218 return -ENODEV;
219
220 cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL);
221 if (!cinfo)
222 return -ENOMEM;
223
224 cinfo->iobase = of_iomap(node, 0);
225 cinfo->dev = &pdev->dev;
226 pm_runtime_enable(cinfo->dev);
227
228 pm_runtime_get_sync(cinfo->dev);
229 atl_write(cinfo, DRA7_ATL_PCLKMUX_REG(0), DRA7_ATL_PCLKMUX);
230
231 for (i = 0; i < DRA7_ATL_INSTANCES; i++) {
232 struct device_node *cfg_node;
233 char prop[5];
234 struct dra7_atl_desc *cdesc;
235 struct of_phandle_args clkspec;
236 struct clk *clk;
237 int rc;
238
239 rc = of_parse_phandle_with_args(node, "ti,provided-clocks",
240 NULL, i, &clkspec);
241
242 if (rc) {
243 pr_err("%s: failed to lookup atl clock %d\n", __func__,
244 i);
245 return -EINVAL;
246 }
247
248 clk = of_clk_get_from_provider(&clkspec);
249
250 cdesc = to_atl_desc(__clk_get_hw(clk));
251 cdesc->cinfo = cinfo;
252 cdesc->id = i;
253
254 /* Get configuration for the ATL instances */
255 snprintf(prop, sizeof(prop), "atl%u", i);
256 cfg_node = of_find_node_by_name(node, prop);
257 if (cfg_node) {
258 ret = of_property_read_u32(cfg_node, "bws",
259 &cdesc->bws);
260 ret |= of_property_read_u32(cfg_node, "aws",
261 &cdesc->aws);
262 if (!ret) {
263 cdesc->valid = true;
264 atl_write(cinfo, DRA7_ATL_BWSMUX_REG(i),
265 cdesc->bws);
266 atl_write(cinfo, DRA7_ATL_AWSMUX_REG(i),
267 cdesc->aws);
268 }
269 }
270
271 cdesc->probed = true;
272 /*
273 * Enable the clock if it has been asked prior to loading the
274 * hw driver
275 */
276 if (cdesc->enabled)
277 atl_clk_enable(__clk_get_hw(clk));
278 }
279 pm_runtime_put_sync(cinfo->dev);
280
281 return ret;
282}
283
284static int of_dra7_atl_clk_remove(struct platform_device *pdev)
285{
286 pm_runtime_disable(&pdev->dev);
287
288 return 0;
289}
290
291static struct of_device_id of_dra7_atl_clk_match_tbl[] = {
292 { .compatible = "ti,dra7-atl", },
293 {},
294};
295MODULE_DEVICE_TABLE(of, of_dra7_atl_clk_match_tbl);
296
297static struct platform_driver dra7_atl_clk_driver = {
298 .driver = {
299 .name = "dra7-atl",
300 .owner = THIS_MODULE,
301 .of_match_table = of_dra7_atl_clk_match_tbl,
302 },
303 .probe = of_dra7_atl_clk_probe,
304 .remove = of_dra7_atl_clk_remove,
305};
306
307module_platform_driver(dra7_atl_clk_driver);
308
309MODULE_DESCRIPTION("Clock driver for DRA7 Audio Tracking Logic");
310MODULE_ALIAS("platform:dra7-atl-clock");
311MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
312MODULE_LICENSE("GPL v2");