1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * cht-bsw-nau8824.c - ASoc Machine driver for Intel Cherryview-based 4 * platforms Cherrytrail and Braswell, with nau8824 codec. 5 * 6 * Copyright (C) 2018 Intel Corp 7 * Copyright (C) 2018 Nuvoton Technology Corp 8 * 9 * Author: Wang, Joseph C <joequant@gmail.com> 10 * Co-author: John Hsu <KCHSU0@nuvoton.com> 11 * This file is based on cht_bsw_rt5672.c and cht-bsw-max98090.c 12 */ 13 14 #include <linux/module.h> 15 #include <linux/platform_device.h> 16 #include <linux/slab.h> 17 #include <sound/pcm.h> 18 #include <sound/pcm_params.h> 19 #include <sound/soc.h> 20 #include <sound/soc-acpi.h> 21 #include <sound/jack.h> 22 #include <linux/input.h> 23 #include "../atom/sst-atom-controls.h" 24 #include "../../codecs/nau8824.h" 25 26 struct cht_mc_private { 27 struct snd_soc_jack jack; 28 }; 29 30 static struct snd_soc_jack_pin cht_bsw_jack_pins[] = { 31 { 32 .pin = "Headphone", 33 .mask = SND_JACK_HEADPHONE, 34 }, 35 { 36 .pin = "Headset Mic", 37 .mask = SND_JACK_MICROPHONE, 38 }, 39 }; 40 41 static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { 42 SND_SOC_DAPM_HP("Headphone", NULL), 43 SND_SOC_DAPM_MIC("Headset Mic", NULL), 44 SND_SOC_DAPM_MIC("Int Mic", NULL), 45 SND_SOC_DAPM_SPK("Ext Spk", NULL), 46 }; 47 48 static const struct snd_soc_dapm_route cht_audio_map[] = { 49 {"Ext Spk", NULL, "SPKOUTL"}, 50 {"Ext Spk", NULL, "SPKOUTR"}, 51 {"Headphone", NULL, "HPOL"}, 52 {"Headphone", NULL, "HPOR"}, 53 {"MIC1", NULL, "Int Mic"}, 54 {"MIC2", NULL, "Int Mic"}, 55 {"HSMIC1", NULL, "Headset Mic"}, 56 {"HSMIC2", NULL, "Headset Mic"}, 57 {"Playback", NULL, "ssp2 Tx"}, 58 {"ssp2 Tx", NULL, "codec_out0"}, 59 {"ssp2 Tx", NULL, "codec_out1"}, 60 {"codec_in0", NULL, "ssp2 Rx" }, 61 {"codec_in1", NULL, "ssp2 Rx" }, 62 {"ssp2 Rx", NULL, "Capture"}, 63 }; 64 65 static const struct snd_kcontrol_new cht_mc_controls[] = { 66 SOC_DAPM_PIN_SWITCH("Headphone"), 67 SOC_DAPM_PIN_SWITCH("Headset Mic"), 68 SOC_DAPM_PIN_SWITCH("Int Mic"), 69 SOC_DAPM_PIN_SWITCH("Ext Spk"), 70 }; 71 72 static int cht_aif1_hw_params(struct snd_pcm_substream *substream, 73 struct snd_pcm_hw_params *params) 74 { 75 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 76 struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); 77 int ret; 78 79 ret = snd_soc_dai_set_sysclk(codec_dai, NAU8824_CLK_FLL_FS, 0, 80 SND_SOC_CLOCK_IN); 81 if (ret < 0) { 82 dev_err(codec_dai->dev, "can't set FS clock %d\n", ret); 83 return ret; 84 } 85 ret = snd_soc_dai_set_pll(codec_dai, 0, 0, params_rate(params), 86 params_rate(params) * 256); 87 if (ret < 0) { 88 dev_err(codec_dai->dev, "can't set FLL: %d\n", ret); 89 return ret; 90 } 91 92 return 0; 93 } 94 95 static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) 96 { 97 struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card); 98 struct snd_soc_jack *jack = &ctx->jack; 99 struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); 100 struct snd_soc_component *component = codec_dai->component; 101 int ret, jack_type; 102 103 /* NAU88L24 supports 4 buttons headset detection 104 * KEY_PLAYPAUSE 105 * KEY_VOICECOMMAND 106 * KEY_VOLUMEUP 107 * KEY_VOLUMEDOWN 108 */ 109 jack_type = SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | 110 SND_JACK_BTN_2 | SND_JACK_BTN_3; 111 ret = snd_soc_card_jack_new_pins(runtime->card, "Headset", jack_type, 112 jack, cht_bsw_jack_pins, ARRAY_SIZE(cht_bsw_jack_pins)); 113 if (ret) { 114 dev_err(runtime->dev, 115 "Headset Jack creation failed %d\n", ret); 116 return ret; 117 } 118 snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); 119 snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); 120 snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); 121 snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); 122 123 nau8824_enable_jack_detect(component, jack); 124 125 return ret; 126 } 127 128 static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, 129 struct snd_pcm_hw_params *params) 130 { 131 struct snd_interval *rate = hw_param_interval(params, 132 SNDRV_PCM_HW_PARAM_RATE); 133 struct snd_interval *channels = hw_param_interval(params, 134 SNDRV_PCM_HW_PARAM_CHANNELS); 135 struct snd_mask *fmt = 136 hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); 137 int ret; 138 139 /* The DSP will convert the FE rate to 48k, stereo, 24bits */ 140 rate->min = rate->max = 48000; 141 channels->min = channels->max = 2; 142 143 /* set SSP2 to 24-bit */ 144 snd_mask_none(fmt); 145 params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); 146 147 /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ 148 ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_codec(rtd, 0), 0xf, 0x1, 4, 24); 149 if (ret < 0) { 150 dev_err(rtd->dev, "can't set codec TDM slot %d\n", ret); 151 return ret; 152 } 153 154 return 0; 155 } 156 157 static int cht_aif1_startup(struct snd_pcm_substream *substream) 158 { 159 return snd_pcm_hw_constraint_single(substream->runtime, 160 SNDRV_PCM_HW_PARAM_RATE, 48000); 161 } 162 163 static const struct snd_soc_ops cht_aif1_ops = { 164 .startup = cht_aif1_startup, 165 }; 166 167 static const struct snd_soc_ops cht_be_ssp2_ops = { 168 .hw_params = cht_aif1_hw_params, 169 }; 170 171 SND_SOC_DAILINK_DEF(dummy, 172 DAILINK_COMP_ARRAY(COMP_DUMMY())); 173 174 SND_SOC_DAILINK_DEF(media, 175 DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai"))); 176 177 SND_SOC_DAILINK_DEF(deepbuffer, 178 DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai"))); 179 180 SND_SOC_DAILINK_DEF(ssp2_port, 181 DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port"))); 182 SND_SOC_DAILINK_DEF(ssp2_codec, 183 DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10508824:00", 184 NAU8824_CODEC_DAI))); 185 186 SND_SOC_DAILINK_DEF(platform, 187 DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform"))); 188 189 static struct snd_soc_dai_link cht_dailink[] = { 190 /* Front End DAI links */ 191 [MERR_DPCM_AUDIO] = { 192 .name = "Audio Port", 193 .stream_name = "Audio", 194 .nonatomic = true, 195 .dynamic = 1, 196 .dpcm_playback = 1, 197 .dpcm_capture = 1, 198 .ops = &cht_aif1_ops, 199 SND_SOC_DAILINK_REG(media, dummy, platform), 200 }, 201 [MERR_DPCM_DEEP_BUFFER] = { 202 .name = "Deep-Buffer Audio Port", 203 .stream_name = "Deep-Buffer Audio", 204 .nonatomic = true, 205 .dynamic = 1, 206 .dpcm_playback = 1, 207 .ops = &cht_aif1_ops, 208 SND_SOC_DAILINK_REG(deepbuffer, dummy, platform), 209 }, 210 /* Back End DAI links */ 211 { 212 /* SSP2 - Codec */ 213 .name = "SSP2-Codec", 214 .id = 0, 215 .no_pcm = 1, 216 .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF 217 | SND_SOC_DAIFMT_CBC_CFC, 218 .init = cht_codec_init, 219 .be_hw_params_fixup = cht_codec_fixup, 220 .dpcm_playback = 1, 221 .dpcm_capture = 1, 222 .ops = &cht_be_ssp2_ops, 223 SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform), 224 }, 225 }; 226 227 /* use space before codec name to simplify card ID, and simplify driver name */ 228 #define SOF_CARD_NAME "bytcht nau8824" /* card name will be 'sof-bytcht nau8824 */ 229 #define SOF_DRIVER_NAME "SOF" 230 231 #define CARD_NAME "chtnau8824" 232 #define DRIVER_NAME NULL /* card name will be used for driver name */ 233 234 /* SoC card */ 235 static struct snd_soc_card snd_soc_card_cht = { 236 .owner = THIS_MODULE, 237 .dai_link = cht_dailink, 238 .num_links = ARRAY_SIZE(cht_dailink), 239 .dapm_widgets = cht_dapm_widgets, 240 .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), 241 .dapm_routes = cht_audio_map, 242 .num_dapm_routes = ARRAY_SIZE(cht_audio_map), 243 .controls = cht_mc_controls, 244 .num_controls = ARRAY_SIZE(cht_mc_controls), 245 }; 246 247 static int snd_cht_mc_probe(struct platform_device *pdev) 248 { 249 struct cht_mc_private *drv; 250 struct snd_soc_acpi_mach *mach; 251 const char *platform_name; 252 bool sof_parent; 253 int ret_val; 254 255 drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); 256 if (!drv) 257 return -ENOMEM; 258 snd_soc_card_set_drvdata(&snd_soc_card_cht, drv); 259 260 /* override platform name, if required */ 261 snd_soc_card_cht.dev = &pdev->dev; 262 mach = pdev->dev.platform_data; 263 platform_name = mach->mach_params.platform; 264 265 ret_val = snd_soc_fixup_dai_links_platform_name(&snd_soc_card_cht, 266 platform_name); 267 if (ret_val) 268 return ret_val; 269 270 sof_parent = snd_soc_acpi_sof_parent(&pdev->dev); 271 272 /* set card and driver name */ 273 if (sof_parent) { 274 snd_soc_card_cht.name = SOF_CARD_NAME; 275 snd_soc_card_cht.driver_name = SOF_DRIVER_NAME; 276 } else { 277 snd_soc_card_cht.name = CARD_NAME; 278 snd_soc_card_cht.driver_name = DRIVER_NAME; 279 } 280 281 snd_soc_card_cht.components = nau8824_components(); 282 283 /* set pm ops */ 284 if (sof_parent) 285 pdev->dev.driver->pm = &snd_soc_pm_ops; 286 287 /* register the soc card */ 288 ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); 289 if (ret_val) { 290 dev_err(&pdev->dev, 291 "snd_soc_register_card failed %d\n", ret_val); 292 return ret_val; 293 } 294 platform_set_drvdata(pdev, &snd_soc_card_cht); 295 296 return ret_val; 297 } 298 299 static struct platform_driver snd_cht_mc_driver = { 300 .driver = { 301 .name = "cht-bsw-nau8824", 302 }, 303 .probe = snd_cht_mc_probe, 304 }; 305 306 module_platform_driver(snd_cht_mc_driver); 307 308 MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver"); 309 MODULE_AUTHOR("Wang, Joseph C <joequant@gmail.com>"); 310 MODULE_AUTHOR("John Hsu <KCHSU0@nuvoton.com>"); 311 MODULE_LICENSE("GPL v2"); 312 MODULE_ALIAS("platform:cht-bsw-nau8824"); 313