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/soc.h> 25 #include <dt-bindings/sound/apq8016-lpass.h> 26 27 struct apq8016_sbc_data { 28 void __iomem *mic_iomux; 29 void __iomem *spkr_iomux; 30 struct snd_soc_dai_link dai_link[]; /* dynamically allocated */ 31 }; 32 33 #define MIC_CTRL_QUA_WS_SLAVE_SEL_10 BIT(17) 34 #define MIC_CTRL_TLMM_SCLK_EN BIT(1) 35 #define SPKR_CTL_PRI_WS_SLAVE_SEL_11 (BIT(17) | BIT(16)) 36 37 static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd) 38 { 39 struct snd_soc_dai *cpu_dai = rtd->cpu_dai; 40 struct snd_soc_card *card = rtd->card; 41 struct apq8016_sbc_data *pdata = snd_soc_card_get_drvdata(card); 42 int rval = 0; 43 44 switch (cpu_dai->id) { 45 case MI2S_PRIMARY: 46 writel(readl(pdata->spkr_iomux) | SPKR_CTL_PRI_WS_SLAVE_SEL_11, 47 pdata->spkr_iomux); 48 break; 49 50 case MI2S_QUATERNARY: 51 /* Configure the Quat MI2S to TLMM */ 52 writel(readl(pdata->mic_iomux) | MIC_CTRL_QUA_WS_SLAVE_SEL_10 | 53 MIC_CTRL_TLMM_SCLK_EN, 54 pdata->mic_iomux); 55 break; 56 57 default: 58 dev_err(card->dev, "unsupported cpu dai configuration\n"); 59 rval = -EINVAL; 60 break; 61 62 } 63 64 return rval; 65 } 66 67 static struct apq8016_sbc_data *apq8016_sbc_parse_of(struct snd_soc_card *card) 68 { 69 struct device *dev = card->dev; 70 struct snd_soc_dai_link *link; 71 struct device_node *np, *codec, *cpu, *node = dev->of_node; 72 struct apq8016_sbc_data *data; 73 int ret, num_links; 74 75 ret = snd_soc_of_parse_card_name(card, "qcom,model"); 76 if (ret) { 77 dev_err(dev, "Error parsing card name: %d\n", ret); 78 return ERR_PTR(ret); 79 } 80 81 /* Populate links */ 82 num_links = of_get_child_count(node); 83 84 /* Allocate the private data and the DAI link array */ 85 data = devm_kzalloc(dev, sizeof(*data) + sizeof(*link) * num_links, 86 GFP_KERNEL); 87 if (!data) 88 return ERR_PTR(-ENOMEM); 89 90 card->dai_link = &data->dai_link[0]; 91 card->num_links = num_links; 92 93 link = data->dai_link; 94 95 for_each_child_of_node(node, np) { 96 cpu = of_get_child_by_name(np, "cpu"); 97 codec = of_get_child_by_name(np, "codec"); 98 99 if (!cpu || !codec) { 100 dev_err(dev, "Can't find cpu/codec DT node\n"); 101 return ERR_PTR(-EINVAL); 102 } 103 104 link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0); 105 if (!link->cpu_of_node) { 106 dev_err(card->dev, "error getting cpu phandle\n"); 107 return ERR_PTR(-EINVAL); 108 } 109 110 link->codec_of_node = of_parse_phandle(codec, "sound-dai", 0); 111 if (!link->codec_of_node) { 112 dev_err(card->dev, "error getting codec phandle\n"); 113 return ERR_PTR(-EINVAL); 114 } 115 116 ret = snd_soc_of_get_dai_name(cpu, &link->cpu_dai_name); 117 if (ret) { 118 dev_err(card->dev, "error getting cpu dai name\n"); 119 return ERR_PTR(ret); 120 } 121 122 ret = snd_soc_of_get_dai_name(codec, &link->codec_dai_name); 123 if (ret) { 124 dev_err(card->dev, "error getting codec dai name\n"); 125 return ERR_PTR(ret); 126 } 127 128 link->platform_of_node = link->cpu_of_node; 129 /* For now we only support playback */ 130 link->playback_only = true; 131 132 ret = of_property_read_string(np, "link-name", &link->name); 133 if (ret) { 134 dev_err(card->dev, "error getting codec dai_link name\n"); 135 return ERR_PTR(ret); 136 } 137 138 link->stream_name = link->name; 139 link->init = apq8016_sbc_dai_init; 140 link++; 141 } 142 143 return data; 144 } 145 146 static int apq8016_sbc_platform_probe(struct platform_device *pdev) 147 { 148 struct device *dev = &pdev->dev; 149 struct snd_soc_card *card; 150 struct apq8016_sbc_data *data; 151 struct resource *res; 152 153 card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); 154 if (!card) 155 return -ENOMEM; 156 157 card->dev = dev; 158 data = apq8016_sbc_parse_of(card); 159 if (IS_ERR(data)) { 160 dev_err(&pdev->dev, "Error resolving dai links: %ld\n", 161 PTR_ERR(data)); 162 return PTR_ERR(data); 163 } 164 165 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mic-iomux"); 166 data->mic_iomux = devm_ioremap_resource(dev, res); 167 if (IS_ERR(data->mic_iomux)) 168 return PTR_ERR(data->mic_iomux); 169 170 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "spkr-iomux"); 171 data->spkr_iomux = devm_ioremap_resource(dev, res); 172 if (IS_ERR(data->spkr_iomux)) 173 return PTR_ERR(data->spkr_iomux); 174 175 platform_set_drvdata(pdev, data); 176 snd_soc_card_set_drvdata(card, data); 177 178 return devm_snd_soc_register_card(&pdev->dev, card); 179 } 180 181 static const struct of_device_id apq8016_sbc_device_id[] = { 182 { .compatible = "qcom,apq8016-sbc-sndcard" }, 183 {}, 184 }; 185 MODULE_DEVICE_TABLE(of, apq8016_sbc_device_id); 186 187 static struct platform_driver apq8016_sbc_platform_driver = { 188 .driver = { 189 .name = "qcom-apq8016-sbc", 190 .of_match_table = of_match_ptr(apq8016_sbc_device_id), 191 }, 192 .probe = apq8016_sbc_platform_probe, 193 }; 194 module_platform_driver(apq8016_sbc_platform_driver); 195 196 MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); 197 MODULE_DESCRIPTION("APQ8016 ASoC Machine Driver"); 198 MODULE_LICENSE("GPL v2"); 199