Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.15.
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * ams-delta.c  --  SoC audio for Amstrad E3 (Delta) videophone
  4 *
  5 * Copyright (C) 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
  6 *
  7 * Initially based on sound/soc/omap/osk5912.x
  8 * Copyright (C) 2008 Mistral Solutions
  9 */
 10
 11#include <linux/gpio/consumer.h>
 12#include <linux/spinlock.h>
 13#include <linux/tty.h>
 14#include <linux/module.h>
 15
 16#include <sound/soc.h>
 17#include <sound/jack.h>
 18
 19#include <asm/mach-types.h>
 20
 21#include <linux/platform_data/asoc-ti-mcbsp.h>
 22
 23#include "omap-mcbsp.h"
 24#include "../codecs/cx20442.h"
 25
 26static struct gpio_desc *handset_mute;
 27static struct gpio_desc *handsfree_mute;
 28
 29static int ams_delta_event_handset(struct snd_soc_dapm_widget *w,
 30				   struct snd_kcontrol *k, int event)
 31{
 32	gpiod_set_value_cansleep(handset_mute, !SND_SOC_DAPM_EVENT_ON(event));
 33	return 0;
 34}
 35
 36static int ams_delta_event_handsfree(struct snd_soc_dapm_widget *w,
 37				     struct snd_kcontrol *k, int event)
 38{
 39	gpiod_set_value_cansleep(handsfree_mute, !SND_SOC_DAPM_EVENT_ON(event));
 40	return 0;
 41}
 42
 43/* Board specific DAPM widgets */
 44static const struct snd_soc_dapm_widget ams_delta_dapm_widgets[] = {
 45	/* Handset */
 46	SND_SOC_DAPM_MIC("Mouthpiece", NULL),
 47	SND_SOC_DAPM_HP("Earpiece", ams_delta_event_handset),
 48	/* Handsfree/Speakerphone */
 49	SND_SOC_DAPM_MIC("Microphone", NULL),
 50	SND_SOC_DAPM_SPK("Speaker", ams_delta_event_handsfree),
 51};
 52
 53/* How they are connected to codec pins */
 54static const struct snd_soc_dapm_route ams_delta_audio_map[] = {
 55	{"TELIN", NULL, "Mouthpiece"},
 56	{"Earpiece", NULL, "TELOUT"},
 57
 58	{"MIC", NULL, "Microphone"},
 59	{"Speaker", NULL, "SPKOUT"},
 60};
 61
 62/*
 63 * Controls, functional after the modem line discipline is activated.
 64 */
 65
 66/* Virtual switch: audio input/output constellations */
 67static const char *ams_delta_audio_mode[] =
 68	{"Mixed", "Handset", "Handsfree", "Speakerphone"};
 69
 70/* Selection <-> pin translation */
 71#define AMS_DELTA_MOUTHPIECE	0
 72#define AMS_DELTA_EARPIECE	1
 73#define AMS_DELTA_MICROPHONE	2
 74#define AMS_DELTA_SPEAKER	3
 75#define AMS_DELTA_AGC		4
 76
 77#define AMS_DELTA_MIXED		((1 << AMS_DELTA_EARPIECE) | \
 78						(1 << AMS_DELTA_MICROPHONE))
 79#define AMS_DELTA_HANDSET	((1 << AMS_DELTA_MOUTHPIECE) | \
 80						(1 << AMS_DELTA_EARPIECE))
 81#define AMS_DELTA_HANDSFREE	((1 << AMS_DELTA_MICROPHONE) | \
 82						(1 << AMS_DELTA_SPEAKER))
 83#define AMS_DELTA_SPEAKERPHONE	(AMS_DELTA_HANDSFREE | (1 << AMS_DELTA_AGC))
 84
 85static const unsigned short ams_delta_audio_mode_pins[] = {
 86	AMS_DELTA_MIXED,
 87	AMS_DELTA_HANDSET,
 88	AMS_DELTA_HANDSFREE,
 89	AMS_DELTA_SPEAKERPHONE,
 90};
 91
 92static unsigned short ams_delta_audio_agc;
 93
 94/*
 95 * Used for passing a codec structure pointer
 96 * from the board initialization code to the tty line discipline.
 97 */
 98static struct snd_soc_component *cx20442_codec;
 99
100static int ams_delta_set_audio_mode(struct snd_kcontrol *kcontrol,
101					struct snd_ctl_elem_value *ucontrol)
102{
103	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
104	struct snd_soc_dapm_context *dapm = &card->dapm;
105	struct soc_enum *control = (struct soc_enum *)kcontrol->private_value;
106	unsigned short pins;
107	int pin, changed = 0;
108
109	/* Refuse any mode changes if we are not able to control the codec. */
110	if (!cx20442_codec->card->pop_time)
111		return -EUNATCH;
112
113	if (ucontrol->value.enumerated.item[0] >= control->items)
114		return -EINVAL;
115
116	snd_soc_dapm_mutex_lock(dapm);
117
118	/* Translate selection to bitmap */
119	pins = ams_delta_audio_mode_pins[ucontrol->value.enumerated.item[0]];
120
121	/* Setup pins after corresponding bits if changed */
122	pin = !!(pins & (1 << AMS_DELTA_MOUTHPIECE));
123
124	if (pin != snd_soc_dapm_get_pin_status(dapm, "Mouthpiece")) {
125		changed = 1;
126		if (pin)
127			snd_soc_dapm_enable_pin_unlocked(dapm, "Mouthpiece");
128		else
129			snd_soc_dapm_disable_pin_unlocked(dapm, "Mouthpiece");
130	}
131	pin = !!(pins & (1 << AMS_DELTA_EARPIECE));
132	if (pin != snd_soc_dapm_get_pin_status(dapm, "Earpiece")) {
133		changed = 1;
134		if (pin)
135			snd_soc_dapm_enable_pin_unlocked(dapm, "Earpiece");
136		else
137			snd_soc_dapm_disable_pin_unlocked(dapm, "Earpiece");
138	}
139	pin = !!(pins & (1 << AMS_DELTA_MICROPHONE));
140	if (pin != snd_soc_dapm_get_pin_status(dapm, "Microphone")) {
141		changed = 1;
142		if (pin)
143			snd_soc_dapm_enable_pin_unlocked(dapm, "Microphone");
144		else
145			snd_soc_dapm_disable_pin_unlocked(dapm, "Microphone");
146	}
147	pin = !!(pins & (1 << AMS_DELTA_SPEAKER));
148	if (pin != snd_soc_dapm_get_pin_status(dapm, "Speaker")) {
149		changed = 1;
150		if (pin)
151			snd_soc_dapm_enable_pin_unlocked(dapm, "Speaker");
152		else
153			snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker");
154	}
155	pin = !!(pins & (1 << AMS_DELTA_AGC));
156	if (pin != ams_delta_audio_agc) {
157		ams_delta_audio_agc = pin;
158		changed = 1;
159		if (pin)
160			snd_soc_dapm_enable_pin_unlocked(dapm, "AGCIN");
161		else
162			snd_soc_dapm_disable_pin_unlocked(dapm, "AGCIN");
163	}
164
165	if (changed)
166		snd_soc_dapm_sync_unlocked(dapm);
167
168	snd_soc_dapm_mutex_unlock(dapm);
169
170	return changed;
171}
172
173static int ams_delta_get_audio_mode(struct snd_kcontrol *kcontrol,
174					struct snd_ctl_elem_value *ucontrol)
175{
176	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
177	struct snd_soc_dapm_context *dapm = &card->dapm;
178	unsigned short pins, mode;
179
180	pins = ((snd_soc_dapm_get_pin_status(dapm, "Mouthpiece") <<
181							AMS_DELTA_MOUTHPIECE) |
182			(snd_soc_dapm_get_pin_status(dapm, "Earpiece") <<
183							AMS_DELTA_EARPIECE));
184	if (pins)
185		pins |= (snd_soc_dapm_get_pin_status(dapm, "Microphone") <<
186							AMS_DELTA_MICROPHONE);
187	else
188		pins = ((snd_soc_dapm_get_pin_status(dapm, "Microphone") <<
189							AMS_DELTA_MICROPHONE) |
190			(snd_soc_dapm_get_pin_status(dapm, "Speaker") <<
191							AMS_DELTA_SPEAKER) |
192			(ams_delta_audio_agc << AMS_DELTA_AGC));
193
194	for (mode = 0; mode < ARRAY_SIZE(ams_delta_audio_mode); mode++)
195		if (pins == ams_delta_audio_mode_pins[mode])
196			break;
197
198	if (mode >= ARRAY_SIZE(ams_delta_audio_mode))
199		return -EINVAL;
200
201	ucontrol->value.enumerated.item[0] = mode;
202
203	return 0;
204}
205
206static SOC_ENUM_SINGLE_EXT_DECL(ams_delta_audio_enum,
207				      ams_delta_audio_mode);
208
209static const struct snd_kcontrol_new ams_delta_audio_controls[] = {
210	SOC_ENUM_EXT("Audio Mode", ams_delta_audio_enum,
211			ams_delta_get_audio_mode, ams_delta_set_audio_mode),
212};
213
214/* Hook switch */
215static struct snd_soc_jack ams_delta_hook_switch;
216static struct snd_soc_jack_gpio ams_delta_hook_switch_gpios[] = {
217	{
218		.name = "hook_switch",
219		.report = SND_JACK_HEADSET,
220		.invert = 1,
221		.debounce_time = 150,
222	}
223};
224
225/* After we are able to control the codec over the modem,
226 * the hook switch can be used for dynamic DAPM reconfiguration. */
227static struct snd_soc_jack_pin ams_delta_hook_switch_pins[] = {
228	/* Handset */
229	{
230		.pin = "Mouthpiece",
231		.mask = SND_JACK_MICROPHONE,
232	},
233	{
234		.pin = "Earpiece",
235		.mask = SND_JACK_HEADPHONE,
236	},
237	/* Handsfree */
238	{
239		.pin = "Microphone",
240		.mask = SND_JACK_MICROPHONE,
241		.invert = 1,
242	},
243	{
244		.pin = "Speaker",
245		.mask = SND_JACK_HEADPHONE,
246		.invert = 1,
247	},
248};
249
250
251/*
252 * Modem line discipline, required for making above controls functional.
253 * Activated from userspace with ldattach, possibly invoked from udev rule.
254 */
255
256/* To actually apply any modem controlled configuration changes to the codec,
257 * we must connect codec DAI pins to the modem for a moment.  Be careful not
258 * to interfere with our digital mute function that shares the same hardware. */
259static struct timer_list cx81801_timer;
260static bool cx81801_cmd_pending;
261static bool ams_delta_muted;
262static DEFINE_SPINLOCK(ams_delta_lock);
263static struct gpio_desc *gpiod_modem_codec;
264
265static void cx81801_timeout(struct timer_list *unused)
266{
267	int muted;
268
269	spin_lock(&ams_delta_lock);
270	cx81801_cmd_pending = 0;
271	muted = ams_delta_muted;
272	spin_unlock(&ams_delta_lock);
273
274	/* Reconnect the codec DAI back from the modem to the CPU DAI
275	 * only if digital mute still off */
276	if (!muted)
277		gpiod_set_value(gpiod_modem_codec, 0);
278}
279
280/* Line discipline .open() */
281static int cx81801_open(struct tty_struct *tty)
282{
283	int ret;
284
285	if (!cx20442_codec)
286		return -ENODEV;
287
288	/*
289	 * Pass the codec structure pointer for use by other ldisc callbacks,
290	 * both the card and the codec specific parts.
291	 */
292	tty->disc_data = cx20442_codec;
293
294	ret = v253_ops.open(tty);
295
296	if (ret < 0)
297		tty->disc_data = NULL;
298
299	return ret;
300}
301
302/* Line discipline .close() */
303static void cx81801_close(struct tty_struct *tty)
304{
305	struct snd_soc_component *component = tty->disc_data;
306	struct snd_soc_dapm_context *dapm = &component->card->dapm;
307
308	del_timer_sync(&cx81801_timer);
309
310	/* Prevent the hook switch from further changing the DAPM pins */
311	INIT_LIST_HEAD(&ams_delta_hook_switch.pins);
312
313	if (!component)
314		return;
315
316	v253_ops.close(tty);
317
318	/* Revert back to default audio input/output constellation */
319	snd_soc_dapm_mutex_lock(dapm);
320
321	snd_soc_dapm_disable_pin_unlocked(dapm, "Mouthpiece");
322	snd_soc_dapm_enable_pin_unlocked(dapm, "Earpiece");
323	snd_soc_dapm_enable_pin_unlocked(dapm, "Microphone");
324	snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker");
325	snd_soc_dapm_disable_pin_unlocked(dapm, "AGCIN");
326
327	snd_soc_dapm_sync_unlocked(dapm);
328
329	snd_soc_dapm_mutex_unlock(dapm);
330}
331
332/* Line discipline .hangup() */
333static void cx81801_hangup(struct tty_struct *tty)
334{
335	cx81801_close(tty);
336}
337
338/* Line discipline .receive_buf() */
339static void cx81801_receive(struct tty_struct *tty, const unsigned char *cp,
340		const char *fp, int count)
341{
342	struct snd_soc_component *component = tty->disc_data;
343	const unsigned char *c;
344	int apply, ret;
345
346	if (!component)
347		return;
348
349	if (!component->card->pop_time) {
350		/* First modem response, complete setup procedure */
351
352		/* Initialize timer used for config pulse generation */
353		timer_setup(&cx81801_timer, cx81801_timeout, 0);
354
355		v253_ops.receive_buf(tty, cp, fp, count);
356
357		/* Link hook switch to DAPM pins */
358		ret = snd_soc_jack_add_pins(&ams_delta_hook_switch,
359					ARRAY_SIZE(ams_delta_hook_switch_pins),
360					ams_delta_hook_switch_pins);
361		if (ret)
362			dev_warn(component->dev,
363				"Failed to link hook switch to DAPM pins, "
364				"will continue with hook switch unlinked.\n");
365
366		return;
367	}
368
369	v253_ops.receive_buf(tty, cp, fp, count);
370
371	for (c = &cp[count - 1]; c >= cp; c--) {
372		if (*c != '\r')
373			continue;
374		/* Complete modem response received, apply config to codec */
375
376		spin_lock_bh(&ams_delta_lock);
377		mod_timer(&cx81801_timer, jiffies + msecs_to_jiffies(150));
378		apply = !ams_delta_muted && !cx81801_cmd_pending;
379		cx81801_cmd_pending = 1;
380		spin_unlock_bh(&ams_delta_lock);
381
382		/* Apply config pulse by connecting the codec to the modem
383		 * if not already done */
384		if (apply)
385			gpiod_set_value(gpiod_modem_codec, 1);
386		break;
387	}
388}
389
390/* Line discipline .write_wakeup() */
391static void cx81801_wakeup(struct tty_struct *tty)
392{
393	v253_ops.write_wakeup(tty);
394}
395
396static struct tty_ldisc_ops cx81801_ops = {
397	.name = "cx81801",
398	.num = N_V253,
399	.owner = THIS_MODULE,
400	.open = cx81801_open,
401	.close = cx81801_close,
402	.hangup = cx81801_hangup,
403	.receive_buf = cx81801_receive,
404	.write_wakeup = cx81801_wakeup,
405};
406
407
408/*
409 * Even if not very useful, the sound card can still work without any of the
410 * above functionality activated.  You can still control its audio input/output
411 * constellation and speakerphone gain from userspace by issuing AT commands
412 * over the modem port.
413 */
414
415static struct snd_soc_ops ams_delta_ops;
416
417
418/* Digital mute implemented using modem/CPU multiplexer.
419 * Shares hardware with codec config pulse generation */
420static bool ams_delta_muted = 1;
421
422static int ams_delta_mute(struct snd_soc_dai *dai, int mute, int direction)
423{
424	int apply;
425
426	if (ams_delta_muted == mute)
427		return 0;
428
429	spin_lock_bh(&ams_delta_lock);
430	ams_delta_muted = mute;
431	apply = !cx81801_cmd_pending;
432	spin_unlock_bh(&ams_delta_lock);
433
434	if (apply)
435		gpiod_set_value(gpiod_modem_codec, !!mute);
436	return 0;
437}
438
439/* Our codec DAI probably doesn't have its own .ops structure */
440static const struct snd_soc_dai_ops ams_delta_dai_ops = {
441	.mute_stream = ams_delta_mute,
442	.no_capture_mute = 1,
443};
444
445/* Will be used if the codec ever has its own digital_mute function */
446static int ams_delta_startup(struct snd_pcm_substream *substream)
447{
448	return ams_delta_mute(NULL, 0, substream->stream);
449}
450
451static void ams_delta_shutdown(struct snd_pcm_substream *substream)
452{
453	ams_delta_mute(NULL, 1, substream->stream);
454}
455
456
457/*
458 * Card initialization
459 */
460
461static int ams_delta_cx20442_init(struct snd_soc_pcm_runtime *rtd)
462{
463	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
464	struct snd_soc_card *card = rtd->card;
465	struct snd_soc_dapm_context *dapm = &card->dapm;
466	int ret;
467	/* Codec is ready, now add/activate board specific controls */
468
469	/* Store a pointer to the codec structure for tty ldisc use */
470	cx20442_codec = asoc_rtd_to_codec(rtd, 0)->component;
471
472	/* Add hook switch - can be used to control the codec from userspace
473	 * even if line discipline fails */
474	ret = snd_soc_card_jack_new_pins(card, "hook_switch", SND_JACK_HEADSET,
475					 &ams_delta_hook_switch, NULL, 0);
476	if (ret)
477		dev_warn(card->dev,
478				"Failed to allocate resources for hook switch, "
479				"will continue without one.\n");
480	else {
481		ret = snd_soc_jack_add_gpiods(card->dev, &ams_delta_hook_switch,
482					ARRAY_SIZE(ams_delta_hook_switch_gpios),
483					ams_delta_hook_switch_gpios);
484		if (ret)
485			dev_warn(card->dev,
486				"Failed to set up hook switch GPIO line, "
487				"will continue with hook switch inactive.\n");
488	}
489
490	gpiod_modem_codec = devm_gpiod_get(card->dev, "modem_codec",
491					   GPIOD_OUT_HIGH);
492	if (IS_ERR(gpiod_modem_codec)) {
493		dev_warn(card->dev, "Failed to obtain modem_codec GPIO\n");
494		return 0;
495	}
496
497	/* Set up digital mute if not provided by the codec */
498	if (!codec_dai->driver->ops) {
499		codec_dai->driver->ops = &ams_delta_dai_ops;
500	} else {
501		ams_delta_ops.startup = ams_delta_startup;
502		ams_delta_ops.shutdown = ams_delta_shutdown;
503	}
504
505	/* Register optional line discipline for over the modem control */
506	ret = tty_register_ldisc(&cx81801_ops);
507	if (ret) {
508		dev_warn(card->dev,
509				"Failed to register line discipline, "
510				"will continue without any controls.\n");
511		return 0;
512	}
513
514	/* Set up initial pin constellation */
515	snd_soc_dapm_disable_pin(dapm, "Mouthpiece");
516	snd_soc_dapm_disable_pin(dapm, "Speaker");
517	snd_soc_dapm_disable_pin(dapm, "AGCIN");
518	snd_soc_dapm_disable_pin(dapm, "AGCOUT");
519
520	return 0;
521}
522
523/* DAI glue - connects codec <--> CPU */
524SND_SOC_DAILINK_DEFS(cx20442,
525	DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.1")),
526	DAILINK_COMP_ARRAY(COMP_CODEC("cx20442-codec", "cx20442-voice")),
527	DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.1")));
528
529static struct snd_soc_dai_link ams_delta_dai_link = {
530	.name = "CX20442",
531	.stream_name = "CX20442",
532	.init = ams_delta_cx20442_init,
533	.ops = &ams_delta_ops,
534	.dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF |
535		   SND_SOC_DAIFMT_CBM_CFM,
536	SND_SOC_DAILINK_REG(cx20442),
537};
538
539/* Audio card driver */
540static struct snd_soc_card ams_delta_audio_card = {
541	.name = "AMS_DELTA",
542	.owner = THIS_MODULE,
543	.dai_link = &ams_delta_dai_link,
544	.num_links = 1,
545
546	.controls = ams_delta_audio_controls,
547	.num_controls = ARRAY_SIZE(ams_delta_audio_controls),
548	.dapm_widgets = ams_delta_dapm_widgets,
549	.num_dapm_widgets = ARRAY_SIZE(ams_delta_dapm_widgets),
550	.dapm_routes = ams_delta_audio_map,
551	.num_dapm_routes = ARRAY_SIZE(ams_delta_audio_map),
552};
553
554/* Module init/exit */
555static int ams_delta_probe(struct platform_device *pdev)
556{
557	struct snd_soc_card *card = &ams_delta_audio_card;
558	int ret;
559
560	card->dev = &pdev->dev;
561
562	handset_mute = devm_gpiod_get(card->dev, "handset_mute",
563				      GPIOD_OUT_HIGH);
564	if (IS_ERR(handset_mute))
565		return PTR_ERR(handset_mute);
566
567	handsfree_mute = devm_gpiod_get(card->dev, "handsfree_mute",
568					GPIOD_OUT_HIGH);
569	if (IS_ERR(handsfree_mute))
570		return PTR_ERR(handsfree_mute);
571
572	ret = snd_soc_register_card(card);
573	if (ret) {
574		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
575		card->dev = NULL;
576		return ret;
577	}
578	return 0;
579}
580
581static int ams_delta_remove(struct platform_device *pdev)
582{
583	struct snd_soc_card *card = platform_get_drvdata(pdev);
584
585	tty_unregister_ldisc(&cx81801_ops);
586
587	snd_soc_unregister_card(card);
588	card->dev = NULL;
589	return 0;
590}
591
592#define DRV_NAME "ams-delta-audio"
593
594static struct platform_driver ams_delta_driver = {
595	.driver = {
596		.name = DRV_NAME,
597	},
598	.probe = ams_delta_probe,
599	.remove = ams_delta_remove,
600};
601
602module_platform_driver(ams_delta_driver);
603
604MODULE_AUTHOR("Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>");
605MODULE_DESCRIPTION("ALSA SoC driver for Amstrad E3 (Delta) videophone");
606MODULE_LICENSE("GPL");
607MODULE_ALIAS("platform:" DRV_NAME);