ALSA: hda - don't build digital output controls if not exist
[pandora-kernel.git] / sound / pci / hda / patch_cirrus.c
index 051302e..f552738 100644 (file)
@@ -30,6 +30,7 @@
  */
 
 struct cs_spec {
+       int board_config;
        struct auto_pin_cfg autocfg;
        struct hda_multi_out multiout;
        struct snd_kcontrol *vmaster_sw;
@@ -52,12 +53,81 @@ struct cs_spec {
 
        struct hda_bind_ctls *capture_bind[2];
 
+       unsigned int gpio_mask;
+       unsigned int gpio_dir;
+       unsigned int gpio_data;
+
        struct hda_pcm pcm_rec[2];      /* PCM information */
 
        unsigned int hp_detect:1;
        unsigned int mic_detect:1;
 };
 
+/* available models */
+enum {
+       CS420X_MBP55,
+       CS420X_AUTO,
+       CS420X_MODELS
+};
+
+/* Vendor-specific processing widget */
+#define CS420X_VENDOR_NID      0x11
+#define CS_DIG_OUT1_PIN_NID    0x10
+#define CS_DIG_OUT2_PIN_NID    0x15
+#define CS_DMIC1_PIN_NID       0x12
+#define CS_DMIC2_PIN_NID       0x0e
+
+/* coef indices */
+#define IDX_SPDIF_STAT         0x0000
+#define IDX_SPDIF_CTL          0x0001
+#define IDX_ADC_CFG            0x0002
+/* SZC bitmask, 4 modes below:
+ * 0 = immediate,
+ * 1 = digital immediate, analog zero-cross
+ * 2 = digtail & analog soft-ramp
+ * 3 = digital soft-ramp, analog zero-cross
+ */
+#define   CS_COEF_ADC_SZC_MASK         (3 << 0)
+#define   CS_COEF_ADC_MIC_SZC_MODE     (3 << 0) /* SZC setup for mic */
+#define   CS_COEF_ADC_LI_SZC_MODE      (3 << 0) /* SZC setup for line-in */
+/* PGA mode: 0 = differential, 1 = signle-ended */
+#define   CS_COEF_ADC_MIC_PGA_MODE     (1 << 5) /* PGA setup for mic */
+#define   CS_COEF_ADC_LI_PGA_MODE      (1 << 6) /* PGA setup for line-in */
+#define IDX_DAC_CFG            0x0003
+/* SZC bitmask, 4 modes below:
+ * 0 = Immediate
+ * 1 = zero-cross
+ * 2 = soft-ramp
+ * 3 = soft-ramp on zero-cross
+ */
+#define   CS_COEF_DAC_HP_SZC_MODE      (3 << 0) /* nid 0x02 */
+#define   CS_COEF_DAC_LO_SZC_MODE      (3 << 2) /* nid 0x03 */
+#define   CS_COEF_DAC_SPK_SZC_MODE     (3 << 4) /* nid 0x04 */
+
+#define IDX_BEEP_CFG           0x0004
+/* 0x0008 - test reg key */
+/* 0x0009 - 0x0014 -> 12 test regs */
+/* 0x0015 - visibility reg */
+
+
+static inline int cs_vendor_coef_get(struct hda_codec *codec, unsigned int idx)
+{
+       snd_hda_codec_write(codec, CS420X_VENDOR_NID, 0,
+                           AC_VERB_SET_COEF_INDEX, idx);
+       return snd_hda_codec_read(codec, CS420X_VENDOR_NID, 0,
+                                 AC_VERB_GET_PROC_COEF, 0);
+}
+
+static inline void cs_vendor_coef_set(struct hda_codec *codec, unsigned int idx,
+                                     unsigned int coef)
+{
+       snd_hda_codec_write(codec, CS420X_VENDOR_NID, 0,
+                           AC_VERB_SET_COEF_INDEX, idx);
+       snd_hda_codec_write(codec, CS420X_VENDOR_NID, 0,
+                           AC_VERB_SET_PROC_COEF, coef);
+}
+
+
 #define HP_EVENT       1
 #define MIC_EVENT      2
 
@@ -295,11 +365,18 @@ static hda_nid_t get_adc(struct hda_codec *codec, hda_nid_t pin,
        return 0;
 }
 
+static int is_active_pin(struct hda_codec *codec, hda_nid_t nid)
+{
+       unsigned int val;
+       val = snd_hda_codec_get_pincfg(codec, nid);
+       return (get_defcfg_connect(val) != AC_JACK_PORT_NONE);
+}
+
 static int parse_output(struct hda_codec *codec)
 {
        struct cs_spec *spec = codec->spec;
        struct auto_pin_cfg *cfg = &spec->autocfg;
-       int i, err, extra_nids;
+       int i, extra_nids;
        hda_nid_t dac;
 
        for (i = 0; i < cfg->line_outs; i++) {
@@ -344,11 +421,10 @@ static int parse_input(struct hda_codec *codec)
 {
        struct cs_spec *spec = codec->spec;
        struct auto_pin_cfg *cfg = &spec->autocfg;
-       int i, n, err;
+       int i;
 
        for (i = 0; i < AUTO_PIN_LAST; i++) {
                hda_nid_t pin = cfg->input_pins[i];
-               struct snd_kcontrol *kctl;
                if (!pin)
                        continue;
                spec->input_idx[spec->num_inputs] = i;
@@ -383,7 +459,6 @@ static int parse_digital_output(struct hda_codec *codec)
        struct cs_spec *spec = codec->spec;
        struct auto_pin_cfg *cfg = &spec->autocfg;
        hda_nid_t nid;
-       int err;
 
        if (!cfg->dig_outs)
                return 0;
@@ -405,12 +480,9 @@ static int parse_digital_input(struct hda_codec *codec)
        struct auto_pin_cfg *cfg = &spec->autocfg;
        int idx;
 
-       if (!cfg->dig_in_pin)
-               return 0;
-       spec->dig_in = get_adc(codec, cfg->dig_in_pin, &idx);
-       if (!spec->dig_in)
-               return 0;
-       return snd_hda_create_spdif_in_ctls(codec, spec->dig_in);
+       if (cfg->dig_in_pin)
+               spec->dig_in = get_adc(codec, cfg->dig_in_pin, &idx);
+       return 0;
 }
 
 /*
@@ -422,7 +494,7 @@ static const char *dir_sfx[2] = { "Playback", "Capture" };
 static int add_mute(struct hda_codec *codec, const char *name, int index,
                    unsigned int pval, int dir, struct snd_kcontrol **kctlp)
 {
-       char tmp[32];
+       char tmp[44];
        struct snd_kcontrol_new knew =
                HDA_CODEC_MUTE_IDX(tmp, index, 0, 0, HDA_OUTPUT);
        knew.private_value = pval;
@@ -572,12 +644,12 @@ static struct snd_kcontrol_new cs_capture_ctls[] = {
        HDA_BIND_VOL("Capture Volume", 0),
 };
 
-static int change_cur_input(struct hda_codec *codec, unsigned int idx)
+static int change_cur_input(struct hda_codec *codec, unsigned int idx,
+                           int force)
 {
        struct cs_spec *spec = codec->spec;
-       struct auto_pin_cfg *cfg = &spec->autocfg;
        
-       if (spec->cur_input == idx)
+       if (spec->cur_input == idx && !force)
                return 0;
        if (spec->cur_adc && spec->cur_adc != spec->adc_nid[idx]) {
                /* stream is running, let's swap the current ADC */
@@ -630,7 +702,7 @@ static int cs_capture_source_put(struct snd_kcontrol *kcontrol,
        if (idx >= spec->num_inputs)
                return -EINVAL;
        idx = spec->input_idx[idx];
-       return change_cur_input(codec, idx);
+       return change_cur_input(codec, idx, 0);
 }
 
 static struct snd_kcontrol_new cs_capture_source = {
@@ -707,6 +779,9 @@ static int build_digital_output(struct hda_codec *codec)
        struct cs_spec *spec = codec->spec;
        int err;
 
+       if (!spec->multiout.dig_out_nid)
+               return 0;
+
        err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid);
        if (err < 0)
                return err;
@@ -773,11 +848,11 @@ static void cs_automic(struct hda_codec *codec)
        present = snd_hda_codec_read(codec, nid, 0,
                                     AC_VERB_GET_PIN_SENSE, 0);
        if (present & AC_PINSENSE_PRESENCE)
-               change_cur_input(codec, spec->automic_idx);
+               change_cur_input(codec, spec->automic_idx, 0);
        else {
                unsigned int imic = (spec->automic_idx == AUTO_PIN_MIC) ?
                        AUTO_PIN_FRONT_MIC : AUTO_PIN_MIC;
-               change_cur_input(codec, imic);
+               change_cur_input(codec, imic, 0);
        }
 }
 
@@ -832,6 +907,7 @@ static void init_input(struct hda_codec *codec)
 {
        struct cs_spec *spec = codec->spec;
        struct auto_pin_cfg *cfg = &spec->autocfg;
+       unsigned int coef;
        int i;
 
        for (i = 0; i < AUTO_PIN_LAST; i++) {
@@ -857,22 +933,75 @@ static void init_input(struct hda_codec *codec)
                                            AC_VERB_SET_UNSOLICITED_ENABLE,
                                            AC_USRSP_EN | MIC_EVENT);
        }
+       change_cur_input(codec, spec->cur_input, 1);
        if (spec->mic_detect)
                cs_automic(codec);
+
+       coef = 0x000a; /* ADC1/2 - Digital and Analog Soft Ramp */
+       if (is_active_pin(codec, CS_DMIC2_PIN_NID))
+               coef |= 0x0500; /* DMIC2 enable 2 channels, disable GPIO1 */
+       if (is_active_pin(codec, CS_DMIC1_PIN_NID))
+               coef |= 0x1800; /* DMIC1 enable 2 channels, disable GPIO0 
+                                * No effect if SPDIF_OUT2 is slected in 
+                                * IDX_SPDIF_CTL.
+                                 */
+       cs_vendor_coef_set(codec, IDX_ADC_CFG, coef);
+}
+
+static struct hda_verb cs_coef_init_verbs[] = {
+       {0x11, AC_VERB_SET_PROC_STATE, 1},
+       {0x11, AC_VERB_SET_COEF_INDEX, IDX_DAC_CFG},
+       {0x11, AC_VERB_SET_PROC_COEF,
+        (0x002a /* DAC1/2/3 SZCMode Soft Ramp */
+         | 0x0040 /* Mute DACs on FIFO error */
+         | 0x1000 /* Enable DACs High Pass Filter */
+         | 0x0400 /* Disable Coefficient Auto increment */
+         )},
+       /* Beep */
+       {0x11, AC_VERB_SET_COEF_INDEX, IDX_DAC_CFG},
+       {0x11, AC_VERB_SET_PROC_COEF, 0x0007}, /* Enable Beep thru DAC1/2/3 */
+
+       {} /* terminator */
+};
+
+/* SPDIF setup */
+static void init_digital(struct hda_codec *codec)
+{
+       unsigned int coef;
+
+       coef = 0x0002; /* SRC_MUTE soft-mute on SPDIF (if no lock) */
+       coef |= 0x0008; /* Replace with mute on error */
+       if (is_active_pin(codec, CS_DIG_OUT2_PIN_NID))
+               coef |= 0x4000; /* RX to TX1 or TX2 Loopthru / SPDIF2
+                                * SPDIF_OUT2 is shared with GPIO1 and
+                                * DMIC_SDA2.
+                                */
+       cs_vendor_coef_set(codec, IDX_SPDIF_CTL, coef);
 }
 
 static int cs_init(struct hda_codec *codec)
 {
        struct cs_spec *spec = codec->spec;
 
+       snd_hda_sequence_write(codec, cs_coef_init_verbs);
+
+       if (spec->gpio_mask) {
+               snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK,
+                                   spec->gpio_mask);
+               snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION,
+                                   spec->gpio_dir);
+               snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
+                                   spec->gpio_data);
+       }
+
        init_output(codec);
        init_input(codec);
+       init_digital(codec);
        return 0;
 }
 
 static int cs_build_controls(struct hda_codec *codec)
 {
-       struct cs_spec *spec = codec->spec;
        int err;
 
        err = build_output(codec);
@@ -924,11 +1053,67 @@ static int cs_parse_auto_config(struct hda_codec *codec)
        int err;
 
        err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+       if (err < 0)
+               return err;
+
+       err = parse_output(codec);
+       if (err < 0)
+               return err;
+       err = parse_input(codec);
+       if (err < 0)
+               return err;
+       err = parse_digital_output(codec);
+       if (err < 0)
+               return err;
+       err = parse_digital_input(codec);
        if (err < 0)
                return err;
        return 0;
 }
 
+static const char *cs420x_models[CS420X_MODELS] = {
+       [CS420X_MBP55] = "mbp55",
+       [CS420X_AUTO] = "auto",
+};
+
+
+static struct snd_pci_quirk cs420x_cfg_tbl[] = {
+       SND_PCI_QUIRK(0x10de, 0xcb79, "MacBookPro 5,5", CS420X_MBP55),
+       {} /* terminator */
+};
+
+struct cs_pincfg {
+       hda_nid_t nid;
+       u32 val;
+};
+
+static struct cs_pincfg mbp55_pincfgs[] = {
+       { 0x09, 0x012b4030 },
+       { 0x0a, 0x90100121 },
+       { 0x0b, 0x90100120 },
+       { 0x0c, 0x400000f0 },
+       { 0x0d, 0x90a00110 },
+       { 0x0e, 0x400000f0 },
+       { 0x0f, 0x400000f0 },
+       { 0x10, 0x014be040 },
+       { 0x12, 0x400000f0 },
+       { 0x15, 0x400000f0 },
+       {} /* terminator */
+};
+
+static struct cs_pincfg *cs_pincfgs[CS420X_MODELS] = {
+       [CS420X_MBP55] = mbp55_pincfgs,
+};
+
+static void fix_pincfg(struct hda_codec *codec, int model)
+{
+       const struct cs_pincfg *cfg = cs_pincfgs[model];
+       if (!cfg)
+               return;
+       for (; cfg->nid; cfg++)
+               snd_hda_codec_set_pincfg(codec, cfg->nid, cfg->val);
+}
+
 
 static int patch_cs420x(struct hda_codec *codec)
 {
@@ -940,20 +1125,22 @@ static int patch_cs420x(struct hda_codec *codec)
                return -ENOMEM;
        codec->spec = spec;
 
-       err = cs_parse_auto_config(codec);
-       if (err < 0)
-               goto error;
+       spec->board_config =
+               snd_hda_check_board_config(codec, CS420X_MODELS,
+                                          cs420x_models, cs420x_cfg_tbl);
+       if (spec->board_config >= 0)
+               fix_pincfg(codec, spec->board_config);
+
+       switch (spec->board_config) {
+       case CS420X_MBP55:
+               /* GPIO3 = EAPD? */
+               spec->gpio_mask = 0x08;
+               spec->gpio_dir = 0x08;
+               spec->gpio_data = 0x08;
+               break;
+       }
 
-       err = parse_output(codec);
-       if (err < 0)
-               goto error;
-       err = parse_input(codec);
-       if (err < 0)
-               goto error;
-       err = parse_digital_output(codec);
-       if (err < 0)
-               goto error;
-       err = parse_digital_input(codec);
+       err = cs_parse_auto_config(codec);
        if (err < 0)
                goto error;