[alsa-devel] New codec for STAC9766 used on Efika
Could you please review this new STAC9766 codec implementation. Some of the controls aren't where they need to be. For example the mutes on the mixer inputs. I'm not sure where they are supposed to go.
How should SPDIF support work? The hardware supports simultaneous use of analog and SPDIF. Do I want one DAI or two? If I have two DAI then ALSA needs to interleave the samples into a single DMA stream. So if one stream isn't active it needs to be filled with silence.
Other options... A single DAI with a switch A single DAI with the same output stream on both analog and SPDIF.
I've tried to add the DAPM routes. I don't know of anyone using this chip on a low power design.
Datasheet is here: http://www.idt.com/products/getDoc.cfm?docID=13134007
I get a few errors from the current config: Failed to add route All Analog Mixer->Pop Bypass Mux dapm: STAC9766: configuring unknown pin OUT3 dapm: STAC9766: configuring unknown pin MONOOUT Failed to add route HPOUTL->Headphone Jack dapm: STAC9766: configuring unknown pin OUT3 dapm: STAC9766: configuring unknown pin MONOOUT Failed to add route HPOUTL->Headphone Jack
/* * stac9766.c -- ALSA Soc STAC9766 codec support * * Copyright 2008 Jon Smirl, Digispeaker * Author: Jon Smirl jonsmirl@gmail.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * Features:- * * o Support for AC97 Codec, S/PDIF * o Support for DAPM */
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/ac97_codec.h> #include <sound/initval.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/soc-of-simple.h>
#include "stac9766.h"
#define STAC9766_VERSION "0.10"
/* * STAC9766 register cache */ static const u16 stac9766_reg[] = { 0x6A90, 0x8000, 0x8000, 0x8000, // 6 0x0000, 0x0000, 0x8008, 0x8008, // e 0x8808, 0x8808, 0x8808, 0x8808, // 16 0x8808, 0x0000, 0x8000, 0x0000, // 1e 0x0000, 0x0000, 0x0000, 0x000f, // 26 0x0a05, 0x0400, 0xbb80, 0x0000, // 2e 0x0000, 0xbb80, 0x0000, 0x0000, // 36 0x0000, 0x2000, 0x0000, 0x0100, // 3e 0x0000, 0x0000, 0x0080, 0x0000, // 46 0x0000, 0x0000, 0x0003, 0xffff, // 4e 0x0000, 0x0000, 0x0000, 0x0000, // 56 0x4000, 0x0000, 0x0000, 0x0000, // 5e 0x1201, 0xFFFF, 0xFFFF, 0x0000, // 66 0x0000, 0x0000, 0x0000, 0x0000, // 6e 0x0000, 0x0000, 0x0000, 0x0006, // 76 0x0000, 0x0000, 0x0000, 0x0000, // 7e };
static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX", "Line", "Stereo Mix", "Mono Mix", "Phone"}; static const char *stac9766_mono_mux[] = {"Mix", "Mic"}; static const char *stac9766_mic_mux[] = {"Mic1", "Mic2"}; static const char *stac9766_SPDIF_mux[] = {"PCM", "ADC Record"}; static const char *stac9766_popbypass_mux[] = {"Normal", "Bypass Mixer"}; static const char *stac9766_record_all_mux[] = {"All analog", "Analog plus DAC"}; static const char *stac9766_3D_separation[] = {"Off", "Low", "Medium", "High"};
static const struct soc_enum stac9766_enum[] = { SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, stac9766_record_mux), /* Record Mux 0 */ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, stac9766_mono_mux), /* Mono Mux 1 */ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, stac9766_mic_mux), /* Mic1/2 Mux 2 */ SOC_ENUM_SINGLE(AC97_SENSE_INFO, 1, 2, stac9766_SPDIF_mux), /* SPDIF Mux 3 */ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, stac9766_popbypass_mux), /* Pop Bypass Mux 4 */ SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 12, 2, stac9766_record_all_mux), /* Record All Mux 5 */ SOC_ENUM_SINGLE(AC97_3D_CONTROL, 2, 4, stac9766_3D_separation), /* 3D Separation 7 */ };
static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = { SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1), SOC_SINGLE("Speaker Playback Switch", AC97_MASTER, 15, 1, 1), SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), SOC_SINGLE("Mixer PC Beep Volume", AC97_PC_BEEP, 1, 15, 1), SOC_SINGLE("PC Beep Frequency", AC97_PC_BEEP, 5, 127, 1), SOC_SINGLE("Mixer Phone Volume", AC97_PHONE, 0, 31, 1), SOC_SINGLE("Mixer Mic Volume", AC97_MIC, 0, 31, 1), SOC_SINGLE("Mic Boost", AC97_MIC, 6, 1, 1), SOC_SINGLE("Mic Gain", AC97_STAC_ANALOG_SPECIAL, 2, 1, 1), SOC_SINGLE("Stereo Mic", AC97_STAC_STEREO_MIC, 0, 1, 1), SOC_DOUBLE("Mixer Line Volume", AC97_LINE, 8, 0, 31, 1), SOC_DOUBLE("Mixer CD Volume", AC97_CD, 8, 0, 31, 1), SOC_DOUBLE("Mixer Video Volume", AC97_VIDEO, 8, 0, 31, 1), SOC_DOUBLE("Mixer AUX Volume", AC97_AUX, 8, 0, 31, 1), SOC_DOUBLE("All Analog PCM Volume", AC97_PCM, 8, 0, 31, 1), SOC_DOUBLE("Record Gain", AC97_REC_GAIN, 8, 0, 31, 1), SOC_SINGLE("Record Gain Switch", AC97_REC_GAIN, 15, 1, 1), SOC_SINGLE("3D Effect Switch", AC97_GENERAL_PURPOSE, 13, 1, 0), SOC_SINGLE("Loopback Test Switch", AC97_GENERAL_PURPOSE, 7, 1, 0), SOC_ENUM("3D Separation", stac9766_enum[6]), };
/* Mixer */ static const struct snd_kcontrol_new stac9766_main_mixer_controls[] = { SOC_DAPM_SINGLE("PC Beep Switch", AC97_PC_BEEP, 15, 1, 1), SOC_DAPM_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1), SOC_DAPM_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1), SOC_DAPM_SINGLE("Line Switch", AC97_LINE, 15, 1, 1), SOC_DAPM_SINGLE("CD Switch", AC97_CD, 15, 1, 1), SOC_DAPM_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1), SOC_DAPM_SINGLE("Video Switch", AC97_VIDEO, 15, 1, 1), };
/* Record All Mixer */ static const struct snd_kcontrol_new stac9766_record_all_mixer_controls[] = { SOC_DAPM_SINGLE("PCM Switch", AC97_PCM, 15, 1, 1), SOC_DAPM_SINGLE("Mixer", 0, 0, 0, 0), };
/* Record Mux 0 */ static const struct snd_kcontrol_new stac9766_record_mux_controls = SOC_DAPM_ENUM("Route", stac9766_enum[0]);
/* Mono Mux 1 */ static const struct snd_kcontrol_new stac9766_mono_mux_controls = SOC_DAPM_ENUM("Route", stac9766_enum[1]);
/* Mic1/2 Mux 2 */ static const struct snd_kcontrol_new stac9766_mic_mux_controls = SOC_DAPM_ENUM("Route", stac9766_enum[2]);
/* SPDIF Mux 3 */ static const struct snd_kcontrol_new stac9766_spdif_mux_controls = SOC_DAPM_ENUM("Route", stac9766_enum[3]);
/* Pop Bypass 4 */ static const struct snd_kcontrol_new stac9766_popbypass_mux_controls = SOC_DAPM_ENUM("Route", stac9766_enum[4]);
/* Record All 5 */ static const struct snd_kcontrol_new stac9766_record_all_mux_controls = SOC_DAPM_ENUM("Route", stac9766_enum[5]);
static const struct snd_soc_dapm_widget stac9766_dapm_widgets[] = { SND_SOC_DAPM_INPUT("PCMOut"), SND_SOC_DAPM_INPUT("PCBEEP"), SND_SOC_DAPM_INPUT("Phone"), SND_SOC_DAPM_INPUT("MIC1"), SND_SOC_DAPM_INPUT("MIC2"), SND_SOC_DAPM_INPUT("Line"), SND_SOC_DAPM_INPUT("CD"), SND_SOC_DAPM_INPUT("AUX"), SND_SOC_DAPM_INPUT("Video"), SND_SOC_DAPM_DAC("DAC", "Analog Playback", AC97_POWERDOWN, 9, 1), SND_SOC_DAPM_MUX("SPDIF Mux", SND_SOC_NOPM, 0, 0, &stac9766_spdif_mux_controls), SND_SOC_DAPM_MUX("Mic1/2 Mux", SND_SOC_NOPM, 0, 0, &stac9766_mic_mux_controls), SND_SOC_DAPM_MIXER("Mixer", SND_SOC_NOPM, 0, 1, &stac9766_main_mixer_controls[0], ARRAY_SIZE(stac9766_main_mixer_controls)), SND_SOC_DAPM_MUX("Record All Mux", SND_SOC_NOPM, 0, 0, &stac9766_record_all_mux_controls), SND_SOC_DAPM_MUX("Record Mux", SND_SOC_NOPM, 0, 0, &stac9766_record_mux_controls), SND_SOC_DAPM_MUX("Mono Mux", SND_SOC_NOPM, 0, 0, &stac9766_mono_mux_controls), SND_SOC_DAPM_MUX("Pop Bypass Mux", SND_SOC_NOPM, 0, 0, &stac9766_popbypass_mux_controls), SND_SOC_DAPM_MIXER("All Analog", SND_SOC_NOPM, 0, 1, &stac9766_record_all_mixer_controls[0], ARRAY_SIZE(stac9766_record_all_mixer_controls)), SND_SOC_DAPM_ADC("ADC", "Analog Capture", AC97_POWERDOWN, 8, 1), SND_SOC_DAPM_OUTPUT("HP"), SND_SOC_DAPM_OUTPUT("LINE"), SND_SOC_DAPM_OUTPUT("MONO"), SND_SOC_DAPM_OUTPUT("PCMIn"), SND_SOC_DAPM_VMID("VMID"), };
static const struct snd_soc_dapm_route audio_map[] = { {"HP", NULL, "Pop Bypass Mux"}, {"LINE", NULL, "Pop Bypass Mux"},
/* Mono Mux */ {"MONO", NULL, "Pop Bypass Mux"}, {"MONO", NULL, "Mic1/2 Mux"},
/* Pop Bypass Mux */ {"Pop Bypass Mux", NULL, "DAC"}, {"Pop Bypass Mux", NULL, "All Analog Mixer"},
/* Record Mux */ {"ADC", NULL, "Mic1/2 Mux"}, {"ADC", NULL, "CD"}, {"ADC", NULL, "Video"}, {"ADC", NULL, "AUX"}, {"ADC", NULL, "Line"}, {"ADC", NULL, "Record All Mux"}, {"ADC", NULL, "Phone"},
{"PCMIn", NULL, "ADC"},
/* Record All Mux */ {"Record All Mux", NULL, "Mixer"}, {"Record All Mux", NULL, "All Analog Mixer"},
/* All Analog Mixer */ {"All Analog", NULL, "Mixer"}, {"All Analog", "PCM Switch", "DAC"},
/* Mixer */ {"Mixer", "PC Beep Switch", "PCBEEP"}, {"Mixer", "Phone Switch", "Phone"}, {"Mixer", "Mic Switch", "Mic1/2 Mux"}, {"Mixer", "Line Switch", "Line"}, {"Mixer", "CD Switch", "CD"}, {"Mixer", "AUX Switch", "AUX"}, {"Mixer", "Video Switch", "Video"},
{"DAC", NULL, "PCMOut"},
/* Mic1/2 Mux */ {"Mic1/2 Mux", NULL, "MIC1"}, {"Mic1/2 Mux", NULL, "MIC2"},
/* SPDIF Mux */ {"SPDIF Mux", NULL, "PCMOut"}, {"SPDIF Mux", NULL, "ADC"},
{NULL, NULL, NULL}, };
static int stac9766_add_widgets(struct snd_soc_codec *codec) { snd_soc_dapm_new_controls(codec, stac9766_dapm_widgets, ARRAY_SIZE(stac9766_dapm_widgets));
snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
snd_soc_dapm_new_widgets(codec); return 0;
}
unsigned int stac9766_ac97_read(struct snd_soc_codec *codec, unsigned int reg) { u16 val = 0, *cache = codec->reg_cache;
if (reg / 2 > ARRAY_SIZE(stac9766_reg)) return -EIO;
if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || AC97_INT_PAGING || reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2) {
val = soc_ac97_ops.read(codec->ac97, reg); return val; } return cache[reg / 2]; }
int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) { u16 *cache = codec->reg_cache;
if (reg / 2 > ARRAY_SIZE(stac9766_reg)) return -EIO;
soc_ac97_ops.write(codec->ac97, reg, val); cache[reg / 2] = val; return 0; }
static int ac97_analog_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; struct snd_pcm_runtime *runtime = substream->runtime; unsigned short reg, vra;
vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
//vra |= 0x4;
stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
printk("AC97_EXTENDED_STATUS %x\n", vra);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) reg = AC97_PCM_FRONT_DAC_RATE; else reg = AC97_PCM_LR_ADC_RATE;
return stac9766_ac97_write(codec, reg, runtime->rate); }
static int ac97_digital_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { printk("stac9766: ac97_digital_prepare\n");
return 0; }
static int stac9766_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { switch (level) { case SND_SOC_BIAS_ON: /* full On */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); break; case SND_SOC_BIAS_PREPARE: /* partial On */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); break; case SND_SOC_BIAS_STANDBY: /* Off, with power */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); break; case SND_SOC_BIAS_OFF: /* Off, without power */ /* disable everything including AC link */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0xffff); break; } codec->bias_level = level; return 0; }
static int stac9766_codec_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec;
stac9766_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; }
static int stac9766_codec_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; u16 id;
/* give the codec an AC97 warm reset to start the link */ codec->ac97->bus->ops->warm_reset(codec->ac97); id = soc_ac97_ops.read(codec->ac97, AC97_VENDOR_ID2); if (id != 0x4c13) { printk(KERN_ERR "stac9766 failed to resume"); return -EIO; } stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
if (codec->suspend_bias_level == SND_SOC_BIAS_ON) stac9766_set_bias_level(codec, SND_SOC_BIAS_ON);
return 0; }
static struct snd_soc_dai_ops stac9766_dai_ops_analog = { .prepare = ac97_analog_prepare, };
static struct snd_soc_dai_ops stac9766_dai_ops_digital = { .prepare = ac97_digital_prepare, };
struct snd_soc_dai stac9766_dai[] = { { .name = "stac9766 analog", .id = 0, .ac97_control = 1,
/* stream cababilities */ .playback = { .stream_name = "stac9766 analog", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S32_BE, }, .capture = { .stream_name = "stac9766 analog", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S32_BE, }, /* alsa ops */ .ops = &stac9766_dai_ops_analog, }, { .name = "stac9766 digital", .id = 1, .ac97_control = 1,
/* stream cababilities */ .playback = { .stream_name = "stac9766 digital", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_32000 | \ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME, }, /* alsa ops */ .ops = &stac9766_dai_ops_digital, }}; EXPORT_SYMBOL_GPL(stac9766_dai);
int stac9766_reset(struct snd_soc_codec *codec, int try_warm) { if (try_warm && soc_ac97_ops.warm_reset) { soc_ac97_ops.warm_reset(codec->ac97); if (stac9766_ac97_read(codec, 0) == stac9766_reg[0]) return 1; }
soc_ac97_ops.reset(codec->ac97); if (soc_ac97_ops.warm_reset) soc_ac97_ops.warm_reset(codec->ac97); if (stac9766_ac97_read(codec, 0) != stac9766_reg[0]) return -EIO; return 0; }
static int stac9766_codec_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec; int ret = 0;
printk(KERN_INFO "STAC9766 SoC Audio Codec %s\n", STAC9766_VERSION);
socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (socdev->card->codec == NULL) return -ENOMEM; codec = socdev->card->codec; mutex_init(&codec->mutex);
codec->reg_cache = kmemdup(stac9766_reg, sizeof(stac9766_reg), GFP_KERNEL); if (codec->reg_cache == NULL) { ret = -ENOMEM; goto cache_err; } codec->reg_cache_size = sizeof(stac9766_reg); codec->reg_cache_step = 2;
codec->name = "STAC9766"; codec->owner = THIS_MODULE; codec->dai = stac9766_dai; codec->num_dai = ARRAY_SIZE(stac9766_dai); codec->write = stac9766_ac97_write; codec->read = stac9766_ac97_read; codec->set_bias_level = stac9766_set_bias_level; INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths);
ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); if (ret < 0) goto codec_err;
/* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) goto pcm_err;
/* do a cold reset for the controller and then try * a warm reset followed by an optional cold reset for codec */ stac9766_reset(codec, 0); ret = stac9766_reset(codec, 1); if (ret < 0) { printk(KERN_ERR "Failed to reset STAC9766: AC97 link error\n"); goto reset_err; }
stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
snd_soc_add_controls(codec, stac9766_snd_ac97_controls, ARRAY_SIZE(stac9766_snd_ac97_controls)); stac9766_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) goto reset_err; return 0;
reset_err: snd_soc_free_pcms(socdev);
pcm_err: snd_soc_free_ac97_codec(codec);
codec_err: kfree(codec->private_data);
cache_err: kfree(socdev->card->codec); socdev->card->codec = NULL; return ret; }
static int stac9766_codec_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec;
if (codec == NULL) return 0;
snd_soc_dapm_free(socdev); snd_soc_free_pcms(socdev); snd_soc_free_ac97_codec(codec); kfree(codec->reg_cache); kfree(codec); return 0; }
struct snd_soc_codec_device soc_codec_dev_stac9766 = { .probe = stac9766_codec_probe, .remove = stac9766_codec_remove, .suspend = stac9766_codec_suspend, .resume = stac9766_codec_resume, }; EXPORT_SYMBOL_GPL(soc_codec_dev_stac9766);
static int __init stac9766_probe(struct platform_device *pdev) { #if defined(CONFIG_SND_SOC_OF_SIMPLE) /* Tell the of_soc helper about this codec */ of_snd_soc_register_codec(&soc_codec_dev_stac9766, pdev->dev.archdata.of_node, stac9766_dai, ARRAY_SIZE(stac9766_dai), pdev->dev.archdata.of_node); #endif return 0; }
static struct platform_driver stac9766_driver = { .probe = stac9766_probe, .driver = { .name = "stac9766", }, };
static __init int stac9766_driver_init(void) { snd_soc_register_dais(stac9766_dai, ARRAY_SIZE(stac9766_dai)); return platform_driver_register(&stac9766_driver); }
static __exit void stac9766_driver_exit(void) { }
module_init(stac9766_driver_init); module_exit(stac9766_driver_exit);
MODULE_DESCRIPTION("ASoC stac9766 driver"); MODULE_AUTHOR("Jon Smirl jonsmirl@gmail.com"); MODULE_LICENSE("GPL");
I moved the switch definitions, I must not have DAPM set up right.
/* * stac9766.c -- ALSA Soc STAC9766 codec support * * Copyright 2008 Jon Smirl, Digispeaker * Author: Jon Smirl jonsmirl@gmail.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * Features:- * * o Support for AC97 Codec, S/PDIF * o Support for DAPM */
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/ac97_codec.h> #include <sound/initval.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/soc-of-simple.h>
#include "stac9766.h"
#define STAC9766_VERSION "0.10"
/* * STAC9766 register cache */ static const u16 stac9766_reg[] = { 0x6A90, 0x8000, 0x8000, 0x8000, // 6 0x0000, 0x0000, 0x8008, 0x8008, // e 0x8808, 0x8808, 0x8808, 0x8808, // 16 0x8808, 0x0000, 0x8000, 0x0000, // 1e 0x0000, 0x0000, 0x0000, 0x000f, // 26 0x0a05, 0x0400, 0xbb80, 0x0000, // 2e 0x0000, 0xbb80, 0x0000, 0x0000, // 36 0x0000, 0x2000, 0x0000, 0x0100, // 3e 0x0000, 0x0000, 0x0080, 0x0000, // 46 0x0000, 0x0000, 0x0003, 0xffff, // 4e 0x0000, 0x0000, 0x0000, 0x0000, // 56 0x4000, 0x0000, 0x0000, 0x0000, // 5e 0x1201, 0xFFFF, 0xFFFF, 0x0000, // 66 0x0000, 0x0000, 0x0000, 0x0000, // 6e 0x0000, 0x0000, 0x0000, 0x0006, // 76 0x0000, 0x0000, 0x0000, 0x0000, // 7e };
static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX", "Line", "Stereo Mix", "Mono Mix", "Phone"}; static const char *stac9766_mono_mux[] = {"Mix", "Mic"}; static const char *stac9766_mic_mux[] = {"Mic1", "Mic2"}; static const char *stac9766_SPDIF_mux[] = {"PCM", "ADC Record"}; static const char *stac9766_popbypass_mux[] = {"Normal", "Bypass Mixer"}; static const char *stac9766_record_all_mux[] = {"All analog", "Analog plus DAC"}; static const char *stac9766_3D_separation[] = {"Off", "Low", "Medium", "High"};
static const struct soc_enum stac9766_enum[] = { SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, stac9766_record_mux), /* Record Mux 0 */ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, stac9766_mono_mux), /* Mono Mux 1 */ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, stac9766_mic_mux), /* Mic1/2 Mux 2 */ SOC_ENUM_SINGLE(AC97_SENSE_INFO, 1, 2, stac9766_SPDIF_mux), /* SPDIF Mux 3 */ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, stac9766_popbypass_mux), /* Pop Bypass Mux 4 */ SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 12, 2, stac9766_record_all_mux), /* Record All Mux 5 */ SOC_ENUM_SINGLE(AC97_3D_CONTROL, 2, 4, stac9766_3D_separation), /* 3D Separation 7 */ };
static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = { SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1), SOC_SINGLE("Speaker Playback Switch", AC97_MASTER, 15, 1, 1), SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), SOC_SINGLE("Mixer PC Beep Volume", AC97_PC_BEEP, 1, 15, 1), SOC_SINGLE("Mixer PC Beep Switch", AC97_PC_BEEP, 15, 1, 1), SOC_SINGLE("PC Beep Frequency", AC97_PC_BEEP, 5, 127, 1), SOC_SINGLE("Mixer Phone Volume", AC97_PHONE, 0, 31, 1), SOC_SINGLE("Mixer Phone Switch", AC97_PHONE, 15, 1, 1), SOC_SINGLE("Mixer Mic Volume", AC97_MIC, 0, 31, 1), SOC_SINGLE("Mixer Mic Switch", AC97_MIC, 15, 1, 1), SOC_SINGLE("Mic Boost", AC97_MIC, 6, 1, 1), SOC_SINGLE("Mic Gain", AC97_STAC_ANALOG_SPECIAL, 2, 1, 1), SOC_SINGLE("Stereo Mic", AC97_STAC_STEREO_MIC, 0, 1, 1), SOC_DOUBLE("Mixer Line Volume", AC97_LINE, 8, 0, 31, 1), SOC_SINGLE("Mixer Line Switch", AC97_LINE, 15, 1, 1), SOC_DOUBLE("Mixer CD Volume", AC97_CD, 8, 0, 31, 1), SOC_SINGLE("Mixer CD Switch", AC97_CD, 15, 1, 1), SOC_DOUBLE("Mixer Video Volume", AC97_VIDEO, 8, 0, 31, 1), SOC_SINGLE("Mixer Video Switch", AC97_VIDEO, 15, 1, 1), SOC_DOUBLE("Mixer AUX Volume", AC97_AUX, 8, 0, 31, 1), SOC_SINGLE("Mixer AUX Switch", AC97_AUX, 15, 1, 1), SOC_DOUBLE("All Analog PCM Volume", AC97_PCM, 8, 0, 31, 1), SOC_SINGLE("All Analog PCM Switch", AC97_PCM, 15, 1, 1), SOC_DOUBLE("Record Gain", AC97_REC_GAIN, 8, 0, 31, 1), SOC_SINGLE("Record Gain Switch", AC97_REC_GAIN, 15, 1, 1), SOC_SINGLE("3D Effect Switch", AC97_GENERAL_PURPOSE, 13, 1, 0), SOC_SINGLE("Loopback Test Switch", AC97_GENERAL_PURPOSE, 7, 1, 0), SOC_ENUM("3D Separation", stac9766_enum[6]), };
/* Mixer */ static const struct snd_kcontrol_new stac9766_main_mixer_controls[] = { SOC_DAPM_SINGLE("PC Beep Switch", AC97_PC_BEEP, 15, 1, 1), SOC_DAPM_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1), SOC_DAPM_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1), SOC_DAPM_SINGLE("Line Switch", AC97_LINE, 15, 1, 1), SOC_DAPM_SINGLE("CD Switch", AC97_CD, 15, 1, 1), SOC_DAPM_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1), SOC_DAPM_SINGLE("Video Switch", AC97_VIDEO, 15, 1, 1), };
/* Record All Mixer */ static const struct snd_kcontrol_new stac9766_record_all_mixer_controls[] = { SOC_DAPM_SINGLE("PCM Switch", AC97_PCM, 15, 1, 1), SOC_DAPM_SINGLE("Mixer", 0, 0, 0, 0), };
/* Record Mux 0 */ static const struct snd_kcontrol_new stac9766_record_mux_controls = SOC_DAPM_ENUM("Route", stac9766_enum[0]);
/* Mono Mux 1 */ static const struct snd_kcontrol_new stac9766_mono_mux_controls = SOC_DAPM_ENUM("Route", stac9766_enum[1]);
/* Mic1/2 Mux 2 */ static const struct snd_kcontrol_new stac9766_mic_mux_controls = SOC_DAPM_ENUM("Route", stac9766_enum[2]);
/* SPDIF Mux 3 */ static const struct snd_kcontrol_new stac9766_spdif_mux_controls = SOC_DAPM_ENUM("Route", stac9766_enum[3]);
/* Pop Bypass 4 */ static const struct snd_kcontrol_new stac9766_popbypass_mux_controls = SOC_DAPM_ENUM("Route", stac9766_enum[4]);
/* Record All 5 */ static const struct snd_kcontrol_new stac9766_record_all_mux_controls = SOC_DAPM_ENUM("Route", stac9766_enum[5]);
static const struct snd_soc_dapm_widget stac9766_dapm_widgets[] = { SND_SOC_DAPM_INPUT("PCMOut"), SND_SOC_DAPM_INPUT("PCBEEP"), SND_SOC_DAPM_INPUT("Phone"), SND_SOC_DAPM_INPUT("MIC1"), SND_SOC_DAPM_INPUT("MIC2"), SND_SOC_DAPM_INPUT("Line"), SND_SOC_DAPM_INPUT("CD"), SND_SOC_DAPM_INPUT("AUX"), SND_SOC_DAPM_INPUT("Video"), SND_SOC_DAPM_DAC("DAC", "Analog Playback", AC97_POWERDOWN, 9, 1), SND_SOC_DAPM_MUX("SPDIF Mux", SND_SOC_NOPM, 0, 0, &stac9766_spdif_mux_controls), SND_SOC_DAPM_MUX("Mic1/2 Mux", SND_SOC_NOPM, 0, 0, &stac9766_mic_mux_controls), SND_SOC_DAPM_MIXER("Mixer", SND_SOC_NOPM, 0, 1, &stac9766_main_mixer_controls[0], ARRAY_SIZE(stac9766_main_mixer_controls)), SND_SOC_DAPM_MUX("Record All Mux", SND_SOC_NOPM, 0, 0, &stac9766_record_all_mux_controls), SND_SOC_DAPM_MUX("Record Mux", SND_SOC_NOPM, 0, 0, &stac9766_record_mux_controls), SND_SOC_DAPM_MUX("Mono Mux", SND_SOC_NOPM, 0, 0, &stac9766_mono_mux_controls), SND_SOC_DAPM_MUX("Pop Bypass Mux", SND_SOC_NOPM, 0, 0, &stac9766_popbypass_mux_controls), SND_SOC_DAPM_MIXER("All Analog", SND_SOC_NOPM, 0, 1, &stac9766_record_all_mixer_controls[0], ARRAY_SIZE(stac9766_record_all_mixer_controls)), SND_SOC_DAPM_ADC("ADC", "Analog Capture", AC97_POWERDOWN, 8, 1), SND_SOC_DAPM_OUTPUT("HP"), SND_SOC_DAPM_OUTPUT("LINE"), SND_SOC_DAPM_OUTPUT("MONO"), SND_SOC_DAPM_OUTPUT("PCMIn"), SND_SOC_DAPM_VMID("VMID"), };
static const struct snd_soc_dapm_route audio_map[] = { {"HP", NULL, "Pop Bypass Mux"}, {"LINE", NULL, "Pop Bypass Mux"},
/* Mono Mux */ {"MONO", NULL, "Pop Bypass Mux"}, {"MONO", NULL, "Mic1/2 Mux"},
/* Pop Bypass Mux */ {"Pop Bypass Mux", NULL, "DAC"}, {"Pop Bypass Mux", NULL, "All Analog Mixer"},
/* Record Mux */ {"ADC", NULL, "Mic1/2 Mux"}, {"ADC", NULL, "CD"}, {"ADC", NULL, "Video"}, {"ADC", NULL, "AUX"}, {"ADC", NULL, "Line"}, {"ADC", NULL, "Record All Mux"}, {"ADC", NULL, "Phone"},
{"PCMIn", NULL, "ADC"},
/* Record All Mux */ {"Record All Mux", NULL, "Mixer"}, {"Record All Mux", NULL, "All Analog Mixer"},
/* All Analog Mixer */ {"All Analog", NULL, "Mixer"}, {"All Analog", "PCM Switch", "DAC"},
/* Mixer */ {"Mixer", "PC Beep Switch", "PCBEEP"}, {"Mixer", "Phone Switch", "Phone"}, {"Mixer", "Mic Switch", "Mic1/2 Mux"}, {"Mixer", "Line Switch", "Line"}, {"Mixer", "CD Switch", "CD"}, {"Mixer", "AUX Switch", "AUX"}, {"Mixer", "Video Switch", "Video"},
{"DAC", NULL, "PCMOut"},
/* Mic1/2 Mux */ {"Mic1/2 Mux", NULL, "MIC1"}, {"Mic1/2 Mux", NULL, "MIC2"},
/* SPDIF Mux */ {"SPDIF Mux", NULL, "PCMOut"}, {"SPDIF Mux", NULL, "ADC"},
{NULL, NULL, NULL}, };
static int stac9766_add_widgets(struct snd_soc_codec *codec) { snd_soc_dapm_new_controls(codec, stac9766_dapm_widgets, ARRAY_SIZE(stac9766_dapm_widgets));
snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
snd_soc_dapm_new_widgets(codec); return 0;
}
unsigned int stac9766_ac97_read(struct snd_soc_codec *codec, unsigned int reg) { u16 val = 0, *cache = codec->reg_cache;
if (reg / 2 > ARRAY_SIZE(stac9766_reg)) return -EIO;
if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || AC97_INT_PAGING || reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2) {
val = soc_ac97_ops.read(codec->ac97, reg); return val; } return cache[reg / 2]; }
int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) { u16 *cache = codec->reg_cache;
if (reg / 2 > ARRAY_SIZE(stac9766_reg)) return -EIO;
soc_ac97_ops.write(codec->ac97, reg, val); cache[reg / 2] = val; return 0; }
static int ac97_analog_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; struct snd_pcm_runtime *runtime = substream->runtime; unsigned short reg, vra;
vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
//vra |= 0x4;
stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
printk("AC97_EXTENDED_STATUS %x\n", vra);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) reg = AC97_PCM_FRONT_DAC_RATE; else reg = AC97_PCM_LR_ADC_RATE;
return stac9766_ac97_write(codec, reg, runtime->rate); }
static int ac97_digital_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { printk("stac9766: ac97_digital_prepare\n");
return 0; }
static int stac9766_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { switch (level) { case SND_SOC_BIAS_ON: /* full On */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); break; case SND_SOC_BIAS_PREPARE: /* partial On */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); break; case SND_SOC_BIAS_STANDBY: /* Off, with power */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); break; case SND_SOC_BIAS_OFF: /* Off, without power */ /* disable everything including AC link */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0xffff); break; } codec->bias_level = level; return 0; }
static int stac9766_codec_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec;
stac9766_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; }
static int stac9766_codec_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; u16 id;
/* give the codec an AC97 warm reset to start the link */ codec->ac97->bus->ops->warm_reset(codec->ac97); id = soc_ac97_ops.read(codec->ac97, AC97_VENDOR_ID2); if (id != 0x4c13) { printk(KERN_ERR "stac9766 failed to resume"); return -EIO; } stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
if (codec->suspend_bias_level == SND_SOC_BIAS_ON) stac9766_set_bias_level(codec, SND_SOC_BIAS_ON);
return 0; }
static struct snd_soc_dai_ops stac9766_dai_ops_analog = { .prepare = ac97_analog_prepare, };
static struct snd_soc_dai_ops stac9766_dai_ops_digital = { .prepare = ac97_digital_prepare, };
struct snd_soc_dai stac9766_dai[] = { { .name = "stac9766 analog", .id = 0, .ac97_control = 1,
/* stream cababilities */ .playback = { .stream_name = "stac9766 analog", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S32_BE, }, .capture = { .stream_name = "stac9766 analog", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S32_BE, }, /* alsa ops */ .ops = &stac9766_dai_ops_analog, }, { .name = "stac9766 digital", .id = 1, .ac97_control = 1,
/* stream cababilities */ .playback = { .stream_name = "stac9766 digital", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_32000 | \ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME, }, /* alsa ops */ .ops = &stac9766_dai_ops_digital, }}; EXPORT_SYMBOL_GPL(stac9766_dai);
int stac9766_reset(struct snd_soc_codec *codec, int try_warm) { if (try_warm && soc_ac97_ops.warm_reset) { soc_ac97_ops.warm_reset(codec->ac97); if (stac9766_ac97_read(codec, 0) == stac9766_reg[0]) return 1; }
soc_ac97_ops.reset(codec->ac97); if (soc_ac97_ops.warm_reset) soc_ac97_ops.warm_reset(codec->ac97); if (stac9766_ac97_read(codec, 0) != stac9766_reg[0]) return -EIO; return 0; }
static int stac9766_codec_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec; int ret = 0;
printk(KERN_INFO "STAC9766 SoC Audio Codec %s\n", STAC9766_VERSION);
socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (socdev->card->codec == NULL) return -ENOMEM; codec = socdev->card->codec; mutex_init(&codec->mutex);
codec->reg_cache = kmemdup(stac9766_reg, sizeof(stac9766_reg), GFP_KERNEL); if (codec->reg_cache == NULL) { ret = -ENOMEM; goto cache_err; } codec->reg_cache_size = sizeof(stac9766_reg); codec->reg_cache_step = 2;
codec->name = "STAC9766"; codec->owner = THIS_MODULE; codec->dai = stac9766_dai; codec->num_dai = ARRAY_SIZE(stac9766_dai); codec->write = stac9766_ac97_write; codec->read = stac9766_ac97_read; codec->set_bias_level = stac9766_set_bias_level; INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths);
ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); if (ret < 0) goto codec_err;
/* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) goto pcm_err;
/* do a cold reset for the controller and then try * a warm reset followed by an optional cold reset for codec */ stac9766_reset(codec, 0); ret = stac9766_reset(codec, 1); if (ret < 0) { printk(KERN_ERR "Failed to reset STAC9766: AC97 link error\n"); goto reset_err; }
stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
snd_soc_add_controls(codec, stac9766_snd_ac97_controls, ARRAY_SIZE(stac9766_snd_ac97_controls)); stac9766_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) goto reset_err; return 0;
reset_err: snd_soc_free_pcms(socdev);
pcm_err: snd_soc_free_ac97_codec(codec);
codec_err: kfree(codec->private_data);
cache_err: kfree(socdev->card->codec); socdev->card->codec = NULL; return ret; }
static int stac9766_codec_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec;
if (codec == NULL) return 0;
snd_soc_dapm_free(socdev); snd_soc_free_pcms(socdev); snd_soc_free_ac97_codec(codec); kfree(codec->reg_cache); kfree(codec); return 0; }
struct snd_soc_codec_device soc_codec_dev_stac9766 = { .probe = stac9766_codec_probe, .remove = stac9766_codec_remove, .suspend = stac9766_codec_suspend, .resume = stac9766_codec_resume, }; EXPORT_SYMBOL_GPL(soc_codec_dev_stac9766);
static int __init stac9766_probe(struct platform_device *pdev) { #if defined(CONFIG_SND_SOC_OF_SIMPLE) /* Tell the of_soc helper about this codec */ of_snd_soc_register_codec(&soc_codec_dev_stac9766, pdev->dev.archdata.of_node, stac9766_dai, ARRAY_SIZE(stac9766_dai), pdev->dev.archdata.of_node); #endif return 0; }
static struct platform_driver stac9766_driver = { .probe = stac9766_probe, .driver = { .name = "stac9766", }, };
static __init int stac9766_driver_init(void) { snd_soc_register_dais(stac9766_dai, ARRAY_SIZE(stac9766_dai)); return platform_driver_register(&stac9766_driver); }
static __exit void stac9766_driver_exit(void) { }
module_init(stac9766_driver_init); module_exit(stac9766_driver_exit);
MODULE_DESCRIPTION("ASoC stac9766 driver"); MODULE_AUTHOR("Jon Smirl jonsmirl@gmail.com"); MODULE_LICENSE("GPL");
On Thu, Apr 30, 2009 at 04:30:12PM -0400, Jon Smirl wrote:
Could you please review this new STAC9766 codec implementation. Some
Please do CC me on ASoC patches; I've asked you to do this several times before and it's standard practice for kernel patch submissions to CC the relevant maintainers.
Overall the driver looks fairly good, just needs a bit of tidying up.
of the controls aren't where they need to be. For example the mutes on the mixer inputs. I'm not sure where they are supposed to go.
Could you be more specific? Generally the mute would be used as a switch and appear in the list of controls passed to the mixer.
How should SPDIF support work? The hardware supports simultaneous use of analog and SPDIF. Do I want one DAI or two? If I have two DAI
Having taken a step back and looked at the datasheet what's going on here is that the CODEC has a S/PDIF output which can take either an AC97 timeslot or the output of the ADC as input. The AC97 timeslot input should probably be represented as a DAI, feeding into a mux selecting the input to be output to S/PDIF.
then ALSA needs to interleave the samples into a single DMA stream. So if one stream isn't active it needs to be filled with silence.
The AC97 controller driver should be able to expose multiple data streams - normally the hardware has direct support for this.
I get a few errors from the current config: Failed to add route All Analog Mixer->Pop Bypass Mux
You've called the mixer both "All Analog Mixer" and "All Analog".
dapm: STAC9766: configuring unknown pin OUT3 dapm: STAC9766: configuring unknown pin MONOOUT Failed to add route HPOUTL->Headphone Jack dapm: STAC9766: configuring unknown pin OUT3 dapm: STAC9766: configuring unknown pin MONOOUT Failed to add route HPOUTL->Headphone Jack
For the rest I'd suggest checking your machine driver; there's no references to any OUT3, HPOUTL or MONOOUT pins in your driver and Headphone Jack should definitely be part of the machine driver.
/*
- stac9766.c -- ALSA Soc STAC9766 codec support
SoC.
- Copyright 2008 Jon Smirl, Digispeaker
2009, I expect.
static const u16 stac9766_reg[] = { 0x6A90, 0x8000, 0x8000, 0x8000, // 6
No C++ comments, please.
static const struct soc_enum stac9766_enum[] = { SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, stac9766_record_mux), /* Record Mux 0 */
Please define individual variables rather than using a table like this - it does nothing for legibility to have to use array indexes to refer to the enums. Some older drivers do this as a result of being converted from pre-ASoC driers where it made more sense to have the table.
SOC_SINGLE("Mixer PC Beep Volume", AC97_PC_BEEP, 1, 15, 1),
For the "Mixer x Volume" controls you should change the name so that it lines up with the names that are generated for the mixer switches. This will allow ALSA to present them as a single control to users, making UIs work better. You want the control names to be the same apart from the final "Switch" or "Volume".
It'd also be worth considering adding TLV information to the volume controls if the gains are documented, but it's not essential.
SOC_SINGLE("Mic Gain", AC97_STAC_ANALOG_SPECIAL, 2, 1, 1),
Mic Volume, probably.
SOC_DOUBLE("Record Gain", AC97_REC_GAIN, 8, 0, 31, 1),
Volume, again - gain and attenuation are both just volumes.
static const struct snd_soc_dapm_widget stac9766_dapm_widgets[] = { SND_SOC_DAPM_INPUT("PCMOut"),
If my brief glance at the datasheet is accurate this is actually your AC97 DAI and shouldn't be a pin.
SND_SOC_DAPM_ADC("ADC", "Analog Capture", AC97_POWERDOWN, 8, 1),
SND_SOC_DAPM_OUTPUT("PCMIn"),
This is the AC97 DAI too.
/* Record Mux */ {"ADC", NULL, "Mic1/2 Mux"}, {"ADC", NULL, "CD"}, {"ADC", NULL, "Video"}, {"ADC", NULL, "AUX"}, {"ADC", NULL, "Line"}, {"ADC", NULL, "Record All Mux"}, {"ADC", NULL, "Phone"},
You should be putting the controls from the mux in rather the NULLs here - at the minute it looks to DAPM like all the paths are permanantly connected.
{NULL, NULL, NULL},
This isn't required.
val = soc_ac97_ops.read(codec->ac97, reg); return val;
} return cache[reg / 2];
Indentation.
static int ac97_analog_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; struct snd_pcm_runtime *runtime = substream->runtime; unsigned short reg, vra;
vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
//vra |= 0x4;
Either this should go or it should be uncommented.
static int ac97_digital_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { printk("stac9766: ac97_digital_prepare\n");
return 0; }
This function can just be removed.
static int stac9766_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { switch (level) { case SND_SOC_BIAS_ON: /* full On */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); break; case SND_SOC_BIAS_PREPARE: /* partial On */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); break;
These should be noops if you're using DAPM.
.playback = { .stream_name = "stac9766 analog", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S32_BE,
Are you sure about this? AC97 generally does 16 bit samples...
static int __init stac9766_probe(struct platform_device *pdev)
__devinit.
static __init int stac9766_driver_init(void) { snd_soc_register_dais(stac9766_dai, ARRAY_SIZE(stac9766_dai));
This should be done when your driver probes - it is only done on module init for drivers which are not able to probe through normal methods.
With this version, why are these errors generated?
ALSA sound/core/control.c:334: control 2:0:0:All Analog PCM Switch:0 is already present asoc: failed to add dapm kcontrol All Analog PCM Switch ALSA sound/core/control.c:334: control 2:0:0:Mixer PC Beep Switch:0 is already present asoc: failed to add dapm kcontrol Mixer PC Beep Switch
Or is the right question, why didn't "Mixer AUX Switch" cause an error?
/* * stac9766.c -- ALSA SoC STAC9766 codec support * * Copyright 2009 Jon Smirl, Digispeaker * Author: Jon Smirl jonsmirl@gmail.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * Features:- * * o Support for AC97 Codec, S/PDIF * o Support for DAPM */
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/ac97_codec.h> #include <sound/initval.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/soc-of-simple.h>
#include "stac9766.h"
#define STAC9766_VERSION "0.10"
/* * STAC9766 register cache */ static const u16 stac9766_reg[] = { 0x6A90, 0x8000, 0x8000, 0x8000, /* 6 */ 0x0000, 0x0000, 0x8008, 0x8008, /* e */ 0x8808, 0x8808, 0x8808, 0x8808, /* 16 */ 0x8808, 0x0000, 0x8000, 0x0000, /* 1e */ 0x0000, 0x0000, 0x0000, 0x000f, /* 26 */ 0x0a05, 0x0400, 0xbb80, 0x0000, /* 2e */ 0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */ 0x0000, 0x2000, 0x0000, 0x0100, /* 3e */ 0x0000, 0x0000, 0x0080, 0x0000, /* 46 */ 0x0000, 0x0000, 0x0003, 0xffff, /* 4e */ 0x0000, 0x0000, 0x0000, 0x0000, /* 56 */ 0x4000, 0x0000, 0x0000, 0x0000, /* 5e */ 0x1201, 0xFFFF, 0xFFFF, 0x0000, /* 66 */ 0x0000, 0x0000, 0x0000, 0x0000, /* 6e */ 0x0000, 0x0000, 0x0000, 0x0006, /* 76 */ 0x0000, 0x0000, 0x0000, 0x0000, /* 7e */ };
static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX", "Line", "Stereo Mix", "Mono Mix", "Phone"}; static const char *stac9766_mono_mux[] = {"Mix", "Mic"}; static const char *stac9766_mic_mux[] = {"Mic1", "Mic2"}; static const char *stac9766_SPDIF_mux[] = {"PCM", "ADC Record"}; static const char *stac9766_popbypass_mux[] = {"Normal", "Bypass Mixer"}; static const char *stac9766_record_all_mux[] = {"All analog", "Analog plus DAC"}; static const char *stac9766_3D_separation[] = {"Off", "Low", "Medium", "High"};
static const struct soc_enum stac9766_record_enum = SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, stac9766_record_mux); /* Record Mux 0 */ static const struct soc_enum stac9766_mono_enum = SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, stac9766_mono_mux); /* Mono Mux 1 */ static const struct soc_enum stac9766_mic_enum = SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, stac9766_mic_mux); /* Mic1/2 Mux 2 */ static const struct soc_enum stac9766_SPDIF_enum = SOC_ENUM_SINGLE(AC97_SENSE_INFO, 1, 2, stac9766_SPDIF_mux); /* SPDIF Mux 3 */ static const struct soc_enum stac9766_popbypass_enum = SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, stac9766_popbypass_mux); /* Pop Bypass Mux 4 */ static const struct soc_enum stac9766_record_all_enum = SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 12, 2, stac9766_record_all_mux); /* Record All Mux 5 */ static const struct soc_enum stac9766_3D_seperation_enum = SOC_ENUM_SINGLE(AC97_3D_CONTROL, 2, 4, stac9766_3D_separation); /* 3D Separation 7 */
static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = { SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1), SOC_SINGLE("Speaker Playback Switch", AC97_MASTER, 15, 1, 1), SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), SOC_SINGLE("Mixer PC Beep Volume", AC97_PC_BEEP, 1, 15, 1), SOC_SINGLE("Mixer PC Beep Switch", AC97_PC_BEEP, 15, 1, 1), SOC_SINGLE("PC Beep Frequency", AC97_PC_BEEP, 5, 127, 1), SOC_SINGLE("Mixer Phone Volume", AC97_PHONE, 0, 31, 1), SOC_SINGLE("Mixer Phone Switch", AC97_PHONE, 15, 1, 1), SOC_SINGLE("Mixer Mic Volume", AC97_MIC, 0, 31, 1), SOC_SINGLE("Mixer Mic Switch", AC97_MIC, 15, 1, 1), SOC_SINGLE("Mic Boost", AC97_MIC, 6, 1, 1), SOC_SINGLE("Mic Gain", AC97_STAC_ANALOG_SPECIAL, 2, 1, 1), SOC_SINGLE("Stereo Mic", AC97_STAC_STEREO_MIC, 0, 1, 1), SOC_DOUBLE("Mixer Line Volume", AC97_LINE, 8, 0, 31, 1), SOC_SINGLE("Mixer Line Switch", AC97_LINE, 15, 1, 1), SOC_DOUBLE("Mixer CD Volume", AC97_CD, 8, 0, 31, 1), SOC_SINGLE("Mixer CD Switch", AC97_CD, 15, 1, 1), SOC_DOUBLE("Mixer Video Volume", AC97_VIDEO, 8, 0, 31, 1), SOC_SINGLE("Mixer Video Switch", AC97_VIDEO, 15, 1, 1), SOC_DOUBLE("Mixer AUX Volume", AC97_AUX, 8, 0, 31, 1), SOC_SINGLE("Mixer AUX Switch", AC97_AUX, 15, 1, 1), SOC_DOUBLE("All Analog PCM Volume", AC97_PCM, 8, 0, 31, 1), SOC_SINGLE("All Analog PCM Switch", AC97_PCM, 15, 1, 1), SOC_DOUBLE("Record Gain", AC97_REC_GAIN, 8, 0, 31, 1), SOC_SINGLE("Record Gain Switch", AC97_REC_GAIN, 15, 1, 1), SOC_SINGLE("3D Effect Switch", AC97_GENERAL_PURPOSE, 13, 1, 0), SOC_SINGLE("Loopback Test Switch", AC97_GENERAL_PURPOSE, 7, 1, 0), SOC_ENUM("3D Separation", stac9766_3D_seperation_enum), };
/* Mixer */ static const struct snd_kcontrol_new stac9766_main_mixer_controls[] = { SOC_DAPM_SINGLE("PC Beep Switch", AC97_PC_BEEP, 15, 1, 1), SOC_DAPM_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1), SOC_DAPM_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1), SOC_DAPM_SINGLE("Line Switch", AC97_LINE, 15, 1, 1), SOC_DAPM_SINGLE("CD Switch", AC97_CD, 15, 1, 1), SOC_DAPM_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1), SOC_DAPM_SINGLE("Video Switch", AC97_VIDEO, 15, 1, 1), };
/* Record All Mixer */ static const struct snd_kcontrol_new stac9766_record_all_mixer_controls[] = { SOC_DAPM_SINGLE("PCM Switch", AC97_PCM, 15, 1, 1), SOC_DAPM_SINGLE("Mixer", 0, 0, 0, 0), };
/* Record Mux 0 */ static const struct snd_kcontrol_new stac9766_record_mux_controls = SOC_DAPM_ENUM("Route", stac9766_record_enum);
/* Mono Mux 1 */ static const struct snd_kcontrol_new stac9766_mono_mux_controls = SOC_DAPM_ENUM("Route", stac9766_mono_enum);
/* Mic1/2 Mux 2 */ static const struct snd_kcontrol_new stac9766_mic_mux_controls = SOC_DAPM_ENUM("Route", stac9766_mic_enum);
/* SPDIF Mux 3 */ static const struct snd_kcontrol_new stac9766_spdif_mux_controls = SOC_DAPM_ENUM("Route", stac9766_SPDIF_enum);
/* Pop Bypass 4 */ static const struct snd_kcontrol_new stac9766_popbypass_mux_controls = SOC_DAPM_ENUM("Route", stac9766_popbypass_enum);
/* Record All 5 */ static const struct snd_kcontrol_new stac9766_record_all_mux_controls = SOC_DAPM_ENUM("Route", stac9766_record_all_enum);
static const struct snd_soc_dapm_widget stac9766_dapm_widgets[] = { SND_SOC_DAPM_INPUT("PCMOut"), SND_SOC_DAPM_INPUT("PCBEEP"), SND_SOC_DAPM_INPUT("Phone"), SND_SOC_DAPM_INPUT("MIC1"), SND_SOC_DAPM_INPUT("MIC2"), SND_SOC_DAPM_INPUT("Line"), SND_SOC_DAPM_INPUT("CD"), SND_SOC_DAPM_INPUT("AUX"), SND_SOC_DAPM_INPUT("Video"), SND_SOC_DAPM_DAC("DAC", "Analog Playback", AC97_POWERDOWN, 9, 1), SND_SOC_DAPM_MUX("SPDIF Mux", SND_SOC_NOPM, 0, 0, &stac9766_spdif_mux_controls), SND_SOC_DAPM_MUX("Mic1/2 Mux", SND_SOC_NOPM, 0, 0, &stac9766_mic_mux_controls), SND_SOC_DAPM_MIXER("Mixer", SND_SOC_NOPM, 0, 1, &stac9766_main_mixer_controls[0], ARRAY_SIZE(stac9766_main_mixer_controls)), SND_SOC_DAPM_MUX("Record All Mux", SND_SOC_NOPM, 0, 0, &stac9766_record_all_mux_controls), SND_SOC_DAPM_MUX("Record Mux", SND_SOC_NOPM, 0, 0, &stac9766_record_mux_controls), SND_SOC_DAPM_MUX("Mono Mux", SND_SOC_NOPM, 0, 0, &stac9766_mono_mux_controls), SND_SOC_DAPM_MUX("Pop Bypass Mux", SND_SOC_NOPM, 0, 0, &stac9766_popbypass_mux_controls), SND_SOC_DAPM_MIXER("All Analog", SND_SOC_NOPM, 0, 1, &stac9766_record_all_mixer_controls[0], ARRAY_SIZE(stac9766_record_all_mixer_controls)), SND_SOC_DAPM_ADC("ADC", "Analog Capture", AC97_POWERDOWN, 8, 1), SND_SOC_DAPM_OUTPUT("HP"), SND_SOC_DAPM_OUTPUT("LINE"), SND_SOC_DAPM_OUTPUT("MONO"), SND_SOC_DAPM_OUTPUT("PCMIn"), SND_SOC_DAPM_VMID("VMID"), };
static const struct snd_soc_dapm_route audio_map[] = { {"HP", NULL, "Pop Bypass Mux"}, {"LINE", NULL, "Pop Bypass Mux"},
/* Mono Mux */ {"MONO", NULL, "Pop Bypass Mux"}, {"MONO", NULL, "Mic1/2 Mux"},
/* Pop Bypass Mux */ {"Pop Bypass Mux", NULL, "DAC"}, {"Pop Bypass Mux", NULL, "All Analog"},
/* Record Mux */ {"ADC", NULL, "Mic1/2 Mux"}, {"ADC", NULL, "CD"}, {"ADC", NULL, "Video"}, {"ADC", NULL, "AUX"}, {"ADC", NULL, "Line"}, {"ADC", NULL, "Record All Mux"}, {"ADC", NULL, "Phone"},
{"PCMIn", NULL, "ADC"},
/* Record All Mux */ {"Record All Mux", NULL, "Mixer"}, {"Record All Mux", NULL, "All Analog"},
/* All Analog Mixer */ {"All Analog", NULL, "Mixer"}, {"All Analog", "PCM Switch", "DAC"},
/* Mixer */ {"Mixer", "PC Beep Switch", "PCBEEP"}, {"Mixer", "Phone Switch", "Phone"}, {"Mixer", "Mic Switch", "Mic1/2 Mux"}, {"Mixer", "Line Switch", "Line"}, {"Mixer", "CD Switch", "CD"}, {"Mixer", "AUX Switch", "AUX"}, {"Mixer", "Video Switch", "Video"},
{"DAC", NULL, "PCMOut"},
/* Mic1/2 Mux */ {"Mic1/2 Mux", NULL, "MIC1"}, {"Mic1/2 Mux", NULL, "MIC2"},
/* SPDIF Mux */ {"SPDIF Mux", NULL, "PCMOut"}, {"SPDIF Mux", NULL, "ADC"}, };
static int stac9766_add_widgets(struct snd_soc_codec *codec) { snd_soc_dapm_new_controls(codec, stac9766_dapm_widgets, ARRAY_SIZE( stac9766_dapm_widgets));
snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
snd_soc_dapm_new_widgets(codec); return 0; }
unsigned int stac9766_ac97_read(struct snd_soc_codec *codec, unsigned int reg) { u16 val = 0, *cache = codec->reg_cache;
if (reg / 2 > ARRAY_SIZE(stac9766_reg)) return -EIO;
if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || AC97_INT_PAGING || reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2) {
val = soc_ac97_ops.read(codec->ac97, reg); return val; } return cache[reg / 2]; }
int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) { u16 *cache = codec->reg_cache;
if (reg / 2 > ARRAY_SIZE(stac9766_reg)) return -EIO;
soc_ac97_ops.write(codec->ac97, reg, val); cache[reg / 2] = val; return 0; }
static int ac97_analog_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; struct snd_pcm_runtime *runtime = substream->runtime; unsigned short reg, vra;
vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
//vra |= 0x4;
stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
printk("AC97_EXTENDED_STATUS %x\n", vra);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) reg = AC97_PCM_FRONT_DAC_RATE; else reg = AC97_PCM_LR_ADC_RATE;
return stac9766_ac97_write(codec, reg, runtime->rate); }
static int ac97_digital_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { printk("stac9766: ac97_digital_prepare\n");
return 0; }
static int stac9766_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { switch (level) { case SND_SOC_BIAS_ON: /* full On */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); break; case SND_SOC_BIAS_PREPARE: /* partial On */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); break; case SND_SOC_BIAS_STANDBY: /* Off, with power */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); break; case SND_SOC_BIAS_OFF: /* Off, without power */ /* disable everything including AC link */ stac9766_ac97_write(codec, AC97_POWERDOWN, 0xffff); break; } codec->bias_level = level; return 0; }
static int stac9766_codec_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec;
stac9766_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; }
static int stac9766_codec_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; u16 id;
/* give the codec an AC97 warm reset to start the link */ codec->ac97->bus->ops->warm_reset(codec->ac97); id = soc_ac97_ops.read(codec->ac97, AC97_VENDOR_ID2); if (id != 0x4c13) { printk(KERN_ERR "stac9766 failed to resume"); return -EIO; } stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
if (codec->suspend_bias_level == SND_SOC_BIAS_ON) stac9766_set_bias_level(codec, SND_SOC_BIAS_ON);
return 0; }
static struct snd_soc_dai_ops stac9766_dai_ops_analog = { .prepare = ac97_analog_prepare, };
static struct snd_soc_dai_ops stac9766_dai_ops_digital = { .prepare = ac97_digital_prepare, };
struct snd_soc_dai stac9766_dai[] = { { .name = "stac9766 analog", .id = 0, .ac97_control = 1,
/* stream cababilities */ .playback = { .stream_name = "stac9766 analog", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S32_BE, }, .capture = { .stream_name = "stac9766 analog", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S32_BE, }, /* alsa ops */ .ops = &stac9766_dai_ops_analog, }, { .name = "stac9766 digital", .id = 1, .ac97_control = 1,
/* stream cababilities */ .playback = { .stream_name = "stac9766 digital", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME, }, /* alsa ops */ .ops = &stac9766_dai_ops_digital, }}; EXPORT_SYMBOL_GPL(stac9766_dai);
int stac9766_reset(struct snd_soc_codec *codec, int try_warm) { if (try_warm && soc_ac97_ops.warm_reset) { soc_ac97_ops.warm_reset(codec->ac97); if (stac9766_ac97_read(codec, 0) == stac9766_reg[0]) return 1; }
soc_ac97_ops.reset(codec->ac97); if (soc_ac97_ops.warm_reset) soc_ac97_ops.warm_reset(codec->ac97); if (stac9766_ac97_read(codec, 0) != stac9766_reg[0]) return -EIO; return 0; }
static int stac9766_codec_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec; int ret = 0;
printk(KERN_INFO "STAC9766 SoC Audio Codec %s\n", STAC9766_VERSION);
socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (socdev->card->codec == NULL) return -ENOMEM; codec = socdev->card->codec; mutex_init(&codec->mutex);
codec->reg_cache = kmemdup(stac9766_reg, sizeof(stac9766_reg), GFP_KERNEL); if (codec->reg_cache == NULL) { ret = -ENOMEM; goto cache_err; } codec->reg_cache_size = sizeof(stac9766_reg); codec->reg_cache_step = 2;
codec->name = "STAC9766"; codec->owner = THIS_MODULE; codec->dai = stac9766_dai; codec->num_dai = ARRAY_SIZE(stac9766_dai); codec->write = stac9766_ac97_write; codec->read = stac9766_ac97_read; codec->set_bias_level = stac9766_set_bias_level; INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths);
ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); if (ret < 0) goto codec_err;
/* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) goto pcm_err;
/* do a cold reset for the controller and then try * a warm reset followed by an optional cold reset for codec */ stac9766_reset(codec, 0); ret = stac9766_reset(codec, 1); if (ret < 0) { printk(KERN_ERR "Failed to reset STAC9766: AC97 link error\n"); goto reset_err; }
stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
snd_soc_add_controls(codec, stac9766_snd_ac97_controls, ARRAY_SIZE( stac9766_snd_ac97_controls)); stac9766_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) goto reset_err; return 0;
reset_err: snd_soc_free_pcms(socdev); pcm_err: snd_soc_free_ac97_codec(codec); codec_err: kfree(codec->private_data); cache_err: kfree(socdev->card->codec); socdev->card->codec = NULL; return ret; }
static int stac9766_codec_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec;
if (codec == NULL) return 0;
snd_soc_dapm_free(socdev); snd_soc_free_pcms(socdev); snd_soc_free_ac97_codec(codec); kfree(codec->reg_cache); kfree(codec); return 0; }
struct snd_soc_codec_device soc_codec_dev_stac9766 = { .probe = stac9766_codec_probe, .remove = stac9766_codec_remove, .suspend = stac9766_codec_suspend, .resume = stac9766_codec_resume, }; EXPORT_SYMBOL_GPL(soc_codec_dev_stac9766);
static int __init stac9766_probe(struct platform_device *pdev) { snd_soc_register_dais(stac9766_dai, ARRAY_SIZE(stac9766_dai)); #if defined(CONFIG_SND_SOC_OF_SIMPLE) /* Tell the of_soc helper about this codec */ of_snd_soc_register_codec(&soc_codec_dev_stac9766, pdev->dev.archdata.of_node, stac9766_dai, ARRAY_SIZE(stac9766_dai), pdev->dev.archdata.of_node); #endif return 0; }
static struct platform_driver stac9766_driver = { .probe = stac9766_probe, .driver = { .name = "stac9766", }, };
static __init int stac9766_driver_init(void) { return platform_driver_register(&stac9766_driver); }
static __exit void stac9766_driver_exit(void) { }
module_init(stac9766_driver_init); module_exit(stac9766_driver_exit);
MODULE_DESCRIPTION("ASoC stac9766 driver"); MODULE_AUTHOR("Jon Smirl jonsmirl@gmail.com"); MODULE_LICENSE("GPL");
On Fri, May 01, 2009 at 11:30:44PM -0400, Jon Smirl wrote:
With this version, why are these errors generated?
ALSA sound/core/control.c:334: control 2:0:0:All Analog PCM Switch:0 is already present
The log message seems fairly clear... Note that controls for mixers get generated names in the form "Mixer Name Switch Name".
On Fri, May 1, 2009 at 6:14 AM, Mark Brown broonie@sirena.org.uk wrote:
.playback = { .stream_name = "stac9766 analog", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S32_BE,
Are you sure about this? AC97 generally does 16 bit samples...
AC97 can be 16, 18 or 20 bit. The link always transmits 20 bits. This codec is a 20b version.
On the mpc5200 side you can only feed the FIFOs 32b data. "For each AC97 slot a 32 bit data word must be in the TxFIFO" So do I make these pair up?
static struct snd_soc_dai psc_ac97_dai_template[] = { { .name = "%s analog", .suspend = psc_ac97_suspend, .resume = psc_ac97_resume, .playback = { .channels_min = 1, .channels_max = 6, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S32_BE, }, .capture = { .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S32_BE, }, .ops = &psc_ac97_analog_ops, }, { .name = "%s digital", .playback = { .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_32000 | \ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE, }, .ops = &psc_ac97_digital_ops, }};
On Fri, May 01, 2009 at 11:46:48PM -0400, Jon Smirl wrote:
On Fri, May 1, 2009 at 6:14 AM, Mark Brown broonie@sirena.org.uk wrote:
Are you sure about this? AC97 generally does 16 bit samples...
AC97 can be 16, 18 or 20 bit. The link always transmits 20 bits. This codec is a 20b version.
Sorry, I meant to say AC97 controllers. Your driver should advertise all rates up to 32 bit so that it can be used with any controller, not just controllers that happen to do 32 bit data.
On Fri, May 1, 2009 at 6:14 AM, Mark Brown broonie@sirena.org.uk wrote:
How should SPDIF support work? The hardware supports simultaneous use of analog and SPDIF. Do I want one DAI or two? If I have two DAI
Having taken a step back and looked at the datasheet what's going on here is that the CODEC has a S/PDIF output which can take either an AC97 timeslot or the output of the ADC as input. The AC97 timeslot input should probably be represented as a DAI, feeding into a mux selecting the input to be output to S/PDIF.
then ALSA needs to interleave the samples into a single DMA stream. So if one stream isn't active it needs to be filled with silence.
The AC97 controller driver should be able to expose multiple data streams - normally the hardware has direct support for this.
mpc5200 hardware can not split the stream's DMA. If there are three active streams they need to be arranged as 6 32b words in a single buffer.
Various choices... 1) always turn on the two output streams and three input streams in the codec and pass them through the mpc5200. Alsa needs to deal with 6 32b samples in the input buffer even if only one ALSA stream is active. 2) Only support one stream in and out from the codec. Use the switches on the codec to decide which stream is visible. 3) this is harder - coordinate between the codec and mpc5200 ac97 driver. When a new stream is turned on in the codec, also turn it on in the mpc5200. 4) always turn on all streams in the codec. manipulate the slot masks in the mpc5200 to only make the ones alsa is interested in visible. This causes error interrupts on the mpc5200, but they can be turned off. Alsa still needs to deal with 6 32b words per sample in the buffer is three streams are active.
Codec DAIs are missing a needed piece of information. You can flip bits in the codec and change the active slots in the AC97 stream. For dynamic configurations the CPU DAI needs to know the slot information.
On Sat, May 02, 2009 at 10:00:28AM -0400, Jon Smirl wrote:
Codec DAIs are missing a needed piece of information. You can flip bits in the codec and change the active slots in the AC97 stream. For dynamic configurations the CPU DAI needs to know the slot information.
Ish. The DAI is mostly fine, it's more that we need per-channel digital routing inside the CODEC - this sort of thing isn't limited to the ability to pick timeslots out of the DAI, it's also possible to do most of the routing that's visible in the analogue domain in the digital domain. The DAI shouldn't really need to worry about what the CODEC is going to do with the data, just as it doesn't worry about what happens with analogue routing.
participants (2)
-
Jon Smirl
-
Mark Brown