]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - sound/soc/fsl/imx-cs42888.c
ARM: dts: imx6-tx6*: fix 'flexcan' labels
[karo-tx-linux.git] / sound / soc / fsl / imx-cs42888.c
1 /*
2  * Copyright (C) 2010-2014 Freescale Semiconductor, Inc. All Rights Reserved.
3  */
4
5 /*
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:
9  *
10  * http://www.opensource.org/licenses/gpl-license.html
11  * http://www.gnu.org/copyleft/gpl.html
12  */
13
14 #include <linux/module.h>
15 #include <linux/of.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>
28
29 #include "fsl_esai.h"
30 #include "fsl_asrc.h"
31
32 #define CODEC_CLK_EXTER_OSC   1
33 #define CODEC_CLK_ESAI_HCKT   2
34
35 struct imx_priv {
36         int hw;
37         int fe_output_rate;
38         int fe_output_width;
39         unsigned int mclk_freq;
40         unsigned int codec_mclk;
41         struct platform_device *pdev;
42 };
43
44 static struct imx_priv card_priv;
45
46 static int imx_cs42888_startup(struct snd_pcm_substream *substream)
47 {
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;
51
52         if (!cpu_dai->active)
53                 priv->hw = 0;
54         return 0;
55 }
56
57 static void imx_cs42888_shutdown(struct snd_pcm_substream *substream)
58 {
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;
62
63         if (!cpu_dai->active)
64                 priv->hw = 0;
65 }
66
67 static const struct {
68         int rate;
69         int ratio1;
70         int ratio2;
71 } sr_vals[] = {
72         { 32000,  5, 3 },
73         { 48000,  5, 3 },
74         { 64000,  2, 1 },
75         { 96000,  2, 1 },
76         { 128000, 2, 1 },
77         { 44100,  5, 3 },
78         { 88200,  2, 1 },
79         { 176400, 0, 0 },
80         { 192000, 0, 0 },
81 };
82
83 static int imx_cs42888_surround_hw_params(struct snd_pcm_substream *substream,
84                                          struct snd_pcm_hw_params *params)
85 {
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;
92         u32 dai_format = 0;
93
94         if (priv->hw)
95                 return 0;
96
97         priv->hw = 1;
98
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;
103                                 break;
104                         }
105                 }
106                 if (i == ARRAY_SIZE(sr_vals)) {
107                         dev_err(&priv->pdev->dev, "Unsupported rate %dHz\n", rate);
108                         return -EINVAL;
109                 }
110
111                 dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
112                         SND_SOC_DAIFMT_CBS_CFS;
113
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,\
121                         SND_SOC_CLOCK_IN);
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;
126                                 break;
127                         }
128                 }
129                 if (i == ARRAY_SIZE(sr_vals)) {
130                         dev_err(&priv->pdev->dev, "Unsupported rate %dHz\n", rate);
131                         return -EINVAL;
132                 }
133
134                 dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
135                         SND_SOC_DAIFMT_CBS_CFS;
136
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,\
142                         SND_SOC_CLOCK_IN);
143         }
144
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);
149         /* set the ratio */
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);
154
155         /* set codec DAI configuration */
156         snd_soc_dai_set_fmt(codec_dai, dai_format);
157         return 0;
158 }
159
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,
164 };
165
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),
169 };
170
171 static const struct snd_soc_dapm_route audio_map[] = {
172         /* Line out jack */
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*/
187 };
188
189 static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
190                                 struct snd_pcm_hw_params *params) {
191
192         struct imx_priv *priv = &card_priv;
193
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);
200         else
201                 snd_mask_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT),
202                                                         SNDRV_PCM_FORMAT_S24_LE);
203         return 0;
204 }
205
206 static struct snd_soc_dai_link imx_cs42888_dai[] = {
207         {
208                 .name = "HiFi",
209                 .stream_name = "HiFi",
210                 .codec_dai_name = "CS42888",
211                 .ops = &imx_cs42888_surround_ops,
212         },
213         {
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",
218                 .dynamic = 1,
219         },
220         {
221                 .name = "HiFi-ASRC-BE",
222                 .stream_name = "HiFi-ASRC-BE",
223                 .codec_dai_name = "CS42888",
224                 .platform_name = "snd-soc-dummy",
225                 .no_pcm = 1,
226                 .ops = &imx_cs42888_surround_ops,
227                 .be_hw_params_fixup = be_hw_params_fixup,
228         },
229 };
230
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),
238 };
239
240 /*
241  * This function will register the snd_soc_pcm_link drivers.
242  */
243 static int imx_cs42888_probe(struct platform_device *pdev)
244 {
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;
253         int ret;
254
255         priv->pdev = pdev;
256
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");
261                 ret = -EINVAL;
262                 goto fail;
263         }
264
265         asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
266         if (asrc_np) {
267                 asrc_pdev = of_find_device_by_node(asrc_np);
268                 if (asrc_pdev) {
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;
274                 }
275         }
276
277         esai_pdev = of_find_device_by_node(esai_np);
278         if (!esai_pdev) {
279                 dev_err(&pdev->dev, "failed to find ESAI platform device\n");
280                 ret = -EINVAL;
281                 goto fail;
282         }
283         codec_dev = of_find_i2c_device_by_node(codec_np);
284         if (!codec_dev) {
285                 dev_err(&pdev->dev, "failed to find codec platform device\n");
286                 ret = -EINVAL;
287                 goto fail;
288         }
289
290         /*if there is no asrc controller, we only enable one device*/
291         if (!asrc_pdev) {
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;
296         } else {
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;
305         }
306
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);
311                 goto fail;
312         }
313         priv->mclk_freq = clk_get_rate(codec_clk);
314
315         ret = of_property_read_string(codec_np, "clock-names", &mclk_name);
316         if (ret) {
317                 dev_err(&pdev->dev, "%s: failed to get mclk source\n", __func__);
318                 goto fail;
319         }
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;
324         else {
325                 dev_err(&pdev->dev, "mclk source is not correct %s\n", mclk_name);
326                 goto fail;
327         }
328
329         snd_soc_card_imx_cs42888.dev = &pdev->dev;
330
331         ret = snd_soc_register_card(&snd_soc_card_imx_cs42888);
332         if (ret)
333                 dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
334 fail:
335         if (esai_np)
336                 of_node_put(esai_np);
337         if (codec_np)
338                 of_node_put(codec_np);
339         return ret;
340 }
341
342 static int imx_cs42888_remove(struct platform_device *pdev)
343 {
344         snd_soc_unregister_card(&snd_soc_card_imx_cs42888);
345         return 0;
346 }
347
348 static const struct of_device_id imx_cs42888_dt_ids[] = {
349         { .compatible = "fsl,imx-audio-cs42888", },
350         { /* sentinel */ }
351 };
352
353 static struct platform_driver imx_cs42888_driver = {
354         .probe = imx_cs42888_probe,
355         .remove = imx_cs42888_remove,
356         .driver = {
357                 .name = "imx-cs42888",
358                 .owner = THIS_MODULE,
359                 .of_match_table = imx_cs42888_dt_ids,
360         },
361 };
362 module_platform_driver(imx_cs42888_driver);
363
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");