18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * ams-delta.c  --  SoC audio for Amstrad E3 (Delta) videophone
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Initially based on sound/soc/omap/osk5912.x
88c2ecf20Sopenharmony_ci * Copyright (C) 2008 Mistral Solutions
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
128c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
138c2ecf20Sopenharmony_ci#include <linux/tty.h>
148c2ecf20Sopenharmony_ci#include <linux/module.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include <sound/soc.h>
178c2ecf20Sopenharmony_ci#include <sound/jack.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <asm/mach-types.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <linux/platform_data/asoc-ti-mcbsp.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#include "omap-mcbsp.h"
248c2ecf20Sopenharmony_ci#include "../codecs/cx20442.h"
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic struct gpio_desc *handset_mute;
278c2ecf20Sopenharmony_cistatic struct gpio_desc *handsfree_mute;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic int ams_delta_event_handset(struct snd_soc_dapm_widget *w,
308c2ecf20Sopenharmony_ci				   struct snd_kcontrol *k, int event)
318c2ecf20Sopenharmony_ci{
328c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(handset_mute, !SND_SOC_DAPM_EVENT_ON(event));
338c2ecf20Sopenharmony_ci	return 0;
348c2ecf20Sopenharmony_ci}
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic int ams_delta_event_handsfree(struct snd_soc_dapm_widget *w,
378c2ecf20Sopenharmony_ci				     struct snd_kcontrol *k, int event)
388c2ecf20Sopenharmony_ci{
398c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(handsfree_mute, !SND_SOC_DAPM_EVENT_ON(event));
408c2ecf20Sopenharmony_ci	return 0;
418c2ecf20Sopenharmony_ci}
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci/* Board specific DAPM widgets */
448c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_widget ams_delta_dapm_widgets[] = {
458c2ecf20Sopenharmony_ci	/* Handset */
468c2ecf20Sopenharmony_ci	SND_SOC_DAPM_MIC("Mouthpiece", NULL),
478c2ecf20Sopenharmony_ci	SND_SOC_DAPM_HP("Earpiece", ams_delta_event_handset),
488c2ecf20Sopenharmony_ci	/* Handsfree/Speakerphone */
498c2ecf20Sopenharmony_ci	SND_SOC_DAPM_MIC("Microphone", NULL),
508c2ecf20Sopenharmony_ci	SND_SOC_DAPM_SPK("Speaker", ams_delta_event_handsfree),
518c2ecf20Sopenharmony_ci};
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci/* How they are connected to codec pins */
548c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_route ams_delta_audio_map[] = {
558c2ecf20Sopenharmony_ci	{"TELIN", NULL, "Mouthpiece"},
568c2ecf20Sopenharmony_ci	{"Earpiece", NULL, "TELOUT"},
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	{"MIC", NULL, "Microphone"},
598c2ecf20Sopenharmony_ci	{"Speaker", NULL, "SPKOUT"},
608c2ecf20Sopenharmony_ci};
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci/*
638c2ecf20Sopenharmony_ci * Controls, functional after the modem line discipline is activated.
648c2ecf20Sopenharmony_ci */
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci/* Virtual switch: audio input/output constellations */
678c2ecf20Sopenharmony_cistatic const char *ams_delta_audio_mode[] =
688c2ecf20Sopenharmony_ci	{"Mixed", "Handset", "Handsfree", "Speakerphone"};
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci/* Selection <-> pin translation */
718c2ecf20Sopenharmony_ci#define AMS_DELTA_MOUTHPIECE	0
728c2ecf20Sopenharmony_ci#define AMS_DELTA_EARPIECE	1
738c2ecf20Sopenharmony_ci#define AMS_DELTA_MICROPHONE	2
748c2ecf20Sopenharmony_ci#define AMS_DELTA_SPEAKER	3
758c2ecf20Sopenharmony_ci#define AMS_DELTA_AGC		4
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci#define AMS_DELTA_MIXED		((1 << AMS_DELTA_EARPIECE) | \
788c2ecf20Sopenharmony_ci						(1 << AMS_DELTA_MICROPHONE))
798c2ecf20Sopenharmony_ci#define AMS_DELTA_HANDSET	((1 << AMS_DELTA_MOUTHPIECE) | \
808c2ecf20Sopenharmony_ci						(1 << AMS_DELTA_EARPIECE))
818c2ecf20Sopenharmony_ci#define AMS_DELTA_HANDSFREE	((1 << AMS_DELTA_MICROPHONE) | \
828c2ecf20Sopenharmony_ci						(1 << AMS_DELTA_SPEAKER))
838c2ecf20Sopenharmony_ci#define AMS_DELTA_SPEAKERPHONE	(AMS_DELTA_HANDSFREE | (1 << AMS_DELTA_AGC))
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_cistatic const unsigned short ams_delta_audio_mode_pins[] = {
868c2ecf20Sopenharmony_ci	AMS_DELTA_MIXED,
878c2ecf20Sopenharmony_ci	AMS_DELTA_HANDSET,
888c2ecf20Sopenharmony_ci	AMS_DELTA_HANDSFREE,
898c2ecf20Sopenharmony_ci	AMS_DELTA_SPEAKERPHONE,
908c2ecf20Sopenharmony_ci};
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic unsigned short ams_delta_audio_agc;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci/*
958c2ecf20Sopenharmony_ci * Used for passing a codec structure pointer
968c2ecf20Sopenharmony_ci * from the board initialization code to the tty line discipline.
978c2ecf20Sopenharmony_ci */
988c2ecf20Sopenharmony_cistatic struct snd_soc_component *cx20442_codec;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_cistatic int ams_delta_set_audio_mode(struct snd_kcontrol *kcontrol,
1018c2ecf20Sopenharmony_ci					struct snd_ctl_elem_value *ucontrol)
1028c2ecf20Sopenharmony_ci{
1038c2ecf20Sopenharmony_ci	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
1048c2ecf20Sopenharmony_ci	struct snd_soc_dapm_context *dapm = &card->dapm;
1058c2ecf20Sopenharmony_ci	struct soc_enum *control = (struct soc_enum *)kcontrol->private_value;
1068c2ecf20Sopenharmony_ci	unsigned short pins;
1078c2ecf20Sopenharmony_ci	int pin, changed = 0;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	/* Refuse any mode changes if we are not able to control the codec. */
1108c2ecf20Sopenharmony_ci	if (!cx20442_codec->card->pop_time)
1118c2ecf20Sopenharmony_ci		return -EUNATCH;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	if (ucontrol->value.enumerated.item[0] >= control->items)
1148c2ecf20Sopenharmony_ci		return -EINVAL;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	snd_soc_dapm_mutex_lock(dapm);
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	/* Translate selection to bitmap */
1198c2ecf20Sopenharmony_ci	pins = ams_delta_audio_mode_pins[ucontrol->value.enumerated.item[0]];
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	/* Setup pins after corresponding bits if changed */
1228c2ecf20Sopenharmony_ci	pin = !!(pins & (1 << AMS_DELTA_MOUTHPIECE));
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (pin != snd_soc_dapm_get_pin_status(dapm, "Mouthpiece")) {
1258c2ecf20Sopenharmony_ci		changed = 1;
1268c2ecf20Sopenharmony_ci		if (pin)
1278c2ecf20Sopenharmony_ci			snd_soc_dapm_enable_pin_unlocked(dapm, "Mouthpiece");
1288c2ecf20Sopenharmony_ci		else
1298c2ecf20Sopenharmony_ci			snd_soc_dapm_disable_pin_unlocked(dapm, "Mouthpiece");
1308c2ecf20Sopenharmony_ci	}
1318c2ecf20Sopenharmony_ci	pin = !!(pins & (1 << AMS_DELTA_EARPIECE));
1328c2ecf20Sopenharmony_ci	if (pin != snd_soc_dapm_get_pin_status(dapm, "Earpiece")) {
1338c2ecf20Sopenharmony_ci		changed = 1;
1348c2ecf20Sopenharmony_ci		if (pin)
1358c2ecf20Sopenharmony_ci			snd_soc_dapm_enable_pin_unlocked(dapm, "Earpiece");
1368c2ecf20Sopenharmony_ci		else
1378c2ecf20Sopenharmony_ci			snd_soc_dapm_disable_pin_unlocked(dapm, "Earpiece");
1388c2ecf20Sopenharmony_ci	}
1398c2ecf20Sopenharmony_ci	pin = !!(pins & (1 << AMS_DELTA_MICROPHONE));
1408c2ecf20Sopenharmony_ci	if (pin != snd_soc_dapm_get_pin_status(dapm, "Microphone")) {
1418c2ecf20Sopenharmony_ci		changed = 1;
1428c2ecf20Sopenharmony_ci		if (pin)
1438c2ecf20Sopenharmony_ci			snd_soc_dapm_enable_pin_unlocked(dapm, "Microphone");
1448c2ecf20Sopenharmony_ci		else
1458c2ecf20Sopenharmony_ci			snd_soc_dapm_disable_pin_unlocked(dapm, "Microphone");
1468c2ecf20Sopenharmony_ci	}
1478c2ecf20Sopenharmony_ci	pin = !!(pins & (1 << AMS_DELTA_SPEAKER));
1488c2ecf20Sopenharmony_ci	if (pin != snd_soc_dapm_get_pin_status(dapm, "Speaker")) {
1498c2ecf20Sopenharmony_ci		changed = 1;
1508c2ecf20Sopenharmony_ci		if (pin)
1518c2ecf20Sopenharmony_ci			snd_soc_dapm_enable_pin_unlocked(dapm, "Speaker");
1528c2ecf20Sopenharmony_ci		else
1538c2ecf20Sopenharmony_ci			snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker");
1548c2ecf20Sopenharmony_ci	}
1558c2ecf20Sopenharmony_ci	pin = !!(pins & (1 << AMS_DELTA_AGC));
1568c2ecf20Sopenharmony_ci	if (pin != ams_delta_audio_agc) {
1578c2ecf20Sopenharmony_ci		ams_delta_audio_agc = pin;
1588c2ecf20Sopenharmony_ci		changed = 1;
1598c2ecf20Sopenharmony_ci		if (pin)
1608c2ecf20Sopenharmony_ci			snd_soc_dapm_enable_pin_unlocked(dapm, "AGCIN");
1618c2ecf20Sopenharmony_ci		else
1628c2ecf20Sopenharmony_ci			snd_soc_dapm_disable_pin_unlocked(dapm, "AGCIN");
1638c2ecf20Sopenharmony_ci	}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	if (changed)
1668c2ecf20Sopenharmony_ci		snd_soc_dapm_sync_unlocked(dapm);
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	snd_soc_dapm_mutex_unlock(dapm);
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	return changed;
1718c2ecf20Sopenharmony_ci}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_cistatic int ams_delta_get_audio_mode(struct snd_kcontrol *kcontrol,
1748c2ecf20Sopenharmony_ci					struct snd_ctl_elem_value *ucontrol)
1758c2ecf20Sopenharmony_ci{
1768c2ecf20Sopenharmony_ci	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
1778c2ecf20Sopenharmony_ci	struct snd_soc_dapm_context *dapm = &card->dapm;
1788c2ecf20Sopenharmony_ci	unsigned short pins, mode;
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	pins = ((snd_soc_dapm_get_pin_status(dapm, "Mouthpiece") <<
1818c2ecf20Sopenharmony_ci							AMS_DELTA_MOUTHPIECE) |
1828c2ecf20Sopenharmony_ci			(snd_soc_dapm_get_pin_status(dapm, "Earpiece") <<
1838c2ecf20Sopenharmony_ci							AMS_DELTA_EARPIECE));
1848c2ecf20Sopenharmony_ci	if (pins)
1858c2ecf20Sopenharmony_ci		pins |= (snd_soc_dapm_get_pin_status(dapm, "Microphone") <<
1868c2ecf20Sopenharmony_ci							AMS_DELTA_MICROPHONE);
1878c2ecf20Sopenharmony_ci	else
1888c2ecf20Sopenharmony_ci		pins = ((snd_soc_dapm_get_pin_status(dapm, "Microphone") <<
1898c2ecf20Sopenharmony_ci							AMS_DELTA_MICROPHONE) |
1908c2ecf20Sopenharmony_ci			(snd_soc_dapm_get_pin_status(dapm, "Speaker") <<
1918c2ecf20Sopenharmony_ci							AMS_DELTA_SPEAKER) |
1928c2ecf20Sopenharmony_ci			(ams_delta_audio_agc << AMS_DELTA_AGC));
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	for (mode = 0; mode < ARRAY_SIZE(ams_delta_audio_mode); mode++)
1958c2ecf20Sopenharmony_ci		if (pins == ams_delta_audio_mode_pins[mode])
1968c2ecf20Sopenharmony_ci			break;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	if (mode >= ARRAY_SIZE(ams_delta_audio_mode))
1998c2ecf20Sopenharmony_ci		return -EINVAL;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	ucontrol->value.enumerated.item[0] = mode;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	return 0;
2048c2ecf20Sopenharmony_ci}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_cistatic SOC_ENUM_SINGLE_EXT_DECL(ams_delta_audio_enum,
2078c2ecf20Sopenharmony_ci				      ams_delta_audio_mode);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new ams_delta_audio_controls[] = {
2108c2ecf20Sopenharmony_ci	SOC_ENUM_EXT("Audio Mode", ams_delta_audio_enum,
2118c2ecf20Sopenharmony_ci			ams_delta_get_audio_mode, ams_delta_set_audio_mode),
2128c2ecf20Sopenharmony_ci};
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci/* Hook switch */
2158c2ecf20Sopenharmony_cistatic struct snd_soc_jack ams_delta_hook_switch;
2168c2ecf20Sopenharmony_cistatic struct snd_soc_jack_gpio ams_delta_hook_switch_gpios[] = {
2178c2ecf20Sopenharmony_ci	{
2188c2ecf20Sopenharmony_ci		.name = "hook_switch",
2198c2ecf20Sopenharmony_ci		.report = SND_JACK_HEADSET,
2208c2ecf20Sopenharmony_ci		.invert = 1,
2218c2ecf20Sopenharmony_ci		.debounce_time = 150,
2228c2ecf20Sopenharmony_ci	}
2238c2ecf20Sopenharmony_ci};
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci/* After we are able to control the codec over the modem,
2268c2ecf20Sopenharmony_ci * the hook switch can be used for dynamic DAPM reconfiguration. */
2278c2ecf20Sopenharmony_cistatic struct snd_soc_jack_pin ams_delta_hook_switch_pins[] = {
2288c2ecf20Sopenharmony_ci	/* Handset */
2298c2ecf20Sopenharmony_ci	{
2308c2ecf20Sopenharmony_ci		.pin = "Mouthpiece",
2318c2ecf20Sopenharmony_ci		.mask = SND_JACK_MICROPHONE,
2328c2ecf20Sopenharmony_ci	},
2338c2ecf20Sopenharmony_ci	{
2348c2ecf20Sopenharmony_ci		.pin = "Earpiece",
2358c2ecf20Sopenharmony_ci		.mask = SND_JACK_HEADPHONE,
2368c2ecf20Sopenharmony_ci	},
2378c2ecf20Sopenharmony_ci	/* Handsfree */
2388c2ecf20Sopenharmony_ci	{
2398c2ecf20Sopenharmony_ci		.pin = "Microphone",
2408c2ecf20Sopenharmony_ci		.mask = SND_JACK_MICROPHONE,
2418c2ecf20Sopenharmony_ci		.invert = 1,
2428c2ecf20Sopenharmony_ci	},
2438c2ecf20Sopenharmony_ci	{
2448c2ecf20Sopenharmony_ci		.pin = "Speaker",
2458c2ecf20Sopenharmony_ci		.mask = SND_JACK_HEADPHONE,
2468c2ecf20Sopenharmony_ci		.invert = 1,
2478c2ecf20Sopenharmony_ci	},
2488c2ecf20Sopenharmony_ci};
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci/*
2528c2ecf20Sopenharmony_ci * Modem line discipline, required for making above controls functional.
2538c2ecf20Sopenharmony_ci * Activated from userspace with ldattach, possibly invoked from udev rule.
2548c2ecf20Sopenharmony_ci */
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci/* To actually apply any modem controlled configuration changes to the codec,
2578c2ecf20Sopenharmony_ci * we must connect codec DAI pins to the modem for a moment.  Be careful not
2588c2ecf20Sopenharmony_ci * to interfere with our digital mute function that shares the same hardware. */
2598c2ecf20Sopenharmony_cistatic struct timer_list cx81801_timer;
2608c2ecf20Sopenharmony_cistatic bool cx81801_cmd_pending;
2618c2ecf20Sopenharmony_cistatic bool ams_delta_muted;
2628c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(ams_delta_lock);
2638c2ecf20Sopenharmony_cistatic struct gpio_desc *gpiod_modem_codec;
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_cistatic void cx81801_timeout(struct timer_list *unused)
2668c2ecf20Sopenharmony_ci{
2678c2ecf20Sopenharmony_ci	int muted;
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	spin_lock(&ams_delta_lock);
2708c2ecf20Sopenharmony_ci	cx81801_cmd_pending = 0;
2718c2ecf20Sopenharmony_ci	muted = ams_delta_muted;
2728c2ecf20Sopenharmony_ci	spin_unlock(&ams_delta_lock);
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	/* Reconnect the codec DAI back from the modem to the CPU DAI
2758c2ecf20Sopenharmony_ci	 * only if digital mute still off */
2768c2ecf20Sopenharmony_ci	if (!muted)
2778c2ecf20Sopenharmony_ci		gpiod_set_value(gpiod_modem_codec, 0);
2788c2ecf20Sopenharmony_ci}
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci/* Line discipline .open() */
2818c2ecf20Sopenharmony_cistatic int cx81801_open(struct tty_struct *tty)
2828c2ecf20Sopenharmony_ci{
2838c2ecf20Sopenharmony_ci	int ret;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	if (!cx20442_codec)
2868c2ecf20Sopenharmony_ci		return -ENODEV;
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	/*
2898c2ecf20Sopenharmony_ci	 * Pass the codec structure pointer for use by other ldisc callbacks,
2908c2ecf20Sopenharmony_ci	 * both the card and the codec specific parts.
2918c2ecf20Sopenharmony_ci	 */
2928c2ecf20Sopenharmony_ci	tty->disc_data = cx20442_codec;
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	ret = v253_ops.open(tty);
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	if (ret < 0)
2978c2ecf20Sopenharmony_ci		tty->disc_data = NULL;
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci	return ret;
3008c2ecf20Sopenharmony_ci}
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci/* Line discipline .close() */
3038c2ecf20Sopenharmony_cistatic void cx81801_close(struct tty_struct *tty)
3048c2ecf20Sopenharmony_ci{
3058c2ecf20Sopenharmony_ci	struct snd_soc_component *component = tty->disc_data;
3068c2ecf20Sopenharmony_ci	struct snd_soc_dapm_context *dapm;
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	del_timer_sync(&cx81801_timer);
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ci	/* Prevent the hook switch from further changing the DAPM pins */
3118c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&ams_delta_hook_switch.pins);
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	if (!component)
3148c2ecf20Sopenharmony_ci		return;
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	v253_ops.close(tty);
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci	dapm = &component->card->dapm;
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	/* Revert back to default audio input/output constellation */
3218c2ecf20Sopenharmony_ci	snd_soc_dapm_mutex_lock(dapm);
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	snd_soc_dapm_disable_pin_unlocked(dapm, "Mouthpiece");
3248c2ecf20Sopenharmony_ci	snd_soc_dapm_enable_pin_unlocked(dapm, "Earpiece");
3258c2ecf20Sopenharmony_ci	snd_soc_dapm_enable_pin_unlocked(dapm, "Microphone");
3268c2ecf20Sopenharmony_ci	snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker");
3278c2ecf20Sopenharmony_ci	snd_soc_dapm_disable_pin_unlocked(dapm, "AGCIN");
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	snd_soc_dapm_sync_unlocked(dapm);
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	snd_soc_dapm_mutex_unlock(dapm);
3328c2ecf20Sopenharmony_ci}
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci/* Line discipline .hangup() */
3358c2ecf20Sopenharmony_cistatic int cx81801_hangup(struct tty_struct *tty)
3368c2ecf20Sopenharmony_ci{
3378c2ecf20Sopenharmony_ci	cx81801_close(tty);
3388c2ecf20Sopenharmony_ci	return 0;
3398c2ecf20Sopenharmony_ci}
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci/* Line discipline .receive_buf() */
3428c2ecf20Sopenharmony_cistatic void cx81801_receive(struct tty_struct *tty,
3438c2ecf20Sopenharmony_ci				const unsigned char *cp, char *fp, int count)
3448c2ecf20Sopenharmony_ci{
3458c2ecf20Sopenharmony_ci	struct snd_soc_component *component = tty->disc_data;
3468c2ecf20Sopenharmony_ci	const unsigned char *c;
3478c2ecf20Sopenharmony_ci	int apply, ret;
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ci	if (!component)
3508c2ecf20Sopenharmony_ci		return;
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	if (!component->card->pop_time) {
3538c2ecf20Sopenharmony_ci		/* First modem response, complete setup procedure */
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_ci		/* Initialize timer used for config pulse generation */
3568c2ecf20Sopenharmony_ci		timer_setup(&cx81801_timer, cx81801_timeout, 0);
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci		v253_ops.receive_buf(tty, cp, fp, count);
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci		/* Link hook switch to DAPM pins */
3618c2ecf20Sopenharmony_ci		ret = snd_soc_jack_add_pins(&ams_delta_hook_switch,
3628c2ecf20Sopenharmony_ci					ARRAY_SIZE(ams_delta_hook_switch_pins),
3638c2ecf20Sopenharmony_ci					ams_delta_hook_switch_pins);
3648c2ecf20Sopenharmony_ci		if (ret)
3658c2ecf20Sopenharmony_ci			dev_warn(component->dev,
3668c2ecf20Sopenharmony_ci				"Failed to link hook switch to DAPM pins, "
3678c2ecf20Sopenharmony_ci				"will continue with hook switch unlinked.\n");
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci		return;
3708c2ecf20Sopenharmony_ci	}
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ci	v253_ops.receive_buf(tty, cp, fp, count);
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_ci	for (c = &cp[count - 1]; c >= cp; c--) {
3758c2ecf20Sopenharmony_ci		if (*c != '\r')
3768c2ecf20Sopenharmony_ci			continue;
3778c2ecf20Sopenharmony_ci		/* Complete modem response received, apply config to codec */
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci		spin_lock_bh(&ams_delta_lock);
3808c2ecf20Sopenharmony_ci		mod_timer(&cx81801_timer, jiffies + msecs_to_jiffies(150));
3818c2ecf20Sopenharmony_ci		apply = !ams_delta_muted && !cx81801_cmd_pending;
3828c2ecf20Sopenharmony_ci		cx81801_cmd_pending = 1;
3838c2ecf20Sopenharmony_ci		spin_unlock_bh(&ams_delta_lock);
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci		/* Apply config pulse by connecting the codec to the modem
3868c2ecf20Sopenharmony_ci		 * if not already done */
3878c2ecf20Sopenharmony_ci		if (apply)
3888c2ecf20Sopenharmony_ci			gpiod_set_value(gpiod_modem_codec, 1);
3898c2ecf20Sopenharmony_ci		break;
3908c2ecf20Sopenharmony_ci	}
3918c2ecf20Sopenharmony_ci}
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci/* Line discipline .write_wakeup() */
3948c2ecf20Sopenharmony_cistatic void cx81801_wakeup(struct tty_struct *tty)
3958c2ecf20Sopenharmony_ci{
3968c2ecf20Sopenharmony_ci	v253_ops.write_wakeup(tty);
3978c2ecf20Sopenharmony_ci}
3988c2ecf20Sopenharmony_ci
3998c2ecf20Sopenharmony_cistatic struct tty_ldisc_ops cx81801_ops = {
4008c2ecf20Sopenharmony_ci	.magic = TTY_LDISC_MAGIC,
4018c2ecf20Sopenharmony_ci	.name = "cx81801",
4028c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
4038c2ecf20Sopenharmony_ci	.open = cx81801_open,
4048c2ecf20Sopenharmony_ci	.close = cx81801_close,
4058c2ecf20Sopenharmony_ci	.hangup = cx81801_hangup,
4068c2ecf20Sopenharmony_ci	.receive_buf = cx81801_receive,
4078c2ecf20Sopenharmony_ci	.write_wakeup = cx81801_wakeup,
4088c2ecf20Sopenharmony_ci};
4098c2ecf20Sopenharmony_ci
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_ci/*
4128c2ecf20Sopenharmony_ci * Even if not very useful, the sound card can still work without any of the
4138c2ecf20Sopenharmony_ci * above functonality activated.  You can still control its audio input/output
4148c2ecf20Sopenharmony_ci * constellation and speakerphone gain from userspace by issuing AT commands
4158c2ecf20Sopenharmony_ci * over the modem port.
4168c2ecf20Sopenharmony_ci */
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_cistatic struct snd_soc_ops ams_delta_ops;
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci/* Digital mute implemented using modem/CPU multiplexer.
4228c2ecf20Sopenharmony_ci * Shares hardware with codec config pulse generation */
4238c2ecf20Sopenharmony_cistatic bool ams_delta_muted = 1;
4248c2ecf20Sopenharmony_ci
4258c2ecf20Sopenharmony_cistatic int ams_delta_mute(struct snd_soc_dai *dai, int mute, int direction)
4268c2ecf20Sopenharmony_ci{
4278c2ecf20Sopenharmony_ci	int apply;
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	if (ams_delta_muted == mute)
4308c2ecf20Sopenharmony_ci		return 0;
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ci	spin_lock_bh(&ams_delta_lock);
4338c2ecf20Sopenharmony_ci	ams_delta_muted = mute;
4348c2ecf20Sopenharmony_ci	apply = !cx81801_cmd_pending;
4358c2ecf20Sopenharmony_ci	spin_unlock_bh(&ams_delta_lock);
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_ci	if (apply)
4388c2ecf20Sopenharmony_ci		gpiod_set_value(gpiod_modem_codec, !!mute);
4398c2ecf20Sopenharmony_ci	return 0;
4408c2ecf20Sopenharmony_ci}
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci/* Our codec DAI probably doesn't have its own .ops structure */
4438c2ecf20Sopenharmony_cistatic const struct snd_soc_dai_ops ams_delta_dai_ops = {
4448c2ecf20Sopenharmony_ci	.mute_stream = ams_delta_mute,
4458c2ecf20Sopenharmony_ci	.no_capture_mute = 1,
4468c2ecf20Sopenharmony_ci};
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ci/* Will be used if the codec ever has its own digital_mute function */
4498c2ecf20Sopenharmony_cistatic int ams_delta_startup(struct snd_pcm_substream *substream)
4508c2ecf20Sopenharmony_ci{
4518c2ecf20Sopenharmony_ci	return ams_delta_mute(NULL, 0, substream->stream);
4528c2ecf20Sopenharmony_ci}
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_cistatic void ams_delta_shutdown(struct snd_pcm_substream *substream)
4558c2ecf20Sopenharmony_ci{
4568c2ecf20Sopenharmony_ci	ams_delta_mute(NULL, 1, substream->stream);
4578c2ecf20Sopenharmony_ci}
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci
4608c2ecf20Sopenharmony_ci/*
4618c2ecf20Sopenharmony_ci * Card initialization
4628c2ecf20Sopenharmony_ci */
4638c2ecf20Sopenharmony_ci
4648c2ecf20Sopenharmony_cistatic int ams_delta_cx20442_init(struct snd_soc_pcm_runtime *rtd)
4658c2ecf20Sopenharmony_ci{
4668c2ecf20Sopenharmony_ci	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
4678c2ecf20Sopenharmony_ci	struct snd_soc_card *card = rtd->card;
4688c2ecf20Sopenharmony_ci	struct snd_soc_dapm_context *dapm = &card->dapm;
4698c2ecf20Sopenharmony_ci	int ret;
4708c2ecf20Sopenharmony_ci	/* Codec is ready, now add/activate board specific controls */
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_ci	/* Store a pointer to the codec structure for tty ldisc use */
4738c2ecf20Sopenharmony_ci	cx20442_codec = asoc_rtd_to_codec(rtd, 0)->component;
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci	/* Add hook switch - can be used to control the codec from userspace
4768c2ecf20Sopenharmony_ci	 * even if line discipline fails */
4778c2ecf20Sopenharmony_ci	ret = snd_soc_card_jack_new(card, "hook_switch", SND_JACK_HEADSET,
4788c2ecf20Sopenharmony_ci				    &ams_delta_hook_switch, NULL, 0);
4798c2ecf20Sopenharmony_ci	if (ret)
4808c2ecf20Sopenharmony_ci		dev_warn(card->dev,
4818c2ecf20Sopenharmony_ci				"Failed to allocate resources for hook switch, "
4828c2ecf20Sopenharmony_ci				"will continue without one.\n");
4838c2ecf20Sopenharmony_ci	else {
4848c2ecf20Sopenharmony_ci		ret = snd_soc_jack_add_gpiods(card->dev, &ams_delta_hook_switch,
4858c2ecf20Sopenharmony_ci					ARRAY_SIZE(ams_delta_hook_switch_gpios),
4868c2ecf20Sopenharmony_ci					ams_delta_hook_switch_gpios);
4878c2ecf20Sopenharmony_ci		if (ret)
4888c2ecf20Sopenharmony_ci			dev_warn(card->dev,
4898c2ecf20Sopenharmony_ci				"Failed to set up hook switch GPIO line, "
4908c2ecf20Sopenharmony_ci				"will continue with hook switch inactive.\n");
4918c2ecf20Sopenharmony_ci	}
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_ci	gpiod_modem_codec = devm_gpiod_get(card->dev, "modem_codec",
4948c2ecf20Sopenharmony_ci					   GPIOD_OUT_HIGH);
4958c2ecf20Sopenharmony_ci	if (IS_ERR(gpiod_modem_codec)) {
4968c2ecf20Sopenharmony_ci		dev_warn(card->dev, "Failed to obtain modem_codec GPIO\n");
4978c2ecf20Sopenharmony_ci		return 0;
4988c2ecf20Sopenharmony_ci	}
4998c2ecf20Sopenharmony_ci
5008c2ecf20Sopenharmony_ci	/* Set up digital mute if not provided by the codec */
5018c2ecf20Sopenharmony_ci	if (!codec_dai->driver->ops) {
5028c2ecf20Sopenharmony_ci		codec_dai->driver->ops = &ams_delta_dai_ops;
5038c2ecf20Sopenharmony_ci	} else {
5048c2ecf20Sopenharmony_ci		ams_delta_ops.startup = ams_delta_startup;
5058c2ecf20Sopenharmony_ci		ams_delta_ops.shutdown = ams_delta_shutdown;
5068c2ecf20Sopenharmony_ci	}
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci	/* Register optional line discipline for over the modem control */
5098c2ecf20Sopenharmony_ci	ret = tty_register_ldisc(N_V253, &cx81801_ops);
5108c2ecf20Sopenharmony_ci	if (ret) {
5118c2ecf20Sopenharmony_ci		dev_warn(card->dev,
5128c2ecf20Sopenharmony_ci				"Failed to register line discipline, "
5138c2ecf20Sopenharmony_ci				"will continue without any controls.\n");
5148c2ecf20Sopenharmony_ci		return 0;
5158c2ecf20Sopenharmony_ci	}
5168c2ecf20Sopenharmony_ci
5178c2ecf20Sopenharmony_ci	/* Set up initial pin constellation */
5188c2ecf20Sopenharmony_ci	snd_soc_dapm_disable_pin(dapm, "Mouthpiece");
5198c2ecf20Sopenharmony_ci	snd_soc_dapm_disable_pin(dapm, "Speaker");
5208c2ecf20Sopenharmony_ci	snd_soc_dapm_disable_pin(dapm, "AGCIN");
5218c2ecf20Sopenharmony_ci	snd_soc_dapm_disable_pin(dapm, "AGCOUT");
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_ci	return 0;
5248c2ecf20Sopenharmony_ci}
5258c2ecf20Sopenharmony_ci
5268c2ecf20Sopenharmony_ci/* DAI glue - connects codec <--> CPU */
5278c2ecf20Sopenharmony_ciSND_SOC_DAILINK_DEFS(cx20442,
5288c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.1")),
5298c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_CODEC("cx20442-codec", "cx20442-voice")),
5308c2ecf20Sopenharmony_ci	DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.1")));
5318c2ecf20Sopenharmony_ci
5328c2ecf20Sopenharmony_cistatic struct snd_soc_dai_link ams_delta_dai_link = {
5338c2ecf20Sopenharmony_ci	.name = "CX20442",
5348c2ecf20Sopenharmony_ci	.stream_name = "CX20442",
5358c2ecf20Sopenharmony_ci	.init = ams_delta_cx20442_init,
5368c2ecf20Sopenharmony_ci	.ops = &ams_delta_ops,
5378c2ecf20Sopenharmony_ci	.dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF |
5388c2ecf20Sopenharmony_ci		   SND_SOC_DAIFMT_CBM_CFM,
5398c2ecf20Sopenharmony_ci	SND_SOC_DAILINK_REG(cx20442),
5408c2ecf20Sopenharmony_ci};
5418c2ecf20Sopenharmony_ci
5428c2ecf20Sopenharmony_ci/* Audio card driver */
5438c2ecf20Sopenharmony_cistatic struct snd_soc_card ams_delta_audio_card = {
5448c2ecf20Sopenharmony_ci	.name = "AMS_DELTA",
5458c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
5468c2ecf20Sopenharmony_ci	.dai_link = &ams_delta_dai_link,
5478c2ecf20Sopenharmony_ci	.num_links = 1,
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	.controls = ams_delta_audio_controls,
5508c2ecf20Sopenharmony_ci	.num_controls = ARRAY_SIZE(ams_delta_audio_controls),
5518c2ecf20Sopenharmony_ci	.dapm_widgets = ams_delta_dapm_widgets,
5528c2ecf20Sopenharmony_ci	.num_dapm_widgets = ARRAY_SIZE(ams_delta_dapm_widgets),
5538c2ecf20Sopenharmony_ci	.dapm_routes = ams_delta_audio_map,
5548c2ecf20Sopenharmony_ci	.num_dapm_routes = ARRAY_SIZE(ams_delta_audio_map),
5558c2ecf20Sopenharmony_ci};
5568c2ecf20Sopenharmony_ci
5578c2ecf20Sopenharmony_ci/* Module init/exit */
5588c2ecf20Sopenharmony_cistatic int ams_delta_probe(struct platform_device *pdev)
5598c2ecf20Sopenharmony_ci{
5608c2ecf20Sopenharmony_ci	struct snd_soc_card *card = &ams_delta_audio_card;
5618c2ecf20Sopenharmony_ci	int ret;
5628c2ecf20Sopenharmony_ci
5638c2ecf20Sopenharmony_ci	card->dev = &pdev->dev;
5648c2ecf20Sopenharmony_ci
5658c2ecf20Sopenharmony_ci	handset_mute = devm_gpiod_get(card->dev, "handset_mute",
5668c2ecf20Sopenharmony_ci				      GPIOD_OUT_HIGH);
5678c2ecf20Sopenharmony_ci	if (IS_ERR(handset_mute))
5688c2ecf20Sopenharmony_ci		return PTR_ERR(handset_mute);
5698c2ecf20Sopenharmony_ci
5708c2ecf20Sopenharmony_ci	handsfree_mute = devm_gpiod_get(card->dev, "handsfree_mute",
5718c2ecf20Sopenharmony_ci					GPIOD_OUT_HIGH);
5728c2ecf20Sopenharmony_ci	if (IS_ERR(handsfree_mute))
5738c2ecf20Sopenharmony_ci		return PTR_ERR(handsfree_mute);
5748c2ecf20Sopenharmony_ci
5758c2ecf20Sopenharmony_ci	ret = snd_soc_register_card(card);
5768c2ecf20Sopenharmony_ci	if (ret) {
5778c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
5788c2ecf20Sopenharmony_ci		card->dev = NULL;
5798c2ecf20Sopenharmony_ci		return ret;
5808c2ecf20Sopenharmony_ci	}
5818c2ecf20Sopenharmony_ci	return 0;
5828c2ecf20Sopenharmony_ci}
5838c2ecf20Sopenharmony_ci
5848c2ecf20Sopenharmony_cistatic int ams_delta_remove(struct platform_device *pdev)
5858c2ecf20Sopenharmony_ci{
5868c2ecf20Sopenharmony_ci	struct snd_soc_card *card = platform_get_drvdata(pdev);
5878c2ecf20Sopenharmony_ci
5888c2ecf20Sopenharmony_ci	if (tty_unregister_ldisc(N_V253) != 0)
5898c2ecf20Sopenharmony_ci		dev_warn(&pdev->dev,
5908c2ecf20Sopenharmony_ci			"failed to unregister V253 line discipline\n");
5918c2ecf20Sopenharmony_ci
5928c2ecf20Sopenharmony_ci	snd_soc_unregister_card(card);
5938c2ecf20Sopenharmony_ci	card->dev = NULL;
5948c2ecf20Sopenharmony_ci	return 0;
5958c2ecf20Sopenharmony_ci}
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_ci#define DRV_NAME "ams-delta-audio"
5988c2ecf20Sopenharmony_ci
5998c2ecf20Sopenharmony_cistatic struct platform_driver ams_delta_driver = {
6008c2ecf20Sopenharmony_ci	.driver = {
6018c2ecf20Sopenharmony_ci		.name = DRV_NAME,
6028c2ecf20Sopenharmony_ci	},
6038c2ecf20Sopenharmony_ci	.probe = ams_delta_probe,
6048c2ecf20Sopenharmony_ci	.remove = ams_delta_remove,
6058c2ecf20Sopenharmony_ci};
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_cimodule_platform_driver(ams_delta_driver);
6088c2ecf20Sopenharmony_ci
6098c2ecf20Sopenharmony_ciMODULE_AUTHOR("Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>");
6108c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ALSA SoC driver for Amstrad E3 (Delta) videophone");
6118c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
6128c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME);
613