| /* |
| * 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 <endian.h> |
| #include <libpayload.h> |
| #include <stdint.h> |
| |
| #include "drivers/storage/ahci.h" |
| #include "drivers/storage/ata.h" |
| #include "drivers/storage/blockdev.h" |
| |
| typedef struct SataDrive { |
| BlockDev dev; |
| |
| AhciCtrlr *ctrlr; |
| AhciIoPort *port; |
| } SataDrive; |
| |
| #define writel_with_flush(a,b) do { writel(a, b); readl(b); } while (0) |
| |
| /* Maximum timeouts for each event */ |
| static const int wait_ms_spinup = 10000; |
| static const int wait_ms_flush = 5000; |
| static const int wait_ms_dataio = 5000; |
| static const int wait_ms_linkup = 4; |
| |
| static void *ahci_port_base(void *base, int port) |
| { |
| return (uint8_t *)base + 0x100 + (port * 0x80); |
| } |
| |
| static void ahci_setup_port(AhciIoPort *port, void *base, int idx) |
| { |
| base = ahci_port_base(base, idx); |
| |
| port->cmd_addr = base; |
| port->scr_addr = (uint8_t *)base + PORT_SCR; |
| } |
| |
| #define WAIT_UNTIL(expr, timeout) \ |
| ({ \ |
| typeof(timeout) __counter = timeout * 1000; \ |
| typeof(expr) __expr_val; \ |
| while (!(__expr_val = (expr)) && __counter--) \ |
| udelay(1); \ |
| __expr_val; \ |
| }) |
| |
| #define WAIT_WHILE(expr, timeout) !WAIT_UNTIL(!(expr), (timeout)) |
| |
| static const char *ahci_decode_speed(uint32_t speed) |
| { |
| switch (speed) { |
| case 1: return "1.5"; |
| case 2: return "3"; |
| case 3: return "6"; |
| default: return "?"; |
| } |
| } |
| |
| static const char *ahci_decode_mode(uint32_t mode) |
| { |
| switch (mode) { |
| case 0x0101: return "IDE"; |
| case 0x0104: return "RAID"; |
| case 0x0106: return "SATA"; |
| default: return "unknown"; |
| } |
| } |
| |
| static void ahci_print_info(AhciCtrlr *ctrlr) |
| { |
| pcidev_t pdev = ctrlr->dev; |
| void *mmio = ctrlr->mmio_base; |
| uint32_t cap, cap2; |
| |
| cap = ctrlr->cap; |
| cap2 = readl(mmio + HOST_CAP2); |
| |
| uint8_t *vers = (uint8_t *)mmio + HOST_VERSION; |
| printf("AHCI %02x%02x.%02x%02x", vers[3], vers[2], vers[1], vers[0]); |
| printf(" %u slots", ((cap >> 8) & 0x1f) + 1); |
| printf(" %u ports", (cap & 0x1f) + 1); |
| uint32_t speed = (cap >> 20) & 0xf; |
| printf(" %s Gbps", ahci_decode_speed(speed)); |
| printf(" %#x impl", ctrlr->port_map); |
| uint32_t mode = pci_read_config16(pdev, 0xa); |
| printf(" %s mode\n", ahci_decode_mode(mode)); |
| |
| printf("flags: "); |
| if (cap & (1 << 31)) printf("64bit "); |
| if (cap & (1 << 30)) printf("ncq "); |
| if (cap & (1 << 28)) printf("ilck "); |
| if (cap & (1 << 27)) printf("stag "); |
| if (cap & (1 << 26)) printf("pm "); |
| if (cap & (1 << 25)) printf("led "); |
| if (cap & (1 << 24)) printf("clo "); |
| if (cap & (1 << 19)) printf("nz "); |
| if (cap & (1 << 18)) printf("only "); |
| if (cap & (1 << 17)) printf("pmp "); |
| if (cap & (1 << 16)) printf("fbss "); |
| if (cap & (1 << 15)) printf("pio "); |
| if (cap & (1 << 14)) printf("slum "); |
| if (cap & (1 << 13)) printf("part "); |
| if (cap & (1 << 7)) printf("ccc "); |
| if (cap & (1 << 6)) printf("ems "); |
| if (cap & (1 << 5)) printf("sxs "); |
| if (cap2 & (1 << 2)) printf("apst "); |
| if (cap2 & (1 << 1)) printf("nvmp "); |
| if (cap2 & (1 << 0)) printf("boh "); |
| puts("\n"); |
| } |
| |
| #define MAX_DATA_BYTE_COUNT (4 * 1024 * 1024) |
| |
| static int ahci_fill_sg(AhciSg *sg, void *buf, int len) |
| { |
| uint32_t sg_count = ((len - 1) / MAX_DATA_BYTE_COUNT) + 1; |
| if (sg_count > AHCI_MAX_SG) { |
| printf("Error: Too much sg!\n"); |
| return -1; |
| } |
| |
| for (int i = 0; i < sg_count; i++) { |
| sg->addr = htolel((uintptr_t)buf + i * MAX_DATA_BYTE_COUNT); |
| sg->addr_hi = 0; |
| uint32_t bytes = MIN(len, MAX_DATA_BYTE_COUNT); |
| sg->flags_size = htolel((bytes - 1) & 0x3fffff); |
| sg++; |
| len -= MAX_DATA_BYTE_COUNT; |
| } |
| |
| return sg_count; |
| } |
| |
| |
| static void ahci_fill_cmd_slot(AhciIoPort *pp, uint32_t opts) |
| { |
| pp->cmd_slot->opts = htolel(opts); |
| pp->cmd_slot->status = 0; |
| pp->cmd_slot->tbl_addr = htolel((uint32_t)(uintptr_t)pp->cmd_tbl); |
| pp->cmd_slot->tbl_addr_hi = 0; |
| } |
| |
| |
| static int ahci_port_start(AhciIoPort *port, int index) |
| { |
| uint8_t *port_mmio = port->port_mmio; |
| |
| port->index = index; |
| |
| uint32_t status = readl(port_mmio + PORT_SCR_STAT); |
| printf("Port %d status: %x\n", index, status); |
| if ((status & 0xf) != 0x3) { |
| printf("No link on this port!\n"); |
| return -1; |
| } |
| |
| uint8_t *mem = memalign(2048, AHCI_PORT_PRIV_DMA_SZ); |
| if (!mem) { |
| printf("No mem for table!\n"); |
| return -1; |
| } |
| memset(mem, 0, AHCI_PORT_PRIV_DMA_SZ); |
| |
| /* |
| * First item in chunk of DMA memory: 32-slot command table, |
| * 32 bytes each in size |
| */ |
| port->cmd_slot = (AhciCommandHeader *)mem; |
| mem += (AHCI_CMD_SLOT_SZ + 224); |
| |
| /* |
| * Second item: Received-FIS area |
| */ |
| port->rx_fis = mem; |
| mem += AHCI_RX_FIS_SZ; |
| |
| /* |
| * Third item: data area for storing a single command |
| * and its scatter-gather table |
| */ |
| port->cmd_tbl = mem; |
| mem += AHCI_CMD_TBL_HDR; |
| |
| port->cmd_tbl_sg = (AhciSg *)mem; |
| |
| writel_with_flush((uintptr_t)port->cmd_slot, port_mmio + PORT_LST_ADDR); |
| |
| writel_with_flush((uintptr_t)port->rx_fis, port_mmio + PORT_FIS_ADDR); |
| |
| writel_with_flush(PORT_CMD_ICC_ACTIVE | PORT_CMD_FIS_RX | |
| PORT_CMD_POWER_ON | PORT_CMD_SPIN_UP | |
| PORT_CMD_START, port_mmio + PORT_CMD); |
| |
| return 0; |
| } |
| |
| |
| static int ahci_device_data_io(AhciIoPort *port, void *fis, int fis_len, |
| void *buf, int buf_len, int is_write, int wait) |
| { |
| |
| uint8_t *port_mmio = port->port_mmio; |
| |
| uint32_t port_status = readl(port_mmio + PORT_SCR_STAT); |
| if ((port_status & 0xf) != 0x3) { |
| printf("No link on port %d!\n", port->index); |
| return -1; |
| } |
| |
| memcpy(port->cmd_tbl, fis, fis_len); |
| |
| int sg_count = 0; |
| if (buf && buf_len) |
| sg_count = ahci_fill_sg(port->cmd_tbl_sg, buf, buf_len); |
| uint32_t opts = (fis_len >> 2) | (sg_count << 16) | (is_write << 6); |
| ahci_fill_cmd_slot(port, opts); |
| |
| writel_with_flush(1, port_mmio + PORT_CMD_ISSUE); |
| |
| // Wait for the command to complete. |
| if (WAIT_WHILE((readl(port_mmio + PORT_CMD_ISSUE) & 0x1), wait)) { |
| printf("AHCI: I/O timeout!\n"); |
| return -1; |
| } |
| |
| return 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. Because writing to the disk in u-boot is very rare, |
| * this flush command will be invoked after every block write. |
| */ |
| static int ahci_io_flush(AhciIoPort *port) |
| { |
| uint8_t fis[20]; |
| |
| // Set up the FIS. |
| memset(fis, 0, 20); |
| fis[0] = 0x27; // Host to device FIS. |
| fis[1] = 1 << 7; // Command FIS. |
| fis[2] = ATA_CMD_FLUSH_CACHE_EXT; |
| |
| if (ahci_device_data_io(port, fis, 20, NULL, 0, 1, |
| wait_ms_flush) < 0) { |
| printf("AHCI: Flush command failed.\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * 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. Let's set default to 128 and allowing to be overwritten if |
| * needed. |
| */ |
| #ifndef MAX_SATA_BLOCKS_READ_WRITE |
| #define MAX_SATA_BLOCKS_READ_WRITE 0x80 |
| #endif |
| |
| static int ahci_read_write(SataDrive *drive, lba_t start, uint16_t count, |
| void *buf, int is_write) |
| { |
| uint8_t fis[20]; |
| |
| // Set up the FIS. |
| memset(fis, 0, 20); |
| fis[0] = 0x27; // Host to device FIS. |
| fis[1] = 1 << 7; // Command FIS. |
| // Command byte |
| fis[2] = is_write ? ATA_CMD_WRITE_SECTORS_EXT : |
| ATA_CMD_READ_SECTORS_EXT; |
| |
| while (count) { |
| uint16_t tblocks = MIN(MAX_SATA_BLOCKS_READ_WRITE, count); |
| uintptr_t tsize = tblocks * drive->dev.block_size; |
| |
| // LBA48 SATA command using 32bit address range. |
| fis[3] = 0xe0; /* features */ |
| fis[4] = (start >> 0) & 0xff; |
| fis[5] = (start >> 8) & 0xff; |
| fis[6] = (start >> 16) & 0xff; |
| fis[7] = 1 << 6; /* device reg: set LBA mode */ |
| fis[8] = ((start >> 24) & 0xff); |
| |
| // Block count. |
| fis[12] = (tblocks >> 0) & 0xff; |
| fis[13] = (tblocks >> 8) & 0xff; |
| |
| // Read/write from AHCI. |
| if (ahci_device_data_io(drive->port, fis, sizeof(fis), buf, |
| tsize, is_write, wait_ms_dataio)) { |
| printf("AHCI: %s command failed.\n", |
| is_write ? "write" : "read"); |
| return -1; |
| } |
| |
| // Flush writes. |
| if (is_write) { |
| if (ahci_io_flush(drive->port) < 0) |
| return -1; |
| } |
| |
| buf = (uint8_t *)buf + tsize; |
| count -= tblocks; |
| start += tblocks; |
| } |
| |
| return 0; |
| } |
| |
| static lba_t ahci_read(BlockDevOps *me, lba_t start, lba_t count, void *buffer) |
| { |
| SataDrive *drive = container_of(me, SataDrive, dev.ops); |
| if (ahci_read_write(drive, start, count, buffer, 0)) { |
| printf("AHCI: Read failed.\n"); |
| return -1; |
| } |
| return count; |
| } |
| |
| static lba_t ahci_write(BlockDevOps *me, lba_t start, lba_t count, |
| const void *buffer) |
| { |
| SataDrive *drive = container_of(me, SataDrive, dev.ops); |
| if (ahci_read_write(drive, start, count, (void *)buffer, 1)) { |
| printf("AHCI: Write failed.\n"); |
| return -1; |
| } |
| return count; |
| } |
| |
| static int ahci_identify(AhciIoPort *port, AtaIdentify *id) |
| { |
| uint8_t fis[20]; |
| |
| memset(fis, 0, 20); |
| // Construct the FIS |
| fis[0] = 0x27; // Host to device FIS. |
| fis[1] = 1 << 7; // Command FIS. |
| // Command byte. |
| fis[2] = ATA_CMD_IDENTIFY_DEVICE; |
| |
| if (ahci_device_data_io(port, fis, 20, id, sizeof(AtaIdentify), 0, |
| wait_ms_dataio)) { |
| printf("AHCI: Identify command failed.\n"); |
| return -1; |
| } |
| printf("size of AtaIdentify is %d.\n", sizeof(AtaIdentify)); |
| |
| return 0; |
| } |
| |
| static int ahci_read_capacity(AhciIoPort *port, lba_t *cap, |
| unsigned *block_size) |
| { |
| AtaIdentify id; |
| |
| if (ahci_identify(port, &id)) |
| return -1; |
| |
| uint32_t cap32; |
| memcpy(&cap32, &id.sectors28, sizeof(cap32)); |
| *cap = letohl(cap32); |
| if (*cap == 0xfffffff) { |
| memcpy(cap, id.sectors48, sizeof(*cap)); |
| *cap = letohll(*cap); |
| } |
| |
| *block_size = 512; |
| return 0; |
| } |
| |
| static int ahci_ctrlr_init(BlockDevCtrlrOps *me) |
| { |
| AhciCtrlr *ctrlr = container_of(me, AhciCtrlr, ctrlr.ops); |
| |
| ctrlr->mmio_base = (void *)pci_read_resource(ctrlr->dev, 5); |
| printf("AHCI MMIO base = %p\n", ctrlr->mmio_base); |
| |
| // JMicron-specific fixup taken from kernel: |
| // make sure we're in AHCI mode |
| if (pci_read_config16(ctrlr->dev, REG_VENDOR_ID) == 0x197b) |
| pci_write_config8(ctrlr->dev, 0x41, 0xa1); |
| |
| /* initialize adapter */ |
| pcidev_t pdev = ctrlr->dev; |
| void *mmio = ctrlr->mmio_base; |
| |
| uint32_t cap_save = readl(mmio + HOST_CAP); |
| cap_save &= ((1 << 28) | (1 << 17)); |
| cap_save |= (1 << 27); |
| |
| // Global controller reset. |
| uint32_t host_ctl = readl(mmio + HOST_CTL); |
| if ((host_ctl & HOST_RESET) == 0) |
| writel_with_flush(host_ctl | HOST_RESET, |
| (uintptr_t)mmio + HOST_CTL); |
| |
| // Reset must complete within 1 second. |
| if (WAIT_WHILE((readl(mmio + HOST_CTL) & HOST_RESET), 1000)) { |
| printf("Controller reset failed.\n"); |
| return -1; |
| } |
| |
| writel_with_flush(HOST_AHCI_EN, mmio + HOST_CTL); |
| writel(cap_save, mmio + HOST_CAP); |
| writel_with_flush(0xf, mmio + HOST_PORTS_IMPL); |
| |
| ctrlr->cap = readl(mmio + HOST_CAP); |
| ctrlr->port_map = readl(mmio + HOST_PORTS_IMPL); |
| ctrlr->n_ports = (ctrlr->cap & 0x1f) + 1; |
| |
| printf("cap %#x port_map %#x n_ports %d\n", |
| ctrlr->cap, ctrlr->port_map, ctrlr->n_ports); |
| |
| for (int i = 0; i < ctrlr->n_ports; i++) { |
| /* Skip ports that are not enabled. */ |
| if (!(ctrlr->port_map & (1 << i))) |
| continue; |
| |
| ctrlr->ports[i].port_mmio = ahci_port_base(mmio, i); |
| uint8_t *port_mmio = (uint8_t *)ctrlr->ports[i].port_mmio; |
| ahci_setup_port(&ctrlr->ports[i], mmio, i); |
| |
| /* make sure port is not active */ |
| uint32_t port_cmd = readl(port_mmio + PORT_CMD); |
| uint32_t port_cmd_bits = |
| PORT_CMD_LIST_ON | PORT_CMD_FIS_ON | |
| PORT_CMD_FIS_RX | PORT_CMD_START; |
| if (port_cmd & port_cmd_bits) { |
| printf("Port %d is active. Deactivating.\n", i); |
| port_cmd &= ~port_cmd_bits; |
| writel_with_flush(port_cmd, port_mmio + PORT_CMD); |
| |
| /* spec says 500 msecs for each bit, so |
| * this is slightly incorrect. |
| */ |
| mdelay(500); |
| } |
| |
| /* Bring up SATA link. */ |
| port_cmd = PORT_CMD_SPIN_UP | PORT_CMD_FIS_RX; |
| writel_with_flush(port_cmd, port_mmio + PORT_CMD); |
| |
| int j; |
| uint32_t tmp; |
| for (j = 0; j < wait_ms_linkup; j++) { |
| tmp = readl(port_mmio + PORT_SCR_STAT); |
| if ((tmp & 0xf) == 0x3) |
| break; |
| mdelay(1); |
| } |
| if (j == wait_ms_linkup) { |
| printf("SATA link %d timeout.\n", i); |
| continue; |
| } else { |
| printf("SATA link %d ok.\n", i); |
| } |
| |
| /* Clear error status */ |
| uint32_t port_scr_err = readl(port_mmio + PORT_SCR_ERR); |
| if (port_scr_err) |
| writel(port_scr_err, port_mmio + PORT_SCR_ERR); |
| |
| /* Wait for SATA device to complete spin-up. */ |
| printf("Waiting for device on port %d... ", i); |
| |
| for (j = 0; j < wait_ms_spinup; j++) { |
| tmp = readl(port_mmio + PORT_TFDATA); |
| if (!(tmp & (ATA_STAT_BUSY | ATA_STAT_DRQ))) |
| break; |
| mdelay(1); |
| } |
| if (j == wait_ms_spinup) |
| printf("timeout.\n"); |
| else |
| printf("ok. Target spinup took %d ms.\n", j); |
| |
| /* Clear error status */ |
| port_scr_err = readl(port_mmio + PORT_SCR_ERR); |
| if (port_scr_err) { |
| printf("PORT_SCR_ERR %#x\n", port_scr_err); |
| writel(port_scr_err, port_mmio + PORT_SCR_ERR); |
| } |
| |
| /* ack any pending irq events for this port */ |
| uint32_t port_irq_stat = readl(port_mmio + PORT_IRQ_STAT); |
| if (port_irq_stat) { |
| printf("PORT_IRQ_STAT 0x%x\n", port_irq_stat); |
| writel(port_irq_stat, port_mmio + PORT_IRQ_STAT); |
| } |
| |
| writel(1 << i, mmio + HOST_IRQ_STAT); |
| |
| /* set irq mask (enables interrupts) */ |
| writel(DEF_PORT_IRQ, port_mmio + PORT_IRQ_MASK); |
| |
| /* register linkup ports */ |
| uint32_t port_scr_stat = readl(port_mmio + PORT_SCR_STAT); |
| printf("Port %d status: 0x%x\n", i, port_scr_stat); |
| if ((port_scr_stat & 0xf) == 0x3) |
| ctrlr->link_port_map |= (0x1 << i); |
| } |
| |
| host_ctl = readl(mmio + HOST_CTL); |
| writel(host_ctl | HOST_IRQ_EN, mmio + HOST_CTL); |
| host_ctl = readl(mmio + HOST_CTL); |
| printf("HOST_CTL 0x%x\n", host_ctl); |
| |
| pci_write_config16(pdev, REG_COMMAND, |
| pci_read_config16(pdev, REG_COMMAND) | REG_COMMAND_BM); |
| |
| ahci_print_info(ctrlr); |
| |
| uint32_t linkmap = ctrlr->link_port_map; |
| |
| for (int i = 0; i < sizeof(linkmap) * 8; i++) { |
| if (((linkmap >> i) & 0x1)) { |
| AhciIoPort *port = &ctrlr->ports[i]; |
| if (ahci_port_start(port, i)) { |
| printf("Can not start port %d\n", i); |
| continue; |
| } |
| lba_t cap; |
| unsigned block_size; |
| if (ahci_read_capacity(port, &cap, &block_size)) { |
| printf("Can't read port %d's capacity.\n", i); |
| continue; |
| } |
| |
| SataDrive *sata_drive = xzalloc(sizeof(*sata_drive)); |
| static const int name_size = 18; |
| char *name = xmalloc(name_size); |
| snprintf(name, name_size, "Sata port %d", i); |
| sata_drive->dev.ops.read = &ahci_read; |
| sata_drive->dev.ops.write = &ahci_write; |
| sata_drive->dev.ops.new_stream = &new_simple_stream; |
| sata_drive->dev.name = name; |
| sata_drive->dev.removable = 0; |
| sata_drive->dev.block_size = block_size; |
| sata_drive->dev.block_count = cap; |
| sata_drive->ctrlr = ctrlr; |
| sata_drive->port = port; |
| list_insert_after(&sata_drive->dev.list_node, |
| &fixed_block_devices); |
| } |
| } |
| |
| ctrlr->ctrlr.need_update = 0; |
| |
| return 0; |
| } |
| |
| AhciCtrlr *new_ahci_ctrlr(pcidev_t dev) |
| { |
| AhciCtrlr *ctrlr = xzalloc(sizeof(*ctrlr)); |
| ctrlr->ctrlr.ops.update = &ahci_ctrlr_init; |
| ctrlr->ctrlr.need_update = 1; |
| ctrlr->dev = dev; |
| return ctrlr; |
| } |