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