[PATCH 18/19] ata: ahci: fixes and updates for Marvell SATA controller
Sascha Hauer
s.hauer at pengutronix.de
Mon Jul 27 07:15:27 PDT 2026
This patch basically rewrites the whole file. It seems most changes are
whitespace only. Please rework in a way that makes the changes visible.
After that we'll likely need a discussion why we need it and how we
integrate it without breaking others.
Sascha
On 2026-07-23 15:57, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l at gmail.com>
>
> ---
> 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
>
>
>
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
More information about the barebox
mailing list