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