Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | /* * 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. */ #include <sound/soc.h> #include <linux/export.h> #include <linux/slab.h> #include <trace/events/asoc.h> 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: WARN(1, "Invalid word_size %d\n", word_size); break; } return false; } static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx, unsigned int word_size) { if (!base) return -1; switch (word_size) { case 1: { const u8 *cache = base; return cache[idx]; } case 2: { const u16 *cache = base; return cache[idx]; } default: WARN(1, "Invalid word_size %d\n", word_size); break; } /* unreachable */ return -1; } int snd_soc_cache_init(struct snd_soc_codec *codec) { const struct snd_soc_codec_driver *codec_drv = codec->driver; size_t reg_size; reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; mutex_init(&codec->cache_rw_mutex); dev_dbg(codec->dev, "ASoC: Initializing cache for %s codec\n", codec->name); if (codec_drv->reg_cache_default) codec->reg_cache = kmemdup(codec_drv->reg_cache_default, reg_size, GFP_KERNEL); else codec->reg_cache = kzalloc(reg_size, GFP_KERNEL); if (!codec->reg_cache) return -ENOMEM; return 0; } /* * NOTE: keep in mind that this function might be called * multiple times. */ int snd_soc_cache_exit(struct snd_soc_codec *codec) { dev_dbg(codec->dev, "ASoC: Destroying cache for %s codec\n", codec->name); kfree(codec->reg_cache); codec->reg_cache = NULL; return 0; } /** * 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) { if (!value) return -EINVAL; mutex_lock(&codec->cache_rw_mutex); if (!ZERO_OR_NULL_PTR(codec->reg_cache)) *value = snd_soc_get_cache_val(codec->reg_cache, reg, codec->driver->reg_word_size); mutex_unlock(&codec->cache_rw_mutex); return 0; } 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) { mutex_lock(&codec->cache_rw_mutex); if (!ZERO_OR_NULL_PTR(codec->reg_cache)) snd_soc_set_cache_val(codec->reg_cache, reg, value, codec->driver->reg_word_size); mutex_unlock(&codec->cache_rw_mutex); return 0; } EXPORT_SYMBOL_GPL(snd_soc_cache_write); static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec) { int i; int ret; const struct snd_soc_codec_driver *codec_drv; unsigned int val; codec_drv = codec->driver; for (i = 0; i < codec_drv->reg_cache_size; ++i) { ret = snd_soc_cache_read(codec, i, &val); if (ret) return ret; if (codec_drv->reg_cache_default) if (snd_soc_get_cache_val(codec_drv->reg_cache_default, i, codec_drv->reg_word_size) == val) continue; WARN_ON(!snd_soc_codec_writable_register(codec, i)); ret = snd_soc_write(codec, i, val); if (ret) return ret; dev_dbg(codec->dev, "ASoC: Synced register %#x, value = %#x\n", i, val); } return 0; } /** * 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) { const char *name = "flat"; int ret; if (!codec->cache_sync) return 0; dev_dbg(codec->dev, "ASoC: Syncing cache for %s codec\n", codec->name); trace_snd_soc_cache_sync(codec, name, "start"); ret = snd_soc_flat_cache_sync(codec); if (!ret) codec->cache_sync = 0; trace_snd_soc_cache_sync(codec, name, "end"); return ret; } EXPORT_SYMBOL_GPL(snd_soc_cache_sync); |