brintos

brintos / linux-shallow public Read only

0
0
Text · 16.0 KiB · 94645f2 Raw
607 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * ams-delta.c  --  SoC audio for Amstrad E3 (Delta) videophone4 *5 * Copyright (C) 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>6 *7 * Initially based on sound/soc/omap/osk5912.x8 * Copyright (C) 2008 Mistral Solutions9 */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 <linux/platform_data/asoc-ti-mcbsp.h>20 21#include "omap-mcbsp.h"22#include "../codecs/cx20442.h"23 24static struct gpio_desc *handset_mute;25static struct gpio_desc *handsfree_mute;26 27static int ams_delta_event_handset(struct snd_soc_dapm_widget *w,28				   struct snd_kcontrol *k, int event)29{30	gpiod_set_value_cansleep(handset_mute, !SND_SOC_DAPM_EVENT_ON(event));31	return 0;32}33 34static int ams_delta_event_handsfree(struct snd_soc_dapm_widget *w,35				     struct snd_kcontrol *k, int event)36{37	gpiod_set_value_cansleep(handsfree_mute, !SND_SOC_DAPM_EVENT_ON(event));38	return 0;39}40 41/* Board specific DAPM widgets */42static const struct snd_soc_dapm_widget ams_delta_dapm_widgets[] = {43	/* Handset */44	SND_SOC_DAPM_MIC("Mouthpiece", NULL),45	SND_SOC_DAPM_HP("Earpiece", ams_delta_event_handset),46	/* Handsfree/Speakerphone */47	SND_SOC_DAPM_MIC("Microphone", NULL),48	SND_SOC_DAPM_SPK("Speaker", ams_delta_event_handsfree),49};50 51/* How they are connected to codec pins */52static const struct snd_soc_dapm_route ams_delta_audio_map[] = {53	{"TELIN", NULL, "Mouthpiece"},54	{"Earpiece", NULL, "TELOUT"},55 56	{"MIC", NULL, "Microphone"},57	{"Speaker", NULL, "SPKOUT"},58};59 60/*61 * Controls, functional after the modem line discipline is activated.62 */63 64/* Virtual switch: audio input/output constellations */65static const char *ams_delta_audio_mode[] =66	{"Mixed", "Handset", "Handsfree", "Speakerphone"};67 68/* Selection <-> pin translation */69#define AMS_DELTA_MOUTHPIECE	070#define AMS_DELTA_EARPIECE	171#define AMS_DELTA_MICROPHONE	272#define AMS_DELTA_SPEAKER	373#define AMS_DELTA_AGC		474 75#define AMS_DELTA_MIXED		((1 << AMS_DELTA_EARPIECE) | \76						(1 << AMS_DELTA_MICROPHONE))77#define AMS_DELTA_HANDSET	((1 << AMS_DELTA_MOUTHPIECE) | \78						(1 << AMS_DELTA_EARPIECE))79#define AMS_DELTA_HANDSFREE	((1 << AMS_DELTA_MICROPHONE) | \80						(1 << AMS_DELTA_SPEAKER))81#define AMS_DELTA_SPEAKERPHONE	(AMS_DELTA_HANDSFREE | (1 << AMS_DELTA_AGC))82 83static const unsigned short ams_delta_audio_mode_pins[] = {84	AMS_DELTA_MIXED,85	AMS_DELTA_HANDSET,86	AMS_DELTA_HANDSFREE,87	AMS_DELTA_SPEAKERPHONE,88};89 90static unsigned short ams_delta_audio_agc;91 92/*93 * Used for passing a codec structure pointer94 * from the board initialization code to the tty line discipline.95 */96static struct snd_soc_component *cx20442_codec;97 98static int ams_delta_set_audio_mode(struct snd_kcontrol *kcontrol,99					struct snd_ctl_elem_value *ucontrol)100{101	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);102	struct snd_soc_dapm_context *dapm = &card->dapm;103	struct soc_enum *control = (struct soc_enum *)kcontrol->private_value;104	unsigned short pins;105	int pin, changed = 0;106 107	/* Refuse any mode changes if we are not able to control the codec. */108	if (!cx20442_codec->card->pop_time)109		return -EUNATCH;110 111	if (ucontrol->value.enumerated.item[0] >= control->items)112		return -EINVAL;113 114	snd_soc_dapm_mutex_lock(dapm);115 116	/* Translate selection to bitmap */117	pins = ams_delta_audio_mode_pins[ucontrol->value.enumerated.item[0]];118 119	/* Setup pins after corresponding bits if changed */120	pin = !!(pins & (1 << AMS_DELTA_MOUTHPIECE));121 122	if (pin != snd_soc_dapm_get_pin_status(dapm, "Mouthpiece")) {123		changed = 1;124		if (pin)125			snd_soc_dapm_enable_pin_unlocked(dapm, "Mouthpiece");126		else127			snd_soc_dapm_disable_pin_unlocked(dapm, "Mouthpiece");128	}129	pin = !!(pins & (1 << AMS_DELTA_EARPIECE));130	if (pin != snd_soc_dapm_get_pin_status(dapm, "Earpiece")) {131		changed = 1;132		if (pin)133			snd_soc_dapm_enable_pin_unlocked(dapm, "Earpiece");134		else135			snd_soc_dapm_disable_pin_unlocked(dapm, "Earpiece");136	}137	pin = !!(pins & (1 << AMS_DELTA_MICROPHONE));138	if (pin != snd_soc_dapm_get_pin_status(dapm, "Microphone")) {139		changed = 1;140		if (pin)141			snd_soc_dapm_enable_pin_unlocked(dapm, "Microphone");142		else143			snd_soc_dapm_disable_pin_unlocked(dapm, "Microphone");144	}145	pin = !!(pins & (1 << AMS_DELTA_SPEAKER));146	if (pin != snd_soc_dapm_get_pin_status(dapm, "Speaker")) {147		changed = 1;148		if (pin)149			snd_soc_dapm_enable_pin_unlocked(dapm, "Speaker");150		else151			snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker");152	}153	pin = !!(pins & (1 << AMS_DELTA_AGC));154	if (pin != ams_delta_audio_agc) {155		ams_delta_audio_agc = pin;156		changed = 1;157		if (pin)158			snd_soc_dapm_enable_pin_unlocked(dapm, "AGCIN");159		else160			snd_soc_dapm_disable_pin_unlocked(dapm, "AGCIN");161	}162 163	if (changed)164		snd_soc_dapm_sync_unlocked(dapm);165 166	snd_soc_dapm_mutex_unlock(dapm);167 168	return changed;169}170 171static int ams_delta_get_audio_mode(struct snd_kcontrol *kcontrol,172					struct snd_ctl_elem_value *ucontrol)173{174	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);175	struct snd_soc_dapm_context *dapm = &card->dapm;176	unsigned short pins, mode;177 178	pins = ((snd_soc_dapm_get_pin_status(dapm, "Mouthpiece") <<179							AMS_DELTA_MOUTHPIECE) |180			(snd_soc_dapm_get_pin_status(dapm, "Earpiece") <<181							AMS_DELTA_EARPIECE));182	if (pins)183		pins |= (snd_soc_dapm_get_pin_status(dapm, "Microphone") <<184							AMS_DELTA_MICROPHONE);185	else186		pins = ((snd_soc_dapm_get_pin_status(dapm, "Microphone") <<187							AMS_DELTA_MICROPHONE) |188			(snd_soc_dapm_get_pin_status(dapm, "Speaker") <<189							AMS_DELTA_SPEAKER) |190			(ams_delta_audio_agc << AMS_DELTA_AGC));191 192	for (mode = 0; mode < ARRAY_SIZE(ams_delta_audio_mode); mode++)193		if (pins == ams_delta_audio_mode_pins[mode])194			break;195 196	if (mode >= ARRAY_SIZE(ams_delta_audio_mode))197		return -EINVAL;198 199	ucontrol->value.enumerated.item[0] = mode;200 201	return 0;202}203 204static SOC_ENUM_SINGLE_EXT_DECL(ams_delta_audio_enum,205				      ams_delta_audio_mode);206 207static const struct snd_kcontrol_new ams_delta_audio_controls[] = {208	SOC_ENUM_EXT("Audio Mode", ams_delta_audio_enum,209			ams_delta_get_audio_mode, ams_delta_set_audio_mode),210};211 212/* Hook switch */213static struct snd_soc_jack ams_delta_hook_switch;214static struct snd_soc_jack_gpio ams_delta_hook_switch_gpios[] = {215	{216		.name = "hook_switch",217		.report = SND_JACK_HEADSET,218		.invert = 1,219		.debounce_time = 150,220	}221};222 223/* After we are able to control the codec over the modem,224 * the hook switch can be used for dynamic DAPM reconfiguration. */225static struct snd_soc_jack_pin ams_delta_hook_switch_pins[] = {226	/* Handset */227	{228		.pin = "Mouthpiece",229		.mask = SND_JACK_MICROPHONE,230	},231	{232		.pin = "Earpiece",233		.mask = SND_JACK_HEADPHONE,234	},235	/* Handsfree */236	{237		.pin = "Microphone",238		.mask = SND_JACK_MICROPHONE,239		.invert = 1,240	},241	{242		.pin = "Speaker",243		.mask = SND_JACK_HEADPHONE,244		.invert = 1,245	},246};247 248 249/*250 * Modem line discipline, required for making above controls functional.251 * Activated from userspace with ldattach, possibly invoked from udev rule.252 */253 254/* To actually apply any modem controlled configuration changes to the codec,255 * we must connect codec DAI pins to the modem for a moment.  Be careful not256 * to interfere with our digital mute function that shares the same hardware. */257static struct timer_list cx81801_timer;258static bool cx81801_cmd_pending;259static bool ams_delta_muted;260static DEFINE_SPINLOCK(ams_delta_lock);261static struct gpio_desc *gpiod_modem_codec;262 263static void cx81801_timeout(struct timer_list *unused)264{265	int muted;266 267	spin_lock(&ams_delta_lock);268	cx81801_cmd_pending = 0;269	muted = ams_delta_muted;270	spin_unlock(&ams_delta_lock);271 272	/* Reconnect the codec DAI back from the modem to the CPU DAI273	 * only if digital mute still off */274	if (!muted)275		gpiod_set_value(gpiod_modem_codec, 0);276}277 278/* Line discipline .open() */279static int cx81801_open(struct tty_struct *tty)280{281	int ret;282 283	if (!cx20442_codec)284		return -ENODEV;285 286	/*287	 * Pass the codec structure pointer for use by other ldisc callbacks,288	 * both the card and the codec specific parts.289	 */290	tty->disc_data = cx20442_codec;291 292	ret = v253_ops.open(tty);293 294	if (ret < 0)295		tty->disc_data = NULL;296 297	return ret;298}299 300/* Line discipline .close() */301static void cx81801_close(struct tty_struct *tty)302{303	struct snd_soc_component *component = tty->disc_data;304	struct snd_soc_dapm_context *dapm;305 306	del_timer_sync(&cx81801_timer);307 308	/* Prevent the hook switch from further changing the DAPM pins */309	INIT_LIST_HEAD(&ams_delta_hook_switch.pins);310 311	if (!component)312		return;313 314	v253_ops.close(tty);315 316	dapm = &component->card->dapm;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 u8 *cp, const u8 *fp,340			    size_t 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 modem383		 * 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 the410 * above functionality activated.  You can still control its audio input/output411 * constellation and speakerphone gain from userspace by issuing AT commands412 * 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 initialization459 */460 461static int ams_delta_cx20442_init(struct snd_soc_pcm_runtime *rtd)462{463	struct snd_soc_dai *codec_dai = snd_soc_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 = snd_soc_rtd_to_codec(rtd, 0)->component;471 472	/* Add hook switch - can be used to control the codec from userspace473	 * 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 void 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}590 591#define DRV_NAME "ams-delta-audio"592 593static struct platform_driver ams_delta_driver = {594	.driver = {595		.name = DRV_NAME,596	},597	.probe = ams_delta_probe,598	.remove = ams_delta_remove,599};600 601module_platform_driver(ams_delta_driver);602 603MODULE_AUTHOR("Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>");604MODULE_DESCRIPTION("ALSA SoC driver for Amstrad E3 (Delta) videophone");605MODULE_LICENSE("GPL");606MODULE_ALIAS("platform:" DRV_NAME);607