Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * cx20442.c  --  CX20442 ALSA Soc Audio driver
0004  *
0005  * Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
0006  *
0007  * Initially based on sound/soc/codecs/wm8400.c
0008  * Copyright 2008, 2009 Wolfson Microelectronics PLC.
0009  * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
0010  */
0011 
0012 #include <linux/tty.h>
0013 #include <linux/slab.h>
0014 #include <linux/module.h>
0015 #include <linux/regulator/consumer.h>
0016 
0017 #include <sound/core.h>
0018 #include <sound/initval.h>
0019 #include <sound/soc.h>
0020 
0021 #include "cx20442.h"
0022 
0023 
0024 struct cx20442_priv {
0025     struct tty_struct *tty;
0026     struct regulator *por;
0027     u8 reg_cache;
0028 };
0029 
0030 #define CX20442_PM      0x0
0031 
0032 #define CX20442_TELIN       0
0033 #define CX20442_TELOUT      1
0034 #define CX20442_MIC     2
0035 #define CX20442_SPKOUT      3
0036 #define CX20442_AGC     4
0037 
0038 static const struct snd_soc_dapm_widget cx20442_dapm_widgets[] = {
0039     SND_SOC_DAPM_OUTPUT("TELOUT"),
0040     SND_SOC_DAPM_OUTPUT("SPKOUT"),
0041     SND_SOC_DAPM_OUTPUT("AGCOUT"),
0042 
0043     SND_SOC_DAPM_MIXER("SPKOUT Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
0044 
0045     SND_SOC_DAPM_PGA("TELOUT Amp", CX20442_PM, CX20442_TELOUT, 0, NULL, 0),
0046     SND_SOC_DAPM_PGA("SPKOUT Amp", CX20442_PM, CX20442_SPKOUT, 0, NULL, 0),
0047     SND_SOC_DAPM_PGA("SPKOUT AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
0048 
0049     SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
0050     SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
0051 
0052     SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
0053 
0054     SND_SOC_DAPM_MICBIAS("TELIN Bias", CX20442_PM, CX20442_TELIN, 0),
0055     SND_SOC_DAPM_MICBIAS("MIC Bias", CX20442_PM, CX20442_MIC, 0),
0056 
0057     SND_SOC_DAPM_PGA("MIC AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
0058 
0059     SND_SOC_DAPM_INPUT("TELIN"),
0060     SND_SOC_DAPM_INPUT("MIC"),
0061     SND_SOC_DAPM_INPUT("AGCIN"),
0062 };
0063 
0064 static const struct snd_soc_dapm_route cx20442_audio_map[] = {
0065     {"TELOUT", NULL, "TELOUT Amp"},
0066 
0067     {"SPKOUT", NULL, "SPKOUT Mixer"},
0068     {"SPKOUT Mixer", NULL, "SPKOUT Amp"},
0069 
0070     {"TELOUT Amp", NULL, "DAC"},
0071     {"SPKOUT Amp", NULL, "DAC"},
0072 
0073     {"SPKOUT Mixer", NULL, "SPKOUT AGC"},
0074     {"SPKOUT AGC", NULL, "AGCIN"},
0075 
0076     {"AGCOUT", NULL, "MIC AGC"},
0077     {"MIC AGC", NULL, "MIC"},
0078 
0079     {"MIC Bias", NULL, "MIC"},
0080     {"Input Mixer", NULL, "MIC Bias"},
0081 
0082     {"TELIN Bias", NULL, "TELIN"},
0083     {"Input Mixer", NULL, "TELIN Bias"},
0084 
0085     {"ADC", NULL, "Input Mixer"},
0086 };
0087 
0088 static unsigned int cx20442_read_reg_cache(struct snd_soc_component *component,
0089                        unsigned int reg)
0090 {
0091     struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
0092 
0093     if (reg >= 1)
0094         return -EINVAL;
0095 
0096     return cx20442->reg_cache;
0097 }
0098 
0099 enum v253_vls {
0100     V253_VLS_NONE = 0,
0101     V253_VLS_T,
0102     V253_VLS_L,
0103     V253_VLS_LT,
0104     V253_VLS_S,
0105     V253_VLS_ST,
0106     V253_VLS_M,
0107     V253_VLS_MST,
0108     V253_VLS_S1,
0109     V253_VLS_S1T,
0110     V253_VLS_MS1T,
0111     V253_VLS_M1,
0112     V253_VLS_M1ST,
0113     V253_VLS_M1S1T,
0114     V253_VLS_H,
0115     V253_VLS_HT,
0116     V253_VLS_MS,
0117     V253_VLS_MS1,
0118     V253_VLS_M1S,
0119     V253_VLS_M1S1,
0120     V253_VLS_TEST,
0121 };
0122 
0123 static int cx20442_pm_to_v253_vls(u8 value)
0124 {
0125     switch (value & ~(1 << CX20442_AGC)) {
0126     case 0:
0127         return V253_VLS_T;
0128     case (1 << CX20442_SPKOUT):
0129     case (1 << CX20442_MIC):
0130     case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
0131         return V253_VLS_M1S1;
0132     case (1 << CX20442_TELOUT):
0133     case (1 << CX20442_TELIN):
0134     case (1 << CX20442_TELOUT) | (1 << CX20442_TELIN):
0135         return V253_VLS_L;
0136     case (1 << CX20442_TELOUT) | (1 << CX20442_MIC):
0137         return V253_VLS_NONE;
0138     }
0139     return -EINVAL;
0140 }
0141 static int cx20442_pm_to_v253_vsp(u8 value)
0142 {
0143     switch (value & ~(1 << CX20442_AGC)) {
0144     case (1 << CX20442_SPKOUT):
0145     case (1 << CX20442_MIC):
0146     case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
0147         return (bool)(value & (1 << CX20442_AGC));
0148     }
0149     return (value & (1 << CX20442_AGC)) ? -EINVAL : 0;
0150 }
0151 
0152 static int cx20442_write(struct snd_soc_component *component, unsigned int reg,
0153                             unsigned int value)
0154 {
0155     struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
0156     int vls, vsp, old, len;
0157     char buf[18];
0158 
0159     if (reg >= 1)
0160         return -EINVAL;
0161 
0162     /* tty and write pointers required for talking to the modem
0163      * are expected to be set by the line discipline initialization code */
0164     if (!cx20442->tty || !cx20442->tty->ops->write)
0165         return -EIO;
0166 
0167     old = cx20442->reg_cache;
0168     cx20442->reg_cache = value;
0169 
0170     vls = cx20442_pm_to_v253_vls(value);
0171     if (vls < 0)
0172         return vls;
0173 
0174     vsp = cx20442_pm_to_v253_vsp(value);
0175     if (vsp < 0)
0176         return vsp;
0177 
0178     if ((vls == V253_VLS_T) ||
0179             (vls == cx20442_pm_to_v253_vls(old))) {
0180         if (vsp == cx20442_pm_to_v253_vsp(old))
0181             return 0;
0182         len = snprintf(buf, ARRAY_SIZE(buf), "at+vsp=%d\r", vsp);
0183     } else if (vsp == cx20442_pm_to_v253_vsp(old))
0184         len = snprintf(buf, ARRAY_SIZE(buf), "at+vls=%d\r", vls);
0185     else
0186         len = snprintf(buf, ARRAY_SIZE(buf),
0187                     "at+vls=%d;+vsp=%d\r", vls, vsp);
0188 
0189     if (unlikely(len > (ARRAY_SIZE(buf) - 1)))
0190         return -ENOMEM;
0191 
0192     dev_dbg(component->dev, "%s: %s\n", __func__, buf);
0193     if (cx20442->tty->ops->write(cx20442->tty, buf, len) != len)
0194         return -EIO;
0195 
0196     return 0;
0197 }
0198 
0199 /*
0200  * Line discpline related code
0201  *
0202  * Any of the callback functions below can be used in two ways:
0203  * 1) registerd by a machine driver as one of line discipline operations,
0204  * 2) called from a machine's provided line discipline callback function
0205  *    in case when extra machine specific code must be run as well.
0206  */
0207 
0208 /* Modem init: echo off, digital speaker off, quiet off, voice mode */
0209 static const char v253_init[] = "ate0m0q0+fclass=8\r";
0210 
0211 /* Line discipline .open() */
0212 static int v253_open(struct tty_struct *tty)
0213 {
0214     int ret, len = strlen(v253_init);
0215 
0216     /* Doesn't make sense without write callback */
0217     if (!tty->ops->write)
0218         return -EINVAL;
0219 
0220     /* Won't work if no codec pointer has been passed by a card driver */
0221     if (!tty->disc_data)
0222         return -ENODEV;
0223 
0224     tty->receive_room = 16;
0225     if (tty->ops->write(tty, v253_init, len) != len) {
0226         ret = -EIO;
0227         goto err;
0228     }
0229     /* Actual setup will be performed after the modem responds. */
0230     return 0;
0231 err:
0232     tty->disc_data = NULL;
0233     return ret;
0234 }
0235 
0236 /* Line discipline .close() */
0237 static void v253_close(struct tty_struct *tty)
0238 {
0239     struct snd_soc_component *component = tty->disc_data;
0240     struct cx20442_priv *cx20442;
0241 
0242     tty->disc_data = NULL;
0243 
0244     if (!component)
0245         return;
0246 
0247     cx20442 = snd_soc_component_get_drvdata(component);
0248 
0249     /* Prevent the codec driver from further accessing the modem */
0250     cx20442->tty = NULL;
0251     component->card->pop_time = 0;
0252 }
0253 
0254 /* Line discipline .hangup() */
0255 static void v253_hangup(struct tty_struct *tty)
0256 {
0257     v253_close(tty);
0258 }
0259 
0260 /* Line discipline .receive_buf() */
0261 static void v253_receive(struct tty_struct *tty, const unsigned char *cp,
0262         const char *fp, int count)
0263 {
0264     struct snd_soc_component *component = tty->disc_data;
0265     struct cx20442_priv *cx20442;
0266 
0267     if (!component)
0268         return;
0269 
0270     cx20442 = snd_soc_component_get_drvdata(component);
0271 
0272     if (!cx20442->tty) {
0273         /* First modem response, complete setup procedure */
0274 
0275         /* Set up codec driver access to modem controls */
0276         cx20442->tty = tty;
0277         component->card->pop_time = 1;
0278     }
0279 }
0280 
0281 struct tty_ldisc_ops v253_ops = {
0282     .name = "cx20442",
0283     .owner = THIS_MODULE,
0284     .open = v253_open,
0285     .close = v253_close,
0286     .hangup = v253_hangup,
0287     .receive_buf = v253_receive,
0288 };
0289 EXPORT_SYMBOL_GPL(v253_ops);
0290 
0291 
0292 /*
0293  * Codec DAI
0294  */
0295 
0296 static struct snd_soc_dai_driver cx20442_dai = {
0297     .name = "cx20442-voice",
0298     .playback = {
0299         .stream_name = "Playback",
0300         .channels_min = 1,
0301         .channels_max = 1,
0302         .rates = SNDRV_PCM_RATE_8000,
0303         .formats = SNDRV_PCM_FMTBIT_S16_LE,
0304     },
0305     .capture = {
0306         .stream_name = "Capture",
0307         .channels_min = 1,
0308         .channels_max = 1,
0309         .rates = SNDRV_PCM_RATE_8000,
0310         .formats = SNDRV_PCM_FMTBIT_S16_LE,
0311     },
0312 };
0313 
0314 static int cx20442_set_bias_level(struct snd_soc_component *component,
0315         enum snd_soc_bias_level level)
0316 {
0317     struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
0318     int err = 0;
0319 
0320     switch (level) {
0321     case SND_SOC_BIAS_PREPARE:
0322         if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_STANDBY)
0323             break;
0324         if (IS_ERR(cx20442->por))
0325             err = PTR_ERR(cx20442->por);
0326         else
0327             err = regulator_enable(cx20442->por);
0328         break;
0329     case SND_SOC_BIAS_STANDBY:
0330         if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_PREPARE)
0331             break;
0332         if (IS_ERR(cx20442->por))
0333             err = PTR_ERR(cx20442->por);
0334         else
0335             err = regulator_disable(cx20442->por);
0336         break;
0337     default:
0338         break;
0339     }
0340 
0341     return err;
0342 }
0343 
0344 static int cx20442_component_probe(struct snd_soc_component *component)
0345 {
0346     struct cx20442_priv *cx20442;
0347 
0348     cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL);
0349     if (cx20442 == NULL)
0350         return -ENOMEM;
0351 
0352     cx20442->por = regulator_get(component->dev, "POR");
0353     if (IS_ERR(cx20442->por)) {
0354         int err = PTR_ERR(cx20442->por);
0355 
0356         dev_warn(component->dev, "failed to get POR supply (%d)", err);
0357         /*
0358          * When running on a non-dt platform and requested regulator
0359          * is not available, regulator_get() never returns
0360          * -EPROBE_DEFER as it is not able to justify if the regulator
0361          * may still appear later.  On the other hand, the board can
0362          * still set full constraints flag at late_initcall in order
0363          * to instruct regulator_get() to return a dummy one if
0364          * sufficient.  Hence, if we get -ENODEV here, let's convert
0365          * it to -EPROBE_DEFER and wait for the board to decide or
0366          * let Deferred Probe infrastructure handle this error.
0367          */
0368         if (err == -ENODEV)
0369             err = -EPROBE_DEFER;
0370         kfree(cx20442);
0371         return err;
0372     }
0373 
0374     cx20442->tty = NULL;
0375 
0376     snd_soc_component_set_drvdata(component, cx20442);
0377     component->card->pop_time = 0;
0378 
0379     return 0;
0380 }
0381 
0382 /* power down chip */
0383 static void cx20442_component_remove(struct snd_soc_component *component)
0384 {
0385     struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
0386 
0387     if (cx20442->tty) {
0388         struct tty_struct *tty = cx20442->tty;
0389         tty_hangup(tty);
0390     }
0391 
0392     if (!IS_ERR(cx20442->por)) {
0393         /* should be already in STANDBY, hence disabled */
0394         regulator_put(cx20442->por);
0395     }
0396 
0397     snd_soc_component_set_drvdata(component, NULL);
0398     kfree(cx20442);
0399 }
0400 
0401 static const struct snd_soc_component_driver cx20442_component_dev = {
0402     .probe          = cx20442_component_probe,
0403     .remove         = cx20442_component_remove,
0404     .set_bias_level     = cx20442_set_bias_level,
0405     .read           = cx20442_read_reg_cache,
0406     .write          = cx20442_write,
0407     .dapm_widgets       = cx20442_dapm_widgets,
0408     .num_dapm_widgets   = ARRAY_SIZE(cx20442_dapm_widgets),
0409     .dapm_routes        = cx20442_audio_map,
0410     .num_dapm_routes    = ARRAY_SIZE(cx20442_audio_map),
0411     .idle_bias_on       = 1,
0412     .use_pmdown_time    = 1,
0413     .endianness     = 1,
0414 };
0415 
0416 static int cx20442_platform_probe(struct platform_device *pdev)
0417 {
0418     return devm_snd_soc_register_component(&pdev->dev,
0419             &cx20442_component_dev, &cx20442_dai, 1);
0420 }
0421 
0422 static struct platform_driver cx20442_platform_driver = {
0423     .driver = {
0424         .name = "cx20442-codec",
0425         },
0426     .probe = cx20442_platform_probe,
0427 };
0428 
0429 module_platform_driver(cx20442_platform_driver);
0430 
0431 MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver");
0432 MODULE_AUTHOR("Janusz Krzysztofik");
0433 MODULE_LICENSE("GPL");
0434 MODULE_ALIAS("platform:cx20442-codec");