linux/sound/soc/codecs/cx20442.c
<<
>>
Prefs
   1/*
   2 * cx20442.c  --  CX20442 ALSA Soc Audio driver
   3 *
   4 * Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
   5 *
   6 * Initially based on sound/soc/codecs/wm8400.c
   7 * Copyright 2008, 2009 Wolfson Microelectronics PLC.
   8 * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
   9 *
  10 *  This program is free software; you can redistribute  it and/or modify it
  11 *  under  the terms of  the GNU General  Public License as published by the
  12 *  Free Software Foundation;  either version 2 of the  License, or (at your
  13 *  option) any later version.
  14 */
  15
  16#include <linux/tty.h>
  17
  18#include <sound/core.h>
  19#include <sound/initval.h>
  20#include <sound/soc-dapm.h>
  21
  22#include "cx20442.h"
  23
  24
  25struct cx20442_priv {
  26        struct snd_soc_codec codec;
  27        u8 reg_cache[1];
  28};
  29
  30#define CX20442_PM              0x0
  31
  32#define CX20442_TELIN           0
  33#define CX20442_TELOUT          1
  34#define CX20442_MIC             2
  35#define CX20442_SPKOUT          3
  36#define CX20442_AGC             4
  37
  38static const struct snd_soc_dapm_widget cx20442_dapm_widgets[] = {
  39        SND_SOC_DAPM_OUTPUT("TELOUT"),
  40        SND_SOC_DAPM_OUTPUT("SPKOUT"),
  41        SND_SOC_DAPM_OUTPUT("AGCOUT"),
  42
  43        SND_SOC_DAPM_MIXER("SPKOUT Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
  44
  45        SND_SOC_DAPM_PGA("TELOUT Amp", CX20442_PM, CX20442_TELOUT, 0, NULL, 0),
  46        SND_SOC_DAPM_PGA("SPKOUT Amp", CX20442_PM, CX20442_SPKOUT, 0, NULL, 0),
  47        SND_SOC_DAPM_PGA("SPKOUT AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
  48
  49        SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
  50        SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
  51
  52        SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
  53
  54        SND_SOC_DAPM_MICBIAS("TELIN Bias", CX20442_PM, CX20442_TELIN, 0),
  55        SND_SOC_DAPM_MICBIAS("MIC Bias", CX20442_PM, CX20442_MIC, 0),
  56
  57        SND_SOC_DAPM_PGA("MIC AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
  58
  59        SND_SOC_DAPM_INPUT("TELIN"),
  60        SND_SOC_DAPM_INPUT("MIC"),
  61        SND_SOC_DAPM_INPUT("AGCIN"),
  62};
  63
  64static const struct snd_soc_dapm_route cx20442_audio_map[] = {
  65        {"TELOUT", NULL, "TELOUT Amp"},
  66
  67        {"SPKOUT", NULL, "SPKOUT Mixer"},
  68        {"SPKOUT Mixer", NULL, "SPKOUT Amp"},
  69
  70        {"TELOUT Amp", NULL, "DAC"},
  71        {"SPKOUT Amp", NULL, "DAC"},
  72
  73        {"SPKOUT Mixer", NULL, "SPKOUT AGC"},
  74        {"SPKOUT AGC", NULL, "AGCIN"},
  75
  76        {"AGCOUT", NULL, "MIC AGC"},
  77        {"MIC AGC", NULL, "MIC"},
  78
  79        {"MIC Bias", NULL, "MIC"},
  80        {"Input Mixer", NULL, "MIC Bias"},
  81
  82        {"TELIN Bias", NULL, "TELIN"},
  83        {"Input Mixer", NULL, "TELIN Bias"},
  84
  85        {"ADC", NULL, "Input Mixer"},
  86};
  87
  88static int cx20442_add_widgets(struct snd_soc_codec *codec)
  89{
  90        snd_soc_dapm_new_controls(codec, cx20442_dapm_widgets,
  91                                  ARRAY_SIZE(cx20442_dapm_widgets));
  92
  93        snd_soc_dapm_add_routes(codec, cx20442_audio_map,
  94                                ARRAY_SIZE(cx20442_audio_map));
  95
  96        snd_soc_dapm_new_widgets(codec);
  97        return 0;
  98}
  99
 100static unsigned int cx20442_read_reg_cache(struct snd_soc_codec *codec,
 101                                                        unsigned int reg)
 102{
 103        u8 *reg_cache = codec->reg_cache;
 104
 105        if (reg >= codec->reg_cache_size)
 106                return -EINVAL;
 107
 108        return reg_cache[reg];
 109}
 110
 111enum v253_vls {
 112        V253_VLS_NONE = 0,
 113        V253_VLS_T,
 114        V253_VLS_L,
 115        V253_VLS_LT,
 116        V253_VLS_S,
 117        V253_VLS_ST,
 118        V253_VLS_M,
 119        V253_VLS_MST,
 120        V253_VLS_S1,
 121        V253_VLS_S1T,
 122        V253_VLS_MS1T,
 123        V253_VLS_M1,
 124        V253_VLS_M1ST,
 125        V253_VLS_M1S1T,
 126        V253_VLS_H,
 127        V253_VLS_HT,
 128        V253_VLS_MS,
 129        V253_VLS_MS1,
 130        V253_VLS_M1S,
 131        V253_VLS_M1S1,
 132        V253_VLS_TEST,
 133};
 134
 135static int cx20442_pm_to_v253_vls(u8 value)
 136{
 137        switch (value & ~(1 << CX20442_AGC)) {
 138        case 0:
 139                return V253_VLS_T;
 140        case (1 << CX20442_SPKOUT):
 141        case (1 << CX20442_MIC):
 142        case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
 143                return V253_VLS_M1S1;
 144        case (1 << CX20442_TELOUT):
 145        case (1 << CX20442_TELIN):
 146        case (1 << CX20442_TELOUT) | (1 << CX20442_TELIN):
 147                return V253_VLS_L;
 148        case (1 << CX20442_TELOUT) | (1 << CX20442_MIC):
 149                return V253_VLS_NONE;
 150        }
 151        return -EINVAL;
 152}
 153static int cx20442_pm_to_v253_vsp(u8 value)
 154{
 155        switch (value & ~(1 << CX20442_AGC)) {
 156        case (1 << CX20442_SPKOUT):
 157        case (1 << CX20442_MIC):
 158        case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
 159                return (bool)(value & (1 << CX20442_AGC));
 160        }
 161        return (value & (1 << CX20442_AGC)) ? -EINVAL : 0;
 162}
 163
 164static int cx20442_write(struct snd_soc_codec *codec, unsigned int reg,
 165                                                        unsigned int value)
 166{
 167        u8 *reg_cache = codec->reg_cache;
 168        int vls, vsp, old, len;
 169        char buf[18];
 170
 171        if (reg >= codec->reg_cache_size)
 172                return -EINVAL;
 173
 174        /* hw_write and control_data pointers required for talking to the modem
 175         * are expected to be set by the line discipline initialization code */
 176        if (!codec->hw_write || !codec->control_data)
 177                return -EIO;
 178
 179        old = reg_cache[reg];
 180        reg_cache[reg] = value;
 181
 182        vls = cx20442_pm_to_v253_vls(value);
 183        if (vls < 0)
 184                return vls;
 185
 186        vsp = cx20442_pm_to_v253_vsp(value);
 187        if (vsp < 0)
 188                return vsp;
 189
 190        if ((vls == V253_VLS_T) ||
 191                        (vls == cx20442_pm_to_v253_vls(old))) {
 192                if (vsp == cx20442_pm_to_v253_vsp(old))
 193                        return 0;
 194                len = snprintf(buf, ARRAY_SIZE(buf), "at+vsp=%d\r", vsp);
 195        } else if (vsp == cx20442_pm_to_v253_vsp(old))
 196                len = snprintf(buf, ARRAY_SIZE(buf), "at+vls=%d\r", vls);
 197        else
 198                len = snprintf(buf, ARRAY_SIZE(buf),
 199                                        "at+vls=%d;+vsp=%d\r", vls, vsp);
 200
 201        if (unlikely(len > (ARRAY_SIZE(buf) - 1)))
 202                return -ENOMEM;
 203
 204        dev_dbg(codec->dev, "%s: %s\n", __func__, buf);
 205        if (codec->hw_write(codec->control_data, buf, len) != len)
 206                return -EIO;
 207
 208        return 0;
 209}
 210
 211
 212/* Moved up here as line discipline referres it during initialization */
 213static struct snd_soc_codec *cx20442_codec;
 214
 215
 216/*
 217 * Line discpline related code
 218 *
 219 * Any of the callback functions below can be used in two ways:
 220 * 1) registerd by a machine driver as one of line discipline operations,
 221 * 2) called from a machine's provided line discipline callback function
 222 *    in case when extra machine specific code must be run as well.
 223 */
 224
 225/* Modem init: echo off, digital speaker off, quiet off, voice mode */
 226static const char *v253_init = "ate0m0q0+fclass=8\r";
 227
 228/* Line discipline .open() */
 229static int v253_open(struct tty_struct *tty)
 230{
 231        struct snd_soc_codec *codec = cx20442_codec;
 232        int ret, len = strlen(v253_init);
 233
 234        /* Doesn't make sense without write callback */
 235        if (!tty->ops->write)
 236                return -EINVAL;
 237
 238        /* Pass the codec structure address for use by other ldisc callbacks */
 239        tty->disc_data = codec;
 240
 241        if (tty->ops->write(tty, v253_init, len) != len) {
 242                ret = -EIO;
 243                goto err;
 244        }
 245        /* Actual setup will be performed after the modem responds. */
 246        return 0;
 247err:
 248        tty->disc_data = NULL;
 249        return ret;
 250}
 251
 252/* Line discipline .close() */
 253static void v253_close(struct tty_struct *tty)
 254{
 255        struct snd_soc_codec *codec = tty->disc_data;
 256
 257        tty->disc_data = NULL;
 258
 259        if (!codec)
 260                return;
 261
 262        /* Prevent the codec driver from further accessing the modem */
 263        codec->hw_write = NULL;
 264        codec->control_data = NULL;
 265        codec->pop_time = 0;
 266}
 267
 268/* Line discipline .hangup() */
 269static int v253_hangup(struct tty_struct *tty)
 270{
 271        v253_close(tty);
 272        return 0;
 273}
 274
 275/* Line discipline .receive_buf() */
 276static void v253_receive(struct tty_struct *tty,
 277                                const unsigned char *cp, char *fp, int count)
 278{
 279        struct snd_soc_codec *codec = tty->disc_data;
 280
 281        if (!codec)
 282                return;
 283
 284        if (!codec->control_data) {
 285                /* First modem response, complete setup procedure */
 286
 287                /* Set up codec driver access to modem controls */
 288                codec->control_data = tty;
 289                codec->hw_write = (hw_write_t)tty->ops->write;
 290                codec->pop_time = 1;
 291        }
 292}
 293
 294/* Line discipline .write_wakeup() */
 295static void v253_wakeup(struct tty_struct *tty)
 296{
 297}
 298
 299struct tty_ldisc_ops v253_ops = {
 300        .magic = TTY_LDISC_MAGIC,
 301        .name = "cx20442",
 302        .owner = THIS_MODULE,
 303        .open = v253_open,
 304        .close = v253_close,
 305        .hangup = v253_hangup,
 306        .receive_buf = v253_receive,
 307        .write_wakeup = v253_wakeup,
 308};
 309EXPORT_SYMBOL_GPL(v253_ops);
 310
 311
 312/*
 313 * Codec DAI
 314 */
 315
 316struct snd_soc_dai cx20442_dai = {
 317        .name = "CX20442",
 318        .playback = {
 319                .stream_name = "Playback",
 320                .channels_min = 1,
 321                .channels_max = 1,
 322                .rates = SNDRV_PCM_RATE_8000,
 323                .formats = SNDRV_PCM_FMTBIT_S16_LE,
 324        },
 325        .capture = {
 326                .stream_name = "Capture",
 327                .channels_min = 1,
 328                .channels_max = 1,
 329                .rates = SNDRV_PCM_RATE_8000,
 330                .formats = SNDRV_PCM_FMTBIT_S16_LE,
 331        },
 332};
 333EXPORT_SYMBOL_GPL(cx20442_dai);
 334
 335static int cx20442_codec_probe(struct platform_device *pdev)
 336{
 337        struct snd_soc_device *socdev = platform_get_drvdata(pdev);
 338        struct snd_soc_codec *codec;
 339        int ret;
 340
 341        if (!cx20442_codec) {
 342                dev_err(&pdev->dev, "cx20442 not yet discovered\n");
 343                return -ENODEV;
 344        }
 345        codec = cx20442_codec;
 346
 347        socdev->card->codec = codec;
 348
 349        /* register pcms */
 350        ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
 351        if (ret < 0) {
 352                dev_err(&pdev->dev, "failed to create pcms\n");
 353                goto pcm_err;
 354        }
 355
 356        cx20442_add_widgets(codec);
 357
 358        ret = snd_soc_init_card(socdev);
 359        if (ret < 0) {
 360                dev_err(&pdev->dev, "failed to register card\n");
 361                goto card_err;
 362        }
 363
 364        return ret;
 365
 366card_err:
 367        snd_soc_free_pcms(socdev);
 368        snd_soc_dapm_free(socdev);
 369pcm_err:
 370        return ret;
 371}
 372
 373/* power down chip */
 374static int cx20442_codec_remove(struct platform_device *pdev)
 375{
 376        struct snd_soc_device *socdev = platform_get_drvdata(pdev);
 377
 378        snd_soc_free_pcms(socdev);
 379        snd_soc_dapm_free(socdev);
 380
 381        return 0;
 382}
 383
 384struct snd_soc_codec_device cx20442_codec_dev = {
 385        .probe =        cx20442_codec_probe,
 386        .remove =       cx20442_codec_remove,
 387};
 388EXPORT_SYMBOL_GPL(cx20442_codec_dev);
 389
 390static int cx20442_register(struct cx20442_priv *cx20442)
 391{
 392        struct snd_soc_codec *codec = &cx20442->codec;
 393        int ret;
 394
 395        mutex_init(&codec->mutex);
 396        INIT_LIST_HEAD(&codec->dapm_widgets);
 397        INIT_LIST_HEAD(&codec->dapm_paths);
 398
 399        codec->name = "CX20442";
 400        codec->owner = THIS_MODULE;
 401        codec->private_data = cx20442;
 402
 403        codec->dai = &cx20442_dai;
 404        codec->num_dai = 1;
 405
 406        codec->reg_cache = &cx20442->reg_cache;
 407        codec->reg_cache_size = ARRAY_SIZE(cx20442->reg_cache);
 408        codec->read = cx20442_read_reg_cache;
 409        codec->write = cx20442_write;
 410
 411        codec->bias_level = SND_SOC_BIAS_OFF;
 412
 413        cx20442_dai.dev = codec->dev;
 414
 415        cx20442_codec = codec;
 416
 417        ret = snd_soc_register_codec(codec);
 418        if (ret != 0) {
 419                dev_err(codec->dev, "Failed to register codec: %d\n", ret);
 420                goto err;
 421        }
 422
 423        ret = snd_soc_register_dai(&cx20442_dai);
 424        if (ret != 0) {
 425                dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
 426                goto err_codec;
 427        }
 428
 429        return 0;
 430
 431err_codec:
 432        snd_soc_unregister_codec(codec);
 433err:
 434        cx20442_codec = NULL;
 435        kfree(cx20442);
 436        return ret;
 437}
 438
 439static void cx20442_unregister(struct cx20442_priv *cx20442)
 440{
 441        snd_soc_unregister_dai(&cx20442_dai);
 442        snd_soc_unregister_codec(&cx20442->codec);
 443
 444        cx20442_codec = NULL;
 445        kfree(cx20442);
 446}
 447
 448static int cx20442_platform_probe(struct platform_device *pdev)
 449{
 450        struct cx20442_priv *cx20442;
 451        struct snd_soc_codec *codec;
 452
 453        cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL);
 454        if (cx20442 == NULL)
 455                return -ENOMEM;
 456
 457        codec = &cx20442->codec;
 458
 459        codec->control_data = NULL;
 460        codec->hw_write = NULL;
 461        codec->pop_time = 0;
 462
 463        codec->dev = &pdev->dev;
 464        platform_set_drvdata(pdev, cx20442);
 465
 466        return cx20442_register(cx20442);
 467}
 468
 469static int __exit cx20442_platform_remove(struct platform_device *pdev)
 470{
 471        struct cx20442_priv *cx20442 = platform_get_drvdata(pdev);
 472
 473        cx20442_unregister(cx20442);
 474        return 0;
 475}
 476
 477static struct platform_driver cx20442_platform_driver = {
 478        .driver = {
 479                .name = "cx20442",
 480                .owner = THIS_MODULE,
 481                },
 482        .probe = cx20442_platform_probe,
 483        .remove = __exit_p(cx20442_platform_remove),
 484};
 485
 486static int __init cx20442_init(void)
 487{
 488        return platform_driver_register(&cx20442_platform_driver);
 489}
 490module_init(cx20442_init);
 491
 492static void __exit cx20442_exit(void)
 493{
 494        platform_driver_unregister(&cx20442_platform_driver);
 495}
 496module_exit(cx20442_exit);
 497
 498MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver");
 499MODULE_AUTHOR("Janusz Krzysztofik");
 500MODULE_LICENSE("GPL");
 501MODULE_ALIAS("platform:cx20442");
 502
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.