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> |
| 17 | |
Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 18 | static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec, |
| 19 | unsigned int reg) |
| 20 | { |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 21 | int ret; |
| 22 | unsigned int val; |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 23 | |
| 24 | if (reg >= codec->driver->reg_cache_size || |
| 25 | snd_soc_codec_volatile_register(codec, reg)) { |
| 26 | if (codec->cache_only) |
| 27 | return -1; |
| 28 | |
Dimitris Papastamos | 5aaa062 | 2010-11-08 15:37:07 +0000 | [diff] [blame] | 29 | BUG_ON(!codec->hw_read); |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 30 | return codec->hw_read(codec, reg); |
| 31 | } |
| 32 | |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 33 | ret = snd_soc_cache_read(codec, reg, &val); |
| 34 | if (ret < 0) |
| 35 | return -1; |
| 36 | return val; |
Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 37 | } |
| 38 | |
| 39 | static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg, |
| 40 | unsigned int value) |
| 41 | { |
Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 42 | u8 data[2]; |
| 43 | int ret; |
| 44 | |
Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 45 | data[0] = (reg << 4) | ((value >> 8) & 0x000f); |
| 46 | data[1] = value & 0x00ff; |
| 47 | |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 48 | if (!snd_soc_codec_volatile_register(codec, reg) && |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 49 | reg < codec->driver->reg_cache_size) { |
| 50 | ret = snd_soc_cache_write(codec, reg, value); |
| 51 | if (ret < 0) |
| 52 | return -1; |
| 53 | } |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 54 | |
Mark Brown | a3032b4 | 2010-02-01 18:48:03 +0000 | [diff] [blame] | 55 | if (codec->cache_only) { |
| 56 | codec->cache_sync = 1; |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 57 | return 0; |
Mark Brown | a3032b4 | 2010-02-01 18:48:03 +0000 | [diff] [blame] | 58 | } |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 59 | |
Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 60 | ret = codec->hw_write(codec->control_data, data, 2); |
| 61 | if (ret == 2) |
| 62 | return 0; |
| 63 | if (ret < 0) |
| 64 | return ret; |
| 65 | else |
| 66 | return -EIO; |
| 67 | } |
| 68 | |
| 69 | #if defined(CONFIG_SPI_MASTER) |
| 70 | static int snd_soc_4_12_spi_write(void *control_data, const char *data, |
| 71 | int len) |
| 72 | { |
| 73 | struct spi_device *spi = control_data; |
| 74 | struct spi_transfer t; |
| 75 | struct spi_message m; |
| 76 | u8 msg[2]; |
| 77 | |
| 78 | if (len <= 0) |
| 79 | return 0; |
| 80 | |
| 81 | msg[0] = data[1]; |
| 82 | msg[1] = data[0]; |
| 83 | |
| 84 | spi_message_init(&m); |
| 85 | memset(&t, 0, (sizeof t)); |
| 86 | |
| 87 | t.tx_buf = &msg[0]; |
| 88 | t.len = len; |
| 89 | |
| 90 | spi_message_add_tail(&t, &m); |
| 91 | spi_sync(spi, &m); |
| 92 | |
| 93 | return len; |
| 94 | } |
| 95 | #else |
| 96 | #define snd_soc_4_12_spi_write NULL |
| 97 | #endif |
| 98 | |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 99 | static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec, |
| 100 | unsigned int reg) |
| 101 | { |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 102 | int ret; |
| 103 | unsigned int val; |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 104 | |
| 105 | if (reg >= codec->driver->reg_cache_size || |
| 106 | snd_soc_codec_volatile_register(codec, reg)) { |
| 107 | if (codec->cache_only) |
| 108 | return -1; |
| 109 | |
Dimitris Papastamos | 5aaa062 | 2010-11-08 15:37:07 +0000 | [diff] [blame] | 110 | BUG_ON(!codec->hw_read); |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 111 | return codec->hw_read(codec, reg); |
| 112 | } |
| 113 | |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 114 | ret = snd_soc_cache_read(codec, reg, &val); |
| 115 | if (ret < 0) |
| 116 | return -1; |
| 117 | return val; |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 118 | } |
| 119 | |
| 120 | static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg, |
| 121 | unsigned int value) |
| 122 | { |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 123 | u8 data[2]; |
| 124 | int ret; |
| 125 | |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 126 | data[0] = (reg << 1) | ((value >> 8) & 0x0001); |
| 127 | data[1] = value & 0x00ff; |
| 128 | |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 129 | if (!snd_soc_codec_volatile_register(codec, reg) && |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 130 | reg < codec->driver->reg_cache_size) { |
| 131 | ret = snd_soc_cache_write(codec, reg, value); |
| 132 | if (ret < 0) |
| 133 | return -1; |
| 134 | } |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 135 | |
Mark Brown | a3032b4 | 2010-02-01 18:48:03 +0000 | [diff] [blame] | 136 | if (codec->cache_only) { |
| 137 | codec->cache_sync = 1; |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 138 | return 0; |
Mark Brown | a3032b4 | 2010-02-01 18:48:03 +0000 | [diff] [blame] | 139 | } |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 140 | |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 141 | ret = codec->hw_write(codec->control_data, data, 2); |
| 142 | if (ret == 2) |
| 143 | return 0; |
| 144 | if (ret < 0) |
| 145 | return ret; |
| 146 | else |
| 147 | return -EIO; |
| 148 | } |
| 149 | |
Mark Brown | 27ded04 | 2009-07-10 23:28:16 +0100 | [diff] [blame] | 150 | #if defined(CONFIG_SPI_MASTER) |
| 151 | static int snd_soc_7_9_spi_write(void *control_data, const char *data, |
| 152 | int len) |
| 153 | { |
| 154 | struct spi_device *spi = control_data; |
| 155 | struct spi_transfer t; |
| 156 | struct spi_message m; |
| 157 | u8 msg[2]; |
| 158 | |
| 159 | if (len <= 0) |
| 160 | return 0; |
| 161 | |
| 162 | msg[0] = data[0]; |
| 163 | msg[1] = data[1]; |
| 164 | |
| 165 | spi_message_init(&m); |
| 166 | memset(&t, 0, (sizeof t)); |
| 167 | |
| 168 | t.tx_buf = &msg[0]; |
| 169 | t.len = len; |
| 170 | |
| 171 | spi_message_add_tail(&t, &m); |
| 172 | spi_sync(spi, &m); |
| 173 | |
| 174 | return len; |
| 175 | } |
| 176 | #else |
| 177 | #define snd_soc_7_9_spi_write NULL |
| 178 | #endif |
| 179 | |
Joonyoung Shim | 341c9b8 | 2009-09-07 12:04:37 +0900 | [diff] [blame] | 180 | static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg, |
| 181 | unsigned int value) |
| 182 | { |
Joonyoung Shim | 341c9b8 | 2009-09-07 12:04:37 +0900 | [diff] [blame] | 183 | u8 data[2]; |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 184 | int ret; |
Joonyoung Shim | 341c9b8 | 2009-09-07 12:04:37 +0900 | [diff] [blame] | 185 | |
Barry Song | f4bee1b | 2010-03-18 16:17:01 +0800 | [diff] [blame] | 186 | reg &= 0xff; |
| 187 | data[0] = reg; |
Joonyoung Shim | 341c9b8 | 2009-09-07 12:04:37 +0900 | [diff] [blame] | 188 | data[1] = value & 0xff; |
| 189 | |
Dimitris Papastamos | 005d65f | 2010-09-22 16:16:06 +0100 | [diff] [blame] | 190 | if (!snd_soc_codec_volatile_register(codec, reg) && |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 191 | reg < codec->driver->reg_cache_size) { |
| 192 | ret = snd_soc_cache_write(codec, reg, value); |
| 193 | if (ret < 0) |
| 194 | return -1; |
| 195 | } |
Joonyoung Shim | 341c9b8 | 2009-09-07 12:04:37 +0900 | [diff] [blame] | 196 | |
Mark Brown | a3032b4 | 2010-02-01 18:48:03 +0000 | [diff] [blame] | 197 | if (codec->cache_only) { |
| 198 | codec->cache_sync = 1; |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 199 | return 0; |
Mark Brown | a3032b4 | 2010-02-01 18:48:03 +0000 | [diff] [blame] | 200 | } |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 201 | |
Joonyoung Shim | 341c9b8 | 2009-09-07 12:04:37 +0900 | [diff] [blame] | 202 | if (codec->hw_write(codec->control_data, data, 2) == 2) |
| 203 | return 0; |
| 204 | else |
| 205 | return -EIO; |
| 206 | } |
| 207 | |
| 208 | static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec, |
| 209 | unsigned int reg) |
| 210 | { |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 211 | int ret; |
| 212 | unsigned int val; |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 213 | |
Barry Song | f4bee1b | 2010-03-18 16:17:01 +0800 | [diff] [blame] | 214 | reg &= 0xff; |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 215 | if (reg >= codec->driver->reg_cache_size || |
| 216 | snd_soc_codec_volatile_register(codec, reg)) { |
| 217 | if (codec->cache_only) |
| 218 | return -1; |
| 219 | |
Dimitris Papastamos | 5aaa062 | 2010-11-08 15:37:07 +0000 | [diff] [blame] | 220 | BUG_ON(!codec->hw_read); |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 221 | return codec->hw_read(codec, reg); |
| 222 | } |
| 223 | |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 224 | ret = snd_soc_cache_read(codec, reg, &val); |
| 225 | if (ret < 0) |
| 226 | return -1; |
| 227 | return val; |
Joonyoung Shim | 341c9b8 | 2009-09-07 12:04:37 +0900 | [diff] [blame] | 228 | } |
| 229 | |
Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 230 | #if defined(CONFIG_SPI_MASTER) |
| 231 | static int snd_soc_8_8_spi_write(void *control_data, const char *data, |
| 232 | int len) |
| 233 | { |
| 234 | struct spi_device *spi = control_data; |
| 235 | struct spi_transfer t; |
| 236 | struct spi_message m; |
| 237 | u8 msg[2]; |
| 238 | |
| 239 | if (len <= 0) |
| 240 | return 0; |
| 241 | |
| 242 | msg[0] = data[0]; |
| 243 | msg[1] = data[1]; |
| 244 | |
| 245 | spi_message_init(&m); |
| 246 | memset(&t, 0, (sizeof t)); |
| 247 | |
| 248 | t.tx_buf = &msg[0]; |
| 249 | t.len = len; |
| 250 | |
| 251 | spi_message_add_tail(&t, &m); |
| 252 | spi_sync(spi, &m); |
| 253 | |
| 254 | return len; |
| 255 | } |
| 256 | #else |
| 257 | #define snd_soc_8_8_spi_write NULL |
| 258 | #endif |
| 259 | |
Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 260 | static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg, |
| 261 | unsigned int value) |
| 262 | { |
Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 263 | u8 data[3]; |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 264 | int ret; |
Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 265 | |
| 266 | data[0] = reg; |
| 267 | data[1] = (value >> 8) & 0xff; |
| 268 | data[2] = value & 0xff; |
| 269 | |
Takashi Iwai | 3e13f65 | 2010-09-23 07:40:16 +0200 | [diff] [blame] | 270 | if (!snd_soc_codec_volatile_register(codec, reg) && |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 271 | reg < codec->driver->reg_cache_size) { |
| 272 | ret = snd_soc_cache_write(codec, reg, value); |
| 273 | if (ret < 0) |
| 274 | return -1; |
| 275 | } |
Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 276 | |
Mark Brown | a3032b4 | 2010-02-01 18:48:03 +0000 | [diff] [blame] | 277 | if (codec->cache_only) { |
| 278 | codec->cache_sync = 1; |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 279 | return 0; |
Mark Brown | a3032b4 | 2010-02-01 18:48:03 +0000 | [diff] [blame] | 280 | } |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 281 | |
Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 282 | if (codec->hw_write(codec->control_data, data, 3) == 3) |
| 283 | return 0; |
| 284 | else |
| 285 | return -EIO; |
| 286 | } |
| 287 | |
| 288 | static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec, |
| 289 | unsigned int reg) |
| 290 | { |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 291 | int ret; |
| 292 | unsigned int val; |
Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 293 | |
Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 294 | if (reg >= codec->driver->reg_cache_size || |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 295 | snd_soc_codec_volatile_register(codec, reg)) { |
| 296 | if (codec->cache_only) |
Dimitris Papastamos | 391d8a0 | 2010-09-21 17:04:07 +0100 | [diff] [blame] | 297 | return -1; |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 298 | |
Dimitris Papastamos | 5aaa062 | 2010-11-08 15:37:07 +0000 | [diff] [blame] | 299 | BUG_ON(!codec->hw_read); |
Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 300 | return codec->hw_read(codec, reg); |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 301 | } |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 302 | |
| 303 | ret = snd_soc_cache_read(codec, reg, &val); |
| 304 | if (ret < 0) |
| 305 | return -1; |
| 306 | return val; |
Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 307 | } |
| 308 | |
Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 309 | #if defined(CONFIG_SPI_MASTER) |
| 310 | static int snd_soc_8_16_spi_write(void *control_data, const char *data, |
| 311 | int len) |
| 312 | { |
| 313 | struct spi_device *spi = control_data; |
| 314 | struct spi_transfer t; |
| 315 | struct spi_message m; |
| 316 | u8 msg[3]; |
| 317 | |
| 318 | if (len <= 0) |
| 319 | return 0; |
| 320 | |
| 321 | msg[0] = data[0]; |
| 322 | msg[1] = data[1]; |
| 323 | msg[2] = data[2]; |
| 324 | |
| 325 | spi_message_init(&m); |
| 326 | memset(&t, 0, (sizeof t)); |
| 327 | |
| 328 | t.tx_buf = &msg[0]; |
| 329 | t.len = len; |
| 330 | |
| 331 | spi_message_add_tail(&t, &m); |
| 332 | spi_sync(spi, &m); |
| 333 | |
| 334 | return len; |
| 335 | } |
| 336 | #else |
| 337 | #define snd_soc_8_16_spi_write NULL |
| 338 | #endif |
| 339 | |
Randy Dunlap | 17244c2 | 2009-08-10 16:04:39 -0700 | [diff] [blame] | 340 | #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) |
Cliff Cai | 85dfcdf | 2010-03-18 16:17:00 +0800 | [diff] [blame] | 341 | static unsigned int snd_soc_8_8_read_i2c(struct snd_soc_codec *codec, |
| 342 | unsigned int r) |
| 343 | { |
| 344 | struct i2c_msg xfer[2]; |
| 345 | u8 reg = r; |
| 346 | u8 data; |
| 347 | int ret; |
| 348 | struct i2c_client *client = codec->control_data; |
| 349 | |
| 350 | /* Write register */ |
| 351 | xfer[0].addr = client->addr; |
| 352 | xfer[0].flags = 0; |
| 353 | xfer[0].len = 1; |
| 354 | xfer[0].buf = ® |
| 355 | |
| 356 | /* Read data */ |
| 357 | xfer[1].addr = client->addr; |
| 358 | xfer[1].flags = I2C_M_RD; |
| 359 | xfer[1].len = 1; |
| 360 | xfer[1].buf = &data; |
| 361 | |
| 362 | ret = i2c_transfer(client->adapter, xfer, 2); |
| 363 | if (ret != 2) { |
| 364 | dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); |
| 365 | return 0; |
| 366 | } |
| 367 | |
| 368 | return data; |
| 369 | } |
| 370 | #else |
| 371 | #define snd_soc_8_8_read_i2c NULL |
| 372 | #endif |
| 373 | |
| 374 | #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) |
Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 375 | static unsigned int snd_soc_8_16_read_i2c(struct snd_soc_codec *codec, |
| 376 | unsigned int r) |
| 377 | { |
| 378 | struct i2c_msg xfer[2]; |
| 379 | u8 reg = r; |
| 380 | u16 data; |
| 381 | int ret; |
| 382 | struct i2c_client *client = codec->control_data; |
| 383 | |
| 384 | /* Write register */ |
| 385 | xfer[0].addr = client->addr; |
| 386 | xfer[0].flags = 0; |
| 387 | xfer[0].len = 1; |
| 388 | xfer[0].buf = ® |
| 389 | |
| 390 | /* Read data */ |
| 391 | xfer[1].addr = client->addr; |
| 392 | xfer[1].flags = I2C_M_RD; |
| 393 | xfer[1].len = 2; |
| 394 | xfer[1].buf = (u8 *)&data; |
| 395 | |
| 396 | ret = i2c_transfer(client->adapter, xfer, 2); |
| 397 | if (ret != 2) { |
| 398 | dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); |
| 399 | return 0; |
| 400 | } |
| 401 | |
| 402 | return (data >> 8) | ((data & 0xff) << 8); |
| 403 | } |
| 404 | #else |
| 405 | #define snd_soc_8_16_read_i2c NULL |
| 406 | #endif |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 407 | |
Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 408 | #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) |
| 409 | static unsigned int snd_soc_16_8_read_i2c(struct snd_soc_codec *codec, |
| 410 | unsigned int r) |
| 411 | { |
| 412 | struct i2c_msg xfer[2]; |
| 413 | u16 reg = r; |
| 414 | u8 data; |
| 415 | int ret; |
| 416 | struct i2c_client *client = codec->control_data; |
| 417 | |
| 418 | /* Write register */ |
| 419 | xfer[0].addr = client->addr; |
| 420 | xfer[0].flags = 0; |
| 421 | xfer[0].len = 2; |
| 422 | xfer[0].buf = (u8 *)® |
| 423 | |
| 424 | /* Read data */ |
| 425 | xfer[1].addr = client->addr; |
| 426 | xfer[1].flags = I2C_M_RD; |
| 427 | xfer[1].len = 1; |
| 428 | xfer[1].buf = &data; |
| 429 | |
| 430 | ret = i2c_transfer(client->adapter, xfer, 2); |
| 431 | if (ret != 2) { |
| 432 | dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); |
| 433 | return 0; |
| 434 | } |
| 435 | |
| 436 | return data; |
| 437 | } |
| 438 | #else |
| 439 | #define snd_soc_16_8_read_i2c NULL |
| 440 | #endif |
| 441 | |
| 442 | static unsigned int snd_soc_16_8_read(struct snd_soc_codec *codec, |
| 443 | unsigned int reg) |
| 444 | { |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 445 | int ret; |
| 446 | unsigned int val; |
Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 447 | |
| 448 | reg &= 0xff; |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 449 | if (reg >= codec->driver->reg_cache_size || |
| 450 | snd_soc_codec_volatile_register(codec, reg)) { |
| 451 | if (codec->cache_only) |
| 452 | return -1; |
| 453 | |
Dimitris Papastamos | 5aaa062 | 2010-11-08 15:37:07 +0000 | [diff] [blame] | 454 | BUG_ON(!codec->hw_read); |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 455 | return codec->hw_read(codec, reg); |
| 456 | } |
| 457 | |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 458 | ret = snd_soc_cache_read(codec, reg, &val); |
| 459 | if (ret < 0) |
| 460 | return -1; |
| 461 | return val; |
Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 462 | } |
| 463 | |
| 464 | static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg, |
| 465 | unsigned int value) |
| 466 | { |
Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 467 | u8 data[3]; |
| 468 | int ret; |
| 469 | |
Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 470 | data[0] = (reg >> 8) & 0xff; |
| 471 | data[1] = reg & 0xff; |
| 472 | data[2] = value; |
| 473 | |
| 474 | reg &= 0xff; |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 475 | if (!snd_soc_codec_volatile_register(codec, reg) && |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 476 | reg < codec->driver->reg_cache_size) { |
| 477 | ret = snd_soc_cache_write(codec, reg, value); |
| 478 | if (ret < 0) |
| 479 | return -1; |
| 480 | } |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 481 | |
Mark Brown | a3032b4 | 2010-02-01 18:48:03 +0000 | [diff] [blame] | 482 | if (codec->cache_only) { |
| 483 | codec->cache_sync = 1; |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 484 | return 0; |
Mark Brown | a3032b4 | 2010-02-01 18:48:03 +0000 | [diff] [blame] | 485 | } |
Mark Brown | 8c961bc | 2010-02-01 18:46:10 +0000 | [diff] [blame] | 486 | |
Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 487 | ret = codec->hw_write(codec->control_data, data, 3); |
| 488 | if (ret == 3) |
| 489 | return 0; |
| 490 | if (ret < 0) |
| 491 | return ret; |
| 492 | else |
| 493 | return -EIO; |
| 494 | } |
| 495 | |
| 496 | #if defined(CONFIG_SPI_MASTER) |
| 497 | static int snd_soc_16_8_spi_write(void *control_data, const char *data, |
| 498 | int len) |
| 499 | { |
| 500 | struct spi_device *spi = control_data; |
| 501 | struct spi_transfer t; |
| 502 | struct spi_message m; |
| 503 | u8 msg[3]; |
| 504 | |
| 505 | if (len <= 0) |
| 506 | return 0; |
| 507 | |
| 508 | msg[0] = data[0]; |
| 509 | msg[1] = data[1]; |
| 510 | msg[2] = data[2]; |
| 511 | |
| 512 | spi_message_init(&m); |
| 513 | memset(&t, 0, (sizeof t)); |
| 514 | |
| 515 | t.tx_buf = &msg[0]; |
| 516 | t.len = len; |
| 517 | |
| 518 | spi_message_add_tail(&t, &m); |
| 519 | spi_sync(spi, &m); |
| 520 | |
| 521 | return len; |
| 522 | } |
| 523 | #else |
| 524 | #define snd_soc_16_8_spi_write NULL |
| 525 | #endif |
| 526 | |
Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 527 | #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) |
| 528 | static unsigned int snd_soc_16_16_read_i2c(struct snd_soc_codec *codec, |
| 529 | unsigned int r) |
| 530 | { |
| 531 | struct i2c_msg xfer[2]; |
| 532 | u16 reg = cpu_to_be16(r); |
| 533 | u16 data; |
| 534 | int ret; |
| 535 | struct i2c_client *client = codec->control_data; |
| 536 | |
| 537 | /* Write register */ |
| 538 | xfer[0].addr = client->addr; |
| 539 | xfer[0].flags = 0; |
| 540 | xfer[0].len = 2; |
| 541 | xfer[0].buf = (u8 *)® |
| 542 | |
| 543 | /* Read data */ |
| 544 | xfer[1].addr = client->addr; |
| 545 | xfer[1].flags = I2C_M_RD; |
| 546 | xfer[1].len = 2; |
| 547 | xfer[1].buf = (u8 *)&data; |
| 548 | |
| 549 | ret = i2c_transfer(client->adapter, xfer, 2); |
| 550 | if (ret != 2) { |
| 551 | dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); |
| 552 | return 0; |
| 553 | } |
| 554 | |
| 555 | return be16_to_cpu(data); |
| 556 | } |
| 557 | #else |
| 558 | #define snd_soc_16_16_read_i2c NULL |
| 559 | #endif |
| 560 | |
| 561 | static unsigned int snd_soc_16_16_read(struct snd_soc_codec *codec, |
| 562 | unsigned int reg) |
| 563 | { |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 564 | int ret; |
| 565 | unsigned int val; |
Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 566 | |
Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 567 | if (reg >= codec->driver->reg_cache_size || |
Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 568 | snd_soc_codec_volatile_register(codec, reg)) { |
| 569 | if (codec->cache_only) |
Dimitris Papastamos | 391d8a0 | 2010-09-21 17:04:07 +0100 | [diff] [blame] | 570 | return -1; |
Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 571 | |
Dimitris Papastamos | 5aaa062 | 2010-11-08 15:37:07 +0000 | [diff] [blame] | 572 | BUG_ON(!codec->hw_read); |
Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 573 | return codec->hw_read(codec, reg); |
| 574 | } |
| 575 | |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 576 | ret = snd_soc_cache_read(codec, reg, &val); |
| 577 | if (ret < 0) |
| 578 | return -1; |
| 579 | |
| 580 | return val; |
Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 581 | } |
| 582 | |
| 583 | static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg, |
| 584 | unsigned int value) |
| 585 | { |
Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 586 | u8 data[4]; |
| 587 | int ret; |
| 588 | |
| 589 | data[0] = (reg >> 8) & 0xff; |
| 590 | data[1] = reg & 0xff; |
| 591 | data[2] = (value >> 8) & 0xff; |
| 592 | data[3] = value & 0xff; |
| 593 | |
Dimitris Papastamos | db49c14 | 2010-09-22 13:25:47 +0100 | [diff] [blame] | 594 | if (!snd_soc_codec_volatile_register(codec, reg) && |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 595 | reg < codec->driver->reg_cache_size) { |
| 596 | ret = snd_soc_cache_write(codec, reg, value); |
| 597 | if (ret < 0) |
| 598 | return -1; |
| 599 | } |
Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 600 | |
| 601 | if (codec->cache_only) { |
| 602 | codec->cache_sync = 1; |
| 603 | return 0; |
| 604 | } |
| 605 | |
| 606 | ret = codec->hw_write(codec->control_data, data, 4); |
| 607 | if (ret == 4) |
| 608 | return 0; |
| 609 | if (ret < 0) |
| 610 | return ret; |
| 611 | else |
| 612 | return -EIO; |
| 613 | } |
Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 614 | |
Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 615 | #if defined(CONFIG_SPI_MASTER) |
| 616 | static int snd_soc_16_16_spi_write(void *control_data, const char *data, |
| 617 | int len) |
| 618 | { |
| 619 | struct spi_device *spi = control_data; |
| 620 | struct spi_transfer t; |
| 621 | struct spi_message m; |
| 622 | u8 msg[4]; |
| 623 | |
| 624 | if (len <= 0) |
| 625 | return 0; |
| 626 | |
| 627 | msg[0] = data[0]; |
| 628 | msg[1] = data[1]; |
| 629 | msg[2] = data[2]; |
| 630 | msg[3] = data[3]; |
| 631 | |
| 632 | spi_message_init(&m); |
| 633 | memset(&t, 0, (sizeof t)); |
| 634 | |
| 635 | t.tx_buf = &msg[0]; |
| 636 | t.len = len; |
| 637 | |
| 638 | spi_message_add_tail(&t, &m); |
| 639 | spi_sync(spi, &m); |
| 640 | |
| 641 | return len; |
| 642 | } |
| 643 | #else |
| 644 | #define snd_soc_16_16_spi_write NULL |
| 645 | #endif |
| 646 | |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 647 | static struct { |
| 648 | int addr_bits; |
| 649 | int data_bits; |
Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 650 | int (*write)(struct snd_soc_codec *codec, unsigned int, unsigned int); |
Mark Brown | 27ded04 | 2009-07-10 23:28:16 +0100 | [diff] [blame] | 651 | int (*spi_write)(void *, const char *, int); |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 652 | unsigned int (*read)(struct snd_soc_codec *, unsigned int); |
Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 653 | unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int); |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 654 | } io_types[] = { |
Mark Brown | d62ab35 | 2009-09-21 04:21:47 -0700 | [diff] [blame] | 655 | { |
Barry Song | 63b62ab | 2010-01-27 11:46:17 +0800 | [diff] [blame] | 656 | .addr_bits = 4, .data_bits = 12, |
| 657 | .write = snd_soc_4_12_write, .read = snd_soc_4_12_read, |
| 658 | .spi_write = snd_soc_4_12_spi_write, |
| 659 | }, |
| 660 | { |
Mark Brown | d62ab35 | 2009-09-21 04:21:47 -0700 | [diff] [blame] | 661 | .addr_bits = 7, .data_bits = 9, |
| 662 | .write = snd_soc_7_9_write, .read = snd_soc_7_9_read, |
Barry Song | 8998c89 | 2009-12-31 10:30:34 +0800 | [diff] [blame] | 663 | .spi_write = snd_soc_7_9_spi_write, |
Mark Brown | d62ab35 | 2009-09-21 04:21:47 -0700 | [diff] [blame] | 664 | }, |
| 665 | { |
| 666 | .addr_bits = 8, .data_bits = 8, |
| 667 | .write = snd_soc_8_8_write, .read = snd_soc_8_8_read, |
Cliff Cai | 85dfcdf | 2010-03-18 16:17:00 +0800 | [diff] [blame] | 668 | .i2c_read = snd_soc_8_8_read_i2c, |
Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 669 | .spi_write = snd_soc_8_8_spi_write, |
Mark Brown | d62ab35 | 2009-09-21 04:21:47 -0700 | [diff] [blame] | 670 | }, |
| 671 | { |
| 672 | .addr_bits = 8, .data_bits = 16, |
| 673 | .write = snd_soc_8_16_write, .read = snd_soc_8_16_read, |
| 674 | .i2c_read = snd_soc_8_16_read_i2c, |
Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 675 | .spi_write = snd_soc_8_16_spi_write, |
Mark Brown | d62ab35 | 2009-09-21 04:21:47 -0700 | [diff] [blame] | 676 | }, |
Barry Song | 994dc42 | 2010-01-27 11:46:18 +0800 | [diff] [blame] | 677 | { |
| 678 | .addr_bits = 16, .data_bits = 8, |
| 679 | .write = snd_soc_16_8_write, .read = snd_soc_16_8_read, |
| 680 | .i2c_read = snd_soc_16_8_read_i2c, |
| 681 | .spi_write = snd_soc_16_8_spi_write, |
| 682 | }, |
Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 683 | { |
| 684 | .addr_bits = 16, .data_bits = 16, |
| 685 | .write = snd_soc_16_16_write, .read = snd_soc_16_16_read, |
| 686 | .i2c_read = snd_soc_16_16_read_i2c, |
Dimitris Papastamos | f479fd9 | 2010-10-04 11:25:13 +0100 | [diff] [blame] | 687 | .spi_write = snd_soc_16_16_spi_write, |
Mark Brown | bc6552f | 2010-03-05 16:27:15 +0000 | [diff] [blame] | 688 | }, |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 689 | }; |
| 690 | |
| 691 | /** |
| 692 | * snd_soc_codec_set_cache_io: Set up standard I/O functions. |
| 693 | * |
| 694 | * @codec: CODEC to configure. |
| 695 | * @type: Type of cache. |
| 696 | * @addr_bits: Number of bits of register address data. |
| 697 | * @data_bits: Number of bits of data per register. |
Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 698 | * @control: Control bus used. |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 699 | * |
| 700 | * Register formats are frequently shared between many I2C and SPI |
| 701 | * devices. In order to promote code reuse the ASoC core provides |
| 702 | * some standard implementations of CODEC read and write operations |
| 703 | * which can be set up using this function. |
| 704 | * |
| 705 | * The caller is responsible for allocating and initialising the |
| 706 | * actual cache. |
| 707 | * |
| 708 | * Note that at present this code cannot be used by CODECs with |
| 709 | * volatile registers. |
| 710 | */ |
| 711 | int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec, |
Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 712 | int addr_bits, int data_bits, |
| 713 | enum snd_soc_control_type control) |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 714 | { |
| 715 | int i; |
| 716 | |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 717 | for (i = 0; i < ARRAY_SIZE(io_types); i++) |
| 718 | if (io_types[i].addr_bits == addr_bits && |
| 719 | io_types[i].data_bits == data_bits) |
| 720 | break; |
| 721 | if (i == ARRAY_SIZE(io_types)) { |
| 722 | printk(KERN_ERR |
| 723 | "No I/O functions for %d bit address %d bit data\n", |
| 724 | addr_bits, data_bits); |
| 725 | return -EINVAL; |
| 726 | } |
| 727 | |
Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 728 | codec->driver->write = io_types[i].write; |
| 729 | codec->driver->read = io_types[i].read; |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 730 | |
Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 731 | switch (control) { |
| 732 | case SND_SOC_CUSTOM: |
| 733 | break; |
| 734 | |
| 735 | case SND_SOC_I2C: |
Randy Dunlap | 17244c2 | 2009-08-10 16:04:39 -0700 | [diff] [blame] | 736 | #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) |
Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 737 | codec->hw_write = (hw_write_t)i2c_master_send; |
| 738 | #endif |
Mark Brown | afa2f10 | 2009-07-10 23:11:24 +0100 | [diff] [blame] | 739 | if (io_types[i].i2c_read) |
| 740 | codec->hw_read = io_types[i].i2c_read; |
Mark Brown | a6d1434 | 2010-08-12 10:59:15 +0100 | [diff] [blame] | 741 | |
| 742 | codec->control_data = container_of(codec->dev, |
| 743 | struct i2c_client, |
| 744 | dev); |
Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 745 | break; |
| 746 | |
| 747 | case SND_SOC_SPI: |
Mark Brown | 27ded04 | 2009-07-10 23:28:16 +0100 | [diff] [blame] | 748 | if (io_types[i].spi_write) |
| 749 | codec->hw_write = io_types[i].spi_write; |
Mark Brown | a6d1434 | 2010-08-12 10:59:15 +0100 | [diff] [blame] | 750 | |
| 751 | codec->control_data = container_of(codec->dev, |
| 752 | struct spi_device, |
| 753 | dev); |
Mark Brown | 7084a42 | 2009-07-10 22:24:27 +0100 | [diff] [blame] | 754 | break; |
| 755 | } |
| 756 | |
Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 757 | return 0; |
| 758 | } |
| 759 | EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io); |
Dimitris Papastamos | 7a30a3d | 2010-11-11 10:04:57 +0000 | [diff] [blame^] | 760 | |
| 761 | static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec) |
| 762 | { |
| 763 | int i; |
| 764 | struct snd_soc_codec_driver *codec_drv; |
| 765 | unsigned int val; |
| 766 | |
| 767 | codec_drv = codec->driver; |
| 768 | for (i = 0; i < codec_drv->reg_cache_size; ++i) { |
| 769 | snd_soc_cache_read(codec, i, &val); |
| 770 | if (codec_drv->reg_cache_default) { |
| 771 | switch (codec_drv->reg_word_size) { |
| 772 | case 1: { |
| 773 | const u8 *cache; |
| 774 | |
| 775 | cache = codec_drv->reg_cache_default; |
| 776 | if (cache[i] == val) |
| 777 | continue; |
| 778 | } |
| 779 | break; |
| 780 | case 2: { |
| 781 | const u16 *cache; |
| 782 | |
| 783 | cache = codec_drv->reg_cache_default; |
| 784 | if (cache[i] == val) |
| 785 | continue; |
| 786 | } |
| 787 | break; |
| 788 | default: |
| 789 | BUG(); |
| 790 | } |
| 791 | } |
| 792 | snd_soc_write(codec, i, val); |
| 793 | dev_dbg(codec->dev, "Synced register %#x, value = %#x\n", |
| 794 | i, val); |
| 795 | } |
| 796 | return 0; |
| 797 | } |
| 798 | |
| 799 | static int snd_soc_flat_cache_write(struct snd_soc_codec *codec, |
| 800 | unsigned int reg, unsigned int value) |
| 801 | { |
| 802 | switch (codec->driver->reg_word_size) { |
| 803 | case 1: { |
| 804 | u8 *cache; |
| 805 | |
| 806 | cache = codec->reg_cache; |
| 807 | cache[reg] = value; |
| 808 | } |
| 809 | break; |
| 810 | case 2: { |
| 811 | u16 *cache; |
| 812 | |
| 813 | cache = codec->reg_cache; |
| 814 | cache[reg] = value; |
| 815 | } |
| 816 | break; |
| 817 | default: |
| 818 | BUG(); |
| 819 | } |
| 820 | |
| 821 | return 0; |
| 822 | } |
| 823 | |
| 824 | static int snd_soc_flat_cache_read(struct snd_soc_codec *codec, |
| 825 | unsigned int reg, unsigned int *value) |
| 826 | { |
| 827 | switch (codec->driver->reg_word_size) { |
| 828 | case 1: { |
| 829 | u8 *cache; |
| 830 | |
| 831 | cache = codec->reg_cache; |
| 832 | *value = cache[reg]; |
| 833 | } |
| 834 | break; |
| 835 | case 2: { |
| 836 | u16 *cache; |
| 837 | |
| 838 | cache = codec->reg_cache; |
| 839 | *value = cache[reg]; |
| 840 | } |
| 841 | break; |
| 842 | default: |
| 843 | BUG(); |
| 844 | } |
| 845 | |
| 846 | return 0; |
| 847 | } |
| 848 | |
| 849 | static int snd_soc_flat_cache_exit(struct snd_soc_codec *codec) |
| 850 | { |
| 851 | if (!codec->reg_cache) |
| 852 | return 0; |
| 853 | kfree(codec->reg_cache); |
| 854 | codec->reg_cache = NULL; |
| 855 | return 0; |
| 856 | } |
| 857 | |
| 858 | static int snd_soc_flat_cache_init(struct snd_soc_codec *codec) |
| 859 | { |
| 860 | struct snd_soc_codec_driver *codec_drv; |
| 861 | size_t reg_size; |
| 862 | |
| 863 | codec_drv = codec->driver; |
| 864 | reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; |
| 865 | |
| 866 | if (codec_drv->reg_cache_default) |
| 867 | codec->reg_cache = kmemdup(codec_drv->reg_cache_default, |
| 868 | reg_size, GFP_KERNEL); |
| 869 | else |
| 870 | codec->reg_cache = kzalloc(reg_size, GFP_KERNEL); |
| 871 | if (!codec->reg_cache) |
| 872 | return -ENOMEM; |
| 873 | |
| 874 | return 0; |
| 875 | } |
| 876 | |
| 877 | /* an array of all supported compression types */ |
| 878 | static const struct snd_soc_cache_ops cache_types[] = { |
| 879 | { |
| 880 | .id = SND_SOC_NO_COMPRESSION, |
| 881 | .init = snd_soc_flat_cache_init, |
| 882 | .exit = snd_soc_flat_cache_exit, |
| 883 | .read = snd_soc_flat_cache_read, |
| 884 | .write = snd_soc_flat_cache_write, |
| 885 | .sync = snd_soc_flat_cache_sync |
| 886 | } |
| 887 | }; |
| 888 | |
| 889 | int snd_soc_cache_init(struct snd_soc_codec *codec) |
| 890 | { |
| 891 | int i; |
| 892 | |
| 893 | for (i = 0; i < ARRAY_SIZE(cache_types); ++i) |
| 894 | if (cache_types[i].id == codec->driver->compress_type) |
| 895 | break; |
| 896 | if (i == ARRAY_SIZE(cache_types)) { |
| 897 | dev_err(codec->dev, "Could not match compress type: %d\n", |
| 898 | codec->driver->compress_type); |
| 899 | return -EINVAL; |
| 900 | } |
| 901 | |
| 902 | mutex_init(&codec->cache_rw_mutex); |
| 903 | codec->cache_ops = &cache_types[i]; |
| 904 | |
| 905 | if (codec->cache_ops->init) |
| 906 | return codec->cache_ops->init(codec); |
| 907 | return -EINVAL; |
| 908 | } |
| 909 | |
| 910 | /* |
| 911 | * NOTE: keep in mind that this function might be called |
| 912 | * multiple times. |
| 913 | */ |
| 914 | int snd_soc_cache_exit(struct snd_soc_codec *codec) |
| 915 | { |
| 916 | if (codec->cache_ops && codec->cache_ops->exit) |
| 917 | return codec->cache_ops->exit(codec); |
| 918 | return -EINVAL; |
| 919 | } |
| 920 | |
| 921 | /** |
| 922 | * snd_soc_cache_read: Fetch the value of a given register from the cache. |
| 923 | * |
| 924 | * @codec: CODEC to configure. |
| 925 | * @reg: The register index. |
| 926 | * @value: The value to be returned. |
| 927 | */ |
| 928 | int snd_soc_cache_read(struct snd_soc_codec *codec, |
| 929 | unsigned int reg, unsigned int *value) |
| 930 | { |
| 931 | int ret; |
| 932 | |
| 933 | mutex_lock(&codec->cache_rw_mutex); |
| 934 | |
| 935 | if (value && codec->cache_ops && codec->cache_ops->read) { |
| 936 | ret = codec->cache_ops->read(codec, reg, value); |
| 937 | mutex_unlock(&codec->cache_rw_mutex); |
| 938 | return ret; |
| 939 | } |
| 940 | |
| 941 | mutex_unlock(&codec->cache_rw_mutex); |
| 942 | return -EINVAL; |
| 943 | } |
| 944 | EXPORT_SYMBOL_GPL(snd_soc_cache_read); |
| 945 | |
| 946 | /** |
| 947 | * snd_soc_cache_write: Set the value of a given register in the cache. |
| 948 | * |
| 949 | * @codec: CODEC to configure. |
| 950 | * @reg: The register index. |
| 951 | * @value: The new register value. |
| 952 | */ |
| 953 | int snd_soc_cache_write(struct snd_soc_codec *codec, |
| 954 | unsigned int reg, unsigned int value) |
| 955 | { |
| 956 | int ret; |
| 957 | |
| 958 | mutex_lock(&codec->cache_rw_mutex); |
| 959 | |
| 960 | if (codec->cache_ops && codec->cache_ops->write) { |
| 961 | ret = codec->cache_ops->write(codec, reg, value); |
| 962 | mutex_unlock(&codec->cache_rw_mutex); |
| 963 | return ret; |
| 964 | } |
| 965 | |
| 966 | mutex_unlock(&codec->cache_rw_mutex); |
| 967 | return -EINVAL; |
| 968 | } |
| 969 | EXPORT_SYMBOL_GPL(snd_soc_cache_write); |
| 970 | |
| 971 | /** |
| 972 | * snd_soc_cache_sync: Sync the register cache with the hardware. |
| 973 | * |
| 974 | * @codec: CODEC to configure. |
| 975 | * |
| 976 | * Any registers that should not be synced should be marked as |
| 977 | * volatile. In general drivers can choose not to use the provided |
| 978 | * syncing functionality if they so require. |
| 979 | */ |
| 980 | int snd_soc_cache_sync(struct snd_soc_codec *codec) |
| 981 | { |
| 982 | int ret; |
| 983 | |
| 984 | if (!codec->cache_sync) { |
| 985 | return 0; |
| 986 | } |
| 987 | |
| 988 | if (codec->cache_ops && codec->cache_ops->sync) { |
| 989 | ret = codec->cache_ops->sync(codec); |
| 990 | if (!ret) |
| 991 | codec->cache_sync = 0; |
| 992 | return ret; |
| 993 | } |
| 994 | |
| 995 | return -EINVAL; |
| 996 | } |
| 997 | EXPORT_SYMBOL_GPL(snd_soc_cache_sync); |