[PATCH 3/3] pata_amd: fix and improve cable detection

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

[Index of Archives]     [Linux Filesystems]     [Linux SCSI]     [Linux RAID]     [Git]     [Kernel Newbies]     [Linux Newbie]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Samba]     [Device Mapper]

  Powered by Linux