ALSA: hda - Add codec bus reset and verb-retry at critical errors

Some machines machine cause a severe CORB/RIRB stall in certain
weird conditions, such as PA access at the start up together with
fglrx driver.  This seems unable to be recovered without the controller
reset.

This patch allows the bus controller reset at critical errors so
that the communication gets recovered again.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2009-06-02 01:16:07 +02:00
parent 8871e5b915
commit 8dd783304e
3 changed files with 55 additions and 23 deletions

View File

@ -165,28 +165,29 @@ static int codec_exec_verb(struct hda_codec *codec, unsigned int cmd,
unsigned int *res) unsigned int *res)
{ {
struct hda_bus *bus = codec->bus; struct hda_bus *bus = codec->bus;
int err, repeated = 0; int err;
if (res) if (res)
*res = -1; *res = -1;
again:
snd_hda_power_up(codec); snd_hda_power_up(codec);
mutex_lock(&bus->cmd_mutex); mutex_lock(&bus->cmd_mutex);
again:
err = bus->ops.command(bus, cmd); err = bus->ops.command(bus, cmd);
if (!err) { if (!err && res)
if (res) {
*res = bus->ops.get_response(bus); *res = bus->ops.get_response(bus);
if (*res == -1 && bus->rirb_error) {
if (repeated++ < 1) {
snd_printd(KERN_WARNING "hda_codec: "
"Trying verb 0x%08x again\n", cmd);
goto again;
}
}
}
}
mutex_unlock(&bus->cmd_mutex); mutex_unlock(&bus->cmd_mutex);
snd_hda_power_down(codec); snd_hda_power_down(codec);
if (res && *res == -1 && bus->rirb_error) {
if (bus->response_reset) {
snd_printd("hda_codec: resetting BUS due to "
"fatal communication error\n");
bus->ops.bus_reset(bus);
}
goto again;
}
/* clear reset-flag when the communication gets recovered */
if (!err)
bus->response_reset = 0;
return err; return err;
} }
@ -3894,11 +3895,10 @@ EXPORT_SYMBOL_HDA(auto_pin_cfg_labels);
/** /**
* snd_hda_suspend - suspend the codecs * snd_hda_suspend - suspend the codecs
* @bus: the HDA bus * @bus: the HDA bus
* @state: suspsend state
* *
* Returns 0 if successful. * Returns 0 if successful.
*/ */
int snd_hda_suspend(struct hda_bus *bus, pm_message_t state) int snd_hda_suspend(struct hda_bus *bus)
{ {
struct hda_codec *codec; struct hda_codec *codec;

View File

@ -574,6 +574,8 @@ struct hda_bus_ops {
/* attach a PCM stream */ /* attach a PCM stream */
int (*attach_pcm)(struct hda_bus *bus, struct hda_codec *codec, int (*attach_pcm)(struct hda_bus *bus, struct hda_codec *codec,
struct hda_pcm *pcm); struct hda_pcm *pcm);
/* reset bus for retry verb */
void (*bus_reset)(struct hda_bus *bus);
#ifdef CONFIG_SND_HDA_POWER_SAVE #ifdef CONFIG_SND_HDA_POWER_SAVE
/* notify power-up/down from codec to controller */ /* notify power-up/down from codec to controller */
void (*pm_notify)(struct hda_bus *bus); void (*pm_notify)(struct hda_bus *bus);
@ -624,6 +626,8 @@ struct hda_bus {
unsigned int needs_damn_long_delay :1; unsigned int needs_damn_long_delay :1;
unsigned int shutdown :1; /* being unloaded */ unsigned int shutdown :1; /* being unloaded */
unsigned int rirb_error:1; /* error in codec communication */ unsigned int rirb_error:1; /* error in codec communication */
unsigned int response_reset:1; /* controller was reset */
unsigned int in_reset:1; /* during reset operation */
}; };
/* /*
@ -907,7 +911,7 @@ void snd_hda_get_codec_name(struct hda_codec *codec, char *name, int namelen);
* power management * power management
*/ */
#ifdef CONFIG_PM #ifdef CONFIG_PM
int snd_hda_suspend(struct hda_bus *bus, pm_message_t state); int snd_hda_suspend(struct hda_bus *bus);
int snd_hda_resume(struct hda_bus *bus); int snd_hda_resume(struct hda_bus *bus);
#endif #endif

View File

@ -661,14 +661,23 @@ static unsigned int azx_rirb_get_response(struct hda_bus *bus)
return -1; return -1;
} }
snd_printk(KERN_ERR SFX "azx_get_response timeout (ERROR): " /* a fatal communication error; need either to reset or to fallback
"last cmd=0x%08x\n", chip->last_cmd); * to the single_cmd mode
/* re-initialize CORB/RIRB */ */
spin_lock_irq(&chip->reg_lock);
bus->rirb_error = 1; bus->rirb_error = 1;
if (!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_free_cmd_io(chip);
azx_init_cmd_io(chip); azx_init_cmd_io(chip);
spin_unlock_irq(&chip->reg_lock);
return -1; return -1;
} }
@ -709,6 +718,7 @@ static int azx_single_send_cmd(struct hda_bus *bus, u32 val)
struct azx *chip = bus->private_data; struct azx *chip = bus->private_data;
int timeout = 50; int timeout = 50;
bus->rirb_error = 0;
while (timeout--) { while (timeout--) {
/* check ICB busy bit */ /* check ICB busy bit */
if (!((azx_readw(chip, IRS) & ICH6_IRS_BUSY))) { if (!((azx_readw(chip, IRS) & ICH6_IRS_BUSY))) {
@ -1247,6 +1257,23 @@ static int azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec,
struct hda_pcm *cpcm); struct hda_pcm *cpcm);
static void azx_stop_chip(struct azx *chip); static void azx_stop_chip(struct azx *chip);
static void azx_bus_reset(struct hda_bus *bus)
{
struct azx *chip = bus->private_data;
int i;
bus->in_reset = 1;
azx_stop_chip(chip);
azx_init_chip(chip);
if (chip->initialized) {
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);
}
bus->in_reset = 0;
}
/* /*
* Codec initialization * Codec initialization
*/ */
@ -1270,6 +1297,7 @@ static int __devinit azx_codec_create(struct azx *chip, const char *model,
bus_temp.ops.command = azx_send_cmd; bus_temp.ops.command = azx_send_cmd;
bus_temp.ops.get_response = azx_get_response; bus_temp.ops.get_response = azx_get_response;
bus_temp.ops.attach_pcm = azx_attach_pcm_stream; bus_temp.ops.attach_pcm = azx_attach_pcm_stream;
bus_temp.ops.bus_reset = azx_bus_reset;
#ifdef CONFIG_SND_HDA_POWER_SAVE #ifdef CONFIG_SND_HDA_POWER_SAVE
bus_temp.power_save = &power_save; bus_temp.power_save = &power_save;
bus_temp.ops.pm_notify = azx_power_notify; bus_temp.ops.pm_notify = azx_power_notify;
@ -1997,7 +2025,7 @@ static int azx_suspend(struct pci_dev *pci, pm_message_t state)
for (i = 0; i < AZX_MAX_PCMS; i++) for (i = 0; i < AZX_MAX_PCMS; i++)
snd_pcm_suspend_all(chip->pcm[i]); snd_pcm_suspend_all(chip->pcm[i]);
if (chip->initialized) if (chip->initialized)
snd_hda_suspend(chip->bus, state); snd_hda_suspend(chip->bus);
azx_stop_chip(chip); azx_stop_chip(chip);
if (chip->irq >= 0) { if (chip->irq >= 0) {
free_irq(chip->irq, chip); free_irq(chip->irq, chip);