1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (c) 2015 The Linux Foundation. All rights reserved. 4 */ 5 6 #include <linux/device.h> 7 #include <linux/module.h> 8 #include <linux/kernel.h> 9 #include <linux/io.h> 10 #include <linux/of.h> 11 #include <linux/clk.h> 12 #include <linux/platform_device.h> 13 #include <sound/pcm.h> 14 #include <sound/pcm_params.h> 15 #include <sound/jack.h> 16 #include <sound/soc.h> 17 #include <uapi/linux/input-event-codes.h> 18 #include <dt-bindings/sound/apq8016-lpass.h> 19 #include <dt-bindings/sound/qcom,q6afe.h> 20 #include "common.h" 21 #include "qdsp6/q6afe.h" 22 23 #define MI2S_COUNT (MI2S_QUATERNARY + 1) 24 25 struct apq8016_sbc_data { 26 struct snd_soc_card card; 27 void __iomem *mic_iomux; 28 void __iomem *spkr_iomux; 29 struct snd_soc_jack jack; 30 bool jack_setup; 31 int mi2s_clk_count[MI2S_COUNT]; 32 }; 33 34 #define MIC_CTRL_TER_WS_SLAVE_SEL BIT(21) 35 #define MIC_CTRL_QUA_WS_SLAVE_SEL_10 BIT(17) 36 #define MIC_CTRL_TLMM_SCLK_EN BIT(1) 37 #define SPKR_CTL_PRI_WS_SLAVE_SEL_11 (BIT(17) | BIT(16)) 38 #define SPKR_CTL_TLMM_MCLK_EN BIT(1) 39 #define SPKR_CTL_TLMM_SCLK_EN BIT(2) 40 #define SPKR_CTL_TLMM_DATA1_EN BIT(3) 41 #define SPKR_CTL_TLMM_WS_OUT_SEL_MASK GENMASK(7, 6) 42 #define SPKR_CTL_TLMM_WS_OUT_SEL_SEC BIT(6) 43 #define SPKR_CTL_TLMM_WS_EN_SEL_MASK GENMASK(19, 18) 44 #define SPKR_CTL_TLMM_WS_EN_SEL_SEC BIT(18) 45 #define DEFAULT_MCLK_RATE 9600000 46 #define MI2S_BCLK_RATE 1536000 47 48 static struct snd_soc_jack_pin apq8016_sbc_jack_pins[] = { 49 { 50 .pin = "Mic Jack", 51 .mask = SND_JACK_MICROPHONE, 52 }, 53 { 54 .pin = "Headphone Jack", 55 .mask = SND_JACK_HEADPHONE, 56 }, 57 }; 58 59 static int apq8016_dai_init(struct snd_soc_pcm_runtime *rtd, int mi2s) 60 { 61 struct snd_soc_dai *codec_dai; 62 struct snd_soc_component *component; 63 struct snd_soc_card *card = rtd->card; 64 struct apq8016_sbc_data *pdata = snd_soc_card_get_drvdata(card); 65 int i, rval; 66 u32 value; 67 68 switch (mi2s) { 69 case MI2S_PRIMARY: 70 writel(readl(pdata->spkr_iomux) | SPKR_CTL_PRI_WS_SLAVE_SEL_11, 71 pdata->spkr_iomux); 72 break; 73 74 case MI2S_QUATERNARY: 75 /* Configure the Quat MI2S to TLMM */ 76 writel(readl(pdata->mic_iomux) | MIC_CTRL_QUA_WS_SLAVE_SEL_10 | 77 MIC_CTRL_TLMM_SCLK_EN, 78 pdata->mic_iomux); 79 break; 80 case MI2S_SECONDARY: 81 /* Clear TLMM_WS_OUT_SEL and TLMM_WS_EN_SEL fields */ 82 value = readl(pdata->spkr_iomux) & 83 ~(SPKR_CTL_TLMM_WS_OUT_SEL_MASK | SPKR_CTL_TLMM_WS_EN_SEL_MASK); 84 /* Configure the Sec MI2S to TLMM */ 85 writel(value | SPKR_CTL_TLMM_MCLK_EN | SPKR_CTL_TLMM_SCLK_EN | 86 SPKR_CTL_TLMM_DATA1_EN | SPKR_CTL_TLMM_WS_OUT_SEL_SEC | 87 SPKR_CTL_TLMM_WS_EN_SEL_SEC, pdata->spkr_iomux); 88 break; 89 case MI2S_TERTIARY: 90 writel(readl(pdata->mic_iomux) | MIC_CTRL_TER_WS_SLAVE_SEL | 91 MIC_CTRL_TLMM_SCLK_EN, 92 pdata->mic_iomux); 93 94 break; 95 96 default: 97 dev_err(card->dev, "unsupported cpu dai configuration\n"); 98 return -EINVAL; 99 100 } 101 102 if (!pdata->jack_setup) { 103 struct snd_jack *jack; 104 105 rval = snd_soc_card_jack_new_pins(card, "Headset Jack", 106 SND_JACK_HEADSET | 107 SND_JACK_HEADPHONE | 108 SND_JACK_BTN_0 | SND_JACK_BTN_1 | 109 SND_JACK_BTN_2 | SND_JACK_BTN_3 | 110 SND_JACK_BTN_4, 111 &pdata->jack, 112 apq8016_sbc_jack_pins, 113 ARRAY_SIZE(apq8016_sbc_jack_pins)); 114 115 if (rval < 0) { 116 dev_err(card->dev, "Unable to add Headphone Jack\n"); 117 return rval; 118 } 119 120 jack = pdata->jack.jack; 121 122 snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); 123 snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); 124 snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP); 125 snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); 126 pdata->jack_setup = true; 127 } 128 129 for_each_rtd_codec_dais(rtd, i, codec_dai) { 130 131 component = codec_dai->component; 132 /* Set default mclk for internal codec */ 133 rval = snd_soc_component_set_sysclk(component, 0, 0, DEFAULT_MCLK_RATE, 134 SND_SOC_CLOCK_IN); 135 if (rval != 0 && rval != -ENOTSUPP) { 136 dev_warn(card->dev, "Failed to set mclk: %d\n", rval); 137 return rval; 138 } 139 rval = snd_soc_component_set_jack(component, &pdata->jack, NULL); 140 if (rval != 0 && rval != -ENOTSUPP) { 141 dev_warn(card->dev, "Failed to set jack: %d\n", rval); 142 return rval; 143 } 144 } 145 146 return 0; 147 } 148 149 static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd) 150 { 151 struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); 152 153 return apq8016_dai_init(rtd, cpu_dai->id); 154 } 155 156 static void apq8016_sbc_add_ops(struct snd_soc_card *card) 157 { 158 struct snd_soc_dai_link *link; 159 int i; 160 161 for_each_card_prelinks(card, i, link) 162 link->init = apq8016_sbc_dai_init; 163 } 164 165 static int qdsp6_dai_get_lpass_id(struct snd_soc_dai *cpu_dai) 166 { 167 switch (cpu_dai->id) { 168 case PRIMARY_MI2S_RX: 169 case PRIMARY_MI2S_TX: 170 return MI2S_PRIMARY; 171 case SECONDARY_MI2S_RX: 172 case SECONDARY_MI2S_TX: 173 return MI2S_SECONDARY; 174 case TERTIARY_MI2S_RX: 175 case TERTIARY_MI2S_TX: 176 return MI2S_TERTIARY; 177 case QUATERNARY_MI2S_RX: 178 case QUATERNARY_MI2S_TX: 179 return MI2S_QUATERNARY; 180 default: 181 return -EINVAL; 182 } 183 } 184 185 static int msm8916_qdsp6_dai_init(struct snd_soc_pcm_runtime *rtd) 186 { 187 struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); 188 189 snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); 190 return apq8016_dai_init(rtd, qdsp6_dai_get_lpass_id(cpu_dai)); 191 } 192 193 static int msm8916_qdsp6_startup(struct snd_pcm_substream *substream) 194 { 195 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 196 struct snd_soc_card *card = rtd->card; 197 struct apq8016_sbc_data *data = snd_soc_card_get_drvdata(card); 198 struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); 199 int mi2s, ret; 200 201 mi2s = qdsp6_dai_get_lpass_id(cpu_dai); 202 if (mi2s < 0) 203 return mi2s; 204 205 if (++data->mi2s_clk_count[mi2s] > 1) 206 return 0; 207 208 ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_BIT_CLK, MI2S_BCLK_RATE, 0); 209 if (ret) 210 dev_err(card->dev, "Failed to enable LPAIF bit clk: %d\n", ret); 211 return ret; 212 } 213 214 static void msm8916_qdsp6_shutdown(struct snd_pcm_substream *substream) 215 { 216 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 217 struct snd_soc_card *card = rtd->card; 218 struct apq8016_sbc_data *data = snd_soc_card_get_drvdata(card); 219 struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); 220 int mi2s, ret; 221 222 mi2s = qdsp6_dai_get_lpass_id(cpu_dai); 223 if (mi2s < 0) 224 return; 225 226 if (--data->mi2s_clk_count[mi2s] > 0) 227 return; 228 229 ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_BIT_CLK, 0, 0); 230 if (ret) 231 dev_err(card->dev, "Failed to disable LPAIF bit clk: %d\n", ret); 232 } 233 234 static const struct snd_soc_ops msm8916_qdsp6_be_ops = { 235 .startup = msm8916_qdsp6_startup, 236 .shutdown = msm8916_qdsp6_shutdown, 237 }; 238 239 static int msm8916_qdsp6_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, 240 struct snd_pcm_hw_params *params) 241 { 242 struct snd_interval *rate = hw_param_interval(params, 243 SNDRV_PCM_HW_PARAM_RATE); 244 struct snd_interval *channels = hw_param_interval(params, 245 SNDRV_PCM_HW_PARAM_CHANNELS); 246 struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); 247 248 rate->min = rate->max = 48000; 249 channels->min = channels->max = 2; 250 snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); 251 252 return 0; 253 } 254 255 static void msm8916_qdsp6_add_ops(struct snd_soc_card *card) 256 { 257 struct snd_soc_dai_link *link; 258 int i; 259 260 /* Make it obvious to userspace that QDSP6 is used */ 261 card->components = "qdsp6"; 262 263 for_each_card_prelinks(card, i, link) { 264 if (link->no_pcm) { 265 link->init = msm8916_qdsp6_dai_init; 266 link->ops = &msm8916_qdsp6_be_ops; 267 link->be_hw_params_fixup = msm8916_qdsp6_be_hw_params_fixup; 268 } 269 } 270 } 271 272 static const struct snd_kcontrol_new apq8016_sbc_snd_controls[] = { 273 SOC_DAPM_PIN_SWITCH("Headphone Jack"), 274 SOC_DAPM_PIN_SWITCH("Mic Jack"), 275 }; 276 277 static const struct snd_soc_dapm_widget apq8016_sbc_dapm_widgets[] = { 278 SND_SOC_DAPM_HP("Headphone Jack", NULL), 279 SND_SOC_DAPM_MIC("Mic Jack", NULL), 280 SND_SOC_DAPM_MIC("Handset Mic", NULL), 281 SND_SOC_DAPM_MIC("Headset Mic", NULL), 282 SND_SOC_DAPM_MIC("Secondary Mic", NULL), 283 SND_SOC_DAPM_MIC("Digital Mic1", NULL), 284 SND_SOC_DAPM_MIC("Digital Mic2", NULL), 285 }; 286 287 static int apq8016_sbc_platform_probe(struct platform_device *pdev) 288 { 289 void (*add_ops)(struct snd_soc_card *card); 290 struct device *dev = &pdev->dev; 291 struct snd_soc_card *card; 292 struct apq8016_sbc_data *data; 293 int ret; 294 295 add_ops = device_get_match_data(&pdev->dev); 296 if (!add_ops) 297 return -EINVAL; 298 299 data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 300 if (!data) 301 return -ENOMEM; 302 303 card = &data->card; 304 card->dev = dev; 305 card->owner = THIS_MODULE; 306 card->dapm_widgets = apq8016_sbc_dapm_widgets; 307 card->num_dapm_widgets = ARRAY_SIZE(apq8016_sbc_dapm_widgets); 308 card->controls = apq8016_sbc_snd_controls; 309 card->num_controls = ARRAY_SIZE(apq8016_sbc_snd_controls); 310 311 ret = qcom_snd_parse_of(card); 312 if (ret) 313 return ret; 314 315 data->mic_iomux = devm_platform_ioremap_resource_byname(pdev, "mic-iomux"); 316 if (IS_ERR(data->mic_iomux)) 317 return PTR_ERR(data->mic_iomux); 318 319 data->spkr_iomux = devm_platform_ioremap_resource_byname(pdev, "spkr-iomux"); 320 if (IS_ERR(data->spkr_iomux)) 321 return PTR_ERR(data->spkr_iomux); 322 323 snd_soc_card_set_drvdata(card, data); 324 325 add_ops(card); 326 return devm_snd_soc_register_card(&pdev->dev, card); 327 } 328 329 static const struct of_device_id apq8016_sbc_device_id[] __maybe_unused = { 330 { .compatible = "qcom,apq8016-sbc-sndcard", .data = apq8016_sbc_add_ops }, 331 { .compatible = "qcom,msm8916-qdsp6-sndcard", .data = msm8916_qdsp6_add_ops }, 332 {}, 333 }; 334 MODULE_DEVICE_TABLE(of, apq8016_sbc_device_id); 335 336 static struct platform_driver apq8016_sbc_platform_driver = { 337 .driver = { 338 .name = "qcom-apq8016-sbc", 339 .of_match_table = of_match_ptr(apq8016_sbc_device_id), 340 }, 341 .probe = apq8016_sbc_platform_probe, 342 }; 343 module_platform_driver(apq8016_sbc_platform_driver); 344 345 MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); 346 MODULE_DESCRIPTION("APQ8016 ASoC Machine Driver"); 347 MODULE_LICENSE("GPL"); 348
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.