#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <sound/soc.h>
+#include <linux/bitmap.h>
+#include <linux/rbtree.h>
+#include <linux/export.h>
-static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec,
- unsigned int reg)
+#include <trace/events/asoc.h>
+
+static bool snd_soc_set_cache_val(void *base, unsigned int idx,
+ unsigned int val, unsigned int word_size)
{
- u16 *cache = codec->reg_cache;
- if (reg >= codec->reg_cache_size)
- return -1;
- return cache[reg];
+ 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 int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg,
- unsigned int value)
+static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx,
+ unsigned int word_size)
{
- u16 *cache = codec->reg_cache;
- u8 data[2];
- int ret;
-
- BUG_ON(codec->volatile_register);
-
- data[0] = (reg << 4) | ((value >> 8) & 0x000f);
- data[1] = value & 0x00ff;
+ if (!base)
+ return -1;
- if (reg < codec->reg_cache_size)
- cache[reg] = value;
- ret = codec->hw_write(codec->control_data, data, 2);
- if (ret == 2)
- return 0;
- if (ret < 0)
- return ret;
- else
- return -EIO;
+ 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;
}
-#if defined(CONFIG_SPI_MASTER)
-static int snd_soc_4_12_spi_write(void *control_data, const char *data,
- int len)
+static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec)
{
- struct spi_device *spi = control_data;
- struct spi_transfer t;
- struct spi_message m;
- u8 msg[2];
-
- if (len <= 0)
- return 0;
-
- msg[0] = data[1];
- msg[1] = data[0];
-
- spi_message_init(&m);
- memset(&t, 0, (sizeof t));
-
- t.tx_buf = &msg[0];
- t.len = len;
-
- spi_message_add_tail(&t, &m);
- spi_sync(spi, &m);
-
- return len;
+ 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->reg_def_copy)
+ if (snd_soc_get_cache_val(codec->reg_def_copy,
+ 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, "Synced register %#x, value = %#x\n",
+ i, val);
+ }
+ return 0;
}
-#else
-#define snd_soc_4_12_spi_write NULL
-#endif
-static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec,
- unsigned int reg)
+static int snd_soc_flat_cache_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
{
- u16 *cache = codec->reg_cache;
- if (reg >= codec->reg_cache_size)
- return -1;
- return cache[reg];
+ snd_soc_set_cache_val(codec->reg_cache, reg, value,
+ codec->driver->reg_word_size);
+ return 0;
}
-static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg,
- unsigned int value)
+static int snd_soc_flat_cache_read(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int *value)
{
- u16 *cache = codec->reg_cache;
- u8 data[2];
- int ret;
-
- BUG_ON(codec->volatile_register);
-
- data[0] = (reg << 1) | ((value >> 8) & 0x0001);
- data[1] = value & 0x00ff;
-
- if (reg < codec->reg_cache_size)
- cache[reg] = value;
- ret = codec->hw_write(codec->control_data, data, 2);
- if (ret == 2)
- return 0;
- if (ret < 0)
- return ret;
- else
- return -EIO;
+ *value = snd_soc_get_cache_val(codec->reg_cache, reg,
+ codec->driver->reg_word_size);
+ return 0;
}
-#if defined(CONFIG_SPI_MASTER)
-static int snd_soc_7_9_spi_write(void *control_data, const char *data,
- int len)
+static int snd_soc_flat_cache_exit(struct snd_soc_codec *codec)
{
- struct spi_device *spi = control_data;
- struct spi_transfer t;
- struct spi_message m;
- u8 msg[2];
-
- if (len <= 0)
+ if (!codec->reg_cache)
return 0;
+ kfree(codec->reg_cache);
+ codec->reg_cache = NULL;
+ return 0;
+}
- msg[0] = data[0];
- msg[1] = data[1];
-
- spi_message_init(&m);
- memset(&t, 0, (sizeof t));
-
- t.tx_buf = &msg[0];
- t.len = len;
-
- spi_message_add_tail(&t, &m);
- spi_sync(spi, &m);
+static int snd_soc_flat_cache_init(struct snd_soc_codec *codec)
+{
+ if (codec->reg_def_copy)
+ codec->reg_cache = kmemdup(codec->reg_def_copy,
+ codec->reg_size, GFP_KERNEL);
+ else
+ codec->reg_cache = kzalloc(codec->reg_size, GFP_KERNEL);
+ if (!codec->reg_cache)
+ return -ENOMEM;
- return len;
+ return 0;
}
-#else
-#define snd_soc_7_9_spi_write NULL
-#endif
-static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg,
- unsigned int value)
+/* an array of all supported compression types */
+static const struct snd_soc_cache_ops cache_types[] = {
+ /* Flat *must* be the first entry for fallback */
+ {
+ .id = SND_SOC_FLAT_COMPRESSION,
+ .name = "flat",
+ .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)
{
- u8 *cache = codec->reg_cache;
- u8 data[2];
+ int i;
- BUG_ON(codec->volatile_register);
+ for (i = 0; i < ARRAY_SIZE(cache_types); ++i)
+ if (cache_types[i].id == codec->compress_type)
+ break;
- data[0] = reg & 0xff;
- data[1] = value & 0xff;
+ /* Fall back to flat compression */
+ if (i == ARRAY_SIZE(cache_types)) {
+ dev_warn(codec->dev, "Could not match compress type: %d\n",
+ codec->compress_type);
+ i = 0;
+ }
- if (reg < codec->reg_cache_size)
- cache[reg] = value;
+ mutex_init(&codec->cache_rw_mutex);
+ codec->cache_ops = &cache_types[i];
- if (codec->hw_write(codec->control_data, data, 2) == 2)
- return 0;
- else
- return -EIO;
+ if (codec->cache_ops->init) {
+ if (codec->cache_ops->name)
+ dev_dbg(codec->dev, "Initializing %s cache for %s codec\n",
+ codec->cache_ops->name, codec->name);
+ return codec->cache_ops->init(codec);
+ }
+ return -ENOSYS;
}
-static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec,
- unsigned int reg)
+/*
+ * NOTE: keep in mind that this function might be called
+ * multiple times.
+ */
+int snd_soc_cache_exit(struct snd_soc_codec *codec)
{
- u8 *cache = codec->reg_cache;
- if (reg >= codec->reg_cache_size)
- return -1;
- return cache[reg];
+ if (codec->cache_ops && codec->cache_ops->exit) {
+ if (codec->cache_ops->name)
+ dev_dbg(codec->dev, "Destroying %s cache for %s codec\n",
+ codec->cache_ops->name, codec->name);
+ return codec->cache_ops->exit(codec);
+ }
+ return -ENOSYS;
}
-static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg,
- unsigned int value)
+/**
+ * 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)
{
- u16 *reg_cache = codec->reg_cache;
- u8 data[3];
+ int ret;
- data[0] = reg;
- data[1] = (value >> 8) & 0xff;
- data[2] = value & 0xff;
+ mutex_lock(&codec->cache_rw_mutex);
- if (!snd_soc_codec_volatile_register(codec, reg))
- reg_cache[reg] = value;
+ 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;
+ }
- if (codec->hw_write(codec->control_data, data, 3) == 3)
- return 0;
- else
- return -EIO;
+ mutex_unlock(&codec->cache_rw_mutex);
+ return -ENOSYS;
}
+EXPORT_SYMBOL_GPL(snd_soc_cache_read);
-static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec,
- unsigned int reg)
+/**
+ * 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)
{
- u16 *cache = codec->reg_cache;
+ int ret;
- if (reg >= codec->reg_cache_size ||
- snd_soc_codec_volatile_register(codec, reg))
- return codec->hw_read(codec, reg);
- else
- return cache[reg];
-}
+ mutex_lock(&codec->cache_rw_mutex);
-#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
-static unsigned int snd_soc_8_16_read_i2c(struct snd_soc_codec *codec,
- unsigned int r)
-{
- struct i2c_msg xfer[2];
- u8 reg = r;
- u16 data;
- int ret;
- struct i2c_client *client = codec->control_data;
-
- /* Write register */
- xfer[0].addr = client->addr;
- xfer[0].flags = 0;
- xfer[0].len = 1;
- xfer[0].buf = ®
-
- /* Read data */
- xfer[1].addr = client->addr;
- xfer[1].flags = I2C_M_RD;
- xfer[1].len = 2;
- xfer[1].buf = (u8 *)&data;
-
- ret = i2c_transfer(client->adapter, xfer, 2);
- if (ret != 2) {
- dev_err(&client->dev, "i2c_transfer() returned %d\n", ret);
- return 0;
+ if (codec->cache_ops && codec->cache_ops->write) {
+ ret = codec->cache_ops->write(codec, reg, value);
+ mutex_unlock(&codec->cache_rw_mutex);
+ return ret;
}
- return (data >> 8) | ((data & 0xff) << 8);
+ mutex_unlock(&codec->cache_rw_mutex);
+ return -ENOSYS;
}
-#else
-#define snd_soc_8_16_read_i2c NULL
-#endif
+EXPORT_SYMBOL_GPL(snd_soc_cache_write);
-#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
-static unsigned int snd_soc_16_8_read_i2c(struct snd_soc_codec *codec,
- unsigned int r)
+/**
+ * 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)
{
- struct i2c_msg xfer[2];
- u16 reg = r;
- u8 data;
int ret;
- struct i2c_client *client = codec->control_data;
-
- /* Write register */
- xfer[0].addr = client->addr;
- xfer[0].flags = 0;
- xfer[0].len = 2;
- xfer[0].buf = (u8 *)®
-
- /* Read data */
- xfer[1].addr = client->addr;
- xfer[1].flags = I2C_M_RD;
- xfer[1].len = 1;
- xfer[1].buf = &data;
-
- ret = i2c_transfer(client->adapter, xfer, 2);
- if (ret != 2) {
- dev_err(&client->dev, "i2c_transfer() returned %d\n", ret);
+ const char *name;
+
+ if (!codec->cache_sync) {
return 0;
}
- return data;
+ if (!codec->cache_ops || !codec->cache_ops->sync)
+ return -ENOSYS;
+
+ if (codec->cache_ops->name)
+ name = codec->cache_ops->name;
+ else
+ name = "unknown";
+
+ if (codec->cache_ops->name)
+ dev_dbg(codec->dev, "Syncing %s cache for %s codec\n",
+ 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;
}
-#else
-#define snd_soc_16_8_read_i2c NULL
-#endif
+EXPORT_SYMBOL_GPL(snd_soc_cache_sync);
-static unsigned int snd_soc_16_8_read(struct snd_soc_codec *codec,
- unsigned int reg)
+static int snd_soc_get_reg_access_index(struct snd_soc_codec *codec,
+ unsigned int reg)
{
- u16 *cache = codec->reg_cache;
-
- reg &= 0xff;
- if (reg >= codec->reg_cache_size)
- return -1;
- return cache[reg];
+ const struct snd_soc_codec_driver *codec_drv;
+ unsigned int min, max, index;
+
+ codec_drv = codec->driver;
+ min = 0;
+ max = codec_drv->reg_access_size - 1;
+ do {
+ index = (min + max) / 2;
+ if (codec_drv->reg_access_default[index].reg == reg)
+ return index;
+ if (codec_drv->reg_access_default[index].reg < reg)
+ min = index + 1;
+ else
+ max = index;
+ } while (min <= max);
+ return -1;
}
-static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg,
- unsigned int value)
+int snd_soc_default_volatile_register(struct snd_soc_codec *codec,
+ unsigned int reg)
{
- u16 *cache = codec->reg_cache;
- u8 data[3];
- int ret;
-
- BUG_ON(codec->volatile_register);
+ int index;
- data[0] = (reg >> 8) & 0xff;
- data[1] = reg & 0xff;
- data[2] = value;
-
- reg &= 0xff;
- if (reg < codec->reg_cache_size)
- cache[reg] = value;
- ret = codec->hw_write(codec->control_data, data, 3);
- if (ret == 3)
+ if (reg >= codec->driver->reg_cache_size)
+ return 1;
+ index = snd_soc_get_reg_access_index(codec, reg);
+ if (index < 0)
return 0;
- if (ret < 0)
- return ret;
- else
- return -EIO;
+ return codec->driver->reg_access_default[index].vol;
}
+EXPORT_SYMBOL_GPL(snd_soc_default_volatile_register);
-#if defined(CONFIG_SPI_MASTER)
-static int snd_soc_16_8_spi_write(void *control_data, const char *data,
- int len)
+int snd_soc_default_readable_register(struct snd_soc_codec *codec,
+ unsigned int reg)
{
- struct spi_device *spi = control_data;
- struct spi_transfer t;
- struct spi_message m;
- u8 msg[3];
+ int index;
- if (len <= 0)
+ if (reg >= codec->driver->reg_cache_size)
+ return 1;
+ index = snd_soc_get_reg_access_index(codec, reg);
+ if (index < 0)
return 0;
-
- msg[0] = data[0];
- msg[1] = data[1];
- msg[2] = data[2];
-
- spi_message_init(&m);
- memset(&t, 0, (sizeof t));
-
- t.tx_buf = &msg[0];
- t.len = len;
-
- spi_message_add_tail(&t, &m);
- spi_sync(spi, &m);
-
- return len;
+ return codec->driver->reg_access_default[index].read;
}
-#else
-#define snd_soc_16_8_spi_write NULL
-#endif
-
-
-static struct {
- int addr_bits;
- int data_bits;
- int (*write)(struct snd_soc_codec *codec, unsigned int, unsigned int);
- int (*spi_write)(void *, const char *, int);
- unsigned int (*read)(struct snd_soc_codec *, unsigned int);
- unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int);
-} io_types[] = {
- {
- .addr_bits = 4, .data_bits = 12,
- .write = snd_soc_4_12_write, .read = snd_soc_4_12_read,
- .spi_write = snd_soc_4_12_spi_write,
- },
- {
- .addr_bits = 7, .data_bits = 9,
- .write = snd_soc_7_9_write, .read = snd_soc_7_9_read,
- .spi_write = snd_soc_7_9_spi_write,
- },
- {
- .addr_bits = 8, .data_bits = 8,
- .write = snd_soc_8_8_write, .read = snd_soc_8_8_read,
- },
- {
- .addr_bits = 8, .data_bits = 16,
- .write = snd_soc_8_16_write, .read = snd_soc_8_16_read,
- .i2c_read = snd_soc_8_16_read_i2c,
- },
- {
- .addr_bits = 16, .data_bits = 8,
- .write = snd_soc_16_8_write, .read = snd_soc_16_8_read,
- .i2c_read = snd_soc_16_8_read_i2c,
- .spi_write = snd_soc_16_8_spi_write,
- },
-};
+EXPORT_SYMBOL_GPL(snd_soc_default_readable_register);
-/**
- * snd_soc_codec_set_cache_io: Set up standard I/O functions.
- *
- * @codec: CODEC to configure.
- * @type: Type of cache.
- * @addr_bits: Number of bits of register address data.
- * @data_bits: Number of bits of data per register.
- * @control: Control bus used.
- *
- * Register formats are frequently shared between many I2C and SPI
- * devices. In order to promote code reuse the ASoC core provides
- * some standard implementations of CODEC read and write operations
- * which can be set up using this function.
- *
- * The caller is responsible for allocating and initialising the
- * actual cache.
- *
- * Note that at present this code cannot be used by CODECs with
- * volatile registers.
- */
-int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,
- int addr_bits, int data_bits,
- enum snd_soc_control_type control)
+int snd_soc_default_writable_register(struct snd_soc_codec *codec,
+ unsigned int reg)
{
- int i;
+ int index;
- for (i = 0; i < ARRAY_SIZE(io_types); i++)
- if (io_types[i].addr_bits == addr_bits &&
- io_types[i].data_bits == data_bits)
- break;
- if (i == ARRAY_SIZE(io_types)) {
- printk(KERN_ERR
- "No I/O functions for %d bit address %d bit data\n",
- addr_bits, data_bits);
- return -EINVAL;
- }
-
- codec->write = io_types[i].write;
- codec->read = io_types[i].read;
-
- switch (control) {
- case SND_SOC_CUSTOM:
- break;
-
- case SND_SOC_I2C:
-#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
- codec->hw_write = (hw_write_t)i2c_master_send;
-#endif
- if (io_types[i].i2c_read)
- codec->hw_read = io_types[i].i2c_read;
- break;
-
- case SND_SOC_SPI:
- if (io_types[i].spi_write)
- codec->hw_write = io_types[i].spi_write;
- break;
- }
-
- return 0;
+ if (reg >= codec->driver->reg_cache_size)
+ return 1;
+ index = snd_soc_get_reg_access_index(codec, reg);
+ if (index < 0)
+ return 0;
+ return codec->driver->reg_access_default[index].write;
}
-EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io);
+EXPORT_SYMBOL_GPL(snd_soc_default_writable_register);