1 // SPDX-License-Identifier: GPL-2.0+ 2 // 3 // Lowland audio support 4 // 5 // Copyright 2011 Wolfson Microelectronics 6 7 #include <sound/soc.h> 8 #include <sound/soc-dapm.h> 9 #include <sound/jack.h> 10 #include <linux/module.h> 11 12 #include "../codecs/wm5100.h" 13 #include "../codecs/wm9081.h" 14 15 #define MCLK1_RATE (44100 * 512) 16 #define CLKOUT_RATE (44100 * 256) 17 18 static struct snd_soc_jack lowland_headset; 19 20 /* Headset jack detection DAPM pins */ 21 static struct snd_soc_jack_pin lowland_headset_pins[] = { 22 { 23 .pin = "Headphone", 24 .mask = SND_JACK_HEADPHONE, 25 }, 26 { 27 .pin = "Headset Mic", 28 .mask = SND_JACK_MICROPHONE, 29 }, 30 { 31 .pin = "Line Out", 32 .mask = SND_JACK_LINEOUT, 33 }, 34 }; 35 36 static int lowland_wm5100_init(struct snd_soc_pcm_runtime *rtd) 37 { 38 struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; 39 int ret; 40 41 ret = snd_soc_component_set_sysclk(component, WM5100_CLK_SYSCLK, 42 WM5100_CLKSRC_MCLK1, MCLK1_RATE, 43 SND_SOC_CLOCK_IN); 44 if (ret < 0) { 45 pr_err("Failed to set SYSCLK clock source: %d\n", ret); 46 return ret; 47 } 48 49 /* Clock OPCLK, used by the other audio components. */ 50 ret = snd_soc_component_set_sysclk(component, WM5100_CLK_OPCLK, 0, 51 CLKOUT_RATE, 0); 52 if (ret < 0) { 53 pr_err("Failed to set OPCLK rate: %d\n", ret); 54 return ret; 55 } 56 57 ret = snd_soc_card_jack_new_pins(rtd->card, "Headset", 58 SND_JACK_LINEOUT | SND_JACK_HEADSET | 59 SND_JACK_BTN_0, 60 &lowland_headset, lowland_headset_pins, 61 ARRAY_SIZE(lowland_headset_pins)); 62 if (ret) 63 return ret; 64 65 wm5100_detect(component, &lowland_headset); 66 67 return 0; 68 } 69 70 static int lowland_wm9081_init(struct snd_soc_pcm_runtime *rtd) 71 { 72 struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; 73 74 snd_soc_dapm_nc_pin(&rtd->card->dapm, "LINEOUT"); 75 76 /* At any time the WM9081 is active it will have this clock */ 77 return snd_soc_component_set_sysclk(component, WM9081_SYSCLK_MCLK, 0, 78 CLKOUT_RATE, 0); 79 } 80 81 static const struct snd_soc_pcm_stream sub_params = { 82 .formats = SNDRV_PCM_FMTBIT_S32_LE, 83 .rate_min = 44100, 84 .rate_max = 44100, 85 .channels_min = 2, 86 .channels_max = 2, 87 }; 88 89 SND_SOC_DAILINK_DEFS(cpu, 90 DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")), 91 DAILINK_COMP_ARRAY(COMP_CODEC("wm5100.1-001a", "wm5100-aif1")), 92 DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0"))); 93 94 SND_SOC_DAILINK_DEFS(baseband, 95 DAILINK_COMP_ARRAY(COMP_CPU("wm5100-aif2")), 96 DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027", "wm1250-ev1"))); 97 98 SND_SOC_DAILINK_DEFS(speaker, 99 DAILINK_COMP_ARRAY(COMP_CPU("wm5100-aif3")), 100 DAILINK_COMP_ARRAY(COMP_CODEC("wm9081.1-006c", "wm9081-hifi"))); 101 102 static struct snd_soc_dai_link lowland_dai[] = { 103 { 104 .name = "CPU", 105 .stream_name = "CPU", 106 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 107 SND_SOC_DAIFMT_CBP_CFP, 108 .init = lowland_wm5100_init, 109 SND_SOC_DAILINK_REG(cpu), 110 }, 111 { 112 .name = "Baseband", 113 .stream_name = "Baseband", 114 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 115 SND_SOC_DAIFMT_CBP_CFP, 116 .ignore_suspend = 1, 117 SND_SOC_DAILINK_REG(baseband), 118 }, 119 { 120 .name = "Sub Speaker", 121 .stream_name = "Sub Speaker", 122 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 123 SND_SOC_DAIFMT_CBP_CFP, 124 .ignore_suspend = 1, 125 .c2c_params = &sub_params, 126 .num_c2c_params = 1, 127 .init = lowland_wm9081_init, 128 SND_SOC_DAILINK_REG(speaker), 129 }, 130 }; 131 132 static struct snd_soc_codec_conf lowland_codec_conf[] = { 133 { 134 .dlc = COMP_CODEC_CONF("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 SOC_DAPM_PIN_SWITCH("Line Out"), 147 }; 148 149 static const struct snd_soc_dapm_widget widgets[] = { 150 SND_SOC_DAPM_HP("Headphone", NULL), 151 SND_SOC_DAPM_MIC("Headset Mic", NULL), 152 SND_SOC_DAPM_LINE("Line Out", NULL), 153 154 SND_SOC_DAPM_SPK("Main Speaker", NULL), 155 156 SND_SOC_DAPM_MIC("Main AMIC", NULL), 157 SND_SOC_DAPM_MIC("Main DMIC", NULL), 158 }; 159 160 static const struct snd_soc_dapm_route audio_paths[] = { 161 { "Sub IN1", NULL, "HPOUT2L" }, 162 { "Sub IN2", NULL, "HPOUT2R" }, 163 164 { "Main Speaker", NULL, "Sub SPKN" }, 165 { "Main Speaker", NULL, "Sub SPKP" }, 166 { "Main Speaker", NULL, "SPKDAT1" }, 167 }; 168 169 static struct snd_soc_card lowland = { 170 .name = "Lowland", 171 .owner = THIS_MODULE, 172 .dai_link = lowland_dai, 173 .num_links = ARRAY_SIZE(lowland_dai), 174 .codec_conf = lowland_codec_conf, 175 .num_configs = ARRAY_SIZE(lowland_codec_conf), 176 177 .controls = controls, 178 .num_controls = ARRAY_SIZE(controls), 179 .dapm_widgets = widgets, 180 .num_dapm_widgets = ARRAY_SIZE(widgets), 181 .dapm_routes = audio_paths, 182 .num_dapm_routes = ARRAY_SIZE(audio_paths), 183 }; 184 185 static int lowland_probe(struct platform_device *pdev) 186 { 187 struct snd_soc_card *card = &lowland; 188 int ret; 189 190 card->dev = &pdev->dev; 191 192 ret = devm_snd_soc_register_card(&pdev->dev, card); 193 if (ret) 194 dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n"); 195 196 return ret; 197 } 198 199 static struct platform_driver lowland_driver = { 200 .driver = { 201 .name = "lowland", 202 .pm = &snd_soc_pm_ops, 203 }, 204 .probe = lowland_probe, 205 }; 206 207 module_platform_driver(lowland_driver); 208 209 MODULE_DESCRIPTION("Lowland audio support"); 210 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 211 MODULE_LICENSE("GPL"); 212 MODULE_ALIAS("platform:lowland"); 213