diff options
Diffstat (limited to 'sound/soc/codecs/wm8961.c')
-rw-r--r-- | sound/soc/codecs/wm8961.c | 237 |
1 files changed, 73 insertions, 164 deletions
diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index 2549d3a297a..4f326f60410 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c | |||
@@ -288,7 +288,7 @@ static u16 wm8961_reg_defaults[] = { | |||
288 | }; | 288 | }; |
289 | 289 | ||
290 | struct wm8961_priv { | 290 | struct wm8961_priv { |
291 | struct snd_soc_codec codec; | 291 | enum snd_soc_control_type control_type; |
292 | int sysclk; | 292 | int sysclk; |
293 | u16 reg_cache[WM8961_MAX_REGISTER]; | 293 | u16 reg_cache[WM8961_MAX_REGISTER]; |
294 | }; | 294 | }; |
@@ -940,8 +940,8 @@ static struct snd_soc_dai_ops wm8961_dai_ops = { | |||
940 | .set_clkdiv = wm8961_set_clkdiv, | 940 | .set_clkdiv = wm8961_set_clkdiv, |
941 | }; | 941 | }; |
942 | 942 | ||
943 | struct snd_soc_dai wm8961_dai = { | 943 | static struct snd_soc_dai_driver wm8961_dai = { |
944 | .name = "WM8961", | 944 | .name = "wm8961-hifi", |
945 | .playback = { | 945 | .playback = { |
946 | .stream_name = "HiFi Playback", | 946 | .stream_name = "HiFi Playback", |
947 | .channels_min = 1, | 947 | .channels_min = 1, |
@@ -956,140 +956,22 @@ struct snd_soc_dai wm8961_dai = { | |||
956 | .formats = WM8961_FORMATS,}, | 956 | .formats = WM8961_FORMATS,}, |
957 | .ops = &wm8961_dai_ops, | 957 | .ops = &wm8961_dai_ops, |
958 | }; | 958 | }; |
959 | EXPORT_SYMBOL_GPL(wm8961_dai); | ||
960 | 959 | ||
961 | 960 | static int wm8961_probe(struct snd_soc_codec *codec) | |
962 | static struct snd_soc_codec *wm8961_codec; | ||
963 | |||
964 | static int wm8961_probe(struct platform_device *pdev) | ||
965 | { | 961 | { |
966 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
967 | struct snd_soc_codec *codec; | ||
968 | int ret = 0; | 962 | int ret = 0; |
969 | |||
970 | if (wm8961_codec == NULL) { | ||
971 | dev_err(&pdev->dev, "Codec device not registered\n"); | ||
972 | return -ENODEV; | ||
973 | } | ||
974 | |||
975 | socdev->card->codec = wm8961_codec; | ||
976 | codec = wm8961_codec; | ||
977 | |||
978 | /* register pcms */ | ||
979 | ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); | ||
980 | if (ret < 0) { | ||
981 | dev_err(codec->dev, "failed to create pcms: %d\n", ret); | ||
982 | goto pcm_err; | ||
983 | } | ||
984 | |||
985 | snd_soc_add_controls(codec, wm8961_snd_controls, | ||
986 | ARRAY_SIZE(wm8961_snd_controls)); | ||
987 | snd_soc_dapm_new_controls(codec, wm8961_dapm_widgets, | ||
988 | ARRAY_SIZE(wm8961_dapm_widgets)); | ||
989 | snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); | ||
990 | |||
991 | return ret; | ||
992 | |||
993 | pcm_err: | ||
994 | return ret; | ||
995 | } | ||
996 | |||
997 | static int wm8961_remove(struct platform_device *pdev) | ||
998 | { | ||
999 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
1000 | |||
1001 | snd_soc_free_pcms(socdev); | ||
1002 | snd_soc_dapm_free(socdev); | ||
1003 | |||
1004 | return 0; | ||
1005 | } | ||
1006 | |||
1007 | #ifdef CONFIG_PM | ||
1008 | static int wm8961_suspend(struct platform_device *pdev, pm_message_t state) | ||
1009 | { | ||
1010 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
1011 | struct snd_soc_codec *codec = socdev->card->codec; | ||
1012 | |||
1013 | wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF); | ||
1014 | |||
1015 | return 0; | ||
1016 | } | ||
1017 | |||
1018 | static int wm8961_resume(struct platform_device *pdev) | ||
1019 | { | ||
1020 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
1021 | struct snd_soc_codec *codec = socdev->card->codec; | ||
1022 | u16 *reg_cache = codec->reg_cache; | ||
1023 | int i; | ||
1024 | |||
1025 | for (i = 0; i < codec->reg_cache_size; i++) { | ||
1026 | if (reg_cache[i] == wm8961_reg_defaults[i]) | ||
1027 | continue; | ||
1028 | |||
1029 | if (i == WM8961_SOFTWARE_RESET) | ||
1030 | continue; | ||
1031 | |||
1032 | snd_soc_write(codec, i, reg_cache[i]); | ||
1033 | } | ||
1034 | |||
1035 | wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); | ||
1036 | |||
1037 | return 0; | ||
1038 | } | ||
1039 | #else | ||
1040 | #define wm8961_suspend NULL | ||
1041 | #define wm8961_resume NULL | ||
1042 | #endif | ||
1043 | |||
1044 | struct snd_soc_codec_device soc_codec_dev_wm8961 = { | ||
1045 | .probe = wm8961_probe, | ||
1046 | .remove = wm8961_remove, | ||
1047 | .suspend = wm8961_suspend, | ||
1048 | .resume = wm8961_resume, | ||
1049 | }; | ||
1050 | EXPORT_SYMBOL_GPL(soc_codec_dev_wm8961); | ||
1051 | |||
1052 | static int wm8961_register(struct wm8961_priv *wm8961) | ||
1053 | { | ||
1054 | struct snd_soc_codec *codec = &wm8961->codec; | ||
1055 | int ret; | ||
1056 | u16 reg; | 963 | u16 reg; |
1057 | 964 | ||
1058 | if (wm8961_codec) { | ||
1059 | dev_err(codec->dev, "Another WM8961 is registered\n"); | ||
1060 | ret = -EINVAL; | ||
1061 | goto err; | ||
1062 | } | ||
1063 | |||
1064 | mutex_init(&codec->mutex); | ||
1065 | INIT_LIST_HEAD(&codec->dapm_widgets); | ||
1066 | INIT_LIST_HEAD(&codec->dapm_paths); | ||
1067 | |||
1068 | snd_soc_codec_set_drvdata(codec, wm8961); | ||
1069 | codec->name = "WM8961"; | ||
1070 | codec->owner = THIS_MODULE; | ||
1071 | codec->dai = &wm8961_dai; | ||
1072 | codec->num_dai = 1; | ||
1073 | codec->reg_cache_size = ARRAY_SIZE(wm8961->reg_cache); | ||
1074 | codec->reg_cache = &wm8961->reg_cache; | ||
1075 | codec->bias_level = SND_SOC_BIAS_OFF; | ||
1076 | codec->set_bias_level = wm8961_set_bias_level; | ||
1077 | codec->volatile_register = wm8961_volatile_register; | ||
1078 | |||
1079 | memcpy(codec->reg_cache, wm8961_reg_defaults, | ||
1080 | sizeof(wm8961_reg_defaults)); | ||
1081 | |||
1082 | ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); | 965 | ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); |
1083 | if (ret != 0) { | 966 | if (ret != 0) { |
1084 | dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); | 967 | dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); |
1085 | goto err; | 968 | return ret; |
1086 | } | 969 | } |
1087 | 970 | ||
1088 | reg = snd_soc_read(codec, WM8961_SOFTWARE_RESET); | 971 | reg = snd_soc_read(codec, WM8961_SOFTWARE_RESET); |
1089 | if (reg != 0x1801) { | 972 | if (reg != 0x1801) { |
1090 | dev_err(codec->dev, "Device is not a WM8961: ID=0x%x\n", reg); | 973 | dev_err(codec->dev, "Device is not a WM8961: ID=0x%x\n", reg); |
1091 | ret = -EINVAL; | 974 | return -EINVAL; |
1092 | goto err; | ||
1093 | } | 975 | } |
1094 | 976 | ||
1095 | /* This isn't volatile - readback doesn't correspond to write */ | 977 | /* This isn't volatile - readback doesn't correspond to write */ |
@@ -1102,7 +984,7 @@ static int wm8961_register(struct wm8961_priv *wm8961) | |||
1102 | ret = wm8961_reset(codec); | 984 | ret = wm8961_reset(codec); |
1103 | if (ret < 0) { | 985 | if (ret < 0) { |
1104 | dev_err(codec->dev, "Failed to issue reset\n"); | 986 | dev_err(codec->dev, "Failed to issue reset\n"); |
1105 | goto err; | 987 | return ret; |
1106 | } | 988 | } |
1107 | 989 | ||
1108 | /* Enable class W */ | 990 | /* Enable class W */ |
@@ -1140,64 +1022,89 @@ static int wm8961_register(struct wm8961_priv *wm8961) | |||
1140 | 1022 | ||
1141 | wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); | 1023 | wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); |
1142 | 1024 | ||
1143 | wm8961_dai.dev = codec->dev; | 1025 | snd_soc_add_controls(codec, wm8961_snd_controls, |
1026 | ARRAY_SIZE(wm8961_snd_controls)); | ||
1027 | snd_soc_dapm_new_controls(codec, wm8961_dapm_widgets, | ||
1028 | ARRAY_SIZE(wm8961_dapm_widgets)); | ||
1029 | snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); | ||
1144 | 1030 | ||
1145 | wm8961_codec = codec; | 1031 | return 0; |
1032 | } | ||
1146 | 1033 | ||
1147 | ret = snd_soc_register_codec(codec); | 1034 | static int wm8961_remove(struct snd_soc_codec *codec) |
1148 | if (ret != 0) { | 1035 | { |
1149 | dev_err(codec->dev, "Failed to register codec: %d\n", ret); | 1036 | wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF); |
1150 | goto err; | 1037 | return 0; |
1151 | } | 1038 | } |
1152 | 1039 | ||
1153 | ret = snd_soc_register_dai(&wm8961_dai); | 1040 | #ifdef CONFIG_PM |
1154 | if (ret != 0) { | 1041 | static int wm8961_suspend(struct snd_soc_codec *codec, pm_message_t state) |
1155 | dev_err(codec->dev, "Failed to register DAI: %d\n", ret); | 1042 | { |
1156 | goto err_codec; | 1043 | wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF); |
1157 | } | ||
1158 | 1044 | ||
1159 | return 0; | 1045 | return 0; |
1160 | |||
1161 | err_codec: | ||
1162 | snd_soc_unregister_codec(codec); | ||
1163 | err: | ||
1164 | kfree(wm8961); | ||
1165 | return ret; | ||
1166 | } | 1046 | } |
1167 | 1047 | ||
1168 | static void wm8961_unregister(struct wm8961_priv *wm8961) | 1048 | static int wm8961_resume(struct snd_soc_codec *codec) |
1169 | { | 1049 | { |
1170 | wm8961_set_bias_level(&wm8961->codec, SND_SOC_BIAS_OFF); | 1050 | u16 *reg_cache = codec->reg_cache; |
1171 | snd_soc_unregister_dai(&wm8961_dai); | 1051 | int i; |
1172 | snd_soc_unregister_codec(&wm8961->codec); | 1052 | |
1173 | kfree(wm8961); | 1053 | for (i = 0; i < codec->driver->reg_cache_size; i++) { |
1174 | wm8961_codec = NULL; | 1054 | if (reg_cache[i] == wm8961_reg_defaults[i]) |
1055 | continue; | ||
1056 | |||
1057 | if (i == WM8961_SOFTWARE_RESET) | ||
1058 | continue; | ||
1059 | |||
1060 | snd_soc_write(codec, i, reg_cache[i]); | ||
1061 | } | ||
1062 | |||
1063 | wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); | ||
1064 | |||
1065 | return 0; | ||
1175 | } | 1066 | } |
1067 | #else | ||
1068 | #define wm8961_suspend NULL | ||
1069 | #define wm8961_resume NULL | ||
1070 | #endif | ||
1071 | |||
1072 | static struct snd_soc_codec_driver soc_codec_dev_wm8961 = { | ||
1073 | .probe = wm8961_probe, | ||
1074 | .remove = wm8961_remove, | ||
1075 | .suspend = wm8961_suspend, | ||
1076 | .resume = wm8961_resume, | ||
1077 | .set_bias_level = wm8961_set_bias_level, | ||
1078 | .reg_cache_size = ARRAY_SIZE(wm8961_reg_defaults), | ||
1079 | .reg_word_size = sizeof(u16), | ||
1080 | .reg_cache_default = wm8961_reg_defaults, | ||
1081 | .volatile_register = wm8961_volatile_register, | ||
1082 | }; | ||
1176 | 1083 | ||
1084 | #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) | ||
1177 | static __devinit int wm8961_i2c_probe(struct i2c_client *i2c, | 1085 | static __devinit int wm8961_i2c_probe(struct i2c_client *i2c, |
1178 | const struct i2c_device_id *id) | 1086 | const struct i2c_device_id *id) |
1179 | { | 1087 | { |
1180 | struct wm8961_priv *wm8961; | 1088 | struct wm8961_priv *wm8961; |
1181 | struct snd_soc_codec *codec; | 1089 | int ret; |
1182 | 1090 | ||
1183 | wm8961 = kzalloc(sizeof(struct wm8961_priv), GFP_KERNEL); | 1091 | wm8961 = kzalloc(sizeof(struct wm8961_priv), GFP_KERNEL); |
1184 | if (wm8961 == NULL) | 1092 | if (wm8961 == NULL) |
1185 | return -ENOMEM; | 1093 | return -ENOMEM; |
1186 | 1094 | ||
1187 | codec = &wm8961->codec; | ||
1188 | |||
1189 | i2c_set_clientdata(i2c, wm8961); | 1095 | i2c_set_clientdata(i2c, wm8961); |
1190 | codec->control_data = i2c; | ||
1191 | |||
1192 | codec->dev = &i2c->dev; | ||
1193 | 1096 | ||
1194 | return wm8961_register(wm8961); | 1097 | ret = snd_soc_register_codec(&i2c->dev, |
1098 | &soc_codec_dev_wm8961, &wm8961_dai, 1); | ||
1099 | if (ret < 0) | ||
1100 | kfree(wm8961); | ||
1101 | return ret; | ||
1195 | } | 1102 | } |
1196 | 1103 | ||
1197 | static __devexit int wm8961_i2c_remove(struct i2c_client *client) | 1104 | static __devexit int wm8961_i2c_remove(struct i2c_client *client) |
1198 | { | 1105 | { |
1199 | struct wm8961_priv *wm8961 = i2c_get_clientdata(client); | 1106 | snd_soc_unregister_codec(&client->dev); |
1200 | wm8961_unregister(wm8961); | 1107 | kfree(i2c_get_clientdata(client)); |
1201 | return 0; | 1108 | return 0; |
1202 | } | 1109 | } |
1203 | 1110 | ||
@@ -1209,35 +1116,37 @@ MODULE_DEVICE_TABLE(i2c, wm8961_i2c_id); | |||
1209 | 1116 | ||
1210 | static struct i2c_driver wm8961_i2c_driver = { | 1117 | static struct i2c_driver wm8961_i2c_driver = { |
1211 | .driver = { | 1118 | .driver = { |
1212 | .name = "wm8961", | 1119 | .name = "wm8961-codec", |
1213 | .owner = THIS_MODULE, | 1120 | .owner = THIS_MODULE, |
1214 | }, | 1121 | }, |
1215 | .probe = wm8961_i2c_probe, | 1122 | .probe = wm8961_i2c_probe, |
1216 | .remove = __devexit_p(wm8961_i2c_remove), | 1123 | .remove = __devexit_p(wm8961_i2c_remove), |
1217 | .id_table = wm8961_i2c_id, | 1124 | .id_table = wm8961_i2c_id, |
1218 | }; | 1125 | }; |
1126 | #endif | ||
1219 | 1127 | ||
1220 | static int __init wm8961_modinit(void) | 1128 | static int __init wm8961_modinit(void) |
1221 | { | 1129 | { |
1222 | int ret; | 1130 | int ret = 0; |
1223 | 1131 | #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) | |
1224 | ret = i2c_add_driver(&wm8961_i2c_driver); | 1132 | ret = i2c_add_driver(&wm8961_i2c_driver); |
1225 | if (ret != 0) { | 1133 | if (ret != 0) { |
1226 | printk(KERN_ERR "Failed to register WM8961 I2C driver: %d\n", | 1134 | printk(KERN_ERR "Failed to register wm8961 I2C driver: %d\n", |
1227 | ret); | 1135 | ret); |
1228 | } | 1136 | } |
1229 | 1137 | #endif | |
1230 | return ret; | 1138 | return ret; |
1231 | } | 1139 | } |
1232 | module_init(wm8961_modinit); | 1140 | module_init(wm8961_modinit); |
1233 | 1141 | ||
1234 | static void __exit wm8961_exit(void) | 1142 | static void __exit wm8961_exit(void) |
1235 | { | 1143 | { |
1144 | #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) | ||
1236 | i2c_del_driver(&wm8961_i2c_driver); | 1145 | i2c_del_driver(&wm8961_i2c_driver); |
1146 | #endif | ||
1237 | } | 1147 | } |
1238 | module_exit(wm8961_exit); | 1148 | module_exit(wm8961_exit); |
1239 | 1149 | ||
1240 | |||
1241 | MODULE_DESCRIPTION("ASoC WM8961 driver"); | 1150 | MODULE_DESCRIPTION("ASoC WM8961 driver"); |
1242 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); | 1151 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); |
1243 | MODULE_LICENSE("GPL"); | 1152 | MODULE_LICENSE("GPL"); |