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