1 /* 2 * Copyright (c) 2015 The Linux Foundation. All rights reserved. 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License version 2 and 6 * only version 2 as published by the Free Software Foundation. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 */ 14 15 #include <linux/device.h> 16 #include <linux/module.h> 17 #include <linux/kernel.h> 18 #include <linux/io.h> 19 #include <linux/of.h> 20 #include <linux/clk.h> 21 #include <linux/platform_device.h> 22 #include <sound/pcm.h> 23 #include <sound/pcm_params.h> 24 #include <sound/jack.h> 25 #include <sound/soc.h> 26 #include <uapi/linux/input-event-codes.h> 27 #include <dt-bindings/sound/apq8016-lpass.h> 28 29 struct apq8016_sbc_data { 30 void __iomem *mic_iomux; 31 void __iomem *spkr_iomux; 32 struct snd_soc_jack jack; 33 bool jack_setup; 34 struct snd_soc_dai_link dai_link[]; /* dynamically allocated */ 35 }; 36 37 #define MIC_CTRL_TER_WS_SLAVE_SEL BIT(21) 38 #define MIC_CTRL_QUA_WS_SLAVE_SEL_10 BIT(17) 39 #define MIC_CTRL_TLMM_SCLK_EN BIT(1) 40 #define SPKR_CTL_PRI_WS_SLAVE_SEL_11 (BIT(17) | BIT(16)) 41 #define DEFAULT_MCLK_RATE 9600000 42 43 static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd) 44 { 45 struct snd_soc_dai *cpu_dai = rtd->cpu_dai; 46 struct snd_soc_component *component; 47 struct snd_soc_dai_link *dai_link = rtd->dai_link; 48 struct snd_soc_card *card = rtd->card; 49 struct apq8016_sbc_data *pdata = snd_soc_card_get_drvdata(card); 50 int i, rval; 51 52 switch (cpu_dai->id) { 53 case MI2S_PRIMARY: 54 writel(readl(pdata->spkr_iomux) | SPKR_CTL_PRI_WS_SLAVE_SEL_11, 55 pdata->spkr_iomux); 56 break; 57 58 case MI2S_QUATERNARY: 59 /* Configure the Quat MI2S to TLMM */ 60 writel(readl(pdata->mic_iomux) | MIC_CTRL_QUA_WS_SLAVE_SEL_10 | 61 MIC_CTRL_TLMM_SCLK_EN, 62 pdata->mic_iomux); 63 break; 64 case MI2S_TERTIARY: 65 writel(readl(pdata->mic_iomux) | MIC_CTRL_TER_WS_SLAVE_SEL | 66 MIC_CTRL_TLMM_SCLK_EN, 67 pdata->mic_iomux); 68 69 break; 70 71 default: 72 dev_err(card->dev, "unsupported cpu dai configuration\n"); 73 return -EINVAL; 74 75 } 76 77 if (!pdata->jack_setup) { 78 struct snd_jack *jack; 79 80 rval = snd_soc_card_jack_new(card, "Headset Jack", 81 SND_JACK_HEADSET | 82 SND_JACK_HEADPHONE | 83 SND_JACK_BTN_0 | SND_JACK_BTN_1 | 84 SND_JACK_BTN_2 | SND_JACK_BTN_3 | 85 SND_JACK_BTN_4, 86 &pdata->jack, NULL, 0); 87 88 if (rval < 0) { 89 dev_err(card->dev, "Unable to add Headphone Jack\n"); 90 return rval; 91 } 92 93 jack = pdata->jack.jack; 94 95 snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); 96 snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); 97 snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP); 98 snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); 99 pdata->jack_setup = true; 100 } 101 102 for (i = 0 ; i < dai_link->num_codecs; i++) { 103 struct snd_soc_dai *dai = rtd->codec_dais[i]; 104 105 component = dai->component; 106 /* Set default mclk for internal codec */ 107 rval = snd_soc_component_set_sysclk(component, 0, 0, DEFAULT_MCLK_RATE, 108 SND_SOC_CLOCK_IN); 109 if (rval != 0 && rval != -ENOTSUPP) { 110 dev_warn(card->dev, "Failed to set mclk: %d\n", rval); 111 return rval; 112 } 113 rval = snd_soc_component_set_jack(component, &pdata->jack, NULL); 114 if (rval != 0 && rval != -ENOTSUPP) { 115 dev_warn(card->dev, "Failed to set jack: %d\n", rval); 116 return rval; 117 } 118 } 119 120 return 0; 121 } 122 123 static struct apq8016_sbc_data *apq8016_sbc_parse_of(struct snd_soc_card *card) 124 { 125 struct device *dev = card->dev; 126 struct snd_soc_dai_link *link; 127 struct device_node *np, *codec, *cpu, *node = dev->of_node; 128 struct apq8016_sbc_data *data; 129 int ret, num_links; 130 131 ret = snd_soc_of_parse_card_name(card, "qcom,model"); 132 if (ret) { 133 dev_err(dev, "Error parsing card name: %d\n", ret); 134 return ERR_PTR(ret); 135 } 136 137 /* DAPM routes */ 138 if (of_property_read_bool(node, "qcom,audio-routing")) { 139 ret = snd_soc_of_parse_audio_routing(card, 140 "qcom,audio-routing"); 141 if (ret) 142 return ERR_PTR(ret); 143 } 144 145 146 /* Populate links */ 147 num_links = of_get_child_count(node); 148 149 /* Allocate the private data and the DAI link array */ 150 data = devm_kzalloc(dev, 151 struct_size(data, dai_link, num_links), 152 GFP_KERNEL); 153 if (!data) 154 return ERR_PTR(-ENOMEM); 155 156 card->dai_link = &data->dai_link[0]; 157 card->num_links = num_links; 158 159 link = data->dai_link; 160 161 for_each_child_of_node(node, np) { 162 cpu = of_get_child_by_name(np, "cpu"); 163 codec = of_get_child_by_name(np, "codec"); 164 165 if (!cpu || !codec) { 166 dev_err(dev, "Can't find cpu/codec DT node\n"); 167 ret = -EINVAL; 168 goto error; 169 } 170 171 link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0); 172 if (!link->cpu_of_node) { 173 dev_err(card->dev, "error getting cpu phandle\n"); 174 ret = -EINVAL; 175 goto error; 176 } 177 178 ret = snd_soc_of_get_dai_name(cpu, &link->cpu_dai_name); 179 if (ret) { 180 dev_err(card->dev, "error getting cpu dai name\n"); 181 goto error; 182 } 183 184 ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); 185 186 if (ret < 0) { 187 dev_err(card->dev, "error getting codec dai name\n"); 188 goto error; 189 } 190 191 link->platform_of_node = link->cpu_of_node; 192 ret = of_property_read_string(np, "link-name", &link->name); 193 if (ret) { 194 dev_err(card->dev, "error getting codec dai_link name\n"); 195 goto error; 196 } 197 198 link->stream_name = link->name; 199 link->init = apq8016_sbc_dai_init; 200 link++; 201 202 of_node_put(cpu); 203 of_node_put(codec); 204 } 205 206 return data; 207 208 error: 209 of_node_put(np); 210 of_node_put(cpu); 211 of_node_put(codec); 212 return ERR_PTR(ret); 213 } 214 215 static const struct snd_soc_dapm_widget apq8016_sbc_dapm_widgets[] = { 216 217 SND_SOC_DAPM_MIC("Handset Mic", NULL), 218 SND_SOC_DAPM_MIC("Headset Mic", NULL), 219 SND_SOC_DAPM_MIC("Secondary Mic", NULL), 220 SND_SOC_DAPM_MIC("Digital Mic1", NULL), 221 SND_SOC_DAPM_MIC("Digital Mic2", NULL), 222 }; 223 224 static int apq8016_sbc_platform_probe(struct platform_device *pdev) 225 { 226 struct device *dev = &pdev->dev; 227 struct snd_soc_card *card; 228 struct apq8016_sbc_data *data; 229 struct resource *res; 230 231 card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); 232 if (!card) 233 return -ENOMEM; 234 235 card->dev = dev; 236 card->dapm_widgets = apq8016_sbc_dapm_widgets; 237 card->num_dapm_widgets = ARRAY_SIZE(apq8016_sbc_dapm_widgets); 238 data = apq8016_sbc_parse_of(card); 239 if (IS_ERR(data)) { 240 dev_err(&pdev->dev, "Error resolving dai links: %ld\n", 241 PTR_ERR(data)); 242 return PTR_ERR(data); 243 } 244 245 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mic-iomux"); 246 data->mic_iomux = devm_ioremap_resource(dev, res); 247 if (IS_ERR(data->mic_iomux)) 248 return PTR_ERR(data->mic_iomux); 249 250 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "spkr-iomux"); 251 data->spkr_iomux = devm_ioremap_resource(dev, res); 252 if (IS_ERR(data->spkr_iomux)) 253 return PTR_ERR(data->spkr_iomux); 254 255 snd_soc_card_set_drvdata(card, data); 256 257 return devm_snd_soc_register_card(&pdev->dev, card); 258 } 259 260 static const struct of_device_id apq8016_sbc_device_id[] = { 261 { .compatible = "qcom,apq8016-sbc-sndcard" }, 262 {}, 263 }; 264 MODULE_DEVICE_TABLE(of, apq8016_sbc_device_id); 265 266 static struct platform_driver apq8016_sbc_platform_driver = { 267 .driver = { 268 .name = "qcom-apq8016-sbc", 269 .of_match_table = of_match_ptr(apq8016_sbc_device_id), 270 }, 271 .probe = apq8016_sbc_platform_probe, 272 }; 273 module_platform_driver(apq8016_sbc_platform_driver); 274 275 MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); 276 MODULE_DESCRIPTION("APQ8016 ASoC Machine Driver"); 277 MODULE_LICENSE("GPL v2"); 278