aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/clk/at91
diff options
context:
space:
mode:
authorBoris BREZILLON <b.brezillon@overkiz.com>2013-10-11 04:44:49 -0400
committerNicolas Ferre <nicolas.ferre@atmel.com>2013-12-02 09:31:22 -0500
commit38d34c3120b5588e2bd561baa4c5cfef1a4917bb (patch)
treef064287617170449b03ead6cc9e63bf2c5014ab5 /drivers/clk/at91
parent7e682b44376e8caa6fc57acbf818b52747b1f922 (diff)
clk: at91: add PMC main clock
This patch adds new at91 main oscillator clock implementation using common clk framework. If rate is not provided during clock registration it is calculated using the slow clock (main clk parent in this case) rate and MCFR register. Signed-off-by: Boris BREZILLON <b.brezillon@overkiz.com> Acked-by: Mike Turquette <mturquette@linaro.org> Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com>
Diffstat (limited to 'drivers/clk/at91')
-rw-r--r--drivers/clk/at91/Makefile1
-rw-r--r--drivers/clk/at91/clk-main.c187
-rw-r--r--drivers/clk/at91/pmc.c5
-rw-r--r--drivers/clk/at91/pmc.h3
4 files changed, 196 insertions, 0 deletions
diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index 1d4fb214bbc4..44105bd44aa1 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -3,3 +3,4 @@
3# 3#
4 4
5obj-y += pmc.o 5obj-y += pmc.o
6obj-y += clk-main.o
diff --git a/drivers/clk/at91/clk-main.c b/drivers/clk/at91/clk-main.c
new file mode 100644
index 000000000000..8e9e8cc0412d
--- /dev/null
+++ b/drivers/clk/at91/clk-main.c
@@ -0,0 +1,187 @@
1/*
2 * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 */
10
11#include <linux/clk-provider.h>
12#include <linux/clkdev.h>
13#include <linux/clk/at91_pmc.h>
14#include <linux/delay.h>
15#include <linux/of.h>
16#include <linux/of_address.h>
17#include <linux/of_irq.h>
18#include <linux/io.h>
19#include <linux/interrupt.h>
20#include <linux/irq.h>
21#include <linux/sched.h>
22#include <linux/wait.h>
23
24#include "pmc.h"
25
26#define SLOW_CLOCK_FREQ 32768
27#define MAINF_DIV 16
28#define MAINFRDY_TIMEOUT (((MAINF_DIV + 1) * USEC_PER_SEC) / \
29 SLOW_CLOCK_FREQ)
30#define MAINF_LOOP_MIN_WAIT (USEC_PER_SEC / SLOW_CLOCK_FREQ)
31#define MAINF_LOOP_MAX_WAIT MAINFRDY_TIMEOUT
32
33struct clk_main {
34 struct clk_hw hw;
35 struct at91_pmc *pmc;
36 unsigned long rate;
37 unsigned int irq;
38 wait_queue_head_t wait;
39};
40
41#define to_clk_main(hw) container_of(hw, struct clk_main, hw)
42
43static irqreturn_t clk_main_irq_handler(int irq, void *dev_id)
44{
45 struct clk_main *clkmain = (struct clk_main *)dev_id;
46
47 wake_up(&clkmain->wait);
48 disable_irq_nosync(clkmain->irq);
49
50 return IRQ_HANDLED;
51}
52
53static int clk_main_prepare(struct clk_hw *hw)
54{
55 struct clk_main *clkmain = to_clk_main(hw);
56 struct at91_pmc *pmc = clkmain->pmc;
57 unsigned long halt_time, timeout;
58 u32 tmp;
59
60 while (!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MOSCS)) {
61 enable_irq(clkmain->irq);
62 wait_event(clkmain->wait,
63 pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MOSCS);
64 }
65
66 if (clkmain->rate)
67 return 0;
68
69 timeout = jiffies + usecs_to_jiffies(MAINFRDY_TIMEOUT);
70 do {
71 halt_time = jiffies;
72 tmp = pmc_read(pmc, AT91_CKGR_MCFR);
73 if (tmp & AT91_PMC_MAINRDY)
74 return 0;
75 usleep_range(MAINF_LOOP_MIN_WAIT, MAINF_LOOP_MAX_WAIT);
76 } while (time_before(halt_time, timeout));
77
78 return 0;
79}
80
81static int clk_main_is_prepared(struct clk_hw *hw)
82{
83 struct clk_main *clkmain = to_clk_main(hw);
84
85 return !!(pmc_read(clkmain->pmc, AT91_PMC_SR) & AT91_PMC_MOSCS);
86}
87
88static unsigned long clk_main_recalc_rate(struct clk_hw *hw,
89 unsigned long parent_rate)
90{
91 u32 tmp;
92 struct clk_main *clkmain = to_clk_main(hw);
93 struct at91_pmc *pmc = clkmain->pmc;
94
95 if (clkmain->rate)
96 return clkmain->rate;
97
98 tmp = pmc_read(pmc, AT91_CKGR_MCFR) & AT91_PMC_MAINF;
99 clkmain->rate = (tmp * parent_rate) / MAINF_DIV;
100
101 return clkmain->rate;
102}
103
104static const struct clk_ops main_ops = {
105 .prepare = clk_main_prepare,
106 .is_prepared = clk_main_is_prepared,
107 .recalc_rate = clk_main_recalc_rate,
108};
109
110static struct clk * __init
111at91_clk_register_main(struct at91_pmc *pmc,
112 unsigned int irq,
113 const char *name,
114 const char *parent_name,
115 unsigned long rate)
116{
117 int ret;
118 struct clk_main *clkmain;
119 struct clk *clk = NULL;
120 struct clk_init_data init;
121
122 if (!pmc || !irq || !name)
123 return ERR_PTR(-EINVAL);
124
125 if (!rate && !parent_name)
126 return ERR_PTR(-EINVAL);
127
128 clkmain = kzalloc(sizeof(*clkmain), GFP_KERNEL);
129 if (!clkmain)
130 return ERR_PTR(-ENOMEM);
131
132 init.name = name;
133 init.ops = &main_ops;
134 init.parent_names = parent_name ? &parent_name : NULL;
135 init.num_parents = parent_name ? 1 : 0;
136 init.flags = parent_name ? 0 : CLK_IS_ROOT;
137
138 clkmain->hw.init = &init;
139 clkmain->rate = rate;
140 clkmain->pmc = pmc;
141 clkmain->irq = irq;
142 init_waitqueue_head(&clkmain->wait);
143 irq_set_status_flags(clkmain->irq, IRQ_NOAUTOEN);
144 ret = request_irq(clkmain->irq, clk_main_irq_handler,
145 IRQF_TRIGGER_HIGH, "clk-main", clkmain);
146 if (ret)
147 return ERR_PTR(ret);
148
149 clk = clk_register(NULL, &clkmain->hw);
150 if (IS_ERR(clk)) {
151 free_irq(clkmain->irq, clkmain);
152 kfree(clkmain);
153 }
154
155 return clk;
156}
157
158
159
160static void __init
161of_at91_clk_main_setup(struct device_node *np, struct at91_pmc *pmc)
162{
163 struct clk *clk;
164 unsigned int irq;
165 const char *parent_name;
166 const char *name = np->name;
167 u32 rate = 0;
168
169 parent_name = of_clk_get_parent_name(np, 0);
170 of_property_read_string(np, "clock-output-names", &name);
171 of_property_read_u32(np, "clock-frequency", &rate);
172 irq = irq_of_parse_and_map(np, 0);
173 if (!irq)
174 return;
175
176 clk = at91_clk_register_main(pmc, irq, name, parent_name, rate);
177 if (IS_ERR(clk))
178 return;
179
180 of_clk_add_provider(np, of_clk_src_simple_get, clk);
181}
182
183void __init of_at91rm9200_clk_main_setup(struct device_node *np,
184 struct at91_pmc *pmc)
185{
186 of_at91_clk_main_setup(np, pmc);
187}
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index 69c116a6ba4f..3d35b7313868 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -229,6 +229,11 @@ out_free_pmc:
229} 229}
230 230
231static const struct of_device_id pmc_clk_ids[] __initdata = { 231static const struct of_device_id pmc_clk_ids[] __initdata = {
232 /* Main clock */
233 {
234 .compatible = "atmel,at91rm9200-clk-main",
235 .data = of_at91rm9200_clk_main_setup,
236 },
232 { /*sentinel*/ } 237 { /*sentinel*/ }
233}; 238};
234 239
diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index d92b946cdc9e..729aa46c7289 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -58,4 +58,7 @@ static inline void pmc_write(struct at91_pmc *pmc, int offset, u32 value)
58int of_at91_get_clk_range(struct device_node *np, const char *propname, 58int of_at91_get_clk_range(struct device_node *np, const char *propname,
59 struct clk_range *range); 59 struct clk_range *range);
60 60
61extern void __init of_at91rm9200_clk_main_setup(struct device_node *np,
62 struct at91_pmc *pmc);
63
61#endif /* __PMC_H_ */ 64#endif /* __PMC_H_ */