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