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