1 /* 2 * bytcht-da7213.c - ASoc Machine driver for Intel Baytrail and 3 * Cherrytrail-based platforms, with Dialog DA7213 codec 4 * 5 * Copyright (C) 2017 Intel Corporation 6 * Author: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> 7 * 8 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; version 2 of the License. 13 * 14 * This program is distributed in the hope that it will be useful, but 15 * WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * General Public License for more details. 18 * 19 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 */ 21 22 #include <linux/module.h> 23 #include <linux/acpi.h> 24 #include <linux/platform_device.h> 25 #include <linux/slab.h> 26 #include <asm/platform_sst_audio.h> 27 #include <sound/pcm.h> 28 #include <sound/pcm_params.h> 29 #include <sound/soc.h> 30 #include <sound/soc-acpi.h> 31 #include "../../codecs/da7213.h" 32 #include "../atom/sst-atom-controls.h" 33 34 static const struct snd_kcontrol_new controls[] = { 35 SOC_DAPM_PIN_SWITCH("Headphone Jack"), 36 SOC_DAPM_PIN_SWITCH("Headset Mic"), 37 SOC_DAPM_PIN_SWITCH("Mic"), 38 SOC_DAPM_PIN_SWITCH("Aux In"), 39 }; 40 41 static const struct snd_soc_dapm_widget dapm_widgets[] = { 42 SND_SOC_DAPM_HP("Headphone Jack", NULL), 43 SND_SOC_DAPM_MIC("Headset Mic", NULL), 44 SND_SOC_DAPM_MIC("Mic", NULL), 45 SND_SOC_DAPM_LINE("Aux In", NULL), 46 }; 47 48 static const struct snd_soc_dapm_route audio_map[] = { 49 {"Headphone Jack", NULL, "HPL"}, 50 {"Headphone Jack", NULL, "HPR"}, 51 52 {"AUXL", NULL, "Aux In"}, 53 {"AUXR", NULL, "Aux In"}, 54 55 /* Assume Mic1 is linked to Headset and Mic2 to on-board mic */ 56 {"MIC1", NULL, "Headset Mic"}, 57 {"MIC2", NULL, "Mic"}, 58 59 /* SOC-codec link */ 60 {"ssp2 Tx", NULL, "codec_out0"}, 61 {"ssp2 Tx", NULL, "codec_out1"}, 62 {"codec_in0", NULL, "ssp2 Rx"}, 63 {"codec_in1", NULL, "ssp2 Rx"}, 64 65 {"Playback", NULL, "ssp2 Tx"}, 66 {"ssp2 Rx", NULL, "Capture"}, 67 }; 68 69 static int codec_fixup(struct snd_soc_pcm_runtime *rtd, 70 struct snd_pcm_hw_params *params) 71 { 72 int ret; 73 struct snd_interval *rate = hw_param_interval(params, 74 SNDRV_PCM_HW_PARAM_RATE); 75 struct snd_interval *channels = hw_param_interval(params, 76 SNDRV_PCM_HW_PARAM_CHANNELS); 77 78 /* The DSP will convert the FE rate to 48k, stereo, 24bits */ 79 rate->min = rate->max = 48000; 80 channels->min = channels->max = 2; 81 82 /* set SSP2 to 24-bit */ 83 params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); 84 85 /* 86 * Default mode for SSP configuration is TDM 4 slot, override config 87 * with explicit setting to I2S 2ch 24-bit. The word length is set with 88 * dai_set_tdm_slot() since there is no other API exposed 89 */ 90 ret = snd_soc_dai_set_fmt(rtd->cpu_dai, 91 SND_SOC_DAIFMT_I2S | 92 SND_SOC_DAIFMT_NB_NF | 93 SND_SOC_DAIFMT_CBS_CFS); 94 if (ret < 0) { 95 dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); 96 return ret; 97 } 98 99 ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); 100 if (ret < 0) { 101 dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); 102 return ret; 103 } 104 105 return 0; 106 } 107 108 static int aif1_startup(struct snd_pcm_substream *substream) 109 { 110 return snd_pcm_hw_constraint_single(substream->runtime, 111 SNDRV_PCM_HW_PARAM_RATE, 48000); 112 } 113 114 static int aif1_hw_params(struct snd_pcm_substream *substream, 115 struct snd_pcm_hw_params *params) 116 { 117 struct snd_soc_pcm_runtime *rtd = substream->private_data; 118 struct snd_soc_dai *codec_dai = rtd->codec_dai; 119 int ret; 120 121 ret = snd_soc_dai_set_sysclk(codec_dai, DA7213_CLKSRC_MCLK, 122 19200000, SND_SOC_CLOCK_IN); 123 if (ret < 0) 124 dev_err(codec_dai->dev, "can't set codec sysclk configuration\n"); 125 126 ret = snd_soc_dai_set_pll(codec_dai, 0, 127 DA7213_SYSCLK_PLL_SRM, 0, DA7213_PLL_FREQ_OUT_98304000); 128 if (ret < 0) { 129 dev_err(codec_dai->dev, "failed to start PLL: %d\n", ret); 130 return -EIO; 131 } 132 133 return ret; 134 } 135 136 static int aif1_hw_free(struct snd_pcm_substream *substream) 137 { 138 struct snd_soc_pcm_runtime *rtd = substream->private_data; 139 struct snd_soc_dai *codec_dai = rtd->codec_dai; 140 int ret; 141 142 ret = snd_soc_dai_set_pll(codec_dai, 0, 143 DA7213_SYSCLK_MCLK, 0, 0); 144 if (ret < 0) { 145 dev_err(codec_dai->dev, "failed to stop PLL: %d\n", ret); 146 return -EIO; 147 } 148 149 return ret; 150 } 151 152 static const struct snd_soc_ops aif1_ops = { 153 .startup = aif1_startup, 154 }; 155 156 static const struct snd_soc_ops ssp2_ops = { 157 .hw_params = aif1_hw_params, 158 .hw_free = aif1_hw_free, 159 160 }; 161 162 static struct snd_soc_dai_link dailink[] = { 163 [MERR_DPCM_AUDIO] = { 164 .name = "Audio Port", 165 .stream_name = "Audio", 166 .cpu_dai_name = "media-cpu-dai", 167 .codec_dai_name = "snd-soc-dummy-dai", 168 .codec_name = "snd-soc-dummy", 169 .platform_name = "sst-mfld-platform", 170 .nonatomic = true, 171 .dynamic = 1, 172 .dpcm_playback = 1, 173 .dpcm_capture = 1, 174 .ops = &aif1_ops, 175 }, 176 [MERR_DPCM_DEEP_BUFFER] = { 177 .name = "Deep-Buffer Audio Port", 178 .stream_name = "Deep-Buffer Audio", 179 .cpu_dai_name = "deepbuffer-cpu-dai", 180 .codec_dai_name = "snd-soc-dummy-dai", 181 .codec_name = "snd-soc-dummy", 182 .platform_name = "sst-mfld-platform", 183 .nonatomic = true, 184 .dynamic = 1, 185 .dpcm_playback = 1, 186 .ops = &aif1_ops, 187 }, 188 /* CODEC<->CODEC link */ 189 /* back ends */ 190 { 191 .name = "SSP2-Codec", 192 .id = 0, 193 .cpu_dai_name = "ssp2-port", 194 .platform_name = "sst-mfld-platform", 195 .no_pcm = 1, 196 .codec_dai_name = "da7213-hifi", 197 .codec_name = "i2c-DLGS7213:00", 198 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 199 | SND_SOC_DAIFMT_CBS_CFS, 200 .be_hw_params_fixup = codec_fixup, 201 .nonatomic = true, 202 .dpcm_playback = 1, 203 .dpcm_capture = 1, 204 .ops = &ssp2_ops, 205 }, 206 }; 207 208 /* SoC card */ 209 static struct snd_soc_card bytcht_da7213_card = { 210 .name = "bytcht-da7213", 211 .owner = THIS_MODULE, 212 .dai_link = dailink, 213 .num_links = ARRAY_SIZE(dailink), 214 .controls = controls, 215 .num_controls = ARRAY_SIZE(controls), 216 .dapm_widgets = dapm_widgets, 217 .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), 218 .dapm_routes = audio_map, 219 .num_dapm_routes = ARRAY_SIZE(audio_map), 220 }; 221 222 static char codec_name[SND_ACPI_I2C_ID_LEN]; 223 224 static int bytcht_da7213_probe(struct platform_device *pdev) 225 { 226 struct snd_soc_card *card; 227 struct snd_soc_acpi_mach *mach; 228 const char *i2c_name = NULL; 229 int dai_index = 0; 230 int ret_val = 0; 231 int i; 232 233 mach = (&pdev->dev)->platform_data; 234 card = &bytcht_da7213_card; 235 card->dev = &pdev->dev; 236 237 /* fix index of codec dai */ 238 for (i = 0; i < ARRAY_SIZE(dailink); i++) { 239 if (!strcmp(dailink[i].codec_name, "i2c-DLGS7213:00")) { 240 dai_index = i; 241 break; 242 } 243 } 244 245 /* fixup codec name based on HID */ 246 i2c_name = acpi_dev_get_first_match_name(mach->id, NULL, -1); 247 if (i2c_name) { 248 snprintf(codec_name, sizeof(codec_name), 249 "%s%s", "i2c-", i2c_name); 250 dailink[dai_index].codec_name = codec_name; 251 } 252 253 ret_val = devm_snd_soc_register_card(&pdev->dev, card); 254 if (ret_val) { 255 dev_err(&pdev->dev, 256 "snd_soc_register_card failed %d\n", ret_val); 257 return ret_val; 258 } 259 platform_set_drvdata(pdev, card); 260 return ret_val; 261 } 262 263 static struct platform_driver bytcht_da7213_driver = { 264 .driver = { 265 .name = "bytcht_da7213", 266 }, 267 .probe = bytcht_da7213_probe, 268 }; 269 module_platform_driver(bytcht_da7213_driver); 270 271 MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail+DA7213 Machine driver"); 272 MODULE_AUTHOR("Pierre-Louis Bossart"); 273 MODULE_LICENSE("GPL v2"); 274 MODULE_ALIAS("platform:bytcht_da7213"); 275