diff options
author | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2012-10-04 11:31:52 -0400 |
---|---|---|
committer | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2012-10-05 15:17:55 -0400 |
commit | e10f871190ce2f912317c874a56b9cc417e46e84 (patch) | |
tree | 4a8eaddb1f41889bfd39e52b3865c12abc801c23 /sound | |
parent | 09d5d5880eb436d623013a1e3c32ad33ae8e6b09 (diff) |
ASoC: wm2200: Initial DSP support
Support download and execution of firmwares to the DSPs on the WM2200.
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/codecs/wm2200.c | 240 | ||||
-rw-r--r-- | sound/soc/codecs/wmfw.h | 56 |
2 files changed, 294 insertions, 2 deletions
diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c index f24ef0aeeb32..e3f549b65b08 100644 --- a/sound/soc/codecs/wm2200.c +++ b/sound/soc/codecs/wm2200.c | |||
@@ -15,6 +15,7 @@ | |||
15 | #include <linux/init.h> | 15 | #include <linux/init.h> |
16 | #include <linux/delay.h> | 16 | #include <linux/delay.h> |
17 | #include <linux/pm.h> | 17 | #include <linux/pm.h> |
18 | #include <linux/firmware.h> | ||
18 | #include <linux/gcd.h> | 19 | #include <linux/gcd.h> |
19 | #include <linux/gpio.h> | 20 | #include <linux/gpio.h> |
20 | #include <linux/i2c.h> | 21 | #include <linux/i2c.h> |
@@ -32,6 +33,39 @@ | |||
32 | #include <sound/wm2200.h> | 33 | #include <sound/wm2200.h> |
33 | 34 | ||
34 | #include "wm2200.h" | 35 | #include "wm2200.h" |
36 | #include "wmfw.h" | ||
37 | |||
38 | #define WM2200_DSP_CONTROL_1 0x00 | ||
39 | #define WM2200_DSP_CONTROL_2 0x02 | ||
40 | #define WM2200_DSP_CONTROL_3 0x03 | ||
41 | #define WM2200_DSP_CONTROL_4 0x04 | ||
42 | #define WM2200_DSP_CONTROL_5 0x06 | ||
43 | #define WM2200_DSP_CONTROL_6 0x07 | ||
44 | #define WM2200_DSP_CONTROL_7 0x08 | ||
45 | #define WM2200_DSP_CONTROL_8 0x09 | ||
46 | #define WM2200_DSP_CONTROL_9 0x0A | ||
47 | #define WM2200_DSP_CONTROL_10 0x0B | ||
48 | #define WM2200_DSP_CONTROL_11 0x0C | ||
49 | #define WM2200_DSP_CONTROL_12 0x0D | ||
50 | #define WM2200_DSP_CONTROL_13 0x0F | ||
51 | #define WM2200_DSP_CONTROL_14 0x10 | ||
52 | #define WM2200_DSP_CONTROL_15 0x11 | ||
53 | #define WM2200_DSP_CONTROL_16 0x12 | ||
54 | #define WM2200_DSP_CONTROL_17 0x13 | ||
55 | #define WM2200_DSP_CONTROL_18 0x14 | ||
56 | #define WM2200_DSP_CONTROL_19 0x16 | ||
57 | #define WM2200_DSP_CONTROL_20 0x17 | ||
58 | #define WM2200_DSP_CONTROL_21 0x18 | ||
59 | #define WM2200_DSP_CONTROL_22 0x1A | ||
60 | #define WM2200_DSP_CONTROL_23 0x1B | ||
61 | #define WM2200_DSP_CONTROL_24 0x1C | ||
62 | #define WM2200_DSP_CONTROL_25 0x1E | ||
63 | #define WM2200_DSP_CONTROL_26 0x20 | ||
64 | #define WM2200_DSP_CONTROL_27 0x21 | ||
65 | #define WM2200_DSP_CONTROL_28 0x22 | ||
66 | #define WM2200_DSP_CONTROL_29 0x23 | ||
67 | #define WM2200_DSP_CONTROL_30 0x24 | ||
68 | #define WM2200_DSP_CONTROL_31 0x26 | ||
35 | 69 | ||
36 | /* The code assumes DCVDD is generated internally */ | 70 | /* The code assumes DCVDD is generated internally */ |
37 | #define WM2200_NUM_CORE_SUPPLIES 2 | 71 | #define WM2200_NUM_CORE_SUPPLIES 2 |
@@ -953,6 +987,206 @@ static int wm2200_reset(struct wm2200_priv *wm2200) | |||
953 | } | 987 | } |
954 | } | 988 | } |
955 | 989 | ||
990 | static int wm2200_dsp_load(struct snd_soc_codec *codec, int base) | ||
991 | { | ||
992 | const struct firmware *firmware; | ||
993 | struct regmap *regmap = codec->control_data; | ||
994 | unsigned int pos = 0; | ||
995 | const struct wmfw_header *header; | ||
996 | const struct wmfw_adsp1_sizes *adsp1_sizes; | ||
997 | const struct wmfw_footer *footer; | ||
998 | const struct wmfw_region *region; | ||
999 | const char *file, *region_name; | ||
1000 | char *text; | ||
1001 | unsigned int dm, pm, zm, reg; | ||
1002 | int regions = 0; | ||
1003 | int ret, offset, type; | ||
1004 | |||
1005 | switch (base) { | ||
1006 | case WM2200_DSP1_CONTROL_1: | ||
1007 | file = "wm2200-dsp1.wmfw"; | ||
1008 | dm = WM2200_DSP1_DM_BASE; | ||
1009 | pm = WM2200_DSP1_PM_BASE; | ||
1010 | zm = WM2200_DSP1_ZM_BASE; | ||
1011 | break; | ||
1012 | case WM2200_DSP2_CONTROL_1: | ||
1013 | file = "wm2200-dsp2.wmfw"; | ||
1014 | dm = WM2200_DSP2_DM_BASE; | ||
1015 | pm = WM2200_DSP2_PM_BASE; | ||
1016 | zm = WM2200_DSP2_ZM_BASE; | ||
1017 | break; | ||
1018 | default: | ||
1019 | dev_err(codec->dev, "BASE %x\n", base); | ||
1020 | BUG_ON(1); | ||
1021 | return -EINVAL; | ||
1022 | } | ||
1023 | |||
1024 | ret = request_firmware(&firmware, file, codec->dev); | ||
1025 | if (ret != 0) { | ||
1026 | dev_err(codec->dev, "Failed to request '%s'\n", file); | ||
1027 | return ret; | ||
1028 | } | ||
1029 | |||
1030 | pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer); | ||
1031 | if (pos >= firmware->size) { | ||
1032 | dev_err(codec->dev, "%s: file too short, %d bytes\n", | ||
1033 | file, firmware->size); | ||
1034 | return -EINVAL; | ||
1035 | } | ||
1036 | |||
1037 | header = (void*)&firmware->data[0]; | ||
1038 | |||
1039 | if (memcmp(&header->magic[0], "WMFW", 4) != 0) { | ||
1040 | dev_err(codec->dev, "%s: invalid magic\n", file); | ||
1041 | return -EINVAL; | ||
1042 | } | ||
1043 | |||
1044 | if (header->ver != 0) { | ||
1045 | dev_err(codec->dev, "%s: unknown file format %d\n", | ||
1046 | file, header->ver); | ||
1047 | return -EINVAL; | ||
1048 | } | ||
1049 | |||
1050 | if (le32_to_cpu(header->len) != sizeof(*header) + | ||
1051 | sizeof(*adsp1_sizes) + sizeof(*footer)) { | ||
1052 | dev_err(codec->dev, "%s: unexpected header length %d\n", | ||
1053 | file, le32_to_cpu(header->len)); | ||
1054 | return -EINVAL; | ||
1055 | } | ||
1056 | |||
1057 | if (header->core != WMFW_ADSP1) { | ||
1058 | dev_err(codec->dev, "%s: invalid core %d\n", | ||
1059 | file, header->core); | ||
1060 | return -EINVAL; | ||
1061 | } | ||
1062 | |||
1063 | adsp1_sizes = (void *)&(header[1]); | ||
1064 | footer = (void *)&(adsp1_sizes[1]); | ||
1065 | |||
1066 | dev_dbg(codec->dev, "%s: %d DM, %d PM, %d ZM\n", | ||
1067 | file, le32_to_cpu(adsp1_sizes->dm), | ||
1068 | le32_to_cpu(adsp1_sizes->pm), le32_to_cpu(adsp1_sizes->zm)); | ||
1069 | |||
1070 | dev_dbg(codec->dev, "%s: timestamp %llu\n", file, | ||
1071 | le64_to_cpu(footer->timestamp)); | ||
1072 | |||
1073 | while (pos < firmware->size && | ||
1074 | pos - firmware->size > sizeof(*region)) { | ||
1075 | region = (void *)&(firmware->data[pos]); | ||
1076 | region_name = "Unknown"; | ||
1077 | reg = 0; | ||
1078 | text = NULL; | ||
1079 | offset = le32_to_cpu(region->offset) & 0xffffff; | ||
1080 | type = be32_to_cpu(region->type) & 0xff; | ||
1081 | |||
1082 | switch (type) { | ||
1083 | case WMFW_NAME_TEXT: | ||
1084 | region_name = "Firmware name"; | ||
1085 | text = kzalloc(le32_to_cpu(region->len) + 1, | ||
1086 | GFP_KERNEL); | ||
1087 | break; | ||
1088 | case WMFW_INFO_TEXT: | ||
1089 | region_name = "Information"; | ||
1090 | text = kzalloc(le32_to_cpu(region->len) + 1, | ||
1091 | GFP_KERNEL); | ||
1092 | break; | ||
1093 | case WMFW_ABSOLUTE: | ||
1094 | region_name = "Absolute"; | ||
1095 | reg = offset; | ||
1096 | break; | ||
1097 | case WMFW_ADSP1_PM: | ||
1098 | region_name = "PM"; | ||
1099 | reg = pm + (offset * 3); | ||
1100 | break; | ||
1101 | case WMFW_ADSP1_DM: | ||
1102 | region_name = "DM"; | ||
1103 | reg = dm + (offset * 2); | ||
1104 | break; | ||
1105 | case WMFW_ADSP1_ZM: | ||
1106 | region_name = "ZM"; | ||
1107 | reg = zm + (offset * 2); | ||
1108 | break; | ||
1109 | default: | ||
1110 | dev_warn(codec->dev, | ||
1111 | "%s.%d: Unknown region type %x at %d(%x)\n", | ||
1112 | file, regions, type, pos, pos); | ||
1113 | break; | ||
1114 | } | ||
1115 | |||
1116 | dev_dbg(codec->dev, "%s.%d: %d bytes at %d in %s\n", file, | ||
1117 | regions, le32_to_cpu(region->len), offset, | ||
1118 | region_name); | ||
1119 | |||
1120 | if (text) { | ||
1121 | memcpy(text, region->data, le32_to_cpu(region->len)); | ||
1122 | dev_info(codec->dev, "%s: %s\n", file, text); | ||
1123 | kfree(text); | ||
1124 | } | ||
1125 | |||
1126 | if (reg) { | ||
1127 | ret = regmap_raw_write(regmap, reg, region->data, | ||
1128 | le32_to_cpu(region->len)); | ||
1129 | if (ret != 0) { | ||
1130 | dev_err(codec->dev, | ||
1131 | "%s.%d: Failed to write %d bytes at %d in %s: %d\n", | ||
1132 | file, regions, | ||
1133 | le32_to_cpu(region->len), offset, | ||
1134 | region_name, ret); | ||
1135 | goto out; | ||
1136 | } | ||
1137 | } | ||
1138 | |||
1139 | pos += le32_to_cpu(region->len) + sizeof(*region); | ||
1140 | regions++; | ||
1141 | } | ||
1142 | |||
1143 | if (pos > firmware->size) | ||
1144 | dev_warn(codec->dev, "%s.%d: %d bytes at end of file\n", | ||
1145 | file, regions, pos - firmware->size); | ||
1146 | |||
1147 | out: | ||
1148 | release_firmware(firmware); | ||
1149 | |||
1150 | return ret; | ||
1151 | } | ||
1152 | |||
1153 | static int wm2200_dsp_ev(struct snd_soc_dapm_widget *w, | ||
1154 | struct snd_kcontrol *kcontrol, | ||
1155 | int event) | ||
1156 | { | ||
1157 | struct snd_soc_codec *codec = w->codec; | ||
1158 | int base = w->reg - WM2200_DSP_CONTROL_30; | ||
1159 | int ret; | ||
1160 | |||
1161 | switch (event) { | ||
1162 | case SND_SOC_DAPM_POST_PMU: | ||
1163 | ret = wm2200_dsp_load(codec, base); | ||
1164 | if (ret != 0) | ||
1165 | return ret; | ||
1166 | |||
1167 | /* Start the core running */ | ||
1168 | snd_soc_update_bits(codec, w->reg, | ||
1169 | WM2200_DSP1_CORE_ENA | WM2200_DSP1_START, | ||
1170 | WM2200_DSP1_CORE_ENA | WM2200_DSP1_START); | ||
1171 | break; | ||
1172 | |||
1173 | case SND_SOC_DAPM_PRE_PMD: | ||
1174 | /* Halt the core */ | ||
1175 | snd_soc_update_bits(codec, w->reg, | ||
1176 | WM2200_DSP1_CORE_ENA | WM2200_DSP1_START, | ||
1177 | 0); | ||
1178 | |||
1179 | snd_soc_update_bits(codec, base + WM2200_DSP_CONTROL_19, | ||
1180 | WM2200_DSP1_WDMA_BUFFER_LENGTH_MASK, 0); | ||
1181 | break; | ||
1182 | |||
1183 | default: | ||
1184 | break; | ||
1185 | } | ||
1186 | |||
1187 | return 0; | ||
1188 | } | ||
1189 | |||
956 | static DECLARE_TLV_DB_SCALE(in_tlv, -6300, 100, 0); | 1190 | static DECLARE_TLV_DB_SCALE(in_tlv, -6300, 100, 0); |
957 | static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0); | 1191 | static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0); |
958 | static DECLARE_TLV_DB_SCALE(out_tlv, -6400, 100, 0); | 1192 | static DECLARE_TLV_DB_SCALE(out_tlv, -6400, 100, 0); |
@@ -1301,9 +1535,11 @@ SND_SOC_DAPM_PGA("LHPF2", WM2200_HPLPF2_1, WM2200_LHPF2_ENA_SHIFT, 0, | |||
1301 | NULL, 0), | 1535 | NULL, 0), |
1302 | 1536 | ||
1303 | SND_SOC_DAPM_PGA_E("DSP1", WM2200_DSP1_CONTROL_30, WM2200_DSP1_SYS_ENA_SHIFT, | 1537 | SND_SOC_DAPM_PGA_E("DSP1", WM2200_DSP1_CONTROL_30, WM2200_DSP1_SYS_ENA_SHIFT, |
1304 | 0, NULL, 0, NULL, 0), | 1538 | 0, NULL, 0, wm2200_dsp_ev, |
1539 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), | ||
1305 | SND_SOC_DAPM_PGA_E("DSP2", WM2200_DSP2_CONTROL_30, WM2200_DSP2_SYS_ENA_SHIFT, | 1540 | SND_SOC_DAPM_PGA_E("DSP2", WM2200_DSP2_CONTROL_30, WM2200_DSP2_SYS_ENA_SHIFT, |
1306 | 0, NULL, 0, NULL, 0), | 1541 | 0, NULL, 0, wm2200_dsp_ev, |
1542 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), | ||
1307 | 1543 | ||
1308 | SND_SOC_DAPM_AIF_OUT("AIF1TX1", "Capture", 0, | 1544 | SND_SOC_DAPM_AIF_OUT("AIF1TX1", "Capture", 0, |
1309 | WM2200_AUDIO_IF_1_22, WM2200_AIF1TX1_ENA_SHIFT, 0), | 1545 | WM2200_AUDIO_IF_1_22, WM2200_AIF1TX1_ENA_SHIFT, 0), |
diff --git a/sound/soc/codecs/wmfw.h b/sound/soc/codecs/wmfw.h new file mode 100644 index 000000000000..ef37316f0643 --- /dev/null +++ b/sound/soc/codecs/wmfw.h | |||
@@ -0,0 +1,56 @@ | |||
1 | /* | ||
2 | * wmfw.h - Wolfson firmware format information | ||
3 | * | ||
4 | * Copyright 2012 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 | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | */ | ||
12 | |||
13 | #ifndef __WMFW_H | ||
14 | #define __WMFW_H | ||
15 | |||
16 | #include <linux/types.h> | ||
17 | |||
18 | struct wmfw_header { | ||
19 | char magic[4]; | ||
20 | __le32 len; | ||
21 | __le16 rev; | ||
22 | u8 core; | ||
23 | u8 ver; | ||
24 | } __packed; | ||
25 | |||
26 | struct wmfw_footer { | ||
27 | __le64 timestamp; | ||
28 | __le32 checksum; | ||
29 | } __packed; | ||
30 | |||
31 | struct wmfw_adsp1_sizes { | ||
32 | __le32 dm; | ||
33 | __le32 pm; | ||
34 | __le32 zm; | ||
35 | } __packed; | ||
36 | |||
37 | struct wmfw_region { | ||
38 | union { | ||
39 | __be32 type; | ||
40 | __le32 offset; | ||
41 | }; | ||
42 | __le32 len; | ||
43 | u8 data[]; | ||
44 | } __packed; | ||
45 | |||
46 | #define WMFW_ADSP1 1 | ||
47 | |||
48 | #define WMFW_ABSOLUTE 0xf0 | ||
49 | #define WMFW_NAME_TEXT 0xfe | ||
50 | #define WMFW_INFO_TEXT 0xff | ||
51 | |||
52 | #define WMFW_ADSP1_PM 2 | ||
53 | #define WMFW_ADSP1_DM 3 | ||
54 | #define WMFW_ADSP1_ZM 4 | ||
55 | |||
56 | #endif | ||