diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index bf12f68..1db6eb0 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -2730,6 +2730,11 @@ int ata_down_xfermask_limit(struct ata_device *dev, unsigned int sel) udma_mask = 0; break; + case ATA_DNXFER_ANY: + highbit = fls(xfer_mask) - 1; + xfer_mask &= ~(1 << highbit); + break; + default: BUG(); } @@ -2773,6 +2778,12 @@ static int ata_dev_set_mode(struct ata_device *dev) if (dev->xfer_shift == ATA_SHIFT_PIO && ata_id_is_cfa(dev->id)) err_mask &= ~AC_ERR_DEV; + if (err_mask == AC_ERR_DEV) { + ata_dev_printk(dev, KERN_WARNING, "device rejected " + "transfer mode, lowering speed\n"); + return -EAGAIN; + } + if (err_mask) { ata_dev_printk(dev, KERN_ERR, "failed to set xfermode " "(err_mask=0x%x)\n", err_mask); @@ -2912,10 +2923,24 @@ int ata_do_set_mode(struct ata_port *ap, struct ata_device **r_failed_dev) */ int ata_set_mode(struct ata_port *ap, struct ata_device **r_failed_dev) { + int rc; + + retry: /* has private set_mode? */ if (ap->ops->set_mode) - return ap->ops->set_mode(ap, r_failed_dev); - return ata_do_set_mode(ap, r_failed_dev); + rc = ap->ops->set_mode(ap, r_failed_dev); + else + rc = ata_do_set_mode(ap, r_failed_dev); + + /* lower speed and retry if driver or controller vetoed the mode */ + if (rc == -EAGAIN) { + if (!(ap->pflags & ATA_PFLAG_FROZEN) && + !ata_down_xfermask_limit(*r_failed_dev, ATA_DNXFER_ANY)) + goto retry; + rc = -EIO; + } + + return rc; } /** diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h index ba17fc5..ece1ecf 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -48,6 +48,7 @@ enum { ATA_DNXFER_40C = 2, /* apply 40c cable limit */ ATA_DNXFER_FORCE_PIO = 3, /* force PIO */ ATA_DNXFER_FORCE_PIO0 = 4, /* force PIO0 */ + ATA_DNXFER_ANY = 5, /* speed down any */ ATA_DNXFER_QUIET = (1 << 31), };