ALSA: hda - Add sanity check in PCM open callback
[pandora-kernel.git] / sound / pci / hda / hda_intel.c
index f63bc65..1877d95 100644 (file)
@@ -148,7 +148,7 @@ MODULE_DESCRIPTION("Intel HDA driver");
 #define ICH6_REG_OUTPAY                        0x04
 #define ICH6_REG_INPAY                 0x06
 #define ICH6_REG_GCTL                  0x08
-#define   ICH6_GCTL_RESET      (1 << 1)   /* controller reset */
+#define   ICH6_GCTL_RESET      (1 << 0)   /* controller reset */
 #define   ICH6_GCTL_FCNTRL     (1 << 1)   /* flush control */
 #define   ICH6_GCTL_UNSOL      (1 << 8)   /* accept unsol. response enable */
 #define ICH6_REG_WAKEEN                        0x0c
@@ -661,14 +661,23 @@ static unsigned int azx_rirb_get_response(struct hda_bus *bus)
                return -1;
        }
 
-       snd_printk(KERN_ERR SFX "azx_get_response timeout (ERROR): "
-                  "last cmd=0x%08x\n", chip->last_cmd);
-       /* re-initialize CORB/RIRB */
-       spin_lock_irq(&chip->reg_lock);
+       /* a fatal communication error; need either to reset or to fallback
+        * to the single_cmd mode
+        */
        bus->rirb_error = 1;
+       if (bus->allow_bus_reset && !bus->response_reset && !bus->in_reset) {
+               bus->response_reset = 1;
+               return -1; /* give a chance to retry */
+       }
+
+       snd_printk(KERN_ERR "hda_intel: azx_get_response timeout, "
+                  "switching to single_cmd mode: last cmd=0x%08x\n",
+                  chip->last_cmd);
+       chip->single_cmd = 1;
+       bus->response_reset = 0;
+       /* re-initialize CORB/RIRB */
        azx_free_cmd_io(chip);
        azx_init_cmd_io(chip);
-       spin_unlock_irq(&chip->reg_lock);
        return -1;
 }
 
@@ -709,6 +718,7 @@ static int azx_single_send_cmd(struct hda_bus *bus, u32 val)
        struct azx *chip = bus->private_data;
        int timeout = 50;
 
+       bus->rirb_error = 0;
        while (timeout--) {
                /* check ICB busy bit */
                if (!((azx_readw(chip, IRS) & ICH6_IRS_BUSY))) {
@@ -1247,6 +1257,26 @@ static int azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec,
                                 struct hda_pcm *cpcm);
 static void azx_stop_chip(struct azx *chip);
 
+static void azx_bus_reset(struct hda_bus *bus)
+{
+       struct azx *chip = bus->private_data;
+
+       bus->in_reset = 1;
+       azx_stop_chip(chip);
+       azx_init_chip(chip);
+#ifdef CONFIG_PM
+       if (chip->initialized) {
+               int i;
+
+               for (i = 0; i < AZX_MAX_PCMS; i++)
+                       snd_pcm_suspend_all(chip->pcm[i]);
+               snd_hda_suspend(chip->bus);
+               snd_hda_resume(chip->bus);
+       }
+#endif
+       bus->in_reset = 0;
+}
+
 /*
  * Codec initialization
  */
@@ -1270,6 +1300,7 @@ static int __devinit azx_codec_create(struct azx *chip, const char *model,
        bus_temp.ops.command = azx_send_cmd;
        bus_temp.ops.get_response = azx_get_response;
        bus_temp.ops.attach_pcm = azx_attach_pcm_stream;
+       bus_temp.ops.bus_reset = azx_bus_reset;
 #ifdef CONFIG_SND_HDA_POWER_SAVE
        bus_temp.power_save = &power_save;
        bus_temp.ops.pm_notify = azx_power_notify;
@@ -1423,6 +1454,7 @@ static int azx_pcm_open(struct snd_pcm_substream *substream)
                mutex_unlock(&chip->open_mutex);
                return err;
        }
+       snd_pcm_limit_hw_rates(runtime);
        spin_lock_irqsave(&chip->reg_lock, flags);
        azx_dev->substream = substream;
        azx_dev->running = 0;
@@ -1432,6 +1464,12 @@ static int azx_pcm_open(struct snd_pcm_substream *substream)
        snd_pcm_set_sync(substream);
        mutex_unlock(&chip->open_mutex);
 
+       if (snd_BUG_ON(!runtime->hw.channels_min || !runtime->hw.channels_max))
+               return -EINVAL;
+       if (snd_BUG_ON(!runtime->hw.formats))
+               return -EINVAL;
+       if (snd_BUG_ON(!runtime->hw.rates))
+               return -EINVAL;
        return 0;
 }
 
@@ -1997,7 +2035,7 @@ static int azx_suspend(struct pci_dev *pci, pm_message_t state)
        for (i = 0; i < AZX_MAX_PCMS; i++)
                snd_pcm_suspend_all(chip->pcm[i]);
        if (chip->initialized)
-               snd_hda_suspend(chip->bus, state);
+               snd_hda_suspend(chip->bus);
        azx_stop_chip(chip);
        if (chip->irq >= 0) {
                free_irq(chip->irq, chip);