blob: 0075690e5bb529e70bfefd0332f7d7be427a498d [file] [log] [blame]
Dimitris Papastamos2cbbb572011-09-19 14:34:03 +01001/*
2 * Register cache access API - LZO caching support
3 *
4 * Copyright 2011 Wolfson Microelectronics plc
5 *
6 * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.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 version 2 as
10 * published by the Free Software Foundation.
11 */
12
13#include <linux/slab.h>
14#include <linux/lzo.h>
15
16#include "internal.h"
17
18struct regcache_lzo_ctx {
19 void *wmem;
20 void *dst;
21 const void *src;
22 size_t src_len;
23 size_t dst_len;
24 size_t decompressed_size;
25 unsigned long *sync_bmp;
26 int sync_bmp_nbits;
27};
28
29#define LZO_BLOCK_NUM 8
Mark Brown82732bd2011-10-18 00:37:00 +010030static int regcache_lzo_block_count(struct regmap *map)
Dimitris Papastamos2cbbb572011-09-19 14:34:03 +010031{
32 return LZO_BLOCK_NUM;
33}
34
35static int regcache_lzo_prepare(struct regcache_lzo_ctx *lzo_ctx)
36{
37 lzo_ctx->wmem = kmalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL);
38 if (!lzo_ctx->wmem)
39 return -ENOMEM;
40 return 0;
41}
42
43static int regcache_lzo_compress(struct regcache_lzo_ctx *lzo_ctx)
44{
45 size_t compress_size;
46 int ret;
47
48 ret = lzo1x_1_compress(lzo_ctx->src, lzo_ctx->src_len,
49 lzo_ctx->dst, &compress_size, lzo_ctx->wmem);
50 if (ret != LZO_E_OK || compress_size > lzo_ctx->dst_len)
51 return -EINVAL;
52 lzo_ctx->dst_len = compress_size;
53 return 0;
54}
55
56static int regcache_lzo_decompress(struct regcache_lzo_ctx *lzo_ctx)
57{
58 size_t dst_len;
59 int ret;
60
61 dst_len = lzo_ctx->dst_len;
62 ret = lzo1x_decompress_safe(lzo_ctx->src, lzo_ctx->src_len,
63 lzo_ctx->dst, &dst_len);
64 if (ret != LZO_E_OK || dst_len != lzo_ctx->dst_len)
65 return -EINVAL;
66 return 0;
67}
68
69static int regcache_lzo_compress_cache_block(struct regmap *map,
70 struct regcache_lzo_ctx *lzo_ctx)
71{
72 int ret;
73
74 lzo_ctx->dst_len = lzo1x_worst_compress(PAGE_SIZE);
75 lzo_ctx->dst = kmalloc(lzo_ctx->dst_len, GFP_KERNEL);
76 if (!lzo_ctx->dst) {
77 lzo_ctx->dst_len = 0;
78 return -ENOMEM;
79 }
80
81 ret = regcache_lzo_compress(lzo_ctx);
82 if (ret < 0)
83 return ret;
84 return 0;
85}
86
87static int regcache_lzo_decompress_cache_block(struct regmap *map,
88 struct regcache_lzo_ctx *lzo_ctx)
89{
90 int ret;
91
92 lzo_ctx->dst_len = lzo_ctx->decompressed_size;
93 lzo_ctx->dst = kmalloc(lzo_ctx->dst_len, GFP_KERNEL);
94 if (!lzo_ctx->dst) {
95 lzo_ctx->dst_len = 0;
96 return -ENOMEM;
97 }
98
99 ret = regcache_lzo_decompress(lzo_ctx);
100 if (ret < 0)
101 return ret;
102 return 0;
103}
104
105static inline int regcache_lzo_get_blkindex(struct regmap *map,
106 unsigned int reg)
107{
108 return (reg * map->cache_word_size) /
Mark Brown82732bd2011-10-18 00:37:00 +0100109 DIV_ROUND_UP(map->cache_size_raw,
110 regcache_lzo_block_count(map));
Dimitris Papastamos2cbbb572011-09-19 14:34:03 +0100111}
112
113static inline int regcache_lzo_get_blkpos(struct regmap *map,
114 unsigned int reg)
115{
Mark Brown82732bd2011-10-18 00:37:00 +0100116 return reg % (DIV_ROUND_UP(map->cache_size_raw,
117 regcache_lzo_block_count(map)) /
Dimitris Papastamos2cbbb572011-09-19 14:34:03 +0100118 map->cache_word_size);
119}
120
121static inline int regcache_lzo_get_blksize(struct regmap *map)
122{
Mark Brown82732bd2011-10-18 00:37:00 +0100123 return DIV_ROUND_UP(map->cache_size_raw,
124 regcache_lzo_block_count(map));
Dimitris Papastamos2cbbb572011-09-19 14:34:03 +0100125}
126
127static int regcache_lzo_init(struct regmap *map)
128{
129 struct regcache_lzo_ctx **lzo_blocks;
130 size_t bmp_size;
131 int ret, i, blksize, blkcount;
132 const char *p, *end;
133 unsigned long *sync_bmp;
134
135 ret = 0;
136
Mark Brown82732bd2011-10-18 00:37:00 +0100137 blkcount = regcache_lzo_block_count(map);
Dimitris Papastamos2cbbb572011-09-19 14:34:03 +0100138 map->cache = kzalloc(blkcount * sizeof *lzo_blocks,
139 GFP_KERNEL);
140 if (!map->cache)
141 return -ENOMEM;
142 lzo_blocks = map->cache;
143
144 /*
145 * allocate a bitmap to be used when syncing the cache with
146 * the hardware. Each time a register is modified, the corresponding
147 * bit is set in the bitmap, so we know that we have to sync
148 * that register.
149 */
150 bmp_size = map->num_reg_defaults_raw;
151 sync_bmp = kmalloc(BITS_TO_LONGS(bmp_size) * sizeof(long),
152 GFP_KERNEL);
153 if (!sync_bmp) {
154 ret = -ENOMEM;
155 goto err;
156 }
157 bitmap_zero(sync_bmp, bmp_size);
158
159 /* allocate the lzo blocks and initialize them */
160 for (i = 0; i < blkcount; i++) {
161 lzo_blocks[i] = kzalloc(sizeof **lzo_blocks,
162 GFP_KERNEL);
163 if (!lzo_blocks[i]) {
164 kfree(sync_bmp);
165 ret = -ENOMEM;
166 goto err;
167 }
168 lzo_blocks[i]->sync_bmp = sync_bmp;
169 lzo_blocks[i]->sync_bmp_nbits = bmp_size;
170 /* alloc the working space for the compressed block */
171 ret = regcache_lzo_prepare(lzo_blocks[i]);
172 if (ret < 0)
173 goto err;
174 }
175
176 blksize = regcache_lzo_get_blksize(map);
177 p = map->reg_defaults_raw;
178 end = map->reg_defaults_raw + map->cache_size_raw;
179 /* compress the register map and fill the lzo blocks */
180 for (i = 0; i < blkcount; i++, p += blksize) {
181 lzo_blocks[i]->src = p;
182 if (p + blksize > end)
183 lzo_blocks[i]->src_len = end - p;
184 else
185 lzo_blocks[i]->src_len = blksize;
186 ret = regcache_lzo_compress_cache_block(map,
187 lzo_blocks[i]);
188 if (ret < 0)
189 goto err;
190 lzo_blocks[i]->decompressed_size =
191 lzo_blocks[i]->src_len;
192 }
193
194 return 0;
195err:
196 regcache_exit(map);
197 return ret;
198}
199
200static int regcache_lzo_exit(struct regmap *map)
201{
202 struct regcache_lzo_ctx **lzo_blocks;
203 int i, blkcount;
204
205 lzo_blocks = map->cache;
206 if (!lzo_blocks)
207 return 0;
208
Mark Brown82732bd2011-10-18 00:37:00 +0100209 blkcount = regcache_lzo_block_count(map);
Dimitris Papastamos2cbbb572011-09-19 14:34:03 +0100210 /*
211 * the pointer to the bitmap used for syncing the cache
212 * is shared amongst all lzo_blocks. Ensure it is freed
213 * only once.
214 */
215 if (lzo_blocks[0])
216 kfree(lzo_blocks[0]->sync_bmp);
217 for (i = 0; i < blkcount; i++) {
218 if (lzo_blocks[i]) {
219 kfree(lzo_blocks[i]->wmem);
220 kfree(lzo_blocks[i]->dst);
221 }
222 /* each lzo_block is a pointer returned by kmalloc or NULL */
223 kfree(lzo_blocks[i]);
224 }
225 kfree(lzo_blocks);
226 map->cache = NULL;
227 return 0;
228}
229
230static int regcache_lzo_read(struct regmap *map,
231 unsigned int reg, unsigned int *value)
232{
233 struct regcache_lzo_ctx *lzo_block, **lzo_blocks;
234 int ret, blkindex, blkpos;
235 size_t blksize, tmp_dst_len;
236 void *tmp_dst;
237
Dimitris Papastamos2cbbb572011-09-19 14:34:03 +0100238 /* index of the compressed lzo block */
239 blkindex = regcache_lzo_get_blkindex(map, reg);
240 /* register index within the decompressed block */
241 blkpos = regcache_lzo_get_blkpos(map, reg);
242 /* size of the compressed block */
243 blksize = regcache_lzo_get_blksize(map);
244 lzo_blocks = map->cache;
245 lzo_block = lzo_blocks[blkindex];
246
247 /* save the pointer and length of the compressed block */
248 tmp_dst = lzo_block->dst;
249 tmp_dst_len = lzo_block->dst_len;
250
251 /* prepare the source to be the compressed block */
252 lzo_block->src = lzo_block->dst;
253 lzo_block->src_len = lzo_block->dst_len;
254
255 /* decompress the block */
256 ret = regcache_lzo_decompress_cache_block(map, lzo_block);
257 if (ret >= 0)
258 /* fetch the value from the cache */
259 *value = regcache_get_val(lzo_block->dst, blkpos,
260 map->cache_word_size);
261
262 kfree(lzo_block->dst);
263 /* restore the pointer and length of the compressed block */
264 lzo_block->dst = tmp_dst;
265 lzo_block->dst_len = tmp_dst_len;
Mark Brown6e6ace02011-10-09 13:23:31 +0100266
267 return ret;
Dimitris Papastamos2cbbb572011-09-19 14:34:03 +0100268}
269
270static int regcache_lzo_write(struct regmap *map,
271 unsigned int reg, unsigned int value)
272{
273 struct regcache_lzo_ctx *lzo_block, **lzo_blocks;
274 int ret, blkindex, blkpos;
275 size_t blksize, tmp_dst_len;
276 void *tmp_dst;
277
278 /* index of the compressed lzo block */
279 blkindex = regcache_lzo_get_blkindex(map, reg);
280 /* register index within the decompressed block */
281 blkpos = regcache_lzo_get_blkpos(map, reg);
282 /* size of the compressed block */
283 blksize = regcache_lzo_get_blksize(map);
284 lzo_blocks = map->cache;
285 lzo_block = lzo_blocks[blkindex];
286
287 /* save the pointer and length of the compressed block */
288 tmp_dst = lzo_block->dst;
289 tmp_dst_len = lzo_block->dst_len;
290
291 /* prepare the source to be the compressed block */
292 lzo_block->src = lzo_block->dst;
293 lzo_block->src_len = lzo_block->dst_len;
294
295 /* decompress the block */
296 ret = regcache_lzo_decompress_cache_block(map, lzo_block);
297 if (ret < 0) {
298 kfree(lzo_block->dst);
299 goto out;
300 }
301
302 /* write the new value to the cache */
303 if (regcache_set_val(lzo_block->dst, blkpos, value,
304 map->cache_word_size)) {
305 kfree(lzo_block->dst);
306 goto out;
307 }
308
309 /* prepare the source to be the decompressed block */
310 lzo_block->src = lzo_block->dst;
311 lzo_block->src_len = lzo_block->dst_len;
312
313 /* compress the block */
314 ret = regcache_lzo_compress_cache_block(map, lzo_block);
315 if (ret < 0) {
316 kfree(lzo_block->dst);
317 kfree(lzo_block->src);
318 goto out;
319 }
320
321 /* set the bit so we know we have to sync this register */
322 set_bit(reg, lzo_block->sync_bmp);
323 kfree(tmp_dst);
324 kfree(lzo_block->src);
325 return 0;
326out:
327 lzo_block->dst = tmp_dst;
328 lzo_block->dst_len = tmp_dst_len;
329 return ret;
330}
331
332static int regcache_lzo_sync(struct regmap *map)
333{
334 struct regcache_lzo_ctx **lzo_blocks;
335 unsigned int val;
336 int i;
337 int ret;
338
339 lzo_blocks = map->cache;
340 for_each_set_bit(i, lzo_blocks[0]->sync_bmp, lzo_blocks[0]->sync_bmp_nbits) {
341 ret = regcache_read(map, i, &val);
342 if (ret)
343 return ret;
344 map->cache_bypass = 1;
Dimitris Papastamos13753a92011-09-29 14:36:25 +0100345 ret = _regmap_write(map, i, val);
Dimitris Papastamos2cbbb572011-09-19 14:34:03 +0100346 map->cache_bypass = 0;
347 if (ret)
348 return ret;
349 dev_dbg(map->dev, "Synced register %#x, value %#x\n",
350 i, val);
351 }
352
353 return 0;
354}
355
356struct regcache_ops regcache_lzo_ops = {
357 .type = REGCACHE_LZO,
358 .name = "lzo",
359 .init = regcache_lzo_init,
360 .exit = regcache_lzo_exit,
361 .read = regcache_lzo_read,
362 .write = regcache_lzo_write,
363 .sync = regcache_lzo_sync
364};