]>
Commit | Line | Data |
---|---|---|
45f5ff81 SW |
1 | /* |
2 | * Register map access API - MMIO support | |
3 | * | |
4 | * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms and conditions of the GNU General Public License, | |
8 | * version 2, as published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope it will be useful, but WITHOUT | |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | */ | |
18 | ||
878ec67b | 19 | #include <linux/clk.h> |
45f5ff81 | 20 | #include <linux/err.h> |
45f5ff81 SW |
21 | #include <linux/io.h> |
22 | #include <linux/module.h> | |
23 | #include <linux/regmap.h> | |
24 | #include <linux/slab.h> | |
25 | ||
26 | struct regmap_mmio_context { | |
27 | void __iomem *regs; | |
93258040 | 28 | unsigned reg_bytes; |
45f5ff81 | 29 | unsigned val_bytes; |
93258040 | 30 | unsigned pad_bytes; |
878ec67b | 31 | struct clk *clk; |
45f5ff81 SW |
32 | }; |
33 | ||
41b0c2c9 XL |
34 | static inline void regmap_mmio_regsize_check(size_t reg_size) |
35 | { | |
93258040 XL |
36 | switch (reg_size) { |
37 | case 1: | |
38 | case 2: | |
39 | case 4: | |
40 | #ifdef CONFIG_64BIT | |
41 | case 8: | |
42 | #endif | |
43 | break; | |
44 | default: | |
45 | BUG(); | |
46 | } | |
41b0c2c9 XL |
47 | } |
48 | ||
451485ba XL |
49 | static int regmap_mmio_regbits_check(size_t reg_bits) |
50 | { | |
51 | switch (reg_bits) { | |
52 | case 8: | |
53 | case 16: | |
54 | case 32: | |
55 | #ifdef CONFIG_64BIT | |
56 | case 64: | |
57 | #endif | |
58 | return 0; | |
59 | default: | |
60 | return -EINVAL; | |
61 | } | |
62 | } | |
63 | ||
2e804b7c | 64 | static inline void regmap_mmio_count_check(size_t count, u32 offset) |
41b0c2c9 | 65 | { |
2e804b7c | 66 | BUG_ON(count <= offset); |
41b0c2c9 XL |
67 | } |
68 | ||
88cb32c6 XL |
69 | static inline unsigned int |
70 | regmap_mmio_get_offset(const void *reg, size_t reg_size) | |
71 | { | |
72 | switch (reg_size) { | |
73 | case 1: | |
74 | return *(u8 *)reg; | |
75 | case 2: | |
76 | return *(u16 *)reg; | |
77 | case 4: | |
78 | return *(u32 *)reg; | |
79 | #ifdef CONFIG_64BIT | |
80 | case 8: | |
81 | return *(u64 *)reg; | |
82 | #endif | |
83 | default: | |
84 | BUG(); | |
85 | } | |
41b0c2c9 XL |
86 | } |
87 | ||
45f5ff81 SW |
88 | static int regmap_mmio_gather_write(void *context, |
89 | const void *reg, size_t reg_size, | |
90 | const void *val, size_t val_size) | |
91 | { | |
92 | struct regmap_mmio_context *ctx = context; | |
88cb32c6 | 93 | unsigned int offset; |
878ec67b | 94 | int ret; |
45f5ff81 | 95 | |
41b0c2c9 | 96 | regmap_mmio_regsize_check(reg_size); |
40606dba | 97 | |
6b8e090e | 98 | if (!IS_ERR(ctx->clk)) { |
878ec67b PZ |
99 | ret = clk_enable(ctx->clk); |
100 | if (ret < 0) | |
101 | return ret; | |
102 | } | |
103 | ||
88cb32c6 | 104 | offset = regmap_mmio_get_offset(reg, reg_size); |
45f5ff81 SW |
105 | |
106 | while (val_size) { | |
107 | switch (ctx->val_bytes) { | |
108 | case 1: | |
29bb45f2 | 109 | __raw_writeb(*(u8 *)val, ctx->regs + offset); |
45f5ff81 SW |
110 | break; |
111 | case 2: | |
29bb45f2 | 112 | __raw_writew(*(u16 *)val, ctx->regs + offset); |
45f5ff81 SW |
113 | break; |
114 | case 4: | |
29bb45f2 | 115 | __raw_writel(*(u32 *)val, ctx->regs + offset); |
45f5ff81 SW |
116 | break; |
117 | #ifdef CONFIG_64BIT | |
118 | case 8: | |
29bb45f2 | 119 | __raw_writeq(*(u64 *)val, ctx->regs + offset); |
45f5ff81 SW |
120 | break; |
121 | #endif | |
122 | default: | |
123 | /* Should be caught by regmap_mmio_check_config */ | |
40606dba | 124 | BUG(); |
45f5ff81 SW |
125 | } |
126 | val_size -= ctx->val_bytes; | |
127 | val += ctx->val_bytes; | |
128 | offset += ctx->val_bytes; | |
129 | } | |
130 | ||
6b8e090e | 131 | if (!IS_ERR(ctx->clk)) |
878ec67b PZ |
132 | clk_disable(ctx->clk); |
133 | ||
45f5ff81 SW |
134 | return 0; |
135 | } | |
136 | ||
137 | static int regmap_mmio_write(void *context, const void *data, size_t count) | |
138 | { | |
93258040 | 139 | struct regmap_mmio_context *ctx = context; |
88cb32c6 | 140 | unsigned int offset = ctx->reg_bytes + ctx->pad_bytes; |
93258040 | 141 | |
2e804b7c | 142 | regmap_mmio_count_check(count, offset); |
40606dba | 143 | |
93258040 XL |
144 | return regmap_mmio_gather_write(context, data, ctx->reg_bytes, |
145 | data + offset, count - offset); | |
45f5ff81 SW |
146 | } |
147 | ||
148 | static int regmap_mmio_read(void *context, | |
149 | const void *reg, size_t reg_size, | |
150 | void *val, size_t val_size) | |
151 | { | |
152 | struct regmap_mmio_context *ctx = context; | |
88cb32c6 | 153 | unsigned int offset; |
878ec67b | 154 | int ret; |
45f5ff81 | 155 | |
41b0c2c9 | 156 | regmap_mmio_regsize_check(reg_size); |
40606dba | 157 | |
6b8e090e | 158 | if (!IS_ERR(ctx->clk)) { |
878ec67b PZ |
159 | ret = clk_enable(ctx->clk); |
160 | if (ret < 0) | |
161 | return ret; | |
162 | } | |
163 | ||
88cb32c6 | 164 | offset = regmap_mmio_get_offset(reg, reg_size); |
45f5ff81 SW |
165 | |
166 | while (val_size) { | |
167 | switch (ctx->val_bytes) { | |
168 | case 1: | |
29bb45f2 | 169 | *(u8 *)val = __raw_readb(ctx->regs + offset); |
45f5ff81 SW |
170 | break; |
171 | case 2: | |
29bb45f2 | 172 | *(u16 *)val = __raw_readw(ctx->regs + offset); |
45f5ff81 SW |
173 | break; |
174 | case 4: | |
29bb45f2 | 175 | *(u32 *)val = __raw_readl(ctx->regs + offset); |
45f5ff81 SW |
176 | break; |
177 | #ifdef CONFIG_64BIT | |
178 | case 8: | |
29bb45f2 | 179 | *(u64 *)val = __raw_readq(ctx->regs + offset); |
45f5ff81 SW |
180 | break; |
181 | #endif | |
182 | default: | |
183 | /* Should be caught by regmap_mmio_check_config */ | |
40606dba | 184 | BUG(); |
45f5ff81 SW |
185 | } |
186 | val_size -= ctx->val_bytes; | |
187 | val += ctx->val_bytes; | |
188 | offset += ctx->val_bytes; | |
189 | } | |
190 | ||
6b8e090e | 191 | if (!IS_ERR(ctx->clk)) |
878ec67b PZ |
192 | clk_disable(ctx->clk); |
193 | ||
45f5ff81 SW |
194 | return 0; |
195 | } | |
196 | ||
197 | static void regmap_mmio_free_context(void *context) | |
198 | { | |
878ec67b PZ |
199 | struct regmap_mmio_context *ctx = context; |
200 | ||
6b8e090e | 201 | if (!IS_ERR(ctx->clk)) { |
878ec67b PZ |
202 | clk_unprepare(ctx->clk); |
203 | clk_put(ctx->clk); | |
204 | } | |
45f5ff81 SW |
205 | kfree(context); |
206 | } | |
207 | ||
208 | static struct regmap_bus regmap_mmio = { | |
209 | .fast_io = true, | |
210 | .write = regmap_mmio_write, | |
211 | .gather_write = regmap_mmio_gather_write, | |
212 | .read = regmap_mmio_read, | |
213 | .free_context = regmap_mmio_free_context, | |
6a55244e SW |
214 | .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, |
215 | .val_format_endian_default = REGMAP_ENDIAN_NATIVE, | |
45f5ff81 SW |
216 | }; |
217 | ||
878ec67b PZ |
218 | static struct regmap_mmio_context *regmap_mmio_gen_context(struct device *dev, |
219 | const char *clk_id, | |
220 | void __iomem *regs, | |
45f5ff81 SW |
221 | const struct regmap_config *config) |
222 | { | |
223 | struct regmap_mmio_context *ctx; | |
f01ee60f | 224 | int min_stride; |
878ec67b | 225 | int ret; |
45f5ff81 | 226 | |
451485ba XL |
227 | ret = regmap_mmio_regbits_check(config->reg_bits); |
228 | if (ret) | |
229 | return ERR_PTR(ret); | |
45f5ff81 SW |
230 | |
231 | if (config->pad_bits) | |
232 | return ERR_PTR(-EINVAL); | |
233 | ||
234 | switch (config->val_bits) { | |
235 | case 8: | |
f01ee60f SW |
236 | /* The core treats 0 as 1 */ |
237 | min_stride = 0; | |
238 | break; | |
45f5ff81 | 239 | case 16: |
f01ee60f SW |
240 | min_stride = 2; |
241 | break; | |
45f5ff81 | 242 | case 32: |
f01ee60f SW |
243 | min_stride = 4; |
244 | break; | |
45f5ff81 SW |
245 | #ifdef CONFIG_64BIT |
246 | case 64: | |
f01ee60f SW |
247 | min_stride = 8; |
248 | break; | |
45f5ff81 | 249 | #endif |
45f5ff81 SW |
250 | default: |
251 | return ERR_PTR(-EINVAL); | |
252 | } | |
253 | ||
f01ee60f SW |
254 | if (config->reg_stride < min_stride) |
255 | return ERR_PTR(-EINVAL); | |
256 | ||
6a55244e SW |
257 | switch (config->reg_format_endian) { |
258 | case REGMAP_ENDIAN_DEFAULT: | |
259 | case REGMAP_ENDIAN_NATIVE: | |
260 | break; | |
261 | default: | |
262 | return ERR_PTR(-EINVAL); | |
263 | } | |
264 | ||
46335119 | 265 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); |
45f5ff81 SW |
266 | if (!ctx) |
267 | return ERR_PTR(-ENOMEM); | |
268 | ||
269 | ctx->regs = regs; | |
270 | ctx->val_bytes = config->val_bits / 8; | |
93258040 XL |
271 | ctx->reg_bytes = config->reg_bits / 8; |
272 | ctx->pad_bytes = config->pad_bits / 8; | |
6b8e090e | 273 | ctx->clk = ERR_PTR(-ENODEV); |
45f5ff81 | 274 | |
878ec67b PZ |
275 | if (clk_id == NULL) |
276 | return ctx; | |
277 | ||
278 | ctx->clk = clk_get(dev, clk_id); | |
279 | if (IS_ERR(ctx->clk)) { | |
280 | ret = PTR_ERR(ctx->clk); | |
281 | goto err_free; | |
282 | } | |
283 | ||
284 | ret = clk_prepare(ctx->clk); | |
285 | if (ret < 0) { | |
286 | clk_put(ctx->clk); | |
287 | goto err_free; | |
288 | } | |
289 | ||
45f5ff81 | 290 | return ctx; |
878ec67b PZ |
291 | |
292 | err_free: | |
293 | kfree(ctx); | |
294 | ||
295 | return ERR_PTR(ret); | |
45f5ff81 SW |
296 | } |
297 | ||
3cfe7a74 NB |
298 | struct regmap *__regmap_init_mmio_clk(struct device *dev, const char *clk_id, |
299 | void __iomem *regs, | |
300 | const struct regmap_config *config, | |
301 | struct lock_class_key *lock_key, | |
302 | const char *lock_name) | |
45f5ff81 SW |
303 | { |
304 | struct regmap_mmio_context *ctx; | |
305 | ||
878ec67b | 306 | ctx = regmap_mmio_gen_context(dev, clk_id, regs, config); |
45f5ff81 SW |
307 | if (IS_ERR(ctx)) |
308 | return ERR_CAST(ctx); | |
309 | ||
3cfe7a74 NB |
310 | return __regmap_init(dev, ®map_mmio, ctx, config, |
311 | lock_key, lock_name); | |
45f5ff81 | 312 | } |
3cfe7a74 | 313 | EXPORT_SYMBOL_GPL(__regmap_init_mmio_clk); |
45f5ff81 | 314 | |
3cfe7a74 NB |
315 | struct regmap *__devm_regmap_init_mmio_clk(struct device *dev, |
316 | const char *clk_id, | |
317 | void __iomem *regs, | |
318 | const struct regmap_config *config, | |
319 | struct lock_class_key *lock_key, | |
320 | const char *lock_name) | |
45f5ff81 SW |
321 | { |
322 | struct regmap_mmio_context *ctx; | |
323 | ||
878ec67b | 324 | ctx = regmap_mmio_gen_context(dev, clk_id, regs, config); |
45f5ff81 SW |
325 | if (IS_ERR(ctx)) |
326 | return ERR_CAST(ctx); | |
327 | ||
3cfe7a74 NB |
328 | return __devm_regmap_init(dev, ®map_mmio, ctx, config, |
329 | lock_key, lock_name); | |
45f5ff81 | 330 | } |
3cfe7a74 | 331 | EXPORT_SYMBOL_GPL(__devm_regmap_init_mmio_clk); |
45f5ff81 SW |
332 | |
333 | MODULE_LICENSE("GPL v2"); |