xref: /linux/sound/soc/fsl/imx-es8328.c (revision a3a02a52bcfcbcc4a637d4b68bf1bc391c9fad02)
1 // SPDX-License-Identifier: GPL-2.0+
2 //
3 // Copyright 2012 Freescale Semiconductor, Inc.
4 // Copyright 2012 Linaro Ltd.
5 
6 #include <linux/gpio/consumer.h>
7 #include <linux/module.h>
8 #include <linux/of.h>
9 #include <linux/of_platform.h>
10 #include <linux/i2c.h>
11 #include <sound/soc.h>
12 #include <sound/jack.h>
13 
14 #include "imx-audmux.h"
15 
16 #define DAI_NAME_SIZE	32
17 #define MUX_PORT_MAX	7
18 
19 struct imx_es8328_data {
20 	struct device *dev;
21 	struct snd_soc_dai_link dai;
22 	struct snd_soc_card card;
23 	char codec_dai_name[DAI_NAME_SIZE];
24 	char platform_name[DAI_NAME_SIZE];
25 	struct gpio_desc *jack_gpiod;
26 };
27 
28 static struct snd_soc_jack_gpio headset_jack_gpios[] = {
29 	{
30 		.name = "headset-gpio",
31 		.report = SND_JACK_HEADSET,
32 		.invert = 0,
33 		.debounce_time = 200,
34 	},
35 };
36 
37 static struct snd_soc_jack headset_jack;
38 static struct snd_soc_jack_pin headset_jack_pins[] = {
39 	{
40 		.pin = "Headphone",
41 		.mask = SND_JACK_HEADPHONE,
42 	},
43 	{
44 		.pin = "Mic Jack",
45 		.mask = SND_JACK_MICROPHONE,
46 	},
47 };
48 
49 static int imx_es8328_dai_init(struct snd_soc_pcm_runtime *rtd)
50 {
51 	struct imx_es8328_data *data = container_of(rtd->card,
52 					struct imx_es8328_data, card);
53 	int ret = 0;
54 
55 	if (data->jack_gpiod) {
56 		/* Headphone jack detection */
57 		ret = snd_soc_card_jack_new_pins(rtd->card, "Headphone",
58 						 SND_JACK_HEADSET | SND_JACK_BTN_0,
59 						 &headset_jack,
60 						 headset_jack_pins,
61 						 ARRAY_SIZE(headset_jack_pins));
62 		if (ret)
63 			return ret;
64 
65 		headset_jack_gpios[0].desc = data->jack_gpiod;
66 		ret = snd_soc_jack_add_gpios(&headset_jack,
67 					     ARRAY_SIZE(headset_jack_gpios),
68 					     headset_jack_gpios);
69 	}
70 
71 	return ret;
72 }
73 
74 static const struct snd_soc_dapm_widget imx_es8328_dapm_widgets[] = {
75 	SND_SOC_DAPM_MIC("Mic Jack", NULL),
76 	SND_SOC_DAPM_HP("Headphone", NULL),
77 	SND_SOC_DAPM_SPK("Speaker", NULL),
78 	SND_SOC_DAPM_REGULATOR_SUPPLY("audio-amp", 1, 0),
79 };
80 
81 static const struct snd_kcontrol_new imx_es8328_controls[] = {
82 	SOC_DAPM_PIN_SWITCH("Headphone"),
83 	SOC_DAPM_PIN_SWITCH("Mic Jack"),
84 };
85 
86 static int imx_es8328_probe(struct platform_device *pdev)
87 {
88 	struct device_node *np = pdev->dev.of_node;
89 	struct device_node *ssi_np = NULL, *codec_np = NULL;
90 	struct platform_device *ssi_pdev;
91 	struct imx_es8328_data *data;
92 	struct snd_soc_dai_link_component *comp;
93 	u32 int_port, ext_port;
94 	int ret;
95 	struct device *dev = &pdev->dev;
96 
97 	ret = of_property_read_u32(np, "mux-int-port", &int_port);
98 	if (ret) {
99 		dev_err(dev, "mux-int-port missing or invalid\n");
100 		goto fail;
101 	}
102 	if (int_port > MUX_PORT_MAX || int_port == 0) {
103 		dev_err(dev, "mux-int-port: hardware only has %d mux ports\n",
104 			MUX_PORT_MAX);
105 		ret = -EINVAL;
106 		goto fail;
107 	}
108 
109 	ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
110 	if (ret) {
111 		dev_err(dev, "mux-ext-port missing or invalid\n");
112 		goto fail;
113 	}
114 	if (ext_port > MUX_PORT_MAX || ext_port == 0) {
115 		dev_err(dev, "mux-ext-port: hardware only has %d mux ports\n",
116 			MUX_PORT_MAX);
117 		ret = -EINVAL;
118 		goto fail;
119 	}
120 
121 	/*
122 	 * The port numbering in the hardware manual starts at 1, while
123 	 * the audmux API expects it starts at 0.
124 	 */
125 	int_port--;
126 	ext_port--;
127 	ret = imx_audmux_v2_configure_port(int_port,
128 			IMX_AUDMUX_V2_PTCR_SYN |
129 			IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
130 			IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
131 			IMX_AUDMUX_V2_PTCR_TFSDIR |
132 			IMX_AUDMUX_V2_PTCR_TCLKDIR,
133 			IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
134 	if (ret) {
135 		dev_err(dev, "audmux internal port setup failed\n");
136 		return ret;
137 	}
138 	ret = imx_audmux_v2_configure_port(ext_port,
139 			IMX_AUDMUX_V2_PTCR_SYN,
140 			IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
141 	if (ret) {
142 		dev_err(dev, "audmux external port setup failed\n");
143 		return ret;
144 	}
145 
146 	ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
147 	codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
148 	if (!ssi_np || !codec_np) {
149 		dev_err(dev, "phandle missing or invalid\n");
150 		ret = -EINVAL;
151 		goto fail;
152 	}
153 
154 	ssi_pdev = of_find_device_by_node(ssi_np);
155 	if (!ssi_pdev) {
156 		dev_err(dev, "failed to find SSI platform device\n");
157 		ret = -EINVAL;
158 		goto fail;
159 	}
160 
161 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
162 	if (!data) {
163 		ret = -ENOMEM;
164 		goto put_device;
165 	}
166 
167 	comp = devm_kzalloc(dev, 2 * sizeof(*comp), GFP_KERNEL);
168 	if (!comp) {
169 		ret = -ENOMEM;
170 		goto put_device;
171 	}
172 
173 	data->dev = dev;
174 
175 	data->jack_gpiod = devm_gpiod_get_optional(dev, "jack", GPIOD_IN);
176 	if (IS_ERR(data->jack_gpiod)) {
177 		ret = PTR_ERR(data->jack_gpiod);
178 		goto put_device;
179 	}
180 
181 	/*
182 	 * CPU == Platform
183 	 * platform is using soc-generic-dmaengine-pcm
184 	 */
185 	data->dai.cpus		=
186 	data->dai.platforms	= &comp[0];
187 	data->dai.codecs	= &comp[1];
188 
189 	data->dai.num_cpus	= 1;
190 	data->dai.num_codecs	= 1;
191 	data->dai.num_platforms	= 1;
192 
193 	data->dai.name = "hifi";
194 	data->dai.stream_name = "hifi";
195 	data->dai.codecs->dai_name = "es8328-hifi-analog";
196 	data->dai.codecs->of_node = codec_np;
197 	data->dai.cpus->of_node = ssi_np;
198 	data->dai.init = &imx_es8328_dai_init;
199 	data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
200 			    SND_SOC_DAIFMT_CBP_CFP;
201 
202 	data->card.dev = dev;
203 	data->card.dapm_widgets = imx_es8328_dapm_widgets;
204 	data->card.num_dapm_widgets = ARRAY_SIZE(imx_es8328_dapm_widgets);
205 	data->card.controls = imx_es8328_controls;
206 	data->card.num_controls = ARRAY_SIZE(imx_es8328_controls);
207 	ret = snd_soc_of_parse_card_name(&data->card, "model");
208 	if (ret) {
209 		dev_err(dev, "Unable to parse card name\n");
210 		goto put_device;
211 	}
212 	ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
213 	if (ret) {
214 		dev_err(dev, "Unable to parse routing: %d\n", ret);
215 		goto put_device;
216 	}
217 	data->card.num_links = 1;
218 	data->card.owner = THIS_MODULE;
219 	data->card.dai_link = &data->dai;
220 
221 	ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
222 	if (ret) {
223 		dev_err(dev, "Unable to register: %d\n", ret);
224 		goto put_device;
225 	}
226 
227 	platform_set_drvdata(pdev, data);
228 put_device:
229 	put_device(&ssi_pdev->dev);
230 fail:
231 	of_node_put(ssi_np);
232 	of_node_put(codec_np);
233 
234 	return ret;
235 }
236 
237 static const struct of_device_id imx_es8328_dt_ids[] = {
238 	{ .compatible = "fsl,imx-audio-es8328", },
239 	{ /* sentinel */ }
240 };
241 MODULE_DEVICE_TABLE(of, imx_es8328_dt_ids);
242 
243 static struct platform_driver imx_es8328_driver = {
244 	.driver = {
245 		.name = "imx-es8328",
246 		.of_match_table = imx_es8328_dt_ids,
247 	},
248 	.probe = imx_es8328_probe,
249 };
250 module_platform_driver(imx_es8328_driver);
251 
252 MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>");
253 MODULE_DESCRIPTION("Kosagi i.MX6 ES8328 ASoC machine driver");
254 MODULE_LICENSE("GPL v2");
255 MODULE_ALIAS("platform:imx-audio-es8328");
256