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