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_card_jack_new(rtd->card, "Headset", SND_JACK_LINEOUT | 60 SND_JACK_HEADSET | SND_JACK_BTN_0, 61 &lowland_headset, lowland_headset_pins, 62 ARRAY_SIZE(lowland_headset_pins)); 63 if (ret) 64 return ret; 65 66 wm5100_detect(codec, &lowland_headset); 67 68 return 0; 69 } 70 71 static int lowland_wm9081_init(struct snd_soc_pcm_runtime *rtd) 72 { 73 struct snd_soc_codec *codec = rtd->codec; 74 75 snd_soc_dapm_nc_pin(&codec->dapm, "LINEOUT"); 76 77 /* At any time the WM9081 is active it will have this clock */ 78 return snd_soc_codec_set_sysclk(codec, WM9081_SYSCLK_MCLK, 0, 79 CLKOUT_RATE, 0); 80 } 81 82 static const struct snd_soc_pcm_stream sub_params = { 83 .formats = SNDRV_PCM_FMTBIT_S32_LE, 84 .rate_min = 44100, 85 .rate_max = 44100, 86 .channels_min = 2, 87 .channels_max = 2, 88 }; 89 90 static struct snd_soc_dai_link lowland_dai[] = { 91 { 92 .name = "CPU", 93 .stream_name = "CPU", 94 .cpu_dai_name = "samsung-i2s.0", 95 .codec_dai_name = "wm5100-aif1", 96 .platform_name = "samsung-i2s.0", 97 .codec_name = "wm5100.1-001a", 98 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 99 SND_SOC_DAIFMT_CBM_CFM, 100 .init = lowland_wm5100_init, 101 }, 102 { 103 .name = "Baseband", 104 .stream_name = "Baseband", 105 .cpu_dai_name = "wm5100-aif2", 106 .codec_dai_name = "wm1250-ev1", 107 .codec_name = "wm1250-ev1.1-0027", 108 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 109 SND_SOC_DAIFMT_CBM_CFM, 110 .ignore_suspend = 1, 111 }, 112 { 113 .name = "Sub Speaker", 114 .stream_name = "Sub Speaker", 115 .cpu_dai_name = "wm5100-aif3", 116 .codec_dai_name = "wm9081-hifi", 117 .codec_name = "wm9081.1-006c", 118 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 119 SND_SOC_DAIFMT_CBM_CFM, 120 .ignore_suspend = 1, 121 .params = &sub_params, 122 .init = lowland_wm9081_init, 123 }, 124 }; 125 126 static struct snd_soc_codec_conf lowland_codec_conf[] = { 127 { 128 .dev_name = "wm9081.1-006c", 129 .name_prefix = "Sub", 130 }, 131 }; 132 133 static const struct snd_kcontrol_new controls[] = { 134 SOC_DAPM_PIN_SWITCH("Main Speaker"), 135 SOC_DAPM_PIN_SWITCH("Main DMIC"), 136 SOC_DAPM_PIN_SWITCH("Main AMIC"), 137 SOC_DAPM_PIN_SWITCH("WM1250 Input"), 138 SOC_DAPM_PIN_SWITCH("WM1250 Output"), 139 SOC_DAPM_PIN_SWITCH("Headphone"), 140 }; 141 142 static struct snd_soc_dapm_widget widgets[] = { 143 SND_SOC_DAPM_HP("Headphone", NULL), 144 SND_SOC_DAPM_MIC("Headset Mic", NULL), 145 146 SND_SOC_DAPM_SPK("Main Speaker", NULL), 147 148 SND_SOC_DAPM_MIC("Main AMIC", NULL), 149 SND_SOC_DAPM_MIC("Main DMIC", NULL), 150 }; 151 152 static struct snd_soc_dapm_route audio_paths[] = { 153 { "Sub IN1", NULL, "HPOUT2L" }, 154 { "Sub IN2", NULL, "HPOUT2R" }, 155 156 { "Main Speaker", NULL, "Sub SPKN" }, 157 { "Main Speaker", NULL, "Sub SPKP" }, 158 { "Main Speaker", NULL, "SPKDAT1" }, 159 }; 160 161 static struct snd_soc_card lowland = { 162 .name = "Lowland", 163 .owner = THIS_MODULE, 164 .dai_link = lowland_dai, 165 .num_links = ARRAY_SIZE(lowland_dai), 166 .codec_conf = lowland_codec_conf, 167 .num_configs = ARRAY_SIZE(lowland_codec_conf), 168 169 .controls = controls, 170 .num_controls = ARRAY_SIZE(controls), 171 .dapm_widgets = widgets, 172 .num_dapm_widgets = ARRAY_SIZE(widgets), 173 .dapm_routes = audio_paths, 174 .num_dapm_routes = ARRAY_SIZE(audio_paths), 175 }; 176 177 static int lowland_probe(struct platform_device *pdev) 178 { 179 struct snd_soc_card *card = &lowland; 180 int ret; 181 182 card->dev = &pdev->dev; 183 184 ret = devm_snd_soc_register_card(&pdev->dev, card); 185 if (ret) 186 dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", 187 ret); 188 189 return ret; 190 } 191 192 static struct platform_driver lowland_driver = { 193 .driver = { 194 .name = "lowland", 195 .pm = &snd_soc_pm_ops, 196 }, 197 .probe = lowland_probe, 198 }; 199 200 module_platform_driver(lowland_driver); 201 202 MODULE_DESCRIPTION("Lowland audio support"); 203 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 204 MODULE_LICENSE("GPL"); 205 MODULE_ALIAS("platform:lowland"); 206