2 * Copyright (C) 2010-2014 Freescale Semiconductor, Inc. All Rights Reserved.
6 * The code contained herein is licensed under the GNU General Public
7 * License. You may obtain a copy of the GNU General Public License
8 * Version 2 or later at the following locations:
10 * http://www.opensource.org/licenses/gpl-license.html
11 * http://www.gnu.org/copyleft/gpl.html
14 #include <linux/module.h>
16 #include <linux/of_platform.h>
17 #include <linux/of_i2c.h>
18 #include <linux/slab.h>
19 #include <linux/device.h>
20 #include <linux/i2c.h>
21 #include <linux/clk.h>
22 #include <linux/delay.h>
23 #include <sound/core.h>
24 #include <sound/pcm.h>
25 #include <sound/soc.h>
26 #include <sound/initval.h>
27 #include <sound/pcm_params.h>
32 #define CODEC_CLK_EXTER_OSC 1
33 #define CODEC_CLK_ESAI_HCKT 2
39 unsigned int mclk_freq;
40 unsigned int codec_mclk;
41 struct platform_device *pdev;
44 static struct imx_priv card_priv;
46 static int imx_cs42888_startup(struct snd_pcm_substream *substream)
48 struct snd_soc_pcm_runtime *rtd = substream->private_data;
49 struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
50 struct imx_priv *priv = &card_priv;
57 static void imx_cs42888_shutdown(struct snd_pcm_substream *substream)
59 struct snd_soc_pcm_runtime *rtd = substream->private_data;
60 struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
61 struct imx_priv *priv = &card_priv;
83 static int imx_cs42888_surround_hw_params(struct snd_pcm_substream *substream,
84 struct snd_pcm_hw_params *params)
86 struct snd_soc_pcm_runtime *rtd = substream->private_data;
87 struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
88 struct snd_soc_dai *codec_dai = rtd->codec_dai;
89 struct imx_priv *priv = &card_priv;
90 unsigned int rate = params_rate(params);
91 unsigned int lrclk_ratio = 0, i;
99 if (priv->codec_mclk & CODEC_CLK_ESAI_HCKT) {
100 for (i = 0; i < ARRAY_SIZE(sr_vals); i++) {
101 if (sr_vals[i].rate == rate) {
102 lrclk_ratio = sr_vals[i].ratio1;
106 if (i == ARRAY_SIZE(sr_vals)) {
107 dev_err(&priv->pdev->dev, "Unsupported rate %dHz\n", rate);
111 dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
112 SND_SOC_DAIFMT_CBS_CFS;
114 /* set the ESAI system clock as output */
115 snd_soc_dai_set_sysclk(cpu_dai, ESAI_CLK_EXTAL_DIV,
116 priv->mclk_freq, SND_SOC_CLOCK_OUT);
117 snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PM, 2);
118 snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PM, 2);
119 /* set codec Master clock */
120 snd_soc_dai_set_sysclk(codec_dai, 0, priv->mclk_freq,\
122 } else if (priv->codec_mclk & CODEC_CLK_EXTER_OSC) {
123 for (i = 0; i < ARRAY_SIZE(sr_vals); i++) {
124 if (sr_vals[i].rate == rate) {
125 lrclk_ratio = sr_vals[i].ratio2;
129 if (i == ARRAY_SIZE(sr_vals)) {
130 dev_err(&priv->pdev->dev, "Unsupported rate %dHz\n", rate);
134 dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
135 SND_SOC_DAIFMT_CBS_CFS;
137 snd_soc_dai_set_sysclk(cpu_dai, ESAI_CLK_EXTAL,
138 priv->mclk_freq, SND_SOC_CLOCK_OUT);
139 snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PM, 0);
140 snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PM, 0);
141 snd_soc_dai_set_sysclk(codec_dai, 0, priv->mclk_freq,\
145 /* set cpu DAI configuration */
146 snd_soc_dai_set_fmt(cpu_dai, dai_format);
147 /* set i.MX active slot mask */
148 snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 32);
150 snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PSR, 1);
151 snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_FP, lrclk_ratio);
152 snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PSR, 1);
153 snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_FP, lrclk_ratio);
155 /* set codec DAI configuration */
156 snd_soc_dai_set_fmt(codec_dai, dai_format);
160 static struct snd_soc_ops imx_cs42888_surround_ops = {
161 .startup = imx_cs42888_startup,
162 .shutdown = imx_cs42888_shutdown,
163 .hw_params = imx_cs42888_surround_hw_params,
166 static const struct snd_soc_dapm_widget imx_cs42888_dapm_widgets[] = {
167 SND_SOC_DAPM_LINE("Line Out Jack", NULL),
168 SND_SOC_DAPM_LINE("Line In Jack", NULL),
171 static const struct snd_soc_dapm_route audio_map[] = {
173 {"Line Out Jack", NULL, "AOUT1L"},
174 {"Line Out Jack", NULL, "AOUT1R"},
175 {"Line Out Jack", NULL, "AOUT2L"},
176 {"Line Out Jack", NULL, "AOUT2R"},
177 {"Line Out Jack", NULL, "AOUT3L"},
178 {"Line Out Jack", NULL, "AOUT3R"},
179 {"Line Out Jack", NULL, "AOUT4L"},
180 {"Line Out Jack", NULL, "AOUT4R"},
181 {"AIN1L", NULL, "Line In Jack"},
182 {"AIN1R", NULL, "Line In Jack"},
183 {"AIN2L", NULL, "Line In Jack"},
184 {"AIN2R", NULL, "Line In Jack"},
185 {"esai-Playback", NULL, "asrc-Playback"},
186 {"codec-Playback", NULL, "esai-Playback"},/*Playback is the codec dai*/
189 static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
190 struct snd_pcm_hw_params *params) {
192 struct imx_priv *priv = &card_priv;
194 hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min = priv->fe_output_rate;
195 hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->max = priv->fe_output_rate;
196 snd_mask_none(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT));
197 if (priv->fe_output_width == 16)
198 snd_mask_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT),
199 SNDRV_PCM_FORMAT_S16_LE);
201 snd_mask_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT),
202 SNDRV_PCM_FORMAT_S24_LE);
206 static struct snd_soc_dai_link imx_cs42888_dai[] = {
209 .stream_name = "HiFi",
210 .codec_dai_name = "CS42888",
211 .ops = &imx_cs42888_surround_ops,
214 .name = "HiFi-ASRC-FE",
215 .stream_name = "HiFi-ASRC-FE",
216 .codec_name = "snd-soc-dummy",
217 .codec_dai_name = "snd-soc-dummy-dai",
221 .name = "HiFi-ASRC-BE",
222 .stream_name = "HiFi-ASRC-BE",
223 .codec_dai_name = "CS42888",
224 .platform_name = "snd-soc-dummy",
226 .ops = &imx_cs42888_surround_ops,
227 .be_hw_params_fixup = be_hw_params_fixup,
231 static struct snd_soc_card snd_soc_card_imx_cs42888 = {
232 .name = "cs42888-audio",
233 .dai_link = imx_cs42888_dai,
234 .dapm_widgets = imx_cs42888_dapm_widgets,
235 .num_dapm_widgets = ARRAY_SIZE(imx_cs42888_dapm_widgets),
236 .dapm_routes = audio_map,
237 .num_dapm_routes = ARRAY_SIZE(audio_map),
241 * This function will register the snd_soc_pcm_link drivers.
243 static int imx_cs42888_probe(struct platform_device *pdev)
245 struct device_node *esai_np, *codec_np;
246 struct device_node *asrc_np;
247 struct platform_device *esai_pdev;
248 struct platform_device *asrc_pdev = NULL;
249 struct i2c_client *codec_dev;
250 struct imx_priv *priv = &card_priv;
251 struct clk *codec_clk = NULL;
252 const char *mclk_name;
257 esai_np = of_parse_phandle(pdev->dev.of_node, "esai-controller", 0);
258 codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
259 if (!esai_np || !codec_np) {
260 dev_err(&pdev->dev, "phandle missing or invalid\n");
265 asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
267 asrc_pdev = of_find_device_by_node(asrc_np);
269 struct fsl_asrc_p2p *asrc_p2p;
270 asrc_p2p = platform_get_drvdata(asrc_pdev);
271 asrc_p2p->per_dev = ESAI;
272 priv->fe_output_rate = asrc_p2p->output_rate;
273 priv->fe_output_width = asrc_p2p->output_width;
277 esai_pdev = of_find_device_by_node(esai_np);
279 dev_err(&pdev->dev, "failed to find ESAI platform device\n");
283 codec_dev = of_find_i2c_device_by_node(codec_np);
285 dev_err(&pdev->dev, "failed to find codec platform device\n");
290 /*if there is no asrc controller, we only enable one device*/
292 imx_cs42888_dai[0].codec_of_node = codec_np;
293 imx_cs42888_dai[0].cpu_dai_name = dev_name(&esai_pdev->dev);
294 imx_cs42888_dai[0].platform_of_node = esai_np;
295 snd_soc_card_imx_cs42888.num_links = 1;
297 imx_cs42888_dai[0].codec_of_node = codec_np;
298 imx_cs42888_dai[0].cpu_dai_name = dev_name(&esai_pdev->dev);
299 imx_cs42888_dai[0].platform_of_node = esai_np;
300 imx_cs42888_dai[1].cpu_dai_name = dev_name(&asrc_pdev->dev);
301 imx_cs42888_dai[1].platform_name = "imx-pcm-asrc";
302 imx_cs42888_dai[2].codec_of_node = codec_np;
303 imx_cs42888_dai[2].cpu_dai_name = dev_name(&esai_pdev->dev);
304 snd_soc_card_imx_cs42888.num_links = 3;
307 codec_clk = devm_clk_get(&codec_dev->dev, NULL);
308 if (IS_ERR(codec_clk)) {
309 ret = PTR_ERR(codec_clk);
310 dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
313 priv->mclk_freq = clk_get_rate(codec_clk);
315 ret = of_property_read_string(codec_np, "clock-names", &mclk_name);
317 dev_err(&pdev->dev, "%s: failed to get mclk source\n", __func__);
320 if (!strcmp(mclk_name, "codec_osc"))
321 priv->codec_mclk = CODEC_CLK_EXTER_OSC;
322 else if (!strcmp(mclk_name, "esai"))
323 priv->codec_mclk = CODEC_CLK_ESAI_HCKT;
325 dev_err(&pdev->dev, "mclk source is not correct %s\n", mclk_name);
329 snd_soc_card_imx_cs42888.dev = &pdev->dev;
331 ret = snd_soc_register_card(&snd_soc_card_imx_cs42888);
333 dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
336 of_node_put(esai_np);
338 of_node_put(codec_np);
342 static int imx_cs42888_remove(struct platform_device *pdev)
344 snd_soc_unregister_card(&snd_soc_card_imx_cs42888);
348 static const struct of_device_id imx_cs42888_dt_ids[] = {
349 { .compatible = "fsl,imx-audio-cs42888", },
353 static struct platform_driver imx_cs42888_driver = {
354 .probe = imx_cs42888_probe,
355 .remove = imx_cs42888_remove,
357 .name = "imx-cs42888",
358 .owner = THIS_MODULE,
359 .of_match_table = imx_cs42888_dt_ids,
362 module_platform_driver(imx_cs42888_driver);
364 MODULE_AUTHOR("Freescale Semiconductor, Inc.");
365 MODULE_DESCRIPTION("ALSA SoC cs42888 Machine Layer Driver");
366 MODULE_ALIAS("platform:imx-cs42888");
367 MODULE_LICENSE("GPL");