soc-cache.c 6.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
/*
 * soc-cache.c  --  ASoC register cache helpers
 *
 * Copyright 2009 Wolfson Microelectronics PLC.
 *
 * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
 *
 *  This program is free software; you can redistribute  it and/or modify it
 *  under  the terms of  the GNU General  Public License as published by the
 *  Free Software Foundation;  either version 2 of the  License, or (at your
 *  option) any later version.
 */

14
#include <linux/i2c.h>
15
#include <linux/spi/spi.h>
16
#include <sound/soc.h>
17
#include <linux/bitmap.h>
18
#include <linux/rbtree.h>
19
#include <linux/export.h>
20

21 22
#include <trace/events/asoc.h>

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
static bool snd_soc_set_cache_val(void *base, unsigned int idx,
				  unsigned int val, unsigned int word_size)
{
	switch (word_size) {
	case 1: {
		u8 *cache = base;
		if (cache[idx] == val)
			return true;
		cache[idx] = val;
		break;
	}
	case 2: {
		u16 *cache = base;
		if (cache[idx] == val)
			return true;
		cache[idx] = val;
		break;
	}
	default:
		BUG();
	}
	return false;
}

static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx,
		unsigned int word_size)
{
50 51 52
	if (!base)
		return -1;

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
	switch (word_size) {
	case 1: {
		const u8 *cache = base;
		return cache[idx];
	}
	case 2: {
		const u16 *cache = base;
		return cache[idx];
	}
	default:
		BUG();
	}
	/* unreachable */
	return -1;
}

69 70 71
static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec)
{
	int i;
72
	int ret;
73
	const struct snd_soc_codec_driver *codec_drv;
74 75 76 77
	unsigned int val;

	codec_drv = codec->driver;
	for (i = 0; i < codec_drv->reg_cache_size; ++i) {
78 79 80
		ret = snd_soc_cache_read(codec, i, &val);
		if (ret)
			return ret;
81 82
		if (codec->reg_def_copy)
			if (snd_soc_get_cache_val(codec->reg_def_copy,
83 84
						  i, codec_drv->reg_word_size) == val)
				continue;
85 86 87

		WARN_ON(!snd_soc_codec_writable_register(codec, i));

88 89 90
		ret = snd_soc_write(codec, i, val);
		if (ret)
			return ret;
91
		dev_dbg(codec->dev, "ASoC: Synced register %#x, value = %#x\n",
92 93 94 95 96 97 98 99
			i, val);
	}
	return 0;
}

static int snd_soc_flat_cache_write(struct snd_soc_codec *codec,
				    unsigned int reg, unsigned int value)
{
100 101
	snd_soc_set_cache_val(codec->reg_cache, reg, value,
			      codec->driver->reg_word_size);
102 103 104 105 106 107
	return 0;
}

static int snd_soc_flat_cache_read(struct snd_soc_codec *codec,
				   unsigned int reg, unsigned int *value)
{
108 109
	*value = snd_soc_get_cache_val(codec->reg_cache, reg,
				       codec->driver->reg_word_size);
110 111 112 113 114 115 116 117 118 119 120 121 122 123
	return 0;
}

static int snd_soc_flat_cache_exit(struct snd_soc_codec *codec)
{
	if (!codec->reg_cache)
		return 0;
	kfree(codec->reg_cache);
	codec->reg_cache = NULL;
	return 0;
}

static int snd_soc_flat_cache_init(struct snd_soc_codec *codec)
{
124 125
	if (codec->reg_def_copy)
		codec->reg_cache = kmemdup(codec->reg_def_copy,
126
					   codec->reg_size, GFP_KERNEL);
127
	else
128
		codec->reg_cache = kzalloc(codec->reg_size, GFP_KERNEL);
129 130 131 132 133 134 135 136
	if (!codec->reg_cache)
		return -ENOMEM;

	return 0;
}

/* an array of all supported compression types */
static const struct snd_soc_cache_ops cache_types[] = {
137
	/* Flat *must* be the first entry for fallback */
138
	{
139
		.id = SND_SOC_FLAT_COMPRESSION,
140
		.name = "flat",
141 142 143 144 145
		.init = snd_soc_flat_cache_init,
		.exit = snd_soc_flat_cache_exit,
		.read = snd_soc_flat_cache_read,
		.write = snd_soc_flat_cache_write,
		.sync = snd_soc_flat_cache_sync
146
	},
147 148 149 150 151 152 153
};

int snd_soc_cache_init(struct snd_soc_codec *codec)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(cache_types); ++i)
154
		if (cache_types[i].id == codec->compress_type)
155
			break;
156 157

	/* Fall back to flat compression */
158
	if (i == ARRAY_SIZE(cache_types)) {
159
		dev_warn(codec->dev, "ASoC: Could not match compress type: %d\n",
160 161
			 codec->compress_type);
		i = 0;
162 163 164 165 166
	}

	mutex_init(&codec->cache_rw_mutex);
	codec->cache_ops = &cache_types[i];

167 168
	if (codec->cache_ops->init) {
		if (codec->cache_ops->name)
169
			dev_dbg(codec->dev, "ASoC: Initializing %s cache for %s codec\n",
170
				codec->cache_ops->name, codec->name);
171
		return codec->cache_ops->init(codec);
172
	}
173
	return -ENOSYS;
174 175 176 177 178 179 180 181
}

/*
 * NOTE: keep in mind that this function might be called
 * multiple times.
 */
int snd_soc_cache_exit(struct snd_soc_codec *codec)
{
182 183
	if (codec->cache_ops && codec->cache_ops->exit) {
		if (codec->cache_ops->name)
184
			dev_dbg(codec->dev, "ASoC: Destroying %s cache for %s codec\n",
185
				codec->cache_ops->name, codec->name);
186
		return codec->cache_ops->exit(codec);
187
	}
188
	return -ENOSYS;
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
}

/**
 * snd_soc_cache_read: Fetch the value of a given register from the cache.
 *
 * @codec: CODEC to configure.
 * @reg: The register index.
 * @value: The value to be returned.
 */
int snd_soc_cache_read(struct snd_soc_codec *codec,
		       unsigned int reg, unsigned int *value)
{
	int ret;

	mutex_lock(&codec->cache_rw_mutex);

	if (value && codec->cache_ops && codec->cache_ops->read) {
		ret = codec->cache_ops->read(codec, reg, value);
		mutex_unlock(&codec->cache_rw_mutex);
		return ret;
	}

	mutex_unlock(&codec->cache_rw_mutex);
212
	return -ENOSYS;
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
}
EXPORT_SYMBOL_GPL(snd_soc_cache_read);

/**
 * snd_soc_cache_write: Set the value of a given register in the cache.
 *
 * @codec: CODEC to configure.
 * @reg: The register index.
 * @value: The new register value.
 */
int snd_soc_cache_write(struct snd_soc_codec *codec,
			unsigned int reg, unsigned int value)
{
	int ret;

	mutex_lock(&codec->cache_rw_mutex);

	if (codec->cache_ops && codec->cache_ops->write) {
		ret = codec->cache_ops->write(codec, reg, value);
		mutex_unlock(&codec->cache_rw_mutex);
		return ret;
	}

	mutex_unlock(&codec->cache_rw_mutex);
237
	return -ENOSYS;
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
}
EXPORT_SYMBOL_GPL(snd_soc_cache_write);

/**
 * snd_soc_cache_sync: Sync the register cache with the hardware.
 *
 * @codec: CODEC to configure.
 *
 * Any registers that should not be synced should be marked as
 * volatile.  In general drivers can choose not to use the provided
 * syncing functionality if they so require.
 */
int snd_soc_cache_sync(struct snd_soc_codec *codec)
{
	int ret;
253
	const char *name;
254 255 256 257 258

	if (!codec->cache_sync) {
		return 0;
	}

259
	if (!codec->cache_ops || !codec->cache_ops->sync)
260
		return -ENOSYS;
261

262 263 264 265 266
	if (codec->cache_ops->name)
		name = codec->cache_ops->name;
	else
		name = "unknown";

267
	if (codec->cache_ops->name)
268
		dev_dbg(codec->dev, "ASoC: Syncing %s cache for %s codec\n",
269 270 271 272 273 274 275
			codec->cache_ops->name, codec->name);
	trace_snd_soc_cache_sync(codec, name, "start");
	ret = codec->cache_ops->sync(codec);
	if (!ret)
		codec->cache_sync = 0;
	trace_snd_soc_cache_sync(codec, name, "end");
	return ret;
276 277
}
EXPORT_SYMBOL_GPL(snd_soc_cache_sync);