Cable detection on Nvidia PATA hosts is pathetic. The current nv_cable_detect() assumes that the native cable detection only mistakes 80c as 40c but this isn't true. On ASUS A8N-E, cable register almost always says 80c is attached and it also manages to trick the drive that the cable is 80c. Also, BIOS checking, and before the get_acpi_cbl() update ACPI checking too, in the current implementation were useless because they read the setting _after_ reset is complete which forces the host into PIO0, so they never triggered. Reimplement nv_cable_detect() such that... * BIOS setting is cached during controller initialization and restored on driver detach. * BIOS and ACPI cable results are considered more important. Native cable bits are used iff both BIOS and ACPI results are PATA_UNK. If BIOS or ACPI indicates 80c, it's 80c. If none indicates 80c, it's 40c. This change makes cable detection work correctly all the time on ASUS A8N-E and should also work well on other NV PATA configurations. Signed-off-by: Tejun Heo <htejun@xxxxxxxxx> --- drivers/ata/pata_amd.c | 112 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 22 deletions(-) Index: work/drivers/ata/pata_amd.c =================================================================== --- work.orig/drivers/ata/pata_amd.c +++ work/drivers/ata/pata_amd.c @@ -255,26 +255,69 @@ static int nv_cable_detect(struct ata_po { static const u8 bitmask[2] = {0x03, 0x0C}; struct pci_dev *pdev = to_pci_dev(ap->host->dev); + struct ata_device *dev; + char acpi_str[32] = ""; + int native_cbl, bios_cbl, acpi_cbl, verdict; + u32 saved_udma, udma; u8 ata66; - u16 udma; - int cbl; + /* native method, this is usually broken */ pci_read_config_byte(pdev, 0x52, &ata66); if (ata66 & bitmask[ap->port_no]) - cbl = ATA_CBL_PATA80; + native_cbl = ATA_CBL_PATA80; else - cbl = ATA_CBL_PATA40; + native_cbl = ATA_CBL_PATA40; - /* We now have to double check because the Nvidia boxes BIOS - doesn't always set the cable bits but does set mode bits */ - pci_read_config_word(pdev, 0x62 - 2 * ap->port_no, &udma); - if ((udma & 0xC4) == 0xC4 || (udma & 0xC400) == 0xC400) - cbl = ATA_CBL_PATA80; - /* And a triple check across suspend/resume with ACPI around */ - if ((ap->pflags & ATA_PFLAG_INIT_GTM_VALID) && - ata_acpi_cbl(ap, &ap->acpi_init_gtm) == ATA_CBL_PATA80) - cbl = ATA_CBL_PATA80; - return cbl; + /* peek BIOS configuration */ + bios_cbl = ATA_CBL_PATA_UNK; + + saved_udma = udma = (unsigned long)ap->host->private_data; + if (ap->port_no == 0) + udma >>= 16; + + ata_link_for_each_dev(dev, &ap->link) { + u32 tmp = udma; + + if (dev->devno == 0) + tmp >>= 8; + + if (!ata_dev_enabled(dev) || (tmp & 0xC0) != 0xC0) + continue; + + if (tmp & 0x4) + bios_cbl = ATA_CBL_PATA80; + else + bios_cbl = ATA_CBL_PATA40; + } + + /* and ACPI configuration */ + acpi_cbl = ATA_CBL_PATA_UNK; + if (ap->pflags & ATA_PFLAG_INIT_GTM_VALID) + acpi_cbl = ata_acpi_cbl(ap, &ap->acpi_init_gtm); + + /* Three values collected. Native value is usually + * untrustworthy. Consider it iff both BIOS and ACPI values + * are unknown; otherwise, choose higher of the two. + */ + if (bios_cbl == ATA_CBL_PATA_UNK && acpi_cbl == ATA_CBL_PATA_UNK) + verdict = native_cbl; + else if (bios_cbl == ATA_CBL_PATA80 || acpi_cbl == ATA_CBL_PATA80) + verdict = ATA_CBL_PATA80; + else + verdict = ATA_CBL_PATA40; + + if (ap->pflags & ATA_PFLAG_INIT_GTM_VALID) + snprintf(acpi_str, sizeof(acpi_str), " (%u:%u:0x%x)", + ap->acpi_init_gtm.drive[0].dma, + ap->acpi_init_gtm.drive[1].dma, + ap->acpi_init_gtm.flags); + + ata_port_printk(ap, KERN_DEBUG, "nv_cable_detect: native=%d (0x%x) " + "BIOS=%d (0x%x) ACPI=%d%s verdict=%d\n", + native_cbl, ata66, bios_cbl, saved_udma, acpi_cbl, + acpi_str, verdict); + + return verdict; } /** @@ -314,6 +357,14 @@ static void nv133_set_dmamode(struct ata timing_setup(ap, adev, 0x50, adev->dma_mode, 4); } +static void nv_host_stop(struct ata_host *host) +{ + u32 udma = (unsigned long)host->private_data; + + /* restore PCI config register 0x60 */ + pci_write_config_dword(to_pci_dev(host->dev), 0x60, udma); +} + static struct scsi_host_template amd_sht = { .module = THIS_MODULE, .name = DRV_NAME, @@ -495,6 +546,7 @@ static struct ata_port_operations nv100_ .irq_on = ata_irq_on, .port_start = ata_sff_port_start, + .host_stop = nv_host_stop, }; static struct ata_port_operations nv133_port_ops = { @@ -528,6 +580,7 @@ static struct ata_port_operations nv133_ .irq_on = ata_irq_on, .port_start = ata_sff_port_start, + .host_stop = nv_host_stop, }; static int amd_init_one(struct pci_dev *pdev, const struct pci_device_id *id) @@ -614,7 +667,8 @@ static int amd_init_one(struct pci_dev * .port_ops = &amd100_port_ops } }; - const struct ata_port_info *ppi[] = { NULL, NULL }; + struct ata_port_info pi; + const struct ata_port_info *ppi[] = { &pi, NULL }; static int printed_version; int type = id->driver_data; u8 fifo; @@ -628,6 +682,19 @@ static int amd_init_one(struct pci_dev * if (type == 1 && pdev->revision > 0x7) type = 2; + /* Serenade ? */ + if (type == 5 && pdev->subsystem_vendor == PCI_VENDOR_ID_AMD && + pdev->subsystem_device == PCI_DEVICE_ID_AMD_SERENADE) + type = 6; /* UDMA 100 only */ + + /* + * Okay, type is determined now. Apply type-specific workarounds. + */ + pi = info[type]; + + if (type < 3) + ata_pci_clear_simplex(pdev); + /* Check for AMD7411 */ if (type == 3) /* FIFO is broken */ @@ -635,16 +702,17 @@ static int amd_init_one(struct pci_dev * else pci_write_config_byte(pdev, 0x41, fifo | 0xF0); - /* Serenade ? */ - if (type == 5 && pdev->subsystem_vendor == PCI_VENDOR_ID_AMD && - pdev->subsystem_device == PCI_DEVICE_ID_AMD_SERENADE) - type = 6; /* UDMA 100 only */ + /* Cable detection on Nvidia chips doesn't work too well, + * cache BIOS programmed UDMA mode. + */ + if (type == 7 || type == 8) { + u32 udma; - if (type < 3) - ata_pci_clear_simplex(pdev); + pci_read_config_dword(pdev, 0x60, &udma); + pi.private_data = (void *)(unsigned long)udma; + } /* And fire it up */ - ppi[0] = &info[type]; return ata_pci_init_one(pdev, ppi); } - To unsubscribe from this list: send the line "unsubscribe linux-ide" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html