diff options
author | Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com> | 2013-10-17 17:54:07 -0400 |
---|---|---|
committer | Mike Turquette <mturquette@linaro.org> | 2013-12-12 22:23:59 -0500 |
commit | f94859c215b6d977794108a1a9a101239e393c09 (patch) | |
tree | 8de13d7d3cd0d6a27219207e388037fd3407f16c /drivers/clk/shmobile/clk-mstp.c | |
parent | abe844aa5bb50444ac3e02aed89b431823d6ad56 (diff) |
clk: shmobile: Add MSTP clock support
MSTP clocks are gate clocks controlled through a register that handles
up to 32 clocks. The register is often sparsely populated.
Those clocks are found on Renesas ARM SoCs.
Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
Signed-off-by: Mike Turquette <mturquette@linaro.org>
Diffstat (limited to 'drivers/clk/shmobile/clk-mstp.c')
-rw-r--r-- | drivers/clk/shmobile/clk-mstp.c | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/drivers/clk/shmobile/clk-mstp.c b/drivers/clk/shmobile/clk-mstp.c new file mode 100644 index 000000000000..e576b60de20e --- /dev/null +++ b/drivers/clk/shmobile/clk-mstp.c | |||
@@ -0,0 +1,229 @@ | |||
1 | /* | ||
2 | * R-Car MSTP clocks | ||
3 | * | ||
4 | * Copyright (C) 2013 Ideas On Board SPRL | ||
5 | * | ||
6 | * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.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 as published by | ||
10 | * the Free Software Foundation; version 2 of the License. | ||
11 | */ | ||
12 | |||
13 | #include <linux/clk-provider.h> | ||
14 | #include <linux/clkdev.h> | ||
15 | #include <linux/io.h> | ||
16 | #include <linux/of.h> | ||
17 | #include <linux/of_address.h> | ||
18 | #include <linux/spinlock.h> | ||
19 | |||
20 | /* | ||
21 | * MSTP clocks. We can't use standard gate clocks as we need to poll on the | ||
22 | * status register when enabling the clock. | ||
23 | */ | ||
24 | |||
25 | #define MSTP_MAX_CLOCKS 32 | ||
26 | |||
27 | /** | ||
28 | * struct mstp_clock_group - MSTP gating clocks group | ||
29 | * | ||
30 | * @data: clocks in this group | ||
31 | * @smstpcr: module stop control register | ||
32 | * @mstpsr: module stop status register (optional) | ||
33 | * @lock: protects writes to SMSTPCR | ||
34 | */ | ||
35 | struct mstp_clock_group { | ||
36 | struct clk_onecell_data data; | ||
37 | void __iomem *smstpcr; | ||
38 | void __iomem *mstpsr; | ||
39 | spinlock_t lock; | ||
40 | }; | ||
41 | |||
42 | /** | ||
43 | * struct mstp_clock - MSTP gating clock | ||
44 | * @hw: handle between common and hardware-specific interfaces | ||
45 | * @bit_index: control bit index | ||
46 | * @group: MSTP clocks group | ||
47 | */ | ||
48 | struct mstp_clock { | ||
49 | struct clk_hw hw; | ||
50 | u32 bit_index; | ||
51 | struct mstp_clock_group *group; | ||
52 | }; | ||
53 | |||
54 | #define to_mstp_clock(_hw) container_of(_hw, struct mstp_clock, hw) | ||
55 | |||
56 | static int cpg_mstp_clock_endisable(struct clk_hw *hw, bool enable) | ||
57 | { | ||
58 | struct mstp_clock *clock = to_mstp_clock(hw); | ||
59 | struct mstp_clock_group *group = clock->group; | ||
60 | u32 bitmask = BIT(clock->bit_index); | ||
61 | unsigned long flags; | ||
62 | unsigned int i; | ||
63 | u32 value; | ||
64 | |||
65 | spin_lock_irqsave(&group->lock, flags); | ||
66 | |||
67 | value = clk_readl(group->smstpcr); | ||
68 | if (enable) | ||
69 | value &= ~bitmask; | ||
70 | else | ||
71 | value |= bitmask; | ||
72 | clk_writel(value, group->smstpcr); | ||
73 | |||
74 | spin_unlock_irqrestore(&group->lock, flags); | ||
75 | |||
76 | if (!enable || !group->mstpsr) | ||
77 | return 0; | ||
78 | |||
79 | for (i = 1000; i > 0; --i) { | ||
80 | if (!(clk_readl(group->mstpsr) & bitmask)) | ||
81 | break; | ||
82 | cpu_relax(); | ||
83 | } | ||
84 | |||
85 | if (!i) { | ||
86 | pr_err("%s: failed to enable %p[%d]\n", __func__, | ||
87 | group->smstpcr, clock->bit_index); | ||
88 | return -ETIMEDOUT; | ||
89 | } | ||
90 | |||
91 | return 0; | ||
92 | } | ||
93 | |||
94 | static int cpg_mstp_clock_enable(struct clk_hw *hw) | ||
95 | { | ||
96 | return cpg_mstp_clock_endisable(hw, true); | ||
97 | } | ||
98 | |||
99 | static void cpg_mstp_clock_disable(struct clk_hw *hw) | ||
100 | { | ||
101 | cpg_mstp_clock_endisable(hw, false); | ||
102 | } | ||
103 | |||
104 | static int cpg_mstp_clock_is_enabled(struct clk_hw *hw) | ||
105 | { | ||
106 | struct mstp_clock *clock = to_mstp_clock(hw); | ||
107 | struct mstp_clock_group *group = clock->group; | ||
108 | u32 value; | ||
109 | |||
110 | if (group->mstpsr) | ||
111 | value = clk_readl(group->mstpsr); | ||
112 | else | ||
113 | value = clk_readl(group->smstpcr); | ||
114 | |||
115 | return !!(value & BIT(clock->bit_index)); | ||
116 | } | ||
117 | |||
118 | static const struct clk_ops cpg_mstp_clock_ops = { | ||
119 | .enable = cpg_mstp_clock_enable, | ||
120 | .disable = cpg_mstp_clock_disable, | ||
121 | .is_enabled = cpg_mstp_clock_is_enabled, | ||
122 | }; | ||
123 | |||
124 | static struct clk * __init | ||
125 | cpg_mstp_clock_register(const char *name, const char *parent_name, | ||
126 | unsigned int index, struct mstp_clock_group *group) | ||
127 | { | ||
128 | struct clk_init_data init; | ||
129 | struct mstp_clock *clock; | ||
130 | struct clk *clk; | ||
131 | |||
132 | clock = kzalloc(sizeof(*clock), GFP_KERNEL); | ||
133 | if (!clock) { | ||
134 | pr_err("%s: failed to allocate MSTP clock.\n", __func__); | ||
135 | return ERR_PTR(-ENOMEM); | ||
136 | } | ||
137 | |||
138 | init.name = name; | ||
139 | init.ops = &cpg_mstp_clock_ops; | ||
140 | init.flags = CLK_IS_BASIC; | ||
141 | init.parent_names = &parent_name; | ||
142 | init.num_parents = 1; | ||
143 | |||
144 | clock->bit_index = index; | ||
145 | clock->group = group; | ||
146 | clock->hw.init = &init; | ||
147 | |||
148 | clk = clk_register(NULL, &clock->hw); | ||
149 | |||
150 | if (IS_ERR(clk)) | ||
151 | kfree(clock); | ||
152 | |||
153 | return clk; | ||
154 | } | ||
155 | |||
156 | static void __init cpg_mstp_clocks_init(struct device_node *np) | ||
157 | { | ||
158 | struct mstp_clock_group *group; | ||
159 | struct clk **clks; | ||
160 | unsigned int i; | ||
161 | |||
162 | group = kzalloc(sizeof(*group), GFP_KERNEL); | ||
163 | clks = kzalloc(MSTP_MAX_CLOCKS * sizeof(*clks), GFP_KERNEL); | ||
164 | if (group == NULL || clks == NULL) { | ||
165 | kfree(group); | ||
166 | kfree(clks); | ||
167 | pr_err("%s: failed to allocate group\n", __func__); | ||
168 | return; | ||
169 | } | ||
170 | |||
171 | spin_lock_init(&group->lock); | ||
172 | group->data.clks = clks; | ||
173 | |||
174 | group->smstpcr = of_iomap(np, 0); | ||
175 | group->mstpsr = of_iomap(np, 1); | ||
176 | |||
177 | if (group->smstpcr == NULL) { | ||
178 | pr_err("%s: failed to remap SMSTPCR\n", __func__); | ||
179 | kfree(group); | ||
180 | kfree(clks); | ||
181 | return; | ||
182 | } | ||
183 | |||
184 | for (i = 0; i < MSTP_MAX_CLOCKS; ++i) { | ||
185 | const char *parent_name; | ||
186 | const char *name; | ||
187 | u32 clkidx; | ||
188 | int ret; | ||
189 | |||
190 | /* Skip clocks with no name. */ | ||
191 | ret = of_property_read_string_index(np, "clock-output-names", | ||
192 | i, &name); | ||
193 | if (ret < 0 || strlen(name) == 0) | ||
194 | continue; | ||
195 | |||
196 | parent_name = of_clk_get_parent_name(np, i); | ||
197 | ret = of_property_read_u32_index(np, "renesas,clock-indices", i, | ||
198 | &clkidx); | ||
199 | if (parent_name == NULL || ret < 0) | ||
200 | break; | ||
201 | |||
202 | if (clkidx >= MSTP_MAX_CLOCKS) { | ||
203 | pr_err("%s: invalid clock %s %s index %u)\n", | ||
204 | __func__, np->name, name, clkidx); | ||
205 | continue; | ||
206 | } | ||
207 | |||
208 | clks[clkidx] = cpg_mstp_clock_register(name, parent_name, i, | ||
209 | group); | ||
210 | if (!IS_ERR(clks[clkidx])) { | ||
211 | group->data.clk_num = max(group->data.clk_num, clkidx); | ||
212 | /* | ||
213 | * Register a clkdev to let board code retrieve the | ||
214 | * clock by name and register aliases for non-DT | ||
215 | * devices. | ||
216 | * | ||
217 | * FIXME: Remove this when all devices that require a | ||
218 | * clock will be instantiated from DT. | ||
219 | */ | ||
220 | clk_register_clkdev(clks[clkidx], name, NULL); | ||
221 | } else { | ||
222 | pr_err("%s: failed to register %s %s clock (%ld)\n", | ||
223 | __func__, np->name, name, PTR_ERR(clks[clkidx])); | ||
224 | } | ||
225 | } | ||
226 | |||
227 | of_clk_add_provider(np, of_clk_src_onecell_get, &group->data); | ||
228 | } | ||
229 | CLK_OF_DECLARE(cpg_mstp_clks, "renesas,cpg-mstp-clocks", cpg_mstp_clocks_init); | ||