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 SW |
20 | #include <linux/err.h> |
21 | #include <linux/init.h> | |
22 | #include <linux/io.h> | |
23 | #include <linux/module.h> | |
24 | #include <linux/regmap.h> | |
25 | #include <linux/slab.h> | |
26 | ||
27 | struct regmap_mmio_context { | |
28 | void __iomem *regs; | |
29 | unsigned val_bytes; | |
878ec67b | 30 | struct clk *clk; |
45f5ff81 SW |
31 | }; |
32 | ||
33 | static int regmap_mmio_gather_write(void *context, | |
34 | const void *reg, size_t reg_size, | |
35 | const void *val, size_t val_size) | |
36 | { | |
37 | struct regmap_mmio_context *ctx = context; | |
38 | u32 offset; | |
878ec67b | 39 | int ret; |
45f5ff81 | 40 | |
40606dba SW |
41 | BUG_ON(reg_size != 4); |
42 | ||
6b8e090e | 43 | if (!IS_ERR(ctx->clk)) { |
878ec67b PZ |
44 | ret = clk_enable(ctx->clk); |
45 | if (ret < 0) | |
46 | return ret; | |
47 | } | |
48 | ||
6a55244e | 49 | offset = *(u32 *)reg; |
45f5ff81 SW |
50 | |
51 | while (val_size) { | |
52 | switch (ctx->val_bytes) { | |
53 | case 1: | |
54 | writeb(*(u8 *)val, ctx->regs + offset); | |
55 | break; | |
56 | case 2: | |
6a55244e | 57 | writew(*(u16 *)val, ctx->regs + offset); |
45f5ff81 SW |
58 | break; |
59 | case 4: | |
6a55244e | 60 | writel(*(u32 *)val, ctx->regs + offset); |
45f5ff81 SW |
61 | break; |
62 | #ifdef CONFIG_64BIT | |
63 | case 8: | |
6a55244e | 64 | writeq(*(u64 *)val, ctx->regs + offset); |
45f5ff81 SW |
65 | break; |
66 | #endif | |
67 | default: | |
68 | /* Should be caught by regmap_mmio_check_config */ | |
40606dba | 69 | BUG(); |
45f5ff81 SW |
70 | } |
71 | val_size -= ctx->val_bytes; | |
72 | val += ctx->val_bytes; | |
73 | offset += ctx->val_bytes; | |
74 | } | |
75 | ||
6b8e090e | 76 | if (!IS_ERR(ctx->clk)) |
878ec67b PZ |
77 | clk_disable(ctx->clk); |
78 | ||
45f5ff81 SW |
79 | return 0; |
80 | } | |
81 | ||
82 | static int regmap_mmio_write(void *context, const void *data, size_t count) | |
83 | { | |
40606dba SW |
84 | BUG_ON(count < 4); |
85 | ||
45f5ff81 SW |
86 | return regmap_mmio_gather_write(context, data, 4, data + 4, count - 4); |
87 | } | |
88 | ||
89 | static int regmap_mmio_read(void *context, | |
90 | const void *reg, size_t reg_size, | |
91 | void *val, size_t val_size) | |
92 | { | |
93 | struct regmap_mmio_context *ctx = context; | |
94 | u32 offset; | |
878ec67b | 95 | int ret; |
45f5ff81 | 96 | |
40606dba SW |
97 | BUG_ON(reg_size != 4); |
98 | ||
6b8e090e | 99 | if (!IS_ERR(ctx->clk)) { |
878ec67b PZ |
100 | ret = clk_enable(ctx->clk); |
101 | if (ret < 0) | |
102 | return ret; | |
103 | } | |
104 | ||
6a55244e | 105 | offset = *(u32 *)reg; |
45f5ff81 SW |
106 | |
107 | while (val_size) { | |
108 | switch (ctx->val_bytes) { | |
109 | case 1: | |
110 | *(u8 *)val = readb(ctx->regs + offset); | |
111 | break; | |
112 | case 2: | |
6a55244e | 113 | *(u16 *)val = readw(ctx->regs + offset); |
45f5ff81 SW |
114 | break; |
115 | case 4: | |
6a55244e | 116 | *(u32 *)val = readl(ctx->regs + offset); |
45f5ff81 SW |
117 | break; |
118 | #ifdef CONFIG_64BIT | |
119 | case 8: | |
6a55244e | 120 | *(u64 *)val = readq(ctx->regs + offset); |
45f5ff81 SW |
121 | break; |
122 | #endif | |
123 | default: | |
124 | /* Should be caught by regmap_mmio_check_config */ | |
40606dba | 125 | BUG(); |
45f5ff81 SW |
126 | } |
127 | val_size -= ctx->val_bytes; | |
128 | val += ctx->val_bytes; | |
129 | offset += ctx->val_bytes; | |
130 | } | |
131 | ||
6b8e090e | 132 | if (!IS_ERR(ctx->clk)) |
878ec67b PZ |
133 | clk_disable(ctx->clk); |
134 | ||
45f5ff81 SW |
135 | return 0; |
136 | } | |
137 | ||
138 | static void regmap_mmio_free_context(void *context) | |
139 | { | |
878ec67b PZ |
140 | struct regmap_mmio_context *ctx = context; |
141 | ||
6b8e090e | 142 | if (!IS_ERR(ctx->clk)) { |
878ec67b PZ |
143 | clk_unprepare(ctx->clk); |
144 | clk_put(ctx->clk); | |
145 | } | |
45f5ff81 SW |
146 | kfree(context); |
147 | } | |
148 | ||
149 | static struct regmap_bus regmap_mmio = { | |
150 | .fast_io = true, | |
151 | .write = regmap_mmio_write, | |
152 | .gather_write = regmap_mmio_gather_write, | |
153 | .read = regmap_mmio_read, | |
154 | .free_context = regmap_mmio_free_context, | |
6a55244e SW |
155 | .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, |
156 | .val_format_endian_default = REGMAP_ENDIAN_NATIVE, | |
45f5ff81 SW |
157 | }; |
158 | ||
878ec67b PZ |
159 | static struct regmap_mmio_context *regmap_mmio_gen_context(struct device *dev, |
160 | const char *clk_id, | |
161 | void __iomem *regs, | |
45f5ff81 SW |
162 | const struct regmap_config *config) |
163 | { | |
164 | struct regmap_mmio_context *ctx; | |
f01ee60f | 165 | int min_stride; |
878ec67b | 166 | int ret; |
45f5ff81 SW |
167 | |
168 | if (config->reg_bits != 32) | |
169 | return ERR_PTR(-EINVAL); | |
170 | ||
171 | if (config->pad_bits) | |
172 | return ERR_PTR(-EINVAL); | |
173 | ||
174 | switch (config->val_bits) { | |
175 | case 8: | |
f01ee60f SW |
176 | /* The core treats 0 as 1 */ |
177 | min_stride = 0; | |
178 | break; | |
45f5ff81 | 179 | case 16: |
f01ee60f SW |
180 | min_stride = 2; |
181 | break; | |
45f5ff81 | 182 | case 32: |
f01ee60f SW |
183 | min_stride = 4; |
184 | break; | |
45f5ff81 SW |
185 | #ifdef CONFIG_64BIT |
186 | case 64: | |
f01ee60f SW |
187 | min_stride = 8; |
188 | break; | |
45f5ff81 SW |
189 | #endif |
190 | break; | |
191 | default: | |
192 | return ERR_PTR(-EINVAL); | |
193 | } | |
194 | ||
f01ee60f SW |
195 | if (config->reg_stride < min_stride) |
196 | return ERR_PTR(-EINVAL); | |
197 | ||
6a55244e SW |
198 | switch (config->reg_format_endian) { |
199 | case REGMAP_ENDIAN_DEFAULT: | |
200 | case REGMAP_ENDIAN_NATIVE: | |
201 | break; | |
202 | default: | |
203 | return ERR_PTR(-EINVAL); | |
204 | } | |
205 | ||
46335119 | 206 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); |
45f5ff81 SW |
207 | if (!ctx) |
208 | return ERR_PTR(-ENOMEM); | |
209 | ||
210 | ctx->regs = regs; | |
211 | ctx->val_bytes = config->val_bits / 8; | |
6b8e090e | 212 | ctx->clk = ERR_PTR(-ENODEV); |
45f5ff81 | 213 | |
878ec67b PZ |
214 | if (clk_id == NULL) |
215 | return ctx; | |
216 | ||
217 | ctx->clk = clk_get(dev, clk_id); | |
218 | if (IS_ERR(ctx->clk)) { | |
219 | ret = PTR_ERR(ctx->clk); | |
220 | goto err_free; | |
221 | } | |
222 | ||
223 | ret = clk_prepare(ctx->clk); | |
224 | if (ret < 0) { | |
225 | clk_put(ctx->clk); | |
226 | goto err_free; | |
227 | } | |
228 | ||
45f5ff81 | 229 | return ctx; |
878ec67b PZ |
230 | |
231 | err_free: | |
232 | kfree(ctx); | |
233 | ||
234 | return ERR_PTR(ret); | |
45f5ff81 SW |
235 | } |
236 | ||
237 | /** | |
878ec67b | 238 | * regmap_init_mmio_clk(): Initialise register map with register clock |
45f5ff81 SW |
239 | * |
240 | * @dev: Device that will be interacted with | |
878ec67b | 241 | * @clk_id: register clock consumer ID |
45f5ff81 SW |
242 | * @regs: Pointer to memory-mapped IO region |
243 | * @config: Configuration for register map | |
244 | * | |
245 | * The return value will be an ERR_PTR() on error or a valid pointer to | |
246 | * a struct regmap. | |
247 | */ | |
878ec67b PZ |
248 | struct regmap *regmap_init_mmio_clk(struct device *dev, const char *clk_id, |
249 | void __iomem *regs, | |
250 | const struct regmap_config *config) | |
45f5ff81 SW |
251 | { |
252 | struct regmap_mmio_context *ctx; | |
253 | ||
878ec67b | 254 | ctx = regmap_mmio_gen_context(dev, clk_id, regs, config); |
45f5ff81 SW |
255 | if (IS_ERR(ctx)) |
256 | return ERR_CAST(ctx); | |
257 | ||
258 | return regmap_init(dev, ®map_mmio, ctx, config); | |
259 | } | |
878ec67b | 260 | EXPORT_SYMBOL_GPL(regmap_init_mmio_clk); |
45f5ff81 SW |
261 | |
262 | /** | |
878ec67b | 263 | * devm_regmap_init_mmio_clk(): Initialise managed register map with clock |
45f5ff81 SW |
264 | * |
265 | * @dev: Device that will be interacted with | |
878ec67b | 266 | * @clk_id: register clock consumer ID |
45f5ff81 SW |
267 | * @regs: Pointer to memory-mapped IO region |
268 | * @config: Configuration for register map | |
269 | * | |
270 | * The return value will be an ERR_PTR() on error or a valid pointer | |
271 | * to a struct regmap. The regmap will be automatically freed by the | |
272 | * device management code. | |
273 | */ | |
878ec67b PZ |
274 | struct regmap *devm_regmap_init_mmio_clk(struct device *dev, const char *clk_id, |
275 | void __iomem *regs, | |
276 | const struct regmap_config *config) | |
45f5ff81 SW |
277 | { |
278 | struct regmap_mmio_context *ctx; | |
279 | ||
878ec67b | 280 | ctx = regmap_mmio_gen_context(dev, clk_id, regs, config); |
45f5ff81 SW |
281 | if (IS_ERR(ctx)) |
282 | return ERR_CAST(ctx); | |
283 | ||
284 | return devm_regmap_init(dev, ®map_mmio, ctx, config); | |
285 | } | |
878ec67b | 286 | EXPORT_SYMBOL_GPL(devm_regmap_init_mmio_clk); |
45f5ff81 SW |
287 | |
288 | MODULE_LICENSE("GPL v2"); |