0001
0002
0003
0004
0005
0006
0007
0008
0009
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
0163
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
0201
0202
0203
0204
0205
0206
0207
0208
0209 static const char v253_init[] = "ate0m0q0+fclass=8\r";
0210
0211
0212 static int v253_open(struct tty_struct *tty)
0213 {
0214 int ret, len = strlen(v253_init);
0215
0216
0217 if (!tty->ops->write)
0218 return -EINVAL;
0219
0220
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
0230 return 0;
0231 err:
0232 tty->disc_data = NULL;
0233 return ret;
0234 }
0235
0236
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
0250 cx20442->tty = NULL;
0251 component->card->pop_time = 0;
0252 }
0253
0254
0255 static void v253_hangup(struct tty_struct *tty)
0256 {
0257 v253_close(tty);
0258 }
0259
0260
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
0274
0275
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
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
0359
0360
0361
0362
0363
0364
0365
0366
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
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
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");