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 | ||
45f5ff81 SW |
40 | offset = be32_to_cpup(reg); |
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: | |
48 | writew(be16_to_cpup(val), ctx->regs + offset); | |
49 | break; | |
50 | case 4: | |
51 | writel(be32_to_cpup(val), ctx->regs + offset); | |
52 | break; | |
53 | #ifdef CONFIG_64BIT | |
54 | case 8: | |
55 | writeq(be64_to_cpup(val), ctx->regs + offset); | |
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 | ||
45f5ff81 SW |
86 | offset = be32_to_cpup(reg); |
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: | |
94 | *(u16 *)val = cpu_to_be16(readw(ctx->regs + offset)); | |
95 | break; | |
96 | case 4: | |
97 | *(u32 *)val = cpu_to_be32(readl(ctx->regs + offset)); | |
98 | break; | |
99 | #ifdef CONFIG_64BIT | |
100 | case 8: | |
101 | *(u64 *)val = cpu_to_be32(readq(ctx->regs + offset)); | |
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, | |
127 | }; | |
128 | ||
129 | struct regmap_mmio_context *regmap_mmio_gen_context(void __iomem *regs, | |
130 | const struct regmap_config *config) | |
131 | { | |
132 | struct regmap_mmio_context *ctx; | |
f01ee60f | 133 | int min_stride; |
45f5ff81 SW |
134 | |
135 | if (config->reg_bits != 32) | |
136 | return ERR_PTR(-EINVAL); | |
137 | ||
138 | if (config->pad_bits) | |
139 | return ERR_PTR(-EINVAL); | |
140 | ||
141 | switch (config->val_bits) { | |
142 | case 8: | |
f01ee60f SW |
143 | /* The core treats 0 as 1 */ |
144 | min_stride = 0; | |
145 | break; | |
45f5ff81 | 146 | case 16: |
f01ee60f SW |
147 | min_stride = 2; |
148 | break; | |
45f5ff81 | 149 | case 32: |
f01ee60f SW |
150 | min_stride = 4; |
151 | break; | |
45f5ff81 SW |
152 | #ifdef CONFIG_64BIT |
153 | case 64: | |
f01ee60f SW |
154 | min_stride = 8; |
155 | break; | |
45f5ff81 SW |
156 | #endif |
157 | break; | |
158 | default: | |
159 | return ERR_PTR(-EINVAL); | |
160 | } | |
161 | ||
f01ee60f SW |
162 | if (config->reg_stride < min_stride) |
163 | return ERR_PTR(-EINVAL); | |
164 | ||
45f5ff81 SW |
165 | ctx = kzalloc(GFP_KERNEL, sizeof(*ctx)); |
166 | if (!ctx) | |
167 | return ERR_PTR(-ENOMEM); | |
168 | ||
169 | ctx->regs = regs; | |
170 | ctx->val_bytes = config->val_bits / 8; | |
171 | ||
172 | return ctx; | |
173 | } | |
174 | ||
175 | /** | |
176 | * regmap_init_mmio(): Initialise register map | |
177 | * | |
178 | * @dev: Device that will be interacted with | |
179 | * @regs: Pointer to memory-mapped IO region | |
180 | * @config: Configuration for register map | |
181 | * | |
182 | * The return value will be an ERR_PTR() on error or a valid pointer to | |
183 | * a struct regmap. | |
184 | */ | |
185 | struct regmap *regmap_init_mmio(struct device *dev, | |
186 | void __iomem *regs, | |
187 | const struct regmap_config *config) | |
188 | { | |
189 | struct regmap_mmio_context *ctx; | |
190 | ||
191 | ctx = regmap_mmio_gen_context(regs, config); | |
192 | if (IS_ERR(ctx)) | |
193 | return ERR_CAST(ctx); | |
194 | ||
195 | return regmap_init(dev, ®map_mmio, ctx, config); | |
196 | } | |
197 | EXPORT_SYMBOL_GPL(regmap_init_mmio); | |
198 | ||
199 | /** | |
200 | * devm_regmap_init_mmio(): Initialise managed register map | |
201 | * | |
202 | * @dev: Device that will be interacted with | |
203 | * @regs: Pointer to memory-mapped IO region | |
204 | * @config: Configuration for register map | |
205 | * | |
206 | * The return value will be an ERR_PTR() on error or a valid pointer | |
207 | * to a struct regmap. The regmap will be automatically freed by the | |
208 | * device management code. | |
209 | */ | |
210 | struct regmap *devm_regmap_init_mmio(struct device *dev, | |
211 | void __iomem *regs, | |
212 | const struct regmap_config *config) | |
213 | { | |
214 | struct regmap_mmio_context *ctx; | |
215 | ||
216 | ctx = regmap_mmio_gen_context(regs, config); | |
217 | if (IS_ERR(ctx)) | |
218 | return ERR_CAST(ctx); | |
219 | ||
220 | return devm_regmap_init(dev, ®map_mmio, ctx, config); | |
221 | } | |
222 | EXPORT_SYMBOL_GPL(devm_regmap_init_mmio); | |
223 | ||
224 | MODULE_LICENSE("GPL v2"); |