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