summaryrefslogtreecommitdiffstats
path: root/drivers/ata/ahci.c
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2012-12-05 16:26:18 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2012-12-07 12:19:45 +0100
commit358ba37082ee094f89abaab63c1272fc50ac1f9f (patch)
treeea796edc5bff6c33d336e2b516563176052049aa /drivers/ata/ahci.c
parent9dfc9a8e752fe511fe2e844f522c8a5b059bedef (diff)
downloadbarebox-358ba37082ee094f89abaab63c1272fc50ac1f9f.tar.gz
barebox-358ba37082ee094f89abaab63c1272fc50ac1f9f.tar.xz
ata: Add ahci support
This adds ahci controller support based on U-Boot ahci support. Unlike U-Boot we do not push the SCSI layer in between, but use the ata interface directly. Tested on a Freescale i.MX53. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/ata/ahci.c')
-rw-r--r--drivers/ata/ahci.c678
1 files changed, 678 insertions, 0 deletions
diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
new file mode 100644
index 0000000000..14de3c5061
--- /dev/null
+++ b/drivers/ata/ahci.c
@@ -0,0 +1,678 @@
+/*
+ * Copyright (C) Freescale Semiconductor, Inc. 2006.
+ * Author: Jason Jin<Jason.jin@freescale.com>
+ * Zhang Wei<wei.zhang@freescale.com>
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ * with the reference on libata and ahci drvier in kernel
+ *
+ */
+
+#include <common.h>
+#include <init.h>
+#include <errno.h>
+#include <io.h>
+#include <malloc.h>
+#include <scsi.h>
+#include <linux/ctype.h>
+#include <disks.h>
+#include <asm/mmu.h>
+#include <ata_drive.h>
+#include <sizes.h>
+#include <clock.h>
+
+#include "ahci.h"
+
+#define AHCI_MAX_DATA_BYTE_COUNT SZ_4M
+
+/*
+ * Some controllers limit number of blocks they can read/write at once.
+ * Contemporary SSD devices work much faster if the read/write size is aligned
+ * to a power of 2.
+ */
+#define MAX_SATA_BLOCKS_READ_WRITE 0x80
+
+/* Maximum timeouts for each event */
+#define WAIT_SPINUP (10 * SECOND)
+#define WAIT_DATAIO (5 * SECOND)
+#define WAIT_FLUSH (5 * SECOND)
+#define WAIT_LINKUP (4 * MSECOND)
+
+#define ahci_port_debug(port, fmt, arg...) \
+ dev_dbg(port->ahci->dev, "port %d: " fmt, port->num, ##arg)
+
+#define ahci_port_info(port, fmt, arg...) \
+ dev_info(port->ahci->dev, "port %d: " fmt, port->num, ##arg)
+
+#define ahci_debug(ahci, fmt, arg...) \
+ dev_dbg(ahci->dev, fmt, ##arg)
+
+struct ahci_cmd_hdr {
+ u32 opts;
+ u32 status;
+ u32 tbl_addr;
+ u32 tbl_addr_hi;
+ u32 reserved[4];
+};
+
+struct ahci_sg {
+ u32 addr;
+ u32 addr_hi;
+ u32 reserved;
+ u32 flags_size;
+};
+
+static inline void ahci_iowrite(struct ahci_device *ahci, int ofs, u32 val)
+{
+ writel(val, ahci->mmio_base + ofs);
+}
+
+static inline u32 ahci_ioread(struct ahci_device *ahci, int ofs)
+{
+ return readl(ahci->mmio_base + ofs);
+}
+
+static inline void ahci_iowrite_f(struct ahci_device *ahci, int ofs, u32 val)
+{
+ writel(val, ahci->mmio_base + ofs);
+ readl(ahci->mmio_base);
+}
+
+static inline void ahci_port_write(struct ahci_port *port, int ofs, u32 val)
+{
+ writel(val, port->port_mmio + ofs);
+}
+
+static inline void ahci_port_write_f(struct ahci_port *port, int ofs, u32 val)
+{
+ writel(val, port->port_mmio + ofs);
+ readl(port->port_mmio + ofs);
+}
+
+static inline u32 ahci_port_read(struct ahci_port *port, int ofs)
+{
+ return readl(port->port_mmio + ofs);
+}
+
+static inline void __iomem *ahci_port_base(void __iomem *base, int port)
+{
+ return base + 0x100 + (port * 0x80);
+}
+
+static int ahci_link_ok(struct ahci_port *ahci_port, int verbose)
+{
+ u32 val = ahci_port_read(ahci_port, PORT_SCR_STAT) & 0xf;
+
+ if (val == 0x3)
+ return true;
+
+ if (verbose)
+ dev_err(ahci_port->ahci->dev, "port %d: no link\n", ahci_port->num);
+
+ return false;
+}
+
+static void ahci_fill_cmd_slot(struct ahci_port *ahci_port, u32 opts)
+{
+ ahci_port->cmd_slot->opts = cpu_to_le32(opts);
+ ahci_port->cmd_slot->status = 0;
+ ahci_port->cmd_slot->tbl_addr =
+ cpu_to_le32((unsigned long)ahci_port->cmd_tbl & 0xffffffff);
+ ahci_port->cmd_slot->tbl_addr_hi = 0;
+}
+
+static int ahci_fill_sg(struct ahci_port *ahci_port, const void *buf, int buf_len)
+{
+ struct ahci_sg *ahci_sg = ahci_port->cmd_tbl_sg;
+ u32 sg_count;
+
+ sg_count = ((buf_len - 1) / AHCI_MAX_DATA_BYTE_COUNT) + 1;
+ if (sg_count > AHCI_MAX_SG)
+ return -EINVAL;
+
+ while (buf_len) {
+ unsigned int now = min(AHCI_MAX_DATA_BYTE_COUNT, buf_len);
+
+ ahci_sg->addr = cpu_to_le32((u32)buf);
+ ahci_sg->addr_hi = 0;
+ ahci_sg->flags_size = cpu_to_le32(now - 1);
+
+ buf_len -= now;
+ buf += now;
+ }
+
+ 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)
+{
+ u32 opts;
+ int sg_count;
+ int ret;
+
+ if (!ahci_link_ok(ahci_port, 1))
+ return -EIO;
+
+ if (wbuf)
+ dma_flush_range((unsigned long)wbuf, (unsigned long)wbuf + buf_len);
+
+ memcpy((unsigned char *)ahci_port->cmd_tbl, fis, fis_len);
+
+ sg_count = ahci_fill_sg(ahci_port, rbuf ? rbuf : wbuf, buf_len);
+ opts = (fis_len >> 2) | (sg_count << 16);
+ if (wbuf)
+ opts |= 1 << 6;
+ ahci_fill_cmd_slot(ahci_port, opts);
+
+ ahci_port_write_f(ahci_port, PORT_CMD_ISSUE, 1);
+
+ ret = wait_on_timeout(WAIT_DATAIO,
+ (readl(ahci_port->port_mmio + PORT_CMD_ISSUE) & 0x1) == 0);
+ if (ret)
+ return -ETIMEDOUT;
+
+ if (rbuf)
+ dma_inv_range((unsigned long)rbuf, (unsigned long)rbuf + buf_len);
+
+ return 0;
+}
+
+/*
+ * SCSI INQUIRY command operation.
+ */
+static int ahci_read_id(struct ata_port *ata, void *buf)
+{
+ struct ahci_port *ahci = container_of(ata, struct ahci_port, ata);
+ u8 fis[20];
+ int ret;
+
+ memset(fis, 0, sizeof(fis));
+
+ /* Construct the FIS */
+ fis[0] = 0x27; /* Host to device FIS. */
+ fis[1] = 1 << 7; /* Command FIS. */
+ fis[2] = ATA_CMD_ID_ATA; /* Command byte. */
+
+ ret = ahci_io(ahci, fis, sizeof(fis), buf, NULL, SECTOR_SIZE);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static int ahci_rw(struct ata_port *ata, void *rbuf, const void *wbuf,
+ unsigned int block, int num_blocks)
+{
+ struct ahci_port *ahci = container_of(ata, struct ahci_port, ata);
+ u8 fis[20];
+ int ret;
+
+ memset(fis, 0, sizeof(fis));
+
+ /* Construct the FIS */
+ fis[0] = 0x27; /* Host to device FIS. */
+ fis[1] = 1 << 7; /* Command FIS. */
+ fis[2] = wbuf ? ATA_CMD_WRITE_EXT : ATA_CMD_READ_EXT; /* Command byte. */
+
+ while (num_blocks) {
+ int now;
+
+ now = min(MAX_SATA_BLOCKS_READ_WRITE, num_blocks);
+
+ fis[4] = (block >> 0) & 0xff;
+ fis[5] = (block >> 8) & 0xff;
+ fis[6] = (block >> 16) & 0xff;
+ fis[7] = 1 << 6; /* device reg: set LBA mode */
+ fis[8] = ((block >> 24) & 0xff);
+ fis[3] = 0xe0; /* features */
+
+ /* Block (sector) count */
+ fis[12] = (now >> 0) & 0xff;
+ fis[13] = (now >> 8) & 0xff;
+
+ ret = ahci_io(ahci, fis, sizeof(fis), rbuf, wbuf, now * SECTOR_SIZE);
+ if (ret)
+ return ret;
+
+ if (rbuf)
+ rbuf += now * SECTOR_SIZE;
+ if (wbuf)
+ wbuf += now * SECTOR_SIZE;
+ num_blocks -= now;
+ block += now;
+ }
+
+ return 0;
+}
+
+static int ahci_read(struct ata_port *ata, void *buf, unsigned int block,
+ int num_blocks)
+{
+ return ahci_rw(ata, buf, NULL, block, num_blocks);
+}
+
+static int ahci_write(struct ata_port *ata, const void *buf, unsigned int block,
+ int num_blocks)
+{
+ return ahci_rw(ata, NULL, buf, block, num_blocks);
+}
+
+static struct ata_port_operations ahci_ops = {
+ .read_id = ahci_read_id,
+ .read = ahci_read,
+ .write = ahci_write,
+};
+
+static int ahci_init_port(struct ahci_port *ahci_port)
+{
+ void __iomem *port_mmio;
+ u32 val, cmd;
+ int ret;
+
+ port_mmio = ahci_port->port_mmio;
+
+ /* 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);
+ }
+
+ /*
+ * First item in chunk of DMA memory: 32-slot command table,
+ * 32 bytes each in size
+ */
+ ahci_port->cmd_slot = dma_alloc_coherent(AHCI_CMD_SLOT_SZ * 32);
+ if (!ahci_port->cmd_slot) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ ahci_port_debug(ahci_port, "cmd_slot = 0x%x\n", (unsigned)ahci_port->cmd_slot);
+
+ /*
+ * Second item: Received-FIS area
+ */
+ ahci_port->rx_fis = (unsigned long)dma_alloc_coherent(AHCI_RX_FIS_SZ);
+ if (!ahci_port->rx_fis) {
+ ret = -ENOMEM;
+ goto err_alloc1;
+ }
+
+ /*
+ * Third item: data area for storing a single command
+ * and its scatter-gather table
+ */
+ ahci_port->cmd_tbl = dma_alloc_coherent(AHCI_CMD_TBL_SZ);
+ if (!ahci_port->cmd_tbl) {
+ ret = -ENOMEM;
+ goto err_alloc2;
+ }
+
+ memset(ahci_port->cmd_slot, 0, AHCI_CMD_SLOT_SZ * 32);
+ memset((void *)ahci_port->rx_fis, 0, AHCI_RX_FIS_SZ);
+ memset(ahci_port->cmd_tbl, 0, AHCI_CMD_TBL_SZ);
+
+ ahci_port_debug(ahci_port, "cmd_tbl_dma = 0x%p\n", ahci_port->cmd_tbl);
+
+ ahci_port->cmd_tbl_sg = ahci_port->cmd_tbl + AHCI_CMD_TBL_HDR_SZ;
+
+ ahci_port_write_f(ahci_port, PORT_LST_ADDR, (u32)ahci_port->cmd_slot);
+ ahci_port_write_f(ahci_port, PORT_FIS_ADDR, ahci_port->rx_fis);
+
+ /*
+ * 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) & 0xf) == 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,
+ (readl(port_mmio + PORT_TFDATA) &
+ (ATA_STATUS_BUSY | ATA_STATUS_DRQ)) == 0);
+ if (ret) {
+ ahci_port_info(ahci_port, "timeout.\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);
+
+ ahci_port->ata.ops = &ahci_ops;
+
+ if ((val & 0xf) == 0x03)
+ return 0;
+
+ ret = -ENODEV;
+
+err_init:
+ dma_free_coherent(ahci_port->cmd_tbl, AHCI_CMD_TBL_SZ);
+err_alloc2:
+ dma_free_coherent((void *)ahci_port->rx_fis, AHCI_RX_FIS_SZ);
+err_alloc1:
+ dma_free_coherent(ahci_port->cmd_slot, AHCI_CMD_SLOT_SZ * 32);
+err_alloc:
+ return ret;
+}
+
+static int ahci_host_init(struct ahci_device *ahci)
+{
+ u8 *mmio = (u8 *)ahci->mmio_base;
+ u32 tmp, cap_save;
+ int i, ret;
+
+ ahci_debug(ahci, "ahci_host_init: start\n");
+
+ cap_save = readl(mmio + HOST_CAP);
+ cap_save &= ((1 << 28) | (1 << 17));
+ cap_save |= (1 << 27); /* 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, (readl(mmio + 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 & 0x1f) + 1;
+
+ ahci_debug(ahci, "cap 0x%x port_map 0x%x n_ports %d\n",
+ ahci->cap, ahci->port_map, ahci->n_ports);
+
+ for (i = 0; i < ahci->n_ports; i++) {
+ struct ahci_port *ahci_port = &ahci->ports[i];
+
+ ahci_port->num = i;
+ ahci_port->ahci = ahci;
+ ahci_port->ata.dev = ahci->dev;
+ ahci_port->port_mmio = ahci_port_base(mmio, i);
+ ret = ahci_init_port(ahci_port);
+ if (!ret)
+ ahci->link_port_map |= 1 << i;
+ }
+
+ tmp = ahci_ioread(ahci, HOST_CTL);
+ ahci_iowrite(ahci, HOST_CTL, tmp | HOST_IRQ_EN);
+ tmp = ahci_ioread(ahci, HOST_CTL);
+
+ return 0;
+}
+
+static int ahci_port_start(struct ahci_port *ahci_port, u8 port)
+{
+ if (!ahci_link_ok(ahci_port, 1))
+ return -EIO;
+
+ ahci_port_write_f(ahci_port, PORT_CMD,
+ PORT_CMD_ICC_ACTIVE | PORT_CMD_FIS_RX |
+ PORT_CMD_POWER_ON | PORT_CMD_SPIN_UP |
+ PORT_CMD_START);
+
+ ata_port_register(&ahci_port->ata);
+
+ return 0;
+}
+
+static int __ahci_host_init(struct ahci_device *ahci)
+{
+ int i, rc = 0;
+ u32 linkmap;
+
+ 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 */
+
+ /* initialize adapter */
+ rc = ahci_host_init(ahci);
+ if (rc)
+ goto err_out;
+
+ linkmap = ahci->link_port_map;
+
+ for (i = 0; i < 32; i++) {
+ if (((linkmap >> i) & 0x01)) {
+ if (ahci_port_start(&ahci->ports[i], i)) {
+ printf("Can not start port %d\n", i);
+ continue;
+ }
+ }
+ }
+err_out:
+ return rc;
+}
+
+#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;
+ const char *speed_s;
+ const char *scc_s;
+
+ vers = ahci_ioread(ahci, HOST_VERSION);
+ cap = ahci->cap;
+ cap2 = ahci_ioread(ahci, HOST_CAP2);
+ impl = ahci->port_map;
+
+ speed = (cap >> 20) & 0xf;
+ if (speed == 1)
+ speed_s = "1.5";
+ else if (speed == 2)
+ speed_s = "3";
+ else if (speed == 3)
+ speed_s = "6";
+ else
+ speed_s = "?";
+
+ scc_s = "SATA";
+
+ printf("AHCI %02x%02x.%02x%02x "
+ "%u slots %u ports %s Gbps 0x%x impl %s mode\n",
+ (vers >> 24) & 0xff,
+ (vers >> 16) & 0xff,
+ (vers >> 8) & 0xff,
+ vers & 0xff,
+ ((cap >> 8) & 0x1f) + 1, (cap & 0x1f) + 1, speed_s, impl, scc_s);
+
+ printf("flags: "
+ "%s%s%s%s%s%s%s"
+ "%s%s%s%s%s%s%s"
+ "%s%s%s%s%s%s\n",
+ cap & (1 << 31) ? "64bit " : "",
+ cap & (1 << 30) ? "ncq " : "",
+ cap & (1 << 28) ? "ilck " : "",
+ cap & (1 << 27) ? "stag " : "",
+ cap & (1 << 26) ? "pm " : "",
+ cap & (1 << 25) ? "led " : "",
+ cap & (1 << 24) ? "clo " : "",
+ cap & (1 << 19) ? "nz " : "",
+ cap & (1 << 18) ? "only " : "",
+ cap & (1 << 17) ? "pmp " : "",
+ cap & (1 << 16) ? "fbss " : "",
+ cap & (1 << 15) ? "pio " : "",
+ cap & (1 << 14) ? "slum " : "",
+ cap & (1 << 13) ? "part " : "",
+ cap & (1 << 7) ? "ccc " : "",
+ cap & (1 << 6) ? "ems " : "",
+ cap & (1 << 5) ? "sxs " : "",
+ cap2 & (1 << 2) ? "apst " : "",
+ cap2 & (1 << 1) ? "nvmp " : "",
+ cap2 & (1 << 0) ? "boh " : "");
+}
+
+void ahci_info(struct device_d *dev)
+{
+ struct ahci_device *ahci = dev->priv;
+
+ ahci_print_info(ahci);
+}
+
+int ahci_add_host(struct ahci_device *ahci)
+{
+ __ahci_host_init(ahci);
+
+ return 0;
+}
+
+static int ahci_probe(struct device_d *dev)
+{
+ struct ahci_device *ahci;
+ void __iomem *regs;
+ int ret;
+
+ ahci = xzalloc(sizeof(*ahci));
+
+ regs = dev_request_mem_region(dev, 0);
+
+ ahci->dev = dev;
+ ahci->mmio_base = regs;
+ dev->priv = ahci;
+
+ ret = ahci_add_host(ahci);
+ if (ret)
+ free(ahci);
+
+ return ret;
+}
+
+static struct driver_d ahci_driver = {
+ .name = "ahci",
+ .probe = ahci_probe,
+ .info = ahci_info,
+};
+
+static int ahci_init(void)
+{
+ return platform_driver_register(&ahci_driver);
+}
+
+device_initcall(ahci_init);