|
@@ -18,7 +18,8 @@
|
|
|
static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec,
|
|
|
unsigned int reg)
|
|
|
{
|
|
|
- u16 *cache = codec->reg_cache;
|
|
|
+ int ret;
|
|
|
+ unsigned int val;
|
|
|
|
|
|
if (reg >= codec->driver->reg_cache_size ||
|
|
|
snd_soc_codec_volatile_register(codec, reg)) {
|
|
@@ -29,13 +30,15 @@ static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec,
|
|
|
return codec->hw_read(codec, reg);
|
|
|
}
|
|
|
|
|
|
- return cache[reg];
|
|
|
+ ret = snd_soc_cache_read(codec, reg, &val);
|
|
|
+ if (ret < 0)
|
|
|
+ return -1;
|
|
|
+ return val;
|
|
|
}
|
|
|
|
|
|
static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
unsigned int value)
|
|
|
{
|
|
|
- u16 *cache = codec->reg_cache;
|
|
|
u8 data[2];
|
|
|
int ret;
|
|
|
|
|
@@ -43,8 +46,11 @@ static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
data[1] = value & 0x00ff;
|
|
|
|
|
|
if (!snd_soc_codec_volatile_register(codec, reg) &&
|
|
|
- reg < codec->driver->reg_cache_size)
|
|
|
- cache[reg] = value;
|
|
|
+ reg < codec->driver->reg_cache_size) {
|
|
|
+ ret = snd_soc_cache_write(codec, reg, value);
|
|
|
+ if (ret < 0)
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
|
|
|
if (codec->cache_only) {
|
|
|
codec->cache_sync = 1;
|
|
@@ -93,7 +99,8 @@ static int snd_soc_4_12_spi_write(void *control_data, const char *data,
|
|
|
static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec,
|
|
|
unsigned int reg)
|
|
|
{
|
|
|
- u16 *cache = codec->reg_cache;
|
|
|
+ int ret;
|
|
|
+ unsigned int val;
|
|
|
|
|
|
if (reg >= codec->driver->reg_cache_size ||
|
|
|
snd_soc_codec_volatile_register(codec, reg)) {
|
|
@@ -104,13 +111,15 @@ static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec,
|
|
|
return codec->hw_read(codec, reg);
|
|
|
}
|
|
|
|
|
|
- return cache[reg];
|
|
|
+ ret = snd_soc_cache_read(codec, reg, &val);
|
|
|
+ if (ret < 0)
|
|
|
+ return -1;
|
|
|
+ return val;
|
|
|
}
|
|
|
|
|
|
static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
unsigned int value)
|
|
|
{
|
|
|
- u16 *cache = codec->reg_cache;
|
|
|
u8 data[2];
|
|
|
int ret;
|
|
|
|
|
@@ -118,8 +127,11 @@ static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
data[1] = value & 0x00ff;
|
|
|
|
|
|
if (!snd_soc_codec_volatile_register(codec, reg) &&
|
|
|
- reg < codec->driver->reg_cache_size)
|
|
|
- cache[reg] = value;
|
|
|
+ reg < codec->driver->reg_cache_size) {
|
|
|
+ ret = snd_soc_cache_write(codec, reg, value);
|
|
|
+ if (ret < 0)
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
|
|
|
if (codec->cache_only) {
|
|
|
codec->cache_sync = 1;
|
|
@@ -168,16 +180,19 @@ static int snd_soc_7_9_spi_write(void *control_data, const char *data,
|
|
|
static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
unsigned int value)
|
|
|
{
|
|
|
- u8 *cache = codec->reg_cache;
|
|
|
u8 data[2];
|
|
|
+ int ret;
|
|
|
|
|
|
reg &= 0xff;
|
|
|
data[0] = reg;
|
|
|
data[1] = value & 0xff;
|
|
|
|
|
|
if (!snd_soc_codec_volatile_register(codec, reg) &&
|
|
|
- reg < codec->driver->reg_cache_size)
|
|
|
- cache[reg] = value;
|
|
|
+ reg < codec->driver->reg_cache_size) {
|
|
|
+ ret = snd_soc_cache_write(codec, reg, value);
|
|
|
+ if (ret < 0)
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
|
|
|
if (codec->cache_only) {
|
|
|
codec->cache_sync = 1;
|
|
@@ -193,7 +208,8 @@ static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec,
|
|
|
unsigned int reg)
|
|
|
{
|
|
|
- u8 *cache = codec->reg_cache;
|
|
|
+ int ret;
|
|
|
+ unsigned int val;
|
|
|
|
|
|
reg &= 0xff;
|
|
|
if (reg >= codec->driver->reg_cache_size ||
|
|
@@ -205,7 +221,10 @@ static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec,
|
|
|
return codec->hw_read(codec, reg);
|
|
|
}
|
|
|
|
|
|
- return cache[reg];
|
|
|
+ ret = snd_soc_cache_read(codec, reg, &val);
|
|
|
+ if (ret < 0)
|
|
|
+ return -1;
|
|
|
+ return val;
|
|
|
}
|
|
|
|
|
|
#if defined(CONFIG_SPI_MASTER)
|
|
@@ -241,16 +260,19 @@ static int snd_soc_8_8_spi_write(void *control_data, const char *data,
|
|
|
static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
unsigned int value)
|
|
|
{
|
|
|
- u16 *reg_cache = codec->reg_cache;
|
|
|
u8 data[3];
|
|
|
+ int ret;
|
|
|
|
|
|
data[0] = reg;
|
|
|
data[1] = (value >> 8) & 0xff;
|
|
|
data[2] = value & 0xff;
|
|
|
|
|
|
if (!snd_soc_codec_volatile_register(codec, reg) &&
|
|
|
- reg < codec->driver->reg_cache_size)
|
|
|
- reg_cache[reg] = value;
|
|
|
+ reg < codec->driver->reg_cache_size) {
|
|
|
+ ret = snd_soc_cache_write(codec, reg, value);
|
|
|
+ if (ret < 0)
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
|
|
|
if (codec->cache_only) {
|
|
|
codec->cache_sync = 1;
|
|
@@ -266,7 +288,8 @@ static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec,
|
|
|
unsigned int reg)
|
|
|
{
|
|
|
- u16 *cache = codec->reg_cache;
|
|
|
+ int ret;
|
|
|
+ unsigned int val;
|
|
|
|
|
|
if (reg >= codec->driver->reg_cache_size ||
|
|
|
snd_soc_codec_volatile_register(codec, reg)) {
|
|
@@ -275,9 +298,12 @@ static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec,
|
|
|
|
|
|
BUG_ON(!codec->hw_read);
|
|
|
return codec->hw_read(codec, reg);
|
|
|
- } else {
|
|
|
- return cache[reg];
|
|
|
}
|
|
|
+
|
|
|
+ ret = snd_soc_cache_read(codec, reg, &val);
|
|
|
+ if (ret < 0)
|
|
|
+ return -1;
|
|
|
+ return val;
|
|
|
}
|
|
|
|
|
|
#if defined(CONFIG_SPI_MASTER)
|
|
@@ -416,7 +442,8 @@ static unsigned int snd_soc_16_8_read_i2c(struct snd_soc_codec *codec,
|
|
|
static unsigned int snd_soc_16_8_read(struct snd_soc_codec *codec,
|
|
|
unsigned int reg)
|
|
|
{
|
|
|
- u8 *cache = codec->reg_cache;
|
|
|
+ int ret;
|
|
|
+ unsigned int val;
|
|
|
|
|
|
reg &= 0xff;
|
|
|
if (reg >= codec->driver->reg_cache_size ||
|
|
@@ -428,13 +455,15 @@ static unsigned int snd_soc_16_8_read(struct snd_soc_codec *codec,
|
|
|
return codec->hw_read(codec, reg);
|
|
|
}
|
|
|
|
|
|
- return cache[reg];
|
|
|
+ ret = snd_soc_cache_read(codec, reg, &val);
|
|
|
+ if (ret < 0)
|
|
|
+ return -1;
|
|
|
+ return val;
|
|
|
}
|
|
|
|
|
|
static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
unsigned int value)
|
|
|
{
|
|
|
- u8 *cache = codec->reg_cache;
|
|
|
u8 data[3];
|
|
|
int ret;
|
|
|
|
|
@@ -444,8 +473,11 @@ static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
|
|
|
reg &= 0xff;
|
|
|
if (!snd_soc_codec_volatile_register(codec, reg) &&
|
|
|
- reg < codec->driver->reg_cache_size)
|
|
|
- cache[reg] = value;
|
|
|
+ reg < codec->driver->reg_cache_size) {
|
|
|
+ ret = snd_soc_cache_write(codec, reg, value);
|
|
|
+ if (ret < 0)
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
|
|
|
if (codec->cache_only) {
|
|
|
codec->cache_sync = 1;
|
|
@@ -529,7 +561,8 @@ static unsigned int snd_soc_16_16_read_i2c(struct snd_soc_codec *codec,
|
|
|
static unsigned int snd_soc_16_16_read(struct snd_soc_codec *codec,
|
|
|
unsigned int reg)
|
|
|
{
|
|
|
- u16 *cache = codec->reg_cache;
|
|
|
+ int ret;
|
|
|
+ unsigned int val;
|
|
|
|
|
|
if (reg >= codec->driver->reg_cache_size ||
|
|
|
snd_soc_codec_volatile_register(codec, reg)) {
|
|
@@ -540,13 +573,16 @@ static unsigned int snd_soc_16_16_read(struct snd_soc_codec *codec,
|
|
|
return codec->hw_read(codec, reg);
|
|
|
}
|
|
|
|
|
|
- return cache[reg];
|
|
|
+ ret = snd_soc_cache_read(codec, reg, &val);
|
|
|
+ if (ret < 0)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ return val;
|
|
|
}
|
|
|
|
|
|
static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
unsigned int value)
|
|
|
{
|
|
|
- u16 *cache = codec->reg_cache;
|
|
|
u8 data[4];
|
|
|
int ret;
|
|
|
|
|
@@ -556,8 +592,11 @@ static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
data[3] = value & 0xff;
|
|
|
|
|
|
if (!snd_soc_codec_volatile_register(codec, reg) &&
|
|
|
- reg < codec->driver->reg_cache_size)
|
|
|
- cache[reg] = value;
|
|
|
+ reg < codec->driver->reg_cache_size) {
|
|
|
+ ret = snd_soc_cache_write(codec, reg, value);
|
|
|
+ if (ret < 0)
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
|
|
|
if (codec->cache_only) {
|
|
|
codec->cache_sync = 1;
|
|
@@ -718,3 +757,241 @@ int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,
|
|
|
return 0;
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io);
|
|
|
+
|
|
|
+static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ struct snd_soc_codec_driver *codec_drv;
|
|
|
+ unsigned int val;
|
|
|
+
|
|
|
+ codec_drv = codec->driver;
|
|
|
+ for (i = 0; i < codec_drv->reg_cache_size; ++i) {
|
|
|
+ snd_soc_cache_read(codec, i, &val);
|
|
|
+ if (codec_drv->reg_cache_default) {
|
|
|
+ switch (codec_drv->reg_word_size) {
|
|
|
+ case 1: {
|
|
|
+ const u8 *cache;
|
|
|
+
|
|
|
+ cache = codec_drv->reg_cache_default;
|
|
|
+ if (cache[i] == val)
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 2: {
|
|
|
+ const u16 *cache;
|
|
|
+
|
|
|
+ cache = codec_drv->reg_cache_default;
|
|
|
+ if (cache[i] == val)
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ BUG();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ snd_soc_write(codec, i, val);
|
|
|
+ dev_dbg(codec->dev, "Synced register %#x, value = %#x\n",
|
|
|
+ i, val);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int snd_soc_flat_cache_write(struct snd_soc_codec *codec,
|
|
|
+ unsigned int reg, unsigned int value)
|
|
|
+{
|
|
|
+ switch (codec->driver->reg_word_size) {
|
|
|
+ case 1: {
|
|
|
+ u8 *cache;
|
|
|
+
|
|
|
+ cache = codec->reg_cache;
|
|
|
+ cache[reg] = value;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 2: {
|
|
|
+ u16 *cache;
|
|
|
+
|
|
|
+ cache = codec->reg_cache;
|
|
|
+ cache[reg] = value;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ BUG();
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int snd_soc_flat_cache_read(struct snd_soc_codec *codec,
|
|
|
+ unsigned int reg, unsigned int *value)
|
|
|
+{
|
|
|
+ switch (codec->driver->reg_word_size) {
|
|
|
+ case 1: {
|
|
|
+ u8 *cache;
|
|
|
+
|
|
|
+ cache = codec->reg_cache;
|
|
|
+ *value = cache[reg];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 2: {
|
|
|
+ u16 *cache;
|
|
|
+
|
|
|
+ cache = codec->reg_cache;
|
|
|
+ *value = cache[reg];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ BUG();
|
|
|
+ }
|
|
|
+
|
|
|
+ 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)
|
|
|
+{
|
|
|
+ struct snd_soc_codec_driver *codec_drv;
|
|
|
+ size_t reg_size;
|
|
|
+
|
|
|
+ codec_drv = codec->driver;
|
|
|
+ reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size;
|
|
|
+
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+/* an array of all supported compression types */
|
|
|
+static const struct snd_soc_cache_ops cache_types[] = {
|
|
|
+ {
|
|
|
+ .id = SND_SOC_NO_COMPRESSION,
|
|
|
+ .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
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+int snd_soc_cache_init(struct snd_soc_codec *codec)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < ARRAY_SIZE(cache_types); ++i)
|
|
|
+ if (cache_types[i].id == codec->driver->compress_type)
|
|
|
+ break;
|
|
|
+ if (i == ARRAY_SIZE(cache_types)) {
|
|
|
+ dev_err(codec->dev, "Could not match compress type: %d\n",
|
|
|
+ codec->driver->compress_type);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_init(&codec->cache_rw_mutex);
|
|
|
+ codec->cache_ops = &cache_types[i];
|
|
|
+
|
|
|
+ if (codec->cache_ops->init)
|
|
|
+ return codec->cache_ops->init(codec);
|
|
|
+ return -EINVAL;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * NOTE: keep in mind that this function might be called
|
|
|
+ * multiple times.
|
|
|
+ */
|
|
|
+int snd_soc_cache_exit(struct snd_soc_codec *codec)
|
|
|
+{
|
|
|
+ if (codec->cache_ops && codec->cache_ops->exit)
|
|
|
+ return codec->cache_ops->exit(codec);
|
|
|
+ return -EINVAL;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 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);
|
|
|
+ return -EINVAL;
|
|
|
+}
|
|
|
+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);
|
|
|
+ return -EINVAL;
|
|
|
+}
|
|
|
+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;
|
|
|
+
|
|
|
+ if (!codec->cache_sync) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (codec->cache_ops && codec->cache_ops->sync) {
|
|
|
+ ret = codec->cache_ops->sync(codec);
|
|
|
+ if (!ret)
|
|
|
+ codec->cache_sync = 0;
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ return -EINVAL;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(snd_soc_cache_sync);
|