1 /* 2 * Lowland audio support 3 * 4 * Copyright 2011 Wolfson Microelectronics 5 * 6 * This program is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License as published by the 8 * Free Software Foundation; either version 2 of the License, or (at your 9 * option) any later version. 10 */ 11 12 #include <sound/soc.h> 13 #include <sound/soc-dapm.h> 14 #include <sound/jack.h> 15 #include <linux/gpio.h> 16 #include <linux/module.h> 17 18 #include "../codecs/wm5100.h" 19 #include "../codecs/wm9081.h" 20 21 #define MCLK1_RATE (44100 * 512) 22 #define CLKOUT_RATE (44100 * 256) 23 24 static struct snd_soc_jack lowland_headset; 25 26 /* Headset jack detection DAPM pins */ 27 static struct snd_soc_jack_pin lowland_headset_pins[] = { 28 { 29 .pin = "Headphone", 30 .mask = SND_JACK_HEADPHONE | SND_JACK_LINEOUT, 31 }, 32 { 33 .pin = "Headset Mic", 34 .mask = SND_JACK_MICROPHONE, 35 }, 36 }; 37 38 static int lowland_wm5100_init(struct snd_soc_pcm_runtime *rtd) 39 { 40 struct snd_soc_codec *codec = rtd->codec; 41 int ret; 42 43 ret = snd_soc_codec_set_sysclk(codec, WM5100_CLK_SYSCLK, 44 WM5100_CLKSRC_MCLK1, MCLK1_RATE, 45 SND_SOC_CLOCK_IN); 46 if (ret < 0) { 47 pr_err("Failed to set SYSCLK clock source: %d\n", ret); 48 return ret; 49 } 50 51 /* Clock OPCLK, used by the other audio components. */ 52 ret = snd_soc_codec_set_sysclk(codec, WM5100_CLK_OPCLK, 0, 53 CLKOUT_RATE, 0); 54 if (ret < 0) { 55 pr_err("Failed to set OPCLK rate: %d\n", ret); 56 return ret; 57 } 58 59 ret = snd_soc_jack_new(codec, "Headset", 60 SND_JACK_LINEOUT | SND_JACK_HEADSET | 61 SND_JACK_BTN_0, 62 &lowland_headset); 63 if (ret) 64 return ret; 65 66 ret = snd_soc_jack_add_pins(&lowland_headset, 67 ARRAY_SIZE(lowland_headset_pins), 68 lowland_headset_pins); 69 if (ret) 70 return ret; 71 72 wm5100_detect(codec, &lowland_headset); 73 74 return 0; 75 } 76 77 static int lowland_wm9081_init(struct snd_soc_pcm_runtime *rtd) 78 { 79 struct snd_soc_codec *codec = rtd->codec; 80 81 snd_soc_dapm_nc_pin(&codec->dapm, "LINEOUT"); 82 83 /* At any time the WM9081 is active it will have this clock */ 84 return snd_soc_codec_set_sysclk(codec, WM9081_SYSCLK_MCLK, 0, 85 CLKOUT_RATE, 0); 86 } 87 88 static const struct snd_soc_pcm_stream sub_params = { 89 .formats = SNDRV_PCM_FMTBIT_S32_LE, 90 .rate_min = 44100, 91 .rate_max = 44100, 92 .channels_min = 2, 93 .channels_max = 2, 94 }; 95 96 static struct snd_soc_dai_link lowland_dai[] = { 97 { 98 .name = "CPU", 99 .stream_name = "CPU", 100 .cpu_dai_name = "samsung-i2s.0", 101 .codec_dai_name = "wm5100-aif1", 102 .platform_name = "samsung-i2s.0", 103 .codec_name = "wm5100.1-001a", 104 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 105 SND_SOC_DAIFMT_CBM_CFM, 106 .init = lowland_wm5100_init, 107 }, 108 { 109 .name = "Baseband", 110 .stream_name = "Baseband", 111 .cpu_dai_name = "wm5100-aif2", 112 .codec_dai_name = "wm1250-ev1", 113 .codec_name = "wm1250-ev1.1-0027", 114 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 115 SND_SOC_DAIFMT_CBM_CFM, 116 .ignore_suspend = 1, 117 }, 118 { 119 .name = "Sub Speaker", 120 .stream_name = "Sub Speaker", 121 .cpu_dai_name = "wm5100-aif3", 122 .codec_dai_name = "wm9081-hifi", 123 .codec_name = "wm9081.1-006c", 124 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 125 SND_SOC_DAIFMT_CBM_CFM, 126 .ignore_suspend = 1, 127 .params = &sub_params, 128 .init = lowland_wm9081_init, 129 }, 130 }; 131 132 static struct snd_soc_codec_conf lowland_codec_conf[] = { 133 { 134 .dev_name = "wm9081.1-006c", 135 .name_prefix = "Sub", 136 }, 137 }; 138 139 static const struct snd_kcontrol_new controls[] = { 140 SOC_DAPM_PIN_SWITCH("Main Speaker"), 141 SOC_DAPM_PIN_SWITCH("Main DMIC"), 142 SOC_DAPM_PIN_SWITCH("Main AMIC"), 143 SOC_DAPM_PIN_SWITCH("WM1250 Input"), 144 SOC_DAPM_PIN_SWITCH("WM1250 Output"), 145 SOC_DAPM_PIN_SWITCH("Headphone"), 146 }; 147 148 static struct snd_soc_dapm_widget widgets[] = { 149 SND_SOC_DAPM_HP("Headphone", NULL), 150 SND_SOC_DAPM_MIC("Headset Mic", NULL), 151 152 SND_SOC_DAPM_SPK("Main Speaker", NULL), 153 154 SND_SOC_DAPM_MIC("Main AMIC", NULL), 155 SND_SOC_DAPM_MIC("Main DMIC", NULL), 156 }; 157 158 static struct snd_soc_dapm_route audio_paths[] = { 159 { "Sub IN1", NULL, "HPOUT2L" }, 160 { "Sub IN2", NULL, "HPOUT2R" }, 161 162 { "Main Speaker", NULL, "Sub SPKN" }, 163 { "Main Speaker", NULL, "Sub SPKP" }, 164 { "Main Speaker", NULL, "SPKDAT1" }, 165 }; 166 167 static struct snd_soc_card lowland = { 168 .name = "Lowland", 169 .owner = THIS_MODULE, 170 .dai_link = lowland_dai, 171 .num_links = ARRAY_SIZE(lowland_dai), 172 .codec_conf = lowland_codec_conf, 173 .num_configs = ARRAY_SIZE(lowland_codec_conf), 174 175 .controls = controls, 176 .num_controls = ARRAY_SIZE(controls), 177 .dapm_widgets = widgets, 178 .num_dapm_widgets = ARRAY_SIZE(widgets), 179 .dapm_routes = audio_paths, 180 .num_dapm_routes = ARRAY_SIZE(audio_paths), 181 }; 182 183 static int lowland_probe(struct platform_device *pdev) 184 { 185 struct snd_soc_card *card = &lowland; 186 int ret; 187 188 card->dev = &pdev->dev; 189 190 ret = snd_soc_register_card(card); 191 if (ret) { 192 dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", 193 ret); 194 return ret; 195 } 196 197 return 0; 198 } 199 200 static int lowland_remove(struct platform_device *pdev) 201 { 202 struct snd_soc_card *card = platform_get_drvdata(pdev); 203 204 snd_soc_unregister_card(card); 205 206 return 0; 207 } 208 209 static struct platform_driver lowland_driver = { 210 .driver = { 211 .name = "lowland", 212 .owner = THIS_MODULE, 213 .pm = &snd_soc_pm_ops, 214 }, 215 .probe = lowland_probe, 216 .remove = lowland_remove, 217 }; 218 219 module_platform_driver(lowland_driver); 220 221 MODULE_DESCRIPTION("Lowland audio support"); 222 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 223 MODULE_LICENSE("GPL"); 224 MODULE_ALIAS("platform:lowland"); 225