aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolin Chen <nicoleotsuka@gmail.com>2013-06-10 14:43:30 -0400
committerMark Brown <broonie@linaro.org>2013-06-12 11:08:24 -0400
commit8de2ae2a7f1fd71dc56d6b014029f93093e9c5d5 (patch)
treef49a12b6717132820389caa5484b6757a43c6f00
parentdbdf6b54340e1671439a4a5efbd15b7a0b14eacb (diff)
ASoC: fsl: add imx-wm8962 machine driver
This is the initial imx-wm8962 device-tree-only machine driver working with fsl_ssi driver. More features can be added on top of it later. Signed-off-by: Nicolin Chen <b42378@freescale.com> Signed-off-by: Mark Brown <broonie@linaro.org>
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt46
-rw-r--r--sound/soc/fsl/Kconfig12
-rw-r--r--sound/soc/fsl/Makefile2
-rw-r--r--sound/soc/fsl/imx-wm8962.c323
4 files changed, 383 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt b/Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt
new file mode 100644
index 000000000000..f49450a87890
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt
@@ -0,0 +1,46 @@
1Freescale i.MX audio complex with WM8962 codec
2
3Required properties:
4- compatible : "fsl,imx-audio-wm8962"
5- model : The user-visible name of this sound complex
6- ssi-controller : The phandle of the i.MX SSI controller
7- audio-codec : The phandle of the WM8962 audio codec
8- audio-routing : A list of the connections between audio components.
9 Each entry is a pair of strings, the first being the connection's sink,
10 the second being the connection's source. Valid names could be power
11 supplies, WM8962 pins, and the jacks on the board:
12
13 Power supplies:
14 * Mic Bias
15
16 Board connectors:
17 * Mic Jack
18 * Headphone Jack
19 * Ext Spk
20
21- mux-int-port : The internal port of the i.MX audio muxer (AUDMUX)
22- mux-ext-port : The external port of the i.MX audio muxer
23
24Note: The AUDMUX port numbering should start at 1, which is consistent with
25hardware manual.
26
27Example:
28
29sound {
30 compatible = "fsl,imx6q-sabresd-wm8962",
31 "fsl,imx-audio-wm8962";
32 model = "wm8962-audio";
33 ssi-controller = <&ssi2>;
34 audio-codec = <&codec>;
35 audio-routing =
36 "Headphone Jack", "HPOUTL",
37 "Headphone Jack", "HPOUTR",
38 "Ext Spk", "SPKOUTL",
39 "Ext Spk", "SPKOUTR",
40 "MICBIAS", "AMIC",
41 "IN3R", "MICBIAS",
42 "DMIC", "MICBIAS",
43 "DMICDAT", "DMIC";
44 mux-int-port = <2>;
45 mux-ext-port = <3>;
46};
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index 7860cc27e5b2..aa438546c912 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -168,6 +168,18 @@ config SND_SOC_EUKREA_TLV320
168 Enable I2S based access to the TLV320AIC23B codec attached 168 Enable I2S based access to the TLV320AIC23B codec attached
169 to the SSI interface 169 to the SSI interface
170 170
171config SND_SOC_IMX_WM8962
172 tristate "SoC Audio support for i.MX boards with wm8962"
173 depends on OF && I2C
174 select SND_SOC_WM8962
175 select SND_SOC_IMX_PCM_DMA
176 select SND_SOC_IMX_AUDMUX
177 select SND_SOC_FSL_SSI
178 select SND_SOC_FSL_UTILS
179 help
180 Say Y if you want to add support for SoC audio on an i.MX board with
181 a wm8962 codec.
182
171config SND_SOC_IMX_SGTL5000 183config SND_SOC_IMX_SGTL5000
172 tristate "SoC Audio support for i.MX boards with sgtl5000" 184 tristate "SoC Audio support for i.MX boards with sgtl5000"
173 depends on OF && I2C 185 depends on OF && I2C
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index 91883f8a2321..d4b4aa8b5649 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -42,6 +42,7 @@ snd-soc-phycore-ac97-objs := phycore-ac97.o
42snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o 42snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o
43snd-soc-wm1133-ev1-objs := wm1133-ev1.o 43snd-soc-wm1133-ev1-objs := wm1133-ev1.o
44snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o 44snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o
45snd-soc-imx-wm8962-objs := imx-wm8962.o
45snd-soc-imx-mc13783-objs := imx-mc13783.o 46snd-soc-imx-mc13783-objs := imx-mc13783.o
46 47
47obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o 48obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
@@ -49,4 +50,5 @@ obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
49obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o 50obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o
50obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o 51obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
51obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o 52obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o
53obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o
52obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o 54obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o
diff --git a/sound/soc/fsl/imx-wm8962.c b/sound/soc/fsl/imx-wm8962.c
new file mode 100644
index 000000000000..52a36a90f4f4
--- /dev/null
+++ b/sound/soc/fsl/imx-wm8962.c
@@ -0,0 +1,323 @@
1/*
2 * Copyright 2013 Freescale Semiconductor, Inc.
3 *
4 * Based on imx-sgtl5000.c
5 * Copyright 2012 Freescale Semiconductor, Inc.
6 * Copyright 2012 Linaro Ltd.
7 *
8 * The code contained herein is licensed under the GNU General Public
9 * License. You may obtain a copy of the GNU General Public License
10 * Version 2 or later at the following locations:
11 *
12 * http://www.opensource.org/licenses/gpl-license.html
13 * http://www.gnu.org/copyleft/gpl.html
14 */
15
16#include <linux/module.h>
17#include <linux/of_platform.h>
18#include <linux/of_i2c.h>
19#include <linux/slab.h>
20#include <linux/clk.h>
21#include <sound/soc.h>
22#include <sound/pcm_params.h>
23#include <sound/soc-dapm.h>
24#include <linux/pinctrl/consumer.h>
25
26#include "../codecs/wm8962.h"
27#include "imx-audmux.h"
28
29#define DAI_NAME_SIZE 32
30
31struct imx_wm8962_data {
32 struct snd_soc_dai_link dai;
33 struct snd_soc_card card;
34 char codec_dai_name[DAI_NAME_SIZE];
35 char platform_name[DAI_NAME_SIZE];
36 struct clk *codec_clk;
37 unsigned int clk_frequency;
38};
39
40struct imx_priv {
41 struct platform_device *pdev;
42};
43static struct imx_priv card_priv;
44
45static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = {
46 SND_SOC_DAPM_HP("Headphone Jack", NULL),
47 SND_SOC_DAPM_SPK("Ext Spk", NULL),
48 SND_SOC_DAPM_MIC("AMIC", NULL),
49 SND_SOC_DAPM_MIC("DMIC", NULL),
50};
51
52static int sample_rate = 44100;
53static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE;
54
55static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
56 struct snd_pcm_hw_params *params)
57{
58 sample_rate = params_rate(params);
59 sample_format = params_format(params);
60
61 return 0;
62}
63
64static struct snd_soc_ops imx_hifi_ops = {
65 .hw_params = imx_hifi_hw_params,
66};
67
68static int imx_wm8962_set_bias_level(struct snd_soc_card *card,
69 struct snd_soc_dapm_context *dapm,
70 enum snd_soc_bias_level level)
71{
72 struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
73 struct imx_priv *priv = &card_priv;
74 struct imx_wm8962_data *data = platform_get_drvdata(priv->pdev);
75 struct device *dev = &priv->pdev->dev;
76 unsigned int pll_out;
77 int ret;
78
79 if (dapm->dev != codec_dai->dev)
80 return 0;
81
82 switch (level) {
83 case SND_SOC_BIAS_PREPARE:
84 if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
85 if (sample_format == SNDRV_PCM_FORMAT_S24_LE)
86 pll_out = sample_rate * 384;
87 else
88 pll_out = sample_rate * 256;
89
90 ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
91 WM8962_FLL_MCLK, data->clk_frequency,
92 pll_out);
93 if (ret < 0) {
94 dev_err(dev, "failed to start FLL: %d\n", ret);
95 return ret;
96 }
97
98 ret = snd_soc_dai_set_sysclk(codec_dai,
99 WM8962_SYSCLK_FLL, pll_out,
100 SND_SOC_CLOCK_IN);
101 if (ret < 0) {
102 dev_err(dev, "failed to set SYSCLK: %d\n", ret);
103 return ret;
104 }
105 }
106 break;
107
108 case SND_SOC_BIAS_STANDBY:
109 if (dapm->bias_level == SND_SOC_BIAS_PREPARE) {
110 ret = snd_soc_dai_set_sysclk(codec_dai,
111 WM8962_SYSCLK_MCLK, data->clk_frequency,
112 SND_SOC_CLOCK_IN);
113 if (ret < 0) {
114 dev_err(dev,
115 "failed to switch away from FLL: %d\n",
116 ret);
117 return ret;
118 }
119
120 ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
121 0, 0, 0);
122 if (ret < 0) {
123 dev_err(dev, "failed to stop FLL: %d\n", ret);
124 return ret;
125 }
126 }
127 break;
128
129 default:
130 break;
131 }
132
133 dapm->bias_level = level;
134
135 return 0;
136}
137
138static int imx_wm8962_late_probe(struct snd_soc_card *card)
139{
140 struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
141 struct imx_priv *priv = &card_priv;
142 struct imx_wm8962_data *data = platform_get_drvdata(priv->pdev);
143 struct device *dev = &priv->pdev->dev;
144 int ret;
145
146 ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
147 data->clk_frequency, SND_SOC_CLOCK_IN);
148 if (ret < 0)
149 dev_err(dev, "failed to set sysclk in %s\n", __func__);
150
151 return ret;
152}
153
154static int imx_wm8962_probe(struct platform_device *pdev)
155{
156 struct device_node *np = pdev->dev.of_node;
157 struct device_node *ssi_np, *codec_np;
158 struct platform_device *ssi_pdev;
159 struct imx_priv *priv = &card_priv;
160 struct i2c_client *codec_dev;
161 struct imx_wm8962_data *data;
162 int int_port, ext_port;
163 int ret;
164
165 priv->pdev = pdev;
166
167 ret = of_property_read_u32(np, "mux-int-port", &int_port);
168 if (ret) {
169 dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
170 return ret;
171 }
172 ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
173 if (ret) {
174 dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
175 return ret;
176 }
177
178 /*
179 * The port numbering in the hardware manual starts at 1, while
180 * the audmux API expects it starts at 0.
181 */
182 int_port--;
183 ext_port--;
184 ret = imx_audmux_v2_configure_port(int_port,
185 IMX_AUDMUX_V2_PTCR_SYN |
186 IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
187 IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
188 IMX_AUDMUX_V2_PTCR_TFSDIR |
189 IMX_AUDMUX_V2_PTCR_TCLKDIR,
190 IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
191 if (ret) {
192 dev_err(&pdev->dev, "audmux internal port setup failed\n");
193 return ret;
194 }
195 imx_audmux_v2_configure_port(ext_port,
196 IMX_AUDMUX_V2_PTCR_SYN,
197 IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
198 if (ret) {
199 dev_err(&pdev->dev, "audmux external port setup failed\n");
200 return ret;
201 }
202
203 ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
204 codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
205 if (!ssi_np || !codec_np) {
206 dev_err(&pdev->dev, "phandle missing or invalid\n");
207 ret = -EINVAL;
208 goto fail;
209 }
210
211 ssi_pdev = of_find_device_by_node(ssi_np);
212 if (!ssi_pdev) {
213 dev_err(&pdev->dev, "failed to find SSI platform device\n");
214 ret = -EINVAL;
215 goto fail;
216 }
217 codec_dev = of_find_i2c_device_by_node(codec_np);
218 if (!codec_dev || !codec_dev->driver) {
219 dev_err(&pdev->dev, "failed to find codec platform device\n");
220 return -EINVAL;
221 }
222
223 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
224 if (!data) {
225 ret = -ENOMEM;
226 goto fail;
227 }
228
229 data->codec_clk = devm_clk_get(&codec_dev->dev, NULL);
230 if (IS_ERR(data->codec_clk)) {
231 ret = PTR_ERR(data->codec_clk);
232 dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
233 goto fail;
234 }
235
236 data->clk_frequency = clk_get_rate(data->codec_clk);
237 ret = clk_prepare_enable(data->codec_clk);
238 if (ret) {
239 dev_err(&codec_dev->dev, "failed to enable codec clk: %d\n", ret);
240 goto fail;
241 }
242
243 data->dai.name = "HiFi";
244 data->dai.stream_name = "HiFi";
245 data->dai.codec_dai_name = "wm8962";
246 data->dai.codec_of_node = codec_np;
247 data->dai.cpu_dai_name = dev_name(&ssi_pdev->dev);
248 data->dai.platform_of_node = ssi_np;
249 data->dai.ops = &imx_hifi_ops;
250 data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
251 SND_SOC_DAIFMT_CBM_CFM;
252
253 data->card.dev = &pdev->dev;
254 ret = snd_soc_of_parse_card_name(&data->card, "model");
255 if (ret)
256 goto clk_fail;
257 ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
258 if (ret)
259 goto clk_fail;
260 data->card.num_links = 1;
261 data->card.dai_link = &data->dai;
262 data->card.dapm_widgets = imx_wm8962_dapm_widgets;
263 data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8962_dapm_widgets);
264
265 data->card.late_probe = imx_wm8962_late_probe;
266 data->card.set_bias_level = imx_wm8962_set_bias_level;
267
268 ret = snd_soc_register_card(&data->card);
269 if (ret) {
270 dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
271 goto clk_fail;
272 }
273
274 platform_set_drvdata(pdev, data);
275 of_node_put(ssi_np);
276 of_node_put(codec_np);
277
278 return 0;
279
280clk_fail:
281 if (!IS_ERR(data->codec_clk))
282 clk_disable_unprepare(data->codec_clk);
283fail:
284 if (ssi_np)
285 of_node_put(ssi_np);
286 if (codec_np)
287 of_node_put(codec_np);
288
289 return ret;
290}
291
292static int imx_wm8962_remove(struct platform_device *pdev)
293{
294 struct imx_wm8962_data *data = platform_get_drvdata(pdev);
295
296 if (!IS_ERR(data->codec_clk))
297 clk_disable_unprepare(data->codec_clk);
298 snd_soc_unregister_card(&data->card);
299
300 return 0;
301}
302
303static const struct of_device_id imx_wm8962_dt_ids[] = {
304 { .compatible = "fsl,imx-audio-wm8962", },
305 { /* sentinel */ }
306};
307MODULE_DEVICE_TABLE(of, imx_wm8962_dt_ids);
308
309static struct platform_driver imx_wm8962_driver = {
310 .driver = {
311 .name = "imx-wm8962",
312 .owner = THIS_MODULE,
313 .of_match_table = imx_wm8962_dt_ids,
314 },
315 .probe = imx_wm8962_probe,
316 .remove = imx_wm8962_remove,
317};
318module_platform_driver(imx_wm8962_driver);
319
320MODULE_AUTHOR("Freescale Semiconductor, Inc.");
321MODULE_DESCRIPTION("Freescale i.MX WM8962 ASoC machine driver");
322MODULE_LICENSE("GPL v2");
323MODULE_ALIAS("platform:imx-wm8962");