root/sound/soc/codecs/cx20442.c

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

DEFINITIONS

This source file includes following definitions.
  1. cx20442_read_reg_cache
  2. cx20442_pm_to_v253_vls
  3. cx20442_pm_to_v253_vsp
  4. cx20442_write
  5. v253_open
  6. v253_close
  7. v253_hangup
  8. v253_receive
  9. v253_wakeup
  10. cx20442_set_bias_level
  11. cx20442_component_probe
  12. cx20442_component_remove
  13. cx20442_platform_probe

   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 
  24 struct 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 
  38 static 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 
  64 static 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 
  88 static 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 
  99 enum 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 
 123 static 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 }
 141 static 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 
 152 static 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 */
 209 static const char *v253_init = "ate0m0q0+fclass=8\r";
 210 
 211 /* Line discipline .open() */
 212 static 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;
 231 err:
 232         tty->disc_data = NULL;
 233         return ret;
 234 }
 235 
 236 /* Line discipline .close() */
 237 static 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() */
 255 static int v253_hangup(struct tty_struct *tty)
 256 {
 257         v253_close(tty);
 258         return 0;
 259 }
 260 
 261 /* Line discipline .receive_buf() */
 262 static void v253_receive(struct tty_struct *tty,
 263                                 const unsigned char *cp, char *fp, int count)
 264 {
 265         struct snd_soc_component *component = tty->disc_data;
 266         struct cx20442_priv *cx20442;
 267 
 268         if (!component)
 269                 return;
 270 
 271         cx20442 = snd_soc_component_get_drvdata(component);
 272 
 273         if (!cx20442->tty) {
 274                 /* First modem response, complete setup procedure */
 275 
 276                 /* Set up codec driver access to modem controls */
 277                 cx20442->tty = tty;
 278                 component->card->pop_time = 1;
 279         }
 280 }
 281 
 282 /* Line discipline .write_wakeup() */
 283 static void v253_wakeup(struct tty_struct *tty)
 284 {
 285 }
 286 
 287 struct tty_ldisc_ops v253_ops = {
 288         .magic = TTY_LDISC_MAGIC,
 289         .name = "cx20442",
 290         .owner = THIS_MODULE,
 291         .open = v253_open,
 292         .close = v253_close,
 293         .hangup = v253_hangup,
 294         .receive_buf = v253_receive,
 295         .write_wakeup = v253_wakeup,
 296 };
 297 EXPORT_SYMBOL_GPL(v253_ops);
 298 
 299 
 300 /*
 301  * Codec DAI
 302  */
 303 
 304 static struct snd_soc_dai_driver cx20442_dai = {
 305         .name = "cx20442-voice",
 306         .playback = {
 307                 .stream_name = "Playback",
 308                 .channels_min = 1,
 309                 .channels_max = 1,
 310                 .rates = SNDRV_PCM_RATE_8000,
 311                 .formats = SNDRV_PCM_FMTBIT_S16_LE,
 312         },
 313         .capture = {
 314                 .stream_name = "Capture",
 315                 .channels_min = 1,
 316                 .channels_max = 1,
 317                 .rates = SNDRV_PCM_RATE_8000,
 318                 .formats = SNDRV_PCM_FMTBIT_S16_LE,
 319         },
 320 };
 321 
 322 static int cx20442_set_bias_level(struct snd_soc_component *component,
 323                 enum snd_soc_bias_level level)
 324 {
 325         struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
 326         int err = 0;
 327 
 328         switch (level) {
 329         case SND_SOC_BIAS_PREPARE:
 330                 if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_STANDBY)
 331                         break;
 332                 if (IS_ERR(cx20442->por))
 333                         err = PTR_ERR(cx20442->por);
 334                 else
 335                         err = regulator_enable(cx20442->por);
 336                 break;
 337         case SND_SOC_BIAS_STANDBY:
 338                 if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_PREPARE)
 339                         break;
 340                 if (IS_ERR(cx20442->por))
 341                         err = PTR_ERR(cx20442->por);
 342                 else
 343                         err = regulator_disable(cx20442->por);
 344                 break;
 345         default:
 346                 break;
 347         }
 348 
 349         return err;
 350 }
 351 
 352 static int cx20442_component_probe(struct snd_soc_component *component)
 353 {
 354         struct cx20442_priv *cx20442;
 355 
 356         cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL);
 357         if (cx20442 == NULL)
 358                 return -ENOMEM;
 359 
 360         cx20442->por = regulator_get(component->dev, "POR");
 361         if (IS_ERR(cx20442->por)) {
 362                 int err = PTR_ERR(cx20442->por);
 363 
 364                 dev_warn(component->dev, "failed to get POR supply (%d)", err);
 365                 /*
 366                  * When running on a non-dt platform and requested regulator
 367                  * is not available, regulator_get() never returns
 368                  * -EPROBE_DEFER as it is not able to justify if the regulator
 369                  * may still appear later.  On the other hand, the board can
 370                  * still set full constraints flag at late_initcall in order
 371                  * to instruct regulator_get() to return a dummy one if
 372                  * sufficient.  Hence, if we get -ENODEV here, let's convert
 373                  * it to -EPROBE_DEFER and wait for the board to decide or
 374                  * let Deferred Probe infrastructure handle this error.
 375                  */
 376                 if (err == -ENODEV)
 377                         err = -EPROBE_DEFER;
 378                 kfree(cx20442);
 379                 return err;
 380         }
 381 
 382         cx20442->tty = NULL;
 383 
 384         snd_soc_component_set_drvdata(component, cx20442);
 385         component->card->pop_time = 0;
 386 
 387         return 0;
 388 }
 389 
 390 /* power down chip */
 391 static void cx20442_component_remove(struct snd_soc_component *component)
 392 {
 393         struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
 394 
 395         if (cx20442->tty) {
 396                 struct tty_struct *tty = cx20442->tty;
 397                 tty_hangup(tty);
 398         }
 399 
 400         if (!IS_ERR(cx20442->por)) {
 401                 /* should be already in STANDBY, hence disabled */
 402                 regulator_put(cx20442->por);
 403         }
 404 
 405         snd_soc_component_set_drvdata(component, NULL);
 406         kfree(cx20442);
 407 }
 408 
 409 static const struct snd_soc_component_driver cx20442_component_dev = {
 410         .probe                  = cx20442_component_probe,
 411         .remove                 = cx20442_component_remove,
 412         .set_bias_level         = cx20442_set_bias_level,
 413         .read                   = cx20442_read_reg_cache,
 414         .write                  = cx20442_write,
 415         .dapm_widgets           = cx20442_dapm_widgets,
 416         .num_dapm_widgets       = ARRAY_SIZE(cx20442_dapm_widgets),
 417         .dapm_routes            = cx20442_audio_map,
 418         .num_dapm_routes        = ARRAY_SIZE(cx20442_audio_map),
 419         .idle_bias_on           = 1,
 420         .use_pmdown_time        = 1,
 421         .endianness             = 1,
 422         .non_legacy_dai_naming  = 1,
 423 };
 424 
 425 static int cx20442_platform_probe(struct platform_device *pdev)
 426 {
 427         return devm_snd_soc_register_component(&pdev->dev,
 428                         &cx20442_component_dev, &cx20442_dai, 1);
 429 }
 430 
 431 static struct platform_driver cx20442_platform_driver = {
 432         .driver = {
 433                 .name = "cx20442-codec",
 434                 },
 435         .probe = cx20442_platform_probe,
 436 };
 437 
 438 module_platform_driver(cx20442_platform_driver);
 439 
 440 MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver");
 441 MODULE_AUTHOR("Janusz Krzysztofik");
 442 MODULE_LICENSE("GPL");
 443 MODULE_ALIAS("platform:cx20442-codec");

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