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