OpenWrt – Rev 4

Subversion Repositories:
Rev:
/*
 * sata_oxnas
 *      A driver to interface the 934 based sata core present in the ox820
 *      with libata and scsi
 * based on sata_oxnas driver by Ma Haijun <mahaijuns@gmail.com>
 * based on ox820 sata code by:
 *  Copyright (c) 2007 Oxford Semiconductor Ltd.
 *
 * 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, 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.
 */

#include <linux/ata.h>
#include <linux/libata.h>
#include <linux/of_platform.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/clk.h>
#include <linux/reset.h>

#include <linux/io.h>
#include <linux/sizes.h>

static inline void oxnas_register_clear_mask(void __iomem *p, unsigned mask)
{
        u32 val = readl_relaxed(p);

        val &= ~mask;
        writel_relaxed(val, p);
}

static inline void oxnas_register_set_mask(void __iomem *p, unsigned mask)
{
        u32 val = readl_relaxed(p);

        val |= mask;
        writel_relaxed(val, p);
}

static inline void oxnas_register_value_mask(void __iomem *p,
                                             unsigned mask, unsigned new_value)
{
        /* TODO sanity check mask & new_value = new_value */
        u32 val = readl_relaxed(p);

        val &= ~mask;
        val |= new_value;
        writel_relaxed(val, p);
}

/* sgdma request structure */
struct sgdma_request {
        volatile u32 qualifier;
        volatile u32 control;
        dma_addr_t src_pa;
        dma_addr_t dst_pa;
} __packed __aligned(4);


/* Controller information */
enum {
        SATA_OXNAS_MAX_PRD = 254,
        SATA_OXNAS_DMA_SIZE = SATA_OXNAS_MAX_PRD *
                                sizeof(struct ata_bmdma_prd) +
                                sizeof(struct sgdma_request),
        SATA_OXNAS_MAX_PORTS    = 2,
        /** The different Oxsemi SATA core version numbers */
        SATA_OXNAS_CORE_VERSION = 0x1f3,
        SATA_OXNAS_IRQ_FLAG     = IRQF_SHARED,
        SATA_OXNAS_HOST_FLAGS   = (ATA_FLAG_SATA | ATA_FLAG_PIO_DMA |
                        ATA_FLAG_NO_ATAPI /*| ATA_FLAG_NCQ*/),
        SATA_OXNAS_QUEUE_DEPTH  = 32,

        SATA_OXNAS_DMA_BOUNDARY = 0xFFFFFFFF,
};


/*
 * SATA Port Registers
 */
enum {
        /** sata host port register offsets */
        ORB1 = 0x00,
        ORB2 = 0x04,
        ORB3 = 0x08,
        ORB4 = 0x0C,
        ORB5 = 0x10,
        MASTER_STATUS = 0x10,
        FIS_CTRL = 0x18,
        FIS_DATA = 0x1C,
        INT_STATUS = 0x30,
        INT_CLEAR = 0x30,
        INT_ENABLE = 0x34,
        INT_DISABLE = 0x38,
        VERSION = 0x3C,
        SATA_CONTROL = 0x5C,
        SATA_COMMAND = 0x60,
        HID_FEATURES = 0x64,
        PORT_CONTROL = 0x68,
        DRIVE_CONTROL = 0x6C,
        /** These registers allow access to the link layer registers
        that reside in a different clock domain to the processor bus */
        LINK_DATA = 0x70,
        LINK_RD_ADDR = 0x74,
        LINK_WR_ADDR = 0x78,
        LINK_CONTROL = 0x7C,
        /* window control */
        WIN1LO = 0x80,
        WIN1HI = 0x84,
        WIN2LO = 0x88,
        WIN2HI = 0x8C,
        WIN0_CONTROL = 0x90,
};

/** sata port register bits */
enum{
        /**
         * commands to issue in the master status to tell it to move shadow ,
         * registers to the actual device ,
         */
        SATA_OPCODE_MASK = 0x00000007,
        CMD_WRITE_TO_ORB_REGS_NO_COMMAND = 0x4,
        CMD_WRITE_TO_ORB_REGS = 0x2,
        CMD_SYNC_ESCAPE = 0x7,
        CMD_CORE_BUSY = (1 << 7),
        CMD_DRIVE_SELECT_SHIFT = 12,
        CMD_DRIVE_SELECT_MASK = (0xf << CMD_DRIVE_SELECT_SHIFT),

        /** interrupt bits */
        INT_END_OF_CMD = 1 << 0,
        INT_LINK_SERROR = 1 << 1,
        INT_ERROR = 1 << 2,
        INT_LINK_IRQ = 1 << 3,
        INT_REG_ACCESS_ERR = 1 << 7,
        INT_BIST_FIS = 1 << 11,
        INT_MASKABLE =  INT_END_OF_CMD |
                        INT_LINK_SERROR |
                        INT_ERROR |
                        INT_LINK_IRQ |
                        INT_REG_ACCESS_ERR |
                        INT_BIST_FIS,
        INT_WANT =      INT_END_OF_CMD |
                        INT_LINK_SERROR |
                        INT_REG_ACCESS_ERR |
                        INT_ERROR,
        INT_ERRORS =    INT_LINK_SERROR |
                        INT_REG_ACCESS_ERR |
                        INT_ERROR,

        /** raw interrupt bits, unmaskable, but do not generate interrupts */
        RAW_END_OF_CMD  = INT_END_OF_CMD << 16,
        RAW_LINK_SERROR = INT_LINK_SERROR  << 16,
        RAW_ERROR  = INT_ERROR << 16,
        RAW_LINK_IRQ  = INT_LINK_IRQ << 16,
        RAW_REG_ACCESS_ERR = INT_REG_ACCESS_ERR << 16,
        RAW_BIST_FIS  = INT_BIST_FIS << 16,
        RAW_WANT  = INT_WANT << 16,
        RAW_ERRORS  = INT_ERRORS << 16,

        /**
         * variables to write to the device control register to set the current
         * device, ie. master or slave.
         */
        DR_CON_48 = 2,
        DR_CON_28 = 0,

        SATA_CTL_ERR_MASK = 0x00000016,

};

/* ATA SGDMA register offsets */
enum {
        SGDMA_CONTROL = 0x0,
        SGDMA_STATUS = 0x4,
        SGDMA_REQUESTPTR = 0x8,
        SGDMA_RESETS = 0xC,
        SGDMA_CORESIZE = 0x10,
};

/* DMA controller register offsets */
enum {
        DMA_CONTROL = 0x0,
        DMA_CORESIZE = 0x20,

        DMA_CONTROL_RESET = (1 << 12),
};

enum {
        /* see DMA core docs for the values. Out means from memory (bus A) out
         * to disk (bus B) */
        SGDMA_REQCTL0OUT = 0x0497c03d,
        /* burst mode disabled when no micro code used */
        SGDMA_REQCTL0IN = 0x0493a3c1,
        SGDMA_REQCTL1OUT = 0x0497c07d,
        SGDMA_REQCTL1IN = 0x0497a3c5,
        SGDMA_CONTROL_NOGO = 0x3e,
        SGDMA_CONTROL_GO = SGDMA_CONTROL_NOGO | 1,
        SGDMA_ERRORMASK = 0x3f,
        SGDMA_BUSY = 0x80,

        SGDMA_RESETS_CTRL = 1 << 0,
        SGDMA_RESETS_ARBT = 1 << 1,
        SGDMA_RESETS_AHB = 1 << 2,
        SGDMA_RESETS_ALL =      SGDMA_RESETS_CTRL |
                                SGDMA_RESETS_ARBT |
                                SGDMA_RESETS_AHB,

        /* Final EOTs */
        SGDMA_REQQUAL = 0x00220001,

};

/** SATA core register offsets */
enum {
        DM_DBG1 = 0x000,
        RAID_SET = 0x004,
        DM_DBG2 = 0x008,
        DATACOUNT_PORT0 = 0x010,
        DATACOUNT_PORT1 = 0x014,
        CORE_INT_STATUS = 0x030,
        CORE_INT_CLEAR = 0x030,
        CORE_INT_ENABLE = 0x034,
        CORE_INT_DISABLE  = 0x038,
        CORE_REBUILD_ENABLE = 0x050,
        CORE_FAILED_PORT_R = 0x054,
        DEVICE_CONTROL = 0x068,
        EXCESS = 0x06C,
        RAID_SIZE_LOW = 0x070,
        RAID_SIZE_HIGH = 0x074,
        PORT_ERROR_MASK = 0x078,
        IDLE_STATUS = 0x07C,
        RAID_CONTROL = 0x090,
        DATA_PLANE_CTRL = 0x0AC,
        CORE_DATAPLANE_STAT = 0x0b8,
        PROC_PC = 0x100,
        CONFIG_IN = 0x3d8,
        PROC_START = 0x3f0,
        PROC_RESET = 0x3f4,
        UCODE_STORE = 0x1000,
        RAID_WP_BOT_LOW = 0x1FF0,
        RAID_WP_BOT_HIGH  = 0x1FF4,
        RAID_WP_TOP_LOW = 0x1FF8,
        RAID_WP_TOP_HIGH = 0x1FFC,
        DATA_MUX_RAM0 = 0x8000,
        DATA_MUX_RAM1 = 0xA000,
        PORT_SIZE = 0x10000,
};

enum {
        /* Sata core debug1 register bits */
        CORE_PORT0_DATA_DIR_BIT = 20,
        CORE_PORT1_DATA_DIR_BIT = 21,
        CORE_PORT0_DATA_DIR = 1 << CORE_PORT0_DATA_DIR_BIT,
        CORE_PORT1_DATA_DIR = 1 << CORE_PORT1_DATA_DIR_BIT,

        /** sata core control register bits */
        SCTL_CLR_ERR = 0x00003016,
        RAID_CLR_ERR = 0x0000011e,

        /* Interrupts direct from the ports */
        NORMAL_INTS_WANTED = 0x00000303,

        /* shift these left by port number */
        COREINT_HOST = 0x00000001,
        COREINT_END = 0x00000100,
        CORERAW_HOST = COREINT_HOST << 16,
        CORERAW_END = COREINT_END  << 16,

        /* Interrupts from the RAID controller only */
        RAID_INTS_WANTED = 0x00008300,

        /* The bits in the IDLE_STATUS that, when set indicate an idle core */
        IDLE_CORES = (1 << 18) | (1 << 19),

        /* Data plane control error-mask mask and bit, these bit in the data
         * plane control mask out errors from the ports that prevent the SGDMA
         * care from sending an interrupt */
        DPC_ERROR_MASK = 0x00000300,
        DPC_ERROR_MASK_BIT = 0x00000100,
        /* enable jbod micro-code */
        DPC_JBOD_UCODE = 1 << 0,
        DPC_FIS_SWCH = 1 << 1,

        /** Device Control register bits */
        DEVICE_CONTROL_DMABT = 1 << 4,
        DEVICE_CONTROL_ABORT = 1 << 2,
        DEVICE_CONTROL_PAD = 1 << 3,
        DEVICE_CONTROL_PADPAT = 1 << 16,
        DEVICE_CONTROL_PRTRST = 1 << 8,
        DEVICE_CONTROL_RAMRST = 1 << 12,
        DEVICE_CONTROL_ATA_ERR_OVERRIDE = 1 << 28,

        /** oxsemi HW raid modes */
        OXNASSATA_NOTRAID = 0,
        OXNASSATA_RAID0 = 1,
        OXNASSATA_RAID1 = 2,
        /** OX820 specific HW-RAID register values */
        RAID_TWODISKS = 3,
        UNKNOWN_MODE = ~0,

        CONFIG_IN_RESUME = 2,
};

/* SATA PHY Registers */
enum {
        PHY_STAT = 0x00,
        PHY_DATA = 0x04,
};

enum {
        STAT_READ_VALID = (1 << 21),
        STAT_CR_ACK = (1 << 20),
        STAT_CR_READ = (1 << 19),
        STAT_CR_WRITE = (1 << 18),
        STAT_CAP_DATA = (1 << 17),
        STAT_CAP_ADDR = (1 << 16),

        STAT_ACK_ANY =  STAT_CR_ACK |
                        STAT_CR_READ |
                        STAT_CR_WRITE |
                        STAT_CAP_DATA |
                        STAT_CAP_ADDR,

        CR_READ_ENABLE = (1 << 16),
        CR_WRITE_ENABLE = (1 << 17),
        CR_CAP_DATA = (1 << 18),
};

enum {
        /* Link layer registers */
        SERROR_IRQ_MASK = 5,
};

enum {
        OXNAS_SATA_SOFTRESET = 1,
        OXNAS_SATA_REINIT = 2,
};

enum {
        OXNAS_SATA_UCODE_RAID0,
        OXNAS_SATA_UCODE_RAID1,
        OXNAS_SATA_UCODE_JBOD,
        OXNAS_SATA_UCODE_NONE,
};

enum {
        SATA_UNLOCKED,
        SATA_WRITER,
        SATA_READER,
        SATA_REBUILD,
        SATA_HWRAID,
        SATA_SCSI_STACK
};

typedef irqreturn_t (*oxnas_sata_isr_callback_t)(int, unsigned long, int);

struct sata_oxnas_host_priv {
        void __iomem *port_base;
        void __iomem *dmactl_base;
        void __iomem *sgdma_base;
        void __iomem *core_base;
        void __iomem *phy_base;
        dma_addr_t dma_base;
        void __iomem *dma_base_va;
        size_t dma_size;
        int irq;
        int n_ports;
        int current_ucode;
        u32 port_frozen;
        u32 port_in_eh;
        struct clk *clk;
        struct reset_control *rst_sata;
        struct reset_control *rst_link;
        struct reset_control *rst_phy;
        spinlock_t phy_lock;
        spinlock_t core_lock;
        int core_locked;
        int reentrant_port_no;
        int hw_lock_count;
        int direct_lock_count;
        void *locker_uid;
        int current_locker_type;
        int scsi_nonblocking_attempts;
        oxnas_sata_isr_callback_t isr_callback;
        void *isr_arg;
        wait_queue_head_t fast_wait_queue;
        wait_queue_head_t scsi_wait_queue;
};


struct sata_oxnas_port_priv {
        void __iomem *port_base;
        void __iomem *dmactl_base;
        void __iomem *sgdma_base;
        void __iomem *core_base;
        struct sgdma_request *sgdma_request;
        dma_addr_t sgdma_request_pa;
};

static u8 sata_oxnas_check_status(struct ata_port *ap);
static int sata_oxnas_cleanup(struct ata_host *ah);
static void sata_oxnas_tf_load(struct ata_port *ap,
                                const struct ata_taskfile *tf);
static void sata_oxnas_irq_on(struct ata_port *ap);
static void sata_oxnas_post_reset_init(struct ata_port *ap);

static int sata_oxnas_acquire_hw(struct ata_port *ap, int may_sleep,
                                 int timeout_jiffies);
static void sata_oxnas_release_hw(struct ata_port *ap);

static const void *HW_LOCKER_UID = (void *)0xdeadbeef;

/***************************************************************************
* ASIC access
***************************************************************************/
static void wait_cr_ack(void __iomem *phy_base)
{
        while ((ioread32(phy_base + PHY_STAT) >> 16) & 0x1f)
                ; /* wait for an ack bit to be set */
}

static u16 read_cr(void __iomem *phy_base, u16 address)
{
        iowrite32((u32)address, phy_base + PHY_STAT);
        wait_cr_ack(phy_base);
        iowrite32(CR_READ_ENABLE, phy_base + PHY_DATA);
        wait_cr_ack(phy_base);
        return (u16)ioread32(phy_base + PHY_STAT);
}

static void write_cr(void __iomem *phy_base, u16 data, u16 address)
{
        iowrite32((u32)address, phy_base + PHY_STAT);
        wait_cr_ack(phy_base);
        iowrite32((data | CR_CAP_DATA), phy_base + PHY_DATA);
        wait_cr_ack(phy_base);
        iowrite32(CR_WRITE_ENABLE, phy_base + PHY_DATA);
        wait_cr_ack(phy_base);
}

#define PH_GAIN          2
#define FR_GAIN          3
#define PH_GAIN_OFFSET  6
#define FR_GAIN_OFFSET  8
#define PH_GAIN_MASK  (0x3 << PH_GAIN_OFFSET)
#define FR_GAIN_MASK  (0x3 << FR_GAIN_OFFSET)
#define USE_INT_SETTING  (1<<5)

void workaround5458(struct ata_host *ah)
{
        struct sata_oxnas_host_priv *hd = ah->private_data;
        void __iomem *phy_base = hd->phy_base;
        u16 rx_control;
        unsigned i;

        for (i = 0; i < 2; i++) {
                rx_control = read_cr(phy_base, 0x201d + (i << 8));
                rx_control &= ~(PH_GAIN_MASK | FR_GAIN_MASK);
                rx_control |= PH_GAIN << PH_GAIN_OFFSET;
                rx_control |= (FR_GAIN << FR_GAIN_OFFSET) | USE_INT_SETTING;
                write_cr(phy_base, rx_control, 0x201d+(i<<8));
        }
}

/**
 * allows access to the link layer registers
 * @param link_reg the link layer register to access (oxsemi indexing ie
 *              00 = static config, 04 = phy ctrl)
 */
void sata_oxnas_link_write(struct ata_port *ap, unsigned int link_reg, u32 val)
{
        struct sata_oxnas_port_priv *pd = ap->private_data;
        struct sata_oxnas_host_priv *hd = ap->host->private_data;
        void __iomem *port_base = pd->port_base;
        u32 patience;
        unsigned long flags;

        DPRINTK("P%d [0x%02x]->0x%08x\n", ap->port_no, link_reg, val);

        spin_lock_irqsave(&hd->phy_lock, flags);
        iowrite32(val, port_base + LINK_DATA);

        /* accessed twice as a work around for a bug in the SATA abp bridge
         * hardware (bug 6828) */
        iowrite32(link_reg , port_base + LINK_WR_ADDR);
        ioread32(port_base + LINK_WR_ADDR);

        for (patience = 0x100000; patience > 0; --patience) {
                if (ioread32(port_base + LINK_CONTROL) & 0x00000001)
                        break;
        }
        spin_unlock_irqrestore(&hd->phy_lock, flags);
}

static int sata_oxnas_scr_write_port(struct ata_port *ap, unsigned int sc_reg,
                                        u32 val)
{
        sata_oxnas_link_write(ap, 0x20 + (sc_reg * 4), val);
        return 0;
}

static int sata_oxnas_scr_write(struct ata_link *link, unsigned int sc_reg,
                                u32 val)
{
        return sata_oxnas_scr_write_port(link->ap, sc_reg, val);
}

u32 sata_oxnas_link_read(struct ata_port *ap, unsigned int link_reg)
{
        struct sata_oxnas_port_priv *pd = ap->private_data;
        struct sata_oxnas_host_priv *hd = ap->host->private_data;
        void __iomem *port_base = pd->port_base;
        u32 result;
        u32 patience;
        unsigned long flags;

        spin_lock_irqsave(&hd->phy_lock, flags);
        /* accessed twice as a work around for a bug in the SATA abp bridge
         * hardware (bug 6828) */
        iowrite32(link_reg, port_base + LINK_RD_ADDR);
        ioread32(port_base + LINK_RD_ADDR);

        for (patience = 0x100000; patience > 0; --patience) {
                if (ioread32(port_base + LINK_CONTROL) & 0x00000001)
                        break;
        }
        if (patience == 0)
                DPRINTK("link read timed out for port %d\n", ap->port_no);

        result = ioread32(port_base + LINK_DATA);
        spin_unlock_irqrestore(&hd->phy_lock, flags);

        return result;
}

static int sata_oxnas_scr_read_port(struct ata_port *ap, unsigned int sc_reg,
                                        u32 *val)
{
        *val = sata_oxnas_link_read(ap, 0x20 + (sc_reg*4));
        return 0;
}

static int sata_oxnas_scr_read(struct ata_link *link,
                             unsigned int sc_reg, u32 *val)
{
        return sata_oxnas_scr_read_port(link->ap, sc_reg, val);
}

/**
 * sata_oxnas_irq_clear is called during probe just before the interrupt handler is
 * registered, to be sure hardware is quiet. It clears and masks interrupt bits
 * in the SATA core.
 *
 * @param ap hardware with the registers in
 */
static void sata_oxnas_irq_clear(struct ata_port *ap)
{
        struct sata_oxnas_port_priv *port_priv = ap->private_data;

        /* clear pending interrupts */
        iowrite32(~0, port_priv->port_base + INT_CLEAR);
        iowrite32(COREINT_END, port_priv->core_base + CORE_INT_CLEAR);
}

/**
 * qc_issue is used to make a command active, once the hardware and S/G tables
 * have been prepared. IDE BMDMA drivers use the helper function
 * ata_qc_issue_prot() for taskfile protocol-based dispatch. More advanced
 * drivers roll their own ->qc_issue implementation, using this as the
 * "issue new ATA command to hardware" hook.
 * @param qc the queued command to issue
 */
static unsigned int sata_oxnas_qc_issue(struct ata_queued_cmd *qc)
{
        struct sata_oxnas_port_priv *pd = qc->ap->private_data;
        struct sata_oxnas_host_priv *hd = qc->ap->host->private_data;

        void __iomem *port_base = pd->port_base;
        void __iomem *core_base = pd->core_base;
        int port_no = qc->ap->port_no;
        int no_microcode = (hd->current_ucode == UNKNOWN_MODE);
        u32 reg;

        /* check the core is idle */
        if (ioread32(port_base + SATA_COMMAND) & CMD_CORE_BUSY) {
                int count = 0;

                DPRINTK("core busy for a command on port %d\n",
                        qc->ap->port_no);
                do {
                        mdelay(1);
                        if (++count > 100) {
                                DPRINTK("core busy for a command on port %d\n",
                                        qc->ap->port_no);
                                /* CrazyDumpDebug(); */
                                sata_oxnas_cleanup(qc->ap->host);
                        }
                } while (ioread32(port_base + SATA_COMMAND) & CMD_CORE_BUSY);
        }

        /* enable passing of error signals to DMA sub-core by clearing the
         * appropriate bit */
        reg = ioread32(core_base + DATA_PLANE_CTRL);
        if (no_microcode)
                reg |= (DPC_ERROR_MASK_BIT | (DPC_ERROR_MASK_BIT << 1));
        reg &= ~(DPC_ERROR_MASK_BIT << port_no);
        iowrite32(reg, core_base + DATA_PLANE_CTRL);

        /* Disable all interrupts for ports and RAID controller */
        iowrite32(~0, port_base + INT_DISABLE);

        /* Disable all interrupts for core */
        iowrite32(~0, core_base + CORE_INT_DISABLE);
        wmb();

        /* Load the command settings into the orb registers */
        sata_oxnas_tf_load(qc->ap, &qc->tf);

        /* both pio and dma commands use dma */
        if (ata_is_dma(qc->tf.protocol) || ata_is_pio(qc->tf.protocol)) {
                /* Start the DMA */
                iowrite32(SGDMA_CONTROL_GO,     pd->sgdma_base + SGDMA_CONTROL);
                wmb();
        }

        /* enable End of command interrupt */
        iowrite32(INT_WANT, port_base + INT_ENABLE);
        iowrite32(COREINT_END, core_base + CORE_INT_ENABLE);
        wmb();

        /* Start the command */
        reg = ioread32(port_base + SATA_COMMAND);
        reg &= ~SATA_OPCODE_MASK;
        reg |= CMD_WRITE_TO_ORB_REGS;
        iowrite32(reg , port_base + SATA_COMMAND);
        wmb();

        return 0;
}

/**
 * Will schedule the libATA error handler on the premise that there has
 * been a hotplug event on the port specified
 */
void sata_oxnas_checkforhotplug(struct ata_port *ap)
{
        DPRINTK("ENTER\n");

        ata_ehi_hotplugged(&ap->link.eh_info);
        ata_port_freeze(ap);
}


/**************************************************************************/
/* Locking                                                                */
/**************************************************************************/
/**
 * The underlying function that controls access to the sata core
 *
 * @return non-zero indicates that you have acquired exclusive access to the
 *         sata core.
 */
static int __acquire_sata_core(
        struct ata_host *ah,
        int port_no,
        oxnas_sata_isr_callback_t callback,
        void                    *arg,
        int                      may_sleep,
        int                      timeout_jiffies,
        int                      hw_access,
        void                    *uid,
        int                      locker_type)
{
        unsigned long end = jiffies + timeout_jiffies;
        int           acquired = 0;
        unsigned long flags;
        int           timed_out = 0;
        struct sata_oxnas_host_priv *hd;

        DEFINE_WAIT(wait);

        if (!ah)
                return acquired;

        hd = ah->private_data;

        spin_lock_irqsave(&hd->core_lock, flags);

        DPRINTK("Entered uid %p, port %d, h/w count %d, d count %d, "
                    "callback %p, hw_access %d, core_locked %d, "
                    "reentrant_port_no %d, isr_callback %p\n",
                uid, port_no, hd->hw_lock_count, hd->direct_lock_count,
                callback, hw_access, hd->core_locked, hd->reentrant_port_no,
                hd->isr_callback);

        while (!timed_out) {
                if (hd->core_locked ||
                    (!hw_access && hd->scsi_nonblocking_attempts)) {
                        /* Can only allow access if from SCSI/SATA stack and if
                         * reentrant access is allowed and this access is to the
                         * same port for which the lock is current held
                         */
                        if (hw_access && (port_no == hd->reentrant_port_no)) {
                                BUG_ON(!hd->hw_lock_count);
                                ++(hd->hw_lock_count);

                                DPRINTK("Allow SCSI/SATA re-entrant access to "
                                        "uid %p port %d\n", uid, port_no);
                                acquired = 1;
                                break;
                        } else if (!hw_access) {
                                if ((locker_type == SATA_READER) &&
                                    (hd->current_locker_type == SATA_READER)) {
                                        WARN(1,
                                                "Already locked by reader, "
                                                "uid %p, locker_uid %p, "
                                                "port %d, h/w count %d, "
                                                "d count %d, hw_access %d\n",
                                                uid, hd->locker_uid, port_no,
                                                hd->hw_lock_count,
                                                hd->direct_lock_count,
                                                hw_access);
                                        goto check_uid;
                                }

                                if ((locker_type != SATA_READER) &&
                                    (locker_type != SATA_WRITER)) {
                                        goto wait_for_lock;
                                }

check_uid:
                                WARN(uid == hd->locker_uid, "Attempt to lock "
                                        "by locker type %d uid %p, already "
                                        "locked by locker type %d with "
                                        "locker_uid %p, port %d, "
                                        "h/w count %d, d count %d, "
                                        "hw_access %d\n", locker_type, uid,
                                        hd->current_locker_type,
                                        hd->locker_uid, port_no,
                                        hd->hw_lock_count,
                                        hd->direct_lock_count, hw_access);
                        }
                } else {
                        WARN(hd->hw_lock_count || hd->direct_lock_count,
                                "Core unlocked but counts non-zero: uid %p, "
                                "locker_uid %p, port %d, h/w count %d, "
                                "d count %d, hw_access %d\n", uid,
                                hd->locker_uid, port_no, hd->hw_lock_count,
                                hd->direct_lock_count, hw_access);

                        BUG_ON(hd->current_locker_type != SATA_UNLOCKED);

                        WARN(hd->locker_uid, "Attempt to lock uid %p when "
                                "locker_uid %p is non-zero,  port %d, "
                                "h/w count %d, d count %d, hw_access %d\n",
                                uid, hd->locker_uid, port_no, hd->hw_lock_count,
                                hd->direct_lock_count, hw_access);

                        if (!hw_access) {
                                /* Direct access attempting to acquire
                                 * non-contented lock
                                 */
                                /* Must have callback for direct access */
                                BUG_ON(!callback);
                                /* Sanity check lock state */
                                BUG_ON(hd->reentrant_port_no != -1);

                                hd->isr_callback = callback;
                                hd->isr_arg = arg;
                                ++(hd->direct_lock_count);

                                hd->current_locker_type = locker_type;
                        } else {
                                /* SCSI/SATA attempting to acquire
                                 * non-contented lock
                                 */
                                /* No callbacks for SCSI/SATA access */
                                BUG_ON(callback);
                                /* No callback args for SCSI/SATA access */
                                BUG_ON(arg);

                                /* Sanity check lock state */
                                BUG_ON(hd->isr_callback);
                                BUG_ON(hd->isr_arg);

                                ++(hd->hw_lock_count);
                                hd->reentrant_port_no = port_no;

                                hd->current_locker_type = SATA_SCSI_STACK;
                        }

                        hd->core_locked = 1;
                        hd->locker_uid = uid;
                        acquired = 1;
                        break;
                }

wait_for_lock:
                if (!may_sleep) {
                        DPRINTK("Denying for uid %p locker_type %d, "
                        "hw_access %d, port %d, current_locker_type %d as "
                        "cannot sleep\n", uid, locker_type, hw_access, port_no,
                        hd->current_locker_type);

                        if (hw_access)
                                ++(hd->scsi_nonblocking_attempts);

                        break;
                }

                /* Core is locked and we're allowed to sleep, so wait to be
                 * awoken when the core is unlocked
                 */
                for (;;) {
                        prepare_to_wait(hw_access ? &hd->scsi_wait_queue :
                                                    &hd->fast_wait_queue,
                                        &wait, TASK_UNINTERRUPTIBLE);
                        if (!hd->core_locked &&
                            !(!hw_access && hd->scsi_nonblocking_attempts)) {
                                /* We're going to use variables that will have
                                 * been changed by the waker prior to clearing
                                 * core_locked so we need to ensure we see
                                 * changes to all those variables
                                 */
                                smp_rmb();
                                break;
                        }
                        if (time_after(jiffies, end)) {
                                printk(KERN_WARNING "__acquire_sata_core() "
                                        "uid %p failing for port %d timed out, "
                                        "locker_uid %p, h/w count %d, "
                                        "d count %d, callback %p, hw_access %d, "
                                        "core_locked %d, reentrant_port_no %d, "
                                        "isr_callback %p, isr_arg %p\n", uid,
                                        port_no, hd->locker_uid,
                                        hd->hw_lock_count,
                                        hd->direct_lock_count, callback,
                                        hw_access, hd->core_locked,
                                        hd->reentrant_port_no, hd->isr_callback,
                                        hd->isr_arg);
                                timed_out = 1;
                                break;
                        }
                        spin_unlock_irqrestore(&hd->core_lock, flags);
                        if (!schedule_timeout(4*HZ)) {
                                printk(KERN_INFO "__acquire_sata_core() uid %p, "
                                        "locker_uid %p, timed-out of "
                                        "schedule(), checking overall timeout\n",
                                        uid, hd->locker_uid);
                        }
                        spin_lock_irqsave(&hd->core_lock, flags);
                }
                finish_wait(hw_access ? &hd->scsi_wait_queue :
                                        &hd->fast_wait_queue, &wait);
        }

        if (hw_access && acquired) {
                if (hd->scsi_nonblocking_attempts)
                        hd->scsi_nonblocking_attempts = 0;

                /* Wake any other SCSI/SATA waiters so they can get reentrant
                 * access to the same port if appropriate. This is because if
                 * the SATA core is locked by fast access, or SCSI/SATA access
                 * to other port, then can have >1 SCSI/SATA waiters on the wait
                 * list so want to give reentrant accessors a chance to get
                 * access ASAP
                 */
                if (!list_empty(&hd->scsi_wait_queue.head))
                        wake_up(&hd->scsi_wait_queue);
        }

        DPRINTK("Leaving uid %p with acquired = %d, port %d, callback %p\n",
                uid, acquired, port_no, callback);

        spin_unlock_irqrestore(&hd->core_lock, flags);

        return acquired;
}

int sata_core_has_fast_waiters(struct ata_host *ah)
{
        int has_waiters;
        unsigned long flags;
        struct sata_oxnas_host_priv *hd = ah->private_data;

        spin_lock_irqsave(&hd->core_lock, flags);
        has_waiters = !list_empty(&hd->fast_wait_queue.head);
        spin_unlock_irqrestore(&hd->core_lock, flags);

        return has_waiters;
}
EXPORT_SYMBOL(sata_core_has_fast_waiters);

int sata_core_has_scsi_waiters(struct ata_host *ah)
{
        int has_waiters;
        unsigned long flags;
        struct sata_oxnas_host_priv *hd = ah->private_data;

        spin_lock_irqsave(&hd->core_lock, flags);
        has_waiters = hd->scsi_nonblocking_attempts ||
                      !list_empty(&hd->scsi_wait_queue.head);
        spin_unlock_irqrestore(&hd->core_lock, flags);

        return has_waiters;
}
EXPORT_SYMBOL(sata_core_has_scsi_waiters);

/*
 * ata_port operation to gain ownership of the SATA hardware prior to issuing
 * a command against a SATA host. Allows any number of users of the port against
 * which the lock was first acquired, thus enforcing that only one SATA core
 * port may be operated on at once.
 */
static int sata_oxnas_acquire_hw(
        struct ata_port *ap,
        int may_sleep,
        int timeout_jiffies)
{
        return __acquire_sata_core(ap->host, ap->port_no, NULL, 0, may_sleep,
                                   timeout_jiffies, 1, (void *)HW_LOCKER_UID,
                                   SATA_SCSI_STACK);
}

/*
 * operation to release ownership of the SATA hardware
 */
static void sata_oxnas_release_hw(struct ata_port *ap)
{
        unsigned long flags;
        int released = 0;
        struct sata_oxnas_host_priv *hd = ap->host->private_data;

        spin_lock_irqsave(&hd->core_lock, flags);

        DPRINTK("Entered port_no = %d, h/w count %d, d count %d, "
                "core locked = %d, reentrant_port_no = %d, isr_callback %p\n",
                ap->port_no, hd->hw_lock_count, hd->direct_lock_count,
                hd->core_locked, hd->reentrant_port_no, hd->isr_callback);

        if (!hd->core_locked) {
                /* Nobody holds the SATA lock */
                printk(KERN_WARNING "Nobody holds SATA lock, port_no %d\n",
                       ap->port_no);
                released = 1;
        } else if (!hd->hw_lock_count) {
                /* SCSI/SATA has released without holding the lock */
                printk(KERN_WARNING "SCSI/SATA does not hold SATA lock, "
                       "port_no %d\n", ap->port_no);
        } else {
                /* Trap incorrect usage */
                BUG_ON(hd->reentrant_port_no == -1);
                BUG_ON(ap->port_no != hd->reentrant_port_no);
                BUG_ON(hd->direct_lock_count);
                BUG_ON(hd->current_locker_type != SATA_SCSI_STACK);

                WARN(!hd->locker_uid || (hd->locker_uid != HW_LOCKER_UID),
                        "Invalid locker uid %p, h/w count %d, d count %d, "
                        "reentrant_port_no %d, core_locked %d, "
                        "isr_callback %p\n", hd->locker_uid, hd->hw_lock_count,
                        hd->direct_lock_count, hd->reentrant_port_no,
                        hd->core_locked, hd->isr_callback);

                if (--(hd->hw_lock_count)) {
                        DPRINTK("Still nested port_no %d\n", ap->port_no);
                } else {
                        DPRINTK("Release port_no %d\n", ap->port_no);
                        hd->reentrant_port_no = -1;
                        hd->isr_callback = NULL;
                        hd->current_locker_type = SATA_UNLOCKED;
                        hd->locker_uid = 0;
                        hd->core_locked = 0;
                        released = 1;
                        wake_up(!list_empty(&hd->scsi_wait_queue.head) ?
                                                &hd->scsi_wait_queue :
                                                &hd->fast_wait_queue);
                }
        }

        DPRINTK("Leaving, port_no %d, count %d\n", ap->port_no,
                hd->hw_lock_count);

        spin_unlock_irqrestore(&hd->core_lock, flags);

        /* CONFIG_SATA_OX820_DIRECT_HWRAID */
        /*    if (released)
             ox820hwraid_restart_queue();
        } */
}

static inline int sata_oxnas_is_host_frozen(struct ata_host *ah)
{
        struct sata_oxnas_host_priv *hd = ah->private_data;

        smp_rmb();
        return hd->port_in_eh || hd->port_frozen;
}


static inline u32 sata_oxnas_hostportbusy(struct ata_port *ap)
{
        struct sata_oxnas_host_priv *hd = ap->host->private_data;

        return (ioread32(hd->port_base + SATA_COMMAND) & CMD_CORE_BUSY) ||
               (hd->n_ports > 1 &&
                (ioread32(hd->port_base + PORT_SIZE + SATA_COMMAND) &
                 CMD_CORE_BUSY));
}

static inline u32 sata_oxnas_hostdmabusy(struct ata_port *ap)
{
        struct sata_oxnas_port_priv *pd = ap->private_data;

        return ioread32(pd->sgdma_base + SGDMA_STATUS) & SGDMA_BUSY;
}


/**
 * Turns on the cores clock and resets it
 */
static void sata_oxnas_reset_core(struct ata_host *ah)
{
        struct sata_oxnas_host_priv *host_priv = ah->private_data;
        int n;

        DPRINTK("ENTER\n");
        clk_prepare_enable(host_priv->clk);

        reset_control_assert(host_priv->rst_sata);
        reset_control_assert(host_priv->rst_link);
        reset_control_assert(host_priv->rst_phy);

        udelay(50);

        /* un-reset the PHY, then Link and Controller */
        reset_control_deassert(host_priv->rst_phy);
        udelay(50);

        reset_control_deassert(host_priv->rst_sata);
        reset_control_deassert(host_priv->rst_link);
        udelay(50);

        workaround5458(ah);
        /* tune for sata compatibility */
        sata_oxnas_link_write(ah->ports[0], 0x60, 0x2988);

        for (n = 0; n < host_priv->n_ports; n++) {
                /* each port in turn */
                sata_oxnas_link_write(ah->ports[n], 0x70, 0x55629);
        }
        udelay(50);
}


/**
 * Called after an identify device command has worked out what kind of device
 * is on the port
 *
 * @param port The port to configure
 * @param pdev The hardware associated with controlling the port
 */
static void sata_oxnas_dev_config(struct ata_device *pdev)
{
        struct sata_oxnas_port_priv *pd = pdev->link->ap->private_data;
        void __iomem *port_base = pd->port_base;
        u32 reg;

        DPRINTK("ENTER\n");
        /* Set the bits to put the port into 28 or 48-bit node */
        reg = ioread32(port_base + DRIVE_CONTROL);
        reg &= ~3;
        reg |= (pdev->flags & ATA_DFLAG_LBA48) ? DR_CON_48 : DR_CON_28;
        iowrite32(reg, port_base + DRIVE_CONTROL);

        /* if this is an ATA-6 disk, put port into ATA-5 auto translate mode */
        if (pdev->flags & ATA_DFLAG_LBA48) {
                reg = ioread32(port_base + PORT_CONTROL);
                reg |= 2;
                iowrite32(reg, port_base + PORT_CONTROL);
        }
}
/**
 * called to write a taskfile into the ORB registers
 * @param ap hardware with the registers in
 * @param tf taskfile to write to the registers
 */
static void sata_oxnas_tf_load(struct ata_port *ap,
                                const struct ata_taskfile *tf)
{
        u32 count = 0;
        u32 Orb1 = 0;
        u32 Orb2 = 0;
        u32 Orb3 = 0;
        u32 Orb4 = 0;
        u32 Command_Reg;

        struct sata_oxnas_port_priv *port_priv = ap->private_data;
        void __iomem *port_base = port_priv->port_base;
        unsigned int is_addr = tf->flags & ATA_TFLAG_ISADDR;

        /* wait a maximum of 10ms for the core to be idle */
        do {
                Command_Reg = ioread32(port_base + SATA_COMMAND);
                if (!(Command_Reg & CMD_CORE_BUSY))
                        break;
                count++;
                udelay(50);
        } while (count < 200);

        /* check if the ctl register has interrupts disabled or enabled and
         * modify the interrupt enable registers on the ata core as required */
        if (tf->ctl & ATA_NIEN) {
                /* interrupts disabled */
                u32 mask = (COREINT_END << ap->port_no);

                iowrite32(mask, port_priv->core_base + CORE_INT_DISABLE);
                sata_oxnas_irq_clear(ap);
        } else {
                sata_oxnas_irq_on(ap);
        }

        Orb2 |= (tf->command) << 24;

        /* write 48 or 28 bit tf parameters */
        if (is_addr) {
                /* set LBA bit as it's an address */
                Orb1 |= (tf->device & ATA_LBA) << 24;

                if (tf->flags & ATA_TFLAG_LBA48) {
                        Orb1 |= ATA_LBA << 24;
                        Orb2 |= (tf->hob_nsect) << 8;
                        Orb3 |= (tf->hob_lbal) << 24;
                        Orb4 |= (tf->hob_lbam) << 0;
                        Orb4 |= (tf->hob_lbah) << 8;
                        Orb4 |= (tf->hob_feature) << 16;
                } else {
                        Orb3 |= (tf->device & 0xf) << 24;
                }

                /* write 28-bit lba */
                Orb2 |= (tf->nsect) << 0;
                Orb2 |= (tf->feature) << 16;
                Orb3 |= (tf->lbal) << 0;
                Orb3 |= (tf->lbam) << 8;
                Orb3 |= (tf->lbah) << 16;
                Orb4 |= (tf->ctl) << 24;
        }

        if (tf->flags & ATA_TFLAG_DEVICE)
                Orb1 |= (tf->device) << 24;

        ap->last_ctl = tf->ctl;

        /* write values to registers */
        iowrite32(Orb1, port_base + ORB1);
        iowrite32(Orb2, port_base + ORB2);
        iowrite32(Orb3, port_base + ORB3);
        iowrite32(Orb4, port_base + ORB4);
}


void sata_oxnas_set_mode(struct ata_host *ah, u32 mode, u32 force)
{
        struct sata_oxnas_host_priv *host_priv = ah->private_data;
        void __iomem *core_base = host_priv->core_base;

        unsigned int *src;
        void __iomem *dst;
        unsigned int progmicrocode = 0;
        unsigned int changeparameters = 0;

        u32 previous_mode;

        /* these micro-code programs _should_ include the version word */

        /* JBOD */
        static const unsigned int jbod[] = {
                0x07B400AC, 0x0228A280, 0x00200001, 0x00204002, 0x00224001,
                0x00EE0009, 0x00724901, 0x01A24903, 0x00E40009, 0x00224001,
                0x00621120, 0x0183C908, 0x00E20005, 0x00718908, 0x0198A206,
                0x00621124, 0x0183C908, 0x00E20046, 0x00621104, 0x0183C908,
                0x00E20015, 0x00EE009D, 0x01A3E301, 0x00E2001B, 0x0183C900,
                0x00E2001B, 0x00210001, 0x00EE0020, 0x01A3E302, 0x00E2009D,
                0x0183C901, 0x00E2009D, 0x00210002, 0x0235D700, 0x0208A204,
                0x0071C908, 0x000F8207, 0x000FC207, 0x0071C920, 0x000F8507,
                0x000FC507, 0x0228A240, 0x02269A40, 0x00094004, 0x00621104,
                0x0180C908, 0x00E40031, 0x00621112, 0x01A3C801, 0x00E2002B,
                0x00294000, 0x0228A220, 0x01A69ABF, 0x002F8000, 0x002FC000,
                0x0198A204, 0x0001C022, 0x01B1A220, 0x0001C106, 0x00088007,
                0x0183C903, 0x00E2009D, 0x0228A220, 0x0071890C, 0x0208A206,
                0x0198A206, 0x0001C022, 0x01B1A220, 0x0001C106, 0x00088007,
                0x00EE009D, 0x00621104, 0x0183C908, 0x00E2004A, 0x00EE009D,
                0x01A3C901, 0x00E20050, 0x0021E7FF, 0x0183E007, 0x00E2009D,
                0x00EE0054, 0x0061600B, 0x0021E7FF, 0x0183C507, 0x00E2009D,
                0x01A3E301, 0x00E2005A, 0x0183C900, 0x00E2005A, 0x00210001,
                0x00EE005F, 0x01A3E302, 0x00E20005, 0x0183C901, 0x00E20005,
                0x00210002, 0x0235D700, 0x0208A204, 0x000F8109, 0x000FC109,
                0x0071C918, 0x000F8407, 0x000FC407, 0x0001C022, 0x01A1A2BF,
                0x0001C106, 0x00088007, 0x02269A40, 0x00094004, 0x00621112,
                0x01A3C801, 0x00E4007F, 0x00621104, 0x0180C908, 0x00E4008D,
                0x00621128, 0x0183C908, 0x00E2006C, 0x01A3C901, 0x00E2007B,
                0x0021E7FF, 0x0183E007, 0x00E2007F, 0x00EE006C, 0x0061600B,
                0x0021E7FF, 0x0183C507, 0x00E4006C, 0x00621111, 0x01A3C801,
                0x00E2007F, 0x00621110, 0x01A3C801, 0x00E20082, 0x0228A220,
                0x00621119, 0x01A3C801, 0x00E20086, 0x0001C022, 0x01B1A220,
                0x0001C106, 0x00088007, 0x0198A204, 0x00294000, 0x01A69ABF,
                0x002F8000, 0x002FC000, 0x0183C903, 0x00E20005, 0x0228A220,
                0x0071890C, 0x0208A206, 0x0198A206, 0x0001C022, 0x01B1A220,
                0x0001C106, 0x00088007, 0x00EE009D, 0x00621128, 0x0183C908,
                0x00E20005, 0x00621104, 0x0183C908, 0x00E200A6, 0x0062111C,
                0x0183C908, 0x00E20005, 0x0071890C, 0x0208A206, 0x0198A206,
                0x00718908, 0x0208A206, 0x00EE0005, ~0
        };

        /* Bi-Modal RAID-0/1 */
        static const unsigned int raid[] = {
                0x00F20145, 0x00EE20FA, 0x00EE20A7, 0x0001C009, 0x00EE0004,
                0x00220000, 0x0001000B, 0x037003FF, 0x00700018, 0x037003FE,
                0x037043FD, 0x00704118, 0x037043FC, 0x01A3D240, 0x00E20017,
                0x00B3C235, 0x00E40018, 0x0093C104, 0x00E80014, 0x0093C004,
                0x00E80017, 0x01020000, 0x00274020, 0x00EE0083, 0x0080C904,
                0x0093C104, 0x00EA0020, 0x0093C103, 0x00EC001F, 0x00220002,
                0x00924104, 0x0005C009, 0x00EE0058, 0x0093CF04, 0x00E80026,
                0x00900F01, 0x00600001, 0x00910400, 0x00EE0058, 0x00601604,
                0x01A00003, 0x00E2002C, 0x01018000, 0x00274040, 0x00EE0083,
                0x0093CF03, 0x00EC0031, 0x00220003, 0x00924F04, 0x0005C009,
                0x00810104, 0x00B3C235, 0x00E20037, 0x0022C000, 0x00218210,
                0x00EE0039, 0x0022C001, 0x00218200, 0x00600401, 0x00A04901,
                0x00604101, 0x01A0C401, 0x00E20040, 0x00216202, 0x00EE0041,
                0x00216101, 0x02018506, 0x00EE2141, 0x00904901, 0x00E20049,
                0x00A00401, 0x00600001, 0x02E0C301, 0x00EE2141, 0x00216303,
                0x037003EE, 0x01A3C001, 0x00E40105, 0x00250080, 0x00204000,
                0x002042F1, 0x0004C001, 0x00230001, 0x00100006, 0x02C18605,
                0x00100006, 0x01A3D502, 0x00E20055, 0x00EE0053, 0x00004009,
                0x00000004, 0x00B3C235, 0x00E40062, 0x0022C001, 0x0020C000,
                0x00EE2141, 0x0020C001, 0x00EE2141, 0x00EE006B, 0x0022C000,
                0x0060D207, 0x00EE2141, 0x00B3C242, 0x00E20069, 0x01A3D601,
                0x00E2006E, 0x02E0C301, 0x00EE2141, 0x00230001, 0x00301303,
                0x00EE007B, 0x00218210, 0x01A3C301, 0x00E20073, 0x00216202,
                0x00EE0074, 0x00216101, 0x02018506, 0x00214000, 0x037003EE,
                0x01A3C001, 0x00E40108, 0x00230001, 0x00100006, 0x00250080,
                0x00204000, 0x002042F1, 0x0004C001, 0x00EE007F, 0x0024C000,
                0x01A3D1F0, 0x00E20088, 0x00230001, 0x00300000, 0x01A3D202,
                0x00E20085, 0x00EE00A5, 0x00B3C800, 0x00E20096, 0x00218000,
                0x00924709, 0x0005C009, 0x00B20802, 0x00E40093, 0x037103FD,
                0x00710418, 0x037103FC, 0x00EE0006, 0x00220000, 0x0001000F,
                0x00EE0006, 0x00800B0C, 0x00B00001, 0x00204000, 0x00208550,
                0x00208440, 0x002083E0, 0x00208200, 0x00208100, 0x01008000,
                0x037083EE, 0x02008212, 0x02008216, 0x01A3C201, 0x00E400A5,
                0x0100C000, 0x00EE20FA, 0x02800000, 0x00208000, 0x00B24C00,
                0x00E400AD, 0x00224001, 0x00724910, 0x0005C009, 0x00B3CDC4,
                0x00E200D5, 0x00B3CD29, 0x00E200D5, 0x00B3CD20, 0x00E200D5,
                0x00B3CD24, 0x00E200D5, 0x00B3CDC5, 0x00E200D2, 0x00B3CD39,
                0x00E200D2, 0x00B3CD30, 0x00E200D2, 0x00B3CD34, 0x00E200D2,
                0x00B3CDCA, 0x00E200CF, 0x00B3CD35, 0x00E200CF, 0x00B3CDC8,
                0x00E200CC, 0x00B3CD25, 0x00E200CC, 0x00B3CD40, 0x00E200CB,
                0x00B3CD42, 0x00E200CB, 0x01018000, 0x00EE0083, 0x0025C000,
                0x036083EE, 0x0000800D, 0x00EE00D8, 0x036083EE, 0x00208035,
                0x00EE00DA, 0x036083EE, 0x00208035, 0x00EE00DA, 0x00208007,
                0x036083EE, 0x00208025, 0x036083EF, 0x02400000, 0x01A3D208,
                0x00E200D8, 0x0067120A, 0x0021C000, 0x0021C224, 0x00220000,
                0x00404B1C, 0x00600105, 0x00800007, 0x0020C00E, 0x00214000,
                0x01004000, 0x01A0411F, 0x00404E01, 0x01A3C101, 0x00E200F1,
                0x00B20800, 0x00E400D8, 0x00220001, 0x0080490B, 0x00B04101,
                0x0040411C, 0x00EE00E1, 0x02269A01, 0x01020000, 0x02275D80,
                0x01A3D202, 0x00E200F4, 0x01B75D80, 0x01030000, 0x01B69A01,
                0x00EE00D8, 0x01A3D204, 0x00E40104, 0x00224000, 0x0020C00E,
                0x0020001E, 0x00214000, 0x01004000, 0x0212490E, 0x00214001,
                0x01004000, 0x02400000, 0x00B3D702, 0x00E80112, 0x00EE010E,
                0x00B3D702, 0x00E80112, 0x00B3D702, 0x00E4010E, 0x00230001,
                0x00EE0140, 0x00200005, 0x036003EE, 0x00204001, 0x00EE0116,
                0x00230001, 0x00100006, 0x02C18605, 0x00100006, 0x01A3D1F0,
                0x00E40083, 0x037003EE, 0x01A3C002, 0x00E20121, 0x0020A300,
                0x0183D102, 0x00E20124, 0x037003EE, 0x01A00005, 0x036003EE,
                0x01A0910F, 0x00B3C20F, 0x00E2012F, 0x01A3D502, 0x00E20116,
                0x01A3C002, 0x00E20116, 0x00B3D702, 0x00E4012C, 0x00300000,
                0x00EE011F, 0x02C18605, 0x00100006, 0x00EE0116, 0x01A3D1F0,
                0x00E40083, 0x037003EE, 0x01A3C004, 0x00E20088, 0x00200003,
                0x036003EE, 0x01A3D502, 0x00E20136, 0x00230001, 0x00B3C101,
                0x00E4012C, 0x00100006, 0x02C18605, 0x00100006, 0x00204000,
                0x00EE0116, 0x00100006, 0x01A3D1F0, 0x00E40083, 0x01000000,
                0x02400000, ~0
        };

        DPRINTK("ENTER: mode:%d, force:%d\n", mode, force);

        if (force)
                previous_mode = UNKNOWN_MODE;
        else
                previous_mode = host_priv->current_ucode;

        if (mode == previous_mode)
                return;

        host_priv->current_ucode = mode;

        /* decide what needs to be done using the STD in my logbook */
        switch (previous_mode) {
        case OXNASSATA_RAID1:
                switch (mode) {
                case OXNASSATA_RAID0:
                        changeparameters = 1;
                        break;
                case OXNASSATA_NOTRAID:
                        changeparameters = 1;
                        progmicrocode = 1;
                        break;
                }
                break;
        case OXNASSATA_RAID0:
                switch (mode) {
                case OXNASSATA_RAID1:
                        changeparameters = 1;
                        break;
                case OXNASSATA_NOTRAID:
                        changeparameters = 1;
                        progmicrocode = 1;
                        break;
                }
                break;
        case OXNASSATA_NOTRAID:
                switch (mode) {
                case OXNASSATA_RAID0:
                case OXNASSATA_RAID1:
                        changeparameters = 1;
                        progmicrocode = 1;
                        break;
                }
                break;
        case UNKNOWN_MODE:
                changeparameters = 1;
                progmicrocode = 1;
                break;
        }

        /* no need to reprogram everything if already in the right mode */
        if (progmicrocode) {
                /* reset micro-code processor */
                iowrite32(1, core_base + PROC_RESET);
                wmb();

                /* select micro-code */
                switch (mode) {
                case OXNASSATA_RAID1:
                case OXNASSATA_RAID0:
                        VPRINTK("Loading RAID micro-code\n");
                        src = (unsigned int *)&raid[1];
                        break;
                case OXNASSATA_NOTRAID:
                        VPRINTK("Loading JBOD micro-code\n");
                        src = (unsigned int *)&jbod[1];
                        break;
                default:
                        BUG();
                        break;
                }

                /* load micro code */
                dst = core_base + UCODE_STORE;
                while (*src != ~0) {
                        iowrite32(*src, dst);
                        src++;
                        dst += sizeof(*src);
                }
                wmb();
        }

        if (changeparameters) {
                u32 reg;
                /* set other mode dependent flags */
                switch (mode) {
                case OXNASSATA_RAID1:
                        /* clear JBOD mode */
                        reg = ioread32(core_base + DATA_PLANE_CTRL);
                        reg |= DPC_JBOD_UCODE;
                        reg &= ~DPC_FIS_SWCH;
                        iowrite32(reg, core_base + DATA_PLANE_CTRL);
                        wmb();

                        /* set the hardware up for RAID-1 */
                        iowrite32(0, core_base + RAID_WP_BOT_LOW);
                        iowrite32(0, core_base + RAID_WP_BOT_HIGH);
                        iowrite32(0xffffffff, core_base + RAID_WP_TOP_LOW);
                        iowrite32(0x7fffffff, core_base + RAID_WP_TOP_HIGH);
                        iowrite32(0, core_base + RAID_SIZE_LOW);
                        iowrite32(0, core_base + RAID_SIZE_HIGH);
                        wmb();
                        break;
                case OXNASSATA_RAID0:
                        /* clear JBOD mode */
                        reg = ioread32(core_base + DATA_PLANE_CTRL);
                        reg |= DPC_JBOD_UCODE;
                        reg &= ~DPC_FIS_SWCH;
                        iowrite32(reg, core_base + DATA_PLANE_CTRL);
                        wmb();

                        /* set the hardware up for RAID-1 */
                        iowrite32(0, core_base + RAID_WP_BOT_LOW);
                        iowrite32(0, core_base + RAID_WP_BOT_HIGH);
                        iowrite32(0xffffffff, core_base + RAID_WP_TOP_LOW);
                        iowrite32(0x7fffffff, core_base + RAID_WP_TOP_HIGH);
                        iowrite32(0xffffffff, core_base + RAID_SIZE_LOW);
                        iowrite32(0x7fffffff, core_base + RAID_SIZE_HIGH);
                        wmb();
                        break;
                case OXNASSATA_NOTRAID:
                        /* enable jbod mode */
                        reg = ioread32(core_base + DATA_PLANE_CTRL);
                        reg &= ~DPC_JBOD_UCODE;
                        reg &= ~DPC_FIS_SWCH;
                        iowrite32(reg, core_base + DATA_PLANE_CTRL);
                        wmb();

                        /* start micro-code processor*/
                        iowrite32(1, core_base + PROC_START);
                        break;
                default:
                        reg = ioread32(core_base + DATA_PLANE_CTRL);
                        reg |= DPC_JBOD_UCODE;
                        reg &= ~DPC_FIS_SWCH;
                        iowrite32(reg, core_base + DATA_PLANE_CTRL);
                        wmb();
                        break;
                }
        }
}

/**
 * sends a sync-escape if there is a link present
 */
static inline void sata_oxnas_send_sync_escape(struct ata_port *ap)
{
        struct sata_oxnas_port_priv *pd = ap->private_data;
        u32 reg;

        /* read the SSTATUS register and only send a sync escape if there is a
        * link active */
        if ((sata_oxnas_link_read(ap, 0x20) & 3) == 3) {
                reg = ioread32(pd->port_base + SATA_COMMAND);
                reg &= ~SATA_OPCODE_MASK;
                reg |= CMD_SYNC_ESCAPE;
                iowrite32(reg, pd->port_base + SATA_COMMAND);
        }
}

/* clears errors */
static inline void sata_oxnas_clear_CS_error(struct ata_port *ap)
{
        struct sata_oxnas_port_priv *pd = ap->private_data;
        u32 *base = pd->port_base;
        u32 reg;

        reg = ioread32(base + SATA_CONTROL);
        reg &= SATA_CTL_ERR_MASK;
        iowrite32(reg, base + SATA_CONTROL);
}

static inline void sata_oxnas_reset_sgdma(struct ata_port *ap)
{
        struct sata_oxnas_port_priv *pd = ap->private_data;

        iowrite32(SGDMA_RESETS_CTRL, pd->sgdma_base + SGDMA_RESETS);
}

static inline void sata_oxnas_reset_dma(struct ata_port *ap, int assert)
{
        struct sata_oxnas_port_priv *pd = ap->private_data;
        u32 reg;

        reg = ioread32(pd->dmactl_base + DMA_CONTROL);
        if (assert)
                reg |= DMA_CONTROL_RESET;
        else
                reg &= ~DMA_CONTROL_RESET;

        iowrite32(reg, pd->dmactl_base + DMA_CONTROL);
};

/**
 * Clears the error caused by the core's registers being accessed when the
 * core is busy.
 */
static inline void sata_oxnas_clear_reg_access_error(struct ata_port *ap)
{
        struct sata_oxnas_port_priv *pd = ap->private_data;
        u32 *base = pd->port_base;
        u32 reg;

        reg = ioread32(base + INT_STATUS);

        DPRINTK("ENTER\n");
        if (reg & INT_REG_ACCESS_ERR) {
                DPRINTK("clearing register access error on port %d\n",
                        ap->port_no);
                iowrite32(INT_REG_ACCESS_ERR, base + INT_STATUS);
        }
        reg = ioread32(base + INT_STATUS);
        if (reg & INT_REG_ACCESS_ERR)
                DPRINTK("register access error didn't clear\n");
}

static inline void sata_oxnas_clear_sctl_error(struct ata_port *ap)
{
        struct sata_oxnas_port_priv *pd = ap->private_data;
        u32 *base = pd->port_base;
        u32 reg;

        reg = ioread32(base + SATA_CONTROL);
        reg |= SCTL_CLR_ERR;
        iowrite32(reg, base + SATA_CONTROL);
}

static inline void sata_oxnas_clear_raid_error(struct ata_host *ah)
{
        return;
};

/**
 * Clean up all the state machines in the sata core.
 * @return post cleanup action required
 */
static int sata_oxnas_cleanup(struct ata_host *ah)
{
        struct sata_oxnas_host_priv *hd = ah->private_data;
        int actions_required = 0;
        int n;

        printk(KERN_INFO "sata_oxnas: resetting SATA core\n");
        /* core not recovering, reset it */
        mdelay(5);
        sata_oxnas_reset_core(ah);
        mdelay(5);
        actions_required |= OXNAS_SATA_REINIT;
        /* Perform any SATA core re-initialisation after reset post reset init
         * needs to be called for both ports as there's one reset for both
         * ports */
        for (n = 0; n < hd->n_ports; n++)
                sata_oxnas_post_reset_init(ah->ports[n]);


        return actions_required;
}

/**
 *  ata_qc_new - Request an available ATA command, for queueing
 *  @ap: Port associated with device @dev
 *  @return non zero will refuse a new command, zero will may grant on subject
 *          to conditions elsewhere.
 *
 */
static int sata_oxnas_qc_new(struct ata_port *ap)
{
        struct sata_oxnas_host_priv *hd = ap->host->private_data;

        DPRINTK("port %d\n", ap->port_no);
        smp_rmb();
        if (hd->port_frozen || hd->port_in_eh)
                return 1;
        else
                return !sata_oxnas_acquire_hw(ap, 0, 0);
}

/**
 * releases the lock on the port the command used
 */
static void sata_oxnas_qc_free(struct ata_queued_cmd *qc)
{
        DPRINTK("\n");
        sata_oxnas_release_hw(qc->ap);
}

static void sata_oxnas_freeze(struct ata_port *ap)
{
        struct sata_oxnas_host_priv *hd = ap->host->private_data;

        DPRINTK("\n");
        hd->port_frozen |= BIT(ap->port_no);
        smp_wmb();
}

static void sata_oxnas_thaw(struct ata_port *ap)
{
        struct sata_oxnas_host_priv *hd = ap->host->private_data;

        DPRINTK("\n");
        hd->port_frozen &= ~BIT(ap->port_no);
        smp_wmb();
}

void sata_oxnas_freeze_host(struct ata_port *ap)
{
        struct sata_oxnas_host_priv *hd = ap->host->private_data;

        DPRINTK("ENTER\n");
        hd->port_in_eh |= BIT(ap->port_no);
        smp_wmb();
}

void sata_oxnas_thaw_host(struct ata_port *ap)
{
        struct sata_oxnas_host_priv *hd = ap->host->private_data;

        DPRINTK("ENTER\n");
        hd->port_in_eh &= ~BIT(ap->port_no);
        smp_wmb();
}

static void sata_oxnas_post_internal_cmd(struct ata_queued_cmd *qc)
{
        DPRINTK("ENTER\n");
        /* If the core is busy here, make it idle */
        if (qc->flags & ATA_QCFLAG_FAILED)
                sata_oxnas_cleanup(qc->ap->host);
}


/**
 * turn on the interrupts
 *
 * @param ap Hardware with the registers in
 */
static void sata_oxnas_irq_on(struct ata_port *ap)
{
        struct sata_oxnas_port_priv *pd = ap->private_data;
        u32 mask = (COREINT_END << ap->port_no);

        /* Clear pending interrupts */
        iowrite32(~0, pd->port_base + INT_CLEAR);
        iowrite32(mask, pd->core_base + CORE_INT_STATUS);
        wmb();

        /* enable End of command interrupt */
        iowrite32(INT_WANT, pd->port_base + INT_ENABLE);
        iowrite32(mask, pd->core_base + CORE_INT_ENABLE);
}


/** @return true if the port has a cable connected */
int sata_oxnas_check_link(struct ata_port *ap)
{
        int reg;

        sata_oxnas_scr_read_port(ap, SCR_STATUS, &reg);
        /* Check for the cable present indicated by SCR status bit-0 set */
        return reg & 0x1;
}

/**
 *      ata_std_postreset - standard postreset callback
 *      @link: the target ata_link
 *      @classes: classes of attached devices
 *
 *      This function is invoked after a successful reset. Note that
 *      the device might have been reset more than once using
 *      different reset methods before postreset is invoked.
 *
 *      LOCKING:
 *      Kernel thread context (may sleep)
 */
static void sata_oxnas_postreset(struct ata_link *link, unsigned int *classes)
{
        struct ata_port *ap = link->ap;
        struct sata_oxnas_host_priv *hd = ap->host->private_data;

        unsigned int dev;

        DPRINTK("ENTER\n");
        ata_std_postreset(link, classes);

        /* turn on phy error detection by removing the masks */
        sata_oxnas_link_write(ap->host->ports[0], 0x0c, 0x30003);
        if (hd->n_ports > 1)
                sata_oxnas_link_write(ap->host->ports[1], 0x0c, 0x30003);

        /* bail out if no device is present */
        if (classes[0] == ATA_DEV_NONE && classes[1] == ATA_DEV_NONE) {
                DPRINTK("EXIT, no device\n");
                return;
        }

        /* go through all the devices and configure them */
        for (dev = 0; dev < ATA_MAX_DEVICES; ++dev) {
                if (ap->link.device[dev].class == ATA_DEV_ATA)
                        sata_oxnas_dev_config(&(ap->link.device[dev]));
        }

        DPRINTK("EXIT\n");
}

/**
 * Called to read the hardware registers / DMA buffers, to
 * obtain the current set of taskfile register values.
 * @param ap hardware with the registers in
 * @param tf taskfile to read the registers into
 */
static void sata_oxnas_tf_read(struct ata_port *ap, struct ata_taskfile *tf)
{
        struct sata_oxnas_port_priv *port_priv = ap->private_data;
        void __iomem *port_base = port_priv->port_base;
        /* read the orb registers */
        u32 Orb1 = ioread32(port_base + ORB1);
        u32 Orb2 = ioread32(port_base + ORB2);
        u32 Orb3 = ioread32(port_base + ORB3);
        u32 Orb4 = ioread32(port_base + ORB4);

        /* read common 28/48 bit tf parameters */
        tf->device = (Orb1 >> 24);
        tf->nsect = (Orb2 >> 0);
        tf->feature = (Orb2 >> 16);
        tf->command = sata_oxnas_check_status(ap);

        /* read 48 or 28 bit tf parameters */
        if (tf->flags & ATA_TFLAG_LBA48) {
                tf->hob_nsect = (Orb2 >> 8);
                tf->lbal = (Orb3 >> 0);
                tf->lbam = (Orb3 >> 8);
                tf->lbah = (Orb3 >> 16);
                tf->hob_lbal = (Orb3 >> 24);
                tf->hob_lbam = (Orb4 >> 0);
                tf->hob_lbah = (Orb4 >> 8);
                /* feature ext and control are write only */
        } else {
                /* read 28-bit lba */
                tf->lbal = (Orb3 >> 0);
                tf->lbam = (Orb3 >> 8);
                tf->lbah = (Orb3 >> 16);
        }
}

/**
 * Read a result task-file from the sata core registers.
 */
static bool sata_oxnas_qc_fill_rtf(struct ata_queued_cmd *qc)
{
        /* Read the most recently received FIS from the SATA core ORB registers
         and convert to an ATA taskfile */
        sata_oxnas_tf_read(qc->ap, &qc->result_tf);
        return true;
}

/**
 * Reads the Status ATA shadow register from hardware.
 *
 * @return The status register
 */
static u8 sata_oxnas_check_status(struct ata_port *ap)
{
        u32 Reg;
        u8 status;
        struct sata_oxnas_port_priv *port_priv = ap->private_data;
        void __iomem *port_base = port_priv->port_base;

        /* read byte 3 of Orb2 register */
        status = ioread32(port_base + ORB2) >> 24;

        /* check for the drive going missing indicated by SCR status bits
         * 0-3 = 0 */
        sata_oxnas_scr_read_port(ap, SCR_STATUS, &Reg);

        if (!(Reg & 0x1)) {
                status |= ATA_DF;
                status |= ATA_ERR;
        }

        return status;
}

static inline void sata_oxnas_reset_ucode(struct ata_host *ah, int force,
                                          int no_microcode)
{
        struct sata_oxnas_host_priv *hd = ah->private_data;

        DPRINTK("ENTER\n");
        if (no_microcode) {
                u32 reg;

                sata_oxnas_set_mode(ah, UNKNOWN_MODE, force);
                reg = ioread32(hd->core_base + DEVICE_CONTROL);
                reg |= DEVICE_CONTROL_ATA_ERR_OVERRIDE;
                iowrite32(reg, hd->core_base + DEVICE_CONTROL);
        } else {
                /* JBOD uCode */
                sata_oxnas_set_mode(ah, OXNASSATA_NOTRAID, force);
                /* Turn the work around off as it may have been left on by any
                 * HW-RAID code that we've been working with */
                iowrite32(0x0, hd->core_base + PORT_ERROR_MASK);
        }
}

/**
 * Prepare as much as possible for a command without involving anything that is
 * shared between ports.
 */
static void sata_oxnas_qc_prep(struct ata_queued_cmd *qc)
{
        struct sata_oxnas_port_priv *pd;
        int port_no = qc->ap->port_no;

        /* if the port's not connected, complete now with an error */
        if (!sata_oxnas_check_link(qc->ap)) {
                ata_port_err(qc->ap,
                        "port %d not connected completing with error\n",
                        port_no);
                qc->err_mask |= AC_ERR_ATA_BUS;
                ata_qc_complete(qc);
        }

        sata_oxnas_reset_ucode(qc->ap->host, 0, 0);

        /* both pio and dma commands use dma */
        if (ata_is_dma(qc->tf.protocol) || ata_is_pio(qc->tf.protocol)) {

                /* program the scatterlist into the prd table */
                ata_bmdma_qc_prep(qc);

                /* point the sgdma controller at the dma request structure */
                pd = qc->ap->private_data;

                iowrite32(pd->sgdma_request_pa,
                                pd->sgdma_base + SGDMA_REQUESTPTR);

                /* setup the request table */
                if (port_no == 0) {
                        pd->sgdma_request->control =
                                (qc->dma_dir == DMA_FROM_DEVICE) ?
                                        SGDMA_REQCTL0IN : SGDMA_REQCTL0OUT;
                } else {
                        pd->sgdma_request->control =
                                (qc->dma_dir == DMA_FROM_DEVICE) ?
                                        SGDMA_REQCTL1IN : SGDMA_REQCTL1OUT;
                }
                pd->sgdma_request->qualifier = SGDMA_REQQUAL;
                pd->sgdma_request->src_pa = qc->ap->bmdma_prd_dma;
                pd->sgdma_request->dst_pa = qc->ap->bmdma_prd_dma;
                smp_wmb();

                /* tell it to wait */
                iowrite32(SGDMA_CONTROL_NOGO, pd->sgdma_base + SGDMA_CONTROL);
        }
}

static int sata_oxnas_port_start(struct ata_port *ap)
{
        struct sata_oxnas_host_priv *host_priv = ap->host->private_data;
        struct device *dev = ap->host->dev;
        struct sata_oxnas_port_priv *pp;
        void *mem;
        dma_addr_t mem_dma;

        DPRINTK("ENTER\n");

        pp = kzalloc(sizeof(*pp), GFP_KERNEL);
        if (!pp)
                return -ENOMEM;

        pp->port_base = host_priv->port_base +
                        (ap->port_no ? PORT_SIZE : 0);
        pp->dmactl_base = host_priv->dmactl_base +
                         (ap->port_no ? DMA_CORESIZE : 0);
        pp->sgdma_base = host_priv->sgdma_base +
                         (ap->port_no ? SGDMA_CORESIZE : 0);
        pp->core_base = host_priv->core_base;

        /* preallocated */
        if (host_priv->dma_size >= SATA_OXNAS_DMA_SIZE * host_priv->n_ports) {
                DPRINTK("using preallocated DMA\n");
                mem_dma = host_priv->dma_base +
                                (ap->port_no ? SATA_OXNAS_DMA_SIZE : 0);
                mem = ioremap(mem_dma, SATA_OXNAS_DMA_SIZE);
        } else {
                mem = dma_alloc_coherent(dev, SATA_OXNAS_DMA_SIZE, &mem_dma,
                                         GFP_KERNEL);
        }
        if (!mem)
                goto err_ret;

        pp->sgdma_request_pa = mem_dma;
        pp->sgdma_request = mem;

        ap->bmdma_prd_dma = mem_dma + sizeof(struct sgdma_request);
        ap->bmdma_prd = mem + sizeof(struct sgdma_request);

        ap->private_data = pp;

        sata_oxnas_post_reset_init(ap);

        return 0;

err_ret:
        kfree(pp);
        return -ENOMEM;

}

static void sata_oxnas_port_stop(struct ata_port *ap)
{
        struct device *dev = ap->host->dev;
        struct sata_oxnas_port_priv *pp = ap->private_data;
        struct sata_oxnas_host_priv *host_priv = ap->host->private_data;

        DPRINTK("ENTER\n");
        ap->private_data = NULL;
        if (host_priv->dma_size) {
                iounmap(pp->sgdma_request);
        } else {
                dma_free_coherent(dev, SATA_OXNAS_DMA_SIZE,
                                  pp->sgdma_request, pp->sgdma_request_pa);
        }

        kfree(pp);
}


static void sata_oxnas_post_reset_init(struct ata_port *ap)
{
        uint dev;

        /* force to load u-code only once after reset */
        sata_oxnas_reset_ucode(ap->host, !ap->port_no, 0);

        /* turn on phy error detection by removing the masks */
        sata_oxnas_link_write(ap, 0x0C, 0x30003);

        /* enable hotplug event detection */
        sata_oxnas_scr_write_port(ap, SCR_ERROR, ~0);
        sata_oxnas_scr_write_port(ap, SERROR_IRQ_MASK, 0x03feffff);
        sata_oxnas_scr_write_port(ap, SCR_ACTIVE, ~0 & ~(1 << 26) & ~(1 << 16));

        /* enable interrupts for ports */
        sata_oxnas_irq_on(ap);

        /* go through all the devices and configure them */
        for (dev = 0; dev < ATA_MAX_DEVICES; ++dev) {
                if (ap->link.device[dev].class == ATA_DEV_ATA) {
                        sata_std_hardreset(&ap->link, NULL, jiffies + HZ);
                        sata_oxnas_dev_config(&(ap->link.device[dev]));
                }
        }

        /* clean up any remaining errors */
        sata_oxnas_scr_write_port(ap, SCR_ERROR, ~0);
        VPRINTK("done\n");
}

/**
 * host_stop() is called when the rmmod or hot unplug process begins. The
 * hook must stop all hardware interrupts, DMA engines, etc.
 *
 * @param ap hardware with the registers in
 */
static void sata_oxnas_host_stop(struct ata_host *host_set)
{
        DPRINTK("\n");
}


#define ERROR_HW_ACQUIRE_TIMEOUT_JIFFIES (10 * HZ)
static void sata_oxnas_error_handler(struct ata_port *ap)
{
        DPRINTK("Enter port_no %d\n", ap->port_no);
        sata_oxnas_freeze_host(ap);

        /* If the core is busy here, make it idle */
        sata_oxnas_cleanup(ap->host);

        ata_std_error_handler(ap);

        sata_oxnas_thaw_host(ap);
}

static int sata_oxnas_softreset(struct ata_link *link, unsigned int *class,
                                 unsigned long deadline)
{
        struct ata_port *ap = link->ap;
        struct sata_oxnas_port_priv *pd = ap->private_data;
        void __iomem *port_base = pd->port_base;
        int rc;

        struct ata_taskfile tf;
        u32 Command_Reg;

        DPRINTK("ENTER\n");

        port_base = pd->port_base;

        if (ata_link_offline(link)) {
                DPRINTK("PHY reports no device\n");
                *class = ATA_DEV_NONE;
                goto out;
        }

        /* write value to register */
        iowrite32(0, port_base + ORB1);
        iowrite32(0, port_base + ORB2);
        iowrite32(0, port_base + ORB3);
        iowrite32((ap->ctl) << 24, port_base + ORB4);

        /* command the core to send a control FIS */
        Command_Reg = ioread32(port_base + SATA_COMMAND);
        Command_Reg &= ~SATA_OPCODE_MASK;
        Command_Reg |= CMD_WRITE_TO_ORB_REGS_NO_COMMAND;
        iowrite32(Command_Reg, port_base + SATA_COMMAND);
        udelay(20);     /* FIXME: flush */

        /* write value to register */
        iowrite32((ap->ctl | ATA_SRST) << 24, port_base + ORB4);

        /* command the core to send a control FIS */
        Command_Reg &= ~SATA_OPCODE_MASK;
        Command_Reg |= CMD_WRITE_TO_ORB_REGS_NO_COMMAND;
        iowrite32(Command_Reg, port_base + SATA_COMMAND);
        udelay(20);     /* FIXME: flush */

        /* write value to register */
        iowrite32((ap->ctl) << 24, port_base + ORB4);

        /* command the core to send a control FIS */
        Command_Reg &= ~SATA_OPCODE_MASK;
        Command_Reg |= CMD_WRITE_TO_ORB_REGS_NO_COMMAND;
        iowrite32(Command_Reg, port_base + SATA_COMMAND);

        msleep(150);

        rc = ata_sff_wait_ready(link, deadline);

        /* if link is occupied, -ENODEV too is an error */
        if (rc && (rc != -ENODEV || sata_scr_valid(link))) {
                ata_link_printk(link, KERN_ERR, "SRST failed (errno=%d)\n", rc);
                return rc;
        }

        /* determine by signature whether we have ATA or ATAPI devices */
        sata_oxnas_tf_read(ap, &tf);
        *class = ata_dev_classify(&tf);

        if (*class == ATA_DEV_UNKNOWN)
                *class = ATA_DEV_NONE;

out:
        DPRINTK("EXIT, class=%u\n", *class);
        return 0;
}


int     sata_oxnas_init_controller(struct ata_host *host)
{
        return 0;
}

/**
 * Ref bug-6320
 *
 * This code is a work around for a DMA hardware bug that will repeat the
 * penultimate 8-bytes on some reads. This code will check that the amount
 * of data transferred is a multiple of 512 bytes, if not the in it will
 * fetch the correct data from a buffer in the SATA core and copy it into
 * memory.
 *
 * @param port SATA port to check and if necessary, correct.
 */
static int sata_oxnas_bug_6320_detect(struct ata_port *ap)
{
        struct sata_oxnas_port_priv *pd = ap->private_data;
        void __iomem *core_base = pd->core_base;
        int is_read;
        int quads_transferred;
        int remainder;
        int sector_quads_remaining;
        int bug_present = 0;

        /* Only want to apply fix to reads */
        is_read = !(ioread32(core_base + DM_DBG1) & (ap->port_no ?
                        BIT(CORE_PORT1_DATA_DIR_BIT) :
                                BIT(CORE_PORT0_DATA_DIR_BIT)));

        /* Check for an incomplete transfer, i.e. not a multiple of 512 bytes
           transferred (datacount_port register counts quads transferred) */
        quads_transferred =
                ioread32(core_base + (ap->port_no ?
                                        DATACOUNT_PORT1 : DATACOUNT_PORT0));

        remainder = quads_transferred & 0x7f;
        sector_quads_remaining = remainder ? (0x80 - remainder) : 0;

        if (is_read && (sector_quads_remaining == 2)) {
                bug_present = 1;
        } else if (sector_quads_remaining) {
                if (is_read) {
                        ata_port_warn(ap, "SATA read fixup cannot deal with "
                                "%d quads remaining\n",
                                sector_quads_remaining);
                } else {
                        ata_port_warn(ap, "SATA write fixup of %d quads "
                                "remaining not supported\n",
                                sector_quads_remaining);
                }
        }

        return bug_present;
}

/* This port done an interrupt */
static void sata_oxnas_port_irq(struct ata_port *ap, int force_error)
{
        struct ata_queued_cmd *qc;
        struct sata_oxnas_port_priv *pd = ap->private_data;
        void __iomem *port_base = pd->port_base;

        u32 int_status;
        unsigned long flags = 0;

        DPRINTK("ENTER port %d irqstatus %x\n", ap->port_no,
                ioread32(port_base + INT_STATUS));

        if (ap->qc_active & (1 << ATA_TAG_INTERNAL)) {
                        qc = ata_qc_from_tag(ap, ATA_TAG_INTERNAL);
                        DPRINTK("completing non-ncq cmd\n");

                        if (qc)
                                ata_qc_complete(qc);

                        return;
        }

        qc = ata_qc_from_tag(ap, ap->link.active_tag);


        /* record the port's interrupt */
        int_status = ioread32(port_base + INT_STATUS);

        /* If there's no command associated with this IRQ, ignore it. We may get
         * spurious interrupts when cleaning-up after a failed command, ignore
         * these too. */
        if (likely(qc)) {
                /* get the status before any error cleanup */
                qc->err_mask = ac_err_mask(sata_oxnas_check_status(ap));
                if (force_error) {
                        /* Pretend there has been a link error */
                        qc->err_mask |= AC_ERR_ATA_BUS;
                        DPRINTK(" ####force error####\n");
                }
                /* tell libata we're done */
                local_irq_save(flags);
                sata_oxnas_irq_clear(ap);
                local_irq_restore(flags);
                ata_qc_complete(qc);
        } else {
                VPRINTK("Ignoring interrupt, can't find the command tag="
                        "%d %08x\n", ap->link.active_tag, ap->qc_active);
        }

        /* maybe a hotplug event */
        if (unlikely(int_status & INT_LINK_SERROR)) {
                u32 serror;

                sata_oxnas_scr_read_port(ap, SCR_ERROR, &serror);
                if (serror & (SERR_DEV_XCHG | SERR_PHYRDY_CHG)) {
                        ata_ehi_hotplugged(&ap->link.eh_info);
                        ata_port_freeze(ap);
                }
        }
}

/**
 * irq_handler is the interrupt handling routine registered with the system,
 * by libata.
 */
static irqreturn_t sata_oxnas_interrupt(int irq, void *dev_instance)
{
        struct ata_host *ah = dev_instance;
        struct sata_oxnas_host_priv *hd = ah->private_data;
        void __iomem *core_base = hd->core_base;

        u32 int_status;
        irqreturn_t ret = IRQ_NONE;
        u32 port_no;
        u32 mask;
        int bug_present;

        /* loop until there are no more interrupts */
        while ((int_status = (ioread32(core_base + CORE_INT_STATUS)) &
                (COREINT_END | (COREINT_END << 1)))) {

                /* clear any interrupt */
                iowrite32(int_status, core_base + CORE_INT_CLEAR);

                /* Only need workaround_bug_6320 for single disk systems as dual
                 * disk will use uCode which prevents this read underrun problem
                 * from occurring.
                 * All single disk systems will use port 0 */
                for (port_no = 0; port_no < hd->n_ports; ++port_no) {
                        /* check the raw end of command interrupt to see if the
                         * port is done */
                        mask = (COREINT_END << port_no);
                        if (!(int_status & mask))
                                continue;

                        /* this port had an interrupt, clear it */
                        iowrite32(mask, core_base + CORE_INT_CLEAR);
                        /* check for bug 6320 only if no microcode was loaded */
                        bug_present = (hd->current_ucode == UNKNOWN_MODE) &&
                                sata_oxnas_bug_6320_detect(ah->ports[port_no]);

                        sata_oxnas_port_irq(ah->ports[port_no],
                                                bug_present);
                        ret = IRQ_HANDLED;
                }
        }

        return ret;
}

/*
 * scsi mid-layer and libata interface structures
 */
static struct scsi_host_template sata_oxnas_sht = {
        ATA_NCQ_SHT("sata_oxnas"),
        .can_queue = SATA_OXNAS_QUEUE_DEPTH,
        .sg_tablesize = SATA_OXNAS_MAX_PRD,
        .dma_boundary = ATA_DMA_BOUNDARY,
        .unchecked_isa_dma  = 0,
};


static struct ata_port_operations sata_oxnas_ops = {
        .inherits = &sata_port_ops,
        .qc_prep = sata_oxnas_qc_prep,
        .qc_issue = sata_oxnas_qc_issue,
        .qc_fill_rtf = sata_oxnas_qc_fill_rtf,
        .qc_new = sata_oxnas_qc_new,
        .qc_free = sata_oxnas_qc_free,

        .scr_read = sata_oxnas_scr_read,
        .scr_write = sata_oxnas_scr_write,

        .freeze = sata_oxnas_freeze,
        .thaw = sata_oxnas_thaw,
        .softreset = sata_oxnas_softreset,
        /* .hardreset = sata_oxnas_hardreset, */
        .postreset = sata_oxnas_postreset,
        .error_handler = sata_oxnas_error_handler,
        .post_internal_cmd = sata_oxnas_post_internal_cmd,

        .port_start = sata_oxnas_port_start,
        .port_stop = sata_oxnas_port_stop,

        .host_stop = sata_oxnas_host_stop,
        /* .pmp_attach = sata_oxnas_pmp_attach, */
        /* .pmp_detach = sata_oxnas_pmp_detach, */
        .sff_check_status = sata_oxnas_check_status,
        .acquire_hw = sata_oxnas_acquire_hw,
};

static const struct ata_port_info sata_oxnas_port_info = {
        .flags = SATA_OXNAS_HOST_FLAGS,
        .pio_mask = ATA_PIO4,
        .udma_mask = ATA_UDMA6,
        .port_ops = &sata_oxnas_ops,
};

static int sata_oxnas_probe(struct platform_device *ofdev)
{
        int retval = -ENXIO;
        int n_ports = 0;
        void __iomem *port_base = NULL;
        void __iomem *dmactl_base = NULL;
        void __iomem *sgdma_base = NULL;
        void __iomem *core_base = NULL;
        void __iomem *phy_base = NULL;
        struct reset_control *rstc;

        struct resource res = {};
        struct sata_oxnas_host_priv *host_priv = NULL;
        int irq = 0;
        struct ata_host *host = NULL;
        struct clk *clk = NULL;

        const struct ata_port_info *ppi[] = { &sata_oxnas_port_info, NULL };

        of_property_read_u32(ofdev->dev.of_node, "nr-ports", &n_ports);
        if (n_ports < 1 || n_ports > SATA_OXNAS_MAX_PORTS)
                goto error_exit_with_cleanup;

        port_base = of_iomap(ofdev->dev.of_node, 0);
        if (!port_base)
                goto error_exit_with_cleanup;

        dmactl_base = of_iomap(ofdev->dev.of_node, 1);
        if (!dmactl_base)
                goto error_exit_with_cleanup;

        sgdma_base = of_iomap(ofdev->dev.of_node, 2);
        if (!sgdma_base)
                goto error_exit_with_cleanup;

        core_base = of_iomap(ofdev->dev.of_node, 3);
        if (!core_base)
                goto error_exit_with_cleanup;

        phy_base = of_iomap(ofdev->dev.of_node, 4);
        if (!phy_base)
                goto error_exit_with_cleanup;

        host_priv = devm_kzalloc(&ofdev->dev,
                                        sizeof(struct sata_oxnas_host_priv),
                                        GFP_KERNEL);
        if (!host_priv)
                goto error_exit_with_cleanup;

        host_priv->port_base = port_base;
        host_priv->dmactl_base = dmactl_base;
        host_priv->sgdma_base = sgdma_base;
        host_priv->core_base = core_base;
        host_priv->phy_base = phy_base;
        host_priv->n_ports = n_ports;
        host_priv->current_ucode = UNKNOWN_MODE;

        if (!of_address_to_resource(ofdev->dev.of_node, 5, &res)) {
                host_priv->dma_base = res.start;
                host_priv->dma_size = resource_size(&res);
        }

        irq = irq_of_parse_and_map(ofdev->dev.of_node, 0);
        if (!irq) {
                dev_err(&ofdev->dev, "invalid irq from platform\n");
                goto error_exit_with_cleanup;
        }
        host_priv->irq = irq;

        clk = of_clk_get(ofdev->dev.of_node, 0);
        if (IS_ERR(clk)) {
                retval = PTR_ERR(clk);
                clk = NULL;
                goto error_exit_with_cleanup;
        }
        host_priv->clk = clk;

        rstc = devm_reset_control_get(&ofdev->dev, "sata");
        if (IS_ERR(rstc)) {
                retval = PTR_ERR(rstc);
                goto error_exit_with_cleanup;
        }
        host_priv->rst_sata = rstc;

        rstc = devm_reset_control_get(&ofdev->dev, "link");
        if (IS_ERR(rstc)) {
                retval = PTR_ERR(rstc);
                goto error_exit_with_cleanup;
        }
        host_priv->rst_link = rstc;

        rstc = devm_reset_control_get(&ofdev->dev, "phy");
        if (IS_ERR(rstc)) {
                retval = PTR_ERR(rstc);
                goto error_exit_with_cleanup;
        }
        host_priv->rst_phy = rstc;

        /* allocate host structure */
        host = ata_host_alloc_pinfo(&ofdev->dev, ppi, n_ports);

        if (!host) {
                retval = -ENOMEM;
                goto error_exit_with_cleanup;
        }
        host->private_data = host_priv;
        host->iomap = port_base;

        /* initialize core locking and queues */
        init_waitqueue_head(&host_priv->fast_wait_queue);
        init_waitqueue_head(&host_priv->scsi_wait_queue);
        spin_lock_init(&host_priv->phy_lock);
        spin_lock_init(&host_priv->core_lock);
        host_priv->core_locked = 0;
        host_priv->reentrant_port_no = -1;
        host_priv->hw_lock_count = 0;
        host_priv->direct_lock_count = 0;
        host_priv->locker_uid = 0;
        host_priv->current_locker_type = SATA_UNLOCKED;
        host_priv->isr_arg = NULL;
        host_priv->isr_callback = NULL;

        /* initialize host controller */
        retval = sata_oxnas_init_controller(host);
        if (retval)
                goto error_exit_with_cleanup;

        /*
         * Now, register with libATA core, this will also initiate the
         * device discovery process, invoking our port_start() handler &
         * error_handler() to execute a dummy softreset EH session
         */
        ata_host_activate(host, irq, sata_oxnas_interrupt, SATA_OXNAS_IRQ_FLAG,
                          &sata_oxnas_sht);

        return 0;

error_exit_with_cleanup:
        if (irq)
                irq_dispose_mapping(host_priv->irq);
        if (clk)
                clk_put(clk);
        if (host)
                ata_host_detach(host);
        if (port_base)
                iounmap(port_base);
        if (sgdma_base)
                iounmap(sgdma_base);
        if (core_base)
                iounmap(core_base);
        if (phy_base)
                iounmap(phy_base);
        return retval;
}


static int sata_oxnas_remove(struct platform_device *ofdev)
{
        struct ata_host *host = dev_get_drvdata(&ofdev->dev);
        struct sata_oxnas_host_priv *host_priv = host->private_data;

        ata_host_detach(host);

        irq_dispose_mapping(host_priv->irq);
        iounmap(host_priv->port_base);
        iounmap(host_priv->sgdma_base);
        iounmap(host_priv->core_base);

        /* reset Controller, Link and PHY */
        reset_control_assert(host_priv->rst_sata);
        reset_control_assert(host_priv->rst_link);
        reset_control_assert(host_priv->rst_phy);

        /* Disable the clock to the SATA block */
        clk_disable_unprepare(host_priv->clk);
        clk_put(host_priv->clk);

        return 0;
}

#ifdef CONFIG_PM
static int sata_oxnas_suspend(struct platform_device *op, pm_message_t state)
{
        struct ata_host *host = dev_get_drvdata(&op->dev);

        return ata_host_suspend(host, state);
}

static int sata_oxnas_resume(struct platform_device *op)
{
        struct ata_host *host = dev_get_drvdata(&op->dev);
        int ret;

        ret = sata_oxnas_init_controller(host);
        if (ret) {
                dev_err(&op->dev, "Error initializing hardware\n");
                return ret;
        }
        ata_host_resume(host);
        return 0;
}
#endif



static struct of_device_id oxnas_sata_match[] = {
        {
                .compatible = "plxtech,nas782x-sata",
        },
        {},
};

MODULE_DEVICE_TABLE(of, oxnas_sata_match);

static struct platform_driver oxnas_sata_driver = {
        .driver = {
                .name = "oxnas-sata",
                .owner = THIS_MODULE,
                .of_match_table = oxnas_sata_match,
        },
        .probe          = sata_oxnas_probe,
        .remove         = sata_oxnas_remove,
#ifdef CONFIG_PM
        .suspend        = sata_oxnas_suspend,
        .resume         = sata_oxnas_resume,
#endif
};

module_platform_driver(oxnas_sata_driver);

MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");
MODULE_AUTHOR("Oxford Semiconductor Ltd.");
MODULE_DESCRIPTION("low-level driver for Oxford 934 SATA core");