Merge branches 'acpi-resources', 'acpi-battery', 'acpi-doc' and 'acpi-pnp'
[linux-2.6-block.git] / sound / pci / hda / hda_generic.c
index 8ec5289f8e058538892aa04c402a4db9967c94c7..788f969b1a680e50f61940e31fe1a4609ccce402 100644 (file)
@@ -140,6 +140,9 @@ static void parse_user_hints(struct hda_codec *codec)
        val = snd_hda_get_bool_hint(codec, "single_adc_amp");
        if (val >= 0)
                codec->single_adc_amp = !!val;
+       val = snd_hda_get_bool_hint(codec, "power_save_node");
+       if (val >= 0)
+               codec->power_save_node = !!val;
 
        val = snd_hda_get_bool_hint(codec, "auto_mute");
        if (val >= 0)
@@ -648,12 +651,24 @@ static bool is_active_nid(struct hda_codec *codec, hda_nid_t nid,
                          unsigned int dir, unsigned int idx)
 {
        struct hda_gen_spec *spec = codec->spec;
+       int type = get_wcaps_type(get_wcaps(codec, nid));
        int i, n;
 
+       if (nid == codec->core.afg)
+               return true;
+
        for (n = 0; n < spec->paths.used; n++) {
                struct nid_path *path = snd_array_elem(&spec->paths, n);
                if (!path->active)
                        continue;
+               if (codec->power_save_node) {
+                       if (!path->stream_enabled)
+                               continue;
+                       /* ignore unplugged paths except for DAC/ADC */
+                       if (!(path->pin_enabled || path->pin_fixed) &&
+                           type != AC_WID_AUD_OUT && type != AC_WID_AUD_IN)
+                               continue;
+               }
                for (i = 0; i < path->depth; i++) {
                        if (path->path[i] == nid) {
                                if (dir == HDA_OUTPUT || path->idx[i] == idx)
@@ -807,6 +822,44 @@ static void activate_amp_in(struct hda_codec *codec, struct nid_path *path,
        }
 }
 
+/* sync power of each widget in the the given path */
+static hda_nid_t path_power_update(struct hda_codec *codec,
+                                  struct nid_path *path,
+                                  bool allow_powerdown)
+{
+       hda_nid_t nid, changed = 0;
+       int i, state;
+
+       for (i = 0; i < path->depth; i++) {
+               nid = path->path[i];
+               if (!(get_wcaps(codec, nid) & AC_WCAP_POWER))
+                       continue;
+               if (nid == codec->core.afg)
+                       continue;
+               if (!allow_powerdown || is_active_nid_for_any(codec, nid))
+                       state = AC_PWRST_D0;
+               else
+                       state = AC_PWRST_D3;
+               if (!snd_hda_check_power_state(codec, nid, state)) {
+                       snd_hda_codec_write(codec, nid, 0,
+                                           AC_VERB_SET_POWER_STATE, state);
+                       changed = nid;
+                       if (state == AC_PWRST_D0)
+                               snd_hdac_regmap_sync_node(&codec->core, nid);
+               }
+       }
+       return changed;
+}
+
+/* do sync with the last power state change */
+static void sync_power_state_change(struct hda_codec *codec, hda_nid_t nid)
+{
+       if (nid) {
+               msleep(10);
+               snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0);
+       }
+}
+
 /**
  * snd_hda_activate_path - activate or deactivate the given path
  * @codec: the HDA codec
@@ -825,15 +878,13 @@ void snd_hda_activate_path(struct hda_codec *codec, struct nid_path *path,
        if (!enable)
                path->active = false;
 
+       /* make sure the widget is powered up */
+       if (enable && (spec->power_down_unused || codec->power_save_node))
+               path_power_update(codec, path, codec->power_save_node);
+
        for (i = path->depth - 1; i >= 0; i--) {
                hda_nid_t nid = path->path[i];
-               if (enable && spec->power_down_unused) {
-                       /* make sure the widget is powered up */
-                       if (!snd_hda_check_power_state(codec, nid, AC_PWRST_D0))
-                               snd_hda_codec_write(codec, nid, 0,
-                                                   AC_VERB_SET_POWER_STATE,
-                                                   AC_PWRST_D0);
-               }
+
                if (enable && path->multi[i])
                        snd_hda_codec_update_cache(codec, nid, 0,
                                            AC_VERB_SET_CONNECT_SEL,
@@ -853,28 +904,10 @@ EXPORT_SYMBOL_GPL(snd_hda_activate_path);
 static void path_power_down_sync(struct hda_codec *codec, struct nid_path *path)
 {
        struct hda_gen_spec *spec = codec->spec;
-       bool changed = false;
-       int i;
 
-       if (!spec->power_down_unused || path->active)
+       if (!(spec->power_down_unused || codec->power_save_node) || path->active)
                return;
-
-       for (i = 0; i < path->depth; i++) {
-               hda_nid_t nid = path->path[i];
-               if (!snd_hda_check_power_state(codec, nid, AC_PWRST_D3) &&
-                   !is_active_nid_for_any(codec, nid)) {
-                       snd_hda_codec_write(codec, nid, 0,
-                                           AC_VERB_SET_POWER_STATE,
-                                           AC_PWRST_D3);
-                       changed = true;
-               }
-       }
-
-       if (changed) {
-               msleep(10);
-               snd_hda_codec_read(codec, path->path[0], 0,
-                                  AC_VERB_GET_POWER_STATE, 0);
-       }
+       sync_power_state_change(codec, path_power_update(codec, path, true));
 }
 
 /* turn on/off EAPD on the given pin */
@@ -1574,6 +1607,7 @@ static int check_aamix_out_path(struct hda_codec *codec, int path_idx)
                return 0;
        /* print_nid_path(codec, "output-aamix", path); */
        path->active = false; /* unused as default */
+       path->pin_fixed = true; /* static route */
        return snd_hda_get_path_idx(codec, path);
 }
 
@@ -1863,12 +1897,11 @@ static void debug_show_configs(struct hda_codec *codec,
 static void fill_all_dac_nids(struct hda_codec *codec)
 {
        struct hda_gen_spec *spec = codec->spec;
-       int i;
-       hda_nid_t nid = codec->start_nid;
+       hda_nid_t nid;
 
        spec->num_all_dacs = 0;
        memset(spec->all_dacs, 0, sizeof(spec->all_dacs));
-       for (i = 0; i < codec->num_nodes; i++, nid++) {
+       for_each_hda_codec_node(nid, codec) {
                if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_AUD_OUT)
                        continue;
                if (spec->num_all_dacs >= ARRAY_SIZE(spec->all_dacs)) {
@@ -2998,6 +3031,7 @@ static int new_analog_input(struct hda_codec *codec, int input_idx,
        }
 
        path->active = true;
+       path->stream_enabled = true; /* no DAC/ADC involved */
        err = add_loopback_list(spec, mix_nid, idx);
        if (err < 0)
                return err;
@@ -3009,6 +3043,8 @@ static int new_analog_input(struct hda_codec *codec, int input_idx,
                if (path) {
                        print_nid_path(codec, "loopback-merge", path);
                        path->active = true;
+                       path->pin_fixed = true; /* static route */
+                       path->stream_enabled = true; /* no DAC/ADC involved */
                        spec->loopback_merge_path =
                                snd_hda_get_path_idx(codec, path);
                }
@@ -3030,10 +3066,9 @@ static int fill_adc_nids(struct hda_codec *codec)
        hda_nid_t nid;
        hda_nid_t *adc_nids = spec->adc_nids;
        int max_nums = ARRAY_SIZE(spec->adc_nids);
-       int i, nums = 0;
+       int nums = 0;
 
-       nid = codec->start_nid;
-       for (i = 0; i < codec->num_nodes; i++, nid++) {
+       for_each_hda_codec_node(nid, codec) {
                unsigned int caps = get_wcaps(codec, nid);
                int type = get_wcaps_type(caps);
 
@@ -3224,7 +3259,8 @@ static int create_input_ctls(struct hda_codec *codec)
                val = PIN_IN;
                if (cfg->inputs[i].type == AUTO_PIN_MIC)
                        val |= snd_hda_get_default_vref(codec, pin);
-               if (pin != spec->hp_mic_pin)
+               if (pin != spec->hp_mic_pin &&
+                   !snd_hda_codec_get_pin_target(codec, pin))
                        set_pin_target(codec, pin, val, false);
 
                if (mixer) {
@@ -3346,11 +3382,6 @@ static int cap_put_caller(struct snd_kcontrol *kcontrol,
        imux = &spec->input_mux;
        adc_idx = kcontrol->id.index;
        mutex_lock(&codec->control_mutex);
-       /* we use the cache-only update at first since multiple input paths
-        * may shared the same amp; by updating only caches, the redundant
-        * writes to hardware can be reduced.
-        */
-       codec->cached_write = 1;
        for (i = 0; i < imux->num_items; i++) {
                path = get_input_path(codec, adc_idx, i);
                if (!path || !path->ctls[type])
@@ -3358,12 +3389,9 @@ static int cap_put_caller(struct snd_kcontrol *kcontrol,
                kcontrol->private_value = path->ctls[type];
                err = func(kcontrol, ucontrol);
                if (err < 0)
-                       goto error;
+                       break;
        }
- error:
-       codec->cached_write = 0;
        mutex_unlock(&codec->control_mutex);
-       snd_hda_codec_flush_cache(codec); /* flush the updates */
        if (err >= 0 && spec->cap_sync_hook)
                spec->cap_sync_hook(codec, kcontrol, ucontrol);
        return err;
@@ -3810,6 +3838,7 @@ static void parse_digital(struct hda_codec *codec)
                        continue;
                print_nid_path(codec, "digout", path);
                path->active = true;
+               path->pin_fixed = true; /* no jack detection */
                spec->digout_paths[i] = snd_hda_get_path_idx(codec, path);
                set_pin_target(codec, pin, PIN_OUT, false);
                if (!nums) {
@@ -3826,8 +3855,7 @@ static void parse_digital(struct hda_codec *codec)
 
        if (spec->autocfg.dig_in_pin) {
                pin = spec->autocfg.dig_in_pin;
-               dig_nid = codec->start_nid;
-               for (i = 0; i < codec->num_nodes; i++, dig_nid++) {
+               for_each_hda_codec_node(dig_nid, codec) {
                        unsigned int wcaps = get_wcaps(codec, dig_nid);
                        if (get_wcaps_type(wcaps) != AC_WID_AUD_IN)
                                continue;
@@ -3837,6 +3865,7 @@ static void parse_digital(struct hda_codec *codec)
                        if (path) {
                                print_nid_path(codec, "digin", path);
                                path->active = true;
+                               path->pin_fixed = true; /* no jack */
                                spec->dig_in_nid = dig_nid;
                                spec->digin_path = snd_hda_get_path_idx(codec, path);
                                set_pin_target(codec, pin, PIN_IN, false);
@@ -3896,6 +3925,238 @@ static int mux_select(struct hda_codec *codec, unsigned int adc_idx,
        return 1;
 }
 
+/* power up/down widgets in the all paths that match with the given NID
+ * as terminals (either start- or endpoint)
+ *
+ * returns the last changed NID, or zero if unchanged.
+ */
+static hda_nid_t set_path_power(struct hda_codec *codec, hda_nid_t nid,
+                               int pin_state, int stream_state)
+{
+       struct hda_gen_spec *spec = codec->spec;
+       hda_nid_t last, changed = 0;
+       struct nid_path *path;
+       int n;
+
+       for (n = 0; n < spec->paths.used; n++) {
+               path = snd_array_elem(&spec->paths, n);
+               if (path->path[0] == nid ||
+                   path->path[path->depth - 1] == nid) {
+                       bool pin_old = path->pin_enabled;
+                       bool stream_old = path->stream_enabled;
+
+                       if (pin_state >= 0)
+                               path->pin_enabled = pin_state;
+                       if (stream_state >= 0)
+                               path->stream_enabled = stream_state;
+                       if ((!path->pin_fixed && path->pin_enabled != pin_old)
+                           || path->stream_enabled != stream_old) {
+                               last = path_power_update(codec, path, true);
+                               if (last)
+                                       changed = last;
+                       }
+               }
+       }
+       return changed;
+}
+
+/* check the jack status for power control */
+static bool detect_pin_state(struct hda_codec *codec, hda_nid_t pin)
+{
+       if (!is_jack_detectable(codec, pin))
+               return true;
+       return snd_hda_jack_detect_state(codec, pin) != HDA_JACK_NOT_PRESENT;
+}
+
+/* power up/down the paths of the given pin according to the jack state;
+ * power = 0/1 : only power up/down if it matches with the jack state,
+ *       < 0   : force power up/down to follow the jack sate
+ *
+ * returns the last changed NID, or zero if unchanged.
+ */
+static hda_nid_t set_pin_power_jack(struct hda_codec *codec, hda_nid_t pin,
+                                   int power)
+{
+       bool on;
+
+       if (!codec->power_save_node)
+               return 0;
+
+       on = detect_pin_state(codec, pin);
+
+       if (power >= 0 && on != power)
+               return 0;
+       return set_path_power(codec, pin, on, -1);
+}
+
+static void pin_power_callback(struct hda_codec *codec,
+                              struct hda_jack_callback *jack,
+                              bool on)
+{
+       if (jack && jack->tbl->nid)
+               sync_power_state_change(codec,
+                                       set_pin_power_jack(codec, jack->tbl->nid, on));
+}
+
+/* callback only doing power up -- called at first */
+static void pin_power_up_callback(struct hda_codec *codec,
+                                 struct hda_jack_callback *jack)
+{
+       pin_power_callback(codec, jack, true);
+}
+
+/* callback only doing power down -- called at last */
+static void pin_power_down_callback(struct hda_codec *codec,
+                                   struct hda_jack_callback *jack)
+{
+       pin_power_callback(codec, jack, false);
+}
+
+/* set up the power up/down callbacks */
+static void add_pin_power_ctls(struct hda_codec *codec, int num_pins,
+                              const hda_nid_t *pins, bool on)
+{
+       int i;
+       hda_jack_callback_fn cb =
+               on ? pin_power_up_callback : pin_power_down_callback;
+
+       for (i = 0; i < num_pins && pins[i]; i++) {
+               if (is_jack_detectable(codec, pins[i]))
+                       snd_hda_jack_detect_enable_callback(codec, pins[i], cb);
+               else
+                       set_path_power(codec, pins[i], true, -1);
+       }
+}
+
+/* enabled power callback to each available I/O pin with jack detections;
+ * the digital I/O pins are excluded because of the unreliable detectsion
+ */
+static void add_all_pin_power_ctls(struct hda_codec *codec, bool on)
+{
+       struct hda_gen_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       int i;
+
+       if (!codec->power_save_node)
+               return;
+       add_pin_power_ctls(codec, cfg->line_outs, cfg->line_out_pins, on);
+       if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+               add_pin_power_ctls(codec, cfg->hp_outs, cfg->hp_pins, on);
+       if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
+               add_pin_power_ctls(codec, cfg->speaker_outs, cfg->speaker_pins, on);
+       for (i = 0; i < cfg->num_inputs; i++)
+               add_pin_power_ctls(codec, 1, &cfg->inputs[i].pin, on);
+}
+
+/* sync path power up/down with the jack states of given pins */
+static void sync_pin_power_ctls(struct hda_codec *codec, int num_pins,
+                               const hda_nid_t *pins)
+{
+       int i;
+
+       for (i = 0; i < num_pins && pins[i]; i++)
+               if (is_jack_detectable(codec, pins[i]))
+                       set_pin_power_jack(codec, pins[i], -1);
+}
+
+/* sync path power up/down with pins; called at init and resume */
+static void sync_all_pin_power_ctls(struct hda_codec *codec)
+{
+       struct hda_gen_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       int i;
+
+       if (!codec->power_save_node)
+               return;
+       sync_pin_power_ctls(codec, cfg->line_outs, cfg->line_out_pins);
+       if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+               sync_pin_power_ctls(codec, cfg->hp_outs, cfg->hp_pins);
+       if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
+               sync_pin_power_ctls(codec, cfg->speaker_outs, cfg->speaker_pins);
+       for (i = 0; i < cfg->num_inputs; i++)
+               sync_pin_power_ctls(codec, 1, &cfg->inputs[i].pin);
+}
+
+/* add fake paths if not present yet */
+static int add_fake_paths(struct hda_codec *codec, hda_nid_t nid,
+                          int num_pins, const hda_nid_t *pins)
+{
+       struct hda_gen_spec *spec = codec->spec;
+       struct nid_path *path;
+       int i;
+
+       for (i = 0; i < num_pins; i++) {
+               if (!pins[i])
+                       break;
+               if (get_nid_path(codec, nid, pins[i], 0))
+                       continue;
+               path = snd_array_new(&spec->paths);
+               if (!path)
+                       return -ENOMEM;
+               memset(path, 0, sizeof(*path));
+               path->depth = 2;
+               path->path[0] = nid;
+               path->path[1] = pins[i];
+               path->active = true;
+       }
+       return 0;
+}
+
+/* create fake paths to all outputs from beep */
+static int add_fake_beep_paths(struct hda_codec *codec)
+{
+       struct hda_gen_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       hda_nid_t nid = spec->beep_nid;
+       int err;
+
+       if (!codec->power_save_node || !nid)
+               return 0;
+       err = add_fake_paths(codec, nid, cfg->line_outs, cfg->line_out_pins);
+       if (err < 0)
+               return err;
+       if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
+               err = add_fake_paths(codec, nid, cfg->hp_outs, cfg->hp_pins);
+               if (err < 0)
+                       return err;
+       }
+       if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
+               err = add_fake_paths(codec, nid, cfg->speaker_outs,
+                                    cfg->speaker_pins);
+               if (err < 0)
+                       return err;
+       }
+       return 0;
+}
+
+/* power up/down beep widget and its output paths */
+static void beep_power_hook(struct hda_beep *beep, bool on)
+{
+       set_path_power(beep->codec, beep->nid, -1, on);
+}
+
+/**
+ * snd_hda_gen_fix_pin_power - Fix the power of the given pin widget to D0
+ * @codec: the HDA codec
+ * @pin: NID of pin to fix
+ */
+int snd_hda_gen_fix_pin_power(struct hda_codec *codec, hda_nid_t pin)
+{
+       struct hda_gen_spec *spec = codec->spec;
+       struct nid_path *path;
+
+       path = snd_array_new(&spec->paths);
+       if (!path)
+               return -ENOMEM;
+       memset(path, 0, sizeof(*path));
+       path->depth = 1;
+       path->path[0] = pin;
+       path->active = true;
+       path->pin_fixed = true;
+       path->stream_enabled = true;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_fix_pin_power);
 
 /*
  * Jack detections for HP auto-mute and mic-switch
@@ -3933,6 +4194,10 @@ static void do_automute(struct hda_codec *codec, int num_pins, hda_nid_t *pins,
                if (!nid)
                        break;
 
+               oldval = snd_hda_codec_get_pin_target(codec, nid);
+               if (oldval & PIN_IN)
+                       continue; /* no mute for inputs */
+
                if (spec->auto_mute_via_amp) {
                        struct nid_path *path;
                        hda_nid_t mute_nid;
@@ -3947,29 +4212,32 @@ static void do_automute(struct hda_codec *codec, int num_pins, hda_nid_t *pins,
                                spec->mute_bits |= (1ULL << mute_nid);
                        else
                                spec->mute_bits &= ~(1ULL << mute_nid);
-                       set_pin_eapd(codec, nid, !mute);
                        continue;
+               } else {
+                       /* don't reset VREF value in case it's controlling
+                        * the amp (see alc861_fixup_asus_amp_vref_0f())
+                        */
+                       if (spec->keep_vref_in_automute)
+                               val = oldval & ~PIN_HP;
+                       else
+                               val = 0;
+                       if (!mute)
+                               val |= oldval;
+                       /* here we call update_pin_ctl() so that the pinctl is
+                        * changed without changing the pinctl target value;
+                        * the original target value will be still referred at
+                        * the init / resume again
+                        */
+                       update_pin_ctl(codec, nid, val);
                }
 
-               oldval = snd_hda_codec_get_pin_target(codec, nid);
-               if (oldval & PIN_IN)
-                       continue; /* no mute for inputs */
-               /* don't reset VREF value in case it's controlling
-                * the amp (see alc861_fixup_asus_amp_vref_0f())
-                */
-               if (spec->keep_vref_in_automute)
-                       val = oldval & ~PIN_HP;
-               else
-                       val = 0;
-               if (!mute)
-                       val |= oldval;
-               /* here we call update_pin_ctl() so that the pinctl is changed
-                * without changing the pinctl target value;
-                * the original target value will be still referred at the
-                * init / resume again
-                */
-               update_pin_ctl(codec, nid, val);
                set_pin_eapd(codec, nid, !mute);
+               if (codec->power_save_node) {
+                       bool on = !mute;
+                       if (on)
+                               on = detect_pin_state(codec, nid);
+                       set_path_power(codec, nid, on, -1);
+               }
        }
 }
 
@@ -4436,7 +4704,11 @@ unsigned int snd_hda_gen_path_power_filter(struct hda_codec *codec,
                                                  hda_nid_t nid,
                                                  unsigned int power_state)
 {
-       if (power_state != AC_PWRST_D0 || nid == codec->afg)
+       struct hda_gen_spec *spec = codec->spec;
+
+       if (!spec->power_down_unused && !codec->power_save_node)
+               return power_state;
+       if (power_state != AC_PWRST_D0 || nid == codec->core.afg)
                return power_state;
        if (get_wcaps_type(get_wcaps(codec, nid)) >= AC_WID_POWER)
                return power_state;
@@ -4465,6 +4737,21 @@ static void mute_all_mixer_nid(struct hda_codec *codec, hda_nid_t mix)
        }
 }
 
+/**
+ * snd_hda_gen_stream_pm - Stream power management callback
+ * @codec: the HDA codec
+ * @nid: audio widget
+ * @on: power on/off flag
+ *
+ * Set this in patch_ops.stream_pm.  Only valid with power_save_node flag.
+ */
+void snd_hda_gen_stream_pm(struct hda_codec *codec, hda_nid_t nid, bool on)
+{
+       if (codec->power_save_node)
+               set_path_power(codec, nid, -1, on);
+}
+EXPORT_SYMBOL_GPL(snd_hda_gen_stream_pm);
+
 /**
  * snd_hda_gen_parse_auto_config - Parse the given BIOS configuration and
  * set up the hda_gen_spec
@@ -4549,6 +4836,9 @@ int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
        if (err < 0)
                return err;
 
+       /* add power-down pin callbacks at first */
+       add_all_pin_power_ctls(codec, false);
+
        spec->const_channel_count = spec->ext_channel_count;
        /* check the multiple speaker and headphone pins */
        if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
@@ -4618,6 +4908,9 @@ int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
                }
        }
 
+       /* add power-up pin callbacks at last */
+       add_all_pin_power_ctls(codec, true);
+
        /* mute all aamix input initially */
        if (spec->mixer_nid)
                mute_all_mixer_nid(codec, spec->mixer_nid);
@@ -4625,13 +4918,20 @@ int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
  dig_only:
        parse_digital(codec);
 
-       if (spec->power_down_unused)
-               codec->power_filter = snd_hda_gen_path_power_filter;
+       if (spec->power_down_unused || codec->power_save_node)
+               if (!codec->power_filter)
+                       codec->power_filter = snd_hda_gen_path_power_filter;
 
        if (!spec->no_analog && spec->beep_nid) {
                err = snd_hda_attach_beep_device(codec, spec->beep_nid);
                if (err < 0)
                        return err;
+               if (codec->beep && codec->power_save_node) {
+                       err = add_fake_beep_paths(codec);
+                       if (err < 0)
+                               return err;
+                       codec->beep->power_hook = beep_power_hook;
+               }
        }
 
        return 1;
@@ -4675,7 +4975,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec)
                err = snd_hda_create_dig_out_ctls(codec,
                                                  spec->multiout.dig_out_nid,
                                                  spec->multiout.dig_out_nid,
-                                                 spec->pcm_rec[1].pcm_type);
+                                                 spec->pcm_rec[1]->pcm_type);
                if (err < 0)
                        return err;
                if (!spec->no_analog) {
@@ -5137,6 +5437,33 @@ static void fill_pcm_stream_name(char *str, size_t len, const char *sfx,
        strlcat(str, sfx, len);
 }
 
+/* copy PCM stream info from @default_str, and override non-NULL entries
+ * from @spec_str and @nid
+ */
+static void setup_pcm_stream(struct hda_pcm_stream *str,
+                            const struct hda_pcm_stream *default_str,
+                            const struct hda_pcm_stream *spec_str,
+                            hda_nid_t nid)
+{
+       *str = *default_str;
+       if (nid)
+               str->nid = nid;
+       if (spec_str) {
+               if (spec_str->substreams)
+                       str->substreams = spec_str->substreams;
+               if (spec_str->channels_min)
+                       str->channels_min = spec_str->channels_min;
+               if (spec_str->channels_max)
+                       str->channels_max = spec_str->channels_max;
+               if (spec_str->rates)
+                       str->rates = spec_str->rates;
+               if (spec_str->formats)
+                       str->formats = spec_str->formats;
+               if (spec_str->maxbps)
+                       str->maxbps = spec_str->maxbps;
+       }
+}
+
 /**
  * snd_hda_gen_build_pcms - build PCM streams based on the parsed results
  * @codec: the HDA codec
@@ -5146,27 +5473,25 @@ static void fill_pcm_stream_name(char *str, size_t len, const char *sfx,
 int snd_hda_gen_build_pcms(struct hda_codec *codec)
 {
        struct hda_gen_spec *spec = codec->spec;
-       struct hda_pcm *info = spec->pcm_rec;
-       const struct hda_pcm_stream *p;
+       struct hda_pcm *info;
        bool have_multi_adcs;
 
-       codec->num_pcms = 1;
-       codec->pcm_info = info;
-
        if (spec->no_analog)
                goto skip_analog;
 
        fill_pcm_stream_name(spec->stream_name_analog,
                             sizeof(spec->stream_name_analog),
-                            " Analog", codec->chip_name);
-       info->name = spec->stream_name_analog;
+                            " Analog", codec->core.chip_name);
+       info = snd_hda_codec_pcm_new(codec, "%s", spec->stream_name_analog);
+       if (!info)
+               return -ENOMEM;
+       spec->pcm_rec[0] = info;
 
        if (spec->multiout.num_dacs > 0) {
-               p = spec->stream_analog_playback;
-               if (!p)
-                       p = &pcm_analog_playback;
-               info->stream[SNDRV_PCM_STREAM_PLAYBACK] = *p;
-               info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dac_nids[0];
+               setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
+                                &pcm_analog_playback,
+                                spec->stream_analog_playback,
+                                spec->multiout.dac_nids[0]);
                info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
                        spec->multiout.max_channels;
                if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT &&
@@ -5175,15 +5500,11 @@ int snd_hda_gen_build_pcms(struct hda_codec *codec)
                                snd_pcm_2_1_chmaps;
        }
        if (spec->num_adc_nids) {
-               p = spec->stream_analog_capture;
-               if (!p) {
-                       if (spec->dyn_adc_switch)
-                               p = &dyn_adc_pcm_analog_capture;
-                       else
-                               p = &pcm_analog_capture;
-               }
-               info->stream[SNDRV_PCM_STREAM_CAPTURE] = *p;
-               info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0];
+               setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
+                                (spec->dyn_adc_switch ?
+                                 &dyn_adc_pcm_analog_capture : &pcm_analog_capture),
+                                spec->stream_analog_capture,
+                                spec->adc_nids[0]);
        }
 
  skip_analog:
@@ -5191,29 +5512,27 @@ int snd_hda_gen_build_pcms(struct hda_codec *codec)
        if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
                fill_pcm_stream_name(spec->stream_name_digital,
                                     sizeof(spec->stream_name_digital),
-                                    " Digital", codec->chip_name);
-               codec->num_pcms = 2;
+                                    " Digital", codec->core.chip_name);
+               info = snd_hda_codec_pcm_new(codec, "%s",
+                                            spec->stream_name_digital);
+               if (!info)
+                       return -ENOMEM;
                codec->slave_dig_outs = spec->multiout.slave_dig_outs;
-               info = spec->pcm_rec + 1;
-               info->name = spec->stream_name_digital;
+               spec->pcm_rec[1] = info;
                if (spec->dig_out_type)
                        info->pcm_type = spec->dig_out_type;
                else
                        info->pcm_type = HDA_PCM_TYPE_SPDIF;
-               if (spec->multiout.dig_out_nid) {
-                       p = spec->stream_digital_playback;
-                       if (!p)
-                               p = &pcm_digital_playback;
-                       info->stream[SNDRV_PCM_STREAM_PLAYBACK] = *p;
-                       info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dig_out_nid;
-               }
-               if (spec->dig_in_nid) {
-                       p = spec->stream_digital_capture;
-                       if (!p)
-                               p = &pcm_digital_capture;
-                       info->stream[SNDRV_PCM_STREAM_CAPTURE] = *p;
-                       info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid;
-               }
+               if (spec->multiout.dig_out_nid)
+                       setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
+                                        &pcm_digital_playback,
+                                        spec->stream_digital_playback,
+                                        spec->multiout.dig_out_nid);
+               if (spec->dig_in_nid)
+                       setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
+                                        &pcm_digital_capture,
+                                        spec->stream_digital_capture,
+                                        spec->dig_in_nid);
        }
 
        if (spec->no_analog)
@@ -5228,35 +5547,30 @@ int snd_hda_gen_build_pcms(struct hda_codec *codec)
        if (spec->alt_dac_nid || have_multi_adcs) {
                fill_pcm_stream_name(spec->stream_name_alt_analog,
                                     sizeof(spec->stream_name_alt_analog),
-                            " Alt Analog", codec->chip_name);
-               codec->num_pcms = 3;
-               info = spec->pcm_rec + 2;
-               info->name = spec->stream_name_alt_analog;
-               if (spec->alt_dac_nid) {
-                       p = spec->stream_analog_alt_playback;
-                       if (!p)
-                               p = &pcm_analog_alt_playback;
-                       info->stream[SNDRV_PCM_STREAM_PLAYBACK] = *p;
-                       info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
-                               spec->alt_dac_nid;
-               } else {
-                       info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
-                               pcm_null_stream;
-                       info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = 0;
-               }
+                            " Alt Analog", codec->core.chip_name);
+               info = snd_hda_codec_pcm_new(codec, "%s",
+                                            spec->stream_name_alt_analog);
+               if (!info)
+                       return -ENOMEM;
+               spec->pcm_rec[2] = info;
+               if (spec->alt_dac_nid)
+                       setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
+                                        &pcm_analog_alt_playback,
+                                        spec->stream_analog_alt_playback,
+                                        spec->alt_dac_nid);
+               else
+                       setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
+                                        &pcm_null_stream, NULL, 0);
                if (have_multi_adcs) {
-                       p = spec->stream_analog_alt_capture;
-                       if (!p)
-                               p = &pcm_analog_alt_capture;
-                       info->stream[SNDRV_PCM_STREAM_CAPTURE] = *p;
-                       info->stream[SNDRV_PCM_STREAM_CAPTURE].nid =
-                               spec->adc_nids[1];
+                       setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
+                                        &pcm_analog_alt_capture,
+                                        spec->stream_analog_alt_capture,
+                                        spec->adc_nids[1]);
                        info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams =
                                spec->num_adc_nids - 1;
                } else {
-                       info->stream[SNDRV_PCM_STREAM_CAPTURE] =
-                               pcm_null_stream;
-                       info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = 0;
+                       setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
+                                        &pcm_null_stream, NULL, 0);
                }
        }
 
@@ -5452,8 +5766,6 @@ int snd_hda_gen_init(struct hda_codec *codec)
 
        snd_hda_apply_verbs(codec);
 
-       codec->cached_write = 1;
-
        init_multi_out(codec);
        init_extra_out(codec);
        init_multi_io(codec);
@@ -5464,10 +5776,12 @@ int snd_hda_gen_init(struct hda_codec *codec)
 
        clear_unsol_on_unused_pins(codec);
 
+       sync_all_pin_power_ctls(codec);
+
        /* call init functions of standard auto-mute helpers */
        update_automute_all(codec);
 
-       snd_hda_codec_flush_cache(codec);
+       regcache_sync(codec->core.regmap);
 
        if (spec->vmaster_mute.sw_kctl && spec->vmaster_mute.hook)
                snd_hda_sync_vmaster_hook(&spec->vmaster_mute);
@@ -5524,13 +5838,11 @@ static const struct hda_codec_ops generic_patch_ops = {
 #endif
 };
 
-/**
+/*
  * snd_hda_parse_generic_codec - Generic codec parser
  * @codec: the HDA codec
- *
- * This should be called from the HDA codec core.
  */
-int snd_hda_parse_generic_codec(struct hda_codec *codec)
+static int snd_hda_parse_generic_codec(struct hda_codec *codec)
 {
        struct hda_gen_spec *spec;
        int err;
@@ -5556,7 +5868,17 @@ error:
        snd_hda_gen_free(codec);
        return err;
 }
-EXPORT_SYMBOL_GPL(snd_hda_parse_generic_codec);
+
+static const struct hda_codec_preset snd_hda_preset_generic[] = {
+       { .id = HDA_CODEC_ID_GENERIC, .patch = snd_hda_parse_generic_codec },
+       {} /* terminator */
+};
+
+static struct hda_codec_driver generic_driver = {
+       .preset = snd_hda_preset_generic,
+};
+
+module_hda_codec_driver(generic_driver);
 
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("Generic HD-audio codec parser");