1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // |
3 | // Copyright 2012 Freescale Semiconductor, Inc. |
4 | // Copyright 2012 Linaro Ltd. |
5 | |
6 | #include <linux/module.h> |
7 | #include <linux/of.h> |
8 | #include <linux/of_platform.h> |
9 | #include <linux/i2c.h> |
10 | #include <linux/clk.h> |
11 | #include <sound/soc.h> |
12 | |
13 | #include "../codecs/sgtl5000.h" |
14 | #include "imx-audmux.h" |
15 | |
16 | #define DAI_NAME_SIZE 32 |
17 | |
18 | struct imx_sgtl5000_data { |
19 | struct snd_soc_dai_link dai; |
20 | struct snd_soc_card card; |
21 | char codec_dai_name[DAI_NAME_SIZE]; |
22 | char platform_name[DAI_NAME_SIZE]; |
23 | struct clk *codec_clk; |
24 | unsigned int clk_frequency; |
25 | }; |
26 | |
27 | static int imx_sgtl5000_dai_init(struct snd_soc_pcm_runtime *rtd) |
28 | { |
29 | struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card: rtd->card); |
30 | struct device *dev = rtd->card->dev; |
31 | int ret; |
32 | |
33 | ret = snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), SGTL5000_SYSCLK, |
34 | freq: data->clk_frequency, SND_SOC_CLOCK_IN); |
35 | if (ret) { |
36 | dev_err(dev, "could not set codec driver clock params\n" ); |
37 | return ret; |
38 | } |
39 | |
40 | return 0; |
41 | } |
42 | |
43 | static const struct snd_soc_dapm_widget imx_sgtl5000_dapm_widgets[] = { |
44 | SND_SOC_DAPM_MIC("Mic Jack" , NULL), |
45 | SND_SOC_DAPM_LINE("Line In Jack" , NULL), |
46 | SND_SOC_DAPM_HP("Headphone Jack" , NULL), |
47 | SND_SOC_DAPM_SPK("Line Out Jack" , NULL), |
48 | SND_SOC_DAPM_SPK("Ext Spk" , NULL), |
49 | }; |
50 | |
51 | static int imx_sgtl5000_probe(struct platform_device *pdev) |
52 | { |
53 | struct device_node *np = pdev->dev.of_node; |
54 | struct device_node *ssi_np, *codec_np; |
55 | struct platform_device *ssi_pdev; |
56 | struct i2c_client *codec_dev; |
57 | struct imx_sgtl5000_data *data = NULL; |
58 | struct snd_soc_dai_link_component *comp; |
59 | int int_port, ext_port; |
60 | int ret; |
61 | |
62 | ret = of_property_read_u32(np, propname: "mux-int-port" , out_value: &int_port); |
63 | if (ret) { |
64 | dev_err(&pdev->dev, "mux-int-port missing or invalid\n" ); |
65 | return ret; |
66 | } |
67 | ret = of_property_read_u32(np, propname: "mux-ext-port" , out_value: &ext_port); |
68 | if (ret) { |
69 | dev_err(&pdev->dev, "mux-ext-port missing or invalid\n" ); |
70 | return ret; |
71 | } |
72 | |
73 | /* |
74 | * The port numbering in the hardware manual starts at 1, while |
75 | * the audmux API expects it starts at 0. |
76 | */ |
77 | int_port--; |
78 | ext_port--; |
79 | ret = imx_audmux_v2_configure_port(port: int_port, |
80 | IMX_AUDMUX_V2_PTCR_SYN | |
81 | IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | |
82 | IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | |
83 | IMX_AUDMUX_V2_PTCR_TFSDIR | |
84 | IMX_AUDMUX_V2_PTCR_TCLKDIR, |
85 | IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); |
86 | if (ret) { |
87 | dev_err(&pdev->dev, "audmux internal port setup failed\n" ); |
88 | return ret; |
89 | } |
90 | ret = imx_audmux_v2_configure_port(port: ext_port, |
91 | IMX_AUDMUX_V2_PTCR_SYN, |
92 | IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); |
93 | if (ret) { |
94 | dev_err(&pdev->dev, "audmux external port setup failed\n" ); |
95 | return ret; |
96 | } |
97 | |
98 | ssi_np = of_parse_phandle(np: pdev->dev.of_node, phandle_name: "ssi-controller" , index: 0); |
99 | codec_np = of_parse_phandle(np: pdev->dev.of_node, phandle_name: "audio-codec" , index: 0); |
100 | if (!ssi_np || !codec_np) { |
101 | dev_err(&pdev->dev, "phandle missing or invalid\n" ); |
102 | ret = -EINVAL; |
103 | goto fail; |
104 | } |
105 | |
106 | ssi_pdev = of_find_device_by_node(np: ssi_np); |
107 | if (!ssi_pdev) { |
108 | dev_dbg(&pdev->dev, "failed to find SSI platform device\n" ); |
109 | ret = -EPROBE_DEFER; |
110 | goto fail; |
111 | } |
112 | put_device(dev: &ssi_pdev->dev); |
113 | codec_dev = of_find_i2c_device_by_node(node: codec_np); |
114 | if (!codec_dev) { |
115 | dev_dbg(&pdev->dev, "failed to find codec platform device\n" ); |
116 | ret = -EPROBE_DEFER; |
117 | goto fail; |
118 | } |
119 | |
120 | data = devm_kzalloc(dev: &pdev->dev, size: sizeof(*data), GFP_KERNEL); |
121 | if (!data) { |
122 | ret = -ENOMEM; |
123 | goto put_device; |
124 | } |
125 | |
126 | comp = devm_kzalloc(dev: &pdev->dev, size: 3 * sizeof(*comp), GFP_KERNEL); |
127 | if (!comp) { |
128 | ret = -ENOMEM; |
129 | goto put_device; |
130 | } |
131 | |
132 | data->codec_clk = clk_get(dev: &codec_dev->dev, NULL); |
133 | if (IS_ERR(ptr: data->codec_clk)) { |
134 | ret = PTR_ERR(ptr: data->codec_clk); |
135 | goto put_device; |
136 | } |
137 | |
138 | data->clk_frequency = clk_get_rate(clk: data->codec_clk); |
139 | |
140 | data->dai.cpus = &comp[0]; |
141 | data->dai.codecs = &comp[1]; |
142 | data->dai.platforms = &comp[2]; |
143 | |
144 | data->dai.num_cpus = 1; |
145 | data->dai.num_codecs = 1; |
146 | data->dai.num_platforms = 1; |
147 | |
148 | data->dai.name = "HiFi" ; |
149 | data->dai.stream_name = "HiFi" ; |
150 | data->dai.codecs->dai_name = "sgtl5000" ; |
151 | data->dai.codecs->of_node = codec_np; |
152 | data->dai.cpus->of_node = ssi_np; |
153 | data->dai.platforms->of_node = ssi_np; |
154 | data->dai.init = &imx_sgtl5000_dai_init; |
155 | data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
156 | SND_SOC_DAIFMT_CBP_CFP; |
157 | |
158 | data->card.dev = &pdev->dev; |
159 | ret = snd_soc_of_parse_card_name(card: &data->card, propname: "model" ); |
160 | if (ret) |
161 | goto put_device; |
162 | ret = snd_soc_of_parse_audio_routing(card: &data->card, propname: "audio-routing" ); |
163 | if (ret) |
164 | goto put_device; |
165 | data->card.num_links = 1; |
166 | data->card.owner = THIS_MODULE; |
167 | data->card.dai_link = &data->dai; |
168 | data->card.dapm_widgets = imx_sgtl5000_dapm_widgets; |
169 | data->card.num_dapm_widgets = ARRAY_SIZE(imx_sgtl5000_dapm_widgets); |
170 | |
171 | platform_set_drvdata(pdev, data: &data->card); |
172 | snd_soc_card_set_drvdata(card: &data->card, data); |
173 | |
174 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card: &data->card); |
175 | if (ret) { |
176 | dev_err_probe(dev: &pdev->dev, err: ret, fmt: "snd_soc_register_card failed\n" ); |
177 | goto put_device; |
178 | } |
179 | |
180 | of_node_put(node: ssi_np); |
181 | of_node_put(node: codec_np); |
182 | |
183 | return 0; |
184 | |
185 | put_device: |
186 | put_device(dev: &codec_dev->dev); |
187 | fail: |
188 | if (data && !IS_ERR(ptr: data->codec_clk)) |
189 | clk_put(clk: data->codec_clk); |
190 | of_node_put(node: ssi_np); |
191 | of_node_put(node: codec_np); |
192 | |
193 | return ret; |
194 | } |
195 | |
196 | static void imx_sgtl5000_remove(struct platform_device *pdev) |
197 | { |
198 | struct snd_soc_card *card = platform_get_drvdata(pdev); |
199 | struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card); |
200 | |
201 | clk_put(clk: data->codec_clk); |
202 | } |
203 | |
204 | static const struct of_device_id imx_sgtl5000_dt_ids[] = { |
205 | { .compatible = "fsl,imx-audio-sgtl5000" , }, |
206 | { /* sentinel */ } |
207 | }; |
208 | MODULE_DEVICE_TABLE(of, imx_sgtl5000_dt_ids); |
209 | |
210 | static struct platform_driver imx_sgtl5000_driver = { |
211 | .driver = { |
212 | .name = "imx-sgtl5000" , |
213 | .pm = &snd_soc_pm_ops, |
214 | .of_match_table = imx_sgtl5000_dt_ids, |
215 | }, |
216 | .probe = imx_sgtl5000_probe, |
217 | .remove_new = imx_sgtl5000_remove, |
218 | }; |
219 | module_platform_driver(imx_sgtl5000_driver); |
220 | |
221 | MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>" ); |
222 | MODULE_DESCRIPTION("Freescale i.MX SGTL5000 ASoC machine driver" ); |
223 | MODULE_LICENSE("GPL v2" ); |
224 | MODULE_ALIAS("platform:imx-sgtl5000" ); |
225 | |