root/sound/soc/ti/ams-delta.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ams_delta_event_handset
  2. ams_delta_event_handsfree
  3. ams_delta_set_audio_mode
  4. ams_delta_get_audio_mode
  5. cx81801_timeout
  6. cx81801_open
  7. cx81801_close
  8. cx81801_hangup
  9. cx81801_receive
  10. cx81801_wakeup
  11. ams_delta_digital_mute
  12. ams_delta_startup
  13. ams_delta_shutdown
  14. ams_delta_cx20442_init
  15. ams_delta_probe
  16. ams_delta_remove

   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 static struct gpio_desc *handset_mute;
  27 static struct gpio_desc *handsfree_mute;
  28 
  29 static int ams_delta_event_handset(struct snd_soc_dapm_widget *w,
  30                                    struct snd_kcontrol *k, int event)
  31 {
  32         gpiod_set_value_cansleep(handset_mute, !SND_SOC_DAPM_EVENT_ON(event));
  33         return 0;
  34 }
  35 
  36 static int ams_delta_event_handsfree(struct snd_soc_dapm_widget *w,
  37                                      struct snd_kcontrol *k, int event)
  38 {
  39         gpiod_set_value_cansleep(handsfree_mute, !SND_SOC_DAPM_EVENT_ON(event));
  40         return 0;
  41 }
  42 
  43 /* Board specific DAPM widgets */
  44 static const struct snd_soc_dapm_widget ams_delta_dapm_widgets[] = {
  45         /* Handset */
  46         SND_SOC_DAPM_MIC("Mouthpiece", NULL),
  47         SND_SOC_DAPM_HP("Earpiece", ams_delta_event_handset),
  48         /* Handsfree/Speakerphone */
  49         SND_SOC_DAPM_MIC("Microphone", NULL),
  50         SND_SOC_DAPM_SPK("Speaker", ams_delta_event_handsfree),
  51 };
  52 
  53 /* How they are connected to codec pins */
  54 static const struct snd_soc_dapm_route ams_delta_audio_map[] = {
  55         {"TELIN", NULL, "Mouthpiece"},
  56         {"Earpiece", NULL, "TELOUT"},
  57 
  58         {"MIC", NULL, "Microphone"},
  59         {"Speaker", NULL, "SPKOUT"},
  60 };
  61 
  62 /*
  63  * Controls, functional after the modem line discipline is activated.
  64  */
  65 
  66 /* Virtual switch: audio input/output constellations */
  67 static const char *ams_delta_audio_mode[] =
  68         {"Mixed", "Handset", "Handsfree", "Speakerphone"};
  69 
  70 /* Selection <-> pin translation */
  71 #define AMS_DELTA_MOUTHPIECE    0
  72 #define AMS_DELTA_EARPIECE      1
  73 #define AMS_DELTA_MICROPHONE    2
  74 #define AMS_DELTA_SPEAKER       3
  75 #define AMS_DELTA_AGC           4
  76 
  77 #define AMS_DELTA_MIXED         ((1 << AMS_DELTA_EARPIECE) | \
  78                                                 (1 << AMS_DELTA_MICROPHONE))
  79 #define AMS_DELTA_HANDSET       ((1 << AMS_DELTA_MOUTHPIECE) | \
  80                                                 (1 << AMS_DELTA_EARPIECE))
  81 #define AMS_DELTA_HANDSFREE     ((1 << AMS_DELTA_MICROPHONE) | \
  82                                                 (1 << AMS_DELTA_SPEAKER))
  83 #define AMS_DELTA_SPEAKERPHONE  (AMS_DELTA_HANDSFREE | (1 << AMS_DELTA_AGC))
  84 
  85 static const unsigned short ams_delta_audio_mode_pins[] = {
  86         AMS_DELTA_MIXED,
  87         AMS_DELTA_HANDSET,
  88         AMS_DELTA_HANDSFREE,
  89         AMS_DELTA_SPEAKERPHONE,
  90 };
  91 
  92 static unsigned short ams_delta_audio_agc;
  93 
  94 /*
  95  * Used for passing a codec structure pointer
  96  * from the board initialization code to the tty line discipline.
  97  */
  98 static struct snd_soc_component *cx20442_codec;
  99 
 100 static int ams_delta_set_audio_mode(struct snd_kcontrol *kcontrol,
 101                                         struct snd_ctl_elem_value *ucontrol)
 102 {
 103         struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
 104         struct snd_soc_dapm_context *dapm = &card->dapm;
 105         struct soc_enum *control = (struct soc_enum *)kcontrol->private_value;
 106         unsigned short pins;
 107         int pin, changed = 0;
 108 
 109         /* Refuse any mode changes if we are not able to control the codec. */
 110         if (!cx20442_codec->card->pop_time)
 111                 return -EUNATCH;
 112 
 113         if (ucontrol->value.enumerated.item[0] >= control->items)
 114                 return -EINVAL;
 115 
 116         snd_soc_dapm_mutex_lock(dapm);
 117 
 118         /* Translate selection to bitmap */
 119         pins = ams_delta_audio_mode_pins[ucontrol->value.enumerated.item[0]];
 120 
 121         /* Setup pins after corresponding bits if changed */
 122         pin = !!(pins & (1 << AMS_DELTA_MOUTHPIECE));
 123 
 124         if (pin != snd_soc_dapm_get_pin_status(dapm, "Mouthpiece")) {
 125                 changed = 1;
 126                 if (pin)
 127                         snd_soc_dapm_enable_pin_unlocked(dapm, "Mouthpiece");
 128                 else
 129                         snd_soc_dapm_disable_pin_unlocked(dapm, "Mouthpiece");
 130         }
 131         pin = !!(pins & (1 << AMS_DELTA_EARPIECE));
 132         if (pin != snd_soc_dapm_get_pin_status(dapm, "Earpiece")) {
 133                 changed = 1;
 134                 if (pin)
 135                         snd_soc_dapm_enable_pin_unlocked(dapm, "Earpiece");
 136                 else
 137                         snd_soc_dapm_disable_pin_unlocked(dapm, "Earpiece");
 138         }
 139         pin = !!(pins & (1 << AMS_DELTA_MICROPHONE));
 140         if (pin != snd_soc_dapm_get_pin_status(dapm, "Microphone")) {
 141                 changed = 1;
 142                 if (pin)
 143                         snd_soc_dapm_enable_pin_unlocked(dapm, "Microphone");
 144                 else
 145                         snd_soc_dapm_disable_pin_unlocked(dapm, "Microphone");
 146         }
 147         pin = !!(pins & (1 << AMS_DELTA_SPEAKER));
 148         if (pin != snd_soc_dapm_get_pin_status(dapm, "Speaker")) {
 149                 changed = 1;
 150                 if (pin)
 151                         snd_soc_dapm_enable_pin_unlocked(dapm, "Speaker");
 152                 else
 153                         snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker");
 154         }
 155         pin = !!(pins & (1 << AMS_DELTA_AGC));
 156         if (pin != ams_delta_audio_agc) {
 157                 ams_delta_audio_agc = pin;
 158                 changed = 1;
 159                 if (pin)
 160                         snd_soc_dapm_enable_pin_unlocked(dapm, "AGCIN");
 161                 else
 162                         snd_soc_dapm_disable_pin_unlocked(dapm, "AGCIN");
 163         }
 164 
 165         if (changed)
 166                 snd_soc_dapm_sync_unlocked(dapm);
 167 
 168         snd_soc_dapm_mutex_unlock(dapm);
 169 
 170         return changed;
 171 }
 172 
 173 static int ams_delta_get_audio_mode(struct snd_kcontrol *kcontrol,
 174                                         struct snd_ctl_elem_value *ucontrol)
 175 {
 176         struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
 177         struct snd_soc_dapm_context *dapm = &card->dapm;
 178         unsigned short pins, mode;
 179 
 180         pins = ((snd_soc_dapm_get_pin_status(dapm, "Mouthpiece") <<
 181                                                         AMS_DELTA_MOUTHPIECE) |
 182                         (snd_soc_dapm_get_pin_status(dapm, "Earpiece") <<
 183                                                         AMS_DELTA_EARPIECE));
 184         if (pins)
 185                 pins |= (snd_soc_dapm_get_pin_status(dapm, "Microphone") <<
 186                                                         AMS_DELTA_MICROPHONE);
 187         else
 188                 pins = ((snd_soc_dapm_get_pin_status(dapm, "Microphone") <<
 189                                                         AMS_DELTA_MICROPHONE) |
 190                         (snd_soc_dapm_get_pin_status(dapm, "Speaker") <<
 191                                                         AMS_DELTA_SPEAKER) |
 192                         (ams_delta_audio_agc << AMS_DELTA_AGC));
 193 
 194         for (mode = 0; mode < ARRAY_SIZE(ams_delta_audio_mode); mode++)
 195                 if (pins == ams_delta_audio_mode_pins[mode])
 196                         break;
 197 
 198         if (mode >= ARRAY_SIZE(ams_delta_audio_mode))
 199                 return -EINVAL;
 200 
 201         ucontrol->value.enumerated.item[0] = mode;
 202 
 203         return 0;
 204 }
 205 
 206 static SOC_ENUM_SINGLE_EXT_DECL(ams_delta_audio_enum,
 207                                       ams_delta_audio_mode);
 208 
 209 static const struct snd_kcontrol_new ams_delta_audio_controls[] = {
 210         SOC_ENUM_EXT("Audio Mode", ams_delta_audio_enum,
 211                         ams_delta_get_audio_mode, ams_delta_set_audio_mode),
 212 };
 213 
 214 /* Hook switch */
 215 static struct snd_soc_jack ams_delta_hook_switch;
 216 static struct snd_soc_jack_gpio ams_delta_hook_switch_gpios[] = {
 217         {
 218                 .name = "hook_switch",
 219                 .report = SND_JACK_HEADSET,
 220                 .invert = 1,
 221                 .debounce_time = 150,
 222         }
 223 };
 224 
 225 /* After we are able to control the codec over the modem,
 226  * the hook switch can be used for dynamic DAPM reconfiguration. */
 227 static struct snd_soc_jack_pin ams_delta_hook_switch_pins[] = {
 228         /* Handset */
 229         {
 230                 .pin = "Mouthpiece",
 231                 .mask = SND_JACK_MICROPHONE,
 232         },
 233         {
 234                 .pin = "Earpiece",
 235                 .mask = SND_JACK_HEADPHONE,
 236         },
 237         /* Handsfree */
 238         {
 239                 .pin = "Microphone",
 240                 .mask = SND_JACK_MICROPHONE,
 241                 .invert = 1,
 242         },
 243         {
 244                 .pin = "Speaker",
 245                 .mask = SND_JACK_HEADPHONE,
 246                 .invert = 1,
 247         },
 248 };
 249 
 250 
 251 /*
 252  * Modem line discipline, required for making above controls functional.
 253  * Activated from userspace with ldattach, possibly invoked from udev rule.
 254  */
 255 
 256 /* To actually apply any modem controlled configuration changes to the codec,
 257  * we must connect codec DAI pins to the modem for a moment.  Be careful not
 258  * to interfere with our digital mute function that shares the same hardware. */
 259 static struct timer_list cx81801_timer;
 260 static bool cx81801_cmd_pending;
 261 static bool ams_delta_muted;
 262 static DEFINE_SPINLOCK(ams_delta_lock);
 263 static struct gpio_desc *gpiod_modem_codec;
 264 
 265 static void cx81801_timeout(struct timer_list *unused)
 266 {
 267         int muted;
 268 
 269         spin_lock(&ams_delta_lock);
 270         cx81801_cmd_pending = 0;
 271         muted = ams_delta_muted;
 272         spin_unlock(&ams_delta_lock);
 273 
 274         /* Reconnect the codec DAI back from the modem to the CPU DAI
 275          * only if digital mute still off */
 276         if (!muted)
 277                 gpiod_set_value(gpiod_modem_codec, 0);
 278 }
 279 
 280 /* Line discipline .open() */
 281 static int cx81801_open(struct tty_struct *tty)
 282 {
 283         int ret;
 284 
 285         if (!cx20442_codec)
 286                 return -ENODEV;
 287 
 288         /*
 289          * Pass the codec structure pointer for use by other ldisc callbacks,
 290          * both the card and the codec specific parts.
 291          */
 292         tty->disc_data = cx20442_codec;
 293 
 294         ret = v253_ops.open(tty);
 295 
 296         if (ret < 0)
 297                 tty->disc_data = NULL;
 298 
 299         return ret;
 300 }
 301 
 302 /* Line discipline .close() */
 303 static void cx81801_close(struct tty_struct *tty)
 304 {
 305         struct snd_soc_component *component = tty->disc_data;
 306         struct snd_soc_dapm_context *dapm = &component->card->dapm;
 307 
 308         del_timer_sync(&cx81801_timer);
 309 
 310         /* Prevent the hook switch from further changing the DAPM pins */
 311         INIT_LIST_HEAD(&ams_delta_hook_switch.pins);
 312 
 313         if (!component)
 314                 return;
 315 
 316         v253_ops.close(tty);
 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() */
 333 static int cx81801_hangup(struct tty_struct *tty)
 334 {
 335         cx81801_close(tty);
 336         return 0;
 337 }
 338 
 339 /* Line discipline .receive_buf() */
 340 static void cx81801_receive(struct tty_struct *tty,
 341                                 const unsigned char *cp, char *fp, int count)
 342 {
 343         struct snd_soc_component *component = tty->disc_data;
 344         const unsigned char *c;
 345         int apply, ret;
 346 
 347         if (!component)
 348                 return;
 349 
 350         if (!component->card->pop_time) {
 351                 /* First modem response, complete setup procedure */
 352 
 353                 /* Initialize timer used for config pulse generation */
 354                 timer_setup(&cx81801_timer, cx81801_timeout, 0);
 355 
 356                 v253_ops.receive_buf(tty, cp, fp, count);
 357 
 358                 /* Link hook switch to DAPM pins */
 359                 ret = snd_soc_jack_add_pins(&ams_delta_hook_switch,
 360                                         ARRAY_SIZE(ams_delta_hook_switch_pins),
 361                                         ams_delta_hook_switch_pins);
 362                 if (ret)
 363                         dev_warn(component->dev,
 364                                 "Failed to link hook switch to DAPM pins, "
 365                                 "will continue with hook switch unlinked.\n");
 366 
 367                 return;
 368         }
 369 
 370         v253_ops.receive_buf(tty, cp, fp, count);
 371 
 372         for (c = &cp[count - 1]; c >= cp; c--) {
 373                 if (*c != '\r')
 374                         continue;
 375                 /* Complete modem response received, apply config to codec */
 376 
 377                 spin_lock_bh(&ams_delta_lock);
 378                 mod_timer(&cx81801_timer, jiffies + msecs_to_jiffies(150));
 379                 apply = !ams_delta_muted && !cx81801_cmd_pending;
 380                 cx81801_cmd_pending = 1;
 381                 spin_unlock_bh(&ams_delta_lock);
 382 
 383                 /* Apply config pulse by connecting the codec to the modem
 384                  * if not already done */
 385                 if (apply)
 386                         gpiod_set_value(gpiod_modem_codec, 1);
 387                 break;
 388         }
 389 }
 390 
 391 /* Line discipline .write_wakeup() */
 392 static void cx81801_wakeup(struct tty_struct *tty)
 393 {
 394         v253_ops.write_wakeup(tty);
 395 }
 396 
 397 static struct tty_ldisc_ops cx81801_ops = {
 398         .magic = TTY_LDISC_MAGIC,
 399         .name = "cx81801",
 400         .owner = THIS_MODULE,
 401         .open = cx81801_open,
 402         .close = cx81801_close,
 403         .hangup = cx81801_hangup,
 404         .receive_buf = cx81801_receive,
 405         .write_wakeup = cx81801_wakeup,
 406 };
 407 
 408 
 409 /*
 410  * Even if not very useful, the sound card can still work without any of the
 411  * above functonality activated.  You can still control its audio input/output
 412  * constellation and speakerphone gain from userspace by issuing AT commands
 413  * over the modem port.
 414  */
 415 
 416 static struct snd_soc_ops ams_delta_ops;
 417 
 418 
 419 /* Digital mute implemented using modem/CPU multiplexer.
 420  * Shares hardware with codec config pulse generation */
 421 static bool ams_delta_muted = 1;
 422 
 423 static int ams_delta_digital_mute(struct snd_soc_dai *dai, int mute)
 424 {
 425         int apply;
 426 
 427         if (ams_delta_muted == mute)
 428                 return 0;
 429 
 430         spin_lock_bh(&ams_delta_lock);
 431         ams_delta_muted = mute;
 432         apply = !cx81801_cmd_pending;
 433         spin_unlock_bh(&ams_delta_lock);
 434 
 435         if (apply)
 436                 gpiod_set_value(gpiod_modem_codec, !!mute);
 437         return 0;
 438 }
 439 
 440 /* Our codec DAI probably doesn't have its own .ops structure */
 441 static const struct snd_soc_dai_ops ams_delta_dai_ops = {
 442         .digital_mute = ams_delta_digital_mute,
 443 };
 444 
 445 /* Will be used if the codec ever has its own digital_mute function */
 446 static int ams_delta_startup(struct snd_pcm_substream *substream)
 447 {
 448         return ams_delta_digital_mute(NULL, 0);
 449 }
 450 
 451 static void ams_delta_shutdown(struct snd_pcm_substream *substream)
 452 {
 453         ams_delta_digital_mute(NULL, 1);
 454 }
 455 
 456 
 457 /*
 458  * Card initialization
 459  */
 460 
 461 static int ams_delta_cx20442_init(struct snd_soc_pcm_runtime *rtd)
 462 {
 463         struct snd_soc_dai *codec_dai = rtd->codec_dai;
 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 = rtd->codec_dai->component;
 471 
 472         /* Add hook switch - can be used to control the codec from userspace
 473          * even if line discipline fails */
 474         ret = snd_soc_card_jack_new(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(N_V253, &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 */
 524 SND_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 
 529 static 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 */
 540 static 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 */
 555 static 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 
 581 static int ams_delta_remove(struct platform_device *pdev)
 582 {
 583         struct snd_soc_card *card = platform_get_drvdata(pdev);
 584 
 585         if (tty_unregister_ldisc(N_V253) != 0)
 586                 dev_warn(&pdev->dev,
 587                         "failed to unregister V253 line discipline\n");
 588 
 589         snd_soc_unregister_card(card);
 590         card->dev = NULL;
 591         return 0;
 592 }
 593 
 594 #define DRV_NAME "ams-delta-audio"
 595 
 596 static struct platform_driver ams_delta_driver = {
 597         .driver = {
 598                 .name = DRV_NAME,
 599         },
 600         .probe = ams_delta_probe,
 601         .remove = ams_delta_remove,
 602 };
 603 
 604 module_platform_driver(ams_delta_driver);
 605 
 606 MODULE_AUTHOR("Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>");
 607 MODULE_DESCRIPTION("ALSA SoC driver for Amstrad E3 (Delta) videophone");
 608 MODULE_LICENSE("GPL");
 609 MODULE_ALIAS("platform:" DRV_NAME);

/* [<][>][^][v][top][bottom][index][help] */