/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2007 Dave Chapman
 *
 * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
//#define LOGF_ENABLE

#include "config.h"
#include "storage.h"
#include "timer.h"
#include "kernel.h"
#include "string.h"
#include "power.h"
#include "panic.h"
#include "mmcdefs-target.h"
#include "s5l87xx.h"
#include "led.h"
#include "debug.h"
#include "panic.h"
#include "fs_defines.h"
#include "logf.h"

#ifndef ATA_RETRIES
#define ATA_RETRIES 3
#endif

#define CMD_READ_SECTORS           0x20
#define CMD_READ_DMA_EXT           0x25
#define CMD_READ_MULTIPLE_EXT      0x29
#define CMD_WRITE_SECTORS          0x30
#define CMD_WRITE_DMA_EXT          0x35
#define CMD_WRITE_MULTIPLE_EXT     0x39
#ifdef HAVE_ATA_SMART
#define CMD_SMART                  0xB0
#endif
#define CMD_READ_MULTIPLE          0xC4
#define CMD_WRITE_MULTIPLE         0xC5
#define CMD_READ_DMA               0xC8
#define CMD_WRITE_DMA              0xCA
#define CMD_STANDBY_IMMEDIATE      0xE0
#define CMD_FLUSH_CACHE            0xE7
#define CMD_FLUSH_CACHE_EXT        0xEA
#define CMD_IDENTIFY               0xEC
#define CMD_SET_FEATURES           0xEF

#define CEATA_POWERUP_TIMEOUT 20000000
#define CEATA_COMMAND_TIMEOUT 1000000
#define CEATA_DAT_NONBUSY_TIMEOUT 5000000
#define CEATA_MMC_RCA 1

/** static, private data **/
static uint8_t ceata_taskfile[16] STORAGE_ALIGN_ATTR;
static bool ceata;
static bool ata_dma;
static struct semaphore ata_wakeup;
static long ata_last_activity_value = -1;
static long ata_sleep_timeout = 7 * HZ;
static bool ata_powered;
static struct semaphore mmc_wakeup;
static struct semaphore mmc_comp_wakeup;
#ifdef HAVE_ATA_DMA
static int dma_mode = 0;
static uint32_t ata_dma_flags;
#endif

static const int ata_retries = ATA_RETRIES;
static const bool ata_error_srst = true;

static int ata_reset(void);

#include "ata-common.c"

static uint16_t ata_read_cbr(uint32_t volatile* reg)
{
    while (!(ATA_PIO_READY & 2));
    volatile uint32_t dummy __attribute__((unused)) = *reg;
    while (!(ATA_PIO_READY & 1));
    return ATA_PIO_RDATA;
}

static void ata_write_cbr(uint32_t volatile* reg, uint16_t data)
{
    while (!(ATA_PIO_READY & 2));
    *reg = data;
}

static int ata_wait_for_not_bsy(long timeout)
{
    long startusec = USEC_TIMER;
    while (true)
    {
        uint8_t csd = ata_read_cbr(&ATA_PIO_CSD);
        if (!(csd & BIT(7)))
            return 0;
        if (TIMEOUT_EXPIRED(startusec, timeout))
            RET_ERR(0);
        yield();
    }
}

static int ata_wait_for_rdy(long timeout)
{
    long startusec = USEC_TIMER;
    PASS_RC(ata_wait_for_not_bsy(timeout), 1, 0);
    while (true)
    {
        uint8_t dad = ata_read_cbr(&ATA_PIO_DAD);
        if (dad & BIT(6))
            return 0;
        if (TIMEOUT_EXPIRED(startusec, timeout))
            RET_ERR(1);
        yield();
    }
}

static int ata_wait_for_start_of_transfer(long timeout)
{
    long startusec = USEC_TIMER;
    PASS_RC(ata_wait_for_not_bsy(timeout), 2, 0);
    while (true)
    {
        uint8_t dad = ata_read_cbr(&ATA_PIO_DAD);
        if (dad & BIT(0))
            RET_ERR(1);
        if ((dad & (BIT(7) | BIT(3))) == BIT(3))
            return 0;
        if (TIMEOUT_EXPIRED(startusec, timeout))
            RET_ERR(2);
        yield();
    }
}

static int ata_wait_for_end_of_transfer(long timeout)
{
    PASS_RC(ata_wait_for_not_bsy(timeout), 2, 0);
    uint8_t dad = ata_read_cbr(&ATA_PIO_DAD);
    if (dad & BIT(0))
        RET_ERR(1);
    if ((dad & (BIT(3) | BITRANGE(5, 7))) == BIT(6))
        return 0;
    RET_ERR(2);
}

static int mmc_dsta_check_command_success(bool disable_crc)
{
    int rc = 0;
    uint32_t dsta = SDCI_DSTA;
    if (dsta & SDCI_DSTA_RESTOUTE)
        rc |= 1;
    if (dsta & SDCI_DSTA_RESENDE)
        rc |= 2;
    if (dsta & SDCI_DSTA_RESINDE)
        rc |= 4;
    if (!disable_crc)
        if (dsta & SDCI_DSTA_RESCRCE)
            rc |= 8;
    if (rc)
        RET_ERR(rc);
    return 0;
}

static bool mmc_send_command(uint32_t cmd, uint32_t arg, uint32_t* result, int timeout)
{
    long starttime = USEC_TIMER;
    while ((SDCI_STATE & SDCI_STATE_CMD_STATE_MASK) != SDCI_STATE_CMD_STATE_CMD_IDLE)
    {
        if (TIMEOUT_EXPIRED(starttime, timeout))
            RET_ERR(0);
        yield();
    }
    SDCI_STAC = SDCI_STAC_CLR_CMDEND | SDCI_STAC_CLR_BIT_3
              | SDCI_STAC_CLR_RESEND | SDCI_STAC_CLR_DATEND
              | SDCI_STAC_CLR_DAT_CRCEND | SDCI_STAC_CLR_CRC_STAEND
              | SDCI_STAC_CLR_RESTOUTE | SDCI_STAC_CLR_RESENDE
              | SDCI_STAC_CLR_RESINDE | SDCI_STAC_CLR_RESCRCE
              | SDCI_STAC_CLR_WR_DATCRCE | SDCI_STAC_CLR_RD_DATCRCE
              | SDCI_STAC_CLR_RD_DATENDE0 | SDCI_STAC_CLR_RD_DATENDE1
              | SDCI_STAC_CLR_RD_DATENDE2 | SDCI_STAC_CLR_RD_DATENDE3
              | SDCI_STAC_CLR_RD_DATENDE4 | SDCI_STAC_CLR_RD_DATENDE5
              | SDCI_STAC_CLR_RD_DATENDE6 | SDCI_STAC_CLR_RD_DATENDE7;
    SDCI_ARGU = arg;
    SDCI_CMD = cmd;
    if (!(SDCI_DSTA & SDCI_DSTA_CMDRDY))
        RET_ERR(1);
    SDCI_CMD = cmd | SDCI_CMD_CMDSTR;
    long sleepbase = USEC_TIMER;
    while (TIMEOUT_EXPIRED(sleepbase, 1000))
        yield();
    while (!(SDCI_DSTA & SDCI_DSTA_CMDEND))
    {
        if (TIMEOUT_EXPIRED(starttime, timeout))
            RET_ERR(2);
        yield();
    }
    if ((cmd & SDCI_CMD_RES_TYPE_MASK) != SDCI_CMD_RES_TYPE_NONE)
    {
        while (!(SDCI_DSTA & SDCI_DSTA_RESEND))
        {
            if (TIMEOUT_EXPIRED(starttime, timeout))
                RET_ERR(3);
            yield();
        }
        if (cmd & SDCI_CMD_RES_BUSY)
            while (SDCI_DSTA & SDCI_DSTA_DAT_BUSY)
            {
                if (TIMEOUT_EXPIRED(starttime, CEATA_DAT_NONBUSY_TIMEOUT))
                    RET_ERR(4);
                yield();
            }
    }
    bool nocrc = (cmd & SDCI_CMD_RES_SIZE_MASK) == SDCI_CMD_RES_SIZE_136;
    PASS_RC(mmc_dsta_check_command_success(nocrc), 3, 5);
    if (result)
        *result = SDCI_RESP0;
    return 0;
}

static int mmc_get_card_status(uint32_t* result)
{
    return mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_SEND_STATUS)
                          | SDCI_CMD_CMD_TYPE_AC | SDCI_CMD_RES_TYPE_R1
                          | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NCR,
                            MMC_CMD_SEND_STATUS_RCA(CEATA_MMC_RCA), result, CEATA_COMMAND_TIMEOUT);
}

static int mmc_init(void)
{
    sleep(HZ / 10);
    PASS_RC(mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_GO_IDLE_STATE)
                           | SDCI_CMD_CMD_TYPE_BC | SDCI_CMD_RES_TYPE_NONE
                           | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NID,
                             0, NULL, CEATA_COMMAND_TIMEOUT), 3, 0);
    long startusec = USEC_TIMER;
    uint32_t result;
    do
    {
        if (TIMEOUT_EXPIRED(startusec, CEATA_POWERUP_TIMEOUT))
            RET_ERR(1);
        sleep(HZ / 100);
        PASS_RC(mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_SEND_OP_COND)
                               | SDCI_CMD_CMD_TYPE_BCR | SDCI_CMD_RES_TYPE_R3
                               | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NID,
                                 MMC_CMD_SEND_OP_COND_OCR(MMC_OCR_270_360),
                                 NULL, CEATA_COMMAND_TIMEOUT), 3, 2);
        result = SDCI_RESP0;
    }
    while (!(result & MMC_OCR_POWER_UP_DONE));
    PASS_RC(mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_ALL_SEND_CID)
                           | SDCI_CMD_CMD_TYPE_BCR | SDCI_CMD_RES_TYPE_R2
                           | SDCI_CMD_RES_SIZE_136 | SDCI_CMD_NCR_NID_NID,
                             0, NULL, CEATA_COMMAND_TIMEOUT), 3, 3);
    PASS_RC(mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_SET_RELATIVE_ADDR)
                           | SDCI_CMD_CMD_TYPE_BCR | SDCI_CMD_RES_TYPE_R1
                           | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NCR,
                             MMC_CMD_SET_RELATIVE_ADDR_RCA(CEATA_MMC_RCA),
                             NULL, CEATA_COMMAND_TIMEOUT), 3, 4);
    PASS_RC(mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_SELECT_CARD)
                           | SDCI_CMD_CMD_TYPE_AC | SDCI_CMD_RES_TYPE_R1
                           | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NCR,
                             MMC_CMD_SELECT_CARD_RCA(CEATA_MMC_RCA),
                             NULL, CEATA_COMMAND_TIMEOUT), 3, 5);
    PASS_RC(mmc_get_card_status(&result), 3, 6);
    if ((result & MMC_STATUS_CURRENT_STATE_MASK) != MMC_STATUS_CURRENT_STATE_TRAN)
        RET_ERR(7);
    return 0;
}

static int mmc_fastio_write(uint32_t addr, uint32_t data)
{
    return mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_FAST_IO)
                          | SDCI_CMD_CMD_TYPE_AC | SDCI_CMD_RES_TYPE_R4
                          | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NCR,
                            MMC_CMD_FAST_IO_RCA(CEATA_MMC_RCA) | MMC_CMD_FAST_IO_DIRECTION_WRITE
                          | MMC_CMD_FAST_IO_ADDRESS(addr) | MMC_CMD_FAST_IO_DATA(data),
                            NULL, CEATA_COMMAND_TIMEOUT);
}

static int mmc_fastio_read(uint32_t addr, uint32_t* data)
{
    return mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_FAST_IO)
                          | SDCI_CMD_CMD_TYPE_AC | SDCI_CMD_RES_TYPE_R4
                          | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NCR,
                            MMC_CMD_FAST_IO_RCA(CEATA_MMC_RCA) | MMC_CMD_FAST_IO_DIRECTION_READ
                          | MMC_CMD_FAST_IO_ADDRESS(addr), data, CEATA_COMMAND_TIMEOUT);
}

static int ceata_soft_reset(void)
{
    PASS_RC(mmc_fastio_write(6, 4), 2, 0);
    sleep(HZ / 100);
    PASS_RC(mmc_fastio_write(6, 0), 2, 1);
    sleep(HZ / 100);
    long startusec = USEC_TIMER;
    uint32_t status;
    do
    {
        PASS_RC(mmc_fastio_read(0xf, &status), 2, 2);
        if (TIMEOUT_EXPIRED(startusec, CEATA_POWERUP_TIMEOUT))
            RET_ERR(3);
        sleep(HZ / 100);
    }
    while (status & 0x80);
    return 0;
}

static int mmc_dsta_check_data_success(void)
{
    int rc = 0;
    uint32_t dsta = SDCI_DSTA;
    if (dsta & (SDCI_DSTA_WR_DATCRCE | SDCI_DSTA_RD_DATCRCE))
    {
        if (dsta & SDCI_DSTA_WR_DATCRCE)
            rc |= 1;
        if (dsta & SDCI_DSTA_RD_DATCRCE)
            rc |= 2;
        if ((dsta & SDCI_DSTA_WR_CRC_STATUS_MASK) == SDCI_DSTA_WR_CRC_STATUS_TXERR)
            rc |= 4;
        else if ((dsta & SDCI_DSTA_WR_CRC_STATUS_MASK) == SDCI_DSTA_WR_CRC_STATUS_CARDERR)
            rc |= 8;
    }
    if (dsta & (SDCI_DSTA_RD_DATENDE0 | SDCI_DSTA_RD_DATENDE1 | SDCI_DSTA_RD_DATENDE2
              | SDCI_DSTA_RD_DATENDE3 | SDCI_DSTA_RD_DATENDE4 | SDCI_DSTA_RD_DATENDE5
              | SDCI_DSTA_RD_DATENDE6 | SDCI_DSTA_RD_DATENDE7))
        rc |= 16;
    if (rc)
        RET_ERR(rc);
    return 0;
}

static void mmc_discard_irq(void)
{
    SDCI_IRQ = SDCI_IRQ_DAT_DONE_INT | SDCI_IRQ_MASK_MASK_IOCARD_IRQ_INT
             | SDCI_IRQ_MASK_MASK_READ_WAIT_INT;
    semaphore_wait(&mmc_wakeup, 0);
}

static int ceata_read_multiple_register(uint32_t addr, void* dest, uint32_t size)
{
    if (size > 0x10)
        RET_ERR(0);
    mmc_discard_irq();
    SDCI_DMASIZE = size;
    SDCI_DMACOUNT = 1;
    SDCI_DMAADDR = dest;
    SDCI_DCTRL = SDCI_DCTRL_TXFIFORST | SDCI_DCTRL_RXFIFORST;
    commit_discard_dcache();
    PASS_RC(mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_CEATA_RW_MULTIPLE_REG)
                           | SDCI_CMD_CMD_TYPE_ADTC | SDCI_CMD_RES_TYPE_R1
                           | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NCR,
                             MMC_CMD_CEATA_RW_MULTIPLE_REG_DIRECTION_READ
                           | MMC_CMD_CEATA_RW_MULTIPLE_REG_ADDRESS(addr & 0xfc)
                           | MMC_CMD_CEATA_RW_MULTIPLE_REG_COUNT(size & 0xfc),
                             NULL, CEATA_COMMAND_TIMEOUT), 2, 1);
    if (semaphore_wait(&mmc_wakeup, CEATA_COMMAND_TIMEOUT * HZ / 1000000) == OBJ_WAIT_TIMEDOUT)
        RET_ERR(2);
    PASS_RC(mmc_dsta_check_data_success(), 2, 3);
    return 0;
}

static int ceata_write_multiple_register(uint32_t addr, void* dest, uint32_t size)
{
    uint32_t i;
    if (size > 0x10)
        RET_ERR(0);
    mmc_discard_irq();
    SDCI_DMASIZE = size;
    SDCI_DMACOUNT = 0;
    SDCI_DCTRL = SDCI_DCTRL_TXFIFORST | SDCI_DCTRL_RXFIFORST;
    PASS_RC(mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_CEATA_RW_MULTIPLE_REG)
                           | SDCI_CMD_CMD_TYPE_ADTC | SDCI_CMD_CMD_RD_WR
                           | SDCI_CMD_RES_BUSY | SDCI_CMD_RES_TYPE_R1
                           | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NCR,
                             MMC_CMD_CEATA_RW_MULTIPLE_REG_DIRECTION_WRITE
                           | MMC_CMD_CEATA_RW_MULTIPLE_REG_ADDRESS(addr & 0xfc)
                           | MMC_CMD_CEATA_RW_MULTIPLE_REG_COUNT(size & 0xfc),
                             NULL, CEATA_COMMAND_TIMEOUT), 3, 1);
    SDCI_DCTRL = SDCI_DCTRL_TRCONT_TX;
    for (i = 0; i < size / 4; i++)
        SDCI_DATA = ((uint32_t*)dest)[i];
    long startusec = USEC_TIMER;
    if (semaphore_wait(&mmc_wakeup, CEATA_COMMAND_TIMEOUT * HZ / 1000000) == OBJ_WAIT_TIMEDOUT)
        RET_ERR(2);
    while ((SDCI_STATE & SDCI_STATE_DAT_STATE_MASK) != SDCI_STATE_DAT_STATE_IDLE)
    {
        if (TIMEOUT_EXPIRED(startusec, CEATA_COMMAND_TIMEOUT))
            RET_ERR(3);
        yield();
    }
    PASS_RC(mmc_dsta_check_data_success(), 3, 4);
    return 0;
}

static int ceata_init(int buswidth)
{
    uint32_t result;
    PASS_RC(mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_SWITCH) | SDCI_CMD_RES_BUSY
                           | SDCI_CMD_CMD_TYPE_AC | SDCI_CMD_RES_TYPE_R1
                           | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NCR,
                             MMC_CMD_SWITCH_ACCESS_WRITE_BYTE
                           | MMC_CMD_SWITCH_INDEX(MMC_CMD_SWITCH_FIELD_HS_TIMING)
                           | MMC_CMD_SWITCH_VALUE(MMC_CMD_SWITCH_FIELD_HS_TIMING_HIGH_SPEED),
                             &result, CEATA_COMMAND_TIMEOUT), 3, 0);
    if (result & MMC_STATUS_SWITCH_ERROR)
        RET_ERR(1);
    if (buswidth > 1)
    {
        int setting;
        if (buswidth == 4)
            setting = MMC_CMD_SWITCH_FIELD_BUS_WIDTH_4BIT;
        else if (buswidth == 8)
            setting = MMC_CMD_SWITCH_FIELD_BUS_WIDTH_8BIT;
        else
            setting = MMC_CMD_SWITCH_FIELD_BUS_WIDTH_1BIT;
        PASS_RC(mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_SWITCH) | SDCI_CMD_RES_BUSY
                               | SDCI_CMD_CMD_TYPE_AC | SDCI_CMD_RES_TYPE_R1
                               | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NCR,
                                 MMC_CMD_SWITCH_ACCESS_WRITE_BYTE
                               | MMC_CMD_SWITCH_INDEX(MMC_CMD_SWITCH_FIELD_BUS_WIDTH)
                               | MMC_CMD_SWITCH_VALUE(setting),
                                 &result, CEATA_COMMAND_TIMEOUT), 3, 2);
        if (result & MMC_STATUS_SWITCH_ERROR)
            RET_ERR(3);
        if (buswidth == 4)
            SDCI_CTRL = (SDCI_CTRL & ~SDCI_CTRL_BUS_WIDTH_MASK) | SDCI_CTRL_BUS_WIDTH_4BIT;
        else if (buswidth == 8)
            SDCI_CTRL = (SDCI_CTRL & ~SDCI_CTRL_BUS_WIDTH_MASK) | SDCI_CTRL_BUS_WIDTH_8BIT;
    }
    PASS_RC(ceata_soft_reset(), 3, 4);
    PASS_RC(ceata_read_multiple_register(0, ceata_taskfile, 0x10), 3, 5);
    if (ceata_taskfile[0xc] != 0xce || ceata_taskfile[0xd] != 0xaa)
        RET_ERR(6);
    PASS_RC(mmc_fastio_write(6, 0), 3, 7);
    return 0;
}

static int ceata_check_error(void)
{
    uint32_t status, error;
    PASS_RC(mmc_fastio_read(0xf, &status), 2, 0);
    if (status & 1)
    {
        PASS_RC(mmc_fastio_read(0x9, &error), 2, 1);
        RET_ERR((error << 2) | 2);
    }
    return 0;
}

static int ceata_wait_idle(void)
{
    long startusec = USEC_TIMER;
    while (true)
    {
        uint32_t status;
        PASS_RC(mmc_fastio_read(0xf, &status), 1, 0);
        if (!(status & 0x88))
            return 0;
        if (TIMEOUT_EXPIRED(startusec, CEATA_DAT_NONBUSY_TIMEOUT))
            RET_ERR(1);
        sleep(HZ / 20);
    }
}

static int ceata_cancel_command(void)
{
    GPIOCMD = 0x9000e;
    udelay(1);
    GPIOCMD = 0x9000f;
    udelay(1);
    GPIOCMD = 0x90003;
    udelay(1);
    PASS_RC(mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_STOP_TRANSMISSION)
                           | SDCI_CMD_CMD_TYPE_AC | SDCI_CMD_RES_TYPE_R1 | SDCI_CMD_RES_BUSY
                           | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NCR,
                             0, NULL, CEATA_COMMAND_TIMEOUT), 1, 0);
    PASS_RC(ceata_wait_idle(), 1, 1);
    return 0;
}

/* Note COUNT is in terms of MMC blocks, ie 512 bytes */
static int ceata_rw_multiple_block(bool write, void* buf, uint32_t count, long timeout)
{
    mmc_discard_irq();
    uint32_t responsetype;
    uint32_t cmdtype;
    uint32_t direction;
    if (write)
    {
        cmdtype = SDCI_CMD_CMD_TYPE_ADTC | SDCI_CMD_CMD_RD_WR;
        responsetype = SDCI_CMD_RES_TYPE_R1 | SDCI_CMD_RES_BUSY;
        direction = MMC_CMD_CEATA_RW_MULTIPLE_BLOCK_DIRECTION_WRITE;
    }
    else
    {
        cmdtype = SDCI_CMD_CMD_TYPE_ADTC;
        responsetype = SDCI_CMD_RES_TYPE_R1;
        direction = MMC_CMD_CEATA_RW_MULTIPLE_BLOCK_DIRECTION_READ;
    }
    SDCI_DMASIZE = 512; /* Default MMC block size is 512.  Can be negotiated to 1K or 4K */
    SDCI_DMAADDR = buf;
    SDCI_DMACOUNT = count;
    SDCI_DCTRL = SDCI_DCTRL_TXFIFORST | SDCI_DCTRL_RXFIFORST;
    commit_discard_dcache();
    PASS_RC(mmc_send_command(SDCI_CMD_CMD_NUM(MMC_CMD_CEATA_RW_MULTIPLE_BLOCK)
                           | SDCI_CMD_CMD_TYPE_ADTC | cmdtype | responsetype
                           | SDCI_CMD_RES_SIZE_48 | SDCI_CMD_NCR_NID_NCR,
                             direction | MMC_CMD_CEATA_RW_MULTIPLE_BLOCK_COUNT(count),
                             NULL, CEATA_COMMAND_TIMEOUT), 3, 0);
    if (write)
        SDCI_DCTRL = SDCI_DCTRL_TRCONT_TX;
    if (semaphore_wait(&mmc_wakeup, timeout) == OBJ_WAIT_TIMEDOUT)
    {
        PASS_RC(ceata_cancel_command(), 3, 1);
        RET_ERR(2);
    }
    PASS_RC(mmc_dsta_check_data_success(), 3, 3);
    if (semaphore_wait(&mmc_comp_wakeup, timeout) == OBJ_WAIT_TIMEDOUT)
    {
        PASS_RC(ceata_cancel_command(), 3, 4);
        RET_ERR(5);
    }
    PASS_RC(ceata_check_error(), 3, 6);
    return 0;
}

static int ata_identify(uint16_t* buf)
{
    int i;
    if (ceata)
    {
        memset(ceata_taskfile, 0, 16);
        ceata_taskfile[0xf] = CMD_IDENTIFY;
        PASS_RC(ceata_wait_idle(), 2, 0);
        PASS_RC(ceata_write_multiple_register(0, ceata_taskfile, 16), 2, 1);
        PASS_RC(ceata_rw_multiple_block(false, buf, 1, CEATA_COMMAND_TIMEOUT * HZ / 1000000), 2, 2);
    }
    else
    {
        PASS_RC(ata_wait_for_not_bsy(10000000), 1, 0);
        ata_write_cbr(&ATA_PIO_DVR, 0);
        ata_write_cbr(&ATA_PIO_CSD, CMD_IDENTIFY);
        PASS_RC(ata_wait_for_start_of_transfer(10000000), 1, 1);
        for (i = 0; i < ATA_IDENTIFY_WORDS; i++)
            buf[i] = ata_read_cbr(&ATA_PIO_DTR);
    }
    return 0;
}

static void ata_set_active(void)
{
    ata_last_activity_value = current_tick;
}

bool ata_disk_is_active(void)
{
    return ata_powered;
}

static int ata_set_feature(uint32_t feature, uint32_t param)
{
    if (ceata)
    {
        memset(ceata_taskfile, 0, 16);
        ceata_taskfile[0x1] = feature;
        ceata_taskfile[0x2] = param;
        ceata_taskfile[0xf] = CMD_SET_FEATURES;
        PASS_RC(ceata_wait_idle(), 2, 0);
        PASS_RC(ceata_write_multiple_register(0, ceata_taskfile, 16), 2, 1);
        PASS_RC(ceata_wait_idle(), 2, 2);
    }
    else
    {
        PASS_RC(ata_wait_for_rdy(2000000), 2, 0);
        ata_write_cbr(&ATA_PIO_DVR, 0);
        ata_write_cbr(&ATA_PIO_FED, feature);
        ata_write_cbr(&ATA_PIO_SCR, param);
        ata_write_cbr(&ATA_PIO_CSD, CMD_SET_FEATURES);
        PASS_RC(ata_wait_for_rdy(2000000), 2, 1);
    }
    return 0;
}

#ifdef HAVE_ATA_DMA
static int udmatimes[ATA_MAX_UDMA + 1] = {
        0x4071152,
        0x2050d52,
#if ATA_MAX_UDMA >= 2
        0x2030a52,
#endif
#if ATA_MAX_UDMA >= 3
        0x1020a52,
#endif
#if ATA_MAX_UDMA >= 4
        0x1010a52,
#endif
};
static int mwdmatimes[ATA_MAX_MWDMA + 1] = {
        0x1c175,
        0x7083,
        0x5072,
};

static int ata_get_best_mode(unsigned short identword, int max, int modetype)
{
    unsigned short testbit = BIT_N(max);

    while (1) {
        if (identword & testbit)
            return max | modetype;
        testbit >>= 1;
        if (!testbit)
            return 0;
        max--;
    }
}
#endif

/*
 * ATA_UDMA_TIME register is documented on s3c6400 datasheet, information
 * included in s5l8700 datasheet is wrong or not valid for s5l8702.
 *
 * On Classic (boosted):
 *  HClk = 108 MHz. -> T = ~9.26 ns.
 *
 * Configured values (in nanoseconds):
 *
 *  UDMA  ATA_UDMA_TIME  tACK  tRP    tSS   tDVS  tDVH  Tcyc   WR(MB/s)
 *  0     0x4071152      27.8  166.7  55.6  74.1  46.3  120.4  16.6
 *  1     0x2050d52      27.8  129.6  55.6  55.6  27.8   83.4  24
 *  2     0x2030a52      27.8  101.8  55.6  37    27.8   64.8  30.9
 *  3     0x1020a52      27.8  101.8  55.6  27.8  18.5   46.3  43.2
 *  4     0x1010a52      27.8  101.8  55.6  18.5  18.5   37    54
 *
 *  Tcyc = tDVS+tDVH
 *  WR[bytes/s] = 1/Tcyc[s] * 2[bytes]
 */
static int ata_power_up(void)
{
    logf("ata POWERUP %ld", current_tick);

    ata_set_active();
    ide_power_enable(true);
    long spinup_start = current_tick;
    if (ceata) {
        ata_lba48 = true;
        ata_dma = true;
#ifdef HAVE_ATA_DMA
        dma_mode = 0xff; /* Canary */
#endif
        PCON(8) = 0x33333333;
        PCON(9) = 0x00000033;
        PCON(11) |= 0xf;
        ATA_UNKNOWN = 0;
        ATA_CONTROL = 0;
        PWRCON(0) &= ~(1 << 9);
        SDCI_RESET = 0xa5;
        sleep(HZ / 100);
        GPIOUNK380 = 0;
        PUNK8 = 0xff;
        SDCI_CTRL = SDCI_CTRL_SDCIEN | SDCI_CTRL_CLK_SEL_SDCLK
                  | SDCI_CTRL_BIT_8 | SDCI_CTRL_BIT_14;
        SDCI_CLKDIV = SDCI_CDIV_CLKDIV(260);
        GPIOCMD = 0xb000f;
        SDCI_IRQ_MASK = SDCI_IRQ_MASK_MASK_DAT_DONE_INT | SDCI_IRQ_MASK_MASK_IOCARD_IRQ_INT;
        PASS_RC(mmc_init(), 3, 0);
        SDCI_CLKDIV = SDCI_CDIV_CLKDIV(4);
        sleep(HZ / 100);
        PASS_RC(ceata_init(8), 3, 1);
        PASS_RC(ata_identify(identify_info), 3, 2);
    } else {
        PCON(7) = 0x44444444;
        PCON(8) = 0x44444444;
        PCON(9) = 0x44444444;
        PCON(10) = (PCON(10) & ~0xffff) | 0x4444;
        PWRCON(0) &= ~(1 << 5);
        ATA_CFG = BIT(0);
        sleep(HZ / 100);
        ATA_CFG = 0;
        sleep(HZ / 100);
        ATA_SWRST = BIT(0);
        sleep(HZ / 100);
        ATA_SWRST = 0;
        sleep(HZ / 10);
        ATA_CONTROL = BIT(0);
        sleep(HZ / 5);
        ATA_PIO_TIME = 0x191f7;
        ATA_PIO_LHR = 0;
        ATA_CFG = BIT(6);
        while (!(ATA_PIO_READY & BIT(1))) yield();

        PASS_RC(ata_identify(identify_info), 3, 3);

        uint32_t piotime = 0x11f3; /* PIO0-2? */
        if (identify_info[53] & BIT(1)) /* Word 64..70 valid */
        {
            if (identify_info[64] & BIT(1))
                piotime = 0x2072; /* PIO mode 4 */
            else if (identify_info[64] & BIT(0))
                piotime = 0x7083; /* PIO mode 3 */
        }
        ATA_PIO_TIME = piotime;

        uint32_t param = 0;
#ifdef HAVE_ATA_DMA
        ata_dma_flags = 0;
        if ((identify_info[53] & BIT(2)) && (identify_info[88] & BITRANGE(0, 4))) /* Any UDMA */
        {
            int max_udma = ATA_MAX_UDMA;
#if ATA_MAX_UDMA > 2
            if (!identify_info[76] && !(identify_info[93] & BIT(13))) /* w93b13 is only valid for PATA, w76 is 0 PATA */
                max_udma = 2;
#endif
            param = ata_get_best_mode(identify_info[88], max_udma, 0x40);
            ATA_UDMA_TIME = udmatimes[param & 0xf];
            ata_dma_flags = BIT(2) | BIT(3) | BIT(9) | BIT(10);
        }
        if (!param && identify_info[63] & BITRANGE(0, 2)) /* Fall back to any MWDMA */
        {
            param = ata_get_best_mode(identify_info[63], ATA_MAX_MWDMA, 0x20);
            ATA_MDMA_TIME = mwdmatimes[param & 0xf];
            ata_dma_flags = BIT(3) | BIT(10);
        }
        dma_mode = param;
#endif /* HAVE_ATA_DMA */
        ata_dma = param ? true : false;
        PASS_RC(ata_set_feature(0x03, param), 3, 4); /* Transfer mode */

        /* SET_FEATURE only supported on PATA, not CE-ATA */
        if (identify_info[82] & BIT(5))
            PASS_RC(ata_set_feature(0x02, 0), 3, 5); /* Enable volatile write cache */
        if (identify_info[82] & BIT(6))
            PASS_RC(ata_set_feature(0xaa, 0), 3, 6); /* Enable read lookahead */
        if (identify_info[83] & BIT(3))
            PASS_RC(ata_set_feature(0x05, 0x80), 3, 7); /* Enable lowest power mode w/o standby */
        if (identify_info[83] & BIT(9))
            PASS_RC(ata_set_feature(0x42, 0x80), 3, 8); /* Enable lowest noise mode */

        PASS_RC(ata_identify(identify_info), 3, 9); /* Finally, re-read identify info */
    }

    spinup_time = current_tick - spinup_start;

    total_sectors = (identify_info[61] << 16) | identify_info[60];
    if (ceata || (identify_info[83] & BIT(10) && total_sectors == 0x0FFFFFFF))
    {
        total_sectors = ((uint64_t)identify_info[103] << 48) |
                ((uint64_t)identify_info[102] << 32) |
                ((uint64_t)identify_info[101] << 16) |
                identify_info[100];
        ata_lba48 = true;
    } else {
        ata_lba48 = false;
    }

    ata_powered = true;
    ata_set_active();
    return 0;
}

static void ata_power_down(void)
{
    if (!ata_powered)
        return;

    logf("ata POWERDOWN %ld", current_tick);

    PCON(7) = 0;
    PCON(8) = 0;
    PCON(9) = 0;
    PCON(10) &= ~0xffff;
    PCON(11) &= ~0xf;
    ide_power_enable(false);
    ata_powered = false;
}

static int ata_rw_chunk_internal(uint64_t sector, uint32_t cnt, void* buffer, bool write)
{
    if (ceata) {
        /* Despite the fact that CE-ATA specifies a minimum logical
           sector size of 4096 bytes, the actual transfer commands
           must be specified in units of 512 bytes. So scale the
           sector count up and the LBA down.

           On CE-ATA devies, the partition table and filesytem is
           formatted with 4K logical sectors, so this will be safe.
        */
        cnt <<= (identify_info[106] - 9);
        sector <<= (identify_info[106] - 9);

        memset(ceata_taskfile, 0, 16);
        ceata_taskfile[0x2] = cnt >> 8;
        ceata_taskfile[0x3] = sector >> 24;
        ceata_taskfile[0x4] = sector >> 32;
        ceata_taskfile[0x5] = sector >> 40;
        ceata_taskfile[0xa] = cnt;
        ceata_taskfile[0xb] = sector;
        ceata_taskfile[0xc] = sector >> 8;
        ceata_taskfile[0xd] = sector >> 16;
        ceata_taskfile[0xf] = write ? CMD_WRITE_DMA_EXT : CMD_READ_DMA_EXT;
        PASS_RC(ceata_wait_idle(), 2, 0);
        PASS_RC(ceata_write_multiple_register(0, ceata_taskfile, 16), 2, 1);
        PASS_RC(ceata_rw_multiple_block(write, buffer, cnt, CEATA_COMMAND_TIMEOUT * HZ / 1000000), 2, 2);
        return 0;
    }

    {
        PASS_RC(ata_wait_for_rdy(100000), 2, 0);
        ata_write_cbr(&ATA_PIO_DVR, 0);
        if (ata_lba48)
        {
            ata_write_cbr(&ATA_PIO_SCR, (cnt >> 8) & 0xff);
            ata_write_cbr(&ATA_PIO_SCR, (cnt) & 0xff);
            ata_write_cbr(&ATA_PIO_LHR, (sector >> 40) & 0xff);
            ata_write_cbr(&ATA_PIO_LMR, (sector >> 32) & 0xff);
            ata_write_cbr(&ATA_PIO_LLR, (sector >> 24) & 0xff);
            ata_write_cbr(&ATA_PIO_LHR, (sector >> 16) & 0xff);
            ata_write_cbr(&ATA_PIO_LMR, (sector >> 8) & 0xff);
            ata_write_cbr(&ATA_PIO_LLR, (sector) & 0xff);
            ata_write_cbr(&ATA_PIO_DVR, BIT(6));
            if (write)
                ata_write_cbr(&ATA_PIO_CSD, ata_dma ? CMD_WRITE_DMA_EXT : CMD_WRITE_MULTIPLE_EXT);
            else
                ata_write_cbr(&ATA_PIO_CSD, ata_dma ? CMD_READ_DMA_EXT : CMD_READ_MULTIPLE_EXT);
        }
        else
        {
            ata_write_cbr(&ATA_PIO_SCR, (cnt) & 0xff);
            ata_write_cbr(&ATA_PIO_LHR, (sector >> 16) & 0xff);
            ata_write_cbr(&ATA_PIO_LMR, (sector >> 8) & 0xff);
            ata_write_cbr(&ATA_PIO_LLR, (sector) & 0xff);
            ata_write_cbr(&ATA_PIO_DVR, BIT(6) | ((sector >> 24) & 0xf)); /* LBA28, mask off upper 4 bits of 32-bit sector address */
            if (write)
                ata_write_cbr(&ATA_PIO_CSD, ata_dma ? CMD_WRITE_DMA : CMD_WRITE_SECTORS);
            else
                ata_write_cbr(&ATA_PIO_CSD, ata_dma ? CMD_READ_DMA : CMD_READ_MULTIPLE);
        }
#ifdef HAVE_ATA_DMA
        if (ata_dma)
        {
            PASS_RC(ata_wait_for_start_of_transfer(500000), 2, 1);
            if (write)
            {
                ATA_SBUF_START = buffer;
                ATA_SBUF_SIZE = log_sector_size * cnt;
                ATA_CFG |= BIT(4);
            }
            else
            {
                ATA_TBUF_START = buffer;
                ATA_TBUF_SIZE = log_sector_size * cnt;
                ATA_CFG &= ~BIT(4);
            }
            ATA_XFR_NUM = log_sector_size * cnt - 1;
            ATA_CFG |= ata_dma_flags;
            ATA_CFG &= ~(BIT(7) | BIT(8));
            semaphore_wait(&ata_wakeup, 0);
            ATA_IRQ = BITRANGE(0, 4);
            ATA_IRQ_MASK = BIT(0);
            ATA_COMMAND = BIT(0);
            if (semaphore_wait(&ata_wakeup, 500000 * HZ / 1000000) == OBJ_WAIT_TIMEDOUT)
            {
                ATA_COMMAND = BIT(1);
                ATA_CFG &= ~(BITRANGE(2, 3) | BIT(12));
                RET_ERR(2);
            }
            ATA_COMMAND = BIT(1);
            ATA_CFG &= ~(BITRANGE(2, 3) | BIT(12));
        }
        else
#endif // HAVE_ATA_DMA
        {
            while (cnt--)
            {
                uint16_t i;
                PASS_RC(ata_wait_for_start_of_transfer(500000), 2, 1);
                if (write)
                    for (i = 0; i < log_sector_size/2; i++)
                        ata_write_cbr(&ATA_PIO_DTR, ((uint16_t*)buffer)[i]);
                else
                    for (i = 0; i < log_sector_size/2; i++)
                        ((uint16_t*)buffer)[i] = ata_read_cbr(&ATA_PIO_DTR);
                buffer += log_sector_size;
            }
        }
        PASS_RC(ata_wait_for_end_of_transfer(100000), 2, 3);
        return 0;
    }
}

static int ata_rw_chunk(uint64_t sector, uint32_t cnt, void* buffer, bool write)
{
    led(true);
    int rc = ata_rw_chunk_internal(sector, cnt, buffer, write);
    led(false);
    return rc;
}

static int ata_transfer_sectors(uint64_t sector, int count, void* buffer, int write)
{
    if (!ata_powered)
        ata_power_up();
    if (sector + count > total_sectors)
        RET_ERR(0);
    ata_set_active();
    if (ata_dma && write)
        commit_dcache();
    else if (ata_dma)
        commit_discard_dcache();
    if (!ceata)
        ATA_COMMAND = BIT(1);

    logf("ata XFER (%d) %d @ %llu", write, count, sector);

    while (count)
    {
        uint32_t cnt = MIN(ata_lba48 ? 65536 : 256, count);
        int rc = -1;
        rc = ata_rw_chunk(sector, cnt, buffer, write);
        if (rc && ata_error_srst)
            ata_reset();
        if (rc && ata_retries)
        {
            void* buf = buffer;
            uint64_t sect;
            for (sect = sector; sect < sector + cnt; sect++)
            {
                rc = -1;
                int tries = ata_retries;
                while (tries-- && rc)
                {
                    rc = ata_rw_chunk(sect, 1, buf, write);
                    if (rc && ata_error_srst)
                        ata_reset();
                }
                if (rc)
                    break;
                buf += log_sector_size;
            }
        }
        PASS_RC(rc, 1, 1);
        buffer += log_sector_size * cnt;
        sector += cnt;
        count -= cnt;
    }
    ata_set_active();
    return 0;
}

/* API Functions */
int ata_soft_reset(void)
{
    int rc;
    mutex_lock(&ata_mutex);
    if (!ata_powered)
        PASS_RC(ata_power_up(), 1, 0);
    ata_set_active();
    if (ceata)
    {
        rc = ceata_soft_reset();
    }
    else
    {
        ata_write_cbr(&ATA_PIO_DAD, BIT(1) | BIT(2));
        udelay(10);
        ata_write_cbr(&ATA_PIO_DAD, 0);
        rc = ata_wait_for_rdy(3000000);
    }
    ata_set_active();
    mutex_unlock(&ata_mutex);
    PASS_RC(rc, 1, 1);
    return 0;
}

int ata_hard_reset(void)
{
    mutex_lock(&ata_mutex);
    PASS_RC(ata_power_up(), 0, 0);
    ata_set_active();
    mutex_unlock(&ata_mutex);
    return 0;
}

static int ata_reset(void)
{
    int rc;
    mutex_lock(&ata_mutex);
    if (!ata_powered)
        PASS_RC(ata_power_up(), 2, 0);
    ata_set_active();
    rc = ata_soft_reset();
    if (IS_ERR(rc))
    {
        rc = ata_hard_reset();
        if (IS_ERR(rc))
        {
            rc = ERR_RC((rc << 2) | 1);
            ata_power_down();
            sleep(HZ * 3);
            int rc2 = ata_power_up();
            if (IS_ERR(rc2))
                rc = ERR_RC((rc << 2) | 2);
        }
        else rc = 1;
    }
    ata_set_active();
    mutex_unlock(&ata_mutex);
    return rc;
}

#ifndef MAX_PHYS_SECTOR_SIZE
int ata_read_sectors(IF_MD(int drive,) sector_t start, int incount,
                     void* inbuf)
{
#ifdef HAVE_MULTIDRIVE
    (void)drive; /* unused for now */
#endif

    mutex_lock(&ata_mutex);
    int rc = ata_transfer_sectors(start, incount, inbuf, false);
    mutex_unlock(&ata_mutex);
    return rc;
}

int ata_write_sectors(IF_MD(int drive,) sector_t start, int count,
                      const void* outbuf)
{
#ifdef HAVE_MULTIDRIVE
    (void)drive; /* unused for now */
#endif

    mutex_lock(&ata_mutex);
    int rc = ata_transfer_sectors(start, count, (void*)((uint32_t)outbuf), true);
    mutex_unlock(&ata_mutex);
    return rc;
}
#endif /* ndef MAX_PHYS_SECTOR_SIZE */

void ata_spindown(int seconds)
{
    ata_sleep_timeout = seconds * HZ;
}

static void ata_flush_cache(void)
{
    uint8_t cmd;

    logf("ata FLUSH CACHE %ld", current_tick);

    if (ceata) {
        memset(ceata_taskfile, 0, 16);
        ceata_taskfile[0xf] = CMD_FLUSH_CACHE_EXT;  /* CE-ATA only supports EXT */
        ceata_wait_idle();
        ceata_write_multiple_register(0, ceata_taskfile, 16);
        ceata_wait_idle();
    } else {
        if (!canflush) {
            return;
        } else if (ata_lba48 && identify_info[83] & BIT(13)) {
            cmd = CMD_FLUSH_CACHE_EXT; /* Flag, optional, ATA-6 and up, for use with LBA48 devices. Mandatory for CE-ATA */
        } else if (identify_info[83] & BIT(12)) {
            cmd = CMD_FLUSH_CACHE; /* Flag, mandatory, ATA-6 and up */
        } else if (identify_info[80] >= BIT(5)) {  /* Use >= instead of '&' because bits lower than the latest standard we support don't have to be set */
            cmd = CMD_FLUSH_CACHE; /* No flag, mandatory, ATA-5  (Optional for ATA-4) */
        } else {
            /* If neither command is supported then don't issue it. */
            canflush = 0;
            return;
        }

        ata_wait_for_rdy(1000000);
        ata_write_cbr(&ATA_PIO_DVR, 0);
        ata_write_cbr(&ATA_PIO_CSD, cmd);
        ata_wait_for_rdy(1000000);
    }
}

int ata_flush(void)
{
    if (ata_powered) {
        mutex_lock(&ata_mutex);
        ata_flush_cache();
        mutex_unlock(&ata_mutex);
    }
    return 0;
}

void ata_sleepnow(void)
{
    mutex_lock(&ata_mutex);

    ata_flush_cache();

    if (ceata || ata_disk_can_sleep()) {
        logf("ata SLEEP %ld", current_tick);

        if (ceata) {
            memset(ceata_taskfile, 0, 16);
            ceata_taskfile[0xf] = CMD_STANDBY_IMMEDIATE;
            ceata_wait_idle();
            ceata_write_multiple_register(0, ceata_taskfile, 16);
            ceata_wait_idle();
            sleep(HZ);
            PWRCON(0) |= (1 << 9);
        } else {
            ata_wait_for_rdy(1000000);
            ata_write_cbr(&ATA_PIO_DVR, 0);
            ata_write_cbr(&ATA_PIO_CSD, CMD_STANDBY_IMMEDIATE);
            ata_wait_for_rdy(1000000);
            sleep(HZ / 30);
            ATA_CONTROL = 0;
            while (!(ATA_CONTROL & BIT(1)))
                yield();
            PWRCON(0) |= (1 << 5);
        }
    }

    if (ceata || ata_disk_can_sleep() || canflush)
        ata_power_down(); // XXX add a powerdown delay similar to main ATA driver?

    mutex_unlock(&ata_mutex);
}

void ata_spin(void)
{
    ata_set_active();
}

long ata_last_disk_activity(void)
{
    return ata_last_activity_value;
}

int ata_init(void)
{
    mutex_init(&ata_mutex);
    semaphore_init(&ata_wakeup, 1, 0);
    semaphore_init(&mmc_wakeup, 1, 0);
    semaphore_init(&mmc_comp_wakeup, 1, 0);
    ceata = PDAT(11) & BIT(1);
    ata_powered = false;
    total_sectors = 0;

    /* get identify_info */
    mutex_lock(&ata_mutex);
    int rc = ata_power_up(); /* Includes identify() call */
    mutex_unlock(&ata_mutex);
    if (IS_ERR(rc))
        return rc;

    /* Logical sector size */
    if (ceata)
        log_sector_size = 1 << identify_info[106];
    else if ((identify_info[106] & 0xd000) == 0x5000) /* B14, B12 */
        log_sector_size = (identify_info[117] | (identify_info[118] << 16)) * 2;
    else
        log_sector_size = 512;

#ifndef MAX_VARIABLE_LOG_SECTOR
    if (log_sector_size != SECTOR_SIZE)
        panicf("Bad logical sector size (%ld)", log_sector_size);
#else
    if (log_sector_size > MAX_VARIABLE_LOG_SECTOR)
        panicf("Logical sector size too large (%ld)", log_sector_size);
#endif

#ifdef MAX_PHYS_SECTOR_SIZE
    rc = ata_get_phys_sector_mult();
    if (IS_ERR(rc))
        return rc;
#endif

    return 0;
}

#ifdef HAVE_ATA_SMART
static int ata_smart(uint16_t* buf, uint8_t cmd)
{
    if (!ata_powered) PASS_RC(ata_power_up(), 3, 0);
    if (ceata)
    {
        memset(ceata_taskfile, 0, 16);
        ceata_taskfile[0xc] = 0x4f;
        ceata_taskfile[0xd] = 0xc2;
        ceata_taskfile[0xe] = BIT(6);
        ceata_taskfile[0xf] = CMD_SMART;
        PASS_RC(ceata_wait_idle(), 3, 1);
        if (((uint8_t*)identify_info)[54] != 'A')  /* Model != aAmsung */
        {
            ceata_taskfile[0x9] = 0xd8; /* SMART enable operations */
            PASS_RC(ceata_write_multiple_register(0, ceata_taskfile, 16), 3, 2);
            PASS_RC(ceata_check_error(), 3, 3);
        }
        ceata_taskfile[0x9] = cmd;
        PASS_RC(ceata_write_multiple_register(0, ceata_taskfile, 16), 3, 4);
        PASS_RC(ceata_rw_multiple_block(false, buf, 1, CEATA_COMMAND_TIMEOUT * HZ / 1000000), 3, 5);
    }
    else
    {
        int i;
        PASS_RC(ata_wait_for_not_bsy(10000000), 3, 6);
        ata_write_cbr(&ATA_PIO_FED, cmd);
        ata_write_cbr(&ATA_PIO_LMR, 0x4f);
        ata_write_cbr(&ATA_PIO_LHR, 0xc2);
        ata_write_cbr(&ATA_PIO_DVR, BIT(6));
        ata_write_cbr(&ATA_PIO_CSD, CMD_SMART);
        PASS_RC(ata_wait_for_start_of_transfer(10000000), 3, 7);
        for (i = 0; i < 0x100; i++)
            buf[i] = ata_read_cbr(&ATA_PIO_DTR);
    }
    ata_set_active();
    return 0;
}

int ata_read_smart(struct ata_smart_values* smart_data, uint8_t cmd)
{
    mutex_lock(&ata_mutex);
    int rc = ata_smart((uint16_t*)smart_data, cmd);
    mutex_unlock(&ata_mutex);
    return rc;
}
#endif /* HAVE_ATA_SMART */

#ifdef CONFIG_STORAGE_MULTI
static int ata_num_drives(int first_drive)
{
    /* We don't care which logical drive number(s) we have been assigned */
    (void)first_drive;

    return 1;
}
#endif

unsigned short* ata_get_identify(void)
{
    return identify_info;
}

#ifdef HAVE_ATA_DMA
int ata_get_dma_mode(void)
{
    return dma_mode;
}
#endif

void INT_ATA(void)
{
    uint32_t ata_irq = ATA_IRQ;
    ATA_IRQ = ata_irq;
    if (ata_irq & ATA_IRQ_MASK)
        semaphore_release(&ata_wakeup);
    ATA_IRQ_MASK = 0;
}

void INT_MMC(void)
{
    uint32_t irq = SDCI_IRQ;
    if (irq & SDCI_IRQ_DAT_DONE_INT)
        semaphore_release(&mmc_wakeup);
    if (irq & SDCI_IRQ_IOCARD_IRQ_INT)
        semaphore_release(&mmc_comp_wakeup);
    SDCI_IRQ = irq;
}

int ata_event(long id, intptr_t data)
{
    int rc = 0;

    /* GCC does a lousy job culling unreachable cases in the default handler
       if statements are in a switch statement, so we'll do it this way. Only
       the first case is frequently hit anyway. */
    if (LIKELY(id == Q_STORAGE_TICK))
    {
        if (!ata_powered ||
            TIME_BEFORE(current_tick, ata_last_activity_value + ata_sleep_timeout))
        {
            STG_EVENT_ASSERT_ACTIVE(STORAGE_ATA);
        }
    }
    else if (id == Q_STORAGE_SLEEPNOW)
    {
        ata_sleepnow();
    }
    else if (id == Q_STORAGE_SLEEP)
    {
        ata_last_activity_value = current_tick - ata_sleep_timeout + HZ / 5;
    }
    else if (id == SYS_USB_CONNECTED)
    {
        STG_EVENT_ASSERT_ACTIVE(STORAGE_ATA);
    }
    else
    {
        rc = storage_event_default_handler(id, data, ata_last_activity_value,
                                           STORAGE_ATA);
    }

    return rc;
}
