| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 1 | /* | 
|  | 2 | * soc-cache.c  --  ASoC register cache helpers | 
|  | 3 | * | 
|  | 4 | * Copyright 2009 Wolfson Microelectronics PLC. | 
|  | 5 | * | 
|  | 6 | * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> | 
|  | 7 | * | 
|  | 8 | *  This program is free software; you can redistribute  it and/or modify it | 
|  | 9 | *  under  the terms of  the GNU General  Public License as published by the | 
|  | 10 | *  Free Software Foundation;  either version 2 of the  License, or (at your | 
|  | 11 | *  option) any later version. | 
|  | 12 | */ | 
|  | 13 |  | 
| Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 14 | #include <linux/i2c.h> | 
| Mark Brown | 27ded04 | 2009-07-10 23:28:16 +0100 | [diff] [blame] | 15 | #include <linux/spi/spi.h> | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 16 | #include <sound/soc.h> | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 17 | #include <linux/lzo.h> | 
|  | 18 | #include <linux/bitmap.h> | 
| Dimitris Papastamos | a7f387d | 2010-11-11 10:04:59 +0000 | [diff] [blame] | 19 | #include <linux/rbtree.h> | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 20 |  | 
| Dimitris Papastamos | c358e64 | 2011-01-21 15:29:02 +0000 | [diff] [blame] | 21 | #include <trace/events/asoc.h> | 
|  | 22 |  | 
| Dimitris Papastamos | 30539a1 | 2011-03-22 10:37:00 +0000 | [diff] [blame] | 23 | #if defined(CONFIG_SPI_MASTER) | 
|  | 24 | static int do_spi_write(void *control_data, const void *msg, | 
|  | 25 | int len) | 
|  | 26 | { | 
|  | 27 | struct spi_device *spi = control_data; | 
|  | 28 | struct spi_transfer t; | 
|  | 29 | struct spi_message m; | 
|  | 30 |  | 
|  | 31 | if (len <= 0) | 
|  | 32 | return 0; | 
|  | 33 |  | 
|  | 34 | spi_message_init(&m); | 
|  | 35 | memset(&t, 0, sizeof t); | 
|  | 36 |  | 
|  | 37 | t.tx_buf = msg; | 
|  | 38 | t.len = len; | 
|  | 39 |  | 
|  | 40 | spi_message_add_tail(&t, &m); | 
|  | 41 | spi_sync(spi, &m); | 
|  | 42 |  | 
|  | 43 | return len; | 
|  | 44 | } | 
|  | 45 | #endif | 
|  | 46 |  | 
| Dimitris Papastamos | 26e9984 | 2011-03-22 10:36:58 +0000 | [diff] [blame] | 47 | static int do_hw_write(struct snd_soc_codec *codec, unsigned int reg, | 
|  | 48 | unsigned int value, const void *data, int len) | 
|  | 49 | { | 
|  | 50 | int ret; | 
|  | 51 |  | 
|  | 52 | if (!snd_soc_codec_volatile_register(codec, reg) && | 
|  | 53 | reg < codec->driver->reg_cache_size && | 
|  | 54 | !codec->cache_bypass) { | 
|  | 55 | ret = snd_soc_cache_write(codec, reg, value); | 
|  | 56 | if (ret < 0) | 
|  | 57 | return -1; | 
|  | 58 | } | 
|  | 59 |  | 
|  | 60 | if (codec->cache_only) { | 
|  | 61 | codec->cache_sync = 1; | 
|  | 62 | return 0; | 
|  | 63 | } | 
|  | 64 |  | 
|  | 65 | ret = codec->hw_write(codec->control_data, data, len); | 
|  | 66 | if (ret == len) | 
|  | 67 | return 0; | 
|  | 68 | if (ret < 0) | 
|  | 69 | return ret; | 
|  | 70 | else | 
|  | 71 | return -EIO; | 
|  | 72 | } | 
|  | 73 |  | 
| Dimitris Papastamos | b8cbc19 | 2011-03-22 10:36:59 +0000 | [diff] [blame] | 74 | static unsigned int do_hw_read(struct snd_soc_codec *codec, unsigned int reg) | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 75 | { | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 76 | int ret; | 
|  | 77 | unsigned int val; | 
| Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 78 |  | 
|  | 79 | if (reg >= codec->driver->reg_cache_size || | 
| Dimitris Papastamos | b8cbc19 | 2011-03-22 10:36:59 +0000 | [diff] [blame] | 80 | snd_soc_codec_volatile_register(codec, reg) || | 
|  | 81 | codec->cache_bypass) { | 
|  | 82 | if (codec->cache_only) | 
|  | 83 | return -1; | 
| Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 84 |  | 
| Dimitris Papastamos | b8cbc19 | 2011-03-22 10:36:59 +0000 | [diff] [blame] | 85 | BUG_ON(!codec->hw_read); | 
|  | 86 | return codec->hw_read(codec, reg); | 
| Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 87 | } | 
|  | 88 |  | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 89 | ret = snd_soc_cache_read(codec, reg, &val); | 
|  | 90 | if (ret < 0) | 
|  | 91 | return -1; | 
|  | 92 | return val; | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 93 | } | 
|  | 94 |  | 
| Dimitris Papastamos | b8cbc19 | 2011-03-22 10:36:59 +0000 | [diff] [blame] | 95 | static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec, | 
| Dimitris Papastamos | fbda182 | 2011-03-28 11:39:14 +0100 | [diff] [blame] | 96 | unsigned int reg) | 
| Dimitris Papastamos | b8cbc19 | 2011-03-22 10:36:59 +0000 | [diff] [blame] | 97 | { | 
|  | 98 | return do_hw_read(codec, reg); | 
|  | 99 | } | 
|  | 100 |  | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 101 | static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg, | 
| Dimitris Papastamos | fbda182 | 2011-03-28 11:39:14 +0100 | [diff] [blame] | 102 | unsigned int value) | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 103 | { | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 104 | u8 data[2]; | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 105 |  | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 106 | data[0] = (reg << 4) | ((value >> 8) & 0x000f); | 
|  | 107 | data[1] = value & 0x00ff; | 
|  | 108 |  | 
| Dimitris Papastamos | 26e9984 | 2011-03-22 10:36:58 +0000 | [diff] [blame] | 109 | return do_hw_write(codec, reg, value, data, 2); | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 110 | } | 
|  | 111 |  | 
|  | 112 | #if defined(CONFIG_SPI_MASTER) | 
|  | 113 | static int snd_soc_4_12_spi_write(void *control_data, const char *data, | 
| Dimitris Papastamos | fbda182 | 2011-03-28 11:39:14 +0100 | [diff] [blame] | 114 | int len) | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 115 | { | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 116 | u8 msg[2]; | 
|  | 117 |  | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 118 | msg[0] = data[1]; | 
|  | 119 | msg[1] = data[0]; | 
|  | 120 |  | 
| Dimitris Papastamos | 30539a1 | 2011-03-22 10:37:00 +0000 | [diff] [blame] | 121 | return do_spi_write(control_data, msg, len); | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 122 | } | 
|  | 123 | #else | 
|  | 124 | #define snd_soc_4_12_spi_write NULL | 
|  | 125 | #endif | 
|  | 126 |  | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 127 | static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec, | 
|  | 128 | unsigned int reg) | 
|  | 129 | { | 
| Dimitris Papastamos | b8cbc19 | 2011-03-22 10:36:59 +0000 | [diff] [blame] | 130 | return do_hw_read(codec, reg); | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 131 | } | 
|  | 132 |  | 
|  | 133 | static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg, | 
|  | 134 | unsigned int value) | 
|  | 135 | { | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 136 | u8 data[2]; | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 137 |  | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 138 | data[0] = (reg << 1) | ((value >> 8) & 0x0001); | 
|  | 139 | data[1] = value & 0x00ff; | 
|  | 140 |  | 
| Dimitris Papastamos | 26e9984 | 2011-03-22 10:36:58 +0000 | [diff] [blame] | 141 | return do_hw_write(codec, reg, value, data, 2); | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 142 | } | 
|  | 143 |  | 
| Mark Brown | 27ded04 | 2009-07-10 23:28:16 +0100 | [diff] [blame] | 144 | #if defined(CONFIG_SPI_MASTER) | 
|  | 145 | static int snd_soc_7_9_spi_write(void *control_data, const char *data, | 
|  | 146 | int len) | 
|  | 147 | { | 
| Mark Brown | 27ded04 | 2009-07-10 23:28:16 +0100 | [diff] [blame] | 148 | u8 msg[2]; | 
|  | 149 |  | 
| Mark Brown | 27ded04 | 2009-07-10 23:28:16 +0100 | [diff] [blame] | 150 | msg[0] = data[0]; | 
|  | 151 | msg[1] = data[1]; | 
|  | 152 |  | 
| Dimitris Papastamos | 30539a1 | 2011-03-22 10:37:00 +0000 | [diff] [blame] | 153 | return do_spi_write(control_data, msg, len); | 
| Mark Brown | 27ded04 | 2009-07-10 23:28:16 +0100 | [diff] [blame] | 154 | } | 
|  | 155 | #else | 
|  | 156 | #define snd_soc_7_9_spi_write NULL | 
|  | 157 | #endif | 
|  | 158 |  | 
| Joonyoung Shim | 341c9b8 | 2009-09-07 12:04:37 +0900 | [diff] [blame] | 159 | static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg, | 
|  | 160 | unsigned int value) | 
|  | 161 | { | 
| Joonyoung Shim | 341c9b8 | 2009-09-07 12:04:37 +0900 | [diff] [blame] | 162 | u8 data[2]; | 
|  | 163 |  | 
| Barry Song | f4bee1b | 2010-03-18 16:17:01 +0800 | [diff] [blame] | 164 | reg &= 0xff; | 
|  | 165 | data[0] = reg; | 
| Joonyoung Shim | 341c9b8 | 2009-09-07 12:04:37 +0900 | [diff] [blame] | 166 | data[1] = value & 0xff; | 
|  | 167 |  | 
| Dimitris Papastamos | 26e9984 | 2011-03-22 10:36:58 +0000 | [diff] [blame] | 168 | return do_hw_write(codec, reg, value, data, 2); | 
| Joonyoung Shim | 341c9b8 | 2009-09-07 12:04:37 +0900 | [diff] [blame] | 169 | } | 
|  | 170 |  | 
|  | 171 | static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec, | 
|  | 172 | unsigned int reg) | 
|  | 173 | { | 
| Dimitris Papastamos | b8cbc19 | 2011-03-22 10:36:59 +0000 | [diff] [blame] | 174 | return do_hw_read(codec, reg); | 
| Joonyoung Shim | 341c9b8 | 2009-09-07 12:04:37 +0900 | [diff] [blame] | 175 | } | 
|  | 176 |  | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 177 | #if defined(CONFIG_SPI_MASTER) | 
|  | 178 | static int snd_soc_8_8_spi_write(void *control_data, const char *data, | 
|  | 179 | int len) | 
|  | 180 | { | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 181 | u8 msg[2]; | 
|  | 182 |  | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 183 | msg[0] = data[0]; | 
|  | 184 | msg[1] = data[1]; | 
|  | 185 |  | 
| Dimitris Papastamos | 30539a1 | 2011-03-22 10:37:00 +0000 | [diff] [blame] | 186 | return do_spi_write(control_data, msg, len); | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 187 | } | 
|  | 188 | #else | 
|  | 189 | #define snd_soc_8_8_spi_write NULL | 
|  | 190 | #endif | 
|  | 191 |  | 
| Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 192 | static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg, | 
|  | 193 | unsigned int value) | 
|  | 194 | { | 
| Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 195 | u8 data[3]; | 
|  | 196 |  | 
|  | 197 | data[0] = reg; | 
|  | 198 | data[1] = (value >> 8) & 0xff; | 
|  | 199 | data[2] = value & 0xff; | 
|  | 200 |  | 
| Dimitris Papastamos | 26e9984 | 2011-03-22 10:36:58 +0000 | [diff] [blame] | 201 | return do_hw_write(codec, reg, value, data, 3); | 
| Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 202 | } | 
|  | 203 |  | 
|  | 204 | static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec, | 
|  | 205 | unsigned int reg) | 
|  | 206 | { | 
| Dimitris Papastamos | b8cbc19 | 2011-03-22 10:36:59 +0000 | [diff] [blame] | 207 | return do_hw_read(codec, reg); | 
| Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 208 | } | 
|  | 209 |  | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 210 | #if defined(CONFIG_SPI_MASTER) | 
|  | 211 | static int snd_soc_8_16_spi_write(void *control_data, const char *data, | 
| Dimitris Papastamos | fbda182 | 2011-03-28 11:39:14 +0100 | [diff] [blame] | 212 | int len) | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 213 | { | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 214 | u8 msg[3]; | 
|  | 215 |  | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 216 | msg[0] = data[0]; | 
|  | 217 | msg[1] = data[1]; | 
|  | 218 | msg[2] = data[2]; | 
|  | 219 |  | 
| Dimitris Papastamos | 30539a1 | 2011-03-22 10:37:00 +0000 | [diff] [blame] | 220 | return do_spi_write(control_data, msg, len); | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 221 | } | 
|  | 222 | #else | 
|  | 223 | #define snd_soc_8_16_spi_write NULL | 
|  | 224 | #endif | 
|  | 225 |  | 
| Randy Dunlap | 17244c2 | 2009-08-10 16:04:39 -0700 | [diff] [blame] | 226 | #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) | 
| Dimitris Papastamos | f3594f5 | 2011-03-22 10:37:01 +0000 | [diff] [blame] | 227 | static unsigned int do_i2c_read(struct snd_soc_codec *codec, | 
|  | 228 | void *reg, int reglen, | 
|  | 229 | void *data, int datalen) | 
| Cliff Cai | 85dfcdf | 2010-03-18 16:17:00 +0800 | [diff] [blame] | 230 | { | 
|  | 231 | struct i2c_msg xfer[2]; | 
| Cliff Cai | 85dfcdf | 2010-03-18 16:17:00 +0800 | [diff] [blame] | 232 | int ret; | 
|  | 233 | struct i2c_client *client = codec->control_data; | 
|  | 234 |  | 
|  | 235 | /* Write register */ | 
|  | 236 | xfer[0].addr = client->addr; | 
|  | 237 | xfer[0].flags = 0; | 
| Dimitris Papastamos | f3594f5 | 2011-03-22 10:37:01 +0000 | [diff] [blame] | 238 | xfer[0].len = reglen; | 
|  | 239 | xfer[0].buf = reg; | 
| Cliff Cai | 85dfcdf | 2010-03-18 16:17:00 +0800 | [diff] [blame] | 240 |  | 
|  | 241 | /* Read data */ | 
|  | 242 | xfer[1].addr = client->addr; | 
|  | 243 | xfer[1].flags = I2C_M_RD; | 
| Dimitris Papastamos | f3594f5 | 2011-03-22 10:37:01 +0000 | [diff] [blame] | 244 | xfer[1].len = datalen; | 
|  | 245 | xfer[1].buf = data; | 
| Cliff Cai | 85dfcdf | 2010-03-18 16:17:00 +0800 | [diff] [blame] | 246 |  | 
|  | 247 | ret = i2c_transfer(client->adapter, xfer, 2); | 
| Dimitris Papastamos | f3594f5 | 2011-03-22 10:37:01 +0000 | [diff] [blame] | 248 | dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); | 
|  | 249 | if (ret == 2) | 
| Cliff Cai | 85dfcdf | 2010-03-18 16:17:00 +0800 | [diff] [blame] | 250 | return 0; | 
| Dimitris Papastamos | f3594f5 | 2011-03-22 10:37:01 +0000 | [diff] [blame] | 251 | else if (ret < 0) | 
|  | 252 | return ret; | 
|  | 253 | else | 
|  | 254 | return -EIO; | 
|  | 255 | } | 
|  | 256 | #endif | 
| Cliff Cai | 85dfcdf | 2010-03-18 16:17:00 +0800 | [diff] [blame] | 257 |  | 
| Dimitris Papastamos | f3594f5 | 2011-03-22 10:37:01 +0000 | [diff] [blame] | 258 | #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) | 
|  | 259 | static unsigned int snd_soc_8_8_read_i2c(struct snd_soc_codec *codec, | 
| Dimitris Papastamos | fbda182 | 2011-03-28 11:39:14 +0100 | [diff] [blame] | 260 | unsigned int r) | 
| Dimitris Papastamos | f3594f5 | 2011-03-22 10:37:01 +0000 | [diff] [blame] | 261 | { | 
|  | 262 | u8 reg = r; | 
|  | 263 | u8 data; | 
|  | 264 | int ret; | 
|  | 265 |  | 
|  | 266 | ret = do_i2c_read(codec, ®, 1, &data, 1); | 
|  | 267 | if (ret < 0) | 
|  | 268 | return 0; | 
| Cliff Cai | 85dfcdf | 2010-03-18 16:17:00 +0800 | [diff] [blame] | 269 | return data; | 
|  | 270 | } | 
|  | 271 | #else | 
|  | 272 | #define snd_soc_8_8_read_i2c NULL | 
|  | 273 | #endif | 
|  | 274 |  | 
|  | 275 | #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) | 
| Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 276 | static unsigned int snd_soc_8_16_read_i2c(struct snd_soc_codec *codec, | 
|  | 277 | unsigned int r) | 
|  | 278 | { | 
| Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 279 | u8 reg = r; | 
|  | 280 | u16 data; | 
|  | 281 | int ret; | 
| Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 282 |  | 
| Dimitris Papastamos | f3594f5 | 2011-03-22 10:37:01 +0000 | [diff] [blame] | 283 | ret = do_i2c_read(codec, ®, 1, &data, 2); | 
|  | 284 | if (ret < 0) | 
| Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 285 | return 0; | 
| Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 286 | return (data >> 8) | ((data & 0xff) << 8); | 
|  | 287 | } | 
|  | 288 | #else | 
|  | 289 | #define snd_soc_8_16_read_i2c NULL | 
|  | 290 | #endif | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 291 |  | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 292 | #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) | 
|  | 293 | static unsigned int snd_soc_16_8_read_i2c(struct snd_soc_codec *codec, | 
|  | 294 | unsigned int r) | 
|  | 295 | { | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 296 | u16 reg = r; | 
|  | 297 | u8 data; | 
|  | 298 | int ret; | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 299 |  | 
| Dimitris Papastamos | f3594f5 | 2011-03-22 10:37:01 +0000 | [diff] [blame] | 300 | ret = do_i2c_read(codec, ®, 2, &data, 1); | 
|  | 301 | if (ret < 0) | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 302 | return 0; | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 303 | return data; | 
|  | 304 | } | 
|  | 305 | #else | 
|  | 306 | #define snd_soc_16_8_read_i2c NULL | 
|  | 307 | #endif | 
|  | 308 |  | 
|  | 309 | static unsigned int snd_soc_16_8_read(struct snd_soc_codec *codec, | 
| Dimitris Papastamos | fbda182 | 2011-03-28 11:39:14 +0100 | [diff] [blame] | 310 | unsigned int reg) | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 311 | { | 
| Dimitris Papastamos | b8cbc19 | 2011-03-22 10:36:59 +0000 | [diff] [blame] | 312 | return do_hw_read(codec, reg); | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 313 | } | 
|  | 314 |  | 
|  | 315 | static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg, | 
| Dimitris Papastamos | fbda182 | 2011-03-28 11:39:14 +0100 | [diff] [blame] | 316 | unsigned int value) | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 317 | { | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 318 | u8 data[3]; | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 319 |  | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 320 | data[0] = (reg >> 8) & 0xff; | 
|  | 321 | data[1] = reg & 0xff; | 
|  | 322 | data[2] = value; | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 323 | reg &= 0xff; | 
| Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 324 |  | 
| Dimitris Papastamos | 26e9984 | 2011-03-22 10:36:58 +0000 | [diff] [blame] | 325 | return do_hw_write(codec, reg, value, data, 3); | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 326 | } | 
|  | 327 |  | 
|  | 328 | #if defined(CONFIG_SPI_MASTER) | 
|  | 329 | static int snd_soc_16_8_spi_write(void *control_data, const char *data, | 
| Dimitris Papastamos | fbda182 | 2011-03-28 11:39:14 +0100 | [diff] [blame] | 330 | int len) | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 331 | { | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 332 | u8 msg[3]; | 
|  | 333 |  | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 334 | msg[0] = data[0]; | 
|  | 335 | msg[1] = data[1]; | 
|  | 336 | msg[2] = data[2]; | 
|  | 337 |  | 
| Dimitris Papastamos | 30539a1 | 2011-03-22 10:37:00 +0000 | [diff] [blame] | 338 | return do_spi_write(control_data, msg, len); | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 339 | } | 
|  | 340 | #else | 
|  | 341 | #define snd_soc_16_8_spi_write NULL | 
|  | 342 | #endif | 
|  | 343 |  | 
| Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 344 | #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) | 
|  | 345 | static unsigned int snd_soc_16_16_read_i2c(struct snd_soc_codec *codec, | 
|  | 346 | unsigned int r) | 
|  | 347 | { | 
| Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 348 | u16 reg = cpu_to_be16(r); | 
|  | 349 | u16 data; | 
|  | 350 | int ret; | 
| Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 351 |  | 
| Dimitris Papastamos | f3594f5 | 2011-03-22 10:37:01 +0000 | [diff] [blame] | 352 | ret = do_i2c_read(codec, ®, 2, &data, 2); | 
|  | 353 | if (ret < 0) | 
| Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 354 | return 0; | 
| Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 355 | return be16_to_cpu(data); | 
|  | 356 | } | 
|  | 357 | #else | 
|  | 358 | #define snd_soc_16_16_read_i2c NULL | 
|  | 359 | #endif | 
|  | 360 |  | 
|  | 361 | static unsigned int snd_soc_16_16_read(struct snd_soc_codec *codec, | 
|  | 362 | unsigned int reg) | 
|  | 363 | { | 
| Dimitris Papastamos | b8cbc19 | 2011-03-22 10:36:59 +0000 | [diff] [blame] | 364 | return do_hw_read(codec, reg); | 
| Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 365 | } | 
|  | 366 |  | 
|  | 367 | static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg, | 
|  | 368 | unsigned int value) | 
|  | 369 | { | 
| Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 370 | u8 data[4]; | 
| Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 371 |  | 
|  | 372 | data[0] = (reg >> 8) & 0xff; | 
|  | 373 | data[1] = reg & 0xff; | 
|  | 374 | data[2] = (value >> 8) & 0xff; | 
|  | 375 | data[3] = value & 0xff; | 
|  | 376 |  | 
| Dimitris Papastamos | 26e9984 | 2011-03-22 10:36:58 +0000 | [diff] [blame] | 377 | return do_hw_write(codec, reg, value, data, 4); | 
| Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 378 | } | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 379 |  | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 380 | #if defined(CONFIG_SPI_MASTER) | 
|  | 381 | static int snd_soc_16_16_spi_write(void *control_data, const char *data, | 
| Dimitris Papastamos | fbda182 | 2011-03-28 11:39:14 +0100 | [diff] [blame] | 382 | int len) | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 383 | { | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 384 | u8 msg[4]; | 
|  | 385 |  | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 386 | msg[0] = data[0]; | 
|  | 387 | msg[1] = data[1]; | 
|  | 388 | msg[2] = data[2]; | 
|  | 389 | msg[3] = data[3]; | 
|  | 390 |  | 
| Dimitris Papastamos | 30539a1 | 2011-03-22 10:37:00 +0000 | [diff] [blame] | 391 | return do_spi_write(control_data, msg, len); | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 392 | } | 
|  | 393 | #else | 
|  | 394 | #define snd_soc_16_16_spi_write NULL | 
|  | 395 | #endif | 
|  | 396 |  | 
| Dimitris Papastamos | 5fb609d | 2011-03-22 10:37:03 +0000 | [diff] [blame] | 397 | /* Primitive bulk write support for soc-cache.  The data pointed to by `data' needs | 
|  | 398 | * to already be in the form the hardware expects including any leading register specific | 
|  | 399 | * data.  Any data written through this function will not go through the cache as it | 
|  | 400 | * only handles writing to volatile or out of bounds registers. | 
|  | 401 | */ | 
|  | 402 | static int snd_soc_hw_bulk_write_raw(struct snd_soc_codec *codec, unsigned int reg, | 
|  | 403 | const void *data, size_t len) | 
|  | 404 | { | 
|  | 405 | int ret; | 
|  | 406 |  | 
|  | 407 | /* Ensure that the base register is volatile.  Subsequently | 
|  | 408 | * any other register that is touched by this routine should be | 
|  | 409 | * volatile as well to ensure that we don't get out of sync with | 
|  | 410 | * the cache. | 
|  | 411 | */ | 
|  | 412 | if (!snd_soc_codec_volatile_register(codec, reg) | 
|  | 413 | && reg < codec->driver->reg_cache_size) | 
|  | 414 | return -EINVAL; | 
|  | 415 |  | 
|  | 416 | switch (codec->control_type) { | 
|  | 417 | case SND_SOC_I2C: | 
|  | 418 | ret = i2c_master_send(codec->control_data, data, len); | 
|  | 419 | break; | 
|  | 420 | case SND_SOC_SPI: | 
|  | 421 | ret = do_spi_write(codec->control_data, data, len); | 
|  | 422 | break; | 
|  | 423 | default: | 
|  | 424 | BUG(); | 
|  | 425 | } | 
|  | 426 |  | 
|  | 427 | if (ret == len) | 
|  | 428 | return 0; | 
|  | 429 | if (ret < 0) | 
|  | 430 | return ret; | 
|  | 431 | else | 
|  | 432 | return -EIO; | 
|  | 433 | } | 
|  | 434 |  | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 435 | static struct { | 
|  | 436 | int addr_bits; | 
|  | 437 | int data_bits; | 
| Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 438 | int (*write)(struct snd_soc_codec *codec, unsigned int, unsigned int); | 
| Mark Brown | 27ded04 | 2009-07-10 23:28:16 +0100 | [diff] [blame] | 439 | int (*spi_write)(void *, const char *, int); | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 440 | unsigned int (*read)(struct snd_soc_codec *, unsigned int); | 
| Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 441 | unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int); | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 442 | } io_types[] = { | 
| Mark Brown | d62ab35 | 2009-09-21 04:21:47 -0700 | [diff] [blame] | 443 | { | 
| Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 444 | .addr_bits = 4, .data_bits = 12, | 
|  | 445 | .write = snd_soc_4_12_write, .read = snd_soc_4_12_read, | 
|  | 446 | .spi_write = snd_soc_4_12_spi_write, | 
|  | 447 | }, | 
|  | 448 | { | 
| Mark Brown | d62ab35 | 2009-09-21 04:21:47 -0700 | [diff] [blame] | 449 | .addr_bits = 7, .data_bits = 9, | 
|  | 450 | .write = snd_soc_7_9_write, .read = snd_soc_7_9_read, | 
| Barry Song | 8998c89 | 2009-12-31 10:30:34 +0800 | [diff] [blame] | 451 | .spi_write = snd_soc_7_9_spi_write, | 
| Mark Brown | d62ab35 | 2009-09-21 04:21:47 -0700 | [diff] [blame] | 452 | }, | 
|  | 453 | { | 
|  | 454 | .addr_bits = 8, .data_bits = 8, | 
|  | 455 | .write = snd_soc_8_8_write, .read = snd_soc_8_8_read, | 
| Cliff Cai | 85dfcdf | 2010-03-18 16:17:00 +0800 | [diff] [blame] | 456 | .i2c_read = snd_soc_8_8_read_i2c, | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 457 | .spi_write = snd_soc_8_8_spi_write, | 
| Mark Brown | d62ab35 | 2009-09-21 04:21:47 -0700 | [diff] [blame] | 458 | }, | 
|  | 459 | { | 
|  | 460 | .addr_bits = 8, .data_bits = 16, | 
|  | 461 | .write = snd_soc_8_16_write, .read = snd_soc_8_16_read, | 
|  | 462 | .i2c_read = snd_soc_8_16_read_i2c, | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 463 | .spi_write = snd_soc_8_16_spi_write, | 
| Mark Brown | d62ab35 | 2009-09-21 04:21:47 -0700 | [diff] [blame] | 464 | }, | 
| Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 465 | { | 
|  | 466 | .addr_bits = 16, .data_bits = 8, | 
|  | 467 | .write = snd_soc_16_8_write, .read = snd_soc_16_8_read, | 
|  | 468 | .i2c_read = snd_soc_16_8_read_i2c, | 
|  | 469 | .spi_write = snd_soc_16_8_spi_write, | 
|  | 470 | }, | 
| Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 471 | { | 
|  | 472 | .addr_bits = 16, .data_bits = 16, | 
|  | 473 | .write = snd_soc_16_16_write, .read = snd_soc_16_16_read, | 
|  | 474 | .i2c_read = snd_soc_16_16_read_i2c, | 
| Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 475 | .spi_write = snd_soc_16_16_spi_write, | 
| Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 476 | }, | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 477 | }; | 
|  | 478 |  | 
|  | 479 | /** | 
|  | 480 | * snd_soc_codec_set_cache_io: Set up standard I/O functions. | 
|  | 481 | * | 
|  | 482 | * @codec: CODEC to configure. | 
|  | 483 | * @type: Type of cache. | 
|  | 484 | * @addr_bits: Number of bits of register address data. | 
|  | 485 | * @data_bits: Number of bits of data per register. | 
| Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 486 | * @control: Control bus used. | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 487 | * | 
|  | 488 | * Register formats are frequently shared between many I2C and SPI | 
|  | 489 | * devices.  In order to promote code reuse the ASoC core provides | 
|  | 490 | * some standard implementations of CODEC read and write operations | 
|  | 491 | * which can be set up using this function. | 
|  | 492 | * | 
|  | 493 | * The caller is responsible for allocating and initialising the | 
|  | 494 | * actual cache. | 
|  | 495 | * | 
|  | 496 | * Note that at present this code cannot be used by CODECs with | 
|  | 497 | * volatile registers. | 
|  | 498 | */ | 
|  | 499 | int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec, | 
| Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 500 | int addr_bits, int data_bits, | 
|  | 501 | enum snd_soc_control_type control) | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 502 | { | 
|  | 503 | int i; | 
|  | 504 |  | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 505 | for (i = 0; i < ARRAY_SIZE(io_types); i++) | 
|  | 506 | if (io_types[i].addr_bits == addr_bits && | 
|  | 507 | io_types[i].data_bits == data_bits) | 
|  | 508 | break; | 
|  | 509 | if (i == ARRAY_SIZE(io_types)) { | 
|  | 510 | printk(KERN_ERR | 
|  | 511 | "No I/O functions for %d bit address %d bit data\n", | 
|  | 512 | addr_bits, data_bits); | 
|  | 513 | return -EINVAL; | 
|  | 514 | } | 
|  | 515 |  | 
| Mark Brown | c3acec2 | 2010-12-02 16:15:29 +0000 | [diff] [blame] | 516 | codec->write = io_types[i].write; | 
|  | 517 | codec->read = io_types[i].read; | 
| Dimitris Papastamos | 5fb609d | 2011-03-22 10:37:03 +0000 | [diff] [blame] | 518 | codec->bulk_write_raw = snd_soc_hw_bulk_write_raw; | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 519 |  | 
| Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 520 | switch (control) { | 
|  | 521 | case SND_SOC_CUSTOM: | 
|  | 522 | break; | 
|  | 523 |  | 
|  | 524 | case SND_SOC_I2C: | 
| Randy Dunlap | 17244c2 | 2009-08-10 16:04:39 -0700 | [diff] [blame] | 525 | #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) | 
| Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 526 | codec->hw_write = (hw_write_t)i2c_master_send; | 
|  | 527 | #endif | 
| Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 528 | if (io_types[i].i2c_read) | 
|  | 529 | codec->hw_read = io_types[i].i2c_read; | 
| Mark Brown | a6d1434 | 2010-08-12 10:59:15 +0100 | [diff] [blame] | 530 |  | 
|  | 531 | codec->control_data = container_of(codec->dev, | 
|  | 532 | struct i2c_client, | 
|  | 533 | dev); | 
| Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 534 | break; | 
|  | 535 |  | 
|  | 536 | case SND_SOC_SPI: | 
| Mark Brown | 27ded04 | 2009-07-10 23:28:16 +0100 | [diff] [blame] | 537 | if (io_types[i].spi_write) | 
|  | 538 | codec->hw_write = io_types[i].spi_write; | 
| Mark Brown | a6d1434 | 2010-08-12 10:59:15 +0100 | [diff] [blame] | 539 |  | 
|  | 540 | codec->control_data = container_of(codec->dev, | 
|  | 541 | struct spi_device, | 
|  | 542 | dev); | 
| Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 543 | break; | 
|  | 544 | } | 
|  | 545 |  | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 546 | return 0; | 
|  | 547 | } | 
|  | 548 | EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io); | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 549 |  | 
| Dimitris Papastamos | 1321e88 | 2011-01-11 11:29:49 +0000 | [diff] [blame] | 550 | static bool snd_soc_set_cache_val(void *base, unsigned int idx, | 
|  | 551 | unsigned int val, unsigned int word_size) | 
|  | 552 | { | 
|  | 553 | switch (word_size) { | 
|  | 554 | case 1: { | 
|  | 555 | u8 *cache = base; | 
|  | 556 | if (cache[idx] == val) | 
|  | 557 | return true; | 
|  | 558 | cache[idx] = val; | 
|  | 559 | break; | 
|  | 560 | } | 
|  | 561 | case 2: { | 
|  | 562 | u16 *cache = base; | 
|  | 563 | if (cache[idx] == val) | 
|  | 564 | return true; | 
|  | 565 | cache[idx] = val; | 
|  | 566 | break; | 
|  | 567 | } | 
|  | 568 | default: | 
|  | 569 | BUG(); | 
|  | 570 | } | 
|  | 571 | return false; | 
|  | 572 | } | 
|  | 573 |  | 
|  | 574 | static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx, | 
|  | 575 | unsigned int word_size) | 
|  | 576 | { | 
|  | 577 | switch (word_size) { | 
|  | 578 | case 1: { | 
|  | 579 | const u8 *cache = base; | 
|  | 580 | return cache[idx]; | 
|  | 581 | } | 
|  | 582 | case 2: { | 
|  | 583 | const u16 *cache = base; | 
|  | 584 | return cache[idx]; | 
|  | 585 | } | 
|  | 586 | default: | 
|  | 587 | BUG(); | 
|  | 588 | } | 
|  | 589 | /* unreachable */ | 
|  | 590 | return -1; | 
|  | 591 | } | 
|  | 592 |  | 
| Dimitris Papastamos | a7f387d | 2010-11-11 10:04:59 +0000 | [diff] [blame] | 593 | struct snd_soc_rbtree_node { | 
|  | 594 | struct rb_node node; | 
|  | 595 | unsigned int reg; | 
|  | 596 | unsigned int value; | 
|  | 597 | unsigned int defval; | 
|  | 598 | } __attribute__ ((packed)); | 
|  | 599 |  | 
|  | 600 | struct snd_soc_rbtree_ctx { | 
|  | 601 | struct rb_root root; | 
|  | 602 | }; | 
|  | 603 |  | 
|  | 604 | static struct snd_soc_rbtree_node *snd_soc_rbtree_lookup( | 
|  | 605 | struct rb_root *root, unsigned int reg) | 
|  | 606 | { | 
|  | 607 | struct rb_node *node; | 
|  | 608 | struct snd_soc_rbtree_node *rbnode; | 
|  | 609 |  | 
|  | 610 | node = root->rb_node; | 
|  | 611 | while (node) { | 
|  | 612 | rbnode = container_of(node, struct snd_soc_rbtree_node, node); | 
|  | 613 | if (rbnode->reg < reg) | 
|  | 614 | node = node->rb_left; | 
|  | 615 | else if (rbnode->reg > reg) | 
|  | 616 | node = node->rb_right; | 
|  | 617 | else | 
|  | 618 | return rbnode; | 
|  | 619 | } | 
|  | 620 |  | 
|  | 621 | return NULL; | 
|  | 622 | } | 
|  | 623 |  | 
| Dimitris Papastamos | a7f387d | 2010-11-11 10:04:59 +0000 | [diff] [blame] | 624 | static int snd_soc_rbtree_insert(struct rb_root *root, | 
|  | 625 | struct snd_soc_rbtree_node *rbnode) | 
|  | 626 | { | 
|  | 627 | struct rb_node **new, *parent; | 
|  | 628 | struct snd_soc_rbtree_node *rbnode_tmp; | 
|  | 629 |  | 
|  | 630 | parent = NULL; | 
|  | 631 | new = &root->rb_node; | 
|  | 632 | while (*new) { | 
|  | 633 | rbnode_tmp = container_of(*new, struct snd_soc_rbtree_node, | 
|  | 634 | node); | 
|  | 635 | parent = *new; | 
|  | 636 | if (rbnode_tmp->reg < rbnode->reg) | 
|  | 637 | new = &((*new)->rb_left); | 
|  | 638 | else if (rbnode_tmp->reg > rbnode->reg) | 
|  | 639 | new = &((*new)->rb_right); | 
|  | 640 | else | 
|  | 641 | return 0; | 
|  | 642 | } | 
|  | 643 |  | 
|  | 644 | /* insert the node into the rbtree */ | 
|  | 645 | rb_link_node(&rbnode->node, parent, new); | 
|  | 646 | rb_insert_color(&rbnode->node, root); | 
|  | 647 |  | 
|  | 648 | return 1; | 
|  | 649 | } | 
|  | 650 |  | 
|  | 651 | static int snd_soc_rbtree_cache_sync(struct snd_soc_codec *codec) | 
|  | 652 | { | 
|  | 653 | struct snd_soc_rbtree_ctx *rbtree_ctx; | 
|  | 654 | struct rb_node *node; | 
|  | 655 | struct snd_soc_rbtree_node *rbnode; | 
|  | 656 | unsigned int val; | 
| Dimitris Papastamos | 7a33d4c | 2010-11-29 10:24:54 +0000 | [diff] [blame] | 657 | int ret; | 
| Dimitris Papastamos | a7f387d | 2010-11-11 10:04:59 +0000 | [diff] [blame] | 658 |  | 
|  | 659 | rbtree_ctx = codec->reg_cache; | 
|  | 660 | for (node = rb_first(&rbtree_ctx->root); node; node = rb_next(node)) { | 
|  | 661 | rbnode = rb_entry(node, struct snd_soc_rbtree_node, node); | 
|  | 662 | if (rbnode->value == rbnode->defval) | 
|  | 663 | continue; | 
| Dimitris Papastamos | f20eda5 | 2011-03-28 11:39:15 +0100 | [diff] [blame] | 664 | WARN_ON(codec->writable_register && | 
|  | 665 | codec->writable_register(codec, rbnode->reg)); | 
| Dimitris Papastamos | 7a33d4c | 2010-11-29 10:24:54 +0000 | [diff] [blame] | 666 | ret = snd_soc_cache_read(codec, rbnode->reg, &val); | 
|  | 667 | if (ret) | 
|  | 668 | return ret; | 
| Dimitris Papastamos | 9978007 | 2011-01-19 14:53:37 +0000 | [diff] [blame] | 669 | codec->cache_bypass = 1; | 
| Dimitris Papastamos | 7a33d4c | 2010-11-29 10:24:54 +0000 | [diff] [blame] | 670 | ret = snd_soc_write(codec, rbnode->reg, val); | 
| Dimitris Papastamos | 9978007 | 2011-01-19 14:53:37 +0000 | [diff] [blame] | 671 | codec->cache_bypass = 0; | 
| Dimitris Papastamos | 7a33d4c | 2010-11-29 10:24:54 +0000 | [diff] [blame] | 672 | if (ret) | 
|  | 673 | return ret; | 
| Dimitris Papastamos | a7f387d | 2010-11-11 10:04:59 +0000 | [diff] [blame] | 674 | dev_dbg(codec->dev, "Synced register %#x, value = %#x\n", | 
|  | 675 | rbnode->reg, val); | 
|  | 676 | } | 
|  | 677 |  | 
|  | 678 | return 0; | 
|  | 679 | } | 
|  | 680 |  | 
|  | 681 | static int snd_soc_rbtree_cache_write(struct snd_soc_codec *codec, | 
|  | 682 | unsigned int reg, unsigned int value) | 
|  | 683 | { | 
|  | 684 | struct snd_soc_rbtree_ctx *rbtree_ctx; | 
|  | 685 | struct snd_soc_rbtree_node *rbnode; | 
|  | 686 |  | 
|  | 687 | rbtree_ctx = codec->reg_cache; | 
|  | 688 | rbnode = snd_soc_rbtree_lookup(&rbtree_ctx->root, reg); | 
|  | 689 | if (rbnode) { | 
|  | 690 | if (rbnode->value == value) | 
|  | 691 | return 0; | 
|  | 692 | rbnode->value = value; | 
|  | 693 | } else { | 
|  | 694 | /* bail out early, no need to create the rbnode yet */ | 
|  | 695 | if (!value) | 
|  | 696 | return 0; | 
|  | 697 | /* | 
|  | 698 | * for uninitialized registers whose value is changed | 
|  | 699 | * from the default zero, create an rbnode and insert | 
|  | 700 | * it into the tree. | 
|  | 701 | */ | 
|  | 702 | rbnode = kzalloc(sizeof *rbnode, GFP_KERNEL); | 
|  | 703 | if (!rbnode) | 
|  | 704 | return -ENOMEM; | 
|  | 705 | rbnode->reg = reg; | 
|  | 706 | rbnode->value = value; | 
|  | 707 | snd_soc_rbtree_insert(&rbtree_ctx->root, rbnode); | 
|  | 708 | } | 
|  | 709 |  | 
|  | 710 | return 0; | 
|  | 711 | } | 
|  | 712 |  | 
|  | 713 | static int snd_soc_rbtree_cache_read(struct snd_soc_codec *codec, | 
|  | 714 | unsigned int reg, unsigned int *value) | 
|  | 715 | { | 
|  | 716 | struct snd_soc_rbtree_ctx *rbtree_ctx; | 
|  | 717 | struct snd_soc_rbtree_node *rbnode; | 
|  | 718 |  | 
|  | 719 | rbtree_ctx = codec->reg_cache; | 
|  | 720 | rbnode = snd_soc_rbtree_lookup(&rbtree_ctx->root, reg); | 
|  | 721 | if (rbnode) { | 
|  | 722 | *value = rbnode->value; | 
|  | 723 | } else { | 
|  | 724 | /* uninitialized registers default to 0 */ | 
|  | 725 | *value = 0; | 
|  | 726 | } | 
|  | 727 |  | 
|  | 728 | return 0; | 
|  | 729 | } | 
|  | 730 |  | 
|  | 731 | static int snd_soc_rbtree_cache_exit(struct snd_soc_codec *codec) | 
|  | 732 | { | 
|  | 733 | struct rb_node *next; | 
|  | 734 | struct snd_soc_rbtree_ctx *rbtree_ctx; | 
|  | 735 | struct snd_soc_rbtree_node *rbtree_node; | 
|  | 736 |  | 
|  | 737 | /* if we've already been called then just return */ | 
|  | 738 | rbtree_ctx = codec->reg_cache; | 
|  | 739 | if (!rbtree_ctx) | 
|  | 740 | return 0; | 
|  | 741 |  | 
|  | 742 | /* free up the rbtree */ | 
|  | 743 | next = rb_first(&rbtree_ctx->root); | 
|  | 744 | while (next) { | 
|  | 745 | rbtree_node = rb_entry(next, struct snd_soc_rbtree_node, node); | 
|  | 746 | next = rb_next(&rbtree_node->node); | 
|  | 747 | rb_erase(&rbtree_node->node, &rbtree_ctx->root); | 
|  | 748 | kfree(rbtree_node); | 
|  | 749 | } | 
|  | 750 |  | 
|  | 751 | /* release the resources */ | 
|  | 752 | kfree(codec->reg_cache); | 
|  | 753 | codec->reg_cache = NULL; | 
|  | 754 |  | 
|  | 755 | return 0; | 
|  | 756 | } | 
|  | 757 |  | 
|  | 758 | static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec) | 
|  | 759 | { | 
| Dimitris Papastamos | 1321e88 | 2011-01-11 11:29:49 +0000 | [diff] [blame] | 760 | struct snd_soc_rbtree_node *rbtree_node; | 
| Dimitris Papastamos | a7f387d | 2010-11-11 10:04:59 +0000 | [diff] [blame] | 761 | struct snd_soc_rbtree_ctx *rbtree_ctx; | 
| Dimitris Papastamos | 1321e88 | 2011-01-11 11:29:49 +0000 | [diff] [blame] | 762 | unsigned int val; | 
|  | 763 | unsigned int word_size; | 
|  | 764 | int i; | 
|  | 765 | int ret; | 
| Dimitris Papastamos | a7f387d | 2010-11-11 10:04:59 +0000 | [diff] [blame] | 766 |  | 
|  | 767 | codec->reg_cache = kmalloc(sizeof *rbtree_ctx, GFP_KERNEL); | 
|  | 768 | if (!codec->reg_cache) | 
|  | 769 | return -ENOMEM; | 
|  | 770 |  | 
|  | 771 | rbtree_ctx = codec->reg_cache; | 
|  | 772 | rbtree_ctx->root = RB_ROOT; | 
|  | 773 |  | 
| Dimitris Papastamos | 3335ddc | 2010-12-02 16:11:05 +0000 | [diff] [blame] | 774 | if (!codec->reg_def_copy) | 
| Dimitris Papastamos | a7f387d | 2010-11-11 10:04:59 +0000 | [diff] [blame] | 775 | return 0; | 
|  | 776 |  | 
| Dimitris Papastamos | 1321e88 | 2011-01-11 11:29:49 +0000 | [diff] [blame] | 777 | /* | 
|  | 778 | * populate the rbtree with the initialized registers.  All other | 
|  | 779 | * registers will be inserted when they are first modified. | 
|  | 780 | */ | 
|  | 781 | word_size = codec->driver->reg_word_size; | 
|  | 782 | for (i = 0; i < codec->driver->reg_cache_size; ++i) { | 
|  | 783 | val = snd_soc_get_cache_val(codec->reg_def_copy, i, word_size); | 
|  | 784 | if (!val) | 
|  | 785 | continue; | 
|  | 786 | rbtree_node = kzalloc(sizeof *rbtree_node, GFP_KERNEL); | 
|  | 787 | if (!rbtree_node) { | 
|  | 788 | ret = -ENOMEM; | 
|  | 789 | snd_soc_cache_exit(codec); | 
|  | 790 | break; | 
|  | 791 | } | 
|  | 792 | rbtree_node->reg = i; | 
|  | 793 | rbtree_node->value = val; | 
|  | 794 | rbtree_node->defval = val; | 
|  | 795 | snd_soc_rbtree_insert(&rbtree_ctx->root, rbtree_node); | 
| Dimitris Papastamos | a7f387d | 2010-11-11 10:04:59 +0000 | [diff] [blame] | 796 | } | 
|  | 797 |  | 
|  | 798 | return 0; | 
|  | 799 | } | 
|  | 800 |  | 
| Mark Brown | 68d44ee | 2010-12-21 17:19:56 +0000 | [diff] [blame] | 801 | #ifdef CONFIG_SND_SOC_CACHE_LZO | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 802 | struct snd_soc_lzo_ctx { | 
|  | 803 | void *wmem; | 
|  | 804 | void *dst; | 
|  | 805 | const void *src; | 
|  | 806 | size_t src_len; | 
|  | 807 | size_t dst_len; | 
|  | 808 | size_t decompressed_size; | 
|  | 809 | unsigned long *sync_bmp; | 
|  | 810 | int sync_bmp_nbits; | 
|  | 811 | }; | 
|  | 812 |  | 
|  | 813 | #define LZO_BLOCK_NUM 8 | 
|  | 814 | static int snd_soc_lzo_block_count(void) | 
|  | 815 | { | 
|  | 816 | return LZO_BLOCK_NUM; | 
|  | 817 | } | 
|  | 818 |  | 
|  | 819 | static int snd_soc_lzo_prepare(struct snd_soc_lzo_ctx *lzo_ctx) | 
|  | 820 | { | 
|  | 821 | lzo_ctx->wmem = kmalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL); | 
|  | 822 | if (!lzo_ctx->wmem) | 
|  | 823 | return -ENOMEM; | 
|  | 824 | return 0; | 
|  | 825 | } | 
|  | 826 |  | 
|  | 827 | static int snd_soc_lzo_compress(struct snd_soc_lzo_ctx *lzo_ctx) | 
|  | 828 | { | 
|  | 829 | size_t compress_size; | 
|  | 830 | int ret; | 
|  | 831 |  | 
|  | 832 | ret = lzo1x_1_compress(lzo_ctx->src, lzo_ctx->src_len, | 
|  | 833 | lzo_ctx->dst, &compress_size, lzo_ctx->wmem); | 
|  | 834 | if (ret != LZO_E_OK || compress_size > lzo_ctx->dst_len) | 
|  | 835 | return -EINVAL; | 
|  | 836 | lzo_ctx->dst_len = compress_size; | 
|  | 837 | return 0; | 
|  | 838 | } | 
|  | 839 |  | 
|  | 840 | static int snd_soc_lzo_decompress(struct snd_soc_lzo_ctx *lzo_ctx) | 
|  | 841 | { | 
|  | 842 | size_t dst_len; | 
|  | 843 | int ret; | 
|  | 844 |  | 
|  | 845 | dst_len = lzo_ctx->dst_len; | 
|  | 846 | ret = lzo1x_decompress_safe(lzo_ctx->src, lzo_ctx->src_len, | 
|  | 847 | lzo_ctx->dst, &dst_len); | 
|  | 848 | if (ret != LZO_E_OK || dst_len != lzo_ctx->dst_len) | 
|  | 849 | return -EINVAL; | 
|  | 850 | return 0; | 
|  | 851 | } | 
|  | 852 |  | 
|  | 853 | static int snd_soc_lzo_compress_cache_block(struct snd_soc_codec *codec, | 
|  | 854 | struct snd_soc_lzo_ctx *lzo_ctx) | 
|  | 855 | { | 
|  | 856 | int ret; | 
|  | 857 |  | 
|  | 858 | lzo_ctx->dst_len = lzo1x_worst_compress(PAGE_SIZE); | 
|  | 859 | lzo_ctx->dst = kmalloc(lzo_ctx->dst_len, GFP_KERNEL); | 
|  | 860 | if (!lzo_ctx->dst) { | 
|  | 861 | lzo_ctx->dst_len = 0; | 
|  | 862 | return -ENOMEM; | 
|  | 863 | } | 
|  | 864 |  | 
|  | 865 | ret = snd_soc_lzo_compress(lzo_ctx); | 
|  | 866 | if (ret < 0) | 
|  | 867 | return ret; | 
|  | 868 | return 0; | 
|  | 869 | } | 
|  | 870 |  | 
|  | 871 | static int snd_soc_lzo_decompress_cache_block(struct snd_soc_codec *codec, | 
|  | 872 | struct snd_soc_lzo_ctx *lzo_ctx) | 
|  | 873 | { | 
|  | 874 | int ret; | 
|  | 875 |  | 
|  | 876 | lzo_ctx->dst_len = lzo_ctx->decompressed_size; | 
|  | 877 | lzo_ctx->dst = kmalloc(lzo_ctx->dst_len, GFP_KERNEL); | 
|  | 878 | if (!lzo_ctx->dst) { | 
|  | 879 | lzo_ctx->dst_len = 0; | 
|  | 880 | return -ENOMEM; | 
|  | 881 | } | 
|  | 882 |  | 
|  | 883 | ret = snd_soc_lzo_decompress(lzo_ctx); | 
|  | 884 | if (ret < 0) | 
|  | 885 | return ret; | 
|  | 886 | return 0; | 
|  | 887 | } | 
|  | 888 |  | 
|  | 889 | static inline int snd_soc_lzo_get_blkindex(struct snd_soc_codec *codec, | 
|  | 890 | unsigned int reg) | 
|  | 891 | { | 
| Mark Brown | 001ae4c | 2010-12-02 16:21:08 +0000 | [diff] [blame] | 892 | const struct snd_soc_codec_driver *codec_drv; | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 893 |  | 
|  | 894 | codec_drv = codec->driver; | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 895 | return (reg * codec_drv->reg_word_size) / | 
| Dimitris Papastamos | aea170a | 2011-01-12 10:38:58 +0000 | [diff] [blame] | 896 | DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count()); | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 897 | } | 
|  | 898 |  | 
|  | 899 | static inline int snd_soc_lzo_get_blkpos(struct snd_soc_codec *codec, | 
|  | 900 | unsigned int reg) | 
|  | 901 | { | 
| Mark Brown | 001ae4c | 2010-12-02 16:21:08 +0000 | [diff] [blame] | 902 | const struct snd_soc_codec_driver *codec_drv; | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 903 |  | 
|  | 904 | codec_drv = codec->driver; | 
| Dimitris Papastamos | aea170a | 2011-01-12 10:38:58 +0000 | [diff] [blame] | 905 | return reg % (DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count()) / | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 906 | codec_drv->reg_word_size); | 
|  | 907 | } | 
|  | 908 |  | 
|  | 909 | static inline int snd_soc_lzo_get_blksize(struct snd_soc_codec *codec) | 
|  | 910 | { | 
| Mark Brown | 001ae4c | 2010-12-02 16:21:08 +0000 | [diff] [blame] | 911 | const struct snd_soc_codec_driver *codec_drv; | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 912 |  | 
|  | 913 | codec_drv = codec->driver; | 
| Dimitris Papastamos | aea170a | 2011-01-12 10:38:58 +0000 | [diff] [blame] | 914 | return DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count()); | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 915 | } | 
|  | 916 |  | 
|  | 917 | static int snd_soc_lzo_cache_sync(struct snd_soc_codec *codec) | 
|  | 918 | { | 
|  | 919 | struct snd_soc_lzo_ctx **lzo_blocks; | 
|  | 920 | unsigned int val; | 
|  | 921 | int i; | 
| Dimitris Papastamos | 7a33d4c | 2010-11-29 10:24:54 +0000 | [diff] [blame] | 922 | int ret; | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 923 |  | 
|  | 924 | lzo_blocks = codec->reg_cache; | 
|  | 925 | for_each_set_bit(i, lzo_blocks[0]->sync_bmp, lzo_blocks[0]->sync_bmp_nbits) { | 
| Dimitris Papastamos | f20eda5 | 2011-03-28 11:39:15 +0100 | [diff] [blame] | 926 | WARN_ON(codec->writable_register && | 
|  | 927 | codec->writable_register(codec, i)); | 
| Dimitris Papastamos | 7a33d4c | 2010-11-29 10:24:54 +0000 | [diff] [blame] | 928 | ret = snd_soc_cache_read(codec, i, &val); | 
|  | 929 | if (ret) | 
|  | 930 | return ret; | 
| Dimitris Papastamos | 9978007 | 2011-01-19 14:53:37 +0000 | [diff] [blame] | 931 | codec->cache_bypass = 1; | 
| Dimitris Papastamos | 7a33d4c | 2010-11-29 10:24:54 +0000 | [diff] [blame] | 932 | ret = snd_soc_write(codec, i, val); | 
| Dimitris Papastamos | 9978007 | 2011-01-19 14:53:37 +0000 | [diff] [blame] | 933 | codec->cache_bypass = 0; | 
| Dimitris Papastamos | 7a33d4c | 2010-11-29 10:24:54 +0000 | [diff] [blame] | 934 | if (ret) | 
|  | 935 | return ret; | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 936 | dev_dbg(codec->dev, "Synced register %#x, value = %#x\n", | 
|  | 937 | i, val); | 
|  | 938 | } | 
|  | 939 |  | 
|  | 940 | return 0; | 
|  | 941 | } | 
|  | 942 |  | 
|  | 943 | static int snd_soc_lzo_cache_write(struct snd_soc_codec *codec, | 
|  | 944 | unsigned int reg, unsigned int value) | 
|  | 945 | { | 
|  | 946 | struct snd_soc_lzo_ctx *lzo_block, **lzo_blocks; | 
|  | 947 | int ret, blkindex, blkpos; | 
|  | 948 | size_t blksize, tmp_dst_len; | 
|  | 949 | void *tmp_dst; | 
|  | 950 |  | 
|  | 951 | /* index of the compressed lzo block */ | 
|  | 952 | blkindex = snd_soc_lzo_get_blkindex(codec, reg); | 
|  | 953 | /* register index within the decompressed block */ | 
|  | 954 | blkpos = snd_soc_lzo_get_blkpos(codec, reg); | 
|  | 955 | /* size of the compressed block */ | 
|  | 956 | blksize = snd_soc_lzo_get_blksize(codec); | 
|  | 957 | lzo_blocks = codec->reg_cache; | 
|  | 958 | lzo_block = lzo_blocks[blkindex]; | 
|  | 959 |  | 
|  | 960 | /* save the pointer and length of the compressed block */ | 
|  | 961 | tmp_dst = lzo_block->dst; | 
|  | 962 | tmp_dst_len = lzo_block->dst_len; | 
|  | 963 |  | 
|  | 964 | /* prepare the source to be the compressed block */ | 
|  | 965 | lzo_block->src = lzo_block->dst; | 
|  | 966 | lzo_block->src_len = lzo_block->dst_len; | 
|  | 967 |  | 
|  | 968 | /* decompress the block */ | 
|  | 969 | ret = snd_soc_lzo_decompress_cache_block(codec, lzo_block); | 
|  | 970 | if (ret < 0) { | 
|  | 971 | kfree(lzo_block->dst); | 
|  | 972 | goto out; | 
|  | 973 | } | 
|  | 974 |  | 
|  | 975 | /* write the new value to the cache */ | 
| Dimitris Papastamos | 1321e88 | 2011-01-11 11:29:49 +0000 | [diff] [blame] | 976 | if (snd_soc_set_cache_val(lzo_block->dst, blkpos, value, | 
|  | 977 | codec->driver->reg_word_size)) { | 
|  | 978 | kfree(lzo_block->dst); | 
|  | 979 | goto out; | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 980 | } | 
|  | 981 |  | 
|  | 982 | /* prepare the source to be the decompressed block */ | 
|  | 983 | lzo_block->src = lzo_block->dst; | 
|  | 984 | lzo_block->src_len = lzo_block->dst_len; | 
|  | 985 |  | 
|  | 986 | /* compress the block */ | 
|  | 987 | ret = snd_soc_lzo_compress_cache_block(codec, lzo_block); | 
|  | 988 | if (ret < 0) { | 
|  | 989 | kfree(lzo_block->dst); | 
|  | 990 | kfree(lzo_block->src); | 
|  | 991 | goto out; | 
|  | 992 | } | 
|  | 993 |  | 
|  | 994 | /* set the bit so we know we have to sync this register */ | 
|  | 995 | set_bit(reg, lzo_block->sync_bmp); | 
|  | 996 | kfree(tmp_dst); | 
|  | 997 | kfree(lzo_block->src); | 
|  | 998 | return 0; | 
|  | 999 | out: | 
|  | 1000 | lzo_block->dst = tmp_dst; | 
|  | 1001 | lzo_block->dst_len = tmp_dst_len; | 
|  | 1002 | return ret; | 
|  | 1003 | } | 
|  | 1004 |  | 
|  | 1005 | static int snd_soc_lzo_cache_read(struct snd_soc_codec *codec, | 
|  | 1006 | unsigned int reg, unsigned int *value) | 
|  | 1007 | { | 
|  | 1008 | struct snd_soc_lzo_ctx *lzo_block, **lzo_blocks; | 
|  | 1009 | int ret, blkindex, blkpos; | 
|  | 1010 | size_t blksize, tmp_dst_len; | 
|  | 1011 | void *tmp_dst; | 
|  | 1012 |  | 
|  | 1013 | *value = 0; | 
|  | 1014 | /* index of the compressed lzo block */ | 
|  | 1015 | blkindex = snd_soc_lzo_get_blkindex(codec, reg); | 
|  | 1016 | /* register index within the decompressed block */ | 
|  | 1017 | blkpos = snd_soc_lzo_get_blkpos(codec, reg); | 
|  | 1018 | /* size of the compressed block */ | 
|  | 1019 | blksize = snd_soc_lzo_get_blksize(codec); | 
|  | 1020 | lzo_blocks = codec->reg_cache; | 
|  | 1021 | lzo_block = lzo_blocks[blkindex]; | 
|  | 1022 |  | 
|  | 1023 | /* save the pointer and length of the compressed block */ | 
|  | 1024 | tmp_dst = lzo_block->dst; | 
|  | 1025 | tmp_dst_len = lzo_block->dst_len; | 
|  | 1026 |  | 
|  | 1027 | /* prepare the source to be the compressed block */ | 
|  | 1028 | lzo_block->src = lzo_block->dst; | 
|  | 1029 | lzo_block->src_len = lzo_block->dst_len; | 
|  | 1030 |  | 
|  | 1031 | /* decompress the block */ | 
|  | 1032 | ret = snd_soc_lzo_decompress_cache_block(codec, lzo_block); | 
| Dimitris Papastamos | 1321e88 | 2011-01-11 11:29:49 +0000 | [diff] [blame] | 1033 | if (ret >= 0) | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1034 | /* fetch the value from the cache */ | 
| Dimitris Papastamos | 1321e88 | 2011-01-11 11:29:49 +0000 | [diff] [blame] | 1035 | *value = snd_soc_get_cache_val(lzo_block->dst, blkpos, | 
|  | 1036 | codec->driver->reg_word_size); | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1037 |  | 
|  | 1038 | kfree(lzo_block->dst); | 
|  | 1039 | /* restore the pointer and length of the compressed block */ | 
|  | 1040 | lzo_block->dst = tmp_dst; | 
|  | 1041 | lzo_block->dst_len = tmp_dst_len; | 
|  | 1042 | return 0; | 
|  | 1043 | } | 
|  | 1044 |  | 
|  | 1045 | static int snd_soc_lzo_cache_exit(struct snd_soc_codec *codec) | 
|  | 1046 | { | 
|  | 1047 | struct snd_soc_lzo_ctx **lzo_blocks; | 
|  | 1048 | int i, blkcount; | 
|  | 1049 |  | 
|  | 1050 | lzo_blocks = codec->reg_cache; | 
|  | 1051 | if (!lzo_blocks) | 
|  | 1052 | return 0; | 
|  | 1053 |  | 
|  | 1054 | blkcount = snd_soc_lzo_block_count(); | 
|  | 1055 | /* | 
|  | 1056 | * the pointer to the bitmap used for syncing the cache | 
|  | 1057 | * is shared amongst all lzo_blocks.  Ensure it is freed | 
|  | 1058 | * only once. | 
|  | 1059 | */ | 
|  | 1060 | if (lzo_blocks[0]) | 
|  | 1061 | kfree(lzo_blocks[0]->sync_bmp); | 
|  | 1062 | for (i = 0; i < blkcount; ++i) { | 
|  | 1063 | if (lzo_blocks[i]) { | 
|  | 1064 | kfree(lzo_blocks[i]->wmem); | 
|  | 1065 | kfree(lzo_blocks[i]->dst); | 
|  | 1066 | } | 
|  | 1067 | /* each lzo_block is a pointer returned by kmalloc or NULL */ | 
|  | 1068 | kfree(lzo_blocks[i]); | 
|  | 1069 | } | 
|  | 1070 | kfree(lzo_blocks); | 
|  | 1071 | codec->reg_cache = NULL; | 
|  | 1072 | return 0; | 
|  | 1073 | } | 
|  | 1074 |  | 
|  | 1075 | static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec) | 
|  | 1076 | { | 
|  | 1077 | struct snd_soc_lzo_ctx **lzo_blocks; | 
| Dimitris Papastamos | aea170a | 2011-01-12 10:38:58 +0000 | [diff] [blame] | 1078 | size_t bmp_size; | 
| Mark Brown | 001ae4c | 2010-12-02 16:21:08 +0000 | [diff] [blame] | 1079 | const struct snd_soc_codec_driver *codec_drv; | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1080 | int ret, tofree, i, blksize, blkcount; | 
|  | 1081 | const char *p, *end; | 
|  | 1082 | unsigned long *sync_bmp; | 
|  | 1083 |  | 
|  | 1084 | ret = 0; | 
|  | 1085 | codec_drv = codec->driver; | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1086 |  | 
|  | 1087 | /* | 
|  | 1088 | * If we have not been given a default register cache | 
|  | 1089 | * then allocate a dummy zero-ed out region, compress it | 
|  | 1090 | * and remember to free it afterwards. | 
|  | 1091 | */ | 
|  | 1092 | tofree = 0; | 
| Dimitris Papastamos | 3335ddc | 2010-12-02 16:11:05 +0000 | [diff] [blame] | 1093 | if (!codec->reg_def_copy) | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1094 | tofree = 1; | 
|  | 1095 |  | 
| Dimitris Papastamos | 3335ddc | 2010-12-02 16:11:05 +0000 | [diff] [blame] | 1096 | if (!codec->reg_def_copy) { | 
| Dimitris Papastamos | aea170a | 2011-01-12 10:38:58 +0000 | [diff] [blame] | 1097 | codec->reg_def_copy = kzalloc(codec->reg_size, GFP_KERNEL); | 
| Dimitris Papastamos | 3335ddc | 2010-12-02 16:11:05 +0000 | [diff] [blame] | 1098 | if (!codec->reg_def_copy) | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1099 | return -ENOMEM; | 
|  | 1100 | } | 
|  | 1101 |  | 
|  | 1102 | blkcount = snd_soc_lzo_block_count(); | 
|  | 1103 | codec->reg_cache = kzalloc(blkcount * sizeof *lzo_blocks, | 
|  | 1104 | GFP_KERNEL); | 
|  | 1105 | if (!codec->reg_cache) { | 
|  | 1106 | ret = -ENOMEM; | 
|  | 1107 | goto err_tofree; | 
|  | 1108 | } | 
|  | 1109 | lzo_blocks = codec->reg_cache; | 
|  | 1110 |  | 
|  | 1111 | /* | 
|  | 1112 | * allocate a bitmap to be used when syncing the cache with | 
|  | 1113 | * the hardware.  Each time a register is modified, the corresponding | 
|  | 1114 | * bit is set in the bitmap, so we know that we have to sync | 
|  | 1115 | * that register. | 
|  | 1116 | */ | 
|  | 1117 | bmp_size = codec_drv->reg_cache_size; | 
| Dimitris Papastamos | 465d7fc | 2010-12-14 15:15:36 +0000 | [diff] [blame] | 1118 | sync_bmp = kmalloc(BITS_TO_LONGS(bmp_size) * sizeof(long), | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1119 | GFP_KERNEL); | 
|  | 1120 | if (!sync_bmp) { | 
|  | 1121 | ret = -ENOMEM; | 
|  | 1122 | goto err; | 
|  | 1123 | } | 
| Dimitris Papastamos | 09c74a9 | 2010-11-29 11:43:33 +0000 | [diff] [blame] | 1124 | bitmap_zero(sync_bmp, bmp_size); | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1125 |  | 
|  | 1126 | /* allocate the lzo blocks and initialize them */ | 
|  | 1127 | for (i = 0; i < blkcount; ++i) { | 
|  | 1128 | lzo_blocks[i] = kzalloc(sizeof **lzo_blocks, | 
|  | 1129 | GFP_KERNEL); | 
|  | 1130 | if (!lzo_blocks[i]) { | 
|  | 1131 | kfree(sync_bmp); | 
|  | 1132 | ret = -ENOMEM; | 
|  | 1133 | goto err; | 
|  | 1134 | } | 
|  | 1135 | lzo_blocks[i]->sync_bmp = sync_bmp; | 
| Dimitris Papastamos | 04f8fd1 | 2011-01-11 11:24:02 +0000 | [diff] [blame] | 1136 | lzo_blocks[i]->sync_bmp_nbits = bmp_size; | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1137 | /* alloc the working space for the compressed block */ | 
|  | 1138 | ret = snd_soc_lzo_prepare(lzo_blocks[i]); | 
|  | 1139 | if (ret < 0) | 
|  | 1140 | goto err; | 
|  | 1141 | } | 
|  | 1142 |  | 
|  | 1143 | blksize = snd_soc_lzo_get_blksize(codec); | 
| Dimitris Papastamos | 3335ddc | 2010-12-02 16:11:05 +0000 | [diff] [blame] | 1144 | p = codec->reg_def_copy; | 
| Dimitris Papastamos | aea170a | 2011-01-12 10:38:58 +0000 | [diff] [blame] | 1145 | end = codec->reg_def_copy + codec->reg_size; | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1146 | /* compress the register map and fill the lzo blocks */ | 
|  | 1147 | for (i = 0; i < blkcount; ++i, p += blksize) { | 
|  | 1148 | lzo_blocks[i]->src = p; | 
|  | 1149 | if (p + blksize > end) | 
|  | 1150 | lzo_blocks[i]->src_len = end - p; | 
|  | 1151 | else | 
|  | 1152 | lzo_blocks[i]->src_len = blksize; | 
|  | 1153 | ret = snd_soc_lzo_compress_cache_block(codec, | 
|  | 1154 | lzo_blocks[i]); | 
|  | 1155 | if (ret < 0) | 
|  | 1156 | goto err; | 
|  | 1157 | lzo_blocks[i]->decompressed_size = | 
|  | 1158 | lzo_blocks[i]->src_len; | 
|  | 1159 | } | 
|  | 1160 |  | 
| Dimitris Papastamos | 3335ddc | 2010-12-02 16:11:05 +0000 | [diff] [blame] | 1161 | if (tofree) { | 
|  | 1162 | kfree(codec->reg_def_copy); | 
|  | 1163 | codec->reg_def_copy = NULL; | 
|  | 1164 | } | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1165 | return 0; | 
|  | 1166 | err: | 
|  | 1167 | snd_soc_cache_exit(codec); | 
|  | 1168 | err_tofree: | 
| Dimitris Papastamos | 3335ddc | 2010-12-02 16:11:05 +0000 | [diff] [blame] | 1169 | if (tofree) { | 
|  | 1170 | kfree(codec->reg_def_copy); | 
|  | 1171 | codec->reg_def_copy = NULL; | 
|  | 1172 | } | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1173 | return ret; | 
|  | 1174 | } | 
| Mark Brown | 68d44ee | 2010-12-21 17:19:56 +0000 | [diff] [blame] | 1175 | #endif | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1176 |  | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1177 | static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec) | 
|  | 1178 | { | 
|  | 1179 | int i; | 
| Dimitris Papastamos | 7a33d4c | 2010-11-29 10:24:54 +0000 | [diff] [blame] | 1180 | int ret; | 
| Mark Brown | 001ae4c | 2010-12-02 16:21:08 +0000 | [diff] [blame] | 1181 | const struct snd_soc_codec_driver *codec_drv; | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1182 | unsigned int val; | 
|  | 1183 |  | 
|  | 1184 | codec_drv = codec->driver; | 
|  | 1185 | for (i = 0; i < codec_drv->reg_cache_size; ++i) { | 
| Dimitris Papastamos | f20eda5 | 2011-03-28 11:39:15 +0100 | [diff] [blame] | 1186 | WARN_ON(codec->writable_register && | 
|  | 1187 | codec->writable_register(codec, i)); | 
| Dimitris Papastamos | 7a33d4c | 2010-11-29 10:24:54 +0000 | [diff] [blame] | 1188 | ret = snd_soc_cache_read(codec, i, &val); | 
|  | 1189 | if (ret) | 
|  | 1190 | return ret; | 
| Dimitris Papastamos | d779fce | 2011-01-12 10:22:28 +0000 | [diff] [blame] | 1191 | if (codec->reg_def_copy) | 
|  | 1192 | if (snd_soc_get_cache_val(codec->reg_def_copy, | 
| Dimitris Papastamos | 1321e88 | 2011-01-11 11:29:49 +0000 | [diff] [blame] | 1193 | i, codec_drv->reg_word_size) == val) | 
|  | 1194 | continue; | 
| Dimitris Papastamos | 7a33d4c | 2010-11-29 10:24:54 +0000 | [diff] [blame] | 1195 | ret = snd_soc_write(codec, i, val); | 
|  | 1196 | if (ret) | 
|  | 1197 | return ret; | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1198 | dev_dbg(codec->dev, "Synced register %#x, value = %#x\n", | 
|  | 1199 | i, val); | 
|  | 1200 | } | 
|  | 1201 | return 0; | 
|  | 1202 | } | 
|  | 1203 |  | 
|  | 1204 | static int snd_soc_flat_cache_write(struct snd_soc_codec *codec, | 
|  | 1205 | unsigned int reg, unsigned int value) | 
|  | 1206 | { | 
| Dimitris Papastamos | 1321e88 | 2011-01-11 11:29:49 +0000 | [diff] [blame] | 1207 | snd_soc_set_cache_val(codec->reg_cache, reg, value, | 
|  | 1208 | codec->driver->reg_word_size); | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1209 | return 0; | 
|  | 1210 | } | 
|  | 1211 |  | 
|  | 1212 | static int snd_soc_flat_cache_read(struct snd_soc_codec *codec, | 
|  | 1213 | unsigned int reg, unsigned int *value) | 
|  | 1214 | { | 
| Dimitris Papastamos | 1321e88 | 2011-01-11 11:29:49 +0000 | [diff] [blame] | 1215 | *value = snd_soc_get_cache_val(codec->reg_cache, reg, | 
|  | 1216 | codec->driver->reg_word_size); | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1217 | return 0; | 
|  | 1218 | } | 
|  | 1219 |  | 
|  | 1220 | static int snd_soc_flat_cache_exit(struct snd_soc_codec *codec) | 
|  | 1221 | { | 
|  | 1222 | if (!codec->reg_cache) | 
|  | 1223 | return 0; | 
|  | 1224 | kfree(codec->reg_cache); | 
|  | 1225 | codec->reg_cache = NULL; | 
|  | 1226 | return 0; | 
|  | 1227 | } | 
|  | 1228 |  | 
|  | 1229 | static int snd_soc_flat_cache_init(struct snd_soc_codec *codec) | 
|  | 1230 | { | 
| Mark Brown | 001ae4c | 2010-12-02 16:21:08 +0000 | [diff] [blame] | 1231 | const struct snd_soc_codec_driver *codec_drv; | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1232 |  | 
|  | 1233 | codec_drv = codec->driver; | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1234 |  | 
| Dimitris Papastamos | d779fce | 2011-01-12 10:22:28 +0000 | [diff] [blame] | 1235 | if (codec->reg_def_copy) | 
|  | 1236 | codec->reg_cache = kmemdup(codec->reg_def_copy, | 
| Dimitris Papastamos | aea170a | 2011-01-12 10:38:58 +0000 | [diff] [blame] | 1237 | codec->reg_size, GFP_KERNEL); | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1238 | else | 
| Dimitris Papastamos | aea170a | 2011-01-12 10:38:58 +0000 | [diff] [blame] | 1239 | codec->reg_cache = kzalloc(codec->reg_size, GFP_KERNEL); | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1240 | if (!codec->reg_cache) | 
|  | 1241 | return -ENOMEM; | 
|  | 1242 |  | 
|  | 1243 | return 0; | 
|  | 1244 | } | 
|  | 1245 |  | 
|  | 1246 | /* an array of all supported compression types */ | 
|  | 1247 | static const struct snd_soc_cache_ops cache_types[] = { | 
| Mark Brown | be4fcdd | 2010-12-21 17:09:48 +0000 | [diff] [blame] | 1248 | /* Flat *must* be the first entry for fallback */ | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1249 | { | 
| Dimitris Papastamos | df0701b | 2010-11-29 10:54:28 +0000 | [diff] [blame] | 1250 | .id = SND_SOC_FLAT_COMPRESSION, | 
| Dimitris Papastamos | 0d735ea | 2010-12-06 09:51:57 +0000 | [diff] [blame] | 1251 | .name = "flat", | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1252 | .init = snd_soc_flat_cache_init, | 
|  | 1253 | .exit = snd_soc_flat_cache_exit, | 
|  | 1254 | .read = snd_soc_flat_cache_read, | 
|  | 1255 | .write = snd_soc_flat_cache_write, | 
|  | 1256 | .sync = snd_soc_flat_cache_sync | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1257 | }, | 
| Mark Brown | 68d44ee | 2010-12-21 17:19:56 +0000 | [diff] [blame] | 1258 | #ifdef CONFIG_SND_SOC_CACHE_LZO | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1259 | { | 
|  | 1260 | .id = SND_SOC_LZO_COMPRESSION, | 
| Dimitris Papastamos | 0d735ea | 2010-12-06 09:51:57 +0000 | [diff] [blame] | 1261 | .name = "LZO", | 
| Dimitris Papastamos | cc28fb8 | 2010-11-11 10:04:58 +0000 | [diff] [blame] | 1262 | .init = snd_soc_lzo_cache_init, | 
|  | 1263 | .exit = snd_soc_lzo_cache_exit, | 
|  | 1264 | .read = snd_soc_lzo_cache_read, | 
|  | 1265 | .write = snd_soc_lzo_cache_write, | 
|  | 1266 | .sync = snd_soc_lzo_cache_sync | 
| Dimitris Papastamos | a7f387d | 2010-11-11 10:04:59 +0000 | [diff] [blame] | 1267 | }, | 
| Mark Brown | 68d44ee | 2010-12-21 17:19:56 +0000 | [diff] [blame] | 1268 | #endif | 
| Dimitris Papastamos | a7f387d | 2010-11-11 10:04:59 +0000 | [diff] [blame] | 1269 | { | 
|  | 1270 | .id = SND_SOC_RBTREE_COMPRESSION, | 
| Dimitris Papastamos | 0d735ea | 2010-12-06 09:51:57 +0000 | [diff] [blame] | 1271 | .name = "rbtree", | 
| Dimitris Papastamos | a7f387d | 2010-11-11 10:04:59 +0000 | [diff] [blame] | 1272 | .init = snd_soc_rbtree_cache_init, | 
|  | 1273 | .exit = snd_soc_rbtree_cache_exit, | 
|  | 1274 | .read = snd_soc_rbtree_cache_read, | 
|  | 1275 | .write = snd_soc_rbtree_cache_write, | 
|  | 1276 | .sync = snd_soc_rbtree_cache_sync | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1277 | } | 
|  | 1278 | }; | 
|  | 1279 |  | 
|  | 1280 | int snd_soc_cache_init(struct snd_soc_codec *codec) | 
|  | 1281 | { | 
|  | 1282 | int i; | 
|  | 1283 |  | 
|  | 1284 | for (i = 0; i < ARRAY_SIZE(cache_types); ++i) | 
| Dimitris Papastamos | 23bbce3 | 2010-12-02 14:53:01 +0000 | [diff] [blame] | 1285 | if (cache_types[i].id == codec->compress_type) | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1286 | break; | 
| Mark Brown | be4fcdd | 2010-12-21 17:09:48 +0000 | [diff] [blame] | 1287 |  | 
|  | 1288 | /* Fall back to flat compression */ | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1289 | if (i == ARRAY_SIZE(cache_types)) { | 
| Mark Brown | be4fcdd | 2010-12-21 17:09:48 +0000 | [diff] [blame] | 1290 | dev_warn(codec->dev, "Could not match compress type: %d\n", | 
|  | 1291 | codec->compress_type); | 
|  | 1292 | i = 0; | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1293 | } | 
|  | 1294 |  | 
|  | 1295 | mutex_init(&codec->cache_rw_mutex); | 
|  | 1296 | codec->cache_ops = &cache_types[i]; | 
|  | 1297 |  | 
| Dimitris Papastamos | 0d735ea | 2010-12-06 09:51:57 +0000 | [diff] [blame] | 1298 | if (codec->cache_ops->init) { | 
|  | 1299 | if (codec->cache_ops->name) | 
|  | 1300 | dev_dbg(codec->dev, "Initializing %s cache for %s codec\n", | 
|  | 1301 | codec->cache_ops->name, codec->name); | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1302 | return codec->cache_ops->init(codec); | 
| Dimitris Papastamos | 0d735ea | 2010-12-06 09:51:57 +0000 | [diff] [blame] | 1303 | } | 
| Dimitris Papastamos | acd6145 | 2011-03-22 10:48:49 +0000 | [diff] [blame] | 1304 | return -ENOSYS; | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1305 | } | 
|  | 1306 |  | 
|  | 1307 | /* | 
|  | 1308 | * NOTE: keep in mind that this function might be called | 
|  | 1309 | * multiple times. | 
|  | 1310 | */ | 
|  | 1311 | int snd_soc_cache_exit(struct snd_soc_codec *codec) | 
|  | 1312 | { | 
| Dimitris Papastamos | 0d735ea | 2010-12-06 09:51:57 +0000 | [diff] [blame] | 1313 | if (codec->cache_ops && codec->cache_ops->exit) { | 
|  | 1314 | if (codec->cache_ops->name) | 
|  | 1315 | dev_dbg(codec->dev, "Destroying %s cache for %s codec\n", | 
|  | 1316 | codec->cache_ops->name, codec->name); | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1317 | return codec->cache_ops->exit(codec); | 
| Dimitris Papastamos | 0d735ea | 2010-12-06 09:51:57 +0000 | [diff] [blame] | 1318 | } | 
| Dimitris Papastamos | acd6145 | 2011-03-22 10:48:49 +0000 | [diff] [blame] | 1319 | return -ENOSYS; | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1320 | } | 
|  | 1321 |  | 
|  | 1322 | /** | 
|  | 1323 | * snd_soc_cache_read: Fetch the value of a given register from the cache. | 
|  | 1324 | * | 
|  | 1325 | * @codec: CODEC to configure. | 
|  | 1326 | * @reg: The register index. | 
|  | 1327 | * @value: The value to be returned. | 
|  | 1328 | */ | 
|  | 1329 | int snd_soc_cache_read(struct snd_soc_codec *codec, | 
|  | 1330 | unsigned int reg, unsigned int *value) | 
|  | 1331 | { | 
|  | 1332 | int ret; | 
|  | 1333 |  | 
|  | 1334 | mutex_lock(&codec->cache_rw_mutex); | 
|  | 1335 |  | 
|  | 1336 | if (value && codec->cache_ops && codec->cache_ops->read) { | 
|  | 1337 | ret = codec->cache_ops->read(codec, reg, value); | 
|  | 1338 | mutex_unlock(&codec->cache_rw_mutex); | 
|  | 1339 | return ret; | 
|  | 1340 | } | 
|  | 1341 |  | 
|  | 1342 | mutex_unlock(&codec->cache_rw_mutex); | 
| Dimitris Papastamos | acd6145 | 2011-03-22 10:48:49 +0000 | [diff] [blame] | 1343 | return -ENOSYS; | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1344 | } | 
|  | 1345 | EXPORT_SYMBOL_GPL(snd_soc_cache_read); | 
|  | 1346 |  | 
|  | 1347 | /** | 
|  | 1348 | * snd_soc_cache_write: Set the value of a given register in the cache. | 
|  | 1349 | * | 
|  | 1350 | * @codec: CODEC to configure. | 
|  | 1351 | * @reg: The register index. | 
|  | 1352 | * @value: The new register value. | 
|  | 1353 | */ | 
|  | 1354 | int snd_soc_cache_write(struct snd_soc_codec *codec, | 
|  | 1355 | unsigned int reg, unsigned int value) | 
|  | 1356 | { | 
|  | 1357 | int ret; | 
|  | 1358 |  | 
|  | 1359 | mutex_lock(&codec->cache_rw_mutex); | 
|  | 1360 |  | 
|  | 1361 | if (codec->cache_ops && codec->cache_ops->write) { | 
|  | 1362 | ret = codec->cache_ops->write(codec, reg, value); | 
|  | 1363 | mutex_unlock(&codec->cache_rw_mutex); | 
|  | 1364 | return ret; | 
|  | 1365 | } | 
|  | 1366 |  | 
|  | 1367 | mutex_unlock(&codec->cache_rw_mutex); | 
| Dimitris Papastamos | acd6145 | 2011-03-22 10:48:49 +0000 | [diff] [blame] | 1368 | return -ENOSYS; | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1369 | } | 
|  | 1370 | EXPORT_SYMBOL_GPL(snd_soc_cache_write); | 
|  | 1371 |  | 
|  | 1372 | /** | 
|  | 1373 | * snd_soc_cache_sync: Sync the register cache with the hardware. | 
|  | 1374 | * | 
|  | 1375 | * @codec: CODEC to configure. | 
|  | 1376 | * | 
|  | 1377 | * Any registers that should not be synced should be marked as | 
|  | 1378 | * volatile.  In general drivers can choose not to use the provided | 
|  | 1379 | * syncing functionality if they so require. | 
|  | 1380 | */ | 
|  | 1381 | int snd_soc_cache_sync(struct snd_soc_codec *codec) | 
|  | 1382 | { | 
|  | 1383 | int ret; | 
| Dimitris Papastamos | c358e64 | 2011-01-21 15:29:02 +0000 | [diff] [blame] | 1384 | const char *name; | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1385 |  | 
|  | 1386 | if (!codec->cache_sync) { | 
|  | 1387 | return 0; | 
|  | 1388 | } | 
|  | 1389 |  | 
| Dan Carpenter | 46fdaa3 | 2011-02-07 22:01:41 +0300 | [diff] [blame] | 1390 | if (!codec->cache_ops || !codec->cache_ops->sync) | 
| Dimitris Papastamos | acd6145 | 2011-03-22 10:48:49 +0000 | [diff] [blame] | 1391 | return -ENOSYS; | 
| Dan Carpenter | 46fdaa3 | 2011-02-07 22:01:41 +0300 | [diff] [blame] | 1392 |  | 
| Dimitris Papastamos | c358e64 | 2011-01-21 15:29:02 +0000 | [diff] [blame] | 1393 | if (codec->cache_ops->name) | 
|  | 1394 | name = codec->cache_ops->name; | 
|  | 1395 | else | 
|  | 1396 | name = "unknown"; | 
|  | 1397 |  | 
| Dan Carpenter | 46fdaa3 | 2011-02-07 22:01:41 +0300 | [diff] [blame] | 1398 | if (codec->cache_ops->name) | 
|  | 1399 | dev_dbg(codec->dev, "Syncing %s cache for %s codec\n", | 
|  | 1400 | codec->cache_ops->name, codec->name); | 
|  | 1401 | trace_snd_soc_cache_sync(codec, name, "start"); | 
|  | 1402 | ret = codec->cache_ops->sync(codec); | 
|  | 1403 | if (!ret) | 
|  | 1404 | codec->cache_sync = 0; | 
|  | 1405 | trace_snd_soc_cache_sync(codec, name, "end"); | 
|  | 1406 | return ret; | 
| Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame] | 1407 | } | 
|  | 1408 | EXPORT_SYMBOL_GPL(snd_soc_cache_sync); | 
| Dimitris Papastamos | 066d16c | 2011-01-13 12:20:36 +0000 | [diff] [blame] | 1409 |  | 
|  | 1410 | static int snd_soc_get_reg_access_index(struct snd_soc_codec *codec, | 
|  | 1411 | unsigned int reg) | 
|  | 1412 | { | 
|  | 1413 | const struct snd_soc_codec_driver *codec_drv; | 
|  | 1414 | unsigned int min, max, index; | 
|  | 1415 |  | 
|  | 1416 | codec_drv = codec->driver; | 
|  | 1417 | min = 0; | 
|  | 1418 | max = codec_drv->reg_access_size - 1; | 
|  | 1419 | do { | 
|  | 1420 | index = (min + max) / 2; | 
|  | 1421 | if (codec_drv->reg_access_default[index].reg == reg) | 
|  | 1422 | return index; | 
|  | 1423 | if (codec_drv->reg_access_default[index].reg < reg) | 
|  | 1424 | min = index + 1; | 
|  | 1425 | else | 
|  | 1426 | max = index; | 
|  | 1427 | } while (min <= max); | 
|  | 1428 | return -1; | 
|  | 1429 | } | 
|  | 1430 |  | 
|  | 1431 | int snd_soc_default_volatile_register(struct snd_soc_codec *codec, | 
|  | 1432 | unsigned int reg) | 
|  | 1433 | { | 
|  | 1434 | int index; | 
|  | 1435 |  | 
|  | 1436 | if (reg >= codec->driver->reg_cache_size) | 
|  | 1437 | return 1; | 
|  | 1438 | index = snd_soc_get_reg_access_index(codec, reg); | 
|  | 1439 | if (index < 0) | 
|  | 1440 | return 0; | 
|  | 1441 | return codec->driver->reg_access_default[index].vol; | 
|  | 1442 | } | 
|  | 1443 | EXPORT_SYMBOL_GPL(snd_soc_default_volatile_register); | 
|  | 1444 |  | 
|  | 1445 | int snd_soc_default_readable_register(struct snd_soc_codec *codec, | 
|  | 1446 | unsigned int reg) | 
|  | 1447 | { | 
|  | 1448 | int index; | 
|  | 1449 |  | 
|  | 1450 | if (reg >= codec->driver->reg_cache_size) | 
|  | 1451 | return 1; | 
|  | 1452 | index = snd_soc_get_reg_access_index(codec, reg); | 
|  | 1453 | if (index < 0) | 
|  | 1454 | return 0; | 
|  | 1455 | return codec->driver->reg_access_default[index].read; | 
|  | 1456 | } | 
|  | 1457 | EXPORT_SYMBOL_GPL(snd_soc_default_readable_register); | 
| Dimitris Papastamos | 8020454 | 2011-03-24 13:45:17 +0000 | [diff] [blame] | 1458 |  | 
|  | 1459 | int snd_soc_default_writable_register(struct snd_soc_codec *codec, | 
|  | 1460 | unsigned int reg) | 
|  | 1461 | { | 
|  | 1462 | int index; | 
|  | 1463 |  | 
|  | 1464 | if (reg >= codec->driver->reg_cache_size) | 
|  | 1465 | return 1; | 
|  | 1466 | index = snd_soc_get_reg_access_index(codec, reg); | 
|  | 1467 | if (index < 0) | 
|  | 1468 | return 0; | 
|  | 1469 | return codec->driver->reg_access_default[index].write; | 
|  | 1470 | } | 
|  | 1471 | EXPORT_SYMBOL_GPL(snd_soc_default_writable_register); |