linux/sound/soc/codecs/cx20442.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * cx20442.c  --  CX20442 ALSA Soc Audio driver
   4 *
   5 * Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
   6 *
   7 * Initially based on sound/soc/codecs/wm8400.c
   8 * Copyright 2008, 2009 Wolfson Microelectronics PLC.
   9 * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
  10 */
  11
  12#include <linux/tty.h>
  13#include <linux/slab.h>
  14#include <linux/module.h>
  15#include <linux/regulator/consumer.h>
  16
  17#include <sound/core.h>
  18#include <sound/initval.h>
  19#include <sound/soc.h>
  20
  21#include "cx20442.h"
  22
  23
  24struct cx20442_priv {
  25        struct tty_struct *tty;
  26        struct regulator *por;
  27        u8 reg_cache;
  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 unsigned int cx20442_read_reg_cache(struct snd_soc_component *component,
  89                                           unsigned int reg)
  90{
  91        struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
  92
  93        if (reg >= 1)
  94                return -EINVAL;
  95
  96        return cx20442->reg_cache;
  97}
  98
  99enum v253_vls {
 100        V253_VLS_NONE = 0,
 101        V253_VLS_T,
 102        V253_VLS_L,
 103        V253_VLS_LT,
 104        V253_VLS_S,
 105        V253_VLS_ST,
 106        V253_VLS_M,
 107        V253_VLS_MST,
 108        V253_VLS_S1,
 109        V253_VLS_S1T,
 110        V253_VLS_MS1T,
 111        V253_VLS_M1,
 112        V253_VLS_M1ST,
 113        V253_VLS_M1S1T,
 114        V253_VLS_H,
 115        V253_VLS_HT,
 116        V253_VLS_MS,
 117        V253_VLS_MS1,
 118        V253_VLS_M1S,
 119        V253_VLS_M1S1,
 120        V253_VLS_TEST,
 121};
 122
 123static int cx20442_pm_to_v253_vls(u8 value)
 124{
 125        switch (value & ~(1 << CX20442_AGC)) {
 126        case 0:
 127                return V253_VLS_T;
 128        case (1 << CX20442_SPKOUT):
 129        case (1 << CX20442_MIC):
 130        case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
 131                return V253_VLS_M1S1;
 132        case (1 << CX20442_TELOUT):
 133        case (1 << CX20442_TELIN):
 134        case (1 << CX20442_TELOUT) | (1 << CX20442_TELIN):
 135                return V253_VLS_L;
 136        case (1 << CX20442_TELOUT) | (1 << CX20442_MIC):
 137                return V253_VLS_NONE;
 138        }
 139        return -EINVAL;
 140}
 141static int cx20442_pm_to_v253_vsp(u8 value)
 142{
 143        switch (value & ~(1 << CX20442_AGC)) {
 144        case (1 << CX20442_SPKOUT):
 145        case (1 << CX20442_MIC):
 146        case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
 147                return (bool)(value & (1 << CX20442_AGC));
 148        }
 149        return (value & (1 << CX20442_AGC)) ? -EINVAL : 0;
 150}
 151
 152static int cx20442_write(struct snd_soc_component *component, unsigned int reg,
 153                                                        unsigned int value)
 154{
 155        struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
 156        int vls, vsp, old, len;
 157        char buf[18];
 158
 159        if (reg >= 1)
 160                return -EINVAL;
 161
 162        /* tty and write pointers required for talking to the modem
 163         * are expected to be set by the line discipline initialization code */
 164        if (!cx20442->tty || !cx20442->tty->ops->write)
 165                return -EIO;
 166
 167        old = cx20442->reg_cache;
 168        cx20442->reg_cache = value;
 169
 170        vls = cx20442_pm_to_v253_vls(value);
 171        if (vls < 0)
 172                return vls;
 173
 174        vsp = cx20442_pm_to_v253_vsp(value);
 175        if (vsp < 0)
 176                return vsp;
 177
 178        if ((vls == V253_VLS_T) ||
 179                        (vls == cx20442_pm_to_v253_vls(old))) {
 180                if (vsp == cx20442_pm_to_v253_vsp(old))
 181                        return 0;
 182                len = snprintf(buf, ARRAY_SIZE(buf), "at+vsp=%d\r", vsp);
 183        } else if (vsp == cx20442_pm_to_v253_vsp(old))
 184                len = snprintf(buf, ARRAY_SIZE(buf), "at+vls=%d\r", vls);
 185        else
 186                len = snprintf(buf, ARRAY_SIZE(buf),
 187                                        "at+vls=%d;+vsp=%d\r", vls, vsp);
 188
 189        if (unlikely(len > (ARRAY_SIZE(buf) - 1)))
 190                return -ENOMEM;
 191
 192        dev_dbg(component->dev, "%s: %s\n", __func__, buf);
 193        if (cx20442->tty->ops->write(cx20442->tty, buf, len) != len)
 194                return -EIO;
 195
 196        return 0;
 197}
 198
 199/*
 200 * Line discpline related code
 201 *
 202 * Any of the callback functions below can be used in two ways:
 203 * 1) registerd by a machine driver as one of line discipline operations,
 204 * 2) called from a machine's provided line discipline callback function
 205 *    in case when extra machine specific code must be run as well.
 206 */
 207
 208/* Modem init: echo off, digital speaker off, quiet off, voice mode */
 209static const char v253_init[] = "ate0m0q0+fclass=8\r";
 210
 211/* Line discipline .open() */
 212static int v253_open(struct tty_struct *tty)
 213{
 214        int ret, len = strlen(v253_init);
 215
 216        /* Doesn't make sense without write callback */
 217        if (!tty->ops->write)
 218                return -EINVAL;
 219
 220        /* Won't work if no codec pointer has been passed by a card driver */
 221        if (!tty->disc_data)
 222                return -ENODEV;
 223
 224        tty->receive_room = 16;
 225        if (tty->ops->write(tty, v253_init, len) != len) {
 226                ret = -EIO;
 227                goto err;
 228        }
 229        /* Actual setup will be performed after the modem responds. */
 230        return 0;
 231err:
 232        tty->disc_data = NULL;
 233        return ret;
 234}
 235
 236/* Line discipline .close() */
 237static void v253_close(struct tty_struct *tty)
 238{
 239        struct snd_soc_component *component = tty->disc_data;
 240        struct cx20442_priv *cx20442;
 241
 242        tty->disc_data = NULL;
 243
 244        if (!component)
 245                return;
 246
 247        cx20442 = snd_soc_component_get_drvdata(component);
 248
 249        /* Prevent the codec driver from further accessing the modem */
 250        cx20442->tty = NULL;
 251        component->card->pop_time = 0;
 252}
 253
 254/* Line discipline .hangup() */
 255static void v253_hangup(struct tty_struct *tty)
 256{
 257        v253_close(tty);
 258}
 259
 260/* Line discipline .receive_buf() */
 261static void v253_receive(struct tty_struct *tty, const unsigned char *cp,
 262                const char *fp, int count)
 263{
 264        struct snd_soc_component *component = tty->disc_data;
 265        struct cx20442_priv *cx20442;
 266
 267        if (!component)
 268                return;
 269
 270        cx20442 = snd_soc_component_get_drvdata(component);
 271
 272        if (!cx20442->tty) {
 273                /* First modem response, complete setup procedure */
 274
 275                /* Set up codec driver access to modem controls */
 276                cx20442->tty = tty;
 277                component->card->pop_time = 1;
 278        }
 279}
 280
 281struct tty_ldisc_ops v253_ops = {
 282        .name = "cx20442",
 283        .owner = THIS_MODULE,
 284        .open = v253_open,
 285        .close = v253_close,
 286        .hangup = v253_hangup,
 287        .receive_buf = v253_receive,
 288};
 289EXPORT_SYMBOL_GPL(v253_ops);
 290
 291
 292/*
 293 * Codec DAI
 294 */
 295
 296static struct snd_soc_dai_driver cx20442_dai = {
 297        .name = "cx20442-voice",
 298        .playback = {
 299                .stream_name = "Playback",
 300                .channels_min = 1,
 301                .channels_max = 1,
 302                .rates = SNDRV_PCM_RATE_8000,
 303                .formats = SNDRV_PCM_FMTBIT_S16_LE,
 304        },
 305        .capture = {
 306                .stream_name = "Capture",
 307                .channels_min = 1,
 308                .channels_max = 1,
 309                .rates = SNDRV_PCM_RATE_8000,
 310                .formats = SNDRV_PCM_FMTBIT_S16_LE,
 311        },
 312};
 313
 314static int cx20442_set_bias_level(struct snd_soc_component *component,
 315                enum snd_soc_bias_level level)
 316{
 317        struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
 318        int err = 0;
 319
 320        switch (level) {
 321        case SND_SOC_BIAS_PREPARE:
 322                if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_STANDBY)
 323                        break;
 324                if (IS_ERR(cx20442->por))
 325                        err = PTR_ERR(cx20442->por);
 326                else
 327                        err = regulator_enable(cx20442->por);
 328                break;
 329        case SND_SOC_BIAS_STANDBY:
 330                if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_PREPARE)
 331                        break;
 332                if (IS_ERR(cx20442->por))
 333                        err = PTR_ERR(cx20442->por);
 334                else
 335                        err = regulator_disable(cx20442->por);
 336                break;
 337        default:
 338                break;
 339        }
 340
 341        return err;
 342}
 343
 344static int cx20442_component_probe(struct snd_soc_component *component)
 345{
 346        struct cx20442_priv *cx20442;
 347
 348        cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL);
 349        if (cx20442 == NULL)
 350                return -ENOMEM;
 351
 352        cx20442->por = regulator_get(component->dev, "POR");
 353        if (IS_ERR(cx20442->por)) {
 354                int err = PTR_ERR(cx20442->por);
 355
 356                dev_warn(component->dev, "failed to get POR supply (%d)", err);
 357                /*
 358                 * When running on a non-dt platform and requested regulator
 359                 * is not available, regulator_get() never returns
 360                 * -EPROBE_DEFER as it is not able to justify if the regulator
 361                 * may still appear later.  On the other hand, the board can
 362                 * still set full constraints flag at late_initcall in order
 363                 * to instruct regulator_get() to return a dummy one if
 364                 * sufficient.  Hence, if we get -ENODEV here, let's convert
 365                 * it to -EPROBE_DEFER and wait for the board to decide or
 366                 * let Deferred Probe infrastructure handle this error.
 367                 */
 368                if (err == -ENODEV)
 369                        err = -EPROBE_DEFER;
 370                kfree(cx20442);
 371                return err;
 372        }
 373
 374        cx20442->tty = NULL;
 375
 376        snd_soc_component_set_drvdata(component, cx20442);
 377        component->card->pop_time = 0;
 378
 379        return 0;
 380}
 381
 382/* power down chip */
 383static void cx20442_component_remove(struct snd_soc_component *component)
 384{
 385        struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
 386
 387        if (cx20442->tty) {
 388                struct tty_struct *tty = cx20442->tty;
 389                tty_hangup(tty);
 390        }
 391
 392        if (!IS_ERR(cx20442->por)) {
 393                /* should be already in STANDBY, hence disabled */
 394                regulator_put(cx20442->por);
 395        }
 396
 397        snd_soc_component_set_drvdata(component, NULL);
 398        kfree(cx20442);
 399}
 400
 401static const struct snd_soc_component_driver cx20442_component_dev = {
 402        .probe                  = cx20442_component_probe,
 403        .remove                 = cx20442_component_remove,
 404        .set_bias_level         = cx20442_set_bias_level,
 405        .read                   = cx20442_read_reg_cache,
 406        .write                  = cx20442_write,
 407        .dapm_widgets           = cx20442_dapm_widgets,
 408        .num_dapm_widgets       = ARRAY_SIZE(cx20442_dapm_widgets),
 409        .dapm_routes            = cx20442_audio_map,
 410        .num_dapm_routes        = ARRAY_SIZE(cx20442_audio_map),
 411        .idle_bias_on           = 1,
 412        .use_pmdown_time        = 1,
 413        .endianness             = 1,
 414        .non_legacy_dai_naming  = 1,
 415};
 416
 417static int cx20442_platform_probe(struct platform_device *pdev)
 418{
 419        return devm_snd_soc_register_component(&pdev->dev,
 420                        &cx20442_component_dev, &cx20442_dai, 1);
 421}
 422
 423static struct platform_driver cx20442_platform_driver = {
 424        .driver = {
 425                .name = "cx20442-codec",
 426                },
 427        .probe = cx20442_platform_probe,
 428};
 429
 430module_platform_driver(cx20442_platform_driver);
 431
 432MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver");
 433MODULE_AUTHOR("Janusz Krzysztofik");
 434MODULE_LICENSE("GPL");
 435MODULE_ALIAS("platform:cx20442-codec");
 436