diff options
Diffstat (limited to 'sound/soc/intel/boards/cht_bsw_rt5645.c')
| -rw-r--r-- | sound/soc/intel/boards/cht_bsw_rt5645.c | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/sound/soc/intel/boards/cht_bsw_rt5645.c b/sound/soc/intel/boards/cht_bsw_rt5645.c new file mode 100644 index 000000000000..93bb6711ba3d --- /dev/null +++ b/sound/soc/intel/boards/cht_bsw_rt5645.c | |||
| @@ -0,0 +1,324 @@ | |||
| 1 | /* | ||
| 2 | * cht-bsw-rt5645.c - ASoc Machine driver for Intel Cherryview-based platforms | ||
| 3 | * Cherrytrail and Braswell, with RT5645 codec. | ||
| 4 | * | ||
| 5 | * Copyright (C) 2015 Intel Corp | ||
| 6 | * Author: Fang, Yang A <yang.a.fang@intel.com> | ||
| 7 | * N,Harshapriya <harshapriya.n@intel.com> | ||
| 8 | * This file is modified from cht_bsw_rt5672.c | ||
| 9 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| 10 | * | ||
| 11 | * This program is free software; you can redistribute it and/or modify | ||
| 12 | * it under the terms of the GNU General Public License as published by | ||
| 13 | * the Free Software Foundation; version 2 of the License. | ||
| 14 | * | ||
| 15 | * This program is distributed in the hope that it will be useful, but | ||
| 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 18 | * General Public License for more details. | ||
| 19 | * | ||
| 20 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| 21 | */ | ||
| 22 | |||
| 23 | #include <linux/module.h> | ||
| 24 | #include <linux/platform_device.h> | ||
| 25 | #include <linux/slab.h> | ||
| 26 | #include <sound/pcm.h> | ||
| 27 | #include <sound/pcm_params.h> | ||
| 28 | #include <sound/soc.h> | ||
| 29 | #include <sound/jack.h> | ||
| 30 | #include "../../codecs/rt5645.h" | ||
| 31 | #include "../sst-atom-controls.h" | ||
| 32 | |||
| 33 | #define CHT_PLAT_CLK_3_HZ 19200000 | ||
| 34 | #define CHT_CODEC_DAI "rt5645-aif1" | ||
| 35 | |||
| 36 | struct cht_mc_private { | ||
| 37 | struct snd_soc_jack hp_jack; | ||
| 38 | struct snd_soc_jack mic_jack; | ||
| 39 | }; | ||
| 40 | |||
| 41 | static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card) | ||
| 42 | { | ||
| 43 | int i; | ||
| 44 | |||
| 45 | for (i = 0; i < card->num_rtd; i++) { | ||
| 46 | struct snd_soc_pcm_runtime *rtd; | ||
| 47 | |||
| 48 | rtd = card->rtd + i; | ||
| 49 | if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI, | ||
| 50 | strlen(CHT_CODEC_DAI))) | ||
| 51 | return rtd->codec_dai; | ||
| 52 | } | ||
| 53 | return NULL; | ||
| 54 | } | ||
| 55 | |||
| 56 | static int platform_clock_control(struct snd_soc_dapm_widget *w, | ||
| 57 | struct snd_kcontrol *k, int event) | ||
| 58 | { | ||
| 59 | struct snd_soc_dapm_context *dapm = w->dapm; | ||
| 60 | struct snd_soc_card *card = dapm->card; | ||
| 61 | struct snd_soc_dai *codec_dai; | ||
| 62 | int ret; | ||
| 63 | |||
| 64 | codec_dai = cht_get_codec_dai(card); | ||
| 65 | if (!codec_dai) { | ||
| 66 | dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n"); | ||
| 67 | return -EIO; | ||
| 68 | } | ||
| 69 | |||
| 70 | if (!SND_SOC_DAPM_EVENT_OFF(event)) | ||
| 71 | return 0; | ||
| 72 | |||
| 73 | /* Set codec sysclk source to its internal clock because codec PLL will | ||
| 74 | * be off when idle and MCLK will also be off by ACPI when codec is | ||
| 75 | * runtime suspended. Codec needs clock for jack detection and button | ||
| 76 | * press. | ||
| 77 | */ | ||
| 78 | ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_RCCLK, | ||
| 79 | 0, SND_SOC_CLOCK_IN); | ||
| 80 | if (ret < 0) { | ||
| 81 | dev_err(card->dev, "can't set codec sysclk: %d\n", ret); | ||
| 82 | return ret; | ||
| 83 | } | ||
| 84 | |||
| 85 | return 0; | ||
| 86 | } | ||
| 87 | |||
| 88 | static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { | ||
| 89 | SND_SOC_DAPM_HP("Headphone", NULL), | ||
| 90 | SND_SOC_DAPM_MIC("Headset Mic", NULL), | ||
| 91 | SND_SOC_DAPM_MIC("Int Mic", NULL), | ||
| 92 | SND_SOC_DAPM_SPK("Ext Spk", NULL), | ||
| 93 | SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, | ||
| 94 | platform_clock_control, SND_SOC_DAPM_POST_PMD), | ||
| 95 | }; | ||
| 96 | |||
| 97 | static const struct snd_soc_dapm_route cht_audio_map[] = { | ||
| 98 | {"IN1P", NULL, "Headset Mic"}, | ||
| 99 | {"IN1N", NULL, "Headset Mic"}, | ||
| 100 | {"DMIC L1", NULL, "Int Mic"}, | ||
| 101 | {"DMIC R1", NULL, "Int Mic"}, | ||
| 102 | {"Headphone", NULL, "HPOL"}, | ||
| 103 | {"Headphone", NULL, "HPOR"}, | ||
| 104 | {"Ext Spk", NULL, "SPOL"}, | ||
| 105 | {"Ext Spk", NULL, "SPOR"}, | ||
| 106 | {"AIF1 Playback", NULL, "ssp2 Tx"}, | ||
| 107 | {"ssp2 Tx", NULL, "codec_out0"}, | ||
| 108 | {"ssp2 Tx", NULL, "codec_out1"}, | ||
| 109 | {"codec_in0", NULL, "ssp2 Rx" }, | ||
| 110 | {"codec_in1", NULL, "ssp2 Rx" }, | ||
| 111 | {"ssp2 Rx", NULL, "AIF1 Capture"}, | ||
| 112 | {"Headphone", NULL, "Platform Clock"}, | ||
| 113 | {"Headset Mic", NULL, "Platform Clock"}, | ||
| 114 | {"Int Mic", NULL, "Platform Clock"}, | ||
| 115 | {"Ext Spk", NULL, "Platform Clock"}, | ||
| 116 | }; | ||
| 117 | |||
| 118 | static const struct snd_kcontrol_new cht_mc_controls[] = { | ||
| 119 | SOC_DAPM_PIN_SWITCH("Headphone"), | ||
| 120 | SOC_DAPM_PIN_SWITCH("Headset Mic"), | ||
| 121 | SOC_DAPM_PIN_SWITCH("Int Mic"), | ||
| 122 | SOC_DAPM_PIN_SWITCH("Ext Spk"), | ||
| 123 | }; | ||
| 124 | |||
| 125 | static int cht_aif1_hw_params(struct snd_pcm_substream *substream, | ||
| 126 | struct snd_pcm_hw_params *params) | ||
| 127 | { | ||
| 128 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
| 129 | struct snd_soc_dai *codec_dai = rtd->codec_dai; | ||
| 130 | int ret; | ||
| 131 | |||
| 132 | /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ | ||
| 133 | ret = snd_soc_dai_set_pll(codec_dai, 0, RT5645_PLL1_S_MCLK, | ||
| 134 | CHT_PLAT_CLK_3_HZ, params_rate(params) * 512); | ||
| 135 | if (ret < 0) { | ||
| 136 | dev_err(rtd->dev, "can't set codec pll: %d\n", ret); | ||
| 137 | return ret; | ||
| 138 | } | ||
| 139 | |||
| 140 | ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_PLL1, | ||
| 141 | params_rate(params) * 512, SND_SOC_CLOCK_IN); | ||
| 142 | if (ret < 0) { | ||
| 143 | dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); | ||
| 144 | return ret; | ||
| 145 | } | ||
| 146 | |||
| 147 | return 0; | ||
| 148 | } | ||
| 149 | |||
| 150 | static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) | ||
| 151 | { | ||
| 152 | int ret; | ||
| 153 | struct snd_soc_codec *codec = runtime->codec; | ||
| 154 | struct snd_soc_dai *codec_dai = runtime->codec_dai; | ||
| 155 | struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card); | ||
| 156 | |||
| 157 | /* Select clk_i2s1_asrc as ASRC clock source */ | ||
| 158 | rt5645_sel_asrc_clk_src(codec, | ||
| 159 | RT5645_DA_STEREO_FILTER | | ||
| 160 | RT5645_DA_MONO_L_FILTER | | ||
| 161 | RT5645_DA_MONO_R_FILTER | | ||
| 162 | RT5645_AD_STEREO_FILTER, | ||
| 163 | RT5645_CLK_SEL_I2S1_ASRC); | ||
| 164 | |||
| 165 | /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ | ||
| 166 | ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24); | ||
| 167 | if (ret < 0) { | ||
| 168 | dev_err(runtime->dev, "can't set codec TDM slot %d\n", ret); | ||
| 169 | return ret; | ||
| 170 | } | ||
| 171 | |||
| 172 | ret = snd_soc_card_jack_new(runtime->card, "Headphone Jack", | ||
| 173 | SND_JACK_HEADPHONE, &ctx->hp_jack, | ||
| 174 | NULL, 0); | ||
| 175 | if (ret) { | ||
| 176 | dev_err(runtime->dev, "HP jack creation failed %d\n", ret); | ||
| 177 | return ret; | ||
| 178 | } | ||
| 179 | |||
| 180 | ret = snd_soc_card_jack_new(runtime->card, "Mic Jack", | ||
| 181 | SND_JACK_MICROPHONE, &ctx->mic_jack, | ||
| 182 | NULL, 0); | ||
| 183 | if (ret) { | ||
| 184 | dev_err(runtime->dev, "Mic jack creation failed %d\n", ret); | ||
| 185 | return ret; | ||
| 186 | } | ||
| 187 | |||
| 188 | rt5645_set_jack_detect(codec, &ctx->hp_jack, &ctx->mic_jack); | ||
| 189 | |||
| 190 | return ret; | ||
| 191 | } | ||
| 192 | |||
| 193 | static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, | ||
| 194 | struct snd_pcm_hw_params *params) | ||
| 195 | { | ||
| 196 | struct snd_interval *rate = hw_param_interval(params, | ||
| 197 | SNDRV_PCM_HW_PARAM_RATE); | ||
| 198 | struct snd_interval *channels = hw_param_interval(params, | ||
| 199 | SNDRV_PCM_HW_PARAM_CHANNELS); | ||
| 200 | |||
| 201 | /* The DSP will covert the FE rate to 48k, stereo, 24bits */ | ||
| 202 | rate->min = rate->max = 48000; | ||
| 203 | channels->min = channels->max = 2; | ||
| 204 | |||
| 205 | /* set SSP2 to 24-bit */ | ||
| 206 | params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); | ||
| 207 | return 0; | ||
| 208 | } | ||
| 209 | |||
| 210 | static unsigned int rates_48000[] = { | ||
| 211 | 48000, | ||
| 212 | }; | ||
| 213 | |||
| 214 | static struct snd_pcm_hw_constraint_list constraints_48000 = { | ||
| 215 | .count = ARRAY_SIZE(rates_48000), | ||
| 216 | .list = rates_48000, | ||
| 217 | }; | ||
| 218 | |||
| 219 | static int cht_aif1_startup(struct snd_pcm_substream *substream) | ||
| 220 | { | ||
| 221 | return snd_pcm_hw_constraint_list(substream->runtime, 0, | ||
| 222 | SNDRV_PCM_HW_PARAM_RATE, | ||
| 223 | &constraints_48000); | ||
| 224 | } | ||
| 225 | |||
| 226 | static struct snd_soc_ops cht_aif1_ops = { | ||
| 227 | .startup = cht_aif1_startup, | ||
| 228 | }; | ||
| 229 | |||
| 230 | static struct snd_soc_ops cht_be_ssp2_ops = { | ||
| 231 | .hw_params = cht_aif1_hw_params, | ||
| 232 | }; | ||
| 233 | |||
| 234 | static struct snd_soc_dai_link cht_dailink[] = { | ||
| 235 | [MERR_DPCM_AUDIO] = { | ||
| 236 | .name = "Audio Port", | ||
| 237 | .stream_name = "Audio", | ||
| 238 | .cpu_dai_name = "media-cpu-dai", | ||
| 239 | .codec_dai_name = "snd-soc-dummy-dai", | ||
| 240 | .codec_name = "snd-soc-dummy", | ||
| 241 | .platform_name = "sst-mfld-platform", | ||
| 242 | .ignore_suspend = 1, | ||
| 243 | .dynamic = 1, | ||
| 244 | .dpcm_playback = 1, | ||
| 245 | .dpcm_capture = 1, | ||
| 246 | .ops = &cht_aif1_ops, | ||
| 247 | }, | ||
| 248 | [MERR_DPCM_COMPR] = { | ||
| 249 | .name = "Compressed Port", | ||
| 250 | .stream_name = "Compress", | ||
| 251 | .cpu_dai_name = "compress-cpu-dai", | ||
| 252 | .codec_dai_name = "snd-soc-dummy-dai", | ||
| 253 | .codec_name = "snd-soc-dummy", | ||
| 254 | .platform_name = "sst-mfld-platform", | ||
| 255 | }, | ||
| 256 | /* CODEC<->CODEC link */ | ||
| 257 | /* back ends */ | ||
| 258 | { | ||
| 259 | .name = "SSP2-Codec", | ||
| 260 | .be_id = 1, | ||
| 261 | .cpu_dai_name = "ssp2-port", | ||
| 262 | .platform_name = "sst-mfld-platform", | ||
| 263 | .no_pcm = 1, | ||
| 264 | .codec_dai_name = "rt5645-aif1", | ||
| 265 | .codec_name = "i2c-10EC5645:00", | ||
| 266 | .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF | ||
| 267 | | SND_SOC_DAIFMT_CBS_CFS, | ||
| 268 | .init = cht_codec_init, | ||
| 269 | .be_hw_params_fixup = cht_codec_fixup, | ||
| 270 | .ignore_suspend = 1, | ||
| 271 | .dpcm_playback = 1, | ||
| 272 | .dpcm_capture = 1, | ||
| 273 | .ops = &cht_be_ssp2_ops, | ||
| 274 | }, | ||
| 275 | }; | ||
| 276 | |||
| 277 | /* SoC card */ | ||
| 278 | static struct snd_soc_card snd_soc_card_cht = { | ||
| 279 | .name = "chtrt5645", | ||
| 280 | .dai_link = cht_dailink, | ||
| 281 | .num_links = ARRAY_SIZE(cht_dailink), | ||
| 282 | .dapm_widgets = cht_dapm_widgets, | ||
| 283 | .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), | ||
| 284 | .dapm_routes = cht_audio_map, | ||
| 285 | .num_dapm_routes = ARRAY_SIZE(cht_audio_map), | ||
| 286 | .controls = cht_mc_controls, | ||
| 287 | .num_controls = ARRAY_SIZE(cht_mc_controls), | ||
| 288 | }; | ||
| 289 | |||
| 290 | static int snd_cht_mc_probe(struct platform_device *pdev) | ||
| 291 | { | ||
| 292 | int ret_val = 0; | ||
| 293 | struct cht_mc_private *drv; | ||
| 294 | |||
| 295 | drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_ATOMIC); | ||
| 296 | if (!drv) | ||
| 297 | return -ENOMEM; | ||
| 298 | |||
| 299 | snd_soc_card_cht.dev = &pdev->dev; | ||
| 300 | snd_soc_card_set_drvdata(&snd_soc_card_cht, drv); | ||
| 301 | ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); | ||
| 302 | if (ret_val) { | ||
| 303 | dev_err(&pdev->dev, | ||
| 304 | "snd_soc_register_card failed %d\n", ret_val); | ||
| 305 | return ret_val; | ||
| 306 | } | ||
| 307 | platform_set_drvdata(pdev, &snd_soc_card_cht); | ||
| 308 | return ret_val; | ||
| 309 | } | ||
| 310 | |||
| 311 | static struct platform_driver snd_cht_mc_driver = { | ||
| 312 | .driver = { | ||
| 313 | .name = "cht-bsw-rt5645", | ||
| 314 | .pm = &snd_soc_pm_ops, | ||
| 315 | }, | ||
| 316 | .probe = snd_cht_mc_probe, | ||
| 317 | }; | ||
| 318 | |||
| 319 | module_platform_driver(snd_cht_mc_driver) | ||
| 320 | |||
| 321 | MODULE_DESCRIPTION("ASoC Intel(R) Braswell Machine driver"); | ||
| 322 | MODULE_AUTHOR("Fang, Yang A,N,Harshapriya"); | ||
| 323 | MODULE_LICENSE("GPL v2"); | ||
| 324 | MODULE_ALIAS("platform:cht-bsw-rt5645"); | ||
