1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright 2017-2020 NXP
4 #include <linux/module.h>
5 #include <linux/of_platform.h>
6 #include <sound/jack.h>
7 #include <sound/pcm_params.h>
8 #include <sound/hdmi-codec.h>
12 * struct cpu_priv - CPU private data
13 * @sysclk_id: SYSCLK ids for set_sysclk()
14 * @slot_width: Slot width of each frame
16 * Note: [1] for tx and [0] for rx
23 struct imx_hdmi_data
{
24 struct snd_soc_dai_link dai
;
25 struct snd_soc_card card
;
26 struct snd_soc_jack hdmi_jack
;
27 struct snd_soc_jack_pin hdmi_jack_pin
;
28 struct cpu_priv cpu_priv
;
32 static int imx_hdmi_hw_params(struct snd_pcm_substream
*substream
,
33 struct snd_pcm_hw_params
*params
)
35 struct snd_soc_pcm_runtime
*rtd
= snd_soc_substream_to_rtd(substream
);
36 struct imx_hdmi_data
*data
= snd_soc_card_get_drvdata(rtd
->card
);
37 bool tx
= substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
;
38 struct snd_soc_dai
*cpu_dai
= snd_soc_rtd_to_cpu(rtd
, 0);
39 struct snd_soc_card
*card
= rtd
->card
;
40 struct device
*dev
= card
->dev
;
41 u32 slot_width
= data
->cpu_priv
.slot_width
;
44 /* MCLK always is (256 or 192) * rate. */
45 ret
= snd_soc_dai_set_sysclk(cpu_dai
, data
->cpu_priv
.sysclk_id
[tx
],
46 8 * slot_width
* params_rate(params
),
47 tx
? SND_SOC_CLOCK_OUT
: SND_SOC_CLOCK_IN
);
48 if (ret
&& ret
!= -ENOTSUPP
) {
49 dev_err(dev
, "failed to set cpu sysclk: %d\n", ret
);
53 ret
= snd_soc_dai_set_tdm_slot(cpu_dai
, 0, 0, 2, slot_width
);
54 if (ret
&& ret
!= -ENOTSUPP
) {
55 dev_err(dev
, "failed to set cpu dai tdm slot: %d\n", ret
);
62 static const struct snd_soc_ops imx_hdmi_ops
= {
63 .hw_params
= imx_hdmi_hw_params
,
66 static const struct snd_soc_dapm_widget imx_hdmi_widgets
[] = {
67 SND_SOC_DAPM_LINE("HDMI Jack", NULL
),
70 static int imx_hdmi_init(struct snd_soc_pcm_runtime
*rtd
)
72 struct snd_soc_card
*card
= rtd
->card
;
73 struct snd_soc_dai
*codec_dai
= snd_soc_rtd_to_codec(rtd
, 0);
74 struct snd_soc_component
*component
= codec_dai
->component
;
75 struct imx_hdmi_data
*data
= snd_soc_card_get_drvdata(card
);
78 data
->hdmi_jack_pin
.pin
= "HDMI Jack";
79 data
->hdmi_jack_pin
.mask
= SND_JACK_LINEOUT
;
80 /* enable jack detection */
81 ret
= snd_soc_card_jack_new_pins(card
, "HDMI Jack", SND_JACK_LINEOUT
,
83 &data
->hdmi_jack_pin
, 1);
85 dev_err(card
->dev
, "Can't new HDMI Jack %d\n", ret
);
89 ret
= snd_soc_component_set_jack(component
, &data
->hdmi_jack
, NULL
);
90 if (ret
&& ret
!= -ENOTSUPP
) {
91 dev_err(card
->dev
, "Can't set HDMI Jack %d\n", ret
);
98 static int imx_hdmi_probe(struct platform_device
*pdev
)
100 struct device_node
*np
= pdev
->dev
.of_node
;
101 bool hdmi_out
= of_property_read_bool(np
, "hdmi-out");
102 bool hdmi_in
= of_property_read_bool(np
, "hdmi-in");
103 struct snd_soc_dai_link_component
*dlc
;
104 struct platform_device
*cpu_pdev
;
105 struct device_node
*cpu_np
;
106 struct imx_hdmi_data
*data
;
109 dlc
= devm_kzalloc(&pdev
->dev
, 3 * sizeof(*dlc
), GFP_KERNEL
);
113 cpu_np
= of_parse_phandle(np
, "audio-cpu", 0);
115 dev_err(&pdev
->dev
, "cpu dai phandle missing or invalid\n");
120 cpu_pdev
= of_find_device_by_node(cpu_np
);
122 dev_err(&pdev
->dev
, "failed to find SAI platform device\n");
127 data
= devm_kzalloc(&pdev
->dev
, sizeof(*data
), GFP_KERNEL
);
130 put_device(&cpu_pdev
->dev
);
134 data
->dai
.cpus
= &dlc
[0];
135 data
->dai
.num_cpus
= 1;
136 data
->dai
.platforms
= &dlc
[1];
137 data
->dai
.num_platforms
= 1;
138 data
->dai
.codecs
= &dlc
[2];
139 data
->dai
.num_codecs
= 1;
141 data
->dai
.name
= "i.MX HDMI";
142 data
->dai
.stream_name
= "i.MX HDMI";
143 data
->dai
.cpus
->dai_name
= dev_name(&cpu_pdev
->dev
);
144 data
->dai
.platforms
->of_node
= cpu_np
;
145 data
->dai
.ops
= &imx_hdmi_ops
;
146 data
->dai
.playback_only
= true;
147 data
->dai
.capture_only
= false;
148 data
->dai
.init
= imx_hdmi_init
;
150 put_device(&cpu_pdev
->dev
);
152 if (of_node_name_eq(cpu_np
, "sai")) {
153 data
->cpu_priv
.sysclk_id
[1] = FSL_SAI_CLK_MAST1
;
154 data
->cpu_priv
.sysclk_id
[0] = FSL_SAI_CLK_MAST1
;
157 if (of_device_is_compatible(np
, "fsl,imx-audio-sii902x")) {
158 data
->dai_fmt
= SND_SOC_DAIFMT_LEFT_J
;
159 data
->cpu_priv
.slot_width
= 24;
161 data
->dai_fmt
= SND_SOC_DAIFMT_I2S
;
162 data
->cpu_priv
.slot_width
= 32;
165 if ((hdmi_out
&& hdmi_in
) || (!hdmi_out
&& !hdmi_in
)) {
166 dev_err(&pdev
->dev
, "Invalid HDMI DAI link\n");
172 data
->dai
.playback_only
= true;
173 data
->dai
.capture_only
= false;
174 data
->dai
.codecs
->dai_name
= "i2s-hifi";
175 data
->dai
.codecs
->name
= "hdmi-audio-codec.1";
176 data
->dai
.dai_fmt
= data
->dai_fmt
|
177 SND_SOC_DAIFMT_NB_NF
|
178 SND_SOC_DAIFMT_CBC_CFC
;
182 data
->dai
.playback_only
= false;
183 data
->dai
.capture_only
= true;
184 data
->dai
.codecs
->dai_name
= "i2s-hifi";
185 data
->dai
.codecs
->name
= "hdmi-audio-codec.2";
186 data
->dai
.dai_fmt
= data
->dai_fmt
|
187 SND_SOC_DAIFMT_NB_NF
|
188 SND_SOC_DAIFMT_CBP_CFP
;
191 data
->card
.dapm_widgets
= imx_hdmi_widgets
;
192 data
->card
.num_dapm_widgets
= ARRAY_SIZE(imx_hdmi_widgets
);
193 data
->card
.dev
= &pdev
->dev
;
194 data
->card
.owner
= THIS_MODULE
;
195 ret
= snd_soc_of_parse_card_name(&data
->card
, "model");
199 data
->card
.num_links
= 1;
200 data
->card
.dai_link
= &data
->dai
;
202 snd_soc_card_set_drvdata(&data
->card
, data
);
203 ret
= devm_snd_soc_register_card(&pdev
->dev
, &data
->card
);
205 dev_err_probe(&pdev
->dev
, ret
, "snd_soc_register_card failed\n");
215 static const struct of_device_id imx_hdmi_dt_ids
[] = {
216 { .compatible
= "fsl,imx-audio-hdmi", },
217 { .compatible
= "fsl,imx-audio-sii902x", },
220 MODULE_DEVICE_TABLE(of
, imx_hdmi_dt_ids
);
222 static struct platform_driver imx_hdmi_driver
= {
225 .pm
= &snd_soc_pm_ops
,
226 .of_match_table
= imx_hdmi_dt_ids
,
228 .probe
= imx_hdmi_probe
,
230 module_platform_driver(imx_hdmi_driver
);
232 MODULE_AUTHOR("Freescale Semiconductor, Inc.");
233 MODULE_DESCRIPTION("Freescale i.MX hdmi audio ASoC machine driver");
234 MODULE_LICENSE("GPL v2");
235 MODULE_ALIAS("platform:imx-hdmi");