diff options
author | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2012-10-05 14:43:18 -0400 |
---|---|---|
committer | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2012-10-05 15:17:56 -0400 |
commit | 6e87badd3f38e1a095d6e1b13828246c3e8486b5 (patch) | |
tree | e9e08f552e7d24750e04563ecb3515a90deb835c /sound | |
parent | e10f871190ce2f912317c874a56b9cc417e46e84 (diff) |
ASoC: wm2200: Provide initial coefficient loading
Allow a coefficient set provided using the Wolfson callibration tools to
be provided along with the firmware files. Currently only coefficient
files which configure absolute register addresses are supported.
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/codecs/wm2200.c | 194 | ||||
-rw-r--r-- | sound/soc/codecs/wmfw.h | 43 |
2 files changed, 237 insertions, 0 deletions
diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c index e3f549b65b08..06d4e612a164 100644 --- a/sound/soc/codecs/wm2200.c +++ b/sound/soc/codecs/wm2200.c | |||
@@ -1150,6 +1150,192 @@ out: | |||
1150 | return ret; | 1150 | return ret; |
1151 | } | 1151 | } |
1152 | 1152 | ||
1153 | static int wm2200_setup_algs(struct snd_soc_codec *codec, int base) | ||
1154 | { | ||
1155 | struct regmap *regmap = codec->control_data; | ||
1156 | struct wmfw_adsp1_id_hdr id; | ||
1157 | struct wmfw_adsp1_alg_hdr *alg; | ||
1158 | size_t algs; | ||
1159 | int zm, dm, pm, ret, i; | ||
1160 | __be32 val; | ||
1161 | |||
1162 | switch (base) { | ||
1163 | case WM2200_DSP1_CONTROL_1: | ||
1164 | dm = WM2200_DSP1_DM_BASE; | ||
1165 | pm = WM2200_DSP1_PM_BASE; | ||
1166 | zm = WM2200_DSP1_ZM_BASE; | ||
1167 | break; | ||
1168 | case WM2200_DSP2_CONTROL_1: | ||
1169 | dm = WM2200_DSP2_DM_BASE; | ||
1170 | pm = WM2200_DSP2_PM_BASE; | ||
1171 | zm = WM2200_DSP2_ZM_BASE; | ||
1172 | break; | ||
1173 | default: | ||
1174 | dev_err(codec->dev, "BASE %x\n", base); | ||
1175 | BUG_ON(1); | ||
1176 | return -EINVAL; | ||
1177 | } | ||
1178 | |||
1179 | ret = regmap_raw_read(regmap, dm, &id, sizeof(id)); | ||
1180 | if (ret != 0) { | ||
1181 | dev_err(codec->dev, "Failed to read algorithm info: %d\n", | ||
1182 | ret); | ||
1183 | return ret; | ||
1184 | } | ||
1185 | |||
1186 | algs = be32_to_cpu(id.algs); | ||
1187 | dev_info(codec->dev, "Firmware: %x v%d.%d.%d, %d algorithms\n", | ||
1188 | be32_to_cpu(id.fw.id), | ||
1189 | (be32_to_cpu(id.fw.ver) & 0xff000) >> 16, | ||
1190 | (be32_to_cpu(id.fw.ver) & 0xff00) >> 8, | ||
1191 | be32_to_cpu(id.fw.ver) & 0xff, | ||
1192 | algs); | ||
1193 | |||
1194 | /* Read the terminator first to validate the length */ | ||
1195 | ret = regmap_raw_read(regmap, dm + | ||
1196 | (sizeof(id) + (algs * sizeof(*alg))) / 2, | ||
1197 | &val, sizeof(val)); | ||
1198 | if (ret != 0) { | ||
1199 | dev_err(codec->dev, "Failed to read algorithm list end: %d\n", | ||
1200 | ret); | ||
1201 | return ret; | ||
1202 | } | ||
1203 | |||
1204 | if (be32_to_cpu(val) != 0xbedead) | ||
1205 | dev_warn(codec->dev, "Algorithm list end %x 0x%x != 0xbeadead\n", | ||
1206 | (sizeof(id) + (algs * sizeof(*alg))) / 2, | ||
1207 | be32_to_cpu(val)); | ||
1208 | |||
1209 | alg = kzalloc(sizeof(*alg) * algs, GFP_KERNEL); | ||
1210 | if (!alg) | ||
1211 | return -ENOMEM; | ||
1212 | |||
1213 | ret = regmap_raw_read(regmap, dm + (sizeof(id) / 2), | ||
1214 | alg, algs * sizeof(*alg)); | ||
1215 | if (ret != 0) { | ||
1216 | dev_err(codec->dev, "Failed to read algorithm list: %d\n", | ||
1217 | ret); | ||
1218 | goto out; | ||
1219 | } | ||
1220 | |||
1221 | for (i = 0; i < algs; i++) { | ||
1222 | dev_info(codec->dev, "%d: ID %x v%d.%d.%d\n", | ||
1223 | i, be32_to_cpu(alg[i].alg.id), | ||
1224 | (be32_to_cpu(alg[i].alg.ver) & 0xff000) >> 16, | ||
1225 | (be32_to_cpu(alg[i].alg.ver) & 0xff00) >> 8, | ||
1226 | be32_to_cpu(alg[i].alg.ver) & 0xff); | ||
1227 | } | ||
1228 | |||
1229 | out: | ||
1230 | kfree(alg); | ||
1231 | return ret; | ||
1232 | } | ||
1233 | |||
1234 | static int wm2200_load_coeff(struct snd_soc_codec *codec, int base) | ||
1235 | { | ||
1236 | struct regmap *regmap = codec->control_data; | ||
1237 | struct wmfw_coeff_hdr *hdr; | ||
1238 | struct wmfw_coeff_item *blk; | ||
1239 | const struct firmware *firmware; | ||
1240 | const char *file, *region_name; | ||
1241 | int ret, dm, pm, zm, pos, blocks, type, offset, reg; | ||
1242 | |||
1243 | switch (base) { | ||
1244 | case WM2200_DSP1_CONTROL_1: | ||
1245 | file = "wm2200-dsp1.bin"; | ||
1246 | dm = WM2200_DSP1_DM_BASE; | ||
1247 | pm = WM2200_DSP1_PM_BASE; | ||
1248 | zm = WM2200_DSP1_ZM_BASE; | ||
1249 | break; | ||
1250 | case WM2200_DSP2_CONTROL_1: | ||
1251 | file = "wm2200-dsp2.bin"; | ||
1252 | dm = WM2200_DSP2_DM_BASE; | ||
1253 | pm = WM2200_DSP2_PM_BASE; | ||
1254 | zm = WM2200_DSP2_ZM_BASE; | ||
1255 | break; | ||
1256 | default: | ||
1257 | dev_err(codec->dev, "BASE %x\n", base); | ||
1258 | BUG_ON(1); | ||
1259 | return -EINVAL; | ||
1260 | } | ||
1261 | |||
1262 | ret = request_firmware(&firmware, file, codec->dev); | ||
1263 | if (ret != 0) { | ||
1264 | dev_err(codec->dev, "Failed to request '%s'\n", file); | ||
1265 | return ret; | ||
1266 | } | ||
1267 | |||
1268 | if (sizeof(*hdr) >= firmware->size) { | ||
1269 | dev_err(codec->dev, "%s: file too short, %d bytes\n", | ||
1270 | file, firmware->size); | ||
1271 | return -EINVAL; | ||
1272 | } | ||
1273 | |||
1274 | hdr = (void*)&firmware->data[0]; | ||
1275 | if (memcmp(hdr->magic, "WMDR", 4) != 0) { | ||
1276 | dev_err(codec->dev, "%s: invalid magic\n", file); | ||
1277 | return -EINVAL; | ||
1278 | } | ||
1279 | |||
1280 | dev_dbg(codec->dev, "%s: v%d.%d.%d\n", file, | ||
1281 | (le32_to_cpu(hdr->ver) >> 16) & 0xff, | ||
1282 | (le32_to_cpu(hdr->ver) >> 8) & 0xff, | ||
1283 | le32_to_cpu(hdr->ver) & 0xff); | ||
1284 | |||
1285 | pos = le32_to_cpu(hdr->len); | ||
1286 | |||
1287 | blocks = 0; | ||
1288 | while (pos < firmware->size && | ||
1289 | pos - firmware->size > sizeof(*blk)) { | ||
1290 | blk = (void*)(&firmware->data[pos]); | ||
1291 | |||
1292 | type = be32_to_cpu(blk->type) & 0xff; | ||
1293 | offset = le32_to_cpu(blk->offset) & 0xffffff; | ||
1294 | |||
1295 | dev_dbg(codec->dev, "%s.%d: %x v%d.%d.%d\n", | ||
1296 | file, blocks, le32_to_cpu(blk->id), | ||
1297 | (le32_to_cpu(blk->ver) >> 16) & 0xff, | ||
1298 | (le32_to_cpu(blk->ver) >> 8) & 0xff, | ||
1299 | le32_to_cpu(blk->ver) & 0xff); | ||
1300 | dev_dbg(codec->dev, "%s.%d: %d bytes at 0x%x in %x\n", | ||
1301 | file, blocks, le32_to_cpu(blk->len), offset, type); | ||
1302 | |||
1303 | reg = 0; | ||
1304 | region_name = "Unknown"; | ||
1305 | switch (type) { | ||
1306 | case WMFW_NAME_TEXT: | ||
1307 | case WMFW_INFO_TEXT: | ||
1308 | break; | ||
1309 | case WMFW_ABSOLUTE: | ||
1310 | region_name = "register"; | ||
1311 | reg = offset; | ||
1312 | break; | ||
1313 | default: | ||
1314 | dev_err(codec->dev, "Unknown region type %x\n", type); | ||
1315 | break; | ||
1316 | } | ||
1317 | |||
1318 | if (reg) { | ||
1319 | ret = regmap_raw_write(regmap, reg, blk->data, | ||
1320 | le32_to_cpu(blk->len)); | ||
1321 | if (ret != 0) { | ||
1322 | dev_err(codec->dev, | ||
1323 | "%s.%d: Failed to write to %x in %s\n", | ||
1324 | file, blocks, reg, region_name); | ||
1325 | } | ||
1326 | } | ||
1327 | |||
1328 | pos += le32_to_cpu(blk->len) + sizeof(*blk); | ||
1329 | blocks++; | ||
1330 | } | ||
1331 | |||
1332 | if (pos > firmware->size) | ||
1333 | dev_warn(codec->dev, "%s.%d: %d bytes at end of file\n", | ||
1334 | file, blocks, pos - firmware->size); | ||
1335 | |||
1336 | return 0; | ||
1337 | } | ||
1338 | |||
1153 | static int wm2200_dsp_ev(struct snd_soc_dapm_widget *w, | 1339 | static int wm2200_dsp_ev(struct snd_soc_dapm_widget *w, |
1154 | struct snd_kcontrol *kcontrol, | 1340 | struct snd_kcontrol *kcontrol, |
1155 | int event) | 1341 | int event) |
@@ -1164,6 +1350,14 @@ static int wm2200_dsp_ev(struct snd_soc_dapm_widget *w, | |||
1164 | if (ret != 0) | 1350 | if (ret != 0) |
1165 | return ret; | 1351 | return ret; |
1166 | 1352 | ||
1353 | ret = wm2200_setup_algs(codec, base); | ||
1354 | if (ret != 0) | ||
1355 | return ret; | ||
1356 | |||
1357 | ret = wm2200_load_coeff(codec, base); | ||
1358 | if (ret != 0) | ||
1359 | return ret; | ||
1360 | |||
1167 | /* Start the core running */ | 1361 | /* Start the core running */ |
1168 | snd_soc_update_bits(codec, w->reg, | 1362 | snd_soc_update_bits(codec, w->reg, |
1169 | WM2200_DSP1_CORE_ENA | WM2200_DSP1_START, | 1363 | WM2200_DSP1_CORE_ENA | WM2200_DSP1_START, |
diff --git a/sound/soc/codecs/wmfw.h b/sound/soc/codecs/wmfw.h index ef37316f0643..5791f8e440ad 100644 --- a/sound/soc/codecs/wmfw.h +++ b/sound/soc/codecs/wmfw.h | |||
@@ -43,6 +43,49 @@ struct wmfw_region { | |||
43 | u8 data[]; | 43 | u8 data[]; |
44 | } __packed; | 44 | } __packed; |
45 | 45 | ||
46 | struct wmfw_id_hdr { | ||
47 | __be32 core_id; | ||
48 | __be32 core_rev; | ||
49 | __be32 id; | ||
50 | __be32 ver; | ||
51 | } __packed; | ||
52 | |||
53 | struct wmfw_adsp1_id_hdr { | ||
54 | struct wmfw_id_hdr fw; | ||
55 | __be32 zm; | ||
56 | __be32 dm; | ||
57 | __be32 algs; | ||
58 | } __packed; | ||
59 | |||
60 | struct wmfw_alg_hdr { | ||
61 | __be32 id; | ||
62 | __be32 ver; | ||
63 | } __packed; | ||
64 | |||
65 | struct wmfw_adsp1_alg_hdr { | ||
66 | struct wmfw_alg_hdr alg; | ||
67 | __be32 zm; | ||
68 | __be32 dm; | ||
69 | } __packed; | ||
70 | |||
71 | struct wmfw_coeff_hdr { | ||
72 | u8 magic[4]; | ||
73 | __le32 len; | ||
74 | __le32 ver; | ||
75 | u8 data[]; | ||
76 | } __packed; | ||
77 | |||
78 | struct wmfw_coeff_item { | ||
79 | union { | ||
80 | __be32 type; | ||
81 | __le32 offset; | ||
82 | }; | ||
83 | __le32 id; | ||
84 | __le32 ver; | ||
85 | __le32 sr; | ||
86 | __le32 len; | ||
87 | u8 data[]; | ||
88 | } __packed; | ||
46 | #define WMFW_ADSP1 1 | 89 | #define WMFW_ADSP1 1 |
47 | 90 | ||
48 | #define WMFW_ABSOLUTE 0xf0 | 91 | #define WMFW_ABSOLUTE 0xf0 |