Linux Audio

Check our new training course

Real-Time Linux with PREEMPT_RT training

Feb 18-20, 2025
Register
Loading...
Note: File does not exist in v6.13.7.
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * SoC audio for HTC Magician
  4 *
  5 * Copyright (c) 2006 Philipp Zabel <philipp.zabel@gmail.com>
  6 *
  7 * based on spitz.c,
  8 * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
  9 *          Richard Purdie <richard@openedhand.com>
 10 */
 11
 12#include <linux/module.h>
 13#include <linux/timer.h>
 14#include <linux/interrupt.h>
 15#include <linux/platform_device.h>
 16#include <linux/delay.h>
 17#include <linux/gpio/consumer.h>
 18#include <linux/i2c.h>
 19
 20#include <sound/core.h>
 21#include <sound/pcm.h>
 22#include <sound/pcm_params.h>
 23#include <sound/soc.h>
 24
 25#include <asm/mach-types.h>
 26#include "../codecs/uda1380.h"
 27#include "pxa2xx-i2s.h"
 28#include "pxa-ssp.h"
 29
 30#define MAGICIAN_MIC       0
 31#define MAGICIAN_MIC_EXT   1
 32
 33static int magician_hp_switch;
 34static int magician_spk_switch = 1;
 35static int magician_in_sel = MAGICIAN_MIC;
 36
 37static struct gpio_desc *gpiod_spk_power, *gpiod_ep_power, *gpiod_mic_power;
 38static struct gpio_desc *gpiod_in_sel0, *gpiod_in_sel1;
 39
 40static void magician_ext_control(struct snd_soc_dapm_context *dapm)
 41{
 42
 43	snd_soc_dapm_mutex_lock(dapm);
 44
 45	if (magician_spk_switch)
 46		snd_soc_dapm_enable_pin_unlocked(dapm, "Speaker");
 47	else
 48		snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker");
 49	if (magician_hp_switch)
 50		snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack");
 51	else
 52		snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack");
 53
 54	switch (magician_in_sel) {
 55	case MAGICIAN_MIC:
 56		snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Mic");
 57		snd_soc_dapm_enable_pin_unlocked(dapm, "Call Mic");
 58		break;
 59	case MAGICIAN_MIC_EXT:
 60		snd_soc_dapm_disable_pin_unlocked(dapm, "Call Mic");
 61		snd_soc_dapm_enable_pin_unlocked(dapm, "Headset Mic");
 62		break;
 63	}
 64
 65	snd_soc_dapm_sync_unlocked(dapm);
 66
 67	snd_soc_dapm_mutex_unlock(dapm);
 68}
 69
 70static int magician_startup(struct snd_pcm_substream *substream)
 71{
 72	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
 73
 74	/* check the jack status at stream startup */
 75	magician_ext_control(&rtd->card->dapm);
 76
 77	return 0;
 78}
 79
 80/*
 81 * Magician uses SSP port for playback.
 82 */
 83static int magician_playback_hw_params(struct snd_pcm_substream *substream,
 84				       struct snd_pcm_hw_params *params)
 85{
 86	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
 87	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
 88	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
 89	unsigned int width;
 90	int ret = 0;
 91
 92	/* set codec DAI configuration */
 93	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_MSB |
 94			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_BC_FC);
 95	if (ret < 0)
 96		return ret;
 97
 98	/* set cpu DAI configuration */
 99	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
100			SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_BP_FP);
101	if (ret < 0)
102		return ret;
103
104	width = snd_pcm_format_physical_width(params_format(params));
105	ret = snd_soc_dai_set_tdm_slot(cpu_dai, 1, 0, 1, width);
106	if (ret < 0)
107		return ret;
108
109	/* set audio clock as clock source */
110	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_AUDIO, 0,
111			SND_SOC_CLOCK_OUT);
112	if (ret < 0)
113		return ret;
114
115	return 0;
116}
117
118/*
119 * Magician uses I2S for capture.
120 */
121static int magician_capture_hw_params(struct snd_pcm_substream *substream,
122				      struct snd_pcm_hw_params *params)
123{
124	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
125	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
126	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
127	int ret = 0;
128
129	/* set codec DAI configuration */
130	ret = snd_soc_dai_set_fmt(codec_dai,
131			SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF |
132			SND_SOC_DAIFMT_BC_FC);
133	if (ret < 0)
134		return ret;
135
136	/* set cpu DAI configuration */
137	ret = snd_soc_dai_set_fmt(cpu_dai,
138			SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF |
139			SND_SOC_DAIFMT_BP_FP);
140	if (ret < 0)
141		return ret;
142
143	/* set the I2S system clock as output */
144	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
145			SND_SOC_CLOCK_OUT);
146	if (ret < 0)
147		return ret;
148
149	return 0;
150}
151
152static const struct snd_soc_ops magician_capture_ops = {
153	.startup = magician_startup,
154	.hw_params = magician_capture_hw_params,
155};
156
157static const struct snd_soc_ops magician_playback_ops = {
158	.startup = magician_startup,
159	.hw_params = magician_playback_hw_params,
160};
161
162static int magician_get_hp(struct snd_kcontrol *kcontrol,
163			     struct snd_ctl_elem_value *ucontrol)
164{
165	ucontrol->value.integer.value[0] = magician_hp_switch;
166	return 0;
167}
168
169static int magician_set_hp(struct snd_kcontrol *kcontrol,
170			     struct snd_ctl_elem_value *ucontrol)
171{
172	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
173
174	if (magician_hp_switch == ucontrol->value.integer.value[0])
175		return 0;
176
177	magician_hp_switch = ucontrol->value.integer.value[0];
178	magician_ext_control(&card->dapm);
179	return 1;
180}
181
182static int magician_get_spk(struct snd_kcontrol *kcontrol,
183			    struct snd_ctl_elem_value *ucontrol)
184{
185	ucontrol->value.integer.value[0] = magician_spk_switch;
186	return 0;
187}
188
189static int magician_set_spk(struct snd_kcontrol *kcontrol,
190			    struct snd_ctl_elem_value *ucontrol)
191{
192	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
193
194	if (magician_spk_switch == ucontrol->value.integer.value[0])
195		return 0;
196
197	magician_spk_switch = ucontrol->value.integer.value[0];
198	magician_ext_control(&card->dapm);
199	return 1;
200}
201
202static int magician_get_input(struct snd_kcontrol *kcontrol,
203			      struct snd_ctl_elem_value *ucontrol)
204{
205	ucontrol->value.enumerated.item[0] = magician_in_sel;
206	return 0;
207}
208
209static int magician_set_input(struct snd_kcontrol *kcontrol,
210			      struct snd_ctl_elem_value *ucontrol)
211{
212	if (magician_in_sel == ucontrol->value.enumerated.item[0])
213		return 0;
214
215	magician_in_sel = ucontrol->value.enumerated.item[0];
216
217	switch (magician_in_sel) {
218	case MAGICIAN_MIC:
219		gpiod_set_value(gpiod_in_sel1, 1);
220		break;
221	case MAGICIAN_MIC_EXT:
222		gpiod_set_value(gpiod_in_sel1, 0);
223	}
224
225	return 1;
226}
227
228static int magician_spk_power(struct snd_soc_dapm_widget *w,
229				struct snd_kcontrol *k, int event)
230{
231	gpiod_set_value(gpiod_spk_power, SND_SOC_DAPM_EVENT_ON(event));
232	return 0;
233}
234
235static int magician_hp_power(struct snd_soc_dapm_widget *w,
236				struct snd_kcontrol *k, int event)
237{
238	gpiod_set_value(gpiod_ep_power, SND_SOC_DAPM_EVENT_ON(event));
239	return 0;
240}
241
242static int magician_mic_bias(struct snd_soc_dapm_widget *w,
243				struct snd_kcontrol *k, int event)
244{
245	gpiod_set_value(gpiod_mic_power, SND_SOC_DAPM_EVENT_ON(event));
246	return 0;
247}
248
249/* magician machine dapm widgets */
250static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
251	SND_SOC_DAPM_HP("Headphone Jack", magician_hp_power),
252	SND_SOC_DAPM_SPK("Speaker", magician_spk_power),
253	SND_SOC_DAPM_MIC("Call Mic", magician_mic_bias),
254	SND_SOC_DAPM_MIC("Headset Mic", magician_mic_bias),
255};
256
257/* magician machine audio_map */
258static const struct snd_soc_dapm_route audio_map[] = {
259
260	/* Headphone connected to VOUTL, VOUTR */
261	{"Headphone Jack", NULL, "VOUTL"},
262	{"Headphone Jack", NULL, "VOUTR"},
263
264	/* Speaker connected to VOUTL, VOUTR */
265	{"Speaker", NULL, "VOUTL"},
266	{"Speaker", NULL, "VOUTR"},
267
268	/* Mics are connected to VINM */
269	{"VINM", NULL, "Headset Mic"},
270	{"VINM", NULL, "Call Mic"},
271};
272
273static const char * const input_select[] = {"Call Mic", "Headset Mic"};
274static const struct soc_enum magician_in_sel_enum =
275	SOC_ENUM_SINGLE_EXT(2, input_select);
276
277static const struct snd_kcontrol_new uda1380_magician_controls[] = {
278	SOC_SINGLE_BOOL_EXT("Headphone Switch",
279			(unsigned long)&magician_hp_switch,
280			magician_get_hp, magician_set_hp),
281	SOC_SINGLE_BOOL_EXT("Speaker Switch",
282			(unsigned long)&magician_spk_switch,
283			magician_get_spk, magician_set_spk),
284	SOC_ENUM_EXT("Input Select", magician_in_sel_enum,
285			magician_get_input, magician_set_input),
286};
287
288/* magician digital audio interface glue - connects codec <--> CPU */
289SND_SOC_DAILINK_DEFS(playback,
290	DAILINK_COMP_ARRAY(COMP_CPU("pxa-ssp-dai.0")),
291	DAILINK_COMP_ARRAY(COMP_CODEC("uda1380-codec.0-0018",
292				      "uda1380-hifi-playback")),
293	DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
294
295SND_SOC_DAILINK_DEFS(capture,
296	DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-i2s")),
297	DAILINK_COMP_ARRAY(COMP_CODEC("uda1380-codec.0-0018",
298				      "uda1380-hifi-capture")),
299	DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
300
301static struct snd_soc_dai_link magician_dai[] = {
302{
303	.name = "uda1380",
304	.stream_name = "UDA1380 Playback",
305	.ops = &magician_playback_ops,
306	SND_SOC_DAILINK_REG(playback),
307},
308{
309	.name = "uda1380",
310	.stream_name = "UDA1380 Capture",
311	.ops = &magician_capture_ops,
312	SND_SOC_DAILINK_REG(capture),
313}
314};
315
316/* magician audio machine driver */
317static struct snd_soc_card snd_soc_card_magician = {
318	.name = "Magician",
319	.owner = THIS_MODULE,
320	.dai_link = magician_dai,
321	.num_links = ARRAY_SIZE(magician_dai),
322
323	.controls = uda1380_magician_controls,
324	.num_controls = ARRAY_SIZE(uda1380_magician_controls),
325	.dapm_widgets = uda1380_dapm_widgets,
326	.num_dapm_widgets = ARRAY_SIZE(uda1380_dapm_widgets),
327	.dapm_routes = audio_map,
328	.num_dapm_routes = ARRAY_SIZE(audio_map),
329	.fully_routed = true,
330};
331
332static int magician_audio_probe(struct platform_device *pdev)
333{
334	struct device *dev = &pdev->dev;
335
336	gpiod_spk_power = devm_gpiod_get(dev, "SPK_POWER", GPIOD_OUT_LOW);
337	if (IS_ERR(gpiod_spk_power))
338		return PTR_ERR(gpiod_spk_power);
339	gpiod_ep_power = devm_gpiod_get(dev, "EP_POWER", GPIOD_OUT_LOW);
340	if (IS_ERR(gpiod_ep_power))
341		return PTR_ERR(gpiod_ep_power);
342	gpiod_mic_power = devm_gpiod_get(dev, "MIC_POWER", GPIOD_OUT_LOW);
343	if (IS_ERR(gpiod_mic_power))
344		return PTR_ERR(gpiod_mic_power);
345	gpiod_in_sel0 = devm_gpiod_get(dev, "IN_SEL0", GPIOD_OUT_HIGH);
346	if (IS_ERR(gpiod_in_sel0))
347		return PTR_ERR(gpiod_in_sel0);
348	gpiod_in_sel1 = devm_gpiod_get(dev, "IN_SEL1", GPIOD_OUT_LOW);
349	if (IS_ERR(gpiod_in_sel1))
350		return PTR_ERR(gpiod_in_sel1);
351
352	snd_soc_card_magician.dev = &pdev->dev;
353	return devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_magician);
354}
355
356static struct platform_driver magician_audio_driver = {
357	.driver.name = "magician-audio",
358	.driver.pm = &snd_soc_pm_ops,
359	.probe = magician_audio_probe,
360};
361module_platform_driver(magician_audio_driver);
362
363MODULE_AUTHOR("Philipp Zabel");
364MODULE_DESCRIPTION("ALSA SoC Magician");
365MODULE_LICENSE("GPL");
366MODULE_ALIAS("platform:magician-audio");