[PATCH 18/19] ata: ahci: fixes and updates for Marvell SATA controller

Luca Lauro <[email protected]>
Newsgroups org.kernel.feeds.b4-sent,org.infradead.lists.barebox
Message-ID <[email protected]>
---
 drivers/ata/ahci.c | 656 ++++++++++++++++++++++++++++++-----------------------
 drivers/ata/ahci.h |   1 +
 2 files changed, 374 insertions(+), 283 deletions(-)

diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index 819dc37b3e..6c32393959 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -19,7 +19,9 @@
 #include <disks.h>
 #include <ata_drive.h>
 #include <linux/sizes.h>
+#include <linux/pci.h>
 #include <clock.h>
+#include <poweroff.h>
 
 #include "ahci.h"
 
@@ -47,6 +49,54 @@
 #define ahci_debug(ahci, fmt, arg...) \
 	dev_dbg(ahci->dev, fmt, ##arg)
 
+#define ATA_CMD_FLUSH_EXT 0xEA
+#define ATA_CMD_STANDBYNOW1 0xE0
+
+#ifndef PCI_VENDOR_ID_MARVELL_EXT
+#define PCI_VENDOR_ID_MARVELL_EXT 0x1b4b
+#endif
+
+static LIST_HEAD(ahci_devices);
+
+static const struct pci_device_id ahci_pci_tbl[] = {
+    { PCI_DEVICE(PCI_VENDOR_ID_MARVELL_EXT, 0x9170) },
+    { 0, }
+};
+
+static int ahci_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+    struct ahci_device *ahci;
+    void __iomem *mmio;
+    int ret;
+
+    dev_info(&pdev->dev, "ahci: PCI probe %04x:%04x rev %02x\n",
+         pdev->vendor, pdev->device, pdev->revision);
+
+    ret = pci_enable_device(pdev);
+    if (ret)
+        return ret;
+
+    pci_set_master(pdev);
+
+    mmio = pci_iomap(pdev, 5);
+    if (!mmio)
+        return -ENODEV;
+
+    ahci = xzalloc(sizeof(*ahci));
+    ahci->dev = &pdev->dev;
+    ahci->mmio_base = mmio;
+    pdev->dev.priv = ahci;
+
+    ret = ahci_add_host(ahci);
+    if (ret) {
+        free(ahci);
+        pdev->dev.priv = NULL;
+        return ret;
+    }
+
+    return 0;
+}
+
 struct ahci_cmd_hdr {
 	u32	opts;
 	u32	status;
@@ -148,45 +198,63 @@ static int ahci_fill_sg(struct ahci_port *ahci_port, dma_addr_t buf_dma, int buf
 	return sg_count;
 }
 
-static int ahci_io(struct ahci_port *ahci_port, u8 *fis, int fis_len, void *rbuf,
-		const void *wbuf, int buf_len)
+static int ahci_io(struct ahci_port *ahci_port, u8 *fis, int fis_len,
+                   void *rbuf, const void *wbuf, int buf_len)
 {
-	u32 opts;
-	int sg_count;
-	int ret;
-	void *buf;
-	dma_addr_t buf_dma;
-	enum dma_data_direction dma_dir;
+    u32 opts;
+    int sg_count = 0;
+    int ret;
+    void *buf = NULL;
+    dma_addr_t buf_dma = 0;
+    enum dma_data_direction dma_dir = DMA_NONE;
 
-	if (!ahci_link_ok(ahci_port, 1))
-		return -EIO;
+    if (!ahci_link_ok(ahci_port, 1))
+        return -EIO;
 
-	if (wbuf) {
-		buf = (void *)wbuf;
-		dma_dir = DMA_TO_DEVICE;
-	} else {
-		buf = rbuf;
-		dma_dir = DMA_FROM_DEVICE;
-	}
+    if (buf_len > 0) {
+        if (wbuf) {
+            buf = (void *)wbuf;
+            dma_dir = DMA_TO_DEVICE;
+        } else {
+            buf = rbuf;
+            dma_dir = DMA_FROM_DEVICE;
+        }
 
-	buf_dma = dma_map_single(ahci_port->ahci->dev, buf, buf_len, dma_dir);
+        buf_dma = dma_map_single(ahci_port->ahci->dev, buf, buf_len, dma_dir);
+        sg_count = ahci_fill_sg(ahci_port, buf_dma, buf_len);
+    }
 
-	memcpy(ahci_port->cmd_tbl, fis, fis_len);
+    memcpy(ahci_port->cmd_tbl, fis, fis_len);
 
-	sg_count = ahci_fill_sg(ahci_port, buf_dma, buf_len);
-	opts = (fis_len >> 2) | (sg_count << 16);
-	if (wbuf)
-		opts |= CMD_LIST_OPTS_WRITE;
-	ahci_fill_cmd_slot(ahci_port, opts);
+    opts = (fis_len >> 2) | (sg_count << 16);
+    if (wbuf && buf_len > 0)
+        opts |= CMD_LIST_OPTS_WRITE;
+    ahci_fill_cmd_slot(ahci_port, opts);
 
-	ahci_port_write_f(ahci_port, PORT_CMD_ISSUE, 1);
+    ahci_port_write_f(ahci_port, PORT_CMD_ISSUE, 1);
 
-	ret = wait_on_timeout(WAIT_DATAIO,
-			(ahci_port_read(ahci_port, PORT_CMD_ISSUE) & 0x1) == 0);
+    ret = wait_on_timeout(WAIT_DATAIO,
+            (ahci_port_read(ahci_port, PORT_CMD_ISSUE) & 0x1) == 0);
 
-	dma_unmap_single(ahci_port->ahci->dev, buf_dma, buf_len, dma_dir);
+    if (buf_len > 0)
+        dma_unmap_single(ahci_port->ahci->dev, buf_dma, buf_len, dma_dir);
 
-	return ret;
+    return ret;
+}
+
+static int ahci_ata_nodata(struct ahci_port *ahci, u8 command, u8 feature)
+{
+    u8 fis[20] = {
+        0x27,        /* Host to device FIS */
+        1 << 7,      /* Command FIS */
+        command,     /* Command */
+        feature,     /* Features */
+    };
+
+    if (!ahci_link_ok(ahci, 0))
+        return -ENODEV;
+
+    return ahci_io(ahci, fis, sizeof(fis), NULL, NULL, 0);
 }
 
 /*
@@ -272,152 +340,88 @@ static int ahci_write(struct ata_port *ata, const void *buf, sector_t block,
 
 static int ahci_init_port(struct ahci_port *ahci_port)
 {
-	u32 val, cmd;
-	void *mem;
-	dma_addr_t mem_dma;
-	int ret;
-
-	/* make sure port is not active */
-	val = ahci_port_read(ahci_port, PORT_CMD);
-	if (val & (PORT_CMD_LIST_ON | PORT_CMD_FIS_ON | PORT_CMD_FIS_RX | PORT_CMD_START)) {
-		ahci_port_debug(ahci_port, "Port is active. Deactivating.\n");
-		val &= ~(PORT_CMD_LIST_ON | PORT_CMD_FIS_ON |
-			 PORT_CMD_FIS_RX | PORT_CMD_START);
-		ahci_port_write(ahci_port, PORT_CMD, val);
-
-		/*
-		 * spec says 500 msecs for each bit, so
-		 * this is slightly incorrect.
-		 */
-		mdelay(500);
-	}
-
-	mem = dma_alloc_coherent(DMA_DEVICE_BROKEN,
-				 AHCI_PORT_PRIV_DMA_SZ, &mem_dma);
-	if (!mem) {
-		return -ENOMEM;
-	}
-
-	/*
-	 * First item in chunk of DMA memory: 32-slot command list,
-	 * 32 bytes each in size
-	 */
-	ahci_port->cmd_slot = mem;
-	ahci_port->cmd_slot_dma = mem_dma;
-
-	ahci_port_debug(ahci_port, "cmd_slot = 0x%p (0x%pad)\n",
-			ahci_port->cmd_slot, &ahci_port->cmd_slot_dma);
-
-	/*
-	 * Second item: Received-FIS area
-	 */
-	ahci_port->rx_fis = mem + AHCI_CMD_LIST_SZ;
-	ahci_port->rx_fis_dma = mem_dma + AHCI_CMD_LIST_SZ;
-
-	/*
-	 * Third item: data area for storing a single command
-	 * and its scatter-gather table
-	 */
-	ahci_port->cmd_tbl = mem + AHCI_CMD_LIST_SZ + AHCI_RX_FIS_SZ;
-	ahci_port->cmd_tbl_dma = mem_dma + AHCI_CMD_LIST_SZ + AHCI_RX_FIS_SZ;
-
-	ahci_port_debug(ahci_port, "cmd_tbl = 0x%p (0x%pad)\n",
-			ahci_port->cmd_tbl, &ahci_port->cmd_tbl_dma);
-
-	ahci_port->cmd_tbl_sg = ahci_port->cmd_tbl + AHCI_CMD_TBL_HDR_SZ;
-
-	ahci_port_write_f(ahci_port, PORT_LST_ADDR, lower_32_bits(ahci_port->cmd_slot_dma));
-	if (ahci_port->ahci->cap & HOST_CAP_64)
-		ahci_port_write_f(ahci_port, PORT_LST_ADDR_HI, upper_32_bits(ahci_port->cmd_slot_dma));
-	ahci_port_write_f(ahci_port, PORT_FIS_ADDR, lower_32_bits(ahci_port->rx_fis_dma));
-	if (ahci_port->ahci->cap & HOST_CAP_64)
-		ahci_port_write_f(ahci_port, PORT_FIS_ADDR_HI, upper_32_bits(ahci_port->rx_fis_dma));
-
-	/*
-	 * Add the spinup command to whatever mode bits may
-	 * already be on in the command register.
-	 */
-	cmd = ahci_port_read(ahci_port, PORT_CMD);
-	cmd |= PORT_CMD_FIS_RX;
-	cmd |= PORT_CMD_SPIN_UP;
-	cmd |= PORT_CMD_ICC_ACTIVE;
-	ahci_port_write_f(ahci_port, PORT_CMD, cmd);
-
-	mdelay(10);
-
-	cmd = ahci_port_read(ahci_port, PORT_CMD);
-	cmd |= PORT_CMD_START;
-	ahci_port_write_f(ahci_port, PORT_CMD, cmd);
-
-	/*
-	 * Bring up SATA link.
-	 * SATA link bringup time is usually less than 1 ms; only very
-	 * rarely has it taken between 1-2 ms. Never seen it above 2 ms.
-	 */
-	ret = wait_on_timeout(WAIT_LINKUP,
-			(ahci_port_read(ahci_port, PORT_SCR_STAT) & PORT_SCR_STAT_DET) == 0x3);
-	if (ret) {
-		ahci_port_info(ahci_port, "SATA link timeout\n");
-		ret = -ETIMEDOUT;
-		goto err_init;
-	}
-
-	ahci_port_info(ahci_port, "SATA link ok\n");
-
-	/* Clear error status */
-	val = ahci_port_read(ahci_port, PORT_SCR_ERR);
-	if (val)
-		ahci_port_write(ahci_port, PORT_SCR_ERR, val);
-
-	ahci_port_info(ahci_port, "Spinning up device...\n");
-
-	ret = wait_on_timeout(WAIT_SPINUP,
-			((ahci_port_read(ahci_port, PORT_TFDATA) &
-			 (ATA_STATUS_BUSY | ATA_STATUS_DRQ)) == 0) ||
-			((ahci_port_read(ahci_port, PORT_SCR_STAT) &
-			 PORT_SCR_STAT_DET) == 1));
-	if (ret) {
-		ahci_port_info(ahci_port, "timeout.\n");
-		ret = -ENODEV;
-		goto err_init;
-	}
-
-	if ((ahci_port_read(ahci_port, PORT_SCR_STAT) & PORT_SCR_STAT_DET) == 1) {
-		ahci_port_info(ahci_port, "down.\n");
-		ret = -ENODEV;
-		goto err_init;
-	}
-
-	ahci_port_info(ahci_port, "ok.\n");
-
-	val = ahci_port_read(ahci_port, PORT_SCR_ERR);
-
-	ahci_port_write(ahci_port, PORT_SCR_ERR, val);
-
-	/* ack any pending irq events for this port */
-	val = ahci_port_read(ahci_port, PORT_IRQ_STAT);
-	if (val)
-		ahci_port_write(ahci_port, PORT_IRQ_STAT, val);
-
-	ahci_iowrite(ahci_port->ahci, HOST_IRQ_STAT, 1 << ahci_port->num);
-
-	/* set irq mask (enables interrupts) */
-	ahci_port_write(ahci_port, PORT_IRQ_MASK, DEF_PORT_IRQ);
-
-	/* register linkup ports */
-	val = ahci_port_read(ahci_port, PORT_SCR_STAT);
-
-	ahci_port_debug(ahci_port, "status: 0x%08x\n", val);
-
-	if ((val & PORT_SCR_STAT_DET) == 0x3)
-		return 0;
-
-	ret = -ENODEV;
-
-err_init:
-	dma_free_coherent(DMA_DEVICE_BROKEN,
-			  mem, mem_dma, AHCI_PORT_PRIV_DMA_SZ);
-	return ret;
+    u32 cmd, val;
+    void *mem;
+    dma_addr_t mem_dma;
+    int ret;
+
+    /* 1. Disable port (clear ST, FRE) */
+    cmd = ahci_port_read(ahci_port, PORT_CMD);
+    cmd &= ~(PORT_CMD_START | PORT_CMD_FIS_RX | PORT_CMD_FIS_ON |
+             PORT_CMD_LIST_ON | PORT_CMD_SPIN_UP);
+    ahci_port_write_f(ahci_port, PORT_CMD, cmd);
+
+    /* Wait for FR=0 and CR=0 */
+    ret = wait_on_timeout(SECOND,
+        !(ahci_port_read(ahci_port, PORT_CMD) &
+          (PORT_CMD_FIS_ON | PORT_CMD_LIST_ON)));
+    if (ret)
+        dev_warn(ahci_port->ahci->dev, "timeout waiting for port disable\n");
+
+    /* 2. Clear errors */
+    val = ahci_port_read(ahci_port, PORT_SCR_ERR);
+    if (val)
+        ahci_port_write(ahci_port, PORT_SCR_ERR, val);
+
+    /* 3. COMRESET: write DET=1 then DET=0 */
+    ahci_port_write(ahci_port, PORT_SCR_CTL, 1);
+    udelay(1000);
+    ahci_port_write(ahci_port, PORT_SCR_CTL, 0);
+
+    /* 4. Wait for PHY ready */
+    ret = wait_on_timeout(SECOND,
+        (ahci_port_read(ahci_port, PORT_SCR_STAT) & PORT_SCR_STAT_DET) == 0x3);
+    if (ret) {
+        ahci_port_info(ahci_port, "PHY not ready after COMRESET\n");
+        return -ETIMEDOUT;
+    }
+
+    /* 5. Allocate DMA memory (unchanged) */
+    mem = dma_alloc_coherent(DMA_DEVICE_BROKEN,
+                 AHCI_PORT_PRIV_DMA_SZ, &mem_dma);
+    if (!mem)
+        return -ENOMEM;
+
+    ahci_port->cmd_slot = mem;
+    ahci_port->cmd_slot_dma = mem_dma;
+    ahci_port->rx_fis = mem + AHCI_CMD_LIST_SZ;
+    ahci_port->rx_fis_dma = mem_dma + AHCI_CMD_LIST_SZ;
+    ahci_port->cmd_tbl = mem + AHCI_CMD_LIST_SZ + AHCI_RX_FIS_SZ;
+    ahci_port->cmd_tbl_dma = mem_dma + AHCI_CMD_LIST_SZ + AHCI_RX_FIS_SZ;
+    ahci_port->cmd_tbl_sg = ahci_port->cmd_tbl + AHCI_CMD_TBL_HDR_SZ;
+
+    /* 6. Program command list + FIS base addresses */
+    ahci_port_write_f(ahci_port, PORT_LST_ADDR,
+        lower_32_bits(ahci_port->cmd_slot_dma));
+    if (ahci_port->ahci->cap & HOST_CAP_64)
+        ahci_port_write_f(ahci_port, PORT_LST_ADDR_HI,
+            upper_32_bits(ahci_port->cmd_slot_dma));
+
+    ahci_port_write_f(ahci_port, PORT_FIS_ADDR,
+        lower_32_bits(ahci_port->rx_fis_dma));
+    if (ahci_port->ahci->cap & HOST_CAP_64)
+        ahci_port_write_f(ahci_port, PORT_FIS_ADDR_HI,
+            upper_32_bits(ahci_port->rx_fis_dma));
+
+    /* 7. Enable FIS receive engine */
+    cmd = ahci_port_read(ahci_port, PORT_CMD);
+    cmd |= PORT_CMD_FIS_RX;
+    ahci_port_write_f(ahci_port, PORT_CMD, cmd);
+
+    /* 8. Enable port start */
+    cmd |= PORT_CMD_START;
+    ahci_port_write_f(ahci_port, PORT_CMD, cmd);
+
+    /* 9. Wait for device ready (TFDATA not BUSY) */
+    ret = wait_on_timeout(WAIT_SPINUP,
+        !(ahci_port_read(ahci_port, PORT_TFDATA) &
+          (ATA_STATUS_BUSY | ATA_STATUS_DRQ)));
+    if (ret) {
+        ahci_port_info(ahci_port, "device not ready\n");
+        return -ENODEV;
+    }
+
+    return 0;
 }
 
 static int ahci_port_start(struct ata_port *ata_port)
@@ -441,49 +445,12 @@ static int ahci_port_start(struct ata_port *ata_port)
 }
 
 static struct ata_port_operations ahci_ops = {
-	.init = ahci_port_start,
-	.read_id = ahci_read_id,
-	.read = ahci_read,
-	.write = ahci_write,
+    .init       = ahci_port_start,
+    .read_id    = ahci_read_id,
+    .read       = ahci_read,
+    .write      = ahci_write,
 };
 
-#if 0
-/*
- * In the general case of generic rotating media it makes sense to have a
- * flush capability. It probably even makes sense in the case of SSDs because
- * one cannot always know for sure what kind of internal cache/flush mechanism
- * is embodied therein. At first it was planned to invoke this after the last
- * write to disk and before rebooting. In practice, knowing, a priori, which
- * is the last write is difficult. Because writing to the disk in u-boot is
- * very rare, this flush command will be invoked after every block write.
- */
-static int ata_io_flush(u8 port)
-{
-	u8 fis[20];
-	struct ahci_ioports *pp = &(probe_ent->port[port]);
-	volatile u8 *port_mmio = (volatile u8 *)pp->port_mmio;
-	u32 cmd_fis_len = 5;	/* five dwords */
-
-	/* Preset the FIS */
-	memset(fis, 0, 20);
-	fis[0] = 0x27;		 /* Host to device FIS. */
-	fis[1] = 1 << 7;	 /* Command FIS. */
-	fis[2] = ATA_CMD_FLUSH_EXT;
-
-	memcpy((unsigned char *)pp->cmd_tbl, fis, 20);
-	ahci_fill_cmd_slot(pp, cmd_fis_len);
-	mywritel_with_flush(1, port_mmio + PORT_CMD_ISSUE);
-
-	if (waiting_for_cmd_completed(port_mmio + PORT_CMD_ISSUE,
-			WAIT_MS_FLUSH, 0x1)) {
-		debug("scsi_ahci: flush command timeout on port %d.\n", port);
-		return -EIO;
-	}
-
-	return 0;
-}
-#endif
-
 void ahci_print_info(struct ahci_device *ahci)
 {
 	u32 vers, cap, cap2, impl, speed;
@@ -570,73 +537,71 @@ static int ahci_detect(struct device *dev)
 
 int ahci_add_host(struct ahci_device *ahci)
 {
-	u32 tmp, cap_save;
-	int n_ports, i, ret;
-
-	ahci->host_flags = ATA_FLAG_SATA
-				| ATA_FLAG_NO_LEGACY
-				| ATA_FLAG_MMIO
-				| ATA_FLAG_PIO_DMA
-				| ATA_FLAG_NO_ATAPI;
-	ahci->pio_mask = 0x1f;
-	ahci->udma_mask = 0x7f;	/* FIXME: assume to support UDMA6 */
-
-	ahci_debug(ahci, "ahci_host_init: start\n");
-
-	cap_save = ahci_ioread(ahci, HOST_CAP);
-	cap_save &= (HOST_CAP_SMPS | HOST_CAP_SPM);
-	cap_save |= HOST_CAP_SSS;  /* Staggered Spin-up. Not needed. */
-
-	/* global controller reset */
-	tmp = ahci_ioread(ahci, HOST_CTL);
-	if ((tmp & HOST_RESET) == 0)
-		ahci_iowrite_f(ahci, HOST_CTL, tmp | HOST_RESET);
-
-	/*
-	 * reset must complete within 1 second, or
-	 * the hardware should be considered fried.
-	 */
-	ret = wait_on_timeout(SECOND, (ahci_ioread(ahci, HOST_CTL) & HOST_RESET) == 0);
-	if (ret) {
-		ahci_debug(ahci, "controller reset failed (0x%x)\n", tmp);
-		return -ENODEV;
-	}
-
-	ahci_iowrite_f(ahci, HOST_CTL, HOST_AHCI_EN);
-	ahci_iowrite(ahci, HOST_CAP, cap_save);
-	ahci_iowrite_f(ahci, HOST_PORTS_IMPL, 0xf);
-
-	ahci->cap = ahci_ioread(ahci, HOST_CAP);
-	ahci->port_map = ahci_ioread(ahci, HOST_PORTS_IMPL);
-	ahci->n_ports = (ahci->cap & HOST_CAP_NP) + 1;
-
-	ahci_debug(ahci, "cap 0x%x  port_map 0x%x  n_ports %d\n",
-	      ahci->cap, ahci->port_map, ahci->n_ports);
-
-	n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
-
-	for (i = 0; i < n_ports; i++) {
-		struct ahci_port *ahci_port = &ahci->ports[i];
-
-		if (!(ahci->port_map & (1 << i)))
-			continue;
-
-		ahci_port->num = i;
-		ahci_port->ahci = ahci;
-		ahci_port->ata.dev = ahci->dev;
-		ahci_port->port_mmio = ahci_port_base(ahci->mmio_base, i);
-		ahci_port->ata.ops = &ahci_ops;
-		ahci_port->ata.ahci = true;
-		ata_port_register(&ahci_port->ata);
-	}
-
-	tmp = ahci_ioread(ahci, HOST_CTL);
-	ahci_iowrite(ahci, HOST_CTL, tmp | HOST_IRQ_EN);
-	tmp = ahci_ioread(ahci, HOST_CTL);
-
-	ahci->dev->detect = ahci_detect;
-
-	return 0;
+    u32 tmp;
+    int n_ports, i, ret;
+
+    ahci->host_flags = ATA_FLAG_SATA
+                | ATA_FLAG_NO_LEGACY
+                | ATA_FLAG_MMIO
+                | ATA_FLAG_PIO_DMA
+                | ATA_FLAG_NO_ATAPI;
+    ahci->pio_mask = 0x1f;
+    ahci->udma_mask = 0x7f; /* FIXME: assume to support UDMA6 */
+
+    ahci_debug(ahci, "ahci_host_init: start\n");
+
+    /*
+     * Global controller reset: forziamo sempre il bit HOST_RESET,
+     * come fa il kernel Linux, e aspettiamo che torni a 0.
+     */
+    tmp = ahci_ioread(ahci, HOST_CTL);
+    ahci_iowrite_f(ahci, HOST_CTL, tmp | HOST_RESET);
+
+    ret = wait_on_timeout(SECOND,
+                  (ahci_ioread(ahci, HOST_CTL) & HOST_RESET) == 0);
+    if (ret) {
+        ahci_debug(ahci, "controller reset failed (HOST_CTL=0x%x)\n",
+               ahci_ioread(ahci, HOST_CTL));
+        return -ENODEV;
+    }
+
+
+    tmp = ahci_ioread(ahci, HOST_CTL);
+    tmp |= HOST_AHCI_EN;
+    ahci_iowrite_f(ahci, HOST_CTL, tmp);
+
+    ahci->cap = ahci_ioread(ahci, HOST_CAP);
+    ahci->port_map = ahci_ioread(ahci, HOST_PORTS_IMPL);
+    ahci->n_ports = (ahci->cap & HOST_CAP_NP) + 1;
+
+    ahci_debug(ahci, "cap 0x%x  port_map 0x%x  n_ports %d\n",
+           ahci->cap, ahci->port_map, ahci->n_ports);
+
+    n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
+
+    for (i = 0; i < n_ports; i++) {
+        struct ahci_port *ahci_port = &ahci->ports[i];
+
+        if (!(ahci->port_map & (1 << i)))
+            continue;
+
+        ahci_port->num = i;
+        ahci_port->ahci = ahci;
+        ahci_port->ata.dev = ahci->dev;
+        ahci_port->port_mmio = ahci_port_base(ahci->mmio_base, i);
+        ahci_port->ata.ops = &ahci_ops;
+        ahci_port->ata.ahci = true;
+        ata_port_register(&ahci_port->ata);
+    }
+
+    /* enable HBA level interrupts */
+    tmp = ahci_ioread(ahci, HOST_CTL);
+    ahci_iowrite(ahci, HOST_CTL, tmp | HOST_IRQ_EN);
+
+    ahci->dev->detect = ahci_detect;
+	list_add(&ahci->list, &ahci_devices);
+
+    return 0;
 }
 
 static int ahci_probe(struct device *dev)
@@ -665,6 +630,124 @@ static int ahci_probe(struct device *dev)
 	return ret;
 }
 
+/* -------------------------------------------------------------------------- */
+/*                       AHCI shutdown helpers (kernel-like)                  */
+/* -------------------------------------------------------------------------- */
+
+/* Stop DMA engine (clear START, wait LIST_ON=0) */
+static int ahci_stop_engine(struct ahci_port *port)
+{
+    u32 cmd;
+
+    cmd = ahci_port_read(port, PORT_CMD);
+
+    /* Already stopped? */
+    if (!(cmd & (PORT_CMD_START | PORT_CMD_LIST_ON)))
+        return 0;
+
+    /* Clear START */
+    cmd &= ~PORT_CMD_START;
+    ahci_port_write_f(port, PORT_CMD, cmd);
+
+    /* Wait for LIST_ON to clear */
+    return wait_on_timeout(500 * MSECOND,
+        !(ahci_port_read(port, PORT_CMD) & PORT_CMD_LIST_ON));
+}
+
+/* Stop FIS receive engine (clear FIS_RX, wait FIS_ON=0) */
+static int ahci_stop_fis_rx(struct ahci_port *port)
+{
+    u32 cmd;
+
+    cmd = ahci_port_read(port, PORT_CMD);
+    cmd &= ~PORT_CMD_FIS_RX;
+    ahci_port_write_f(port, PORT_CMD, cmd);
+
+    /* Wait for FIS_ON to clear */
+    return wait_on_timeout(1000 * MSECOND,
+        !(ahci_port_read(port, PORT_CMD) & PORT_CMD_FIS_ON));
+}
+
+/* Stop all ports (libata_pci_shutdown_one equivalent) */
+static void __maybe_unused ahci_shutdown_host(struct ahci_device *ahci)
+{
+    int i, n_ports;
+
+    n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
+
+    for (i = 0; i < n_ports; i++) {
+        struct ahci_port *port = &ahci->ports[i];
+
+        if (!(ahci->port_map & (1 << i)))
+            continue;
+
+        /* Stop DMA engine */
+        ahci_stop_engine(port);
+
+        /* Stop FIS receive engine */
+        ahci_stop_fis_rx(port);
+    }
+}
+
+/* Issue FLUSH EXT + STANDBY IMMEDIATE */
+static void ahci_port_shutdown(struct ahci_port *port)
+{
+    if (!port->cmd_tbl || !port->cmd_slot)
+        return;
+
+    if (!ahci_link_ok(port, 0))
+        return;
+
+    if (ahci_ata_nodata(port, ATA_CMD_FLUSH_EXT, 0))
+        ahci_port_info(port, "FLUSH EXT failed\n");
+
+    if (ahci_ata_nodata(port, ATA_CMD_STANDBYNOW1, 0))
+        ahci_port_info(port, "STANDBY IMMEDIATE failed\n");
+}
+
+/* Full poweroff sequence */
+static void ahci_poweroff(struct poweroff_handler *handler, unsigned long flags)
+{
+    struct ahci_device *ahci;
+    int i, n_ports;
+
+    list_for_each_entry(ahci, &ahci_devices, list) {
+
+        if (!ahci->mmio_base)
+            continue;
+
+        /* 1. FLUSH + STANDBY su tutte le porte attive */
+        n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
+
+        for (i = 0; i < n_ports; i++) {
+            struct ahci_port *port = &ahci->ports[i];
+
+            if (!(ahci->port_map & (1 << i)))
+                continue;
+
+            ahci_port_shutdown(port);
+        }
+
+        /* 2. (opzionale) spegnere il controller dopo i comandi
+         *    Se vuoi tenerlo, fallo SOLO dopo i comandi:
+         *
+         * ahci_shutdown_host(ahci);
+         */
+    }
+}
+
+static struct poweroff_handler ahci_po_handler = {
+    .poweroff = ahci_poweroff,
+    .priority = 200,   /* più alto di gpio-poweroff */
+};
+
+static int ahci_register_poweroff(void)
+{
+    poweroff_handler_register(&ahci_po_handler);
+    return 0;
+}
+postcore_initcall(ahci_register_poweroff);
+
 static __maybe_unused struct of_device_id ahci_dt_ids[] = {
 	{
 		.compatible = "calxeda,hb-ahci",
@@ -674,6 +757,13 @@ static __maybe_unused struct of_device_id ahci_dt_ids[] = {
 };
 MODULE_DEVICE_TABLE(of, ahci_dt_ids);
 
+static struct pci_driver ahci_pci_driver = {
+	.name = "ahci-pci",
+	.id_table = ahci_pci_tbl,
+	.probe = ahci_pci_probe,
+};
+device_pci_driver(ahci_pci_driver);
+
 static struct driver ahci_driver = {
 	.name   = "ahci",
 	.probe  = ahci_probe,
diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h
index 196bde73c2..d2a19f4648 100644
--- a/drivers/ata/ahci.h
+++ b/drivers/ata/ahci.h
@@ -184,6 +184,7 @@ struct ahci_port {
 };
 
 struct ahci_device {
+	struct list_head list;
 	struct device		*dev;
 	struct ahci_port	ports[AHCI_MAX_PORTS];
 	u32			n_ports;

-- 
2.47.3
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.