linux/sound/soc/ti/ams-delta.c
<<
>>
Prefs
   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 */
  27static 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 */
  37static 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 */
  50static 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
  68static 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
  75static 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 */
  81static struct snd_soc_component *cx20442_codec;
  82
  83static 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
 156static 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
 189static SOC_ENUM_SINGLE_EXT_DECL(ams_delta_audio_enum,
 190                                      ams_delta_audio_mode);
 191
 192static 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 */
 198static struct snd_soc_jack ams_delta_hook_switch;
 199static 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. */
 210static 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. */
 242static struct timer_list cx81801_timer;
 243static bool cx81801_cmd_pending;
 244static bool ams_delta_muted;
 245static DEFINE_SPINLOCK(ams_delta_lock);
 246static struct gpio_desc *gpiod_modem_codec;
 247
 248static 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() */
 264static 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() */
 286static 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() */
 316static int cx81801_hangup(struct tty_struct *tty)
 317{
 318        cx81801_close(tty);
 319        return 0;
 320}
 321
 322/* Line discipline .receive_buf() */
 323static 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() */
 375static void cx81801_wakeup(struct tty_struct *tty)
 376{
 377        v253_ops.write_wakeup(tty);
 378}
 379
 380static 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
 399static 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 */
 404static bool ams_delta_muted = 1;
 405
 406static 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 */
 424static 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 */
 429static int ams_delta_startup(struct snd_pcm_substream *substream)
 430{
 431        return ams_delta_digital_mute(NULL, 0);
 432}
 433
 434static 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
 444static 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 */
 507SND_SOC_DAILINK_DEFS(cx20442,
 508        DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.1")),
 509        DAILINK_COMP_ARRAY(COMP_CODEC("cx20442-codec", "cx20442-voice")),
 510        DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.1")));
 511
 512static struct snd_soc_dai_link ams_delta_dai_link = {
 513        .name = "CX20442",
 514        .stream_name = "CX20442",
 515        .init = ams_delta_cx20442_init,
 516        .ops = &ams_delta_ops,
 517        .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF |
 518                   SND_SOC_DAIFMT_CBM_CFM,
 519        SND_SOC_DAILINK_REG(cx20442),
 520};
 521
 522/* Audio card driver */
 523static struct snd_soc_card ams_delta_audio_card = {
 524        .name = "AMS_DELTA",
 525        .owner = THIS_MODULE,
 526        .dai_link = &ams_delta_dai_link,
 527        .num_links = 1,
 528
 529        .controls = ams_delta_audio_controls,
 530        .num_controls = ARRAY_SIZE(ams_delta_audio_controls),
 531        .dapm_widgets = ams_delta_dapm_widgets,
 532        .num_dapm_widgets = ARRAY_SIZE(ams_delta_dapm_widgets),
 533        .dapm_routes = ams_delta_audio_map,
 534        .num_dapm_routes = ARRAY_SIZE(ams_delta_audio_map),
 535};
 536
 537/* Module init/exit */
 538static int ams_delta_probe(struct platform_device *pdev)
 539{
 540        struct snd_soc_card *card = &ams_delta_audio_card;
 541        int ret;
 542
 543        card->dev = &pdev->dev;
 544
 545        ret = snd_soc_register_card(card);
 546        if (ret) {
 547                dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
 548                card->dev = NULL;
 549                return ret;
 550        }
 551        return 0;
 552}
 553
 554static int ams_delta_remove(struct platform_device *pdev)
 555{
 556        struct snd_soc_card *card = platform_get_drvdata(pdev);
 557
 558        if (tty_unregister_ldisc(N_V253) != 0)
 559                dev_warn(&pdev->dev,
 560                        "failed to unregister V253 line discipline\n");
 561
 562        snd_soc_unregister_card(card);
 563        card->dev = NULL;
 564        return 0;
 565}
 566
 567#define DRV_NAME "ams-delta-audio"
 568
 569static struct platform_driver ams_delta_driver = {
 570        .driver = {
 571                .name = DRV_NAME,
 572        },
 573        .probe = ams_delta_probe,
 574        .remove = ams_delta_remove,
 575};
 576
 577module_platform_driver(ams_delta_driver);
 578
 579MODULE_AUTHOR("Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>");
 580MODULE_DESCRIPTION("ALSA SoC driver for Amstrad E3 (Delta) videophone");
 581MODULE_LICENSE("GPL");
 582MODULE_ALIAS("platform:" DRV_NAME);
 583