2 * Copyright (c) 2015 The Linux Foundation. All rights reserved.
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.
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.
15 #include <linux/device.h>
16 #include <linux/module.h>
17 #include <linux/kernel.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>
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 */
33 #define MIC_CTRL_TER_WS_SLAVE_SEL BIT(21)
34 #define MIC_CTRL_QUA_WS_SLAVE_SEL_10 BIT(17)
35 #define MIC_CTRL_TLMM_SCLK_EN BIT(1)
36 #define SPKR_CTL_PRI_WS_SLAVE_SEL_11 (BIT(17) | BIT(16))
38 static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime
*rtd
)
40 struct snd_soc_dai
*cpu_dai
= rtd
->cpu_dai
;
41 struct snd_soc_card
*card
= rtd
->card
;
42 struct apq8016_sbc_data
*pdata
= snd_soc_card_get_drvdata(card
);
45 switch (cpu_dai
->id
) {
47 writel(readl(pdata
->spkr_iomux
) | SPKR_CTL_PRI_WS_SLAVE_SEL_11
,
52 /* Configure the Quat MI2S to TLMM */
53 writel(readl(pdata
->mic_iomux
) | MIC_CTRL_QUA_WS_SLAVE_SEL_10
|
54 MIC_CTRL_TLMM_SCLK_EN
,
58 writel(readl(pdata
->mic_iomux
) | MIC_CTRL_TER_WS_SLAVE_SEL
|
59 MIC_CTRL_TLMM_SCLK_EN
,
65 dev_err(card
->dev
, "unsupported cpu dai configuration\n");
74 static struct apq8016_sbc_data
*apq8016_sbc_parse_of(struct snd_soc_card
*card
)
76 struct device
*dev
= card
->dev
;
77 struct snd_soc_dai_link
*link
;
78 struct device_node
*np
, *codec
, *cpu
, *node
= dev
->of_node
;
79 struct apq8016_sbc_data
*data
;
82 ret
= snd_soc_of_parse_card_name(card
, "qcom,model");
84 dev_err(dev
, "Error parsing card name: %d\n", ret
);
89 if (of_property_read_bool(node
, "qcom,audio-routing")) {
90 ret
= snd_soc_of_parse_audio_routing(card
,
91 "qcom,audio-routing");
98 num_links
= of_get_child_count(node
);
100 /* Allocate the private data and the DAI link array */
101 data
= devm_kzalloc(dev
, sizeof(*data
) + sizeof(*link
) * num_links
,
104 return ERR_PTR(-ENOMEM
);
106 card
->dai_link
= &data
->dai_link
[0];
107 card
->num_links
= num_links
;
109 link
= data
->dai_link
;
111 for_each_child_of_node(node
, np
) {
112 cpu
= of_get_child_by_name(np
, "cpu");
113 codec
= of_get_child_by_name(np
, "codec");
115 if (!cpu
|| !codec
) {
116 dev_err(dev
, "Can't find cpu/codec DT node\n");
117 return ERR_PTR(-EINVAL
);
120 link
->cpu_of_node
= of_parse_phandle(cpu
, "sound-dai", 0);
121 if (!link
->cpu_of_node
) {
122 dev_err(card
->dev
, "error getting cpu phandle\n");
123 return ERR_PTR(-EINVAL
);
126 ret
= snd_soc_of_get_dai_name(cpu
, &link
->cpu_dai_name
);
128 dev_err(card
->dev
, "error getting cpu dai name\n");
132 ret
= snd_soc_of_get_dai_link_codecs(dev
, codec
, link
);
135 dev_err(card
->dev
, "error getting codec dai name\n");
139 link
->platform_of_node
= link
->cpu_of_node
;
140 ret
= of_property_read_string(np
, "link-name", &link
->name
);
142 dev_err(card
->dev
, "error getting codec dai_link name\n");
146 link
->stream_name
= link
->name
;
147 link
->init
= apq8016_sbc_dai_init
;
154 static const struct snd_soc_dapm_widget apq8016_sbc_dapm_widgets
[] = {
156 SND_SOC_DAPM_MIC("Handset Mic", NULL
),
157 SND_SOC_DAPM_MIC("Headset Mic", NULL
),
158 SND_SOC_DAPM_MIC("Secondary Mic", NULL
),
159 SND_SOC_DAPM_MIC("Digital Mic1", NULL
),
160 SND_SOC_DAPM_MIC("Digital Mic2", NULL
),
163 static int apq8016_sbc_platform_probe(struct platform_device
*pdev
)
165 struct device
*dev
= &pdev
->dev
;
166 struct snd_soc_card
*card
;
167 struct apq8016_sbc_data
*data
;
168 struct resource
*res
;
170 card
= devm_kzalloc(dev
, sizeof(*card
), GFP_KERNEL
);
175 card
->dapm_widgets
= apq8016_sbc_dapm_widgets
;
176 card
->num_dapm_widgets
= ARRAY_SIZE(apq8016_sbc_dapm_widgets
);
177 data
= apq8016_sbc_parse_of(card
);
179 dev_err(&pdev
->dev
, "Error resolving dai links: %ld\n",
181 return PTR_ERR(data
);
184 res
= platform_get_resource_byname(pdev
, IORESOURCE_MEM
, "mic-iomux");
185 data
->mic_iomux
= devm_ioremap_resource(dev
, res
);
186 if (IS_ERR(data
->mic_iomux
))
187 return PTR_ERR(data
->mic_iomux
);
189 res
= platform_get_resource_byname(pdev
, IORESOURCE_MEM
, "spkr-iomux");
190 data
->spkr_iomux
= devm_ioremap_resource(dev
, res
);
191 if (IS_ERR(data
->spkr_iomux
))
192 return PTR_ERR(data
->spkr_iomux
);
194 platform_set_drvdata(pdev
, data
);
195 snd_soc_card_set_drvdata(card
, data
);
197 return devm_snd_soc_register_card(&pdev
->dev
, card
);
200 static const struct of_device_id apq8016_sbc_device_id
[] = {
201 { .compatible
= "qcom,apq8016-sbc-sndcard" },
204 MODULE_DEVICE_TABLE(of
, apq8016_sbc_device_id
);
206 static struct platform_driver apq8016_sbc_platform_driver
= {
208 .name
= "qcom-apq8016-sbc",
209 .of_match_table
= of_match_ptr(apq8016_sbc_device_id
),
211 .probe
= apq8016_sbc_platform_probe
,
213 module_platform_driver(apq8016_sbc_platform_driver
);
215 MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org");
216 MODULE_DESCRIPTION("APQ8016 ASoC Machine Driver");
217 MODULE_LICENSE("GPL v2");